Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 filter global namespace while looking for cluster scoped resources #1520

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions pkg/cache/multi_namespace_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -50,9 +50,15 @@ func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc {
if err != nil {
return nil, err
}
// create a cache for cluster scoped resources
namespaces = append(namespaces, globalCache)

caches := map[string]Cache{}

// create a cache for cluster scoped resources
gCache, err := New(config, opts)
if err != nil {
return nil, fmt.Errorf("error creating global cache %v", err)
}

for _, ns := range namespaces {
opts.Namespace = ns
c, err := New(config, opts)
Expand All @@ -61,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
}
}

Expand All @@ -73,36 +79,82 @@ type multiNamespaceCache struct {
namespaceToCache map[string]Cache
Scheme *runtime.Scheme
RESTMapper meta.RESTMapper
clusterCache Cache
}

var _ Cache = &multiNamespaceCache{}

// Methods for multiNamespaceCache to conform to the Informers interface
func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
informers := map[string]Informer{}

// If the object is clusterscoped, get the informer from clusterCache,
// if not use the namespaced caches.
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
if err != nil {
return nil, err
}
if !isNamespaced {
varshaprasad96 marked this conversation as resolved.
Show resolved Hide resolved
clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj)
if err != nil {
return nil, err
}
informers[globalCache] = clusterCacheInf

return &multiNamespaceInformer{namespaceToInformer: informers}, nil
}

for ns, cache := range c.namespaceToCache {
informer, err := cache.GetInformer(ctx, obj)
if err != nil {
return nil, err
}
informers[ns] = informer
}

return &multiNamespaceInformer{namespaceToInformer: informers}, nil
}

func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
informers := map[string]Informer{}

// If the object is clusterscoped, get the informer from clusterCache,
// if not use the namespaced caches.
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
}

for ns, cache := range c.namespaceToCache {
informer, err := cache.GetInformerForKind(ctx, gvk)
if err != nil {
return nil, err
}
informers[ns] = informer
}

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)
Expand All @@ -111,6 +163,7 @@ func (c *multiNamespaceCache) Start(ctx context.Context) error {
}
}(ns, cache)
}

<-ctx.Done()
return nil
}
Expand All @@ -122,10 +175,24 @@ 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
}

func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
if err != nil {
return nil
}

if !isNamespaced {
return c.clusterCache.IndexField(ctx, obj, field, extractValue)
}

for _, cache := range c.namespaceToCache {
if err := cache.IndexField(ctx, obj, field, extractValue); err != nil {
return err
Expand All @@ -142,8 +209,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]
Expand All @@ -165,8 +231,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 {
Expand Down
8 changes: 7 additions & 1 deletion pkg/internal/objectutil/objectutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down