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 "github.com/stretchr/testify/require"
14 "k8s.io/apimachinery/pkg/util/validation/field"
15 "k8s.io/kubectl/pkg/scheme"
16 "sigs.k8s.io/cli-utils/pkg/apis/actuation"
17 "sigs.k8s.io/cli-utils/pkg/apply/event"
18 "sigs.k8s.io/cli-utils/pkg/inventory"
19 pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
20 "sigs.k8s.io/cli-utils/pkg/kstatus/status"
21 "sigs.k8s.io/cli-utils/pkg/kstatus/watcher"
22 "sigs.k8s.io/cli-utils/pkg/multierror"
23 "sigs.k8s.io/cli-utils/pkg/object"
24 "sigs.k8s.io/cli-utils/pkg/object/validation"
25 "sigs.k8s.io/cli-utils/pkg/testutil"
26 )
27
28 var (
29 codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
30 resources = map[string]string{
31 "deployment": `
32 apiVersion: apps/v1
33 kind: Deployment
34 metadata:
35 name: foo
36 namespace: default
37 uid: dep-uid
38 generation: 1
39 spec:
40 replicas: 1
41 `,
42 "secret": `
43 apiVersion: v1
44 kind: Secret
45 metadata:
46 name: secret
47 namespace: default
48 uid: secret-uid
49 generation: 1
50 type: Opaque
51 spec:
52 foo: bar
53 `,
54 "inventory": `
55 apiVersion: v1
56 kind: ConfigMap
57 metadata:
58 name: test-inventory-obj
59 namespace: test-namespace
60 labels:
61 cli-utils.sigs.k8s.io/inventory-id: test-app-label
62 data: {}
63 `,
64 "obj1": `
65 apiVersion: v1
66 kind: Pod
67 metadata:
68 name: obj1
69 namespace: test-namespace
70 spec: {}
71 `,
72 "obj2": `
73 apiVersion: v1
74 kind: Pod
75 metadata:
76 name: obj2
77 namespace: test-namespace
78 spec: {}
79 `,
80 "clusterScopedObj": `
81 apiVersion: rbac.authorization.k8s.io/v1
82 kind: ClusterRole
83 metadata:
84 name: cluster-scoped-1
85 `,
86 }
87 )
88
89
90 func TestApplier(t *testing.T) {
91 testCases := map[string]struct {
92 namespace string
93
94 resources object.UnstructuredSet
95
96 invInfo inventoryInfo
97
98 clusterObjs object.UnstructuredSet
99
100 options ApplierOptions
101
102 statusEvents []pollevent.Event
103
104 expectedStatusEvents []testutil.ExpEvent
105
106 expectedEvents []testutil.ExpEvent
107
108 expectRunTimeout bool
109
110 expectTestTimeout bool
111 }{
112 "initial apply without status or prune": {
113 namespace: "default",
114 resources: object.UnstructuredSet{
115 testutil.Unstructured(t, resources["deployment"]),
116 },
117 invInfo: inventoryInfo{
118 name: "abc-123",
119 namespace: "default",
120 id: "test",
121 },
122 clusterObjs: object.UnstructuredSet{},
123 options: ApplierOptions{
124 NoPrune: true,
125 InventoryPolicy: inventory.PolicyMustMatch,
126 },
127 expectedEvents: []testutil.ExpEvent{
128 {
129 EventType: event.InitType,
130 InitEvent: &testutil.ExpInitEvent{},
131 },
132 {
133 EventType: event.ActionGroupType,
134 ActionGroupEvent: &testutil.ExpActionGroupEvent{
135 GroupName: "inventory-add-0",
136 Action: event.InventoryAction,
137 Type: event.Started,
138 },
139 },
140 {
141 EventType: event.ActionGroupType,
142 ActionGroupEvent: &testutil.ExpActionGroupEvent{
143 GroupName: "inventory-add-0",
144 Action: event.InventoryAction,
145 Type: event.Finished,
146 },
147 },
148
149 {
150 EventType: event.ActionGroupType,
151 ActionGroupEvent: &testutil.ExpActionGroupEvent{
152 GroupName: "apply-0",
153 Action: event.ApplyAction,
154 Type: event.Started,
155 },
156 },
157 {
158 EventType: event.ApplyType,
159 ApplyEvent: &testutil.ExpApplyEvent{
160 GroupName: "apply-0",
161 Status: event.ApplySuccessful,
162 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
163 },
164 },
165 {
166 EventType: event.ActionGroupType,
167 ActionGroupEvent: &testutil.ExpActionGroupEvent{
168 GroupName: "apply-0",
169 Action: event.ApplyAction,
170 Type: event.Finished,
171 },
172 },
173 {
174 EventType: event.ActionGroupType,
175 ActionGroupEvent: &testutil.ExpActionGroupEvent{
176 GroupName: "wait-0",
177 Action: event.WaitAction,
178 Type: event.Started,
179 },
180 },
181 {
182 EventType: event.WaitType,
183 WaitEvent: &testutil.ExpWaitEvent{
184 GroupName: "wait-0",
185 Status: event.ReconcilePending,
186 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
187 },
188 },
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 },
216 expectTestTimeout: true,
217 },
218 "first apply multiple resources with status and prune": {
219 namespace: "default",
220 resources: object.UnstructuredSet{
221 testutil.Unstructured(t, resources["deployment"]),
222 testutil.Unstructured(t, resources["secret"]),
223 },
224 invInfo: inventoryInfo{
225 name: "inv-123",
226 namespace: "default",
227 id: "test",
228 },
229 clusterObjs: object.UnstructuredSet{},
230 options: ApplierOptions{
231 ReconcileTimeout: time.Minute,
232 InventoryPolicy: inventory.PolicyMustMatch,
233 EmitStatusEvents: true,
234 },
235 statusEvents: []pollevent.Event{
236 {
237 Type: pollevent.ResourceUpdateEvent,
238 Resource: &pollevent.ResourceStatus{
239 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
240 Status: status.InProgressStatus,
241 Resource: testutil.Unstructured(t, resources["deployment"]),
242 },
243 },
244 {
245 Type: pollevent.ResourceUpdateEvent,
246 Resource: &pollevent.ResourceStatus{
247 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
248 Status: status.CurrentStatus,
249 Resource: testutil.Unstructured(t, resources["deployment"]),
250 },
251 },
252 {
253 Type: pollevent.ResourceUpdateEvent,
254 Resource: &pollevent.ResourceStatus{
255 Identifier: testutil.ToIdentifier(t, resources["secret"]),
256 Status: status.CurrentStatus,
257 Resource: testutil.Unstructured(t, resources["secret"]),
258 },
259 },
260 },
261 expectedStatusEvents: []testutil.ExpEvent{
262 {
263 EventType: event.StatusType,
264 StatusEvent: &testutil.ExpStatusEvent{
265 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
266 Status: status.InProgressStatus,
267 },
268 },
269 {
270 EventType: event.StatusType,
271 StatusEvent: &testutil.ExpStatusEvent{
272 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
273 Status: status.CurrentStatus,
274 },
275 },
276 {
277 EventType: event.StatusType,
278 StatusEvent: &testutil.ExpStatusEvent{
279 Identifier: testutil.ToIdentifier(t, resources["secret"]),
280 Status: status.CurrentStatus,
281 },
282 },
283 },
284 expectedEvents: []testutil.ExpEvent{
285 {
286 EventType: event.InitType,
287 InitEvent: &testutil.ExpInitEvent{},
288 },
289 {
290 EventType: event.ActionGroupType,
291 ActionGroupEvent: &testutil.ExpActionGroupEvent{
292 GroupName: "inventory-add-0",
293 Action: event.InventoryAction,
294 Type: event.Started,
295 },
296 },
297 {
298 EventType: event.ActionGroupType,
299 ActionGroupEvent: &testutil.ExpActionGroupEvent{
300 GroupName: "inventory-add-0",
301 Action: event.InventoryAction,
302 Type: event.Finished,
303 },
304 },
305
306 {
307 EventType: event.ActionGroupType,
308 ActionGroupEvent: &testutil.ExpActionGroupEvent{
309 GroupName: "apply-0",
310 Action: event.ApplyAction,
311 Type: event.Started,
312 },
313 },
314
315 {
316 EventType: event.ApplyType,
317 ApplyEvent: &testutil.ExpApplyEvent{
318 GroupName: "apply-0",
319 Status: event.ApplySuccessful,
320 Identifier: testutil.ToIdentifier(t, resources["secret"]),
321 },
322 },
323 {
324 EventType: event.ApplyType,
325 ApplyEvent: &testutil.ExpApplyEvent{
326 GroupName: "apply-0",
327 Status: event.ApplySuccessful,
328 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
329 },
330 },
331 {
332 EventType: event.ActionGroupType,
333 ActionGroupEvent: &testutil.ExpActionGroupEvent{
334 GroupName: "apply-0",
335 Action: event.ApplyAction,
336 Type: event.Finished,
337 },
338 },
339 {
340 EventType: event.ActionGroupType,
341 ActionGroupEvent: &testutil.ExpActionGroupEvent{
342 GroupName: "wait-0",
343 Action: event.WaitAction,
344 Type: event.Started,
345 },
346 },
347
348 {
349 EventType: event.WaitType,
350 WaitEvent: &testutil.ExpWaitEvent{
351 GroupName: "wait-0",
352 Status: event.ReconcilePending,
353 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
354 },
355 },
356 {
357 EventType: event.WaitType,
358 WaitEvent: &testutil.ExpWaitEvent{
359 GroupName: "wait-0",
360 Status: event.ReconcilePending,
361 Identifier: testutil.ToIdentifier(t, resources["secret"]),
362 },
363 },
364
365 {
366 EventType: event.WaitType,
367 WaitEvent: &testutil.ExpWaitEvent{
368 GroupName: "wait-0",
369 Status: event.ReconcileSuccessful,
370 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
371 },
372 },
373 {
374 EventType: event.WaitType,
375 WaitEvent: &testutil.ExpWaitEvent{
376 GroupName: "wait-0",
377 Status: event.ReconcileSuccessful,
378 Identifier: testutil.ToIdentifier(t, resources["secret"]),
379 },
380 },
381 {
382 EventType: event.ActionGroupType,
383 ActionGroupEvent: &testutil.ExpActionGroupEvent{
384 GroupName: "wait-0",
385 Action: event.WaitAction,
386 Type: event.Finished,
387 },
388 },
389 {
390 EventType: event.ActionGroupType,
391 ActionGroupEvent: &testutil.ExpActionGroupEvent{
392 GroupName: "inventory-set-0",
393 Action: event.InventoryAction,
394 Type: event.Started,
395 },
396 },
397 {
398 EventType: event.ActionGroupType,
399 ActionGroupEvent: &testutil.ExpActionGroupEvent{
400 GroupName: "inventory-set-0",
401 Action: event.InventoryAction,
402 Type: event.Finished,
403 },
404 },
405 },
406 },
407 "apply multiple existing resources with status and prune": {
408 namespace: "default",
409 resources: object.UnstructuredSet{
410 testutil.Unstructured(t, resources["deployment"]),
411 testutil.Unstructured(t, resources["secret"]),
412 },
413 invInfo: inventoryInfo{
414 name: "inv-123",
415 namespace: "default",
416 id: "test",
417 set: object.ObjMetadataSet{
418 object.UnstructuredToObjMetadata(
419 testutil.Unstructured(t, resources["deployment"]),
420 ),
421 },
422 },
423 clusterObjs: object.UnstructuredSet{
424 testutil.Unstructured(t, resources["deployment"]),
425 },
426 options: ApplierOptions{
427 ReconcileTimeout: time.Minute,
428 InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
429 EmitStatusEvents: true,
430 },
431 statusEvents: []pollevent.Event{
432 {
433 Type: pollevent.ResourceUpdateEvent,
434 Resource: &pollevent.ResourceStatus{
435 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
436 Status: status.CurrentStatus,
437 Resource: testutil.Unstructured(t, resources["deployment"]),
438 },
439 },
440 {
441 Type: pollevent.ResourceUpdateEvent,
442 Resource: &pollevent.ResourceStatus{
443 Identifier: testutil.ToIdentifier(t, resources["secret"]),
444 Status: status.CurrentStatus,
445 Resource: testutil.Unstructured(t, resources["secret"]),
446 },
447 },
448 },
449 expectedStatusEvents: []testutil.ExpEvent{
450 {
451 EventType: event.StatusType,
452 StatusEvent: &testutil.ExpStatusEvent{
453 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
454 Status: status.CurrentStatus,
455 },
456 },
457 {
458 EventType: event.StatusType,
459 StatusEvent: &testutil.ExpStatusEvent{
460 Identifier: testutil.ToIdentifier(t, resources["secret"]),
461 Status: status.CurrentStatus,
462 },
463 },
464 },
465 expectedEvents: []testutil.ExpEvent{
466 {
467 EventType: event.InitType,
468 InitEvent: &testutil.ExpInitEvent{},
469 },
470 {
471 EventType: event.ActionGroupType,
472 ActionGroupEvent: &testutil.ExpActionGroupEvent{
473 GroupName: "inventory-add-0",
474 Action: event.InventoryAction,
475 Type: event.Started,
476 },
477 },
478 {
479 EventType: event.ActionGroupType,
480 ActionGroupEvent: &testutil.ExpActionGroupEvent{
481 GroupName: "inventory-add-0",
482 Action: event.InventoryAction,
483 Type: event.Finished,
484 },
485 },
486
487 {
488 EventType: event.ActionGroupType,
489 ActionGroupEvent: &testutil.ExpActionGroupEvent{
490 GroupName: "apply-0",
491 Action: event.ApplyAction,
492 Type: event.Started,
493 },
494 },
495
496 {
497 EventType: event.ApplyType,
498 ApplyEvent: &testutil.ExpApplyEvent{
499 GroupName: "apply-0",
500 Status: event.ApplySuccessful,
501 Identifier: testutil.ToIdentifier(t, resources["secret"]),
502 },
503 },
504 {
505 EventType: event.ApplyType,
506 ApplyEvent: &testutil.ExpApplyEvent{
507 GroupName: "apply-0",
508 Status: event.ApplySuccessful,
509 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
510 },
511 },
512 {
513 EventType: event.ActionGroupType,
514 ActionGroupEvent: &testutil.ExpActionGroupEvent{
515 GroupName: "apply-0",
516 Action: event.ApplyAction,
517 Type: event.Finished,
518 },
519 },
520 {
521 EventType: event.ActionGroupType,
522 ActionGroupEvent: &testutil.ExpActionGroupEvent{
523 GroupName: "wait-0",
524 Action: event.WaitAction,
525 Type: event.Started,
526 },
527 },
528
529 {
530 EventType: event.WaitType,
531 WaitEvent: &testutil.ExpWaitEvent{
532 GroupName: "wait-0",
533 Status: event.ReconcilePending,
534 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
535 },
536 },
537 {
538 EventType: event.WaitType,
539 WaitEvent: &testutil.ExpWaitEvent{
540 GroupName: "wait-0",
541 Status: event.ReconcilePending,
542 Identifier: testutil.ToIdentifier(t, resources["secret"]),
543 },
544 },
545
546 {
547 EventType: event.WaitType,
548 WaitEvent: &testutil.ExpWaitEvent{
549 GroupName: "wait-0",
550 Status: event.ReconcileSuccessful,
551 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
552 },
553 },
554 {
555 EventType: event.WaitType,
556 WaitEvent: &testutil.ExpWaitEvent{
557 GroupName: "wait-0",
558 Status: event.ReconcileSuccessful,
559 Identifier: testutil.ToIdentifier(t, resources["secret"]),
560 },
561 },
562 {
563 EventType: event.ActionGroupType,
564 ActionGroupEvent: &testutil.ExpActionGroupEvent{
565 GroupName: "wait-0",
566 Action: event.WaitAction,
567 Type: event.Finished,
568 },
569 },
570 {
571 EventType: event.ActionGroupType,
572 ActionGroupEvent: &testutil.ExpActionGroupEvent{
573 GroupName: "inventory-set-0",
574 Action: event.InventoryAction,
575 Type: event.Started,
576 },
577 },
578 {
579 EventType: event.ActionGroupType,
580 ActionGroupEvent: &testutil.ExpActionGroupEvent{
581 GroupName: "inventory-set-0",
582 Action: event.InventoryAction,
583 Type: event.Finished,
584 },
585 },
586 },
587 },
588 "apply no resources and prune all existing": {
589 namespace: "default",
590 resources: object.UnstructuredSet{},
591 invInfo: inventoryInfo{
592 name: "inv-123",
593 namespace: "default",
594 id: "test",
595 set: object.ObjMetadataSet{
596 object.UnstructuredToObjMetadata(
597 testutil.Unstructured(t, resources["deployment"]),
598 ),
599 object.UnstructuredToObjMetadata(
600 testutil.Unstructured(t, resources["secret"]),
601 ),
602 },
603 },
604 clusterObjs: object.UnstructuredSet{
605 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
606 testutil.Unstructured(t, resources["secret"], testutil.AddOwningInv(t, "test")),
607 },
608 options: ApplierOptions{
609 InventoryPolicy: inventory.PolicyMustMatch,
610 EmitStatusEvents: true,
611 },
612 statusEvents: []pollevent.Event{
613 {
614 Type: pollevent.ResourceUpdateEvent,
615 Resource: &pollevent.ResourceStatus{
616 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
617 Status: status.InProgressStatus,
618 },
619 },
620 {
621 Type: pollevent.ResourceUpdateEvent,
622 Resource: &pollevent.ResourceStatus{
623 Identifier: testutil.ToIdentifier(t, resources["secret"]),
624 Status: status.InProgressStatus,
625 },
626 },
627 {
628 Type: pollevent.ResourceUpdateEvent,
629 Resource: &pollevent.ResourceStatus{
630 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
631 Status: status.NotFoundStatus,
632 },
633 },
634 {
635 Type: pollevent.ResourceUpdateEvent,
636 Resource: &pollevent.ResourceStatus{
637 Identifier: testutil.ToIdentifier(t, resources["secret"]),
638 Status: status.NotFoundStatus,
639 },
640 },
641 },
642 expectedStatusEvents: []testutil.ExpEvent{
643 {
644 EventType: event.ActionGroupType,
645 ActionGroupEvent: &testutil.ExpActionGroupEvent{
646 GroupName: "inventory-add-0",
647 Action: event.InventoryAction,
648 Type: event.Started,
649 },
650 },
651 {
652 EventType: event.ActionGroupType,
653 ActionGroupEvent: &testutil.ExpActionGroupEvent{
654 GroupName: "inventory-add-0",
655 Action: event.InventoryAction,
656 Type: event.Finished,
657 },
658 },
659 {
660 EventType: event.StatusType,
661 StatusEvent: &testutil.ExpStatusEvent{
662 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
663 Status: status.InProgressStatus,
664 },
665 },
666 {
667 EventType: event.StatusType,
668 StatusEvent: &testutil.ExpStatusEvent{
669 Identifier: testutil.ToIdentifier(t, resources["secret"]),
670 Status: status.InProgressStatus,
671 },
672 },
673 {
674 EventType: event.StatusType,
675 StatusEvent: &testutil.ExpStatusEvent{
676 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
677 Status: status.NotFoundStatus,
678 },
679 },
680 {
681 EventType: event.StatusType,
682 StatusEvent: &testutil.ExpStatusEvent{
683 Identifier: testutil.ToIdentifier(t, resources["secret"]),
684 Status: status.NotFoundStatus,
685 },
686 },
687 },
688 expectedEvents: []testutil.ExpEvent{
689 {
690 EventType: event.InitType,
691 InitEvent: &testutil.ExpInitEvent{},
692 },
693 {
694 EventType: event.ActionGroupType,
695 ActionGroupEvent: &testutil.ExpActionGroupEvent{
696 GroupName: "prune-0",
697 Action: event.PruneAction,
698 Type: event.Started,
699 },
700 },
701
702 {
703 EventType: event.PruneType,
704 PruneEvent: &testutil.ExpPruneEvent{
705 GroupName: "prune-0",
706 Status: event.PruneSuccessful,
707 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
708 },
709 },
710 {
711 EventType: event.PruneType,
712 PruneEvent: &testutil.ExpPruneEvent{
713 GroupName: "prune-0",
714 Status: event.PruneSuccessful,
715 Identifier: testutil.ToIdentifier(t, resources["secret"]),
716 },
717 },
718 {
719 EventType: event.ActionGroupType,
720 ActionGroupEvent: &testutil.ExpActionGroupEvent{
721 GroupName: "prune-0",
722 Action: event.PruneAction,
723 Type: event.Finished,
724 },
725 },
726 {
727 EventType: event.ActionGroupType,
728 ActionGroupEvent: &testutil.ExpActionGroupEvent{
729 GroupName: "wait-0",
730 Action: event.WaitAction,
731 Type: event.Started,
732 },
733 },
734
735 {
736 EventType: event.WaitType,
737 WaitEvent: &testutil.ExpWaitEvent{
738 GroupName: "wait-0",
739 Status: event.ReconcilePending,
740 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
741 },
742 },
743 {
744 EventType: event.WaitType,
745 WaitEvent: &testutil.ExpWaitEvent{
746 GroupName: "wait-0",
747 Status: event.ReconcilePending,
748 Identifier: testutil.ToIdentifier(t, resources["secret"]),
749 },
750 },
751
752 {
753 EventType: event.WaitType,
754 WaitEvent: &testutil.ExpWaitEvent{
755 GroupName: "wait-0",
756 Status: event.ReconcileSuccessful,
757 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
758 },
759 },
760 {
761 EventType: event.WaitType,
762 WaitEvent: &testutil.ExpWaitEvent{
763 GroupName: "wait-0",
764 Status: event.ReconcileSuccessful,
765 Identifier: testutil.ToIdentifier(t, resources["secret"]),
766 },
767 },
768 {
769 EventType: event.ActionGroupType,
770 ActionGroupEvent: &testutil.ExpActionGroupEvent{
771 GroupName: "wait-0",
772 Action: event.WaitAction,
773 Type: event.Finished,
774 },
775 },
776 {
777 EventType: event.ActionGroupType,
778 ActionGroupEvent: &testutil.ExpActionGroupEvent{
779 GroupName: "inventory-set-0",
780 Action: event.InventoryAction,
781 Type: event.Started,
782 },
783 },
784 {
785 EventType: event.ActionGroupType,
786 ActionGroupEvent: &testutil.ExpActionGroupEvent{
787 GroupName: "inventory-set-0",
788 Action: event.InventoryAction,
789 Type: event.Finished,
790 },
791 },
792 },
793 },
794 "apply resource with existing object belonging to different inventory": {
795 namespace: "default",
796 resources: object.UnstructuredSet{
797 testutil.Unstructured(t, resources["deployment"]),
798 },
799 invInfo: inventoryInfo{
800 name: "abc-123",
801 namespace: "default",
802 id: "test",
803 },
804 clusterObjs: object.UnstructuredSet{
805 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "unmatched")),
806 },
807 options: ApplierOptions{
808 ReconcileTimeout: time.Minute,
809 InventoryPolicy: inventory.PolicyMustMatch,
810 EmitStatusEvents: true,
811 },
812
813
814
815
816 statusEvents: []pollevent.Event{},
817 expectedStatusEvents: []testutil.ExpEvent{},
818 expectedEvents: []testutil.ExpEvent{
819 {
820 EventType: event.InitType,
821 InitEvent: &testutil.ExpInitEvent{},
822 },
823 {
824 EventType: event.ActionGroupType,
825 ActionGroupEvent: &testutil.ExpActionGroupEvent{
826 GroupName: "inventory-add-0",
827 Action: event.InventoryAction,
828 Type: event.Started,
829 },
830 },
831 {
832 EventType: event.ActionGroupType,
833 ActionGroupEvent: &testutil.ExpActionGroupEvent{
834 GroupName: "inventory-add-0",
835 Action: event.InventoryAction,
836 Type: event.Finished,
837 },
838 },
839 {
840 EventType: event.ActionGroupType,
841 ActionGroupEvent: &testutil.ExpActionGroupEvent{
842 GroupName: "apply-0",
843 Action: event.ApplyAction,
844 Type: event.Started,
845 },
846 },
847 {
848 EventType: event.ApplyType,
849 ApplyEvent: &testutil.ExpApplyEvent{
850 GroupName: "apply-0",
851 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
852 Status: event.ApplySkipped,
853 Error: &inventory.PolicyPreventedActuationError{
854 Strategy: actuation.ActuationStrategyApply,
855 Policy: inventory.PolicyMustMatch,
856 Status: inventory.NoMatch,
857 },
858 },
859 },
860 {
861 EventType: event.ActionGroupType,
862 ActionGroupEvent: &testutil.ExpActionGroupEvent{
863 GroupName: "apply-0",
864 Action: event.ApplyAction,
865 Type: event.Finished,
866 },
867 },
868 {
869 EventType: event.ActionGroupType,
870 ActionGroupEvent: &testutil.ExpActionGroupEvent{
871 GroupName: "wait-0",
872 Action: event.WaitAction,
873 Type: event.Started,
874 },
875 },
876 {
877 EventType: event.WaitType,
878 WaitEvent: &testutil.ExpWaitEvent{
879 GroupName: "wait-0",
880 Status: event.ReconcileSkipped,
881 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
882 },
883 },
884 {
885 EventType: event.ActionGroupType,
886 ActionGroupEvent: &testutil.ExpActionGroupEvent{
887 GroupName: "wait-0",
888 Action: event.WaitAction,
889 Type: event.Finished,
890 },
891 },
892 {
893 EventType: event.ActionGroupType,
894 ActionGroupEvent: &testutil.ExpActionGroupEvent{
895 GroupName: "inventory-set-0",
896 Action: event.InventoryAction,
897 Type: event.Started,
898 },
899 },
900 {
901 EventType: event.ActionGroupType,
902 ActionGroupEvent: &testutil.ExpActionGroupEvent{
903 GroupName: "inventory-set-0",
904 Action: event.InventoryAction,
905 Type: event.Finished,
906 },
907 },
908 },
909 },
910 "resources belonging to a different inventory should not be pruned": {
911 namespace: "default",
912 resources: object.UnstructuredSet{},
913 invInfo: inventoryInfo{
914 name: "abc-123",
915 namespace: "default",
916 id: "test",
917 set: object.ObjMetadataSet{
918 object.UnstructuredToObjMetadata(
919 testutil.Unstructured(t, resources["deployment"]),
920 ),
921 },
922 },
923 clusterObjs: object.UnstructuredSet{
924 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "unmatched")),
925 },
926 options: ApplierOptions{
927 InventoryPolicy: inventory.PolicyMustMatch,
928 EmitStatusEvents: true,
929 },
930 expectedEvents: []testutil.ExpEvent{
931 {
932 EventType: event.InitType,
933 InitEvent: &testutil.ExpInitEvent{},
934 },
935 {
936 EventType: event.ActionGroupType,
937 ActionGroupEvent: &testutil.ExpActionGroupEvent{
938 GroupName: "inventory-add-0",
939 Action: event.InventoryAction,
940 Type: event.Started,
941 },
942 },
943 {
944 EventType: event.ActionGroupType,
945 ActionGroupEvent: &testutil.ExpActionGroupEvent{
946 GroupName: "inventory-add-0",
947 Action: event.InventoryAction,
948 Type: event.Finished,
949 },
950 },
951 {
952 EventType: event.ActionGroupType,
953 ActionGroupEvent: &testutil.ExpActionGroupEvent{
954 GroupName: "prune-0",
955 Action: event.PruneAction,
956 Type: event.Started,
957 },
958 },
959 {
960 EventType: event.PruneType,
961 PruneEvent: &testutil.ExpPruneEvent{
962 GroupName: "prune-0",
963 Status: event.PruneSkipped,
964 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
965 Error: &inventory.PolicyPreventedActuationError{
966 Strategy: actuation.ActuationStrategyDelete,
967 Policy: inventory.PolicyMustMatch,
968 Status: inventory.NoMatch,
969 },
970 },
971 },
972 {
973 EventType: event.ActionGroupType,
974 ActionGroupEvent: &testutil.ExpActionGroupEvent{
975 GroupName: "prune-0",
976 Action: event.PruneAction,
977 Type: event.Finished,
978 },
979 },
980 {
981 EventType: event.ActionGroupType,
982 ActionGroupEvent: &testutil.ExpActionGroupEvent{
983 GroupName: "wait-0",
984 Action: event.WaitAction,
985 Type: event.Started,
986 },
987 },
988 {
989 EventType: event.WaitType,
990 WaitEvent: &testutil.ExpWaitEvent{
991 GroupName: "wait-0",
992 Status: event.ReconcileSkipped,
993 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
994 },
995 },
996 {
997 EventType: event.ActionGroupType,
998 ActionGroupEvent: &testutil.ExpActionGroupEvent{
999 GroupName: "wait-0",
1000 Action: event.WaitAction,
1001 Type: event.Finished,
1002 },
1003 },
1004 {
1005 EventType: event.ActionGroupType,
1006 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1007 GroupName: "inventory-set-0",
1008 Action: event.InventoryAction,
1009 Type: event.Started,
1010 },
1011 },
1012 {
1013 EventType: event.ActionGroupType,
1014 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1015 GroupName: "inventory-set-0",
1016 Action: event.InventoryAction,
1017 Type: event.Finished,
1018 },
1019 },
1020 },
1021 },
1022 "prune with inventory object annotation matched": {
1023 namespace: "default",
1024 resources: object.UnstructuredSet{},
1025 invInfo: inventoryInfo{
1026 name: "abc-123",
1027 namespace: "default",
1028 id: "test",
1029 set: object.ObjMetadataSet{
1030 object.UnstructuredToObjMetadata(
1031 testutil.Unstructured(t, resources["deployment"]),
1032 ),
1033 },
1034 },
1035 clusterObjs: object.UnstructuredSet{
1036 testutil.Unstructured(t, resources["deployment"], testutil.AddOwningInv(t, "test")),
1037 },
1038 options: ApplierOptions{
1039 InventoryPolicy: inventory.PolicyMustMatch,
1040 EmitStatusEvents: true,
1041 },
1042 statusEvents: []pollevent.Event{
1043 {
1044 Type: pollevent.ResourceUpdateEvent,
1045 Resource: &pollevent.ResourceStatus{
1046 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1047 Status: status.InProgressStatus,
1048 },
1049 },
1050 {
1051 Type: pollevent.ResourceUpdateEvent,
1052 Resource: &pollevent.ResourceStatus{
1053 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1054 Status: status.NotFoundStatus,
1055 },
1056 },
1057 },
1058 expectedStatusEvents: []testutil.ExpEvent{
1059 {
1060 EventType: event.StatusType,
1061 StatusEvent: &testutil.ExpStatusEvent{
1062 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1063 Status: status.InProgressStatus,
1064 },
1065 },
1066 {
1067 EventType: event.StatusType,
1068 StatusEvent: &testutil.ExpStatusEvent{
1069 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1070 Status: status.NotFoundStatus,
1071 },
1072 },
1073 },
1074 expectedEvents: []testutil.ExpEvent{
1075 {
1076 EventType: event.InitType,
1077 InitEvent: &testutil.ExpInitEvent{},
1078 },
1079 {
1080 EventType: event.ActionGroupType,
1081 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1082 GroupName: "inventory-add-0",
1083 Action: event.InventoryAction,
1084 Type: event.Started,
1085 },
1086 },
1087 {
1088 EventType: event.ActionGroupType,
1089 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1090 GroupName: "inventory-add-0",
1091 Action: event.InventoryAction,
1092 Type: event.Finished,
1093 },
1094 },
1095 {
1096 EventType: event.ActionGroupType,
1097 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1098 GroupName: "prune-0",
1099 Action: event.PruneAction,
1100 Type: event.Started,
1101 },
1102 },
1103 {
1104 EventType: event.PruneType,
1105 PruneEvent: &testutil.ExpPruneEvent{
1106 GroupName: "prune-0",
1107 Status: event.PruneSuccessful,
1108 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1109 },
1110 },
1111 {
1112 EventType: event.ActionGroupType,
1113 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1114 GroupName: "prune-0",
1115 Action: event.PruneAction,
1116 Type: event.Finished,
1117 },
1118 },
1119 {
1120 EventType: event.ActionGroupType,
1121 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1122 GroupName: "wait-0",
1123 Action: event.WaitAction,
1124 Type: event.Started,
1125 },
1126 },
1127
1128 {
1129 EventType: event.WaitType,
1130 WaitEvent: &testutil.ExpWaitEvent{
1131 GroupName: "wait-0",
1132 Status: event.ReconcilePending,
1133 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1134 },
1135 },
1136 {
1137 EventType: event.WaitType,
1138 WaitEvent: &testutil.ExpWaitEvent{
1139 GroupName: "wait-0",
1140 Status: event.ReconcileSuccessful,
1141 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1142 },
1143 },
1144 {
1145 EventType: event.ActionGroupType,
1146 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1147 GroupName: "wait-0",
1148 Action: event.WaitAction,
1149 Type: event.Finished,
1150 },
1151 },
1152 {
1153 EventType: event.ActionGroupType,
1154 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1155 GroupName: "inventory-set-0",
1156 Action: event.InventoryAction,
1157 Type: event.Started,
1158 },
1159 },
1160 {
1161 EventType: event.ActionGroupType,
1162 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1163 GroupName: "inventory-set-0",
1164 Action: event.InventoryAction,
1165 Type: event.Finished,
1166 },
1167 },
1168 },
1169 },
1170 "SkipInvalid - skip invalid objects and apply valid objects": {
1171 namespace: "default",
1172 resources: object.UnstructuredSet{
1173 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1174 "$.metadata.name", "",
1175 }),
1176 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1177 "$.kind", "",
1178 }),
1179 testutil.Unstructured(t, resources["secret"]),
1180 },
1181 invInfo: inventoryInfo{
1182 name: "inv-123",
1183 namespace: "default",
1184 id: "test",
1185 },
1186 clusterObjs: object.UnstructuredSet{},
1187 options: ApplierOptions{
1188 ReconcileTimeout: time.Minute,
1189 InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
1190 EmitStatusEvents: true,
1191 ValidationPolicy: validation.SkipInvalid,
1192 },
1193 statusEvents: []pollevent.Event{
1194 {
1195 Type: pollevent.ResourceUpdateEvent,
1196 Resource: &pollevent.ResourceStatus{
1197 Identifier: testutil.ToIdentifier(t, resources["secret"]),
1198 Status: status.CurrentStatus,
1199 Resource: testutil.Unstructured(t, resources["secret"]),
1200 },
1201 },
1202 },
1203 expectedStatusEvents: []testutil.ExpEvent{
1204 {
1205 EventType: event.StatusType,
1206 StatusEvent: &testutil.ExpStatusEvent{
1207 Identifier: testutil.ToIdentifier(t, resources["secret"]),
1208 Status: status.CurrentStatus,
1209 },
1210 },
1211 },
1212 expectedEvents: []testutil.ExpEvent{
1213 {
1214 EventType: event.ValidationType,
1215 ValidationEvent: &testutil.ExpValidationEvent{
1216 Identifiers: object.ObjMetadataSet{
1217 object.UnstructuredToObjMetadata(
1218 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1219 "$.metadata.name", "",
1220 }),
1221 ),
1222 },
1223 Error: testutil.EqualErrorString(validation.NewError(
1224 field.Required(field.NewPath("metadata", "name"), "name is required"),
1225 object.UnstructuredToObjMetadata(
1226 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1227 "$.metadata.name", "",
1228 }),
1229 ),
1230 ).Error()),
1231 },
1232 },
1233 {
1234 EventType: event.ValidationType,
1235 ValidationEvent: &testutil.ExpValidationEvent{
1236 Identifiers: object.ObjMetadataSet{
1237 object.UnstructuredToObjMetadata(
1238 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1239 "$.kind", "",
1240 }),
1241 ),
1242 },
1243 Error: testutil.EqualErrorString(validation.NewError(
1244 field.Required(field.NewPath("kind"), "kind is required"),
1245 object.UnstructuredToObjMetadata(
1246 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1247 "$.kind", "",
1248 }),
1249 ),
1250 ).Error()),
1251 },
1252 },
1253 {
1254 EventType: event.InitType,
1255 InitEvent: &testutil.ExpInitEvent{},
1256 },
1257 {
1258 EventType: event.ActionGroupType,
1259 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1260 GroupName: "inventory-add-0",
1261 Action: event.InventoryAction,
1262 Type: event.Started,
1263 },
1264 },
1265 {
1266 EventType: event.ActionGroupType,
1267 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1268 GroupName: "inventory-add-0",
1269 Action: event.InventoryAction,
1270 Type: event.Finished,
1271 },
1272 },
1273
1274 {
1275 EventType: event.ActionGroupType,
1276 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1277 GroupName: "apply-0",
1278 Action: event.ApplyAction,
1279 Type: event.Started,
1280 },
1281 },
1282
1283 {
1284 EventType: event.ApplyType,
1285 ApplyEvent: &testutil.ExpApplyEvent{
1286 GroupName: "apply-0",
1287 Status: event.ApplySuccessful,
1288 Identifier: testutil.ToIdentifier(t, resources["secret"]),
1289 },
1290 },
1291 {
1292 EventType: event.ActionGroupType,
1293 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1294 GroupName: "apply-0",
1295 Action: event.ApplyAction,
1296 Type: event.Finished,
1297 },
1298 },
1299 {
1300 EventType: event.ActionGroupType,
1301 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1302 GroupName: "wait-0",
1303 Action: event.WaitAction,
1304 Type: event.Started,
1305 },
1306 },
1307
1308 {
1309 EventType: event.WaitType,
1310 WaitEvent: &testutil.ExpWaitEvent{
1311 GroupName: "wait-0",
1312 Status: event.ReconcilePending,
1313 Identifier: testutil.ToIdentifier(t, resources["secret"]),
1314 },
1315 },
1316 {
1317 EventType: event.WaitType,
1318 WaitEvent: &testutil.ExpWaitEvent{
1319 GroupName: "wait-0",
1320 Status: event.ReconcileSuccessful,
1321 Identifier: testutil.ToIdentifier(t, resources["secret"]),
1322 },
1323 },
1324 {
1325 EventType: event.ActionGroupType,
1326 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1327 GroupName: "wait-0",
1328 Action: event.WaitAction,
1329 Type: event.Finished,
1330 },
1331 },
1332 {
1333 EventType: event.ActionGroupType,
1334 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1335 GroupName: "inventory-set-0",
1336 Action: event.InventoryAction,
1337 Type: event.Started,
1338 },
1339 },
1340 {
1341 EventType: event.ActionGroupType,
1342 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1343 GroupName: "inventory-set-0",
1344 Action: event.InventoryAction,
1345 Type: event.Finished,
1346 },
1347 },
1348 },
1349 },
1350 "ExitEarly - exit early on invalid objects and skip valid objects": {
1351 namespace: "default",
1352 resources: object.UnstructuredSet{
1353 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1354 "$.metadata.name", "",
1355 }),
1356 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1357 "$.kind", "",
1358 }),
1359 testutil.Unstructured(t, resources["secret"]),
1360 },
1361 invInfo: inventoryInfo{
1362 name: "inv-123",
1363 namespace: "default",
1364 id: "test",
1365 },
1366 clusterObjs: object.UnstructuredSet{},
1367 options: ApplierOptions{
1368 ReconcileTimeout: time.Minute,
1369 InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
1370 EmitStatusEvents: true,
1371 ValidationPolicy: validation.ExitEarly,
1372 },
1373 statusEvents: []pollevent.Event{},
1374 expectedStatusEvents: []testutil.ExpEvent{},
1375 expectedEvents: []testutil.ExpEvent{
1376 {
1377 EventType: event.ErrorType,
1378 ErrorEvent: &testutil.ExpErrorEvent{
1379 Err: testutil.EqualErrorString(multierror.New(
1380 validation.NewError(
1381 field.Required(field.NewPath("metadata", "name"), "name is required"),
1382 object.UnstructuredToObjMetadata(
1383 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1384 "$.metadata.name", "",
1385 }),
1386 ),
1387 ),
1388 validation.NewError(
1389 field.Required(field.NewPath("kind"), "kind is required"),
1390 object.UnstructuredToObjMetadata(
1391 testutil.Unstructured(t, resources["deployment"], JSONPathSetter{
1392 "$.kind", "",
1393 }),
1394 ),
1395 ),
1396 ).Error()),
1397 },
1398 },
1399 },
1400 },
1401 }
1402
1403 for tn, tc := range testCases {
1404 t.Run(tn, func(t *testing.T) {
1405 statusWatcher := newFakeWatcher(tc.statusEvents)
1406
1407
1408
1409 validObjs := object.UnstructuredSet{}
1410 for _, obj := range tc.resources {
1411 id := object.UnstructuredToObjMetadata(obj)
1412 if id.GroupKind.Kind == "" || id.Name == "" {
1413 continue
1414 }
1415 validObjs = append(validObjs, obj)
1416 }
1417
1418 applier := newTestApplier(t,
1419 tc.invInfo,
1420 validObjs,
1421 tc.clusterObjs,
1422 statusWatcher,
1423 )
1424
1425
1426 runCtx, runCancel := context.WithCancel(context.Background())
1427 defer runCancel()
1428
1429
1430 testTimeout := 10 * time.Second
1431 testCtx, testCancel := context.WithTimeout(context.Background(), testTimeout)
1432 defer testCancel()
1433
1434 eventChannel := applier.Run(runCtx, tc.invInfo.toWrapped(), tc.resources, tc.options)
1435
1436
1437 var once sync.Once
1438
1439 var events []event.Event
1440
1441 loop:
1442 for {
1443 select {
1444 case <-testCtx.Done():
1445
1446 runCancel()
1447 if tc.expectTestTimeout {
1448 assert.Equal(t, context.DeadlineExceeded, testCtx.Err(), "Applier.Run failed to exit, but not because of expected timeout")
1449 } else {
1450 t.Errorf("Applier.Run failed to exit (timeout: %s)", testTimeout)
1451 }
1452 break loop
1453
1454 case e, ok := <-eventChannel:
1455 if !ok {
1456
1457 testCancel()
1458 break loop
1459 }
1460 if e.Type == event.ActionGroupType &&
1461 e.ActionGroupEvent.Status == event.Finished {
1462
1463 if e.ActionGroupEvent.Action == event.ApplyAction ||
1464 e.ActionGroupEvent.Action == event.PruneAction {
1465 once.Do(func() {
1466
1467 statusWatcher.Start()
1468 })
1469 }
1470 }
1471 events = append(events, e)
1472 }
1473 }
1474
1475
1476 receivedEvents := testutil.EventsToExpEvents(events)
1477
1478
1479 for _, e := range tc.expectedStatusEvents {
1480 var removed int
1481 receivedEvents, removed = testutil.RemoveEqualEvents(receivedEvents, e)
1482 if removed < 1 {
1483 t.Errorf("Expected status event not received: %#v", e.StatusEvent)
1484 }
1485 }
1486
1487
1488 testutil.SortExpEvents(receivedEvents)
1489
1490
1491 testutil.AssertEqual(t, tc.expectedEvents, receivedEvents,
1492 "Actual events (%d) do not match expected events (%d)",
1493 len(receivedEvents), len(tc.expectedEvents))
1494
1495
1496
1497 switch {
1498 case tc.expectRunTimeout:
1499 assert.Equal(t, context.DeadlineExceeded, runCtx.Err(), "Applier.Run exited, but not by expected context timeout")
1500 case tc.expectTestTimeout:
1501 assert.Equal(t, context.Canceled, runCtx.Err(), "Applier.Run exited, but not because of expected context cancellation")
1502 default:
1503 assert.Nil(t, runCtx.Err(), "Applier.Run exited, but context error is not nil")
1504 }
1505 })
1506 }
1507 }
1508
1509 func TestApplierCancel(t *testing.T) {
1510 testCases := map[string]struct {
1511
1512 resources object.UnstructuredSet
1513
1514 invInfo inventoryInfo
1515
1516 clusterObjs object.UnstructuredSet
1517
1518 options ApplierOptions
1519
1520 runTimeout time.Duration
1521
1522 testTimeout time.Duration
1523
1524 statusEvents []pollevent.Event
1525
1526 expectedStatusEvents []testutil.ExpEvent
1527
1528 expectedEvents []testutil.ExpEvent
1529
1530 expectRunTimeout bool
1531 }{
1532 "cancelled by caller while waiting for reconcile": {
1533 expectRunTimeout: true,
1534 runTimeout: 2 * time.Second,
1535 testTimeout: 30 * time.Second,
1536 resources: object.UnstructuredSet{
1537 testutil.Unstructured(t, resources["deployment"]),
1538 },
1539 invInfo: inventoryInfo{
1540 name: "abc-123",
1541 namespace: "test",
1542 id: "test",
1543 },
1544 clusterObjs: object.UnstructuredSet{},
1545 options: ApplierOptions{
1546
1547 EmitStatusEvents: true,
1548 NoPrune: true,
1549 InventoryPolicy: inventory.PolicyMustMatch,
1550
1551 ReconcileTimeout: 1 * time.Minute,
1552 },
1553 statusEvents: []pollevent.Event{
1554 {
1555 Type: pollevent.ResourceUpdateEvent,
1556 Resource: &pollevent.ResourceStatus{
1557 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1558 Status: status.InProgressStatus,
1559 Resource: testutil.Unstructured(t, resources["deployment"]),
1560 },
1561 },
1562 {
1563 Type: pollevent.ResourceUpdateEvent,
1564 Resource: &pollevent.ResourceStatus{
1565 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1566 Status: status.InProgressStatus,
1567 Resource: testutil.Unstructured(t, resources["deployment"]),
1568 },
1569 },
1570
1571 },
1572 expectedStatusEvents: []testutil.ExpEvent{
1573 {
1574 EventType: event.StatusType,
1575 StatusEvent: &testutil.ExpStatusEvent{
1576 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1577 Status: status.InProgressStatus,
1578 },
1579 },
1580 },
1581 expectedEvents: []testutil.ExpEvent{
1582 {
1583
1584 EventType: event.InitType,
1585 InitEvent: &testutil.ExpInitEvent{},
1586 },
1587 {
1588
1589 EventType: event.ActionGroupType,
1590 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1591 Action: event.InventoryAction,
1592 GroupName: "inventory-add-0",
1593 Type: event.Started,
1594 },
1595 },
1596 {
1597
1598 EventType: event.ActionGroupType,
1599 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1600 Action: event.InventoryAction,
1601 GroupName: "inventory-add-0",
1602 Type: event.Finished,
1603 },
1604 },
1605 {
1606
1607 EventType: event.ActionGroupType,
1608 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1609 Action: event.ApplyAction,
1610 GroupName: "apply-0",
1611 Type: event.Started,
1612 },
1613 },
1614 {
1615
1616 EventType: event.ApplyType,
1617 ApplyEvent: &testutil.ExpApplyEvent{
1618 GroupName: "apply-0",
1619 Status: event.ApplySuccessful,
1620 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1621 },
1622 },
1623 {
1624
1625 EventType: event.ActionGroupType,
1626 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1627 Action: event.ApplyAction,
1628 GroupName: "apply-0",
1629 Type: event.Finished,
1630 },
1631 },
1632 {
1633
1634 EventType: event.ActionGroupType,
1635 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1636 Action: event.WaitAction,
1637 GroupName: "wait-0",
1638 Type: event.Started,
1639 },
1640 },
1641 {
1642
1643 EventType: event.WaitType,
1644 WaitEvent: &testutil.ExpWaitEvent{
1645 GroupName: "wait-0",
1646 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1647 Status: event.ReconcilePending,
1648 },
1649 },
1650
1651
1652
1653 {
1654
1655 EventType: event.ActionGroupType,
1656 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1657 Action: event.WaitAction,
1658 GroupName: "wait-0",
1659 Type: event.Finished,
1660 },
1661 },
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681 {
1682
1683 EventType: event.ErrorType,
1684 ErrorEvent: &testutil.ExpErrorEvent{
1685 Err: context.DeadlineExceeded,
1686 },
1687 },
1688 },
1689 },
1690 "completed with timeout": {
1691 expectRunTimeout: false,
1692 runTimeout: 10 * time.Second,
1693 testTimeout: 30 * time.Second,
1694 resources: object.UnstructuredSet{
1695 testutil.Unstructured(t, resources["deployment"]),
1696 },
1697 invInfo: inventoryInfo{
1698 name: "abc-123",
1699 namespace: "test",
1700 id: "test",
1701 },
1702 clusterObjs: object.UnstructuredSet{},
1703 options: ApplierOptions{
1704
1705 EmitStatusEvents: true,
1706 NoPrune: true,
1707 InventoryPolicy: inventory.PolicyMustMatch,
1708
1709 ReconcileTimeout: 1 * time.Minute,
1710 },
1711 statusEvents: []pollevent.Event{
1712 {
1713 Type: pollevent.ResourceUpdateEvent,
1714 Resource: &pollevent.ResourceStatus{
1715 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1716 Status: status.InProgressStatus,
1717 Resource: testutil.Unstructured(t, resources["deployment"]),
1718 },
1719 },
1720 {
1721 Type: pollevent.ResourceUpdateEvent,
1722 Resource: &pollevent.ResourceStatus{
1723 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1724 Status: status.CurrentStatus,
1725 Resource: testutil.Unstructured(t, resources["deployment"]),
1726 },
1727 },
1728
1729 },
1730 expectedStatusEvents: []testutil.ExpEvent{
1731 {
1732 EventType: event.StatusType,
1733 StatusEvent: &testutil.ExpStatusEvent{
1734 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1735 Status: status.InProgressStatus,
1736 },
1737 },
1738 {
1739 EventType: event.StatusType,
1740 StatusEvent: &testutil.ExpStatusEvent{
1741 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1742 Status: status.CurrentStatus,
1743 },
1744 },
1745 },
1746 expectedEvents: []testutil.ExpEvent{
1747 {
1748
1749 EventType: event.InitType,
1750 InitEvent: &testutil.ExpInitEvent{},
1751 },
1752 {
1753
1754 EventType: event.ActionGroupType,
1755 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1756 Action: event.InventoryAction,
1757 GroupName: "inventory-add-0",
1758 Type: event.Started,
1759 },
1760 },
1761 {
1762
1763 EventType: event.ActionGroupType,
1764 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1765 Action: event.InventoryAction,
1766 GroupName: "inventory-add-0",
1767 Type: event.Finished,
1768 },
1769 },
1770 {
1771
1772 EventType: event.ActionGroupType,
1773 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1774 Action: event.ApplyAction,
1775 GroupName: "apply-0",
1776 Type: event.Started,
1777 },
1778 },
1779 {
1780
1781 EventType: event.ApplyType,
1782 ApplyEvent: &testutil.ExpApplyEvent{
1783 GroupName: "apply-0",
1784 Status: event.ApplySuccessful,
1785 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1786 },
1787 },
1788 {
1789
1790 EventType: event.ActionGroupType,
1791 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1792 Action: event.ApplyAction,
1793 GroupName: "apply-0",
1794 Type: event.Finished,
1795 },
1796 },
1797 {
1798
1799 EventType: event.ActionGroupType,
1800 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1801 Action: event.WaitAction,
1802 GroupName: "wait-0",
1803 Type: event.Started,
1804 },
1805 },
1806
1807 {
1808
1809 EventType: event.WaitType,
1810 WaitEvent: &testutil.ExpWaitEvent{
1811 GroupName: "wait-0",
1812 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1813 Status: event.ReconcilePending,
1814 },
1815 },
1816 {
1817
1818 EventType: event.WaitType,
1819 WaitEvent: &testutil.ExpWaitEvent{
1820 GroupName: "wait-0",
1821 Identifier: testutil.ToIdentifier(t, resources["deployment"]),
1822 Status: event.ReconcileSuccessful,
1823 },
1824 },
1825 {
1826
1827 EventType: event.ActionGroupType,
1828 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1829 Action: event.WaitAction,
1830 GroupName: "wait-0",
1831 Type: event.Finished,
1832 },
1833 },
1834 {
1835
1836 EventType: event.ActionGroupType,
1837 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1838 Action: event.InventoryAction,
1839 GroupName: "inventory-set-0",
1840 Type: event.Started,
1841 },
1842 },
1843 {
1844
1845 EventType: event.ActionGroupType,
1846 ActionGroupEvent: &testutil.ExpActionGroupEvent{
1847 Action: event.InventoryAction,
1848 GroupName: "inventory-set-0",
1849 Type: event.Finished,
1850 },
1851 },
1852 },
1853 },
1854 }
1855
1856 for tn, tc := range testCases {
1857 t.Run(tn, func(t *testing.T) {
1858 statusWatcher := newFakeWatcher(tc.statusEvents)
1859
1860 applier := newTestApplier(t,
1861 tc.invInfo,
1862 tc.resources,
1863 tc.clusterObjs,
1864 statusWatcher,
1865 )
1866
1867
1868 runCtx, runCancel := context.WithTimeout(context.Background(), tc.runTimeout)
1869 defer runCancel()
1870
1871
1872 testCtx, testCancel := context.WithTimeout(context.Background(), tc.testTimeout)
1873 defer testCancel()
1874
1875 eventChannel := applier.Run(runCtx, tc.invInfo.toWrapped(), tc.resources, tc.options)
1876
1877
1878 var once sync.Once
1879
1880 var events []event.Event
1881
1882 loop:
1883 for {
1884 select {
1885 case <-testCtx.Done():
1886
1887 runCancel()
1888 t.Errorf("Applier.Run failed to respond to cancellation (expected: %s, timeout: %s)", tc.runTimeout, tc.testTimeout)
1889 break loop
1890
1891 case e, ok := <-eventChannel:
1892 if !ok {
1893
1894 testCancel()
1895 break loop
1896 }
1897 events = append(events, e)
1898
1899 if e.Type == event.ActionGroupType &&
1900 e.ActionGroupEvent.Status == event.Finished {
1901
1902 if e.ActionGroupEvent.Action == event.ApplyAction ||
1903 e.ActionGroupEvent.Action == event.PruneAction {
1904 once.Do(func() {
1905
1906 statusWatcher.Start()
1907 })
1908 }
1909 }
1910 }
1911 }
1912
1913
1914 receivedEvents := testutil.EventsToExpEvents(events)
1915
1916
1917 for _, e := range tc.expectedStatusEvents {
1918 var removed int
1919 receivedEvents, removed = testutil.RemoveEqualEvents(receivedEvents, e)
1920 if removed < 1 {
1921 t.Errorf("Expected status event not received: %#v", e.StatusEvent)
1922 }
1923 }
1924
1925
1926 testutil.SortExpEvents(receivedEvents)
1927
1928
1929 testutil.AssertEqual(t, tc.expectedEvents, receivedEvents,
1930 "Actual events (%d) do not match expected events (%d)",
1931 len(receivedEvents), len(tc.expectedEvents))
1932
1933
1934
1935 if tc.expectRunTimeout {
1936 assert.Equal(t, context.DeadlineExceeded, runCtx.Err(), "Applier.Run exited, but not by expected timeout")
1937 } else {
1938 assert.NoError(t, runCtx.Err(), "Applier.Run exited, but not by expected timeout")
1939 }
1940 })
1941 }
1942 }
1943
1944 func TestReadAndPrepareObjectsNilInv(t *testing.T) {
1945 applier := Applier{}
1946 _, _, err := applier.prepareObjects(nil, object.UnstructuredSet{}, ApplierOptions{})
1947 assert.Error(t, err)
1948 }
1949
1950 func TestReadAndPrepareObjects(t *testing.T) {
1951 inventoryObj := testutil.Unstructured(t, resources["inventory"])
1952 inventory := inventory.WrapInventoryInfoObj(inventoryObj)
1953
1954 obj1 := testutil.Unstructured(t, resources["obj1"])
1955 obj2 := testutil.Unstructured(t, resources["obj2"])
1956 clusterScopedObj := testutil.Unstructured(t, resources["clusterScopedObj"])
1957
1958 testCases := map[string]struct {
1959
1960 clusterObjs object.UnstructuredSet
1961
1962 invInfo inventoryInfo
1963
1964 resources object.UnstructuredSet
1965
1966 applyObjs object.UnstructuredSet
1967
1968 pruneObjs object.UnstructuredSet
1969
1970 isError bool
1971 }{
1972 "objects include inventory": {
1973 invInfo: inventoryInfo{
1974 name: inventory.Name(),
1975 namespace: inventory.Namespace(),
1976 id: inventory.ID(),
1977 },
1978 resources: object.UnstructuredSet{inventoryObj},
1979 isError: true,
1980 },
1981 "empty inventory, empty objects, apply none, prune none": {
1982 invInfo: inventoryInfo{
1983 name: inventory.Name(),
1984 namespace: inventory.Namespace(),
1985 id: inventory.ID(),
1986 },
1987 },
1988 "one in inventory, empty objects, prune one": {
1989 clusterObjs: object.UnstructuredSet{obj1},
1990 invInfo: inventoryInfo{
1991 name: inventory.Name(),
1992 namespace: inventory.Namespace(),
1993 id: inventory.ID(),
1994 set: object.ObjMetadataSet{
1995 object.UnstructuredToObjMetadata(obj1),
1996 },
1997 },
1998 pruneObjs: object.UnstructuredSet{obj1},
1999 },
2000 "all in inventory, apply all": {
2001 invInfo: inventoryInfo{
2002 name: inventory.Name(),
2003 namespace: inventory.Namespace(),
2004 id: inventory.ID(),
2005 set: object.ObjMetadataSet{
2006 object.UnstructuredToObjMetadata(obj1),
2007 object.UnstructuredToObjMetadata(clusterScopedObj),
2008 },
2009 },
2010 resources: object.UnstructuredSet{obj1, clusterScopedObj},
2011 applyObjs: object.UnstructuredSet{obj1, clusterScopedObj},
2012 },
2013 "disjoint set, apply new, prune old": {
2014 clusterObjs: object.UnstructuredSet{obj2},
2015 invInfo: inventoryInfo{
2016 name: inventory.Name(),
2017 namespace: inventory.Namespace(),
2018 id: inventory.ID(),
2019 set: object.ObjMetadataSet{
2020 object.UnstructuredToObjMetadata(obj2),
2021 },
2022 },
2023 resources: object.UnstructuredSet{obj1, clusterScopedObj},
2024 applyObjs: object.UnstructuredSet{obj1, clusterScopedObj},
2025 pruneObjs: object.UnstructuredSet{obj2},
2026 },
2027 "most in inventory, apply all": {
2028 clusterObjs: object.UnstructuredSet{obj2},
2029 invInfo: inventoryInfo{
2030 name: inventory.Name(),
2031 namespace: inventory.Namespace(),
2032 id: inventory.ID(),
2033 set: object.ObjMetadataSet{
2034 object.UnstructuredToObjMetadata(obj2),
2035 },
2036 },
2037 resources: object.UnstructuredSet{obj1, obj2, clusterScopedObj},
2038 applyObjs: object.UnstructuredSet{obj1, obj2, clusterScopedObj},
2039 pruneObjs: object.UnstructuredSet{},
2040 },
2041 }
2042
2043 for name, tc := range testCases {
2044 t.Run(name, func(t *testing.T) {
2045 applier := newTestApplier(t,
2046 tc.invInfo,
2047 tc.resources,
2048 tc.clusterObjs,
2049
2050 watcher.BlindStatusWatcher{},
2051 )
2052
2053 applyObjs, pruneObjs, err := applier.prepareObjects(tc.invInfo.toWrapped(), tc.resources, ApplierOptions{})
2054 if tc.isError {
2055 assert.Error(t, err)
2056 return
2057 }
2058 require.NoError(t, err)
2059
2060 testutil.AssertEqual(t, applyObjs, tc.applyObjs,
2061 "Actual applied objects (%d) do not match expected applied objects (%d)",
2062 len(applyObjs), len(tc.applyObjs))
2063
2064 testutil.AssertEqual(t, pruneObjs, tc.pruneObjs,
2065 "Actual pruned objects (%d) do not match expected pruned objects (%d)",
2066 len(pruneObjs), len(tc.pruneObjs))
2067 })
2068 }
2069 }
2070
View as plain text