fix: gateway router should wait for accepted condition

It can take some time for changes to propagate for cloud load balancers,
so flagger should ensure the route changes are current before proceeding
with any more.

Signed-off-by: Steven Davidovitz <sdavidovitz@groq.com>
This commit is contained in:
Steven Davidovitz
2025-04-07 16:19:53 -07:00
parent ae72e15049
commit bb7ad65462
3 changed files with 125 additions and 1 deletions

2
go.mod
View File

@@ -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

View File

@@ -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

View File

@@ -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)
})
}