diff --git a/pkg/deploy/elbv2/load_balancer_manager.go b/pkg/deploy/elbv2/load_balancer_manager.go index 70df7f226..fca93cefe 100644 --- a/pkg/deploy/elbv2/load_balancer_manager.go +++ b/pkg/deploy/elbv2/load_balancer_manager.go @@ -3,6 +3,7 @@ package elbv2 import ( "context" "fmt" + 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" @@ -154,6 +155,7 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithIPAddressType(ctx func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { desiredSubnets := sets.NewString() + desiredIPv6Addresses := sets.NewString() desiredSubnetsSourceNATPrefixes := sets.NewString() currentSubnetsSourceNATPrefixes := sets.NewString() for _, mapping := range resLB.Spec.SubnetMappings { @@ -161,13 +163,20 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx if mapping.SourceNatIpv6Prefix != nil { desiredSubnetsSourceNATPrefixes.Insert(awssdk.ToString(mapping.SourceNatIpv6Prefix)) } + if mapping.IPv6Address != nil { + desiredIPv6Addresses.Insert(awssdk.ToString(mapping.IPv6Address)) + } } currentSubnets := sets.NewString() + currentIPv6Addresses := sets.NewString() for _, az := range sdkLB.LoadBalancer.AvailabilityZones { currentSubnets.Insert(awssdk.ToString(az.SubnetId)) if len(az.SourceNatIpv6Prefixes) != 0 { currentSubnetsSourceNATPrefixes.Insert(az.SourceNatIpv6Prefixes[0]) } + if len(az.LoadBalancerAddresses) > 0 && az.LoadBalancerAddresses[0].IPv6Address != nil { + currentIPv6Addresses.Insert(awssdk.ToString(az.LoadBalancerAddresses[0].IPv6Address)) + } } sdkLBEnablePrefixForIpv6SourceNatValue := string(elbv2model.EnablePrefixForIpv6SourceNatOff) resLBEnablePrefixForIpv6SourceNatValue := string(elbv2model.EnablePrefixForIpv6SourceNatOff) @@ -176,7 +185,9 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx resLBEnablePrefixForIpv6SourceNatValue = string(resLB.Spec.EnablePrefixForIpv6SourceNat) - if desiredSubnets.Equal(currentSubnets) && desiredSubnetsSourceNATPrefixes.Equal(currentSubnetsSourceNATPrefixes) && sdkLBEnablePrefixForIpv6SourceNatValue == resLBEnablePrefixForIpv6SourceNatValue { + isFirstTimeIPv6Setup := currentIPv6Addresses.Len() == 0 && desiredIPv6Addresses.Len() > 0 + needsDualstackIPv6Update := isIPv4ToDualstackUpdate(resLB, sdkLB) && isFirstTimeIPv6Setup + if !needsDualstackIPv6Update && desiredSubnets.Equal(currentSubnets) && desiredSubnetsSourceNATPrefixes.Equal(currentSubnetsSourceNATPrefixes) && sdkLBEnablePrefixForIpv6SourceNatValue == resLBEnablePrefixForIpv6SourceNatValue { return nil } @@ -350,3 +361,15 @@ func isEnforceSGInboundRulesOnPrivateLinkUpdated(resLB *elbv2model.LoadBalancer, return true, currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic } + +func isIPv4ToDualstackUpdate(resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) bool { + if &resLB.Spec.IPAddressType == nil { + return false + } + desiredIPAddressType := string(resLB.Spec.IPAddressType) + currentIPAddressType := sdkLB.LoadBalancer.IpAddressType + isIPAddressTypeUpdated := desiredIPAddressType != string(currentIPAddressType) + return isIPAddressTypeUpdated && + resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork && + desiredIPAddressType == string(elbv2model.IPAddressTypeDualStack) +} diff --git a/pkg/deploy/elbv2/load_balancer_manager_test.go b/pkg/deploy/elbv2/load_balancer_manager_test.go index e35f75346..eae5a9be2 100644 --- a/pkg/deploy/elbv2/load_balancer_manager_test.go +++ b/pkg/deploy/elbv2/load_balancer_manager_test.go @@ -2,10 +2,12 @@ package elbv2 import ( "context" + "testing" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/aws/aws-sdk-go/aws" "github.com/go-logr/logr" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" - "testing" awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" @@ -609,6 +611,55 @@ func Test_defaultLoadBalancerManager_updateSDKLoadBalancerWithSubnetMappings(t * }, wantErr: nil, }, + { + name: "Set NLB IPv6 address in dualstack mode during ip address type modification ", + fields: fields{ + setSubnetsWithContextCall: setSubnetsWithContextCall{ + req: &elbv2sdk.SetSubnetsInput{ + LoadBalancerArn: awssdk.String("LoadBalancerArn"), + SubnetMappings: []elbv2types.SubnetMapping{ + { + SubnetId: awssdk.String("subnet-A"), + IPv6Address: aws.String("2600:1f18::1"), + }, + { + SubnetId: awssdk.String("subnet-B"), + IPv6Address: aws.String("2600:1f18::2"), + }, + }, + }, + resp: &elbv2sdk.SetSubnetsOutput{}, + }, + }, + args: args{ + resLB: &elbv2model.LoadBalancer{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), + Spec: elbv2model.LoadBalancerSpec{ + SubnetMappings: []elbv2model.SubnetMapping{ + { + SubnetID: "subnet-A", + IPv6Address: aws.String("2600:1f18::1"), + }, + { + SubnetID: "subnet-B", + IPv6Address: aws.String("2600:1f18::2"), + }, + }, + Type: elbv2model.LoadBalancerTypeNetwork, + IPAddressType: elbv2model.IPAddressTypeDualStack, + }, + }, + sdkLB: LoadBalancerWithTags{ + LoadBalancer: &elbv2types.LoadBalancer{ + LoadBalancerArn: awssdk.String("LoadBalancerArn"), + Type: elbv2types.LoadBalancerTypeEnumNetwork, + AvailabilityZones: []elbv2types.AvailabilityZone{{SubnetId: awssdk.String("subnet-A")}, {SubnetId: awssdk.String("subnet-B")}}, + IpAddressType: elbv2types.IpAddressTypeIpv4, + }, + }, + }, + wantErr: nil, + }, } for _, tt := range tests { @@ -629,6 +680,7 @@ func Test_defaultLoadBalancerManager_updateSDKLoadBalancerWithSubnetMappings(t * } else { assert.NoError(t, err) } + }) } }