From f43a36bfbe4284aa5f4e8c0b1feac857945adbfb Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Mon, 7 Aug 2023 15:42:49 +0300 Subject: [PATCH 1/2] feat: support IPAM Manager for VPC creation --- api/v1beta1/awscluster_conversion.go | 16 ++++++ api/v1beta1/conversion.go | 4 ++ api/v1beta1/zz_generated.conversion.go | 16 +++--- api/v1beta2/awscluster_webhook.go | 9 ++++ api/v1beta2/awscluster_webhook_test.go | 27 ++++++++++ api/v1beta2/network_types.go | 15 ++++++ api/v1beta2/zz_generated.deepcopy.go | 20 +++++++ .../bootstrap/cluster_api_controller.go | 2 + .../bootstrap/fixtures/customsuffix.yaml | 2 + .../bootstrap/fixtures/default.yaml | 2 + .../fixtures/with_all_secret_backends.yaml | 2 + .../fixtures/with_allow_assume_role.yaml | 2 + .../fixtures/with_bootstrap_user.yaml | 2 + .../fixtures/with_custom_bootstrap_user.yaml | 2 + .../with_different_instance_profiles.yaml | 2 + .../bootstrap/fixtures/with_eks_console.yaml | 2 + .../fixtures/with_eks_default_roles.yaml | 2 + .../bootstrap/fixtures/with_eks_disable.yaml | 2 + .../fixtures/with_eks_kms_prefix.yaml | 2 + .../fixtures/with_extra_statements.yaml | 2 + .../bootstrap/fixtures/with_s3_bucket.yaml | 2 + .../fixtures/with_ssm_secret_backend.yaml | 2 + ...ster.x-k8s.io_awsmanagedcontrolplanes.yaml | 38 +++++++++++++ ...tructure.cluster.x-k8s.io_awsclusters.yaml | 19 +++++++ ....cluster.x-k8s.io_awsclustertemplates.yaml | 20 +++++++ pkg/cloud/filter/ec2.go | 9 ++++ pkg/cloud/services/network/vpc.go | 53 +++++++++++++++++-- 27 files changed, 262 insertions(+), 14 deletions(-) diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go index 92f0e96a9f..a48ae427b0 100644 --- a/api/v1beta1/awscluster_conversion.go +++ b/api/v1beta1/awscluster_conversion.go @@ -54,6 +54,14 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Network.SecurityGroups[role] = sg } + if restored.Spec.NetworkSpec.VPC.IPAMPool != nil { + if dst.Spec.NetworkSpec.VPC.IPAMPool == nil { + dst.Spec.NetworkSpec.VPC.IPAMPool = &infrav2.IPAMPool{} + } + + restoreIPAMPool(restored.Spec.NetworkSpec.VPC.IPAMPool, dst.Spec.NetworkSpec.VPC.IPAMPool) + } + return nil } @@ -66,6 +74,14 @@ func restoreControlPlaneLoadBalancerStatus(restored, dst *infrav2.LoadBalancer) dst.ELBListeners = restored.ELBListeners } +// restoreIPAMPool manually restores the ipam pool data. +// Assumes restored and dst are non-nil. +func restoreIPAMPool(restored, dst *infrav2.IPAMPool) { + dst.ID = restored.ID + dst.Name = restored.Name + dst.NetmaskLength = restored.NetmaskLength +} + // restoreControlPlaneLoadBalancer manually restores the control plane loadbalancer data. // Assumes restored and dst are non-nil. func restoreControlPlaneLoadBalancer(restored, dst *infrav2.AWSLoadBalancerSpec) { diff --git a/api/v1beta1/conversion.go b/api/v1beta1/conversion.go index 2f7c76b006..88cd03aa47 100644 --- a/api/v1beta1/conversion.go +++ b/api/v1beta1/conversion.go @@ -82,3 +82,7 @@ func Convert_v1beta2_LoadBalancer_To_v1beta1_ClassicELB(in *v1beta2.LoadBalancer func Convert_v1beta2_IngressRule_To_v1beta1_IngressRule(in *v1beta2.IngressRule, out *IngressRule, s conversion.Scope) error { return autoConvert_v1beta2_IngressRule_To_v1beta1_IngressRule(in, out, s) } + +func Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VPCSpec, s conversion.Scope) error { + return autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in, out, s) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 6a5e3effa0..9735c4b375 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -535,11 +535,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.VPCSpec)(nil), (*VPCSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(a.(*v1beta2.VPCSpec), b.(*VPCSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*Volume)(nil), (*v1beta2.Volume)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_Volume_To_v1beta2_Volume(a.(*Volume), b.(*v1beta2.Volume), scope) }); err != nil { @@ -600,6 +595,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.VPCSpec)(nil), (*VPCSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(a.(*v1beta2.VPCSpec), b.(*VPCSpec), scope) + }); err != nil { + return err + } return nil } @@ -2240,6 +2240,7 @@ func Convert_v1beta1_VPCSpec_To_v1beta2_VPCSpec(in *VPCSpec, out *v1beta2.VPCSpe func autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VPCSpec, s conversion.Scope) error { out.ID = in.ID out.CidrBlock = in.CidrBlock + // WARNING: in.IPAMPool requires manual conversion: does not exist in peer-type out.IPv6 = (*IPv6)(unsafe.Pointer(in.IPv6)) out.InternetGatewayID = (*string)(unsafe.Pointer(in.InternetGatewayID)) out.Tags = *(*Tags)(unsafe.Pointer(&in.Tags)) @@ -2248,11 +2249,6 @@ func autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VP return nil } -// Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec is an autogenerated conversion function. -func Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VPCSpec, s conversion.Scope) error { - return autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in, out, s) -} - func autoConvert_v1beta1_Volume_To_v1beta2_Volume(in *Volume, out *v1beta2.Volume, s conversion.Scope) error { out.DeviceName = in.DeviceName out.Size = in.Size diff --git a/api/v1beta2/awscluster_webhook.go b/api/v1beta2/awscluster_webhook.go index 51d35f18ba..6d3826db9e 100644 --- a/api/v1beta2/awscluster_webhook.go +++ b/api/v1beta2/awscluster_webhook.go @@ -189,6 +189,15 @@ func (r *AWSCluster) validateNetwork() field.ErrorList { allErrs = append(allErrs, field.Invalid(field.NewPath("subnets"), r.Spec.NetworkSpec.Subnets, "IPv6 cannot be used with unmanaged clusters at this time.")) } } + + if r.Spec.NetworkSpec.VPC.CidrBlock != "" && r.Spec.NetworkSpec.VPC.IPAMPool != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("cidrBlock"), r.Spec.NetworkSpec.VPC.CidrBlock, "cidrBlock and ipamPool cannot be used together")) + } + + if r.Spec.NetworkSpec.VPC.IPAMPool != nil && r.Spec.NetworkSpec.VPC.IPAMPool.ID == "" && r.Spec.NetworkSpec.VPC.IPAMPool.Name == "" { + allErrs = append(allErrs, field.Invalid(field.NewPath("ipamPool"), r.Spec.NetworkSpec.VPC.IPAMPool, "ipamPool must have either id or name")) + } + return allErrs } diff --git a/api/v1beta2/awscluster_webhook_test.go b/api/v1beta2/awscluster_webhook_test.go index dfce2b2c7c..977f0f4944 100644 --- a/api/v1beta2/awscluster_webhook_test.go +++ b/api/v1beta2/awscluster_webhook_test.go @@ -335,6 +335,33 @@ func TestAWSClusterValidateCreate(t *testing.T) { }, wantErr: false, }, + { + name: "rejects cidrBlock and ipamPool if set together", + cluster: &AWSCluster{ + Spec: AWSClusterSpec{ + NetworkSpec: NetworkSpec{ + VPC: VPCSpec{ + CidrBlock: "10.0.0.0/16", + IPAMPool: &IPAMPool{}, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "rejects ipamPool if id or name not set", + cluster: &AWSCluster{ + Spec: AWSClusterSpec{ + NetworkSpec: NetworkSpec{ + VPC: VPCSpec{ + IPAMPool: &IPAMPool{}, + }, + }, + }, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/api/v1beta2/network_types.go b/api/v1beta2/network_types.go index 48e1dc6b55..fd929459af 100644 --- a/api/v1beta2/network_types.go +++ b/api/v1beta2/network_types.go @@ -245,6 +245,18 @@ type IPv6 struct { EgressOnlyInternetGatewayID *string `json:"egressOnlyInternetGatewayId,omitempty"` } +// IPAMPool defines the IPAM pool to be used for VPC. +type IPAMPool struct { + // ID is the ID of the IPAM pool this provider should use to create VPC. + ID string `json:"id,omitempty"` + // Name is the name of the IPAM pool this provider should use to create VPC. + Name string `json:"name,omitempty"` + // The netmask length of the IPv4 CIDR you want to allocate to VPC from + // an Amazon VPC IP Address Manager (IPAM) pool. + // Defaults to /16 for IPv4 if not specified. + NetmaskLength int64 `json:"netmaskLength,omitempty"` +} + // VPCSpec configures an AWS VPC. type VPCSpec struct { // ID is the vpc-id of the VPC this provider should use to create resources. @@ -254,6 +266,9 @@ type VPCSpec struct { // Defaults to 10.0.0.0/16. CidrBlock string `json:"cidrBlock,omitempty"` + // IPAMPool defines the IPAM pool to be used for VPC. + IPAMPool *IPAMPool `json:"ipamPool,omitempty"` + // IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. // This field cannot be set on AWSCluster object. // +optional diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 80cb1064fa..a25277e9ef 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -1252,6 +1252,21 @@ func (in *Filter) DeepCopy() *Filter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMPool) DeepCopyInto(out *IPAMPool) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMPool. +func (in *IPAMPool) DeepCopy() *IPAMPool { + if in == nil { + return nil + } + out := new(IPAMPool) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPv6) DeepCopyInto(out *IPv6) { *out = *in @@ -1822,6 +1837,11 @@ func (in *TargetGroupSpec) DeepCopy() *TargetGroupSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VPCSpec) DeepCopyInto(out *VPCSpec) { *out = *in + if in.IPAMPool != nil { + in, out := &in.IPAMPool, &out.IPAMPool + *out = new(IPAMPool) + **out = **in + } if in.IPv6 != nil { in, out := &in.IPv6, &out.IPv6 *out = new(IPv6) diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go index 06e0b01b22..306e42b6fa 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go @@ -81,6 +81,8 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { Effect: iamv1.EffectAllow, Resource: iamv1.Resources{iamv1.Any}, Action: iamv1.Actions{ + "ec2:DescribeIpamPools", + "ec2:AllocateIpamPoolCidr", "ec2:AttachNetworkInterface", "ec2:DetachNetworkInterface", "ec2:AllocateAddress", diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml index 199b71db89..4e8363613a 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml index 2517f95f56..a80690a96b 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml index a98163dd89..ee66bea15e 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml @@ -146,6 +146,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml index 70d64f00d5..434fddba7a 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml index 4ce0f4051b..b720969774 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml @@ -146,6 +146,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml index 90c3dcd1aa..24e4cec098 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml @@ -146,6 +146,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml index b96943557b..8ee3b29977 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml index d45b795fbb..8cd2240b08 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml index 004bfb8b55..499780ff40 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml index 9fca898137..2831264078 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml index 8f6e5af704..f2c51d9578 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml index c64b9eb595..d0c9d30c39 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml @@ -146,6 +146,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml index 1f165c4b68..978b4aebf3 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml index 56f1a2eb69..a3e1bf5afc 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml @@ -140,6 +140,8 @@ Resources: PolicyDocument: Statement: - Action: + - ec2:DescribeIpamPools + - ec2:AllocateIpamPoolCidr - ec2:AttachNetworkInterface - ec2:DetachNetworkInterface - ec2:AllocateAddress diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 52f3aaa4e7..f562c1bf8d 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -492,6 +492,25 @@ spec: description: InternetGatewayID is the id of the internet gateway associated with the VPC. type: string + ipamPool: + description: IPAMPool defines the IPAM pool to be used for + VPC. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this provider + should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you want + to allocate to VPC from an Amazon VPC IP Address Manager + (IPAM) pool. Defaults to /16 for IPv4 if not specified. + format: int64 + type: integer + type: object ipv6: description: IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. This field @@ -1940,6 +1959,25 @@ spec: description: InternetGatewayID is the id of the internet gateway associated with the VPC. type: string + ipamPool: + description: IPAMPool defines the IPAM pool to be used for + VPC. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this provider + should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you want + to allocate to VPC from an Amazon VPC IP Address Manager + (IPAM) pool. Defaults to /16 for IPv4 if not specified. + format: int64 + type: integer + type: object ipv6: description: IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. This field diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml index 609fad7339..98c38222c9 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml @@ -1295,6 +1295,25 @@ spec: description: InternetGatewayID is the id of the internet gateway associated with the VPC. type: string + ipamPool: + description: IPAMPool defines the IPAM pool to be used for + VPC. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this provider + should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you want + to allocate to VPC from an Amazon VPC IP Address Manager + (IPAM) pool. Defaults to /16 for IPv4 if not specified. + format: int64 + type: integer + type: object ipv6: description: IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. This field diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml index 66f3927be0..13e2d0c2e8 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml @@ -902,6 +902,26 @@ spec: description: InternetGatewayID is the id of the internet gateway associated with the VPC. type: string + ipamPool: + description: IPAMPool defines the IPAM pool to be + used for VPC. + properties: + id: + description: ID is the ID of the IPAM pool this + provider should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool + this provider should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR + you want to allocate to VPC from an Amazon VPC + IP Address Manager (IPAM) pool. Defaults to + /16 for IPv4 if not specified. + format: int64 + type: integer + type: object ipv6: description: IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. diff --git a/pkg/cloud/filter/ec2.go b/pkg/cloud/filter/ec2.go index b1e3e65f1f..d1d886f73b 100644 --- a/pkg/cloud/filter/ec2.go +++ b/pkg/cloud/filter/ec2.go @@ -31,6 +31,7 @@ const ( filterNameState = "state" filterNameVpcAttachment = "attachment.vpc-id" filterAvailabilityZone = "availability-zone" + filterNameIPAMPoolID = "ipam-pool-id" ) // EC2 exposes the ec2 sdk related filters. @@ -88,6 +89,14 @@ func (ec2Filters) ProviderOwned(clusterName string) *ec2.Filter { } } +// IPAM returns a filter based on the id of the IPAM Pool. +func (ec2Filters) IPAM(ipamPoolID string) *ec2.Filter { + return &ec2.Filter{ + Name: aws.String(filterNameIPAMPoolID), + Values: aws.StringSlice([]string{ipamPoolID}), + } +} + // VPC returns a filter based on the id of the VPC. func (ec2Filters) VPC(vpcID string) *ec2.Filter { return &ec2.Filter{ diff --git a/pkg/cloud/services/network/vpc.go b/pkg/cloud/services/network/vpc.go index 04a720c85a..79975f4fff 100644 --- a/pkg/cloud/services/network/vpc.go +++ b/pkg/cloud/services/network/vpc.go @@ -37,7 +37,8 @@ import ( ) const ( - defaultVPCCidr = "10.0.0.0/16" + defaultVPCCidr = "10.0.0.0/16" + defaultIpamV4NetmaskLength = 16 ) func (s *Service) reconcileVPC() error { @@ -195,6 +196,35 @@ func (s *Service) ensureManagedVPCAttributes(vpc *infrav1.VPCSpec) error { return nil } +func (s *Service) getIPAMPoolID() (*string, error) { + input := &ec2.DescribeIpamPoolsInput{} + + if s.scope.VPC().IPAMPool.ID != "" { + input.Filters = append(input.Filters, filter.EC2.IPAM(s.scope.VPC().IPAMPool.ID)) + } + + if s.scope.VPC().IPAMPool.Name != "" { + input.Filters = append(input.Filters, filter.EC2.Name(s.scope.VPC().IPAMPool.Name)) + } + + output, err := s.EC2Client.DescribeIpamPools(input) + if err != nil { + record.Warnf(s.scope.InfraCluster(), "FailedCreateVPC", "Failed to describe IPAM Pools: %v", err) + return nil, errors.Wrap(err, "failed to describe IPAM Pools") + } + + switch len(output.IpamPools) { + case 0: + record.Warnf(s.scope.InfraCluster(), "FailedCreateVPC", "IPAM not found") + return nil, fmt.Errorf("IPAM not found") + case 1: + return output.IpamPools[0].IpamPoolId, nil + default: + record.Warnf(s.scope.InfraCluster(), "FailedCreateVPC", "multiple IPAMs found") + return nil, fmt.Errorf("multiple IPAMs found") + } +} + func (s *Service) createVPC() (*infrav1.VPCSpec, error) { input := &ec2.CreateVpcInput{ TagSpecifications: []*ec2.TagSpecification{ @@ -211,10 +241,25 @@ func (s *Service) createVPC() (*infrav1.VPCSpec, error) { input.AmazonProvidedIpv6CidrBlock = aws.Bool(s.scope.VPC().IsIPv6Enabled()) } - if s.scope.VPC().CidrBlock == "" { - s.scope.VPC().CidrBlock = defaultVPCCidr + if s.scope.VPC().IPAMPool != nil { + ipamPoolID, err := s.getIPAMPoolID() + if err != nil { + return nil, errors.Wrap(err, "failed to get IPAM Pool ID") + } + + if s.scope.VPC().IPAMPool.NetmaskLength == 0 { + s.scope.VPC().IPAMPool.NetmaskLength = defaultIpamV4NetmaskLength + } + + input.Ipv4IpamPoolId = ipamPoolID + input.Ipv4NetmaskLength = aws.Int64(s.scope.VPC().IPAMPool.NetmaskLength) + } else { + if s.scope.VPC().CidrBlock == "" { + s.scope.VPC().CidrBlock = defaultVPCCidr + } + + input.CidrBlock = &s.scope.VPC().CidrBlock } - input.CidrBlock = &s.scope.VPC().CidrBlock out, err := s.EC2Client.CreateVpc(input) if err != nil { From 5f272fb1061e3df9b0d491981957caeb9c826849 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Thu, 24 Aug 2023 12:26:17 +0300 Subject: [PATCH 2/2] feat: support IPAM Manager for VPC IPv6 creation --- api/v1beta1/awscluster_conversion.go | 8 +++ api/v1beta1/conversion.go | 4 ++ api/v1beta1/zz_generated.conversion.go | 36 +++++++---- api/v1beta2/network_types.go | 12 +++- api/v1beta2/zz_generated.deepcopy.go | 5 ++ ...ster.x-k8s.io_awsmanagedcontrolplanes.yaml | 60 ++++++++++++++++--- ...tructure.cluster.x-k8s.io_awsclusters.yaml | 30 ++++++++-- ....cluster.x-k8s.io_awsclustertemplates.yaml | 35 +++++++++-- .../v1beta2/awsmanagedcontrolplane_webhook.go | 15 +++++ .../awsmanagedcontrolplane_webhook_test.go | 38 ++++++++++++ pkg/cloud/services/network/vpc.go | 31 +++++++--- 11 files changed, 237 insertions(+), 37 deletions(-) diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go index a48ae427b0..f4d87fe520 100644 --- a/api/v1beta1/awscluster_conversion.go +++ b/api/v1beta1/awscluster_conversion.go @@ -62,6 +62,14 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error { restoreIPAMPool(restored.Spec.NetworkSpec.VPC.IPAMPool, dst.Spec.NetworkSpec.VPC.IPAMPool) } + if restored.Spec.NetworkSpec.VPC.IsIPv6Enabled() && restored.Spec.NetworkSpec.VPC.IPv6.IPAMPool != nil { + if dst.Spec.NetworkSpec.VPC.IPv6.IPAMPool == nil { + dst.Spec.NetworkSpec.VPC.IPv6.IPAMPool = &infrav2.IPAMPool{} + } + + restoreIPAMPool(restored.Spec.NetworkSpec.VPC.IPv6.IPAMPool, dst.Spec.NetworkSpec.VPC.IPv6.IPAMPool) + } + return nil } diff --git a/api/v1beta1/conversion.go b/api/v1beta1/conversion.go index 88cd03aa47..766c0ed528 100644 --- a/api/v1beta1/conversion.go +++ b/api/v1beta1/conversion.go @@ -86,3 +86,7 @@ func Convert_v1beta2_IngressRule_To_v1beta1_IngressRule(in *v1beta2.IngressRule, func Convert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VPCSpec, s conversion.Scope) error { return autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in, out, s) } + +func Convert_v1beta2_IPv6_To_v1beta1_IPv6(in *v1beta2.IPv6, out *IPv6, s conversion.Scope) error { + return autoConvert_v1beta2_IPv6_To_v1beta1_IPv6(in, out, s) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 9735c4b375..0aa5ec269b 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -440,11 +440,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.IPv6)(nil), (*IPv6)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_IPv6_To_v1beta1_IPv6(a.(*v1beta2.IPv6), b.(*IPv6), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*Ignition)(nil), (*v1beta2.Ignition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_Ignition_To_v1beta2_Ignition(a.(*Ignition), b.(*v1beta2.Ignition), scope) }); err != nil { @@ -575,6 +570,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.IPv6)(nil), (*IPv6)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_IPv6_To_v1beta1_IPv6(a.(*v1beta2.IPv6), b.(*IPv6), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.IngressRule)(nil), (*IngressRule)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_IngressRule_To_v1beta1_IngressRule(a.(*v1beta2.IngressRule), b.(*IngressRule), scope) }); err != nil { @@ -1885,14 +1885,10 @@ func autoConvert_v1beta2_IPv6_To_v1beta1_IPv6(in *v1beta2.IPv6, out *IPv6, s con out.CidrBlock = in.CidrBlock out.PoolID = in.PoolID out.EgressOnlyInternetGatewayID = (*string)(unsafe.Pointer(in.EgressOnlyInternetGatewayID)) + // WARNING: in.IPAMPool requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_IPv6_To_v1beta1_IPv6 is an autogenerated conversion function. -func Convert_v1beta2_IPv6_To_v1beta1_IPv6(in *v1beta2.IPv6, out *IPv6, s conversion.Scope) error { - return autoConvert_v1beta2_IPv6_To_v1beta1_IPv6(in, out, s) -} - func autoConvert_v1beta1_Ignition_To_v1beta2_Ignition(in *Ignition, out *v1beta2.Ignition, s conversion.Scope) error { out.Version = in.Version return nil @@ -2224,7 +2220,15 @@ func Convert_v1beta2_SubnetSpec_To_v1beta1_SubnetSpec(in *v1beta2.SubnetSpec, ou func autoConvert_v1beta1_VPCSpec_To_v1beta2_VPCSpec(in *VPCSpec, out *v1beta2.VPCSpec, s conversion.Scope) error { out.ID = in.ID out.CidrBlock = in.CidrBlock - out.IPv6 = (*v1beta2.IPv6)(unsafe.Pointer(in.IPv6)) + if in.IPv6 != nil { + in, out := &in.IPv6, &out.IPv6 + *out = new(v1beta2.IPv6) + if err := Convert_v1beta1_IPv6_To_v1beta2_IPv6(*in, *out, s); err != nil { + return err + } + } else { + out.IPv6 = nil + } out.InternetGatewayID = (*string)(unsafe.Pointer(in.InternetGatewayID)) out.Tags = *(*v1beta2.Tags)(unsafe.Pointer(&in.Tags)) out.AvailabilityZoneUsageLimit = (*int)(unsafe.Pointer(in.AvailabilityZoneUsageLimit)) @@ -2241,7 +2245,15 @@ func autoConvert_v1beta2_VPCSpec_To_v1beta1_VPCSpec(in *v1beta2.VPCSpec, out *VP out.ID = in.ID out.CidrBlock = in.CidrBlock // WARNING: in.IPAMPool requires manual conversion: does not exist in peer-type - out.IPv6 = (*IPv6)(unsafe.Pointer(in.IPv6)) + if in.IPv6 != nil { + in, out := &in.IPv6, &out.IPv6 + *out = new(IPv6) + if err := Convert_v1beta2_IPv6_To_v1beta1_IPv6(*in, *out, s); err != nil { + return err + } + } else { + out.IPv6 = nil + } out.InternetGatewayID = (*string)(unsafe.Pointer(in.InternetGatewayID)) out.Tags = *(*Tags)(unsafe.Pointer(&in.Tags)) out.AvailabilityZoneUsageLimit = (*int)(unsafe.Pointer(in.AvailabilityZoneUsageLimit)) diff --git a/api/v1beta2/network_types.go b/api/v1beta2/network_types.go index fd929459af..1fed93351b 100644 --- a/api/v1beta2/network_types.go +++ b/api/v1beta2/network_types.go @@ -233,16 +233,24 @@ type NetworkSpec struct { // IPv6 contains ipv6 specific settings for the network. type IPv6 struct { // CidrBlock is the CIDR block provided by Amazon when VPC has enabled IPv6. + // Mutually exclusive with IPAMPool. // +optional CidrBlock string `json:"cidrBlock,omitempty"` // PoolID is the IP pool which must be defined in case of BYO IP is defined. + // Must be specified if CidrBlock is set. + // Mutually exclusive with IPAMPool. // +optional PoolID string `json:"poolId,omitempty"` // EgressOnlyInternetGatewayID is the id of the egress only internet gateway associated with an IPv6 enabled VPC. // +optional EgressOnlyInternetGatewayID *string `json:"egressOnlyInternetGatewayId,omitempty"` + + // IPAMPool defines the IPAMv6 pool to be used for VPC. + // Mutually exclusive with CidrBlock. + // +optional + IPAMPool *IPAMPool `json:"ipamPool,omitempty"` } // IPAMPool defines the IPAM pool to be used for VPC. @@ -264,9 +272,11 @@ type VPCSpec struct { // CidrBlock is the CIDR block to be used when the provider creates a managed VPC. // Defaults to 10.0.0.0/16. + // Mutually exclusive with IPAMPool. CidrBlock string `json:"cidrBlock,omitempty"` - // IPAMPool defines the IPAM pool to be used for VPC. + // IPAMPool defines the IPAMv4 pool to be used for VPC. + // Mutually exclusive with CidrBlock. IPAMPool *IPAMPool `json:"ipamPool,omitempty"` // IPv6 contains ipv6 specific settings for the network. Supported only in managed clusters. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index a25277e9ef..4b73c3a202 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -1275,6 +1275,11 @@ func (in *IPv6) DeepCopyInto(out *IPv6) { *out = new(string) **out = **in } + if in.IPAMPool != nil { + in, out := &in.IPAMPool, &out.IPAMPool + *out = new(IPAMPool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPv6. diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index f562c1bf8d..a56a8d8cd5 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -483,6 +483,7 @@ spec: cidrBlock: description: CidrBlock is the CIDR block to be used when the provider creates a managed VPC. Defaults to 10.0.0.0/16. + Mutually exclusive with IPAMPool. type: string id: description: ID is the vpc-id of the VPC this provider should @@ -493,8 +494,8 @@ spec: associated with the VPC. type: string ipamPool: - description: IPAMPool defines the IPAM pool to be used for - VPC. + description: IPAMPool defines the IPAMv4 pool to be used for + VPC. Mutually exclusive with CidrBlock. properties: id: description: ID is the ID of the IPAM pool this provider @@ -518,16 +519,37 @@ spec: properties: cidrBlock: description: CidrBlock is the CIDR block provided by Amazon - when VPC has enabled IPv6. + when VPC has enabled IPv6. Mutually exclusive with IPAMPool. type: string egressOnlyInternetGatewayId: description: EgressOnlyInternetGatewayID is the id of the egress only internet gateway associated with an IPv6 enabled VPC. type: string + ipamPool: + description: IPAMPool defines the IPAMv6 pool to be used + for VPC. Mutually exclusive with CidrBlock. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this + provider should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you + want to allocate to VPC from an Amazon VPC IP Address + Manager (IPAM) pool. Defaults to /16 for IPv4 if + not specified. + format: int64 + type: integer + type: object poolId: description: PoolID is the IP pool which must be defined - in case of BYO IP is defined. + in case of BYO IP is defined. Must be specified if CidrBlock + is set. Mutually exclusive with IPAMPool. type: string type: object tags: @@ -1950,6 +1972,7 @@ spec: cidrBlock: description: CidrBlock is the CIDR block to be used when the provider creates a managed VPC. Defaults to 10.0.0.0/16. + Mutually exclusive with IPAMPool. type: string id: description: ID is the vpc-id of the VPC this provider should @@ -1960,8 +1983,8 @@ spec: associated with the VPC. type: string ipamPool: - description: IPAMPool defines the IPAM pool to be used for - VPC. + description: IPAMPool defines the IPAMv4 pool to be used for + VPC. Mutually exclusive with CidrBlock. properties: id: description: ID is the ID of the IPAM pool this provider @@ -1985,16 +2008,37 @@ spec: properties: cidrBlock: description: CidrBlock is the CIDR block provided by Amazon - when VPC has enabled IPv6. + when VPC has enabled IPv6. Mutually exclusive with IPAMPool. type: string egressOnlyInternetGatewayId: description: EgressOnlyInternetGatewayID is the id of the egress only internet gateway associated with an IPv6 enabled VPC. type: string + ipamPool: + description: IPAMPool defines the IPAMv6 pool to be used + for VPC. Mutually exclusive with CidrBlock. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this + provider should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you + want to allocate to VPC from an Amazon VPC IP Address + Manager (IPAM) pool. Defaults to /16 for IPv4 if + not specified. + format: int64 + type: integer + type: object poolId: description: PoolID is the IP pool which must be defined - in case of BYO IP is defined. + in case of BYO IP is defined. Must be specified if CidrBlock + is set. Mutually exclusive with IPAMPool. type: string type: object tags: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml index 98c38222c9..b8d1a602d5 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml @@ -1286,6 +1286,7 @@ spec: cidrBlock: description: CidrBlock is the CIDR block to be used when the provider creates a managed VPC. Defaults to 10.0.0.0/16. + Mutually exclusive with IPAMPool. type: string id: description: ID is the vpc-id of the VPC this provider should @@ -1296,8 +1297,8 @@ spec: associated with the VPC. type: string ipamPool: - description: IPAMPool defines the IPAM pool to be used for - VPC. + description: IPAMPool defines the IPAMv4 pool to be used for + VPC. Mutually exclusive with CidrBlock. properties: id: description: ID is the ID of the IPAM pool this provider @@ -1321,16 +1322,37 @@ spec: properties: cidrBlock: description: CidrBlock is the CIDR block provided by Amazon - when VPC has enabled IPv6. + when VPC has enabled IPv6. Mutually exclusive with IPAMPool. type: string egressOnlyInternetGatewayId: description: EgressOnlyInternetGatewayID is the id of the egress only internet gateway associated with an IPv6 enabled VPC. type: string + ipamPool: + description: IPAMPool defines the IPAMv6 pool to be used + for VPC. Mutually exclusive with CidrBlock. + properties: + id: + description: ID is the ID of the IPAM pool this provider + should use to create VPC. + type: string + name: + description: Name is the name of the IPAM pool this + provider should use to create VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 CIDR you + want to allocate to VPC from an Amazon VPC IP Address + Manager (IPAM) pool. Defaults to /16 for IPv4 if + not specified. + format: int64 + type: integer + type: object poolId: description: PoolID is the IP pool which must be defined - in case of BYO IP is defined. + in case of BYO IP is defined. Must be specified if CidrBlock + is set. Mutually exclusive with IPAMPool. type: string type: object tags: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml index 13e2d0c2e8..37b42d02d5 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml @@ -892,7 +892,7 @@ spec: cidrBlock: description: CidrBlock is the CIDR block to be used when the provider creates a managed VPC. Defaults - to 10.0.0.0/16. + to 10.0.0.0/16. Mutually exclusive with IPAMPool. type: string id: description: ID is the vpc-id of the VPC this provider @@ -903,8 +903,8 @@ spec: gateway associated with the VPC. type: string ipamPool: - description: IPAMPool defines the IPAM pool to be - used for VPC. + description: IPAMPool defines the IPAMv4 pool to be + used for VPC. Mutually exclusive with CidrBlock. properties: id: description: ID is the ID of the IPAM pool this @@ -929,16 +929,41 @@ spec: properties: cidrBlock: description: CidrBlock is the CIDR block provided - by Amazon when VPC has enabled IPv6. + by Amazon when VPC has enabled IPv6. Mutually + exclusive with IPAMPool. type: string egressOnlyInternetGatewayId: description: EgressOnlyInternetGatewayID is the id of the egress only internet gateway associated with an IPv6 enabled VPC. type: string + ipamPool: + description: IPAMPool defines the IPAMv6 pool + to be used for VPC. Mutually exclusive with + CidrBlock. + properties: + id: + description: ID is the ID of the IPAM pool + this provider should use to create VPC. + type: string + name: + description: Name is the name of the IPAM + pool this provider should use to create + VPC. + type: string + netmaskLength: + description: The netmask length of the IPv4 + CIDR you want to allocate to VPC from an + Amazon VPC IP Address Manager (IPAM) pool. + Defaults to /16 for IPv4 if not specified. + format: int64 + type: integer + type: object poolId: description: PoolID is the IP pool which must - be defined in case of BYO IP is defined. + be defined in case of BYO IP is defined. Must + be specified if CidrBlock is set. Mutually exclusive + with IPAMPool. type: string type: object tags: diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go index 83c7d984fc..f8e9cfc43b 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go @@ -396,6 +396,21 @@ func (r *AWSManagedControlPlane) validateNetwork() field.ErrorList { allErrs = append(allErrs, field.Invalid(poolField, r.Spec.NetworkSpec.VPC.IPv6.PoolID, "poolId cannot be empty if cidrBlock is set")) } + if r.Spec.NetworkSpec.VPC.IsIPv6Enabled() && r.Spec.NetworkSpec.VPC.IPv6.PoolID != "" && r.Spec.NetworkSpec.VPC.IPv6.IPAMPool != nil { + poolField := field.NewPath("spec", "networkSpec", "vpc", "ipv6", "poolId") + allErrs = append(allErrs, field.Invalid(poolField, r.Spec.NetworkSpec.VPC.IPv6.PoolID, "poolId and ipamPool cannot be used together")) + } + + if r.Spec.NetworkSpec.VPC.IsIPv6Enabled() && r.Spec.NetworkSpec.VPC.IPv6.CidrBlock != "" && r.Spec.NetworkSpec.VPC.IPv6.IPAMPool != nil { + cidrBlockField := field.NewPath("spec", "networkSpec", "vpc", "ipv6", "cidrBlock") + allErrs = append(allErrs, field.Invalid(cidrBlockField, r.Spec.NetworkSpec.VPC.IPv6.CidrBlock, "cidrBlock and ipamPool cannot be used together")) + } + + if r.Spec.NetworkSpec.VPC.IsIPv6Enabled() && r.Spec.NetworkSpec.VPC.IPv6.IPAMPool != nil && r.Spec.NetworkSpec.VPC.IPv6.IPAMPool.ID == "" && r.Spec.NetworkSpec.VPC.IPv6.IPAMPool.Name == "" { + ipamPoolField := field.NewPath("spec", "networkSpec", "vpc", "ipv6", "ipamPool") + allErrs = append(allErrs, field.Invalid(ipamPoolField, r.Spec.NetworkSpec.VPC.IPv6.IPAMPool, "ipamPool must have either id or name")) + } + return allErrs } diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook_test.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook_test.go index 0b68d21705..7b5a516a46 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook_test.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook_test.go @@ -432,6 +432,44 @@ func TestWebhookCreateIPv6Details(t *testing.T) { }, err: "poolId cannot be empty if cidrBlock is set", }, + { + name: "both ipv6 poolId and ipamPool are set", + kubeVersion: "v1.22", + networkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + IPv6: &infrav1.IPv6{ + PoolID: "not-empty", + IPAMPool: &infrav1.IPAMPool{}, + }, + }, + }, + err: "poolId and ipamPool cannot be used together", + }, + { + name: "both ipv6 cidrBlock and ipamPool are set", + kubeVersion: "v1.22", + networkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + IPv6: &infrav1.IPv6{ + CidrBlock: "not-empty", + IPAMPool: &infrav1.IPAMPool{}, + }, + }, + }, + err: "cidrBlock and ipamPool cannot be used together", + }, + { + name: "Id or name are not set for IPAMPool", + kubeVersion: "v1.22", + networkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + IPv6: &infrav1.IPv6{ + IPAMPool: &infrav1.IPAMPool{}, + }, + }, + }, + err: "ipamPool must have either id or name", + }, } for _, tc := range tests { diff --git a/pkg/cloud/services/network/vpc.go b/pkg/cloud/services/network/vpc.go index 79975f4fff..541e1dc81c 100644 --- a/pkg/cloud/services/network/vpc.go +++ b/pkg/cloud/services/network/vpc.go @@ -39,6 +39,7 @@ import ( const ( defaultVPCCidr = "10.0.0.0/16" defaultIpamV4NetmaskLength = 16 + defaultIpamV6NetmaskLength = 56 ) func (s *Service) reconcileVPC() error { @@ -232,15 +233,31 @@ func (s *Service) createVPC() (*infrav1.VPCSpec, error) { }, } - // setup BYOIP - if s.scope.VPC().IsIPv6Enabled() && s.scope.VPC().IPv6.CidrBlock != "" { - input.Ipv6CidrBlock = aws.String(s.scope.VPC().IPv6.CidrBlock) - input.Ipv6Pool = aws.String(s.scope.VPC().IPv6.PoolID) - input.AmazonProvidedIpv6CidrBlock = aws.Bool(false) - } else { - input.AmazonProvidedIpv6CidrBlock = aws.Bool(s.scope.VPC().IsIPv6Enabled()) + // IPv6-specific configuration + if s.scope.VPC().IsIPv6Enabled() { + switch { + case s.scope.VPC().IPv6.CidrBlock != "": + input.Ipv6CidrBlock = aws.String(s.scope.VPC().IPv6.CidrBlock) + input.Ipv6Pool = aws.String(s.scope.VPC().IPv6.PoolID) + input.AmazonProvidedIpv6CidrBlock = aws.Bool(false) + case s.scope.VPC().IPv6.IPAMPool != nil: + ipamPoolID, err := s.getIPAMPoolID() + if err != nil { + return nil, errors.Wrap(err, "failed to get IPAM Pool ID") + } + + if s.scope.VPC().IPv6.IPAMPool.NetmaskLength == 0 { + s.scope.VPC().IPv6.IPAMPool.NetmaskLength = defaultIpamV6NetmaskLength + } + + input.Ipv6IpamPoolId = ipamPoolID + input.Ipv6NetmaskLength = aws.Int64(s.scope.VPC().IPv6.IPAMPool.NetmaskLength) + default: + input.AmazonProvidedIpv6CidrBlock = aws.Bool(s.scope.VPC().IsIPv6Enabled()) + } } + // IPv4-specific configuration if s.scope.VPC().IPAMPool != nil { ipamPoolID, err := s.getIPAMPoolID() if err != nil {