From de627e3dd227a65d12e66e3c4b0327ff736b8ffa Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Wed, 27 Sep 2023 15:25:41 +0530 Subject: [PATCH] gatewayapi: add support for b/g mirroring Add support for mirroring requests while performing B/G deployments with Gateway API. A `RequestMirror` filter pointing to the canary service is added to the HTTPRoute during a Canary run. During the Canary run, drift correction for `.spec.rules[].filters` is disabled to avoid removing the mirror filter. Signed-off-by: Sanskar Jaiswal --- pkg/router/gateway_api_v1beta1.go | 33 ++++++++++++++++++++++ pkg/router/gateway_api_v1beta1_test.go | 38 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/pkg/router/gateway_api_v1beta1.go b/pkg/router/gateway_api_v1beta1.go index ff023854e..c1762bad2 100644 --- a/pkg/router/gateway_api_v1beta1.go +++ b/pkg/router/gateway_api_v1beta1.go @@ -187,6 +187,16 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error { ignoreCmpOptions = append(ignoreCmpOptions, cmpopts.IgnoreFields(v1beta1.HTTPBackendRef{}, "Filters")) } + if canary.GetAnalysis().Mirror { + // If a Canary run is in progress, the HTTPRoute rule will have an extra filter of type RequestMirror + // which needs to be ignored so that the requests are mirrored to the canary deployment. + inProgress := canary.Status.Phase == flaggerv1.CanaryPhaseWaiting || canary.Status.Phase == flaggerv1.CanaryPhaseProgressing || + canary.Status.Phase == flaggerv1.CanaryPhaseWaitingPromotion + if inProgress { + ignoreCmpOptions = append(ignoreCmpOptions, cmpopts.IgnoreFields(v1beta1.HTTPRouteRule{}, "Filters")) + } + } + if httpRoute != nil { specDiff := cmp.Diff( httpRoute.Spec, httpRouteSpec, @@ -249,6 +259,12 @@ func (gwr *GatewayAPIV1Beta1Router) GetRoutes(canary *flaggerv1.Canary) ( } } } + for _, filter := range rule.Filters { + if filter.Type == v1beta1.HTTPRouteFilterRequestMirror && filter.RequestMirror != nil && + string(filter.RequestMirror.BackendRef.Name) == canarySvcName { + mirrored = true + } + } } if weightedRule != nil { @@ -307,6 +323,23 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes( }, }, } + + // If B/G mirroring is enabled, then add a route filter which mirrors the traffic + // to the canary service. + if mirrored && canary.GetAnalysis().Iterations > 0 { + weightedRouteRule.Filters = append(weightedRouteRule.Filters, v1beta1.HTTPRouteFilter{ + Type: v1beta1.HTTPRouteFilterRequestMirror, + RequestMirror: &v1beta1.HTTPRequestMirrorFilter{ + BackendRef: v1beta1.BackendObjectReference{ + Group: (*v1beta1.Group)(&backendRefGroup), + Kind: (*v1beta1.Kind)(&backendRefKind), + Name: v1beta1.ObjectName(canarySvcName), + Port: (*v1beta1.PortNumber)(&canary.Spec.Service.Port), + }, + }, + }) + } + httpRouteSpec := v1beta1.HTTPRouteSpec{ CommonRouteSpec: v1beta1.CommonRouteSpec{ ParentRefs: canary.Spec.Service.GatewayRefs, diff --git a/pkg/router/gateway_api_v1beta1_test.go b/pkg/router/gateway_api_v1beta1_test.go index 37a4627df..647061c32 100644 --- a/pkg/router/gateway_api_v1beta1_test.go +++ b/pkg/router/gateway_api_v1beta1_test.go @@ -233,6 +233,44 @@ func TestGatewayAPIV1Beta1Router_Routes(t *testing.T) { } assert.True(t, found) }) + + t.Run("b/g mirror", func(t *testing.T) { + canary := mocks.canary.DeepCopy() + canary.Spec.Analysis.Mirror = true + canary.Spec.Analysis.Iterations = 5 + _, _, cSvcName := canary.GetServiceNames() + + err = router.SetRoutes(canary, 100, 0, true) + hr, err := mocks.meshClient.GatewayapiV1beta1().HTTPRoutes("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + assert.Len(t, hr.Spec.Rules, 1) + + rule := hr.Spec.Rules[0] + var found bool + for _, filter := range rule.Filters { + if filter.Type == v1beta1.HTTPRouteFilterRequestMirror && filter.RequestMirror != nil && + string(filter.RequestMirror.BackendRef.Name) == cSvcName { + found = true + } + } + assert.True(t, found, "could not find request mirror filter in HTTPRoute") + + // Mark the status as progressing to assert that request mirror filter is ignored. + canary.Status.Phase = flaggerv1.CanaryPhaseProgressing + err = router.Reconcile(canary) + require.NoError(t, err) + + hr, err = mocks.meshClient.GatewayapiV1beta1().HTTPRoutes("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + assert.Len(t, hr.Spec.Rules, 1) + assert.Empty(t, cmp.Diff(hr.Spec.Rules[0], rule)) + + err = router.SetRoutes(canary, 100, 0, false) + hr, err = mocks.meshClient.GatewayapiV1beta1().HTTPRoutes("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + assert.Len(t, hr.Spec.Rules, 1) + assert.Len(t, hr.Spec.Rules[0].Filters, 0) + }) } func TestGatewayAPIV1Beta1Router_getSessionAffinityRouteRules(t *testing.T) {