Skip to content

Commit 929b0a5

Browse files
committed
MEDIUM: add multi listeners matching per gateway with a single parent reference from HTTPRoute
1 parent 8fb521f commit 929b0a5

File tree

8 files changed

+137
-141
lines changed

8 files changed

+137
-141
lines changed

k8s/gate/haproxy/routes-maps.go

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -50,56 +50,60 @@ func (b *RouteMgrImpl) fillMaps() {
5050
continue
5151
}
5252
route = route.TreeStatus.OldTreeResource
53-
for _, listener := range route.Listeners.Iterate {
54-
frontendName, err := b.topManager.getFrontendName(listener.Owner, listener.K8sResource)
55-
if err != nil {
56-
b.topManager.logger.LogAttrs(context.Background(), slog.LevelError, "Failed to get frontend name",
57-
logging.LogAttrError(err),
58-
)
59-
}
53+
for _, listeners := range route.Listeners.Iterate {
54+
for _, listener := range listeners {
55+
frontendName, err := b.topManager.getFrontendName(listener.Owner, listener.K8sResource)
56+
if err != nil {
57+
b.topManager.logger.LogAttrs(context.Background(), slog.LevelError, "Failed to get frontend name",
58+
logging.LogAttrError(err),
59+
)
60+
}
6061

61-
pathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_MAP)
62-
pathPrefixMap := mapsStorage.MapPath(frontendName, storage.PATH_PREFIX_MAP)
63-
pathDomainWPathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_DOMAIN_WILDCARD_MAP)
64-
pathregexMap := mapsStorage.MapPath(frontendName, storage.PATH_REGEX_MAP)
65-
mapExact := mapsStorage.GetMapData(pathExactMap)
66-
mapPrefix := mapsStorage.GetMapData(pathPrefixMap)
67-
mapRegex := mapsStorage.GetMapData(pathregexMap)
68-
mapDomainWPathExact := mapsStorage.GetMapData(pathDomainWPathExactMap)
69-
err = b.onDeletedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
70-
// errs.Add(err)
71-
_ = err // TODO ignore error for now
62+
pathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_MAP)
63+
pathPrefixMap := mapsStorage.MapPath(frontendName, storage.PATH_PREFIX_MAP)
64+
pathDomainWPathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_DOMAIN_WILDCARD_MAP)
65+
pathregexMap := mapsStorage.MapPath(frontendName, storage.PATH_REGEX_MAP)
66+
mapExact := mapsStorage.GetMapData(pathExactMap)
67+
mapPrefix := mapsStorage.GetMapData(pathPrefixMap)
68+
mapRegex := mapsStorage.GetMapData(pathregexMap)
69+
mapDomainWPathExact := mapsStorage.GetMapData(pathDomainWPathExactMap)
70+
err = b.onDeletedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
71+
// errs.Add(err)
72+
_ = err // TODO ignore error for now
73+
}
7274
}
7375
}
7476

7577
// Managed HTTPRoutes => Create / update/ delete backends
7678
for routeKey, route := range controllerStore.GateTree.HTTPRoutes {
7779
for _, listener := range route.Listeners.Iterate {
78-
frontendName, err := b.topManager.getFrontendName(listener.Owner, listener.K8sResource)
79-
if err != nil {
80-
b.topManager.logger.LogAttrs(context.Background(), slog.LevelError, "Failed to get frontend name",
81-
logging.LogAttrError(err),
82-
)
83-
}
80+
for _, listener := range listener {
81+
frontendName, err := b.topManager.getFrontendName(listener.Owner, listener.K8sResource)
82+
if err != nil {
83+
b.topManager.logger.LogAttrs(context.Background(), slog.LevelError, "Failed to get frontend name",
84+
logging.LogAttrError(err),
85+
)
86+
}
8487

85-
pathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_MAP)
86-
pathPrefixMap := mapsStorage.MapPath(frontendName, storage.PATH_PREFIX_MAP)
87-
pathDomainWPathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_DOMAIN_WILDCARD_MAP)
88-
pathRegexMap := mapsStorage.MapPath(frontendName, storage.PATH_REGEX_MAP)
89-
mapExact := mapsStorage.GetMapData(pathExactMap)
90-
mapPrefix := mapsStorage.GetMapData(pathPrefixMap)
91-
mapRegex := mapsStorage.GetMapData(pathRegexMap)
92-
mapDomainWPathExact := mapsStorage.GetMapData(pathDomainWPathExactMap)
93-
94-
switch route.TreeStatus.Status {
95-
case store.StatusUnchanged:
96-
continue
97-
case store.StatusUpserted:
98-
err := b.onUpsertedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
99-
errs.Add(err)
100-
case store.StatusDeleted:
101-
err := b.onDeletedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
102-
errs.Add(err)
88+
pathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_MAP)
89+
pathPrefixMap := mapsStorage.MapPath(frontendName, storage.PATH_PREFIX_MAP)
90+
pathDomainWPathExactMap := mapsStorage.MapPath(frontendName, storage.PATH_EXACT_DOMAIN_WILDCARD_MAP)
91+
pathRegexMap := mapsStorage.MapPath(frontendName, storage.PATH_REGEX_MAP)
92+
mapExact := mapsStorage.GetMapData(pathExactMap)
93+
mapPrefix := mapsStorage.GetMapData(pathPrefixMap)
94+
mapRegex := mapsStorage.GetMapData(pathRegexMap)
95+
mapDomainWPathExact := mapsStorage.GetMapData(pathDomainWPathExactMap)
96+
97+
switch route.TreeStatus.Status {
98+
case store.StatusUnchanged:
99+
continue
100+
case store.StatusUpserted:
101+
err := b.onUpsertedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
102+
errs.Add(err)
103+
case store.StatusDeleted:
104+
err := b.onDeletedHTTPRoute(routeKey, route, mapExact, mapPrefix, mapRegex, mapDomainWPathExact)
105+
errs.Add(err)
106+
}
103107
}
104108
}
105109
}

