diff --git a/pkg/networking/subnet_resolver.go b/pkg/networking/subnet_resolver.go index 09133fe57..a5a5f5c67 100644 --- a/pkg/networking/subnet_resolver.go +++ b/pkg/networking/subnet_resolver.go @@ -240,6 +240,7 @@ func (r *defaultSubnetsResolver) ResolveViaNameOrIDSlice(ctx context.Context, su } // listSubnetsByNameOrIDs lists subnets within vpc matching given ID or name. +// The returned subnets will be in the same order as the input subnetNameOrIDs slice. func (r *defaultSubnetsResolver) listSubnetsByNameOrIDs(ctx context.Context, subnetNameOrIDs []string) ([]ec2types.Subnet, error) { var subnetIDs []string var subnetNames []string @@ -250,21 +251,57 @@ func (r *defaultSubnetsResolver) listSubnetsByNameOrIDs(ctx context.Context, sub subnetNames = append(subnetNames, nameOrID) } } - var resolvedSubnets []ec2types.Subnet + + subnetByID := make(map[string]ec2types.Subnet) + subnetByName := make(map[string]ec2types.Subnet) + if len(subnetIDs) > 0 { subnets, err := r.listSubnetsByIDs(ctx, subnetIDs) if err != nil { return nil, err } - resolvedSubnets = append(resolvedSubnets, subnets...) + for _, subnet := range subnets { + subnetByID[awssdk.ToString(subnet.SubnetId)] = subnet + } } if len(subnetNames) > 0 { subnets, err := r.listSubnetsByNames(ctx, subnetNames) if err != nil { return nil, err } - resolvedSubnets = append(resolvedSubnets, subnets...) + for _, subnet := range subnets { + // Extract the Name tag value for mapping + var subnetName string + for _, tag := range subnet.Tags { + if awssdk.ToString(tag.Key) == "Name" { + subnetName = awssdk.ToString(tag.Value) + break + } + } + if subnetName != "" { + subnetByName[subnetName] = subnet + } + } + } + + // Reconstruct the subnet list in the original requested order + resolvedSubnets := make([]ec2types.Subnet, 0, len(subnetNameOrIDs)) + for _, nameOrID := range subnetNameOrIDs { + if strings.HasPrefix(nameOrID, "subnet-") { + if subnet, ok := subnetByID[nameOrID]; ok { + resolvedSubnets = append(resolvedSubnets, subnet) + } else { + return nil, fmt.Errorf("subnet ID not found: %s", nameOrID) + } + } else { + if subnet, ok := subnetByName[nameOrID]; ok { + resolvedSubnets = append(resolvedSubnets, subnet) + } else { + return nil, fmt.Errorf("subnet with Name tag not found: %s", nameOrID) + } + } } + return resolvedSubnets, nil } diff --git a/pkg/networking/subnet_resolver_test.go b/pkg/networking/subnet_resolver_test.go index ebcdbbef0..6b0ca2deb 100644 --- a/pkg/networking/subnet_resolver_test.go +++ b/pkg/networking/subnet_resolver_test.go @@ -2246,6 +2246,12 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { AvailabilityZoneId: awssdk.String("usw2-az1"), AvailableIpAddressCount: awssdk.Int32(8), VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-1"), + }, + }, }, { SubnetId: awssdk.String("subnet-2"), @@ -2253,6 +2259,12 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { AvailabilityZoneId: awssdk.String("usw2-az2"), AvailableIpAddressCount: awssdk.Int32(8), VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-2"), + }, + }, }, }, }, @@ -2292,6 +2304,12 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { AvailabilityZoneId: awssdk.String("usw2-az1"), AvailableIpAddressCount: awssdk.Int32(8), VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-1"), + }, + }, }, { SubnetId: awssdk.String("subnet-2"), @@ -2299,6 +2317,12 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { AvailabilityZoneId: awssdk.String("usw2-az2"), AvailableIpAddressCount: awssdk.Int32(8), VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-2"), + }, + }, }, }, }, @@ -2343,6 +2367,12 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { AvailabilityZoneId: awssdk.String("usw2-az2"), AvailableIpAddressCount: awssdk.Int32(8), VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-2"), + }, + }, }, }, }, @@ -2376,6 +2406,111 @@ func Test_defaultSubnetsResolver_ResolveViaNameOrIDSlice(t *testing.T) { }, }, want: []ec2types.Subnet{ + { + SubnetId: awssdk.String("subnet-1"), + AvailabilityZone: awssdk.String("us-west-2a"), + AvailabilityZoneId: awssdk.String("usw2-az1"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + }, + { + SubnetId: awssdk.String("subnet-2"), + AvailabilityZone: awssdk.String("us-west-2b"), + AvailabilityZoneId: awssdk.String("usw2-az2"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + Tags: []ec2types.Tag{ + { + Key: awssdk.String("Name"), + Value: awssdk.String("name-2"), + }, + }, + }, + }, + }, + { + name: "order is preserved when AWS returns subnets in different order", + fields: fields{ + clusterTagCheckEnabled: true, + albSingleSubnetEnabled: false, + discoveryByReachabilityEnabled: true, + describeSubnetsAsListCalls: []describeSubnetsAsListCall{ + { + input: &ec2sdk.DescribeSubnetsInput{ + SubnetIds: []string{"subnet-3", "subnet-1", "subnet-2"}, + }, + // AWS returns in different order than requested + output: []ec2types.Subnet{ + { + SubnetId: awssdk.String("subnet-1"), + AvailabilityZone: awssdk.String("us-west-2a"), + AvailabilityZoneId: awssdk.String("usw2-az1"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + }, + { + SubnetId: awssdk.String("subnet-2"), + AvailabilityZone: awssdk.String("us-west-2b"), + AvailabilityZoneId: awssdk.String("usw2-az2"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + }, + { + SubnetId: awssdk.String("subnet-3"), + AvailabilityZone: awssdk.String("us-west-2c"), + AvailabilityZoneId: awssdk.String("usw2-az3"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + }, + }, + }, + }, + fetchAZInfosCalls: []fetchAZInfosCall{ + { + availabilityZoneIDs: []string{"usw2-az3"}, + azInfoByAZID: map[string]ec2types.AvailabilityZone{ + "usw2-az3": { + ZoneId: awssdk.String("usw2-az3"), + ZoneType: awssdk.String("availability-zone"), + }, + }, + }, + { + availabilityZoneIDs: []string{"usw2-az1"}, + azInfoByAZID: map[string]ec2types.AvailabilityZone{ + "usw2-az1": { + ZoneId: awssdk.String("usw2-az1"), + ZoneType: awssdk.String("availability-zone"), + }, + }, + }, + { + availabilityZoneIDs: []string{"usw2-az2"}, + azInfoByAZID: map[string]ec2types.AvailabilityZone{ + "usw2-az2": { + ZoneId: awssdk.String("usw2-az2"), + ZoneType: awssdk.String("availability-zone"), + }, + }, + }, + }, + }, + args: args{ + nameOrIDs: []string{"subnet-3", "subnet-1", "subnet-2"}, + opts: []SubnetsResolveOption{ + WithSubnetsResolveLBType(elbv2model.LoadBalancerTypeApplication), + WithSubnetsResolveLBScheme(elbv2model.LoadBalancerSchemeInternetFacing), + }, + }, + // Expected result must be in the requested order, not AWS's order + want: []ec2types.Subnet{ + { + SubnetId: awssdk.String("subnet-3"), + AvailabilityZone: awssdk.String("us-west-2c"), + AvailabilityZoneId: awssdk.String("usw2-az3"), + AvailableIpAddressCount: awssdk.Int32(8), + VpcId: awssdk.String("vpc-dummy"), + }, { SubnetId: awssdk.String("subnet-1"), AvailabilityZone: awssdk.String("us-west-2a"),