sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/ec2/instances_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ec2 18 19 import ( 20 "encoding/base64" 21 "strings" 22 "testing" 23 24 "github.com/aws/aws-sdk-go/aws" 25 "github.com/aws/aws-sdk-go/aws/awserr" 26 "github.com/aws/aws-sdk-go/service/ec2" 27 "github.com/golang/mock/gomock" 28 "github.com/google/go-cmp/cmp" 29 "github.com/pkg/errors" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/utils/pointer" 34 "sigs.k8s.io/controller-runtime/pkg/client/fake" 35 36 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 37 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors" 38 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/filter" 39 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 40 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/userdata" 41 "sigs.k8s.io/cluster-api-provider-aws/test/mocks" 42 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 43 ) 44 45 func TestInstanceIfExists(t *testing.T) { 46 mockCtrl := gomock.NewController(t) 47 defer mockCtrl.Finish() 48 49 testCases := []struct { 50 name string 51 instanceID string 52 expect func(m *mocks.MockEC2APIMockRecorder) 53 check func(instance *infrav1.Instance, err error) 54 }{ 55 { 56 name: "does not exist", 57 instanceID: "hello", 58 expect: func(m *mocks.MockEC2APIMockRecorder) { 59 m.DescribeInstances(gomock.Eq(&ec2.DescribeInstancesInput{ 60 InstanceIds: []*string{aws.String("hello")}, 61 })). 62 Return(nil, awserrors.NewNotFound("not found")) 63 }, 64 check: func(instance *infrav1.Instance, err error) { 65 if err == nil { 66 t.Fatalf("expects error when instance could not be found: %v", err) 67 } 68 69 if instance != nil { 70 t.Fatalf("Did not expect anything but got something: %+v", instance) 71 } 72 }, 73 }, 74 { 75 name: "does not exist with bad request error", 76 instanceID: "hello-does-not-exist", 77 expect: func(m *mocks.MockEC2APIMockRecorder) { 78 m.DescribeInstances(gomock.Eq(&ec2.DescribeInstancesInput{ 79 InstanceIds: []*string{aws.String("hello-does-not-exist")}, 80 })). 81 Return(nil, awserr.New(awserrors.InvalidInstanceID, "does not exist", nil)) 82 }, 83 check: func(instance *infrav1.Instance, err error) { 84 if err == nil { 85 t.Fatalf("expects error when DescribeInstances returns error: %v", err) 86 } 87 88 if instance != nil { 89 t.Fatalf("Did not expect anything but got something: %+v", instance) 90 } 91 }, 92 }, 93 { 94 name: "instance exists", 95 instanceID: "id-1", 96 expect: func(m *mocks.MockEC2APIMockRecorder) { 97 az := "test-zone-1a" 98 m.DescribeInstances(gomock.Eq(&ec2.DescribeInstancesInput{ 99 InstanceIds: []*string{aws.String("id-1")}, 100 })). 101 Return(&ec2.DescribeInstancesOutput{ 102 Reservations: []*ec2.Reservation{ 103 { 104 Instances: []*ec2.Instance{ 105 { 106 InstanceId: aws.String("id-1"), 107 InstanceType: aws.String("m5.large"), 108 SubnetId: aws.String("subnet-1"), 109 ImageId: aws.String("ami-1"), 110 IamInstanceProfile: &ec2.IamInstanceProfile{ 111 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 112 }, 113 State: &ec2.InstanceState{ 114 Code: aws.Int64(16), 115 Name: aws.String(ec2.StateAvailable), 116 }, 117 RootDeviceName: aws.String("device-1"), 118 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 119 { 120 DeviceName: aws.String("device-1"), 121 Ebs: &ec2.EbsInstanceBlockDevice{ 122 VolumeId: aws.String("volume-1"), 123 }, 124 }, 125 }, 126 Placement: &ec2.Placement{ 127 AvailabilityZone: &az, 128 }, 129 }, 130 }, 131 }, 132 }, 133 }, nil) 134 }, 135 check: func(instance *infrav1.Instance, err error) { 136 if err != nil { 137 t.Fatalf("did not expect error: %v", err) 138 } 139 140 if instance == nil { 141 t.Fatalf("expected instance but got nothing") 142 } 143 144 if instance.ID != "id-1" { 145 t.Fatalf("expected id-1 but got: %v", instance.ID) 146 } 147 }, 148 }, 149 { 150 name: "error describing instances", 151 instanceID: "one", 152 expect: func(m *mocks.MockEC2APIMockRecorder) { 153 m.DescribeInstances(&ec2.DescribeInstancesInput{ 154 InstanceIds: []*string{aws.String("one")}, 155 }). 156 Return(nil, errors.New("some unknown error")) 157 }, 158 check: func(i *infrav1.Instance, err error) { 159 if err == nil { 160 t.Fatalf("expected an error but got none.") 161 } 162 }, 163 }, 164 } 165 166 for _, tc := range testCases { 167 t.Run(tc.name, func(t *testing.T) { 168 ec2Mock := mocks.NewMockEC2API(mockCtrl) 169 170 scheme := runtime.NewScheme() 171 _ = infrav1.AddToScheme(scheme) 172 client := fake.NewClientBuilder().WithScheme(scheme).Build() 173 174 scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ 175 Client: client, 176 Cluster: &clusterv1.Cluster{}, 177 AWSCluster: &infrav1.AWSCluster{ 178 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 179 Spec: infrav1.AWSClusterSpec{ 180 NetworkSpec: infrav1.NetworkSpec{ 181 VPC: infrav1.VPCSpec{ 182 ID: "test-vpc", 183 }, 184 }, 185 }, 186 }, 187 }) 188 if err != nil { 189 t.Fatalf("Failed to create test context: %v", err) 190 } 191 192 tc.expect(ec2Mock.EXPECT()) 193 194 s := NewService(scope) 195 s.EC2Client = ec2Mock 196 197 instance, err := s.InstanceIfExists(&tc.instanceID) 198 tc.check(instance, err) 199 }) 200 } 201 } 202 203 func TestTerminateInstance(t *testing.T) { 204 mockCtrl := gomock.NewController(t) 205 defer mockCtrl.Finish() 206 207 instanceNotFoundError := errors.New("instance not found") 208 209 testCases := []struct { 210 name string 211 instanceID string 212 expect func(m *mocks.MockEC2APIMockRecorder) 213 check func(err error) 214 }{ 215 { 216 name: "instance exists", 217 instanceID: "i-exist", 218 expect: func(m *mocks.MockEC2APIMockRecorder) { 219 m.TerminateInstances(gomock.Eq(&ec2.TerminateInstancesInput{ 220 InstanceIds: []*string{aws.String("i-exist")}, 221 })). 222 Return(&ec2.TerminateInstancesOutput{}, nil) 223 }, 224 check: func(err error) { 225 if err != nil { 226 t.Fatalf("did not expect error: %v", err) 227 } 228 }, 229 }, 230 { 231 name: "instance does not exist", 232 instanceID: "i-donotexist", 233 expect: func(m *mocks.MockEC2APIMockRecorder) { 234 m.TerminateInstances(gomock.Eq(&ec2.TerminateInstancesInput{ 235 InstanceIds: []*string{aws.String("i-donotexist")}, 236 })). 237 Return(&ec2.TerminateInstancesOutput{}, instanceNotFoundError) 238 }, 239 check: func(err error) { 240 if err == nil { 241 t.Fatalf("did not expect error: %v", err) 242 } 243 }, 244 }, 245 } 246 247 for _, tc := range testCases { 248 t.Run(tc.name, func(t *testing.T) { 249 ec2Mock := mocks.NewMockEC2API(mockCtrl) 250 251 scheme := runtime.NewScheme() 252 _ = infrav1.AddToScheme(scheme) 253 client := fake.NewClientBuilder().WithScheme(scheme).Build() 254 scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ 255 Client: client, 256 Cluster: &clusterv1.Cluster{}, 257 AWSCluster: &infrav1.AWSCluster{}, 258 }) 259 if err != nil { 260 t.Fatalf("Failed to create test context: %v", err) 261 } 262 263 tc.expect(ec2Mock.EXPECT()) 264 265 s := NewService(scope) 266 s.EC2Client = ec2Mock 267 268 err = s.TerminateInstance(tc.instanceID) 269 tc.check(err) 270 }) 271 } 272 } 273 274 func TestCreateInstance(t *testing.T) { 275 secret := &corev1.Secret{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: "bootstrap-data", 278 }, 279 Data: map[string][]byte{ 280 "value": []byte("data"), 281 }, 282 } 283 284 az := "test-zone-1a" 285 tenancy := "dedicated" 286 287 data := []byte("userData") 288 289 userDataCompressed, err := userdata.GzipBytes(data) 290 if err != nil { 291 t.Fatal("Failed to gzip test user data") 292 } 293 294 isUncompressedFalse := false 295 isUncompressedTrue := true 296 297 testcases := []struct { 298 name string 299 machine clusterv1.Machine 300 machineConfig *infrav1.AWSMachineSpec 301 awsCluster *infrav1.AWSCluster 302 expect func(m *mocks.MockEC2APIMockRecorder) 303 check func(instance *infrav1.Instance, err error) 304 }{ 305 { 306 name: "simple", 307 machine: clusterv1.Machine{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Labels: map[string]string{"set": "node"}, 310 }, 311 Spec: clusterv1.MachineSpec{ 312 Bootstrap: clusterv1.Bootstrap{ 313 DataSecretName: pointer.StringPtr("bootstrap-data"), 314 }, 315 }, 316 }, 317 machineConfig: &infrav1.AWSMachineSpec{ 318 AMI: infrav1.AMIReference{ 319 ID: aws.String("abc"), 320 }, 321 InstanceType: "m5.large", 322 }, 323 awsCluster: &infrav1.AWSCluster{ 324 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 325 Spec: infrav1.AWSClusterSpec{ 326 NetworkSpec: infrav1.NetworkSpec{ 327 Subnets: infrav1.Subnets{ 328 infrav1.SubnetSpec{ 329 ID: "subnet-1", 330 IsPublic: false, 331 }, 332 infrav1.SubnetSpec{ 333 IsPublic: false, 334 }, 335 }, 336 }, 337 }, 338 Status: infrav1.AWSClusterStatus{ 339 Network: infrav1.NetworkStatus{ 340 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 341 infrav1.SecurityGroupControlPlane: { 342 ID: "1", 343 }, 344 infrav1.SecurityGroupNode: { 345 ID: "2", 346 }, 347 infrav1.SecurityGroupLB: { 348 ID: "3", 349 }, 350 }, 351 APIServerELB: infrav1.ClassicELB{ 352 DNSName: "test-apiserver.us-east-1.aws", 353 }, 354 }, 355 }, 356 }, 357 expect: func(m *mocks.MockEC2APIMockRecorder) { 358 m. // TODO: Restore these parameters, but with the tags as well 359 RunInstances(gomock.Any()). 360 Return(&ec2.Reservation{ 361 Instances: []*ec2.Instance{ 362 { 363 State: &ec2.InstanceState{ 364 Name: aws.String(ec2.InstanceStateNamePending), 365 }, 366 IamInstanceProfile: &ec2.IamInstanceProfile{ 367 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 368 }, 369 InstanceId: aws.String("two"), 370 InstanceType: aws.String("m5.large"), 371 SubnetId: aws.String("subnet-1"), 372 ImageId: aws.String("ami-1"), 373 RootDeviceName: aws.String("device-1"), 374 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 375 { 376 DeviceName: aws.String("device-1"), 377 Ebs: &ec2.EbsInstanceBlockDevice{ 378 VolumeId: aws.String("volume-1"), 379 }, 380 }, 381 }, 382 Placement: &ec2.Placement{ 383 AvailabilityZone: &az, 384 }, 385 }, 386 }, 387 }, nil) 388 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 389 Return(nil) 390 }, 391 check: func(instance *infrav1.Instance, err error) { 392 if err != nil { 393 t.Fatalf("did not expect error: %v", err) 394 } 395 }, 396 }, 397 { 398 name: "with availability zone", 399 machine: clusterv1.Machine{ 400 ObjectMeta: metav1.ObjectMeta{ 401 Labels: map[string]string{"set": "node"}, 402 }, 403 Spec: clusterv1.MachineSpec{ 404 Bootstrap: clusterv1.Bootstrap{ 405 DataSecretName: pointer.StringPtr("bootstrap-data"), 406 }, 407 }, 408 }, 409 machineConfig: &infrav1.AWSMachineSpec{ 410 AMI: infrav1.AMIReference{ 411 ID: aws.String("abc"), 412 }, 413 InstanceType: "m5.2xlarge", 414 FailureDomain: aws.String("us-east-1c"), 415 }, 416 awsCluster: &infrav1.AWSCluster{ 417 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 418 Spec: infrav1.AWSClusterSpec{ 419 NetworkSpec: infrav1.NetworkSpec{ 420 Subnets: infrav1.Subnets{ 421 infrav1.SubnetSpec{ 422 ID: "subnet-1", 423 AvailabilityZone: "us-east-1a", 424 IsPublic: false, 425 }, 426 infrav1.SubnetSpec{ 427 ID: "subnet-2", 428 AvailabilityZone: "us-east-1b", 429 IsPublic: false, 430 }, 431 infrav1.SubnetSpec{ 432 ID: "subnet-3", 433 AvailabilityZone: "us-east-1c", 434 IsPublic: false, 435 }, 436 infrav1.SubnetSpec{ 437 ID: "subnet-3-public", 438 AvailabilityZone: "us-east-1c", 439 IsPublic: true, 440 }, 441 }, 442 }, 443 }, 444 Status: infrav1.AWSClusterStatus{ 445 Network: infrav1.NetworkStatus{ 446 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 447 infrav1.SecurityGroupControlPlane: { 448 ID: "1", 449 }, 450 infrav1.SecurityGroupNode: { 451 ID: "2", 452 }, 453 infrav1.SecurityGroupLB: { 454 ID: "3", 455 }, 456 }, 457 APIServerELB: infrav1.ClassicELB{ 458 DNSName: "test-apiserver.us-east-1.aws", 459 }, 460 }, 461 }, 462 }, 463 expect: func(m *mocks.MockEC2APIMockRecorder) { 464 m. 465 RunInstances(gomock.Any()). 466 Return(&ec2.Reservation{ 467 Instances: []*ec2.Instance{ 468 { 469 State: &ec2.InstanceState{ 470 Name: aws.String(ec2.InstanceStateNamePending), 471 }, 472 IamInstanceProfile: &ec2.IamInstanceProfile{ 473 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 474 }, 475 InstanceId: aws.String("two"), 476 InstanceType: aws.String("m5.large"), 477 SubnetId: aws.String("subnet-3"), 478 ImageId: aws.String("ami-1"), 479 RootDeviceName: aws.String("device-1"), 480 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 481 { 482 DeviceName: aws.String("device-1"), 483 Ebs: &ec2.EbsInstanceBlockDevice{ 484 VolumeId: aws.String("volume-1"), 485 }, 486 }, 487 }, 488 Placement: &ec2.Placement{ 489 AvailabilityZone: &az, 490 }, 491 }, 492 }, 493 }, nil) 494 495 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 496 Return(nil) 497 }, 498 check: func(instance *infrav1.Instance, err error) { 499 if err != nil { 500 t.Fatalf("did not expect error: %v", err) 501 } 502 503 if instance.SubnetID != "subnet-3" { 504 t.Fatalf("expected subnet-3 from availability zone us-east-1c, got %q", instance.SubnetID) 505 } 506 }, 507 }, 508 { 509 name: "with ImageLookupOrg specified at the machine level", 510 machine: clusterv1.Machine{ 511 ObjectMeta: metav1.ObjectMeta{ 512 Labels: map[string]string{"set": "node"}, 513 }, 514 Spec: clusterv1.MachineSpec{ 515 Bootstrap: clusterv1.Bootstrap{ 516 DataSecretName: pointer.StringPtr("bootstrap-data"), 517 }, 518 Version: pointer.StringPtr("v1.16.1"), 519 }, 520 }, 521 machineConfig: &infrav1.AWSMachineSpec{ 522 ImageLookupOrg: "test-org-123", 523 InstanceType: "m5.large", 524 }, 525 awsCluster: &infrav1.AWSCluster{ 526 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 527 Spec: infrav1.AWSClusterSpec{ 528 NetworkSpec: infrav1.NetworkSpec{ 529 Subnets: infrav1.Subnets{ 530 infrav1.SubnetSpec{ 531 ID: "subnet-1", 532 IsPublic: false, 533 }, 534 infrav1.SubnetSpec{ 535 IsPublic: false, 536 }, 537 }, 538 }, 539 }, 540 Status: infrav1.AWSClusterStatus{ 541 Network: infrav1.NetworkStatus{ 542 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 543 infrav1.SecurityGroupControlPlane: { 544 ID: "1", 545 }, 546 infrav1.SecurityGroupNode: { 547 ID: "2", 548 }, 549 infrav1.SecurityGroupLB: { 550 ID: "3", 551 }, 552 }, 553 APIServerELB: infrav1.ClassicELB{ 554 DNSName: "test-apiserver.us-east-1.aws", 555 }, 556 }, 557 }, 558 }, 559 expect: func(m *mocks.MockEC2APIMockRecorder) { 560 amiName, err := GenerateAmiName("capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-*", "ubuntu-18.04", "v1.16.1") 561 if err != nil { 562 t.Fatalf("Failed to process ami format: %v", err) 563 } 564 // verify that the ImageLookupOrg is used when finding AMIs 565 m. 566 DescribeImages(gomock.Eq(&ec2.DescribeImagesInput{ 567 Filters: []*ec2.Filter{ 568 { 569 Name: aws.String("owner-id"), 570 Values: []*string{aws.String("test-org-123")}, 571 }, 572 { 573 Name: aws.String("name"), 574 Values: []*string{aws.String(amiName)}, 575 }, 576 { 577 Name: aws.String("architecture"), 578 Values: []*string{aws.String("x86_64")}, 579 }, 580 { 581 Name: aws.String("state"), 582 Values: []*string{aws.String("available")}, 583 }, 584 { 585 Name: aws.String("virtualization-type"), 586 Values: []*string{aws.String("hvm")}, 587 }, 588 }, 589 })). 590 Return(&ec2.DescribeImagesOutput{ 591 Images: []*ec2.Image{ 592 { 593 Name: aws.String("ami-1"), 594 CreationDate: aws.String("2006-01-02T15:04:05.000Z"), 595 }, 596 }, 597 }, nil) 598 m. // TODO: Restore these parameters, but with the tags as well 599 RunInstances(gomock.Any()). 600 Return(&ec2.Reservation{ 601 Instances: []*ec2.Instance{ 602 { 603 State: &ec2.InstanceState{ 604 Name: aws.String(ec2.InstanceStateNamePending), 605 }, 606 IamInstanceProfile: &ec2.IamInstanceProfile{ 607 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 608 }, 609 InstanceId: aws.String("two"), 610 InstanceType: aws.String("m5.large"), 611 SubnetId: aws.String("subnet-1"), 612 ImageId: aws.String("ami-1"), 613 RootDeviceName: aws.String("device-1"), 614 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 615 { 616 DeviceName: aws.String("device-1"), 617 Ebs: &ec2.EbsInstanceBlockDevice{ 618 VolumeId: aws.String("volume-1"), 619 }, 620 }, 621 }, 622 Placement: &ec2.Placement{ 623 AvailabilityZone: &az, 624 }, 625 }, 626 }, 627 }, nil) 628 629 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 630 Return(nil) 631 }, 632 check: func(instance *infrav1.Instance, err error) { 633 if err != nil { 634 t.Fatalf("did not expect error: %v", err) 635 } 636 }, 637 }, 638 { 639 name: "with ImageLookupOrg specified at the cluster-level", 640 machine: clusterv1.Machine{ 641 ObjectMeta: metav1.ObjectMeta{ 642 Labels: map[string]string{"set": "node"}, 643 }, 644 Spec: clusterv1.MachineSpec{ 645 Bootstrap: clusterv1.Bootstrap{ 646 DataSecretName: pointer.StringPtr("bootstrap-data"), 647 }, 648 Version: pointer.StringPtr("v1.16.1"), 649 }, 650 }, 651 machineConfig: &infrav1.AWSMachineSpec{ 652 InstanceType: "m5.large", 653 }, 654 awsCluster: &infrav1.AWSCluster{ 655 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 656 Spec: infrav1.AWSClusterSpec{ 657 NetworkSpec: infrav1.NetworkSpec{ 658 Subnets: infrav1.Subnets{ 659 infrav1.SubnetSpec{ 660 ID: "subnet-1", 661 IsPublic: false, 662 }, 663 infrav1.SubnetSpec{ 664 IsPublic: false, 665 }, 666 }, 667 }, 668 ImageLookupOrg: "cluster-level-image-lookup-org", 669 }, 670 Status: infrav1.AWSClusterStatus{ 671 Network: infrav1.NetworkStatus{ 672 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 673 infrav1.SecurityGroupControlPlane: { 674 ID: "1", 675 }, 676 infrav1.SecurityGroupNode: { 677 ID: "2", 678 }, 679 infrav1.SecurityGroupLB: { 680 ID: "3", 681 }, 682 }, 683 APIServerELB: infrav1.ClassicELB{ 684 DNSName: "test-apiserver.us-east-1.aws", 685 }, 686 }, 687 }, 688 }, 689 expect: func(m *mocks.MockEC2APIMockRecorder) { 690 amiName, err := GenerateAmiName("capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-*", "ubuntu-18.04", "v1.16.1") 691 if err != nil { 692 t.Fatalf("Failed to process ami format: %v", err) 693 } 694 // verify that the ImageLookupOrg is used when finding AMIs 695 m. 696 DescribeImages(gomock.Eq(&ec2.DescribeImagesInput{ 697 Filters: []*ec2.Filter{ 698 { 699 Name: aws.String("owner-id"), 700 Values: []*string{aws.String("cluster-level-image-lookup-org")}, 701 }, 702 { 703 Name: aws.String("name"), 704 Values: []*string{aws.String(amiName)}, 705 }, 706 { 707 Name: aws.String("architecture"), 708 Values: []*string{aws.String("x86_64")}, 709 }, 710 { 711 Name: aws.String("state"), 712 Values: []*string{aws.String("available")}, 713 }, 714 { 715 Name: aws.String("virtualization-type"), 716 Values: []*string{aws.String("hvm")}, 717 }, 718 }, 719 })). 720 Return(&ec2.DescribeImagesOutput{ 721 Images: []*ec2.Image{ 722 { 723 Name: aws.String("ami-1"), 724 CreationDate: aws.String("2006-01-02T15:04:05.000Z"), 725 }, 726 }, 727 }, nil) 728 m. // TODO: Restore these parameters, but with the tags as well 729 RunInstances(gomock.Any()). 730 Return(&ec2.Reservation{ 731 Instances: []*ec2.Instance{ 732 { 733 State: &ec2.InstanceState{ 734 Name: aws.String(ec2.InstanceStateNamePending), 735 }, 736 IamInstanceProfile: &ec2.IamInstanceProfile{ 737 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 738 }, 739 InstanceId: aws.String("two"), 740 InstanceType: aws.String("m5.large"), 741 SubnetId: aws.String("subnet-1"), 742 ImageId: aws.String("ami-1"), 743 RootDeviceName: aws.String("device-1"), 744 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 745 { 746 DeviceName: aws.String("device-1"), 747 Ebs: &ec2.EbsInstanceBlockDevice{ 748 VolumeId: aws.String("volume-1"), 749 }, 750 }, 751 }, 752 Placement: &ec2.Placement{ 753 AvailabilityZone: &az, 754 }, 755 }, 756 }, 757 }, nil) 758 759 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 760 Return(nil) 761 }, 762 check: func(instance *infrav1.Instance, err error) { 763 if err != nil { 764 t.Fatalf("did not expect error: %v", err) 765 } 766 }, 767 }, 768 { 769 name: "AWSMachine ImageLookupOrg overrides AWSCluster ImageLookupOrg", 770 machine: clusterv1.Machine{ 771 ObjectMeta: metav1.ObjectMeta{ 772 Labels: map[string]string{"set": "node"}, 773 }, 774 Spec: clusterv1.MachineSpec{ 775 Bootstrap: clusterv1.Bootstrap{ 776 DataSecretName: pointer.StringPtr("bootstrap-data"), 777 }, 778 Version: pointer.StringPtr("v1.16.1"), 779 }, 780 }, 781 machineConfig: &infrav1.AWSMachineSpec{ 782 InstanceType: "m5.large", 783 ImageLookupOrg: "machine-level-image-lookup-org", 784 }, 785 awsCluster: &infrav1.AWSCluster{ 786 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 787 Spec: infrav1.AWSClusterSpec{ 788 NetworkSpec: infrav1.NetworkSpec{ 789 Subnets: infrav1.Subnets{ 790 infrav1.SubnetSpec{ 791 ID: "subnet-1", 792 IsPublic: false, 793 }, 794 infrav1.SubnetSpec{ 795 IsPublic: false, 796 }, 797 }, 798 }, 799 ImageLookupOrg: "cluster-level-image-lookup-org", 800 }, 801 Status: infrav1.AWSClusterStatus{ 802 Network: infrav1.NetworkStatus{ 803 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 804 infrav1.SecurityGroupControlPlane: { 805 ID: "1", 806 }, 807 infrav1.SecurityGroupNode: { 808 ID: "2", 809 }, 810 infrav1.SecurityGroupLB: { 811 ID: "3", 812 }, 813 }, 814 APIServerELB: infrav1.ClassicELB{ 815 DNSName: "test-apiserver.us-east-1.aws", 816 }, 817 }, 818 }, 819 }, 820 expect: func(m *mocks.MockEC2APIMockRecorder) { 821 amiName, err := GenerateAmiName("capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-*", "ubuntu-18.04", "v1.16.1") 822 if err != nil { 823 t.Fatalf("Failed to process ami format: %v", err) 824 } 825 // verify that the ImageLookupOrg is used when finding AMIs 826 m. 827 DescribeImages(gomock.Eq(&ec2.DescribeImagesInput{ 828 Filters: []*ec2.Filter{ 829 { 830 Name: aws.String("owner-id"), 831 Values: []*string{aws.String("machine-level-image-lookup-org")}, 832 }, 833 { 834 Name: aws.String("name"), 835 Values: []*string{aws.String(amiName)}, 836 }, 837 { 838 Name: aws.String("architecture"), 839 Values: []*string{aws.String("x86_64")}, 840 }, 841 { 842 Name: aws.String("state"), 843 Values: []*string{aws.String("available")}, 844 }, 845 { 846 Name: aws.String("virtualization-type"), 847 Values: []*string{aws.String("hvm")}, 848 }, 849 }, 850 })). 851 Return(&ec2.DescribeImagesOutput{ 852 Images: []*ec2.Image{ 853 { 854 Name: aws.String("ami-1"), 855 CreationDate: aws.String("2006-01-02T15:04:05.000Z"), 856 }, 857 }, 858 }, nil) 859 m. // TODO: Restore these parameters, but with the tags as well 860 RunInstances(gomock.Any()). 861 Return(&ec2.Reservation{ 862 Instances: []*ec2.Instance{ 863 { 864 State: &ec2.InstanceState{ 865 Name: aws.String(ec2.InstanceStateNamePending), 866 }, 867 IamInstanceProfile: &ec2.IamInstanceProfile{ 868 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 869 }, 870 InstanceId: aws.String("two"), 871 InstanceType: aws.String("m5.large"), 872 SubnetId: aws.String("subnet-1"), 873 ImageId: aws.String("ami-1"), 874 RootDeviceName: aws.String("device-1"), 875 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 876 { 877 DeviceName: aws.String("device-1"), 878 Ebs: &ec2.EbsInstanceBlockDevice{ 879 VolumeId: aws.String("volume-1"), 880 }, 881 }, 882 }, 883 Placement: &ec2.Placement{ 884 AvailabilityZone: &az, 885 }, 886 }, 887 }, 888 }, nil) 889 890 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 891 Return(nil) 892 }, 893 check: func(instance *infrav1.Instance, err error) { 894 if err != nil { 895 t.Fatalf("did not expect error: %v", err) 896 } 897 }, 898 }, 899 { 900 name: "subnet filter and failureDomain defined", 901 machine: clusterv1.Machine{ 902 ObjectMeta: metav1.ObjectMeta{ 903 Labels: map[string]string{"set": "node"}, 904 }, 905 Spec: clusterv1.MachineSpec{ 906 Bootstrap: clusterv1.Bootstrap{ 907 DataSecretName: pointer.StringPtr("bootstrap-data"), 908 }, 909 }, 910 }, 911 machineConfig: &infrav1.AWSMachineSpec{ 912 AMI: infrav1.AMIReference{ 913 ID: aws.String("abc"), 914 }, 915 InstanceType: "m5.large", 916 Subnet: &infrav1.AWSResourceReference{ 917 Filters: []infrav1.Filter{{ 918 Name: "tag:some-tag", 919 Values: []string{"some-value"}, 920 }}, 921 }, 922 FailureDomain: aws.String("us-east-1b"), 923 }, 924 awsCluster: &infrav1.AWSCluster{ 925 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 926 Spec: infrav1.AWSClusterSpec{ 927 NetworkSpec: infrav1.NetworkSpec{ 928 VPC: infrav1.VPCSpec{ 929 ID: "vpc-id", 930 }, 931 }, 932 }, 933 Status: infrav1.AWSClusterStatus{ 934 Network: infrav1.NetworkStatus{ 935 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 936 infrav1.SecurityGroupControlPlane: { 937 ID: "1", 938 }, 939 infrav1.SecurityGroupNode: { 940 ID: "2", 941 }, 942 infrav1.SecurityGroupLB: { 943 ID: "3", 944 }, 945 }, 946 APIServerELB: infrav1.ClassicELB{ 947 DNSName: "test-apiserver.us-east-1.aws", 948 }, 949 }, 950 }, 951 }, 952 expect: func(m *mocks.MockEC2APIMockRecorder) { 953 m. 954 DescribeSubnets(&ec2.DescribeSubnetsInput{ 955 Filters: []*ec2.Filter{ 956 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 957 filter.EC2.VPC("vpc-id"), 958 {Name: aws.String("tag:some-tag"), Values: aws.StringSlice([]string{"some-value"})}, 959 }, 960 }). 961 Return(&ec2.DescribeSubnetsOutput{ 962 Subnets: []*ec2.Subnet{{ 963 SubnetId: aws.String("filtered-subnet-1"), 964 AvailabilityZone: aws.String("us-east-1b"), 965 }}, 966 }, nil) 967 m. 968 RunInstances(gomock.Any()). 969 Return(&ec2.Reservation{ 970 Instances: []*ec2.Instance{ 971 { 972 State: &ec2.InstanceState{ 973 Name: aws.String(ec2.InstanceStateNamePending), 974 }, 975 IamInstanceProfile: &ec2.IamInstanceProfile{ 976 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 977 }, 978 InstanceId: aws.String("two"), 979 InstanceType: aws.String("m5.large"), 980 SubnetId: aws.String("subnet-1"), 981 ImageId: aws.String("ami-1"), 982 RootDeviceName: aws.String("device-1"), 983 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 984 { 985 DeviceName: aws.String("device-1"), 986 Ebs: &ec2.EbsInstanceBlockDevice{ 987 VolumeId: aws.String("volume-1"), 988 }, 989 }, 990 }, 991 Placement: &ec2.Placement{ 992 AvailabilityZone: &az, 993 }, 994 }, 995 }, 996 }, nil) 997 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 998 Return(nil) 999 }, 1000 check: func(instance *infrav1.Instance, err error) { 1001 if err != nil { 1002 t.Fatalf("did not expect error: %v", err) 1003 } 1004 }, 1005 }, 1006 { 1007 name: "with subnet ID that belongs to Cluster", 1008 machine: clusterv1.Machine{ 1009 ObjectMeta: metav1.ObjectMeta{ 1010 Labels: map[string]string{"set": "node"}, 1011 }, 1012 Spec: clusterv1.MachineSpec{ 1013 Bootstrap: clusterv1.Bootstrap{ 1014 DataSecretName: pointer.StringPtr("bootstrap-data"), 1015 }, 1016 }, 1017 }, 1018 machineConfig: &infrav1.AWSMachineSpec{ 1019 AMI: infrav1.AMIReference{ 1020 ID: aws.String("abc"), 1021 }, 1022 InstanceType: "m5.large", 1023 Subnet: &infrav1.AWSResourceReference{ 1024 ID: aws.String("matching-subnet"), 1025 }, 1026 }, 1027 awsCluster: &infrav1.AWSCluster{ 1028 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1029 Spec: infrav1.AWSClusterSpec{ 1030 NetworkSpec: infrav1.NetworkSpec{ 1031 VPC: infrav1.VPCSpec{ 1032 ID: "vpc-id", 1033 }, 1034 Subnets: infrav1.Subnets{{ 1035 ID: "matching-subnet", 1036 }}, 1037 }, 1038 }, 1039 Status: infrav1.AWSClusterStatus{ 1040 Network: infrav1.NetworkStatus{ 1041 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1042 infrav1.SecurityGroupControlPlane: { 1043 ID: "1", 1044 }, 1045 infrav1.SecurityGroupNode: { 1046 ID: "2", 1047 }, 1048 infrav1.SecurityGroupLB: { 1049 ID: "3", 1050 }, 1051 }, 1052 APIServerELB: infrav1.ClassicELB{ 1053 DNSName: "test-apiserver.us-east-1.aws", 1054 }, 1055 }, 1056 }, 1057 }, 1058 expect: func(m *mocks.MockEC2APIMockRecorder) { 1059 m. 1060 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1061 Filters: []*ec2.Filter{ 1062 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1063 filter.EC2.VPC("vpc-id"), 1064 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"matching-subnet"})}, 1065 }, 1066 }). 1067 Return(&ec2.DescribeSubnetsOutput{ 1068 Subnets: []*ec2.Subnet{{ 1069 SubnetId: aws.String("matching-subnet"), 1070 AvailabilityZone: aws.String("us-east-1b"), 1071 }}, 1072 }, nil) 1073 m. 1074 RunInstances(gomock.Any()). 1075 Return(&ec2.Reservation{ 1076 Instances: []*ec2.Instance{ 1077 { 1078 State: &ec2.InstanceState{ 1079 Name: aws.String(ec2.InstanceStateNamePending), 1080 }, 1081 IamInstanceProfile: &ec2.IamInstanceProfile{ 1082 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1083 }, 1084 InstanceId: aws.String("two"), 1085 InstanceType: aws.String("m5.large"), 1086 SubnetId: aws.String("matching-subnet"), 1087 ImageId: aws.String("ami-1"), 1088 RootDeviceName: aws.String("device-1"), 1089 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1090 { 1091 DeviceName: aws.String("device-1"), 1092 Ebs: &ec2.EbsInstanceBlockDevice{ 1093 VolumeId: aws.String("volume-1"), 1094 }, 1095 }, 1096 }, 1097 Placement: &ec2.Placement{ 1098 AvailabilityZone: &az, 1099 }, 1100 }, 1101 }, 1102 }, nil) 1103 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 1104 Return(nil) 1105 }, 1106 check: func(instance *infrav1.Instance, err error) { 1107 if err != nil { 1108 t.Fatalf("did not expect error: %v", err) 1109 } 1110 }, 1111 }, 1112 { 1113 name: "with subnet ID that does not exist", 1114 machine: clusterv1.Machine{ 1115 ObjectMeta: metav1.ObjectMeta{ 1116 Labels: map[string]string{"set": "node"}, 1117 }, 1118 Spec: clusterv1.MachineSpec{ 1119 Bootstrap: clusterv1.Bootstrap{ 1120 DataSecretName: pointer.StringPtr("bootstrap-data"), 1121 }, 1122 }, 1123 }, 1124 machineConfig: &infrav1.AWSMachineSpec{ 1125 AMI: infrav1.AMIReference{ 1126 ID: aws.String("abc"), 1127 }, 1128 InstanceType: "m5.large", 1129 Subnet: &infrav1.AWSResourceReference{ 1130 ID: aws.String("non-matching-subnet"), 1131 }, 1132 }, 1133 awsCluster: &infrav1.AWSCluster{ 1134 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1135 Spec: infrav1.AWSClusterSpec{ 1136 NetworkSpec: infrav1.NetworkSpec{ 1137 VPC: infrav1.VPCSpec{ 1138 ID: "vpc-id", 1139 }, 1140 Subnets: infrav1.Subnets{{ 1141 ID: "subnet-1", 1142 }}, 1143 }, 1144 }, 1145 Status: infrav1.AWSClusterStatus{ 1146 Network: infrav1.NetworkStatus{ 1147 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1148 infrav1.SecurityGroupControlPlane: { 1149 ID: "1", 1150 }, 1151 infrav1.SecurityGroupNode: { 1152 ID: "2", 1153 }, 1154 infrav1.SecurityGroupLB: { 1155 ID: "3", 1156 }, 1157 }, 1158 APIServerELB: infrav1.ClassicELB{ 1159 DNSName: "test-apiserver.us-east-1.aws", 1160 }, 1161 }, 1162 }, 1163 }, 1164 expect: func(m *mocks.MockEC2APIMockRecorder) { 1165 m. 1166 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1167 Filters: []*ec2.Filter{ 1168 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1169 filter.EC2.VPC("vpc-id"), 1170 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"non-matching-subnet"})}, 1171 }, 1172 }). 1173 Return(&ec2.DescribeSubnetsOutput{ 1174 Subnets: []*ec2.Subnet{}, 1175 }, nil) 1176 }, 1177 check: func(instance *infrav1.Instance, err error) { 1178 expectedErrMsg := "failed to run machine \"aws-test1\", no subnets available matching criteria" 1179 if err == nil { 1180 t.Fatalf("Expected error, but got nil") 1181 } 1182 1183 if !strings.Contains(err.Error(), expectedErrMsg) { 1184 t.Fatalf("Expected error: %s\nInstead got: %s", expectedErrMsg, err.Error()) 1185 } 1186 }, 1187 }, 1188 { 1189 name: "with subnet ID that does not belong to Cluster", 1190 machine: clusterv1.Machine{ 1191 ObjectMeta: metav1.ObjectMeta{ 1192 Labels: map[string]string{"set": "node"}, 1193 }, 1194 Spec: clusterv1.MachineSpec{ 1195 Bootstrap: clusterv1.Bootstrap{ 1196 DataSecretName: pointer.StringPtr("bootstrap-data"), 1197 }, 1198 }, 1199 }, 1200 machineConfig: &infrav1.AWSMachineSpec{ 1201 AMI: infrav1.AMIReference{ 1202 ID: aws.String("abc"), 1203 }, 1204 InstanceType: "m5.large", 1205 Subnet: &infrav1.AWSResourceReference{ 1206 ID: aws.String("matching-subnet"), 1207 }, 1208 }, 1209 awsCluster: &infrav1.AWSCluster{ 1210 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1211 Spec: infrav1.AWSClusterSpec{ 1212 NetworkSpec: infrav1.NetworkSpec{ 1213 VPC: infrav1.VPCSpec{ 1214 ID: "vpc-id", 1215 }, 1216 Subnets: infrav1.Subnets{{ 1217 ID: "subnet-1", 1218 }}, 1219 }, 1220 }, 1221 Status: infrav1.AWSClusterStatus{ 1222 Network: infrav1.NetworkStatus{ 1223 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1224 infrav1.SecurityGroupControlPlane: { 1225 ID: "1", 1226 }, 1227 infrav1.SecurityGroupNode: { 1228 ID: "2", 1229 }, 1230 infrav1.SecurityGroupLB: { 1231 ID: "3", 1232 }, 1233 }, 1234 APIServerELB: infrav1.ClassicELB{ 1235 DNSName: "test-apiserver.us-east-1.aws", 1236 }, 1237 }, 1238 }, 1239 }, 1240 expect: func(m *mocks.MockEC2APIMockRecorder) { 1241 m. 1242 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1243 Filters: []*ec2.Filter{ 1244 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1245 filter.EC2.VPC("vpc-id"), 1246 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"matching-subnet"})}, 1247 }, 1248 }). 1249 Return(&ec2.DescribeSubnetsOutput{ 1250 Subnets: []*ec2.Subnet{{ 1251 SubnetId: aws.String("matching-subnet"), 1252 }}, 1253 }, nil) 1254 m. 1255 RunInstances(gomock.Any()). 1256 Return(&ec2.Reservation{ 1257 Instances: []*ec2.Instance{ 1258 { 1259 State: &ec2.InstanceState{ 1260 Name: aws.String(ec2.InstanceStateNamePending), 1261 }, 1262 IamInstanceProfile: &ec2.IamInstanceProfile{ 1263 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1264 }, 1265 InstanceId: aws.String("two"), 1266 InstanceType: aws.String("m5.large"), 1267 SubnetId: aws.String("matching-subnet"), 1268 ImageId: aws.String("ami-1"), 1269 RootDeviceName: aws.String("device-1"), 1270 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1271 { 1272 DeviceName: aws.String("device-1"), 1273 Ebs: &ec2.EbsInstanceBlockDevice{ 1274 VolumeId: aws.String("volume-1"), 1275 }, 1276 }, 1277 }, 1278 Placement: &ec2.Placement{ 1279 AvailabilityZone: &az, 1280 }, 1281 }, 1282 }, 1283 }, nil) 1284 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 1285 Return(nil) 1286 }, 1287 check: func(instance *infrav1.Instance, err error) { 1288 if err != nil { 1289 t.Fatalf("did not expect error: %v", err) 1290 } 1291 }, 1292 }, 1293 { 1294 name: "subnet id and failureDomain don't match", 1295 machine: clusterv1.Machine{ 1296 ObjectMeta: metav1.ObjectMeta{ 1297 Labels: map[string]string{"set": "node"}, 1298 }, 1299 Spec: clusterv1.MachineSpec{ 1300 Bootstrap: clusterv1.Bootstrap{ 1301 DataSecretName: pointer.StringPtr("bootstrap-data"), 1302 }, 1303 }, 1304 }, 1305 machineConfig: &infrav1.AWSMachineSpec{ 1306 AMI: infrav1.AMIReference{ 1307 ID: aws.String("abc"), 1308 }, 1309 InstanceType: "m5.large", 1310 Subnet: &infrav1.AWSResourceReference{ 1311 ID: aws.String("subnet-1"), 1312 }, 1313 FailureDomain: aws.String("us-east-1b"), 1314 }, 1315 awsCluster: &infrav1.AWSCluster{ 1316 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1317 Spec: infrav1.AWSClusterSpec{ 1318 NetworkSpec: infrav1.NetworkSpec{ 1319 VPC: infrav1.VPCSpec{ 1320 ID: "vpc-id", 1321 }, 1322 Subnets: infrav1.Subnets{{ 1323 ID: "subnet-1", 1324 AvailabilityZone: "us-west-1b", 1325 }}, 1326 }, 1327 }, 1328 Status: infrav1.AWSClusterStatus{ 1329 Network: infrav1.NetworkStatus{ 1330 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1331 infrav1.SecurityGroupControlPlane: { 1332 ID: "1", 1333 }, 1334 infrav1.SecurityGroupNode: { 1335 ID: "2", 1336 }, 1337 infrav1.SecurityGroupLB: { 1338 ID: "3", 1339 }, 1340 }, 1341 APIServerELB: infrav1.ClassicELB{ 1342 DNSName: "test-apiserver.us-east-1.aws", 1343 }, 1344 }, 1345 }, 1346 }, 1347 expect: func(m *mocks.MockEC2APIMockRecorder) { 1348 m. 1349 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1350 Filters: []*ec2.Filter{ 1351 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1352 filter.EC2.VPC("vpc-id"), 1353 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"subnet-1"})}, 1354 }, 1355 }). 1356 Return(&ec2.DescribeSubnetsOutput{ 1357 Subnets: []*ec2.Subnet{{ 1358 SubnetId: aws.String("subnet-1"), 1359 AvailabilityZone: aws.String("us-west-1b"), 1360 }}, 1361 }, nil) 1362 }, 1363 check: func(instance *infrav1.Instance, err error) { 1364 expectedErrMsg := "failed to run machine \"aws-test1\", found 1 subnets matching criteria but post-filtering failed. subnet \"subnet-1\" availability zone \"us-west-1b\" does not match failure domain \"us-east-1b\"" 1365 if err == nil { 1366 t.Fatalf("Expected error, but got nil") 1367 } 1368 1369 if !strings.Contains(err.Error(), expectedErrMsg) { 1370 t.Fatalf("Expected error: %s\nInstead got: `%s", expectedErrMsg, err.Error()) 1371 } 1372 }, 1373 }, 1374 { 1375 name: "public IP true and failureDomain doesn't have public subnet", 1376 machine: clusterv1.Machine{ 1377 ObjectMeta: metav1.ObjectMeta{ 1378 Labels: map[string]string{"set": "node"}, 1379 }, 1380 Spec: clusterv1.MachineSpec{ 1381 Bootstrap: clusterv1.Bootstrap{ 1382 DataSecretName: pointer.StringPtr("bootstrap-data"), 1383 }, 1384 }, 1385 }, 1386 machineConfig: &infrav1.AWSMachineSpec{ 1387 AMI: infrav1.AMIReference{ 1388 ID: aws.String("abc"), 1389 }, 1390 InstanceType: "m5.large", 1391 FailureDomain: aws.String("us-east-1b"), 1392 PublicIP: aws.Bool(true), 1393 }, 1394 awsCluster: &infrav1.AWSCluster{ 1395 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1396 Spec: infrav1.AWSClusterSpec{ 1397 NetworkSpec: infrav1.NetworkSpec{ 1398 VPC: infrav1.VPCSpec{ 1399 ID: "vpc-id", 1400 }, 1401 Subnets: infrav1.Subnets{{ 1402 ID: "private-subnet-1", 1403 AvailabilityZone: "us-east-1b", 1404 IsPublic: false, 1405 }}, 1406 }, 1407 }, 1408 Status: infrav1.AWSClusterStatus{ 1409 Network: infrav1.NetworkStatus{ 1410 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1411 infrav1.SecurityGroupControlPlane: { 1412 ID: "1", 1413 }, 1414 infrav1.SecurityGroupNode: { 1415 ID: "2", 1416 }, 1417 infrav1.SecurityGroupLB: { 1418 ID: "3", 1419 }, 1420 }, 1421 APIServerELB: infrav1.ClassicELB{ 1422 DNSName: "test-apiserver.us-east-1.aws", 1423 }, 1424 }, 1425 }, 1426 }, 1427 expect: func(m *mocks.MockEC2APIMockRecorder) { 1428 }, 1429 check: func(instance *infrav1.Instance, err error) { 1430 expectedErrMsg := "failed to run machine \"aws-test1\" with public IP, no public subnets available in availability zone \"us-east-1b\"" 1431 if err == nil { 1432 t.Fatalf("Expected error, but got nil") 1433 } 1434 1435 if !strings.Contains(err.Error(), expectedErrMsg) { 1436 t.Fatalf("Expected error: %s\nInstead got: `%s", expectedErrMsg, err.Error()) 1437 } 1438 }, 1439 }, 1440 { 1441 name: "public IP true and public subnet ID given", 1442 machine: clusterv1.Machine{ 1443 ObjectMeta: metav1.ObjectMeta{ 1444 Labels: map[string]string{"set": "node"}, 1445 }, 1446 Spec: clusterv1.MachineSpec{ 1447 Bootstrap: clusterv1.Bootstrap{ 1448 DataSecretName: pointer.StringPtr("bootstrap-data"), 1449 }, 1450 }, 1451 }, 1452 machineConfig: &infrav1.AWSMachineSpec{ 1453 AMI: infrav1.AMIReference{ 1454 ID: aws.String("abc"), 1455 }, 1456 InstanceType: "m5.large", 1457 Subnet: &infrav1.AWSResourceReference{ 1458 ID: aws.String("public-subnet-1"), 1459 }, 1460 PublicIP: aws.Bool(true), 1461 }, 1462 awsCluster: &infrav1.AWSCluster{ 1463 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1464 Spec: infrav1.AWSClusterSpec{ 1465 NetworkSpec: infrav1.NetworkSpec{ 1466 VPC: infrav1.VPCSpec{ 1467 ID: "vpc-id", 1468 }, 1469 Subnets: infrav1.Subnets{{ 1470 ID: "public-subnet-1", 1471 IsPublic: true, 1472 }}, 1473 }, 1474 }, 1475 Status: infrav1.AWSClusterStatus{ 1476 Network: infrav1.NetworkStatus{ 1477 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1478 infrav1.SecurityGroupControlPlane: { 1479 ID: "1", 1480 }, 1481 infrav1.SecurityGroupNode: { 1482 ID: "2", 1483 }, 1484 infrav1.SecurityGroupLB: { 1485 ID: "3", 1486 }, 1487 }, 1488 APIServerELB: infrav1.ClassicELB{ 1489 DNSName: "test-apiserver.us-east-1.aws", 1490 }, 1491 }, 1492 }, 1493 }, 1494 expect: func(m *mocks.MockEC2APIMockRecorder) { 1495 m. 1496 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1497 Filters: []*ec2.Filter{ 1498 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1499 filter.EC2.VPC("vpc-id"), 1500 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"public-subnet-1"})}, 1501 }, 1502 }). 1503 Return(&ec2.DescribeSubnetsOutput{ 1504 Subnets: []*ec2.Subnet{{ 1505 SubnetId: aws.String("public-subnet-1"), 1506 AvailabilityZone: aws.String("us-east-1b"), 1507 MapPublicIpOnLaunch: aws.Bool(true), 1508 }}, 1509 }, nil) 1510 m. 1511 RunInstances(gomock.Any()). 1512 Return(&ec2.Reservation{ 1513 Instances: []*ec2.Instance{ 1514 { 1515 State: &ec2.InstanceState{ 1516 Name: aws.String(ec2.InstanceStateNamePending), 1517 }, 1518 IamInstanceProfile: &ec2.IamInstanceProfile{ 1519 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1520 }, 1521 InstanceId: aws.String("two"), 1522 InstanceType: aws.String("m5.large"), 1523 SubnetId: aws.String("public-subnet-1"), 1524 ImageId: aws.String("ami-1"), 1525 RootDeviceName: aws.String("device-1"), 1526 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1527 { 1528 DeviceName: aws.String("device-1"), 1529 Ebs: &ec2.EbsInstanceBlockDevice{ 1530 VolumeId: aws.String("volume-1"), 1531 }, 1532 }, 1533 }, 1534 Placement: &ec2.Placement{ 1535 AvailabilityZone: &az, 1536 }, 1537 }, 1538 }, 1539 }, nil) 1540 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 1541 Return(nil) 1542 }, 1543 check: func(instance *infrav1.Instance, err error) { 1544 if err != nil { 1545 t.Fatalf("did not expect error: %v", err) 1546 } 1547 }, 1548 }, 1549 { 1550 name: "public IP true and private subnet ID given", 1551 machine: clusterv1.Machine{ 1552 ObjectMeta: metav1.ObjectMeta{ 1553 Labels: map[string]string{"set": "node"}, 1554 }, 1555 Spec: clusterv1.MachineSpec{ 1556 Bootstrap: clusterv1.Bootstrap{ 1557 DataSecretName: pointer.StringPtr("bootstrap-data"), 1558 }, 1559 }, 1560 }, 1561 machineConfig: &infrav1.AWSMachineSpec{ 1562 AMI: infrav1.AMIReference{ 1563 ID: aws.String("abc"), 1564 }, 1565 InstanceType: "m5.large", 1566 Subnet: &infrav1.AWSResourceReference{ 1567 ID: aws.String("private-subnet-1"), 1568 }, 1569 PublicIP: aws.Bool(true), 1570 }, 1571 awsCluster: &infrav1.AWSCluster{ 1572 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1573 Spec: infrav1.AWSClusterSpec{ 1574 NetworkSpec: infrav1.NetworkSpec{ 1575 VPC: infrav1.VPCSpec{ 1576 ID: "vpc-id", 1577 }, 1578 Subnets: infrav1.Subnets{{ 1579 ID: "private-subnet-1", 1580 IsPublic: false, 1581 }}, 1582 }, 1583 }, 1584 Status: infrav1.AWSClusterStatus{ 1585 Network: infrav1.NetworkStatus{ 1586 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1587 infrav1.SecurityGroupControlPlane: { 1588 ID: "1", 1589 }, 1590 infrav1.SecurityGroupNode: { 1591 ID: "2", 1592 }, 1593 infrav1.SecurityGroupLB: { 1594 ID: "3", 1595 }, 1596 }, 1597 APIServerELB: infrav1.ClassicELB{ 1598 DNSName: "test-apiserver.us-east-1.aws", 1599 }, 1600 }, 1601 }, 1602 }, 1603 expect: func(m *mocks.MockEC2APIMockRecorder) { 1604 m. 1605 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1606 Filters: []*ec2.Filter{ 1607 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1608 filter.EC2.VPC("vpc-id"), 1609 {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"private-subnet-1"})}, 1610 }, 1611 }). 1612 Return(&ec2.DescribeSubnetsOutput{ 1613 Subnets: []*ec2.Subnet{{ 1614 SubnetId: aws.String("private-subnet-1"), 1615 AvailabilityZone: aws.String("us-east-1b"), 1616 MapPublicIpOnLaunch: aws.Bool(false), 1617 }}, 1618 }, nil) 1619 }, 1620 check: func(instance *infrav1.Instance, err error) { 1621 expectedErrMsg := "failed to run machine \"aws-test1\", found 1 subnets matching criteria but post-filtering failed. subnet \"private-subnet-1\" is a private subnet." 1622 if err == nil { 1623 t.Fatalf("Expected error, but got nil") 1624 } 1625 1626 if !strings.Contains(err.Error(), expectedErrMsg) { 1627 t.Fatalf("Expected error: %s\nInstead got: `%s", expectedErrMsg, err.Error()) 1628 } 1629 }, 1630 }, 1631 { 1632 name: "both public IP and subnet filter defined", 1633 machine: clusterv1.Machine{ 1634 ObjectMeta: metav1.ObjectMeta{ 1635 Labels: map[string]string{"set": "node"}, 1636 }, 1637 Spec: clusterv1.MachineSpec{ 1638 Bootstrap: clusterv1.Bootstrap{ 1639 DataSecretName: pointer.StringPtr("bootstrap-data"), 1640 }, 1641 }, 1642 }, 1643 machineConfig: &infrav1.AWSMachineSpec{ 1644 AMI: infrav1.AMIReference{ 1645 ID: aws.String("abc"), 1646 }, 1647 InstanceType: "m5.large", 1648 Subnet: &infrav1.AWSResourceReference{ 1649 Filters: []infrav1.Filter{{ 1650 Name: "tag:some-tag", 1651 Values: []string{"some-value"}, 1652 }}, 1653 }, 1654 PublicIP: aws.Bool(true), 1655 }, 1656 awsCluster: &infrav1.AWSCluster{ 1657 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1658 Spec: infrav1.AWSClusterSpec{ 1659 NetworkSpec: infrav1.NetworkSpec{ 1660 VPC: infrav1.VPCSpec{ 1661 ID: "vpc-id", 1662 }, 1663 Subnets: infrav1.Subnets{ 1664 infrav1.SubnetSpec{ 1665 ID: "private-subnet-1", 1666 IsPublic: false, 1667 }, 1668 infrav1.SubnetSpec{ 1669 ID: "public-subnet-1", 1670 IsPublic: true, 1671 }, 1672 }, 1673 }, 1674 }, 1675 Status: infrav1.AWSClusterStatus{ 1676 Network: infrav1.NetworkStatus{ 1677 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1678 infrav1.SecurityGroupControlPlane: { 1679 ID: "1", 1680 }, 1681 infrav1.SecurityGroupNode: { 1682 ID: "2", 1683 }, 1684 infrav1.SecurityGroupLB: { 1685 ID: "3", 1686 }, 1687 }, 1688 APIServerELB: infrav1.ClassicELB{ 1689 DNSName: "test-apiserver.us-east-1.aws", 1690 }, 1691 }, 1692 }, 1693 }, 1694 expect: func(m *mocks.MockEC2APIMockRecorder) { 1695 m. 1696 DescribeSubnets(&ec2.DescribeSubnetsInput{ 1697 Filters: []*ec2.Filter{ 1698 filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), 1699 filter.EC2.VPC("vpc-id"), 1700 {Name: aws.String("tag:some-tag"), Values: aws.StringSlice([]string{"some-value"})}, 1701 }, 1702 }). 1703 Return(&ec2.DescribeSubnetsOutput{ 1704 Subnets: []*ec2.Subnet{{ 1705 SubnetId: aws.String("filtered-subnet-1"), 1706 MapPublicIpOnLaunch: aws.Bool(true), 1707 }}, 1708 }, nil) 1709 m. 1710 RunInstances(gomock.Any()). 1711 Return(&ec2.Reservation{ 1712 Instances: []*ec2.Instance{ 1713 { 1714 State: &ec2.InstanceState{ 1715 Name: aws.String(ec2.InstanceStateNamePending), 1716 }, 1717 IamInstanceProfile: &ec2.IamInstanceProfile{ 1718 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1719 }, 1720 InstanceId: aws.String("two"), 1721 InstanceType: aws.String("m5.large"), 1722 SubnetId: aws.String("public-subnet-1"), 1723 ImageId: aws.String("ami-1"), 1724 RootDeviceName: aws.String("device-1"), 1725 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1726 { 1727 DeviceName: aws.String("device-1"), 1728 Ebs: &ec2.EbsInstanceBlockDevice{ 1729 VolumeId: aws.String("volume-1"), 1730 }, 1731 }, 1732 }, 1733 Placement: &ec2.Placement{ 1734 AvailabilityZone: &az, 1735 }, 1736 }, 1737 }, 1738 }, nil) 1739 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 1740 Return(nil) 1741 }, 1742 check: func(instance *infrav1.Instance, err error) { 1743 if err != nil { 1744 t.Fatalf("did not expect error: %v", err) 1745 } 1746 }, 1747 }, 1748 { 1749 name: "public IP true and public subnet exists", 1750 machine: clusterv1.Machine{ 1751 ObjectMeta: metav1.ObjectMeta{ 1752 Labels: map[string]string{"set": "node"}, 1753 }, 1754 Spec: clusterv1.MachineSpec{ 1755 Bootstrap: clusterv1.Bootstrap{ 1756 DataSecretName: pointer.StringPtr("bootstrap-data"), 1757 }, 1758 }, 1759 }, 1760 machineConfig: &infrav1.AWSMachineSpec{ 1761 AMI: infrav1.AMIReference{ 1762 ID: aws.String("abc"), 1763 }, 1764 InstanceType: "m5.large", 1765 PublicIP: aws.Bool(true), 1766 }, 1767 awsCluster: &infrav1.AWSCluster{ 1768 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1769 Spec: infrav1.AWSClusterSpec{ 1770 NetworkSpec: infrav1.NetworkSpec{ 1771 VPC: infrav1.VPCSpec{ 1772 ID: "vpc-id", 1773 }, 1774 Subnets: infrav1.Subnets{ 1775 infrav1.SubnetSpec{ 1776 ID: "private-subnet-1", 1777 IsPublic: false, 1778 }, 1779 infrav1.SubnetSpec{ 1780 ID: "public-subnet-1", 1781 IsPublic: true, 1782 }, 1783 }, 1784 }, 1785 }, 1786 Status: infrav1.AWSClusterStatus{ 1787 Network: infrav1.NetworkStatus{ 1788 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1789 infrav1.SecurityGroupControlPlane: { 1790 ID: "1", 1791 }, 1792 infrav1.SecurityGroupNode: { 1793 ID: "2", 1794 }, 1795 infrav1.SecurityGroupLB: { 1796 ID: "3", 1797 }, 1798 }, 1799 APIServerELB: infrav1.ClassicELB{ 1800 DNSName: "test-apiserver.us-east-1.aws", 1801 }, 1802 }, 1803 }, 1804 }, 1805 expect: func(m *mocks.MockEC2APIMockRecorder) { 1806 m. 1807 RunInstances(gomock.Any()). 1808 Return(&ec2.Reservation{ 1809 Instances: []*ec2.Instance{ 1810 { 1811 State: &ec2.InstanceState{ 1812 Name: aws.String(ec2.InstanceStateNamePending), 1813 }, 1814 IamInstanceProfile: &ec2.IamInstanceProfile{ 1815 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1816 }, 1817 InstanceId: aws.String("two"), 1818 InstanceType: aws.String("m5.large"), 1819 SubnetId: aws.String("public-subnet-1"), 1820 ImageId: aws.String("ami-1"), 1821 RootDeviceName: aws.String("device-1"), 1822 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1823 { 1824 DeviceName: aws.String("device-1"), 1825 Ebs: &ec2.EbsInstanceBlockDevice{ 1826 VolumeId: aws.String("volume-1"), 1827 }, 1828 }, 1829 }, 1830 Placement: &ec2.Placement{ 1831 AvailabilityZone: &az, 1832 }, 1833 }, 1834 }, 1835 }, nil) 1836 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 1837 Return(nil) 1838 }, 1839 check: func(instance *infrav1.Instance, err error) { 1840 if err != nil { 1841 t.Fatalf("did not expect error: %v", err) 1842 } 1843 }, 1844 }, 1845 { 1846 name: "public IP true and no public subnet exists", 1847 machine: clusterv1.Machine{ 1848 ObjectMeta: metav1.ObjectMeta{ 1849 Labels: map[string]string{"set": "node"}, 1850 }, 1851 Spec: clusterv1.MachineSpec{ 1852 Bootstrap: clusterv1.Bootstrap{ 1853 DataSecretName: pointer.StringPtr("bootstrap-data"), 1854 }, 1855 }, 1856 }, 1857 machineConfig: &infrav1.AWSMachineSpec{ 1858 AMI: infrav1.AMIReference{ 1859 ID: aws.String("abc"), 1860 }, 1861 InstanceType: "m5.large", 1862 PublicIP: aws.Bool(true), 1863 }, 1864 awsCluster: &infrav1.AWSCluster{ 1865 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1866 Spec: infrav1.AWSClusterSpec{ 1867 NetworkSpec: infrav1.NetworkSpec{ 1868 VPC: infrav1.VPCSpec{ 1869 ID: "vpc-id", 1870 }, 1871 Subnets: infrav1.Subnets{ 1872 infrav1.SubnetSpec{ 1873 ID: "private-subnet-1", 1874 IsPublic: false, 1875 }, 1876 }, 1877 }, 1878 }, 1879 Status: infrav1.AWSClusterStatus{ 1880 Network: infrav1.NetworkStatus{ 1881 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1882 infrav1.SecurityGroupControlPlane: { 1883 ID: "1", 1884 }, 1885 infrav1.SecurityGroupNode: { 1886 ID: "2", 1887 }, 1888 infrav1.SecurityGroupLB: { 1889 ID: "3", 1890 }, 1891 }, 1892 APIServerELB: infrav1.ClassicELB{ 1893 DNSName: "test-apiserver.us-east-1.aws", 1894 }, 1895 }, 1896 }, 1897 }, 1898 expect: func(m *mocks.MockEC2APIMockRecorder) { 1899 }, 1900 check: func(instance *infrav1.Instance, err error) { 1901 expectedErrMsg := "failed to run machine \"aws-test1\" with public IP, no public subnets available" 1902 if err == nil { 1903 t.Fatalf("Expected error, but got nil") 1904 } 1905 1906 if !strings.Contains(err.Error(), expectedErrMsg) { 1907 t.Fatalf("Expected error: %s\nInstead got: %s", expectedErrMsg, err.Error()) 1908 } 1909 }, 1910 }, 1911 { 1912 name: "with multiple block device mappings", 1913 machine: clusterv1.Machine{ 1914 ObjectMeta: metav1.ObjectMeta{ 1915 Labels: map[string]string{"set": "node"}, 1916 }, 1917 Spec: clusterv1.MachineSpec{ 1918 Bootstrap: clusterv1.Bootstrap{ 1919 DataSecretName: pointer.StringPtr("bootstrap-data"), 1920 }, 1921 }, 1922 }, 1923 machineConfig: &infrav1.AWSMachineSpec{ 1924 AMI: infrav1.AMIReference{ 1925 ID: aws.String("abc"), 1926 }, 1927 InstanceType: "m5.large", 1928 NonRootVolumes: []infrav1.Volume{{ 1929 DeviceName: "device-2", 1930 Size: 8, 1931 }}, 1932 }, 1933 awsCluster: &infrav1.AWSCluster{ 1934 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1935 Spec: infrav1.AWSClusterSpec{ 1936 NetworkSpec: infrav1.NetworkSpec{ 1937 Subnets: infrav1.Subnets{ 1938 infrav1.SubnetSpec{ 1939 ID: "subnet-1", 1940 IsPublic: false, 1941 }, 1942 infrav1.SubnetSpec{ 1943 IsPublic: false, 1944 }, 1945 }, 1946 }, 1947 }, 1948 Status: infrav1.AWSClusterStatus{ 1949 Network: infrav1.NetworkStatus{ 1950 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 1951 infrav1.SecurityGroupControlPlane: { 1952 ID: "1", 1953 }, 1954 infrav1.SecurityGroupNode: { 1955 ID: "2", 1956 }, 1957 infrav1.SecurityGroupLB: { 1958 ID: "3", 1959 }, 1960 }, 1961 APIServerELB: infrav1.ClassicELB{ 1962 DNSName: "test-apiserver.us-east-1.aws", 1963 }, 1964 }, 1965 }, 1966 }, 1967 expect: func(m *mocks.MockEC2APIMockRecorder) { 1968 m. // TODO: Restore these parameters, but with the tags as well 1969 RunInstances(gomock.Any()). 1970 Return(&ec2.Reservation{ 1971 Instances: []*ec2.Instance{ 1972 { 1973 State: &ec2.InstanceState{ 1974 Name: aws.String(ec2.InstanceStateNamePending), 1975 }, 1976 IamInstanceProfile: &ec2.IamInstanceProfile{ 1977 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 1978 }, 1979 InstanceId: aws.String("two"), 1980 InstanceType: aws.String("m5.large"), 1981 SubnetId: aws.String("subnet-1"), 1982 ImageId: aws.String("ami-1"), 1983 RootDeviceName: aws.String("device-1"), 1984 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 1985 { 1986 DeviceName: aws.String("device-1"), 1987 Ebs: &ec2.EbsInstanceBlockDevice{ 1988 VolumeId: aws.String("volume-1"), 1989 }, 1990 }, 1991 { 1992 DeviceName: aws.String("device-2"), 1993 Ebs: &ec2.EbsInstanceBlockDevice{ 1994 VolumeId: aws.String("volume-2"), 1995 }, 1996 }, 1997 }, 1998 Placement: &ec2.Placement{ 1999 AvailabilityZone: &az, 2000 }, 2001 }, 2002 }, 2003 }, nil) 2004 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2005 Return(nil) 2006 }, 2007 check: func(instance *infrav1.Instance, err error) { 2008 if err != nil { 2009 t.Fatalf("did not expect error: %v", err) 2010 } 2011 }, 2012 }, 2013 { 2014 name: "with dedicated tenancy cloud-config", 2015 machine: clusterv1.Machine{ 2016 ObjectMeta: metav1.ObjectMeta{ 2017 Labels: map[string]string{"set": "node"}, 2018 Namespace: "default", 2019 Name: "machine-aws-test1", 2020 }, 2021 Spec: clusterv1.MachineSpec{ 2022 Bootstrap: clusterv1.Bootstrap{ 2023 DataSecretName: pointer.StringPtr("bootstrap-data"), 2024 }, 2025 }, 2026 }, 2027 machineConfig: &infrav1.AWSMachineSpec{ 2028 AMI: infrav1.AMIReference{ 2029 ID: aws.String("abc"), 2030 }, 2031 InstanceType: "m5.large", 2032 Tenancy: "dedicated", 2033 UncompressedUserData: &isUncompressedFalse, 2034 }, 2035 awsCluster: &infrav1.AWSCluster{ 2036 Spec: infrav1.AWSClusterSpec{ 2037 NetworkSpec: infrav1.NetworkSpec{ 2038 Subnets: infrav1.Subnets{ 2039 infrav1.SubnetSpec{ 2040 ID: "subnet-1", 2041 IsPublic: false, 2042 }, 2043 infrav1.SubnetSpec{ 2044 IsPublic: false, 2045 }, 2046 }, 2047 }, 2048 }, 2049 Status: infrav1.AWSClusterStatus{ 2050 Network: infrav1.NetworkStatus{ 2051 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2052 infrav1.SecurityGroupControlPlane: { 2053 ID: "1", 2054 }, 2055 infrav1.SecurityGroupNode: { 2056 ID: "2", 2057 }, 2058 infrav1.SecurityGroupLB: { 2059 ID: "3", 2060 }, 2061 }, 2062 APIServerELB: infrav1.ClassicELB{ 2063 DNSName: "test-apiserver.us-east-1.aws", 2064 }, 2065 }, 2066 }, 2067 }, 2068 expect: func(m *mocks.MockEC2APIMockRecorder) { 2069 m. // TODO: Restore these parameters, but with the tags as well 2070 RunInstances(gomock.Eq(&ec2.RunInstancesInput{ 2071 ImageId: aws.String("abc"), 2072 InstanceType: aws.String("m5.large"), 2073 KeyName: aws.String("default"), 2074 MaxCount: aws.Int64(1), 2075 MinCount: aws.Int64(1), 2076 Placement: &ec2.Placement{ 2077 Tenancy: &tenancy, 2078 }, 2079 SecurityGroupIds: []*string{aws.String("2"), aws.String("3")}, 2080 SubnetId: aws.String("subnet-1"), 2081 TagSpecifications: []*ec2.TagSpecification{ 2082 { 2083 ResourceType: aws.String("instance"), 2084 Tags: []*ec2.Tag{ 2085 { 2086 Key: aws.String("MachineName"), 2087 Value: aws.String("default/machine-aws-test1"), 2088 }, 2089 { 2090 Key: aws.String("Name"), 2091 Value: aws.String("aws-test1"), 2092 }, 2093 { 2094 Key: aws.String("kubernetes.io/cluster/test1"), 2095 Value: aws.String("owned"), 2096 }, 2097 { 2098 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test1"), 2099 Value: aws.String("owned"), 2100 }, 2101 { 2102 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 2103 Value: aws.String("node"), 2104 }, 2105 }, 2106 }, 2107 }, 2108 UserData: aws.String(base64.StdEncoding.EncodeToString(userDataCompressed)), 2109 })). 2110 Return(&ec2.Reservation{ 2111 Instances: []*ec2.Instance{ 2112 { 2113 State: &ec2.InstanceState{ 2114 Name: aws.String(ec2.InstanceStateNamePending), 2115 }, 2116 IamInstanceProfile: &ec2.IamInstanceProfile{ 2117 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2118 }, 2119 InstanceId: aws.String("two"), 2120 InstanceType: aws.String("m5.large"), 2121 SubnetId: aws.String("subnet-1"), 2122 ImageId: aws.String("ami-1"), 2123 RootDeviceName: aws.String("device-1"), 2124 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2125 { 2126 DeviceName: aws.String("device-1"), 2127 Ebs: &ec2.EbsInstanceBlockDevice{ 2128 VolumeId: aws.String("volume-1"), 2129 }, 2130 }, 2131 }, 2132 Placement: &ec2.Placement{ 2133 AvailabilityZone: &az, 2134 Tenancy: &tenancy, 2135 }, 2136 }, 2137 }, 2138 }, nil) 2139 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2140 Return(nil) 2141 }, 2142 check: func(instance *infrav1.Instance, err error) { 2143 if err != nil { 2144 t.Fatalf("did not expect error: %v", err) 2145 } 2146 }, 2147 }, 2148 { 2149 name: "with dedicated tenancy ignition", 2150 machine: clusterv1.Machine{ 2151 ObjectMeta: metav1.ObjectMeta{ 2152 Labels: map[string]string{"set": "node"}, 2153 Namespace: "default", 2154 Name: "machine-aws-test1", 2155 }, 2156 Spec: clusterv1.MachineSpec{ 2157 Bootstrap: clusterv1.Bootstrap{ 2158 DataSecretName: pointer.StringPtr("bootstrap-data"), 2159 }, 2160 }, 2161 }, 2162 machineConfig: &infrav1.AWSMachineSpec{ 2163 AMI: infrav1.AMIReference{ 2164 ID: aws.String("abc"), 2165 }, 2166 InstanceType: "m5.large", 2167 Tenancy: "dedicated", 2168 UncompressedUserData: &isUncompressedTrue, 2169 Ignition: &infrav1.Ignition{}, 2170 }, 2171 awsCluster: &infrav1.AWSCluster{ 2172 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2173 Spec: infrav1.AWSClusterSpec{ 2174 NetworkSpec: infrav1.NetworkSpec{ 2175 Subnets: infrav1.Subnets{ 2176 infrav1.SubnetSpec{ 2177 ID: "subnet-1", 2178 IsPublic: false, 2179 }, 2180 infrav1.SubnetSpec{ 2181 IsPublic: false, 2182 }, 2183 }, 2184 }, 2185 }, 2186 Status: infrav1.AWSClusterStatus{ 2187 Network: infrav1.NetworkStatus{ 2188 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2189 infrav1.SecurityGroupControlPlane: { 2190 ID: "1", 2191 }, 2192 infrav1.SecurityGroupNode: { 2193 ID: "2", 2194 }, 2195 infrav1.SecurityGroupLB: { 2196 ID: "3", 2197 }, 2198 }, 2199 APIServerELB: infrav1.ClassicELB{ 2200 DNSName: "test-apiserver.us-east-1.aws", 2201 }, 2202 }, 2203 }, 2204 }, 2205 expect: func(m *mocks.MockEC2APIMockRecorder) { 2206 m. // TODO: Restore these parameters, but with the tags as well 2207 RunInstances(gomock.Eq(&ec2.RunInstancesInput{ 2208 ImageId: aws.String("abc"), 2209 InstanceType: aws.String("m5.large"), 2210 KeyName: aws.String("default"), 2211 MaxCount: aws.Int64(1), 2212 MinCount: aws.Int64(1), 2213 Placement: &ec2.Placement{ 2214 Tenancy: &tenancy, 2215 }, 2216 SecurityGroupIds: []*string{aws.String("2"), aws.String("3")}, 2217 SubnetId: aws.String("subnet-1"), 2218 TagSpecifications: []*ec2.TagSpecification{ 2219 { 2220 ResourceType: aws.String("instance"), 2221 Tags: []*ec2.Tag{ 2222 { 2223 Key: aws.String("MachineName"), 2224 Value: aws.String("default/machine-aws-test1"), 2225 }, 2226 { 2227 Key: aws.String("Name"), 2228 Value: aws.String("aws-test1"), 2229 }, 2230 { 2231 Key: aws.String("kubernetes.io/cluster/test1"), 2232 Value: aws.String("owned"), 2233 }, 2234 { 2235 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test1"), 2236 Value: aws.String("owned"), 2237 }, 2238 { 2239 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 2240 Value: aws.String("node"), 2241 }, 2242 }, 2243 }, 2244 }, 2245 UserData: aws.String(base64.StdEncoding.EncodeToString(data)), 2246 })). 2247 Return(&ec2.Reservation{ 2248 Instances: []*ec2.Instance{ 2249 { 2250 State: &ec2.InstanceState{ 2251 Name: aws.String(ec2.InstanceStateNamePending), 2252 }, 2253 IamInstanceProfile: &ec2.IamInstanceProfile{ 2254 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2255 }, 2256 InstanceId: aws.String("two"), 2257 InstanceType: aws.String("m5.large"), 2258 SubnetId: aws.String("subnet-1"), 2259 ImageId: aws.String("ami-1"), 2260 RootDeviceName: aws.String("device-1"), 2261 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2262 { 2263 DeviceName: aws.String("device-1"), 2264 Ebs: &ec2.EbsInstanceBlockDevice{ 2265 VolumeId: aws.String("volume-1"), 2266 }, 2267 }, 2268 }, 2269 Placement: &ec2.Placement{ 2270 AvailabilityZone: &az, 2271 Tenancy: &tenancy, 2272 }, 2273 }, 2274 }, 2275 }, nil) 2276 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2277 Return(nil) 2278 }, 2279 check: func(instance *infrav1.Instance, err error) { 2280 if err != nil { 2281 t.Fatalf("did not expect error: %v", err) 2282 } 2283 }, 2284 }, 2285 { 2286 name: "expect the default SSH key when none is provided", 2287 machine: clusterv1.Machine{ 2288 ObjectMeta: metav1.ObjectMeta{ 2289 Labels: map[string]string{"set": "node"}, 2290 }, 2291 Spec: clusterv1.MachineSpec{ 2292 Bootstrap: clusterv1.Bootstrap{ 2293 DataSecretName: pointer.StringPtr("bootstrap-data"), 2294 }, 2295 Version: pointer.StringPtr("v1.16.1"), 2296 }, 2297 }, 2298 machineConfig: &infrav1.AWSMachineSpec{ 2299 InstanceType: "m5.large", 2300 }, 2301 awsCluster: &infrav1.AWSCluster{ 2302 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2303 Spec: infrav1.AWSClusterSpec{ 2304 NetworkSpec: infrav1.NetworkSpec{ 2305 Subnets: infrav1.Subnets{ 2306 infrav1.SubnetSpec{ 2307 ID: "subnet-1", 2308 IsPublic: false, 2309 }, 2310 infrav1.SubnetSpec{ 2311 IsPublic: false, 2312 }, 2313 }, 2314 }, 2315 }, 2316 Status: infrav1.AWSClusterStatus{ 2317 Network: infrav1.NetworkStatus{ 2318 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2319 infrav1.SecurityGroupControlPlane: { 2320 ID: "1", 2321 }, 2322 infrav1.SecurityGroupNode: { 2323 ID: "2", 2324 }, 2325 infrav1.SecurityGroupLB: { 2326 ID: "3", 2327 }, 2328 }, 2329 APIServerELB: infrav1.ClassicELB{ 2330 DNSName: "test-apiserver.us-east-1.aws", 2331 }, 2332 }, 2333 }, 2334 }, 2335 expect: func(m *mocks.MockEC2APIMockRecorder) { 2336 m. 2337 DescribeImages(gomock.Any()). 2338 Return(&ec2.DescribeImagesOutput{ 2339 Images: []*ec2.Image{ 2340 { 2341 Name: aws.String("ami-1"), 2342 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2343 }, 2344 }, 2345 }, nil) 2346 m. // TODO: Restore these parameters, but with the tags as well 2347 RunInstances(gomock.Any()). 2348 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2349 if input.KeyName == nil { 2350 t.Fatal("Expected key name not to be nil") 2351 } 2352 if *input.KeyName != defaultSSHKeyName { 2353 t.Fatalf("Expected SSH key name to be '%s', not '%s'", defaultSSHKeyName, *input.KeyName) 2354 } 2355 return &ec2.Reservation{ 2356 Instances: []*ec2.Instance{ 2357 { 2358 State: &ec2.InstanceState{ 2359 Name: aws.String(ec2.InstanceStateNamePending), 2360 }, 2361 IamInstanceProfile: &ec2.IamInstanceProfile{ 2362 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2363 }, 2364 InstanceId: aws.String("two"), 2365 InstanceType: aws.String("m5.large"), 2366 SubnetId: aws.String("subnet-1"), 2367 ImageId: aws.String("ami-1"), 2368 RootDeviceName: aws.String("device-1"), 2369 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2370 { 2371 DeviceName: aws.String("device-1"), 2372 Ebs: &ec2.EbsInstanceBlockDevice{ 2373 VolumeId: aws.String("volume-1"), 2374 }, 2375 }, 2376 }, 2377 Placement: &ec2.Placement{ 2378 AvailabilityZone: &az, 2379 }, 2380 }, 2381 }, 2382 }, nil 2383 }) 2384 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2385 Return(nil) 2386 }, 2387 check: func(instance *infrav1.Instance, err error) { 2388 if err != nil { 2389 t.Fatalf("did not expect error: %v", err) 2390 } 2391 }, 2392 }, 2393 { 2394 name: "expect to use the cluster level ssh key name when no machine key name is provided", 2395 machine: clusterv1.Machine{ 2396 ObjectMeta: metav1.ObjectMeta{ 2397 Labels: map[string]string{"set": "node"}, 2398 }, 2399 Spec: clusterv1.MachineSpec{ 2400 Bootstrap: clusterv1.Bootstrap{ 2401 DataSecretName: pointer.StringPtr("bootstrap-data"), 2402 }, 2403 Version: pointer.StringPtr("v1.16.1"), 2404 }, 2405 }, 2406 machineConfig: &infrav1.AWSMachineSpec{ 2407 InstanceType: "m5.large", 2408 }, 2409 awsCluster: &infrav1.AWSCluster{ 2410 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2411 Spec: infrav1.AWSClusterSpec{ 2412 NetworkSpec: infrav1.NetworkSpec{ 2413 Subnets: infrav1.Subnets{ 2414 infrav1.SubnetSpec{ 2415 ID: "subnet-1", 2416 IsPublic: false, 2417 }, 2418 infrav1.SubnetSpec{ 2419 IsPublic: false, 2420 }, 2421 }, 2422 }, 2423 SSHKeyName: aws.String("specific-cluster-key-name"), 2424 }, 2425 Status: infrav1.AWSClusterStatus{ 2426 Network: infrav1.NetworkStatus{ 2427 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2428 infrav1.SecurityGroupControlPlane: { 2429 ID: "1", 2430 }, 2431 infrav1.SecurityGroupNode: { 2432 ID: "2", 2433 }, 2434 infrav1.SecurityGroupLB: { 2435 ID: "3", 2436 }, 2437 }, 2438 APIServerELB: infrav1.ClassicELB{ 2439 DNSName: "test-apiserver.us-east-1.aws", 2440 }, 2441 }, 2442 }, 2443 }, 2444 expect: func(m *mocks.MockEC2APIMockRecorder) { 2445 m. 2446 DescribeImages(gomock.Any()). 2447 Return(&ec2.DescribeImagesOutput{ 2448 Images: []*ec2.Image{ 2449 { 2450 Name: aws.String("ami-1"), 2451 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2452 }, 2453 }, 2454 }, nil) 2455 m. // TODO: Restore these parameters, but with the tags as well 2456 RunInstances(gomock.Any()). 2457 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2458 if input.KeyName == nil { 2459 t.Fatal("Expected key name not to be nil") 2460 } 2461 if *input.KeyName != "specific-cluster-key-name" { 2462 t.Fatalf("Expected SSH key name to be '%s', not '%s'", "specific-cluster-key-name", *input.KeyName) 2463 } 2464 return &ec2.Reservation{ 2465 Instances: []*ec2.Instance{ 2466 { 2467 State: &ec2.InstanceState{ 2468 Name: aws.String(ec2.InstanceStateNamePending), 2469 }, 2470 IamInstanceProfile: &ec2.IamInstanceProfile{ 2471 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2472 }, 2473 InstanceId: aws.String("two"), 2474 InstanceType: aws.String("m5.large"), 2475 SubnetId: aws.String("subnet-1"), 2476 ImageId: aws.String("ami-1"), 2477 RootDeviceName: aws.String("device-1"), 2478 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2479 { 2480 DeviceName: aws.String("device-1"), 2481 Ebs: &ec2.EbsInstanceBlockDevice{ 2482 VolumeId: aws.String("volume-1"), 2483 }, 2484 }, 2485 }, 2486 Placement: &ec2.Placement{ 2487 AvailabilityZone: &az, 2488 }, 2489 }, 2490 }, 2491 }, nil 2492 }) 2493 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2494 Return(nil) 2495 }, 2496 check: func(instance *infrav1.Instance, err error) { 2497 if err != nil { 2498 t.Fatalf("did not expect error: %v", err) 2499 } 2500 }, 2501 }, 2502 { 2503 name: "expect to use the machine level ssh key name when both cluster and machine key names are provided", 2504 machine: clusterv1.Machine{ 2505 ObjectMeta: metav1.ObjectMeta{ 2506 Labels: map[string]string{"set": "node"}, 2507 }, 2508 Spec: clusterv1.MachineSpec{ 2509 Bootstrap: clusterv1.Bootstrap{ 2510 DataSecretName: pointer.StringPtr("bootstrap-data"), 2511 }, 2512 Version: pointer.StringPtr("v1.16.1"), 2513 }, 2514 }, 2515 machineConfig: &infrav1.AWSMachineSpec{ 2516 InstanceType: "m5.large", 2517 SSHKeyName: aws.String("specific-machine-ssh-key-name"), 2518 }, 2519 awsCluster: &infrav1.AWSCluster{ 2520 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2521 Spec: infrav1.AWSClusterSpec{ 2522 NetworkSpec: infrav1.NetworkSpec{ 2523 Subnets: infrav1.Subnets{ 2524 infrav1.SubnetSpec{ 2525 ID: "subnet-1", 2526 IsPublic: false, 2527 }, 2528 infrav1.SubnetSpec{ 2529 IsPublic: false, 2530 }, 2531 }, 2532 }, 2533 SSHKeyName: aws.String("specific-cluster-key-name"), 2534 }, 2535 Status: infrav1.AWSClusterStatus{ 2536 Network: infrav1.NetworkStatus{ 2537 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2538 infrav1.SecurityGroupControlPlane: { 2539 ID: "1", 2540 }, 2541 infrav1.SecurityGroupNode: { 2542 ID: "2", 2543 }, 2544 infrav1.SecurityGroupLB: { 2545 ID: "3", 2546 }, 2547 }, 2548 APIServerELB: infrav1.ClassicELB{ 2549 DNSName: "test-apiserver.us-east-1.aws", 2550 }, 2551 }, 2552 }, 2553 }, 2554 expect: func(m *mocks.MockEC2APIMockRecorder) { 2555 m. 2556 DescribeImages(gomock.Any()). 2557 Return(&ec2.DescribeImagesOutput{ 2558 Images: []*ec2.Image{ 2559 { 2560 Name: aws.String("ami-1"), 2561 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2562 }, 2563 }, 2564 }, nil) 2565 m. // TODO: Restore these parameters, but with the tags as well 2566 RunInstances(gomock.Any()). 2567 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2568 if input.KeyName == nil { 2569 t.Fatal("Expected key name not to be nil") 2570 } 2571 if *input.KeyName != "specific-machine-ssh-key-name" { 2572 t.Fatalf("Expected SSH key name to be '%s', not '%s'", "specific-machine-ssh-key-name", *input.KeyName) 2573 } 2574 return &ec2.Reservation{ 2575 Instances: []*ec2.Instance{ 2576 { 2577 State: &ec2.InstanceState{ 2578 Name: aws.String(ec2.InstanceStateNamePending), 2579 }, 2580 IamInstanceProfile: &ec2.IamInstanceProfile{ 2581 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2582 }, 2583 InstanceId: aws.String("two"), 2584 InstanceType: aws.String("m5.large"), 2585 SubnetId: aws.String("subnet-1"), 2586 ImageId: aws.String("ami-1"), 2587 RootDeviceName: aws.String("device-1"), 2588 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2589 { 2590 DeviceName: aws.String("device-1"), 2591 Ebs: &ec2.EbsInstanceBlockDevice{ 2592 VolumeId: aws.String("volume-1"), 2593 }, 2594 }, 2595 }, 2596 Placement: &ec2.Placement{ 2597 AvailabilityZone: &az, 2598 }, 2599 }, 2600 }, 2601 }, nil 2602 }) 2603 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2604 Return(nil) 2605 }, 2606 check: func(instance *infrav1.Instance, err error) { 2607 if err != nil { 2608 t.Fatalf("did not expect error: %v", err) 2609 } 2610 }, 2611 }, 2612 { 2613 name: "expect ssh key to be unset when cluster key name is empty string and machine key name is nil", 2614 machine: clusterv1.Machine{ 2615 ObjectMeta: metav1.ObjectMeta{ 2616 Labels: map[string]string{"set": "node"}, 2617 }, 2618 Spec: clusterv1.MachineSpec{ 2619 Bootstrap: clusterv1.Bootstrap{ 2620 DataSecretName: pointer.StringPtr("bootstrap-data"), 2621 }, 2622 Version: pointer.StringPtr("v1.16.1"), 2623 }, 2624 }, 2625 machineConfig: &infrav1.AWSMachineSpec{ 2626 InstanceType: "m5.large", 2627 SSHKeyName: nil, 2628 }, 2629 awsCluster: &infrav1.AWSCluster{ 2630 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2631 Spec: infrav1.AWSClusterSpec{ 2632 NetworkSpec: infrav1.NetworkSpec{ 2633 Subnets: infrav1.Subnets{ 2634 infrav1.SubnetSpec{ 2635 ID: "subnet-1", 2636 IsPublic: false, 2637 }, 2638 infrav1.SubnetSpec{ 2639 IsPublic: false, 2640 }, 2641 }, 2642 }, 2643 SSHKeyName: aws.String(""), 2644 }, 2645 Status: infrav1.AWSClusterStatus{ 2646 Network: infrav1.NetworkStatus{ 2647 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2648 infrav1.SecurityGroupControlPlane: { 2649 ID: "1", 2650 }, 2651 infrav1.SecurityGroupNode: { 2652 ID: "2", 2653 }, 2654 infrav1.SecurityGroupLB: { 2655 ID: "3", 2656 }, 2657 }, 2658 APIServerELB: infrav1.ClassicELB{ 2659 DNSName: "test-apiserver.us-east-1.aws", 2660 }, 2661 }, 2662 }, 2663 }, 2664 expect: func(m *mocks.MockEC2APIMockRecorder) { 2665 m. 2666 DescribeImages(gomock.Any()). 2667 Return(&ec2.DescribeImagesOutput{ 2668 Images: []*ec2.Image{ 2669 { 2670 Name: aws.String("ami-1"), 2671 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2672 }, 2673 }, 2674 }, nil) 2675 m. // TODO: Restore these parameters, but with the tags as well 2676 RunInstances(gomock.Any()). 2677 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2678 if input.KeyName != nil { 2679 t.Fatalf("Expected key name to be nil/unspecified, not '%s'", *input.KeyName) 2680 } 2681 return &ec2.Reservation{ 2682 Instances: []*ec2.Instance{ 2683 { 2684 State: &ec2.InstanceState{ 2685 Name: aws.String(ec2.InstanceStateNamePending), 2686 }, 2687 IamInstanceProfile: &ec2.IamInstanceProfile{ 2688 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2689 }, 2690 InstanceId: aws.String("two"), 2691 InstanceType: aws.String("m5.large"), 2692 SubnetId: aws.String("subnet-1"), 2693 ImageId: aws.String("ami-1"), 2694 RootDeviceName: aws.String("device-1"), 2695 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2696 { 2697 DeviceName: aws.String("device-1"), 2698 Ebs: &ec2.EbsInstanceBlockDevice{ 2699 VolumeId: aws.String("volume-1"), 2700 }, 2701 }, 2702 }, 2703 Placement: &ec2.Placement{ 2704 AvailabilityZone: &az, 2705 }, 2706 }, 2707 }, 2708 }, nil 2709 }) 2710 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2711 Return(nil) 2712 }, 2713 check: func(instance *infrav1.Instance, err error) { 2714 if err != nil { 2715 t.Fatalf("did not expect error: %v", err) 2716 } 2717 }, 2718 }, 2719 { 2720 name: "expect ssh key to be unset when cluster key name is empty string and machine key name is empty string", 2721 machine: clusterv1.Machine{ 2722 ObjectMeta: metav1.ObjectMeta{ 2723 Labels: map[string]string{"set": "node"}, 2724 }, 2725 Spec: clusterv1.MachineSpec{ 2726 Bootstrap: clusterv1.Bootstrap{ 2727 DataSecretName: pointer.StringPtr("bootstrap-data"), 2728 }, 2729 Version: pointer.StringPtr("v1.16.1"), 2730 }, 2731 }, 2732 machineConfig: &infrav1.AWSMachineSpec{ 2733 InstanceType: "m5.large", 2734 SSHKeyName: aws.String(""), 2735 }, 2736 awsCluster: &infrav1.AWSCluster{ 2737 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2738 Spec: infrav1.AWSClusterSpec{ 2739 NetworkSpec: infrav1.NetworkSpec{ 2740 Subnets: infrav1.Subnets{ 2741 infrav1.SubnetSpec{ 2742 ID: "subnet-1", 2743 IsPublic: false, 2744 }, 2745 infrav1.SubnetSpec{ 2746 IsPublic: false, 2747 }, 2748 }, 2749 }, 2750 SSHKeyName: aws.String(""), 2751 }, 2752 Status: infrav1.AWSClusterStatus{ 2753 Network: infrav1.NetworkStatus{ 2754 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2755 infrav1.SecurityGroupControlPlane: { 2756 ID: "1", 2757 }, 2758 infrav1.SecurityGroupNode: { 2759 ID: "2", 2760 }, 2761 infrav1.SecurityGroupLB: { 2762 ID: "3", 2763 }, 2764 }, 2765 APIServerELB: infrav1.ClassicELB{ 2766 DNSName: "test-apiserver.us-east-1.aws", 2767 }, 2768 }, 2769 }, 2770 }, 2771 expect: func(m *mocks.MockEC2APIMockRecorder) { 2772 m. 2773 DescribeImages(gomock.Any()). 2774 Return(&ec2.DescribeImagesOutput{ 2775 Images: []*ec2.Image{ 2776 { 2777 Name: aws.String("ami-1"), 2778 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2779 }, 2780 }, 2781 }, nil) 2782 m. // TODO: Restore these parameters, but with the tags as well 2783 RunInstances(gomock.Any()). 2784 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2785 if input.KeyName != nil { 2786 t.Fatalf("Expected key name to be nil/unspecified, not '%s'", *input.KeyName) 2787 } 2788 return &ec2.Reservation{ 2789 Instances: []*ec2.Instance{ 2790 { 2791 State: &ec2.InstanceState{ 2792 Name: aws.String(ec2.InstanceStateNamePending), 2793 }, 2794 IamInstanceProfile: &ec2.IamInstanceProfile{ 2795 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2796 }, 2797 InstanceId: aws.String("two"), 2798 InstanceType: aws.String("m5.large"), 2799 SubnetId: aws.String("subnet-1"), 2800 ImageId: aws.String("ami-1"), 2801 RootDeviceName: aws.String("device-1"), 2802 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2803 { 2804 DeviceName: aws.String("device-1"), 2805 Ebs: &ec2.EbsInstanceBlockDevice{ 2806 VolumeId: aws.String("volume-1"), 2807 }, 2808 }, 2809 }, 2810 Placement: &ec2.Placement{ 2811 AvailabilityZone: &az, 2812 }, 2813 }, 2814 }, 2815 }, nil 2816 }) 2817 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2818 Return(nil) 2819 }, 2820 check: func(instance *infrav1.Instance, err error) { 2821 if err != nil { 2822 t.Fatalf("did not expect error: %v", err) 2823 } 2824 }, 2825 }, 2826 { 2827 name: "expect ssh key to be unset when cluster key name is nil and machine key name is empty string", 2828 machine: clusterv1.Machine{ 2829 ObjectMeta: metav1.ObjectMeta{ 2830 Labels: map[string]string{"set": "node"}, 2831 }, 2832 Spec: clusterv1.MachineSpec{ 2833 Bootstrap: clusterv1.Bootstrap{ 2834 DataSecretName: pointer.StringPtr("bootstrap-data"), 2835 }, 2836 Version: pointer.StringPtr("v1.16.1"), 2837 }, 2838 }, 2839 machineConfig: &infrav1.AWSMachineSpec{ 2840 InstanceType: "m5.large", 2841 SSHKeyName: aws.String(""), 2842 }, 2843 awsCluster: &infrav1.AWSCluster{ 2844 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2845 Spec: infrav1.AWSClusterSpec{ 2846 NetworkSpec: infrav1.NetworkSpec{ 2847 Subnets: infrav1.Subnets{ 2848 infrav1.SubnetSpec{ 2849 ID: "subnet-1", 2850 IsPublic: false, 2851 }, 2852 infrav1.SubnetSpec{ 2853 IsPublic: false, 2854 }, 2855 }, 2856 }, 2857 SSHKeyName: nil, 2858 }, 2859 Status: infrav1.AWSClusterStatus{ 2860 Network: infrav1.NetworkStatus{ 2861 SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 2862 infrav1.SecurityGroupControlPlane: { 2863 ID: "1", 2864 }, 2865 infrav1.SecurityGroupNode: { 2866 ID: "2", 2867 }, 2868 infrav1.SecurityGroupLB: { 2869 ID: "3", 2870 }, 2871 }, 2872 APIServerELB: infrav1.ClassicELB{ 2873 DNSName: "test-apiserver.us-east-1.aws", 2874 }, 2875 }, 2876 }, 2877 }, 2878 expect: func(m *mocks.MockEC2APIMockRecorder) { 2879 m. 2880 DescribeImages(gomock.Any()). 2881 Return(&ec2.DescribeImagesOutput{ 2882 Images: []*ec2.Image{ 2883 { 2884 Name: aws.String("ami-1"), 2885 CreationDate: aws.String("2011-02-08T17:02:31.000Z"), 2886 }, 2887 }, 2888 }, nil) 2889 m. // TODO: Restore these parameters, but with the tags as well 2890 RunInstances(gomock.Any()). 2891 DoAndReturn(func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) { 2892 if input.KeyName != nil { 2893 t.Fatalf("Expected key name to be nil/unspecified, not '%s'", *input.KeyName) 2894 } 2895 return &ec2.Reservation{ 2896 Instances: []*ec2.Instance{ 2897 { 2898 State: &ec2.InstanceState{ 2899 Name: aws.String(ec2.InstanceStateNamePending), 2900 }, 2901 IamInstanceProfile: &ec2.IamInstanceProfile{ 2902 Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), 2903 }, 2904 InstanceId: aws.String("two"), 2905 InstanceType: aws.String("m5.large"), 2906 SubnetId: aws.String("subnet-1"), 2907 ImageId: aws.String("ami-1"), 2908 RootDeviceName: aws.String("device-1"), 2909 BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ 2910 { 2911 DeviceName: aws.String("device-1"), 2912 Ebs: &ec2.EbsInstanceBlockDevice{ 2913 VolumeId: aws.String("volume-1"), 2914 }, 2915 }, 2916 }, 2917 Placement: &ec2.Placement{ 2918 AvailabilityZone: &az, 2919 }, 2920 }, 2921 }, 2922 }, nil 2923 }) 2924 m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). 2925 Return(nil) 2926 }, 2927 check: func(instance *infrav1.Instance, err error) { 2928 if err != nil { 2929 t.Fatalf("did not expect error: %v", err) 2930 } 2931 }, 2932 }, 2933 } 2934 2935 for _, tc := range testcases { 2936 t.Run(tc.name, func(t *testing.T) { 2937 mockCtrl := gomock.NewController(t) 2938 ec2Mock := mocks.NewMockEC2API(mockCtrl) 2939 2940 scheme, err := setupScheme() 2941 if err != nil { 2942 t.Fatalf("failed to create scheme: %v", err) 2943 } 2944 2945 cluster := &clusterv1.Cluster{ 2946 ObjectMeta: metav1.ObjectMeta{ 2947 Name: "test1", 2948 }, 2949 Spec: clusterv1.ClusterSpec{ 2950 ClusterNetwork: &clusterv1.ClusterNetwork{ 2951 ServiceDomain: "cluster.local", 2952 Services: &clusterv1.NetworkRanges{ 2953 CIDRBlocks: []string{"192.168.0.0/16"}, 2954 }, 2955 Pods: &clusterv1.NetworkRanges{ 2956 CIDRBlocks: []string{"192.168.0.0/16"}, 2957 }, 2958 }, 2959 }, 2960 } 2961 2962 machine := &tc.machine 2963 2964 awsMachine := &infrav1.AWSMachine{ 2965 ObjectMeta: metav1.ObjectMeta{ 2966 Name: "aws-test1", 2967 OwnerReferences: []metav1.OwnerReference{ 2968 { 2969 APIVersion: clusterv1.GroupVersion.String(), 2970 Kind: "Machine", 2971 Name: "test1", 2972 }, 2973 }, 2974 }, 2975 } 2976 2977 client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret, cluster, machine).Build() 2978 clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{ 2979 Client: client, 2980 Cluster: cluster, 2981 AWSCluster: tc.awsCluster, 2982 }) 2983 if err != nil { 2984 t.Fatalf("Failed to create test context: %v", err) 2985 } 2986 2987 machineScope, err := scope.NewMachineScope(scope.MachineScopeParams{ 2988 Client: client, 2989 Cluster: cluster, 2990 Machine: machine, 2991 AWSMachine: awsMachine, 2992 InfraCluster: clusterScope, 2993 }) 2994 if err != nil { 2995 t.Fatalf("Failed to create test context: %v", err) 2996 } 2997 machineScope.AWSMachine.Spec = *tc.machineConfig 2998 tc.expect(ec2Mock.EXPECT()) 2999 3000 s := NewService(clusterScope) 3001 s.EC2Client = ec2Mock 3002 3003 instance, err := s.CreateInstance(machineScope, data, "") 3004 tc.check(instance, err) 3005 }) 3006 } 3007 } 3008 3009 func TestGetInstanceMarketOptionsRequest(t *testing.T) { 3010 testCases := []struct { 3011 name string 3012 spotMarketOptions *infrav1.SpotMarketOptions 3013 expectedRequest *ec2.InstanceMarketOptionsRequest 3014 }{ 3015 { 3016 name: "with no Spot options specified", 3017 spotMarketOptions: nil, 3018 expectedRequest: nil, 3019 }, 3020 { 3021 name: "with an empty Spot options specified", 3022 spotMarketOptions: &infrav1.SpotMarketOptions{}, 3023 expectedRequest: &ec2.InstanceMarketOptionsRequest{ 3024 MarketType: aws.String(ec2.MarketTypeSpot), 3025 SpotOptions: &ec2.SpotMarketOptions{ 3026 InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate), 3027 SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime), 3028 }, 3029 }, 3030 }, 3031 { 3032 name: "with an empty MaxPrice specified", 3033 spotMarketOptions: &infrav1.SpotMarketOptions{ 3034 MaxPrice: aws.String(""), 3035 }, 3036 expectedRequest: &ec2.InstanceMarketOptionsRequest{ 3037 MarketType: aws.String(ec2.MarketTypeSpot), 3038 SpotOptions: &ec2.SpotMarketOptions{ 3039 InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate), 3040 SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime), 3041 }, 3042 }, 3043 }, 3044 { 3045 name: "with a valid MaxPrice specified", 3046 spotMarketOptions: &infrav1.SpotMarketOptions{ 3047 MaxPrice: aws.String("0.01"), 3048 }, 3049 expectedRequest: &ec2.InstanceMarketOptionsRequest{ 3050 MarketType: aws.String(ec2.MarketTypeSpot), 3051 SpotOptions: &ec2.SpotMarketOptions{ 3052 InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate), 3053 SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime), 3054 MaxPrice: aws.String("0.01"), 3055 }, 3056 }, 3057 }, 3058 } 3059 3060 for _, tc := range testCases { 3061 t.Run(tc.name, func(t *testing.T) { 3062 request := getInstanceMarketOptionsRequest(tc.spotMarketOptions) 3063 if !cmp.Equal(request, tc.expectedRequest) { 3064 t.Errorf("Case: %s. Got: %v, expected: %v", tc.name, request, tc.expectedRequest) 3065 } 3066 }) 3067 } 3068 } 3069 3070 func TestGetFilteredSecurityGroupID(t *testing.T) { 3071 mockCtrl := gomock.NewController(t) 3072 defer mockCtrl.Finish() 3073 3074 securityGroupFilterName := "sg1" 3075 securityGroupFilterValues := []string{"test"} 3076 securityGroupID := "1" 3077 3078 testCases := []struct { 3079 name string 3080 securityGroup infrav1.AWSResourceReference 3081 expect func(m *mocks.MockEC2APIMockRecorder) 3082 check func(id string, err error) 3083 }{ 3084 { 3085 name: "successfully return security group id", 3086 securityGroup: infrav1.AWSResourceReference{ 3087 Filters: []infrav1.Filter{ 3088 { 3089 Name: securityGroupFilterName, Values: securityGroupFilterValues, 3090 }, 3091 }, 3092 }, 3093 expect: func(m *mocks.MockEC2APIMockRecorder) { 3094 m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{ 3095 Filters: []*ec2.Filter{ 3096 { 3097 Name: aws.String(securityGroupFilterName), 3098 Values: aws.StringSlice(securityGroupFilterValues), 3099 }, 3100 }, 3101 })).Return( 3102 &ec2.DescribeSecurityGroupsOutput{ 3103 SecurityGroups: []*ec2.SecurityGroup{ 3104 { 3105 GroupId: aws.String(securityGroupID), 3106 }, 3107 }, 3108 }, nil) 3109 }, 3110 check: func(id string, err error) { 3111 if err != nil { 3112 t.Fatalf("did not expect error: %v", err) 3113 } 3114 3115 if id != securityGroupID { 3116 t.Fatalf("expected security group id %v but got: %v", securityGroupID, id) 3117 } 3118 }, 3119 }, 3120 { 3121 name: "return early when filters are missing", 3122 securityGroup: infrav1.AWSResourceReference{}, 3123 expect: func(m *mocks.MockEC2APIMockRecorder) {}, 3124 check: func(id string, err error) { 3125 if err != nil { 3126 t.Fatalf("did not expect error: %v", err) 3127 } 3128 3129 if id != "" { 3130 t.Fatalf("didn't expect secutity group id %v", id) 3131 } 3132 }, 3133 }, 3134 { 3135 name: "error describing security group", 3136 securityGroup: infrav1.AWSResourceReference{ 3137 Filters: []infrav1.Filter{ 3138 { 3139 Name: securityGroupFilterName, Values: securityGroupFilterValues, 3140 }, 3141 }, 3142 }, 3143 expect: func(m *mocks.MockEC2APIMockRecorder) { 3144 m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{ 3145 Filters: []*ec2.Filter{ 3146 { 3147 Name: aws.String(securityGroupFilterName), 3148 Values: aws.StringSlice(securityGroupFilterValues), 3149 }, 3150 }, 3151 })).Return(nil, errors.New("some error")) 3152 }, 3153 check: func(id string, err error) { 3154 if err == nil { 3155 t.Fatalf("expected error but got none.") 3156 } 3157 }, 3158 }, 3159 { 3160 name: "error when no security groups found", 3161 securityGroup: infrav1.AWSResourceReference{ 3162 Filters: []infrav1.Filter{ 3163 { 3164 Name: securityGroupFilterName, Values: securityGroupFilterValues, 3165 }, 3166 }, 3167 }, 3168 expect: func(m *mocks.MockEC2APIMockRecorder) { 3169 m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{ 3170 Filters: []*ec2.Filter{ 3171 { 3172 Name: aws.String(securityGroupFilterName), 3173 Values: aws.StringSlice(securityGroupFilterValues), 3174 }, 3175 }, 3176 })).Return( 3177 &ec2.DescribeSecurityGroupsOutput{ 3178 SecurityGroups: []*ec2.SecurityGroup{}, 3179 }, nil) 3180 }, 3181 check: func(id string, err error) { 3182 if err == nil { 3183 t.Fatalf("expected error but got none.") 3184 } 3185 }, 3186 }, 3187 } 3188 3189 for _, tc := range testCases { 3190 t.Run(tc.name, func(t *testing.T) { 3191 ec2Mock := mocks.NewMockEC2API(mockCtrl) 3192 tc.expect(ec2Mock.EXPECT()) 3193 3194 s := Service{ 3195 EC2Client: ec2Mock, 3196 } 3197 3198 id, err := s.getFilteredSecurityGroupID(tc.securityGroup) 3199 tc.check(id, err) 3200 }) 3201 } 3202 }