Skip to content

Commit 6a42f89

Browse files
Use restmapper to identify scope of the object
Modify multinamespaced cache to accept restmapper, which can be used to identify the scope of the object and handle the cluster scoped objects accordingly.
1 parent be66476 commit 6a42f89

File tree

3 files changed

+101
-81
lines changed

3 files changed

+101
-81
lines changed

pkg/cache/cache_test.go

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -494,39 +494,39 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
494494
err := informerCache.Get(context.Background(), svcKey, svc)
495495
Expect(err).To(HaveOccurred())
496496
})
497-
// It("test multinamespaced cache for cluster scoped resources", func() {
498-
// By("creating a multinamespaced cache to watch specific namespaces")
499-
// multi := cache.MultiNamespacedCacheBuilder([]string{"default", testNamespaceOne})
500-
// m, err := multi(cfg, cache.Options{})
501-
// Expect(err).NotTo(HaveOccurred())
502-
503-
// By("running the cache and waiting it for sync")
504-
// go func() {
505-
// defer GinkgoRecover()
506-
// Expect(m.Start(informerCacheCtx)).To(Succeed())
507-
// }()
508-
// Expect(m.WaitForCacheSync(informerCacheCtx)).NotTo(BeFalse())
509-
510-
// By("should be able to fetch cluster scoped resource")
511-
// node := &kcorev1.Node{}
512-
513-
// By("verifying that getting the node works with an empty namespace")
514-
// key1 := client.ObjectKey{Namespace: "", Name: testNodeOne}
515-
// Expect(m.Get(context.Background(), key1, node)).To(Succeed())
516-
517-
// By("verifying if the cluster scoped resources are not duplicated")
518-
// nodeList := &unstructured.UnstructuredList{}
519-
// nodeList.SetGroupVersionKind(schema.GroupVersionKind{
520-
// Group: "",
521-
// Version: "v1",
522-
// Kind: "NodeList",
523-
// })
524-
// Expect(m.List(context.Background(), nodeList)).To(Succeed())
525-
526-
// By("verifying the node list is not empty")
527-
// Expect(nodeList.Items).NotTo(BeEmpty())
528-
// Expect(len(nodeList.Items)).To(BeEquivalentTo(1))
529-
// })
497+
It("test multinamespaced cache for cluster scoped resources", func() {
498+
By("creating a multinamespaced cache to watch specific namespaces")
499+
multi := cache.MultiNamespacedCacheBuilder([]string{"default", testNamespaceOne})
500+
m, err := multi(cfg, cache.Options{})
501+
Expect(err).NotTo(HaveOccurred())
502+
503+
By("running the cache and waiting it for sync")
504+
go func() {
505+
defer GinkgoRecover()
506+
Expect(m.Start(informerCacheCtx)).To(Succeed())
507+
}()
508+
Expect(m.WaitForCacheSync(informerCacheCtx)).NotTo(BeFalse())
509+
510+
By("should be able to fetch cluster scoped resource")
511+
node := &kcorev1.Node{}
512+
513+
By("verifying that getting the node works with an empty namespace")
514+
key1 := client.ObjectKey{Namespace: "", Name: testNodeOne}
515+
Expect(m.Get(context.Background(), key1, node)).To(Succeed())
516+
517+
By("verifying if the cluster scoped resources are not duplicated")
518+
nodeList := &unstructured.UnstructuredList{}
519+
nodeList.SetGroupVersionKind(schema.GroupVersionKind{
520+
Group: "",
521+
Version: "v1",
522+
Kind: "NodeList",
523+
})
524+
Expect(m.List(context.Background(), nodeList)).To(Succeed())
525+
526+
By("verifying the node list is not empty")
527+
Expect(nodeList.Items).NotTo(BeEmpty())
528+
Expect(len(nodeList.Items)).To(BeEquivalentTo(1))
529+
})
530530
})
531531
Context("with metadata-only objects", func() {
532532
It("should be able to list objects that haven't been watched previously", func() {

pkg/cache/multi_namespace_cache.go

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/client-go/rest"
3030
toolscache "k8s.io/client-go/tools/cache"
3131
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
3233
)
3334

3435
// NewCacheFunc - Function for creating a new cache from the options and a rest config
@@ -134,13 +135,16 @@ func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object,
134135
}
135136

136137
func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
137-
// gvk := obj.GetObjectKind().GroupVersionKind()
138-
// mapping, _ := c.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
139-
// if mapping.Scope.Name() == meta.RESTScopeNameRoot {
140-
// // Look into the global cache to fetch the object
141-
// cache := c.namespaceToCache[globalCache]
142-
// return cache.Get(ctx, key, obj)
143-
// }
138+
isNamespaced, err := objectutil.IsNamespacedObject(obj, c.Scheme, c.RESTMapper)
139+
if err != nil {
140+
return err
141+
}
142+
143+
if !isNamespaced {
144+
// Look into the global cache to fetch the object
145+
cache := c.namespaceToCache[globalCache]
146+
return cache.Get(ctx, key, obj)
147+
}
144148

145149
cache, ok := c.namespaceToCache[key.Namespace]
146150
if !ok {
@@ -154,32 +158,23 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
154158
listOpts := client.ListOptions{}
155159
listOpts.ApplyOptions(opts)
156160

157-
// handle cluster scoped objects by looking into global cache
158-
// gvk := list.GetObjectKind().GroupVersionKind()
159-
// mapping, _ := c.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
160-
// if mapping.Scope.Name() == meta.RESTScopeNameRoot {
161-
// // Look at the global cache to get the objects with the specified GVK
162-
// cache := c.namespaceToCache[globalCache]
163-
// err := cache.List(ctx, list, opts...)
164-
// if err != nil {
165-
// return err
166-
// }
167-
// }
161+
isNamespaced, err := objectutil.IsNamespacedObject(list, c.Scheme, c.RESTMapper)
162+
if err != nil {
163+
return err
164+
}
165+
166+
if !isNamespaced {
167+
// Look at the global cache to get the objects with the specified GVK
168+
cache := c.namespaceToCache[globalCache]
169+
return cache.List(ctx, list, opts...)
170+
}
171+
168172
if listOpts.Namespace != corev1.NamespaceAll {
169173
cache, ok := c.namespaceToCache[listOpts.Namespace]
170174
if !ok {
171175
return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", listOpts.Namespace)
172176
}
173-
err := cache.List(ctx, list, opts...)
174-
if err != nil {
175-
return err
176-
}
177-
items, err := apimeta.ExtractList(list)
178-
if err != nil {
179-
return err
180-
}
181-
uniqueItems := removeDuplicates(items)
182-
return apimeta.SetList(list, uniqueItems)
177+
return cache.List(ctx, list, opts...)
183178
}
184179

185180
listAccessor, err := meta.ListAccessor(list)
@@ -210,28 +205,9 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
210205
// The last list call should have the most correct resource version.
211206
resourceVersion = accessor.GetResourceVersion()
212207
}
213-
214-
uniqueItems := removeDuplicates(allItems)
215208
listAccessor.SetResourceVersion(resourceVersion)
216209

217-
return apimeta.SetList(list, uniqueItems)
218-
}
219-
220-
// removeDuplicates removes the duplicate objects obtained from all namespaces so that
221-
// the resulting list has objects with unique name and namespace.
222-
func removeDuplicates(items []runtime.Object) []runtime.Object {
223-
objects := make(map[string]bool)
224-
unique := []runtime.Object{}
225-
226-
for _, obj := range items {
227-
metaObj, _ := meta.Accessor(obj)
228-
key := metaObj.GetName() + " " + metaObj.GetNamespace()
229-
if _, value := objects[key]; !value {
230-
objects[key] = true
231-
unique = append(unique, obj)
232-
}
233-
}
234-
return unique
210+
return apimeta.SetList(list, allItems)
235211
}
236212

237213
// multiNamespaceInformer knows how to handle interacting with the underlying informer across multiple namespaces

pkg/internal/objectutil/filter.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@ limitations under the License.
1717
package objectutil
1818

1919
import (
20+
"errors"
21+
"fmt"
22+
23+
"k8s.io/apimachinery/pkg/api/meta"
2024
apimeta "k8s.io/apimachinery/pkg/api/meta"
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2126
"k8s.io/apimachinery/pkg/labels"
2227
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/apimachinery/pkg/runtime/schema"
29+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2330
)
2431

2532
// FilterWithLabels returns a copy of the items in objs matching labelSel
@@ -40,3 +47,40 @@ func FilterWithLabels(objs []runtime.Object, labelSel labels.Selector) ([]runtim
4047
}
4148
return outItems, nil
4249
}
50+
51+
// IsNamespacedObject returns true if the object is namespace scoped.
52+
// For unstructured objects the gvk is found from the object itself.
53+
func IsNamespacedObject(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
54+
var gvk schema.GroupVersionKind
55+
var err error
56+
57+
_, isUnstructured := obj.(*unstructured.Unstructured)
58+
_, isUnstructuredList := obj.(*unstructured.UnstructuredList)
59+
60+
isUnstructured = isUnstructured || isUnstructuredList
61+
62+
if isUnstructured {
63+
gvk = obj.GetObjectKind().GroupVersionKind()
64+
} else {
65+
gvk, err = apiutil.GVKForObject(obj, scheme)
66+
if err != nil {
67+
return false, err
68+
}
69+
}
70+
71+
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
72+
if err != nil {
73+
return false, fmt.Errorf("failed to get restmapping: %w", err)
74+
}
75+
76+
scope := restmapping.Scope.Name()
77+
78+
if scope == "" {
79+
return false, errors.New("Scope cannot be identified. Empty scope returned")
80+
}
81+
82+
if scope != meta.RESTScopeNameRoot {
83+
return true, nil
84+
}
85+
return false, nil
86+
}

0 commit comments

Comments
 (0)