Skip to content

Commit a6cdec4

Browse files
committed
handle tg config updates for gateway based backends
1 parent 735ee54 commit a6cdec4

File tree

3 files changed

+315
-26
lines changed

3 files changed

+315
-26
lines changed

controllers/gateway/eventhandlers/target_group_configuration_events.go

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/go-logr/logr"
66
corev1 "k8s.io/api/core/v1"
77
"k8s.io/apimachinery/pkg/types"
8+
"k8s.io/apimachinery/pkg/util/sets"
89
"k8s.io/client-go/tools/record"
910
"k8s.io/client-go/util/workqueue"
1011
elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
@@ -13,66 +14,122 @@ import (
1314
"sigs.k8s.io/controller-runtime/pkg/event"
1415
"sigs.k8s.io/controller-runtime/pkg/handler"
1516
"sigs.k8s.io/controller-runtime/pkg/reconcile"
17+
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
1618
)
1719

1820
// NewEnqueueRequestsForTargetGroupConfigurationEvent creates handler for TargetGroupConfiguration resources
19-
func NewEnqueueRequestsForTargetGroupConfigurationEvent(svcEventChan chan<- event.TypedGenericEvent[*corev1.Service],
21+
func NewEnqueueRequestsForTargetGroupConfigurationEvent(svcEventChan chan<- event.TypedGenericEvent[*corev1.Service], tcpRouteEventChan chan<- event.TypedGenericEvent[*gwalpha2.TCPRoute],
2022
k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*elbv2gw.TargetGroupConfiguration, reconcile.Request] {
2123
return &enqueueRequestsForTargetGroupConfigurationEvent{
22-
svcEventChan: svcEventChan,
23-
k8sClient: k8sClient,
24-
eventRecorder: eventRecorder,
25-
logger: logger,
24+
svcEventChan: svcEventChan,
25+
tcpRouteEventChan: tcpRouteEventChan,
26+
k8sClient: k8sClient,
27+
eventRecorder: eventRecorder,
28+
logger: logger,
2629
}
2730
}
2831

2932
var _ handler.TypedEventHandler[*elbv2gw.TargetGroupConfiguration, reconcile.Request] = (*enqueueRequestsForTargetGroupConfigurationEvent)(nil)
3033

3134
// enqueueRequestsForTargetGroupConfigurationEvent handles TargetGroupConfiguration events
3235
type enqueueRequestsForTargetGroupConfigurationEvent struct {
33-
svcEventChan chan<- event.TypedGenericEvent[*corev1.Service]
34-
k8sClient client.Client
35-
eventRecorder record.EventRecorder
36-
logger logr.Logger
36+
svcEventChan chan<- event.TypedGenericEvent[*corev1.Service]
37+
tcpRouteEventChan chan<- event.TypedGenericEvent[*gwalpha2.TCPRoute]
38+
k8sClient client.Client
39+
eventRecorder record.EventRecorder
40+
logger logr.Logger
3741
}
3842

3943
func (h *enqueueRequestsForTargetGroupConfigurationEvent) Create(ctx context.Context, e event.TypedCreateEvent[*elbv2gw.TargetGroupConfiguration], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
4044
tgconfigNew := e.Object
4145
h.logger.V(1).Info("enqueue targetgroupconfiguration create event", "targetgroupconfiguration", tgconfigNew.Name)
42-
h.enqueueImpactedService(ctx, tgconfigNew, queue)
46+
h.enqueueImpactedObject(ctx, tgconfigNew, queue)
4347
}
4448

4549
func (h *enqueueRequestsForTargetGroupConfigurationEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*elbv2gw.TargetGroupConfiguration], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
4650
tgconfigNew := e.ObjectNew
4751
h.logger.V(1).Info("enqueue targetgroupconfiguration update event", "targetgroupconfiguration", tgconfigNew.Name)
48-
h.enqueueImpactedService(ctx, tgconfigNew, queue)
52+
h.enqueueImpactedObject(ctx, tgconfigNew, queue)
4953
}
5054

