Skip to content

Commit e4b9d11

Browse files
committed
gateway-api: index request mirror backends for HTTPRoute and GRPCRoute
Include RequestMirror backend references in the HTTPRoute and GRPCRoute backend-service indexers. Without this, Services referenced only through RequestMirror are missed by the Service -> Route -> Gateway lookup path. That can prevent Gateway reconciliation when a mirrored backend Service changes. Signed-off-by: Marco Hofstetter <marco.hofstetter@isovalent.com>
1 parent 730a69b commit e4b9d11

5 files changed

Lines changed: 179 additions & 11 deletions

File tree

operator/pkg/gateway-api/gateway_reconcile_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,22 @@ func fakeIndexHTTPRouteByBackendService(rawObj client.Object) []string {
13151315
}.String(),
13161316
)
13171317
}
1318+
for _, f := range rule.Filters {
1319+
if f.Type != gatewayv1.HTTPRouteFilterRequestMirror || f.RequestMirror == nil {
1320+
continue
1321+
}
1322+
if !helpers.IsService(f.RequestMirror.BackendRef) {
1323+
continue
1324+
}
1325+
namespace := helpers.NamespaceDerefOr(f.RequestMirror.BackendRef.Namespace, route.Namespace)
1326+
backendServices = append(
1327+
backendServices,
1328+
types.NamespacedName{
1329+
Namespace: namespace,
1330+
Name: string(f.RequestMirror.BackendRef.Name),
1331+
}.String(),
1332+
)
1333+
}
13181334
}
13191335
return backendServices
13201336
}

operator/pkg/gateway-api/indexers/grpcroute.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ func GenerateIndexerGRPCRoutebyBackendService(c client.Client, logger *slog.Logg
101101
}.String(),
102102
)
103103
}
104+
for _, f := range rule.Filters {
105+
if f.Type != gatewayv1.GRPCRouteFilterRequestMirror || f.RequestMirror == nil {
106+
continue
107+
}
108+
namespace := helpers.NamespaceDerefOr(f.RequestMirror.BackendRef.Namespace, route.Namespace)
109+
backendServiceName, err := helpers.GetBackendServiceName(c, namespace, f.RequestMirror.BackendRef)
110+
if err != nil {
111+
logger.Error("Failed to get request mirror backend service name",
112+
logfields.LogSubsys, logfields.GRPCRoute,
113+
logfields.Resource, client.ObjectKeyFromObject(rawObj),
114+
logfields.Error, err)
115+
continue
116+
}
117+
backendServices = append(backendServices,
118+
types.NamespacedName{
119+
Namespace: namespace,
120+
Name: backendServiceName,
121+
}.String(),
122+
)
123+
}
104124
}
105125

106126
return backendServices

operator/pkg/gateway-api/indexers/grpcroute_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package indexers
55

