Skip to content

Commit ce34aeb

Browse files
feat: indexing for referencegrant (#1618)
**Description** This commit adds indexing for referencegrants. The indexing key uses To Kind + namespace in referencegrant for indexing which would help fetch only related referencegrants given aiservicebackend and namespace. **Related Issues/PRs (if applicable)** Closes #1375 --------- Signed-off-by: siddharth1036 <[email protected]>
1 parent 1fa2992 commit ce34aeb

File tree

4 files changed

+440
-4
lines changed

4 files changed

+440
-4
lines changed

internal/controller/controller.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ const (
275275
// to the BackendSecurityPolicy whose targetRefs contains the AIServiceBackend.
276276
k8sClientIndexAIServiceBackendToTargetingBackendSecurityPolicy = "AIServiceBackendToTargetingBackendSecurityPolicy"
277277

278+
// k8sClientIndexReferenceGrantToTargetKind is the index name that maps from namespace/kind to ReferenceGrants, enabling efficient lookup of grants
279+
// allowing access to specific resource types in specific namespaces.
280+
k8sClientIndexReferenceGrantToTargetKind = "ReferenceGrantToTargetKind"
281+
278282
// Indexes for MCP Gateway
279283
//
280284
// k8sClientIndexMCPRouteToAttachedGateway is the index name that maps from a Gateway to the
@@ -305,6 +309,13 @@ func ApplyIndexing(ctx context.Context, indexer func(ctx context.Context, obj cl
305309
return fmt.Errorf("failed to index field for BackendSecurityPolicy targetRefs: %w", err)
306310
}
307311

312+
// Apply indexes for ReferenceGrant.
313+
err = indexer(ctx, &gwapiv1b1.ReferenceGrant{},
314+
k8sClientIndexReferenceGrantToTargetKind, referenceGrantToTargetKindIndexFunc)
315+
if err != nil {
316+
return fmt.Errorf("failed to create index from target kind to ReferenceGrant: %w", err)
317+
}
318+
308319
// Apply indexes to MCP Gateways.
309320
err = indexer(ctx, &aigv1a1.MCPRoute{},
310321
k8sClientIndexMCPRouteToAttachedGateway, mcpRouteToAttachedGatewayIndexFunc)
@@ -408,6 +419,20 @@ func getSecretNameAndNamespace(secretRef *gwapiv1.SecretObjectReference, namespa
408419
return fmt.Sprintf("%s.%s", secretRef.Name, namespace)
409420
}
410421

422+
func getReferenceGrantIndexKey(namespace, kind string) string {
423+
return fmt.Sprintf("%s.%s", namespace, kind)
424+
}
425+
426+
func referenceGrantToTargetKindIndexFunc(o client.Object) []string {
427+
referenceGrant := o.(*gwapiv1b1.ReferenceGrant)
428+
var keys []string
429+
for _, to := range referenceGrant.Spec.To {
430+
key := getReferenceGrantIndexKey(referenceGrant.Namespace, string(to.Kind))
431+
keys = append(keys, key)
432+
}
433+
return keys
434+
}
435+
411436
// newConditions creates a new condition with the given type and message.
412437
//
413438
// Currently, we only set one condition at a time either "Accepted" or "NotAccepted".

internal/controller/controller_test.go

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2121
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
2222
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
23+
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2324

2425
aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1"
2526
)
@@ -266,6 +267,250 @@ func Test_getSecretNameAndNamespace(t *testing.T) {
266267
require.Equal(t, "mysecret.foo", getSecretNameAndNamespace(secretRef, "foo"))
267268
}
268269

270+
func Test_referenceGrantToTargetKindIndexFunc(t *testing.T) {
271+
tests := []struct {
272+
name string
273+
referenceGrant *gwapiv1b1.ReferenceGrant
274+
expectedKeys []string
275+
}{
276+
{
277+
name: "single target kind - AIServiceBackend",
278+
referenceGrant: &gwapiv1b1.ReferenceGrant{
279+
ObjectMeta: metav1.ObjectMeta{
280+
Name: "grant1",
281+
Namespace: "backend-ns",
282+
},
283+
Spec: gwapiv1b1.ReferenceGrantSpec{
284+
From: []gwapiv1b1.ReferenceGrantFrom{
285+
{
286+
Group: "aigateway.envoyproxy.io",
287+
Kind: "AIGatewayRoute",
288+
Namespace: "route-ns",
289+
},
290+
},
291+
To: []gwapiv1b1.ReferenceGrantTo{
292+
{
293+
Group: "aigateway.envoyproxy.io",
294+
Kind: "AIServiceBackend",
295+
},
296+
},
297+
},
298+
},
299+
expectedKeys: []string{"backend-ns.AIServiceBackend"},
300+
},
301+
{
302+
name: "multiple target kinds",
303+
referenceGrant: &gwapiv1b1.ReferenceGrant{
304+
ObjectMeta: metav1.ObjectMeta{
305+
Name: "grant2",
306+
Namespace: "backend-ns",
307+
},
308+
Spec: gwapiv1b1.ReferenceGrantSpec{
309+
From: []gwapiv1b1.ReferenceGrantFrom{
310+
{
311+
Group: "aigateway.envoyproxy.io",
312+
Kind: "AIGatewayRoute",
313+
Namespace: "route-ns",
314+
},
315+
},
316+
To: []gwapiv1b1.ReferenceGrantTo{
317+
{
318+
Group: "aigateway.envoyproxy.io",
319+
Kind: "AIServiceBackend",
320+
},
321+
{
322+
Group: "",
323+
Kind: "Secret",
324+
},
325+
},
326+
},
327+
},
328+
expectedKeys: []string{"backend-ns.AIServiceBackend", "backend-ns.Secret"},
329+
},
330+
{
331+
name: "empty group for core resources",
332+
referenceGrant: &gwapiv1b1.ReferenceGrant{
333+
ObjectMeta: metav1.ObjectMeta{
334+
Name: "grant3",
335+
Namespace: "other-ns",
336+
},
337+
Spec: gwapiv1b1.ReferenceGrantSpec{
338+
From: []gwapiv1b1.ReferenceGrantFrom{
339+
{
340+
Group: "gateway.networking.k8s.io",
341+
Kind: "HTTPRoute",
342+
Namespace: "route-ns",
343+
},
344+
},
345+
To: []gwapiv1b1.ReferenceGrantTo{
346+
{
347+
Group: "",
348+
Kind: "Service",
349+
},
350+
},
351+
},
352+
},
353+
expectedKeys: []string{"other-ns.Service"},
354+
},
355+
{
356+
name: "no target kinds",
357+
referenceGrant: &gwapiv1b1.ReferenceGrant{
358+
ObjectMeta: metav1.ObjectMeta{
359+
Name: "grant4",
360+
Namespace: "backend-ns",
361+
},
362+
Spec: gwapiv1b1.ReferenceGrantSpec{
363+
From: []gwapiv1b1.ReferenceGrantFrom{
364+
{
365+
Group: "aigateway.envoyproxy.io",
366+
Kind: "AIGatewayRoute",
367+
Namespace: "route-ns",
368+
},
369+
},
370+
To: []gwapiv1b1.ReferenceGrantTo{},
371+
},
372+
},
373+
expectedKeys: nil, // nil is returned for empty To array
374+
},
375+
}
376+
377+
for _, tt := range tests {
378+
t.Run(tt.name, func(t *testing.T) {
379+
keys := referenceGrantToTargetKindIndexFunc(tt.referenceGrant)
380+
require.Equal(t, tt.expectedKeys, keys)
381+
})
382+
}
383+
}
384+
385+
func Test_referenceGrantIndexWithQuery(t *testing.T) {
386+
// Create a fake client with the ReferenceGrant index
387+
c := fake.NewClientBuilder().
388+
WithScheme(Scheme).
389+
WithIndex(&gwapiv1b1.ReferenceGrant{}, k8sClientIndexReferenceGrantToTargetKind, referenceGrantToTargetKindIndexFunc).
390+
Build()
391+
392+
// Create multiple ReferenceGrants with different target kinds
393+
grant1 := &gwapiv1b1.ReferenceGrant{
394+
ObjectMeta: metav1.ObjectMeta{
395+
Name: "grant-aiservicebackend",
396+
Namespace: "backend-ns",
397+
},
398+
Spec: gwapiv1b1.ReferenceGrantSpec{
399+
From: []gwapiv1b1.ReferenceGrantFrom{
400+
{
401+
Group: "aigateway.envoyproxy.io",
402+
Kind: "AIGatewayRoute",
403+
Namespace: "route-ns",
404+
},
405+
},
406+
To: []gwapiv1b1.ReferenceGrantTo{
407+
{
408+
Group: "aigateway.envoyproxy.io",
409+
Kind: "AIServiceBackend",
410+
},
411+
},
412+
},
413+
}
414+
415+
grant2 := &gwapiv1b1.ReferenceGrant{
416+
ObjectMeta: metav1.ObjectMeta{
417+
Name: "grant-secret",
418+
Namespace: "backend-ns",
419+
},
420+
Spec: gwapiv1b1.ReferenceGrantSpec{
421+
From: []gwapiv1b1.ReferenceGrantFrom{
422+
{
423+
Group: "gateway.networking.k8s.io",
424+
Kind: "HTTPRoute",
425+
Namespace: "route-ns",
426+
},
427+
},
428+
To: []gwapiv1b1.ReferenceGrantTo{
429+
{
430+
Group: "",
431+
Kind: "Secret",
432+
},
433+
},
434+
},
435+
}
436+
437+
grant3 := &gwapiv1b1.ReferenceGrant{
438+
ObjectMeta: metav1.ObjectMeta{
439+
Name: "grant-multiple",
440+
Namespace: "backend-ns",
441+
},
442+
Spec: gwapiv1b1.ReferenceGrantSpec{
443+
From: []gwapiv1b1.ReferenceGrantFrom{
444+
{
445+
Group: "aigateway.envoyproxy.io",
446+
Kind: "AIGatewayRoute",
447+
Namespace: "route-ns",
448+
},
449+
},
450+
To: []gwapiv1b1.ReferenceGrantTo{
451+
{
452+
Group: "aigateway.envoyproxy.io",
453+
Kind: "AIServiceBackend",
454+
},
455+
{
456+
Group: "",
457+
Kind: "Secret",
458+
},
459+
},
460+
},
461+
}
462+
463+
require.NoError(t, c.Create(t.Context(), grant1))
464+
require.NoError(t, c.Create(t.Context(), grant2))
465+
require.NoError(t, c.Create(t.Context(), grant3))
466+
467+
t.Run("query for AIServiceBackend grants in backend-ns", func(t *testing.T) {
468+
var grants gwapiv1b1.ReferenceGrantList
469+
err := c.List(t.Context(), &grants,
470+
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns.AIServiceBackend"})
471+
require.NoError(t, err)
472+
473+
// Should find grant1 and grant3 (both allow AIServiceBackend in backend-ns)
474+
require.Len(t, grants.Items, 2)
475+
names := []string{grants.Items[0].Name, grants.Items[1].Name}
476+
require.Contains(t, names, "grant-aiservicebackend")
477+
require.Contains(t, names, "grant-multiple")
478+
})
479+
480+
t.Run("query for Secret grants in backend-ns", func(t *testing.T) {
481+
var grants gwapiv1b1.ReferenceGrantList
482+
err := c.List(t.Context(), &grants,
483+
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns.Secret"})
484+
require.NoError(t, err)
485+
486+
// Should find grant2 and grant3 (both allow Secret in backend-ns)
487+
require.Len(t, grants.Items, 2)
488+
names := []string{grants.Items[0].Name, grants.Items[1].Name}
489+
require.Contains(t, names, "grant-secret")
490+
require.Contains(t, names, "grant-multiple")
491+
})
492+
493+
t.Run("query for non-existent kind", func(t *testing.T) {
494+
var grants gwapiv1b1.ReferenceGrantList
495+
err := c.List(t.Context(), &grants,
496+
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns.NonExistentKind"})
497+
require.NoError(t, err)
498+
499+
// Should find nothing
500+
require.Empty(t, grants.Items)
501+
})
502+
503+
t.Run("query with wrong namespace", func(t *testing.T) {
504+
var grants gwapiv1b1.ReferenceGrantList
505+
err := c.List(t.Context(), &grants,
506+
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "wrong-ns.AIServiceBackend"})
507+
require.NoError(t, err)
508+
509+
// Should find nothing (wrong namespace)
510+
require.Empty(t, grants.Items)
511+
})
512+
}
513+
269514
func Test_handleFinalizer(t *testing.T) {
270515
tests := []struct {
271516
name string

internal/controller/referencegrant.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ func (v *referenceGrantValidator) validateAIServiceBackendReference(
5454
return nil
5555
}
5656

57-
// List all ReferenceGrants in the backend namespace
57+
indexKey := getReferenceGrantIndexKey(backendNamespace, aiServiceBackendKind)
5858
var referenceGrants gwapiv1b1.ReferenceGrantList
59-
if err := v.client.List(ctx, &referenceGrants, client.InNamespace(backendNamespace)); err != nil {
60-
return fmt.Errorf("failed to list ReferenceGrants in namespace %s: %w", backendNamespace, err)
59+
if err := v.client.List(ctx, &referenceGrants,
60+
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: indexKey},
61+
); err != nil {
62+
return fmt.Errorf("failed to list ReferenceGrants in namespace %s for kind %s: %w",
63+
backendNamespace, aiServiceBackendKind, err)
6164
}
6265

6366
// Check if any ReferenceGrant allows this cross-namespace reference

0 commit comments

Comments
 (0)