blob: 0bbcd817d2bc0146fdad1fd0a81bfb2607e815a6 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"crypto/md5"
"encoding/binary"
"net"
"sort"
"strings"
)
import (
udpa "github.com/cncf/xds/go/udpa/type/v1"
"k8s.io/client-go/tools/cache"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collection"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
// Statically link protobuf descriptors from UDPA
var _ = udpa.TypedStruct{}
// NamespacedName defines a name and namespace of a resource, with the type elided. This can be used in
// places where the type is implied.
// This is preferred to a ConfigKey with empty Kind, especially in performance sensitive code - hashing this struct
// is 2x faster than ConfigKey.
type NamespacedName struct {
Name string
Namespace string
}
func (key NamespacedName) String() string {
return key.Namespace + "/" + key.Name
}
// ConfigKey describe a specific config item.
// In most cases, the name is the config's name. However, for ServiceEntry it is service's FQDN.
type ConfigKey struct {
Kind config.GroupVersionKind
Name string
Namespace string
}
func (key ConfigKey) HashCode() uint64 {
hash := md5.New()
for _, v := range []string{
key.Name,
key.Namespace,
key.Kind.Kind,
key.Kind.Group,
key.Kind.Version,
} {
hash.Write([]byte(v))
}
var tmp [md5.Size]byte
sum := hash.Sum(tmp[:0])
return binary.BigEndian.Uint64(sum)
}
func (key ConfigKey) String() string {
return key.Kind.Kind + "/" + key.Namespace + "/" + key.Name
}
// ConfigsOfKind extracts configs of the specified kind.
func ConfigsOfKind(configs map[ConfigKey]struct{}, kind config.GroupVersionKind) map[ConfigKey]struct{} {
ret := make(map[ConfigKey]struct{})
for conf := range configs {
if conf.Kind == kind {
ret[conf] = struct{}{}
}
}
return ret
}
// ConfigsHaveKind checks if configurations have the specified kind.
func ConfigsHaveKind(configs map[ConfigKey]struct{}, kind config.GroupVersionKind) bool {
for conf := range configs {
if conf.Kind == kind {
return true
}
}
return false
}
// ConfigNamesOfKind extracts config names of the specified kind.
func ConfigNamesOfKind(configs map[ConfigKey]struct{}, kind config.GroupVersionKind) map[string]struct{} {
ret := sets.New()
for conf := range configs {
if conf.Kind == kind {
ret.Insert(conf.Name)
}
}
return ret
}
// ConfigStore describes a set of platform agnostic APIs that must be supported
// by the underlying platform to store and retrieve Istio configuration.
//
// Configuration key is defined to be a combination of the type, name, and
// namespace of the configuration object. The configuration key is guaranteed
// to be unique in the store.
//
// The storage interface presented here assumes that the underlying storage
// layer supports _Get_ (list), _Update_ (update), _Create_ (create) and
// _Delete_ semantics but does not guarantee any transactional semantics.
//
// _Update_, _Create_, and _Delete_ are mutator operations. These operations
// are asynchronous, and you might not see the effect immediately (e.g. _Get_
// might not return the object by key immediately after you mutate the store.)
// Intermittent errors might occur even though the operation succeeds, so you
// should always check if the object store has been modified even if the
// mutating operation returns an error. Objects should be created with
// _Create_ operation and updated with _Update_ operation.
//
// Resource versions record the last mutation operation on each object. If a
// mutation is applied to a different revision of an object than what the
// underlying storage expects as defined by pure equality, the operation is
// blocked. The client of this interface should not make assumptions about the
// structure or ordering of the revision identifier.
//
// Object references supplied and returned from this interface should be
// treated as read-only. Modifying them violates thread-safety.
type ConfigStore interface {
// Schemas exposes the configuration type schema known by the config store.
// The type schema defines the bidirectional mapping between configuration
// types and the protobuf encoding schema.
Schemas() collection.Schemas
// Get retrieves a configuration element by a type and a key
Get(typ config.GroupVersionKind, name, namespace string) *config.Config
// List returns objects by type and namespace.
// Use "" for the namespace to list across namespaces.
List(typ config.GroupVersionKind, namespace string) ([]config.Config, error)
// Create adds a new configuration object to the store. If an object with the
// same name and namespace for the type already exists, the operation fails
// with no side effects.
Create(config config.Config) (revision string, err error)
// Update modifies an existing configuration object in the store. Update
// requires that the object has been created. Resource version prevents
// overriding a value that has been changed between prior _Get_ and _Put_
// operation to achieve optimistic concurrency. This method returns a new
// revision if the operation succeeds.
Update(config config.Config) (newRevision string, err error)
UpdateStatus(config config.Config) (newRevision string, err error)
// Patch applies only the modifications made in the PatchFunc rather than doing a full replace. Useful to avoid
// read-modify-write conflicts when there are many concurrent-writers to the same resource.
Patch(orig config.Config, patchFn config.PatchFunc) (string, error)
// Delete removes an object from the store by key
// For k8s, resourceVersion must be fulfilled before a deletion is carried out.
// If not possible, a 409 Conflict status will be returned.
Delete(typ config.GroupVersionKind, name, namespace string, resourceVersion *string) error
}
type EventHandler = func(config.Config, config.Config, Event)
// ConfigStoreController is a local fully-replicated cache of the config store with additional handlers. The
// controller actively synchronizes its local state with the remote store and
// provides a notification mechanism to receive update events. As such, the
// notification handlers must be registered prior to calling _Run_, and the
// cache requires initial synchronization grace period after calling _Run_.
//
// Update notifications require the following consistency guarantee: the view
// in the cache must be AT LEAST as fresh as the moment notification arrives, but
// MAY BE more fresh (e.g. if _Delete_ cancels an _Add_ event).
//
// Handlers execute on the single worker queue in the order they are appended.
// Handlers receive the notification event and the associated object. Note
// that all handlers must be registered before starting the cache controller.
type ConfigStoreController interface {
ConfigStore
// RegisterEventHandler adds a handler to receive config update events for a
// configuration type
RegisterEventHandler(kind config.GroupVersionKind, handler EventHandler)
// Run until a signal is received
Run(stop <-chan struct{})
SetWatchErrorHandler(func(r *cache.Reflector, err error)) error
// HasSynced returns true after initial cache synchronization is complete
HasSynced() bool
}
const (
// NamespaceAll is a designated symbol for listing across all namespaces
NamespaceAll = ""
)
// ResolveShortnameToFQDN uses metadata information to resolve a reference
// to shortname of the service to FQDN
func ResolveShortnameToFQDN(hostname string, meta config.Meta) host.Name {
out := hostname
// Treat the wildcard hostname as fully qualified. Any other variant of a wildcard hostname will contain a `.` too,
// and skip the next if, so we only need to check for the literal wildcard itself.
if hostname == "*" {
return host.Name(out)
}
// if the hostname is a valid ipv4 or ipv6 address, do not append domain or namespace
if net.ParseIP(hostname) != nil {
return host.Name(out)
}
// if FQDN is specified, do not append domain or namespace to hostname
if !strings.Contains(hostname, ".") {
if meta.Namespace != "" {
out = out + "." + meta.Namespace
}
// FIXME this is a gross hack to hardcode a service's domain name in kubernetes
// BUG this will break non kubernetes environments if they use shortnames in the
// rules.
if meta.Domain != "" {
out = out + ".svc." + meta.Domain
}
}
return host.Name(out)
}
// resolveGatewayName uses metadata information to resolve a reference
// to shortname of the gateway to FQDN
func resolveGatewayName(gwname string, meta config.Meta) string {
out := gwname
// New way of binding to a gateway in remote namespace
// is ns/name. Old way is either FQDN or short name
if !strings.Contains(gwname, "/") {
if !strings.Contains(gwname, ".") {
// we have a short name. Resolve to a gateway in same namespace
out = meta.Namespace + "/" + gwname
} else {
// parse namespace from FQDN. This is very hacky, but meant for backward compatibility only
// This is a legacy FQDN format. Transform name.ns.svc.cluster.local -> ns/name
i := strings.Index(gwname, ".")
fqdn := strings.Index(gwname[i+1:], ".")
if fqdn == -1 {
out = gwname[i+1:] + "/" + gwname[:i]
} else {
out = gwname[i+1:i+1+fqdn] + "/" + gwname[:i]
}
}
} else {
// remove the . from ./gateway and substitute it with the namespace name
i := strings.Index(gwname, "/")
if gwname[:i] == "." {
out = meta.Namespace + "/" + gwname[i+1:]
}
}
return out
}
// MostSpecificHostMatch compares the map of the stack to the needle, and returns the longest element
// matching the needle, or false if no element in the map matches the needle.
func MostSpecificHostMatch(needle host.Name, m map[host.Name][]*consolidatedDestRule) (host.Name, bool) {
matches := []host.Name{}
// exact match first
if m != nil {
if _, ok := m[needle]; ok {
return needle, true
}
}
if needle.IsWildCarded() {
for h := range m {
// both needle and h are wildcards
if h.IsWildCarded() {
if len(needle) < len(h) {
continue
}
if strings.HasSuffix(string(needle[1:]), string(h[1:])) {
matches = append(matches, h)
}
}
}
} else {
for h := range m {
// only n is wildcard
if h.IsWildCarded() {
if strings.HasSuffix(string(needle), string(h[1:])) {
matches = append(matches, h)
}
}
}
}
if len(matches) > 1 {
// Sort the host names, find the most specific one.
sort.Sort(host.Names(matches))
}
if len(matches) > 0 {
// TODO: return closest match out of all non-exact matching hosts
return matches[0], true
}
return "", false
}
// MostSpecificHostMatch2 compares the map of the stack to the needle, and returns the longest element
// matching the needle, or false if no element in the map matches the needle.
// TODO: merge with MostSpecificHostMatch once go 1.18 is used
func MostSpecificHostMatch2(needle host.Name, m map[host.Name]struct{}) (host.Name, bool) {
matches := []host.Name{}
// exact match first
if m != nil {
if _, ok := m[needle]; ok {
return needle, true
}
}
if needle.IsWildCarded() {
for h := range m {
// both needle and h are wildcards
if h.IsWildCarded() {
if len(needle) < len(h) {
continue
}
if strings.HasSuffix(string(needle[1:]), string(h[1:])) {
matches = append(matches, h)
}
}
}
} else {
for h := range m {
// only n is wildcard
if h.IsWildCarded() {
if strings.HasSuffix(string(needle), string(h[1:])) {
matches = append(matches, h)
}
}
}
}
if len(matches) > 1 {
// Sort the host names, find the most specific one.
sort.Sort(host.Names(matches))
}
if len(matches) > 0 {
// TODO: return closest match out of all non-exact matching hosts
return matches[0], true
}
return "", false
}
// istioConfigStore provides a simple adapter for Istio configuration types
// from the generic config registry
type istioConfigStore struct {
ConfigStore
}
// MakeIstioStore creates a wrapper around a store.
// In pilot it is initialized with a ConfigStoreController, tests only use
// a regular ConfigStore.
func MakeIstioStore(store ConfigStore) ConfigStore {
return &istioConfigStore{store}
}
func (store *istioConfigStore) ServiceEntries() []config.Config {
serviceEntries, err := store.List(gvk.ServiceEntry, NamespaceAll)
if err != nil {
return nil
}
// To ensure the ip allocation logic deterministically
// allocates the same IP to a service entry.
sortConfigByCreationTime(serviceEntries)
return serviceEntries
}
// sortConfigByCreationTime sorts the list of config objects in ascending order by their creation time (if available).
func sortConfigByCreationTime(configs []config.Config) {
sort.Slice(configs, func(i, j int) bool {
// If creation time is the same, then behavior is nondeterministic. In this case, we can
// pick an arbitrary but consistent ordering based on name and namespace, which is unique.
// CreationTimestamp is stored in seconds, so this is not uncommon.
if configs[i].CreationTimestamp == configs[j].CreationTimestamp {
in := configs[i].Name + "." + configs[i].Namespace
jn := configs[j].Name + "." + configs[j].Namespace
return in < jn
}
return configs[i].CreationTimestamp.Before(configs[j].CreationTimestamp)
})
}
// key creates a key from a reference's name and namespace.
func key(name, namespace string) string {
return name + "/" + namespace
}
func (store *istioConfigStore) AuthorizationPolicies(namespace string) []config.Config {
authorizationPolicies, err := store.List(gvk.AuthorizationPolicy, namespace)
if err != nil {
log.Errorf("failed to get AuthorizationPolicy in namespace %s: %v", namespace, err)
return nil
}
return authorizationPolicies
}