istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/config.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"sort"
    19  	"strings"
    20  
    21  	udpa "github.com/cncf/xds/go/udpa/type/v1"
    22  
    23  	"istio.io/istio/pkg/config"
    24  	"istio.io/istio/pkg/config/host"
    25  	"istio.io/istio/pkg/config/schema/collection"
    26  	"istio.io/istio/pkg/config/schema/kind"
    27  	"istio.io/istio/pkg/util/hash"
    28  	netutil "istio.io/istio/pkg/util/net"
    29  	"istio.io/istio/pkg/util/sets"
    30  )
    31  
    32  // Statically link protobuf descriptors from UDPA
    33  var _ = udpa.TypedStruct{}
    34  
    35  type ConfigHash uint64
    36  
    37  // ConfigKey describe a specific config item.
    38  // In most cases, the name is the config's name. However, for ServiceEntry it is service's FQDN.
    39  type ConfigKey struct {
    40  	Kind      kind.Kind
    41  	Name      string
    42  	Namespace string
    43  }
    44  
    45  func (key ConfigKey) HashCode() ConfigHash {
    46  	h := hash.New()
    47  	h.Write([]byte{byte(key.Kind)})
    48  	// Add separator / to avoid collision.
    49  	h.WriteString("/")
    50  	h.WriteString(key.Namespace)
    51  	h.WriteString("/")
    52  	h.WriteString(key.Name)
    53  	return ConfigHash(h.Sum64())
    54  }
    55  
    56  func (key ConfigKey) String() string {
    57  	return key.Kind.String() + "/" + key.Namespace + "/" + key.Name
    58  }
    59  
    60  // ConfigsOfKind extracts configs of the specified kind.
    61  func ConfigsOfKind(configs sets.Set[ConfigKey], kind kind.Kind) sets.Set[ConfigKey] {
    62  	ret := make(sets.Set[ConfigKey])
    63  
    64  	for conf := range configs {
    65  		if conf.Kind == kind {
    66  			ret.Insert(conf)
    67  		}
    68  	}
    69  
    70  	return ret
    71  }
    72  
    73  // HasConfigsOfKind returns true if configs has changes of type kind
    74  func HasConfigsOfKind(configs sets.Set[ConfigKey], kind kind.Kind) bool {
    75  	for c := range configs {
    76  		if c.Kind == kind {
    77  			return true
    78  		}
    79  	}
    80  	return false
    81  }
    82  
    83  // ConfigNamesOfKind extracts config names of the specified kind.
    84  func ConfigNamesOfKind(configs sets.Set[ConfigKey], kind kind.Kind) sets.String {
    85  	ret := sets.New[string]()
    86  
    87  	for conf := range configs {
    88  		if conf.Kind == kind {
    89  			ret.Insert(conf.Name)
    90  		}
    91  	}
    92  
    93  	return ret
    94  }
    95  
    96  // ConfigNameOfKind extracts config names of the specified kind.
    97  func ConfigNameOfKind(configs map[ConfigKey]struct{}, kind kind.Kind) sets.String {
    98  	ret := sets.New[string]()
    99  
   100  	for conf := range configs {
   101  		if conf.Kind == kind {
   102  			ret.Insert(conf.Name)
   103  		}
   104  	}
   105  
   106  	return ret
   107  }
   108  
   109  // ConfigStore describes a set of platform agnostic APIs that must be supported
   110  // by the underlying platform to store and retrieve Istio configuration.
   111  //
   112  // Configuration key is defined to be a combination of the type, name, and
   113  // namespace of the configuration object. The configuration key is guaranteed
   114  // to be unique in the store.
   115  //
   116  // The storage interface presented here assumes that the underlying storage
   117  // layer supports _Get_ (list), _Update_ (update), _Create_ (create) and
   118  // _Delete_ semantics but does not guarantee any transactional semantics.
   119  //
   120  // _Update_, _Create_, and _Delete_ are mutator operations. These operations
   121  // are asynchronous, and you might not see the effect immediately (e.g. _Get_
   122  // might not return the object by key immediately after you mutate the store.)
   123  // Intermittent errors might occur even though the operation succeeds, so you
   124  // should always check if the object store has been modified even if the
   125  // mutating operation returns an error.  Objects should be created with
   126  // _Create_ operation and updated with _Update_ operation.
   127  //
   128  // Resource versions record the last mutation operation on each object. If a
   129  // mutation is applied to a different revision of an object than what the
   130  // underlying storage expects as defined by pure equality, the operation is
   131  // blocked.  The client of this interface should not make assumptions about the
   132  // structure or ordering of the revision identifier.
   133  //
   134  // Object references supplied and returned from this interface should be
   135  // treated as read-only. Modifying them violates thread-safety.
   136  type ConfigStore interface {
   137  	// Schemas exposes the configuration type schema known by the config store.
   138  	// The type schema defines the bidirectional mapping between configuration
   139  	// types and the protobuf encoding schema.
   140  	Schemas() collection.Schemas
   141  
   142  	// Get retrieves a configuration element by a type and a key
   143  	Get(typ config.GroupVersionKind, name, namespace string) *config.Config
   144  
   145  	// List returns objects by type and namespace.
   146  	// Use "" for the namespace to list across namespaces.
   147  	List(typ config.GroupVersionKind, namespace string) []config.Config
   148  
   149  	// Create adds a new configuration object to the store. If an object with the
   150  	// same name and namespace for the type already exists, the operation fails
   151  	// with no side effects.
   152  	Create(config config.Config) (revision string, err error)
   153  
   154  	// Update modifies an existing configuration object in the store.  Update
   155  	// requires that the object has been created.  Resource version prevents
   156  	// overriding a value that has been changed between prior _Get_ and _Put_
   157  	// operation to achieve optimistic concurrency. This method returns a new
   158  	// revision if the operation succeeds.
   159  	Update(config config.Config) (newRevision string, err error)
   160  	UpdateStatus(config config.Config) (newRevision string, err error)
   161  
   162  	// Patch applies only the modifications made in the PatchFunc rather than doing a full replace. Useful to avoid
   163  	// read-modify-write conflicts when there are many concurrent-writers to the same resource.
   164  	Patch(orig config.Config, patchFn config.PatchFunc) (string, error)
   165  
   166  	// Delete removes an object from the store by key
   167  	// For k8s, resourceVersion must be fulfilled before a deletion is carried out.
   168  	// If not possible, a 409 Conflict status will be returned.
   169  	Delete(typ config.GroupVersionKind, name, namespace string, resourceVersion *string) error
   170  }
   171  
   172  type EventHandler = func(config.Config, config.Config, Event)
   173  
   174  // ConfigStoreController is a local fully-replicated cache of the config store with additional handlers.  The
   175  // controller actively synchronizes its local state with the remote store and
   176  // provides a notification mechanism to receive update events. As such, the
   177  // notification handlers must be registered prior to calling _Run_, and the
   178  // cache requires initial synchronization grace period after calling  _Run_.
   179  //
   180  // Update notifications require the following consistency guarantee: the view
   181  // in the cache must be AT LEAST as fresh as the moment notification arrives, but
   182  // MAY BE more fresh (e.g. if _Delete_ cancels an _Add_ event).
   183  //
   184  // Handlers execute on the single worker queue in the order they are appended.
   185  // Handlers receive the notification event and the associated object.  Note
   186  // that all handlers must be registered before starting the cache controller.
   187  type ConfigStoreController interface {
   188  	ConfigStore
   189  
   190  	// RegisterEventHandler adds a handler to receive config update events for a
   191  	// configuration type
   192  	RegisterEventHandler(kind config.GroupVersionKind, handler EventHandler)
   193  
   194  	// Run until a signal is received.
   195  	// Run *should* block, so callers should typically call `go controller.Run(stop)`
   196  	Run(stop <-chan struct{})
   197  
   198  	// HasSynced returns true after initial cache synchronization is complete
   199  	HasSynced() bool
   200  }
   201  
   202  const (
   203  	// NamespaceAll is a designated symbol for listing across all namespaces
   204  	NamespaceAll = ""
   205  )
   206  
   207  // ResolveShortnameToFQDN uses metadata information to resolve a reference
   208  // to shortname of the service to FQDN
   209  func ResolveShortnameToFQDN(hostname string, meta config.Meta) host.Name {
   210  	if len(hostname) == 0 {
   211  		// only happens when the gateway-api BackendRef is invalid
   212  		return ""
   213  	}
   214  	out := hostname
   215  	// Treat the wildcard hostname as fully qualified. Any other variant of a wildcard hostname will contain a `.` too,
   216  	// and skip the next if, so we only need to check for the literal wildcard itself.
   217  	if hostname == "*" {
   218  		return host.Name(out)
   219  	}
   220  
   221  	// if the hostname is a valid ipv4 or ipv6 address, do not append domain or namespace
   222  	if netutil.IsValidIPAddress(hostname) {
   223  		return host.Name(out)
   224  	}
   225  
   226  	// if FQDN is specified, do not append domain or namespace to hostname
   227  	if !strings.Contains(hostname, ".") {
   228  		if meta.Namespace != "" {
   229  			out = out + "." + meta.Namespace
   230  		}
   231  
   232  		// FIXME this is a gross hack to hardcode a service's domain name in kubernetes
   233  		// BUG this will break non kubernetes environments if they use shortnames in the
   234  		// rules.
   235  		if meta.Domain != "" {
   236  			out = out + ".svc." + meta.Domain
   237  		}
   238  	}
   239  
   240  	return host.Name(out)
   241  }
   242  
   243  // resolveGatewayName uses metadata information to resolve a reference
   244  // to shortname of the gateway to FQDN
   245  func resolveGatewayName(gwname string, meta config.Meta) string {
   246  	out := gwname
   247  
   248  	// New way of binding to a gateway in remote namespace
   249  	// is ns/name. Old way is either FQDN or short name
   250  	if !strings.Contains(gwname, "/") {
   251  		if !strings.Contains(gwname, ".") {
   252  			// we have a short name. Resolve to a gateway in same namespace
   253  			out = meta.Namespace + "/" + gwname
   254  		} else {
   255  			// parse namespace from FQDN. This is very hacky, but meant for backward compatibility only
   256  			// This is a legacy FQDN format. Transform name.ns.svc.cluster.local -> ns/name
   257  			i := strings.Index(gwname, ".")
   258  			fqdn := strings.Index(gwname[i+1:], ".")
   259  			if fqdn == -1 {
   260  				out = gwname[i+1:] + "/" + gwname[:i]
   261  			} else {
   262  				out = gwname[i+1:i+1+fqdn] + "/" + gwname[:i]
   263  			}
   264  		}
   265  	} else {
   266  		// remove the . from ./gateway and substitute it with the namespace name
   267  		i := strings.Index(gwname, "/")
   268  		if gwname[:i] == "." {
   269  			out = meta.Namespace + "/" + gwname[i+1:]
   270  		}
   271  	}
   272  	return out
   273  }
   274  
   275  // MostSpecificHostMatch compares the maps of specific and wildcard hosts to the needle, and returns the longest element
   276  // matching the needle and it's value, or false if no element in the maps matches the needle.
   277  func MostSpecificHostMatch[V any](needle host.Name, specific map[host.Name]V, wildcard map[host.Name]V) (host.Name, V, bool) {
   278  	if needle.IsWildCarded() {
   279  		// exact match first
   280  		if v, ok := wildcard[needle]; ok {
   281  			return needle, v, true
   282  		}
   283  
   284  		return mostSpecificHostWildcardMatch(string(needle[1:]), wildcard)
   285  	}
   286  
   287  	// exact match first
   288  	if v, ok := specific[needle]; ok {
   289  		return needle, v, true
   290  	}
   291  
   292  	// check wildcard
   293  	return mostSpecificHostWildcardMatch(string(needle), wildcard)
   294  }
   295  
   296  func mostSpecificHostWildcardMatch[V any](needle string, wildcard map[host.Name]V) (host.Name, V, bool) {
   297  	found := false
   298  	var matchHost host.Name
   299  	var matchValue V
   300  
   301  	for h, v := range wildcard {
   302  		if strings.HasSuffix(needle, string(h[1:])) {
   303  			if !found {
   304  				matchHost = h
   305  				matchValue = wildcard[h]
   306  				found = true
   307  			} else if host.MoreSpecific(h, matchHost) {
   308  				matchHost = h
   309  				matchValue = v
   310  			}
   311  		}
   312  	}
   313  
   314  	return matchHost, matchValue, found
   315  }
   316  
   317  // OldestMatchingHost returns the oldest matching host for a given needle (whether specific or wildcarded)
   318  func OldestMatchingHost(needle host.Name, specific map[host.Name]config.Config, wildcard map[host.Name]config.Config) (host.Name, config.Config, bool) {
   319  	// The algorithm is a bit different than MostSpecificHostMatch. We can't short-circuit on the first
   320  	// match, regardless of whether it's specific or wildcarded. This is because we have to check the timestamp
   321  	// of all configs to make sure there's not an older matching one that we should use instead.
   322  
   323  	if needle.IsWildCarded() {
   324  		needle = needle[1:]
   325  	}
   326  
   327  	found := false
   328  	var matchHost host.Name
   329  	var matchValue config.Config
   330  	// exact match first
   331  	if v, ok := specific[needle]; ok {
   332  		found = true
   333  		matchHost = needle
   334  		matchValue = v
   335  	}
   336  
   337  	// Even if we have a match, we still need to check the wildcard map to see if there's an older match
   338  	for h, v := range wildcard {
   339  		if strings.HasSuffix(string(needle), string(h[1:])) {
   340  			if !found {
   341  				matchHost = h
   342  				matchValue = wildcard[h]
   343  				found = true
   344  			} else if h.Matches(matchHost) && v.GetCreationTimestamp().Before(matchValue.GetCreationTimestamp()) {
   345  				// Only replace if the new match is more specific and older than the current match
   346  				matchHost = h
   347  				matchValue = v
   348  			}
   349  		}
   350  	}
   351  
   352  	return matchHost, matchValue, found
   353  }
   354  
   355  // sortByCreationComparator is a comparator function for sorting config objects by creation time.
   356  func sortByCreationComparator(configs []config.Config) func(i, j int) bool {
   357  	return func(i, j int) bool {
   358  		// If creation time is the same, then behavior is nondeterministic. In this case, we can
   359  		// pick an arbitrary but consistent ordering based on name and namespace, which is unique.
   360  		// CreationTimestamp is stored in seconds, so this is not uncommon.
   361  		if configs[i].CreationTimestamp == configs[j].CreationTimestamp {
   362  			in := configs[i].Name + "." + configs[i].Namespace
   363  			jn := configs[j].Name + "." + configs[j].Namespace
   364  			return in < jn
   365  		}
   366  		return configs[i].CreationTimestamp.Before(configs[j].CreationTimestamp)
   367  	}
   368  }
   369  
   370  // sortConfigByCreationTime sorts the list of config objects in ascending order by their creation time (if available)
   371  func sortConfigByCreationTime(configs []config.Config) []config.Config {
   372  	sort.Slice(configs, sortByCreationComparator(configs))
   373  	return configs
   374  }