Skip to content

Commit 7cd1476

Browse files
authored
Merge pull request #1713 from mingjie-li/main
Gateway API: Sort header filters to avoid canary restarts
2 parents a159421 + b88e080 commit 7cd1476

4 files changed

+114
-0
lines changed

pkg/router/gateway_api.go

+18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"reflect"
23+
"slices"
2324
"strings"
2425

2526
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
@@ -647,10 +648,23 @@ func (gwr *GatewayAPIRouter) mergeMatchConditions(analysis, service []v1.HTTPRou
647648
return merged
648649
}
649650

651+
func sortFiltersV1(headers []v1.HTTPHeader) {
652+
653+
if headers != nil {
654+
slices.SortFunc(headers, func(a, b v1.HTTPHeader) int {
655+
if a.Name == b.Name {
656+
return strings.Compare(a.Value, b.Value)
657+
}
658+
return strings.Compare(string(a.Name), string(b.Name))
659+
})
660+
}
661+
}
662+
650663
func (gwr *GatewayAPIRouter) makeFilters(canary *flaggerv1.Canary) []v1.HTTPRouteFilter {
651664
var filters []v1.HTTPRouteFilter
652665

653666
if canary.Spec.Service.Headers != nil {
667+
654668
if canary.Spec.Service.Headers.Request != nil {
655669
requestHeaderFilter := v1.HTTPRouteFilter{
656670
Type: v1.HTTPRouteFilterRequestHeaderModifier,
@@ -663,13 +677,15 @@ func (gwr *GatewayAPIRouter) makeFilters(canary *flaggerv1.Canary) []v1.HTTPRout
663677
Value: val,
664678
})
665679
}
680+
sortFiltersV1(requestHeaderFilter.RequestHeaderModifier.Add)
666681
for name, val := range canary.Spec.Service.Headers.Request.Set {
667682
requestHeaderFilter.RequestHeaderModifier.Set = append(requestHeaderFilter.RequestHeaderModifier.Set, v1.HTTPHeader{
668683
Name: v1.HTTPHeaderName(name),
669684
Value: val,
670685
})
671686
}
672687

688+
sortFiltersV1(requestHeaderFilter.RequestHeaderModifier.Set)
673689
for _, name := range canary.Spec.Service.Headers.Request.Remove {
674690
requestHeaderFilter.RequestHeaderModifier.Remove = append(requestHeaderFilter.RequestHeaderModifier.Remove, name)
675691
}
@@ -688,12 +704,14 @@ func (gwr *GatewayAPIRouter) makeFilters(canary *flaggerv1.Canary) []v1.HTTPRout
688704
Value: val,
689705
})
690706
}
707+
sortFiltersV1(responseHeaderFilter.ResponseHeaderModifier.Add)
691708
for name, val := range canary.Spec.Service.Headers.Response.Set {
692709
responseHeaderFilter.ResponseHeaderModifier.Set = append(responseHeaderFilter.ResponseHeaderModifier.Set, v1.HTTPHeader{
693710
Name: v1.HTTPHeaderName(name),
694711
Value: val,
695712
})
696713
}
714+
sortFiltersV1(responseHeaderFilter.ResponseHeaderModifier.Set)
697715

698716
for _, name := range canary.Spec.Service.Headers.Response.Remove {
699717
responseHeaderFilter.ResponseHeaderModifier.Remove = append(responseHeaderFilter.ResponseHeaderModifier.Remove, name)

pkg/router/gateway_api_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import (
2424

2525
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
2626
v1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1"
27+
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
2728
"github.com/google/go-cmp/cmp"
29+
"github.com/google/go-cmp/cmp/cmpopts"
2830
"github.com/stretchr/testify/assert"
2931
"github.com/stretchr/testify/require"
3032
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -352,3 +354,40 @@ func TestGatewayAPIRouter_getSessionAffinityRouteRules(t *testing.T) {
352354
assert.Equal(t, string(headerModifier.Add[0].Name), setCookieHeader)
353355
assert.Equal(t, headerModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.PreviousSessionAffinityCookie, maxAgeAttr, -1))
354356
}
357+
358+
func TestGatewayAPIRouter_makeFilters(t *testing.T) {
359+
canary := newTestGatewayAPICanary()
360+
mocks := newFixture(canary)
361+
canary.Spec.Service.Headers = &istiov1beta1.Headers{
362+
Response: &istiov1beta1.HeaderOperations{
363+
Set: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
364+
Add: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
365+
},
366+
Request: &istiov1beta1.HeaderOperations{
367+
Set: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
368+
Add: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
369+
},
370+
}
371+
372+
router := &GatewayAPIRouter{
373+
gatewayAPIClient: mocks.meshClient,
374+
kubeClient: mocks.kubeClient,
375+
logger: mocks.logger,
376+
}
377+
378+
ignoreCmpOptions := []cmp.Option{
379+
cmpopts.IgnoreFields(v1.BackendRef{}, "Weight"),
380+
cmpopts.EquateEmpty(),
381+
}
382+
383+
filters := router.makeFilters(canary)
384+
385+
for i := 0; i < 10; i++ {
386+
newFilters := router.makeFilters(canary)
387+
filtersDiff := cmp.Diff(
388+
filters, newFilters,
389+
ignoreCmpOptions...,
390+
)
391+
assert.Equal(t, "", filtersDiff)
392+
}
393+
}

pkg/router/gateway_api_v1beta1.go

+17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"reflect"
23+
"slices"
2324
"strings"
2425

2526
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
@@ -608,6 +609,18 @@ func (gwr *GatewayAPIV1Beta1Router) mergeMatchConditions(analysis, service []v1b
608609
return merged
609610
}
610611

612+
func sortFiltersV1beta1(headers []v1beta1.HTTPHeader) {
613+
614+
if headers != nil {
615+
slices.SortFunc(headers, func(a, b v1beta1.HTTPHeader) int {
616+
if a.Name == b.Name {
617+
return strings.Compare(a.Value, b.Value)
618+
}
619+
return strings.Compare(string(a.Name), string(b.Name))
620+
})
621+
}
622+
}
623+
611624
func (gwr *GatewayAPIV1Beta1Router) makeFilters(canary *flaggerv1.Canary) []v1beta1.HTTPRouteFilter {
612625
var filters []v1beta1.HTTPRouteFilter
613626

@@ -624,12 +637,14 @@ func (gwr *GatewayAPIV1Beta1Router) makeFilters(canary *flaggerv1.Canary) []v1be
624637
Value: val,
625638
})
626639
}
640+
sortFiltersV1beta1(requestHeaderFilter.RequestHeaderModifier.Add)
627641
for name, val := range canary.Spec.Service.Headers.Request.Set {
628642
requestHeaderFilter.RequestHeaderModifier.Set = append(requestHeaderFilter.RequestHeaderModifier.Set, v1beta1.HTTPHeader{
629643
Name: v1beta1.HTTPHeaderName(name),
630644
Value: val,
631645
})
632646
}
647+
sortFiltersV1beta1(requestHeaderFilter.RequestHeaderModifier.Set)
633648

