1
2
3
4 package apply
5
6 import (
7 "context"
8 "sync"
9 "testing"
10 "time"
11
12 "github.com/stretchr/testify/assert"
13 "sigs.k8s.io/cli-utils/pkg/apply/event"
14 "sigs.k8s.io/cli-utils/pkg/inventory"
15 pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
16 "sigs.k8s.io/cli-utils/pkg/kstatus/status"
17 "sigs.k8s.io/cli-utils/pkg/object"
18 "sigs.k8s.io/cli-utils/pkg/testutil"
19 )
20
21 func TestDestroyerCancel(t *testing.T) {
22 testCases := map[string]struct {
23
24 invInfo inventoryInfo
25
26 clusterObjs object.UnstructuredSet
27
28 options DestroyerOptions
29
30 runTimeout time.Duration
31
32 testTimeout time.Duration
33
34 statusEvents []pollevent.Event
35
36 expectedStatusEvents []testutil.ExpEvent
37
38 expectedEvents []testutil.ExpEvent
39
40 expectRunTimeout bool
41 }{
42 "cancelled by caller while waiting for deletion": {
43 expectRunTimeout: true,
44 runTimeout: 2 * time.Second,
45 testTimeout: 30 * time.Second,
46 invInfo: inventoryInfo{
47 name: "abc-123",
48 namespace: "test",
49 id: "test",
50 set: object.ObjMetadataSet{
51 testutil.ToIdentifier(t, resources["deployment"]),
52 },
53 },
54 clusterObjs: object.UnstructuredSet{
55 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
56 },
57 options: DestroyerOptions{
58 EmitStatusEvents: true,
59
60
61 DeleteTimeout: 1 * time.Minute,
62 },
63 statusEvents: []pollevent.Event{
64 {
65 Type: pollevent.ResourceUpdateEvent,
66 Resource: &pollevent.ResourceStatus{
67 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
68 Status: status.InProgressStatus,
69 Resource: testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
70 },
71 },
72 {
73 Type: pollevent.ResourceUpdateEvent,
74 Resource: &pollevent.ResourceStatus{
75 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
76 Status: status.InProgressStatus,
77 Resource: testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
78 },
79 },
80
81 },
82 expectedStatusEvents: []testutil.ExpEvent{
83 {
84 EventType: event.StatusType,
85 StatusEvent: &testutil.ExpStatusEvent{
86 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
87 Status: status.InProgressStatus,
88 },
89 },
90 },
91 expectedEvents: []testutil.ExpEvent{
92 {
93
94 EventType: event.InitType,
95 InitEvent: &testutil.ExpInitEvent{},
96 },
97 {
98
99 EventType: event.ActionGroupType,
100 ActionGroupEvent: &testutil.ExpActionGroupEvent{
101 Action: event.DeleteAction,
102 GroupName: "prune-0",
103 Type: event.Started,
104 },
105 },
106 {
107
108 EventType: event.DeleteType,
109 DeleteEvent: &testutil.ExpDeleteEvent{
110 GroupName: "prune-0",
111 Status: event.DeleteSuccessful,
112 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
113 Error: nil,
114 },
115 },
116 {
117
118 EventType: event.ActionGroupType,
119 ActionGroupEvent: &testutil.ExpActionGroupEvent{
120 Action: event.DeleteAction,
121 GroupName: "prune-0",
122 Type: event.Finished,
123 },
124 },
125 {
126
127 EventType: event.ActionGroupType,
128 ActionGroupEvent: &testutil.ExpActionGroupEvent{
129 Action: event.WaitAction,
130 GroupName: "wait-0",
131 Type: event.Started,
132 },
133 },
134 {
135
136 EventType: event.WaitType,
137 WaitEvent: &testutil.ExpWaitEvent{
138 GroupName: "wait-0",
139 Status: event.ReconcilePending,
140 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
141 },
142 },
143
144
145 {
146
147 EventType: event.ActionGroupType,
148 ActionGroupEvent: &testutil.ExpActionGroupEvent{
149 Action: event.WaitAction,
150 GroupName: "wait-0",
151 Type: event.Finished,
152 },
153 },
154
155
156 {
157
158 EventType: event.ErrorType,
159 ErrorEvent: &testutil.ExpErrorEvent{
160 Err: context.DeadlineExceeded,
161 },
162 },
163 },
164 },
165 "completed with timeout": {
166 expectRunTimeout: false,
167 runTimeout: 10 * time.Second,
168 testTimeout: 30 * time.Second,
169 invInfo: inventoryInfo{
170 name: "abc-123",
171 namespace: "test",
172 id: "test",
173 set: object.ObjMetadataSet{
174 testutil.ToIdentifier(t, resources["deployment"]),
175 },
176 },
177 clusterObjs: object.UnstructuredSet{
178 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
179 },
180 options: DestroyerOptions{
181 EmitStatusEvents: true,
182
183 DeleteTimeout: 1 * time.Minute,
184 },
185 statusEvents: []pollevent.Event{
186 {
187 Type: pollevent.ResourceUpdateEvent,
188 Resource: &pollevent.ResourceStatus{
189 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
190 Status: status.InProgressStatus,
191 Resource: testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
192 },
193 },
194 {
195 Type: pollevent.ResourceUpdateEvent,
196 Resource: &pollevent.ResourceStatus{
197 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
198 Status: status.NotFoundStatus,
199 },
200 },
201
202 },
203 expectedStatusEvents: []testutil.ExpEvent{
204 {
205 EventType: event.StatusType,
206 StatusEvent: &testutil.ExpStatusEvent{
207 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
208 Status: status.InProgressStatus,
209 },
210 },
211 {
212 EventType: event.StatusType,
213 StatusEvent: &testutil.ExpStatusEvent{
214 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
215 Status: status.NotFoundStatus,
216 },
217 },
218 },
219 expectedEvents: []testutil.ExpEvent{
220 {
221
222 EventType: event.InitType,
223 InitEvent: &testutil.ExpInitEvent{},
224 },
225 {
226
227 EventType: event.ActionGroupType,
228 ActionGroupEvent: &testutil.ExpActionGroupEvent{
229 Action: event.DeleteAction,
230 GroupName: "prune-0",
231 Type: event.Started,
232 },
233 },
234 {
235
236 EventType: event.DeleteType,
237 DeleteEvent: &testutil.ExpDeleteEvent{
238 GroupName: "prune-0",
239 Status: event.DeleteSuccessful,
240 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
241 Error: nil,
242 },
243 },
244 {
245
246 EventType: event.ActionGroupType,
247 ActionGroupEvent: &testutil.ExpActionGroupEvent{
248 Action: event.DeleteAction,
249 GroupName: "prune-0",
250 Type: event.Finished,
251 },
252 },
253 {
254
255 EventType: event.ActionGroupType,
256 ActionGroupEvent: &testutil.ExpActionGroupEvent{
257 Action: event.WaitAction,
258 GroupName: "wait-0",
259 Type: event.Started,
260 },
261 },
262
263 {
264
265 EventType: event.WaitType,
266 WaitEvent: &testutil.ExpWaitEvent{
267 GroupName: "wait-0",
268 Status: event.ReconcilePending,
269 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
270 },
271 },
272 {
273
274 EventType: event.WaitType,
275 WaitEvent: &testutil.ExpWaitEvent{
276 GroupName: "wait-0",
277 Status: event.ReconcileSuccessful,
278 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
279 },
280 },
281 {
282
283 EventType: event.ActionGroupType,
284 ActionGroupEvent: &testutil.ExpActionGroupEvent{
285 Action: event.WaitAction,
286 GroupName: "wait-0",
287 Type: event.Finished,
288 },
289 },
290 {
291
292 EventType: event.ActionGroupType,
293 ActionGroupEvent: &testutil.ExpActionGroupEvent{
294 Action: event.InventoryAction,
295 GroupName: "delete-inventory-0",
296 Type: event.Started,
297 },
298 },
299 {
300
301 EventType: event.ActionGroupType,
302 ActionGroupEvent: &testutil.ExpActionGroupEvent{
303 Action: event.InventoryAction,
304 GroupName: "delete-inventory-0",
305 Type: event.Finished,
306 },
307 },
308 },
309 },
310 }
311
312 for tn, tc := range testCases {
313 t.Run(tn, func(t *testing.T) {
314 statusWatcher := newFakeWatcher(tc.statusEvents)
315
316 invInfo := tc.invInfo.toWrapped()
317
318 destroyer := newTestDestroyer(t,
319 tc.invInfo,
320
321 append(tc.clusterObjs, inventory.InvInfoToConfigMap(invInfo)),
322 statusWatcher,
323 )
324
325
326 runCtx, runCancel := context.WithTimeout(context.Background(), tc.runTimeout)
327 defer runCancel()
328
329
330 testCtx, testCancel := context.WithTimeout(context.Background(), tc.testTimeout)
331 defer testCancel()
332
333 eventChannel := destroyer.Run(runCtx, invInfo, tc.options)
334
335
336 var once sync.Once
337 var events []event.Event
338
339 loop:
340 for {
341 select {
342 case <-testCtx.Done():
343
344 runCancel()
345 t.Errorf("Destroyer.Run failed to respond to cancellation (expected: %s, timeout: %s)", tc.runTimeout, tc.testTimeout)
346 break loop
347
348 case e, ok := <-eventChannel:
349 if !ok {
350
351 testCancel()
352 break loop
353 }
354 events = append(events, e)
355
356 if e.Type == event.ActionGroupType &&
357 e.ActionGroupEvent.Action == event.WaitAction {
358 once.Do(func() {
359
360 statusWatcher.Start()
361 })
362 }
363 }
364 }
365
366
367 receivedEvents := testutil.EventsToExpEvents(events)
368
369
370 for _, e := range tc.expectedStatusEvents {
371 var removed int
372 receivedEvents, removed = testutil.RemoveEqualEvents(receivedEvents, e)
373 if removed < 1 {
374 t.Errorf("Expected status event not received: %#v", e)
375 }
376 }
377
378
379 testutil.SortExpEvents(receivedEvents)
380
381
382 testutil.AssertEqual(t, tc.expectedEvents, receivedEvents,
383 "Actual events (%d) do not match expected events (%d)",
384 len(receivedEvents), len(tc.expectedEvents))
385
386
387
388 if tc.expectRunTimeout {
389 assert.Equal(t, context.DeadlineExceeded, runCtx.Err(), "Destroyer.Run exited, but not by expected timeout")
390 } else {
391 assert.Nil(t, runCtx.Err(), "Destroyer.Run exited, but not by expected timeout")
392 }
393 })
394 }
395 }
396
View as plain text