From 5426ba0ec174bd86cbebc689ebcd3e56639170d3 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 1/7] Update rule management to avoid sporadic 503 errors --- .go-version | 2 +- docs/install/iam_policy.json | 3 +- docs/install/iam_policy_cn.json | 3 +- docs/install/iam_policy_iso.json | 3 +- docs/install/iam_policy_isob.json | 3 +- go.mod | 2 +- pkg/aws/services/elbv2.go | 11 +- pkg/aws/services/elbv2_mocks.go | 15 +++ pkg/deploy/elbv2/listener_rule_manager.go | 105 ++++++++---------- pkg/deploy/elbv2/listener_rule_synthesizer.go | 97 ++++++++++++++-- pkg/deploy/stack_deployer.go | 2 +- 11 files changed, 175 insertions(+), 71 deletions(-) diff --git a/.go-version b/.go-version index 229a27c6f2..a6c2798a48 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.22.8 +1.23.0 diff --git a/docs/install/iam_policy.json b/docs/install/iam_policy.json index 1a5b4d614b..bf5d407d8d 100644 --- a/docs/install/iam_policy.json +++ b/docs/install/iam_policy.json @@ -42,7 +42,8 @@ "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeTrustStores", "elasticloadbalancing:DescribeListenerAttributes", - "elasticloadbalancing:DescribeCapacityReservation" + "elasticloadbalancing:DescribeCapacityReservation", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" }, diff --git a/docs/install/iam_policy_cn.json b/docs/install/iam_policy_cn.json index ba8a39fa79..cee692a5d6 100644 --- a/docs/install/iam_policy_cn.json +++ b/docs/install/iam_policy_cn.json @@ -42,7 +42,8 @@ "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeTrustStores", "elasticloadbalancing:DescribeListenerAttributes", - "elasticloadbalancing:DescribeCapacityReservation" + "elasticloadbalancing:DescribeCapacityReservation", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" }, diff --git a/docs/install/iam_policy_iso.json b/docs/install/iam_policy_iso.json index bd87af7f5e..a0b23d96a9 100644 --- a/docs/install/iam_policy_iso.json +++ b/docs/install/iam_policy_iso.json @@ -39,7 +39,8 @@ "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", - "elasticloadbalancing:DescribeTags" + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" }, diff --git a/docs/install/iam_policy_isob.json b/docs/install/iam_policy_isob.json index e552c458fe..c9f58a0c14 100644 --- a/docs/install/iam_policy_isob.json +++ b/docs/install/iam_policy_isob.json @@ -39,7 +39,8 @@ "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", - "elasticloadbalancing:DescribeTags" + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" }, diff --git a/go.mod b/go.mod index 2346312e90..baf2b23098 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module sigs.k8s.io/aws-load-balancer-controller -go 1.22.8 +go 1.23.0 require ( github.com/aws/aws-sdk-go v1.55.5 diff --git a/pkg/aws/services/elbv2.go b/pkg/aws/services/elbv2.go index 293f61f3b2..c2eb0d4019 100644 --- a/pkg/aws/services/elbv2.go +++ b/pkg/aws/services/elbv2.go @@ -50,7 +50,8 @@ type ELBV2 interface { DescribeRulesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeRulesInput) (*elasticloadbalancingv2.DescribeRulesOutput, error) CreateRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateRuleInput) (*elasticloadbalancingv2.CreateRuleOutput, error) DeleteRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteRuleInput) (*elasticloadbalancingv2.DeleteRuleOutput, error) - ModifyRuleWithContext(ctx context.Context, inout *elasticloadbalancingv2.ModifyRuleInput) (*elasticloadbalancingv2.ModifyRuleOutput, error) + ModifyRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyRuleInput) (*elasticloadbalancingv2.ModifyRuleOutput, error) + SetRulePrioritiesWithContext(ctx context.Context, input *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) RegisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.RegisterTargetsInput) (*elasticloadbalancingv2.RegisterTargetsOutput, error) DeregisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.DeregisterTargetsInput) (*elasticloadbalancingv2.DeregisterTargetsOutput, error) DescribeTrustStoresWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTrustStoresInput) (*elasticloadbalancingv2.DescribeTrustStoresOutput, error) @@ -163,6 +164,14 @@ func (c *elbv2Client) DeleteRuleWithContext(ctx context.Context, input *elasticl return client.DeleteRule(ctx, input) } +func (c *elbv2Client) SetRulePrioritiesWithContext(ctx context.Context, input *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) { + client, err := c.awsClientsProvider.GetELBv2Client(ctx, "SetRulePriorities") + if err != nil { + return nil, err + } + return client.SetRulePriorities(ctx, input) +} + func (c *elbv2Client) CreateRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateRuleInput) (*elasticloadbalancingv2.CreateRuleOutput, error) { client, err := c.getClient(ctx, "CreateRule") if err != nil { diff --git a/pkg/aws/services/elbv2_mocks.go b/pkg/aws/services/elbv2_mocks.go index 1f427dd566..e04343b2a5 100644 --- a/pkg/aws/services/elbv2_mocks.go +++ b/pkg/aws/services/elbv2_mocks.go @@ -621,6 +621,21 @@ func (mr *MockELBV2MockRecorder) SetIpAddressTypeWithContext(arg0, arg1 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIpAddressTypeWithContext", reflect.TypeOf((*MockELBV2)(nil).SetIpAddressTypeWithContext), arg0, arg1) } +// SetRulePrioritiesWithContext mocks base method. +func (m *MockELBV2) SetRulePrioritiesWithContext(arg0 context.Context, arg1 *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRulePrioritiesWithContext", arg0, arg1) + ret0, _ := ret[0].(*elasticloadbalancingv2.SetRulePrioritiesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetRulePrioritiesWithContext indicates an expected call of SetRulePrioritiesWithContext. +func (mr *MockELBV2MockRecorder) SetRulePrioritiesWithContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRulePrioritiesWithContext", reflect.TypeOf((*MockELBV2)(nil).SetRulePrioritiesWithContext), arg0, arg1) +} + // SetSecurityGroupsWithContext mocks base method. func (m *MockELBV2) SetSecurityGroupsWithContext(arg0 context.Context, arg1 *elasticloadbalancingv2.SetSecurityGroupsInput) (*elasticloadbalancingv2.SetSecurityGroupsOutput, error) { m.ctrl.T.Helper() diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index 4efe82d724..0d90e93152 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -6,24 +6,26 @@ import ( elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" - elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/runtime" + "slices" + "strconv" "time" ) // ListenerRuleManager is responsible for create/update/delete ListenerRule resources. type ListenerRuleManager interface { - Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) + Create(ctx context.Context, resLR *elbv2model.ListenerRule, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error + + SetRulePriorities(ctx context.Context, sdkLR []ListenerRuleWithTags, lastAvailablePriority int32) (int32, error) } // NewDefaultListenerRuleManager constructs new defaultListenerRuleManager. @@ -54,8 +56,8 @@ type defaultListenerRuleManager struct { waitLSExistenceTimeout time.Duration } -func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) { - req, err := buildSDKCreateListenerRuleInput(resLR.Spec, m.featureGates) +func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2model.ListenerRule, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) { + req, err := buildSDKCreateListenerRuleInput(resLR.Spec, desiredActionsAndConditions, m.featureGates) if err != nil { return elbv2model.ListenerRuleStatus{}, err } @@ -96,9 +98,6 @@ func (m *defaultListenerRuleManager) Update(ctx context.Context, resLR *elbv2mod return elbv2model.ListenerRuleStatus{}, err } } - if err := m.updateSDKListenerRuleWithSettings(ctx, resLR, sdkLR); err != nil { - return elbv2model.ListenerRuleStatus{}, err - } return buildResListenerRuleStatus(sdkLR), nil } @@ -116,30 +115,21 @@ func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerR return nil } -func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { - desiredActions, err := buildSDKActions(resLR.Spec.Actions, m.featureGates) - if err != nil { - return err - } - desiredConditions := buildSDKRuleConditions(resLR.Spec.Conditions) - if !isSDKListenerRuleSettingsDrifted(resLR.Spec, sdkLR, desiredActions, desiredConditions) { - return nil - } - - req := buildSDKModifyListenerRuleInput(resLR.Spec, desiredActions, desiredConditions) - req.RuleArn = sdkLR.ListenerRule.RuleArn - m.logger.Info("modifying listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID(), - "arn", awssdk.ToString(sdkLR.ListenerRule.RuleArn)) - if _, err := m.elbv2Client.ModifyRuleWithContext(ctx, req); err != nil { - return err - } - m.logger.Info("modified listener rule", - "stackID", resLR.Stack().StackID(), - "resourceID", resLR.ID(), - "arn", awssdk.ToString(sdkLR.ListenerRule.RuleArn)) - return nil +func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, unmatchedSDKLRs []ListenerRuleWithTags, lastAvailablePriority int32) (int32, error) { + for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { + //Update rule priorities + sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) + lastAvailablePriority-- + } + req := buildSDKSetRulePrioritiesInput(unmatchedSDKLRs) + m.logger.Info("setting listener rule priorities", + "rule priority pairs", req.RulePriorities) + if _, err := m.elbv2Client.SetRulePrioritiesWithContext(ctx, req); err != nil { + return lastAvailablePriority, err + } + m.logger.Info("setting listener rule priorities complete", + "rule priority pairs", req.RulePriorities) + return lastAvailablePriority, nil } func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { @@ -149,20 +139,7 @@ func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.C WithIgnoredTagKeys(m.externalManagedTags)) } -func isSDKListenerRuleSettingsDrifted(lrSpec elbv2model.ListenerRuleSpec, sdkLR ListenerRuleWithTags, - desiredActions []elbv2types.Action, desiredConditions []elbv2types.RuleCondition) bool { - - if !cmp.Equal(desiredActions, sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) { - return true - } - if !cmp.Equal(desiredConditions, sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { - return true - } - - return false -} - -func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { +func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { ctx := context.Background() lsARN, err := lrSpec.ListenerARN.Resolve(ctx) if err != nil { @@ -171,22 +148,38 @@ func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, feature sdkObj := &elbv2sdk.CreateRuleInput{} sdkObj.ListenerArn = awssdk.String(lsARN) sdkObj.Priority = awssdk.Int32(lrSpec.Priority) - actions, err := buildSDKActions(lrSpec.Actions, featureGates) - if err != nil { - return nil, err + if desiredActionsAndConditions != nil && desiredActionsAndConditions.desiredActions != nil { + sdkObj.Actions = desiredActionsAndConditions.desiredActions + } else { + actions, err := buildSDKActions(lrSpec.Actions, featureGates) + if err != nil { + return nil, err + } + sdkObj.Actions = actions + } + if desiredActionsAndConditions != nil && desiredActionsAndConditions.desiredConditions != nil { + sdkObj.Conditions = desiredActionsAndConditions.desiredConditions + } else { + sdkObj.Conditions = buildSDKRuleConditions(lrSpec.Conditions) } - sdkObj.Actions = actions - sdkObj.Conditions = buildSDKRuleConditions(lrSpec.Conditions) return sdkObj, nil } -func buildSDKModifyListenerRuleInput(_ elbv2model.ListenerRuleSpec, desiredActions []elbv2types.Action, desiredConditions []elbv2types.RuleCondition) *elbv2sdk.ModifyRuleInput { - sdkObj := &elbv2sdk.ModifyRuleInput{} - sdkObj.Actions = desiredActions - sdkObj.Conditions = desiredConditions +func buildSDKSetRulePrioritiesInput(sdkLRs []ListenerRuleWithTags) *elbv2sdk.SetRulePrioritiesInput { + var rulePriorities []elbv2types.RulePriorityPair + for _, sdkLR := range sdkLRs { + p, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 32) + rulePriorityPair := elbv2types.RulePriorityPair{ + RuleArn: sdkLR.ListenerRule.RuleArn, + Priority: awssdk.Int32(int32(p)), + } + rulePriorities = append(rulePriorities, rulePriorityPair) + } + sdkObj := &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: rulePriorities, + } return sdkObj } - func buildResListenerRuleStatus(sdkLR ListenerRuleWithTags) elbv2model.ListenerRuleStatus { return elbv2model.ListenerRuleStatus{ RuleARN: awssdk.ToString(sdkLR.ListenerRule.RuleArn), diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer.go b/pkg/deploy/elbv2/listener_rule_synthesizer.go index 99a9409367..618ae41fe2 100644 --- a/pkg/deploy/elbv2/listener_rule_synthesizer.go +++ b/pkg/deploy/elbv2/listener_rule_synthesizer.go @@ -3,9 +3,13 @@ package elbv2 import ( "context" awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" + "sigs.k8s.io/aws-load-balancer-controller/pkg/config" + elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "strconv" @@ -13,11 +17,12 @@ import ( // NewListenerRuleSynthesizer constructs new listenerRuleSynthesizer. func NewListenerRuleSynthesizer(elbv2Client services.ELBV2, taggingManager TaggingManager, - lrManager ListenerRuleManager, logger logr.Logger, stack core.Stack) *listenerRuleSynthesizer { + lrManager ListenerRuleManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack) *listenerRuleSynthesizer { return &listenerRuleSynthesizer{ elbv2Client: elbv2Client, lrManager: lrManager, logger: logger, + featureGates: featureGates, taggingManager: taggingManager, stack: stack, } @@ -26,6 +31,7 @@ func NewListenerRuleSynthesizer(elbv2Client services.ELBV2, taggingManager Taggi type listenerRuleSynthesizer struct { elbv2Client services.ELBV2 lrManager ListenerRuleManager + featureGates config.FeatureGates logger logr.Logger taggingManager TaggingManager @@ -62,30 +68,75 @@ func (s *listenerRuleSynthesizer) PostSynthesize(ctx context.Context) error { func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context.Context, lsARN string, resLRs []*elbv2model.ListenerRule) error { sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) + var lastAvailablePriority int32 = 50000 if err != nil { return err } - - matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs := matchResAndSDKListenerRules(resLRs, sdkLRs) - for _, sdkLR := range unmatchedSDKLRs { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + // Find rules which are matching priority + matchedResAndSDKLRsByPriority, unmatchedResLRs, unmatchedSDKLRs := matchResAndSDKListenerRules(resLRs, sdkLRs) + // Push down all the unmatched existing rules on load balancer + if len(unmatchedSDKLRs) != 0 { + p, err := s.lrManager.SetRulePriorities(ctx, unmatchedSDKLRs, lastAvailablePriority) + if err != nil { return err } + lastAvailablePriority = p } + // Create all the unmatched/ new rules on the LB for _, resLR := range unmatchedResLRs { - lrStatus, err := s.lrManager.Create(ctx, resLR) + lrStatus, err := s.lrManager.Create(ctx, resLR, nil) if err != nil { return err } resLR.SetStatus(lrStatus) } - for _, resAndSDKLR := range matchedResAndSDKLRs { + // Filter the rules on the same priority based on their setting (actions and conditions) + matchedResAndSDKLRsBySettings, unmatchedResAndSDKLRsBySettings, unmatchedResLRsDesiredActionsAndConditions, err := s.matchResAndSDKListenerRulesBySettings(matchedResAndSDKLRsByPriority) + if err != nil { + return err + } + + var unmatchedSDKLRsBySettings []ListenerRuleWithTags + var unmatchedResLRsBySettings []*elbv2model.ListenerRule + if len(unmatchedResAndSDKLRsBySettings) != 0 { + for _, resAndSDKLR := range unmatchedResAndSDKLRsBySettings { + unmatchedSDKLRsBySettings = append(unmatchedSDKLRsBySettings, resAndSDKLR.sdkLR) + unmatchedResLRsBySettings = append(unmatchedResLRsBySettings, resAndSDKLR.resLR) + } + // Push down all the unmatched existing rules by settings on load balancer + if _, err := s.lrManager.SetRulePriorities(ctx, unmatchedSDKLRsBySettings, lastAvailablePriority); err != nil { + return err + } + // Create new rules on the same priorities with new settings + for i, resLR := range unmatchedResLRsBySettings { + lrStatus, err := s.lrManager.Create(ctx, resLR, unmatchedResLRsDesiredActionsAndConditions[i]) + if err != nil { + return err + } + resLR.SetStatus(lrStatus) + } + } + + // Update tags for all the matching rules + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) if err != nil { return err } resAndSDKLR.resLR.SetStatus(lsStatus) } + + // Once all the required rules are created and updated, delete all the rules which were pushed down since they are not required anymore + for _, sdkLR := range unmatchedSDKLRs { + if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + return err + } + } + for _, sdkLR := range unmatchedSDKLRsBySettings { + if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + return err + } + } return nil } @@ -110,6 +161,11 @@ type resAndSDKListenerRulePair struct { sdkLR ListenerRuleWithTags } +type resLRDesiredActionsAndConditionsPair struct { + desiredActions []types.Action + desiredConditions []types.RuleCondition +} + func matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, sdkLRs []ListenerRuleWithTags) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags) { var matchedResAndSDKLRs []resAndSDKListenerRulePair var unmatchedResLRs []*elbv2model.ListenerRule @@ -145,6 +201,33 @@ func mapResListenerRuleByPriority(resLRs []*elbv2model.ListenerRule) map[int32]* return resLRByPriority } +// Filter the rules based on their settings aka actions and conditions +func (s *listenerRuleSynthesizer) matchResAndSDKListenerRulesBySettings(matchedResAndSDKLRsByPriority []resAndSDKListenerRulePair) ([]resAndSDKListenerRulePair, []resAndSDKListenerRulePair, []*resLRDesiredActionsAndConditionsPair, error) { + var unmatchedResAndSDKListenerRulesBySettings []resAndSDKListenerRulePair + var matchedResAndSDKListenerRulesBySettings []resAndSDKListenerRulePair + var unmatchedResLRDesiredActionsAndConditionsPair []*resLRDesiredActionsAndConditionsPair + for _, resAndSDKLR := range matchedResAndSDKLRsByPriority { + desiredActions, err := buildSDKActions(resAndSDKLR.resLR.Spec.Actions, s.featureGates) + if err != nil { + return nil, nil, nil, err + } + desiredConditions := buildSDKRuleConditions(resAndSDKLR.resLR.Spec.Conditions) + if !cmp.Equal(desiredActions, resAndSDKLR.sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) || + !cmp.Equal(desiredConditions, resAndSDKLR.sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { + unmatchedResAndSDKListenerRulesBySettings = append(unmatchedResAndSDKListenerRulesBySettings, resAndSDKLR) + // Storing these so that we don't need to build SDK actions and conditions later during building create request input + unmatchedResLRDesiredActionsAndConditionsPair = append(unmatchedResLRDesiredActionsAndConditionsPair, &resLRDesiredActionsAndConditionsPair{ + desiredActions: desiredActions, + desiredConditions: desiredConditions, + }) + } else { + matchedResAndSDKListenerRulesBySettings = append(matchedResAndSDKListenerRulesBySettings, resAndSDKLR) + } + } + + return matchedResAndSDKListenerRulesBySettings, unmatchedResAndSDKListenerRulesBySettings, unmatchedResLRDesiredActionsAndConditionsPair, nil +} + func mapSDKListenerRuleByPriority(sdkLRs []ListenerRuleWithTags) map[int32]ListenerRuleWithTags { sdkLRByPriority := make(map[int32]ListenerRuleWithTags, len(sdkLRs)) for _, sdkLR := range sdkLRs { diff --git a/pkg/deploy/stack_deployer.go b/pkg/deploy/stack_deployer.go index eddba59a93..b7787200dc 100644 --- a/pkg/deploy/stack_deployer.go +++ b/pkg/deploy/stack_deployer.go @@ -93,7 +93,7 @@ func (d *defaultStackDeployer) Deploy(ctx context.Context, stack core.Stack) err elbv2.NewTargetGroupSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2TGManager, d.logger, d.featureGates, stack), elbv2.NewLoadBalancerSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2LBManager, d.logger, d.featureGates, d.controllerConfig, stack), elbv2.NewListenerSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LSManager, d.logger, stack), - elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, stack), + elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, d.featureGates, stack), elbv2.NewTargetGroupBindingSynthesizer(d.k8sClient, d.trackingProvider, d.elbv2TGBManager, d.logger, stack), } From 3072be5820f8aaeef0febfdce868ffeea2ec8673 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 2/7] Update rule management to avoid sporadic 503 errors --- .go-version | 2 +- go.mod | 7 +++---- go.sum | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.go-version b/.go-version index a6c2798a48..ca8ec414e7 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.23.0 +1.23.5 diff --git a/go.mod b/go.mod index baf2b23098..b215ec92d8 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module sigs.k8s.io/aws-load-balancer-controller -go 1.23.0 +go 1.23.5 require ( github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 github.com/aws/aws-sdk-go-v2/service/acm v1.28.4 github.com/aws/aws-sdk-go-v2/service/appmesh v1.27.7 @@ -14,6 +15,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3 github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.31.7 github.com/aws/aws-sdk-go-v2/service/shield v1.27.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 github.com/aws/aws-sdk-go-v2/service/wafregional v1.23.3 github.com/aws/aws-sdk-go-v2/service/wafv2 v1.51.4 github.com/aws/smithy-go v1.22.1 @@ -56,16 +58,13 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/iam v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect diff --git a/go.sum b/go.sum index 6676d7940c..292ee96ce6 100644 --- a/go.sum +++ b/go.sum @@ -60,7 +60,6 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M= -github.com/aws/aws-sdk-go-v2/service/iam v1.36.3/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= From 6a1e6549062337c48eed1e4882154aff448e8fd7 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 3/7] Update rule management to avoid sporadic 503 errors --- .go-version | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.go-version b/.go-version index ca8ec414e7..d8c40e539c 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.23.5 +1.23.6 diff --git a/go.mod b/go.mod index b215ec92d8..a2186f2279 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module sigs.k8s.io/aws-load-balancer-controller -go 1.23.5 +go 1.23.6 require ( github.com/aws/aws-sdk-go v1.55.5 From 16900de56d1d7e4c31943af3ec77bc0194031105 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 4/7] Update rule management to avoid sporadic 503 errors --- pkg/deploy/elbv2/listener_rule_manager.go | 18 +- pkg/deploy/elbv2/listener_rule_synthesizer.go | 168 ++++++------------ pkg/deploy/elbv2/listener_utils.go | 12 ++ 3 files changed, 74 insertions(+), 124 deletions(-) diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index 0d90e93152..d8df9e1c51 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -12,7 +12,6 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/runtime" - "slices" "strconv" "time" ) @@ -25,7 +24,7 @@ type ListenerRuleManager interface { Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error - SetRulePriorities(ctx context.Context, sdkLR []ListenerRuleWithTags, lastAvailablePriority int32) (int32, error) + SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair) error } // NewDefaultListenerRuleManager constructs new defaultListenerRuleManager. @@ -115,21 +114,22 @@ func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerR return nil } -func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, unmatchedSDKLRs []ListenerRuleWithTags, lastAvailablePriority int32) (int32, error) { - for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { +func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair) error { + var sdkLRs []ListenerRuleWithTags + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { //Update rule priorities - sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) - lastAvailablePriority-- + resAndSDKLR.sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(resAndSDKLR.resLR.Spec.Priority))) + sdkLRs = append(sdkLRs, resAndSDKLR.sdkLR) } - req := buildSDKSetRulePrioritiesInput(unmatchedSDKLRs) + req := buildSDKSetRulePrioritiesInput(sdkLRs) m.logger.Info("setting listener rule priorities", "rule priority pairs", req.RulePriorities) if _, err := m.elbv2Client.SetRulePrioritiesWithContext(ctx, req); err != nil { - return lastAvailablePriority, err + return err } m.logger.Info("setting listener rule priorities complete", "rule priority pairs", req.RulePriorities) - return lastAvailablePriority, nil + return nil } func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer.go b/pkg/deploy/elbv2/listener_rule_synthesizer.go index 618ae41fe2..5fc233afa0 100644 --- a/pkg/deploy/elbv2/listener_rule_synthesizer.go +++ b/pkg/deploy/elbv2/listener_rule_synthesizer.go @@ -6,7 +6,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" - "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" @@ -67,75 +66,54 @@ func (s *listenerRuleSynthesizer) PostSynthesize(ctx context.Context) error { } func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context.Context, lsARN string, resLRs []*elbv2model.ListenerRule) error { - sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) - var lastAvailablePriority int32 = 50000 - if err != nil { - return err - } - // Find rules which are matching priority - matchedResAndSDKLRsByPriority, unmatchedResLRs, unmatchedSDKLRs := matchResAndSDKListenerRules(resLRs, sdkLRs) - // Push down all the unmatched existing rules on load balancer - if len(unmatchedSDKLRs) != 0 { - p, err := s.lrManager.SetRulePriorities(ctx, unmatchedSDKLRs, lastAvailablePriority) + // Build desired actions and conditions pairs for resource listener rules. + resLRDesiredActionsAndConditionsPairs := make(map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair, len(resLRs)) + for _, resLR := range resLRs { + resLRDesiredActionsAndConditionsPair, err := buildResLRDesiredActionsAndConditionsPair(resLR, s.featureGates) if err != nil { return err } - lastAvailablePriority = p + resLRDesiredActionsAndConditionsPairs[resLR] = resLRDesiredActionsAndConditionsPair } - // Create all the unmatched/ new rules on the LB - for _, resLR := range unmatchedResLRs { - lrStatus, err := s.lrManager.Create(ctx, resLR, nil) - if err != nil { - return err - } - resLR.SetStatus(lrStatus) + // Find existing listener rules on the load balancer + sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) + if err != nil { + return err } - // Filter the rules on the same priority based on their setting (actions and conditions) - matchedResAndSDKLRsBySettings, unmatchedResAndSDKLRsBySettings, unmatchedResLRsDesiredActionsAndConditions, err := s.matchResAndSDKListenerRulesBySettings(matchedResAndSDKLRsByPriority) + // matchedResAndSDKLRsBySettings : A slice of matched resLR and SDKLR rule pairs that have matching settings like actions and conditions + // unmatchedResLRs : A slice of resLR) that do not have a corresponding match in the sdkLRs. These rules need to be created on the load balancer. + // unmatchedSDKLRs : A slice of sdkLRs that do not have a corresponding match in the resLRs. These rules need to be deleted from the load balancer. + matchedResAndSDKLRsBySettings, unmatchedResLRs, unmatchedSDKLRs, err := s.matchResAndSDKListenerRules(resLRs, sdkLRs, resLRDesiredActionsAndConditionsPairs) if err != nil { return err } - - var unmatchedSDKLRsBySettings []ListenerRuleWithTags - var unmatchedResLRsBySettings []*elbv2model.ListenerRule - if len(unmatchedResAndSDKLRsBySettings) != 0 { - for _, resAndSDKLR := range unmatchedResAndSDKLRsBySettings { - unmatchedSDKLRsBySettings = append(unmatchedSDKLRsBySettings, resAndSDKLR.sdkLR) - unmatchedResLRsBySettings = append(unmatchedResLRsBySettings, resAndSDKLR.resLR) - } - // Push down all the unmatched existing rules by settings on load balancer - if _, err := s.lrManager.SetRulePriorities(ctx, unmatchedSDKLRsBySettings, lastAvailablePriority); err != nil { + for _, sdkLR := range unmatchedSDKLRs { + if err := s.lrManager.Delete(ctx, sdkLR); err != nil { return err } - // Create new rules on the same priorities with new settings - for i, resLR := range unmatchedResLRsBySettings { - lrStatus, err := s.lrManager.Create(ctx, resLR, unmatchedResLRsDesiredActionsAndConditions[i]) - if err != nil { - return err - } - resLR.SetStatus(lrStatus) - } } - - // Update tags for all the matching rules - for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { - lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) + // Re-prioritize matched listener rules. + if len(matchedResAndSDKLRsBySettings) > 0 { + err := s.lrManager.SetRulePriorities(ctx, matchedResAndSDKLRsBySettings) if err != nil { return err } - resAndSDKLR.resLR.SetStatus(lsStatus) } - - // Once all the required rules are created and updated, delete all the rules which were pushed down since they are not required anymore - for _, sdkLR := range unmatchedSDKLRs { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + // Create all the new rules on the LB + for _, resLR := range unmatchedResLRs { + lrStatus, err := s.lrManager.Create(ctx, resLR, resLRDesiredActionsAndConditionsPairs[resLR]) + if err != nil { return err } + resLR.SetStatus(lrStatus) } - for _, sdkLR := range unmatchedSDKLRsBySettings { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + // Update existing listener rules on the load balancer for their tags + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { + lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) + if err != nil { return err } + resAndSDKLR.resLR.SetStatus(lsStatus) } return nil } @@ -166,75 +144,35 @@ type resLRDesiredActionsAndConditionsPair struct { desiredConditions []types.RuleCondition } -func matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, sdkLRs []ListenerRuleWithTags) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags) { - var matchedResAndSDKLRs []resAndSDKListenerRulePair - var unmatchedResLRs []*elbv2model.ListenerRule - var unmatchedSDKLRs []ListenerRuleWithTags - - resLRByPriority := mapResListenerRuleByPriority(resLRs) - sdkLRByPriority := mapSDKListenerRuleByPriority(sdkLRs) - resLRPriorities := sets.Int32KeySet(resLRByPriority) - sdkLRPriorities := sets.Int32KeySet(sdkLRByPriority) - for _, priority := range resLRPriorities.Intersection(sdkLRPriorities).List() { - resLR := resLRByPriority[priority] - sdkLR := sdkLRByPriority[priority] - matchedResAndSDKLRs = append(matchedResAndSDKLRs, resAndSDKListenerRulePair{ - resLR: resLR, - sdkLR: sdkLR, - }) - } - for _, priority := range resLRPriorities.Difference(sdkLRPriorities).List() { - unmatchedResLRs = append(unmatchedResLRs, resLRByPriority[priority]) - } - for _, priority := range sdkLRPriorities.Difference(resLRPriorities).List() { - unmatchedSDKLRs = append(unmatchedSDKLRs, sdkLRByPriority[priority]) - } +func (s *listenerRuleSynthesizer) matchResAndSDKListenerRules(unmatchedResLRs []*elbv2model.ListenerRule, unmatchedSDKLRs []ListenerRuleWithTags, resLRDesiredActionsAndConditionsPairs map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags, error) { + var matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair + var resLRsToCreate []*elbv2model.ListenerRule - return matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs -} - -func mapResListenerRuleByPriority(resLRs []*elbv2model.ListenerRule) map[int32]*elbv2model.ListenerRule { - resLRByPriority := make(map[int32]*elbv2model.ListenerRule, len(resLRs)) - for _, resLR := range resLRs { - resLRByPriority[resLR.Spec.Priority] = resLR - } - return resLRByPriority -} - -// Filter the rules based on their settings aka actions and conditions -func (s *listenerRuleSynthesizer) matchResAndSDKListenerRulesBySettings(matchedResAndSDKLRsByPriority []resAndSDKListenerRulePair) ([]resAndSDKListenerRulePair, []resAndSDKListenerRulePair, []*resLRDesiredActionsAndConditionsPair, error) { - var unmatchedResAndSDKListenerRulesBySettings []resAndSDKListenerRulePair - var matchedResAndSDKListenerRulesBySettings []resAndSDKListenerRulePair - var unmatchedResLRDesiredActionsAndConditionsPair []*resLRDesiredActionsAndConditionsPair - for _, resAndSDKLR := range matchedResAndSDKLRsByPriority { - desiredActions, err := buildSDKActions(resAndSDKLR.resLR.Spec.Actions, s.featureGates) - if err != nil { - return nil, nil, nil, err + for _, resLR := range unmatchedResLRs { + resLRDesiredActionsAndConditionsPair := resLRDesiredActionsAndConditionsPairs[resLR] + found := false + for i := 0; i < len(unmatchedSDKLRs); i++ { + sdkLR := unmatchedSDKLRs[i] + if cmp.Equal(resLRDesiredActionsAndConditionsPair.desiredActions, sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) && + cmp.Equal(resLRDesiredActionsAndConditionsPair.desiredConditions, sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { + sdkLRPriority, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 64) + if resLR.Spec.Priority != int32(sdkLRPriority) { + matchedResAndSDKLRsBySettings = append(matchedResAndSDKLRsBySettings, resAndSDKListenerRulePair{ + resLR: resLR, + sdkLR: sdkLR, + }) + } + unmatchedSDKLRs = append(unmatchedSDKLRs[:i], unmatchedSDKLRs[i+1:]...) + i-- + found = true + break + } } - desiredConditions := buildSDKRuleConditions(resAndSDKLR.resLR.Spec.Conditions) - if !cmp.Equal(desiredActions, resAndSDKLR.sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) || - !cmp.Equal(desiredConditions, resAndSDKLR.sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { - unmatchedResAndSDKListenerRulesBySettings = append(unmatchedResAndSDKListenerRulesBySettings, resAndSDKLR) - // Storing these so that we don't need to build SDK actions and conditions later during building create request input - unmatchedResLRDesiredActionsAndConditionsPair = append(unmatchedResLRDesiredActionsAndConditionsPair, &resLRDesiredActionsAndConditionsPair{ - desiredActions: desiredActions, - desiredConditions: desiredConditions, - }) - } else { - matchedResAndSDKListenerRulesBySettings = append(matchedResAndSDKListenerRulesBySettings, resAndSDKLR) + if !found { + resLRsToCreate = append(resLRsToCreate, resLR) } } - - return matchedResAndSDKListenerRulesBySettings, unmatchedResAndSDKListenerRulesBySettings, unmatchedResLRDesiredActionsAndConditionsPair, nil -} - -func mapSDKListenerRuleByPriority(sdkLRs []ListenerRuleWithTags) map[int32]ListenerRuleWithTags { - sdkLRByPriority := make(map[int32]ListenerRuleWithTags, len(sdkLRs)) - for _, sdkLR := range sdkLRs { - priority, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 64) - sdkLRByPriority[int32(priority)] = sdkLR - } - return sdkLRByPriority + return matchedResAndSDKLRsBySettings, resLRsToCreate, unmatchedSDKLRs, nil } func mapResListenerRuleByListenerARN(resLRs []*elbv2model.ListenerRule) (map[string][]*elbv2model.ListenerRule, error) { diff --git a/pkg/deploy/elbv2/listener_utils.go b/pkg/deploy/elbv2/listener_utils.go index 8a1d8df1f8..5a2f8bd2fa 100644 --- a/pkg/deploy/elbv2/listener_utils.go +++ b/pkg/deploy/elbv2/listener_utils.go @@ -16,6 +16,18 @@ const ( defaultWaitLSExistenceTimeout = 20 * time.Second ) +func buildResLRDesiredActionsAndConditionsPair(resLR *elbv2model.ListenerRule, featureGates config.FeatureGates) (*resLRDesiredActionsAndConditionsPair, error) { + desiredActions, err := buildSDKActions(resLR.Spec.Actions, featureGates) + if err != nil { + return nil, err + } + desiredConditions := buildSDKRuleConditions(resLR.Spec.Conditions) + return &resLRDesiredActionsAndConditionsPair{ + desiredActions: desiredActions, + desiredConditions: desiredConditions, + }, err +} + func buildSDKActions(modelActions []elbv2model.Action, featureGates config.FeatureGates) ([]elbv2types.Action, error) { var sdkActions []elbv2types.Action if len(modelActions) != 0 { From 42385aaaca5e720cf08351732628111ebf9d0d15 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 5/7] Update rule management to avoid sporadic 503 errors --- docs/install/iam_policy.json | 6 +- docs/install/iam_policy_cn.json | 6 +- docs/install/iam_policy_iso.json | 6 +- docs/install/iam_policy_isob.json | 6 +- docs/install/iam_policy_isoe.json | 3 +- docs/install/iam_policy_isof.json | 3 +- docs/install/iam_policy_us-gov.json | 3 +- pkg/deploy/elbv2/listener_rule_manager.go | 55 ++++++++++-- pkg/deploy/elbv2/listener_rule_synthesizer.go | 88 +++++++++++++++---- 9 files changed, 137 insertions(+), 39 deletions(-) diff --git a/docs/install/iam_policy.json b/docs/install/iam_policy.json index bf5d407d8d..0da4ee5647 100644 --- a/docs/install/iam_policy.json +++ b/docs/install/iam_policy.json @@ -42,8 +42,7 @@ "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeTrustStores", "elasticloadbalancing:DescribeListenerAttributes", - "elasticloadbalancing:DescribeCapacityReservation", - "elasticloadbalancing:SetRulePriorities" + "elasticloadbalancing:DescribeCapacityReservation" ], "Resource": "*" }, @@ -240,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_cn.json b/docs/install/iam_policy_cn.json index cee692a5d6..bb475f6ec6 100644 --- a/docs/install/iam_policy_cn.json +++ b/docs/install/iam_policy_cn.json @@ -42,8 +42,7 @@ "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeTrustStores", "elasticloadbalancing:DescribeListenerAttributes", - "elasticloadbalancing:DescribeCapacityReservation", - "elasticloadbalancing:SetRulePriorities" + "elasticloadbalancing:DescribeCapacityReservation" ], "Resource": "*" }, @@ -240,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_iso.json b/docs/install/iam_policy_iso.json index a0b23d96a9..46187d30e2 100644 --- a/docs/install/iam_policy_iso.json +++ b/docs/install/iam_policy_iso.json @@ -39,8 +39,7 @@ "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", - "elasticloadbalancing:DescribeTags", - "elasticloadbalancing:SetRulePriorities" + "elasticloadbalancing:DescribeTags" ], "Resource": "*" }, @@ -235,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isob.json b/docs/install/iam_policy_isob.json index c9f58a0c14..c7107d7107 100644 --- a/docs/install/iam_policy_isob.json +++ b/docs/install/iam_policy_isob.json @@ -39,8 +39,7 @@ "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", - "elasticloadbalancing:DescribeTags", - "elasticloadbalancing:SetRulePriorities" + "elasticloadbalancing:DescribeTags" ], "Resource": "*" }, @@ -235,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isoe.json b/docs/install/iam_policy_isoe.json index 9afb8d4fab..6db25833e7 100644 --- a/docs/install/iam_policy_isoe.json +++ b/docs/install/iam_policy_isoe.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isof.json b/docs/install/iam_policy_isof.json index 2c5054393b..7a43d3e405 100644 --- a/docs/install/iam_policy_isof.json +++ b/docs/install/iam_policy_isof.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_us-gov.json b/docs/install/iam_policy_us-gov.json index 828f77f4d8..7903048514 100644 --- a/docs/install/iam_policy_us-gov.json +++ b/docs/install/iam_policy_us-gov.json @@ -239,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index d8df9e1c51..2117ca608f 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/runtime" + "slices" "strconv" "time" ) @@ -20,11 +21,13 @@ import ( type ListenerRuleManager interface { Create(ctx context.Context, resLR *elbv2model.ListenerRule, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) - Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) + UpdateRules(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) + + UpdateRulesTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error - SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair) error + SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) error } // NewDefaultListenerRuleManager constructs new defaultListenerRuleManager. @@ -91,7 +94,7 @@ func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2mod return buildResListenerRuleStatus(sdkLR), nil } -func (m *defaultListenerRuleManager) Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) { +func (m *defaultListenerRuleManager) UpdateRulesTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) { if m.featureGates.Enabled(config.ListenerRulesTagging) { if err := m.updateSDKListenerRuleWithTags(ctx, resLR, sdkLR); err != nil { return elbv2model.ListenerRuleStatus{}, err @@ -100,6 +103,13 @@ func (m *defaultListenerRuleManager) Update(ctx context.Context, resLR *elbv2mod return buildResListenerRuleStatus(sdkLR), nil } +func (m *defaultListenerRuleManager) UpdateRules(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) { + if err := m.updateSDKListenerRuleWithSettings(ctx, resLR, sdkLR, desiredActionsAndConditions); err != nil { + return elbv2model.ListenerRuleStatus{}, err + } + return buildResListenerRuleStatus(sdkLR), nil +} + func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error { req := &elbv2sdk.DeleteRuleInput{ RuleArn: sdkLR.ListenerRule.RuleArn, @@ -114,10 +124,18 @@ func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerR return nil } -func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair) error { +func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) error { + var lastAvailablePriority int32 = 50000 var sdkLRs []ListenerRuleWithTags + + // Push down all the unmatched existing SDK rules on load balancer so that updated rules can take their place + for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { + sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) + sdkLRs = append(sdkLRs, sdkLR) + lastAvailablePriority-- + } + //Reprioratize matched rules by settings for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { - //Update rule priorities resAndSDKLR.sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(resAndSDKLR.resLR.Spec.Priority))) sdkLRs = append(sdkLRs, resAndSDKLR.sdkLR) } @@ -139,6 +157,26 @@ func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.C WithIgnoredTagKeys(m.externalManagedTags)) } +func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) error { + desiredActions := desiredActionsAndConditions.desiredActions + desiredConditions := desiredActionsAndConditions.desiredConditions + + req := buildSDKModifyListenerRuleInput(resLR.Spec, desiredActions, desiredConditions) + req.RuleArn = sdkLR.ListenerRule.RuleArn + m.logger.Info("modifying listener rule", + "stackID", resLR.Stack().StackID(), + "resourceID", resLR.ID(), + "arn", awssdk.ToString(sdkLR.ListenerRule.RuleArn)) + if _, err := m.elbv2Client.ModifyRuleWithContext(ctx, req); err != nil { + return err + } + m.logger.Info("modified listener rule", + "stackID", resLR.Stack().StackID(), + "resourceID", resLR.ID(), + "arn", awssdk.ToString(sdkLR.ListenerRule.RuleArn)) + return nil +} + func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { ctx := context.Background() lsARN, err := lrSpec.ListenerARN.Resolve(ctx) @@ -165,6 +203,13 @@ func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, desired return sdkObj, nil } +func buildSDKModifyListenerRuleInput(_ elbv2model.ListenerRuleSpec, desiredActions []elbv2types.Action, desiredConditions []elbv2types.RuleCondition) *elbv2sdk.ModifyRuleInput { + sdkObj := &elbv2sdk.ModifyRuleInput{} + sdkObj.Actions = desiredActions + sdkObj.Conditions = desiredConditions + return sdkObj +} + func buildSDKSetRulePrioritiesInput(sdkLRs []ListenerRuleWithTags) *elbv2sdk.SetRulePrioritiesInput { var rulePriorities []elbv2types.RulePriorityPair for _, sdkLR := range sdkLRs { diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer.go b/pkg/deploy/elbv2/listener_rule_synthesizer.go index 5fc233afa0..4c6eef1091 100644 --- a/pkg/deploy/elbv2/listener_rule_synthesizer.go +++ b/pkg/deploy/elbv2/listener_rule_synthesizer.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" @@ -66,6 +67,11 @@ func (s *listenerRuleSynthesizer) PostSynthesize(ctx context.Context) error { } func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context.Context, lsARN string, resLRs []*elbv2model.ListenerRule) error { + // Find existing listener rules on the load balancer + sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) + if err != nil { + return err + } // Build desired actions and conditions pairs for resource listener rules. resLRDesiredActionsAndConditionsPairs := make(map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair, len(resLRs)) for _, resLR := range resLRs { @@ -75,29 +81,28 @@ func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context. } resLRDesiredActionsAndConditionsPairs[resLR] = resLRDesiredActionsAndConditionsPair } - // Find existing listener rules on the load balancer - sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) - if err != nil { - return err - } - // matchedResAndSDKLRsBySettings : A slice of matched resLR and SDKLR rule pairs that have matching settings like actions and conditions - // unmatchedResLRs : A slice of resLR) that do not have a corresponding match in the sdkLRs. These rules need to be created on the load balancer. - // unmatchedSDKLRs : A slice of sdkLRs that do not have a corresponding match in the resLRs. These rules need to be deleted from the load balancer. - matchedResAndSDKLRsBySettings, unmatchedResLRs, unmatchedSDKLRs, err := s.matchResAndSDKListenerRules(resLRs, sdkLRs, resLRDesiredActionsAndConditionsPairs) + // matchedResAndSDKLRsBySettings : A slice of matched resLR and SDKLR rule pairs that have matching settings like actions and conditions. These needs to be only reprioratized to their corresponding priorities + // matchedResAndSDKLRsByPriority : A slice of matched resLR and SDKLR rule pairs that have matching priorities but not settings like actions and conditions. These needs to be modified in place to avoid any 503 errors + // unmatchedResLRs : A slice of resLR that do not have a corresponding match in the sdkLRs. These rules need to be created on the load balancer. + // unmatchedSDKLRs : A slice of sdkLRs that do not have a corresponding match in the resLRs. These rules need to be first pushed down in the priority so that the new rules are created/modified at higher priority first and then deleted from the load balancer to avoid any 503 errors. + matchedResAndSDKLRsBySettings, matchedResAndSDKLRsByPriority, unmatchedResLRs, unmatchedSDKLRs, err := s.matchResAndSDKListenerRules(resLRs, sdkLRs, resLRDesiredActionsAndConditionsPairs) if err != nil { return err } - for _, sdkLR := range unmatchedSDKLRs { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + // Re-prioritize matched listener rules. + if len(matchedResAndSDKLRsBySettings) > 0 { + err := s.lrManager.SetRulePriorities(ctx, matchedResAndSDKLRsBySettings, unmatchedSDKLRs) + if err != nil { return err } } - // Re-prioritize matched listener rules. - if len(matchedResAndSDKLRsBySettings) > 0 { - err := s.lrManager.SetRulePriorities(ctx, matchedResAndSDKLRsBySettings) + // Modify rules in place which are matching priorities + for _, resAndSDKLR := range matchedResAndSDKLRsByPriority { + lsStatus, err := s.lrManager.UpdateRules(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR, resLRDesiredActionsAndConditionsPairs[resAndSDKLR.resLR]) if err != nil { return err } + resAndSDKLR.resLR.SetStatus(lsStatus) } // Create all the new rules on the LB for _, resLR := range unmatchedResLRs { @@ -107,9 +112,15 @@ func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context. } resLR.SetStatus(lrStatus) } + // Delete all unmatched sdk LRs which were pushed down as new rules are either modified or created at higher priority + for _, sdkLR := range unmatchedSDKLRs { + if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + return err + } + } // Update existing listener rules on the load balancer for their tags for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { - lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) + lsStatus, err := s.lrManager.UpdateRulesTags(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) if err != nil { return err } @@ -144,11 +155,14 @@ type resLRDesiredActionsAndConditionsPair struct { desiredConditions []types.RuleCondition } -func (s *listenerRuleSynthesizer) matchResAndSDKListenerRules(unmatchedResLRs []*elbv2model.ListenerRule, unmatchedSDKLRs []ListenerRuleWithTags, resLRDesiredActionsAndConditionsPairs map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags, error) { +func (s *listenerRuleSynthesizer) matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, unmatchedSDKLRs []ListenerRuleWithTags, resLRDesiredActionsAndConditionsPairs map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair) ([]resAndSDKListenerRulePair, []resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags, error) { var matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair + var matchedResAndSDKLRsByPriority []resAndSDKListenerRulePair + var unmatchedResLRs []*elbv2model.ListenerRule var resLRsToCreate []*elbv2model.ListenerRule + var sdkLRsToDelete []ListenerRuleWithTags - for _, resLR := range unmatchedResLRs { + for _, resLR := range resLRs { resLRDesiredActionsAndConditionsPair := resLRDesiredActionsAndConditionsPairs[resLR] found := false for i := 0; i < len(unmatchedSDKLRs); i++ { @@ -169,10 +183,46 @@ func (s *listenerRuleSynthesizer) matchResAndSDKListenerRules(unmatchedResLRs [] } } if !found { - resLRsToCreate = append(resLRsToCreate, resLR) + unmatchedResLRs = append(unmatchedResLRs, resLR) } } - return matchedResAndSDKLRsBySettings, resLRsToCreate, unmatchedSDKLRs, nil + + resLRByPriority := mapResListenerRuleByPriority(unmatchedResLRs) + sdkLRByPriority := mapSDKListenerRuleByPriority(unmatchedSDKLRs) + resLRPriorities := sets.Int32KeySet(resLRByPriority) + sdkLRPriorities := sets.Int32KeySet(sdkLRByPriority) + for _, priority := range resLRPriorities.Intersection(sdkLRPriorities).List() { + resLR := resLRByPriority[priority] + sdkLR := sdkLRByPriority[priority] + matchedResAndSDKLRsByPriority = append(matchedResAndSDKLRsByPriority, resAndSDKListenerRulePair{ + resLR: resLR, + sdkLR: sdkLR, + }) + } + for _, priority := range resLRPriorities.Difference(sdkLRPriorities).List() { + resLRsToCreate = append(resLRsToCreate, resLRByPriority[priority]) + } + for _, priority := range sdkLRPriorities.Difference(resLRPriorities).List() { + sdkLRsToDelete = append(sdkLRsToDelete, sdkLRByPriority[priority]) + } + return matchedResAndSDKLRsBySettings, matchedResAndSDKLRsByPriority, resLRsToCreate, sdkLRsToDelete, nil +} + +func mapResListenerRuleByPriority(resLRs []*elbv2model.ListenerRule) map[int32]*elbv2model.ListenerRule { + resLRByPriority := make(map[int32]*elbv2model.ListenerRule, len(resLRs)) + for _, resLR := range resLRs { + resLRByPriority[resLR.Spec.Priority] = resLR + } + return resLRByPriority +} + +func mapSDKListenerRuleByPriority(sdkLRs []ListenerRuleWithTags) map[int32]ListenerRuleWithTags { + sdkLRByPriority := make(map[int32]ListenerRuleWithTags, len(sdkLRs)) + for _, sdkLR := range sdkLRs { + priority, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 64) + sdkLRByPriority[int32(priority)] = sdkLR + } + return sdkLRByPriority } func mapResListenerRuleByListenerARN(resLRs []*elbv2model.ListenerRule) (map[string][]*elbv2model.ListenerRule, error) { From 75412355c5468b727c2e1cedbba6c733214a6948 Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 6/7] Update rule management to avoid sporadic 503 errors --- pkg/deploy/elbv2/listener_rule_manager.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index 2117ca608f..e71c130f07 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -13,6 +13,7 @@ import ( elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/runtime" "slices" + "sort" "strconv" "time" ) @@ -128,6 +129,12 @@ func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matc var lastAvailablePriority int32 = 50000 var sdkLRs []ListenerRuleWithTags + // Sort the unmatched existing SDK rules based on their priority to be pushed down in same order + sort.Slice(unmatchedSDKLRs, func(i, j int) bool { + priorityI, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[i].ListenerRule.Priority)) + priorityJ, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[j].ListenerRule.Priority)) + return priorityI < priorityJ + }) // Push down all the unmatched existing SDK rules on load balancer so that updated rules can take their place for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) From 3f7216cb4518b19f7ed4a82a990312cffd3c014b Mon Sep 17 00:00:00 2001 From: Shraddha Bang Date: Thu, 30 Jan 2025 14:56:11 -0800 Subject: [PATCH 7/7] Update rule management to avoid sporadic 503 errors --- pkg/deploy/elbv2/listener_rule_manager.go | 44 +- .../elbv2/listener_rule_manager_test.go | 422 ++++++ .../elbv2/listener_rule_synthesizer_test.go | 1280 +++++++++++++++++ 3 files changed, 1724 insertions(+), 22 deletions(-) create mode 100644 pkg/deploy/elbv2/listener_rule_manager_test.go create mode 100644 pkg/deploy/elbv2/listener_rule_synthesizer_test.go diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index e71c130f07..45803b88f8 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -126,27 +126,7 @@ func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerR } func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) error { - var lastAvailablePriority int32 = 50000 - var sdkLRs []ListenerRuleWithTags - - // Sort the unmatched existing SDK rules based on their priority to be pushed down in same order - sort.Slice(unmatchedSDKLRs, func(i, j int) bool { - priorityI, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[i].ListenerRule.Priority)) - priorityJ, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[j].ListenerRule.Priority)) - return priorityI < priorityJ - }) - // Push down all the unmatched existing SDK rules on load balancer so that updated rules can take their place - for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { - sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) - sdkLRs = append(sdkLRs, sdkLR) - lastAvailablePriority-- - } - //Reprioratize matched rules by settings - for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { - resAndSDKLR.sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(resAndSDKLR.resLR.Spec.Priority))) - sdkLRs = append(sdkLRs, resAndSDKLR.sdkLR) - } - req := buildSDKSetRulePrioritiesInput(sdkLRs) + req := buildSDKSetRulePrioritiesInput(matchedResAndSDKLRsBySettings, unmatchedSDKLRs) m.logger.Info("setting listener rule priorities", "rule priority pairs", req.RulePriorities) if _, err := m.elbv2Client.SetRulePrioritiesWithContext(ctx, req); err != nil { @@ -217,8 +197,28 @@ func buildSDKModifyListenerRuleInput(_ elbv2model.ListenerRuleSpec, desiredActio return sdkObj } -func buildSDKSetRulePrioritiesInput(sdkLRs []ListenerRuleWithTags) *elbv2sdk.SetRulePrioritiesInput { +func buildSDKSetRulePrioritiesInput(matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) *elbv2sdk.SetRulePrioritiesInput { var rulePriorities []elbv2types.RulePriorityPair + var lastAvailablePriority int32 = 50000 + var sdkLRs []ListenerRuleWithTags + + // Sort the unmatched existing SDK rules based on their priority to be pushed down in same order + sort.Slice(unmatchedSDKLRs, func(i, j int) bool { + priorityI, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[i].ListenerRule.Priority)) + priorityJ, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[j].ListenerRule.Priority)) + return priorityI < priorityJ + }) + // Push down all the unmatched existing SDK rules on load balancer so that updated rules can take their place + for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { + sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) + sdkLRs = append(sdkLRs, sdkLR) + lastAvailablePriority-- + } + //Re-Prioritize matched rules by settings + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { + resAndSDKLR.sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(resAndSDKLR.resLR.Spec.Priority))) + sdkLRs = append(sdkLRs, resAndSDKLR.sdkLR) + } for _, sdkLR := range sdkLRs { p, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 32) rulePriorityPair := elbv2types.RulePriorityPair{ diff --git a/pkg/deploy/elbv2/listener_rule_manager_test.go b/pkg/deploy/elbv2/listener_rule_manager_test.go new file mode 100644 index 0000000000..c8b0dcc746 --- /dev/null +++ b/pkg/deploy/elbv2/listener_rule_manager_test.go @@ -0,0 +1,422 @@ +package elbv2 + +import ( + awssdk "github.com/aws/aws-sdk-go-v2/aws" + elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + coremodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" + "testing" +) + +func Test_buildSDKSetRulePrioritiesInput(t *testing.T) { + stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) + type args struct { + matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair + unmatchedSDKLRs []ListenerRuleWithTags + } + tests := []struct { + name string + args args + want *elbv2sdk.SetRulePrioritiesInput + }{ + { + name: "Only re-prioritize matched rules by settings", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + unmatchedSDKLRs: []ListenerRuleWithTags{}, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(3), + }, + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(1), + }, + }, + }, + }, + { + name: "push down unmatched sdk rules in order", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{}, + unmatchedSDKLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(50000), + }, + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(49999), + }, + }, + }, + }, + { + name: "Re-prioritize matched rules by settings and also push down unmatched sdk rules in order", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + unmatchedSDKLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-35"), + Priority: awssdk.String("35"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo35-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo35"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-16"), + Priority: awssdk.String("16"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo16-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo16"}, + }, + }, + }, + }, + }, + }, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-35"), + Priority: awssdk.Int32(50000), + }, + { + RuleArn: awssdk.String("arn-16"), + Priority: awssdk.Int32(49999), + }, + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(3), + }, + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(1), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + got := buildSDKSetRulePrioritiesInput(tt.args.matchedResAndSDKLRsBySettings, tt.args.unmatchedSDKLRs) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer_test.go b/pkg/deploy/elbv2/listener_rule_synthesizer_test.go new file mode 100644 index 0000000000..3a654fb218 --- /dev/null +++ b/pkg/deploy/elbv2/listener_rule_synthesizer_test.go @@ -0,0 +1,1280 @@ +package elbv2 + +import ( + awssdk "github.com/aws/aws-sdk-go-v2/aws" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" + "sigs.k8s.io/aws-load-balancer-controller/pkg/config" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + coremodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" + "testing" +) + +func Test_matchResAndSDKListenerRules(t *testing.T) { + stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) + type args struct { + resLRs []*elbv2model.ListenerRule + sdkLRs []ListenerRuleWithTags + } + tests := []struct { + name string + args args + want []resAndSDKListenerRulePair + want1 []resAndSDKListenerRulePair + want2 []*elbv2model.ListenerRule + want3 []ListenerRuleWithTags + wantErr error + }{ + { + name: "all listener rules has match", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: nil, + want1: nil, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "all listener rules settings has match but not priority", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want1: nil, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "some listener rules settings has match but not priority, some listener rules priority match but not settings", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want1: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + }, + }, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "some listener rules settings has match but not priority, some listener rules priority match but not settings(requires modification), some needs to be created and some sdk needs to be deleted", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo5-updated-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 4, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo6-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo6"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 5, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-4"), + Priority: awssdk.String("4"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-5"), + Priority: awssdk.String("5"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo5-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 5, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-4"), + Priority: awssdk.String("4"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + }, + }, + want1: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo5-updated-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want2: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 4, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo6-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo6"}, + }, + }, + }, + }, + }, + }, + want3: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-5"), + Priority: awssdk.String("5"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo5-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + elbv2Client := services.NewMockELBV2(ctrl) + featureGates := config.NewFeatureGates() + s := &listenerRuleSynthesizer{ + elbv2Client: elbv2Client, + featureGates: featureGates, + } + resLRDesiredActionsAndConditionsPairs := make(map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair, len(tt.args.resLRs)) + for _, resLR := range tt.args.resLRs { + resLRDesiredActionsAndConditionsPair, _ := buildResLRDesiredActionsAndConditionsPair(resLR, featureGates) + resLRDesiredActionsAndConditionsPairs[resLR] = resLRDesiredActionsAndConditionsPair + } + got, got1, got2, got3, err := s.matchResAndSDKListenerRules(tt.args.resLRs, tt.args.sdkLRs, resLRDesiredActionsAndConditionsPairs) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want1, got1) + assert.Equal(t, tt.want2, got2) + assert.Equal(t, tt.want3, got3) + } + }) + } +}