From bf87c49320f1ecee369de2ab18c72884f5aa973a Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 17 Mar 2026 15:24:17 +0000 Subject: [PATCH 1/3] Allow customising the HTTPRoute name --- pkg/apis/flagger/v1beta1/canary.go | 15 ++++++++++++ pkg/router/gateway_api.go | 27 ++++++++++++--------- pkg/router/gateway_api_test.go | 39 ++++++++++++++++++++++++++++++ pkg/router/gateway_api_v1beta1.go | 25 ++++++++++--------- 4 files changed, 83 insertions(+), 23 deletions(-) diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go index 950a6fe01..3b5d69cc6 100644 --- a/pkg/apis/flagger/v1beta1/canary.go +++ b/pkg/apis/flagger/v1beta1/canary.go @@ -225,6 +225,11 @@ type CanaryService struct { // +optional Backends []string `json:"backends,omitempty"` + // HTTPRouteName is the name of the HTTPRoute generated by Flagger + // for the Gateway API provider. Defaults to the apex service name. + // +optional + HTTPRouteName string `json:"httpRouteName,omitempty"` + // Apex is metadata to add to the apex service // +optional Apex *CustomMetadata `json:"apex,omitempty"` @@ -610,6 +615,16 @@ func (c *Canary) GetServiceNames() (apexName, primaryName, canaryName string) { return } +// GetHTTPRouteName returns the name to use for the HTTPRoute resource. +// Defaults to the apex service name if not explicitly set. +func (c *Canary) GetHTTPRouteName() string { + if c.Spec.Service.HTTPRouteName != "" { + return c.Spec.Service.HTTPRouteName + } + apexName, _, _ := c.GetServiceNames() + return apexName +} + // GetProgressDeadlineSeconds returns the progress deadline (default 600s) func (c *Canary) GetProgressDeadlineSeconds() int { if c.Spec.ProgressDeadlineSeconds != nil { diff --git a/pkg/router/gateway_api.go b/pkg/router/gateway_api.go index bbcadc28e..ce7ddba03 100644 --- a/pkg/router/gateway_api.go +++ b/pkg/router/gateway_api.go @@ -67,7 +67,8 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { return fmt.Errorf("GatewayRefs must be specified when using Gateway API as a provider.") } - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace @@ -138,7 +139,7 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { } httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1().HTTPRoutes(hrNamespace).Get( - context.TODO(), apexSvcName, metav1.GetOptions{}, + context.TODO(), routeName, metav1.GetOptions{}, ) newMetadata := canary.Spec.Service.Apex @@ -156,7 +157,7 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { if errors.IsNotFound(err) { route := &v1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ - Name: apexSvcName, + Name: routeName, Namespace: hrNamespace, Labels: newMetadata.Labels, Annotations: newMetadata.Annotations, @@ -178,13 +179,13 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { Create(context.TODO(), route, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("HTTPRoute %s.%s create error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s create error: %w", routeName, hrNamespace, err) } gwr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)). Infof("HTTPRoute %s.%s created", route.GetName(), hrNamespace) return nil } else if err != nil { - return fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) } ignoreCmpOptions := []cmp.Option{ @@ -266,11 +267,12 @@ func (gwr *GatewayAPIRouter) GetRoutes(canary *flaggerv1.Canary) ( mirrored bool, err error, ) { - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace - httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1().HTTPRoutes(hrNamespace).Get(context.TODO(), apexSvcName, metav1.GetOptions{}) + httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1().HTTPRoutes(hrNamespace).Get(context.TODO(), routeName, metav1.GetOptions{}) if err != nil { - err = fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + err = fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) return } @@ -285,7 +287,7 @@ func (gwr *GatewayAPIRouter) GetRoutes(canary *flaggerv1.Canary) ( if condition.Type == string(v1.RouteConditionAccepted) && (condition.Status != metav1.ConditionTrue || condition.ObservedGeneration < currentGeneration) { err = fmt.Errorf( "HTTPRoute %s.%s parent %s is not ready (status: %s, observed generation: %d, current generation: %d)", - apexSvcName, hrNamespace, parentRef.Name, string(condition.Status), condition.ObservedGeneration, currentGeneration, + routeName, hrNamespace, parentRef.Name, string(condition.Status), condition.ObservedGeneration, currentGeneration, ) return 0, 0, false, err } @@ -346,11 +348,12 @@ func (gwr *GatewayAPIRouter) SetRoutes( ) error { pWeight := int32(primaryWeight) cWeight := int32(canaryWeight) - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace - httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1().HTTPRoutes(hrNamespace).Get(context.TODO(), apexSvcName, metav1.GetOptions{}) + httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1().HTTPRoutes(hrNamespace).Get(context.TODO(), routeName, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) } hrClone := httpRoute.DeepCopy() hostNames := []v1.Hostname{} diff --git a/pkg/router/gateway_api_test.go b/pkg/router/gateway_api_test.go index 94d871c15..4173372a1 100644 --- a/pkg/router/gateway_api_test.go +++ b/pkg/router/gateway_api_test.go @@ -74,6 +74,45 @@ func TestGatewayAPIRouter_Reconcile(t *testing.T) { assert.Equal(t, httpRoute.Annotations["foo"], "bar") } +func TestGatewayAPIRouter_ReconcileCustomHTTPRouteName(t *testing.T) { + canary := newTestGatewayAPICanary() + canary.Spec.Service.HTTPRouteName = "my-custom-route" + mocks := newFixture(canary) + router := &GatewayAPIRouter{ + gatewayAPIClient: mocks.meshClient, + kubeClient: mocks.kubeClient, + logger: mocks.logger, + } + + err := router.Reconcile(canary) + require.NoError(t, err) + + // HTTPRoute should be created with the custom name + httpRoute, err := router.gatewayAPIClient.GatewayapiV1().HTTPRoutes("default").Get(context.TODO(), "my-custom-route", metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, "my-custom-route", httpRoute.Name) + + // BackendRefs should still point at the primary and canary services + backendRefs := httpRoute.Spec.Rules[0].BackendRefs + require.Equal(t, 2, len(backendRefs)) + assert.Equal(t, v1.ObjectName("podinfo-primary"), backendRefs[0].Name) + assert.Equal(t, v1.ObjectName("podinfo-canary"), backendRefs[1].Name) + + // GetRoutes and SetRoutes should work with the custom name + primaryWeight, canaryWeight, _, err := router.GetRoutes(canary) + require.NoError(t, err) + assert.Equal(t, 100, primaryWeight) + assert.Equal(t, 0, canaryWeight) + + err = router.SetRoutes(canary, 50, 50, false) + require.NoError(t, err) + + primaryWeight, canaryWeight, _, err = router.GetRoutes(canary) + require.NoError(t, err) + assert.Equal(t, 50, primaryWeight) + assert.Equal(t, 50, canaryWeight) +} + func TestGatewayAPIRouter_Routes(t *testing.T) { canary := newTestGatewayAPICanary() mocks := newFixture(canary) diff --git a/pkg/router/gateway_api_v1beta1.go b/pkg/router/gateway_api_v1beta1.go index e7bd1cf0b..fe086c151 100644 --- a/pkg/router/gateway_api_v1beta1.go +++ b/pkg/router/gateway_api_v1beta1.go @@ -60,7 +60,8 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error { return fmt.Errorf("GatewayRefs must be specified when using Gateway API as a provider.") } - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace @@ -119,7 +120,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error { } httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1beta1().HTTPRoutes(hrNamespace).Get( - context.TODO(), apexSvcName, metav1.GetOptions{}, + context.TODO(), routeName, metav1.GetOptions{}, ) newMetadata := canary.Spec.Service.Apex @@ -137,7 +138,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error { if errors.IsNotFound(err) { route := &v1beta1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ - Name: apexSvcName, + Name: routeName, Namespace: hrNamespace, Labels: newMetadata.Labels, Annotations: newMetadata.Annotations, @@ -159,13 +160,13 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error { Create(context.TODO(), route, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("HTTPRoute %s.%s create error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s create error: %w", routeName, hrNamespace, err) } gwr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)). Infof("HTTPRoute %s.%s created", route.GetName(), hrNamespace) return nil } else if err != nil { - return fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) } ignoreCmpOptions := []cmp.Option{ @@ -241,11 +242,12 @@ func (gwr *GatewayAPIV1Beta1Router) GetRoutes(canary *flaggerv1.Canary) ( mirrored bool, err error, ) { - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace - httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1beta1().HTTPRoutes(hrNamespace).Get(context.TODO(), apexSvcName, metav1.GetOptions{}) + httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1beta1().HTTPRoutes(hrNamespace).Get(context.TODO(), routeName, metav1.GetOptions{}) if err != nil { - err = fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + err = fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) return } var weightedRule *v1beta1.HTTPRouteRule @@ -301,11 +303,12 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes( ) error { pWeight := int32(primaryWeight) cWeight := int32(canaryWeight) - apexSvcName, primarySvcName, canarySvcName := canary.GetServiceNames() + _, primarySvcName, canarySvcName := canary.GetServiceNames() + routeName := canary.GetHTTPRouteName() hrNamespace := canary.Namespace - httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1beta1().HTTPRoutes(hrNamespace).Get(context.TODO(), apexSvcName, metav1.GetOptions{}) + httpRoute, err := gwr.gatewayAPIClient.GatewayapiV1beta1().HTTPRoutes(hrNamespace).Get(context.TODO(), routeName, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, hrNamespace, err) + return fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) } hrClone := httpRoute.DeepCopy() hostNames := []v1beta1.Hostname{} From e4e3c1e7ea51fd109dd8593e1ac1f4555c96332b Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Wed, 18 Mar 2026 11:53:51 +0000 Subject: [PATCH 2/3] Revert "fix: gateway router should wait for accepted condition" This reverts commit bb7ad6546290db38fd9b569e3daf8aee116cb579. --- go.mod | 2 +- pkg/router/gateway_api.go | 20 ------- pkg/router/gateway_api_test.go | 104 --------------------------------- 3 files changed, 1 insertion(+), 125 deletions(-) diff --git a/go.mod b/go.mod index 687db7250..e6fb164aa 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( k8s.io/client-go v0.34.1 k8s.io/code-generator v0.34.1 k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 knative.dev/serving v0.46.6 ) @@ -100,6 +99,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect knative.dev/networking v0.0.0-20250902160145-7dad473f6351 // indirect knative.dev/pkg v0.0.0-20250909011231-077dcf0d00e8 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/pkg/router/gateway_api.go b/pkg/router/gateway_api.go index ce7ddba03..900de9649 100644 --- a/pkg/router/gateway_api.go +++ b/pkg/router/gateway_api.go @@ -275,26 +275,6 @@ func (gwr *GatewayAPIRouter) GetRoutes(canary *flaggerv1.Canary) ( err = fmt.Errorf("HTTPRoute %s.%s get error: %w", routeName, hrNamespace, err) return } - - currentGeneration := httpRoute.GetGeneration() - for _, parentRef := range httpRoute.Spec.CommonRouteSpec.ParentRefs { - for _, parentStatus := range httpRoute.Status.Parents { - if !reflect.DeepEqual(parentStatus.ParentRef, parentRef) { - continue - } - - for _, condition := range parentStatus.Conditions { - if condition.Type == string(v1.RouteConditionAccepted) && (condition.Status != metav1.ConditionTrue || condition.ObservedGeneration < currentGeneration) { - err = fmt.Errorf( - "HTTPRoute %s.%s parent %s is not ready (status: %s, observed generation: %d, current generation: %d)", - routeName, hrNamespace, parentRef.Name, string(condition.Status), condition.ObservedGeneration, currentGeneration, - ) - return 0, 0, false, err - } - } - } - } - var weightedRule *v1.HTTPRouteRule for _, rule := range httpRoute.Spec.Rules { // If session affinity is enabled, then we are only interested in the rule diff --git a/pkg/router/gateway_api_test.go b/pkg/router/gateway_api_test.go index 4173372a1..198787a66 100644 --- a/pkg/router/gateway_api_test.go +++ b/pkg/router/gateway_api_test.go @@ -28,7 +28,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1" v1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1" @@ -643,106 +642,3 @@ func TestGatewayAPIRouter_makeFilters_CORS(t *testing.T) { // Assert MaxAge (24h = 86400 seconds) assert.Equal(t, int32(86400), corsFilter.CORS.MaxAge) } - -func TestGatewayAPIRouter_GetRoutes(t *testing.T) { - canary := newTestGatewayAPICanary() - mocks := newFixture(canary) - router := &GatewayAPIRouter{ - gatewayAPIClient: mocks.meshClient, - kubeClient: mocks.kubeClient, - logger: mocks.logger, - } - - httpRoute := &v1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "podinfo", - Generation: 1, - }, - Spec: v1.HTTPRouteSpec{ - Rules: []v1.HTTPRouteRule{ - { - BackendRefs: []v1.HTTPBackendRef{ - { - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Name: "podinfo-canary", - }, - Weight: ptr.To(int32(10)), - }, - }, - { - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Name: "podinfo-primary", - }, - Weight: ptr.To(int32(90)), - }, - }, - }, - }, - }, - CommonRouteSpec: v1.CommonRouteSpec{ - ParentRefs: []v1.ParentReference{ - { - Name: "podinfo", - }, - }, - }, - }, - } - httpRoute, err := router.gatewayAPIClient.GatewayapiV1().HTTPRoutes("default").Create(context.TODO(), httpRoute, metav1.CreateOptions{}) - require.NoError(t, err) - - t.Run("httproute generation", func(t *testing.T) { - httpRoute.ObjectMeta.Generation = 5 - httpRoute.Status.Parents = []v1.RouteParentStatus{ - { - ParentRef: v1.ParentReference{ - Name: "podinfo", - SectionName: ptr.To(v1.SectionName("https")), - }, - Conditions: []metav1.Condition{ - { - Type: string(v1.RouteConditionAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - }, - }, - { - ParentRef: v1.ParentReference{ - Name: "podinfo", - }, - Conditions: []metav1.Condition{ - { - Type: string(v1.RouteConditionAccepted), - Status: metav1.ConditionFalse, - ObservedGeneration: 4, - }, - }, - }, - } - httpRoute, err := router.gatewayAPIClient.GatewayapiV1().HTTPRoutes("default").Update(context.TODO(), httpRoute, metav1.UpdateOptions{}) - require.NoError(t, err) - - _, _, _, err = router.GetRoutes(canary) - require.Error(t, err) - - httpRoute.Status.Parents[1].Conditions[0].ObservedGeneration = 5 - _, err = router.gatewayAPIClient.GatewayapiV1().HTTPRoutes("default").Update(context.TODO(), httpRoute, metav1.UpdateOptions{}) - require.NoError(t, err) - - _, _, _, err = router.GetRoutes(canary) - require.Error(t, err) - - httpRoute.Status.Parents[1].Conditions[0].Status = metav1.ConditionTrue - _, err = router.gatewayAPIClient.GatewayapiV1().HTTPRoutes("default").Update(context.TODO(), httpRoute, metav1.UpdateOptions{}) - require.NoError(t, err) - - primaryWeight, canaryWeight, mirrored, err := router.GetRoutes(canary) - require.NoError(t, err) - assert.Equal(t, 90, primaryWeight) - assert.Equal(t, 10, canaryWeight) - assert.False(t, mirrored) - }) -} From 69eaa17d9253ebe4f45b203ba75d5557fdd6b0e0 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Wed, 18 Mar 2026 12:03:47 +0000 Subject: [PATCH 3/3] Add httpRouteName to the CRD --- artifacts/flagger/crd.yaml | 3 +++ charts/flagger/crds/crd.yaml | 3 +++ kustomize/base/flagger/crd.yaml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index e138b66d4..6f20ae5ad 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -880,6 +880,9 @@ spec: format: string type: string type: array + httpRouteName: + description: The name of the HTTPRoute generated by Flagger for the Gateway API provider. Defaults to the apex service name. + type: string apex: description: Metadata to add to the apex service type: object diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index e138b66d4..6f20ae5ad 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -880,6 +880,9 @@ spec: format: string type: string type: array + httpRouteName: + description: The name of the HTTPRoute generated by Flagger for the Gateway API provider. Defaults to the apex service name. + type: string apex: description: Metadata to add to the apex service type: object diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index e138b66d4..6f20ae5ad 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -880,6 +880,9 @@ spec: format: string type: string type: array + httpRouteName: + description: The name of the HTTPRoute generated by Flagger for the Gateway API provider. Defaults to the apex service name. + type: string apex: description: Metadata to add to the apex service type: object