diff --git a/go.mod b/go.mod index 016dc4f81..56bf27a79 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ 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 ) @@ -99,7 +100,6 @@ 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 f6cdd7f0c..810bf291e 100644 --- a/pkg/router/gateway_api.go +++ b/pkg/router/gateway_api.go @@ -273,6 +273,26 @@ func (gwr *GatewayAPIRouter) GetRoutes(canary *flaggerv1.Canary) ( err = fmt.Errorf("HTTPRoute %s.%s get error: %w", apexSvcName, 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)", + apexSvcName, 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 46541af44..94d871c15 100644 --- a/pkg/router/gateway_api_test.go +++ b/pkg/router/gateway_api_test.go @@ -28,6 +28,7 @@ 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" @@ -603,3 +604,106 @@ 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) + }) +}