66
import (
7+
"log/slog"
78
"reflect"
89
"slices"
910
"testing"
@@ -12,8 +13,11 @@ import (
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/utils/ptr"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1517
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
1618
mcsapiv1beta1 "sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
19+
20+
"github.com/cilium/cilium/operator/pkg/gateway-api/helpers"
1721
)
1822

1923
func TestIndexGRPCRouteByGateway(t *testing.T) {
@@ -182,6 +186,54 @@ func TestIndexGRPCRouteByBackendServiceImport(t *testing.T) {
182186
}
183187
}
184188

189+
func TestGenerateIndexerGRPCRoutebyBackendServiceIncludesRequestMirror(t *testing.T) {
190+
indexer := GenerateIndexerGRPCRoutebyBackendService(
191+
fake.NewClientBuilder().WithScheme(helpers.TestScheme(nil)).Build(),
192+
slog.New(slog.DiscardHandler),
193+
)
194+
195+
route := &gatewayv1.GRPCRoute{
196+
ObjectMeta: metav1.ObjectMeta{
197+
Name: "mirror-route",
198+
Namespace: "default",
199+
},
200+
Spec: gatewayv1.GRPCRouteSpec{
201+
Rules: []gatewayv1.GRPCRouteRule{
202+
{
203+
BackendRefs: []gatewayv1.GRPCBackendRef{
204+
{
205+
BackendRef: gatewayv1.BackendRef{
206+
BackendObjectReference: gatewayv1.BackendObjectReference{
207+
Name: "primary-svc",
208+
},
209+
},
210+
},
211+
},
212+
Filters: []gatewayv1.GRPCRouteFilter{
213+
{
214+
Type: gatewayv1.GRPCRouteFilterRequestMirror,
215+
RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{
216+
BackendRef: gatewayv1.BackendObjectReference{
217+
Name: "mirror-svc",
218+
Namespace: ptr.To[gatewayv1.Namespace]("other-ns"),
219+
},
220+
},
221+
},
222+
},
223+
},
224+
},
225+
},
226+
}
227+
228+
want := []string{
229+
"default/primary-svc",
230+
"other-ns/mirror-svc",
231+
}
232+
if got := indexer(route); !slices.Equal(got, want) {
233+
t.Errorf("GenerateIndexerGRPCRoutebyBackendService() = %v, want %v", got, want)
234+
}
235+
}
236+
185237
var meshGRPCSplit = &gatewayv1.GRPCRoute{
186238
ObjectMeta: metav1.ObjectMeta{
187239
Name: "mesh-split",

operator/pkg/gateway-api/indexers/httproute.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,37 @@ func GenerateIndexerHTTPRouteByBackendService(c client.Client, logger *slog.Logg
105105
)
106106
}
107107
for _, f := range rule.Filters {
108-
if f.Type != gatewayv1.HTTPRouteFilterExternalAuth || f.ExternalAuth == nil {
109-
continue
110-
}
111-
ea := f.ExternalAuth
112-
namespace := helpers.NamespaceDerefOr(ea.BackendRef.Namespace, route.Namespace)
113-
backendServiceName, err := helpers.GetBackendServiceName(c, namespace, ea.BackendRef)
114-
if err != nil {
115-
logger.Error("Failed to get ext_auth backend service name",
116-
logfields.LogSubsys, logfields.HTTPRoute,
117-
logfields.HTTPRoute, client.ObjectKeyFromObject(rawObj),
118-
logfields.Error, err)
108+
var (
109+
namespace string
110+
backendServiceName string
111+
err error
112+
)
113+
114+
switch {
115+
case f.Type == gatewayv1.HTTPRouteFilterRequestMirror && f.RequestMirror != nil:
116+
namespace = helpers.NamespaceDerefOr(f.RequestMirror.BackendRef.Namespace, route.Namespace)
117+
backendServiceName, err = helpers.GetBackendServiceName(c, namespace, f.RequestMirror.BackendRef)
118+
if err != nil {
119+
logger.Error("Failed to get request mirror backend service name",
120+
logfields.LogSubsys, logfields.HTTPRoute,
121+
logfields.HTTPRoute, client.ObjectKeyFromObject(rawObj),
122+
logfields.Error, err)
123+
continue
124+
}
125+
case f.Type == gatewayv1.HTTPRouteFilterExternalAuth && f.ExternalAuth != nil:
126+
namespace = helpers.NamespaceDerefOr(f.ExternalAuth.BackendRef.Namespace, route.Namespace)
127+
backendServiceName, err = helpers.GetBackendServiceName(c, namespace, f.ExternalAuth.BackendRef)
128+
if err != nil {
129+
logger.Error("Failed to get ext_auth backend service name",
130+
logfields.LogSubsys, logfields.HTTPRoute,
131+
logfields.HTTPRoute, client.ObjectKeyFromObject(rawObj),
132+
logfields.Error, err)
133+
continue
134+
}
135+
default:
119136
continue
120137
}
138+
121139
backendServices = append(backendServices,
122140
types.NamespacedName{
123141
Namespace: namespace,

operator/pkg/gateway-api/indexers/httproute_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package indexers
55

66
import (
7+
"log/slog"
78
"reflect"
89
"slices"
910
"testing"
@@ -12,8 +13,11 @@ import (
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/utils/ptr"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1517
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
1618
mcsapiv1beta1 "sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
19+
20+
"github.com/cilium/cilium/operator/pkg/gateway-api/helpers"
1721
)
1822

1923
func TestIndexHTTPRouteByGateway(t *testing.T) {
@@ -264,6 +268,64 @@ func TestIndexHTTPRouteByBackendServiceImport(t *testing.T) {
264268
}
265269
}
266270

271+
func TestGenerateIndexerHTTPRouteByBackendServiceIncludesFilterBackends(t *testing.T) {
272+
indexer := GenerateIndexerHTTPRouteByBackendService(
273+
fake.NewClientBuilder().WithScheme(helpers.TestScheme(nil)).Build(),
274+
slog.New(slog.DiscardHandler),
275+
)
276+
277+
route := &gatewayv1.HTTPRoute{
278+
ObjectMeta: metav1.ObjectMeta{
279+
Name: "mirror-route",
280+
Namespace: "default",
281+
},
282+
Spec: gatewayv1.HTTPRouteSpec{
283+
Rules: []gatewayv1.HTTPRouteRule{
284+
{
285+
BackendRefs: []gatewayv1.HTTPBackendRef{
286+
{
287+
BackendRef: gatewayv1.BackendRef{
288+
BackendObjectReference: gatewayv1.BackendObjectReference{
289+
Name: "primary-svc",
290+
},
291+
},
292+
},
293+
},
294+
Filters: []gatewayv1.HTTPRouteFilter{
295+
{
296+
Type: gatewayv1.HTTPRouteFilterRequestMirror,
297+
RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{
298+
BackendRef: gatewayv1.BackendObjectReference{
299+
Name: "mirror-svc",
300+
Namespace: ptr.To[gatewayv1.Namespace]("other-ns"),
301+
},
302+
},
303+
},
304+
{
305+
Type: gatewayv1.HTTPRouteFilterExternalAuth,
306+
ExternalAuth: &gatewayv1.HTTPExternalAuthFilter{
307+
BackendRef: gatewayv1.BackendObjectReference{
308+
Name: "auth-svc",
309+
Namespace: ptr.To[gatewayv1.Namespace]("auth-ns"),
310+
},
311+
},
312+
},
313+
},
314+
},
315+
},
316+
},
317+
}
318+
319+
want := []string{
320+
"default/primary-svc",
321+
"other-ns/mirror-svc",
322+
"auth-ns/auth-svc",
323+
}
324+
if got := indexer(route); !slices.Equal(got, want) {
325+
t.Errorf("GenerateIndexerHTTPRouteByBackendService() = %v, want %v", got, want)
326+
}
327+
}
328+
267329
var meshSplit = &gatewayv1.HTTPRoute{
268330
ObjectMeta: metav1.ObjectMeta{
269331
Name: "mesh-split",

0 commit comments

Comments
 (0)