1
16
17 package cache_test
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "reflect"
24 "sort"
25 "strconv"
26 "strings"
27 "time"
28
29 . "github.com/onsi/ginkgo/v2"
30 . "github.com/onsi/gomega"
31 corev1 "k8s.io/api/core/v1"
32 apierrors "k8s.io/apimachinery/pkg/api/errors"
33 "k8s.io/apimachinery/pkg/api/meta"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
36 "k8s.io/apimachinery/pkg/fields"
37 "k8s.io/apimachinery/pkg/labels"
38 "k8s.io/apimachinery/pkg/runtime"
39 "k8s.io/apimachinery/pkg/runtime/schema"
40 kscheme "k8s.io/client-go/kubernetes/scheme"
41 "k8s.io/client-go/rest"
42 kcache "k8s.io/client-go/tools/cache"
43 "k8s.io/utils/ptr"
44
45 "sigs.k8s.io/controller-runtime/pkg/cache"
46 "sigs.k8s.io/controller-runtime/pkg/client"
47 "sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
48 )
49
50 const testNodeOne = "test-node-1"
51 const testNodeTwo = "test-node-2"
52 const testNamespaceOne = "test-namespace-1"
53 const testNamespaceTwo = "test-namespace-2"
54 const testNamespaceThree = "test-namespace-3"
55
56
57
58 func createPodWithLabels(name, namespace string, restartPolicy corev1.RestartPolicy, labels map[string]string) client.Object {
59 three := int64(3)
60 if labels == nil {
61 labels = map[string]string{}
62 }
63 labels["test-label"] = name
64 pod := &corev1.Pod{
65 ObjectMeta: metav1.ObjectMeta{
66 Name: name,
67 Namespace: namespace,
68 Labels: labels,
69 },
70 Spec: corev1.PodSpec{
71 Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}},
72 RestartPolicy: restartPolicy,
73 ActiveDeadlineSeconds: &three,
74 },
75 }
76 cl, err := client.New(cfg, client.Options{})
77 Expect(err).NotTo(HaveOccurred())
78 err = cl.Create(context.Background(), pod)
79 Expect(err).NotTo(HaveOccurred())
80 return pod
81 }
82
83 func createSvc(name, namespace string, cl client.Client) client.Object {
84 svc := &corev1.Service{
85 ObjectMeta: metav1.ObjectMeta{
86 Name: name,
87 Namespace: namespace,
88 },
89 Spec: corev1.ServiceSpec{
90 Ports: []corev1.ServicePort{{Port: 1}},
91 },
92 }
93 err := cl.Create(context.Background(), svc)
94 Expect(err).NotTo(HaveOccurred())
95 return svc
96 }
97
98 func createSA(name, namespace string, cl client.Client) client.Object {
99 sa := &corev1.ServiceAccount{
100 ObjectMeta: metav1.ObjectMeta{
101 Name: name,
102 Namespace: namespace,
103 },
104 }
105 err := cl.Create(context.Background(), sa)
106 Expect(err).NotTo(HaveOccurred())
107 return sa
108 }
109
110 func createPod(name, namespace string, restartPolicy corev1.RestartPolicy) client.Object {
111 return createPodWithLabels(name, namespace, restartPolicy, nil)
112 }
113
114 func deletePod(pod client.Object) {
115 cl, err := client.New(cfg, client.Options{})
116 Expect(err).NotTo(HaveOccurred())
117 err = cl.Delete(context.Background(), pod)
118 Expect(err).NotTo(HaveOccurred())
119 }
120
121 var _ = Describe("Informer Cache", func() {
122 CacheTest(cache.New, cache.Options{})
123 NonBlockingGetTest(cache.New, cache.Options{})
124 })
125
126 var _ = Describe("Informer Cache with ReaderFailOnMissingInformer", func() {
127 CacheTestReaderFailOnMissingInformer(cache.New, cache.Options{ReaderFailOnMissingInformer: true})
128 })
129
130 var _ = Describe("Multi-Namespace Informer Cache", func() {
131 CacheTest(cache.New, cache.Options{
132 DefaultNamespaces: map[string]cache.Config{
133 testNamespaceOne: {},
134 testNamespaceTwo: {},
135 "default": {},
136 },
137 })
138 NonBlockingGetTest(cache.New, cache.Options{
139 DefaultNamespaces: map[string]cache.Config{
140 testNamespaceOne: {},
141 testNamespaceTwo: {},
142 "default": {},
143 },
144 })
145 })
146
147 var _ = Describe("Informer Cache without global DeepCopy", func() {
148 CacheTest(cache.New, cache.Options{
149 DefaultUnsafeDisableDeepCopy: ptr.To(true),
150 })
151 NonBlockingGetTest(cache.New, cache.Options{
152 DefaultUnsafeDisableDeepCopy: ptr.To(true),
153 })
154 })
155
156 var _ = Describe("Cache with transformers", func() {
157 var (
158 informerCache cache.Cache
159 informerCacheCtx context.Context
160 informerCacheCancel context.CancelFunc
161 knownPod1 client.Object
162 knownPod2 client.Object
163 knownPod3 client.Object
164 knownPod4 client.Object
165 knownPod5 client.Object
166 knownPod6 client.Object
167 )
168
169 getTransformValue := func(obj client.Object) string {
170 accessor, err := meta.Accessor(obj)
171 if err == nil {
172 annotations := accessor.GetAnnotations()
173 if val, exists := annotations["transformed"]; exists {
174 return val
175 }
176 }
177 return ""
178 }
179
180 BeforeEach(func() {
181 informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
182 Expect(cfg).NotTo(BeNil())
183
184 By("creating three pods")
185 cl, err := client.New(cfg, client.Options{})
186 Expect(err).NotTo(HaveOccurred())
187 err = ensureNode(testNodeOne, cl)
188 Expect(err).NotTo(HaveOccurred())
189 err = ensureNamespace(testNamespaceOne, cl)
190 Expect(err).NotTo(HaveOccurred())
191 err = ensureNamespace(testNamespaceTwo, cl)
192 Expect(err).NotTo(HaveOccurred())
193 err = ensureNamespace(testNamespaceThree, cl)
194 Expect(err).NotTo(HaveOccurred())
195
196 knownPod1 = createPod("test-pod-1", testNamespaceOne, corev1.RestartPolicyNever)
197 knownPod2 = createPod("test-pod-2", testNamespaceTwo, corev1.RestartPolicyAlways)
198 knownPod3 = createPodWithLabels("test-pod-3", testNamespaceTwo, corev1.RestartPolicyOnFailure, map[string]string{"common-label": "common"})
199 knownPod4 = createPodWithLabels("test-pod-4", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"common-label": "common"})
200 knownPod5 = createPod("test-pod-5", testNamespaceOne, corev1.RestartPolicyNever)
201 knownPod6 = createPod("test-pod-6", testNamespaceTwo, corev1.RestartPolicyAlways)
202
203 podGVK := schema.GroupVersionKind{
204 Kind: "Pod",
205 Version: "v1",
206 }
207
208 knownPod1.GetObjectKind().SetGroupVersionKind(podGVK)
209 knownPod2.GetObjectKind().SetGroupVersionKind(podGVK)
210 knownPod3.GetObjectKind().SetGroupVersionKind(podGVK)
211 knownPod4.GetObjectKind().SetGroupVersionKind(podGVK)
212 knownPod5.GetObjectKind().SetGroupVersionKind(podGVK)
213 knownPod6.GetObjectKind().SetGroupVersionKind(podGVK)
214
215 By("creating the informer cache")
216 informerCache, err = cache.New(cfg, cache.Options{
217 DefaultTransform: func(i interface{}) (interface{}, error) {
218 obj := i.(runtime.Object)
219 Expect(obj).NotTo(BeNil())
220
221 accessor, err := meta.Accessor(obj)
222 Expect(err).ToNot(HaveOccurred())
223 annotations := accessor.GetAnnotations()
224
225 if _, exists := annotations["transformed"]; exists {
226
227 return i, nil
228 }
229
230 if annotations == nil {
231 annotations = make(map[string]string)
232 }
233 annotations["transformed"] = "default"
234 accessor.SetAnnotations(annotations)
235 return i, nil
236 },
237 ByObject: map[client.Object]cache.ByObject{
238 &corev1.Pod{}: {
239 Transform: func(i interface{}) (interface{}, error) {
240 obj := i.(runtime.Object)
241 Expect(obj).NotTo(BeNil())
242 accessor, err := meta.Accessor(obj)
243 Expect(err).ToNot(HaveOccurred())
244
245 annotations := accessor.GetAnnotations()
246 if _, exists := annotations["transformed"]; exists {
247
248 return i, nil
249 }
250
251 if annotations == nil {
252 annotations = make(map[string]string)
253 }
254 annotations["transformed"] = "explicit"
255 accessor.SetAnnotations(annotations)
256 return i, nil
257 },
258 },
259 },
260 })
261 Expect(err).NotTo(HaveOccurred())
262 By("running the cache and waiting for it to sync")
263
264 go func(ctx context.Context) {
265 defer GinkgoRecover()
266 Expect(informerCache.Start(ctx)).To(Succeed())
267 }(informerCacheCtx)
268 Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
269 })
270
271 AfterEach(func() {
272 By("cleaning up created pods")
273 deletePod(knownPod1)
274 deletePod(knownPod2)
275 deletePod(knownPod3)
276 deletePod(knownPod4)
277 deletePod(knownPod5)
278 deletePod(knownPod6)
279
280 informerCacheCancel()
281 })
282
283 Context("with structured objects", func() {
284 It("should apply transformers to explicitly specified GVKS", func() {
285 By("listing pods")
286 out := corev1.PodList{}
287 Expect(informerCache.List(context.Background(), &out)).To(Succeed())
288
289 By("verifying that the returned pods were transformed")
290 for i := 0; i < len(out.Items); i++ {
291 Expect(getTransformValue(&out.Items[i])).To(BeIdenticalTo("explicit"))
292 }
293 })
294
295 It("should apply default transformer to objects when none is specified", func() {
296 By("getting the Kubernetes service")
297 svc := &corev1.Service{}
298 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
299 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
300
301 By("verifying that the returned service was transformed")
302 Expect(getTransformValue(svc)).To(BeIdenticalTo("default"))
303 })
304 })
305
306 Context("with unstructured objects", func() {
307 It("should apply transformers to explicitly specified GVKS", func() {
308 By("listing pods")
309 out := unstructured.UnstructuredList{}
310 out.SetGroupVersionKind(schema.GroupVersionKind{
311 Group: "",
312 Version: "v1",
313 Kind: "PodList",
314 })
315 Expect(informerCache.List(context.Background(), &out)).To(Succeed())
316
317 By("verifying that the returned pods were transformed")
318 for i := 0; i < len(out.Items); i++ {
319 Expect(getTransformValue(&out.Items[i])).To(BeIdenticalTo("explicit"))
320 }
321 })
322
323 It("should apply default transformer to objects when none is specified", func() {
324 By("getting the Kubernetes service")
325 svc := &unstructured.Unstructured{}
326 svc.SetGroupVersionKind(schema.GroupVersionKind{
327 Group: "",
328 Version: "v1",
329 Kind: "Service",
330 })
331 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
332 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
333
334 By("verifying that the returned service was transformed")
335 Expect(getTransformValue(svc)).To(BeIdenticalTo("default"))
336 })
337 })
338
339 Context("with metadata-only objects", func() {
340 It("should apply transformers to explicitly specified GVKS", func() {
341 By("listing pods")
342 out := metav1.PartialObjectMetadataList{}
343 out.SetGroupVersionKind(schema.GroupVersionKind{
344 Group: "",
345 Version: "v1",
346 Kind: "PodList",
347 })
348 Expect(informerCache.List(context.Background(), &out)).To(Succeed())
349
350 By("verifying that the returned pods were transformed")
351 for i := 0; i < len(out.Items); i++ {
352 Expect(getTransformValue(&out.Items[i])).To(BeIdenticalTo("explicit"))
353 }
354 })
355 It("should apply default transformer to objects when none is specified", func() {
356 By("getting the Kubernetes service")
357 svc := &metav1.PartialObjectMetadata{}
358 svc.SetGroupVersionKind(schema.GroupVersionKind{
359 Group: "",
360 Version: "v1",
361 Kind: "Service",
362 })
363 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
364 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
365
366 By("verifying that the returned service was transformed")
367 Expect(getTransformValue(svc)).To(BeIdenticalTo("default"))
368 })
369 })
370 })
371
372 var _ = Describe("Cache with selectors", func() {
373 defer GinkgoRecover()
374 var (
375 informerCache cache.Cache
376 informerCacheCtx context.Context
377 informerCacheCancel context.CancelFunc
378 )
379
380 BeforeEach(func() {
381 informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
382 Expect(cfg).NotTo(BeNil())
383 cl, err := client.New(cfg, client.Options{})
384 Expect(err).NotTo(HaveOccurred())
385 err = ensureNamespace(testNamespaceOne, cl)
386 Expect(err).NotTo(HaveOccurred())
387 err = ensureNamespace(testNamespaceTwo, cl)
388 Expect(err).NotTo(HaveOccurred())
389 for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
390 _ = createSA("test-sa-"+strconv.Itoa(idx), namespace, cl)
391 _ = createSvc("test-svc-"+strconv.Itoa(idx), namespace, cl)
392 }
393
394 opts := cache.Options{
395 DefaultFieldSelector: fields.OneTermEqualSelector("metadata.namespace", testNamespaceTwo),
396 ByObject: map[client.Object]cache.ByObject{
397 &corev1.ServiceAccount{}: {
398 Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceOne),
399 },
400 },
401 }
402
403 By("creating the informer cache")
404 informerCache, err = cache.New(cfg, opts)
405 Expect(err).NotTo(HaveOccurred())
406 By("running the cache and waiting for it to sync")
407
408 go func(ctx context.Context) {
409 defer GinkgoRecover()
410 Expect(informerCache.Start(ctx)).To(Succeed())
411 }(informerCacheCtx)
412 Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
413 })
414
415 AfterEach(func() {
416 ctx := context.Background()
417 cl, err := client.New(cfg, client.Options{})
418 Expect(err).NotTo(HaveOccurred())
419 for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
420 err = cl.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-sa-" + strconv.Itoa(idx)}})
421 Expect(err).NotTo(HaveOccurred())
422 err = cl.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-svc-" + strconv.Itoa(idx)}})
423 Expect(err).NotTo(HaveOccurred())
424 }
425 informerCacheCancel()
426 })
427
428 It("Should list serviceaccounts and find exactly one in namespace "+testNamespaceOne, func() {
429 var sas corev1.ServiceAccountList
430 err := informerCache.List(informerCacheCtx, &sas)
431 Expect(err).NotTo(HaveOccurred())
432 Expect(sas.Items).To(HaveLen(1))
433 Expect(sas.Items[0].Namespace).To(Equal(testNamespaceOne))
434 })
435
436 It("Should list services and find exactly one in namespace "+testNamespaceTwo, func() {
437 var svcs corev1.ServiceList
438 err := informerCache.List(informerCacheCtx, &svcs)
439 Expect(err).NotTo(HaveOccurred())
440 Expect(svcs.Items).To(HaveLen(1))
441 Expect(svcs.Items[0].Namespace).To(Equal(testNamespaceTwo))
442 })
443 })
444
445 func CacheTestReaderFailOnMissingInformer(createCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, error), opts cache.Options) {
446 Describe("Cache test with ReaderFailOnMissingInformer = true", func() {
447 var (
448 informerCache cache.Cache
449 informerCacheCtx context.Context
450 informerCacheCancel context.CancelFunc
451 errNotCached *cache.ErrResourceNotCached
452 )
453
454 BeforeEach(func() {
455 informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
456 Expect(cfg).NotTo(BeNil())
457 By("creating the informer cache")
458 var err error
459 informerCache, err = createCacheFunc(cfg, opts)
460 Expect(err).NotTo(HaveOccurred())
461 By("running the cache and waiting for it to sync")
462
463 go func(ctx context.Context) {
464 defer GinkgoRecover()
465 Expect(informerCache.Start(ctx)).To(Succeed())
466 }(informerCacheCtx)
467 Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
468 })
469
470 AfterEach(func() {
471 informerCacheCancel()
472 })
473
474 Describe("as a Reader", func() {
475 Context("with structured objects", func() {
476 It("should not be able to list objects that haven't been watched previously", func() {
477 By("listing all services in the cluster")
478 listObj := &corev1.ServiceList{}
479 Expect(errors.As(informerCache.List(context.Background(), listObj), &errNotCached)).To(BeTrue())
480 })
481
482 It("should not be able to get objects that haven't been watched previously", func() {
483 By("getting the Kubernetes service")
484 svc := &corev1.Service{}
485 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
486 Expect(errors.As(informerCache.Get(context.Background(), svcKey, svc), &errNotCached)).To(BeTrue())
487 })
488
489 It("should be able to list objects that are configured to be watched", func() {
490 By("indicating that we need to watch services")
491 _, err := informerCache.GetInformer(context.Background(), &corev1.Service{})
492 Expect(err).ToNot(HaveOccurred())
493
494 By("listing all services in the cluster")
495 svcList := &corev1.ServiceList{}
496 Expect(informerCache.List(context.Background(), svcList)).To(Succeed())
497
498 By("verifying that the returned service looks reasonable")
499 Expect(svcList.Items).To(HaveLen(1))
500 Expect(svcList.Items[0].Name).To(Equal("kubernetes"))
501 Expect(svcList.Items[0].Namespace).To(Equal("default"))
502 })
503
504 It("should be able to get objects that are configured to be watched", func() {
505 By("indicating that we need to watch services")
506 _, err := informerCache.GetInformer(context.Background(), &corev1.Service{})
507 Expect(err).ToNot(HaveOccurred())
508
509 By("getting the Kubernetes service")
510 svc := &corev1.Service{}
511 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
512 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
513
514 By("verifying that the returned service looks reasonable")
515 Expect(svc.Name).To(Equal("kubernetes"))
516 Expect(svc.Namespace).To(Equal("default"))
517 })
518 })
519 })
520 })
521 }
522
523 func NonBlockingGetTest(createCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, error), opts cache.Options) {
524 Describe("non-blocking get test", func() {
525 var (
526 informerCache cache.Cache
527 informerCacheCtx context.Context
528 informerCacheCancel context.CancelFunc
529 )
530 BeforeEach(func() {
531 informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
532 Expect(cfg).NotTo(BeNil())
533
534 By("creating expected namespaces")
535 cl, err := client.New(cfg, client.Options{})
536 Expect(err).NotTo(HaveOccurred())
537 err = ensureNode(testNodeOne, cl)
538 Expect(err).NotTo(HaveOccurred())
539 err = ensureNamespace(testNamespaceOne, cl)
540 Expect(err).NotTo(HaveOccurred())
541 err = ensureNamespace(testNamespaceTwo, cl)
542 Expect(err).NotTo(HaveOccurred())
543 err = ensureNamespace(testNamespaceThree, cl)
544 Expect(err).NotTo(HaveOccurred())
545
546 By("creating the informer cache")
547 v := reflect.ValueOf(&opts).Elem()
548 newInformerField := v.FieldByName("newInformer")
549 newFakeInformer := func(_ kcache.ListerWatcher, _ runtime.Object, _ time.Duration, _ kcache.Indexers) kcache.SharedIndexInformer {
550 return &controllertest.FakeInformer{Synced: false}
551 }
552 reflect.NewAt(newInformerField.Type(), newInformerField.Addr().UnsafePointer()).
553 Elem().
554 Set(reflect.ValueOf(&newFakeInformer))
555 informerCache, err = createCacheFunc(cfg, opts)
556 Expect(err).NotTo(HaveOccurred())
557 By("running the cache and waiting for it to sync")
558
559 go func(ctx context.Context) {
560 defer GinkgoRecover()
561 Expect(informerCache.Start(ctx)).To(Succeed())
562 }(informerCacheCtx)
563 Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
564 })
565
566 AfterEach(func() {
567 By("cleaning up created pods")
568 informerCacheCancel()
569 })
570
571 Describe("as an Informer", func() {
572 It("should be able to get informer for the object without blocking", func() {
573 By("getting a shared index informer for a pod")
574 pod := &corev1.Pod{
575 ObjectMeta: metav1.ObjectMeta{
576 Name: "informer-obj",
577 Namespace: "default",
578 },
579 Spec: corev1.PodSpec{
580 Containers: []corev1.Container{
581 {
582 Name: "nginx",
583 Image: "nginx",
584 },
585 },
586 },
587 }
588
589 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
590 defer cancel()
591 sii, err := informerCache.GetInformer(ctx, pod, cache.BlockUntilSynced(false))
592 Expect(err).NotTo(HaveOccurred())
593 Expect(sii).NotTo(BeNil())
594 Expect(sii.HasSynced()).To(BeFalse())
595 })
596 })
597 })
598 }
599
600 func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, error), opts cache.Options) {
601 Describe("Cache test", func() {
602 var (
603 informerCache cache.Cache
604 informerCacheCtx context.Context
605 informerCacheCancel context.CancelFunc
606 knownPod1 client.Object
607 knownPod2 client.Object
608 knownPod3 client.Object
609 knownPod4 client.Object
610 knownPod5 client.Object
611 knownPod6 client.Object
612 )
613
614 BeforeEach(func() {
615 informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
616 Expect(cfg).NotTo(BeNil())
617
618 By("creating three pods")
619 cl, err := client.New(cfg, client.Options{})
620 Expect(err).NotTo(HaveOccurred())
621 err = ensureNode(testNodeOne, cl)
622 Expect(err).NotTo(HaveOccurred())
623 err = ensureNode(testNodeTwo, cl)
624 Expect(err).NotTo(HaveOccurred())
625 err = ensureNamespace(testNamespaceOne, cl)
626 Expect(err).NotTo(HaveOccurred())
627 err = ensureNamespace(testNamespaceTwo, cl)
628 Expect(err).NotTo(HaveOccurred())
629 err = ensureNamespace(testNamespaceThree, cl)
630 Expect(err).NotTo(HaveOccurred())
631
632 knownPod1 = createPod("test-pod-1", testNamespaceOne, corev1.RestartPolicyNever)
633 knownPod2 = createPod("test-pod-2", testNamespaceTwo, corev1.RestartPolicyAlways)
634 knownPod3 = createPodWithLabels("test-pod-3", testNamespaceTwo, corev1.RestartPolicyOnFailure, map[string]string{"common-label": "common"})
635 knownPod4 = createPodWithLabels("test-pod-4", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"common-label": "common"})
636 knownPod5 = createPod("test-pod-5", testNamespaceOne, corev1.RestartPolicyNever)
637 knownPod6 = createPod("test-pod-6", testNamespaceTwo, corev1.RestartPolicyAlways)
638
639 podGVK := schema.GroupVersionKind{
640 Kind: "Pod",
641 Version: "v1",
642 }
643
644 knownPod1.GetObjectKind().SetGroupVersionKind(podGVK)
645 knownPod2.GetObjectKind().SetGroupVersionKind(podGVK)
646 knownPod3.GetObjectKind().SetGroupVersionKind(podGVK)
647 knownPod4.GetObjectKind().SetGroupVersionKind(podGVK)
648 knownPod5.GetObjectKind().SetGroupVersionKind(podGVK)
649 knownPod6.GetObjectKind().SetGroupVersionKind(podGVK)
650
651 By("creating the informer cache")
652 informerCache, err = createCacheFunc(cfg, opts)
653 Expect(err).NotTo(HaveOccurred())
654 By("running the cache and waiting for it to sync")
655
656 go func(ctx context.Context) {
657 defer GinkgoRecover()
658 Expect(informerCache.Start(ctx)).To(Succeed())
659 }(informerCacheCtx)
660 Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
661 })
662
663 AfterEach(func() {
664 By("cleaning up created pods")
665 deletePod(knownPod1)
666 deletePod(knownPod2)
667 deletePod(knownPod3)
668 deletePod(knownPod4)
669 deletePod(knownPod5)
670 deletePod(knownPod6)
671
672 informerCacheCancel()
673 })
674
675 Describe("as a Reader", func() {
676 Context("with structured objects", func() {
677 It("should be able to list objects that haven't been watched previously", func() {
678 By("listing all services in the cluster")
679 listObj := &corev1.ServiceList{}
680 Expect(informerCache.List(context.Background(), listObj)).To(Succeed())
681
682 By("verifying that the returned list contains the Kubernetes service")
683
684 Expect(listObj.Items).NotTo(BeEmpty())
685 hasKubeService := false
686 for i := range listObj.Items {
687 svc := &listObj.Items[i]
688 if isKubeService(svc) {
689 hasKubeService = true
690 break
691 }
692 }
693 Expect(hasKubeService).To(BeTrue())
694 })
695
696 It("should be able to get objects that haven't been watched previously", func() {
697 By("getting the Kubernetes service")
698 svc := &corev1.Service{}
699 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
700 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
701
702 By("verifying that the returned service looks reasonable")
703 Expect(svc.Name).To(Equal("kubernetes"))
704 Expect(svc.Namespace).To(Equal("default"))
705 })
706
707 It("should support filtering by labels in a single namespace", func() {
708 By("listing pods with a particular label")
709
710 out := corev1.PodList{}
711 Expect(informerCache.List(context.Background(), &out,
712 client.InNamespace(testNamespaceTwo),
713 client.MatchingLabels(map[string]string{"test-label": "test-pod-2"}))).To(Succeed())
714
715 By("verifying the returned pods have the correct label")
716 Expect(out.Items).NotTo(BeEmpty())
717 Expect(out.Items).Should(HaveLen(1))
718 actual := out.Items[0]
719 Expect(actual.Labels["test-label"]).To(Equal("test-pod-2"))
720 })
721
722 It("should support filtering by labels from multiple namespaces", func() {
723 By("creating another pod with the same label but different namespace")
724 anotherPod := createPod("test-pod-2", testNamespaceOne, corev1.RestartPolicyAlways)
725 defer deletePod(anotherPod)
726
727 By("listing pods with a particular label")
728
729 out := corev1.PodList{}
730 labels := map[string]string{"test-label": "test-pod-2"}
731 Expect(informerCache.List(context.Background(), &out, client.MatchingLabels(labels))).To(Succeed())
732
733 By("verifying multiple pods with the same label in different namespaces are returned")
734 Expect(out.Items).NotTo(BeEmpty())
735 Expect(out.Items).Should(HaveLen(2))
736 for _, actual := range out.Items {
737 Expect(actual.Labels["test-label"]).To(Equal("test-pod-2"))
738 }
739 })
740
741 if !isPodDisableDeepCopy(opts) {
742 It("should be able to list objects with GVK populated", func() {
743 By("listing pods")
744 out := &corev1.PodList{}
745 Expect(informerCache.List(context.Background(), out)).To(Succeed())
746
747 By("verifying that the returned pods have GVK populated")
748 Expect(out.Items).NotTo(BeEmpty())
749 Expect(out.Items).Should(SatisfyAny(HaveLen(5), HaveLen(6)))
750 for _, p := range out.Items {
751 Expect(p.GroupVersionKind()).To(Equal(corev1.SchemeGroupVersion.WithKind("Pod")))
752 }
753 })
754 }
755
756 It("should be able to list objects by namespace", func() {
757 By("listing pods in test-namespace-1")
758 listObj := &corev1.PodList{}
759 Expect(informerCache.List(context.Background(), listObj,
760 client.InNamespace(testNamespaceOne))).To(Succeed())
761
762 By("verifying that the returned pods are in test-namespace-1")
763 Expect(listObj.Items).NotTo(BeEmpty())
764 Expect(listObj.Items).Should(HaveLen(2))
765 for _, item := range listObj.Items {
766 Expect(item.Namespace).To(Equal(testNamespaceOne))
767 }
768 })
769
770 if !isPodDisableDeepCopy(opts) {
771 It("should deep copy the object unless told otherwise", func() {
772 By("retrieving a specific pod from the cache")
773 out := &corev1.Pod{}
774 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
775 Expect(informerCache.Get(context.Background(), podKey, out)).To(Succeed())
776
777 By("verifying the retrieved pod is equal to a known pod")
778 Expect(out).To(Equal(knownPod2))
779
780 By("altering a field in the retrieved pod")
781 *out.Spec.ActiveDeadlineSeconds = 4
782
783 By("verifying the pods are no longer equal")
784 Expect(out).NotTo(Equal(knownPod2))
785 })
786 } else {
787 It("should not deep copy the object if UnsafeDisableDeepCopy is enabled", func() {
788 By("getting a specific pod from the cache twice")
789 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
790 out1 := &corev1.Pod{}
791 Expect(informerCache.Get(context.Background(), podKey, out1)).To(Succeed())
792 out2 := &corev1.Pod{}
793 Expect(informerCache.Get(context.Background(), podKey, out2)).To(Succeed())
794
795 By("verifying the pointer fields in pod have the same addresses")
796 Expect(out1).To(Equal(out2))
797 Expect(reflect.ValueOf(out1.Labels).Pointer()).To(BeIdenticalTo(reflect.ValueOf(out2.Labels).Pointer()))
798
799 By("listing pods from the cache twice")
800 outList1 := &corev1.PodList{}
801 Expect(informerCache.List(context.Background(), outList1, client.InNamespace(testNamespaceOne))).To(Succeed())
802 outList2 := &corev1.PodList{}
803 Expect(informerCache.List(context.Background(), outList2, client.InNamespace(testNamespaceOne))).To(Succeed())
804
805 By("verifying the pointer fields in pod have the same addresses")
806 Expect(outList1.Items).To(HaveLen(len(outList2.Items)))
807 sort.SliceStable(outList1.Items, func(i, j int) bool { return outList1.Items[i].Name <= outList1.Items[j].Name })
808 sort.SliceStable(outList2.Items, func(i, j int) bool { return outList2.Items[i].Name <= outList2.Items[j].Name })
809 for i := range outList1.Items {
810 a := &outList1.Items[i]
811 b := &outList2.Items[i]
812 Expect(a).To(Equal(b))
813 Expect(reflect.ValueOf(a.Labels).Pointer()).To(BeIdenticalTo(reflect.ValueOf(b.Labels).Pointer()))
814 }
815 })
816 }
817
818 It("should return an error if the object is not found", func() {
819 By("getting a service that does not exists")
820 svc := &corev1.Service{}
821 svcKey := client.ObjectKey{Namespace: testNamespaceOne, Name: "unknown"}
822
823 By("verifying that an error is returned")
824 err := informerCache.Get(context.Background(), svcKey, svc)
825 Expect(err).To(HaveOccurred())
826 Expect(apierrors.IsNotFound(err)).To(BeTrue())
827 })
828
829 It("should return an error if getting object in unwatched namespace", func() {
830 By("getting a service that does not exists")
831 svc := &corev1.Service{}
832 svcKey := client.ObjectKey{Namespace: "unknown", Name: "unknown"}
833
834 By("verifying that an error is returned")
835 err := informerCache.Get(context.Background(), svcKey, svc)
836 Expect(err).To(HaveOccurred())
837 })
838
839 It("should return an error when context is cancelled", func() {
840 By("cancelling the context")
841 informerCacheCancel()
842
843 By("listing pods in test-namespace-1 with a cancelled context")
844 listObj := &corev1.PodList{}
845 err := informerCache.List(informerCacheCtx, listObj, client.InNamespace(testNamespaceOne))
846
847 By("verifying that an error is returned")
848 Expect(err).To(HaveOccurred())
849 Expect(apierrors.IsTimeout(err)).To(BeTrue())
850 })
851
852 It("should set the Limit option and limit number of objects to Limit when List is called", func() {
853 opts := &client.ListOptions{Limit: int64(3)}
854 By("verifying that only Limit (3) number of objects are retrieved from the cache")
855 listObj := &corev1.PodList{}
856 Expect(informerCache.List(context.Background(), listObj, opts)).To(Succeed())
857 Expect(listObj.Items).Should(HaveLen(3))
858 })
859
860 It("should return a limited result set matching the correct label", func() {
861 listObj := &corev1.PodList{}
862 labelOpt := client.MatchingLabels(map[string]string{"common-label": "common"})
863 limitOpt := client.Limit(1)
864 By("verifying that only Limit (1) number of objects are retrieved from the cache")
865 Expect(informerCache.List(context.Background(), listObj, labelOpt, limitOpt)).To(Succeed())
866 Expect(listObj.Items).Should(HaveLen(1))
867 })
868
869 It("should return an error if the continue list options is set", func() {
870 listObj := &corev1.PodList{}
871 continueOpt := client.Continue("token")
872 By("verifying that an error is returned")
873 err := informerCache.List(context.Background(), listObj, continueOpt)
874 Expect(err).To(HaveOccurred())
875 })
876 })
877
878 Context("with unstructured objects", func() {
879 It("should be able to list objects that haven't been watched previously", func() {
880 By("listing all services in the cluster")
881 listObj := &unstructured.UnstructuredList{}
882 listObj.SetGroupVersionKind(schema.GroupVersionKind{
883 Group: "",
884 Version: "v1",
885 Kind: "ServiceList",
886 })
887 err := informerCache.List(context.Background(), listObj)
888 Expect(err).To(Succeed())
889
890 By("verifying that the returned list contains the Kubernetes service")
891
892 Expect(listObj.Items).NotTo(BeEmpty())
893 hasKubeService := false
894 for i := range listObj.Items {
895 svc := &listObj.Items[i]
896 if isKubeService(svc) {
897 hasKubeService = true
898 break
899 }
900 }
901 Expect(hasKubeService).To(BeTrue())
902 })
903 It("should be able to get objects that haven't been watched previously", func() {
904 By("getting the Kubernetes service")
905 svc := &unstructured.Unstructured{}
906 svc.SetGroupVersionKind(schema.GroupVersionKind{
907 Group: "",
908 Version: "v1",
909 Kind: "Service",
910 })
911 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
912 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
913
914 By("verifying that the returned service looks reasonable")
915 Expect(svc.GetName()).To(Equal("kubernetes"))
916 Expect(svc.GetNamespace()).To(Equal("default"))
917 })
918
919 It("should support filtering by labels in a single namespace", func() {
920 By("listing pods with a particular label")
921
922 out := unstructured.UnstructuredList{}
923 out.SetGroupVersionKind(schema.GroupVersionKind{
924 Group: "",
925 Version: "v1",
926 Kind: "PodList",
927 })
928 err := informerCache.List(context.Background(), &out,
929 client.InNamespace(testNamespaceTwo),
930 client.MatchingLabels(map[string]string{"test-label": "test-pod-2"}))
931 Expect(err).To(Succeed())
932
933 By("verifying the returned pods have the correct label")
934 Expect(out.Items).NotTo(BeEmpty())
935 Expect(out.Items).Should(HaveLen(1))
936 actual := out.Items[0]
937 Expect(actual.GetLabels()["test-label"]).To(Equal("test-pod-2"))
938 })
939
940 It("should support filtering by labels from multiple namespaces", func() {
941 By("creating another pod with the same label but different namespace")
942 anotherPod := createPod("test-pod-2", testNamespaceOne, corev1.RestartPolicyAlways)
943 defer deletePod(anotherPod)
944
945 By("listing pods with a particular label")
946
947 out := unstructured.UnstructuredList{}
948 out.SetGroupVersionKind(schema.GroupVersionKind{
949 Group: "",
950 Version: "v1",
951 Kind: "PodList",
952 })
953 labels := map[string]string{"test-label": "test-pod-2"}
954 err := informerCache.List(context.Background(), &out, client.MatchingLabels(labels))
955 Expect(err).To(Succeed())
956
957 By("verifying multiple pods with the same label in different namespaces are returned")
958 Expect(out.Items).NotTo(BeEmpty())
959 Expect(out.Items).Should(HaveLen(2))
960 for _, actual := range out.Items {
961 Expect(actual.GetLabels()["test-label"]).To(Equal("test-pod-2"))
962 }
963 })
964
965 It("should be able to list objects by namespace", func() {
966 By("listing pods in test-namespace-1")
967 listObj := &unstructured.UnstructuredList{}
968 listObj.SetGroupVersionKind(schema.GroupVersionKind{
969 Group: "",
970 Version: "v1",
971 Kind: "PodList",
972 })
973 err := informerCache.List(context.Background(), listObj, client.InNamespace(testNamespaceOne))
974 Expect(err).To(Succeed())
975
976 By("verifying that the returned pods are in test-namespace-1")
977 Expect(listObj.Items).NotTo(BeEmpty())
978 Expect(listObj.Items).Should(HaveLen(2))
979 for _, item := range listObj.Items {
980 Expect(item.GetNamespace()).To(Equal(testNamespaceOne))
981 }
982 })
983
984 cacheRestrictSubTests := []struct {
985 nameSuffix string
986 cacheOpts cache.Options
987 }{
988 {
989 nameSuffix: "by using the per-gvk setting",
990 cacheOpts: cache.Options{
991 ByObject: map[client.Object]cache.ByObject{
992 &corev1.Pod{}: {
993 Namespaces: map[string]cache.Config{
994 testNamespaceOne: {},
995 },
996 },
997 },
998 },
999 },
1000 {
1001 nameSuffix: "by using the global DefaultNamespaces setting",
1002 cacheOpts: cache.Options{
1003 DefaultNamespaces: map[string]cache.Config{
1004 testNamespaceOne: {},
1005 },
1006 },
1007 },
1008 }
1009
1010 for _, tc := range cacheRestrictSubTests {
1011 It("should be able to restrict cache to a namespace "+tc.nameSuffix, func() {
1012 By("creating a namespaced cache")
1013 namespacedCache, err := cache.New(cfg, tc.cacheOpts)
1014 Expect(err).NotTo(HaveOccurred())
1015
1016 By("running the cache and waiting for it to sync")
1017 go func() {
1018 defer GinkgoRecover()
1019 Expect(namespacedCache.Start(informerCacheCtx)).To(Succeed())
1020 }()
1021 Expect(namespacedCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
1022
1023 By("listing pods in all namespaces")
1024 out := &unstructured.UnstructuredList{}
1025 out.SetGroupVersionKind(schema.GroupVersionKind{
1026 Group: "",
1027 Version: "v1",
1028 Kind: "PodList",
1029 })
1030 Expect(namespacedCache.List(context.Background(), out)).To(Succeed())
1031
1032 By("verifying the returned pod is from the watched namespace")
1033 Expect(out.Items).NotTo(BeEmpty())
1034 Expect(out.Items).Should(HaveLen(2))
1035 for _, item := range out.Items {
1036 Expect(item.GetNamespace()).To(Equal(testNamespaceOne))
1037 }
1038 By("listing all nodes - should still be able to list a cluster-scoped resource")
1039 nodeList := &unstructured.UnstructuredList{}
1040 nodeList.SetGroupVersionKind(schema.GroupVersionKind{
1041 Group: "",
1042 Version: "v1",
1043 Kind: "NodeList",
1044 })
1045 Expect(namespacedCache.List(context.Background(), nodeList)).To(Succeed())
1046
1047 By("verifying the node list is not empty")
1048 Expect(nodeList.Items).NotTo(BeEmpty())
1049
1050 By("getting a node - should still be able to get a cluster-scoped resource")
1051 node := &unstructured.Unstructured{}
1052 node.SetGroupVersionKind(schema.GroupVersionKind{
1053 Group: "",
1054 Version: "v1",
1055 Kind: "Node",
1056 })
1057
1058 By("verifying that getting the node works with an empty namespace")
1059 key1 := client.ObjectKey{Namespace: "", Name: testNodeOne}
1060 Expect(namespacedCache.Get(context.Background(), key1, node)).To(Succeed())
1061
1062 By("verifying that the namespace is ignored when getting a cluster-scoped resource")
1063 key2 := client.ObjectKey{Namespace: "random", Name: testNodeOne}
1064 Expect(namespacedCache.Get(context.Background(), key2, node)).To(Succeed())
1065 })
1066 }
1067
1068 if !isPodDisableDeepCopy(opts) {
1069 It("should deep copy the object unless told otherwise", func() {
1070 By("retrieving a specific pod from the cache")
1071 out := &unstructured.Unstructured{}
1072 out.SetGroupVersionKind(schema.GroupVersionKind{
1073 Group: "",
1074 Version: "v1",
1075 Kind: "Pod",
1076 })
1077 uKnownPod2 := &unstructured.Unstructured{}
1078 Expect(kscheme.Scheme.Convert(knownPod2, uKnownPod2, nil)).To(Succeed())
1079
1080 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
1081 Expect(informerCache.Get(context.Background(), podKey, out)).To(Succeed())
1082
1083 By("verifying the retrieved pod is equal to a known pod")
1084 Expect(out).To(Equal(uKnownPod2))
1085
1086 By("altering a field in the retrieved pod")
1087 m, _ := out.Object["spec"].(map[string]interface{})
1088 m["activeDeadlineSeconds"] = 4
1089
1090 By("verifying the pods are no longer equal")
1091 Expect(out).NotTo(Equal(knownPod2))
1092 })
1093 } else {
1094 It("should not deep copy the object if UnsafeDisableDeepCopy is enabled", func() {
1095 By("getting a specific pod from the cache twice")
1096 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
1097 out1 := &unstructured.Unstructured{}
1098 out1.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
1099 Expect(informerCache.Get(context.Background(), podKey, out1)).To(Succeed())
1100 out2 := &unstructured.Unstructured{}
1101 out2.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
1102 Expect(informerCache.Get(context.Background(), podKey, out2)).To(Succeed())
1103
1104 By("verifying the pointer fields in pod have the same addresses")
1105 Expect(out1).To(Equal(out2))
1106 Expect(reflect.ValueOf(out1.Object).Pointer()).To(BeIdenticalTo(reflect.ValueOf(out2.Object).Pointer()))
1107
1108 By("listing pods from the cache twice")
1109 outList1 := &unstructured.UnstructuredList{}
1110 outList1.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PodList"})
1111 Expect(informerCache.List(context.Background(), outList1, client.InNamespace(testNamespaceOne))).To(Succeed())
1112 outList2 := &unstructured.UnstructuredList{}
1113 outList2.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PodList"})
1114 Expect(informerCache.List(context.Background(), outList2, client.InNamespace(testNamespaceOne))).To(Succeed())
1115
1116 By("verifying the pointer fields in pod have the same addresses")
1117 Expect(outList1.Items).To(HaveLen(len(outList2.Items)))
1118 sort.SliceStable(outList1.Items, func(i, j int) bool { return outList1.Items[i].GetName() <= outList1.Items[j].GetName() })
1119 sort.SliceStable(outList2.Items, func(i, j int) bool { return outList2.Items[i].GetName() <= outList2.Items[j].GetName() })
1120 for i := range outList1.Items {
1121 a := &outList1.Items[i]
1122 b := &outList2.Items[i]
1123 Expect(a).To(Equal(b))
1124 Expect(reflect.ValueOf(a.Object).Pointer()).To(BeIdenticalTo(reflect.ValueOf(b.Object).Pointer()))
1125 }
1126 })
1127 }
1128
1129 It("should return an error if the object is not found", func() {
1130 By("getting a service that does not exists")
1131 svc := &unstructured.Unstructured{}
1132 svc.SetGroupVersionKind(schema.GroupVersionKind{
1133 Group: "",
1134 Version: "v1",
1135 Kind: "Service",
1136 })
1137 svcKey := client.ObjectKey{Namespace: testNamespaceOne, Name: "unknown"}
1138
1139 By("verifying that an error is returned")
1140 err := informerCache.Get(context.Background(), svcKey, svc)
1141 Expect(err).To(HaveOccurred())
1142 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1143 })
1144 It("should return an error if getting object in unwatched namespace", func() {
1145 By("getting a service that does not exists")
1146 svc := &corev1.Service{}
1147 svcKey := client.ObjectKey{Namespace: "unknown", Name: "unknown"}
1148
1149 By("verifying that an error is returned")
1150 err := informerCache.Get(context.Background(), svcKey, svc)
1151 Expect(err).To(HaveOccurred())
1152 })
1153 It("test multinamespaced cache for cluster scoped resources", func() {
1154 By("creating a multinamespaced cache to watch specific namespaces")
1155 m, err := cache.New(cfg, cache.Options{
1156 DefaultNamespaces: map[string]cache.Config{
1157 "default": {},
1158 testNamespaceOne: {},
1159 },
1160 })
1161 Expect(err).NotTo(HaveOccurred())
1162
1163 By("running the cache and waiting it for sync")
1164 go func() {
1165 defer GinkgoRecover()
1166 Expect(m.Start(informerCacheCtx)).To(Succeed())
1167 }()
1168 Expect(m.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
1169
1170 By("should be able to fetch cluster scoped resource")
1171 node := &corev1.Node{}
1172
1173 By("verifying that getting the node works with an empty namespace")
1174 key1 := client.ObjectKey{Namespace: "", Name: testNodeOne}
1175 Expect(m.Get(context.Background(), key1, node)).To(Succeed())
1176
1177 By("verifying if the cluster scoped resources are not duplicated")
1178 nodeList := &unstructured.UnstructuredList{}
1179 nodeList.SetGroupVersionKind(schema.GroupVersionKind{
1180 Group: "",
1181 Version: "v1",
1182 Kind: "NodeList",
1183 })
1184 Expect(m.List(context.Background(), nodeList)).To(Succeed())
1185
1186 By("verifying the node list is not empty")
1187 Expect(nodeList.Items).NotTo(BeEmpty())
1188 Expect(len(nodeList.Items)).To(BeEquivalentTo(2))
1189 })
1190 It("should return an error if the continue list options is set", func() {
1191 podList := &unstructured.Unstructured{}
1192 continueOpt := client.Continue("token")
1193 By("verifying that an error is returned")
1194 err := informerCache.List(context.Background(), podList, continueOpt)
1195 Expect(err).To(HaveOccurred())
1196 })
1197 })
1198 Context("with metadata-only objects", func() {
1199 It("should be able to list objects that haven't been watched previously", func() {
1200 By("listing all services in the cluster")
1201 listObj := &metav1.PartialObjectMetadataList{}
1202 listObj.SetGroupVersionKind(schema.GroupVersionKind{
1203 Group: "",
1204 Version: "v1",
1205 Kind: "ServiceList",
1206 })
1207 err := informerCache.List(context.Background(), listObj)
1208 Expect(err).To(Succeed())
1209
1210 By("verifying that the returned list contains the Kubernetes service")
1211
1212 Expect(listObj.Items).NotTo(BeEmpty())
1213 hasKubeService := false
1214 for i := range listObj.Items {
1215 svc := &listObj.Items[i]
1216 if isKubeService(svc) {
1217 hasKubeService = true
1218 break
1219 }
1220 }
1221 Expect(hasKubeService).To(BeTrue())
1222 })
1223 It("should be able to get objects that haven't been watched previously", func() {
1224 By("getting the Kubernetes service")
1225 svc := &metav1.PartialObjectMetadata{}
1226 svc.SetGroupVersionKind(schema.GroupVersionKind{
1227 Group: "",
1228 Version: "v1",
1229 Kind: "Service",
1230 })
1231 svcKey := client.ObjectKey{Namespace: "default", Name: "kubernetes"}
1232 Expect(informerCache.Get(context.Background(), svcKey, svc)).To(Succeed())
1233
1234 By("verifying that the returned service looks reasonable")
1235 Expect(svc.GetName()).To(Equal("kubernetes"))
1236 Expect(svc.GetNamespace()).To(Equal("default"))
1237 })
1238
1239 It("should support filtering by labels in a single namespace", func() {
1240 By("listing pods with a particular label")
1241
1242 out := metav1.PartialObjectMetadataList{}
1243 out.SetGroupVersionKind(schema.GroupVersionKind{
1244 Group: "",
1245 Version: "v1",
1246 Kind: "PodList",
1247 })
1248 err := informerCache.List(context.Background(), &out,
1249 client.InNamespace(testNamespaceTwo),
1250 client.MatchingLabels(map[string]string{"test-label": "test-pod-2"}))
1251 Expect(err).To(Succeed())
1252
1253 By("verifying the returned pods have the correct label")
1254 Expect(out.Items).NotTo(BeEmpty())
1255 Expect(out.Items).Should(HaveLen(1))
1256 actual := out.Items[0]
1257 Expect(actual.GetLabels()["test-label"]).To(Equal("test-pod-2"))
1258 })
1259
1260 It("should support filtering by labels from multiple namespaces", func() {
1261 By("creating another pod with the same label but different namespace")
1262 anotherPod := createPod("test-pod-2", testNamespaceOne, corev1.RestartPolicyAlways)
1263 defer deletePod(anotherPod)
1264
1265 By("listing pods with a particular label")
1266
1267 out := metav1.PartialObjectMetadataList{}
1268 out.SetGroupVersionKind(schema.GroupVersionKind{
1269 Group: "",
1270 Version: "v1",
1271 Kind: "PodList",
1272 })
1273 labels := map[string]string{"test-label": "test-pod-2"}
1274 err := informerCache.List(context.Background(), &out, client.MatchingLabels(labels))
1275 Expect(err).To(Succeed())
1276
1277 By("verifying multiple pods with the same label in different namespaces are returned")
1278 Expect(out.Items).NotTo(BeEmpty())
1279 Expect(out.Items).Should(HaveLen(2))
1280 for _, actual := range out.Items {
1281 Expect(actual.GetLabels()["test-label"]).To(Equal("test-pod-2"))
1282 }
1283 })
1284
1285 It("should be able to list objects by namespace", func() {
1286 By("listing pods in test-namespace-1")
1287 listObj := &metav1.PartialObjectMetadataList{}
1288 listObj.SetGroupVersionKind(schema.GroupVersionKind{
1289 Group: "",
1290 Version: "v1",
1291 Kind: "PodList",
1292 })
1293 err := informerCache.List(context.Background(), listObj, client.InNamespace(testNamespaceOne))
1294 Expect(err).To(Succeed())
1295
1296 By("verifying that the returned pods are in test-namespace-1")
1297 Expect(listObj.Items).NotTo(BeEmpty())
1298 Expect(listObj.Items).Should(HaveLen(2))
1299 for _, item := range listObj.Items {
1300 Expect(item.Namespace).To(Equal(testNamespaceOne))
1301 }
1302 })
1303
1304 It("should be able to restrict cache to a namespace", func() {
1305 By("creating a namespaced cache")
1306 namespacedCache, err := cache.New(cfg, cache.Options{DefaultNamespaces: map[string]cache.Config{testNamespaceOne: {}}})
1307 Expect(err).NotTo(HaveOccurred())
1308
1309 By("running the cache and waiting for it to sync")
1310 go func() {
1311 defer GinkgoRecover()
1312 Expect(namespacedCache.Start(informerCacheCtx)).To(Succeed())
1313 }()
1314 Expect(namespacedCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
1315
1316 By("listing pods in all namespaces")
1317 out := &metav1.PartialObjectMetadataList{}
1318 out.SetGroupVersionKind(schema.GroupVersionKind{
1319 Group: "",
1320 Version: "v1",
1321 Kind: "PodList",
1322 })
1323 Expect(namespacedCache.List(context.Background(), out)).To(Succeed())
1324
1325 By("verifying the returned pod is from the watched namespace")
1326 Expect(out.Items).NotTo(BeEmpty())
1327 Expect(out.Items).Should(HaveLen(2))
1328 for _, item := range out.Items {
1329 Expect(item.Namespace).To(Equal(testNamespaceOne))
1330 }
1331 By("listing all nodes - should still be able to list a cluster-scoped resource")
1332 nodeList := &metav1.PartialObjectMetadataList{}
1333 nodeList.SetGroupVersionKind(schema.GroupVersionKind{
1334 Group: "",
1335 Version: "v1",
1336 Kind: "NodeList",
1337 })
1338 Expect(namespacedCache.List(context.Background(), nodeList)).To(Succeed())
1339
1340 By("verifying the node list is not empty")
1341 Expect(nodeList.Items).NotTo(BeEmpty())
1342
1343 By("getting a node - should still be able to get a cluster-scoped resource")
1344 node := &metav1.PartialObjectMetadata{}
1345 node.SetGroupVersionKind(schema.GroupVersionKind{
1346 Group: "",
1347 Version: "v1",
1348 Kind: "Node",
1349 })
1350
1351 By("verifying that getting the node works with an empty namespace")
1352 key1 := client.ObjectKey{Namespace: "", Name: testNodeOne}
1353 Expect(namespacedCache.Get(context.Background(), key1, node)).To(Succeed())
1354
1355 By("verifying that the namespace is ignored when getting a cluster-scoped resource")
1356 key2 := client.ObjectKey{Namespace: "random", Name: testNodeOne}
1357 Expect(namespacedCache.Get(context.Background(), key2, node)).To(Succeed())
1358 })
1359
1360 It("should be able to restrict cache to a namespace for namespaced object and to given selectors for non namespaced object", func() {
1361 By("creating a namespaced cache")
1362 namespacedCache, err := cache.New(cfg, cache.Options{
1363 DefaultNamespaces: map[string]cache.Config{testNamespaceOne: {}},
1364 ByObject: map[client.Object]cache.ByObject{
1365 &corev1.Node{}: {
1366 Label: labels.SelectorFromSet(labels.Set{"name": testNodeTwo}),
1367 },
1368 },
1369 })
1370 Expect(err).NotTo(HaveOccurred())
1371
1372 By("running the cache and waiting for it to sync")
1373 go func() {
1374 defer GinkgoRecover()
1375 Expect(namespacedCache.Start(informerCacheCtx)).To(Succeed())
1376 }()
1377 Expect(namespacedCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
1378
1379 By("listing pods in all namespaces")
1380 out := &metav1.PartialObjectMetadataList{}
1381 out.SetGroupVersionKind(schema.GroupVersionKind{
1382 Group: "",
1383 Version: "v1",
1384 Kind: "PodList",
1385 })
1386 Expect(namespacedCache.List(context.Background(), out)).To(Succeed())
1387
1388 By("verifying the returned pod is from the watched namespace")
1389 Expect(out.Items).NotTo(BeEmpty())
1390 Expect(out.Items).Should(HaveLen(2))
1391 for _, item := range out.Items {
1392 Expect(item.Namespace).To(Equal(testNamespaceOne))
1393 }
1394 By("listing all nodes - should still be able to list a cluster-scoped resource")
1395 nodeList := &metav1.PartialObjectMetadataList{}
1396 nodeList.SetGroupVersionKind(schema.GroupVersionKind{
1397 Group: "",
1398 Version: "v1",
1399 Kind: "NodeList",
1400 })
1401 Expect(namespacedCache.List(context.Background(), nodeList)).To(Succeed())
1402
1403 By("verifying the node list is not empty")
1404 Expect(nodeList.Items).NotTo(BeEmpty())
1405
1406 By("getting a node - should still be able to get a cluster-scoped resource")
1407 node := &metav1.PartialObjectMetadata{}
1408 node.SetGroupVersionKind(schema.GroupVersionKind{
1409 Group: "",
1410 Version: "v1",
1411 Kind: "Node",
1412 })
1413
1414 By("verifying that getting the node works with an empty namespace")
1415 key1 := client.ObjectKey{Namespace: "", Name: testNodeTwo}
1416 Expect(namespacedCache.Get(context.Background(), key1, node)).To(Succeed())
1417
1418 By("verifying that the namespace is ignored when getting a cluster-scoped resource")
1419 key2 := client.ObjectKey{Namespace: "random", Name: testNodeTwo}
1420 Expect(namespacedCache.Get(context.Background(), key2, node)).To(Succeed())
1421
1422 By("verifying that an error is returned for node with not matching label")
1423 key3 := client.ObjectKey{Namespace: "", Name: testNodeOne}
1424 err = namespacedCache.Get(context.Background(), key3, node)
1425 Expect(err).To(HaveOccurred())
1426 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1427 })
1428
1429 if !isPodDisableDeepCopy(opts) {
1430 It("should deep copy the object unless told otherwise", func() {
1431 By("retrieving a specific pod from the cache")
1432 out := &metav1.PartialObjectMetadata{}
1433 out.SetGroupVersionKind(schema.GroupVersionKind{
1434 Group: "",
1435 Version: "v1",
1436 Kind: "Pod",
1437 })
1438 uKnownPod2 := &metav1.PartialObjectMetadata{}
1439 knownPod2.(*corev1.Pod).ObjectMeta.DeepCopyInto(&uKnownPod2.ObjectMeta)
1440 uKnownPod2.SetGroupVersionKind(schema.GroupVersionKind{
1441 Group: "",
1442 Version: "v1",
1443 Kind: "Pod",
1444 })
1445
1446 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
1447 Expect(informerCache.Get(context.Background(), podKey, out)).To(Succeed())
1448
1449 By("verifying the retrieved pod is equal to a known pod")
1450 Expect(out).To(Equal(uKnownPod2))
1451
1452 By("altering a field in the retrieved pod")
1453 out.Labels["foo"] = "bar"
1454
1455 By("verifying the pods are no longer equal")
1456 Expect(out).NotTo(Equal(knownPod2))
1457 })
1458 } else {
1459 It("should not deep copy the object if UnsafeDisableDeepCopy is enabled", func() {
1460 By("getting a specific pod from the cache twice")
1461 podKey := client.ObjectKey{Name: "test-pod-2", Namespace: testNamespaceTwo}
1462 out1 := &metav1.PartialObjectMetadata{}
1463 out1.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
1464 Expect(informerCache.Get(context.Background(), podKey, out1)).To(Succeed())
1465 out2 := &metav1.PartialObjectMetadata{}
1466 out2.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
1467 Expect(informerCache.Get(context.Background(), podKey, out2)).To(Succeed())
1468
1469 By("verifying the pods have the same pointer addresses")
1470 By("verifying the pointer fields in pod have the same addresses")
1471 Expect(out1).To(Equal(out2))
1472 Expect(reflect.ValueOf(out1.Labels).Pointer()).To(BeIdenticalTo(reflect.ValueOf(out2.Labels).Pointer()))
1473
1474 By("listing pods from the cache twice")
1475 outList1 := &metav1.PartialObjectMetadataList{}
1476 outList1.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PodList"})
1477 Expect(informerCache.List(context.Background(), outList1, client.InNamespace(testNamespaceOne))).To(Succeed())
1478 outList2 := &metav1.PartialObjectMetadataList{}
1479 outList2.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PodList"})
1480 Expect(informerCache.List(context.Background(), outList2, client.InNamespace(testNamespaceOne))).To(Succeed())
1481
1482 By("verifying the pointer fields in pod have the same addresses")
1483 Expect(outList1.Items).To(HaveLen(len(outList2.Items)))
1484 sort.SliceStable(outList1.Items, func(i, j int) bool { return outList1.Items[i].Name <= outList1.Items[j].Name })
1485 sort.SliceStable(outList2.Items, func(i, j int) bool { return outList2.Items[i].Name <= outList2.Items[j].Name })
1486 for i := range outList1.Items {
1487 a := &outList1.Items[i]
1488 b := &outList2.Items[i]
1489 Expect(a).To(Equal(b))
1490 Expect(reflect.ValueOf(a.Labels).Pointer()).To(BeIdenticalTo(reflect.ValueOf(b.Labels).Pointer()))
1491 }
1492 })
1493 }
1494
1495 It("should return an error if the object is not found", func() {
1496 By("getting a service that does not exists")
1497 svc := &metav1.PartialObjectMetadata{}
1498 svc.SetGroupVersionKind(schema.GroupVersionKind{
1499 Group: "",
1500 Version: "v1",
1501 Kind: "Service",
1502 })
1503 svcKey := client.ObjectKey{Namespace: testNamespaceOne, Name: "unknown"}
1504
1505 By("verifying that an error is returned")
1506 err := informerCache.Get(context.Background(), svcKey, svc)
1507 Expect(err).To(HaveOccurred())
1508 Expect(apierrors.IsNotFound(err)).To(BeTrue())
1509 })
1510 It("should return an error if getting object in unwatched namespace", func() {
1511 By("getting a service that does not exists")
1512 svc := &corev1.Service{}
1513 svcKey := client.ObjectKey{Namespace: "unknown", Name: "unknown"}
1514
1515 By("verifying that an error is returned")
1516 err := informerCache.Get(context.Background(), svcKey, svc)
1517 Expect(err).To(HaveOccurred())
1518 })
1519 })
1520 type selectorsTestCase struct {
1521 options cache.Options
1522 expectedPods []string
1523 }
1524 DescribeTable(" and cache with selectors", func(tc selectorsTestCase) {
1525 By("creating the cache")
1526 informer, err := cache.New(cfg, tc.options)
1527 Expect(err).NotTo(HaveOccurred())
1528
1529 By("running the cache and waiting for it to sync")
1530 go func() {
1531 defer GinkgoRecover()
1532 Expect(informer.Start(informerCacheCtx)).To(Succeed())
1533 }()
1534 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
1535
1536 By("Checking with structured")
1537 obtainedStructuredPodList := corev1.PodList{}
1538 Expect(informer.List(context.Background(), &obtainedStructuredPodList)).To(Succeed())
1539 Expect(obtainedStructuredPodList.Items).Should(WithTransform(func(pods []corev1.Pod) []string {
1540 obtainedPodNames := []string{}
1541 for _, pod := range pods {
1542 obtainedPodNames = append(obtainedPodNames, pod.Name)
1543 }
1544 return obtainedPodNames
1545 }, ConsistOf(tc.expectedPods)))
1546 for _, pod := range obtainedStructuredPodList.Items {
1547 Expect(informer.Get(context.Background(), client.ObjectKeyFromObject(&pod), &pod)).To(Succeed())
1548 }
1549
1550 By("Checking with unstructured")
1551 obtainedUnstructuredPodList := unstructured.UnstructuredList{}
1552 obtainedUnstructuredPodList.SetGroupVersionKind(schema.GroupVersionKind{
1553 Group: "",
1554 Version: "v1",
1555 Kind: "PodList",
1556 })
1557 err = informer.List(context.Background(), &obtainedUnstructuredPodList)
1558 Expect(err).To(Succeed())
1559 Expect(obtainedUnstructuredPodList.Items).Should(WithTransform(func(pods []unstructured.Unstructured) []string {
1560 obtainedPodNames := []string{}
1561 for _, pod := range pods {
1562 obtainedPodNames = append(obtainedPodNames, pod.GetName())
1563 }
1564 return obtainedPodNames
1565 }, ConsistOf(tc.expectedPods)))
1566 for _, pod := range obtainedUnstructuredPodList.Items {
1567 Expect(informer.Get(context.Background(), client.ObjectKeyFromObject(&pod), &pod)).To(Succeed())
1568 }
1569
1570 By("Checking with metadata")
1571 obtainedMetadataPodList := metav1.PartialObjectMetadataList{}
1572 obtainedMetadataPodList.SetGroupVersionKind(schema.GroupVersionKind{
1573 Group: "",
1574 Version: "v1",
1575 Kind: "PodList",
1576 })
1577 err = informer.List(context.Background(), &obtainedMetadataPodList)
1578 Expect(err).To(Succeed())
1579 Expect(obtainedMetadataPodList.Items).Should(WithTransform(func(pods []metav1.PartialObjectMetadata) []string {
1580 obtainedPodNames := []string{}
1581 for _, pod := range pods {
1582 obtainedPodNames = append(obtainedPodNames, pod.Name)
1583 }
1584 return obtainedPodNames
1585 }, ConsistOf(tc.expectedPods)))
1586 for _, pod := range obtainedMetadataPodList.Items {
1587 Expect(informer.Get(context.Background(), client.ObjectKeyFromObject(&pod), &pod)).To(Succeed())
1588 }
1589 },
1590 Entry("when selectors are empty it has to inform about all the pods", selectorsTestCase{
1591 expectedPods: []string{"test-pod-1", "test-pod-2", "test-pod-3", "test-pod-4", "test-pod-5", "test-pod-6"},
1592 }),
1593 Entry("type-level field selector matches one pod", selectorsTestCase{
1594 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1595 &corev1.Pod{}: {Field: fields.SelectorFromSet(map[string]string{
1596 "metadata.name": "test-pod-2",
1597 })},
1598 }},
1599 expectedPods: []string{"test-pod-2"},
1600 }),
1601 Entry("global field selector matches one pod", selectorsTestCase{
1602 options: cache.Options{
1603 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{
1604 "metadata.name": "test-pod-2",
1605 }),
1606 },
1607 expectedPods: []string{"test-pod-2"},
1608 }),
1609 Entry("type-level field selectors matches multiple pods", selectorsTestCase{
1610 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1611 &corev1.Pod{}: {Field: fields.SelectorFromSet(map[string]string{
1612 "metadata.namespace": testNamespaceTwo,
1613 })},
1614 }},
1615 expectedPods: []string{"test-pod-2", "test-pod-3", "test-pod-6"},
1616 }),
1617 Entry("global field selectors matches multiple pods", selectorsTestCase{
1618 options: cache.Options{
1619 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{
1620 "metadata.namespace": testNamespaceTwo,
1621 }),
1622 },
1623 expectedPods: []string{"test-pod-2", "test-pod-3", "test-pod-6"},
1624 }),
1625 Entry("type-level label selector matches one pod", selectorsTestCase{
1626 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1627 &corev1.Pod{}: {Label: labels.SelectorFromSet(map[string]string{
1628 "test-label": "test-pod-4",
1629 })},
1630 }},
1631 expectedPods: []string{"test-pod-4"},
1632 }),
1633 Entry("namespaces configured, type-level label selector matches everything, overrides global selector", selectorsTestCase{
1634 options: cache.Options{
1635 DefaultNamespaces: map[string]cache.Config{testNamespaceOne: {}},
1636 ByObject: map[client.Object]cache.ByObject{
1637 &corev1.Pod{}: {Label: labels.Everything()},
1638 },
1639 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"does-not": "match-anything"}),
1640 },
1641 expectedPods: []string{"test-pod-1", "test-pod-5"},
1642 }),
1643 Entry("namespaces configured, global selector is used", selectorsTestCase{
1644 options: cache.Options{
1645 DefaultNamespaces: map[string]cache.Config{testNamespaceTwo: {}},
1646 ByObject: map[client.Object]cache.ByObject{
1647 &corev1.Pod{}: {},
1648 },
1649 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"common-label": "common"}),
1650 },
1651 expectedPods: []string{"test-pod-3"},
1652 }),
1653 Entry("global label selector matches one pod", selectorsTestCase{
1654 options: cache.Options{
1655 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{
1656 "test-label": "test-pod-4",
1657 }),
1658 },
1659 expectedPods: []string{"test-pod-4"},
1660 }),
1661 Entry("type-level label selector matches multiple pods", selectorsTestCase{
1662 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1663 &corev1.Pod{}: {Label: labels.SelectorFromSet(map[string]string{
1664 "common-label": "common",
1665 })},
1666 }},
1667 expectedPods: []string{"test-pod-3", "test-pod-4"},
1668 }),
1669 Entry("global label selector matches multiple pods", selectorsTestCase{
1670 options: cache.Options{
1671 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{
1672 "common-label": "common",
1673 }),
1674 },
1675 expectedPods: []string{"test-pod-3", "test-pod-4"},
1676 }),
1677 Entry("type-level label and field selector, matches one pod", selectorsTestCase{
1678 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1679 &corev1.Pod{}: {
1680 Label: labels.SelectorFromSet(map[string]string{"common-label": "common"}),
1681 Field: fields.SelectorFromSet(map[string]string{"metadata.namespace": testNamespaceTwo}),
1682 },
1683 }},
1684 expectedPods: []string{"test-pod-3"},
1685 }),
1686 Entry("global label and field selector, matches one pod", selectorsTestCase{
1687 options: cache.Options{
1688 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"common-label": "common"}),
1689 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{"metadata.namespace": testNamespaceTwo}),
1690 },
1691 expectedPods: []string{"test-pod-3"},
1692 }),
1693 Entry("type-level label selector does not match, no results", selectorsTestCase{
1694 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1695 &corev1.Pod{}: {Label: labels.SelectorFromSet(map[string]string{
1696 "new-label": "new",
1697 })},
1698 }},
1699 expectedPods: []string{},
1700 }),
1701 Entry("global label selector does not match, no results", selectorsTestCase{
1702 options: cache.Options{
1703 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{
1704 "new-label": "new",
1705 }),
1706 },
1707 expectedPods: []string{},
1708 }),
1709 Entry("type-level field selector does not match, no results", selectorsTestCase{
1710 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1711 &corev1.Pod{}: {Field: fields.SelectorFromSet(map[string]string{
1712 "metadata.namespace": "new",
1713 })},
1714 }},
1715 expectedPods: []string{},
1716 }),
1717 Entry("global field selector does not match, no results", selectorsTestCase{
1718 options: cache.Options{
1719 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{
1720 "metadata.namespace": "new",
1721 }),
1722 },
1723 expectedPods: []string{},
1724 }),
1725 Entry("type-level field selector on namespace matches one pod", selectorsTestCase{
1726 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1727 &corev1.Pod{}: {Namespaces: map[string]cache.Config{
1728 testNamespaceTwo: {
1729 FieldSelector: fields.SelectorFromSet(map[string]string{
1730 "metadata.name": "test-pod-2",
1731 }),
1732 },
1733 }},
1734 }},
1735 expectedPods: []string{"test-pod-2"},
1736 }),
1737 Entry("type-level field selector on namespace doesn't match", selectorsTestCase{
1738 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1739 &corev1.Pod{}: {Namespaces: map[string]cache.Config{
1740 testNamespaceTwo: {
1741 FieldSelector: fields.SelectorFromSet(map[string]string{
1742 "metadata.name": "test-pod-doesn-exist",
1743 }),
1744 },
1745 }},
1746 }},
1747 expectedPods: []string{},
1748 }),
1749 Entry("global field selector on namespace matches one pod", selectorsTestCase{
1750 options: cache.Options{
1751 DefaultNamespaces: map[string]cache.Config{
1752 testNamespaceTwo: {
1753 FieldSelector: fields.SelectorFromSet(map[string]string{
1754 "metadata.name": "test-pod-2",
1755 }),
1756 },
1757 },
1758 },
1759 expectedPods: []string{"test-pod-2"},
1760 }),
1761 Entry("global field selector on namespace doesn't match", selectorsTestCase{
1762 options: cache.Options{
1763 DefaultNamespaces: map[string]cache.Config{
1764 testNamespaceTwo: {
1765 FieldSelector: fields.SelectorFromSet(map[string]string{
1766 "metadata.name": "test-pod-doesn-exist",
1767 }),
1768 },
1769 },
1770 },
1771 expectedPods: []string{},
1772 }),
1773 Entry("type-level label selector on namespace matches one pod", selectorsTestCase{
1774 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1775 &corev1.Pod{}: {Namespaces: map[string]cache.Config{
1776 testNamespaceTwo: {
1777 LabelSelector: labels.SelectorFromSet(map[string]string{
1778 "test-label": "test-pod-2",
1779 }),
1780 },
1781 }},
1782 }},
1783 expectedPods: []string{"test-pod-2"},
1784 }),
1785 Entry("type-level label selector on namespace doesn't match", selectorsTestCase{
1786 options: cache.Options{ByObject: map[client.Object]cache.ByObject{
1787 &corev1.Pod{}: {Namespaces: map[string]cache.Config{
1788 testNamespaceTwo: {
1789 LabelSelector: labels.SelectorFromSet(map[string]string{
1790 "test-label": "test-pod-doesn-exist",
1791 }),
1792 },
1793 }},
1794 }},
1795 expectedPods: []string{},
1796 }),
1797 Entry("global label selector on namespace matches one pod", selectorsTestCase{
1798 options: cache.Options{
1799 DefaultNamespaces: map[string]cache.Config{
1800 testNamespaceTwo: {
1801 LabelSelector: labels.SelectorFromSet(map[string]string{
1802 "test-label": "test-pod-2",
1803 }),
1804 },
1805 },
1806 },
1807 expectedPods: []string{"test-pod-2"},
1808 }),
1809 Entry("global label selector on namespace doesn't match", selectorsTestCase{
1810 options: cache.Options{
1811 DefaultNamespaces: map[string]cache.Config{
1812 testNamespaceTwo: {
1813 LabelSelector: labels.SelectorFromSet(map[string]string{
1814 "test-label": "test-pod-doesn-exist",
1815 }),
1816 },
1817 },
1818 },
1819 expectedPods: []string{},
1820 }),
1821 Entry("Only NamespaceAll in DefaultNamespaces returns all pods", selectorsTestCase{
1822 options: cache.Options{
1823 DefaultNamespaces: map[string]cache.Config{
1824 metav1.NamespaceAll: {},
1825 },
1826 },
1827 expectedPods: []string{"test-pod-1", "test-pod-2", "test-pod-3", "test-pod-4", "test-pod-5", "test-pod-6"},
1828 }),
1829 Entry("Only NamespaceAll in ByObject.Namespaces returns all pods", selectorsTestCase{
1830 options: cache.Options{
1831 ByObject: map[client.Object]cache.ByObject{
1832 &corev1.Pod{}: {
1833 Namespaces: map[string]cache.Config{
1834 metav1.NamespaceAll: {},
1835 },
1836 },
1837 },
1838 },
1839 expectedPods: []string{"test-pod-1", "test-pod-2", "test-pod-3", "test-pod-4", "test-pod-5", "test-pod-6"},
1840 }),
1841 Entry("NamespaceAll in DefaultNamespaces creates a cache for all Namespaces that are not in DefaultNamespaces", selectorsTestCase{
1842 options: cache.Options{
1843 DefaultNamespaces: map[string]cache.Config{
1844 metav1.NamespaceAll: {},
1845 testNamespaceOne: {
1846
1847 LabelSelector: labels.SelectorFromSet(labels.Set{"no-present": "not-present"})},
1848 },
1849 },
1850
1851 expectedPods: []string{"test-pod-2", "test-pod-3", "test-pod-4", "test-pod-6"},
1852 }),
1853 Entry("NamespaceAll in ByObject.Namespaces creates a cache for all Namespaces that are not in ByObject.Namespaces", selectorsTestCase{
1854 options: cache.Options{
1855 ByObject: map[client.Object]cache.ByObject{
1856 &corev1.Pod{}: {
1857 Namespaces: map[string]cache.Config{
1858 metav1.NamespaceAll: {},
1859 testNamespaceOne: {
1860
1861 LabelSelector: labels.SelectorFromSet(labels.Set{"no-present": "not-present"})},
1862 },
1863 },
1864 },
1865 },
1866
1867 expectedPods: []string{"test-pod-2", "test-pod-3", "test-pod-4", "test-pod-6"},
1868 }),
1869 )
1870 })
1871 Describe("as an Informer", func() {
1872 It("should error when starting the cache a second time", func() {
1873 err := informerCache.Start(context.Background())
1874 Expect(err).To(HaveOccurred())
1875 Expect(err.Error()).To(ContainSubstring("Informer already started"))
1876 })
1877
1878 Context("with structured objects", func() {
1879 It("should be able to get informer for the object", func() {
1880 By("getting a shared index informer for a pod")
1881 pod := &corev1.Pod{
1882 ObjectMeta: metav1.ObjectMeta{
1883 Name: "informer-obj",
1884 Namespace: "default",
1885 },
1886 Spec: corev1.PodSpec{
1887 Containers: []corev1.Container{
1888 {
1889 Name: "nginx",
1890 Image: "nginx",
1891 },
1892 },
1893 },
1894 }
1895 sii, err := informerCache.GetInformer(context.TODO(), pod)
1896 Expect(err).NotTo(HaveOccurred())
1897 Expect(sii).NotTo(BeNil())
1898 Expect(sii.HasSynced()).To(BeTrue())
1899
1900 By("adding an event handler listening for object creation which sends the object to a channel")
1901 out := make(chan interface{})
1902 addFunc := func(obj interface{}) {
1903 out <- obj
1904 }
1905 _, _ = sii.AddEventHandler(kcache.ResourceEventHandlerFuncs{AddFunc: addFunc})
1906
1907 By("adding an object")
1908 cl, err := client.New(cfg, client.Options{})
1909 Expect(err).NotTo(HaveOccurred())
1910 Expect(cl.Create(context.Background(), pod)).To(Succeed())
1911 defer deletePod(pod)
1912
1913 By("verifying the object is received on the channel")
1914 Eventually(out).Should(Receive(Equal(pod)))
1915 })
1916 It("should be able to stop and restart informers", func() {
1917 By("getting a shared index informer for a pod")
1918 pod := &corev1.Pod{
1919 ObjectMeta: metav1.ObjectMeta{
1920 Name: "informer-obj",
1921 Namespace: "default",
1922 },
1923 Spec: corev1.PodSpec{
1924 Containers: []corev1.Container{
1925 {
1926 Name: "nginx",
1927 Image: "nginx",
1928 },
1929 },
1930 },
1931 }
1932 sii, err := informerCache.GetInformer(context.TODO(), pod)
1933 Expect(err).NotTo(HaveOccurred())
1934 Expect(sii).NotTo(BeNil())
1935 Expect(sii.HasSynced()).To(BeTrue())
1936
1937 By("removing the existing informer")
1938 Expect(informerCache.RemoveInformer(context.TODO(), pod)).To(Succeed())
1939 Eventually(sii.IsStopped).WithTimeout(5 * time.Second).Should(BeTrue())
1940
1941 By("recreating the informer")
1942
1943 sii2, err := informerCache.GetInformer(context.TODO(), pod)
1944 Expect(err).NotTo(HaveOccurred())
1945 Expect(sii2).NotTo(BeNil())
1946 Expect(sii2.HasSynced()).To(BeTrue())
1947
1948 By("validating the two informers are in different states")
1949 Expect(sii.IsStopped()).To(BeTrue())
1950 Expect(sii2.IsStopped()).To(BeFalse())
1951 })
1952 It("should be able to get an informer by group/version/kind", func() {
1953 By("getting an shared index informer for gvk = core/v1/pod")
1954 gvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}
1955 sii, err := informerCache.GetInformerForKind(context.TODO(), gvk)
1956 Expect(err).NotTo(HaveOccurred())
1957 Expect(sii).NotTo(BeNil())
1958 Expect(sii.HasSynced()).To(BeTrue())
1959
1960 By("adding an event handler listening for object creation which sends the object to a channel")
1961 out := make(chan interface{})
1962 addFunc := func(obj interface{}) {
1963 out <- obj
1964 }
1965 _, _ = sii.AddEventHandler(kcache.ResourceEventHandlerFuncs{AddFunc: addFunc})
1966
1967 By("adding an object")
1968 cl, err := client.New(cfg, client.Options{})
1969 Expect(err).NotTo(HaveOccurred())
1970 pod := &corev1.Pod{
1971 ObjectMeta: metav1.ObjectMeta{
1972 Name: "informer-gvk",
1973 Namespace: "default",
1974 },
1975 Spec: corev1.PodSpec{
1976 Containers: []corev1.Container{
1977 {
1978 Name: "nginx",
1979 Image: "nginx",
1980 },
1981 },
1982 },
1983 }
1984 Expect(cl.Create(context.Background(), pod)).To(Succeed())
1985 defer deletePod(pod)
1986
1987 By("verifying the object is received on the channel")
1988 Eventually(out).Should(Receive(Equal(pod)))
1989 })
1990 It("should be able to index an object field then retrieve objects by that field", func() {
1991 By("creating the cache")
1992 informer, err := cache.New(cfg, cache.Options{})
1993 Expect(err).NotTo(HaveOccurred())
1994
1995 By("indexing the restartPolicy field of the Pod object before starting")
1996 pod := &corev1.Pod{}
1997 indexFunc := func(obj client.Object) []string {
1998 return []string{string(obj.(*corev1.Pod).Spec.RestartPolicy)}
1999 }
2000 Expect(informer.IndexField(context.TODO(), pod, "spec.restartPolicy", indexFunc)).To(Succeed())
2001
2002 By("running the cache and waiting for it to sync")
2003 go func() {
2004 defer GinkgoRecover()
2005 Expect(informer.Start(informerCacheCtx)).To(Succeed())
2006 }()
2007 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
2008
2009 By("listing Pods with restartPolicyOnFailure")
2010 listObj := &corev1.PodList{}
2011 Expect(informer.List(context.Background(), listObj,
2012 client.MatchingFields{"spec.restartPolicy": "OnFailure"})).To(Succeed())
2013 By("verifying that the returned pods have correct restart policy")
2014 Expect(listObj.Items).NotTo(BeEmpty())
2015 Expect(listObj.Items).Should(HaveLen(1))
2016 actual := listObj.Items[0]
2017 Expect(actual.Name).To(Equal("test-pod-3"))
2018 })
2019
2020 It("should allow for get informer to be cancelled", func() {
2021 By("creating a context and cancelling it")
2022 informerCacheCancel()
2023
2024 By("getting a shared index informer for a pod with a cancelled context")
2025 pod := &corev1.Pod{
2026 ObjectMeta: metav1.ObjectMeta{
2027 Name: "informer-obj",
2028 Namespace: "default",
2029 },
2030 Spec: corev1.PodSpec{
2031 Containers: []corev1.Container{
2032 {
2033 Name: "nginx",
2034 Image: "nginx",
2035 },
2036 },
2037 },
2038 }
2039 sii, err := informerCache.GetInformer(informerCacheCtx, pod)
2040 Expect(err).To(HaveOccurred())
2041 Expect(sii).To(BeNil())
2042 Expect(apierrors.IsTimeout(err)).To(BeTrue())
2043 })
2044
2045 It("should allow getting an informer by group/version/kind to be cancelled", func() {
2046 By("creating a context and cancelling it")
2047 informerCacheCancel()
2048
2049 By("getting an shared index informer for gvk = core/v1/pod with a cancelled context")
2050 gvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}
2051 sii, err := informerCache.GetInformerForKind(informerCacheCtx, gvk)
2052 Expect(err).To(HaveOccurred())
2053 Expect(sii).To(BeNil())
2054 Expect(apierrors.IsTimeout(err)).To(BeTrue())
2055 })
2056
2057 It("should be able not to change indexer values after indexing cluster-scope objects", func() {
2058 By("creating the cache")
2059 informer, err := cache.New(cfg, cache.Options{})
2060 Expect(err).NotTo(HaveOccurred())
2061
2062 By("indexing the Namespace objects with fixed values before starting")
2063 ns := &corev1.Namespace{}
2064 indexerValues := []string{"a", "b", "c"}
2065 fieldName := "fixedValues"
2066 indexFunc := func(obj client.Object) []string {
2067 return indexerValues
2068 }
2069 Expect(informer.IndexField(context.TODO(), ns, fieldName, indexFunc)).To(Succeed())
2070
2071 By("running the cache and waiting for it to sync")
2072 go func() {
2073 defer GinkgoRecover()
2074 Expect(informer.Start(informerCacheCtx)).To(Succeed())
2075 }()
2076 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
2077
2078 By("listing Namespaces with fixed indexer")
2079 listObj := &corev1.NamespaceList{}
2080 Expect(informer.List(context.Background(), listObj,
2081 client.MatchingFields{fieldName: "a"})).To(Succeed())
2082 Expect(listObj.Items).NotTo(BeZero())
2083
2084 By("verifying the indexing does not change fixed returned values")
2085 Expect(indexerValues).Should(HaveLen(3))
2086 Expect(indexerValues[0]).To(Equal("a"))
2087 Expect(indexerValues[1]).To(Equal("b"))
2088 Expect(indexerValues[2]).To(Equal("c"))
2089 })
2090
2091 It("should be able to matching fields with multiple indexes", func() {
2092 By("creating the cache")
2093 informer, err := cache.New(cfg, cache.Options{})
2094 Expect(err).NotTo(HaveOccurred())
2095
2096 pod := &corev1.Pod{}
2097 By("indexing pods with label before starting")
2098 fieldName1 := "indexByLabel"
2099 indexFunc1 := func(obj client.Object) []string {
2100 return []string{obj.(*corev1.Pod).Labels["common-label"]}
2101 }
2102 Expect(informer.IndexField(context.TODO(), pod, fieldName1, indexFunc1)).To(Succeed())
2103 By("indexing pods with restart policy before starting")
2104 fieldName2 := "indexByPolicy"
2105 indexFunc2 := func(obj client.Object) []string {
2106 return []string{string(obj.(*corev1.Pod).Spec.RestartPolicy)}
2107 }
2108 Expect(informer.IndexField(context.TODO(), pod, fieldName2, indexFunc2)).To(Succeed())
2109
2110 By("running the cache and waiting for it to sync")
2111 go func() {
2112 defer GinkgoRecover()
2113 Expect(informer.Start(informerCacheCtx)).To(Succeed())
2114 }()
2115 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
2116
2117 By("listing pods with label index")
2118 listObj := &corev1.PodList{}
2119 Expect(informer.List(context.Background(), listObj,
2120 client.MatchingFields{fieldName1: "common"})).To(Succeed())
2121 Expect(listObj.Items).To(HaveLen(2))
2122
2123 By("listing pods with restart policy index")
2124 listObj = &corev1.PodList{}
2125 Expect(informer.List(context.Background(), listObj,
2126 client.MatchingFields{fieldName2: string(corev1.RestartPolicyNever)})).To(Succeed())
2127 Expect(listObj.Items).To(HaveLen(3))
2128
2129 By("listing pods with both fixed indexers 1 and 2")
2130 listObj = &corev1.PodList{}
2131 Expect(informer.List(context.Background(), listObj,
2132 client.MatchingFields{fieldName1: "common", fieldName2: string(corev1.RestartPolicyNever)})).To(Succeed())
2133 Expect(listObj.Items).To(HaveLen(1))
2134 })
2135 })
2136 Context("with unstructured objects", func() {
2137 It("should be able to get informer for the object", func() {
2138 By("getting a shared index informer for a pod")
2139
2140 pod := &unstructured.Unstructured{
2141 Object: map[string]interface{}{
2142 "spec": map[string]interface{}{
2143 "containers": []map[string]interface{}{
2144 {
2145 "name": "nginx",
2146 "image": "nginx",
2147 },
2148 },
2149 },
2150 },
2151 }
2152 pod.SetName("informer-obj2")
2153 pod.SetNamespace("default")
2154 pod.SetGroupVersionKind(schema.GroupVersionKind{
2155 Group: "",
2156 Version: "v1",
2157 Kind: "Pod",
2158 })
2159 sii, err := informerCache.GetInformer(context.TODO(), pod)
2160 Expect(err).NotTo(HaveOccurred())
2161 Expect(sii).NotTo(BeNil())
2162 Expect(sii.HasSynced()).To(BeTrue())
2163
2164 By("adding an event handler listening for object creation which sends the object to a channel")
2165 out := make(chan interface{})
2166 addFunc := func(obj interface{}) {
2167 out <- obj
2168 }
2169 _, _ = sii.AddEventHandler(kcache.ResourceEventHandlerFuncs{AddFunc: addFunc})
2170
2171 By("adding an object")
2172 cl, err := client.New(cfg, client.Options{})
2173 Expect(err).NotTo(HaveOccurred())
2174 Expect(cl.Create(context.Background(), pod)).To(Succeed())
2175 defer deletePod(pod)
2176
2177 By("verifying the object is received on the channel")
2178 Eventually(out).Should(Receive(Equal(pod)))
2179 })
2180
2181 It("should be able to stop and restart informers", func() {
2182 By("getting a shared index informer for a pod")
2183 pod := &unstructured.Unstructured{
2184 Object: map[string]interface{}{
2185 "spec": map[string]interface{}{
2186 "containers": []map[string]interface{}{
2187 {
2188 "name": "nginx",
2189 "image": "nginx",
2190 },
2191 },
2192 },
2193 },
2194 }
2195 pod.SetName("informer-obj2")
2196 pod.SetNamespace("default")
2197 pod.SetGroupVersionKind(schema.GroupVersionKind{
2198 Group: "",
2199 Version: "v1",
2200 Kind: "Pod",
2201 })
2202 sii, err := informerCache.GetInformer(context.TODO(), pod)
2203 Expect(err).NotTo(HaveOccurred())
2204 Expect(sii).NotTo(BeNil())
2205 Expect(sii.HasSynced()).To(BeTrue())
2206
2207 By("removing the existing informer")
2208 Expect(informerCache.RemoveInformer(context.TODO(), pod)).To(Succeed())
2209 Eventually(sii.IsStopped).WithTimeout(5 * time.Second).Should(BeTrue())
2210
2211 By("recreating the informer")
2212
2213 sii2, err := informerCache.GetInformer(context.TODO(), pod)
2214 Expect(err).NotTo(HaveOccurred())
2215 Expect(sii2).NotTo(BeNil())
2216 Expect(sii2.HasSynced()).To(BeTrue())
2217
2218 By("validating the two informers are in different states")
2219 Expect(sii.IsStopped()).To(BeTrue())
2220 Expect(sii2.IsStopped()).To(BeFalse())
2221 })
2222
2223 It("should be able to index an object field then retrieve objects by that field", func() {
2224 By("creating the cache")
2225 informer, err := cache.New(cfg, cache.Options{})
2226 Expect(err).NotTo(HaveOccurred())
2227
2228 By("indexing the restartPolicy field of the Pod object before starting")
2229 pod := &unstructured.Unstructured{}
2230 pod.SetGroupVersionKind(schema.GroupVersionKind{
2231 Group: "",
2232 Version: "v1",
2233 Kind: "Pod",
2234 })
2235 indexFunc := func(obj client.Object) []string {
2236 s, ok := obj.(*unstructured.Unstructured).Object["spec"]
2237 if !ok {
2238 return []string{}
2239 }
2240 m, ok := s.(map[string]interface{})
2241 if !ok {
2242 return []string{}
2243 }
2244 return []string{fmt.Sprintf("%v", m["restartPolicy"])}
2245 }
2246 Expect(informer.IndexField(context.TODO(), pod, "spec.restartPolicy", indexFunc)).To(Succeed())
2247
2248 By("running the cache and waiting for it to sync")
2249 go func() {
2250 defer GinkgoRecover()
2251 Expect(informer.Start(informerCacheCtx)).To(Succeed())
2252 }()
2253 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
2254
2255 By("listing Pods with restartPolicyOnFailure")
2256 listObj := &unstructured.UnstructuredList{}
2257 listObj.SetGroupVersionKind(schema.GroupVersionKind{
2258 Group: "",
2259 Version: "v1",
2260 Kind: "PodList",
2261 })
2262 err = informer.List(context.Background(), listObj,
2263 client.MatchingFields{"spec.restartPolicy": "OnFailure"})
2264 Expect(err).To(Succeed())
2265
2266 By("verifying that the returned pods have correct restart policy")
2267 Expect(listObj.Items).NotTo(BeEmpty())
2268 Expect(listObj.Items).Should(HaveLen(1))
2269 actual := listObj.Items[0]
2270 Expect(actual.GetName()).To(Equal("test-pod-3"))
2271 })
2272
2273 It("should allow for get informer to be cancelled", func() {
2274 By("cancelling the context")
2275 informerCacheCancel()
2276
2277 By("getting a shared index informer for a pod with a cancelled context")
2278 pod := &unstructured.Unstructured{}
2279 pod.SetName("informer-obj2")
2280 pod.SetNamespace("default")
2281 pod.SetGroupVersionKind(schema.GroupVersionKind{
2282 Group: "",
2283 Version: "v1",
2284 Kind: "Pod",
2285 })
2286 sii, err := informerCache.GetInformer(informerCacheCtx, pod)
2287 Expect(err).To(HaveOccurred())
2288 Expect(sii).To(BeNil())
2289 Expect(apierrors.IsTimeout(err)).To(BeTrue())
2290 })
2291 })
2292 Context("with metadata-only objects", func() {
2293 It("should be able to get informer for the object", func() {
2294 By("getting a shared index informer for a pod")
2295
2296 pod := &corev1.Pod{
2297 ObjectMeta: metav1.ObjectMeta{
2298 Name: "informer-obj",
2299 Namespace: "default",
2300 },
2301 Spec: corev1.PodSpec{
2302 Containers: []corev1.Container{
2303 {
2304 Name: "nginx",
2305 Image: "nginx",
2306 },
2307 },
2308 },
2309 }
2310
2311 podMeta := &metav1.PartialObjectMetadata{}
2312 pod.ObjectMeta.DeepCopyInto(&podMeta.ObjectMeta)
2313 podMeta.SetGroupVersionKind(schema.GroupVersionKind{
2314 Group: "",
2315 Version: "v1",
2316 Kind: "Pod",
2317 })
2318
2319 sii, err := informerCache.GetInformer(context.TODO(), podMeta)
2320 Expect(err).NotTo(HaveOccurred())
2321 Expect(sii).NotTo(BeNil())
2322 Expect(sii.HasSynced()).To(BeTrue())
2323
2324 By("adding an event handler listening for object creation which sends the object to a channel")
2325 out := make(chan interface{})
2326 addFunc := func(obj interface{}) {
2327 out <- obj
2328 }
2329 _, _ = sii.AddEventHandler(kcache.ResourceEventHandlerFuncs{AddFunc: addFunc})
2330
2331 By("adding an object")
2332 cl, err := client.New(cfg, client.Options{})
2333 Expect(err).NotTo(HaveOccurred())
2334 Expect(cl.Create(context.Background(), pod)).To(Succeed())
2335 defer deletePod(pod)
2336
2337 pod.ObjectMeta.DeepCopyInto(&podMeta.ObjectMeta)
2338
2339 By("verifying the object's metadata is received on the channel")
2340 Eventually(out).Should(Receive(Equal(podMeta)))
2341 })
2342
2343 It("should be able to index an object field then retrieve objects by that field", func() {
2344 By("creating the cache")
2345 informer, err := cache.New(cfg, cache.Options{})
2346 Expect(err).NotTo(HaveOccurred())
2347
2348 By("indexing the restartPolicy field of the Pod object before starting")
2349 pod := &metav1.PartialObjectMetadata{}
2350 pod.SetGroupVersionKind(schema.GroupVersionKind{
2351 Group: "",
2352 Version: "v1",
2353 Kind: "Pod",
2354 })
2355 indexFunc := func(obj client.Object) []string {
2356 metadata := obj.(*metav1.PartialObjectMetadata)
2357 return []string{metadata.Labels["test-label"]}
2358 }
2359 Expect(informer.IndexField(context.TODO(), pod, "metadata.labels.test-label", indexFunc)).To(Succeed())
2360
2361 By("running the cache and waiting for it to sync")
2362 go func() {
2363 defer GinkgoRecover()
2364 Expect(informer.Start(informerCacheCtx)).To(Succeed())
2365 }()
2366 Expect(informer.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
2367
2368 By("listing Pods with restartPolicyOnFailure")
2369 listObj := &metav1.PartialObjectMetadataList{}
2370 gvk := schema.GroupVersionKind{
2371 Group: "",
2372 Version: "v1",
2373 Kind: "PodList",
2374 }
2375 listObj.SetGroupVersionKind(gvk)
2376 err = informer.List(context.Background(), listObj,
2377 client.MatchingFields{"metadata.labels.test-label": "test-pod-3"})
2378 Expect(err).To(Succeed())
2379
2380 By("verifying that the GVK has been preserved for the list object")
2381 Expect(listObj.GroupVersionKind()).To(Equal(gvk))
2382
2383 By("verifying that the returned pods have correct restart policy")
2384 Expect(listObj.Items).NotTo(BeEmpty())
2385 Expect(listObj.Items).Should(HaveLen(1))
2386 actual := listObj.Items[0]
2387 Expect(actual.GetName()).To(Equal("test-pod-3"))
2388
2389 By("verifying that the GVK has been preserved for the item in the list")
2390 Expect(actual.GroupVersionKind()).To(Equal(schema.GroupVersionKind{
2391 Group: "",
2392 Version: "v1",
2393 Kind: "Pod",
2394 }))
2395 })
2396
2397 It("should allow for get informer to be cancelled", func() {
2398 By("creating a context and cancelling it")
2399 ctx, cancel := context.WithCancel(context.Background())
2400 cancel()
2401
2402 By("getting a shared index informer for a pod with a cancelled context")
2403 pod := &metav1.PartialObjectMetadata{}
2404 pod.SetName("informer-obj2")
2405 pod.SetNamespace("default")
2406 pod.SetGroupVersionKind(schema.GroupVersionKind{
2407 Group: "",
2408 Version: "v1",
2409 Kind: "Pod",
2410 })
2411 sii, err := informerCache.GetInformer(ctx, pod)
2412 Expect(err).To(HaveOccurred())
2413 Expect(sii).To(BeNil())
2414 Expect(apierrors.IsTimeout(err)).To(BeTrue())
2415 })
2416 })
2417 })
2418 Describe("use UnsafeDisableDeepCopy list options", func() {
2419 It("should be able to change object in informer cache", func() {
2420 By("listing pods")
2421 out := corev1.PodList{}
2422 Expect(informerCache.List(context.Background(), &out, client.UnsafeDisableDeepCopy)).To(Succeed())
2423 for _, item := range out.Items {
2424 if strings.Compare(item.Name, "test-pod-3") == 0 {
2425 item.Labels["UnsafeDisableDeepCopy"] = "true"
2426 break
2427 }
2428 }
2429
2430 By("verifying that the returned pods were changed")
2431 out2 := corev1.PodList{}
2432 Expect(informerCache.List(context.Background(), &out, client.UnsafeDisableDeepCopy)).To(Succeed())
2433 for _, item := range out2.Items {
2434 if strings.Compare(item.Name, "test-pod-3") == 0 {
2435 Expect(item.Labels["UnsafeDisableDeepCopy"]).To(Equal("true"))
2436 break
2437 }
2438 }
2439 })
2440 })
2441 })
2442 }
2443
2444 var _ = Describe("TransformStripManagedFields", func() {
2445 It("should strip managed fields from an object", func() {
2446 obj := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
2447 ManagedFields: []metav1.ManagedFieldsEntry{{
2448 Manager: "foo",
2449 }},
2450 }}
2451 transformed, err := cache.TransformStripManagedFields()(obj)
2452 Expect(err).NotTo(HaveOccurred())
2453 Expect(transformed).To(Equal(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{}}))
2454 })
2455
2456 It("should not trip over an unexpected object", func() {
2457 transformed, err := cache.TransformStripManagedFields()("foo")
2458 Expect(err).NotTo(HaveOccurred())
2459 Expect(transformed).To(Equal("foo"))
2460 })
2461 })
2462
2463
2464 func ensureNamespace(namespace string, client client.Client) error {
2465 ns := corev1.Namespace{
2466 ObjectMeta: metav1.ObjectMeta{
2467 Name: namespace,
2468 },
2469 TypeMeta: metav1.TypeMeta{
2470 Kind: "Namespace",
2471 APIVersion: "v1",
2472 },
2473 }
2474 err := client.Create(context.TODO(), &ns)
2475 if apierrors.IsAlreadyExists(err) {
2476 return nil
2477 }
2478 return err
2479 }
2480
2481 func ensureNode(name string, client client.Client) error {
2482 node := corev1.Node{
2483 ObjectMeta: metav1.ObjectMeta{
2484 Name: name,
2485 Labels: map[string]string{"name": name},
2486 },
2487 TypeMeta: metav1.TypeMeta{
2488 Kind: "Node",
2489 APIVersion: "v1",
2490 },
2491 }
2492 err := client.Create(context.TODO(), &node)
2493 if apierrors.IsAlreadyExists(err) {
2494 return nil
2495 }
2496 return err
2497 }
2498
2499
2500 func isKubeService(svc metav1.Object) bool {
2501
2502 return svc.GetNamespace() == "default" && svc.GetName() == "kubernetes"
2503 }
2504
2505 func isPodDisableDeepCopy(opts cache.Options) bool {
2506 if opts.ByObject[&corev1.Pod{}].UnsafeDisableDeepCopy != nil {
2507 return *opts.ByObject[&corev1.Pod{}].UnsafeDisableDeepCopy
2508 }
2509 if opts.DefaultUnsafeDisableDeepCopy != nil {
2510 return *opts.DefaultUnsafeDisableDeepCopy
2511 }
2512 return false
2513 }
2514
View as plain text