1
2
3
4 package watcher
5
6 import (
7 "context"
8 "testing"
9 "time"
10
11 "github.com/stretchr/testify/require"
12 appsv1 "k8s.io/api/apps/v1"
13 v1 "k8s.io/api/core/v1"
14 "k8s.io/apimachinery/pkg/api/meta"
15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16 "k8s.io/apimachinery/pkg/runtime"
17 "k8s.io/apimachinery/pkg/runtime/schema"
18 "k8s.io/apimachinery/pkg/util/yaml"
19 "k8s.io/apimachinery/pkg/watch"
20 dynamicfake "k8s.io/client-go/dynamic/fake"
21 clienttesting "k8s.io/client-go/testing"
22 "k8s.io/klog/v2"
23 "k8s.io/kubectl/pkg/scheme"
24 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
25 "sigs.k8s.io/cli-utils/pkg/kstatus/status"
26 "sigs.k8s.io/cli-utils/pkg/object"
27 "sigs.k8s.io/cli-utils/pkg/testutil"
28 )
29
30 var deployment1Yaml = `
31 apiVersion: apps/v1
32 kind: Deployment
33 metadata:
34 name: nginx
35 generation: 1
36 spec:
37 replicas: 1
38 selector:
39 matchLabels:
40 app: nginx
41 template:
42 metadata:
43 labels:
44 app: nginx
45 spec:
46 containers:
47 - name: nginx
48 image: nginx:1.19.6
49 ports:
50 - containerPort: 80
51 `
52
53 var deployment1InProgress1Yaml = `
54 apiVersion: apps/v1
55 kind: Deployment
56 metadata:
57 name: nginx
58 generation: 1
59 spec:
60 replicas: 1
61 selector:
62 matchLabels:
63 app: nginx
64 template:
65 metadata:
66 labels:
67 app: nginx
68 spec:
69 containers:
70 - name: nginx
71 image: nginx:1.19.6
72 ports:
73 - containerPort: 80
74 status:
75 observedGeneration: 1
76 updatedReplicas: 0
77 readyReplicas: 0
78 availableReplicas: 0
79 replicas: 0
80 conditions:
81 - reason: NewReplicaSetAvailable
82 status: "True"
83 type: Progressing
84 message: ReplicaSet "nginx-1" is progressing.
85 - reason: MinimumReplicasUnavailable
86 type: Available
87 status: "False"
88 message: Deployment does not have minimum availability.
89 `
90
91 var deployment1InProgress2Yaml = `
92 apiVersion: apps/v1
93 kind: Deployment
94 metadata:
95 name: nginx
96 generation: 1
97 spec:
98 replicas: 1
99 selector:
100 matchLabels:
101 app: nginx
102 template:
103 metadata:
104 labels:
105 app: nginx
106 spec:
107 containers:
108 - name: nginx
109 image: nginx:1.19.6
110 ports:
111 - containerPort: 80
112 status:
113 observedGeneration: 1
114 updatedReplicas: 1
115 readyReplicas: 0
116 availableReplicas: 0
117 replicas: 1
118 conditions:
119 - reason: NewReplicaSetAvailable
120 status: "True"
121 type: Progressing
122 message: ReplicaSet "nginx-1" is progressing.
123 - reason: MinimumReplicasUnavailable
124 type: Available
125 status: "False"
126 message: Deployment does not have minimum availability.
127 `
128
129 var deployment1CurrentYaml = `
130 apiVersion: apps/v1
131 kind: Deployment
132 metadata:
133 name: nginx
134 generation: 1
135 spec:
136 replicas: 1
137 selector:
138 matchLabels:
139 app: nginx
140 template:
141 metadata:
142 labels:
143 app: nginx
144 spec:
145 containers:
146 - name: nginx
147 image: nginx:1.19.6
148 ports:
149 - containerPort: 80
150 status:
151 observedGeneration: 1
152 updatedReplicas: 1
153 readyReplicas: 1
154 availableReplicas: 1
155 replicas: 1
156 conditions:
157 - reason: NewReplicaSetAvailable
158 status: "True"
159 type: Progressing
160 message: ReplicaSet "nginx-1" has successfully progressed.
161 - reason: MinimumReplicasAvailable
162 type: Available
163 status: "True"
164 message: Deployment has minimum availability.
165 `
166
167 var replicaset1Yaml = `
168 apiVersion: apps/v1
169 kind: ReplicaSet
170 metadata:
171 name: nginx-1
172 generation: 1
173 labels:
174 app: nginx
175 spec:
176 replicas: 1
177 selector:
178 matchLabels:
179 app: nginx
180 `
181
182 var replicaset1InProgress1Yaml = `
183 apiVersion: apps/v1
184 kind: ReplicaSet
185 metadata:
186 name: nginx-1
187 generation: 1
188 labels:
189 app: nginx
190 spec:
191 replicas: 1
192 selector:
193 matchLabels:
194 app: nginx
195 status:
196 observedGeneration: 1
197 replicas: 0
198 readyReplicas: 0
199 availableReplicas: 0
200 fullyLabeledReplicas: 0
201 `
202
203 var replicaset1InProgress2Yaml = `
204 apiVersion: apps/v1
205 kind: ReplicaSet
206 metadata:
207 name: nginx-1
208 generation: 1
209 labels:
210 app: nginx
211 spec:
212 replicas: 1
213 selector:
214 matchLabels:
215 app: nginx
216 status:
217 observedGeneration: 1
218 replicas: 1
219 readyReplicas: 0
220 availableReplicas: 0
221 fullyLabeledReplicas: 1
222 `
223
224 var replicaset1CurrentYaml = `
225 apiVersion: apps/v1
226 kind: ReplicaSet
227 metadata:
228 name: nginx-1
229 generation: 1
230 labels:
231 app: nginx
232 spec:
233 replicas: 1
234 selector:
235 matchLabels:
236 app: nginx
237 status:
238 observedGeneration: 1
239 replicas: 1
240 readyReplicas: 1
241 availableReplicas: 1
242 fullyLabeledReplicas: 1
243 `
244
245 var pod1Yaml = `
246 apiVersion: v1
247 kind: Pod
248 metadata:
249 generation: 1
250 name: test
251 labels:
252 app: nginx
253 `
254
255 var pod1CurrentYaml = `
256 apiVersion: v1
257 kind: Pod
258 metadata:
259 generation: 1
260 name: test
261 labels:
262 app: nginx
263 status:
264 conditions:
265 - type: Ready
266 status: "True"
267 phase: Running
268 `
269
270 func yamlToUnstructured(t *testing.T, yml string) *unstructured.Unstructured {
271 m := make(map[string]interface{})
272 err := yaml.Unmarshal([]byte(yml), &m)
273 if err != nil {
274 t.Fatalf("error parsing yaml: %v", err)
275 return nil
276 }
277 return &unstructured.Unstructured{Object: m}
278 }
279
280 func TestDefaultStatusWatcher(t *testing.T) {
281 deployment1 := yamlToUnstructured(t, deployment1Yaml)
282 deployment1ID := object.UnstructuredToObjMetadata(deployment1)
283 deployment1InProgress1 := yamlToUnstructured(t, deployment1InProgress1Yaml)
284 deployment1InProgress2 := yamlToUnstructured(t, deployment1InProgress2Yaml)
285 deployment1Current := yamlToUnstructured(t, deployment1CurrentYaml)
286
287 replicaset1 := yamlToUnstructured(t, replicaset1Yaml)
288 replicaset1ID := object.UnstructuredToObjMetadata(replicaset1)
289 replicaset1InProgress1 := yamlToUnstructured(t, replicaset1InProgress1Yaml)
290 replicaset1InProgress2 := yamlToUnstructured(t, replicaset1InProgress2Yaml)
291 replicaset1Current := yamlToUnstructured(t, replicaset1CurrentYaml)
292
293 pod1 := yamlToUnstructured(t, pod1Yaml)
294 pod1ID := object.UnstructuredToObjMetadata(pod1)
295 pod1Current := yamlToUnstructured(t, pod1CurrentYaml)
296
297 fakeMapper := testutil.NewFakeRESTMapper(
298 appsv1.SchemeGroupVersion.WithKind("Deployment"),
299 appsv1.SchemeGroupVersion.WithKind("ReplicaSet"),
300 v1.SchemeGroupVersion.WithKind("Pod"),
301 )
302 deployment1GVR := getGVR(t, fakeMapper, deployment1)
303 replicaset1GVR := getGVR(t, fakeMapper, replicaset1)
304 pod1GVR := getGVR(t, fakeMapper, pod1)
305
306
307
308
309 pod2 := pod1.DeepCopy()
310 pod2.SetNamespace("ns-2")
311 pod2.SetName("pod-2")
312 pod2ID := object.UnstructuredToObjMetadata(pod2)
313 pod2Current := yamlToUnstructured(t, pod1CurrentYaml)
314 pod2Current.SetNamespace("ns-2")
315 pod2Current.SetName("pod-2")
316 pod2GVR := getGVR(t, fakeMapper, pod2)
317
318 pod3 := pod1.DeepCopy()
319 pod3.SetNamespace("ns-3")
320 pod3.SetName("pod-3")
321 pod3ID := object.UnstructuredToObjMetadata(pod3)
322 pod3Current := yamlToUnstructured(t, pod1CurrentYaml)
323 pod3Current.SetNamespace("ns-3")
324 pod3Current.SetName("pod-3")
325 pod3GVR := getGVR(t, fakeMapper, pod3)
326
327 testCases := []struct {
328 name string
329 ids object.ObjMetadataSet
330 opts Options
331 clusterUpdates []func(*dynamicfake.FakeDynamicClient)
332 expectedEvents []event.Event
333 }{
334 {
335 name: "single-namespace pod creation",
336 ids: object.ObjMetadataSet{
337 pod1ID,
338 },
339 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
340 func(fakeClient *dynamicfake.FakeDynamicClient) {
341
342 },
343 func(fakeClient *dynamicfake.FakeDynamicClient) {
344 require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
345 },
346 func(fakeClient *dynamicfake.FakeDynamicClient) {
347 require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
348 },
349 },
350 expectedEvents: []event.Event{
351 {
352 Type: event.SyncEvent,
353 },
354 {
355 Resource: &event.ResourceStatus{
356 Identifier: pod1ID,
357 Status: status.InProgressStatus,
358 Resource: pod1,
359 Message: "Pod phase not available",
360 GeneratedResources: nil,
361 },
362 },
363 {
364 Resource: &event.ResourceStatus{
365 Identifier: pod1ID,
366 Status: status.CurrentStatus,
367 Resource: pod1Current,
368 Message: "Pod is Ready",
369 GeneratedResources: nil,
370 },
371 },
372 },
373 },
374 {
375 name: "single-namespace replicaset creation",
376 ids: object.ObjMetadataSet{
377 replicaset1ID,
378 },
379 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
380 func(fakeClient *dynamicfake.FakeDynamicClient) {
381
382 },
383 func(fakeClient *dynamicfake.FakeDynamicClient) {
384 require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1, replicaset1.GetNamespace()))
385 },
386 func(fakeClient *dynamicfake.FakeDynamicClient) {
387 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress1, replicaset1InProgress1.GetNamespace()))
388 },
389 func(fakeClient *dynamicfake.FakeDynamicClient) {
390 require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
391 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress2, replicaset1InProgress2.GetNamespace()))
392 },
393 func(fakeClient *dynamicfake.FakeDynamicClient) {
394 require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
395 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
396 },
397 },
398 expectedEvents: []event.Event{
399 {
400 Type: event.SyncEvent,
401 },
402 {
403 Resource: &event.ResourceStatus{
404 Identifier: replicaset1ID,
405 Status: status.InProgressStatus,
406 Resource: replicaset1,
407 Message: "Labelled: 0/1",
408 GeneratedResources: nil,
409 },
410 },
411 {
412 Resource: &event.ResourceStatus{
413 Identifier: replicaset1ID,
414 Status: status.InProgressStatus,
415 Resource: replicaset1InProgress1,
416 Message: "Labelled: 0/1",
417 GeneratedResources: nil,
418 },
419 },
420 {
421 Resource: &event.ResourceStatus{
422 Identifier: replicaset1ID,
423 Status: status.InProgressStatus,
424 Resource: replicaset1InProgress2,
425 Message: "Available: 0/1",
426 GeneratedResources: event.ResourceStatuses{
427 {
428 Identifier: pod1ID,
429 Status: status.InProgressStatus,
430 Resource: pod1,
431 Message: "Pod phase not available",
432 GeneratedResources: nil,
433 },
434 },
435 },
436 },
437 {
438 Resource: &event.ResourceStatus{
439 Identifier: replicaset1ID,
440 Status: status.CurrentStatus,
441 Resource: replicaset1Current,
442 Message: "ReplicaSet is available. Replicas: 1",
443 GeneratedResources: event.ResourceStatuses{
444 {
445 Identifier: pod1ID,
446 Status: status.CurrentStatus,
447 Resource: pod1Current,
448 Message: "Pod is Ready",
449 GeneratedResources: nil,
450 },
451 },
452 },
453 },
454 },
455 },
456 {
457 name: "single-namespace deployment creation",
458 ids: object.ObjMetadataSet{
459 deployment1ID,
460 },
461 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
462 func(fakeClient *dynamicfake.FakeDynamicClient) {
463
464 },
465 func(fakeClient *dynamicfake.FakeDynamicClient) {
466 require.NoError(t, fakeClient.Tracker().Create(deployment1GVR, deployment1, deployment1.GetNamespace()))
467 },
468 func(fakeClient *dynamicfake.FakeDynamicClient) {
469 require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1, replicaset1.GetNamespace()))
470 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress1, replicaset1InProgress1.GetNamespace()))
471 require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1InProgress1, deployment1InProgress1.GetNamespace()))
472 },
473 func(fakeClient *dynamicfake.FakeDynamicClient) {
474 require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
475 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress2, replicaset1InProgress2.GetNamespace()))
476 require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1InProgress2, deployment1InProgress2.GetNamespace()))
477 },
478 func(fakeClient *dynamicfake.FakeDynamicClient) {
479 require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
480 require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
481 require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1Current, deployment1Current.GetNamespace()))
482 },
483 },
484 expectedEvents: []event.Event{
485 {
486 Type: event.SyncEvent,
487 },
488 {
489 Resource: &event.ResourceStatus{
490 Identifier: deployment1ID,
491 Status: status.InProgressStatus,
492 Resource: deployment1,
493 Message: "Replicas: 0/1",
494 GeneratedResources: nil,
495 },
496 },
497 {
498 Resource: &event.ResourceStatus{
499 Identifier: deployment1ID,
500 Status: status.InProgressStatus,
501 Resource: deployment1InProgress1,
502 Message: "Replicas: 0/1",
503 GeneratedResources: event.ResourceStatuses{
504 {
505 Identifier: replicaset1ID,
506 Status: status.InProgressStatus,
507 Resource: replicaset1InProgress1,
508 Message: "Labelled: 0/1",
509 GeneratedResources: nil,
510 },
511 },
512 },
513 },
514 {
515 Resource: &event.ResourceStatus{
516 Identifier: deployment1ID,
517 Status: status.InProgressStatus,
518 Resource: deployment1InProgress2,
519 Message: "Available: 0/1",
520 GeneratedResources: event.ResourceStatuses{
521 {
522 Identifier: replicaset1ID,
523 Status: status.InProgressStatus,
524 Resource: replicaset1InProgress2,
525 Message: "Available: 0/1",
526 GeneratedResources: event.ResourceStatuses{
527 {
528 Identifier: pod1ID,
529 Status: status.InProgressStatus,
530 Resource: pod1,
531 Message: "Pod phase not available",
532 GeneratedResources: nil,
533 },
534 },
535 },
536 },
537 },
538 },
539 {
540 Resource: &event.ResourceStatus{
541 Identifier: deployment1ID,
542 Status: status.CurrentStatus,
543 Resource: deployment1Current,
544 Message: "Deployment is available. Replicas: 1",
545 GeneratedResources: event.ResourceStatuses{
546 {
547 Identifier: replicaset1ID,
548 Status: status.CurrentStatus,
549 Resource: replicaset1Current,
550 Message: "ReplicaSet is available. Replicas: 1",
551 GeneratedResources: event.ResourceStatuses{
552 {
553 Identifier: pod1ID,
554 Status: status.CurrentStatus,
555 Resource: pod1Current,
556 Message: "Pod is Ready",
557 GeneratedResources: nil,
558 },
559 },
560 },
561 },
562 },
563 },
564 },
565 },
566 {
567 name: "single-namespace deployment deletion",
568 ids: object.ObjMetadataSet{
569 deployment1ID,
570 },
571 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
572 func(fakeClient *dynamicfake.FakeDynamicClient) {
573
574 },
575 func(fakeClient *dynamicfake.FakeDynamicClient) {
576 require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1Current, pod1Current.GetNamespace()))
577 require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
578 require.NoError(t, fakeClient.Tracker().Create(deployment1GVR, deployment1Current, deployment1Current.GetNamespace()))
579 },
580 func(fakeClient *dynamicfake.FakeDynamicClient) {
581 require.NoError(t, fakeClient.Tracker().Delete(pod1GVR, pod1Current.GetNamespace(), pod1Current.GetName()))
582 require.NoError(t, fakeClient.Tracker().Delete(replicaset1GVR, replicaset1Current.GetNamespace(), replicaset1Current.GetName()))
583 require.NoError(t, fakeClient.Tracker().Delete(deployment1GVR, deployment1Current.GetNamespace(), deployment1Current.GetName()))
584 },
585 },
586 expectedEvents: []event.Event{
587 {
588 Type: event.SyncEvent,
589 },
590 {
591 Resource: &event.ResourceStatus{
592 Identifier: deployment1ID,
593 Status: status.CurrentStatus,
594 Resource: deployment1Current,
595 Message: "Deployment is available. Replicas: 1",
596 GeneratedResources: event.ResourceStatuses{
597 {
598 Identifier: replicaset1ID,
599 Status: status.CurrentStatus,
600 Resource: replicaset1Current,
601 Message: "ReplicaSet is available. Replicas: 1",
602 GeneratedResources: event.ResourceStatuses{
603 {
604 Identifier: pod1ID,
605 Status: status.CurrentStatus,
606 Resource: pod1Current,
607 Message: "Pod is Ready",
608 GeneratedResources: nil,
609 },
610 },
611 },
612 },
613 },
614 },
615 {
616 Resource: &event.ResourceStatus{
617 Identifier: deployment1ID,
618 Status: status.NotFoundStatus,
619 Resource: nil,
620 Message: "Resource not found",
621 GeneratedResources: nil,
622 },
623 },
624 },
625 },
626 {
627 name: "multi-namespace pod creation with automatic scope",
628 opts: Options{
629 RESTScopeStrategy: RESTScopeAutomatic,
630 },
631 ids: object.ObjMetadataSet{
632 pod2ID,
633 pod3ID,
634 },
635 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
636 func(fakeClient *dynamicfake.FakeDynamicClient) {
637
638 },
639 func(fakeClient *dynamicfake.FakeDynamicClient) {
640 require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
641 },
642 func(fakeClient *dynamicfake.FakeDynamicClient) {
643 require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
644 },
645 func(fakeClient *dynamicfake.FakeDynamicClient) {
646 require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
647 },
648 func(fakeClient *dynamicfake.FakeDynamicClient) {
649 require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
650 },
651 },
652 expectedEvents: []event.Event{
653 {
654 Type: event.SyncEvent,
655 },
656 {
657 Resource: &event.ResourceStatus{
658 Identifier: pod2ID,
659 Status: status.InProgressStatus,
660 Resource: pod2,
661 Message: "Pod phase not available",
662 GeneratedResources: nil,
663 },
664 },
665 {
666 Resource: &event.ResourceStatus{
667 Identifier: pod3ID,
668 Status: status.InProgressStatus,
669 Resource: pod3,
670 Message: "Pod phase not available",
671 GeneratedResources: nil,
672 },
673 },
674 {
675 Resource: &event.ResourceStatus{
676 Identifier: pod2ID,
677 Status: status.CurrentStatus,
678 Resource: pod2Current,
679 Message: "Pod is Ready",
680 GeneratedResources: nil,
681 },
682 },
683 {
684 Resource: &event.ResourceStatus{
685 Identifier: pod3ID,
686 Status: status.CurrentStatus,
687 Resource: pod3Current,
688 Message: "Pod is Ready",
689 GeneratedResources: nil,
690 },
691 },
692 },
693 },
694 {
695 name: "multi-namespace pod creation with root scope",
696 opts: Options{
697 RESTScopeStrategy: RESTScopeRoot,
698 },
699 ids: object.ObjMetadataSet{
700 pod2ID,
701 pod3ID,
702 },
703 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
704 func(fakeClient *dynamicfake.FakeDynamicClient) {
705
706 },
707 func(fakeClient *dynamicfake.FakeDynamicClient) {
708 require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
709 },
710 func(fakeClient *dynamicfake.FakeDynamicClient) {
711 require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
712 },
713 func(fakeClient *dynamicfake.FakeDynamicClient) {
714 require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
715 },
716 func(fakeClient *dynamicfake.FakeDynamicClient) {
717 require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
718 },
719 },
720 expectedEvents: []event.Event{
721 {
722 Type: event.SyncEvent,
723 },
724 {
725 Resource: &event.ResourceStatus{
726 Identifier: pod2ID,
727 Status: status.InProgressStatus,
728 Resource: pod2,
729 Message: "Pod phase not available",
730 GeneratedResources: nil,
731 },
732 },
733 {
734 Resource: &event.ResourceStatus{
735 Identifier: pod3ID,
736 Status: status.InProgressStatus,
737 Resource: pod3,
738 Message: "Pod phase not available",
739 GeneratedResources: nil,
740 },
741 },
742 {
743 Resource: &event.ResourceStatus{
744 Identifier: pod2ID,
745 Status: status.CurrentStatus,
746 Resource: pod2Current,
747 Message: "Pod is Ready",
748 GeneratedResources: nil,
749 },
750 },
751 {
752 Resource: &event.ResourceStatus{
753 Identifier: pod3ID,
754 Status: status.CurrentStatus,
755 Resource: pod3Current,
756 Message: "Pod is Ready",
757 GeneratedResources: nil,
758 },
759 },
760 },
761 },
762 {
763 name: "multi-namespace pod creation with namespace scope",
764 opts: Options{
765 RESTScopeStrategy: RESTScopeNamespace,
766 },
767 ids: object.ObjMetadataSet{
768 pod2ID,
769 pod3ID,
770 },
771 clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
772 func(fakeClient *dynamicfake.FakeDynamicClient) {
773
774 },
775 func(fakeClient *dynamicfake.FakeDynamicClient) {
776 require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
777 },
778 func(fakeClient *dynamicfake.FakeDynamicClient) {
779 require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
780 },
781 func(fakeClient *dynamicfake.FakeDynamicClient) {
782 require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
783 },
784 func(fakeClient *dynamicfake.FakeDynamicClient) {
785 require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
786 },
787 },
788 expectedEvents: []event.Event{
789 {
790 Type: event.SyncEvent,
791 },
792 {
793 Resource: &event.ResourceStatus{
794 Identifier: pod2ID,
795 Status: status.InProgressStatus,
796 Resource: pod2,
797 Message: "Pod phase not available",
798 GeneratedResources: nil,
799 },
800 },
801 {
802 Resource: &event.ResourceStatus{
803 Identifier: pod3ID,
804 Status: status.InProgressStatus,
805 Resource: pod3,
806 Message: "Pod phase not available",
807 GeneratedResources: nil,
808 },
809 },
810 {
811 Resource: &event.ResourceStatus{
812 Identifier: pod2ID,
813 Status: status.CurrentStatus,
814 Resource: pod2Current,
815 Message: "Pod is Ready",
816 GeneratedResources: nil,
817 },
818 },
819 {
820 Resource: &event.ResourceStatus{
821 Identifier: pod3ID,
822 Status: status.CurrentStatus,
823 Resource: pod3Current,
824 Message: "Pod is Ready",
825 GeneratedResources: nil,
826 },
827 },
828 },
829 },
830 }
831
832 testTimeout := 10 * time.Second
833
834 for _, tc := range testCases {
835 t.Run(tc.name, func(t *testing.T) {
836 ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
837 defer cancel()
838
839 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
840
841
842 fakeClient.PrependReactor("*", "*", func(a clienttesting.Action) (bool, runtime.Object, error) {
843 klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
844 a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
845 return false, nil, nil
846 })
847 fakeClient.PrependWatchReactor("*", func(a clienttesting.Action) (bool, watch.Interface, error) {
848 klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
849 a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
850 return false, nil, nil
851 })
852
853 statusWatcher := NewDefaultStatusWatcher(fakeClient, fakeMapper)
854 eventCh := statusWatcher.Watch(ctx, tc.ids, tc.opts)
855
856 nextCh := make(chan struct{})
857 defer close(nextCh)
858
859
860 go func() {
861 for _, update := range tc.clusterUpdates {
862 <-nextCh
863 update(fakeClient)
864 }
865
866 <-nextCh
867
868 cancel()
869 }()
870
871
872 nextCh <- struct{}{}
873
874 receivedEvents := []event.Event{}
875 for e := range eventCh {
876 receivedEvents = append(receivedEvents, e)
877
878 nextCh <- struct{}{}
879 }
880 testutil.AssertEqual(t, tc.expectedEvents, receivedEvents)
881 })
882 }
883 }
884
885 func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource {
886 gvk := obj.GroupVersionKind()
887 mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
888 require.NoError(t, err)
889 return mapping.Resource
890 }
891
View as plain text