k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/garbagecollector/graph_builder.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package garbagecollector 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "sync" 24 "time" 25 26 "k8s.io/klog/v2" 27 28 v1 "k8s.io/api/core/v1" 29 eventv1 "k8s.io/api/events/v1" 30 "k8s.io/apimachinery/pkg/api/meta" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 utilerrors "k8s.io/apimachinery/pkg/util/errors" 35 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 36 "k8s.io/apimachinery/pkg/util/sets" 37 "k8s.io/apimachinery/pkg/util/wait" 38 "k8s.io/client-go/metadata" 39 "k8s.io/client-go/tools/cache" 40 "k8s.io/client-go/tools/record" 41 "k8s.io/client-go/util/workqueue" 42 "k8s.io/controller-manager/pkg/informerfactory" 43 "k8s.io/kubernetes/pkg/controller/apis/config/scheme" 44 "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly" 45 ) 46 47 type eventType int 48 49 func (e eventType) String() string { 50 switch e { 51 case addEvent: 52 return "add" 53 case updateEvent: 54 return "update" 55 case deleteEvent: 56 return "delete" 57 default: 58 return fmt.Sprintf("unknown(%d)", int(e)) 59 } 60 } 61 62 const ( 63 addEvent eventType = iota 64 updateEvent 65 deleteEvent 66 ) 67 68 type event struct { 69 // virtual indicates this event did not come from an informer, but was constructed artificially 70 virtual bool 71 eventType eventType 72 obj interface{} 73 // the update event comes with an old object, but it's not used by the garbage collector. 74 oldObj interface{} 75 gvk schema.GroupVersionKind 76 } 77 78 // GraphBuilder processes events supplied by the informers, updates uidToNode, 79 // a graph that caches the dependencies as we know, and enqueues 80 // items to the attemptToDelete and attemptToOrphan. 81 type GraphBuilder struct { 82 restMapper meta.RESTMapper 83 84 // each monitor list/watches a resource, the results are funneled to the 85 // dependencyGraphBuilder 86 monitors monitors 87 monitorLock sync.RWMutex 88 // informersStarted is closed after after all of the controllers have been initialized and are running. 89 // After that it is safe to start them here, before that it is not. 90 informersStarted <-chan struct{} 91 92 // stopCh drives shutdown. When a receive from it unblocks, monitors will shut down. 93 // This channel is also protected by monitorLock. 94 stopCh <-chan struct{} 95 96 // running tracks whether Run() has been called. 97 // it is protected by monitorLock. 98 running bool 99 100 eventRecorder record.EventRecorder 101 eventBroadcaster record.EventBroadcaster 102 103 metadataClient metadata.Interface 104 // monitors are the producer of the graphChanges queue, graphBuilder alters 105 // the in-memory graph according to the changes. 106 graphChanges workqueue.TypedRateLimitingInterface[*event] 107 // uidToNode doesn't require a lock to protect, because only the 108 // single-threaded GraphBuilder.processGraphChanges() reads/writes it. 109 uidToNode *concurrentUIDToNode 110 // GraphBuilder is the producer of attemptToDelete and attemptToOrphan, GC is the consumer. 111 attemptToDelete workqueue.TypedRateLimitingInterface[*node] 112 attemptToOrphan workqueue.TypedRateLimitingInterface[*node] 113 // GraphBuilder and GC share the absentOwnerCache. Objects that are known to 114 // be non-existent are added to the cached. 115 absentOwnerCache *ReferenceCache 116 sharedInformers informerfactory.InformerFactory 117 ignoredResources map[schema.GroupResource]struct{} 118 } 119 120 // monitor runs a Controller with a local stop channel. 121 type monitor struct { 122 controller cache.Controller 123 store cache.Store 124 125 // stopCh stops Controller. If stopCh is nil, the monitor is considered to be 126 // not yet started. 127 stopCh chan struct{} 128 } 129 130 // Run is intended to be called in a goroutine. Multiple calls of this is an 131 // error. 132 func (m *monitor) Run() { 133 m.controller.Run(m.stopCh) 134 } 135 136 type monitors map[schema.GroupVersionResource]*monitor 137 138 func NewDependencyGraphBuilder( 139 ctx context.Context, 140 metadataClient metadata.Interface, 141 mapper meta.ResettableRESTMapper, 142 ignoredResources map[schema.GroupResource]struct{}, 143 sharedInformers informerfactory.InformerFactory, 144 informersStarted <-chan struct{}, 145 ) *GraphBuilder { 146 eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) 147 148 attemptToDelete := workqueue.NewTypedRateLimitingQueueWithConfig( 149 workqueue.DefaultTypedControllerRateLimiter[*node](), 150 workqueue.TypedRateLimitingQueueConfig[*node]{ 151 Name: "garbage_collector_attempt_to_delete", 152 }, 153 ) 154 attemptToOrphan := workqueue.NewTypedRateLimitingQueueWithConfig( 155 workqueue.DefaultTypedControllerRateLimiter[*node](), 156 workqueue.TypedRateLimitingQueueConfig[*node]{ 157 Name: "garbage_collector_attempt_to_orphan", 158 }, 159 ) 160 absentOwnerCache := NewReferenceCache(500) 161 graphBuilder := &GraphBuilder{ 162 eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "garbage-collector-controller"}), 163 eventBroadcaster: eventBroadcaster, 164 metadataClient: metadataClient, 165 informersStarted: informersStarted, 166 restMapper: mapper, 167 graphChanges: workqueue.NewTypedRateLimitingQueueWithConfig( 168 workqueue.DefaultTypedControllerRateLimiter[*event](), 169 workqueue.TypedRateLimitingQueueConfig[*event]{ 170 Name: "garbage_collector_graph_changes", 171 }, 172 ), 173 uidToNode: &concurrentUIDToNode{ 174 uidToNode: make(map[types.UID]*node), 175 }, 176 attemptToDelete: attemptToDelete, 177 attemptToOrphan: attemptToOrphan, 178 absentOwnerCache: absentOwnerCache, 179 sharedInformers: sharedInformers, 180 ignoredResources: ignoredResources, 181 } 182 183 return graphBuilder 184 } 185 186 func (gb *GraphBuilder) controllerFor(logger klog.Logger, resource schema.GroupVersionResource, kind schema.GroupVersionKind) (cache.Controller, cache.Store, error) { 187 handlers := cache.ResourceEventHandlerFuncs{ 188 // add the event to the dependencyGraphBuilder's graphChanges. 189 AddFunc: func(obj interface{}) { 190 event := &event{ 191 eventType: addEvent, 192 obj: obj, 193 gvk: kind, 194 } 195 gb.graphChanges.Add(event) 196 }, 197 UpdateFunc: func(oldObj, newObj interface{}) { 198 // TODO: check if there are differences in the ownerRefs, 199 // finalizers, and DeletionTimestamp; if not, ignore the update. 200 event := &event{ 201 eventType: updateEvent, 202 obj: newObj, 203 oldObj: oldObj, 204 gvk: kind, 205 } 206 gb.graphChanges.Add(event) 207 }, 208 DeleteFunc: func(obj interface{}) { 209 // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it 210 if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { 211 obj = deletedFinalStateUnknown.Obj 212 } 213 event := &event{ 214 eventType: deleteEvent, 215 obj: obj, 216 gvk: kind, 217 } 218 gb.graphChanges.Add(event) 219 }, 220 } 221 222 shared, err := gb.sharedInformers.ForResource(resource) 223 if err != nil { 224 logger.V(4).Error(err, "unable to use a shared informer", "resource", resource, "kind", kind) 225 return nil, nil, err 226 } 227 logger.V(4).Info("using a shared informer", "resource", resource, "kind", kind) 228 // need to clone because it's from a shared cache 229 shared.Informer().AddEventHandlerWithResyncPeriod(handlers, ResourceResyncTime) 230 return shared.Informer().GetController(), shared.Informer().GetStore(), nil 231 } 232 233 // syncMonitors rebuilds the monitor set according to the supplied resources, 234 // creating or deleting monitors as necessary. It will return any error 235 // encountered, but will make an attempt to create a monitor for each resource 236 // instead of immediately exiting on an error. It may be called before or after 237 // Run. Monitors are NOT started as part of the sync. To ensure all existing 238 // monitors are started, call startMonitors. 239 func (gb *GraphBuilder) syncMonitors(logger klog.Logger, resources map[schema.GroupVersionResource]struct{}) error { 240 gb.monitorLock.Lock() 241 defer gb.monitorLock.Unlock() 242 243 toRemove := gb.monitors 244 if toRemove == nil { 245 toRemove = monitors{} 246 } 247 current := monitors{} 248 errs := []error{} 249 kept := 0 250 added := 0 251 for resource := range resources { 252 if _, ok := gb.ignoredResources[resource.GroupResource()]; ok { 253 continue 254 } 255 if m, ok := toRemove[resource]; ok { 256 current[resource] = m 257 delete(toRemove, resource) 258 kept++ 259 continue 260 } 261 kind, err := gb.restMapper.KindFor(resource) 262 if err != nil { 263 errs = append(errs, fmt.Errorf("couldn't look up resource %q: %v", resource, err)) 264 continue 265 } 266 c, s, err := gb.controllerFor(logger, resource, kind) 267 if err != nil { 268 errs = append(errs, fmt.Errorf("couldn't start monitor for resource %q: %v", resource, err)) 269 continue 270 } 271 current[resource] = &monitor{store: s, controller: c} 272 added++ 273 } 274 gb.monitors = current 275 276 for _, monitor := range toRemove { 277 if monitor.stopCh != nil { 278 close(monitor.stopCh) 279 } 280 } 281 282 logger.V(4).Info("synced monitors", "added", added, "kept", kept, "removed", len(toRemove)) 283 // NewAggregate returns nil if errs is 0-length 284 return utilerrors.NewAggregate(errs) 285 } 286 287 // startMonitors ensures the current set of monitors are running. Any newly 288 // started monitors will also cause shared informers to be started. 289 // 290 // If called before Run, startMonitors does nothing (as there is no stop channel 291 // to support monitor/informer execution). 292 func (gb *GraphBuilder) startMonitors(logger klog.Logger) { 293 gb.monitorLock.Lock() 294 defer gb.monitorLock.Unlock() 295 296 if !gb.running { 297 return 298 } 299 300 // we're waiting until after the informer start that happens once all the controllers are initialized. This ensures 301 // that they don't get unexpected events on their work queues. 302 <-gb.informersStarted 303 304 monitors := gb.monitors 305 started := 0 306 for _, monitor := range monitors { 307 if monitor.stopCh == nil { 308 monitor.stopCh = make(chan struct{}) 309 gb.sharedInformers.Start(gb.stopCh) 310 go monitor.Run() 311 started++ 312 } 313 } 314 logger.V(4).Info("started new monitors", "new", started, "current", len(monitors)) 315 } 316 317 // IsResourceSynced returns true if a monitor exists for the given resource and has synced 318 func (gb *GraphBuilder) IsResourceSynced(resource schema.GroupVersionResource) bool { 319 gb.monitorLock.Lock() 320 defer gb.monitorLock.Unlock() 321 monitor, ok := gb.monitors[resource] 322 return ok && monitor.controller.HasSynced() 323 } 324 325 // IsSynced returns true if any monitors exist AND all those monitors' 326 // controllers HasSynced functions return true. This means IsSynced could return 327 // true at one time, and then later return false if all monitors were 328 // reconstructed. 329 func (gb *GraphBuilder) IsSynced(logger klog.Logger) bool { 330 gb.monitorLock.Lock() 331 defer gb.monitorLock.Unlock() 332 333 if len(gb.monitors) == 0 { 334 logger.V(4).Info("garbage controller monitor not synced: no monitors") 335 return false 336 } 337 338 for resource, monitor := range gb.monitors { 339 if !monitor.controller.HasSynced() { 340 logger.V(4).Info("garbage controller monitor not yet synced", "resource", resource) 341 return false 342 } 343 } 344 return true 345 } 346 347 // Run sets the stop channel and starts monitor execution until stopCh is 348 // closed. Any running monitors will be stopped before Run returns. 349 func (gb *GraphBuilder) Run(ctx context.Context) { 350 logger := klog.FromContext(ctx) 351 logger.Info("Running", "component", "GraphBuilder") 352 defer logger.Info("Stopping", "component", "GraphBuilder") 353 354 // Set up the stop channel. 355 gb.monitorLock.Lock() 356 gb.stopCh = ctx.Done() 357 gb.running = true 358 gb.monitorLock.Unlock() 359 360 // Start monitors and begin change processing until the stop channel is 361 // closed. 362 gb.startMonitors(logger) 363 wait.Until(func() { gb.runProcessGraphChanges(logger) }, 1*time.Second, ctx.Done()) 364 365 // Stop any running monitors. 366 gb.monitorLock.Lock() 367 defer gb.monitorLock.Unlock() 368 monitors := gb.monitors 369 stopped := 0 370 for _, monitor := range monitors { 371 if monitor.stopCh != nil { 372 stopped++ 373 close(monitor.stopCh) 374 } 375 } 376 377 // reset monitors so that the graph builder can be safely re-run/synced. 378 gb.monitors = nil 379 logger.Info("stopped monitors", "stopped", stopped, "total", len(monitors)) 380 } 381 382 var ignoredResources = map[schema.GroupResource]struct{}{ 383 {Group: "", Resource: "events"}: {}, 384 {Group: eventv1.GroupName, Resource: "events"}: {}, 385 } 386 387 // DefaultIgnoredResources returns the default set of resources that the garbage collector controller 388 // should ignore. This is exposed so downstream integrators can have access to the defaults, and add 389 // to them as necessary when constructing the controller. 390 func DefaultIgnoredResources() map[schema.GroupResource]struct{} { 391 return ignoredResources 392 } 393 394 // enqueueVirtualDeleteEvent is used to add a virtual delete event to be processed for virtual nodes 395 // once it is determined they do not have backing objects in storage 396 func (gb *GraphBuilder) enqueueVirtualDeleteEvent(ref objectReference) { 397 gv, _ := schema.ParseGroupVersion(ref.APIVersion) 398 gb.graphChanges.Add(&event{ 399 virtual: true, 400 eventType: deleteEvent, 401 gvk: gv.WithKind(ref.Kind), 402 obj: &metaonly.MetadataOnlyObject{ 403 TypeMeta: metav1.TypeMeta{APIVersion: ref.APIVersion, Kind: ref.Kind}, 404 ObjectMeta: metav1.ObjectMeta{Namespace: ref.Namespace, UID: ref.UID, Name: ref.Name}, 405 }, 406 }) 407 } 408 409 // addDependentToOwners adds n to owners' dependents list. If the owner does not 410 // exist in the gb.uidToNode yet, a "virtual" node will be created to represent 411 // the owner. The "virtual" node will be enqueued to the attemptToDelete, so that 412 // attemptToDeleteItem() will verify if the owner exists according to the API server. 413 func (gb *GraphBuilder) addDependentToOwners(logger klog.Logger, n *node, owners []metav1.OwnerReference) { 414 // track if some of the referenced owners already exist in the graph and have been observed, 415 // and the dependent's ownerRef does not match their observed coordinates 416 hasPotentiallyInvalidOwnerReference := false 417 418 for _, owner := range owners { 419 ownerNode, ok := gb.uidToNode.Read(owner.UID) 420 if !ok { 421 // Create a "virtual" node in the graph for the owner if it doesn't 422 // exist in the graph yet. 423 ownerNode = &node{ 424 identity: objectReference{ 425 OwnerReference: ownerReferenceCoordinates(owner), 426 Namespace: n.identity.Namespace, 427 }, 428 dependents: make(map[*node]struct{}), 429 virtual: true, 430 } 431 logger.V(5).Info("add virtual item", "identity", ownerNode.identity) 432 gb.uidToNode.Write(ownerNode) 433 } 434 ownerNode.addDependent(n) 435 if !ok { 436 // Enqueue the virtual node into attemptToDelete. 437 // The garbage processor will enqueue a virtual delete 438 // event to delete it from the graph if API server confirms this 439 // owner doesn't exist. 440 gb.attemptToDelete.Add(ownerNode) 441 } else if !hasPotentiallyInvalidOwnerReference { 442 ownerIsNamespaced := len(ownerNode.identity.Namespace) > 0 443 if ownerIsNamespaced && ownerNode.identity.Namespace != n.identity.Namespace { 444 if ownerNode.isObserved() { 445 // The owner node has been observed via an informer 446 // the dependent's namespace doesn't match the observed owner's namespace, this is definitely wrong. 447 // cluster-scoped owners can be referenced as an owner from any namespace or cluster-scoped object. 448 logger.V(2).Info("item references an owner but does not match namespaces", "item", n.identity, "owner", ownerNode.identity) 449 gb.reportInvalidNamespaceOwnerRef(n, owner.UID) 450 } 451 hasPotentiallyInvalidOwnerReference = true 452 } else if !ownerReferenceMatchesCoordinates(owner, ownerNode.identity.OwnerReference) { 453 if ownerNode.isObserved() { 454 // The owner node has been observed via an informer 455 // n's owner reference doesn't match the observed identity, this might be wrong. 456 logger.V(2).Info("item references an owner with coordinates that do not match the observed identity", "item", n.identity, "owner", ownerNode.identity) 457 } 458 hasPotentiallyInvalidOwnerReference = true 459 } else if !ownerIsNamespaced && ownerNode.identity.Namespace != n.identity.Namespace && !ownerNode.isObserved() { 460 // the ownerNode is cluster-scoped and virtual, and does not match the child node's namespace. 461 // the owner could be a missing instance of a namespaced type incorrectly referenced by a cluster-scoped child (issue #98040). 462 // enqueue this child to attemptToDelete to verify parent references. 463 hasPotentiallyInvalidOwnerReference = true 464 } 465 } 466 } 467 468 if hasPotentiallyInvalidOwnerReference { 469 // Enqueue the potentially invalid dependent node into attemptToDelete. 470 // The garbage processor will verify whether the owner references are dangling 471 // and delete the dependent if all owner references are confirmed absent. 472 gb.attemptToDelete.Add(n) 473 } 474 } 475 476 func (gb *GraphBuilder) reportInvalidNamespaceOwnerRef(n *node, invalidOwnerUID types.UID) { 477 var invalidOwnerRef metav1.OwnerReference 478 var found = false 479 for _, ownerRef := range n.owners { 480 if ownerRef.UID == invalidOwnerUID { 481 invalidOwnerRef = ownerRef 482 found = true 483 break 484 } 485 } 486 if !found { 487 return 488 } 489 ref := &v1.ObjectReference{ 490 Kind: n.identity.Kind, 491 APIVersion: n.identity.APIVersion, 492 Namespace: n.identity.Namespace, 493 Name: n.identity.Name, 494 UID: n.identity.UID, 495 } 496 invalidIdentity := objectReference{ 497 OwnerReference: metav1.OwnerReference{ 498 Kind: invalidOwnerRef.Kind, 499 APIVersion: invalidOwnerRef.APIVersion, 500 Name: invalidOwnerRef.Name, 501 UID: invalidOwnerRef.UID, 502 }, 503 Namespace: n.identity.Namespace, 504 } 505 gb.eventRecorder.Eventf(ref, v1.EventTypeWarning, "OwnerRefInvalidNamespace", "ownerRef %s does not exist in namespace %q", invalidIdentity, n.identity.Namespace) 506 } 507 508 // insertNode insert the node to gb.uidToNode; then it finds all owners as listed 509 // in n.owners, and adds the node to their dependents list. 510 func (gb *GraphBuilder) insertNode(logger klog.Logger, n *node) { 511 gb.uidToNode.Write(n) 512 gb.addDependentToOwners(logger, n, n.owners) 513 } 514 515 // removeDependentFromOwners remove n from owners' dependents list. 516 func (gb *GraphBuilder) removeDependentFromOwners(n *node, owners []metav1.OwnerReference) { 517 for _, owner := range owners { 518 ownerNode, ok := gb.uidToNode.Read(owner.UID) 519 if !ok { 520 continue 521 } 522 ownerNode.deleteDependent(n) 523 } 524 } 525 526 // removeNode removes the node from gb.uidToNode, then finds all 527 // owners as listed in n.owners, and removes n from their dependents list. 528 func (gb *GraphBuilder) removeNode(n *node) { 529 gb.uidToNode.Delete(n.identity.UID) 530 gb.removeDependentFromOwners(n, n.owners) 531 } 532 533 type ownerRefPair struct { 534 oldRef metav1.OwnerReference 535 newRef metav1.OwnerReference 536 } 537 538 // TODO: profile this function to see if a naive N^2 algorithm performs better 539 // when the number of references is small. 540 func referencesDiffs(old []metav1.OwnerReference, new []metav1.OwnerReference) (added []metav1.OwnerReference, removed []metav1.OwnerReference, changed []ownerRefPair) { 541 oldUIDToRef := make(map[string]metav1.OwnerReference) 542 for _, value := range old { 543 oldUIDToRef[string(value.UID)] = value 544 } 545 oldUIDSet := sets.StringKeySet(oldUIDToRef) 546 for _, value := range new { 547 newUID := string(value.UID) 548 if oldUIDSet.Has(newUID) { 549 if !reflect.DeepEqual(oldUIDToRef[newUID], value) { 550 changed = append(changed, ownerRefPair{oldRef: oldUIDToRef[newUID], newRef: value}) 551 } 552 oldUIDSet.Delete(newUID) 553 } else { 554 added = append(added, value) 555 } 556 } 557 for oldUID := range oldUIDSet { 558 removed = append(removed, oldUIDToRef[oldUID]) 559 } 560 561 return added, removed, changed 562 } 563 564 func deletionStartsWithFinalizer(oldObj interface{}, newAccessor metav1.Object, matchingFinalizer string) bool { 565 // if the new object isn't being deleted, or doesn't have the finalizer we're interested in, return false 566 if !beingDeleted(newAccessor) || !hasFinalizer(newAccessor, matchingFinalizer) { 567 return false 568 } 569 570 // if the old object is nil, or wasn't being deleted, or didn't have the finalizer, return true 571 if oldObj == nil { 572 return true 573 } 574 oldAccessor, err := meta.Accessor(oldObj) 575 if err != nil { 576 utilruntime.HandleError(fmt.Errorf("cannot access oldObj: %v", err)) 577 return false 578 } 579 return !beingDeleted(oldAccessor) || !hasFinalizer(oldAccessor, matchingFinalizer) 580 } 581 582 func beingDeleted(accessor metav1.Object) bool { 583 return accessor.GetDeletionTimestamp() != nil 584 } 585 586 func hasDeleteDependentsFinalizer(accessor metav1.Object) bool { 587 return hasFinalizer(accessor, metav1.FinalizerDeleteDependents) 588 } 589 590 func hasOrphanFinalizer(accessor metav1.Object) bool { 591 return hasFinalizer(accessor, metav1.FinalizerOrphanDependents) 592 } 593 594 func hasFinalizer(accessor metav1.Object, matchingFinalizer string) bool { 595 finalizers := accessor.GetFinalizers() 596 for _, finalizer := range finalizers { 597 if finalizer == matchingFinalizer { 598 return true 599 } 600 } 601 return false 602 } 603 604 // this function takes newAccessor directly because the caller already 605 // instantiates an accessor for the newObj. 606 func startsWaitingForDependentsDeleted(oldObj interface{}, newAccessor metav1.Object) bool { 607 return deletionStartsWithFinalizer(oldObj, newAccessor, metav1.FinalizerDeleteDependents) 608 } 609 610 // this function takes newAccessor directly because the caller already 611 // instantiates an accessor for the newObj. 612 func startsWaitingForDependentsOrphaned(oldObj interface{}, newAccessor metav1.Object) bool { 613 return deletionStartsWithFinalizer(oldObj, newAccessor, metav1.FinalizerOrphanDependents) 614 } 615 616 // if an blocking ownerReference points to an object gets removed, or gets set to 617 // "BlockOwnerDeletion=false", add the object to the attemptToDelete queue. 618 func (gb *GraphBuilder) addUnblockedOwnersToDeleteQueue(logger klog.Logger, removed []metav1.OwnerReference, changed []ownerRefPair) { 619 for _, ref := range removed { 620 if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion { 621 node, found := gb.uidToNode.Read(ref.UID) 622 if !found { 623 logger.V(5).Info("cannot find uid in uidToNode", "uid", ref.UID) 624 continue 625 } 626 gb.attemptToDelete.Add(node) 627 } 628 } 629 for _, c := range changed { 630 wasBlocked := c.oldRef.BlockOwnerDeletion != nil && *c.oldRef.BlockOwnerDeletion 631 isUnblocked := c.newRef.BlockOwnerDeletion == nil || (c.newRef.BlockOwnerDeletion != nil && !*c.newRef.BlockOwnerDeletion) 632 if wasBlocked && isUnblocked { 633 node, found := gb.uidToNode.Read(c.newRef.UID) 634 if !found { 635 logger.V(5).Info("cannot find uid in uidToNode", "uid", c.newRef.UID) 636 continue 637 } 638 gb.attemptToDelete.Add(node) 639 } 640 } 641 } 642 643 func (gb *GraphBuilder) processTransitions(logger klog.Logger, oldObj interface{}, newAccessor metav1.Object, n *node) { 644 if startsWaitingForDependentsOrphaned(oldObj, newAccessor) { 645 logger.V(5).Info("add item to attemptToOrphan", "item", n.identity) 646 gb.attemptToOrphan.Add(n) 647 return 648 } 649 if startsWaitingForDependentsDeleted(oldObj, newAccessor) { 650 logger.V(2).Info("add item to attemptToDelete, because it's waiting for its dependents to be deleted", "item", n.identity) 651 // if the n is added as a "virtual" node, its deletingDependents field is not properly set, so always set it here. 652 n.markDeletingDependents() 653 for dep := range n.dependents { 654 gb.attemptToDelete.Add(dep) 655 } 656 gb.attemptToDelete.Add(n) 657 } 658 } 659 660 func (gb *GraphBuilder) runProcessGraphChanges(logger klog.Logger) { 661 for gb.processGraphChanges(logger) { 662 } 663 } 664 665 func identityFromEvent(event *event, accessor metav1.Object) objectReference { 666 return objectReference{ 667 OwnerReference: metav1.OwnerReference{ 668 APIVersion: event.gvk.GroupVersion().String(), 669 Kind: event.gvk.Kind, 670 UID: accessor.GetUID(), 671 Name: accessor.GetName(), 672 }, 673 Namespace: accessor.GetNamespace(), 674 } 675 } 676 677 // Dequeueing an event from graphChanges, updating graph, populating dirty_queue. 678 func (gb *GraphBuilder) processGraphChanges(logger klog.Logger) bool { 679 item, quit := gb.graphChanges.Get() 680 if quit { 681 return false 682 } 683 defer gb.graphChanges.Done(item) 684 event := item 685 obj := item.obj 686 accessor, err := meta.Accessor(obj) 687 if err != nil { 688 utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err)) 689 return true 690 } 691 692 logger.V(5).Info("GraphBuilder process object", 693 "apiVersion", event.gvk.GroupVersion().String(), 694 "kind", event.gvk.Kind, 695 "object", klog.KObj(accessor), 696 "uid", string(accessor.GetUID()), 697 "eventType", event.eventType, 698 "virtual", event.virtual, 699 ) 700 701 // Check if the node already exists 702 existingNode, found := gb.uidToNode.Read(accessor.GetUID()) 703 if found && !event.virtual && !existingNode.isObserved() { 704 // this marks the node as having been observed via an informer event 705 // 1. this depends on graphChanges only containing add/update events from the actual informer 706 // 2. this allows things tracking virtual nodes' existence to stop polling and rely on informer events 707 observedIdentity := identityFromEvent(event, accessor) 708 if observedIdentity != existingNode.identity { 709 // find dependents that don't match the identity we observed 710 _, potentiallyInvalidDependents := partitionDependents(existingNode.getDependents(), observedIdentity) 711 // add those potentially invalid dependents to the attemptToDelete queue. 712 // if their owners are still solid the attemptToDelete will be a no-op. 713 // this covers the bad child -> good parent observation sequence. 714 // the good parent -> bad child observation sequence is handled in addDependentToOwners 715 for _, dep := range potentiallyInvalidDependents { 716 if len(observedIdentity.Namespace) > 0 && dep.identity.Namespace != observedIdentity.Namespace { 717 // Namespace mismatch, this is definitely wrong 718 logger.V(2).Info("item references an owner but does not match namespaces", 719 "item", dep.identity, 720 "owner", observedIdentity, 721 ) 722 gb.reportInvalidNamespaceOwnerRef(dep, observedIdentity.UID) 723 } 724 gb.attemptToDelete.Add(dep) 725 } 726 727 // make a copy (so we don't modify the existing node in place), store the observed identity, and replace the virtual node 728 logger.V(2).Info("replacing virtual item with observed item", 729 "virtual", existingNode.identity, 730 "observed", observedIdentity, 731 ) 732 existingNode = existingNode.clone() 733 existingNode.identity = observedIdentity 734 gb.uidToNode.Write(existingNode) 735 } 736 existingNode.markObserved() 737 } 738 switch { 739 case (event.eventType == addEvent || event.eventType == updateEvent) && !found: 740 newNode := &node{ 741 identity: identityFromEvent(event, accessor), 742 dependents: make(map[*node]struct{}), 743 owners: accessor.GetOwnerReferences(), 744 deletingDependents: beingDeleted(accessor) && hasDeleteDependentsFinalizer(accessor), 745 beingDeleted: beingDeleted(accessor), 746 } 747 gb.insertNode(logger, newNode) 748 // the underlying delta_fifo may combine a creation and a deletion into 749 // one event, so we need to further process the event. 750 gb.processTransitions(logger, event.oldObj, accessor, newNode) 751 case (event.eventType == addEvent || event.eventType == updateEvent) && found: 752 // handle changes in ownerReferences 753 added, removed, changed := referencesDiffs(existingNode.owners, accessor.GetOwnerReferences()) 754 if len(added) != 0 || len(removed) != 0 || len(changed) != 0 { 755 // check if the changed dependency graph unblock owners that are 756 // waiting for the deletion of their dependents. 757 gb.addUnblockedOwnersToDeleteQueue(logger, removed, changed) 758 // update the node itself 759 existingNode.owners = accessor.GetOwnerReferences() 760 // Add the node to its new owners' dependent lists. 761 gb.addDependentToOwners(logger, existingNode, added) 762 // remove the node from the dependent list of node that are no longer in 763 // the node's owners list. 764 gb.removeDependentFromOwners(existingNode, removed) 765 } 766 767 if beingDeleted(accessor) { 768 existingNode.markBeingDeleted() 769 } 770 gb.processTransitions(logger, event.oldObj, accessor, existingNode) 771 case event.eventType == deleteEvent: 772 if !found { 773 logger.V(5).Info("item doesn't exist in the graph, this shouldn't happen", 774 "item", accessor.GetUID(), 775 ) 776 return true 777 } 778 779 removeExistingNode := true 780 781 if event.virtual { 782 // this is a virtual delete event, not one observed from an informer 783 deletedIdentity := identityFromEvent(event, accessor) 784 if existingNode.virtual { 785 786 // our existing node is also virtual, we're not sure of its coordinates. 787 // see if any dependents reference this owner with coordinates other than the one we got a virtual delete event for. 788 if matchingDependents, nonmatchingDependents := partitionDependents(existingNode.getDependents(), deletedIdentity); len(nonmatchingDependents) > 0 { 789 790 // some of our dependents disagree on our coordinates, so do not remove the existing virtual node from the graph 791 removeExistingNode = false 792 793 if len(matchingDependents) > 0 { 794 // mark the observed deleted identity as absent 795 gb.absentOwnerCache.Add(deletedIdentity) 796 // attempt to delete dependents that do match the verified deleted identity 797 for _, dep := range matchingDependents { 798 gb.attemptToDelete.Add(dep) 799 } 800 } 801 802 // if the delete event verified existingNode.identity doesn't exist... 803 if existingNode.identity == deletedIdentity { 804 // find an alternative identity our nonmatching dependents refer to us by 805 replacementIdentity := getAlternateOwnerIdentity(nonmatchingDependents, deletedIdentity) 806 if replacementIdentity != nil { 807 // replace the existing virtual node with a new one with one of our other potential identities 808 replacementNode := existingNode.clone() 809 replacementNode.identity = *replacementIdentity 810 gb.uidToNode.Write(replacementNode) 811 // and add the new virtual node back to the attemptToDelete queue 812 gb.attemptToDelete.AddRateLimited(replacementNode) 813 } 814 } 815 } 816 817 } else if existingNode.identity != deletedIdentity { 818 // do not remove the existing real node from the graph based on a virtual delete event 819 removeExistingNode = false 820 821 // our existing node which was observed via informer disagrees with the virtual delete event's coordinates 822 matchingDependents, _ := partitionDependents(existingNode.getDependents(), deletedIdentity) 823 824 if len(matchingDependents) > 0 { 825 // mark the observed deleted identity as absent 826 gb.absentOwnerCache.Add(deletedIdentity) 827 // attempt to delete dependents that do match the verified deleted identity 828 for _, dep := range matchingDependents { 829 gb.attemptToDelete.Add(dep) 830 } 831 } 832 } 833 } 834 835 if removeExistingNode { 836 // removeNode updates the graph 837 gb.removeNode(existingNode) 838 existingNode.dependentsLock.RLock() 839 defer existingNode.dependentsLock.RUnlock() 840 if len(existingNode.dependents) > 0 { 841 gb.absentOwnerCache.Add(identityFromEvent(event, accessor)) 842 } 843 for dep := range existingNode.dependents { 844 gb.attemptToDelete.Add(dep) 845 } 846 for _, owner := range existingNode.owners { 847 ownerNode, found := gb.uidToNode.Read(owner.UID) 848 if !found || !ownerNode.isDeletingDependents() { 849 continue 850 } 851 // this is to let attempToDeleteItem check if all the owner's 852 // dependents are deleted, if so, the owner will be deleted. 853 gb.attemptToDelete.Add(ownerNode) 854 } 855 } 856 } 857 return true 858 } 859 860 // partitionDependents divides the provided dependents into a list which have an ownerReference matching the provided identity, 861 // and ones which have an ownerReference for the given uid that do not match the provided identity. 862 // Note that a dependent with multiple ownerReferences for the target uid can end up in both lists. 863 func partitionDependents(dependents []*node, matchOwnerIdentity objectReference) (matching, nonmatching []*node) { 864 ownerIsNamespaced := len(matchOwnerIdentity.Namespace) > 0 865 for i := range dependents { 866 dep := dependents[i] 867 foundMatch := false 868 foundMismatch := false 869 // if the dep namespace matches or the owner is cluster scoped ... 870 if ownerIsNamespaced && matchOwnerIdentity.Namespace != dep.identity.Namespace { 871 // all references to the parent do not match, since the dependent namespace does not match the owner 872 foundMismatch = true 873 } else { 874 for _, ownerRef := range dep.owners { 875 // ... find the ownerRef with a matching uid ... 876 if ownerRef.UID == matchOwnerIdentity.UID { 877 // ... and check if it matches all coordinates 878 if ownerReferenceMatchesCoordinates(ownerRef, matchOwnerIdentity.OwnerReference) { 879 foundMatch = true 880 } else { 881 foundMismatch = true 882 } 883 } 884 } 885 } 886 887 if foundMatch { 888 matching = append(matching, dep) 889 } 890 if foundMismatch { 891 nonmatching = append(nonmatching, dep) 892 } 893 } 894 return matching, nonmatching 895 } 896 897 func referenceLessThan(a, b objectReference) bool { 898 // kind/apiVersion are more significant than namespace, 899 // so that we get coherent ordering between kinds 900 // regardless of whether they are cluster-scoped or namespaced 901 if a.Kind != b.Kind { 902 return a.Kind < b.Kind 903 } 904 if a.APIVersion != b.APIVersion { 905 return a.APIVersion < b.APIVersion 906 } 907 // namespace is more significant than name 908 if a.Namespace != b.Namespace { 909 return a.Namespace < b.Namespace 910 } 911 // name is more significant than uid 912 if a.Name != b.Name { 913 return a.Name < b.Name 914 } 915 // uid is included for completeness, but is expected to be identical 916 // when getting alternate identities for an owner since they are keyed by uid 917 if a.UID != b.UID { 918 return a.UID < b.UID 919 } 920 return false 921 } 922 923 // getAlternateOwnerIdentity searches deps for owner references which match 924 // verifiedAbsentIdentity.UID but differ in apiVersion/kind/name or namespace. 925 // The first that follows verifiedAbsentIdentity (according to referenceLessThan) is returned. 926 // If none follow verifiedAbsentIdentity, the first (according to referenceLessThan) is returned. 927 // If no alternate identities are found, nil is returned. 928 func getAlternateOwnerIdentity(deps []*node, verifiedAbsentIdentity objectReference) *objectReference { 929 absentIdentityIsClusterScoped := len(verifiedAbsentIdentity.Namespace) == 0 930 931 seenAlternates := map[objectReference]bool{verifiedAbsentIdentity: true} 932 933 // keep track of the first alternate reference (according to referenceLessThan) 934 var first *objectReference 935 // keep track of the first reference following verifiedAbsentIdentity (according to referenceLessThan) 936 var firstFollowing *objectReference 937 938 for _, dep := range deps { 939 for _, ownerRef := range dep.owners { 940 if ownerRef.UID != verifiedAbsentIdentity.UID { 941 // skip references that aren't the uid we care about 942 continue 943 } 944 945 if ownerReferenceMatchesCoordinates(ownerRef, verifiedAbsentIdentity.OwnerReference) { 946 if absentIdentityIsClusterScoped || verifiedAbsentIdentity.Namespace == dep.identity.Namespace { 947 // skip references that exactly match verifiedAbsentIdentity 948 continue 949 } 950 } 951 952 ref := objectReference{OwnerReference: ownerReferenceCoordinates(ownerRef), Namespace: dep.identity.Namespace} 953 if absentIdentityIsClusterScoped && ref.APIVersion == verifiedAbsentIdentity.APIVersion && ref.Kind == verifiedAbsentIdentity.Kind { 954 // we know this apiVersion/kind is cluster-scoped because of verifiedAbsentIdentity, 955 // so clear the namespace from the alternate identity 956 ref.Namespace = "" 957 } 958 959 if seenAlternates[ref] { 960 // skip references we've already seen 961 continue 962 } 963 seenAlternates[ref] = true 964 965 if first == nil || referenceLessThan(ref, *first) { 966 // this alternate comes first lexically 967 first = &ref 968 } 969 if referenceLessThan(verifiedAbsentIdentity, ref) && (firstFollowing == nil || referenceLessThan(ref, *firstFollowing)) { 970 // this alternate is the first following verifiedAbsentIdentity lexically 971 firstFollowing = &ref 972 } 973 } 974 } 975 976 // return the first alternate identity following the verified absent identity, if there is one 977 if firstFollowing != nil { 978 return firstFollowing 979 } 980 // otherwise return the first alternate identity 981 return first 982 } 983 984 func (gb *GraphBuilder) GetGraphResources() ( 985 attemptToDelete workqueue.TypedRateLimitingInterface[*node], 986 attemptToOrphan workqueue.TypedRateLimitingInterface[*node], 987 absentOwnerCache *ReferenceCache, 988 ) { 989 return gb.attemptToDelete, gb.attemptToOrphan, gb.absentOwnerCache 990 } 991 992 type Monitor struct { 993 Store cache.Store 994 Controller cache.Controller 995 } 996 997 // GetMonitor returns a monitor for the given resource. 998 // If the monitor is not synced, it will return an error and the monitor to allow the caller to decide whether to retry. 999 // If the monitor is not found, it will return only an error. 1000 func (gb *GraphBuilder) GetMonitor(ctx context.Context, resource schema.GroupVersionResource) (*Monitor, error) { 1001 gb.monitorLock.RLock() 1002 defer gb.monitorLock.RUnlock() 1003 1004 var monitor *monitor 1005 if m, ok := gb.monitors[resource]; ok { 1006 monitor = m 1007 } else { 1008 for monitorGVR, m := range gb.monitors { 1009 if monitorGVR.Group == resource.Group && monitorGVR.Resource == resource.Resource { 1010 monitor = m 1011 break 1012 } 1013 } 1014 } 1015 1016 if monitor == nil { 1017 return nil, fmt.Errorf("no monitor found for resource %s", resource.String()) 1018 } 1019 1020 resourceMonitor := &Monitor{ 1021 Store: monitor.store, 1022 Controller: monitor.controller, 1023 } 1024 1025 if !cache.WaitForNamedCacheSync( 1026 gb.Name(), 1027 ctx.Done(), 1028 func() bool { 1029 return monitor.controller.HasSynced() 1030 }, 1031 ) { 1032 // returning monitor to allow the caller to decide whether to retry as it can be synced later 1033 return resourceMonitor, fmt.Errorf("dependency graph for resource %s is not synced", resource.String()) 1034 } 1035 1036 return resourceMonitor, nil 1037 } 1038 1039 func (gb *GraphBuilder) Name() string { 1040 return "dependencygraphbuilder" 1041 }