1
2
3
4 package solver
5
6 import (
7 "testing"
8 "time"
9
10 "github.com/google/go-cmp/cmp"
11 "github.com/google/go-cmp/cmp/cmpopts"
12 "github.com/stretchr/testify/assert"
13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14 "sigs.k8s.io/cli-utils/pkg/apis/actuation"
15 "sigs.k8s.io/cli-utils/pkg/apply/prune"
16 "sigs.k8s.io/cli-utils/pkg/apply/task"
17 "sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
18 "sigs.k8s.io/cli-utils/pkg/common"
19 "sigs.k8s.io/cli-utils/pkg/inventory"
20 "sigs.k8s.io/cli-utils/pkg/object"
21 "sigs.k8s.io/cli-utils/pkg/object/graph"
22 "sigs.k8s.io/cli-utils/pkg/object/validation"
23 "sigs.k8s.io/cli-utils/pkg/testutil"
24 )
25
26 var (
27 pruner = &prune.Pruner{}
28 resources = map[string]string{
29 "pod": `
30 kind: Pod
31 apiVersion: v1
32 metadata:
33 name: test-pod
34 namespace: test-namespace
35 `,
36 "default-pod": `
37 kind: Pod
38 apiVersion: v1
39 metadata:
40 name: pod-in-default-namespace
41 namespace: default
42 `,
43 "deployment": `
44 kind: Deployment
45 apiVersion: apps/v1
46 metadata:
47 name: foo
48 namespace: test-namespace
49 uid: dep-uid
50 generation: 1
51 spec:
52 replicas: 1
53 `,
54 "secret": `
55 kind: Secret
56 apiVersion: v1
57 metadata:
58 name: secret
59 namespace: test-namespace
60 uid: secret-uid
61 generation: 1
62 type: Opaque
63 spec:
64 foo: bar
65 `,
66 "namespace": `
67 kind: Namespace
68 apiVersion: v1
69 metadata:
70 name: test-namespace
71 `,
72
73 "crd": `
74 apiVersion: apiextensions.k8s.io/v1
75 kind: CustomResourceDefinition
76 metadata:
77 name: crontabs.stable.example.com
78 spec:
79 group: stable.example.com
80 versions:
81 - name: v1
82 served: true
83 storage: true
84 scope: Namespaced
85 names:
86 plural: crontabs
87 singular: crontab
88 kind: CronTab
89 `,
90 "crontab1": `
91 apiVersion: "stable.example.com/v1"
92 kind: CronTab
93 metadata:
94 name: cron-tab-01
95 namespace: test-namespace
96 `,
97 "crontab2": `
98 apiVersion: "stable.example.com/v1"
99 kind: CronTab
100 metadata:
101 name: cron-tab-02
102 namespace: test-namespace
103 `,
104 }
105 )
106
107 func newInvObject(name, namespace, inventoryID string) *unstructured.Unstructured {
108 return &unstructured.Unstructured{
109 Object: map[string]interface{}{
110 "apiVersion": "v1",
111 "kind": "ConfigMap",
112 "metadata": map[string]interface{}{
113 "name": name,
114 "namespace": namespace,
115 "labels": map[string]interface{}{
116 common.InventoryLabel: inventoryID,
117 },
118 },
119 "data": map[string]string{},
120 },
121 }
122 }
123
124 func TestTaskQueueBuilder_ApplyBuild(t *testing.T) {
125
126 asserter := testutil.NewAsserter(
127 cmpopts.EquateErrors(),
128 waitTaskComparer(),
129 fakeClientComparer(),
130 inventoryInfoComparer(),
131 )
132
133 invInfo := inventory.WrapInventoryInfoObj(newInvObject(
134 "abc-123", "default", "test"))
135
136 testCases := map[string]struct {
137 applyObjs []*unstructured.Unstructured
138 options Options
139 expectedTasks []taskrunner.Task
140 expectedError error
141 expectedStatus []actuation.ObjectStatus
142 }{
143 "no resources, no apply or wait tasks": {
144 applyObjs: []*unstructured.Unstructured{},
145 expectedTasks: []taskrunner.Task{
146 &task.InvAddTask{
147 TaskName: "inventory-add-0",
148 InvClient: &inventory.FakeClient{},
149 InvInfo: invInfo,
150 Objects: object.UnstructuredSet{},
151 },
152 &task.InvSetTask{
153 TaskName: "inventory-set-0",
154 InvClient: &inventory.FakeClient{},
155 InvInfo: invInfo,
156 PrevInventory: object.ObjMetadataSet{},
157 },
158 },
159 },
160 "single resource, one apply task, one wait task": {
161 applyObjs: []*unstructured.Unstructured{
162 testutil.Unstructured(t, resources["deployment"]),
163 },
164 expectedTasks: []taskrunner.Task{
165 &task.InvAddTask{
166 TaskName: "inventory-add-0",
167 InvClient: &inventory.FakeClient{},
168 InvInfo: invInfo,
169 Objects: object.UnstructuredSet{
170 testutil.Unstructured(t, resources["deployment"]),
171 },
172 },
173 &task.ApplyTask{
174 TaskName: "apply-0",
175 Objects: []*unstructured.Unstructured{
176 testutil.Unstructured(t, resources["deployment"]),
177 },
178 },
179 &taskrunner.WaitTask{
180 TaskName: "wait-0",
181 Ids: object.ObjMetadataSet{
182 testutil.ToIdentifier(t, resources["deployment"]),
183 },
184 Condition: taskrunner.AllCurrent,
185 },
186 &task.InvSetTask{
187 TaskName: "inventory-set-0",
188 InvClient: &inventory.FakeClient{},
189 InvInfo: invInfo,
190 PrevInventory: object.ObjMetadataSet{
191 testutil.ToIdentifier(t, resources["deployment"]),
192 },
193 },
194 },
195 expectedStatus: []actuation.ObjectStatus{
196 {
197 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
198 testutil.ToIdentifier(t, resources["deployment"]),
199 ),
200 Strategy: actuation.ActuationStrategyApply,
201 Actuation: actuation.ActuationPending,
202 Reconcile: actuation.ReconcilePending,
203 },
204 },
205 },
206 "multiple resource with no timeout": {
207 applyObjs: []*unstructured.Unstructured{
208 testutil.Unstructured(t, resources["deployment"]),
209 testutil.Unstructured(t, resources["secret"]),
210 },
211 expectedTasks: []taskrunner.Task{
212 &task.InvAddTask{
213 TaskName: "inventory-add-0",
214 InvClient: &inventory.FakeClient{},
215 InvInfo: invInfo,
216 Objects: object.UnstructuredSet{
217 testutil.Unstructured(t, resources["deployment"]),
218 testutil.Unstructured(t, resources["secret"]),
219 },
220 },
221 &task.ApplyTask{
222 TaskName: "apply-0",
223 Objects: []*unstructured.Unstructured{
224 testutil.Unstructured(t, resources["deployment"]),
225 testutil.Unstructured(t, resources["secret"]),
226 },
227 DryRunStrategy: common.DryRunNone,
228 },
229 &taskrunner.WaitTask{
230 TaskName: "wait-0",
231 Ids: object.ObjMetadataSet{
232 testutil.ToIdentifier(t, resources["deployment"]),
233 testutil.ToIdentifier(t, resources["secret"]),
234 },
235 Condition: taskrunner.AllCurrent,
236 },
237 &task.InvSetTask{
238 TaskName: "inventory-set-0",
239 InvClient: &inventory.FakeClient{},
240 InvInfo: invInfo,
241 PrevInventory: object.ObjMetadataSet{
242 testutil.ToIdentifier(t, resources["deployment"]),
243 testutil.ToIdentifier(t, resources["secret"]),
244 },
245 },
246 },
247 expectedStatus: []actuation.ObjectStatus{
248 {
249 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
250 testutil.ToIdentifier(t, resources["deployment"]),
251 ),
252 Strategy: actuation.ActuationStrategyApply,
253 Actuation: actuation.ActuationPending,
254 Reconcile: actuation.ReconcilePending,
255 },
256 {
257 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
258 testutil.ToIdentifier(t, resources["secret"]),
259 ),
260 Strategy: actuation.ActuationStrategyApply,
261 Actuation: actuation.ActuationPending,
262 Reconcile: actuation.ReconcilePending,
263 },
264 },
265 },
266 "multiple resources with reconcile timeout": {
267 applyObjs: []*unstructured.Unstructured{
268 testutil.Unstructured(t, resources["deployment"]),
269 testutil.Unstructured(t, resources["secret"]),
270 },
271 options: Options{
272 ReconcileTimeout: 1 * time.Minute,
273 },
274 expectedTasks: []taskrunner.Task{
275 &task.InvAddTask{
276 TaskName: "inventory-add-0",
277 InvClient: &inventory.FakeClient{},
278 InvInfo: invInfo,
279 Objects: object.UnstructuredSet{
280 testutil.Unstructured(t, resources["secret"]),
281 testutil.Unstructured(t, resources["deployment"]),
282 },
283 },
284 &task.ApplyTask{
285 TaskName: "apply-0",
286 Objects: []*unstructured.Unstructured{
287 testutil.Unstructured(t, resources["secret"]),
288 testutil.Unstructured(t, resources["deployment"]),
289 },
290 DryRunStrategy: common.DryRunNone,
291 },
292 &taskrunner.WaitTask{
293 TaskName: "wait-0",
294 Ids: object.ObjMetadataSet{
295 testutil.ToIdentifier(t, resources["secret"]),
296 testutil.ToIdentifier(t, resources["deployment"]),
297 },
298 Condition: taskrunner.AllCurrent,
299 Timeout: 1 * time.Minute,
300 },
301 &task.InvSetTask{
302 TaskName: "inventory-set-0",
303 InvClient: &inventory.FakeClient{},
304 InvInfo: invInfo,
305 PrevInventory: object.ObjMetadataSet{
306 testutil.ToIdentifier(t, resources["secret"]),
307 testutil.ToIdentifier(t, resources["deployment"]),
308 },
309 },
310 },
311 expectedStatus: []actuation.ObjectStatus{
312 {
313 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
314 testutil.ToIdentifier(t, resources["deployment"]),
315 ),
316 Strategy: actuation.ActuationStrategyApply,
317 Actuation: actuation.ActuationPending,
318 Reconcile: actuation.ReconcilePending,
319 },
320 {
321 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
322 testutil.ToIdentifier(t, resources["secret"]),
323 ),
324 Strategy: actuation.ActuationStrategyApply,
325 Actuation: actuation.ActuationPending,
326 Reconcile: actuation.ReconcilePending,
327 },
328 },
329 },
330 "multiple resources with reconcile timeout and dryrun": {
331 applyObjs: []*unstructured.Unstructured{
332 testutil.Unstructured(t, resources["deployment"]),
333 testutil.Unstructured(t, resources["secret"]),
334 },
335 options: Options{
336 ReconcileTimeout: time.Minute,
337 DryRunStrategy: common.DryRunClient,
338 },
339
340 expectedTasks: []taskrunner.Task{
341 &task.InvAddTask{
342 TaskName: "inventory-add-0",
343 InvClient: &inventory.FakeClient{},
344 InvInfo: invInfo,
345 Objects: object.UnstructuredSet{
346 testutil.Unstructured(t, resources["deployment"]),
347 testutil.Unstructured(t, resources["secret"]),
348 },
349 DryRun: common.DryRunClient,
350 },
351 &task.ApplyTask{
352 TaskName: "apply-0",
353 Objects: []*unstructured.Unstructured{
354 testutil.Unstructured(t, resources["deployment"]),
355 testutil.Unstructured(t, resources["secret"]),
356 },
357 DryRunStrategy: common.DryRunClient,
358 },
359 &task.InvSetTask{
360 TaskName: "inventory-set-0",
361 InvClient: &inventory.FakeClient{},
362 InvInfo: invInfo,
363 PrevInventory: object.ObjMetadataSet{
364 testutil.ToIdentifier(t, resources["deployment"]),
365 testutil.ToIdentifier(t, resources["secret"]),
366 },
367 DryRun: common.DryRunClient,
368 },
369 },
370 expectedStatus: []actuation.ObjectStatus{
371 {
372 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
373 testutil.ToIdentifier(t, resources["deployment"]),
374 ),
375 Strategy: actuation.ActuationStrategyApply,
376 Actuation: actuation.ActuationPending,
377 Reconcile: actuation.ReconcilePending,
378 },
379 {
380 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
381 testutil.ToIdentifier(t, resources["secret"]),
382 ),
383 Strategy: actuation.ActuationStrategyApply,
384 Actuation: actuation.ActuationPending,
385 Reconcile: actuation.ReconcilePending,
386 },
387 },
388 },
389 "multiple resources with reconcile timeout and server-dryrun": {
390 applyObjs: []*unstructured.Unstructured{
391 testutil.Unstructured(t, resources["pod"]),
392 testutil.Unstructured(t, resources["default-pod"]),
393 },
394 options: Options{
395 ReconcileTimeout: time.Minute,
396 DryRunStrategy: common.DryRunServer,
397 },
398
399 expectedTasks: []taskrunner.Task{
400 &task.InvAddTask{
401 TaskName: "inventory-add-0",
402 InvClient: &inventory.FakeClient{},
403 InvInfo: invInfo,
404 Objects: object.UnstructuredSet{
405 testutil.Unstructured(t, resources["pod"]),
406 testutil.Unstructured(t, resources["default-pod"]),
407 },
408 DryRun: common.DryRunServer,
409 },
410 &task.ApplyTask{
411 TaskName: "apply-0",
412 Objects: []*unstructured.Unstructured{
413 testutil.Unstructured(t, resources["pod"]),
414 testutil.Unstructured(t, resources["default-pod"]),
415 },
416 DryRunStrategy: common.DryRunServer,
417 },
418 &task.InvSetTask{
419 TaskName: "inventory-set-0",
420 InvClient: &inventory.FakeClient{},
421 InvInfo: invInfo,
422 PrevInventory: object.ObjMetadataSet{
423 testutil.ToIdentifier(t, resources["pod"]),
424 testutil.ToIdentifier(t, resources["default-pod"]),
425 },
426 DryRun: common.DryRunServer,
427 },
428 },
429 expectedStatus: []actuation.ObjectStatus{
430 {
431 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
432 testutil.ToIdentifier(t, resources["pod"]),
433 ),
434 Strategy: actuation.ActuationStrategyApply,
435 Actuation: actuation.ActuationPending,
436 Reconcile: actuation.ReconcilePending,
437 },
438 {
439 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
440 testutil.ToIdentifier(t, resources["default-pod"]),
441 ),
442 Strategy: actuation.ActuationStrategyApply,
443 Actuation: actuation.ActuationPending,
444 Reconcile: actuation.ReconcilePending,
445 },
446 },
447 },
448 "multiple resources including CRD": {
449 applyObjs: []*unstructured.Unstructured{
450 testutil.Unstructured(t, resources["crontab1"]),
451 testutil.Unstructured(t, resources["crd"]),
452 testutil.Unstructured(t, resources["crontab2"]),
453 },
454 expectedTasks: []taskrunner.Task{
455 &task.InvAddTask{
456 TaskName: "inventory-add-0",
457 InvClient: &inventory.FakeClient{},
458 InvInfo: invInfo,
459 Objects: object.UnstructuredSet{
460 testutil.Unstructured(t, resources["crontab1"]),
461 testutil.Unstructured(t, resources["crd"]),
462 testutil.Unstructured(t, resources["crontab2"]),
463 },
464 },
465 &task.ApplyTask{
466 TaskName: "apply-0",
467 Objects: []*unstructured.Unstructured{
468 testutil.Unstructured(t, resources["crd"]),
469 },
470 DryRunStrategy: common.DryRunNone,
471 },
472 &taskrunner.WaitTask{
473 TaskName: "wait-0",
474 Ids: object.ObjMetadataSet{
475 testutil.ToIdentifier(t, resources["crd"]),
476 },
477 Condition: taskrunner.AllCurrent,
478 },
479 &task.ApplyTask{
480 TaskName: "apply-1",
481 Objects: []*unstructured.Unstructured{
482 testutil.Unstructured(t, resources["crontab1"]),
483 testutil.Unstructured(t, resources["crontab2"]),
484 },
485 DryRunStrategy: common.DryRunNone,
486 },
487 &taskrunner.WaitTask{
488 TaskName: "wait-1",
489 Ids: object.ObjMetadataSet{
490 testutil.ToIdentifier(t, resources["crontab1"]),
491 testutil.ToIdentifier(t, resources["crontab2"]),
492 },
493 Condition: taskrunner.AllCurrent,
494 },
495 &task.InvSetTask{
496 TaskName: "inventory-set-0",
497 InvClient: &inventory.FakeClient{},
498 InvInfo: invInfo,
499 PrevInventory: object.ObjMetadataSet{
500 testutil.ToIdentifier(t, resources["crontab1"]),
501 testutil.ToIdentifier(t, resources["crd"]),
502 testutil.ToIdentifier(t, resources["crontab2"]),
503 },
504 },
505 },
506 expectedStatus: []actuation.ObjectStatus{
507 {
508 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
509 testutil.ToIdentifier(t, resources["crontab1"]),
510 ),
511 Strategy: actuation.ActuationStrategyApply,
512 Actuation: actuation.ActuationPending,
513 Reconcile: actuation.ReconcilePending,
514 },
515 {
516 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
517 testutil.ToIdentifier(t, resources["crd"]),
518 ),
519 Strategy: actuation.ActuationStrategyApply,
520 Actuation: actuation.ActuationPending,
521 Reconcile: actuation.ReconcilePending,
522 },
523 {
524 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
525 testutil.ToIdentifier(t, resources["crontab2"]),
526 ),
527 Strategy: actuation.ActuationStrategyApply,
528 Actuation: actuation.ActuationPending,
529 Reconcile: actuation.ReconcilePending,
530 },
531 },
532 },
533 "no wait with CRDs if it is a dryrun": {
534 applyObjs: []*unstructured.Unstructured{
535 testutil.Unstructured(t, resources["crontab1"]),
536 testutil.Unstructured(t, resources["crd"]),
537 testutil.Unstructured(t, resources["crontab2"]),
538 },
539 options: Options{
540 ReconcileTimeout: time.Minute,
541 DryRunStrategy: common.DryRunClient,
542 },
543 expectedTasks: []taskrunner.Task{
544 &task.InvAddTask{
545 TaskName: "inventory-add-0",
546 InvClient: &inventory.FakeClient{},
547 InvInfo: invInfo,
548 Objects: object.UnstructuredSet{
549 testutil.Unstructured(t, resources["crontab1"]),
550 testutil.Unstructured(t, resources["crd"]),
551 testutil.Unstructured(t, resources["crontab2"]),
552 },
553 DryRun: common.DryRunClient,
554 },
555 &task.ApplyTask{
556 TaskName: "apply-0",
557 Objects: []*unstructured.Unstructured{
558 testutil.Unstructured(t, resources["crd"]),
559 },
560 DryRunStrategy: common.DryRunClient,
561 },
562 &task.ApplyTask{
563 TaskName: "apply-1",
564 Objects: []*unstructured.Unstructured{
565 testutil.Unstructured(t, resources["crontab1"]),
566 testutil.Unstructured(t, resources["crontab2"]),
567 },
568 DryRunStrategy: common.DryRunClient,
569 },
570 &task.InvSetTask{
571 TaskName: "inventory-set-0",
572 InvClient: &inventory.FakeClient{},
573 InvInfo: invInfo,
574 PrevInventory: object.ObjMetadataSet{
575 testutil.ToIdentifier(t, resources["crontab1"]),
576 testutil.ToIdentifier(t, resources["crd"]),
577 testutil.ToIdentifier(t, resources["crontab2"]),
578 },
579 DryRun: common.DryRunClient,
580 },
581 },
582 expectedStatus: []actuation.ObjectStatus{
583 {
584 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
585 testutil.ToIdentifier(t, resources["crontab1"]),
586 ),
587 Strategy: actuation.ActuationStrategyApply,
588 Actuation: actuation.ActuationPending,
589 Reconcile: actuation.ReconcilePending,
590 },
591 {
592 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
593 testutil.ToIdentifier(t, resources["crd"]),
594 ),
595 Strategy: actuation.ActuationStrategyApply,
596 Actuation: actuation.ActuationPending,
597 Reconcile: actuation.ReconcilePending,
598 },
599 {
600 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
601 testutil.ToIdentifier(t, resources["crontab2"]),
602 ),
603 Strategy: actuation.ActuationStrategyApply,
604 Actuation: actuation.ActuationPending,
605 Reconcile: actuation.ReconcilePending,
606 },
607 },
608 },
609 "resources in namespace creates multiple apply tasks": {
610 applyObjs: []*unstructured.Unstructured{
611 testutil.Unstructured(t, resources["namespace"]),
612 testutil.Unstructured(t, resources["pod"]),
613 testutil.Unstructured(t, resources["secret"]),
614 },
615 expectedTasks: []taskrunner.Task{
616 &task.InvAddTask{
617 TaskName: "inventory-add-0",
618 InvClient: &inventory.FakeClient{},
619 InvInfo: invInfo,
620 Objects: object.UnstructuredSet{
621 testutil.Unstructured(t, resources["namespace"]),
622 testutil.Unstructured(t, resources["pod"]),
623 testutil.Unstructured(t, resources["secret"]),
624 },
625 },
626 &task.ApplyTask{
627 TaskName: "apply-0",
628 Objects: []*unstructured.Unstructured{
629 testutil.Unstructured(t, resources["namespace"]),
630 },
631 DryRunStrategy: common.DryRunNone,
632 },
633 &taskrunner.WaitTask{
634 TaskName: "wait-0",
635 Ids: object.ObjMetadataSet{
636 testutil.ToIdentifier(t, resources["namespace"]),
637 },
638 Condition: taskrunner.AllCurrent,
639 },
640 &task.ApplyTask{
641 TaskName: "apply-1",
642 Objects: []*unstructured.Unstructured{
643 testutil.Unstructured(t, resources["secret"]),
644 testutil.Unstructured(t, resources["pod"]),
645 },
646 DryRunStrategy: common.DryRunNone,
647 },
648 &taskrunner.WaitTask{
649 TaskName: "wait-1",
650 Ids: object.ObjMetadataSet{
651 testutil.ToIdentifier(t, resources["secret"]),
652 testutil.ToIdentifier(t, resources["pod"]),
653 },
654 Condition: taskrunner.AllCurrent,
655 },
656 &task.InvSetTask{
657 TaskName: "inventory-set-0",
658 InvClient: &inventory.FakeClient{},
659 InvInfo: invInfo,
660 PrevInventory: object.ObjMetadataSet{
661 testutil.ToIdentifier(t, resources["namespace"]),
662 testutil.ToIdentifier(t, resources["pod"]),
663 testutil.ToIdentifier(t, resources["secret"]),
664 },
665 },
666 },
667 expectedStatus: []actuation.ObjectStatus{
668 {
669 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
670 testutil.ToIdentifier(t, resources["namespace"]),
671 ),
672 Strategy: actuation.ActuationStrategyApply,
673 Actuation: actuation.ActuationPending,
674 Reconcile: actuation.ReconcilePending,
675 },
676 {
677 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
678 testutil.ToIdentifier(t, resources["pod"]),
679 ),
680 Strategy: actuation.ActuationStrategyApply,
681 Actuation: actuation.ActuationPending,
682 Reconcile: actuation.ReconcilePending,
683 },
684 {
685 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
686 testutil.ToIdentifier(t, resources["secret"]),
687 ),
688 Strategy: actuation.ActuationStrategyApply,
689 Actuation: actuation.ActuationPending,
690 Reconcile: actuation.ReconcilePending,
691 },
692 },
693 },
694 "deployment depends on secret creates multiple tasks": {
695 applyObjs: []*unstructured.Unstructured{
696 testutil.Unstructured(t, resources["deployment"],
697 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
698 testutil.Unstructured(t, resources["secret"]),
699 },
700 expectedTasks: []taskrunner.Task{
701 &task.InvAddTask{
702 TaskName: "inventory-add-0",
703 InvClient: &inventory.FakeClient{},
704 InvInfo: invInfo,
705 Objects: object.UnstructuredSet{
706 testutil.Unstructured(t, resources["deployment"],
707 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
708 testutil.Unstructured(t, resources["secret"]),
709 },
710 },
711 &task.ApplyTask{
712 TaskName: "apply-0",
713 Objects: []*unstructured.Unstructured{
714 testutil.Unstructured(t, resources["secret"]),
715 },
716 DryRunStrategy: common.DryRunNone,
717 },
718 &taskrunner.WaitTask{
719 TaskName: "wait-0",
720 Ids: object.ObjMetadataSet{
721 testutil.ToIdentifier(t, resources["secret"]),
722 },
723 Condition: taskrunner.AllCurrent,
724 },
725 &task.ApplyTask{
726 TaskName: "apply-1",
727 Objects: []*unstructured.Unstructured{
728 testutil.Unstructured(t, resources["deployment"],
729 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
730 },
731 DryRunStrategy: common.DryRunNone,
732 },
733 &taskrunner.WaitTask{
734 TaskName: "wait-1",
735 Ids: object.ObjMetadataSet{
736 testutil.ToIdentifier(t, resources["deployment"]),
737 },
738 Condition: taskrunner.AllCurrent,
739 },
740 &task.InvSetTask{
741 TaskName: "inventory-set-0",
742 InvClient: &inventory.FakeClient{},
743 InvInfo: invInfo,
744 PrevInventory: object.ObjMetadataSet{
745 testutil.ToIdentifier(t, resources["deployment"]),
746 testutil.ToIdentifier(t, resources["secret"]),
747 },
748 },
749 },
750 expectedStatus: []actuation.ObjectStatus{
751 {
752 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
753 testutil.ToIdentifier(t, resources["deployment"]),
754 ),
755 Strategy: actuation.ActuationStrategyApply,
756 Actuation: actuation.ActuationPending,
757 Reconcile: actuation.ReconcilePending,
758 },
759 {
760 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
761 testutil.ToIdentifier(t, resources["secret"]),
762 ),
763 Strategy: actuation.ActuationStrategyApply,
764 Actuation: actuation.ActuationPending,
765 Reconcile: actuation.ReconcilePending,
766 },
767 },
768 },
769 "cyclic dependency returns error": {
770 applyObjs: []*unstructured.Unstructured{
771 testutil.Unstructured(t, resources["deployment"],
772 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
773 testutil.Unstructured(t, resources["secret"],
774 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
775 },
776 expectedTasks: []taskrunner.Task{},
777 expectedError: validation.NewError(
778 graph.CyclicDependencyError{
779 Edges: []graph.Edge{
780 {
781 From: testutil.ToIdentifier(t, resources["secret"]),
782 To: testutil.ToIdentifier(t, resources["deployment"]),
783 },
784 {
785 From: testutil.ToIdentifier(t, resources["deployment"]),
786 To: testutil.ToIdentifier(t, resources["secret"]),
787 },
788 },
789 },
790 testutil.ToIdentifier(t, resources["secret"]),
791 testutil.ToIdentifier(t, resources["deployment"]),
792 ),
793 },
794 }
795
796 for tn, tc := range testCases {
797 t.Run(tn, func(t *testing.T) {
798 mapper := testutil.NewFakeRESTMapper()
799
800 for _, t := range tc.expectedTasks {
801 switch typedTask := t.(type) {
802 case *task.ApplyTask:
803 typedTask.Mapper = mapper
804 case *taskrunner.WaitTask:
805 typedTask.Mapper = mapper
806 }
807 }
808
809 applyIds := object.UnstructuredSetToObjMetadataSet(tc.applyObjs)
810 fakeInvClient := inventory.NewFakeClient(applyIds)
811 vCollector := &validation.Collector{}
812 tqb := TaskQueueBuilder{
813 Pruner: pruner,
814 Mapper: mapper,
815 InvClient: fakeInvClient,
816 Collector: vCollector,
817 }
818 taskContext := taskrunner.NewTaskContext(nil, nil)
819 tq := tqb.WithInventory(invInfo).
820 WithApplyObjects(tc.applyObjs).
821 Build(taskContext, tc.options)
822 err := vCollector.ToError()
823 if tc.expectedError != nil {
824 assert.EqualError(t, err, tc.expectedError.Error())
825 return
826 }
827 assert.NoError(t, err)
828 asserter.Equal(t, tc.expectedTasks, tq.tasks)
829
830 actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
831 testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
832 })
833 }
834 }
835
836 func TestTaskQueueBuilder_PruneBuild(t *testing.T) {
837
838 asserter := testutil.NewAsserter(
839 cmpopts.EquateErrors(),
840 waitTaskComparer(),
841 fakeClientComparer(),
842 inventoryInfoComparer(),
843 )
844
845 invInfo := inventory.WrapInventoryInfoObj(newInvObject(
846 "abc-123", "default", "test"))
847
848 testCases := map[string]struct {
849 pruneObjs []*unstructured.Unstructured
850 options Options
851 expectedTasks []taskrunner.Task
852 expectedError error
853 expectedStatus []actuation.ObjectStatus
854 }{
855 "no resources, no apply or prune tasks": {
856 pruneObjs: []*unstructured.Unstructured{},
857 options: Options{Prune: true},
858 expectedTasks: []taskrunner.Task{
859 &task.InvAddTask{
860 TaskName: "inventory-add-0",
861 InvClient: &inventory.FakeClient{},
862 InvInfo: invInfo,
863 Objects: object.UnstructuredSet{},
864 },
865 &task.InvSetTask{
866 TaskName: "inventory-set-0",
867 InvClient: &inventory.FakeClient{},
868 InvInfo: invInfo,
869 PrevInventory: object.ObjMetadataSet{},
870 },
871 },
872 },
873 "single resource, one prune task, one wait task": {
874 pruneObjs: []*unstructured.Unstructured{
875 testutil.Unstructured(t, resources["default-pod"]),
876 },
877 options: Options{Prune: true},
878 expectedTasks: []taskrunner.Task{
879 &task.InvAddTask{
880 TaskName: "inventory-add-0",
881 InvClient: &inventory.FakeClient{},
882 InvInfo: invInfo,
883 Objects: object.UnstructuredSet{},
884 },
885 &task.PruneTask{
886 TaskName: "prune-0",
887 Objects: []*unstructured.Unstructured{
888 testutil.Unstructured(t, resources["default-pod"]),
889 },
890 },
891 &taskrunner.WaitTask{
892 TaskName: "wait-0",
893 Ids: object.ObjMetadataSet{
894 testutil.ToIdentifier(t, resources["default-pod"]),
895 },
896 Condition: taskrunner.AllNotFound,
897 },
898 &task.InvSetTask{
899 TaskName: "inventory-set-0",
900 InvClient: &inventory.FakeClient{},
901 InvInfo: invInfo,
902 PrevInventory: object.ObjMetadataSet{
903 testutil.ToIdentifier(t, resources["default-pod"]),
904 },
905 },
906 },
907 expectedStatus: []actuation.ObjectStatus{
908 {
909 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
910 testutil.ToIdentifier(t, resources["default-pod"]),
911 ),
912 Strategy: actuation.ActuationStrategyDelete,
913 Actuation: actuation.ActuationPending,
914 Reconcile: actuation.ReconcilePending,
915 },
916 },
917 },
918 "multiple resources, one prune task, one wait task": {
919 pruneObjs: []*unstructured.Unstructured{
920 testutil.Unstructured(t, resources["default-pod"]),
921 testutil.Unstructured(t, resources["pod"]),
922 },
923 options: Options{Prune: true},
924 expectedTasks: []taskrunner.Task{
925 &task.InvAddTask{
926 TaskName: "inventory-add-0",
927 InvClient: &inventory.FakeClient{},
928 InvInfo: invInfo,
929 Objects: object.UnstructuredSet{},
930 },
931 &task.PruneTask{
932 TaskName: "prune-0",
933 Objects: []*unstructured.Unstructured{
934 testutil.Unstructured(t, resources["default-pod"]),
935 testutil.Unstructured(t, resources["pod"]),
936 },
937 },
938 &taskrunner.WaitTask{
939 TaskName: "wait-0",
940 Ids: object.ObjMetadataSet{
941 testutil.ToIdentifier(t, resources["default-pod"]),
942 testutil.ToIdentifier(t, resources["pod"]),
943 },
944 Condition: taskrunner.AllNotFound,
945 },
946 &task.InvSetTask{
947 TaskName: "inventory-set-0",
948 InvClient: &inventory.FakeClient{},
949 InvInfo: invInfo,
950 PrevInventory: object.ObjMetadataSet{
951 testutil.ToIdentifier(t, resources["default-pod"]),
952 testutil.ToIdentifier(t, resources["pod"]),
953 },
954 },
955 },
956 expectedStatus: []actuation.ObjectStatus{
957 {
958 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
959 testutil.ToIdentifier(t, resources["default-pod"]),
960 ),
961 Strategy: actuation.ActuationStrategyDelete,
962 Actuation: actuation.ActuationPending,
963 Reconcile: actuation.ReconcilePending,
964 },
965 {
966 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
967 testutil.ToIdentifier(t, resources["pod"]),
968 ),
969 Strategy: actuation.ActuationStrategyDelete,
970 Actuation: actuation.ActuationPending,
971 Reconcile: actuation.ReconcilePending,
972 },
973 },
974 },
975 "dependent resources, two prune tasks, two wait tasks": {
976 pruneObjs: []*unstructured.Unstructured{
977 testutil.Unstructured(t, resources["pod"],
978 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
979 testutil.Unstructured(t, resources["secret"]),
980 },
981 options: Options{Prune: true},
982
983 expectedTasks: []taskrunner.Task{
984 &task.InvAddTask{
985 TaskName: "inventory-add-0",
986 InvClient: &inventory.FakeClient{},
987 InvInfo: invInfo,
988 Objects: object.UnstructuredSet{},
989 },
990 &task.PruneTask{
991 TaskName: "prune-0",
992 Objects: []*unstructured.Unstructured{
993 testutil.Unstructured(t, resources["pod"],
994 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
995 },
996 },
997 &taskrunner.WaitTask{
998 TaskName: "wait-0",
999 Ids: object.ObjMetadataSet{
1000 testutil.ToIdentifier(t, resources["pod"]),
1001 },
1002 Condition: taskrunner.AllNotFound,
1003 },
1004 &task.PruneTask{
1005 TaskName: "prune-1",
1006 Objects: []*unstructured.Unstructured{
1007 testutil.Unstructured(t, resources["secret"]),
1008 },
1009 },
1010 &taskrunner.WaitTask{
1011 TaskName: "wait-1",
1012 Ids: object.ObjMetadataSet{
1013 testutil.ToIdentifier(t, resources["secret"]),
1014 },
1015 Condition: taskrunner.AllNotFound,
1016 },
1017 &task.InvSetTask{
1018 TaskName: "inventory-set-0",
1019 InvClient: &inventory.FakeClient{},
1020 InvInfo: invInfo,
1021 PrevInventory: object.ObjMetadataSet{
1022 testutil.ToIdentifier(t, resources["pod"]),
1023 testutil.ToIdentifier(t, resources["secret"]),
1024 },
1025 },
1026 },
1027 expectedStatus: []actuation.ObjectStatus{
1028 {
1029 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1030 testutil.ToIdentifier(t, resources["pod"]),
1031 ),
1032 Strategy: actuation.ActuationStrategyDelete,
1033 Actuation: actuation.ActuationPending,
1034 Reconcile: actuation.ReconcilePending,
1035 },
1036 {
1037 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1038 testutil.ToIdentifier(t, resources["secret"]),
1039 ),
1040 Strategy: actuation.ActuationStrategyDelete,
1041 Actuation: actuation.ActuationPending,
1042 Reconcile: actuation.ReconcilePending,
1043 },
1044 },
1045 },
1046 "single resource with prune timeout has wait task": {
1047 pruneObjs: []*unstructured.Unstructured{
1048 testutil.Unstructured(t, resources["pod"]),
1049 },
1050 options: Options{
1051 Prune: true,
1052 PruneTimeout: 3 * time.Minute,
1053 },
1054 expectedTasks: []taskrunner.Task{
1055 &task.InvAddTask{
1056 TaskName: "inventory-add-0",
1057 InvClient: &inventory.FakeClient{},
1058 InvInfo: invInfo,
1059 Objects: object.UnstructuredSet{},
1060 },
1061 &task.PruneTask{
1062 TaskName: "prune-0",
1063 Objects: []*unstructured.Unstructured{
1064 testutil.Unstructured(t, resources["pod"]),
1065 },
1066 },
1067 &taskrunner.WaitTask{
1068 TaskName: "wait-0",
1069 Ids: object.ObjMetadataSet{
1070 testutil.ToIdentifier(t, resources["pod"]),
1071 },
1072 Condition: taskrunner.AllNotFound,
1073 Timeout: 3 * time.Minute,
1074 },
1075 &task.InvSetTask{
1076 TaskName: "inventory-set-0",
1077 InvClient: &inventory.FakeClient{},
1078 InvInfo: invInfo,
1079 PrevInventory: object.ObjMetadataSet{
1080 testutil.ToIdentifier(t, resources["pod"]),
1081 },
1082 },
1083 },
1084 expectedStatus: []actuation.ObjectStatus{
1085 {
1086 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1087 testutil.ToIdentifier(t, resources["pod"]),
1088 ),
1089 Strategy: actuation.ActuationStrategyDelete,
1090 Actuation: actuation.ActuationPending,
1091 Reconcile: actuation.ReconcilePending,
1092 },
1093 },
1094 },
1095 "multiple resources with prune timeout and server-dryrun": {
1096 pruneObjs: []*unstructured.Unstructured{
1097 testutil.Unstructured(t, resources["pod"]),
1098 testutil.Unstructured(t, resources["default-pod"]),
1099 },
1100 options: Options{
1101 PruneTimeout: time.Minute,
1102 DryRunStrategy: common.DryRunServer,
1103 Prune: true,
1104 },
1105
1106 expectedTasks: []taskrunner.Task{
1107 &task.InvAddTask{
1108 TaskName: "inventory-add-0",
1109 InvClient: &inventory.FakeClient{},
1110 InvInfo: invInfo,
1111 Objects: object.UnstructuredSet{},
1112 DryRun: common.DryRunServer,
1113 },
1114 &task.PruneTask{
1115 TaskName: "prune-0",
1116 Objects: []*unstructured.Unstructured{
1117 testutil.Unstructured(t, resources["pod"]),
1118 testutil.Unstructured(t, resources["default-pod"]),
1119 },
1120 DryRunStrategy: common.DryRunServer,
1121 },
1122 &task.InvSetTask{
1123 TaskName: "inventory-set-0",
1124 InvClient: &inventory.FakeClient{},
1125 InvInfo: invInfo,
1126 PrevInventory: object.ObjMetadataSet{
1127 testutil.ToIdentifier(t, resources["pod"]),
1128 testutil.ToIdentifier(t, resources["default-pod"]),
1129 },
1130 DryRun: common.DryRunServer,
1131 },
1132 },
1133 expectedStatus: []actuation.ObjectStatus{
1134 {
1135 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1136 testutil.ToIdentifier(t, resources["pod"]),
1137 ),
1138 Strategy: actuation.ActuationStrategyDelete,
1139 Actuation: actuation.ActuationPending,
1140 Reconcile: actuation.ReconcilePending,
1141 },
1142 {
1143 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1144 testutil.ToIdentifier(t, resources["default-pod"]),
1145 ),
1146 Strategy: actuation.ActuationStrategyDelete,
1147 Actuation: actuation.ActuationPending,
1148 Reconcile: actuation.ReconcilePending,
1149 },
1150 },
1151 },
1152 "multiple resources including CRD": {
1153 pruneObjs: []*unstructured.Unstructured{
1154 testutil.Unstructured(t, resources["crontab1"]),
1155 testutil.Unstructured(t, resources["crd"]),
1156 testutil.Unstructured(t, resources["crontab2"]),
1157 },
1158 options: Options{Prune: true},
1159
1160 expectedTasks: []taskrunner.Task{
1161 &task.InvAddTask{
1162 TaskName: "inventory-add-0",
1163 InvClient: &inventory.FakeClient{},
1164 InvInfo: invInfo,
1165 Objects: object.UnstructuredSet{},
1166 },
1167 &task.PruneTask{
1168 TaskName: "prune-0",
1169 Objects: []*unstructured.Unstructured{
1170 testutil.Unstructured(t, resources["crontab1"]),
1171 testutil.Unstructured(t, resources["crontab2"]),
1172 },
1173 },
1174 &taskrunner.WaitTask{
1175 TaskName: "wait-0",
1176 Ids: object.ObjMetadataSet{
1177 testutil.ToIdentifier(t, resources["crontab1"]),
1178 testutil.ToIdentifier(t, resources["crontab2"]),
1179 },
1180 Condition: taskrunner.AllNotFound,
1181 },
1182 &task.PruneTask{
1183 TaskName: "prune-1",
1184 Objects: []*unstructured.Unstructured{
1185 testutil.Unstructured(t, resources["crd"]),
1186 },
1187 },
1188 &taskrunner.WaitTask{
1189 TaskName: "wait-1",
1190 Ids: object.ObjMetadataSet{
1191 testutil.ToIdentifier(t, resources["crd"]),
1192 },
1193 Condition: taskrunner.AllNotFound,
1194 },
1195 &task.InvSetTask{
1196 TaskName: "inventory-set-0",
1197 InvClient: &inventory.FakeClient{},
1198 InvInfo: invInfo,
1199 PrevInventory: object.ObjMetadataSet{
1200 testutil.ToIdentifier(t, resources["crontab1"]),
1201 testutil.ToIdentifier(t, resources["crd"]),
1202 testutil.ToIdentifier(t, resources["crontab2"]),
1203 },
1204 },
1205 },
1206 expectedStatus: []actuation.ObjectStatus{
1207 {
1208 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1209 testutil.ToIdentifier(t, resources["crontab1"]),
1210 ),
1211 Strategy: actuation.ActuationStrategyDelete,
1212 Actuation: actuation.ActuationPending,
1213 Reconcile: actuation.ReconcilePending,
1214 },
1215 {
1216 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1217 testutil.ToIdentifier(t, resources["crd"]),
1218 ),
1219 Strategy: actuation.ActuationStrategyDelete,
1220 Actuation: actuation.ActuationPending,
1221 Reconcile: actuation.ReconcilePending,
1222 },
1223 {
1224 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1225 testutil.ToIdentifier(t, resources["crontab2"]),
1226 ),
1227 Strategy: actuation.ActuationStrategyDelete,
1228 Actuation: actuation.ActuationPending,
1229 Reconcile: actuation.ReconcilePending,
1230 },
1231 },
1232 },
1233 "no wait with CRDs if it is a dryrun": {
1234 pruneObjs: []*unstructured.Unstructured{
1235 testutil.Unstructured(t, resources["crontab1"]),
1236 testutil.Unstructured(t, resources["crd"]),
1237 testutil.Unstructured(t, resources["crontab2"]),
1238 },
1239 options: Options{
1240 ReconcileTimeout: time.Minute,
1241 DryRunStrategy: common.DryRunClient,
1242 Prune: true,
1243 },
1244 expectedTasks: []taskrunner.Task{
1245 &task.InvAddTask{
1246 TaskName: "inventory-add-0",
1247 InvClient: &inventory.FakeClient{},
1248 InvInfo: invInfo,
1249 Objects: object.UnstructuredSet{},
1250 DryRun: common.DryRunClient,
1251 },
1252 &task.PruneTask{
1253 TaskName: "prune-0",
1254 Objects: []*unstructured.Unstructured{
1255 testutil.Unstructured(t, resources["crontab1"]),
1256 testutil.Unstructured(t, resources["crontab2"]),
1257 },
1258 DryRunStrategy: common.DryRunClient,
1259 },
1260 &task.PruneTask{
1261 TaskName: "prune-1",
1262 Objects: []*unstructured.Unstructured{
1263 testutil.Unstructured(t, resources["crd"]),
1264 },
1265 DryRunStrategy: common.DryRunClient,
1266 },
1267 &task.InvSetTask{
1268 TaskName: "inventory-set-0",
1269 InvClient: &inventory.FakeClient{},
1270 InvInfo: invInfo,
1271 PrevInventory: object.ObjMetadataSet{
1272 testutil.ToIdentifier(t, resources["crontab1"]),
1273 testutil.ToIdentifier(t, resources["crd"]),
1274 testutil.ToIdentifier(t, resources["crontab2"]),
1275 },
1276 DryRun: common.DryRunClient,
1277 },
1278 },
1279 expectedStatus: []actuation.ObjectStatus{
1280 {
1281 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1282 testutil.ToIdentifier(t, resources["crontab1"]),
1283 ),
1284 Strategy: actuation.ActuationStrategyDelete,
1285 Actuation: actuation.ActuationPending,
1286 Reconcile: actuation.ReconcilePending,
1287 },
1288 {
1289 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1290 testutil.ToIdentifier(t, resources["crd"]),
1291 ),
1292 Strategy: actuation.ActuationStrategyDelete,
1293 Actuation: actuation.ActuationPending,
1294 Reconcile: actuation.ReconcilePending,
1295 },
1296 {
1297 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1298 testutil.ToIdentifier(t, resources["crontab2"]),
1299 ),
1300 Strategy: actuation.ActuationStrategyDelete,
1301 Actuation: actuation.ActuationPending,
1302 Reconcile: actuation.ReconcilePending,
1303 },
1304 },
1305 },
1306 "resources in namespace creates multiple apply tasks": {
1307 pruneObjs: []*unstructured.Unstructured{
1308 testutil.Unstructured(t, resources["namespace"]),
1309 testutil.Unstructured(t, resources["pod"]),
1310 testutil.Unstructured(t, resources["secret"]),
1311 },
1312 options: Options{Prune: true},
1313 expectedTasks: []taskrunner.Task{
1314 &task.InvAddTask{
1315 TaskName: "inventory-add-0",
1316 InvClient: &inventory.FakeClient{},
1317 InvInfo: invInfo,
1318 Objects: object.UnstructuredSet{},
1319 },
1320 &task.PruneTask{
1321 TaskName: "prune-0",
1322 Objects: []*unstructured.Unstructured{
1323 testutil.Unstructured(t, resources["pod"]),
1324 testutil.Unstructured(t, resources["secret"]),
1325 },
1326 },
1327 &taskrunner.WaitTask{
1328 TaskName: "wait-0",
1329 Ids: object.ObjMetadataSet{
1330 testutil.ToIdentifier(t, resources["pod"]),
1331 testutil.ToIdentifier(t, resources["secret"]),
1332 },
1333 Condition: taskrunner.AllNotFound,
1334 },
1335 &task.PruneTask{
1336 TaskName: "prune-1",
1337 Objects: []*unstructured.Unstructured{
1338 testutil.Unstructured(t, resources["namespace"]),
1339 },
1340 },
1341 &taskrunner.WaitTask{
1342 TaskName: "wait-1",
1343 Ids: object.ObjMetadataSet{
1344 testutil.ToIdentifier(t, resources["namespace"]),
1345 },
1346 Condition: taskrunner.AllNotFound,
1347 },
1348 &task.InvSetTask{
1349 TaskName: "inventory-set-0",
1350 InvClient: &inventory.FakeClient{},
1351 InvInfo: invInfo,
1352 PrevInventory: object.ObjMetadataSet{
1353 testutil.ToIdentifier(t, resources["namespace"]),
1354 testutil.ToIdentifier(t, resources["pod"]),
1355 testutil.ToIdentifier(t, resources["secret"]),
1356 },
1357 },
1358 },
1359 expectedStatus: []actuation.ObjectStatus{
1360 {
1361 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1362 testutil.ToIdentifier(t, resources["namespace"]),
1363 ),
1364 Strategy: actuation.ActuationStrategyDelete,
1365 Actuation: actuation.ActuationPending,
1366 Reconcile: actuation.ReconcilePending,
1367 },
1368 {
1369 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1370 testutil.ToIdentifier(t, resources["pod"]),
1371 ),
1372 Strategy: actuation.ActuationStrategyDelete,
1373 Actuation: actuation.ActuationPending,
1374 Reconcile: actuation.ReconcilePending,
1375 },
1376 {
1377 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1378 testutil.ToIdentifier(t, resources["secret"]),
1379 ),
1380 Strategy: actuation.ActuationStrategyDelete,
1381 Actuation: actuation.ActuationPending,
1382 Reconcile: actuation.ReconcilePending,
1383 },
1384 },
1385 },
1386 "cyclic dependency": {
1387 pruneObjs: []*unstructured.Unstructured{
1388 testutil.Unstructured(t, resources["deployment"],
1389 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1390 testutil.Unstructured(t, resources["secret"],
1391 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
1392 },
1393 options: Options{Prune: true},
1394 expectedTasks: []taskrunner.Task{},
1395 expectedError: validation.NewError(
1396 graph.CyclicDependencyError{
1397 Edges: []graph.Edge{
1398 {
1399 From: testutil.ToIdentifier(t, resources["secret"]),
1400 To: testutil.ToIdentifier(t, resources["deployment"]),
1401 },
1402 {
1403 From: testutil.ToIdentifier(t, resources["deployment"]),
1404 To: testutil.ToIdentifier(t, resources["secret"]),
1405 },
1406 },
1407 },
1408 testutil.ToIdentifier(t, resources["secret"]),
1409 testutil.ToIdentifier(t, resources["deployment"]),
1410 ),
1411 },
1412 "cyclic dependency and valid": {
1413 pruneObjs: []*unstructured.Unstructured{
1414 testutil.Unstructured(t, resources["deployment"],
1415 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1416 testutil.Unstructured(t, resources["secret"],
1417 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
1418 testutil.Unstructured(t, resources["pod"]),
1419 },
1420 options: Options{Prune: true},
1421 expectedTasks: []taskrunner.Task{
1422 &task.InvAddTask{
1423 TaskName: "inventory-add-0",
1424 InvClient: &inventory.FakeClient{},
1425 InvInfo: invInfo,
1426 Objects: object.UnstructuredSet{},
1427 },
1428 &task.PruneTask{
1429 TaskName: "prune-0",
1430 Objects: []*unstructured.Unstructured{
1431 testutil.Unstructured(t, resources["pod"]),
1432 },
1433 },
1434 taskrunner.NewWaitTask(
1435 "wait-0",
1436 object.ObjMetadataSet{
1437 testutil.ToIdentifier(t, resources["pod"]),
1438 },
1439 taskrunner.AllCurrent, 1*time.Second,
1440 testutil.NewFakeRESTMapper(),
1441 ),
1442 &task.InvSetTask{
1443 TaskName: "inventory-set-0",
1444 InvClient: &inventory.FakeClient{},
1445 InvInfo: invInfo,
1446 PrevInventory: object.ObjMetadataSet{
1447 testutil.ToIdentifier(t, resources["pod"]),
1448 },
1449 },
1450 },
1451 expectedError: validation.NewError(
1452 graph.CyclicDependencyError{
1453 Edges: []graph.Edge{
1454 {
1455 From: testutil.ToIdentifier(t, resources["secret"]),
1456 To: testutil.ToIdentifier(t, resources["deployment"]),
1457 },
1458 {
1459 From: testutil.ToIdentifier(t, resources["deployment"]),
1460 To: testutil.ToIdentifier(t, resources["secret"]),
1461 },
1462 },
1463 },
1464 testutil.ToIdentifier(t, resources["secret"]),
1465 testutil.ToIdentifier(t, resources["deployment"]),
1466 ),
1467 },
1468 }
1469
1470 for tn, tc := range testCases {
1471 t.Run(tn, func(t *testing.T) {
1472 mapper := testutil.NewFakeRESTMapper()
1473
1474 for _, t := range tc.expectedTasks {
1475 switch typedTask := t.(type) {
1476 case *task.PruneTask:
1477 typedTask.Pruner = &prune.Pruner{}
1478 case *taskrunner.WaitTask:
1479 typedTask.Mapper = mapper
1480 }
1481 }
1482
1483 pruneIds := object.UnstructuredSetToObjMetadataSet(tc.pruneObjs)
1484 fakeInvClient := inventory.NewFakeClient(pruneIds)
1485 vCollector := &validation.Collector{}
1486 tqb := TaskQueueBuilder{
1487 Pruner: pruner,
1488 Mapper: mapper,
1489 InvClient: fakeInvClient,
1490 Collector: vCollector,
1491 }
1492 taskContext := taskrunner.NewTaskContext(nil, nil)
1493 tq := tqb.WithInventory(invInfo).
1494 WithPruneObjects(tc.pruneObjs).
1495 Build(taskContext, tc.options)
1496 err := vCollector.ToError()
1497 if tc.expectedError != nil {
1498 assert.EqualError(t, err, tc.expectedError.Error())
1499 return
1500 }
1501 assert.NoError(t, err)
1502 asserter.Equal(t, tc.expectedTasks, tq.tasks)
1503
1504 actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
1505 testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
1506 })
1507 }
1508 }
1509
1510 func TestTaskQueueBuilder_ApplyPruneBuild(t *testing.T) {
1511
1512 asserter := testutil.NewAsserter(
1513 cmpopts.EquateErrors(),
1514 waitTaskComparer(),
1515 fakeClientComparer(),
1516 inventoryInfoComparer(),
1517 )
1518
1519 invInfo := inventory.WrapInventoryInfoObj(newInvObject(
1520 "abc-123", "default", "test"))
1521
1522 testCases := map[string]struct {
1523 inventoryIDs object.ObjMetadataSet
1524 applyObjs object.UnstructuredSet
1525 pruneObjs object.UnstructuredSet
1526 options Options
1527 expectedTasks []taskrunner.Task
1528 expectedError error
1529 expectedStatus []actuation.ObjectStatus
1530 }{
1531 "two resources, one apply, one prune": {
1532 inventoryIDs: object.ObjMetadataSet{
1533 testutil.ToIdentifier(t, resources["secret"]),
1534 },
1535 applyObjs: object.UnstructuredSet{
1536 testutil.Unstructured(t, resources["deployment"]),
1537 },
1538 pruneObjs: object.UnstructuredSet{
1539 testutil.Unstructured(t, resources["secret"]),
1540 },
1541 options: Options{Prune: true},
1542 expectedTasks: []taskrunner.Task{
1543 &task.InvAddTask{
1544 TaskName: "inventory-add-0",
1545 InvClient: &inventory.FakeClient{},
1546 InvInfo: invInfo,
1547 Objects: object.UnstructuredSet{
1548 testutil.Unstructured(t, resources["deployment"]),
1549 },
1550 },
1551 &task.ApplyTask{
1552 TaskName: "apply-0",
1553 Objects: []*unstructured.Unstructured{
1554 testutil.Unstructured(t, resources["deployment"]),
1555 },
1556 },
1557 &taskrunner.WaitTask{
1558 TaskName: "wait-0",
1559 Ids: object.ObjMetadataSet{
1560 testutil.ToIdentifier(t, resources["deployment"]),
1561 },
1562 Condition: taskrunner.AllCurrent,
1563 },
1564 &task.PruneTask{
1565 TaskName: "prune-0",
1566 Objects: []*unstructured.Unstructured{
1567 testutil.Unstructured(t, resources["secret"]),
1568 },
1569 },
1570 &taskrunner.WaitTask{
1571 TaskName: "wait-1",
1572 Ids: object.ObjMetadataSet{
1573 testutil.ToIdentifier(t, resources["secret"]),
1574 },
1575 Condition: taskrunner.AllNotFound,
1576 },
1577 &task.InvSetTask{
1578 TaskName: "inventory-set-0",
1579 InvClient: &inventory.FakeClient{},
1580 InvInfo: invInfo,
1581 PrevInventory: object.ObjMetadataSet{
1582 testutil.ToIdentifier(t, resources["secret"]),
1583 },
1584 },
1585 },
1586 expectedStatus: []actuation.ObjectStatus{
1587 {
1588 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1589 testutil.ToIdentifier(t, resources["deployment"]),
1590 ),
1591 Strategy: actuation.ActuationStrategyApply,
1592 Actuation: actuation.ActuationPending,
1593 Reconcile: actuation.ReconcilePending,
1594 },
1595 {
1596 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1597 testutil.ToIdentifier(t, resources["secret"]),
1598 ),
1599 Strategy: actuation.ActuationStrategyDelete,
1600 Actuation: actuation.ActuationPending,
1601 Reconcile: actuation.ReconcilePending,
1602 },
1603 },
1604 },
1605 "prune disabled": {
1606 inventoryIDs: object.ObjMetadataSet{
1607 testutil.ToIdentifier(t, resources["secret"]),
1608 },
1609 applyObjs: object.UnstructuredSet{
1610 testutil.Unstructured(t, resources["deployment"]),
1611 },
1612 pruneObjs: object.UnstructuredSet{
1613 testutil.Unstructured(t, resources["secret"]),
1614 },
1615 options: Options{Prune: false},
1616 expectedTasks: []taskrunner.Task{
1617 &task.InvAddTask{
1618 TaskName: "inventory-add-0",
1619 InvClient: &inventory.FakeClient{},
1620 InvInfo: invInfo,
1621 Objects: object.UnstructuredSet{
1622 testutil.Unstructured(t, resources["deployment"]),
1623 },
1624 },
1625 &task.ApplyTask{
1626 TaskName: "apply-0",
1627 Objects: []*unstructured.Unstructured{
1628 testutil.Unstructured(t, resources["deployment"]),
1629 },
1630 },
1631 &taskrunner.WaitTask{
1632 TaskName: "wait-0",
1633 Ids: object.ObjMetadataSet{
1634 testutil.ToIdentifier(t, resources["deployment"]),
1635 },
1636 Condition: taskrunner.AllCurrent,
1637 },
1638 &task.InvSetTask{
1639 TaskName: "inventory-set-0",
1640 InvClient: &inventory.FakeClient{},
1641 InvInfo: invInfo,
1642 PrevInventory: object.ObjMetadataSet{
1643 testutil.ToIdentifier(t, resources["secret"]),
1644 },
1645 },
1646 },
1647 expectedStatus: []actuation.ObjectStatus{
1648 {
1649 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1650 testutil.ToIdentifier(t, resources["deployment"]),
1651 ),
1652 Strategy: actuation.ActuationStrategyApply,
1653 Actuation: actuation.ActuationPending,
1654 Reconcile: actuation.ReconcilePending,
1655 },
1656 },
1657 },
1658
1659
1660
1661
1662
1663 "dependency: apply -> prune": {
1664 inventoryIDs: object.ObjMetadataSet{
1665 testutil.ToIdentifier(t, resources["secret"]),
1666 },
1667 applyObjs: object.UnstructuredSet{
1668 testutil.Unstructured(t, resources["deployment"],
1669 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1670 },
1671 pruneObjs: object.UnstructuredSet{
1672 testutil.Unstructured(t, resources["secret"]),
1673 },
1674 options: Options{Prune: true},
1675 expectedTasks: []taskrunner.Task{
1676 &task.InvAddTask{
1677 TaskName: "inventory-add-0",
1678 InvClient: &inventory.FakeClient{},
1679 InvInfo: invInfo,
1680 Objects: object.UnstructuredSet{
1681 testutil.Unstructured(t, resources["deployment"],
1682 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1683 },
1684 },
1685 &task.ApplyTask{
1686 TaskName: "apply-0",
1687 Objects: []*unstructured.Unstructured{
1688 testutil.Unstructured(t, resources["deployment"],
1689 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["secret"]))),
1690 },
1691 },
1692 &taskrunner.WaitTask{
1693 TaskName: "wait-0",
1694 Ids: object.ObjMetadataSet{
1695 testutil.ToIdentifier(t, resources["deployment"]),
1696 },
1697 Condition: taskrunner.AllCurrent,
1698 },
1699 &task.PruneTask{
1700 TaskName: "prune-0",
1701 Objects: []*unstructured.Unstructured{
1702 testutil.Unstructured(t, resources["secret"]),
1703 },
1704 },
1705 &taskrunner.WaitTask{
1706 TaskName: "wait-1",
1707 Ids: object.ObjMetadataSet{
1708 testutil.ToIdentifier(t, resources["secret"]),
1709 },
1710 Condition: taskrunner.AllNotFound,
1711 },
1712 &task.InvSetTask{
1713 TaskName: "inventory-set-0",
1714 InvClient: &inventory.FakeClient{},
1715 InvInfo: invInfo,
1716 PrevInventory: object.ObjMetadataSet{
1717 testutil.ToIdentifier(t, resources["secret"]),
1718 },
1719 },
1720 },
1721 expectedStatus: []actuation.ObjectStatus{
1722 {
1723 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1724 testutil.ToIdentifier(t, resources["deployment"]),
1725 ),
1726 Strategy: actuation.ActuationStrategyApply,
1727 Actuation: actuation.ActuationPending,
1728 Reconcile: actuation.ReconcilePending,
1729 },
1730 {
1731 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1732 testutil.ToIdentifier(t, resources["secret"]),
1733 ),
1734 Strategy: actuation.ActuationStrategyDelete,
1735 Actuation: actuation.ActuationPending,
1736 Reconcile: actuation.ReconcilePending,
1737 },
1738 },
1739 },
1740
1741
1742
1743
1744 "dependency: prune -> apply": {
1745 inventoryIDs: object.ObjMetadataSet{
1746 testutil.ToIdentifier(t, resources["secret"]),
1747 },
1748 applyObjs: object.UnstructuredSet{
1749 testutil.Unstructured(t, resources["deployment"]),
1750 },
1751 pruneObjs: object.UnstructuredSet{
1752 testutil.Unstructured(t, resources["secret"],
1753 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
1754 },
1755 options: Options{Prune: true},
1756 expectedTasks: []taskrunner.Task{
1757 &task.InvAddTask{
1758 TaskName: "inventory-add-0",
1759 InvClient: &inventory.FakeClient{},
1760 InvInfo: invInfo,
1761 Objects: object.UnstructuredSet{
1762 testutil.Unstructured(t, resources["deployment"]),
1763 },
1764 },
1765 &task.ApplyTask{
1766 TaskName: "apply-0",
1767 Objects: []*unstructured.Unstructured{
1768 testutil.Unstructured(t, resources["deployment"]),
1769 },
1770 },
1771 &taskrunner.WaitTask{
1772 TaskName: "wait-0",
1773 Ids: object.ObjMetadataSet{
1774 testutil.ToIdentifier(t, resources["deployment"]),
1775 },
1776 Condition: taskrunner.AllCurrent,
1777 },
1778 &task.PruneTask{
1779 TaskName: "prune-0",
1780 Objects: []*unstructured.Unstructured{
1781 testutil.Unstructured(t, resources["secret"],
1782 testutil.AddDependsOn(t, testutil.ToIdentifier(t, resources["deployment"]))),
1783 },
1784 },
1785 &taskrunner.WaitTask{
1786 TaskName: "wait-1",
1787 Ids: object.ObjMetadataSet{
1788 testutil.ToIdentifier(t, resources["secret"]),
1789 },
1790 Condition: taskrunner.AllNotFound,
1791 },
1792 &task.InvSetTask{
1793 TaskName: "inventory-set-0",
1794 InvClient: &inventory.FakeClient{},
1795 InvInfo: invInfo,
1796 PrevInventory: object.ObjMetadataSet{
1797 testutil.ToIdentifier(t, resources["secret"]),
1798 },
1799 },
1800 },
1801 expectedStatus: []actuation.ObjectStatus{
1802 {
1803 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1804 testutil.ToIdentifier(t, resources["deployment"]),
1805 ),
1806 Strategy: actuation.ActuationStrategyApply,
1807 Actuation: actuation.ActuationPending,
1808 Reconcile: actuation.ReconcilePending,
1809 },
1810 {
1811 ObjectReference: inventory.ObjectReferenceFromObjMetadata(
1812 testutil.ToIdentifier(t, resources["secret"]),
1813 ),
1814 Strategy: actuation.ActuationStrategyDelete,
1815 Actuation: actuation.ActuationPending,
1816 Reconcile: actuation.ReconcilePending,
1817 },
1818 },
1819 },
1820 }
1821
1822 for tn, tc := range testCases {
1823 t.Run(tn, func(t *testing.T) {
1824 mapper := testutil.NewFakeRESTMapper()
1825
1826 for _, t := range tc.expectedTasks {
1827 switch typedTask := t.(type) {
1828 case *task.ApplyTask:
1829 typedTask.Mapper = mapper
1830 case *task.PruneTask:
1831 typedTask.Pruner = &prune.Pruner{}
1832 case *taskrunner.WaitTask:
1833 typedTask.Mapper = mapper
1834 }
1835 }
1836
1837 fakeInvClient := inventory.NewFakeClient(tc.inventoryIDs)
1838 vCollector := &validation.Collector{}
1839 tqb := TaskQueueBuilder{
1840 Pruner: pruner,
1841 Mapper: mapper,
1842 InvClient: fakeInvClient,
1843 Collector: vCollector,
1844 }
1845 taskContext := taskrunner.NewTaskContext(nil, nil)
1846 tq := tqb.WithInventory(invInfo).
1847 WithApplyObjects(tc.applyObjs).
1848 WithPruneObjects(tc.pruneObjs).
1849 Build(taskContext, tc.options)
1850
1851 err := vCollector.ToError()
1852 if tc.expectedError != nil {
1853 assert.EqualError(t, err, tc.expectedError.Error())
1854 return
1855 }
1856 assert.NoError(t, err)
1857
1858 asserter.Equal(t, tc.expectedTasks, tq.tasks)
1859
1860 actualStatus := taskContext.InventoryManager().Inventory().Status.Objects
1861 testutil.AssertEqual(t, tc.expectedStatus, actualStatus)
1862 })
1863 }
1864 }
1865
1866
1867 func waitTaskComparer() cmp.Option {
1868 return cmp.Comparer(func(x, y *taskrunner.WaitTask) bool {
1869 if x == nil {
1870 return y == nil
1871 }
1872 if y == nil {
1873 return false
1874 }
1875 return x.TaskName == y.TaskName &&
1876 x.Ids.Hash() == y.Ids.Hash() &&
1877 x.Condition == y.Condition &&
1878 x.Timeout == y.Timeout &&
1879 cmp.Equal(x.Mapper, y.Mapper)
1880 })
1881 }
1882
1883
1884 func fakeClientComparer() cmp.Option {
1885 return cmp.Comparer(func(x, y *inventory.FakeClient) bool {
1886 if x == nil {
1887 return y == nil
1888 }
1889 if y == nil {
1890 return false
1891 }
1892 return true
1893 })
1894 }
1895
1896
1897 func inventoryInfoComparer() cmp.Option {
1898 return cmp.Comparer(func(x, y inventory.Info) bool {
1899 return x.ID() == y.ID() &&
1900 x.Name() == y.Name() &&
1901 x.Namespace() == y.Namespace() &&
1902 x.Strategy() == y.Strategy()
1903 })
1904 }
1905
View as plain text