Skip to content

Commit bc93298

Browse files
authored
Merge pull request #4241 from shuqz/shuqz-httproute
[feat gw-api]implement httproute matching and weighted target group
2 parents 3fb798e + 9bb29d4 commit bc93298

18 files changed

+978
-353
lines changed

pkg/gateway/model/model_build_listener.go

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
1515
certs "sigs.k8s.io/aws-load-balancer-controller/pkg/certs"
1616
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
17-
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress"
1817
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
1918
"sigs.k8s.io/aws-load-balancer-controller/pkg/model/core"
2019
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
@@ -164,63 +163,53 @@ func (l listenerBuilderImpl) buildL4ListenerSpec(ctx context.Context, stack core
164163
return listenerSpec, nil
165164
}
166165

166+
// this is only for L7 ALB
167167
func (l listenerBuilderImpl) buildListenerRules(stack core.Stack, ls *elbv2model.Listener, lb *elbv2model.LoadBalancer, securityGroups securityGroupOutput, gw *gwv1.Gateway, port int32, lbCfg elbv2gw.LoadBalancerConfiguration, routes map[int32][]routeutils.RouteDescriptor) error {
168-
169-
// add hostname handling (sort by precedence order)
170-
sortRoutesByHostnamePrecedence(routes[port])
171-
172-
// TODO for L7 Gateway Implementation
173-
// This is throw away code
174-
// This is temporary implementation for supporting basic multiple HTTPRoute for simple backend refs. We will create default forward action for all the backend refs for all HTTPRoutes for this listener
175-
var rules []ingress.Rule
176-
for _, descriptors := range routes {
177-
for _, descriptor := range descriptors {
178-
for _, rule := range descriptor.GetAttachedRules() {
179-
for _, backend := range rule.GetBackends() {
180-
targetGroup, tgErr := l.tgBuilder.buildTargetGroup(stack, gw, lbCfg, lb.Spec.IPAddressType, descriptor, backend, securityGroups.backendSecurityGroupToken)
181-
if tgErr != nil {
182-
return tgErr
183-
}
184-
// Basic condition
185-
conditions := []elbv2model.RuleCondition{{
186-
Field: elbv2model.RuleConditionFieldPathPattern,
187-
PathPatternConfig: &elbv2model.PathPatternConditionConfig{
188-
Values: []string{"/*"},
189-
},
190-
},
191-
}
192-
193-
// add host header condition
194-
if hostnames := descriptor.GetHostnames(); len(hostnames) > 0 {
195-
hostnamesStringList := make([]string, len(descriptor.GetHostnames()))
196-
for i, j := range descriptor.GetHostnames() {
197-
hostnamesStringList[i] = string(j)
198-
}
199-
conditions = append(conditions, elbv2model.RuleCondition{
200-
Field: elbv2model.RuleConditionFieldHostHeader,
201-
HostHeaderConfig: &elbv2model.HostHeaderConditionConfig{
202-
Values: hostnamesStringList,
203-
},
204-
})
205-
}
206-
207-
actions := buildL4ListenerDefaultActions(targetGroup)
208-
tags, tagsErr := l.tagHelper.getGatewayTags(lbCfg)
209-
if tagsErr != nil {
210-
return tagsErr
211-
}
212-
rules = append(rules, ingress.Rule{
213-
Conditions: conditions,
214-
Actions: actions,
215-
Tags: tags,
216-
})
217-
}
168+
// sort all rules based on precedence
169+
rulesWithPrecedenceOrder := routeutils.SortAllRulesByPrecedence(routes[port])
170+
171+
var albRules []elbv2model.Rule
172+
for _, ruleWithPrecedence := range rulesWithPrecedenceOrder {
173+
route := ruleWithPrecedence.RouteDescriptor
174+
rule := ruleWithPrecedence.Rule
175+
176+
var conditionsList []elbv2model.RuleCondition
177+
var err error
178+
switch route.GetRouteKind() {
179+
case routeutils.HTTPRouteKind:
180+
conditionsList, err = routeutils.BuildHttpRuleConditions(ruleWithPrecedence)
181+
if err != nil {
182+
return err
218183
}
184+
// TODO: add case for GRPC
219185
}
186+
tags, tagsErr := l.tagHelper.getGatewayTags(lbCfg)
187+
if tagsErr != nil {
188+
return tagsErr
189+
}
190+
targetGroupTuples := make([]elbv2model.TargetGroupTuple, 0, len(rule.GetBackends()))
191+
for _, backend := range rule.GetBackends() {
192+
targetGroup, tgErr := l.tgBuilder.buildTargetGroup(stack, gw, lbCfg, lb.Spec.IPAddressType, route, backend, securityGroups.backendSecurityGroupToken)
193+
if tgErr != nil {
194+
return tgErr
195+
}
196+
// weighted target group support
197+
weight := int32(backend.Weight)
198+
targetGroupTuples = append(targetGroupTuples, elbv2model.TargetGroupTuple{
199+
TargetGroupARN: targetGroup.TargetGroupARN(),
200+
Weight: &weight,
201+
})
202+
}
203+
actions := buildL7ListenerActions(targetGroupTuples)
204+
albRules = append(albRules, elbv2model.Rule{
205+
Conditions: conditionsList,
206+
Actions: actions,
207+
Tags: tags,
208+
})
220209
}
221210

222211
priority := int32(1)
223-
for _, rule := range rules {
212+
for _, rule := range albRules {
224213
ruleResID := fmt.Sprintf("%v:%v", port, priority)
225214
_ = elbv2model.NewListenerRule(stack, ruleResID, elbv2model.ListenerRuleSpec{
226215
ListenerARN: ls.ListenerARN(),
@@ -336,6 +325,17 @@ func buildL4ListenerDefaultActions(targetGroup *elbv2model.TargetGroup) []elbv2m
336325
}
337326
}
338327

328+
func buildL7ListenerActions(targetGroupTuple []elbv2model.TargetGroupTuple) []elbv2model.Action {
329+
return []elbv2model.Action{
330+
{
331+
Type: elbv2model.ActionTypeForward,
332+
ForwardConfig: &elbv2model.ForwardActionConfig{
333+
TargetGroups: targetGroupTuple,
334+
},
335+
},
336+
}
337+
}
338+
339339
func buildMutualAuthenticationAttributes(gwLsCfg *gwListenerConfig, lbLsCfg *elbv2gw.ListenerConfiguration) (*elbv2model.MutualAuthenticationAttributes, error) {
340340
// TODO for L7 gateway
341341
return nil, nil

pkg/gateway/model/utilities.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package model
22

33
import (
4-
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
54
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
6-
v1 "sigs.k8s.io/gateway-api/apis/v1"
7-
"sort"
85
"strings"
96
)
107

@@ -21,44 +18,3 @@ func isIPv6Supported(ipAddressType elbv2model.IPAddressType) bool {
2118
func isIPv6CIDR(cidr string) bool {
2219
return strings.Contains(cidr, ":")
2320
}
24-
25-
func getHighestPrecedenceHostname(hostnames []v1.Hostname) string {
26-
if len(hostnames) == 0 {
27-
return ""
28-
}
29-
30-
highestHostname := hostnames[0]
31-
for _, hostname := range hostnames {
32-
if routeutils.GetHostnamePrecedenceOrder(string(hostname), string(highestHostname)) < 0 {
33-
highestHostname = hostname
34-
}
35-
}
36-
return string(highestHostname)
37-
}
38-
39-
func sortRoutesByHostnamePrecedence(routes []routeutils.RouteDescriptor) {
40-
// sort routes based on their highest precedence hostname
41-
sort.SliceStable(routes, func(i, j int) bool {
42-
hostnameOne := routes[i].GetHostnames()
43-
hostnameTwo := routes[j].GetHostnames()
44-
45-
if len(hostnameOne) == 0 && len(hostnameTwo) == 0 {
46-
return false
47-
}
48-
if len(hostnameOne) == 0 {
49-
return false
50-
}
51-
if len(hostnameTwo) == 0 {
52-
return true
53-
}
54-
55-
highestPrecedenceHostnameOne := getHighestPrecedenceHostname(hostnameOne)
56-
highestPrecedenceHostnameTwo := getHighestPrecedenceHostname(hostnameTwo)
57-
58-
precedence := routeutils.GetHostnamePrecedenceOrder(highestPrecedenceHostnameOne, highestPrecedenceHostnameTwo)
59-
if precedence != 0 {
60-
return precedence < 0 // -1 means higher precedence
61-
}
62-
return false
63-
})
64-
}

pkg/gateway/model/utilities_test.go

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package model
22

33
import (
44
"github.com/stretchr/testify/assert"
5-
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
65
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
76
"testing"
87
)
@@ -48,116 +47,3 @@ func Test_IsIPv6Supported(t *testing.T) {
4847
})
4948
}
5049
}
51-
52-
// Test SortRoutesByHostnamePrecedence
53-
func Test_SortRoutesByHostnamePrecedence(t *testing.T) {
54-
tests := []struct {
55-
name string
56-
input []routeutils.RouteDescriptor
57-
expected []routeutils.RouteDescriptor
58-
}{
59-
{
60-
name: "empty routes",
61-
input: []routeutils.RouteDescriptor{},
62-
expected: []routeutils.RouteDescriptor{},
63-
},
64-
{
65-
name: "routes with no hostnames",
66-
input: []routeutils.RouteDescriptor{
67-
&routeutils.MockRoute{Hostnames: []string{}},
68-
&routeutils.MockRoute{Hostnames: []string{}},
69-
},
70-
expected: []routeutils.RouteDescriptor{
71-
&routeutils.MockRoute{Hostnames: []string{}},
72-
&routeutils.MockRoute{Hostnames: []string{}},
73-
},
74-
},
75-
{
76-
name: "mix of empty and non-empty hostnames",
77-
input: []routeutils.RouteDescriptor{
78-
&routeutils.MockRoute{Hostnames: []string{}},
79-
&routeutils.MockRoute{Hostnames: []string{"example.com"}},
80-
&routeutils.MockRoute{Hostnames: []string{"test.com"}},
81-
},
82-
expected: []routeutils.RouteDescriptor{
83-
&routeutils.MockRoute{Hostnames: []string{"example.com"}},
84-
&routeutils.MockRoute{Hostnames: []string{"test.com"}},
85-
&routeutils.MockRoute{Hostnames: []string{}},
86-
},
87-
},
88-
{
89-
name: "with and without wildcard hostnames",
90-
input: []routeutils.RouteDescriptor{
91-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
92-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
93-
},
94-
expected: []routeutils.RouteDescriptor{
95-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
96-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
97-
},
98-
},
99-
{
100-
name: "complex mixed hostnames",
101-
input: []routeutils.RouteDescriptor{
102-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
103-
&routeutils.MockRoute{Hostnames: []string{}},
104-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
105-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
106-
},
107-
expected: []routeutils.RouteDescriptor{
108-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
109-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
110-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
111-
&routeutils.MockRoute{Hostnames: []string{}},
112-
},
113-
},
114-
{
115-
name: "complex mixed hostnames with multiple hostnames in each port",
116-
input: []routeutils.RouteDescriptor{
117-
&routeutils.MockRoute{Hostnames: []string{"*.example.com", "test.details.example.com"}},
118-
&routeutils.MockRoute{Hostnames: []string{}},
119-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
120-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
121-
},
122-
expected: []routeutils.RouteDescriptor{
123-
&routeutils.MockRoute{Hostnames: []string{"*.example.com", "test.details.example.com"}}, // since test.details.example.com has the highest precedence order here
124-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
125-
&routeutils.MockRoute{Hostnames: []string{"test.example.com"}},
126-
&routeutils.MockRoute{Hostnames: []string{}},
127-
},
128-
},
129-
{
130-
name: "shorter hostname with more dots should have higher precedence ",
131-
input: []routeutils.RouteDescriptor{
132-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
133-
&routeutils.MockRoute{Hostnames: []string{"a.b.example.com"}},
134-
&routeutils.MockRoute{Hostnames: []string{}},
135-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
136-
},
137-
expected: []routeutils.RouteDescriptor{
138-
&routeutils.MockRoute{Hostnames: []string{"a.b.example.com"}},
139-
&routeutils.MockRoute{Hostnames: []string{"another.example.com"}},
140-
&routeutils.MockRoute{Hostnames: []string{"*.example.com"}},
141-
&routeutils.MockRoute{Hostnames: []string{}},
142-
},
143-
},
144-
}
145-
146-
for _, tt := range tests {
147-
t.Run(tt.name, func(t *testing.T) {
148-
// Create a copy of input to avoid modifying the test case data
149-
actual := make([]routeutils.RouteDescriptor, len(tt.input))
150-
copy(actual, tt.input)
151-
152-
// Execute the sort
153-
sortRoutesByHostnamePrecedence(actual)
154-
155-
// Verify the result
156-
assert.Equal(t, tt.expected, actual, "sorted routes should match expected order")
157-
158-
// Verify stability of sort
159-
sortRoutesByHostnamePrecedence(actual)
160-
assert.Equal(t, tt.expected, actual, "second sort should maintain the same order (stable sort)")
161-
})
162-
}
163-
}

pkg/gateway/routeutils/descriptor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"k8s.io/apimachinery/pkg/types"
66
"sigs.k8s.io/controller-runtime/pkg/client"
77
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
8+
"time"
89
)
910

1011
// routeMetadataDescriptor a common set of functions that will describe a route.
@@ -18,6 +19,7 @@ type routeMetadataDescriptor interface {
1819
GetRawRoute() interface{}
1920
GetBackendRefs() []gwv1.BackendRef
2021
GetRouteGeneration() int64
22+
GetRouteCreateTimestamp() time.Time
2123
}
2224

2325
// preLoadRouteDescriptor this object is used to represent a route description that has not loaded its child data (services, tg config)

pkg/gateway/routeutils/grpc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
77
"sigs.k8s.io/controller-runtime/pkg/client"
88
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
9+
"time"
910
)
1011

1112
/*
@@ -115,6 +116,10 @@ func (grpcRoute *grpcRouteDescription) GetRouteGeneration() int64 {
115116
return grpcRoute.route.Generation
116117
}
117118

119+
func (grpcRoute *grpcRouteDescription) GetRouteCreateTimestamp() time.Time {
120+
return grpcRoute.route.CreationTimestamp.Time
121+
}
122+
118123
var _ RouteDescriptor = &grpcRouteDescription{}
119124

120125
// Can we use an indexer here to query more efficiently?

pkg/gateway/routeutils/http.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
77
"sigs.k8s.io/controller-runtime/pkg/client"
88
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
9+
"time"
910
)
1011

1112
/*
@@ -107,6 +108,10 @@ func (httpRoute *httpRouteDescription) GetBackendRefs() []gwv1.BackendRef {
107108
return backendRefs
108109
}
109110

111+
func (httpRoute *httpRouteDescription) GetRouteCreateTimestamp() time.Time {
112+
return httpRoute.route.CreationTimestamp.Time
113+
}
114+
110115
func convertHTTPRoute(r gwv1.HTTPRoute) *httpRouteDescription {
111116
return &httpRouteDescription{route: &r, backendLoader: commonBackendLoader}
112117
}

pkg/gateway/routeutils/loader_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sigs.k8s.io/controller-runtime/pkg/client"
1010
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
1111
"testing"
12+
"time"
1213
)
1314

1415
type mockMapper struct {
@@ -71,6 +72,10 @@ func (m *mockRoute) GetAttachedRules() []RouteRule {
7172
panic("implement me")
7273
}
7374

75+
func (m *mockRoute) GetRouteCreateTimestamp() time.Time {
76+
panic("implement me")
77+
}
78+
7479
func TestLoadRoutesForGateway(t *testing.T) {
7580
preLoadHTTPRoutes := []preLoadRouteDescriptor{
7681
&mockRoute{

0 commit comments

Comments
 (0)