1
2
3
4 package filter
5
6 import (
7 "fmt"
8 "testing"
9
10 "k8s.io/apimachinery/pkg/runtime/schema"
11 "sigs.k8s.io/cli-utils/pkg/apis/actuation"
12 "sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
13 "sigs.k8s.io/cli-utils/pkg/common"
14 "sigs.k8s.io/cli-utils/pkg/inventory"
15 "sigs.k8s.io/cli-utils/pkg/object"
16 "sigs.k8s.io/cli-utils/pkg/testutil"
17 )
18
19 var idInvalid = object.ObjMetadata{
20 GroupKind: schema.GroupKind{
21 Kind: "",
22 },
23 Name: "invalid",
24 }
25
26 var idA = object.ObjMetadata{
27 GroupKind: schema.GroupKind{
28 Group: "group-a",
29 Kind: "kind-a",
30 },
31 Name: "name-a",
32 Namespace: "namespace-a",
33 }
34
35 var idB = object.ObjMetadata{
36 GroupKind: schema.GroupKind{
37 Group: "group-b",
38 Kind: "kind-b",
39 },
40 Name: "name-b",
41 Namespace: "namespace-b",
42 }
43
44 func TestDependencyFilter(t *testing.T) {
45 tests := map[string]struct {
46 dryRunStrategy common.DryRunStrategy
47 actuationStrategy actuation.ActuationStrategy
48 contextSetup func(*taskrunner.TaskContext)
49 id object.ObjMetadata
50 expectedError error
51 }{
52 "apply A (no deps)": {
53 actuationStrategy: actuation.ActuationStrategyApply,
54 contextSetup: func(taskContext *taskrunner.TaskContext) {
55 taskContext.Graph().AddVertex(idA)
56 taskContext.InventoryManager().AddPendingApply(idA)
57 },
58 id: idA,
59 expectedError: nil,
60 },
61 "apply A (A -> B) when B is invalid": {
62 actuationStrategy: actuation.ActuationStrategyApply,
63 contextSetup: func(taskContext *taskrunner.TaskContext) {
64 taskContext.Graph().AddVertex(idA)
65 taskContext.Graph().AddVertex(idInvalid)
66 taskContext.Graph().AddEdge(idA, idInvalid)
67 taskContext.InventoryManager().AddPendingApply(idA)
68 taskContext.AddInvalidObject(idInvalid)
69 },
70 id: idA,
71 expectedError: testutil.EqualError(
72 NewFatalError(fmt.Errorf("invalid dependency: %s", idInvalid)),
73 ),
74 },
75 "apply A (A -> B) before B is applied": {
76 actuationStrategy: actuation.ActuationStrategyApply,
77 contextSetup: func(taskContext *taskrunner.TaskContext) {
78 taskContext.Graph().AddVertex(idA)
79 taskContext.Graph().AddVertex(idB)
80 taskContext.Graph().AddEdge(idA, idB)
81 taskContext.InventoryManager().AddPendingApply(idA)
82 taskContext.InventoryManager().AddPendingApply(idB)
83 },
84 id: idA,
85 expectedError: testutil.EqualError(
86 NewFatalError(fmt.Errorf("premature apply: dependency apply actuation pending: %s", idB)),
87 ),
88 },
89 "apply A (A -> B) before B is reconciled": {
90 actuationStrategy: actuation.ActuationStrategyApply,
91 contextSetup: func(taskContext *taskrunner.TaskContext) {
92 taskContext.Graph().AddVertex(idA)
93 taskContext.Graph().AddVertex(idB)
94 taskContext.Graph().AddEdge(idA, idB)
95 taskContext.InventoryManager().AddPendingApply(idA)
96 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
97 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
98 Strategy: actuation.ActuationStrategyApply,
99 Actuation: actuation.ActuationSucceeded,
100 Reconcile: actuation.ReconcilePending,
101 })
102 },
103 id: idA,
104 expectedError: testutil.EqualError(
105 NewFatalError(fmt.Errorf("premature apply: dependency apply reconcile pending: %s", idB)),
106 ),
107 },
108 "apply A (A -> B) after B is reconciled": {
109 actuationStrategy: actuation.ActuationStrategyApply,
110 contextSetup: func(taskContext *taskrunner.TaskContext) {
111 taskContext.Graph().AddVertex(idA)
112 taskContext.Graph().AddVertex(idB)
113 taskContext.Graph().AddEdge(idA, idB)
114 taskContext.InventoryManager().AddPendingApply(idA)
115 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
116 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
117 Strategy: actuation.ActuationStrategyApply,
118 Actuation: actuation.ActuationSucceeded,
119 Reconcile: actuation.ReconcileSucceeded,
120 })
121 },
122 id: idA,
123 expectedError: nil,
124 },
125 "apply A (A -> B) after B apply failed": {
126 actuationStrategy: actuation.ActuationStrategyApply,
127 contextSetup: func(taskContext *taskrunner.TaskContext) {
128 taskContext.Graph().AddVertex(idA)
129 taskContext.Graph().AddVertex(idB)
130 taskContext.Graph().AddEdge(idA, idB)
131 taskContext.InventoryManager().AddPendingApply(idA)
132 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
133 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
134 Strategy: actuation.ActuationStrategyApply,
135 Actuation: actuation.ActuationFailed,
136 Reconcile: actuation.ReconcilePending,
137 })
138 },
139 id: idA,
140 expectedError: &DependencyPreventedActuationError{
141 Object: idA,
142 Strategy: actuation.ActuationStrategyApply,
143 Relationship: RelationshipDependency,
144 Relation: idB,
145 RelationPhase: PhaseActuation,
146 RelationActuationStatus: actuation.ActuationFailed,
147 RelationReconcileStatus: actuation.ReconcilePending,
148 },
149 },
150 "apply A (A -> B) after B apply skipped": {
151 actuationStrategy: actuation.ActuationStrategyApply,
152 contextSetup: func(taskContext *taskrunner.TaskContext) {
153 taskContext.Graph().AddVertex(idA)
154 taskContext.Graph().AddVertex(idB)
155 taskContext.Graph().AddEdge(idA, idB)
156 taskContext.InventoryManager().AddPendingApply(idA)
157 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
158 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
159 Strategy: actuation.ActuationStrategyApply,
160 Actuation: actuation.ActuationSkipped,
161 Reconcile: actuation.ReconcileSkipped,
162 })
163 },
164 id: idA,
165 expectedError: &DependencyPreventedActuationError{
166 Object: idA,
167 Strategy: actuation.ActuationStrategyApply,
168 Relationship: RelationshipDependency,
169 Relation: idB,
170 RelationPhase: PhaseActuation,
171 RelationActuationStatus: actuation.ActuationSkipped,
172 RelationReconcileStatus: actuation.ReconcileSkipped,
173 },
174 },
175 "apply A (A -> B) after B reconcile failed": {
176 actuationStrategy: actuation.ActuationStrategyApply,
177 contextSetup: func(taskContext *taskrunner.TaskContext) {
178 taskContext.Graph().AddVertex(idA)
179 taskContext.Graph().AddVertex(idB)
180 taskContext.Graph().AddEdge(idA, idB)
181 taskContext.InventoryManager().AddPendingApply(idA)
182 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
183 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
184 Strategy: actuation.ActuationStrategyApply,
185 Actuation: actuation.ActuationSucceeded,
186 Reconcile: actuation.ReconcileFailed,
187 })
188 },
189 id: idA,
190 expectedError: &DependencyPreventedActuationError{
191 Object: idA,
192 Strategy: actuation.ActuationStrategyApply,
193 Relationship: RelationshipDependency,
194 Relation: idB,
195 RelationPhase: PhaseReconcile,
196 RelationActuationStatus: actuation.ActuationSucceeded,
197 RelationReconcileStatus: actuation.ReconcileFailed,
198 },
199 },
200 "apply A (A -> B) after B reconcile timeout": {
201 actuationStrategy: actuation.ActuationStrategyApply,
202 contextSetup: func(taskContext *taskrunner.TaskContext) {
203 taskContext.Graph().AddVertex(idA)
204 taskContext.Graph().AddVertex(idB)
205 taskContext.Graph().AddEdge(idA, idB)
206 taskContext.InventoryManager().AddPendingApply(idA)
207 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
208 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
209 Strategy: actuation.ActuationStrategyApply,
210 Actuation: actuation.ActuationSucceeded,
211 Reconcile: actuation.ReconcileTimeout,
212 })
213 },
214 id: idA,
215 expectedError: &DependencyPreventedActuationError{
216 Object: idA,
217 Strategy: actuation.ActuationStrategyApply,
218 Relationship: RelationshipDependency,
219 Relation: idB,
220 RelationPhase: PhaseReconcile,
221 RelationActuationStatus: actuation.ActuationSucceeded,
222 RelationReconcileStatus: actuation.ReconcileTimeout,
223 },
224 },
225
226 "apply A (A -> B) after B reconcile skipped": {
227 actuationStrategy: actuation.ActuationStrategyApply,
228 contextSetup: func(taskContext *taskrunner.TaskContext) {
229 taskContext.Graph().AddVertex(idA)
230 taskContext.Graph().AddVertex(idB)
231 taskContext.Graph().AddEdge(idA, idB)
232 taskContext.InventoryManager().AddPendingApply(idA)
233 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
234 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
235 Strategy: actuation.ActuationStrategyApply,
236 Actuation: actuation.ActuationSucceeded,
237 Reconcile: actuation.ReconcileSkipped,
238 })
239 },
240 id: idA,
241 expectedError: &DependencyPreventedActuationError{
242 Object: idA,
243 Strategy: actuation.ActuationStrategyApply,
244 Relationship: RelationshipDependency,
245 Relation: idB,
246 RelationPhase: PhaseReconcile,
247 RelationActuationStatus: actuation.ActuationSucceeded,
248 RelationReconcileStatus: actuation.ReconcileSkipped,
249 },
250 },
251 "apply A (A -> B) when B delete pending": {
252 actuationStrategy: actuation.ActuationStrategyApply,
253 contextSetup: func(taskContext *taskrunner.TaskContext) {
254 taskContext.Graph().AddVertex(idA)
255 taskContext.Graph().AddVertex(idB)
256 taskContext.Graph().AddEdge(idA, idB)
257 taskContext.InventoryManager().AddPendingApply(idA)
258 taskContext.InventoryManager().AddPendingDelete(idB)
259 },
260 id: idA,
261 expectedError: &DependencyActuationMismatchError{
262 Object: idA,
263 Strategy: actuation.ActuationStrategyApply,
264 Relationship: RelationshipDependency,
265 Relation: idB,
266 RelationStrategy: actuation.ActuationStrategyDelete,
267 },
268 },
269 "delete B (no deps)": {
270 actuationStrategy: actuation.ActuationStrategyDelete,
271 contextSetup: func(taskContext *taskrunner.TaskContext) {
272 taskContext.Graph().AddVertex(idB)
273 taskContext.InventoryManager().AddPendingDelete(idB)
274 },
275 id: idB,
276 expectedError: nil,
277 },
278 "delete B (A -> B) when A is invalid": {
279 actuationStrategy: actuation.ActuationStrategyDelete,
280 contextSetup: func(taskContext *taskrunner.TaskContext) {
281 taskContext.Graph().AddVertex(idInvalid)
282 taskContext.Graph().AddVertex(idB)
283 taskContext.Graph().AddEdge(idInvalid, idB)
284 taskContext.InventoryManager().AddPendingDelete(idB)
285 taskContext.AddInvalidObject(idInvalid)
286 },
287 id: idB,
288 expectedError: testutil.EqualError(
289 NewFatalError(fmt.Errorf("invalid dependent: %s", idInvalid)),
290 ),
291 },
292 "delete B (A -> B) before A is deleted": {
293 actuationStrategy: actuation.ActuationStrategyDelete,
294 contextSetup: func(taskContext *taskrunner.TaskContext) {
295 taskContext.Graph().AddVertex(idA)
296 taskContext.Graph().AddVertex(idB)
297 taskContext.Graph().AddEdge(idA, idB)
298 taskContext.InventoryManager().AddPendingDelete(idB)
299 taskContext.InventoryManager().AddPendingDelete(idA)
300 },
301 id: idB,
302 expectedError: testutil.EqualError(
303 NewFatalError(fmt.Errorf("premature delete: dependent delete actuation pending: %s", idA)),
304 ),
305 },
306 "delete B (A -> B) before A is reconciled": {
307 actuationStrategy: actuation.ActuationStrategyDelete,
308 contextSetup: func(taskContext *taskrunner.TaskContext) {
309 taskContext.Graph().AddVertex(idA)
310 taskContext.Graph().AddVertex(idB)
311 taskContext.Graph().AddEdge(idA, idB)
312 taskContext.InventoryManager().AddPendingDelete(idB)
313 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
314 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
315 Strategy: actuation.ActuationStrategyDelete,
316 Actuation: actuation.ActuationSucceeded,
317 Reconcile: actuation.ReconcilePending,
318 })
319 },
320 id: idB,
321 expectedError: testutil.EqualError(
322 NewFatalError(fmt.Errorf("premature delete: dependent delete reconcile pending: %s", idA)),
323 ),
324 },
325 "delete B (A -> B) after A is reconciled": {
326 actuationStrategy: actuation.ActuationStrategyDelete,
327 contextSetup: func(taskContext *taskrunner.TaskContext) {
328 taskContext.Graph().AddVertex(idA)
329 taskContext.Graph().AddVertex(idB)
330 taskContext.Graph().AddEdge(idA, idB)
331 taskContext.InventoryManager().AddPendingDelete(idB)
332 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
333 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
334 Strategy: actuation.ActuationStrategyDelete,
335 Actuation: actuation.ActuationSucceeded,
336 Reconcile: actuation.ReconcileSucceeded,
337 })
338 },
339 id: idB,
340 expectedError: nil,
341 },
342 "delete B (A -> B) after A delete failed": {
343 actuationStrategy: actuation.ActuationStrategyDelete,
344 contextSetup: func(taskContext *taskrunner.TaskContext) {
345 taskContext.Graph().AddVertex(idA)
346 taskContext.Graph().AddVertex(idB)
347 taskContext.Graph().AddEdge(idA, idB)
348 taskContext.InventoryManager().AddPendingDelete(idB)
349 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
350 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
351 Strategy: actuation.ActuationStrategyDelete,
352 Actuation: actuation.ActuationFailed,
353 Reconcile: actuation.ReconcilePending,
354 })
355 },
356 id: idB,
357 expectedError: &DependencyPreventedActuationError{
358 Object: idB,
359 Strategy: actuation.ActuationStrategyDelete,
360 Relationship: RelationshipDependent,
361 Relation: idA,
362 RelationPhase: PhaseActuation,
363 RelationActuationStatus: actuation.ActuationFailed,
364 RelationReconcileStatus: actuation.ReconcilePending,
365 },
366 },
367 "delete B (A -> B) after A delete skipped": {
368 actuationStrategy: actuation.ActuationStrategyDelete,
369 contextSetup: func(taskContext *taskrunner.TaskContext) {
370 taskContext.Graph().AddVertex(idA)
371 taskContext.Graph().AddVertex(idB)
372 taskContext.Graph().AddEdge(idA, idB)
373 taskContext.InventoryManager().AddPendingDelete(idB)
374 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
375 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
376 Strategy: actuation.ActuationStrategyDelete,
377 Actuation: actuation.ActuationSkipped,
378 Reconcile: actuation.ReconcileSkipped,
379 })
380 },
381 id: idB,
382 expectedError: &DependencyPreventedActuationError{
383 Object: idB,
384 Strategy: actuation.ActuationStrategyDelete,
385 Relationship: RelationshipDependent,
386 Relation: idA,
387 RelationPhase: PhaseActuation,
388 RelationActuationStatus: actuation.ActuationSkipped,
389 RelationReconcileStatus: actuation.ReconcileSkipped,
390 },
391 },
392
393 "delete B (A -> B) after A reconcile failed": {
394 actuationStrategy: actuation.ActuationStrategyDelete,
395 contextSetup: func(taskContext *taskrunner.TaskContext) {
396 taskContext.Graph().AddVertex(idA)
397 taskContext.Graph().AddVertex(idB)
398 taskContext.Graph().AddEdge(idA, idB)
399 taskContext.InventoryManager().AddPendingDelete(idB)
400 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
401 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
402 Strategy: actuation.ActuationStrategyDelete,
403 Actuation: actuation.ActuationSucceeded,
404 Reconcile: actuation.ReconcileFailed,
405 })
406 },
407 id: idB,
408 expectedError: &DependencyPreventedActuationError{
409 Object: idB,
410 Strategy: actuation.ActuationStrategyDelete,
411 Relationship: RelationshipDependent,
412 Relation: idA,
413 RelationPhase: PhaseReconcile,
414 RelationActuationStatus: actuation.ActuationSucceeded,
415 RelationReconcileStatus: actuation.ReconcileFailed,
416 },
417 },
418 "delete B (A -> B) after A reconcile timeout": {
419 actuationStrategy: actuation.ActuationStrategyDelete,
420 contextSetup: func(taskContext *taskrunner.TaskContext) {
421 taskContext.Graph().AddVertex(idA)
422 taskContext.Graph().AddVertex(idB)
423 taskContext.Graph().AddEdge(idA, idB)
424 taskContext.InventoryManager().AddPendingDelete(idB)
425 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
426 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
427 Strategy: actuation.ActuationStrategyDelete,
428 Actuation: actuation.ActuationSucceeded,
429 Reconcile: actuation.ReconcileTimeout,
430 })
431 },
432 id: idB,
433 expectedError: &DependencyPreventedActuationError{
434 Object: idB,
435 Strategy: actuation.ActuationStrategyDelete,
436 Relationship: RelationshipDependent,
437 Relation: idA,
438 RelationPhase: PhaseReconcile,
439 RelationActuationStatus: actuation.ActuationSucceeded,
440 RelationReconcileStatus: actuation.ReconcileTimeout,
441 },
442 },
443
444 "delete B (A -> B) after A reconcile skipped": {
445 actuationStrategy: actuation.ActuationStrategyDelete,
446 contextSetup: func(taskContext *taskrunner.TaskContext) {
447 taskContext.Graph().AddVertex(idA)
448 taskContext.Graph().AddVertex(idB)
449 taskContext.Graph().AddEdge(idA, idB)
450 taskContext.InventoryManager().AddPendingDelete(idB)
451 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
452 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
453 Strategy: actuation.ActuationStrategyDelete,
454 Actuation: actuation.ActuationSucceeded,
455 Reconcile: actuation.ReconcileSkipped,
456 })
457 },
458 id: idB,
459 expectedError: &DependencyPreventedActuationError{
460 Object: idB,
461 Strategy: actuation.ActuationStrategyDelete,
462 Relationship: RelationshipDependent,
463 Relation: idA,
464 RelationPhase: PhaseReconcile,
465 RelationActuationStatus: actuation.ActuationSucceeded,
466 RelationReconcileStatus: actuation.ReconcileSkipped,
467 },
468 },
469 "delete B (A -> B) when A apply succeeded": {
470 actuationStrategy: actuation.ActuationStrategyDelete,
471 contextSetup: func(taskContext *taskrunner.TaskContext) {
472 taskContext.Graph().AddVertex(idA)
473 taskContext.Graph().AddVertex(idB)
474 taskContext.Graph().AddEdge(idA, idB)
475 taskContext.InventoryManager().AddPendingDelete(idB)
476 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
477 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
478 Strategy: actuation.ActuationStrategyApply,
479 Actuation: actuation.ActuationSucceeded,
480 Reconcile: actuation.ReconcileSucceeded,
481 })
482 },
483 id: idB,
484 expectedError: &DependencyActuationMismatchError{
485 Object: idB,
486 Strategy: actuation.ActuationStrategyDelete,
487 Relationship: RelationshipDependent,
488 Relation: idA,
489 RelationStrategy: actuation.ActuationStrategyApply,
490 },
491 },
492 "DryRun: apply A (A -> B) when B apply reconcile pending": {
493 dryRunStrategy: common.DryRunClient,
494 actuationStrategy: actuation.ActuationStrategyApply,
495 contextSetup: func(taskContext *taskrunner.TaskContext) {
496 taskContext.Graph().AddVertex(idA)
497 taskContext.Graph().AddVertex(idB)
498 taskContext.Graph().AddEdge(idA, idB)
499 taskContext.InventoryManager().AddPendingApply(idA)
500 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
501 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
502 Strategy: actuation.ActuationStrategyApply,
503 Actuation: actuation.ActuationSucceeded,
504 Reconcile: actuation.ReconcilePending,
505 })
506 },
507 id: idA,
508 expectedError: nil,
509 },
510 "DryRun: delete B (A -> B) when A delete reconcile pending": {
511 dryRunStrategy: common.DryRunClient,
512 actuationStrategy: actuation.ActuationStrategyDelete,
513 contextSetup: func(taskContext *taskrunner.TaskContext) {
514 taskContext.Graph().AddVertex(idA)
515 taskContext.Graph().AddVertex(idB)
516 taskContext.Graph().AddEdge(idA, idB)
517 taskContext.InventoryManager().AddPendingDelete(idB)
518 taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
519 ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
520 Strategy: actuation.ActuationStrategyDelete,
521 Actuation: actuation.ActuationSucceeded,
522 Reconcile: actuation.ReconcilePending,
523 })
524 },
525 id: idB,
526 expectedError: nil,
527 },
528 }
529
530 for name, tc := range tests {
531 t.Run(name, func(t *testing.T) {
532 taskContext := taskrunner.NewTaskContext(nil, nil)
533 tc.contextSetup(taskContext)
534
535 filter := DependencyFilter{
536 TaskContext: taskContext,
537 ActuationStrategy: tc.actuationStrategy,
538 DryRunStrategy: tc.dryRunStrategy,
539 }
540 obj := defaultObj.DeepCopy()
541 obj.SetGroupVersionKind(tc.id.GroupKind.WithVersion("v1"))
542 obj.SetName(tc.id.Name)
543 obj.SetNamespace(tc.id.Namespace)
544
545 err := filter.Filter(obj)
546 testutil.AssertEqual(t, tc.expectedError, err)
547 })
548 }
549 }
550
View as plain text