...

Source file src/sigs.k8s.io/controller-runtime/pkg/cache/cache_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/cache

     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  }
  2514  

View as plain text