diff --git a/pkg/providers/instance/suite_test.go b/pkg/providers/instance/suite_test.go index 58b3ebdecf63..e999eb6fef08 100644 --- a/pkg/providers/instance/suite_test.go +++ b/pkg/providers/instance/suite_test.go @@ -23,6 +23,7 @@ import ( "sigs.k8s.io/karpenter/pkg/test/v1alpha1" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/awslabs/operatorpkg/object" "github.com/samber/lo" @@ -205,4 +206,38 @@ var _ = Describe("InstanceProvider", func() { retrievedIDs := sets.New[string](lo.Map(instances, func(i *instance.Instance, _ int) string { return i.ID })...) Expect(ids.Equal(retrievedIDs)).To(BeTrue()) }) + It("should not consider subnet with no available IPs for instance creation", func() { + // Prepare the context, nodeClass, and nodeClaim as in the other tests + ExpectApplied(ctx, env.Client, nodeClaim, nodePool, nodeClass) + nodeClass = ExpectExists(ctx, env.Client, nodeClass) + + // Update the EC2 API mock to include this subnet + awsEnv.EC2API.DescribeSubnetsOutput.Set(&ec2.DescribeSubnetsOutput{ + Subnets: []ec2types.Subnet{ + { + SubnetId: aws.String("test-subnet-1"), + AvailabilityZone: aws.String("test-zone-1a"), + AvailableIpAddressCount: aws.Int32(0), // Exhausted + Tags: []ec2types.Tag{{Key: aws.String("Name"), Value: aws.String("test-subnet-1")}}, + }, + { + SubnetId: aws.String("test-subnet-2"), + AvailabilityZone: aws.String("test-zone-1b"), + AvailableIpAddressCount: aws.Int32(5), // Has IPs + Tags: []ec2types.Tag{{Key: aws.String("Name"), Value: aws.String("test-subnet-2")}}, + }, + }, + }) + + instanceTypes, err := cloudProvider.GetInstanceTypes(ctx, nodePool) + Expect(err).ToNot(HaveOccurred()) + + instanceTypes = lo.Filter(instanceTypes, func(i *corecloudprovider.InstanceType, _ int) bool { return i.Name == "m5.xlarge" }) + instance, err := awsEnv.InstanceProvider.Create(ctx, nodeClass, nodeClaim, nil, instanceTypes) + + // Assert that the instance is created using the subnet with available IPs + Expect(err).ToNot(HaveOccurred()) + Expect(instance).ToNot(BeNil()) + Expect(instance.SubnetID).To(Equal("test-subnet-2")) + }) }) diff --git a/pkg/providers/subnet/subnet.go b/pkg/providers/subnet/subnet.go index 959b8bc4ddc8..45c5e7848c2c 100644 --- a/pkg/providers/subnet/subnet.go +++ b/pkg/providers/subnet/subnet.go @@ -168,6 +168,13 @@ func (p *DefaultProvider) ZonalSubnetsForLaunch(ctx context.Context, nodeClass * if trackedIPs, ok := p.inflightIPs[subnet.ID]; ok { prevIPs = trackedIPs } + + // Check if the remaining IP count is insufficient to meet the predicted IP usage; + // if so, remove this subnet zone record from inflightIPs and continue to the next item in the loop。 + if prevIPs-predictedIPsUsed < 0 { + delete(zonalSubnets, subnet.Zone) + continue + } p.inflightIPs[subnet.ID] = prevIPs - predictedIPsUsed } return zonalSubnets, nil