k8s/gate/tree/HTTPRoute-builder.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ func (b *HTTPRouteBuilderImpl) computeTreeGatewayUpdate(gwKey client.ObjectKey,
177177
case store.StatusDeleted:
178178
if treeHTTPRoute != nil {
179179
// Iterate over the listeners of the deleted route and remove the route from each listener
180-
treeHTTPRoute.Listeners.Iterate(func(_ string, listener *Listener) bool {
181-
// This impact the Gateway object (listener status AttachedRoute), so it needs to be done, even so the HTTPRoute by itself is deleted
182-
listener.deleteAttachedRoute(client.ObjectKeyFromObject(treeHTTPRoute.K8sResource), b.ControllerStore)
183-
return true
184-
})
180+
for _, listeners := range treeHTTPRoute.Listeners.Iterate {
181+
for _, listener := range listeners {
182+
// This impact the Gateway object (listener status AttachedRoute), so it needs to be done, even so the HTTPRoute by itself is deleted
183+
listener.deleteAttachedRoute(client.ObjectKeyFromObject(treeHTTPRoute.K8sResource), b.ControllerStore)
184+
}
185+
}
185186
treeHTTPRoute.SetAsDeleted(b.Logger)
186187
treeHTTPRoute.ResetChecks()
187188
}

k8s/gate/tree/HTTPRoute.go

Lines changed: 33 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type HTTPRoute struct {
3434
// K8sResource is the source resource.
3535
K8sResource *gatewayv1.HTTPRoute
3636
// selected listener
37-
Listeners utils.KeyMap[gatewayv1.ParentReference, *Listener] // map[parentRef]
37+
Listeners utils.KeyMap[gatewayv1.ParentReference, []*Listener] // map[parentRef]
3838
// Rules
3939
Rules []*HTTPRouteRule
4040
// Final Conditions
@@ -70,7 +70,7 @@ func NewRoute(k8sObject *gatewayv1.HTTPRoute, controllerName string) *HTTPRoute
7070
Status: store.StatusUpserted,
7171
OldTreeResource: nil,
7272
},
73-
Listeners: utils.NewKeyMap[gatewayv1.ParentReference, *Listener](utils.ParentRefToKey),
73+
Listeners: utils.NewKeyMap[gatewayv1.ParentReference, []*Listener](utils.ParentRefToKey),
7474
CheckParentRefs: CheckResultRoute{
7575
Valid: false,
7676
Conditions: rc.RouteConditions{
@@ -176,20 +176,17 @@ func (r *HTTPRoute) checkParentRefs(controllerStore ControllerStore) {
176176
continue
177177
}
178178

179+
routeConditions.MergeOverrideConditionsForParentRef(parentRef, result.Conditions)
179180
// From now on, parentRef is a managed Gateway
180181

181182
// 1- The parentRef is not Valid
182183
if !result.Valid {
183184
// do not add the parentREf in Listener or check result
184-
routeConditions.MergeOverrideConditionsForParentRef(parentRef, result.Conditions)
185185
continue
186186
}
187187

188188
// 2- The parentRef is Valid
189189
atLeastOneValidParentRef = true
190-
routeConditions.MergeOverrideConditionsForParentRef(parentRef, result.Conditions)
191-
// no specific conditions
192-
r.Listeners.Set(parentRef, result.Listener)
193190
}
194191

195192
// Gather all Conditions for all parentRefs in the result
@@ -207,8 +204,6 @@ type checkParentRefResult struct {
207204
// Conditions has a set of failing conditions
208205
// It is set only if Managed is true
209206
Conditions generic.Conditions
210-
// Listener is the selected Listener
211-
Listener *Listener
212207
// Managed is true if the Gateway is managed by our controller
213208
Managed bool
214209
// Valid is set only if Managed is true
@@ -218,6 +213,7 @@ type checkParentRefResult struct {
218213
// checkParentRef checks 1 parentRef and returns:
219214
// - a bool indicating if the parentRef is valid, and if it's not, a set of conditions detailing why
220215
func (r *HTTPRoute) checkParentRef(parentRef gatewayv1.ParentReference, controllerStore ControllerStore) checkParentRefResult {
216+
// Vérifie si le type de parentRef est supporté
221217
if !isParentRefGroupKindSupported(parentRef, controllerStore.ExtractGVK) {
222218
return checkParentRefResult{
223219
Managed: false,
@@ -226,34 +222,27 @@ func (r *HTTPRoute) checkParentRef(parentRef gatewayv1.ParentReference, controll
226222
}
227223
}
228224

225+
// Récupère le Gateway correspondant
229226
gwKey := GetParentRefNamespacedName(parentRef, r.K8sResource)
230-
231227
treeGw, ok := controllerStore.GateTree.Gateways[gwKey]
232228
if ok && treeGw.TreeStatus.Status == store.StatusDeleted {
233-
// gateway exists, but it was deleted
234229
return checkParentRefResult{
235230
Managed: true,
236231
Valid: false,
237232
Conditions: rc.ConditionNotAcceptedNoMatchingParent(),
238233
}
239234
}
240235
if !ok {
241-
// It's a whole different story, it means the Gateway does not exists,
242-
// we can not know if it's managed by our controller or not
243236
return checkParentRefResult{
244237
Managed: false,
245238
Valid: false,
246239
Conditions: generic.Conditions{},
247240
}
248241
}
249242

250-
// From now on, the parent references an exising Gateway managed by our controller
251-
// SectionName is optional
252-
// 1- if set, the only attachable listener is the one references by the sectionName
253-
// 2- if not set, all listeners from the Gateway are attachable
243+
// Collecte les listeners attachables
254244
attachableListeners := make([]*Listener, 0)
255245
if parentRef.SectionName != nil {
256-
// Check if the Gateway has this listener
257246
listener, ok := treeGw.Listeners[string(*parentRef.SectionName)]
258247
if !ok {
259248
return checkParentRefResult{
@@ -262,94 +251,59 @@ func (r *HTTPRoute) checkParentRef(parentRef gatewayv1.ParentReference, controll
262251
Conditions: rc.ConditionNotAcceptedNoMatchingParent(),
263252
}
264253
}
265-
// We found the listener, it does exists
266254
attachableListeners = append(attachableListeners, listener)
267255
} else {
268256
for _, listener := range treeGw.Listeners {
269257
attachableListeners = append(attachableListeners, listener)
270258
}
271259
}
272260

273-
// If we have only 1 attache listener, just check the hostnames
274-
if len(attachableListeners) == 1 {
275-
listener := attachableListeners[0]
276-
if !listener.Valid {
277-
return checkParentRefResult{
278-
Managed: true,
279-
Valid: false,
280-
Conditions: rc.ConditionNotAcceptedNoMatchingParent(),
281-
}
282-
}
283-
if matched := matchHostname(r.K8sResource.Spec.Hostnames, listener.K8sResource.Hostname); matched {
284-
// AllowedRouteKind ???
285-
allowedRouteKind := r.isAllowedRouteKind(listener, controllerStore.ExtractGVK)
286-
if !allowedRouteKind {
287-
return checkParentRefResult{
288-
Managed: true,
289-
Valid: false,
290-
Conditions: rc.ConditionNotAcceptedRouteReasonNotAllowedByListeners(),
291-
}
292-
}
293-
294-
// Set the Listener attached Route
295-
listener.addAttachedRoute(client.ObjectKeyFromObject(r.K8sResource), controllerStore)
296-
297-
return checkParentRefResult{
298-
Managed: true,
299-
Valid: true,
300-
Listener: listener,
301-
Conditions: rc.ConditionAccepted(),
302-
}
303-
}
304-
return checkParentRefResult{
305-
Managed: true,
306-
Valid: false,
307-
Conditions: rc.ConditionNotAcceptedNoMatchingHostname(),
308-
}
309-
}
261+
validListeners := make([]*Listener, 0)
262+
conds := generic.Conditions{}
310263

311-
// Now, case where we need to find a listener among the list
312-
var matched bool
313-
var matchedListener *Listener
314264
for _, listener := range attachableListeners {
265+
// Vérifie la validité du listener
315266
if !listener.Valid {
267+
conds.MergeOverrideConditions(rc.ConditionNotAcceptedNoMatchingParent())
316268
continue
317269
}
318270

319-
// Match Hostname ?
320-
if matchHostname(r.K8sResource.Spec.Hostnames, listener.K8sResource.Hostname) {
321-
matched = true
322-
matchedListener = listener
323-
break
271+
// Vérifie le hostname
272+
if !matchHostname(r.K8sResource.Spec.Hostnames, listener.K8sResource.Hostname) {
273+
conds.MergeOverrideConditions(rc.ConditionNotAcceptedNoMatchingHostname())
274+
continue
324275
}
325-
}
326-
if !matched {
327-
return checkParentRefResult{
328-
Managed: true,
329-
Valid: false,
330-
Conditions: rc.ConditionNotAcceptedNoMatchingParent(),
276+
277+
// Vérifie le type de route autorisé
278+
if !r.isAllowedRouteKind(listener, controllerStore.ExtractGVK) {
279+
conds.MergeOverrideConditions(rc.ConditionNotAcceptedRouteReasonNotAllowedByListeners())
280+
continue
331281
}
332-
}
333282

334-
// We have found a listener
335-
// Allowed RouteKind ??
336-
allowedRouteKind := r.isAllowedRouteKind(matchedListener, controllerStore.ExtractGVK)
337-
// Set the Listener attached Route
338-
matchedListener.addAttachedRoute(client.ObjectKeyFromObject(r.K8sResource), controllerStore)
283+
// Listener valide, attache la route
284+
listener.addAttachedRoute(client.ObjectKeyFromObject(r.K8sResource), controllerStore)
285+
validListeners = append(validListeners, listener)
286+
287+
// Met à jour la KeyMap r.Listeners
288+
existing, _ := r.Listeners.Get(parentRef)
289+
existing = append(existing, listener)
290+
r.Listeners.Set(parentRef, existing)
291+
}
339292

340-
if !allowedRouteKind {
293+
// Si aucun listener valide, retourne les conditions cumulées
294+
if len(validListeners) == 0 {
341295
return checkParentRefResult{
342296
Managed: true,
343297
Valid: false,
344-
Conditions: rc.ConditionNotAcceptedRouteReasonNotAllowedByListeners(),
298+
Conditions: conds,
345299
}
346300
}
347301

302+
// Au moins un listener valide
348303
return checkParentRefResult{
349304
Managed: true,
350305
Valid: true,
351-
Listener: matchedListener,
352-
Conditions: generic.Conditions{},
306+
Conditions: rc.ConditionAccepted(),
353307
}
354308
}
355309

test/integration/base/base.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import (
1919
"context"
2020
"os"
2121
"path"
22+
"path/filepath"
2223
"sort"
2324
"strconv"
25+
"strings"
2426
"syscall"
2527
"time"
2628

@@ -352,6 +354,28 @@ func (b *BaseSuite) BackendFromManifest(manifestPath, manifestName string) *mode
352354
return &be
353355
}
354356

357+
func (b *BaseSuite) GetMapFileFrom(mapFileRelativePath string) ([]string, error) {
358+
var mapFile []byte
359+
mapFile, err := os.ReadFile(filepath.Join(b.test.HaproxyCfgDir, "maps", mapFileRelativePath))
360+
if err != nil {
361+
return nil, err
362+
}
363+
return strings.Split(string(mapFile), "\n"), nil
364+
}
365+
366+
func (b *BaseSuite) CheckEntryInMapFile(mapFileRelativePath, key, value string) bool {
367+
mapFile, err := b.GetMapFileFrom(mapFileRelativePath)
368+
if err != nil {
369+
return false
370+
}
371+
for _, line := range mapFile {
372+
if line == key+" "+value {
373+
return true
374+
}
375+
}
376+
return false
377+
}
378+
355379
// func (b *BaseSuite) exportFrontend(fe *models.Frontend) {
356380
// // Marshal the Go struct into a YAML byte slice.
357381
// // This process converts the Go data structure into its YAML representation.

test/integration/base/inttest.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type IntTest struct {
8787
TestEnv *envtest.Environment
8888
cancel context.CancelFunc
8989
Namespace string
90+
HaproxyCfgDir string
9091
}
9192

9293
func NewIntTest(t *testing.T) (test IntTest, err error) {
@@ -210,7 +211,7 @@ func (test *IntTest) StartTestEnv(t *testing.T) { //revive:disable:function-leng
210211
// // ----------------
211212
// // Start Haproxy App manager
212213
var wg sync.WaitGroup
213-
214+
test.HaproxyCfgDir = gateconfig.HaproxyParams.CfgDir
214215
haproxyClient, err := hapapi.New(gateconfig.Logger, gateconfig.HaproxyParams.CfgDir,
215216
gateconfig.HaproxyParams.MainCfgFile, gateconfig.HaproxyParams.HaproxyBinary, gateconfig.HaproxyParams.RuntimeSocket)
216217
if err != nil {

0 commit comments

Comments
 (0)