From 3a2f7426422a1436b4f66b7559d81e4d08cb7df4 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Jan 2026 14:11:50 -0800 Subject: [PATCH 1/2] CORS-4073: validate instance type support IPv6 in dual-stack In order to attach IPv6 addresses to the ENI of EC2 instances, the instance type must support IPv6 networking. The installer must validate it by inspecting the networking capabilities of instance type via EC2 API calls. --- pkg/asset/installconfig/aws/instancetypes.go | 49 +++++++++++++------- pkg/asset/installconfig/aws/metadata.go | 4 +- pkg/asset/installconfig/aws/validation.go | 8 ++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/pkg/asset/installconfig/aws/instancetypes.go b/pkg/asset/installconfig/aws/instancetypes.go index 152b3aa7109..f50a3f4011c 100644 --- a/pkg/asset/installconfig/aws/instancetypes.go +++ b/pkg/asset/installconfig/aws/instancetypes.go @@ -4,36 +4,53 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" ) +// Networking describes the network settings for an instance type. +type Networking struct { + // IPv6Supported indicates whether IPv6 is supported. + IPv6Supported bool +} + // InstanceType holds metadata for an instance type. type InstanceType struct { DefaultVCpus int64 MemInMiB int64 Arches []string + Networking Networking } // instanceTypes retrieves a list of instance types for the given region. -func instanceTypes(ctx context.Context, session *session.Session, region string) (map[string]InstanceType, error) { +func instanceTypes(ctx context.Context, client *ec2.Client) (map[string]InstanceType, error) { types := map[string]InstanceType{} - client := ec2.New(session, aws.NewConfig().WithRegion(region)) - if err := client.DescribeInstanceTypesPagesWithContext(ctx, - &ec2.DescribeInstanceTypesInput{}, - func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool { - for _, info := range page.InstanceTypes { - types[*info.InstanceType] = InstanceType{ - DefaultVCpus: aws.Int64Value(info.VCpuInfo.DefaultVCpus), - MemInMiB: aws.Int64Value(info.MemoryInfo.SizeInMiB), - Arches: aws.StringValueSlice(info.ProcessorInfo.SupportedArchitectures), + paginator := ec2.NewDescribeInstanceTypesPaginator(client, &ec2.DescribeInstanceTypesInput{}) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list instance types: %w", err) + } + + for _, sdkTypeInfo := range page.InstanceTypes { + typeInfo := InstanceType{ + DefaultVCpus: int64(aws.ToInt32(sdkTypeInfo.VCpuInfo.DefaultVCpus)), + MemInMiB: aws.ToInt64(sdkTypeInfo.MemoryInfo.SizeInMiB), + } + + for _, arch := range sdkTypeInfo.ProcessorInfo.SupportedArchitectures { + typeInfo.Arches = append(typeInfo.Arches, string(arch)) + } + + if netInfo := sdkTypeInfo.NetworkInfo; netInfo != nil { + typeInfo.Networking = Networking{ + IPv6Supported: aws.ToBool(netInfo.Ipv6Supported), } } - return !lastPage - }); err != nil { - return nil, fmt.Errorf("fetching instance types: %w", err) + + types[string(sdkTypeInfo.InstanceType)] = typeInfo + } } return types, nil diff --git a/pkg/asset/installconfig/aws/metadata.go b/pkg/asset/installconfig/aws/metadata.go index f9ca37ad22c..25db30ac22d 100644 --- a/pkg/asset/installconfig/aws/metadata.go +++ b/pkg/asset/installconfig/aws/metadata.go @@ -377,12 +377,12 @@ func (m *Metadata) InstanceTypes(ctx context.Context) (map[string]InstanceType, defer m.mutex.Unlock() if len(m.instanceTypes) == 0 { - session, err := m.unlockedSession(ctx) + client, err := m.EC2Client(ctx) if err != nil { return nil, err } - m.instanceTypes, err = instanceTypes(ctx, session, m.Region) + m.instanceTypes, err = instanceTypes(ctx, client) if err != nil { return nil, fmt.Errorf("error listing instance types: %w", err) } diff --git a/pkg/asset/installconfig/aws/validation.go b/pkg/asset/installconfig/aws/validation.go index bb8e31e9644..e91335403ea 100644 --- a/pkg/asset/installconfig/aws/validation.go +++ b/pkg/asset/installconfig/aws/validation.go @@ -447,6 +447,14 @@ func validateMachinePool(ctx context.Context, meta *Metadata, fldPath *field.Pat errMsg := fmt.Sprintf("instance type supported architectures %s do not match specified architecture %s", sets.List(instanceArches), arch) allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg)) } + + // dual-stack: the instance type must support IPv6 networking + if platform.IPFamily.DualStackEnabled() { + if !typeMeta.Networking.IPv6Supported { + errMsg := fmt.Sprintf("instance type %s does not support IPv6 networking", pool.InstanceType) + allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg)) + } + } } else { errMsg := fmt.Sprintf("instance type %s not found", pool.InstanceType) allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg)) From adfe5e7b4a973a0d599de841a3a586a454e44727 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Jan 2026 14:11:55 -0800 Subject: [PATCH 2/2] tests: add unit tests for IPv6 networking validations --- .../installconfig/aws/validation_test.go | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/pkg/asset/installconfig/aws/validation_test.go b/pkg/asset/installconfig/aws/validation_test.go index 34d155a7534..b95112fc051 100644 --- a/pkg/asset/installconfig/aws/validation_test.go +++ b/pkg/asset/installconfig/aws/validation_test.go @@ -22,6 +22,7 @@ import ( "github.com/openshift/installer/pkg/ipnet" "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/aws" + "github.com/openshift/installer/pkg/types/network" ) var ( @@ -155,6 +156,37 @@ func TestValidate(t *testing.T) { }, expectErr: `^compute\[1\].architecture: Invalid value: "arm64": all compute machine pools must be of the same architecture$`, }, + { + name: "valid dual-stack with IPv6 supporting instance types", + installConfig: icBuild.build(icBuild.withInstanceType("m5.xlarge", "m5.xlarge", "m5.large"), icBuild.withIPFamily(network.DualStackIPv4Primary)), + availRegions: validAvailRegions(), + availZones: validAvailZones(), + instanceTypes: validInstanceTypes(), + }, + { + name: "invalid dual-stack control plane instance type does not support IPv6", + installConfig: icBuild.build(icBuild.withInstanceType("m5.xlarge", "m1.xlarge", "m5.large"), icBuild.withIPFamily(network.DualStackIPv4Primary)), + availRegions: validAvailRegions(), + availZones: validAvailZones(), + instanceTypes: validInstanceTypes(), + expectErr: `controlPlane\.platform\.aws\.type: Invalid value: "m1\.xlarge": instance type m1\.xlarge does not support IPv6 networking`, + }, + { + name: "invalid dual-stack compute instance type does not support IPv6", + installConfig: icBuild.build(icBuild.withInstanceType("m5.xlarge", "m5.xlarge", "m1.xlarge"), icBuild.withIPFamily(network.DualStackIPv4Primary)), + availRegions: validAvailRegions(), + availZones: validAvailZones(), + instanceTypes: validInstanceTypes(), + expectErr: `compute\[0\]\.platform\.aws\.type: Invalid value: "m1\.xlarge": instance type m1\.xlarge does not support IPv6 networking`, + }, + { + name: "invalid dual-stack default machine platform instance types do not support IPv6", + installConfig: icBuild.build(icBuild.withInstanceType("m1.xlarge", "", ""), icBuild.withIPFamily(network.DualStackIPv6Primary)), + availRegions: validAvailRegions(), + availZones: validAvailZones(), + instanceTypes: validInstanceTypes(), + expectErr: `controlPlane\.platform\.aws\.type: Invalid value: "m1\.xlarge": instance type m1\.xlarge does not support IPv6 networking.*compute\[0\]\.platform\.aws\.type: Invalid value: "m1\.xlarge": instance type m1\.xlarge does not support IPv6 networking`, + }, { name: "invalid edge pool, missing zones", installConfig: icBuild.build( @@ -1761,21 +1793,41 @@ func validInstanceTypes() map[string]InstanceType { DefaultVCpus: 1, MemInMiB: 2048, Arches: []string{ec2.ArchitectureTypeX8664}, + Networking: Networking{ + IPv6Supported: true, + }, }, "m5.large": { DefaultVCpus: 2, MemInMiB: 8192, Arches: []string{ec2.ArchitectureTypeX8664}, + Networking: Networking{ + IPv6Supported: true, + }, }, "m5.xlarge": { DefaultVCpus: 4, MemInMiB: 16384, Arches: []string{ec2.ArchitectureTypeX8664}, + Networking: Networking{ + IPv6Supported: true, + }, }, "m6g.xlarge": { DefaultVCpus: 4, MemInMiB: 16384, Arches: []string{ec2.ArchitectureTypeArm64}, + Networking: Networking{ + IPv6Supported: true, + }, + }, + "m1.xlarge": { + DefaultVCpus: 4, + MemInMiB: 15360, + Arches: []string{ec2.ArchitectureTypeX8664}, + Networking: Networking{ + IPv6Supported: false, + }, }, } } @@ -2025,3 +2077,9 @@ func (icBuild icBuildForAWS) withPublicIPv4Pool(publicIPv4Pool string) icOption ic.Platform.AWS.PublicIpv4Pool = publicIPv4Pool } } + +func (icBuild icBuildForAWS) withIPFamily(ipFamily network.IPFamily) icOption { + return func(ic *types.InstallConfig) { + ic.Platform.AWS.IPFamily = ipFamily + } +}