Skip to content

Commit 0c99fc7

Browse files
✨ Cache-Backed Client: Support listOpts.Limit (#1479)
* Enable list opts in informer cache List - add test for informer cache limit option Signed-off-by: Madhav Jivrajani <madhav.jiv@gmail.com> * enable Limit option in multi namespace cache Signed-off-by: Madhav Jivrajani <madhav.jiv@gmail.com>
1 parent a17ac06 commit 0c99fc7

File tree

3 files changed

+67
-23
lines changed

3 files changed

+67
-23
lines changed

pkg/cache/cache_test.go

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
100100
knownPod2 client.Object
101101
knownPod3 client.Object
102102
knownPod4 client.Object
103+
knownPod5 client.Object
104+
knownPod6 client.Object
103105
)
104106

105107
BeforeEach(func() {
@@ -122,14 +124,20 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
122124
knownPod2 = createPod("test-pod-2", testNamespaceTwo, kcorev1.RestartPolicyAlways)
123125
knownPod3 = createPodWithLabels("test-pod-3", testNamespaceTwo, kcorev1.RestartPolicyOnFailure, map[string]string{"common-label": "common"})
124126
knownPod4 = createPodWithLabels("test-pod-4", testNamespaceThree, kcorev1.RestartPolicyNever, map[string]string{"common-label": "common"})
127+
knownPod5 = createPod("test-pod-5", testNamespaceOne, kcorev1.RestartPolicyNever)
128+
knownPod6 = createPod("test-pod-6", testNamespaceTwo, kcorev1.RestartPolicyAlways)
129+
125130
podGVK := schema.GroupVersionKind{
126131
Kind: "Pod",
127132
Version: "v1",
128133
}
134+
129135
knownPod1.GetObjectKind().SetGroupVersionKind(podGVK)
130136
knownPod2.GetObjectKind().SetGroupVersionKind(podGVK)
131137
knownPod3.GetObjectKind().SetGroupVersionKind(podGVK)
132138
knownPod4.GetObjectKind().SetGroupVersionKind(podGVK)
139+
knownPod5.GetObjectKind().SetGroupVersionKind(podGVK)
140+
knownPod6.GetObjectKind().SetGroupVersionKind(podGVK)
133141

134142
By("creating the informer cache")
135143
informerCache, err = createCacheFunc(cfg, cache.Options{})
@@ -149,6 +157,8 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
149157
deletePod(knownPod2)
150158
deletePod(knownPod3)
151159
deletePod(knownPod4)
160+
deletePod(knownPod5)
161+
deletePod(knownPod6)
152162

153163
informerCacheCancel()
154164
})
@@ -226,7 +236,7 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
226236

227237
By("verifying that the returned pods have GVK populated")
228238
Expect(out.Items).NotTo(BeEmpty())
229-
Expect(out.Items).Should(SatisfyAny(HaveLen(3), HaveLen(4)))
239+
Expect(out.Items).Should(SatisfyAny(HaveLen(5), HaveLen(6)))
230240
for _, p := range out.Items {
231241
Expect(p.GroupVersionKind()).To(Equal(kcorev1.SchemeGroupVersion.WithKind("Pod")))
232242
}
@@ -240,9 +250,10 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
240250

241251
By("verifying that the returned pods are in test-namespace-1")
242252
Expect(listObj.Items).NotTo(BeEmpty())
243-
Expect(listObj.Items).Should(HaveLen(1))
244-
actual := listObj.Items[0]
245-
Expect(actual.Namespace).To(Equal(testNamespaceOne))
253+
Expect(listObj.Items).Should(HaveLen(2))
254+
for _, item := range listObj.Items {
255+
Expect(item.Namespace).To(Equal(testNamespaceOne))
256+
}
246257
})
247258

248259
It("should deep copy the object unless told otherwise", func() {
@@ -295,7 +306,15 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
295306
Expect(errors.IsTimeout(err)).To(BeTrue())
296307
})
297308

