Skip to content

Commit c5f2fc0

Browse files
authored
Merge pull request kubernetes#127099 from jpbetz/object-selectors-ga
Add filtered informers tests for CRD field selectors
2 parents e2c17c0 + e9744c1 commit c5f2fc0

File tree

2 files changed

+149
-22
lines changed

2 files changed

+149
-22
lines changed

staging/src/k8s.io/client-go/dynamic/dynamicinformer/informer_test.go

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"github.com/google/go-cmp/cmp"
25+
2526
"k8s.io/apimachinery/pkg/api/equality"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -37,6 +38,7 @@ type triggerFunc func(gvr schema.GroupVersionResource, ns string, fakeClient *fa
3738
func triggerFactory(t *testing.T) triggerFunc {
3839
return func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, _ *unstructured.Unstructured) *unstructured.Unstructured {
3940
testObject := newUnstructured("apps/v1", "Deployment", "ns-foo", "name-foo")
41+
testObject.SetLabels(map[string]string{"environment": "test"})
4042
createdObj, err := fakeClient.Resource(gvr).Namespace(ns).Create(context.TODO(), testObject, metav1.CreateOptions{})
4143
if err != nil {
4244
t.Error(err)
@@ -55,13 +57,14 @@ func handler(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandle
5557

5658
func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
5759
scenarios := []struct {
58-
name string
59-
existingObj *unstructured.Unstructured
60-
gvr schema.GroupVersionResource
61-
informNS string
62-
ns string
63-
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured
64-
handler func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs
60+
name string
61+
existingObj *unstructured.Unstructured
62+
gvr schema.GroupVersionResource
63+
informNS string
64+
ns string
65+
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured
66+
handler func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs
67+
tweakListOptions dynamicinformer.TweakListOptionsFunc
6568
}{
6669
// scenario 1
6770
{
@@ -81,6 +84,50 @@ func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
8184
trigger: triggerFactory(t),
8285
handler: handler,
8386
},
87+
{
88+
name: "tweak options: test adding an object not matching field selector should not trigger AddFunc",
89+
informNS: "ns-foo",
90+
ns: "ns-foo",
91+
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
92+
trigger: triggerFactory(t),
93+
handler: handler,
94+
tweakListOptions: func(opts *metav1.ListOptions) {
95+
opts.FieldSelector = "metadata.name=name-bar"
96+
},
97+
},
98+
{
99+
name: "tweak options: test adding an object matching field selector should trigger AddFunc",
100+
informNS: "ns-foo",
101+
ns: "ns-foo",
102+
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
103+
trigger: triggerFactory(t),
104+
handler: handler,
105+
tweakListOptions: func(opts *metav1.ListOptions) {
106+
opts.FieldSelector = "metadata.name=name-foo"
107+
},
108+
},
109+
{
110+
name: "tweak options: test adding an object not matching label selector should not trigger AddFunc",
111+
informNS: "ns-foo",
112+
ns: "ns-foo",
113+
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
114+
trigger: triggerFactory(t),
115+
handler: handler,
116+
tweakListOptions: func(opts *metav1.ListOptions) {
117+
opts.LabelSelector = "environment=production"
118+
},
119+
},
120+
{
121+
name: "tweak options: test adding an object matching label selector should trigger AddFunc",
122+
informNS: "ns-foo",
123+
ns: "ns-foo",
124+
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
125+
trigger: triggerFactory(t),
126+
handler: handler,
127+
tweakListOptions: func(opts *metav1.ListOptions) {
128+
opts.LabelSelector = "environment=test"
129+
},
130+
},
84131
}
85132

86133
for _, ts := range scenarios {

test/e2e/apimachinery/crd_selectable_fields.go

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"github.com/onsi/gomega"
23+
"sync"
2324
"time"
2425

2526
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -32,6 +33,8 @@ import (
3233
"k8s.io/apimachinery/pkg/watch"
3334
"k8s.io/apiserver/pkg/storage/names"
3435
"k8s.io/client-go/dynamic"
36+
"k8s.io/client-go/dynamic/dynamicinformer"
37+
"k8s.io/client-go/tools/cache"
3538
"k8s.io/kubernetes/test/e2e/framework"
3639
"k8s.io/kubernetes/test/utils/crd"
3740
imageutils "k8s.io/kubernetes/test/utils/image"
@@ -150,15 +153,44 @@ var _ = SIGDescribe("CustomResourceFieldSelectors [Privileged:ClusterAdmin]", fu
150153

151154
ginkgo.By("Watching with field selectors")
152155

153-
v2Client, gvr := customResourceClient(crd, "v2")
154-
hostWatch, err := v2Client.Namespace(f.Namespace.Name).Watch(ctx, metav1.ListOptions{FieldSelector: "host=host1"})
156+
v2Client, v2gvr := customResourceClient(crd, "v2")
157+
v2hostWatch, err := v2Client.Namespace(f.Namespace.Name).Watch(ctx, metav1.ListOptions{FieldSelector: "host=host1"})
155158
framework.ExpectNoError(err, "watching custom resources with field selector")
159+
v2hostWatchAcc := watchAccumulator(v2hostWatch)
156160
v2hostPortWatch, err := v2Client.Namespace(f.Namespace.Name).Watch(ctx, metav1.ListOptions{FieldSelector: "host=host1,port=80"})
157161
framework.ExpectNoError(err, "watching custom resources with field selector")
162+
framework.ExpectNoError(err, "watching custom resources with field selector")
163+
v2hostPortWatchAcc := watchAccumulator(v2hostPortWatch)
158164

159165
v1Client, _ := customResourceClient(crd, "v1")
160166
v1hostPortWatch, err := v1Client.Namespace(f.Namespace.Name).Watch(ctx, metav1.ListOptions{FieldSelector: "hostPort=host1:80"})
161167
framework.ExpectNoError(err, "watching custom resources with field selector")
168+
v1hostPortWatchAcc := watchAccumulator(v1hostPortWatch)
169+
170+
ginkgo.By("Registering informers with field selectors")
171+
172+
informerCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
173+
defer cancel()
174+
175+
hostInformer := dynamicinformer.NewFilteredDynamicSharedInformerFactory(f.DynamicClient, 0, f.Namespace.Name, func(opts *metav1.ListOptions) {
176+
opts.FieldSelector = "host=host1"
177+
})
178+
hostInformer.Start(informerCtx.Done())
179+
180+
hostPortInformer := dynamicinformer.NewFilteredDynamicSharedInformerFactory(f.DynamicClient, 0, f.Namespace.Name, func(opts *metav1.ListOptions) {
181+
opts.FieldSelector = "host=host1,port=80"
182+
})
183+
hostPortInformer.Start(informerCtx.Done())
184+
185+
v2HostInformer := hostInformer.ForResource(v2gvr).Informer()
186+
go v2HostInformer.Run(informerCtx.Done())
187+
v2HostPortInformer := hostPortInformer.ForResource(v2gvr).Informer()
188+
go v2HostPortInformer.Run(informerCtx.Done())
189+
190+
v2HostInformerAcc := informerAccumulator(v2HostInformer)
191+
v2HostPortInformerAcc := informerAccumulator(v2HostPortInformer)
192+
193+
framework.ExpectNoError(err, "adding event handler")
162194

163195
ginkgo.By("Creating custom resources")
164196
toCreate := []map[string]any{
@@ -181,7 +213,7 @@ var _ = SIGDescribe("CustomResourceFieldSelectors [Privileged:ClusterAdmin]", fu
181213
crNames[i] = name
182214

183215
obj := map[string]interface{}{
184-
"apiVersion": gvr.Group + "/" + gvr.Version,
216+
"apiVersion": v2gvr.Group + "/" + v2gvr.Version,
185217
"kind": crd.Spec.Names.Kind,
186218
"metadata": map[string]interface{}{
187219
"name": name,
@@ -215,15 +247,23 @@ var _ = SIGDescribe("CustomResourceFieldSelectors [Privileged:ClusterAdmin]", fu
215247
gomega.Expect(listResultToNames(list)).To(gomega.Equal(sets.New(crNames[1])))
216248

217249
ginkgo.By("Waiting for watch events to contain v2 custom resources for field selector host=host1")
218-
gomega.Eventually(ctx, watchAccumulator(hostWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
250+
gomega.Eventually(ctx, v2hostWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
219251
Should(gomega.Equal(addedEvents(sets.New(crNames[0], crNames[1]))))
220252

221253
ginkgo.By("Waiting for watch events to contain v2 custom resources for field selector host=host1,port=80")
222-
gomega.Eventually(ctx, watchAccumulator(v2hostPortWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
254+
gomega.Eventually(ctx, v2hostPortWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
223255
Should(gomega.Equal(addedEvents(sets.New(crNames[0]))))
224256

225257
ginkgo.By("Waiting for watch events to contain v1 custom resources for field selector hostPort=host1:80")
226-
gomega.Eventually(ctx, watchAccumulator(v1hostPortWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
258+
gomega.Eventually(ctx, v1hostPortWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
259+
Should(gomega.Equal(addedEvents(sets.New(crNames[0]))))
260+
261+
ginkgo.By("Waiting for informer events to contain v2 custom resources for field selector host=host1")
262+
gomega.Eventually(ctx, v2HostInformerAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
263+
Should(gomega.Equal(addedEvents(sets.New(crNames[0], crNames[1]))))
264+
265+
ginkgo.By("Waiting for informer events to contain v2 custom resources for field selector host=host1,port=80")
266+
gomega.Eventually(ctx, v2HostPortInformerAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
227267
Should(gomega.Equal(addedEvents(sets.New(crNames[0]))))
228268

229269
ginkgo.By("Deleting one custom resources to ensure that deletions are observed")
@@ -249,18 +289,25 @@ var _ = SIGDescribe("CustomResourceFieldSelectors [Privileged:ClusterAdmin]", fu
249289
gomega.Expect(listResultToNames(list)).To(gomega.Equal(sets.New[string]()))
250290

251291
ginkgo.By("Waiting for v2 watch events after updates and deletes for field selector host=host1")
252-
gomega.Eventually(ctx, watchAccumulator(hostWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
253-
Should(gomega.Equal(deletedEvents(sets.New(crNames[0], crNames[1]))))
292+
gomega.Eventually(ctx, v2hostWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
293+
Should(gomega.Equal(addedAndDeletedEvents(sets.New(crNames[0], crNames[1]), sets.New(crNames[0], crNames[1]))))
254294

255295
ginkgo.By("Waiting for v2 watch events after updates and deletes for field selector host=host1,port=80")
256-
gomega.Eventually(ctx, watchAccumulator(v2hostPortWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
257-
Should(gomega.Equal(deletedEvents(sets.New(crNames[0]))))
296+
gomega.Eventually(ctx, v2hostPortWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
297+
Should(gomega.Equal(addedAndDeletedEvents(sets.New(crNames[0]), sets.New(crNames[0]))))
258298

259299
ginkgo.By("Waiting for v1 watch events after updates and deletes for field selector hostPort=host1:80")
260-
gomega.Eventually(ctx, watchAccumulator(v1hostPortWatch)).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
261-
Should(gomega.Equal(deletedEvents(sets.New(crNames[0]))))
262-
})
300+
gomega.Eventually(ctx, v1hostPortWatchAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
301+
Should(gomega.Equal(addedAndDeletedEvents(sets.New(crNames[0]), sets.New(crNames[0]))))
263302

303+
ginkgo.By("Waiting for v2 informer events after updates and deletes for field selector host=host1")
304+
gomega.Eventually(ctx, v2HostInformerAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
305+
Should(gomega.Equal(addedAndDeletedEvents(sets.New(crNames[0], crNames[1]), sets.New(crNames[0], crNames[1]))))
306+
307+
ginkgo.By("Waiting for v2 informer events after updates and deletes for field selector host=host1,port=80")
308+
gomega.Eventually(ctx, v2HostPortInformerAcc).WithPolling(5 * time.Millisecond).WithTimeout(30 * time.Second).
309+
Should(gomega.Equal(addedAndDeletedEvents(sets.New(crNames[0]), sets.New(crNames[0]))))
310+
})
264311
})
265312
})
266313

@@ -276,8 +323,8 @@ func addedEvents(added sets.Set[string]) *accumulatedEvents {
276323
return &accumulatedEvents{added: added, deleted: sets.New[string]()}
277324
}
278325

279-
func deletedEvents(deleted sets.Set[string]) *accumulatedEvents {
280-
return &accumulatedEvents{added: sets.New[string](), deleted: deleted}
326+
func addedAndDeletedEvents(added, deleted sets.Set[string]) *accumulatedEvents {
327+
return &accumulatedEvents{added: added, deleted: deleted}
281328
}
282329

283330
func watchAccumulator(w watch.Interface) func(ctx context.Context) (*accumulatedEvents, error) {
@@ -301,6 +348,39 @@ func watchAccumulator(w watch.Interface) func(ctx context.Context) (*accumulated
301348
}
302349
}
303350

351+
func informerAccumulator(informer cache.SharedIndexInformer) func(ctx context.Context) (*accumulatedEvents, error) {
352+
var lock sync.Mutex
353+
result := emptyEvents()
354+
355+
_, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
356+
AddFunc: func(obj any) {
357+
defer ginkgo.GinkgoRecover()
358+
lock.Lock()
359+
defer lock.Unlock()
360+
result.added.Insert(obj.(*unstructured.Unstructured).GetName())
361+
},
362+
UpdateFunc: func(oldObj, newObj any) {
363+
defer ginkgo.GinkgoRecover()
364+
// ignoring
365+
},
366+
DeleteFunc: func(obj any) {
367+
defer ginkgo.GinkgoRecover()
368+
lock.Lock()
369+
defer lock.Unlock()
370+
result.deleted.Insert(obj.(*unstructured.Unstructured).GetName())
371+
},
372+
})
373+
374+
return func(ctx context.Context) (*accumulatedEvents, error) {
375+
if err != nil {
376+
return nil, err
377+
}
378+
lock.Lock()
379+
defer lock.Unlock()
380+
return result, nil
381+
}
382+
}
383+
304384
func listResultToNames(list *unstructured.UnstructuredList) sets.Set[string] {
305385
found := sets.New[string]()
306386
for _, i := range list.Items {

0 commit comments

Comments
 (0)