From badabfed1d906e8f17a40c0752de450594fcf27c Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Sun, 13 Feb 2022 12:31:16 +0530 Subject: [PATCH] add logic for a/b testing Signed-off-by: Sanskar Jaiswal --- pkg/router/gateway_api.go | 125 +++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 35 deletions(-) diff --git a/pkg/router/gateway_api.go b/pkg/router/gateway_api.go index 9a14ad4b1..a3b22b9e6 100644 --- a/pkg/router/gateway_api.go +++ b/pkg/router/gateway_api.go @@ -72,8 +72,7 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { for _, host := range canary.Spec.Service.Hosts { hostNames = append(hostNames, v1alpha2.Hostname(host)) } - requestMatches := canary.Spec.Service.Match - matches, err := gwr.mapRouteMatches(requestMatches) + matches, err := gwr.mapRouteMatches(canary.Spec.Service.Match) if err != nil { return fmt.Errorf("Invalid request matching selectors: %w", err) } @@ -96,32 +95,31 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { Matches: matches, BackendRefs: []v1alpha2.HTTPBackendRef{ { - BackendRef: v1alpha2.BackendRef{ - BackendObjectReference: v1alpha2.BackendObjectReference{ - Group: (*v1alpha2.Group)(&backendRefGroup), - Kind: (*v1alpha2.Kind)(&backendRefKind), - Name: v1alpha2.ObjectName(primarySvcName), - Port: (*v1alpha2.PortNumber)(&canary.Spec.Service.Port), - }, - Weight: &initialPrimaryWeight, - }, + BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port), }, { - BackendRef: v1alpha2.BackendRef{ - BackendObjectReference: v1alpha2.BackendObjectReference{ - Group: (*v1alpha2.Group)(&backendRefGroup), - Kind: (*v1alpha2.Kind)(&backendRefKind), - Name: v1alpha2.ObjectName(canarySvcName), - Port: (*v1alpha2.PortNumber)(&canary.Spec.Service.Port), - }, - Weight: &initialCanaryWeight, - }, + BackendRef: gwr.makeBackendRef(canarySvcName, initialCanaryWeight, canary.Spec.Service.Port), }, }, }, }, } + // A/B testing + if len(canary.GetAnalysis().Match) > 0 { + analysisMatches, _ := gwr.mapRouteMatches(canary.GetAnalysis().Match) + // serviceMatches, _ := gwr.mapRouteMatches(canary.Spec.Service.Match) + httpRouteSpec.Rules[0].Matches = analysisMatches + httpRouteSpec.Rules = append(httpRouteSpec.Rules, v1alpha2.HTTPRouteRule{ + Matches: matches, + BackendRefs: []v1alpha2.HTTPBackendRef{ + { + BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port), + }, + }, + }) + } + httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1alpha2().HTTPRoutes(hrNamespace).Get( context.TODO(), apexSvcName, metav1.GetOptions{}, ) @@ -200,14 +198,18 @@ func (gwr *GatewayAPIRouter) GetRoutes(canary *flaggerv1.Canary) ( return } for _, rule := range httpRoute.Spec.Rules { - for _, backendRef := range rule.BackendRefs { - if backendRef.Name == v1alpha2.ObjectName(primarySvcName) { - primaryWeight = int(*backendRef.Weight) - } - if backendRef.Name == v1alpha2.ObjectName(canarySvcName) { - canaryWeight = int(*backendRef.Weight) + // A/B testing: Avoid reading the rule with only for backendRef. + if len(rule.BackendRefs) == 2 { + for _, backendRef := range rule.BackendRefs { + if backendRef.Name == v1alpha2.ObjectName(primarySvcName) { + primaryWeight = int(*backendRef.Weight) + } + if backendRef.Name == v1alpha2.ObjectName(canarySvcName) { + canaryWeight = int(*backendRef.Weight) + } } } + } return } @@ -230,16 +232,57 @@ func (gwr *GatewayAPIRouter) SetRoutes( return fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) } hrClone := httpRoute.DeepCopy() - for i, rule := range hrClone.Spec.Rules { - for j, backendRef := range rule.BackendRefs { - if backendRef.Name == v1alpha2.ObjectName(primarySvcName) { - hrClone.Spec.Rules[i].BackendRefs[j].Weight = &pWeight - } - if backendRef.Name == v1alpha2.ObjectName(canarySvcName) { - hrClone.Spec.Rules[i].BackendRefs[j].Weight = &cWeight - } - } + hostNames := []v1alpha2.Hostname{} + for _, host := range canary.Spec.Service.Hosts { + hostNames = append(hostNames, v1alpha2.Hostname(host)) + } + matches, err := gwr.mapRouteMatches(canary.Spec.Service.Match) + if err != nil { + return fmt.Errorf("Invalid request matching selectors: %w", err) } + if len(matches) == 0 { + matches = append(matches, v1alpha2.HTTPRouteMatch{ + Path: &v1alpha2.HTTPPathMatch{ + Type: &pathMatchType, + Value: &pathMatchValue, + }, + }) + } + httpRouteSpec := v1alpha2.HTTPRouteSpec{ + CommonRouteSpec: v1alpha2.CommonRouteSpec{ + ParentRefs: canary.Spec.Service.GatewayRefs, + }, + Hostnames: hostNames, + Rules: []v1alpha2.HTTPRouteRule{ + { + Matches: matches, + BackendRefs: []v1alpha2.HTTPBackendRef{ + { + BackendRef: gwr.makeBackendRef(primarySvcName, pWeight, canary.Spec.Service.Port), + }, + { + BackendRef: gwr.makeBackendRef(canarySvcName, cWeight, canary.Spec.Service.Port), + }, + }, + }, + }, + } + hrClone.Spec = httpRouteSpec + + // A/B testing + if len(canary.GetAnalysis().Match) > 0 { + analysisMatches, _ := gwr.mapRouteMatches(canary.GetAnalysis().Match) + hrClone.Spec.Rules[0].Matches = analysisMatches + hrClone.Spec.Rules = append(hrClone.Spec.Rules, v1alpha2.HTTPRouteRule{ + Matches: matches, + BackendRefs: []v1alpha2.HTTPBackendRef{ + { + BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port), + }, + }, + }) + } + _, err = gwr.gatewayAPIClient.GatewayapiV1alpha2().HTTPRoutes(hrNamespace).Update(context.TODO(), hrClone, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("HTTPRoute %s.%s update error: %w", hrClone.GetName(), hrNamespace, err) @@ -328,3 +371,15 @@ func (gwr *GatewayAPIRouter) mapRouteMatches(requestMatches []v1alpha3.HTTPMatch return matches, nil } + +func (gwr *GatewayAPIRouter) makeBackendRef(svcName string, weight, port int32) v1alpha2.BackendRef { + return v1alpha2.BackendRef{ + BackendObjectReference: v1alpha2.BackendObjectReference{ + Group: (*v1alpha2.Group)(&backendRefGroup), + Kind: (*v1alpha2.Kind)(&backendRefKind), + Name: v1alpha2.ObjectName(svcName), + Port: (*v1alpha2.PortNumber)(&port), + }, + Weight: &weight, + } +}