309+
It("should set the Limit option and limit number of objects to Limit when List is called", func() {
310+
opts := &client.ListOptions{Limit: int64(3)}
311+
By("verifying that only Limit (3) number of objects are retrieved from the cache")
312+
listObj := &kcorev1.PodList{}
313+
Expect(informerCache.List(context.Background(), listObj, opts)).To(Succeed())
314+
Expect(listObj.Items).Should(HaveLen(3))
315+
})
298316
})
317+
299318
Context("with unstructured objects", func() {
300319
It("should be able to list objects that haven't been watched previously", func() {
301320
By("listing all services in the cluster")
@@ -396,9 +415,10 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
396415

397416
By("verifying that the returned pods are in test-namespace-1")
398417
Expect(listObj.Items).NotTo(BeEmpty())
399-
Expect(listObj.Items).Should(HaveLen(1))
400-
actual := listObj.Items[0]
401-
Expect(actual.GetNamespace()).To(Equal(testNamespaceOne))
418+
Expect(listObj.Items).Should(HaveLen(2))
419+
for _, item := range listObj.Items {
420+
Expect(item.GetNamespace()).To(Equal(testNamespaceOne))
421+
}
402422
})
403423

404424
It("should be able to restrict cache to a namespace", func() {
@@ -424,9 +444,10 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
424444

425445
By("verifying the returned pod is from the watched namespace")
426446
Expect(out.Items).NotTo(BeEmpty())
427-
Expect(out.Items).Should(HaveLen(1))
428-
Expect(out.Items[0].GetNamespace()).To(Equal(testNamespaceOne))
429-
447+
Expect(out.Items).Should(HaveLen(2))
448+
for _, item := range out.Items {
449+
Expect(item.GetNamespace()).To(Equal(testNamespaceOne))
450+
}
430451
By("listing all nodes - should still be able to list a cluster-scoped resource")
431452
nodeList := &unstructured.UnstructuredList{}
432453
nodeList.SetGroupVersionKind(schema.GroupVersionKind{
@@ -639,9 +660,10 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
639660

640661
By("verifying that the returned pods are in test-namespace-1")
641662
Expect(listObj.Items).NotTo(BeEmpty())
642-
Expect(listObj.Items).Should(HaveLen(1))
643-
actual := listObj.Items[0]
644-
Expect(actual.GetNamespace()).To(Equal(testNamespaceOne))
663+
Expect(listObj.Items).Should(HaveLen(2))
664+
for _, item := range listObj.Items {
665+
Expect(item.Namespace).To(Equal(testNamespaceOne))
666+
}
645667
})
646668

647669
It("should be able to restrict cache to a namespace", func() {
@@ -667,9 +689,10 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
667689

668690
By("verifying the returned pod is from the watched namespace")
669691
Expect(out.Items).NotTo(BeEmpty())
670-
Expect(out.Items).Should(HaveLen(1))
671-
Expect(out.Items[0].GetNamespace()).To(Equal(testNamespaceOne))
672-
692+
Expect(out.Items).Should(HaveLen(2))
693+
for _, item := range out.Items {
694+
Expect(item.Namespace).To(Equal(testNamespaceOne))
695+
}
673696
By("listing all nodes - should still be able to list a cluster-scoped resource")
674697
nodeList := &kmetav1.PartialObjectMetadataList{}
675698
nodeList.SetGroupVersionKind(schema.GroupVersionKind{
@@ -828,25 +851,25 @@ func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (ca
828851
Entry("when selectors are empty it has to inform about all the pods", selectorsTestCase{
829852
fieldSelectors: map[string]string{},
830853
labelSelectors: map[string]string{},
831-
expectedPods: []string{"test-pod-1", "test-pod-2", "test-pod-3", "test-pod-4"},
854+
expectedPods: []string{"test-pod-1", "test-pod-2", "test-pod-3", "test-pod-4", "test-pod-5", "test-pod-6"},
832855
}),
833856
Entry("when field matches one pod it has to inform about it", selectorsTestCase{
834857
fieldSelectors: map[string]string{"metadata.name": "test-pod-2"},
835858
expectedPods: []string{"test-pod-2"},
836859
}),
837-
Entry("when field matches multiple pods it has to infor about all of them", selectorsTestCase{
860+
Entry("when field matches multiple pods it has to inform about all of them", selectorsTestCase{
838861
fieldSelectors: map[string]string{"metadata.namespace": testNamespaceTwo},
839-
expectedPods: []string{"test-pod-2", "test-pod-3"},
862+
expectedPods: []string{"test-pod-2", "test-pod-3", "test-pod-6"},
840863
}),
841864
Entry("when label matches one pod it has to inform about it", selectorsTestCase{
842865
labelSelectors: map[string]string{"test-label": "test-pod-4"},
843866
expectedPods: []string{"test-pod-4"},
844867
}),
845-
Entry("when label matches multiple pods it has to infor about all of them", selectorsTestCase{
868+
Entry("when label matches multiple pods it has to inform about all of them", selectorsTestCase{
846869
labelSelectors: map[string]string{"common-label": "common"},
847870
expectedPods: []string{"test-pod-3", "test-pod-4"},
848871
}),
849-
Entry("when label and field matches one pod it has to infor about about it", selectorsTestCase{
872+
Entry("when label and field matches one pod it has to inform about about it", selectorsTestCase{
850873
labelSelectors: map[string]string{"common-label": "common"},
851874
fieldSelectors: map[string]string{"metadata.namespace": testNamespaceTwo},
852875
expectedPods: []string{"test-pod-3"},

pkg/cache/internal/cache_reader.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,15 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
125125
labelSel = listOpts.LabelSelector
126126
}
127127

128+
limitSet := listOpts.Limit > 0
129+
128130
runtimeObjs := make([]runtime.Object, 0, len(objs))
129-
for _, item := range objs {
131+
for i, item := range objs {
132+
// if the Limit option is set and the number of items
133+
// listed exceeds this limit, then stop reading.
134+
if limitSet && int64(i) >= listOpts.Limit {
135+
break
136+
}
130137
obj, isObj := item.(runtime.Object)
131138
if !isObj {
132139
return fmt.Errorf("cache contained %T, which is not an Object", obj)

pkg/cache/multi_namespace_cache.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,13 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
186186
if err != nil {
187187
return err
188188
}
189+
190+
limitSet := listOpts.Limit > 0
191+
189192
var resourceVersion string
190193
for _, cache := range c.namespaceToCache {
191194
listObj := list.DeepCopyObject().(client.ObjectList)
192-
err = cache.List(ctx, listObj, opts...)
195+
err = cache.List(ctx, listObj, &listOpts)
193196
if err != nil {
194197
return err
195198
}
@@ -204,6 +207,17 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
204207
allItems = append(allItems, items...)
205208
// The last list call should have the most correct resource version.
206209
resourceVersion = accessor.GetResourceVersion()
210+
if limitSet {
211+
// decrement Limit by the number of items
212+
// fetched from the current namespace.
213+
listOpts.Limit -= int64(len(items))
214+
// if a Limit was set and the number of
215+
// items read has reached this set limit,
216+
// then stop reading.
217+
if listOpts.Limit == 0 {
218+
break
219+
}
220+
}
207221
}
208222
listAccessor.SetResourceVersion(resourceVersion)
209223

0 commit comments

Comments
 (0)