Skip to content

Commit 68ab772

Browse files
authored
Merge pull request #623 from sdowell/fix-retain-reconcile-fail
fix: retain objects which failed to reconcile
2 parents 833d70f + cd911bf commit 68ab772

File tree

4 files changed

+102
-41
lines changed

4 files changed

+102
-41
lines changed

pkg/apply/task/delete_inv_task_test.go

+11-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
apierrors "k8s.io/apimachinery/pkg/api/errors"
1010
"k8s.io/apimachinery/pkg/runtime/schema"
11-
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
1211
"sigs.k8s.io/cli-utils/pkg/apply/cache"
1312
"sigs.k8s.io/cli-utils/pkg/apply/event"
1413
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
@@ -24,6 +23,7 @@ func TestDeleteInvTask(t *testing.T) {
2423
id3 := object.UnstructuredToObjMetadata(obj3)
2524
testCases := map[string]struct {
2625
prevInventory object.ObjMetadataSet
26+
deletedObjs object.ObjMetadataSet
2727
failedDeletes object.ObjMetadataSet
2828
failedReconciles object.ObjMetadataSet
2929
timeoutReconciles object.ObjMetadataSet
@@ -54,20 +54,23 @@ func TestDeleteInvTask(t *testing.T) {
5454
},
5555
"inventory is updated instead of deleted in case of reconcile failure": {
5656
prevInventory: object.ObjMetadataSet{id1, id2, id3},
57+
deletedObjs: object.ObjMetadataSet{id1, id2, id3},
5758
failedReconciles: object.ObjMetadataSet{id1},
5859
err: nil,
5960
isError: false,
6061
expectedObjs: object.ObjMetadataSet{id1},
6162
},
6263
"inventory is updated instead of deleted in case of reconcile timeout": {
6364
prevInventory: object.ObjMetadataSet{id1, id2, id3},
65+
deletedObjs: object.ObjMetadataSet{id1, id2, id3},
6466
timeoutReconciles: object.ObjMetadataSet{id1},
6567
err: nil,
6668
isError: false,
6769
expectedObjs: object.ObjMetadataSet{id1},
6870
},
6971
"inventory is updated instead of deleted in case of pruning/reconcile failure": {
7072
prevInventory: object.ObjMetadataSet{id1, id2, id3},
73+
deletedObjs: object.ObjMetadataSet{id1, id2, id3},
7174
failedReconciles: object.ObjMetadataSet{id1},
7275
failedDeletes: object.ObjMetadataSet{id2},
7376
err: nil,
@@ -82,34 +85,20 @@ func TestDeleteInvTask(t *testing.T) {
8285
eventChannel := make(chan event.Event)
8386
resourceCache := cache.NewResourceCacheMap()
8487
context := taskrunner.NewTaskContext(eventChannel, resourceCache)
88+
im := context.InventoryManager()
89+
for _, deleteObj := range tc.deletedObjs {
90+
im.AddSuccessfulDelete(deleteObj, "unused-uid")
91+
}
8592
for _, failedDelete := range tc.failedDeletes {
86-
context.InventoryManager().AddFailedDelete(failedDelete)
93+
im.AddFailedDelete(failedDelete)
8794
}
8895
for _, failedReconcile := range tc.failedReconciles {
89-
context.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
90-
ObjectReference: actuation.ObjectReference{
91-
Group: failedReconcile.GroupKind.Group,
92-
Kind: failedReconcile.GroupKind.Kind,
93-
Name: failedReconcile.Name,
94-
Namespace: failedReconcile.Namespace,
95-
},
96-
})
97-
context.AddInvalidObject(failedReconcile)
98-
if err := context.InventoryManager().SetFailedReconcile(failedReconcile); err != nil {
96+
if err := im.SetFailedReconcile(failedReconcile); err != nil {
9997
t.Fatal(err)
10098
}
10199
}
102100
for _, timeoutReconcile := range tc.timeoutReconciles {
103-
context.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
104-
ObjectReference: actuation.ObjectReference{
105-
Group: timeoutReconcile.GroupKind.Group,
106-
Kind: timeoutReconcile.GroupKind.Kind,
107-
Name: timeoutReconcile.Name,
108-
Namespace: timeoutReconcile.Namespace,
109-
},
110-
})
111-
context.AddInvalidObject(timeoutReconcile)
112-
if err := context.InventoryManager().SetTimeoutReconcile(timeoutReconcile); err != nil {
101+
if err := im.SetTimeoutReconcile(timeoutReconcile); err != nil {
113102
t.Fatal(err)
114103
}
115104
}

pkg/apply/task/inv_set_task.go

+12
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ func (i *DeleteOrUpdateInvTask) updateInventory(taskContext *taskrunner.TaskCont
131131
klog.V(4).Infof("keep in inventory %d skipped prunes", len(pruneSkips))
132132
invObjs = invObjs.Union(pruneSkips)
133133

134+
// If an object failed to reconcile and was previously stored in the inventory,
135+
// then keep it in the inventory so it can be waited on next time.
136+
reconcileFailures := i.PrevInventory.Intersection(im.FailedReconciles())
137+
klog.V(4).Infof("set inventory %d failed reconciles", len(reconcileFailures))
138+
invObjs = invObjs.Union(reconcileFailures)
139+
140+
// If an object timed out reconciling and was previously stored in the inventory,
141+
// then keep it in the inventory so it can be waited on next time.
142+
reconcileTimeouts := i.PrevInventory.Intersection(im.TimeoutReconciles())
143+
klog.V(4).Infof("keep in inventory %d timeout reconciles", len(reconcileTimeouts))
144+
invObjs = invObjs.Union(reconcileTimeouts)
145+
134146
// If an object is abandoned, then remove it from the inventory.
135147
abandonedObjects := taskContext.AbandonedObjects()
136148
klog.V(4).Infof("remove from inventory %d abandoned objects", len(abandonedObjects))

pkg/apply/task/inv_set_task_test.go

+49-9
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ func TestInvSetTask(t *testing.T) {
2929
idInvalid := object.UnstructuredToObjMetadata(objInvalid)
3030

3131
tests := map[string]struct {
32-
prevInventory object.ObjMetadataSet
33-
appliedObjs object.ObjMetadataSet
34-
failedApplies object.ObjMetadataSet
35-
failedDeletes object.ObjMetadataSet
36-
skippedApplies object.ObjMetadataSet
37-
skippedDeletes object.ObjMetadataSet
38-
abandonedObjs object.ObjMetadataSet
39-
invalidObjs object.ObjMetadataSet
40-
expectedObjs object.ObjMetadataSet
32+
prevInventory object.ObjMetadataSet
33+
appliedObjs object.ObjMetadataSet
34+
deletedObjs object.ObjMetadataSet
35+
failedApplies object.ObjMetadataSet
36+
failedDeletes object.ObjMetadataSet
37+
skippedApplies object.ObjMetadataSet
38+
skippedDeletes object.ObjMetadataSet
39+
failedReconciles object.ObjMetadataSet
40+
timeoutReconciles object.ObjMetadataSet
41+
abandonedObjs object.ObjMetadataSet
42+
invalidObjs object.ObjMetadataSet
43+
expectedObjs object.ObjMetadataSet
4144
}{
4245
"no apply objs, no prune failures; no inventory": {
4346
expectedObjs: object.ObjMetadataSet{},
@@ -160,6 +163,30 @@ func TestInvSetTask(t *testing.T) {
160163
invalidObjs: object.ObjMetadataSet{idInvalid},
161164
expectedObjs: object.ObjMetadataSet{id3},
162165
},
166+
"applied object failed to reconcile": {
167+
prevInventory: object.ObjMetadataSet{},
168+
appliedObjs: object.ObjMetadataSet{id3},
169+
failedReconciles: object.ObjMetadataSet{id3},
170+
expectedObjs: object.ObjMetadataSet{id3},
171+
},
172+
"deleted object failed to reconcile": {
173+
prevInventory: object.ObjMetadataSet{id3},
174+
deletedObjs: object.ObjMetadataSet{id3},
175+
failedReconciles: object.ObjMetadataSet{id3},
176+
expectedObjs: object.ObjMetadataSet{id3},
177+
},
178+
"applied object timed out to reconcile": {
179+
prevInventory: object.ObjMetadataSet{},
180+
appliedObjs: object.ObjMetadataSet{id3},
181+
timeoutReconciles: object.ObjMetadataSet{id3},
182+
expectedObjs: object.ObjMetadataSet{id3},
183+
},
184+
"deleted object timed out to reconcile": {
185+
prevInventory: object.ObjMetadataSet{id3},
186+
deletedObjs: object.ObjMetadataSet{id3},
187+
timeoutReconciles: object.ObjMetadataSet{id3},
188+
expectedObjs: object.ObjMetadataSet{id3},
189+
},
163190
}
164191

165192
for name, tc := range tests {
@@ -180,6 +207,9 @@ func TestInvSetTask(t *testing.T) {
180207
for _, applyObj := range tc.appliedObjs {
181208
im.AddSuccessfulApply(applyObj, "unusued-uid", int64(0))
182209
}
210+
for _, deleteObj := range tc.deletedObjs {
211+
im.AddSuccessfulDelete(deleteObj, "unused-uid")
212+
}
183213
for _, applyFailure := range tc.failedApplies {
184214
im.AddFailedApply(applyFailure)
185215
}
@@ -198,6 +228,16 @@ func TestInvSetTask(t *testing.T) {
198228
for _, invalidObj := range tc.invalidObjs {
199229
context.AddInvalidObject(invalidObj)
200230
}
231+
for _, failedReconcile := range tc.failedReconciles {
232+
if err := im.SetFailedReconcile(failedReconcile); err != nil {
233+
t.Fatal(err)
234+
}
235+
}
236+
for _, timeoutReconcile := range tc.timeoutReconciles {
237+
if err := im.SetTimeoutReconcile(timeoutReconcile); err != nil {
238+
t.Fatal(err)
239+
}
240+
}
201241
if taskName != task.Name() {
202242
t.Errorf("expected task name (%s), got (%s)", taskName, task.Name())
203243
}

test/e2e/destroy_reconciliation_failure_test.go

+30-10
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,40 @@ func destroyReconciliationFailureTest(ctx context.Context, c client.Client, invC
6161

6262
options := apply.DestroyerOptions{
6363
InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
64-
DeleteTimeout: 15 * time.Second, // one pod is expected to be not pruned, so set a shorter timeout
64+
DeleteTimeout: 10 * time.Second, // one pod is expected to be not pruned, so set a short timeout
6565
}
66-
_ = e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
67-
68-
By("Verify pod1 is deleted")
69-
e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podObject)
66+
// we should be able to run destroy multiple times and continue tracking the
67+
// object in the inventory
68+
expectedCounts := []struct {
69+
specCount int
70+
statusCount int
71+
}{
72+
{
73+
specCount: 1, // one object failed to reconcile, so is retained
74+
statusCount: 2, // status for two objects, one deleted successfully
75+
},
76+
{
77+
specCount: 1,
78+
statusCount: 1, // only one object in inventory now, still failing to reconcile
79+
},
80+
{
81+
specCount: 1,
82+
statusCount: 1,
83+
},
84+
}
85+
for _, ec := range expectedCounts {
86+
_ = e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
7087

71-
By("Verify podWithFinalizerObject is not deleted but has deletion timestamp")
72-
podWithFinalizerObject = e2eutil.AssertHasDeletionTimestamp(ctx, c, podWithFinalizerObject)
88+
By("Verify pod1 is deleted")
89+
e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podObject)
7390

74-
By("Verify inventory")
75-
// The inventory should still have the Pod with the finalizer.
76-
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 0, 2)
91+
By("Verify podWithFinalizerObject is not deleted but has deletion timestamp")
92+
podWithFinalizerObject = e2eutil.AssertHasDeletionTimestamp(ctx, c, podWithFinalizerObject)
7793

94+
By("Verify inventory")
95+
// The inventory should still have the Pod with the finalizer.
96+
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, ec.specCount, ec.statusCount)
97+
}
7898
// remove the finalizer
7999
podWithFinalizerObject = e2eutil.WithoutFinalizers(podWithFinalizerObject)
80100
e2eutil.ApplyUnstructured(ctx, c, podWithFinalizerObject)

0 commit comments

Comments
 (0)