Skip to content

Commit 476a75c

Browse files
authored
Merge pull request #14 from sttts/sttts-indexes-test
🌱 test/e2e: test scoped indexes
2 parents 6fb4c62 + dbce2c0 commit 476a75c

File tree

3 files changed

+151
-14
lines changed

3 files changed

+151
-14
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
golang.org/x/sync v0.11.0
1818
golang.org/x/sys v0.30.0
1919
k8s.io/api v0.32.3
20+
k8s.io/apiextensions-apiserver v0.32.1
2021
k8s.io/apimachinery v0.32.3
2122
k8s.io/client-go v0.32.3
2223
k8s.io/klog/v2 v2.130.1
@@ -73,7 +74,6 @@ require (
7374
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
7475
gopkg.in/inf.v0 v0.9.1 // indirect
7576
gopkg.in/yaml.v3 v3.0.1 // indirect
76-
k8s.io/apiextensions-apiserver v0.32.1 // indirect
7777
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
7878
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
7979
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect

test/e2e/apiexport_test.go

Lines changed: 141 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ import (
2727

2828
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3031
"k8s.io/apimachinery/pkg/runtime"
32+
runtimeschema "k8s.io/apimachinery/pkg/runtime/schema"
3133
"k8s.io/apimachinery/pkg/util/sets"
3234
"k8s.io/apimachinery/pkg/util/wait"
35+
"k8s.io/client-go/discovery"
3336
"k8s.io/client-go/rest"
3437
"sigs.k8s.io/controller-runtime/pkg/client"
3538
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -57,11 +60,11 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
5760
ctx context.Context
5861
cancel context.CancelFunc
5962

60-
cli clusterclient.ClusterClient
61-
provider, consumer logicalcluster.Path
62-
consumerWS *tenancyv1alpha1.Workspace
63-
mgr mcmanager.Manager
64-
vwEndpoint string
63+
cli clusterclient.ClusterClient
64+
provider, consumer, other logicalcluster.Path
65+
consumerWS *tenancyv1alpha1.Workspace
66+
mgr mcmanager.Manager
67+
vwEndpoint string
6568
)
6669

6770
BeforeAll(func() {
@@ -73,6 +76,7 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
7376

7477
_, provider = envtest.NewWorkspaceFixture(GinkgoT(), cli, core.RootCluster.Path(), envtest.WithNamePrefix("provider"))
7578
consumerWS, consumer = envtest.NewWorkspaceFixture(GinkgoT(), cli, core.RootCluster.Path(), envtest.WithNamePrefix("consumer"))
79+
_, other = envtest.NewWorkspaceFixture(GinkgoT(), cli, core.RootCluster.Path(), envtest.WithNamePrefix("other"))
7680

7781
By(fmt.Sprintf("creating a schema in the provider workspace %q", provider))
7882
schema := &apisv1alpha1.APIResourceSchema{
@@ -94,6 +98,7 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
9498
Raw: []byte(`{"type":"object","properties":{"spec":{"type":"object","properties":{"message":{"type":"string"}}}}}`),
9599
},
96100
Storage: true,
101+
Served: true,
97102
}},
98103
},
99104
}
@@ -127,7 +132,7 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
127132
err = cli.Cluster(provider).Create(ctx, endpoitns)
128133
Expect(err).NotTo(HaveOccurred())
129134

130-
By(fmt.Sprintf("creating an APIBinding in the consumer workspace %q", consumer))
135+
By(fmt.Sprintf("creating an APIBinding in the other workspace %q", other))
131136
binding := &apisv1alpha1.APIBinding{
132137
ObjectMeta: metav1.ObjectMeta{
133138
Name: "example.com",
@@ -141,6 +146,23 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
141146
},
142147
},
143148
}
149+
err = cli.Cluster(other).Create(ctx, binding)
150+
Expect(err).NotTo(HaveOccurred())
151+
152+
By(fmt.Sprintf("creating an APIBinding in the consumer workspace %q", consumer))
153+
binding = &apisv1alpha1.APIBinding{
154+
ObjectMeta: metav1.ObjectMeta{
155+
Name: "example.com",
156+
},
157+
Spec: apisv1alpha1.APIBindingSpec{
158+
Reference: apisv1alpha1.BindingReference{
159+
Export: &apisv1alpha1.ExportBindingReference{
160+
Path: provider.String(),
161+
Name: export.Name,
162+
},
163+
},
164+
},
165+
}
144166
err = cli.Cluster(consumer).Create(ctx, binding)
145167
Expect(err).NotTo(HaveOccurred())
146168