5155
func (h *enqueueRequestsForTargetGroupConfigurationEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*elbv2gw.TargetGroupConfiguration], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
5256
tgconfig := e.Object
5357
h.logger.V(1).Info("enqueue targetgroupconfiguration delete event", "targetgroupconfiguration", tgconfig.Name)
54-
h.enqueueImpactedService(ctx, tgconfig, queue)
58+
h.enqueueImpactedObject(ctx, tgconfig, queue)
5559
}
5660

5761
func (h *enqueueRequestsForTargetGroupConfigurationEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*elbv2gw.TargetGroupConfiguration], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
5862
tgconfig := e.Object
5963
h.logger.V(1).Info("enqueue targetgroupconfiguration generic event", "targetgroupconfiguration", tgconfig.Name)
60-
h.enqueueImpactedService(ctx, tgconfig, queue)
64+
h.enqueueImpactedObject(ctx, tgconfig, queue)
6165
}
6266

63-
func (h *enqueueRequestsForTargetGroupConfigurationEvent) enqueueImpactedService(ctx context.Context, tgconfig *elbv2gw.TargetGroupConfiguration, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
64-
svcName := types.NamespacedName{Namespace: tgconfig.Namespace, Name: tgconfig.Spec.TargetReference.Name}
65-
svc := &corev1.Service{}
66-
if err := h.k8sClient.Get(ctx, svcName, svc); err != nil {
67-
h.logger.V(1).Info("ignoring targetgroupconfiguration event for unknown service",
67+
func (h *enqueueRequestsForTargetGroupConfigurationEvent) enqueueImpactedObject(ctx context.Context, tgconfig *elbv2gw.TargetGroupConfiguration, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) {
68+
objName := types.NamespacedName{Namespace: tgconfig.Namespace, Name: tgconfig.Spec.TargetReference.Name}
69+
70+
if tgconfig.Spec.TargetReference.Kind == nil || *tgconfig.Spec.TargetReference.Kind == "Service" {
71+
svc := &corev1.Service{}
72+
if err := h.k8sClient.Get(ctx, objName, svc); err != nil {
73+
h.logger.V(1).Info("ignoring targetgroupconfiguration event for unknown service",
74+
"targetgroupconfiguration", k8s.NamespacedName(tgconfig),
75+
"service", k8s.NamespacedName(svc))
76+
return
77+
}
78+
h.logger.V(1).Info("enqueue service for targetgroupconfiguration event",
6879
"targetgroupconfiguration", k8s.NamespacedName(tgconfig),
6980
"service", k8s.NamespacedName(svc))
70-
return
81+
h.svcEventChan <- event.TypedGenericEvent[*corev1.Service]{
82+
Object: svc,
83+
}
84+
}
85+
86+
// TODO - We should probably use an indexer here, we have a task to do this.
87+
if tgconfig.Spec.TargetReference.Kind != nil && *tgconfig.Spec.TargetReference.Kind == "Gateway" && h.tcpRouteEventChan != nil {
88+
tcpRouteList := &gwalpha2.TCPRouteList{}
89+
90+
if err := h.k8sClient.List(ctx, tcpRouteList); err != nil {
91+
h.logger.V(1).Info("failed to list tcp routes for target group configuration event", "targetgroupconfiguration", k8s.NamespacedName(tgconfig))
92+
return
93+
}
94+
95+
impactedRoutes := getImpactedTCPRoutes(tcpRouteList, tgconfig)
96+
for i := range impactedRoutes {
97+
h.tcpRouteEventChan <- event.TypedGenericEvent[*gwalpha2.TCPRoute]{
98+
Object: impactedRoutes[i],
99+
}
100+
}
101+
71102
}
72-
h.logger.V(1).Info("enqueue service for targetgroupconfiguration event",
73-
"targetgroupconfiguration", k8s.NamespacedName(tgconfig),
74-
"service", k8s.NamespacedName(svc))
75-
h.svcEventChan <- event.TypedGenericEvent[*corev1.Service]{
76-
Object: svc,
103+
}
104+
105+
func getImpactedTCPRoutes(list *gwalpha2.TCPRouteList, tgconfig *elbv2gw.TargetGroupConfiguration) []*gwalpha2.TCPRoute {
106+
seen := sets.Set[types.NamespacedName]{}
107+
res := make([]*gwalpha2.TCPRoute, 0)
108+
109+
for i, route := range list.Items {
110+
nsn := k8s.NamespacedName(&route)
111+
for _, rule := range route.Spec.Rules {
112+
for _, beRef := range rule.BackendRefs {
113+
if beRef.Kind != nil && *beRef.Kind == "Gateway" {
114+
if string(beRef.Name) == tgconfig.Spec.TargetReference.Name {
115+
116+
// The route backend ns
117+
var routeNs string
118+
if beRef.Namespace == nil {
119+
routeNs = route.Namespace
120+
} else {
121+
routeNs = string(*beRef.Namespace)
122+
}
123+
124+
if routeNs == tgconfig.Namespace && !seen.Has(nsn) {
125+
res = append(res, &list.Items[i])
126+
seen.Insert(nsn)
127+
}
128+
129+
}
130+
}
131+
}
132+
}
77133
}
134+
return res
78135
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package eventhandlers
2+
3+
import (
4+
awssdk "github.com/aws/aws-sdk-go-v2/aws"
5+
"github.com/stretchr/testify/assert"
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
"k8s.io/apimachinery/pkg/types"
8+
elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
10+
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
11+
"testing"
12+
)
13+
14+
func TestGetImpactedTCPRoutes(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
list *gwalpha2.TCPRouteList
18+
tgconfig *elbv2gw.TargetGroupConfiguration
19+
want []types.NamespacedName
20+
}{
21+
{
22+
name: "no routes",
23+
list: &gwalpha2.TCPRouteList{},
24+
tgconfig: &elbv2gw.TargetGroupConfiguration{
25+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
26+
Spec: elbv2gw.TargetGroupConfigurationSpec{
27+
TargetReference: elbv2gw.Reference{
28+
Name: "test-gateway",
29+
Kind: awssdk.String("Gateway"),
30+
},
31+
},
32+
},
33+
want: []types.NamespacedName{},
34+
},
35+
{
36+
name: "matching gateway backend",
37+
list: &gwalpha2.TCPRouteList{
38+
Items: []gwalpha2.TCPRoute{
39+
{
40+
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "test-ns"},
41+
Spec: gwalpha2.TCPRouteSpec{
42+
Rules: []gwalpha2.TCPRouteRule{
43+
{
44+
BackendRefs: []gwalpha2.BackendRef{
45+
{
46+
BackendObjectReference: gwalpha2.BackendObjectReference{
47+
Name: "test-gateway",
48+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
},
56+
},
57+
},
58+
tgconfig: &elbv2gw.TargetGroupConfiguration{
59+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
60+
Spec: elbv2gw.TargetGroupConfigurationSpec{
61+
TargetReference: elbv2gw.Reference{
62+
Name: "test-gateway",
63+
Kind: awssdk.String("Gateway"),
64+
},
65+
},
66+
},
67+
want: []types.NamespacedName{
68+
{Name: "route1", Namespace: "test-ns"},
69+
},
70+
},
71+
{
72+
name: "non-matching gateway name",
73+
list: &gwalpha2.TCPRouteList{
74+
Items: []gwalpha2.TCPRoute{
75+
{
76+
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "test-ns"},
77+
Spec: gwalpha2.TCPRouteSpec{
78+
Rules: []gwalpha2.TCPRouteRule{
79+
{
80+
BackendRefs: []gwalpha2.BackendRef{
81+
{
82+
BackendObjectReference: gwalpha2.BackendObjectReference{
83+
Name: "other-gateway",
84+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
85+
},
86+
},
87+
},
88+
},
89+
},
90+
},
91+
},
92+
},
93+
},
94+
tgconfig: &elbv2gw.TargetGroupConfiguration{
95+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
96+
Spec: elbv2gw.TargetGroupConfigurationSpec{
97+
TargetReference: elbv2gw.Reference{
98+
Name: "test-gateway",
99+
Kind: awssdk.String("Gateway"),
100+
},
101+
},
102+
},
103+
want: []types.NamespacedName{},
104+
},
105+
{
106+
name: "different namespace",
107+
list: &gwalpha2.TCPRouteList{
108+
Items: []gwalpha2.TCPRoute{
109+
{
110+
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "other-ns"},
111+
Spec: gwalpha2.TCPRouteSpec{
112+
Rules: []gwalpha2.TCPRouteRule{
113+
{
114+
BackendRefs: []gwalpha2.BackendRef{
115+
{
116+
BackendObjectReference: gwalpha2.BackendObjectReference{
117+
Name: "test-gateway",
118+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
119+
},
120+
},
121+
},
122+
},
123+
},
124+
},
125+
},
126+
},
127+
},
128+
tgconfig: &elbv2gw.TargetGroupConfiguration{
129+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
130+
Spec: elbv2gw.TargetGroupConfigurationSpec{
131+
TargetReference: elbv2gw.Reference{
132+
Name: "test-gateway",
133+
Kind: awssdk.String("Gateway"),
134+
},
135+
},
136+
},
137+
want: []types.NamespacedName{},
138+
},
139+
{
140+
name: "cross-namespace with explicit namespace",
141+
list: &gwalpha2.TCPRouteList{
142+
Items: []gwalpha2.TCPRoute{
143+
{
144+
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "route-ns"},
145+
Spec: gwalpha2.TCPRouteSpec{
146+
Rules: []gwalpha2.TCPRouteRule{
147+
{
148+
BackendRefs: []gwalpha2.BackendRef{
149+
{
150+
BackendObjectReference: gwalpha2.BackendObjectReference{
151+
Name: "test-gateway",
152+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
153+
Namespace: (*gwalpha2.Namespace)(awssdk.String("test-ns")),
154+
},
155+
},
156+
},
157+
},
158+
},
159+
},
160+
},
161+
},
162+
},
163+
tgconfig: &elbv2gw.TargetGroupConfiguration{
164+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
165+
Spec: elbv2gw.TargetGroupConfigurationSpec{
166+
TargetReference: elbv2gw.Reference{
167+
Name: "test-gateway",
168+
Kind: awssdk.String("Gateway"),
169+
},
170+
},
171+
},
172+
want: []types.NamespacedName{
173+
{Name: "route1", Namespace: "route-ns"},
174+
},
175+
},
176+
{
177+
name: "duplicate routes filtered",
178+
list: &gwalpha2.TCPRouteList{
179+
Items: []gwalpha2.TCPRoute{
180+
{
181+
ObjectMeta: metav1.ObjectMeta{Name: "route1", Namespace: "test-ns"},
182+
Spec: gwalpha2.TCPRouteSpec{
183+
Rules: []gwalpha2.TCPRouteRule{
184+
{
185+
BackendRefs: []gwalpha2.BackendRef{
186+
{
187+
BackendObjectReference: gwalpha2.BackendObjectReference{
188+
Name: "test-gateway",
189+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
190+
},
191+
},
192+
{
193+
BackendObjectReference: gwalpha2.BackendObjectReference{
194+
Name: "test-gateway",
195+
Kind: (*gwalpha2.Kind)(awssdk.String("Gateway")),
196+
},
197+
},
198+
},
199+
},
200+
},
201+
},
202+
},
203+
},
204+
},
205+
tgconfig: &elbv2gw.TargetGroupConfiguration{
206+
ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"},
207+
Spec: elbv2gw.TargetGroupConfigurationSpec{
208+
TargetReference: elbv2gw.Reference{
209+
Name: "test-gateway",
210+
Kind: awssdk.String("Gateway"),
211+
},
212+
},
213+
},
214+
want: []types.NamespacedName{
215+
{Name: "route1", Namespace: "test-ns"},
216+
},
217+
},
218+
}
219+
220+
for _, tt := range tests {
221+
t.Run(tt.name, func(t *testing.T) {
222+
got := getImpactedTCPRoutes(tt.list, tt.tgconfig)
223+
res := make([]types.NamespacedName, 0)
224+
225+
for i := range got {
226+
res = append(res, k8s.NamespacedName(got[i]))
227+
}
228+
229+
assert.Equal(t, tt.want, res)
230+
})
231+
}
232+
}

0 commit comments

Comments
 (0)