diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go index c91939295b..95ccdc723d 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go @@ -131,6 +131,7 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeVpcs", + "ec2:DescribeDhcpOptions", "ec2:DescribeVpcAttribute", "ec2:DescribeVpcEndpoints", "ec2:DescribeVolumes", diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml index d5b5505009..901858498d 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml index a2c06a7e28..7e94429981 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 e965556dc5..0fbe28169e 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml @@ -196,6 +196,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 46b4121509..f8a2bae712 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml index 6fc278b78a..489bfd4ba0 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml @@ -196,6 +196,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 4cb1a565cf..7992953ec0 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml @@ -196,6 +196,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 1dd528076b..63d9a5d55e 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml index 80f96c8d6d..6f56f07367 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 9ce26aff22..28977affab 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml index 76af2c7aee..794ab27ec6 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 67e78b9504..74aa22f799 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml index ef5fd59980..a239b9e89c 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml @@ -196,6 +196,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml index 39bd20ef2c..28126ce41c 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes 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 472fdaacf3..1b6cfe4f54 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml @@ -190,6 +190,7 @@ Resources: - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcs + - ec2:DescribeDhcpOptions - ec2:DescribeVpcAttribute - ec2:DescribeVpcEndpoints - ec2:DescribeVolumes diff --git a/controllers/awsmachine_controller_test.go b/controllers/awsmachine_controller_test.go index 01122cad0e..52f4025d52 100644 --- a/controllers/awsmachine_controller_test.go +++ b/controllers/awsmachine_controller_test.go @@ -114,6 +114,10 @@ func TestAWSMachineReconcilerIntegrationTests(t *testing.T) { }}}) g.Expect(err).To(BeNil()) cs.Cluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}} + cs.AWSCluster.Spec.NetworkSpec.VPC = infrav1.VPCSpec{ + ID: "vpc-exists", + CidrBlock: "10.0.0.0/16", + } cs.AWSCluster.Status.Network.APIServerELB.DNSName = DNSName cs.AWSCluster.Spec.ControlPlaneLoadBalancer = &infrav1.AWSLoadBalancerSpec{ LoadBalancerType: infrav1.LoadBalancerTypeClassic, @@ -283,6 +287,10 @@ func TestAWSMachineReconcilerIntegrationTests(t *testing.T) { g.Expect(err).To(BeNil()) cs.Cluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}} cs.AWSCluster.Status.Network.APIServerELB.DNSName = DNSName + cs.AWSCluster.Spec.NetworkSpec.VPC = infrav1.VPCSpec{ + ID: "vpc-exists", + CidrBlock: "10.0.0.0/16", + } cs.AWSCluster.Spec.ControlPlaneLoadBalancer = &infrav1.AWSLoadBalancerSpec{ LoadBalancerType: infrav1.LoadBalancerTypeClassic, } diff --git a/pkg/cloud/services/ec2/instances.go b/pkg/cloud/services/ec2/instances.go index 9e77c06aef..960e960bbb 100644 --- a/pkg/cloud/services/ec2/instances.go +++ b/pkg/cloud/services/ec2/instances.go @@ -25,6 +25,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/pkg/errors" "k8s.io/utils/ptr" @@ -913,6 +914,8 @@ func (s *Service) SDKToInstance(v *ec2.Instance) (*infrav1.Instance, error) { func (s *Service) getInstanceAddresses(instance *ec2.Instance) []clusterv1.MachineAddress { addresses := []clusterv1.MachineAddress{} + // Check if the DHCP Option Set has domain name set + domainName := s.GetDHCPOptionSetDomainName(s.EC2Client, instance.VpcId) for _, eni := range instance.NetworkInterfaces { privateDNSAddress := clusterv1.MachineAddress{ Type: clusterv1.MachineInternalDNS, @@ -922,8 +925,18 @@ func (s *Service) getInstanceAddresses(instance *ec2.Instance) []clusterv1.Machi Type: clusterv1.MachineInternalIP, Address: aws.StringValue(eni.PrivateIpAddress), } + addresses = append(addresses, privateDNSAddress, privateIPAddress) + if domainName != nil { + // Add secondary private DNS Name with domain name set in DHCP Option Set + additionalPrivateDNSAddress := clusterv1.MachineAddress{ + Type: clusterv1.MachineInternalDNS, + Address: fmt.Sprintf("%s.%s", strings.Split(privateDNSAddress.Address, ".")[0], *domainName), + } + addresses = append(addresses, additionalPrivateDNSAddress) + } + // An elastic IP is attached if association is non nil pointer if eni.Association != nil { publicDNSAddress := clusterv1.MachineAddress{ @@ -937,6 +950,7 @@ func (s *Service) getInstanceAddresses(instance *ec2.Instance) []clusterv1.Machi addresses = append(addresses, publicDNSAddress, publicIPAddress) } } + return addresses } @@ -1035,6 +1049,54 @@ func (s *Service) ModifyInstanceMetadataOptions(instanceID string, options *infr return nil } +// GetDHCPOptionSetDomainName returns the domain DNS name for the VPC from the DHCP Options. +func (s *Service) GetDHCPOptionSetDomainName(ec2client ec2iface.EC2API, vpcID *string) *string { + log := s.scope.GetLogger() + + if vpcID == nil { + log.Info("vpcID is nil, skipping DHCP Option Set discovery") + return nil + } + + vpcInput := &ec2.DescribeVpcsInput{ + VpcIds: []*string{vpcID}, + } + + vpcResult, err := ec2client.DescribeVpcs(vpcInput) + if err != nil { + log.Info("failed to describe VPC, skipping DHCP Option Set discovery", "vpcID", *vpcID, "Error", err.Error()) + return nil + } + + dhcpInput := &ec2.DescribeDhcpOptionsInput{ + DhcpOptionsIds: []*string{vpcResult.Vpcs[0].DhcpOptionsId}, + } + + dhcpResult, err := ec2client.DescribeDhcpOptions(dhcpInput) + if err != nil { + log.Error(err, "failed to describe DHCP Options Set", "DhcpOptionsSet", *dhcpResult) + return nil + } + + for _, dhcpConfig := range dhcpResult.DhcpOptions[0].DhcpConfigurations { + if *dhcpConfig.Key == "domain-name" { + if len(dhcpConfig.Values) == 0 { + return nil + } + domainName := dhcpConfig.Values[0].Value + // default domainName is 'ec2.internal' in us-east-1 and 'region.compute.internal' in the other regions. + if (s.scope.Region() == "us-east-1" && *domainName == "ec2.internal") || + (s.scope.Region() != "us-east-1" && *domainName == fmt.Sprintf("%s.compute.internal", s.scope.Region())) { + return nil + } + + return domainName + } + } + + return nil +} + // filterGroups filters a list for a string. func filterGroups(list []string, strToFilter string) (newList []string) { for _, item := range list { diff --git a/pkg/cloud/services/ec2/instances_test.go b/pkg/cloud/services/ec2/instances_test.go index 37b31115b2..ce0d2be468 100644 --- a/pkg/cloud/services/ec2/instances_test.go +++ b/pkg/cloud/services/ec2/instances_test.go @@ -28,6 +28,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" + . "github.com/onsi/gomega" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -335,6 +336,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -462,6 +466,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: true, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -973,6 +980,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -1122,6 +1132,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, ImageLookupOrg: "cluster-level-image-lookup-org", }, @@ -1273,6 +1286,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, ImageLookupOrg: "cluster-level-image-lookup-org", }, @@ -3144,6 +3160,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -3266,6 +3285,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -3422,6 +3444,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -3581,6 +3606,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -3733,6 +3761,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, }, Status: infrav1.AWSClusterStatus{ @@ -3862,6 +3893,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, SSHKeyName: aws.String("specific-cluster-key-name"), }, @@ -3993,6 +4027,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, SSHKeyName: aws.String("specific-cluster-key-name"), }, @@ -4124,6 +4161,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, SSHKeyName: aws.String(""), }, @@ -4252,6 +4292,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, SSHKeyName: aws.String(""), }, @@ -4380,6 +4423,9 @@ func TestCreateInstance(t *testing.T) { IsPublic: false, }, }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, }, SSHKeyName: nil, }, @@ -4478,8 +4524,173 @@ func TestCreateInstance(t *testing.T) { } }, }, - } + { + name: "expect instace PrivateDNSName to be different when DHCP Option has domain name is set in the VPC", + machine: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"set": "node"}, + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + DataSecretName: ptr.To[string]("bootstrap-data"), + }, + }, + }, + machineConfig: &infrav1.AWSMachineSpec{ + AMI: infrav1.AMIReference{ + ID: aws.String("abc"), + }, + InstanceType: "m5.large", + }, + awsCluster: &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + ID: "subnet-1", + IsPublic: false, + }, + infrav1.SubnetSpec{ + IsPublic: false, + }, + }, + VPC: infrav1.VPCSpec{ + ID: "vpc-exists", + }, + }, + }, + Status: infrav1.AWSClusterStatus{ + Network: infrav1.NetworkStatus{ + SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ + infrav1.SecurityGroupControlPlane: { + ID: "1", + }, + infrav1.SecurityGroupNode: { + ID: "2", + }, + infrav1.SecurityGroupLB: { + ID: "3", + }, + }, + APIServerELB: infrav1.LoadBalancer{ + DNSName: "test-apiserver.us-east-1.aws", + }, + }, + }, + }, + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. // TODO: Restore these parameters, but with the tags as well + RunInstancesWithContext(context.TODO(), gomock.Any()). + Return(&ec2.Reservation{ + Instances: []*ec2.Instance{ + { + State: &ec2.InstanceState{ + Name: aws.String(ec2.InstanceStateNamePending), + }, + IamInstanceProfile: &ec2.IamInstanceProfile{ + Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + }, + InstanceId: aws.String("two"), + InstanceType: aws.String("m5.large"), + SubnetId: aws.String("subnet-1"), + ImageId: aws.String("ami-1"), + RootDeviceName: aws.String("device-1"), + BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ + { + DeviceName: aws.String("device-1"), + Ebs: &ec2.EbsInstanceBlockDevice{ + VolumeId: aws.String("volume-1"), + }, + }, + }, + Placement: &ec2.Placement{ + AvailabilityZone: &az, + }, + NetworkInterfaces: []*ec2.InstanceNetworkInterface{ + { + NetworkInterfaceId: aws.String("eni-1"), + PrivateIpAddress: aws.String("192.168.1.10"), + PrivateDnsName: aws.String("ip-192-168-1-10.ec2.internal"), + }, + }, + VpcId: aws.String("vpc-exists"), + }, + }, + }, nil) + m. + DescribeInstanceTypesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeInstanceTypesInput{ + InstanceTypes: []*string{ + aws.String("m5.large"), + }, + })). + Return(&ec2.DescribeInstanceTypesOutput{ + InstanceTypes: []*ec2.InstanceTypeInfo{ + { + ProcessorInfo: &ec2.ProcessorInfo{ + SupportedArchitectures: []*string{ + aws.String("x86_64"), + }, + }, + }, + }, + }, nil) + m. + DescribeNetworkInterfacesWithContext(context.TODO(), gomock.Any()). + Return(&ec2.DescribeNetworkInterfacesOutput{ + NetworkInterfaces: []*ec2.NetworkInterface{}, + }, nil) + m. + DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String("vpc-exists")}, + }).Return(&ec2.DescribeVpcsOutput{ + Vpcs: []*ec2.Vpc{ + { + VpcId: aws.String("vpc-exists"), + CidrBlock: aws.String("192.168.1.0/24"), + IsDefault: aws.Bool(false), + State: aws.String("available"), + DhcpOptionsId: aws.String("dopt-12345678"), + }, + }, + }, nil) + m. + DescribeDhcpOptions(&ec2.DescribeDhcpOptionsInput{ + DhcpOptionsIds: []*string{aws.String("dopt-12345678")}, + }).Return(&ec2.DescribeDhcpOptionsOutput{ + DhcpOptions: []*ec2.DhcpOptions{ + { + DhcpConfigurations: []*ec2.DhcpConfiguration{ + { + Key: aws.String("domain-name"), + Values: []*ec2.AttributeValue{ + { + Value: aws.String("example.com"), + }, + }, + }, + }, + }, + }, + }, nil) + }, + check: func(instance *infrav1.Instance, err error) { + g := NewWithT(t) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(len(instance.Addresses)).To(Equal(3)) + for _, address := range instance.Addresses { + if address.Type == clusterv1.MachineInternalIP { + g.Expect(address.Address).To(Equal("192.168.1.10")) + } + + if address.Type == clusterv1.MachineInternalDNS { + g.Expect(address.Address).To(Or(Equal("ip-192-168-1-10.ec2.internal"), Equal("ip-192-168-1-10.example.com"))) + } + } + }, + }, + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockCtrl := gomock.NewController(t) @@ -4795,3 +5006,148 @@ func TestGetFilteredSecurityGroupID(t *testing.T) { }) } } + +func TestGetDHCPOptionSetDomainName(t *testing.T) { + testsCases := []struct { + name string + vpcID string + dhcpOpt *ec2.DhcpOptions + expectedPrivateDNSName *string + mockCalls func(m *mocks.MockEC2APIMockRecorder) + }{ + { + name: "dhcpOptions with domain-name", + vpcID: "vpc-exists", + dhcpOpt: &ec2.DhcpOptions{ + DhcpConfigurations: []*ec2.DhcpConfiguration{ + { + Key: aws.String("domain-name"), + Values: []*ec2.AttributeValue{ + { + Value: aws.String("example.com"), + }, + }, + }, + }, + }, + expectedPrivateDNSName: aws.String("example.com"), + mockCalls: mockedGetPrivateDNSDomainNameFromDHCPOptionsCalls, + }, + { + name: "dhcpOptions without domain-name", + vpcID: "vpc-empty-domain-name", + dhcpOpt: &ec2.DhcpOptions{ + DhcpConfigurations: []*ec2.DhcpConfiguration{ + { + Key: aws.String("domain-name"), + Values: []*ec2.AttributeValue{}, + }, + }, + }, + expectedPrivateDNSName: nil, + mockCalls: mockedGetPrivateDNSDomainNameFromDHCPOptionsEmptyCalls, + }, + } + for _, tc := range testsCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + ec2Mock := mocks.NewMockEC2API(mockCtrl) + scheme, err := setupScheme() + g.Expect(err).ToNot(HaveOccurred()) + expect := func(m *mocks.MockEC2APIMockRecorder) { + tc.mockCalls(m) + } + expect(ec2Mock.EXPECT()) + + client := fake.NewClientBuilder().WithScheme(scheme).Build() + + cs, err := scope.NewClusterScope( + scope.ClusterScopeParams{ + Client: client, + Cluster: &clusterv1.Cluster{}, + AWSCluster: &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + ID: tc.vpcID, + }, + }, + }, + }, + }) + g.Expect(err).ToNot(HaveOccurred()) + + ec2Svc := NewService(cs) + ec2Svc.EC2Client = ec2Mock + dhcpOptsDomainName := ec2Svc.GetDHCPOptionSetDomainName(ec2Svc.EC2Client, &cs.VPC().ID) + g.Expect(dhcpOptsDomainName).To(Equal(tc.expectedPrivateDNSName)) + }) + } +} + +func mockedGetPrivateDNSDomainNameFromDHCPOptionsCalls(m *mocks.MockEC2APIMockRecorder) { + m.DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String("vpc-exists")}, + }).Return(&ec2.DescribeVpcsOutput{ + Vpcs: []*ec2.Vpc{ + { + VpcId: aws.String("vpc-exists"), + CidrBlock: aws.String("10.0.0.0/16"), + IsDefault: aws.Bool(false), + State: aws.String("available"), + DhcpOptionsId: aws.String("dopt-12345678"), + }, + }, + }, nil) + m.DescribeDhcpOptions(&ec2.DescribeDhcpOptionsInput{ + DhcpOptionsIds: []*string{aws.String("dopt-12345678")}, + }).Return(&ec2.DescribeDhcpOptionsOutput{ + DhcpOptions: []*ec2.DhcpOptions{ + { + DhcpConfigurations: []*ec2.DhcpConfiguration{ + { + Key: aws.String("domain-name"), + Values: []*ec2.AttributeValue{ + { + Value: aws.String("example.com"), + }, + }, + }, + }, + }, + }, + }, nil) +} + +func mockedGetPrivateDNSDomainNameFromDHCPOptionsEmptyCalls(m *mocks.MockEC2APIMockRecorder) { + m.DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: []*string{aws.String("vpc-empty-domain-name")}, + }).Return(&ec2.DescribeVpcsOutput{ + Vpcs: []*ec2.Vpc{ + { + VpcId: aws.String("vpc-exists"), + CidrBlock: aws.String("10.0.0.0/16"), + IsDefault: aws.Bool(false), + State: aws.String("available"), + DhcpOptionsId: aws.String("dopt-empty"), + }, + }, + }, nil) + m.DescribeDhcpOptions(&ec2.DescribeDhcpOptionsInput{ + DhcpOptionsIds: []*string{aws.String("dopt-empty")}, + }).Return(&ec2.DescribeDhcpOptionsOutput{ + DhcpOptions: []*ec2.DhcpOptions{ + { + DhcpConfigurations: []*ec2.DhcpConfiguration{ + { + Key: aws.String("domain-name"), + Values: []*ec2.AttributeValue{}, + }, + }, + }, + }, + }, nil) +}