diff --git a/pkg/cache/multi_namespace_cache.go b/pkg/cache/multi_namespace_cache.go index ed37b6dd7f..0265dcc885 100644 --- a/pkg/cache/multi_namespace_cache.go +++ b/pkg/cache/multi_namespace_cache.go @@ -41,7 +41,7 @@ const globalCache = "_cluster-scope" // MultiNamespacedCacheBuilder - Builder function to create a new multi-namespaced cache. // This will scope the cache to a list of namespaces. Listing for all namespaces // will list for all the namespaces that this knows about. By default this will create -// a global cache for cluster scoped resource (having empty namespace). Note that this is not intended +// a global cache for cluster scoped resource. Note that this is not intended // to be used for excluding namespaces, this is better done via a Predicate. Also note that // you may face performance issues when using this with a high number of namespaces. func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { @@ -59,9 +59,6 @@ func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { return nil, fmt.Errorf("error creating global cache %v", err) } - // add global cache to the cacheMap - caches[globalCache] = gCache - for _, ns := range namespaces { opts.Namespace = ns c, err := New(config, opts) @@ -70,7 +67,7 @@ func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { } caches[ns] = c } - return &multiNamespaceCache{namespaceToCache: caches, Scheme: opts.Scheme, RESTMapper: opts.Mapper}, nil + return &multiNamespaceCache{namespaceToCache: caches, Scheme: opts.Scheme, RESTMapper: opts.Mapper, clusterCache: gCache}, nil } } @@ -82,6 +79,7 @@ type multiNamespaceCache struct { namespaceToCache map[string]Cache Scheme *runtime.Scheme RESTMapper meta.RESTMapper + clusterCache Cache } var _ Cache = &multiNamespaceCache{} @@ -96,6 +94,21 @@ func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object } informers[ns] = informer } + + isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper) + if err != nil { + return nil, err + } + + if !isNamespaced { + clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj) + if err != nil { + return nil, err + } + informers[globalCache] = clusterCacheInf + + } + return &multiNamespaceInformer{namespaceToInformer: informers}, nil } @@ -108,10 +121,33 @@ func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema } informers[ns] = informer } + + isNamespaced, err := objectutil.IsAPINamespacedWithGVK(gvk, c.Scheme, c.RESTMapper) + if err != nil { + return nil, err + } + + if !isNamespaced { + clusterCacheInf, err := c.clusterCache.GetInformerForKind(ctx, gvk) + if err != nil { + return nil, err + } + informers[globalCache] = clusterCacheInf + } + return &multiNamespaceInformer{namespaceToInformer: informers}, nil } func (c *multiNamespaceCache) Start(ctx context.Context) error { + // start global cache + go func() { + err := c.clusterCache.Start(ctx) + if err != nil { + log.Error(err, "cluster scoped cache failed to start") + } + }() + + // start namespaced caches for ns, cache := range c.namespaceToCache { go func(ns string, cache Cache) { err := cache.Start(ctx) @@ -120,6 +156,7 @@ func (c *multiNamespaceCache) Start(ctx context.Context) error { } }(ns, cache) } + <-ctx.Done() return nil } @@ -131,6 +168,11 @@ func (c *multiNamespaceCache) WaitForCacheSync(ctx context.Context) bool { synced = s } } + + // check if cluster scoped cache has synced + if !c.clusterCache.WaitForCacheSync(ctx) { + synced = false + } return synced } @@ -140,6 +182,10 @@ func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object, return err } } + + if err := c.clusterCache.IndexField(ctx, obj, field, extractValue); err != nil { + return fmt.Errorf("error adding index on object with cluster scoped cache %v", err) + } return nil } @@ -151,8 +197,7 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj if !isNamespaced { // Look into the global cache to fetch the object - cache := c.namespaceToCache[globalCache] - return cache.Get(ctx, key, obj) + return c.clusterCache.Get(ctx, key, obj) } cache, ok := c.namespaceToCache[key.Namespace] @@ -174,8 +219,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList, if !isNamespaced { // Look at the global cache to get the objects with the specified GVK - cache := c.namespaceToCache[globalCache] - return cache.List(ctx, list, opts...) + return c.clusterCache.List(ctx, list, opts...) } if listOpts.Namespace != corev1.NamespaceAll { @@ -199,10 +243,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList, limitSet := listOpts.Limit > 0 var resourceVersion string - for ns, cache := range c.namespaceToCache { - if ns == globalCache { - continue - } + for _, cache := range c.namespaceToCache { listObj := list.DeepCopyObject().(client.ObjectList) err = cache.List(ctx, listObj, &listOpts) if err != nil { diff --git a/pkg/internal/objectutil/objectutil.go b/pkg/internal/objectutil/objectutil.go index baa62f0bfa..5264da3cc1 100644 --- a/pkg/internal/objectutil/objectutil.go +++ b/pkg/internal/objectutil/objectutil.go @@ -55,7 +55,13 @@ func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apim return false, err } - restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}) + return IsAPINamespacedWithGVK(gvk, scheme, restmapper) +} + +// IsAPINamespacedWithGVK returns true if the object having the provided +// GVK is namespace scoped. +func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) { + restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind}) if err != nil { return false, fmt.Errorf("failed to get restmapping: %w", err) }