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 }