Skip to content

Commit 790f314

Browse files
authored
rework deletion policy (#221)
1 parent bcb10f2 commit 790f314

File tree

3 files changed

+43
-23
lines changed

3 files changed

+43
-23
lines changed

pkg/reconciler/reconciler.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ const (
5858
maxOrder = math.MaxInt16
5959
)
6060

61+
const (
62+
forceReapplyPeriod = 60 * time.Minute
63+
)
64+
6165
var adoptionPolicyByAnnotation = map[string]AdoptionPolicy{
6266
types.AdoptionPolicyNever: AdoptionPolicyNever,
6367
types.AdoptionPolicyIfUnowned: AdoptionPolicyIfUnowned,
@@ -78,8 +82,10 @@ var updatePolicyByAnnotation = map[string]UpdatePolicy{
7882
}
7983

8084
var deletePolicyByAnnotation = map[string]DeletePolicy{
81-
types.DeletePolicyDelete: DeletePolicyDelete,
82-
types.DeletePolicyOrphan: DeletePolicyOrphan,
85+
types.DeletePolicyDelete: DeletePolicyDelete,
86+
types.DeletePolicyOrphan: DeletePolicyOrphan,
87+
types.DeletePolicyOrphanOnApply: DeletePolicyOrphanOnApply,
88+
types.DeletePolicyOrphanOnDelete: DeletePolicyOrphanOnDelete,
8389
}
8490

8591
// ReconcilerOptions are creation options for a Reconciler.
@@ -146,7 +152,7 @@ type Reconciler struct {
146152
}
147153

148154
// Create new reconciler.
149-
// The passed name should be fully qualified; it will be used as field owner and finalizer.
155+
// The passed name should be fully qualified; by default it will be used as field owner and finalizer.
150156
// The passed client's scheme must recognize at least the core group (v1) and apiextensions.k8s.io/v1 and apiregistration.k8s.io/v1.
151157
func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions) *Reconciler {
152158
// TOOD: validate options
@@ -209,7 +215,8 @@ func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions)
209215
// Objects which are instances of namespaced types will be placed into the namespace passed to Apply(), if they have no namespace defined in their manifest.
210216
// An update of an existing object will be performed if it is considered to be out of sync; that means:
211217
// - the object's manifest has changed, and the effective reconcile policy is ReconcilePolicyOnObjectChange or ReconcilePolicyOnObjectOrComponentChange or
212-
// - the specified component revision has changed and the effective reconcile policy is ReconcilePolicyOnObjectOrComponentChange.
218+
// - the specified component revision has changed and the effective reconcile policy is ReconcilePolicyOnObjectOrComponentChange or
219+
// - periodically after forceReapplyPeriod.
213220
//
214221
// The update itself will be done as follows:
215222
// - if the effective update policy is UpdatePolicyReplace, a http PUT request will be sent to the Kubernetes API
@@ -223,11 +230,10 @@ func NewReconciler(name string, clnt cluster.Client, options ReconcilerOptions)
223230
// and it might be re-applied/re-purged in case it runs out of sync. Within a wave, objects are processed following a certain internal order;
224231
// in particular, instances of types which are part of the wave are processed only if all other objects in that wave have a ready state.
225232
//
226-
// Redundant objects will be removed; that means, a http DELETE request will be sent to the Kubernetes API; note that an effective Orphan deletion
227-
// policy will not prevent deletion here; the deletion policy will only be honored when the component as whole gets deleted.
233+
// Redundant objects will be removed; that means, a http DELETE request will be sent to the Kubernetes API.
228234
//
229235
// This method will change the passed inventory (add or remove elements, change elements). If Apply() returns true, then all objects are successfully reconciled;
230-
// otherwise, if it returns false, the caller should recall it timely, until it returns true. In any case, the passed inventory should match the state of the
236+
// otherwise, if it returns false, the caller should re-call it periodically, until it returns true. In any case, the passed inventory should match the state of the
231237
// inventory after the previous invocation of Apply(); usually, the caller saves the inventory after calling Apply(), and loads it before calling Apply().
232238
// The namespace and ownerId arguments should not be changed across subsequent invocations of Apply(); the componentRevision should be incremented only.
233239
func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, objects []client.Object, namespace string, ownerId string, componentRevision int64) (bool, error) {
@@ -658,7 +664,7 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
658664
numUnready++
659665
} else if existingObject.GetDeletionTimestamp().IsZero() &&
660666
// TODO: make force-reconcile period (60 minutes as of now) configurable
661-
(existingObject.GetAnnotations()[r.annotationKeyDigest] != item.Digest || item.LastAppliedAt == nil || item.LastAppliedAt.Time.Before(now.Add(-60*time.Minute))) {
667+
(existingObject.GetAnnotations()[r.annotationKeyDigest] != item.Digest || item.LastAppliedAt == nil || item.LastAppliedAt.Time.Before(now.Add(-forceReapplyPeriod))) {
662668
switch updatePolicy {
663669
case UpdatePolicyRecreate:
664670
if err := r.deleteObject(ctx, object, existingObject, hashedOwnerId); err != nil {
@@ -712,6 +718,9 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
712718
if numPurged > 0 {
713719
return false, nil
714720
}
721+
// TODO: we should do deletion of redundant objects (in the current apply stage) here
722+
// maybe it would even make sense to introduce something like a delete-on-apply-policy (Early,Regular,Late)
723+
// and maybe even a delete-on-apply-order ...
715724
} else {
716725
return false, nil
717726
}
@@ -752,12 +761,13 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
752761
return false, errors.Wrapf(err, "error reading object %s", item)
753762
}
754763

755-
// note: objects becoming obsolete during an apply are no longer honoring deletion policy (orphan)
756-
// TODO: not sure if there is a case where someone would like to orphan such resources while applying;
757-
// if so, then we probably should introduce a third deletion policy, OrphanAlsoOnApply or OrphanAlways or similar ...
758-
// maybe even more values could be required, such as OrphanOnlyOnApply ...
759-
// in any case, the following code should be revisited; cleaned up or adjusted
760-
orphan := false
764+
// note: the effective deletion policy is always the last known one of the dependent object,
765+
// that is, the one determined when the object was contained in the manifests the last time;
766+
// just-in-time changes of the default deletion policy on the component thus have no impact on the
767+
// deletion policy of redundant objects; dependent objects are orphaned if they have an effective
768+
// Orphan or OrphanOnApply deletion policy.
769+
770+
orphan := item.DeletePolicy == DeletePolicyOrphan || item.DeletePolicy == DeletePolicyOrphanOnApply
761771

762772
switch item.Phase {
763773
case PhaseScheduledForDeletion:
@@ -817,7 +827,8 @@ func (r *Reconciler) Apply(ctx context.Context, inventory *[]*InventoryItem, obj
817827
// objects having a certain delete order will only start if all objects with lower delete order are gone. Within a wave, objects are
818828
// deleted following a certain internal ordering; in particular, if there are instances of types which are part of the wave, then these
819829
// instances will be deleted first; only if all such instances are gone, the remaining objects of the wave will be deleted.
820-
// Objects which have an effective Orphan deletion policy will not be touched (remain in the cluster), but will no longer appear in the inventory.
830+
// Objects which have an effective Orphan or OrphanOnDelete deletion policy will not be touched (remain in the cluster),
831+
// but will no longer appear in the inventory.
821832
//
822833
// This method will change the passed inventory (remove elements, change elements). If Delete() returns true, then all objects are gone; otherwise,
823834
// if it returns false, the caller should recall it timely, until it returns true. In any case, the passed inventory should match the state of the
@@ -855,7 +866,7 @@ func (r *Reconciler) Delete(ctx context.Context, inventory *[]*InventoryItem, ow
855866
return false, errors.Wrapf(err, "error reading object %s", item)
856867
}
857868

858-
orphan := item.DeletePolicy == DeletePolicyOrphan
869+
orphan := item.DeletePolicy == DeletePolicyOrphan || item.DeletePolicy == DeletePolicyOrphanOnDelete
859870

860871
switch item.Phase {
861872
case PhaseDeleting:
@@ -905,9 +916,11 @@ func (r *Reconciler) Delete(ctx context.Context, inventory *[]*InventoryItem, ow
905916
// Check if the object set defined by inventory is ready for deletion; that means: check if the inventory contains
906917
// types (as custom resource definition or from an api service), while there exist instances of these types in the cluster,
907918
// which are not contained in the inventory. There is one exception of this rule: if all objects in the inventory have their
908-
// delete policy set to DeletePolicyOrphan, then the deletion of the component is immediately allowed.
919+
// deletion policy set to Orphan or OrphanOnDelete, then the deletion of the component is immediately allowed.
909920
func (r *Reconciler) IsDeletionAllowed(ctx context.Context, inventory *[]*InventoryItem) (bool, string, error) {
910-
if slices.All(*inventory, func(item *InventoryItem) bool { return item.DeletePolicy == DeletePolicyOrphan }) {
921+
if slices.All(*inventory, func(item *InventoryItem) bool {
922+
return item.DeletePolicy == DeletePolicyOrphan || item.DeletePolicy == DeletePolicyOrphanOnDelete
923+
}) {
911924
return true, "", nil
912925
}
913926
for _, item := range *inventory {
@@ -1246,7 +1259,7 @@ func (r *Reconciler) getDeletePolicy(object client.Object) (DeletePolicy, error)
12461259
switch deletePolicy {
12471260
case "", types.DeletePolicyDefault:
12481261
return r.deletePolicy, nil
1249-
case types.DeletePolicyDelete, types.DeletePolicyOrphan:
1262+
case types.DeletePolicyDelete, types.DeletePolicyOrphan, types.DeletePolicyOrphanOnApply, types.DeletePolicyOrphanOnDelete:
12501263
return deletePolicyByAnnotation[deletePolicy], nil
12511264
default:
12521265
return "", fmt.Errorf("invalid value for annotation %s: %s", r.annotationKeyDeletePolicy, deletePolicy)

pkg/reconciler/types.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,13 @@ type DeletePolicy string
7575
const (
7676
// Delete dependent objects.
7777
DeletePolicyDelete DeletePolicy = "Delete"
78-
// Orphan dependent objects.
78+
// Orphan dependent objects; that is, always, both if they become redundant when the component is applied,
79+
// and if the component is deleted.
7980
DeletePolicyOrphan DeletePolicy = "Orphan"
81+
// Orphan dependent objects if they become redundant when the compponent is applied.
82+
DeletePolicyOrphanOnApply DeletePolicy = "OrphanOnApply"
83+
// Orphan dependent objects if the component is deleted.
84+
DeletePolicyOrphanOnDelete DeletePolicy = "OrphanOnDelete"
8085
)
8186

8287
// MissingNamespacesPolicy defines what the reconciler does if namespaces of dependent objects are not existing.

pkg/types/constants.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ const (
4040
)
4141

4242
const (
43-
DeletePolicyDefault = "default"
44-
DeletePolicyDelete = "delete"
45-
DeletePolicyOrphan = "orphan"
43+
DeletePolicyDefault = "default"
44+
DeletePolicyDelete = "delete"
45+
DeletePolicyOrphan = "orphan"
46+
DeletePolicyOrphanOnApply = "orphan-on-apply"
47+
DeletePolicyOrphanOnDelete = "orphan-on-delete"
4648
)
4749

4850
const (

0 commit comments

Comments
 (0)