sigs.k8s.io/controller-runtime@v0.18.2/pkg/cache/cache_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // TODO(community): Pull these helper functions into testenv. 57 // Restart policy is included to allow indexing on that field. 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 // Includes restart policy since these objects are indexed on this field. 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 // Avoid performing transformation multiple times. 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 // Avoid performing transformation multiple times. 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 // pass as an arg so that we don't race between close and re-assign 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 // pass as an arg so that we don't race between close and re-assign 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 // pass as an arg so that we don't race between close and re-assign 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 // pass as an arg so that we don't race between close and re-assign 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 // Includes restart policy since these objects are indexed on this field. 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 // pass as an arg so that we don't race between close and re-assign 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 // NB: kubernetes default service is automatically created in testenv. 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 // NB: each pod has a "test-label": <pod-name> 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 // NB: each pod has a "test-label": <pod-name> 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 // NB: kubernetes default service is automatically created in testenv. 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 // NB: each pod has a "test-label": <pod-name> 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 // NB: each pod has a "test-label": <pod-name> 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 // NB: kubernetes default service is automatically created in testenv. 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 // NB: each pod has a "test-label": <pod-name> 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 // NB: each pod has a "test-label": <pod-name> 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()) //nolint:gosec // We don't retain the pointer 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()) //nolint:gosec // We don't retain the pointer 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()) //nolint:gosec // We don't retain the pointer 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 // labels.Nothing when serialized matches everything, so we have to construct our own "match nothing" selector 1847 LabelSelector: labels.SelectorFromSet(labels.Set{"no-present": "not-present"})}, 1848 }, 1849 }, 1850 // All pods that are not in NamespaceOne 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 // labels.Nothing when serialized matches everything, so we have to construct our own "match nothing" selector 1861 LabelSelector: labels.SelectorFromSet(labels.Set{"no-present": "not-present"})}, 1862 }, 1863 }, 1864 }, 1865 }, 1866 // All pods that are not in NamespaceOne 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 // re-copy the result in so that we can match on it properly 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 { // test-pod-3 has labels 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 // ensureNamespace installs namespace of a given name if not exists. 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 //nolint:interfacer 2500 func isKubeService(svc metav1.Object) bool { 2501 // grumble grumble linters grumble grumble 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 }