634649
for _, name := range canary.Spec.Service.Headers.Request.Remove {
635650
requestHeaderFilter.RequestHeaderModifier.Remove = append(requestHeaderFilter.RequestHeaderModifier.Remove, name)
@@ -649,12 +664,14 @@ func (gwr *GatewayAPIV1Beta1Router) makeFilters(canary *flaggerv1.Canary) []v1be
649664
Value: val,
650665
})
651666
}
667+
sortFiltersV1beta1(responseHeaderFilter.ResponseHeaderModifier.Add)
652668
for name, val := range canary.Spec.Service.Headers.Response.Set {
653669
responseHeaderFilter.ResponseHeaderModifier.Set = append(responseHeaderFilter.ResponseHeaderModifier.Set, v1beta1.HTTPHeader{
654670
Name: v1beta1.HTTPHeaderName(name),
655671
Value: val,
656672
})
657673
}
674+
sortFiltersV1beta1(responseHeaderFilter.ResponseHeaderModifier.Set)
658675

659676
for _, name := range canary.Spec.Service.Headers.Response.Remove {
660677
responseHeaderFilter.ResponseHeaderModifier.Remove = append(responseHeaderFilter.ResponseHeaderModifier.Remove, name)

pkg/router/gateway_api_v1beta1_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import (
2323
"testing"
2424

2525
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
26+
v1 "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1"
2627
"github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1"
28+
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
2729
"github.com/google/go-cmp/cmp"
30+
"github.com/google/go-cmp/cmp/cmpopts"
2831
"github.com/stretchr/testify/assert"
2932
"github.com/stretchr/testify/require"
3033
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -349,3 +352,40 @@ func TestGatewayAPIV1Beta1Router_getSessionAffinityRouteRules(t *testing.T) {
349352
assert.Equal(t, string(headerModifier.Add[0].Name), setCookieHeader)
350353
assert.Equal(t, headerModifier.Add[0].Value, fmt.Sprintf("%s; %s=%d", canary.Status.PreviousSessionAffinityCookie, maxAgeAttr, -1))
351354
}
355+
356+
func TestGatewayAPIV1Beta1Router_makeFilters(t *testing.T) {
357+
canary := newTestGatewayAPICanary()
358+
mocks := newFixture(canary)
359+
canary.Spec.Service.Headers = &istiov1beta1.Headers{
360+
Response: &istiov1beta1.HeaderOperations{
361+
Set: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
362+
Add: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
363+
},
364+
Request: &istiov1beta1.HeaderOperations{
365+
Set: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
366+
Add: map[string]string{"h1": "v1", "h2": "v2", "h3": "v3"},
367+
},
368+
}
369+
370+
router := &GatewayAPIV1Beta1Router{
371+
gatewayAPIClient: mocks.meshClient,
372+
kubeClient: mocks.kubeClient,
373+
logger: mocks.logger,
374+
}
375+
376+
ignoreCmpOptions := []cmp.Option{
377+
cmpopts.IgnoreFields(v1.BackendRef{}, "Weight"),
378+
cmpopts.EquateEmpty(),
379+
}
380+
381+
filters := router.makeFilters(canary)
382+
383+
for i := 0; i < 10; i++ {
384+
newFilters := router.makeFilters(canary)
385+
filtersDiff := cmp.Diff(
386+
filters, newFilters,
387+
ignoreCmpOptions...,
388+
)
389+
assert.Equal(t, "", filtersDiff)
390+
}
391+
}

0 commit comments

Comments
 (0)