Skip to content

Commit d3492ab

Browse files
author
Nimrod Shneor
committed
Add support for implicit paging in un/structured clients
1 parent 9e78e65 commit d3492ab

File tree

4 files changed

+124
-0
lines changed

4 files changed

+124
-0
lines changed

pkg/client/client_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package client_test
1919
import (
2020
"context"
2121
"fmt"
22+
"strconv"
2223
"sync/atomic"
2324
"time"
2425

@@ -47,6 +48,14 @@ func deleteDeployment(ctx context.Context, dep *appsv1.Deployment, ns string) {
4748
}
4849
}
4950

51+
func deletePod(ctx context.Context, pod *corev1.Pod, ns string) {
52+
_, err := clientset.CoreV1().Pods(ns).Get(ctx, pod.Name, metav1.GetOptions{})
53+
if err == nil {
54+
err = clientset.CoreV1().Pods(ns).Delete(ctx, pod.Name, metav1.DeleteOptions{})
55+
Expect(err).NotTo(HaveOccurred())
56+
}
57+
}
58+
5059
func deleteNamespace(ctx context.Context, ns *corev1.Namespace) {
5160
ns, err := clientset.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{})
5261
if err != nil {
@@ -2085,6 +2094,45 @@ var _ = Describe("Client", func() {
20852094
Expect(deps.Items[1].Name).To(Equal(dep4.Name))
20862095
}, serverSideTimeoutSeconds)
20872096

2097+
It("should list in pages large sets of objects using ListPages", func() {
2098+
buildPod := func(suffix string) *corev1.Pod {
2099+
return &corev1.Pod{
2100+
ObjectMeta: metav1.ObjectMeta{
2101+
Name: fmt.Sprintf("pod-%s", suffix),
2102+
},
2103+
Spec: corev1.PodSpec{
2104+
Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}},
2105+
},
2106+
}
2107+
}
2108+
2109+
By("creating 150 pods")
2110+
workLoad := 150
2111+
for workLoad > 0 {
2112+
pod := buildPod(strconv.Itoa(workLoad))
2113+
defer deletePod(ctx, pod, ns)
2114+
pod, err := clientset.
2115+
CoreV1().
2116+
Pods(ns).
2117+
Create(ctx, pod, metav1.CreateOptions{})
2118+
Expect(err).NotTo(HaveOccurred())
2119+
workLoad--
2120+
defer deletePod(ctx, pod, ns)
2121+
}
2122+
2123+
cl, err := client.New(cfg, client.Options{})
2124+
Expect(err).NotTo(HaveOccurred())
2125+
2126+
By("listing all pods with ListPages")
2127+
pods := &corev1.PodList{}
2128+
err = cl.ListPages(context.Background(), pods, nil)
2129+
Expect(err).NotTo(HaveOccurred())
2130+
Expect(pods.Items).To(HaveLen(client.DefaultPageLimit))
2131+
Expect(pods.Continue).NotTo(BeEmpty())
2132+
Expect(pods.Items[0].Name).To(Equal("pod-1"))
2133+
2134+
}, serverSideTimeoutSeconds)
2135+
20882136
PIt("should fail if the object doesn't have meta", func() {
20892137

20902138
})

pkg/client/interfaces.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ type Reader interface {
5555
// successful call, Items field in the list will be populated with the
5656
// result returned from the server.
5757
List(ctx context.Context, list ObjectList, opts ...ListOption) error
58+
59+
// // Retrieves a list of objects in "chunks" (of size one hundred by default)
60+
// // for a given namespace and list options.
61+
// // One can pass a callback function to process each chunk recieved from the server.
62+
// // On a successful call, Items field in the list will be populated with the
63+
// // result returned from the server.
64+
// ListPages(ctx context.Context, obj ObjectList, callback func(obj ObjectList) error, opts ...ListOption) error
5865
}
5966

6067
// Writer knows how to create, delete, and update Kubernetes objects.

pkg/client/options.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import (
2323
"k8s.io/apimachinery/pkg/selection"
2424
)
2525

26+
const (
27+
// DefaultPageLimit represents the default limit used when specifyinh the 'Page' ListOption.
28+
DefaultPageLimit = 100
29+
)
30+
2631
// {{{ "Functional" Option Interfaces
2732

2833
// CreateOption is some configuration that modifies options for a create request.

pkg/client/typed_client.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package client
1919
import (
2020
"context"
2121

22+
apimeta "k8s.io/apimachinery/pkg/api/meta"
2223
"k8s.io/apimachinery/pkg/runtime"
2324
)
2425

@@ -143,6 +144,68 @@ func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object) error
143144
Name(key.Name).Do(ctx).Into(obj)
144145
}
145146

147+
func (c *typedClient) ListPages(ctx context.Context, obj ObjectList,
148+
callback func(obj ObjectList) error, opts ...ListOption) error {
149+
r, err := c.cache.getResource(obj)
150+
if err != nil {
151+
return err
152+
}
153+
listOpts := ListOptions{}
154+
listOpts.ApplyOptions(opts)
155+
156+
// Fetch items at chunks of one hundred if not specified differently.
157+
if listOpts.Limit == 0 {
158+
Limit(DefaultPageLimit).ApplyToList(&listOpts)
159+
}
160+
161+
// Retrieve initial chunck of data.
162+
var allItems []runtime.Object
163+
var interimResult ObjectList
164+
err = r.Get().
165+
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
166+
Resource(r.resource()).
167+
VersionedParams(listOpts.AsListOptions(), c.paramCodec).
168+
Do(ctx).Into(interimResult)
169+
if err != nil {
170+
return err
171+
}
172+
173+
if err := callback(interimResult); err != nil {
174+
return err
175+
}
176+
177+
items, err := apimeta.ExtractList(interimResult)
178+
if err != nil {
179+
return err
180+
}
181+
allItems = append(allItems, items...)
182+
183+
// Continue while there are more chunks.
184+
for interimResult.GetContinue() != "" {
185+
Continue(interimResult.GetContinue()).ApplyToList(&listOpts)
186+
err = r.Get().
187+
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
188+
Resource(r.resource()).
189+
VersionedParams(listOpts.AsListOptions(), c.paramCodec).
190+
Do(ctx).Into(interimResult)
191+
if err != nil {
192+
return err
193+
}
194+
195+
if err := callback(interimResult); err != nil {
196+
return err
197+
}
198+
199+
items, err = apimeta.ExtractList(interimResult)
200+
if err != nil {
201+
return err
202+
}
203+
allItems = append(allItems, items...)
204+
}
205+
206+
return apimeta.SetList(obj, allItems)
207+
}
208+
146209
// List implements client.Client
147210
func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
148211
r, err := c.cache.getResource(obj)
@@ -151,6 +214,7 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti
151214
}
152215
listOpts := ListOptions{}
153216
listOpts.ApplyOptions(opts)
217+
154218
return r.Get().
155219
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
156220
Resource(r.resource()).

0 commit comments

Comments
 (0)