|
8 | 8 | "k8s.io/apimachinery/pkg/types" |
9 | 9 | elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1" |
10 | 10 | "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway" |
| 11 | + "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/constants" |
11 | 12 | "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" |
12 | 13 | "sigs.k8s.io/controller-runtime/pkg/client" |
13 | 14 | gwv1 "sigs.k8s.io/gateway-api/apis/v1" |
@@ -35,46 +36,65 @@ type Backend struct { |
35 | 36 | } |
36 | 37 |
|
37 | 38 | type attachedRuleAccumulator[RuleType any] interface { |
38 | | - accumulateRules(ctx context.Context, k8sClient client.Client, route preLoadRouteDescriptor, rules []RuleType, backendRefIterator func(RuleType) []gwv1.BackendRef, ruleConverter func(*RuleType, []Backend) RouteRule) ([]RouteRule, []routeLoadError) |
| 39 | + accumulateRules(ctx context.Context, k8sClient client.Client, route preLoadRouteDescriptor, rules []RuleType, backendRefIterator func(RuleType) []gwv1.BackendRef, listenerRuleConfigRefs func(RuleType) []gwv1.LocalObjectReference, ruleConverter func(*RuleType, []Backend, *elbv2gw.ListenerRuleConfiguration) RouteRule) ([]RouteRule, []routeLoadError) |
39 | 40 | } |
40 | 41 |
|
41 | 42 | type attachedRuleAccumulatorImpl[RuleType any] struct { |
42 | | - backendLoader func(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error, error) |
| 43 | + backendLoader func(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error, error) |
| 44 | + listenerRuleConfigLoader func(ctx context.Context, k8sClient client.Client, routeIdentifier types.NamespacedName, routeKind RouteKind, listenerRuleConfigRefs []gwv1.LocalObjectReference) (*elbv2gw.ListenerRuleConfiguration, error, error) |
43 | 45 | } |
44 | 46 |
|
45 | | -func newAttachedRuleAccumulator[RuleType any](backendLoader func(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error, error)) attachedRuleAccumulator[RuleType] { |
| 47 | +func newAttachedRuleAccumulator[RuleType any](backendLoader func(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error, error), |
| 48 | + listenerRuleConfigLoader func(ctx context.Context, k8sClient client.Client, routeIdentifier types.NamespacedName, routeKind RouteKind, listenerRuleConfigRefs []gwv1.LocalObjectReference) (*elbv2gw.ListenerRuleConfiguration, error, error)) attachedRuleAccumulator[RuleType] { |
46 | 49 | return &attachedRuleAccumulatorImpl[RuleType]{ |
47 | | - backendLoader: backendLoader, |
| 50 | + backendLoader: backendLoader, |
| 51 | + listenerRuleConfigLoader: listenerRuleConfigLoader, |
48 | 52 | } |
49 | 53 | } |
50 | 54 |
|
51 | | -func (ara *attachedRuleAccumulatorImpl[RuleType]) accumulateRules(ctx context.Context, k8sClient client.Client, route preLoadRouteDescriptor, rules []RuleType, backendRefIterator func(RuleType) []gwv1.BackendRef, ruleConverter func(*RuleType, []Backend) RouteRule) ([]RouteRule, []routeLoadError) { |
| 55 | +func (ara *attachedRuleAccumulatorImpl[RuleType]) accumulateRules(ctx context.Context, k8sClient client.Client, route preLoadRouteDescriptor, rules []RuleType, backendRefIterator func(RuleType) []gwv1.BackendRef, listenerRuleConfigRefs func(RuleType) []gwv1.LocalObjectReference, ruleConverter func(*RuleType, []Backend, *elbv2gw.ListenerRuleConfiguration) RouteRule) ([]RouteRule, []routeLoadError) { |
52 | 56 | convertedRules := make([]RouteRule, 0) |
53 | 57 | allErrors := make([]routeLoadError, 0) |
54 | 58 | for _, rule := range rules { |
55 | 59 | convertedBackends := make([]Backend, 0) |
56 | | - for _, backend := range backendRefIterator(rule) { |
57 | | - convertedBackend, warningErr, fatalErr := ara.backendLoader(ctx, k8sClient, backend, backend, route.GetRouteNamespacedName(), route.GetRouteKind()) |
58 | | - if warningErr != nil { |
59 | | - allErrors = append(allErrors, routeLoadError{ |
60 | | - Err: warningErr, |
61 | | - }) |
62 | | - } |
| 60 | + listenerRuleConfig, lrcWarningErr, lrcfatalErr := ara.listenerRuleConfigLoader(ctx, k8sClient, route.GetRouteNamespacedName(), route.GetRouteKind(), listenerRuleConfigRefs(rule)) |
| 61 | + if lrcWarningErr != nil { |
| 62 | + allErrors = append(allErrors, routeLoadError{ |
| 63 | + Err: lrcWarningErr, |
| 64 | + }) |
| 65 | + } |
| 66 | + // usually happens due to K8s Api outage |
| 67 | + if lrcfatalErr != nil { |
| 68 | + allErrors = append(allErrors, routeLoadError{ |
| 69 | + Err: lrcfatalErr, |
| 70 | + Fatal: true, |
| 71 | + }) |
| 72 | + return nil, allErrors |
| 73 | + } |
| 74 | + // If ListenerRuleConfig is loaded properly without any warning errors, then only load backends, else it should be treated as no valid backend to send with fixed 503 response |
| 75 | + if lrcWarningErr == nil { |
| 76 | + for _, backend := range backendRefIterator(rule) { |
| 77 | + convertedBackend, warningErr, fatalErr := ara.backendLoader(ctx, k8sClient, backend, backend, route.GetRouteNamespacedName(), route.GetRouteKind()) |
| 78 | + if warningErr != nil { |
| 79 | + allErrors = append(allErrors, routeLoadError{ |
| 80 | + Err: warningErr, |
| 81 | + }) |
| 82 | + } |
63 | 83 |
|
64 | | - if fatalErr != nil { |
65 | | - allErrors = append(allErrors, routeLoadError{ |
66 | | - Err: fatalErr, |
67 | | - Fatal: true, |
68 | | - }) |
69 | | - return nil, allErrors |
70 | | - } |
| 84 | + if fatalErr != nil { |
| 85 | + allErrors = append(allErrors, routeLoadError{ |
| 86 | + Err: fatalErr, |
| 87 | + Fatal: true, |
| 88 | + }) |
| 89 | + return nil, allErrors |
| 90 | + } |
71 | 91 |
|
72 | | - if convertedBackend != nil { |
73 | | - convertedBackends = append(convertedBackends, *convertedBackend) |
| 92 | + if convertedBackend != nil { |
| 93 | + convertedBackends = append(convertedBackends, *convertedBackend) |
| 94 | + } |
74 | 95 | } |
75 | 96 | } |
76 | | - |
77 | | - convertedRules = append(convertedRules, ruleConverter(&rule, convertedBackends)) |
| 97 | + convertedRules = append(convertedRules, ruleConverter(&rule, convertedBackends, listenerRuleConfig)) |
78 | 98 | } |
79 | 99 | return convertedRules, allErrors |
80 | 100 | } |
@@ -290,3 +310,60 @@ func referenceGrantCheck(ctx context.Context, k8sClient client.Client, svcIdenti |
290 | 310 |
|
291 | 311 | return false, nil |
292 | 312 | } |
| 313 | + |
| 314 | +func listenerRuleConfigLoader(ctx context.Context, k8sClient client.Client, routeIdentifier types.NamespacedName, routeKind RouteKind, listenerRuleConfigsRefs []gwv1.LocalObjectReference) (*elbv2gw.ListenerRuleConfiguration, error, error) { |
| 315 | + if len(listenerRuleConfigsRefs) == 0 { |
| 316 | + return nil, nil, nil |
| 317 | + } |
| 318 | + // This is warning error so that the reconcile cycle does not stop. |
| 319 | + if len(listenerRuleConfigsRefs) > 1 { |
| 320 | + initialErrorMessage := "Only one listener rule config can be referenced per route rule, found multiple" |
| 321 | + wrappedGatewayErrorMessage := generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier) |
| 322 | + return nil, wrapError(errors.Errorf("%s", initialErrorMessage), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonIncompatibleFilters, &wrappedGatewayErrorMessage, nil), nil |
| 323 | + } |
| 324 | + listenerRuleCfgId := types.NamespacedName{ |
| 325 | + Namespace: routeIdentifier.Namespace, |
| 326 | + Name: string(listenerRuleConfigsRefs[0].Name), |
| 327 | + } |
| 328 | + listenerRuleCfg := &elbv2gw.ListenerRuleConfiguration{} |
| 329 | + err := k8sClient.Get(ctx, listenerRuleCfgId, listenerRuleCfg) |
| 330 | + if err != nil { |
| 331 | + convertToNotFoundError := client.IgnoreNotFound(err) |
| 332 | + |
| 333 | + if convertToNotFoundError == nil { |
| 334 | + // ListenerRuleConfig not found, post an updated status. |
| 335 | + initialErrorMessage := fmt.Sprintf("ListenerRuleConfiguration [%v] not found)", listenerRuleCfgId.String()) |
| 336 | + wrappedGatewayErrorMessage := generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier) |
| 337 | + return nil, wrapError(errors.Errorf("%s", initialErrorMessage), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonIncompatibleFilters, &wrappedGatewayErrorMessage, nil), nil |
| 338 | + } |
| 339 | + |
| 340 | + return nil, nil, errors.Wrapf(err, "Unable to load listener rule config [%v] for route [%v]", listenerRuleCfgId.String(), routeIdentifier.String()) |
| 341 | + } |
| 342 | + return listenerRuleCfg, nil, nil |
| 343 | +} |
| 344 | + |
| 345 | +// getListenerRuleConfigForRuleGeneric is a generic helper that extracts ListenerRuleConfiguration |
| 346 | +// references from ExtensionRef filters in route rules |
| 347 | +func getListenerRuleConfigForRuleGeneric[FilterType any]( |
| 348 | + filters []FilterType, |
| 349 | + isExtensionRefType func(filter FilterType) bool, |
| 350 | + getExtensionRef func(filter FilterType) *gwv1.LocalObjectReference, |
| 351 | +) []gwv1.LocalObjectReference { |
| 352 | + listenerRuleConfigsRefs := make([]gwv1.LocalObjectReference, 0) |
| 353 | + for _, filter := range filters { |
| 354 | + if !isExtensionRefType(filter) { |
| 355 | + continue |
| 356 | + } |
| 357 | + extRef := getExtensionRef(filter) |
| 358 | + if extRef != nil && |
| 359 | + string(extRef.Group) == constants.ControllerCRDGroupVersion && |
| 360 | + string(extRef.Kind) == constants.ListenerRuleConfiguration { |
| 361 | + listenerRuleConfigsRefs = append(listenerRuleConfigsRefs, gwv1.LocalObjectReference{ |
| 362 | + Group: constants.ControllerCRDGroupVersion, |
| 363 | + Kind: constants.ListenerRuleConfiguration, |
| 364 | + Name: extRef.Name, |
| 365 | + }) |
| 366 | + } |
| 367 | + } |
| 368 | + return listenerRuleConfigsRefs |
| 369 | +} |
0 commit comments