Skip to content

Commit f3896f2

Browse files
committed
Reduce duplicated DescribeTargetGroups calls
1 parent 7399a96 commit f3896f2

File tree

6 files changed

+228
-9
lines changed

6 files changed

+228
-9
lines changed

pkg/deploy/elbv2/frontend_nlb_target_synthesizer.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"sigs.k8s.io/controller-runtime/pkg/client"
1717
)
1818

19-
func NewFrontendNlbTargetSynthesizer(k8sClient client.Client, trackingProvider tracking.Provider, taggingManager TaggingManager, frontendNlbTargetsManager FrontendNlbTargetsManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack, frontendNlbTargetGroupDesiredState *core.FrontendNlbTargetGroupDesiredState) *frontendNlbTargetSynthesizer {
19+
func NewFrontendNlbTargetSynthesizer(k8sClient client.Client, trackingProvider tracking.Provider, taggingManager TaggingManager, frontendNlbTargetsManager FrontendNlbTargetsManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack, frontendNlbTargetGroupDesiredState *core.FrontendNlbTargetGroupDesiredState, targetGroupCache *TargetGroupCache) *frontendNlbTargetSynthesizer {
2020
return &frontendNlbTargetSynthesizer{
2121
k8sClient: k8sClient,
2222
trackingProvider: trackingProvider,
@@ -26,6 +26,7 @@ func NewFrontendNlbTargetSynthesizer(k8sClient client.Client, trackingProvider t
2626
logger: logger,
2727
stack: stack,
2828
frontendNlbTargetGroupDesiredState: frontendNlbTargetGroupDesiredState,
29+
targetGroupCache: targetGroupCache,
2930
}
3031
}
3132

@@ -38,6 +39,7 @@ type frontendNlbTargetSynthesizer struct {
3839
logger logr.Logger
3940
stack core.Stack
4041
frontendNlbTargetGroupDesiredState *core.FrontendNlbTargetGroupDesiredState
42+
targetGroupCache *TargetGroupCache
4143
}
4244

4345
// Synthesize processes AWS target groups and deregisters ALB targets based on the desired state.
@@ -161,6 +163,13 @@ func filterALBTargetGroups(targetGroups []*elbv2model.TargetGroup) []*elbv2model
161163

162164
func (s *frontendNlbTargetSynthesizer) findSDKTargetGroups(ctx context.Context) ([]TargetGroupWithTags, error) {
163165
stackTags := s.trackingProvider.StackTags(s.stack)
164-
return s.taggingManager.ListTargetGroups(ctx,
166+
targetGroups, err := s.taggingManager.ListTargetGroups(ctx,
165167
tracking.TagsAsTagFilter(stackTags))
168+
169+
// Store in cache for the target group synthesizer to use
170+
if err == nil {
171+
s.targetGroupCache.SetSDKTargetGroups(targetGroups)
172+
}
173+
174+
return targetGroups, err
166175
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package elbv2
2+
3+
import (
4+
"sync"
5+
)
6+
7+
// TargetGroupCache provides a cache for sharing target group data between synthesizers
8+
type TargetGroupCache struct {
9+
sdkTargetGroups []TargetGroupWithTags
10+
mutex sync.RWMutex
11+
}
12+
13+
// NewTargetGroupCache creates a new target group cache
14+
func NewTargetGroupCache() *TargetGroupCache {
15+
return &TargetGroupCache{}
16+
}
17+
18+
// SetSDKTargetGroups stores the SDK target groups in the cache
19+
func (c *TargetGroupCache) SetSDKTargetGroups(targetGroups []TargetGroupWithTags) {
20+
c.mutex.Lock()
21+
defer c.mutex.Unlock()
22+
c.sdkTargetGroups = targetGroups
23+
}
24+
25+
// GetSDKTargetGroups retrieves the SDK target groups from the cache
26+
func (c *TargetGroupCache) GetSDKTargetGroups() ([]TargetGroupWithTags, bool) {
27+
c.mutex.RLock()
28+
defer c.mutex.RUnlock()
29+
if c.sdkTargetGroups == nil {
30+
return nil, false
31+
}
32+
return c.sdkTargetGroups, true
33+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package elbv2
2+
3+
import (
4+
"testing"
5+
6+
awssdk "github.com/aws/aws-sdk-go-v2/aws"
7+
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestTargetGroupCache(t *testing.T) {
12+
targetGroups := []TargetGroupWithTags{
13+
{
14+
TargetGroup: &elbv2types.TargetGroup{
15+
TargetGroupArn: awssdk.String("arn-1"),
16+
},
17+
},
18+
{
19+
TargetGroup: &elbv2types.TargetGroup{
20+
TargetGroupArn: awssdk.String("arn-2"),
21+
},
22+
},
23+
}
24+
25+
t.Run("empty cache returns no data", func(t *testing.T) {
26+
cache := NewTargetGroupCache()
27+
groups, exists := cache.GetSDKTargetGroups()
28+
assert.False(t, exists)
29+
assert.Nil(t, groups)
30+
})
31+
32+
t.Run("can store and retrieve target groups", func(t *testing.T) {
33+
cache := NewTargetGroupCache()
34+
cache.SetSDKTargetGroups(targetGroups)
35+
36+
groups, exists := cache.GetSDKTargetGroups()
37+
assert.True(t, exists)
38+
assert.Equal(t, targetGroups, groups)
39+
})
40+
41+
t.Run("can update stored target groups", func(t *testing.T) {
42+
cache := NewTargetGroupCache()
43+
44+
cache.SetSDKTargetGroups(targetGroups)
45+
46+
updatedTargetGroups := []TargetGroupWithTags{
47+
{
48+
TargetGroup: &elbv2types.TargetGroup{
49+
TargetGroupArn: awssdk.String("arn-3"),
50+
},
51+
},
52+
}
53+
cache.SetSDKTargetGroups(updatedTargetGroups)
54+
55+
groups, exists := cache.GetSDKTargetGroups()
56+
assert.True(t, exists)
57+
assert.Equal(t, updatedTargetGroups, groups)
58+
assert.NotEqual(t, targetGroups, groups)
59+
})
60+
}

pkg/deploy/elbv2/target_group_synthesizer.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
// NewTargetGroupSynthesizer constructs targetGroupSynthesizer
1818
func NewTargetGroupSynthesizer(elbv2Client services.ELBV2, trackingProvider tracking.Provider, taggingManager TaggingManager,
19-
tgManager TargetGroupManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack) *targetGroupSynthesizer {
19+
tgManager TargetGroupManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack, targetGroupCache *TargetGroupCache) *targetGroupSynthesizer {
2020
return &targetGroupSynthesizer{
2121
elbv2Client: elbv2Client,
2222
trackingProvider: trackingProvider,
@@ -26,6 +26,7 @@ func NewTargetGroupSynthesizer(elbv2Client services.ELBV2, trackingProvider trac
2626
logger: logger,
2727
stack: stack,
2828
unmatchedSDKTGs: nil,
29+
targetGroupCache: targetGroupCache,
2930
}
3031
}
3132

@@ -38,8 +39,9 @@ type targetGroupSynthesizer struct {
3839
featureGates config.FeatureGates
3940
logger logr.Logger
4041

41-
stack core.Stack
42-
unmatchedSDKTGs []TargetGroupWithTags
42+
stack core.Stack
43+
unmatchedSDKTGs []TargetGroupWithTags
44+
targetGroupCache *TargetGroupCache
4345
}
4446

4547
func (s *targetGroupSynthesizer) Synthesize(ctx context.Context) error {
@@ -87,11 +89,21 @@ func (s *targetGroupSynthesizer) PostSynthesize(ctx context.Context) error {
8789

8890
// findSDKTargetGroups will find all AWS TargetGroups created for stack.
8991
func (s *targetGroupSynthesizer) findSDKTargetGroups(ctx context.Context) ([]TargetGroupWithTags, error) {
92+
// Check if target groups are already cached from FrontendNlbTargetSynthesizer
93+
if targetGroups, exists := s.targetGroupCache.GetSDKTargetGroups(); exists {
94+
s.logger.Info("Using cached target groups from FrontendNlbTargetSynthesizer")
95+
return targetGroups, nil
96+
}
97+
98+
// If not in cache, fetch from AWS
99+
s.logger.Info("No cached target groups found, fetching from AWS")
90100
stackTags := s.trackingProvider.StackTags(s.stack)
91101
stackTagsLegacy := s.trackingProvider.StackTagsLegacy(s.stack)
92-
return s.taggingManager.ListTargetGroups(ctx,
102+
targetGroups, err := s.taggingManager.ListTargetGroups(ctx,
93103
tracking.TagsAsTagFilter(stackTags),
94104
tracking.TagsAsTagFilter(stackTagsLegacy))
105+
106+
return targetGroups, err
95107
}
96108

97109
type resAndSDKTargetGroupPair struct {

pkg/deploy/elbv2/target_group_synthesizer_test.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package elbv2
22

33
import (
4+
"context"
5+
"testing"
6+
47
awssdk "github.com/aws/aws-sdk-go-v2/aws"
58
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
9+
"github.com/go-logr/logr"
10+
"github.com/golang/mock/gomock"
611
"github.com/pkg/errors"
712
"github.com/stretchr/testify/assert"
813
"k8s.io/apimachinery/pkg/util/intstr"
914
"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
15+
"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking"
1016
coremodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core"
1117
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
12-
"testing"
18+
"sigs.k8s.io/controller-runtime/pkg/log"
1319
)
1420

1521
func Test_matchResAndSDKTargetGroups(t *testing.T) {
@@ -992,3 +998,99 @@ func Test_isSDKTargetGroupRequiresReplacementDueToNLBHealthCheck(t *testing.T) {
992998
})
993999
}
9941000
}
1001+
func Test_targetGroupSynthesizer_findSDKTargetGroups(t *testing.T) {
1002+
stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"})
1003+
logger := logr.New(log.NullLogSink{})
1004+
1005+
tests := []struct {
1006+
name string
1007+
cacheHasTargetGroups bool
1008+
cachedTargetGroups []TargetGroupWithTags
1009+
setupMocks func(mockTagging *MockTaggingManager)
1010+
wantTargetGroups []TargetGroupWithTags
1011+
wantError error
1012+
}{
1013+
{
1014+
name: "should use cached target groups when available",
1015+
cacheHasTargetGroups: true,
1016+
cachedTargetGroups: []TargetGroupWithTags{
1017+
{
1018+
TargetGroup: &elbv2types.TargetGroup{
1019+
TargetGroupArn: awssdk.String("cached-arn"),
1020+
},
1021+
},
1022+
},
1023+
wantTargetGroups: []TargetGroupWithTags{
1024+
{
1025+
TargetGroup: &elbv2types.TargetGroup{
1026+
TargetGroupArn: awssdk.String("cached-arn"),
1027+
},
1028+
},
1029+
},
1030+
},
1031+
{
1032+
name: "should fetch from AWS when cache is empty",
1033+
cacheHasTargetGroups: false,
1034+
setupMocks: func(mockTagging *MockTaggingManager) {
1035+
mockTagging.EXPECT().ListTargetGroups(gomock.Any(), gomock.Any(), gomock.Any()).Return(
1036+
[]TargetGroupWithTags{
1037+
{
1038+
TargetGroup: &elbv2types.TargetGroup{
1039+
TargetGroupArn: awssdk.String("aws-arn"),
1040+
},
1041+
},
1042+
}, nil)
1043+
},
1044+
wantTargetGroups: []TargetGroupWithTags{
1045+
{
1046+
TargetGroup: &elbv2types.TargetGroup{
1047+
TargetGroupArn: awssdk.String("aws-arn"),
1048+
},
1049+
},
1050+
},
1051+
},
1052+
{
1053+
name: "should return error from AWS",
1054+
cacheHasTargetGroups: false,
1055+
setupMocks: func(mockTagging *MockTaggingManager) {
1056+
mockTagging.EXPECT().ListTargetGroups(gomock.Any(), gomock.Any(), gomock.Any()).Return(
1057+
nil, errors.New("aws error"))
1058+
},
1059+
wantError: errors.New("aws error"),
1060+
},
1061+
}
1062+
1063+
for _, tt := range tests {
1064+
t.Run(tt.name, func(t *testing.T) {
1065+
ctrl := gomock.NewController(t)
1066+
defer ctrl.Finish()
1067+
1068+
cache := NewTargetGroupCache()
1069+
if tt.cacheHasTargetGroups {
1070+
cache.SetSDKTargetGroups(tt.cachedTargetGroups)
1071+
}
1072+
1073+
mockTaggingManager := NewMockTaggingManager(ctrl)
1074+
if tt.setupMocks != nil {
1075+
tt.setupMocks(mockTaggingManager)
1076+
}
1077+
1078+
synthesizer := &targetGroupSynthesizer{
1079+
trackingProvider: tracking.NewDefaultProvider("ingress.k8s.aws", "my-cluster"),
1080+
taggingManager: mockTaggingManager,
1081+
logger: logger,
1082+
stack: stack,
1083+
targetGroupCache: cache,
1084+
}
1085+
1086+
gotTargetGroups, gotErr := synthesizer.findSDKTargetGroups(context.Background())
1087+
1088+
if tt.wantError != nil {
1089+
assert.EqualError(t, gotErr, tt.wantError.Error())
1090+
} else {
1091+
assert.NoError(t, gotErr)
1092+
assert.Equal(t, tt.wantTargetGroups, gotTargetGroups)
1093+
}
1094+
})
1095+
}
1096+
}

pkg/deploy/stack_deployer.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,19 @@ type ResourceSynthesizer interface {
101101

102102
// Deploy a resource stack.
103103
func (d *defaultStackDeployer) Deploy(ctx context.Context, stack core.Stack, metricsCollector lbcmetrics.MetricCollector, controllerName string, frontendNlbTargetGroupDesiredState *core.FrontendNlbTargetGroupDesiredState) error {
104+
// Create a shared target group cache to avoid duplicate API calls
105+
targetGroupCache := elbv2.NewTargetGroupCache()
106+
104107
synthesizers := []ResourceSynthesizer{
105108
ec2.NewSecurityGroupSynthesizer(d.cloud.EC2(), d.trackingProvider, d.ec2TaggingManager, d.ec2SGManager, d.vpcID, d.logger, stack),
106109
}
107110

108111
if controllerName == ingressController {
109-
synthesizers = append(synthesizers, elbv2.NewFrontendNlbTargetSynthesizer(d.k8sClient, d.trackingProvider, d.elbv2TaggingManager, d.elbv2FrontendNlbTargetsManager, d.logger, d.featureGates, stack, frontendNlbTargetGroupDesiredState))
112+
synthesizers = append(synthesizers, elbv2.NewFrontendNlbTargetSynthesizer(d.k8sClient, d.trackingProvider, d.elbv2TaggingManager, d.elbv2FrontendNlbTargetsManager, d.logger, d.featureGates, stack, frontendNlbTargetGroupDesiredState, targetGroupCache))
110113
}
111114

112115
synthesizers = append(synthesizers,
113-
elbv2.NewTargetGroupSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2TGManager, d.logger, d.featureGates, stack),
116+
elbv2.NewTargetGroupSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2TGManager, d.logger, d.featureGates, stack, targetGroupCache),
114117
elbv2.NewLoadBalancerSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2LBManager, d.logger, d.featureGates, d.controllerConfig, stack),
115118
elbv2.NewListenerSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LSManager, d.logger, stack),
116119
elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, d.featureGates, stack),

0 commit comments

Comments
 (0)