Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/providers/v1/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ const ServiceAnnotationLoadBalancerTargetGroupAttributes = "service.beta.kuberne
// static IP addresses for the NLB. Only supported on elbv2 (NLB)
const ServiceAnnotationLoadBalancerEIPAllocations = "service.beta.kubernetes.io/aws-load-balancer-eip-allocations"

// ServiceAnnotationLoadBalancerPrivateIPv4Addresses is the annotation used on the
// service to specify a comma separated list of Private IPv4 addresses to use as
// static IP addresses for the NLB. Only supported on elbv2 (NLB)
const ServiceAnnotationLoadBalancerPrivateIPv4Addresses = "service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses"

// ServiceAnnotationLoadBalancerTargetNodeLabels is the annotation used on the service
// to specify a comma-separated list of key-value pairs which will be used to select
// the target nodes for the load balancer
Expand Down
15 changes: 13 additions & 2 deletions pkg/providers/v1/aws_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,17 @@ func (c *Cloud) ensureLoadBalancerv2(ctx context.Context, namespacedName types.N
}
}

var privateIPv4Addresses []string
if privateIPList, present := annotations[ServiceAnnotationLoadBalancerPrivateIPv4Addresses]; present {
privateIPv4Addresses = strings.Split(privateIPList, ",")
if len(privateIPv4Addresses) != len(discoveredSubnetIDs) {
return nil, fmt.Errorf("error creating load balancer: Must have same number of Private IPv4Addresses (%d) and SubnetIDs (%d)", len(privateIPv4Addresses), len(discoveredSubnetIDs))
}
}

// We are supposed to specify one subnet per AZ.
// TODO: What happens if we have more than one subnet per AZ?
createRequest.SubnetMappings = createSubnetMappings(discoveredSubnetIDs, allocationIDs)
createRequest.SubnetMappings = createSubnetMappings(discoveredSubnetIDs, allocationIDs, privateIPv4Addresses)

for k, v := range tags {
createRequest.Tags = append(createRequest.Tags, elbv2types.Tag{
Expand Down Expand Up @@ -1466,14 +1474,17 @@ func elbListenersAreEqual(actual, expected elbtypes.Listener) bool {
return true
}

func createSubnetMappings(subnetIDs []string, allocationIDs []string) []elbv2types.SubnetMapping {
func createSubnetMappings(subnetIDs []string, allocationIDs []string, privateIPv4Addresses []string) []elbv2types.SubnetMapping {
response := []elbv2types.SubnetMapping{}

for index, id := range subnetIDs {
sm := elbv2types.SubnetMapping{SubnetId: aws.String(id)}
if len(allocationIDs) > 0 {
sm.AllocationId = aws.String(allocationIDs[index])
}
if len(privateIPv4Addresses) > 0 {
sm.PrivateIPv4Address = aws.String(privateIPv4Addresses[index])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any guarantee that the order of private IP belongs to the subnet in the same index?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no guarantee, but the user must provide it in the same order.

Even in the aws-load-balancer-controller it is the same behaviour.
https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/service/annotations/#private-ipv4-addresses

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't provide it in the same order, then the loadbalancer creation will fail from aws sdk itself.

}
response = append(response, sm)
}

Expand Down
64 changes: 64 additions & 0 deletions pkg/providers/v1/aws_loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,70 @@ func TestCloud_buildTargetGroupAttributes(t *testing.T) {
}
}

func TestCreateSubnetMappings(t *testing.T) {
tests := []struct {
name string
subnetIDs []string
allocationIDs []string
privateIPv4Addresses []string
expectedSubnetMappings []elbv2types.SubnetMapping
}{
{
name: "Add allocation ids",
subnetIDs: []string{"subnet-1234", "subnet-3456"},
allocationIDs: []string{"eipalloc-2345", "eipalloc-4567"},
privateIPv4Addresses: []string{},
expectedSubnetMappings: []elbv2types.SubnetMapping{
{
SubnetId: aws.String("subnet-1234"),
AllocationId: aws.String("eipalloc-2345"),
},
{
SubnetId: aws.String("subnet-3456"),
AllocationId: aws.String("eipalloc-4567"),
},
},
},
{
name: "Add Private ip address",
subnetIDs: []string{"subnet-1234", "subnet-3456"},
allocationIDs: []string{},
privateIPv4Addresses: []string{"10.1.2.3", "10.2.3.4"},
expectedSubnetMappings: []elbv2types.SubnetMapping{
{
SubnetId: aws.String("subnet-1234"),
PrivateIPv4Address: aws.String("10.1.2.3"),
},
{
SubnetId: aws.String("subnet-3456"),
PrivateIPv4Address: aws.String("10.2.3.4"),
},
},
},
{
name: "No private ips and allocation ids",
subnetIDs: []string{"subnet-1234", "subnet-3456"},
allocationIDs: []string{},
privateIPv4Addresses: []string{},
expectedSubnetMappings: []elbv2types.SubnetMapping{
{
SubnetId: aws.String("subnet-1234"),
},
{
SubnetId: aws.String("subnet-3456"),
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actualSubnetMappings := createSubnetMappings(tt.subnetIDs, tt.allocationIDs, tt.privateIPv4Addresses)
assert.Equal(t, tt.expectedSubnetMappings, actualSubnetMappings)
})
}
}

// Unit test generated by Cursor AI
func TestGetKeyValuePropertiesFromAnnotation_TargetGroupAttributes(t *testing.T) {
tests := []struct {
Expand Down