@@ -370,7 +370,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
370370 }
371371 }
372372
373- // add/update inventory with target objects
373+ // prepare ( add/update) new inventory with target objects
374374 // TODO: review this; it would be cleaner to use a DeepCopy method for a []*InventoryItem type (if there would be such a type)
375375 newInventory := slices .Collect (* inventory , func (item * InventoryItem ) * InventoryItem { return item .DeepCopy () })
376376 numAdded := 0
@@ -435,7 +435,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
435435 }
436436 }
437437
438- // mark obsolete inventory items (clear digest)
438+ // mark obsolete items (clear digest) in new inventory
439439 for _ , item := range newInventory {
440440 found := false
441441 for _ , object := range objects {
@@ -451,7 +451,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
451451 }
452452 }
453453
454- // validate object set :
454+ // validate new inventory :
455455 // - check that all managed instances have apply-order greater than or equal to the according managed type
456456 // - check that all managed instances have delete-order less than or equal to the according managed type
457457 // - check that no managed types are about to be deleted (empty digest) unless all related managed instances are as well
@@ -491,7 +491,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
491491 }
492492 }
493493
494- // accept inventory for further processing, put into right order for future deletion
494+ // accept new inventory for further processing, put into right order for future deletion
495495 * inventory = sortObjectsForDelete (newInventory )
496496
497497 // trigger another reconcile if something was added (to be sure that it is persisted)
@@ -504,136 +504,77 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
504504 // - the persisted inventory at least has the same object keys as the in-memory inventory
505505 // now it is about to synchronize the cluster state with the inventory
506506
507- // delete redundant objects and maintain inventory;
508- // objects are deleted in waves according to their delete order;
509- // that means, only if all redundant objects of a wave are gone or comppleted, the next
510- // wave will be processed; within each wave, objects which are instances of managed
511- // types are deleted before all other objects, and namespaces will only be deleted
512- // if they are not used by any object in the inventory (note that this may cause deadlocks)
513- numManagedToBeDeleted := 0
514- numToBeDeleted := 0
515- for k , item := range * inventory {
516- // if this is the first object of an order, then
517- // count instances of managed types in this wave which are about to be deleted
518- if k == 0 || (* inventory )[k - 1 ].DeleteOrder < item .DeleteOrder {
519- log .V (2 ).Info ("begin of deletion wave" , "order" , item .DeleteOrder )
520- numManagedToBeDeleted = 0
521- for j := k ; j < len (* inventory ) && (* inventory )[j ].DeleteOrder == item .DeleteOrder ; j ++ {
522- _item := (* inventory )[j ]
523- if (_item .Phase == PhaseScheduledForDeletion || _item .Phase == PhaseScheduledForCompletion || _item .Phase == PhaseDeleting || _item .Phase == PhaseCompleting ) && isInstanceOfManagedType (* inventory , _item ) {
524- numManagedToBeDeleted ++
507+ // note: after this point, it is also guaranteed that objects is contained in the persisted inventory;
508+ // the inventory therefore consists of two parts:
509+ // - items which are contained in objects
510+ // these items can have one of the following phases:
511+ // - PhaseScheduledForApplication
512+ // - PhaseCreating
513+ // - PhaseUpdating
514+ // - PhaseReady
515+ // - PhaseScheduledForCompletion
516+ // - PhaseCompleting
517+ // - PhaseCompleted
518+ // - items which are not contained in objects
519+ // their phase is one of the following:
520+ // - PhaseScheduledForDeletion
521+ // - PhaseDeleting
522+
523+ // create missing namespaces
524+ if r .createMissingNamespaces {
525+ for _ , namespace := range findMissingNamespaces (objects ) {
526+ if err := r .client .Get (ctx , apitypes.NamespacedName {Name : namespace }, & corev1.Namespace {}); err != nil {
527+ if ! apierrors .IsNotFound (err ) {
528+ return false , errors .Wrapf (err , "error reading namespace %s" , namespace )
529+ }
530+ if err := r .client .Create (ctx , & corev1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : namespace }}, client .FieldOwner (r .name )); err != nil {
531+ return false , errors .Wrapf (err , "error creating namespace %s" , namespace )
525532 }
526533 }
527534 }
535+ }
528536
529- if item .Phase == PhaseScheduledForDeletion || item .Phase == PhaseScheduledForCompletion || item .Phase == PhaseDeleting || item .Phase == PhaseCompleting {
530- // fetch object (if existing)
537+ // put objects into right order for applying
538+ objects = sortObjectsForApply (objects , getApplyOrder )
539+
540+ // finish due completions
541+ // note that completions do not honor delete-order or delete-policy
542+ // however, due to the way how PhaseScheduledForCompletion is set, the affected objects will
543+ // always be in one and the same apply order
544+ // in addition deletions are triggered in the canonical deletion order (but not waited for)
545+ numToBeCompleted := 0
546+ for _ , item := range * inventory {
547+ if item .Phase == PhaseScheduledForCompletion || item .Phase == PhaseCompleting {
531548 existingObject , err := r .readObject (ctx , item )
532549 if err != nil {
533550 return false , errors .Wrapf (err , "error reading object %s" , item )
534551 }
535552
536- orphan := item .DeletePolicy == DeletePolicyOrphan
537-
538553 switch item .Phase {
539- case PhaseScheduledForDeletion :
540- // delete namespaces after all contained inventory items
541- // delete all instances of managed types before remaining objects; this ensures that no objects are prematurely
542- // deleted which are needed for the deletion of the managed instances, such as webhook servers, api servers, ...
543- if (! isNamespace (item ) || ! isNamespaceUsed (* inventory , item .Name )) && (numManagedToBeDeleted == 0 || isInstanceOfManagedType (* inventory , item )) {
544- if orphan {
545- item .Phase = ""
546- } else {
547- // note: here is a theoretical risk that we delete an existing foreign object, because informers are not yet synced
548- // however not sending the delete request is also not an option, because this might lead to orphaned own dependents
549- // TODO: perform an additional owner id check
550- if err := r .deleteObject (ctx , item , existingObject ); err != nil {
551- return false , errors .Wrapf (err , "error deleting object %s" , item )
552- }
553- item .Phase = PhaseDeleting
554- item .Status = status .TerminatingStatus
555- numToBeDeleted ++
556- }
557- } else {
558- numToBeDeleted ++
559- }
560554 case PhaseScheduledForCompletion :
561- // delete namespaces after all contained inventory items
562- // delete all instances of managed types before remaining objects; this ensures that no objects are prematurely
563- // deleted which are needed for the deletion of the managed instances, such as webhook servers, api servers, ...
564- if (! isNamespace (item ) || ! isNamespaceUsed (* inventory , item .Name )) && (numManagedToBeDeleted == 0 || isInstanceOfManagedType (* inventory , item )) {
565- if orphan {
566- return false , fmt .Errorf ("invalid usage of deletion policy: object %s is scheduled for completion and therefore cannot be orphaned" , item )
567- } else {
568- // note: here is a theoretical risk that we delete an existing foreign object, because informers are not yet synced
569- // however not sending the delete request is also not an option, because this might lead to orphaned own dependents
570- // TODO: perform an additional owner id check
571- if err := r .deleteObject (ctx , item , existingObject ); err != nil {
572- return false , errors .Wrapf (err , "error deleting object %s" , item )
573- }
574- item .Phase = PhaseCompleting
575- item .Status = status .TerminatingStatus
576- numToBeDeleted ++
577- }
578- } else {
579- numToBeDeleted ++
580- }
581- case PhaseDeleting :
582- // if object is gone, we can remove it from inventory
583- if existingObject == nil {
584- item .Phase = ""
585- } else {
586- numToBeDeleted ++
555+ if err := r .deleteObject (ctx , item , existingObject ); err != nil {
556+ return false , errors .Wrapf (err , "error deleting object %s" , item )
587557 }
558+ item .Phase = PhaseCompleting
559+ item .Status = status .TerminatingStatus
560+ numToBeCompleted ++
588561 case PhaseCompleting :
589- // if object is gone, it is set to completed, and kept in inventory
590562 if existingObject == nil {
591563 item .Phase = PhaseCompleted
592564 item .Status = ""
593565 } else {
594- numToBeDeleted ++
566+ numToBeCompleted ++
595567 }
596- default :
597- // note: any other phase value would indicate a severe code problem, so we want to see the panic in that case
598- panic ("this cannot happen" )
599- }
600- }
601-
602- // trigger another reconcile if this is the last object of the wave, and some deletions are not yet completed
603- if k == len (* inventory )- 1 || (* inventory )[k + 1 ].DeleteOrder > item .DeleteOrder {
604- log .V (2 ).Info ("end of deletion wave" , "order" , item .DeleteOrder )
605- if numToBeDeleted > 0 {
606- break
607568 }
608569 }
609570 }
610571
611- * inventory = slices .Select (* inventory , func (item * InventoryItem ) bool { return item .Phase != "" })
612-
613- // trigger another reconcile
614- if numToBeDeleted > 0 {
572+ // trigger another reconcile if any to-be-completed objects are left
573+ if numToBeCompleted > 0 {
615574 return false , nil
616575 }
617576
618- // note: after this point, PhaseScheduledForDeletion, PhaseScheduledForCompletion, PhaseDeleting, PhaseCompleting cannot occur anymore in inventory
619- // in other words: the inventory and objects contains the same resources
620-
621- // create missing namespaces
622- if r .createMissingNamespaces {
623- for _ , namespace := range findMissingNamespaces (objects ) {
624- if err := r .client .Get (ctx , apitypes.NamespacedName {Name : namespace }, & corev1.Namespace {}); err != nil {
625- if ! apierrors .IsNotFound (err ) {
626- return false , errors .Wrapf (err , "error reading namespace %s" , namespace )
627- }
628- if err := r .client .Create (ctx , & corev1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : namespace }}, client .FieldOwner (r .name )); err != nil {
629- return false , errors .Wrapf (err , "error creating namespace %s" , namespace )
630- }
631- }
632- }
633- }
634-
635- // put objects into right order for applying
636- objects = sortObjectsForApply (objects , getApplyOrder )
577+ // note: after this point, PhaseScheduledForCompletion, PhaseCompleting cannot occur anymore in inventory
637578
638579 // apply objects and maintain inventory;
639580 // objects are applied (i.e. created/updated) in waves according to their apply order;
@@ -748,7 +689,94 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
748689 }
749690 }
750691
751- return numUnready == 0 , nil
692+ // trigger another reconcile if any unready objects are left
693+ if numUnready > 0 {
694+ return false , nil
695+ }
696+
697+ // delete redundant objects and maintain inventory;
698+ // objects are deleted in waves according to their delete order;
699+ // that means, only if all redundant objects of a wave are gone , the next
700+ // wave will be processed; within each wave, objects which are instances of managed
701+ // types are deleted before all other objects, and namespaces will only be deleted
702+ // if they are not used by any object in the inventory (note that this may cause deadlocks)
703+ numManagedToBeDeleted := 0
704+ numToBeDeleted := 0
705+ for k , item := range * inventory {
706+ // if this is the first object of an order, then
707+ // count instances of managed types in this wave which are about to be deleted
708+ if k == 0 || (* inventory )[k - 1 ].DeleteOrder < item .DeleteOrder {
709+ log .V (2 ).Info ("begin of deletion wave" , "order" , item .DeleteOrder )
710+ numManagedToBeDeleted = 0
711+ for j := k ; j < len (* inventory ) && (* inventory )[j ].DeleteOrder == item .DeleteOrder ; j ++ {
712+ _item := (* inventory )[j ]
713+ if (_item .Phase == PhaseScheduledForDeletion || _item .Phase == PhaseDeleting ) && isInstanceOfManagedType (* inventory , _item ) {
714+ numManagedToBeDeleted ++
715+ }
716+ }
717+ }
718+
719+ if item .Phase == PhaseScheduledForDeletion || item .Phase == PhaseDeleting {
720+ // fetch object (if existing)
721+ existingObject , err := r .readObject (ctx , item )
722+ if err != nil {
723+ return false , errors .Wrapf (err , "error reading object %s" , item )
724+ }
725+
726+ orphan := item .DeletePolicy == DeletePolicyOrphan
727+
728+ switch item .Phase {
729+ case PhaseScheduledForDeletion :
730+ // delete namespaces after all contained inventory items
731+ // delete all instances of managed types before remaining objects; this ensures that no objects are prematurely
732+ // deleted which are needed for the deletion of the managed instances, such as webhook servers, api servers, ...
733+ if (! isNamespace (item ) || ! isNamespaceUsed (* inventory , item .Name )) && (numManagedToBeDeleted == 0 || isInstanceOfManagedType (* inventory , item )) {
734+ if orphan {
735+ item .Phase = ""
736+ } else {
737+ // note: here is a theoretical risk that we delete an existing foreign object, because informers are not yet synced
738+ // however not sending the delete request is also not an option, because this might lead to orphaned own dependents
739+ // TODO: perform an additional owner id check
740+ if err := r .deleteObject (ctx , item , existingObject ); err != nil {
741+ return false , errors .Wrapf (err , "error deleting object %s" , item )
742+ }
743+ item .Phase = PhaseDeleting
744+ item .Status = status .TerminatingStatus
745+ numToBeDeleted ++
746+ }
747+ } else {
748+ numToBeDeleted ++
749+ }
750+ case PhaseDeleting :
751+ // if object is gone, we can remove it from inventory
752+ if existingObject == nil {
753+ item .Phase = ""
754+ } else {
755+ numToBeDeleted ++
756+ }
757+ default :
758+ // note: any other phase value would indicate a severe code problem, so we want to see the panic in that case
759+ panic ("this cannot happen" )
760+ }
761+ }
762+
763+ // trigger another reconcile if this is the last object of the wave, and some deletions are not yet finished
764+ if k == len (* inventory )- 1 || (* inventory )[k + 1 ].DeleteOrder > item .DeleteOrder {
765+ log .V (2 ).Info ("end of deletion wave" , "order" , item .DeleteOrder )
766+ if numToBeDeleted > 0 {
767+ break
768+ }
769+ }
770+ }
771+
772+ * inventory = slices .Select (* inventory , func (item * InventoryItem ) bool { return item .Phase != "" })
773+
774+ // trigger another reconcile if any to-be-deleted objects are left
775+ if numToBeDeleted > 0 {
776+ return false , nil
777+ }
778+
779+ return true , nil
752780}
753781
754782// Delete objects stored in the inventory from the target cluster and maintain inventory.
0 commit comments