Skip to content

Commit 0991311

Browse files
committed
Look up existing target groups and listeners
Signed-off-by: Nolan Brubaker <[email protected]>
1 parent 16a6004 commit 0991311

File tree

4 files changed

+194
-82
lines changed

4 files changed

+194
-82
lines changed

controllers/awscluster_controller_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) {
303303
mockedCreateLBV2Calls(t, e)
304304
mockedDescribeInstanceCall(m)
305305
mockedDescribeAvailabilityZones(m, []string{"us-east-1c", "us-east-1a"})
306+
mockedDescribeTargetGroupsCall(t, e)
307+
mockedDescribeListenersCall(t, e)
306308
}
307309

308310
expect(ec2Mock.EXPECT(), elbv2Mock.EXPECT())

controllers/helpers_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,52 @@ func mockedCreateLBV2Calls(t *testing.T, m *mocks.MockELBV2APIMockRecorder) {
291291
})).MaxTimes(1)
292292
}
293293

294+
func mockedDescribeTargetGroupsCall(t *testing.T, m *mocks.MockELBV2APIMockRecorder) {
295+
t.Helper()
296+
m.DescribeTargetGroups(gomock.Eq(&elbv2.DescribeTargetGroupsInput{
297+
LoadBalancerArn: lbArn,
298+
})).
299+
Return(&elbv2.DescribeTargetGroupsOutput{
300+
NextMarker: new(string),
301+
TargetGroups: []*elbv2.TargetGroup{
302+
{
303+
HealthCheckEnabled: aws.Bool(true),
304+
HealthCheckIntervalSeconds: new(int64),
305+
HealthCheckPath: new(string),
306+
HealthCheckPort: new(string),
307+
HealthCheckProtocol: new(string),
308+
HealthCheckTimeoutSeconds: new(int64),
309+
HealthyThresholdCount: new(int64),
310+
IpAddressType: new(string),
311+
LoadBalancerArns: []*string{lbArn},
312+
Matcher: &elbv2.Matcher{},
313+
Port: new(int64),
314+
Protocol: new(string),
315+
ProtocolVersion: new(string),
316+
TargetGroupArn: aws.String("arn::targetgroup"),
317+
TargetGroupName: new(string),
318+
TargetType: new(string),
319+
UnhealthyThresholdCount: new(int64),
320+
VpcId: new(string),
321+
}},
322+
}, nil)
323+
}
324+
325+
func mockedDescribeListenersCall(t *testing.T, m *mocks.MockELBV2APIMockRecorder) {
326+
m.DescribeListeners(gomock.Eq(&elbv2.DescribeListenersInput{
327+
LoadBalancerArn: lbArn,
328+
})).
329+
Return(&elbv2.DescribeListenersOutput{
330+
Listeners: []*elbv2.Listener{{
331+
DefaultActions: []*elbv2.Action{{
332+
TargetGroupArn: aws.String("arn::targetgroup"),
333+
}},
334+
ListenerArn: aws.String("arn::listener"),
335+
LoadBalancerArn: lbArn,
336+
}},
337+
}, nil)
338+
}
339+
294340
func mockedDeleteLBCalls(expectV2Call bool, mv2 *mocks.MockELBV2APIMockRecorder, m *mocks.MockELBAPIMockRecorder) {
295341
if expectV2Call {
296342
mv2.DescribeLoadBalancers(gomock.Any()).Return(describeLBOutputV2, nil)

pkg/cloud/services/elb/loadbalancer.go

Lines changed: 133 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func (s *Service) reconcileV2LB(lbSpec *infrav1.AWSLoadBalancerSpec) error {
113113
lb.LoadBalancerType = lbSpec.LoadBalancerType
114114
if lb.IsManaged(s.scope.Name()) {
115115
// Reconcile the target groups and listeners from the spec and the ones currently attached to the load balancer.
116-
_, err := s.reconcileTargetGroupsAndListeners(lb, lbSpec)
116+
_, _, err := s.reconcileTargetGroupsAndListeners(lb, lbSpec)
117117
if err != nil {
118118
return errors.Wrapf(err, "failed to create target groups/listeners for load balancer %q", lb.Name)
119119
}
@@ -1529,111 +1529,162 @@ func (s *Service) reconcileV2LBTags(lb *infrav1.LoadBalancer, desiredTags map[st
15291529
return nil
15301530
}
15311531

1532-
func (s *Service) reconcileTargetGroupsAndListeners(spec *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBalancerSpec) ([]*elbv2.TargetGroup, error) {
1533-
1534-
describeInput := &elbv2.DescribeTargetGroupsInput{
1535-
LoadBalancerArn: aws.String(spec.ARN),
1536-
}
1537-
targetGroups, err := s.ELBV2Client.DescribeTargetGroups(describeInput)
1532+
// reconcileTargetGroupsAndListeners reconciles a Load Balancer's defined listeners with corresponding AWS Target Groups and Listeners.
1533+
// These are combined into a single function since they are tightly integrated.
1534+
func (s *Service) reconcileTargetGroupsAndListeners(spec *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBalancerSpec) ([]*elbv2.TargetGroup, []*elbv2.Listener, error) {
1535+
existingTargetGroups, err := s.ELBV2Client.DescribeTargetGroups(
1536+
&elbv2.DescribeTargetGroupsInput{
1537+
LoadBalancerArn: aws.String(spec.ARN),
1538+
})
15381539
if err != nil {
15391540
s.scope.Error(err, "could not describe target groups for load balancer", "arn", spec.ARN)
1540-
return nil, err
1541+
return nil, nil, err
1542+
}
1543+
1544+
existingListeners, err := s.ELBV2Client.DescribeListeners(
1545+
&elbv2.DescribeListenersInput{
1546+
LoadBalancerArn: aws.String(spec.ARN),
1547+
})
1548+
if err != nil {
1549+
s.scope.Error(err, "could not describe listeners for load balancer", "arn", spec.ARN)
15411550
}
15421551

1543-
// Target Groups already exist, no need to do additional work.
1544-
// TODO(nrb): This will need to match the infrav1.Listener type
1545-
if len(targetGroups.TargetGroups) > 0 {
1546-
return targetGroups.TargetGroups, nil
1552+
if len(existingTargetGroups.TargetGroups) == len(existingListeners.Listeners) && len(existingListeners.Listeners) == len(spec.ELBListeners) {
1553+
return existingTargetGroups.TargetGroups, existingListeners.Listeners, nil
15471554
}
15481555

1549-
groups := make([]*elbv2.TargetGroup, len(spec.ELBListeners))
1556+
createdTargetGroups := make([]*elbv2.TargetGroup, 0, len(spec.ELBListeners))
1557+
createdListeners := make([]*elbv2.Listener, 0, len(spec.ELBListeners))
15501558

15511559
// TODO(Skarlso): Add options to set up SSL.
15521560
// https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3899
15531561
for _, ln := range spec.ELBListeners {
1554-
// create the target group first
1555-
targetGroupInput := &elbv2.CreateTargetGroupInput{
1556-
Name: aws.String(ln.TargetGroup.Name),
1557-
Port: aws.Int64(ln.TargetGroup.Port),
1558-
Protocol: aws.String(ln.TargetGroup.Protocol.String()),
1559-
VpcId: aws.String(ln.TargetGroup.VpcID),
1560-
Tags: converters.MapToV2Tags(spec.Tags),
1561-
HealthCheckIntervalSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckIntervalSec),
1562-
HealthCheckTimeoutSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckTimeoutSec),
1563-
HealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerHealthThresholdCount),
1564-
UnhealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerUnhealthThresholdCount),
1565-
}
1566-
if s.scope.VPC().IsIPv6Enabled() {
1567-
targetGroupInput.IpAddressType = aws.String("ipv6")
1568-
}
1569-
if ln.TargetGroup.HealthCheck != nil {
1570-
targetGroupInput.HealthCheckEnabled = aws.Bool(true)
1571-
targetGroupInput.HealthCheckProtocol = ln.TargetGroup.HealthCheck.Protocol
1572-
targetGroupInput.HealthCheckPort = ln.TargetGroup.HealthCheck.Port
1573-
if ln.TargetGroup.HealthCheck.Path != nil {
1574-
targetGroupInput.HealthCheckPath = ln.TargetGroup.HealthCheck.Path
1562+
var group *elbv2.TargetGroup
1563+
for _, g := range existingTargetGroups.TargetGroups {
1564+
if *g.TargetGroupName == ln.TargetGroup.Name {
1565+
group = g
15751566
}
1576-
if ln.TargetGroup.HealthCheck.IntervalSeconds != nil {
1577-
targetGroupInput.HealthCheckIntervalSeconds = ln.TargetGroup.HealthCheck.IntervalSeconds
1578-
}
1579-
if ln.TargetGroup.HealthCheck.TimeoutSeconds != nil {
1580-
targetGroupInput.HealthCheckTimeoutSeconds = ln.TargetGroup.HealthCheck.TimeoutSeconds
1581-
}
1582-
if ln.TargetGroup.HealthCheck.ThresholdCount != nil {
1583-
targetGroupInput.HealthyThresholdCount = ln.TargetGroup.HealthCheck.ThresholdCount
1567+
}
1568+
// create the target group first
1569+
if group == nil {
1570+
group, err = s.createTargetGroup(ln, spec.Tags)
1571+
if err != nil {
1572+
return nil, nil, err
15841573
}
1585-
if ln.TargetGroup.HealthCheck.UnhealthyThresholdCount != nil {
1586-
targetGroupInput.UnhealthyThresholdCount = ln.TargetGroup.HealthCheck.UnhealthyThresholdCount
1574+
createdTargetGroups = append(createdTargetGroups, group)
1575+
1576+
if !lbSpec.PreserveClientIP {
1577+
targetGroupAttributeInput := &elbv2.ModifyTargetGroupAttributesInput{
1578+
TargetGroupArn: group.TargetGroupArn,
1579+
Attributes: []*elbv2.TargetGroupAttribute{
1580+
{
1581+
Key: aws.String(infrav1.TargetGroupAttributeEnablePreserveClientIP),
1582+
Value: aws.String("false"),
1583+
},
1584+
},
1585+
}
1586+
if _, err := s.ELBV2Client.ModifyTargetGroupAttributes(targetGroupAttributeInput); err != nil {
1587+
return nil, nil, errors.Wrapf(err, "failed to modify target group attribute")
1588+
}
15871589
}
15881590
}
1589-
s.scope.Debug("creating target group", "group", targetGroupInput, "listener", ln)
1590-
group, err := s.ELBV2Client.CreateTargetGroup(targetGroupInput)
1591-
if err != nil {
1592-
return nil, errors.Wrapf(err, "failed to create target group for load balancer")
1593-
}
1594-
if len(group.TargetGroups) == 0 {
1595-
return nil, errors.New("no target group was created; the returned list is empty")
1596-
}
1597-
groups = append(groups, group.TargetGroups...)
15981591

1599-
if !lbSpec.PreserveClientIP {
1600-
targetGroupAttributeInput := &elbv2.ModifyTargetGroupAttributesInput{
1601-
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
1602-
Attributes: []*elbv2.TargetGroupAttribute{
1603-
{
1604-
Key: aws.String(infrav1.TargetGroupAttributeEnablePreserveClientIP),
1605-
Value: aws.String("false"),
1606-
},
1607-
},
1592+
var listener *elbv2.Listener
1593+
for _, l := range existingListeners.Listeners {
1594+
if l.DefaultActions != nil && l.DefaultActions[0].TargetGroupArn == group.TargetGroupArn {
1595+
listener = l
16081596
}
1609-
if _, err := s.ELBV2Client.ModifyTargetGroupAttributes(targetGroupAttributeInput); err != nil {
1610-
return nil, errors.Wrapf(err, "failed to modify target group attribute")
1597+
}
1598+
1599+
if listener == nil {
1600+
listener, err = s.createListener(ln, group, spec.ARN, spec.Tags)
1601+
if err != nil {
1602+
return nil, nil, err
16111603
}
16121604
}
16131605

1614-
listenerInput := &elbv2.CreateListenerInput{
1615-
DefaultActions: []*elbv2.Action{
1616-
{
1617-
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
1618-
Type: aws.String(elbv2.ActionTypeEnumForward),
1619-
},
1606+
createdListeners = append(createdListeners, listener)
1607+
}
1608+
1609+
return createdTargetGroups, createdListeners, nil
1610+
}
1611+
1612+
// createListener creates a single Listener
1613+
func (s *Service) createListener(ln infrav1.Listener, group *elbv2.TargetGroup, lbARN string, tags map[string]string) (*elbv2.Listener, error) {
1614+
listenerInput := &elbv2.CreateListenerInput{
1615+
DefaultActions: []*elbv2.Action{
1616+
{
1617+
TargetGroupArn: group.TargetGroupArn,
1618+
Type: aws.String(elbv2.ActionTypeEnumForward),
16201619
},
1621-
LoadBalancerArn: aws.String(spec.ARN),
1622-
Port: aws.Int64(ln.Port),
1623-
Protocol: aws.String(string(ln.Protocol)),
1624-
Tags: converters.MapToV2Tags(spec.Tags),
1620+
},
1621+
LoadBalancerArn: aws.String(lbARN),
1622+
Port: aws.Int64(ln.Port),
1623+
Protocol: aws.String(string(ln.Protocol)),
1624+
Tags: converters.MapToV2Tags(tags),
1625+
}
1626+
// Create ClassicELBListeners
1627+
listener, err := s.ELBV2Client.CreateListener(listenerInput)
1628+
if err != nil {
1629+
return nil, errors.Wrap(err, "failed to create listener")
1630+
}
1631+
if len(listener.Listeners) == 0 {
1632+
return nil, errors.New("no listener was created; the returned list is empty")
1633+
}
1634+
if len(listener.Listeners) > 1 {
1635+
return nil, errors.New("more than one listener created; expected only one")
1636+
}
1637+
return listener.Listeners[0], nil
1638+
}
1639+
1640+
// createTargetGroup creates a single Target Group
1641+
func (s *Service) createTargetGroup(ln infrav1.Listener, tags map[string]string) (*elbv2.TargetGroup, error) {
1642+
targetGroupInput := &elbv2.CreateTargetGroupInput{
1643+
Name: aws.String(ln.TargetGroup.Name),
1644+
Port: aws.Int64(ln.TargetGroup.Port),
1645+
Protocol: aws.String(ln.TargetGroup.Protocol.String()),
1646+
VpcId: aws.String(ln.TargetGroup.VpcID),
1647+
Tags: converters.MapToV2Tags(tags),
1648+
HealthCheckIntervalSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckIntervalSec),
1649+
HealthCheckTimeoutSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckTimeoutSec),
1650+
HealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerHealthThresholdCount),
1651+
UnhealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerUnhealthThresholdCount),
1652+
}
1653+
if s.scope.VPC().IsIPv6Enabled() {
1654+
targetGroupInput.IpAddressType = aws.String("ipv6")
1655+
}
1656+
if ln.TargetGroup.HealthCheck != nil {
1657+
targetGroupInput.HealthCheckEnabled = aws.Bool(true)
1658+
targetGroupInput.HealthCheckProtocol = ln.TargetGroup.HealthCheck.Protocol
1659+
targetGroupInput.HealthCheckPort = ln.TargetGroup.HealthCheck.Port
1660+
if ln.TargetGroup.HealthCheck.Path != nil {
1661+
targetGroupInput.HealthCheckPath = ln.TargetGroup.HealthCheck.Path
16251662
}
1626-
// Create ClassicELBListeners
1627-
listener, err := s.ELBV2Client.CreateListener(listenerInput)
1628-
if err != nil {
1629-
return nil, errors.Wrap(err, "failed to create listener")
1663+
if ln.TargetGroup.HealthCheck.IntervalSeconds != nil {
1664+
targetGroupInput.HealthCheckIntervalSeconds = ln.TargetGroup.HealthCheck.IntervalSeconds
1665+
}
1666+
if ln.TargetGroup.HealthCheck.TimeoutSeconds != nil {
1667+
targetGroupInput.HealthCheckTimeoutSeconds = ln.TargetGroup.HealthCheck.TimeoutSeconds
16301668
}
1631-
if len(listener.Listeners) == 0 {
1632-
return nil, errors.New("no listener was created; the returned list is empty")
1669+
if ln.TargetGroup.HealthCheck.ThresholdCount != nil {
1670+
targetGroupInput.HealthyThresholdCount = ln.TargetGroup.HealthCheck.ThresholdCount
1671+
}
1672+
if ln.TargetGroup.HealthCheck.UnhealthyThresholdCount != nil {
1673+
targetGroupInput.UnhealthyThresholdCount = ln.TargetGroup.HealthCheck.UnhealthyThresholdCount
16331674
}
16341675
}
1635-
1636-
return groups, nil
1676+
s.scope.Debug("creating target group", "group", targetGroupInput, "listener", ln)
1677+
group, err := s.ELBV2Client.CreateTargetGroup(targetGroupInput)
1678+
if err != nil {
1679+
return nil, errors.Wrapf(err, "failed to create target group for load balancer")
1680+
}
1681+
if len(group.TargetGroups) == 0 {
1682+
return nil, errors.New("no target group was created; the returned list is empty")
1683+
}
1684+
if len(group.TargetGroups) > 1 {
1685+
return nil, errors.New("more than one target group created; expected only one")
1686+
}
1687+
return group.TargetGroups[0], nil
16371688
}
16381689

16391690
func (s *Service) getHealthCheckTarget() string {

pkg/cloud/services/elb/loadbalancer_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2535,6 +2535,19 @@ func TestReconcileV2LB(t *testing.T) {
25352535
},
25362536
}}).
25372537
Return(&elbv2.ModifyLoadBalancerAttributesOutput{}, nil)
2538+
2539+
m.DescribeListeners(gomock.Eq(&elbv2.DescribeListenersInput{
2540+
LoadBalancerArn: aws.String(elbArn),
2541+
})).
2542+
Return(&elbv2.DescribeListenersOutput{
2543+
Listeners: []*elbv2.Listener{{
2544+
DefaultActions: []*elbv2.Action{{
2545+
TargetGroupArn: aws.String("arn::targetgroup"),
2546+
}},
2547+
ListenerArn: aws.String("arn::listener"),
2548+
LoadBalancerArn: aws.String(elbArn),
2549+
}},
2550+
}, nil)
25382551
m.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{LoadBalancerArn: aws.String(elbArn)}).Return(
25392552
&elbv2.DescribeLoadBalancerAttributesOutput{
25402553
Attributes: []*elbv2.LoadBalancerAttribute{

0 commit comments

Comments
 (0)