@@ -149,35 +171,104 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
149171
envtest.Eventually(GinkgoT(), func() (bool, string) {
150172
err := cli.Cluster(provider).Get(ctx, client.ObjectKey{Name: "example.com"}, endpoints)
151173
if err != nil {
152-
return false, fmt.Sprintf("failed to get APIExportEndpointSlice in %s: %v", provider, err)
174+
return false, fmt.Sprintf("failed to get APIExportEndpointSlice in %q: %v", provider, err)
153175
}
154176
return len(endpoints.Status.APIExportEndpoints) > 0, toYAML(GinkgoT(), endpoints)
155-
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to see endpoints in APIExportEndpointSlice in %s", provider)
177+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to see endpoints in APIExportEndpointSlice in %q", provider)
156178
vwEndpoint = endpoints.Status.APIExportEndpoints[0].URL
179+
180+
By(fmt.Sprintf("waiting until the APIBinding in the consumer workspace %q to be ready", consumer))
181+
envtest.Eventually(GinkgoT(), func() (bool, string) {
182+
current := &apisv1alpha1.APIBinding{}
183+
err := cli.Cluster(consumer).Get(ctx, client.ObjectKey{Name: "example.com"}, current)
184+
if err != nil {
185+
return false, fmt.Sprintf("failed to get APIBinding in %q: %v", consumer, err)
186+
}
187+
if current.Status.Phase != apisv1alpha1.APIBindingPhaseBound {
188+
return false, fmt.Sprintf("binding not bound:\n\n%s", toYAML(GinkgoT(), current))
189+
}
190+
return true, ""
191+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to wait for APIBinding in consumer workspace to be ready %q", consumer)
192+
193+
By("waiting until things can be listed in the consumer workspace")
194+
envtest.Eventually(GinkgoT(), func() (bool, string) {
195+
u := &unstructured.UnstructuredList{}
196+
u.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "ThingList"})
197+
err = cli.Cluster(consumer).List(ctx, u)
198+
if err != nil {
199+
return false, fmt.Sprintf("failed to list things in %s: %v", consumer, err)
200+
}
201+
return true, ""
202+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to wait for things to be listable in consumer workspace %q", consumer)
157203
})
158204

159205
Describe("with a multicluster provider and manager", func() {
160206
var (
161207
lock sync.RWMutex
162208
engaged = sets.NewString()
209+
p *virtualworkspace.Provider
163210
g *errgroup.Group
164211
cancelGroup context.CancelFunc
165212
)
166213

167214
BeforeAll(func() {
215+
By("creating a stone in the consumer workspace", func() {
216+
thing := &unstructured.Unstructured{}
217+
thing.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Thing"})
218+
thing.SetName("stone")
219+
thing.SetLabels(map[string]string{"color": "gray"})
220+
err := cli.Cluster(consumer).Create(ctx, thing)
221+
Expect(err).NotTo(HaveOccurred())
222+
})
223+
224+
By("creating a box in the other workspace", func() {
225+
thing := &unstructured.Unstructured{}
226+
thing.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Thing"})
227+
thing.SetName("box")
228+
thing.SetLabels(map[string]string{"color": "white"})
229+
err := cli.Cluster(other).Create(ctx, thing)
230+
Expect(err).NotTo(HaveOccurred())
231+
})
232+
168233
By("creating a multicluster provider for APIBindings against the apiexport virtual workspace")
169234
vwConfig := rest.CopyConfig(kcpConfig)
170235
vwConfig.Host = vwEndpoint
171-
p, err := virtualworkspace.New(vwConfig, &apisv1alpha1.APIBinding{}, virtualworkspace.Options{})
236+
var err error
237+
p, err = virtualworkspace.New(vwConfig, &apisv1alpha1.APIBinding{}, virtualworkspace.Options{})
172238
Expect(err).NotTo(HaveOccurred())
173239

240+
By("waiting for discovery of the virtual workspace to show 'example.com'")
241+
wildcardConfig := rest.CopyConfig(vwConfig)
242+
wildcardConfig.Host += logicalcluster.Wildcard.RequestPath()
243+
disc, err := discovery.NewDiscoveryClientForConfig(wildcardConfig)
244+
Expect(err).NotTo(HaveOccurred())
245+
envtest.Eventually(GinkgoT(), func() (bool, string) {
246+
ret, err := disc.ServerGroups()
247+
Expect(err).NotTo(HaveOccurred())
248+
for _, g := range ret.Groups {
249+
if g.Name == "example.com" {
250+
return true, ""
251+
}
252+
}
253+
return false, fmt.Sprintf("failed to find group example.com in:\n%s", toYAML(GinkgoT(), ret))
254+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to find group example.com in the virtual workspace")
255+
174256
By("creating a manager against the provider workspace")
175257
rootConfig := rest.CopyConfig(kcpConfig)
176258
rootConfig.Host += provider.RequestPath()
177259
mgr, err = mcmanager.New(rootConfig, p, mcmanager.Options{})
178260
Expect(err).NotTo(HaveOccurred())
179261

180-
By("creating a reconciler for the APIBinding")
262+
By("adding an index on label 'color'")
263+
thing := &unstructured.Unstructured{}
264+
thing.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Thing"})
265+
err = mgr.GetFieldIndexer().IndexField(ctx, thing, "color", func(obj client.Object) []string {
266+
u := obj.(*unstructured.Unstructured)
267+
return []string{u.GetLabels()["color"]}
268+
})
269+
Expect(err).NotTo(HaveOccurred())
270+
271+
By("creating a reconciler for APIBindings")
181272
err = mcbuilder.ControllerManagedBy(mgr).
182273
Named("things").
183274
For(&apisv1alpha1.APIBinding{}).
@@ -211,6 +302,46 @@ var _ = Describe("VirtualWorkspace Provider", Ordered, func() {
211302
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to see the consumer workspace %q as a cluster", consumer)
212303
})
213304

305+
It("sees only the stone in the consumer clusters", func() {
306+
consumerCl, err := mgr.GetCluster(ctx, consumerWS.Spec.Cluster)
307+
Expect(err).NotTo(HaveOccurred())
308+
309+
envtest.Eventually(GinkgoT(), func() (success bool, reason string) {
310+
l := &unstructured.UnstructuredList{}
311+
l.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "ThingList"})
312+
err = consumerCl.GetCache().List(ctx, l)
313+
if err != nil {
314+
return false, fmt.Sprintf("failed to list things in the consumer cluster cache: %v", err)
315+
}
316+
if len(l.Items) != 1 {
317+
return false, fmt.Sprintf("expected 1 item, got %d\n\n%s", len(l.Items), toYAML(GinkgoT(), l.Object))
318+
} else if name := l.Items[0].GetName(); name != "stone" {
319+
return false, fmt.Sprintf("expected item name to be stone, got %q\n\n%s", name, toYAML(GinkgoT(), l.Items[0]))
320+
}
321+
return true, ""
322+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to see the stone in the consumer cluster")
323+
})
324+
325+
It("sees only the stone as grey thing in the consumer clusters", func() {
326+
consumerCl, err := mgr.GetCluster(ctx, consumerWS.Spec.Cluster)
327+
Expect(err).NotTo(HaveOccurred())
328+
329+
envtest.Eventually(GinkgoT(), func() (success bool, reason string) {
330+
l := &unstructured.UnstructuredList{}
331+
l.SetGroupVersionKind(runtimeschema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "ThingList"})
332+
err = consumerCl.GetCache().List(ctx, l, client.MatchingFields{"color": "gray"})
333+
if err != nil {
334+
return false, fmt.Sprintf("failed to list things in the consumer cluster cache: %v", err)
335+
}
336+
if len(l.Items) != 1 {
337+
return false, fmt.Sprintf("expected 1 item, got %d\n\n%s", len(l.Items), toYAML(GinkgoT(), l.Object))
338+
} else if name := l.Items[0].GetName(); name != "stone" {
339+
return false, fmt.Sprintf("expected item name to be stone, got %q\n\n%s", name, toYAML(GinkgoT(), l.Items[0]))
340+
}
341+
return true, ""
342+
}, wait.ForeverTestTimeout, time.Millisecond*100, "failed to see the stone as only thing of color 'grey' in the consumer cluster")
343+
})
344+
214345
AfterAll(func() {
215346
cancelGroup()
216347
err := g.Wait()

virtualworkspace/forked_cache_reader.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func byIndexes(indexer cache.Indexer, requires fields.Requirements, clusterName
212212
indexers := indexer.GetIndexers()
213213
_, isClusterAware := indexers[kcpcache.ClusterAndNamespaceIndexName]
214214
for idx, req := range requires {
215-
indexName := fieldIndexName(req.Field)
215+
indexName := fieldIndexName(isClusterAware, req.Field)
216216
var indexedValue string
217217
if isClusterAware {
218218
indexedValue = keyToClusteredKey(clusterName.String(), namespace, req.Value)
@@ -270,7 +270,10 @@ func objectKeyToStoreKey(k client.ObjectKey) string {
270270

271271
// fieldIndexName constructs the name of the index over the given field,
272272
// for use with an indexer.
273-
func fieldIndexName(field string) string {
273+
func fieldIndexName(clusterAware bool, field string) string {
274+
if clusterAware {
275+
return "field:cluster/" + field
276+
}
274277
return "field:" + field
275278
}
276279

@@ -289,7 +292,10 @@ func keyToNamespacedKey(ns string, baseKey string) string {
289292
// keyToClusteredKey prefixes the given index key with a cluster name
290293
// for use in field selector indexes.
291294
func keyToClusteredKey(clusterName string, ns string, baseKey string) string {
292-
return clusterName + "|" + keyToNamespacedKey(ns, baseKey)
295+
if ns != "" {
296+
return ns + "/" + clusterName + "/" + baseKey
297+
}
298+
return allNamespacesNamespace + "/" + clusterName + "/" + baseKey
293299
}
294300

295301
// requiresExactMatch checks if the given field selector is of the form `k=v` or `k==v`.

0 commit comments

Comments
 (0)