sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/network/subnets_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 network 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "testing" 23 24 "github.com/aws/aws-sdk-go/aws" 25 "github.com/aws/aws-sdk-go/service/ec2" 26 "github.com/golang/mock/gomock" 27 "github.com/google/go-cmp/cmp" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "sigs.k8s.io/controller-runtime/pkg/client/fake" 31 32 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 33 ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1" 34 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 35 "sigs.k8s.io/cluster-api-provider-aws/test/mocks" 36 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 37 ) 38 39 const ( 40 subnetsVPCID = "vpc-subnets" 41 ) 42 43 func TestReconcileSubnets(t *testing.T) { 44 testCases := []struct { 45 name string 46 input ScopeBuilder 47 expect func(m *mocks.MockEC2APIMockRecorder) 48 errorExpected bool 49 }{ 50 { 51 name: "Unmanaged VPC, 2 existing subnets in vpc, 2 subnet in spec, subnets match, with routes, should succeed", 52 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 53 VPC: infrav1.VPCSpec{ 54 ID: subnetsVPCID, 55 }, 56 Subnets: []infrav1.SubnetSpec{ 57 { 58 ID: "subnet-1", 59 }, 60 { 61 ID: "subnet-2", 62 }, 63 }, 64 }), 65 expect: func(m *mocks.MockEC2APIMockRecorder) { 66 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 67 Filters: []*ec2.Filter{ 68 { 69 Name: aws.String("state"), 70 Values: []*string{aws.String("pending"), aws.String("available")}, 71 }, 72 { 73 Name: aws.String("vpc-id"), 74 Values: []*string{aws.String(subnetsVPCID)}, 75 }, 76 }, 77 })). 78 Return(&ec2.DescribeSubnetsOutput{ 79 Subnets: []*ec2.Subnet{ 80 { 81 VpcId: aws.String(subnetsVPCID), 82 SubnetId: aws.String("subnet-1"), 83 AvailabilityZone: aws.String("us-east-1a"), 84 CidrBlock: aws.String("10.0.10.0/24"), 85 MapPublicIpOnLaunch: aws.Bool(false), 86 }, 87 { 88 VpcId: aws.String(subnetsVPCID), 89 SubnetId: aws.String("subnet-2"), 90 AvailabilityZone: aws.String("us-east-1a"), 91 CidrBlock: aws.String("10.0.20.0/24"), 92 MapPublicIpOnLaunch: aws.Bool(false), 93 }, 94 }, 95 }, nil) 96 97 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 98 Return(&ec2.DescribeRouteTablesOutput{ 99 RouteTables: []*ec2.RouteTable{ 100 { 101 VpcId: aws.String(subnetsVPCID), 102 Associations: []*ec2.RouteTableAssociation{ 103 { 104 SubnetId: aws.String("subnet-1"), 105 RouteTableId: aws.String("rt-12345"), 106 }, 107 }, 108 Routes: []*ec2.Route{ 109 { 110 GatewayId: aws.String("igw-12345"), 111 }, 112 }, 113 }, 114 }, 115 }, nil) 116 117 m.DescribeNatGatewaysPages( 118 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 119 Filter: []*ec2.Filter{ 120 { 121 Name: aws.String("vpc-id"), 122 Values: []*string{aws.String(subnetsVPCID)}, 123 }, 124 { 125 Name: aws.String("state"), 126 Values: []*string{aws.String("pending"), aws.String("available")}, 127 }, 128 }, 129 }), 130 gomock.Any()).Return(nil) 131 132 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 133 Resources: aws.StringSlice([]string{"subnet-1"}), 134 Tags: []*ec2.Tag{ 135 { 136 Key: aws.String("kubernetes.io/cluster/test-cluster"), 137 Value: aws.String("shared"), 138 }, 139 { 140 Key: aws.String("kubernetes.io/role/elb"), 141 Value: aws.String("1"), 142 }, 143 }, 144 })). 145 Return(&ec2.CreateTagsOutput{}, nil) 146 147 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 148 Resources: aws.StringSlice([]string{"subnet-2"}), 149 Tags: []*ec2.Tag{ 150 { 151 Key: aws.String("kubernetes.io/cluster/test-cluster"), 152 Value: aws.String("shared"), 153 }, 154 { 155 Key: aws.String("kubernetes.io/role/internal-elb"), 156 Value: aws.String("1"), 157 }, 158 }, 159 })). 160 Return(&ec2.CreateTagsOutput{}, nil) 161 }, 162 }, 163 { 164 name: "Unmanaged VPC, 2 existing subnets in vpc, 2 subnet in spec, subnets match, no routes, should succeed", 165 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 166 VPC: infrav1.VPCSpec{ 167 ID: subnetsVPCID, 168 }, 169 Subnets: []infrav1.SubnetSpec{ 170 { 171 ID: "subnet-1", 172 }, 173 { 174 ID: "subnet-2", 175 }, 176 }, 177 }), 178 expect: func(m *mocks.MockEC2APIMockRecorder) { 179 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 180 Filters: []*ec2.Filter{ 181 { 182 Name: aws.String("state"), 183 Values: []*string{aws.String("pending"), aws.String("available")}, 184 }, 185 { 186 Name: aws.String("vpc-id"), 187 Values: []*string{aws.String(subnetsVPCID)}, 188 }, 189 }, 190 })). 191 Return(&ec2.DescribeSubnetsOutput{ 192 Subnets: []*ec2.Subnet{ 193 { 194 VpcId: aws.String(subnetsVPCID), 195 SubnetId: aws.String("subnet-1"), 196 AvailabilityZone: aws.String("us-east-1a"), 197 CidrBlock: aws.String("10.0.10.0/24"), 198 MapPublicIpOnLaunch: aws.Bool(false), 199 }, 200 { 201 VpcId: aws.String(subnetsVPCID), 202 SubnetId: aws.String("subnet-2"), 203 AvailabilityZone: aws.String("us-east-1a"), 204 CidrBlock: aws.String("10.0.20.0/24"), 205 MapPublicIpOnLaunch: aws.Bool(false), 206 }, 207 }, 208 }, nil) 209 210 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 211 Return(&ec2.DescribeRouteTablesOutput{}, nil) 212 213 m.DescribeNatGatewaysPages( 214 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 215 Filter: []*ec2.Filter{ 216 { 217 Name: aws.String("vpc-id"), 218 Values: []*string{aws.String(subnetsVPCID)}, 219 }, 220 { 221 Name: aws.String("state"), 222 Values: []*string{aws.String("pending"), aws.String("available")}, 223 }, 224 }, 225 }), 226 gomock.Any()).Return(nil) 227 228 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 229 Resources: aws.StringSlice([]string{"subnet-1"}), 230 Tags: []*ec2.Tag{ 231 { 232 Key: aws.String("kubernetes.io/cluster/test-cluster"), 233 Value: aws.String("shared"), 234 }, 235 { 236 Key: aws.String("kubernetes.io/role/internal-elb"), 237 Value: aws.String("1"), 238 }, 239 }, 240 })). 241 Return(&ec2.CreateTagsOutput{}, nil) 242 243 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 244 Resources: aws.StringSlice([]string{"subnet-2"}), 245 Tags: []*ec2.Tag{ 246 { 247 Key: aws.String("kubernetes.io/cluster/test-cluster"), 248 Value: aws.String("shared"), 249 }, 250 { 251 Key: aws.String("kubernetes.io/role/internal-elb"), 252 Value: aws.String("1"), 253 }, 254 }, 255 })). 256 Return(&ec2.CreateTagsOutput{}, nil) 257 }, 258 errorExpected: false, 259 }, 260 { 261 name: "Unmanaged VPC, 2 existing matching subnets, subnet tagging fails, should succeed", 262 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 263 VPC: infrav1.VPCSpec{ 264 ID: subnetsVPCID, 265 }, 266 Subnets: []infrav1.SubnetSpec{ 267 { 268 ID: "subnet-1", 269 }, 270 { 271 ID: "subnet-2", 272 }, 273 }, 274 }), 275 expect: func(m *mocks.MockEC2APIMockRecorder) { 276 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 277 Filters: []*ec2.Filter{ 278 { 279 Name: aws.String("state"), 280 Values: []*string{aws.String("pending"), aws.String("available")}, 281 }, 282 { 283 Name: aws.String("vpc-id"), 284 Values: []*string{aws.String(subnetsVPCID)}, 285 }, 286 }, 287 })). 288 Return(&ec2.DescribeSubnetsOutput{ 289 Subnets: []*ec2.Subnet{ 290 { 291 VpcId: aws.String(subnetsVPCID), 292 SubnetId: aws.String("subnet-1"), 293 AvailabilityZone: aws.String("us-east-1a"), 294 CidrBlock: aws.String("10.0.10.0/24"), 295 MapPublicIpOnLaunch: aws.Bool(false), 296 }, 297 { 298 VpcId: aws.String(subnetsVPCID), 299 SubnetId: aws.String("subnet-2"), 300 AvailabilityZone: aws.String("us-east-1a"), 301 CidrBlock: aws.String("10.0.20.0/24"), 302 MapPublicIpOnLaunch: aws.Bool(false), 303 }, 304 }, 305 }, nil) 306 307 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 308 Return(&ec2.DescribeRouteTablesOutput{ 309 RouteTables: []*ec2.RouteTable{ 310 { 311 VpcId: aws.String(subnetsVPCID), 312 Associations: []*ec2.RouteTableAssociation{ 313 { 314 SubnetId: aws.String("subnet-1"), 315 RouteTableId: aws.String("rt-12345"), 316 }, 317 }, 318 Routes: []*ec2.Route{ 319 { 320 GatewayId: aws.String("igw-12345"), 321 }, 322 }, 323 }, 324 }, 325 }, nil) 326 327 m.DescribeNatGatewaysPages( 328 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 329 Filter: []*ec2.Filter{ 330 { 331 Name: aws.String("vpc-id"), 332 Values: []*string{aws.String(subnetsVPCID)}, 333 }, 334 { 335 Name: aws.String("state"), 336 Values: []*string{aws.String("pending"), aws.String("available")}, 337 }, 338 }, 339 }), 340 gomock.Any()).Return(nil) 341 342 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 343 Resources: aws.StringSlice([]string{"subnet-1"}), 344 Tags: []*ec2.Tag{ 345 { 346 Key: aws.String("kubernetes.io/cluster/test-cluster"), 347 Value: aws.String("shared"), 348 }, 349 { 350 Key: aws.String("kubernetes.io/role/elb"), 351 Value: aws.String("1"), 352 }, 353 }, 354 })). 355 Return(&ec2.CreateTagsOutput{}, fmt.Errorf("tagging failed")) 356 }, 357 }, 358 { 359 name: "Unmanaged VPC, 2 existing subnets in vpc, 0 subnet in spec, should fail", 360 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 361 VPC: infrav1.VPCSpec{ 362 ID: subnetsVPCID, 363 }, 364 Subnets: []infrav1.SubnetSpec{}, 365 }), 366 expect: func(m *mocks.MockEC2APIMockRecorder) { 367 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 368 Filters: []*ec2.Filter{ 369 { 370 Name: aws.String("state"), 371 Values: []*string{aws.String("pending"), aws.String("available")}, 372 }, 373 { 374 Name: aws.String("vpc-id"), 375 Values: []*string{aws.String(subnetsVPCID)}, 376 }, 377 }, 378 })). 379 Return(&ec2.DescribeSubnetsOutput{ 380 Subnets: []*ec2.Subnet{ 381 { 382 VpcId: aws.String(subnetsVPCID), 383 SubnetId: aws.String("subnet-1"), 384 AvailabilityZone: aws.String("us-east-1a"), 385 CidrBlock: aws.String("10.0.10.0/24"), 386 MapPublicIpOnLaunch: aws.Bool(false), 387 }, 388 { 389 VpcId: aws.String(subnetsVPCID), 390 SubnetId: aws.String("subnet-2"), 391 AvailabilityZone: aws.String("us-east-1a"), 392 CidrBlock: aws.String("10.0.20.0/24"), 393 MapPublicIpOnLaunch: aws.Bool(false), 394 }, 395 }, 396 }, nil) 397 398 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 399 Return(&ec2.DescribeRouteTablesOutput{}, nil) 400 401 m.DescribeNatGatewaysPages( 402 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 403 Filter: []*ec2.Filter{ 404 { 405 Name: aws.String("vpc-id"), 406 Values: []*string{aws.String(subnetsVPCID)}, 407 }, 408 { 409 Name: aws.String("state"), 410 Values: []*string{aws.String("pending"), aws.String("available")}, 411 }, 412 }, 413 }), 414 gomock.Any()).Return(nil) 415 }, 416 errorExpected: true, 417 }, 418 { 419 name: "Unmanaged VPC, 0 existing subnets in vpc, 2 subnets in spec, should fail", 420 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 421 VPC: infrav1.VPCSpec{ 422 ID: subnetsVPCID, 423 }, 424 Subnets: []infrav1.SubnetSpec{ 425 { 426 AvailabilityZone: "us-east-1a", 427 CidrBlock: "10.1.0.0/16", 428 IsPublic: false, 429 }, 430 { 431 AvailabilityZone: "us-east-1b", 432 CidrBlock: "10.2.0.0/16", 433 IsPublic: true, 434 }, 435 }, 436 }), 437 expect: func(m *mocks.MockEC2APIMockRecorder) { 438 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 439 Filters: []*ec2.Filter{ 440 { 441 Name: aws.String("state"), 442 Values: []*string{aws.String("pending"), aws.String("available")}, 443 }, 444 { 445 Name: aws.String("vpc-id"), 446 Values: []*string{aws.String(subnetsVPCID)}, 447 }, 448 }, 449 })). 450 Return(&ec2.DescribeSubnetsOutput{}, nil) 451 452 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 453 Return(&ec2.DescribeRouteTablesOutput{}, nil) 454 455 m.DescribeNatGatewaysPages( 456 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 457 Filter: []*ec2.Filter{ 458 { 459 Name: aws.String("vpc-id"), 460 Values: []*string{aws.String(subnetsVPCID)}, 461 }, 462 { 463 Name: aws.String("state"), 464 Values: []*string{aws.String("pending"), aws.String("available")}, 465 }, 466 }, 467 }), 468 gomock.Any()).Return(nil) 469 }, 470 errorExpected: true, 471 }, 472 { 473 name: "Unmanaged VPC, 2 subnets exist, 2 private subnet in spec, should succeed", 474 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 475 VPC: infrav1.VPCSpec{ 476 ID: subnetsVPCID, 477 }, 478 Subnets: []infrav1.SubnetSpec{ 479 { 480 AvailabilityZone: "us-east-1a", 481 CidrBlock: "10.0.10.0/24", 482 IsPublic: false, 483 }, 484 { 485 AvailabilityZone: "us-east-1b", 486 CidrBlock: "10.0.20.0/24", 487 IsPublic: false, 488 }, 489 }, 490 }), 491 expect: func(m *mocks.MockEC2APIMockRecorder) { 492 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 493 Filters: []*ec2.Filter{ 494 { 495 Name: aws.String("state"), 496 Values: []*string{aws.String("pending"), aws.String("available")}, 497 }, 498 { 499 Name: aws.String("vpc-id"), 500 Values: []*string{aws.String(subnetsVPCID)}, 501 }, 502 }, 503 })). 504 Return(&ec2.DescribeSubnetsOutput{ 505 Subnets: []*ec2.Subnet{ 506 { 507 VpcId: aws.String(subnetsVPCID), 508 SubnetId: aws.String("subnet-1"), 509 AvailabilityZone: aws.String("us-east-1a"), 510 CidrBlock: aws.String("10.0.10.0/24"), 511 MapPublicIpOnLaunch: aws.Bool(false), 512 }, 513 { 514 VpcId: aws.String(subnetsVPCID), 515 SubnetId: aws.String("subnet-2"), 516 AvailabilityZone: aws.String("us-east-1a"), 517 CidrBlock: aws.String("10.0.20.0/24"), 518 MapPublicIpOnLaunch: aws.Bool(false), 519 }, 520 }, 521 }, nil) 522 523 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 524 Return(&ec2.DescribeRouteTablesOutput{}, nil) 525 526 m.DescribeNatGatewaysPages( 527 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 528 Filter: []*ec2.Filter{ 529 { 530 Name: aws.String("vpc-id"), 531 Values: []*string{aws.String(subnetsVPCID)}, 532 }, 533 { 534 Name: aws.String("state"), 535 Values: []*string{aws.String("pending"), aws.String("available")}, 536 }, 537 }, 538 }), 539 gomock.Any()).Return(nil) 540 541 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 542 Resources: aws.StringSlice([]string{"subnet-1"}), 543 Tags: []*ec2.Tag{ 544 { 545 Key: aws.String("kubernetes.io/cluster/test-cluster"), 546 Value: aws.String("shared"), 547 }, 548 { 549 Key: aws.String("kubernetes.io/role/internal-elb"), 550 Value: aws.String("1"), 551 }, 552 }, 553 })). 554 Return(&ec2.CreateTagsOutput{}, nil) 555 556 m.CreateTags(gomock.Eq(&ec2.CreateTagsInput{ 557 Resources: aws.StringSlice([]string{"subnet-2"}), 558 Tags: []*ec2.Tag{ 559 { 560 Key: aws.String("kubernetes.io/cluster/test-cluster"), 561 Value: aws.String("shared"), 562 }, 563 { 564 Key: aws.String("kubernetes.io/role/internal-elb"), 565 Value: aws.String("1"), 566 }, 567 }, 568 })). 569 Return(&ec2.CreateTagsOutput{}, nil) 570 }, 571 errorExpected: false, 572 }, 573 { 574 name: "Managed VPC, no subnets exist, 1 private and 1 public subnet in spec, create both", 575 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 576 VPC: infrav1.VPCSpec{ 577 ID: subnetsVPCID, 578 Tags: infrav1.Tags{ 579 infrav1.ClusterTagKey("test-cluster"): "owned", 580 }, 581 }, 582 Subnets: []infrav1.SubnetSpec{ 583 { 584 AvailabilityZone: "us-east-1a", 585 CidrBlock: "10.1.0.0/16", 586 IsPublic: false, 587 }, 588 { 589 AvailabilityZone: "us-east-1b", 590 CidrBlock: "10.2.0.0/16", 591 IsPublic: true, 592 }, 593 }, 594 }), 595 expect: func(m *mocks.MockEC2APIMockRecorder) { 596 describeCall := m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 597 Filters: []*ec2.Filter{ 598 { 599 Name: aws.String("state"), 600 Values: []*string{aws.String("pending"), aws.String("available")}, 601 }, 602 { 603 Name: aws.String("vpc-id"), 604 Values: []*string{aws.String(subnetsVPCID)}, 605 }, 606 }, 607 })). 608 Return(&ec2.DescribeSubnetsOutput{}, nil) 609 610 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 611 Return(&ec2.DescribeRouteTablesOutput{}, nil) 612 613 m.DescribeNatGatewaysPages( 614 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 615 Filter: []*ec2.Filter{ 616 { 617 Name: aws.String("vpc-id"), 618 Values: []*string{aws.String(subnetsVPCID)}, 619 }, 620 { 621 Name: aws.String("state"), 622 Values: []*string{aws.String("pending"), aws.String("available")}, 623 }, 624 }, 625 }), 626 gomock.Any()).Return(nil) 627 628 firstSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 629 VpcId: aws.String(subnetsVPCID), 630 CidrBlock: aws.String("10.1.0.0/16"), 631 AvailabilityZone: aws.String("us-east-1a"), 632 TagSpecifications: []*ec2.TagSpecification{ 633 { 634 ResourceType: aws.String("subnet"), 635 Tags: []*ec2.Tag{ 636 { 637 Key: aws.String("Name"), 638 Value: aws.String("test-cluster-subnet-private-us-east-1a"), 639 }, 640 { 641 Key: aws.String("kubernetes.io/cluster/test-cluster"), 642 Value: aws.String("shared"), 643 }, 644 { 645 Key: aws.String("kubernetes.io/role/internal-elb"), 646 Value: aws.String("1"), 647 }, 648 { 649 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 650 Value: aws.String("owned"), 651 }, 652 { 653 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 654 Value: aws.String("private"), 655 }, 656 }, 657 }, 658 }, 659 })). 660 Return(&ec2.CreateSubnetOutput{ 661 Subnet: &ec2.Subnet{ 662 VpcId: aws.String(subnetsVPCID), 663 SubnetId: aws.String("subnet-1"), 664 CidrBlock: aws.String("10.1.0.0/16"), 665 AvailabilityZone: aws.String("us-east-1a"), 666 MapPublicIpOnLaunch: aws.Bool(false), 667 }, 668 }, nil). 669 After(describeCall) 670 671 m.WaitUntilSubnetAvailable(gomock.Any()). 672 After(firstSubnet) 673 674 secondSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 675 VpcId: aws.String(subnetsVPCID), 676 CidrBlock: aws.String("10.2.0.0/16"), 677 AvailabilityZone: aws.String("us-east-1b"), 678 TagSpecifications: []*ec2.TagSpecification{ 679 { 680 ResourceType: aws.String("subnet"), 681 Tags: []*ec2.Tag{ 682 { 683 Key: aws.String("Name"), 684 Value: aws.String("test-cluster-subnet-public-us-east-1b"), 685 }, 686 { 687 Key: aws.String("kubernetes.io/cluster/test-cluster"), 688 Value: aws.String("shared"), 689 }, 690 { 691 Key: aws.String("kubernetes.io/role/elb"), 692 Value: aws.String("1"), 693 }, 694 { 695 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 696 Value: aws.String("owned"), 697 }, 698 { 699 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 700 Value: aws.String("public"), 701 }, 702 }, 703 }, 704 }, 705 })). 706 Return(&ec2.CreateSubnetOutput{ 707 Subnet: &ec2.Subnet{ 708 VpcId: aws.String(subnetsVPCID), 709 SubnetId: aws.String("subnet-2"), 710 CidrBlock: aws.String("10.2.0.0/16"), 711 AvailabilityZone: aws.String("us-east-1a"), 712 MapPublicIpOnLaunch: aws.Bool(false), 713 }, 714 }, nil). 715 After(firstSubnet) 716 717 m.WaitUntilSubnetAvailable(gomock.Any()). 718 After(secondSubnet) 719 720 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 721 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 722 Value: aws.Bool(true), 723 }, 724 SubnetId: aws.String("subnet-2"), 725 }). 726 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 727 After(secondSubnet) 728 }, 729 }, 730 { 731 name: "Managed VPC, no subnets exist, 1 private subnet in spec (no public subnet), should fail", 732 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 733 VPC: infrav1.VPCSpec{ 734 ID: subnetsVPCID, 735 Tags: infrav1.Tags{ 736 infrav1.ClusterTagKey("test-cluster"): "owned", 737 }, 738 }, 739 Subnets: []infrav1.SubnetSpec{ 740 { 741 AvailabilityZone: "us-east-1a", 742 CidrBlock: "10.1.0.0/16", 743 IsPublic: false, 744 }, 745 }, 746 }), 747 expect: func(m *mocks.MockEC2APIMockRecorder) { 748 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 749 Filters: []*ec2.Filter{ 750 { 751 Name: aws.String("state"), 752 Values: []*string{aws.String("pending"), aws.String("available")}, 753 }, 754 { 755 Name: aws.String("vpc-id"), 756 Values: []*string{aws.String(subnetsVPCID)}, 757 }, 758 }, 759 })). 760 Return(&ec2.DescribeSubnetsOutput{}, nil) 761 762 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 763 Return(&ec2.DescribeRouteTablesOutput{}, nil) 764 765 m.DescribeNatGatewaysPages( 766 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 767 Filter: []*ec2.Filter{ 768 { 769 Name: aws.String("vpc-id"), 770 Values: []*string{aws.String(subnetsVPCID)}, 771 }, 772 { 773 Name: aws.String("state"), 774 Values: []*string{aws.String("pending"), aws.String("available")}, 775 }, 776 }, 777 }), 778 gomock.Any()).Return(nil) 779 }, 780 errorExpected: true, 781 }, 782 { 783 name: "Managed VPC, no existing subnets exist, one az, expect one private and one public from default", 784 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 785 VPC: infrav1.VPCSpec{ 786 ID: subnetsVPCID, 787 Tags: infrav1.Tags{ 788 infrav1.ClusterTagKey("test-cluster"): "owned", 789 }, 790 CidrBlock: defaultVPCCidr, 791 }, 792 Subnets: []infrav1.SubnetSpec{}, 793 }), 794 expect: func(m *mocks.MockEC2APIMockRecorder) { 795 m.DescribeAvailabilityZones(gomock.Any()). 796 Return(&ec2.DescribeAvailabilityZonesOutput{ 797 AvailabilityZones: []*ec2.AvailabilityZone{ 798 { 799 ZoneName: aws.String("us-east-1c"), 800 }, 801 }, 802 }, nil) 803 804 describeCall := m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 805 Filters: []*ec2.Filter{ 806 { 807 Name: aws.String("state"), 808 Values: []*string{aws.String("pending"), aws.String("available")}, 809 }, 810 { 811 Name: aws.String("vpc-id"), 812 Values: []*string{aws.String(subnetsVPCID)}, 813 }, 814 }, 815 })). 816 Return(&ec2.DescribeSubnetsOutput{}, nil) 817 818 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 819 Return(&ec2.DescribeRouteTablesOutput{}, nil) 820 821 m.DescribeNatGatewaysPages( 822 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 823 Filter: []*ec2.Filter{ 824 { 825 Name: aws.String("vpc-id"), 826 Values: []*string{aws.String(subnetsVPCID)}, 827 }, 828 { 829 Name: aws.String("state"), 830 Values: []*string{aws.String("pending"), aws.String("available")}, 831 }, 832 }, 833 }), 834 gomock.Any()).Return(nil) 835 836 firstSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 837 VpcId: aws.String(subnetsVPCID), 838 CidrBlock: aws.String("10.0.0.0/17"), 839 AvailabilityZone: aws.String("us-east-1c"), 840 TagSpecifications: []*ec2.TagSpecification{ 841 { 842 ResourceType: aws.String("subnet"), 843 Tags: []*ec2.Tag{ 844 { 845 Key: aws.String("Name"), 846 Value: aws.String("test-cluster-subnet-public-us-east-1c"), 847 }, 848 { 849 Key: aws.String("kubernetes.io/cluster/test-cluster"), 850 Value: aws.String("shared"), 851 }, 852 { 853 Key: aws.String("kubernetes.io/role/elb"), 854 Value: aws.String("1"), 855 }, 856 { 857 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 858 Value: aws.String("owned"), 859 }, 860 { 861 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 862 Value: aws.String("public"), 863 }, 864 }, 865 }, 866 }, 867 })). 868 Return(&ec2.CreateSubnetOutput{ 869 Subnet: &ec2.Subnet{ 870 VpcId: aws.String(subnetsVPCID), 871 SubnetId: aws.String("subnet-1"), 872 CidrBlock: aws.String("10.0.0.0/17"), 873 AvailabilityZone: aws.String("us-east-1c"), 874 MapPublicIpOnLaunch: aws.Bool(false), 875 }, 876 }, nil). 877 After(describeCall) 878 879 m.WaitUntilSubnetAvailable(gomock.Any()). 880 After(firstSubnet) 881 882 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 883 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 884 Value: aws.Bool(true), 885 }, 886 SubnetId: aws.String("subnet-1"), 887 }). 888 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 889 After(firstSubnet) 890 891 secondSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 892 VpcId: aws.String(subnetsVPCID), 893 CidrBlock: aws.String("10.0.128.0/17"), 894 AvailabilityZone: aws.String("us-east-1c"), 895 TagSpecifications: []*ec2.TagSpecification{ 896 { 897 ResourceType: aws.String("subnet"), 898 Tags: []*ec2.Tag{ 899 { 900 Key: aws.String("Name"), 901 Value: aws.String("test-cluster-subnet-private-us-east-1c"), 902 }, 903 { 904 Key: aws.String("kubernetes.io/cluster/test-cluster"), 905 Value: aws.String("shared"), 906 }, 907 { 908 Key: aws.String("kubernetes.io/role/internal-elb"), 909 Value: aws.String("1"), 910 }, 911 { 912 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 913 Value: aws.String("owned"), 914 }, 915 { 916 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 917 Value: aws.String("private"), 918 }, 919 }, 920 }, 921 }, 922 })). 923 Return(&ec2.CreateSubnetOutput{ 924 Subnet: &ec2.Subnet{ 925 VpcId: aws.String(subnetsVPCID), 926 SubnetId: aws.String("subnet-2"), 927 CidrBlock: aws.String("10.0.128.0/17"), 928 AvailabilityZone: aws.String("us-east-1c"), 929 MapPublicIpOnLaunch: aws.Bool(false), 930 }, 931 }, nil). 932 After(firstSubnet) 933 934 m.WaitUntilSubnetAvailable(gomock.Any()). 935 After(secondSubnet) 936 }, 937 }, 938 { 939 name: "Managed VPC, no existing subnets exist, two az's, expect two private and two public from default", 940 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 941 VPC: infrav1.VPCSpec{ 942 ID: subnetsVPCID, 943 Tags: infrav1.Tags{ 944 infrav1.ClusterTagKey("test-cluster"): "owned", 945 }, 946 CidrBlock: defaultVPCCidr, 947 }, 948 Subnets: []infrav1.SubnetSpec{}, 949 }), 950 expect: func(m *mocks.MockEC2APIMockRecorder) { 951 m.DescribeAvailabilityZones(gomock.Any()). 952 Return(&ec2.DescribeAvailabilityZonesOutput{ 953 AvailabilityZones: []*ec2.AvailabilityZone{ 954 { 955 ZoneName: aws.String("us-east-1b"), 956 }, 957 { 958 ZoneName: aws.String("us-east-1c"), 959 }, 960 }, 961 }, nil) 962 963 describeCall := m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 964 Filters: []*ec2.Filter{ 965 { 966 Name: aws.String("state"), 967 Values: []*string{aws.String("pending"), aws.String("available")}, 968 }, 969 { 970 Name: aws.String("vpc-id"), 971 Values: []*string{aws.String(subnetsVPCID)}, 972 }, 973 }, 974 })). 975 Return(&ec2.DescribeSubnetsOutput{}, nil) 976 977 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 978 Return(&ec2.DescribeRouteTablesOutput{}, nil) 979 980 m.DescribeNatGatewaysPages( 981 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 982 Filter: []*ec2.Filter{ 983 { 984 Name: aws.String("vpc-id"), 985 Values: []*string{aws.String(subnetsVPCID)}, 986 }, 987 { 988 Name: aws.String("state"), 989 Values: []*string{aws.String("pending"), aws.String("available")}, 990 }, 991 }, 992 }), 993 gomock.Any()).Return(nil) 994 995 zone1PublicSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 996 VpcId: aws.String(subnetsVPCID), 997 CidrBlock: aws.String("10.0.0.0/19"), 998 AvailabilityZone: aws.String("us-east-1b"), 999 TagSpecifications: []*ec2.TagSpecification{ 1000 { 1001 ResourceType: aws.String("subnet"), 1002 Tags: []*ec2.Tag{ 1003 { 1004 Key: aws.String("Name"), 1005 Value: aws.String("test-cluster-subnet-public-us-east-1b"), 1006 }, 1007 { 1008 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1009 Value: aws.String("shared"), 1010 }, 1011 { 1012 Key: aws.String("kubernetes.io/role/elb"), 1013 Value: aws.String("1"), 1014 }, 1015 { 1016 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1017 Value: aws.String("owned"), 1018 }, 1019 { 1020 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1021 Value: aws.String("public"), 1022 }, 1023 }, 1024 }, 1025 }, 1026 })). 1027 Return(&ec2.CreateSubnetOutput{ 1028 Subnet: &ec2.Subnet{ 1029 VpcId: aws.String(subnetsVPCID), 1030 SubnetId: aws.String("subnet-1"), 1031 CidrBlock: aws.String("10.0.0.0/19"), 1032 AvailabilityZone: aws.String("us-east-1b"), 1033 MapPublicIpOnLaunch: aws.Bool(false), 1034 }, 1035 }, nil). 1036 After(describeCall) 1037 1038 m.WaitUntilSubnetAvailable(gomock.Any()). 1039 After(zone1PublicSubnet) 1040 1041 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 1042 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 1043 Value: aws.Bool(true), 1044 }, 1045 SubnetId: aws.String("subnet-1"), 1046 }). 1047 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 1048 After(zone1PublicSubnet) 1049 1050 zone1PrivateSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1051 VpcId: aws.String(subnetsVPCID), 1052 CidrBlock: aws.String("10.0.64.0/18"), 1053 AvailabilityZone: aws.String("us-east-1b"), 1054 TagSpecifications: []*ec2.TagSpecification{ 1055 { 1056 ResourceType: aws.String("subnet"), 1057 Tags: []*ec2.Tag{ 1058 { 1059 Key: aws.String("Name"), 1060 Value: aws.String("test-cluster-subnet-private-us-east-1b"), 1061 }, 1062 { 1063 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1064 Value: aws.String("shared"), 1065 }, 1066 { 1067 Key: aws.String("kubernetes.io/role/internal-elb"), 1068 Value: aws.String("1"), 1069 }, 1070 { 1071 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1072 Value: aws.String("owned"), 1073 }, 1074 { 1075 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1076 Value: aws.String("private"), 1077 }, 1078 }, 1079 }, 1080 }, 1081 })). 1082 Return(&ec2.CreateSubnetOutput{ 1083 Subnet: &ec2.Subnet{ 1084 VpcId: aws.String(subnetsVPCID), 1085 SubnetId: aws.String("subnet-2"), 1086 CidrBlock: aws.String("10.0.64.0/18"), 1087 AvailabilityZone: aws.String("us-east-1b"), 1088 MapPublicIpOnLaunch: aws.Bool(false), 1089 }, 1090 }, nil). 1091 After(zone1PublicSubnet) 1092 1093 m.WaitUntilSubnetAvailable(gomock.Any()). 1094 After(zone1PrivateSubnet) 1095 1096 // zone 2 1097 1098 zone2PublicSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1099 VpcId: aws.String(subnetsVPCID), 1100 CidrBlock: aws.String("10.0.32.0/19"), 1101 AvailabilityZone: aws.String("us-east-1c"), 1102 TagSpecifications: []*ec2.TagSpecification{ 1103 { 1104 ResourceType: aws.String("subnet"), 1105 Tags: []*ec2.Tag{ 1106 { 1107 Key: aws.String("Name"), 1108 Value: aws.String("test-cluster-subnet-public-us-east-1c"), 1109 }, 1110 { 1111 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1112 Value: aws.String("shared"), 1113 }, 1114 { 1115 Key: aws.String("kubernetes.io/role/elb"), 1116 Value: aws.String("1"), 1117 }, 1118 { 1119 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1120 Value: aws.String("owned"), 1121 }, 1122 { 1123 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1124 Value: aws.String("public"), 1125 }, 1126 }, 1127 }, 1128 }, 1129 })). 1130 Return(&ec2.CreateSubnetOutput{ 1131 Subnet: &ec2.Subnet{ 1132 VpcId: aws.String(subnetsVPCID), 1133 SubnetId: aws.String("subnet-1"), 1134 CidrBlock: aws.String("10.0.32.0/19"), 1135 AvailabilityZone: aws.String("us-east-1c"), 1136 MapPublicIpOnLaunch: aws.Bool(false), 1137 }, 1138 }, nil). 1139 After(zone1PrivateSubnet) 1140 1141 m.WaitUntilSubnetAvailable(gomock.Any()). 1142 After(zone2PublicSubnet) 1143 1144 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 1145 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 1146 Value: aws.Bool(true), 1147 }, 1148 SubnetId: aws.String("subnet-1"), 1149 }). 1150 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 1151 After(zone2PublicSubnet) 1152 1153 zone2PrivateSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1154 VpcId: aws.String(subnetsVPCID), 1155 CidrBlock: aws.String("10.0.128.0/18"), 1156 AvailabilityZone: aws.String("us-east-1c"), 1157 TagSpecifications: []*ec2.TagSpecification{ 1158 { 1159 ResourceType: aws.String("subnet"), 1160 Tags: []*ec2.Tag{ 1161 { 1162 Key: aws.String("Name"), 1163 Value: aws.String("test-cluster-subnet-private-us-east-1c"), 1164 }, 1165 { 1166 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1167 Value: aws.String("shared"), 1168 }, 1169 { 1170 Key: aws.String("kubernetes.io/role/internal-elb"), 1171 Value: aws.String("1"), 1172 }, 1173 { 1174 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1175 Value: aws.String("owned"), 1176 }, 1177 { 1178 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1179 Value: aws.String("private"), 1180 }, 1181 }, 1182 }, 1183 }, 1184 })). 1185 Return(&ec2.CreateSubnetOutput{ 1186 Subnet: &ec2.Subnet{ 1187 VpcId: aws.String(subnetsVPCID), 1188 SubnetId: aws.String("subnet-2"), 1189 CidrBlock: aws.String("10.0.128.0/18"), 1190 AvailabilityZone: aws.String("us-east-1c"), 1191 MapPublicIpOnLaunch: aws.Bool(false), 1192 }, 1193 }, nil). 1194 After(zone2PublicSubnet) 1195 1196 m.WaitUntilSubnetAvailable(gomock.Any()). 1197 After(zone2PrivateSubnet) 1198 }, 1199 }, 1200 { 1201 name: "Managed VPC, no existing subnets exist, two az's, max num azs is 1, expect one private and one public from default", 1202 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 1203 VPC: infrav1.VPCSpec{ 1204 ID: subnetsVPCID, 1205 Tags: infrav1.Tags{ 1206 infrav1.ClusterTagKey("test-cluster"): "owned", 1207 }, 1208 CidrBlock: defaultVPCCidr, 1209 AvailabilityZoneUsageLimit: aws.Int(1), 1210 AvailabilityZoneSelection: &infrav1.AZSelectionSchemeOrdered, 1211 }, 1212 Subnets: []infrav1.SubnetSpec{}, 1213 }), 1214 expect: func(m *mocks.MockEC2APIMockRecorder) { 1215 m.DescribeAvailabilityZones(gomock.Any()). 1216 Return(&ec2.DescribeAvailabilityZonesOutput{ 1217 AvailabilityZones: []*ec2.AvailabilityZone{ 1218 { 1219 ZoneName: aws.String("us-east-1b"), 1220 }, 1221 { 1222 ZoneName: aws.String("us-east-1c"), 1223 }, 1224 }, 1225 }, nil) 1226 1227 describeCall := m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 1228 Filters: []*ec2.Filter{ 1229 { 1230 Name: aws.String("state"), 1231 Values: []*string{aws.String("pending"), aws.String("available")}, 1232 }, 1233 { 1234 Name: aws.String("vpc-id"), 1235 Values: []*string{aws.String(subnetsVPCID)}, 1236 }, 1237 }, 1238 })). 1239 Return(&ec2.DescribeSubnetsOutput{}, nil) 1240 1241 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 1242 Return(&ec2.DescribeRouteTablesOutput{}, nil) 1243 1244 m.DescribeNatGatewaysPages( 1245 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 1246 Filter: []*ec2.Filter{ 1247 { 1248 Name: aws.String("vpc-id"), 1249 Values: []*string{aws.String(subnetsVPCID)}, 1250 }, 1251 { 1252 Name: aws.String("state"), 1253 Values: []*string{aws.String("pending"), aws.String("available")}, 1254 }, 1255 }, 1256 }), 1257 gomock.Any()).Return(nil) 1258 1259 zone1PublicSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1260 VpcId: aws.String(subnetsVPCID), 1261 CidrBlock: aws.String("10.0.0.0/17"), 1262 AvailabilityZone: aws.String("us-east-1b"), 1263 TagSpecifications: []*ec2.TagSpecification{ 1264 { 1265 ResourceType: aws.String("subnet"), 1266 Tags: []*ec2.Tag{ 1267 { 1268 Key: aws.String("Name"), 1269 Value: aws.String("test-cluster-subnet-public-us-east-1b"), 1270 }, 1271 { 1272 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1273 Value: aws.String("shared"), 1274 }, 1275 { 1276 Key: aws.String("kubernetes.io/role/elb"), 1277 Value: aws.String("1"), 1278 }, 1279 { 1280 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1281 Value: aws.String("owned"), 1282 }, 1283 { 1284 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1285 Value: aws.String("public"), 1286 }, 1287 }, 1288 }, 1289 }, 1290 })). 1291 Return(&ec2.CreateSubnetOutput{ 1292 Subnet: &ec2.Subnet{ 1293 VpcId: aws.String(subnetsVPCID), 1294 SubnetId: aws.String("subnet-1"), 1295 CidrBlock: aws.String("10.0.0.0/17"), 1296 AvailabilityZone: aws.String("us-east-1b"), 1297 MapPublicIpOnLaunch: aws.Bool(false), 1298 }, 1299 }, nil). 1300 After(describeCall) 1301 1302 m.WaitUntilSubnetAvailable(gomock.Any()). 1303 After(zone1PublicSubnet) 1304 1305 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 1306 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 1307 Value: aws.Bool(true), 1308 }, 1309 SubnetId: aws.String("subnet-1"), 1310 }). 1311 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 1312 After(zone1PublicSubnet) 1313 1314 zone1PrivateSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1315 VpcId: aws.String(subnetsVPCID), 1316 CidrBlock: aws.String("10.0.128.0/17"), 1317 AvailabilityZone: aws.String("us-east-1b"), 1318 TagSpecifications: []*ec2.TagSpecification{ 1319 { 1320 ResourceType: aws.String("subnet"), 1321 Tags: []*ec2.Tag{ 1322 { 1323 Key: aws.String("Name"), 1324 Value: aws.String("test-cluster-subnet-private-us-east-1b"), 1325 }, 1326 { 1327 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1328 Value: aws.String("shared"), 1329 }, 1330 { 1331 Key: aws.String("kubernetes.io/role/internal-elb"), 1332 Value: aws.String("1"), 1333 }, 1334 { 1335 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1336 Value: aws.String("owned"), 1337 }, 1338 { 1339 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1340 Value: aws.String("private"), 1341 }, 1342 }, 1343 }, 1344 }, 1345 })). 1346 Return(&ec2.CreateSubnetOutput{ 1347 Subnet: &ec2.Subnet{ 1348 VpcId: aws.String(subnetsVPCID), 1349 SubnetId: aws.String("subnet-2"), 1350 CidrBlock: aws.String("10.0.128.0/17"), 1351 AvailabilityZone: aws.String("us-east-1b"), 1352 MapPublicIpOnLaunch: aws.Bool(false), 1353 }, 1354 }, nil). 1355 After(zone1PublicSubnet) 1356 1357 m.WaitUntilSubnetAvailable(gomock.Any()). 1358 After(zone1PrivateSubnet) 1359 }, 1360 }, 1361 { 1362 name: "Managed VPC, existing public subnet, 2 subnets in spec, should create 1 subnet", 1363 input: NewClusterScope().WithNetwork(&infrav1.NetworkSpec{ 1364 VPC: infrav1.VPCSpec{ 1365 ID: subnetsVPCID, 1366 Tags: infrav1.Tags{ 1367 infrav1.ClusterTagKey("test-cluster"): "owned", 1368 }, 1369 }, 1370 Subnets: []infrav1.SubnetSpec{ 1371 { 1372 ID: "subnet-1", 1373 AvailabilityZone: "us-east-1a", 1374 CidrBlock: "10.0.0.0/17", 1375 IsPublic: true, 1376 }, 1377 { 1378 AvailabilityZone: "us-east-1a", 1379 CidrBlock: "10.0.128.0/17", 1380 IsPublic: false, 1381 }, 1382 }, 1383 }), 1384 expect: func(m *mocks.MockEC2APIMockRecorder) { 1385 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 1386 Filters: []*ec2.Filter{ 1387 { 1388 Name: aws.String("state"), 1389 Values: []*string{aws.String("pending"), aws.String("available")}, 1390 }, 1391 { 1392 Name: aws.String("vpc-id"), 1393 Values: []*string{aws.String(subnetsVPCID)}, 1394 }, 1395 }, 1396 })). 1397 Return(&ec2.DescribeSubnetsOutput{ 1398 Subnets: []*ec2.Subnet{ 1399 { 1400 VpcId: aws.String(subnetsVPCID), 1401 SubnetId: aws.String("subnet-1"), 1402 AvailabilityZone: aws.String("us-east-1a"), 1403 CidrBlock: aws.String("10.0.0.0/17"), 1404 Tags: []*ec2.Tag{ 1405 { 1406 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1407 Value: aws.String("owned"), 1408 }, 1409 { 1410 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1411 Value: aws.String("public"), 1412 }, 1413 { 1414 Key: aws.String("Name"), 1415 Value: aws.String("test-cluster-subnet-public"), 1416 }, 1417 { 1418 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1419 Value: aws.String("shared"), 1420 }, 1421 }, 1422 }, 1423 }, 1424 }, nil) 1425 1426 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 1427 Return(&ec2.DescribeRouteTablesOutput{}, nil) 1428 1429 m.DescribeNatGatewaysPages( 1430 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 1431 Filter: []*ec2.Filter{ 1432 { 1433 Name: aws.String("vpc-id"), 1434 Values: []*string{aws.String(subnetsVPCID)}, 1435 }, 1436 { 1437 Name: aws.String("state"), 1438 Values: []*string{aws.String("pending"), aws.String("available")}, 1439 }, 1440 }, 1441 }), 1442 gomock.Any()).Return(nil) 1443 1444 m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1445 VpcId: aws.String(subnetsVPCID), 1446 CidrBlock: aws.String("10.0.128.0/17"), 1447 AvailabilityZone: aws.String("us-east-1a"), 1448 TagSpecifications: []*ec2.TagSpecification{ 1449 { 1450 ResourceType: aws.String("subnet"), 1451 Tags: []*ec2.Tag{ 1452 { 1453 Key: aws.String("Name"), 1454 Value: aws.String("test-cluster-subnet-private-us-east-1a"), 1455 }, 1456 { 1457 Key: aws.String("kubernetes.io/cluster/test-cluster"), 1458 Value: aws.String("shared"), 1459 }, 1460 { 1461 Key: aws.String("kubernetes.io/role/internal-elb"), 1462 Value: aws.String("1"), 1463 }, 1464 { 1465 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1466 Value: aws.String("owned"), 1467 }, 1468 { 1469 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1470 Value: aws.String("private"), 1471 }, 1472 }, 1473 }, 1474 }, 1475 })). 1476 Return(&ec2.CreateSubnetOutput{ 1477 Subnet: &ec2.Subnet{ 1478 VpcId: aws.String(subnetsVPCID), 1479 SubnetId: aws.String("subnet-2"), 1480 CidrBlock: aws.String("10.0.128.0/17"), 1481 AvailabilityZone: aws.String("us-east-1a"), 1482 }, 1483 }, nil) 1484 1485 m.WaitUntilSubnetAvailable(gomock.Any()) 1486 1487 // Public subnet 1488 m.CreateTags(gomock.AssignableToTypeOf(&ec2.CreateTagsInput{})). 1489 Return(nil, nil) 1490 }, 1491 }, 1492 { 1493 name: "With ManagedControlPlaneScope, Managed VPC, no existing subnets exist, two az's, expect two private and two public from default, created with tag including eksClusterName not a name of Cluster resource", 1494 input: NewManagedControlPlaneScope(). 1495 WithEKSClusterName("test-eks-cluster"). 1496 WithNetwork(&infrav1.NetworkSpec{ 1497 VPC: infrav1.VPCSpec{ 1498 ID: subnetsVPCID, 1499 Tags: infrav1.Tags{ 1500 infrav1.ClusterTagKey("test-cluster"): "owned", 1501 }, 1502 CidrBlock: defaultVPCCidr, 1503 }, 1504 Subnets: []infrav1.SubnetSpec{}, 1505 }), 1506 expect: func(m *mocks.MockEC2APIMockRecorder) { 1507 m.DescribeAvailabilityZones(gomock.Any()). 1508 Return(&ec2.DescribeAvailabilityZonesOutput{ 1509 AvailabilityZones: []*ec2.AvailabilityZone{ 1510 { 1511 ZoneName: aws.String("us-east-1b"), 1512 }, 1513 { 1514 ZoneName: aws.String("us-east-1c"), 1515 }, 1516 }, 1517 }, nil) 1518 1519 describeCall := m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 1520 Filters: []*ec2.Filter{ 1521 { 1522 Name: aws.String("state"), 1523 Values: []*string{aws.String("pending"), aws.String("available")}, 1524 }, 1525 { 1526 Name: aws.String("vpc-id"), 1527 Values: []*string{aws.String(subnetsVPCID)}, 1528 }, 1529 }, 1530 })). 1531 Return(&ec2.DescribeSubnetsOutput{}, nil) 1532 1533 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 1534 Return(&ec2.DescribeRouteTablesOutput{}, nil) 1535 1536 m.DescribeNatGatewaysPages( 1537 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 1538 Filter: []*ec2.Filter{ 1539 { 1540 Name: aws.String("vpc-id"), 1541 Values: []*string{aws.String(subnetsVPCID)}, 1542 }, 1543 { 1544 Name: aws.String("state"), 1545 Values: []*string{aws.String("pending"), aws.String("available")}, 1546 }, 1547 }, 1548 }), 1549 gomock.Any()).Return(nil) 1550 1551 zone1PublicSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1552 VpcId: aws.String(subnetsVPCID), 1553 CidrBlock: aws.String("10.0.0.0/19"), 1554 AvailabilityZone: aws.String("us-east-1b"), 1555 TagSpecifications: []*ec2.TagSpecification{ 1556 { 1557 ResourceType: aws.String("subnet"), 1558 Tags: []*ec2.Tag{ 1559 { 1560 Key: aws.String("Name"), 1561 Value: aws.String("test-cluster-subnet-public-us-east-1b"), 1562 }, 1563 { 1564 Key: aws.String("kubernetes.io/cluster/test-eks-cluster"), 1565 Value: aws.String("shared"), 1566 }, 1567 { 1568 Key: aws.String("kubernetes.io/role/elb"), 1569 Value: aws.String("1"), 1570 }, 1571 { 1572 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1573 Value: aws.String("owned"), 1574 }, 1575 { 1576 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1577 Value: aws.String("public"), 1578 }, 1579 }, 1580 }, 1581 }, 1582 })). 1583 Return(&ec2.CreateSubnetOutput{ 1584 Subnet: &ec2.Subnet{ 1585 VpcId: aws.String(subnetsVPCID), 1586 SubnetId: aws.String("subnet-1"), 1587 CidrBlock: aws.String("10.0.0.0/19"), 1588 AvailabilityZone: aws.String("us-east-1b"), 1589 MapPublicIpOnLaunch: aws.Bool(false), 1590 }, 1591 }, nil). 1592 After(describeCall) 1593 1594 m.WaitUntilSubnetAvailable(gomock.Any()). 1595 After(zone1PublicSubnet) 1596 1597 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 1598 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 1599 Value: aws.Bool(true), 1600 }, 1601 SubnetId: aws.String("subnet-1"), 1602 }). 1603 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 1604 After(zone1PublicSubnet) 1605 1606 zone1PrivateSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1607 VpcId: aws.String(subnetsVPCID), 1608 CidrBlock: aws.String("10.0.64.0/18"), 1609 AvailabilityZone: aws.String("us-east-1b"), 1610 TagSpecifications: []*ec2.TagSpecification{ 1611 { 1612 ResourceType: aws.String("subnet"), 1613 Tags: []*ec2.Tag{ 1614 { 1615 Key: aws.String("Name"), 1616 Value: aws.String("test-cluster-subnet-private-us-east-1b"), 1617 }, 1618 { 1619 Key: aws.String("kubernetes.io/cluster/test-eks-cluster"), 1620 Value: aws.String("shared"), 1621 }, 1622 { 1623 Key: aws.String("kubernetes.io/role/internal-elb"), 1624 Value: aws.String("1"), 1625 }, 1626 { 1627 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1628 Value: aws.String("owned"), 1629 }, 1630 { 1631 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1632 Value: aws.String("private"), 1633 }, 1634 }, 1635 }, 1636 }, 1637 })). 1638 Return(&ec2.CreateSubnetOutput{ 1639 Subnet: &ec2.Subnet{ 1640 VpcId: aws.String(subnetsVPCID), 1641 SubnetId: aws.String("subnet-2"), 1642 CidrBlock: aws.String("10.0.64.0/18"), 1643 AvailabilityZone: aws.String("us-east-1b"), 1644 MapPublicIpOnLaunch: aws.Bool(false), 1645 }, 1646 }, nil). 1647 After(zone1PublicSubnet) 1648 1649 m.WaitUntilSubnetAvailable(gomock.Any()). 1650 After(zone1PrivateSubnet) 1651 1652 // zone 2 1653 1654 zone2PublicSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1655 VpcId: aws.String(subnetsVPCID), 1656 CidrBlock: aws.String("10.0.32.0/19"), 1657 AvailabilityZone: aws.String("us-east-1c"), 1658 TagSpecifications: []*ec2.TagSpecification{ 1659 { 1660 ResourceType: aws.String("subnet"), 1661 Tags: []*ec2.Tag{ 1662 { 1663 Key: aws.String("Name"), 1664 Value: aws.String("test-cluster-subnet-public-us-east-1c"), 1665 }, 1666 { 1667 Key: aws.String("kubernetes.io/cluster/test-eks-cluster"), 1668 Value: aws.String("shared"), 1669 }, 1670 { 1671 Key: aws.String("kubernetes.io/role/elb"), 1672 Value: aws.String("1"), 1673 }, 1674 { 1675 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1676 Value: aws.String("owned"), 1677 }, 1678 { 1679 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1680 Value: aws.String("public"), 1681 }, 1682 }, 1683 }, 1684 }, 1685 })). 1686 Return(&ec2.CreateSubnetOutput{ 1687 Subnet: &ec2.Subnet{ 1688 VpcId: aws.String(subnetsVPCID), 1689 SubnetId: aws.String("subnet-1"), 1690 CidrBlock: aws.String("10.0.32.0/19"), 1691 AvailabilityZone: aws.String("us-east-1c"), 1692 MapPublicIpOnLaunch: aws.Bool(false), 1693 }, 1694 }, nil). 1695 After(zone1PrivateSubnet) 1696 1697 m.WaitUntilSubnetAvailable(gomock.Any()). 1698 After(zone2PublicSubnet) 1699 1700 m.ModifySubnetAttribute(&ec2.ModifySubnetAttributeInput{ 1701 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 1702 Value: aws.Bool(true), 1703 }, 1704 SubnetId: aws.String("subnet-1"), 1705 }). 1706 Return(&ec2.ModifySubnetAttributeOutput{}, nil). 1707 After(zone2PublicSubnet) 1708 1709 zone2PrivateSubnet := m.CreateSubnet(gomock.Eq(&ec2.CreateSubnetInput{ 1710 VpcId: aws.String(subnetsVPCID), 1711 CidrBlock: aws.String("10.0.128.0/18"), 1712 AvailabilityZone: aws.String("us-east-1c"), 1713 TagSpecifications: []*ec2.TagSpecification{ 1714 { 1715 ResourceType: aws.String("subnet"), 1716 Tags: []*ec2.Tag{ 1717 { 1718 Key: aws.String("Name"), 1719 Value: aws.String("test-cluster-subnet-private-us-east-1c"), 1720 }, 1721 { 1722 Key: aws.String("kubernetes.io/cluster/test-eks-cluster"), 1723 Value: aws.String("shared"), 1724 }, 1725 { 1726 Key: aws.String("kubernetes.io/role/internal-elb"), 1727 Value: aws.String("1"), 1728 }, 1729 { 1730 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), 1731 Value: aws.String("owned"), 1732 }, 1733 { 1734 Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), 1735 Value: aws.String("private"), 1736 }, 1737 }, 1738 }, 1739 }, 1740 })). 1741 Return(&ec2.CreateSubnetOutput{ 1742 Subnet: &ec2.Subnet{ 1743 VpcId: aws.String(subnetsVPCID), 1744 SubnetId: aws.String("subnet-2"), 1745 CidrBlock: aws.String("10.0.128.0/18"), 1746 AvailabilityZone: aws.String("us-east-1c"), 1747 MapPublicIpOnLaunch: aws.Bool(false), 1748 }, 1749 }, nil). 1750 After(zone2PublicSubnet) 1751 1752 m.WaitUntilSubnetAvailable(gomock.Any()). 1753 After(zone2PrivateSubnet) 1754 }, 1755 }, 1756 } 1757 1758 for _, tc := range testCases { 1759 t.Run(tc.name, func(t *testing.T) { 1760 mockCtrl := gomock.NewController(t) 1761 defer mockCtrl.Finish() 1762 ec2Mock := mocks.NewMockEC2API(mockCtrl) 1763 1764 scope, err := tc.input.Build() 1765 if err != nil { 1766 t.Fatalf("Failed to create test context: %v", err) 1767 } 1768 1769 tc.expect(ec2Mock.EXPECT()) 1770 1771 s := NewService(scope) 1772 s.EC2Client = ec2Mock 1773 err = s.reconcileSubnets() 1774 1775 if tc.errorExpected && err == nil { 1776 t.Fatal("expected error reconciling but not no error") 1777 } 1778 if !tc.errorExpected && err != nil { 1779 t.Fatalf("got an unexpected error: %v", err) 1780 } 1781 }) 1782 } 1783 } 1784 1785 func TestDiscoverSubnets(t *testing.T) { 1786 testCases := []struct { 1787 name string 1788 input *infrav1.NetworkSpec 1789 mocks func(m *mocks.MockEC2APIMockRecorder) 1790 expect []infrav1.SubnetSpec 1791 }{ 1792 { 1793 name: "provided VPC finds internet routes", 1794 input: &infrav1.NetworkSpec{ 1795 VPC: infrav1.VPCSpec{ 1796 ID: subnetsVPCID, 1797 }, 1798 Subnets: []infrav1.SubnetSpec{ 1799 { 1800 ID: "subnet-1", 1801 AvailabilityZone: "us-east-1a", 1802 CidrBlock: "10.0.10.0/24", 1803 IsPublic: true, 1804 }, 1805 { 1806 ID: "subnet-2", 1807 AvailabilityZone: "us-east-1a", 1808 CidrBlock: "10.0.11.0/24", 1809 IsPublic: false, 1810 }, 1811 }, 1812 }, 1813 mocks: func(m *mocks.MockEC2APIMockRecorder) { 1814 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 1815 Filters: []*ec2.Filter{ 1816 { 1817 Name: aws.String("state"), 1818 Values: []*string{aws.String("pending"), aws.String("available")}, 1819 }, 1820 { 1821 Name: aws.String("vpc-id"), 1822 Values: []*string{aws.String(subnetsVPCID)}, 1823 }, 1824 }, 1825 })). 1826 Return(&ec2.DescribeSubnetsOutput{ 1827 Subnets: []*ec2.Subnet{ 1828 { 1829 VpcId: aws.String(subnetsVPCID), 1830 SubnetId: aws.String("subnet-1"), 1831 AvailabilityZone: aws.String("us-east-1a"), 1832 CidrBlock: aws.String("10.0.10.0/24"), 1833 Tags: []*ec2.Tag{ 1834 { 1835 Key: aws.String("Name"), 1836 Value: aws.String("provided-subnet-public"), 1837 }, 1838 }, 1839 }, 1840 { 1841 VpcId: aws.String(subnetsVPCID), 1842 SubnetId: aws.String("subnet-2"), 1843 AvailabilityZone: aws.String("us-east-1a"), 1844 CidrBlock: aws.String("10.0.11.0/24"), 1845 Tags: []*ec2.Tag{ 1846 { 1847 Key: aws.String("Name"), 1848 Value: aws.String("provided-subnet-private"), 1849 }, 1850 }, 1851 }, 1852 }, 1853 }, nil) 1854 1855 m.DescribeRouteTables(gomock.AssignableToTypeOf(&ec2.DescribeRouteTablesInput{})). 1856 Return(&ec2.DescribeRouteTablesOutput{ 1857 RouteTables: []*ec2.RouteTable{ 1858 { 1859 Associations: []*ec2.RouteTableAssociation{ 1860 { 1861 SubnetId: aws.String("subnet-1"), 1862 }, 1863 }, 1864 Routes: []*ec2.Route{ 1865 { 1866 DestinationCidrBlock: aws.String("10.0.10.0/24"), 1867 GatewayId: aws.String("local"), 1868 }, 1869 { 1870 DestinationCidrBlock: aws.String("0.0.0.0/0"), 1871 GatewayId: aws.String("igw-0"), 1872 }, 1873 }, 1874 RouteTableId: aws.String("rtb-1"), 1875 }, 1876 { 1877 Associations: []*ec2.RouteTableAssociation{ 1878 { 1879 SubnetId: aws.String("subnet-2"), 1880 }, 1881 }, 1882 Routes: []*ec2.Route{ 1883 { 1884 DestinationCidrBlock: aws.String("10.0.11.0/24"), 1885 GatewayId: aws.String("local"), 1886 }, 1887 }, 1888 RouteTableId: aws.String("rtb-2"), 1889 }, 1890 }, 1891 }, nil) 1892 1893 m.DescribeNatGatewaysPages( 1894 gomock.Eq(&ec2.DescribeNatGatewaysInput{ 1895 Filter: []*ec2.Filter{ 1896 { 1897 Name: aws.String("vpc-id"), 1898 Values: []*string{aws.String(subnetsVPCID)}, 1899 }, 1900 { 1901 Name: aws.String("state"), 1902 Values: []*string{aws.String("pending"), aws.String("available")}, 1903 }, 1904 }, 1905 }), 1906 gomock.Any()).Return(nil) 1907 1908 m.CreateTags(gomock.AssignableToTypeOf(&ec2.CreateTagsInput{})). 1909 Return(&ec2.CreateTagsOutput{}, nil).AnyTimes() 1910 }, 1911 expect: []infrav1.SubnetSpec{ 1912 { 1913 ID: "subnet-1", 1914 AvailabilityZone: "us-east-1a", 1915 CidrBlock: "10.0.10.0/24", 1916 IsPublic: true, 1917 RouteTableID: aws.String("rtb-1"), 1918 Tags: infrav1.Tags{ 1919 "Name": "provided-subnet-public", 1920 }, 1921 }, 1922 { 1923 ID: "subnet-2", 1924 AvailabilityZone: "us-east-1a", 1925 CidrBlock: "10.0.11.0/24", 1926 IsPublic: false, 1927 RouteTableID: aws.String("rtb-2"), 1928 Tags: infrav1.Tags{ 1929 "Name": "provided-subnet-private", 1930 }, 1931 }, 1932 }, 1933 }, 1934 } 1935 for _, tc := range testCases { 1936 t.Run(tc.name, func(t *testing.T) { 1937 mockCtrl := gomock.NewController(t) 1938 defer mockCtrl.Finish() 1939 ec2Mock := mocks.NewMockEC2API(mockCtrl) 1940 1941 scheme := runtime.NewScheme() 1942 _ = infrav1.AddToScheme(scheme) 1943 client := fake.NewClientBuilder().WithScheme(scheme).Build() 1944 scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ 1945 Client: client, 1946 Cluster: &clusterv1.Cluster{ 1947 ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, 1948 }, 1949 AWSCluster: &infrav1.AWSCluster{ 1950 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 1951 Spec: infrav1.AWSClusterSpec{ 1952 NetworkSpec: *tc.input, 1953 }, 1954 }, 1955 }) 1956 if err != nil { 1957 t.Fatalf("Failed to create test context: %v", err) 1958 } 1959 1960 tc.mocks(ec2Mock.EXPECT()) 1961 1962 s := NewService(scope) 1963 s.EC2Client = ec2Mock 1964 1965 if err := s.reconcileSubnets(); err != nil { 1966 t.Fatalf("got an unexpected error: %v", err) 1967 } 1968 1969 subnets := s.scope.Subnets() 1970 out := make(map[string]infrav1.SubnetSpec) 1971 for _, sn := range subnets { 1972 out[sn.ID] = sn 1973 } 1974 for _, exp := range tc.expect { 1975 sn, ok := out[exp.ID] 1976 if !ok { 1977 t.Errorf("Expected to find subnet %s in %+v", exp.ID, subnets) 1978 continue 1979 } 1980 1981 if !cmp.Equal(sn, exp) { 1982 expected, err := json.MarshalIndent(exp, "", "\t") 1983 if err != nil { 1984 t.Fatalf("got an unexpected error: %v", err) 1985 } 1986 actual, err := json.MarshalIndent(sn, "", "\t") 1987 if err != nil { 1988 t.Fatalf("got an unexpected error: %v", err) 1989 } 1990 t.Errorf("Expected %s, got %s", string(expected), string(actual)) 1991 } 1992 delete(out, exp.ID) 1993 } 1994 if len(out) > 0 { 1995 t.Errorf("Got unexpected subnets: %+v", out) 1996 } 1997 }) 1998 } 1999 } 2000 2001 func TestDeleteSubnets(t *testing.T) { 2002 testCases := []struct { 2003 name string 2004 input *infrav1.NetworkSpec 2005 expect func(m *mocks.MockEC2APIMockRecorder) 2006 errorExpected bool 2007 }{ 2008 { 2009 name: "managed vpc - success", 2010 input: &infrav1.NetworkSpec{ 2011 VPC: infrav1.VPCSpec{ 2012 ID: subnetsVPCID, 2013 Tags: infrav1.Tags{ 2014 infrav1.ClusterTagKey("test-cluster"): "owned", 2015 }, 2016 }, 2017 Subnets: []infrav1.SubnetSpec{ 2018 { 2019 ID: "subnet-1", 2020 }, 2021 { 2022 ID: "subnet-2", 2023 }, 2024 }, 2025 }, 2026 expect: func(m *mocks.MockEC2APIMockRecorder) { 2027 m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{ 2028 Filters: []*ec2.Filter{ 2029 { 2030 Name: aws.String("state"), 2031 Values: []*string{aws.String("pending"), aws.String("available")}, 2032 }, 2033 { 2034 Name: aws.String("vpc-id"), 2035 Values: []*string{aws.String(subnetsVPCID)}, 2036 }, 2037 }, 2038 })). 2039 Return(&ec2.DescribeSubnetsOutput{ 2040 Subnets: []*ec2.Subnet{ 2041 { 2042 VpcId: aws.String(subnetsVPCID), 2043 SubnetId: aws.String("subnet-1"), 2044 AvailabilityZone: aws.String("us-east-1a"), 2045 CidrBlock: aws.String("10.0.10.0/24"), 2046 MapPublicIpOnLaunch: aws.Bool(true), 2047 }, 2048 { 2049 VpcId: aws.String(subnetsVPCID), 2050 SubnetId: aws.String("subnet-2"), 2051 AvailabilityZone: aws.String("us-east-1a"), 2052 CidrBlock: aws.String("10.0.20.0/24"), 2053 MapPublicIpOnLaunch: aws.Bool(false), 2054 }, 2055 }, 2056 }, nil) 2057 2058 m.DeleteSubnet(&ec2.DeleteSubnetInput{ 2059 SubnetId: aws.String("subnet-1"), 2060 }). 2061 Return(nil, nil) 2062 2063 m.DeleteSubnet(&ec2.DeleteSubnetInput{ 2064 SubnetId: aws.String("subnet-2"), 2065 }). 2066 Return(nil, nil) 2067 }, 2068 errorExpected: false, 2069 }, 2070 } 2071 2072 for _, tc := range testCases { 2073 t.Run(tc.name, func(t *testing.T) { 2074 mockCtrl := gomock.NewController(t) 2075 defer mockCtrl.Finish() 2076 2077 ec2Mock := mocks.NewMockEC2API(mockCtrl) 2078 2079 scheme := runtime.NewScheme() 2080 _ = infrav1.AddToScheme(scheme) 2081 2082 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2083 scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ 2084 Client: client, 2085 Cluster: &clusterv1.Cluster{ 2086 ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, 2087 }, 2088 AWSCluster: &infrav1.AWSCluster{ 2089 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2090 Spec: infrav1.AWSClusterSpec{ 2091 NetworkSpec: *tc.input, 2092 }, 2093 }, 2094 }) 2095 if err != nil { 2096 t.Fatalf("Failed to create test context: %v", err) 2097 } 2098 2099 tc.expect(ec2Mock.EXPECT()) 2100 2101 s := NewService(scope) 2102 s.EC2Client = ec2Mock 2103 2104 err = s.deleteSubnets() 2105 if tc.errorExpected && err == nil { 2106 t.Fatal("expected error but not no error") 2107 } 2108 if !tc.errorExpected && err != nil { 2109 t.Fatalf("got an unexpected error: %v", err) 2110 } 2111 }) 2112 } 2113 } 2114 2115 // Test helpers 2116 2117 type ScopeBuilder interface { 2118 Build() (scope.NetworkScope, error) 2119 } 2120 2121 func NewClusterScope() *ClusterScopeBuilder { 2122 return &ClusterScopeBuilder{ 2123 customizers: []func(p *scope.ClusterScopeParams){}, 2124 } 2125 } 2126 2127 type ClusterScopeBuilder struct { 2128 customizers []func(p *scope.ClusterScopeParams) 2129 } 2130 2131 func (b *ClusterScopeBuilder) WithNetwork(n *infrav1.NetworkSpec) *ClusterScopeBuilder { 2132 b.customizers = append(b.customizers, func(p *scope.ClusterScopeParams) { 2133 p.AWSCluster.Spec.NetworkSpec = *n 2134 }) 2135 2136 return b 2137 } 2138 2139 func (b *ClusterScopeBuilder) Build() (scope.NetworkScope, error) { 2140 scheme := runtime.NewScheme() 2141 _ = infrav1.AddToScheme(scheme) 2142 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2143 2144 param := &scope.ClusterScopeParams{ 2145 Client: client, 2146 Cluster: &clusterv1.Cluster{ 2147 ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, 2148 }, 2149 AWSCluster: &infrav1.AWSCluster{ 2150 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2151 Spec: infrav1.AWSClusterSpec{}, 2152 }, 2153 } 2154 2155 for _, customizer := range b.customizers { 2156 customizer(param) 2157 } 2158 2159 return scope.NewClusterScope(*param) 2160 } 2161 2162 func NewManagedControlPlaneScope() *ManagedControlPlaneScopeBuilder { 2163 return &ManagedControlPlaneScopeBuilder{ 2164 customizers: []func(p *scope.ManagedControlPlaneScopeParams){}, 2165 } 2166 } 2167 2168 type ManagedControlPlaneScopeBuilder struct { 2169 customizers []func(p *scope.ManagedControlPlaneScopeParams) 2170 } 2171 2172 func (b *ManagedControlPlaneScopeBuilder) WithNetwork(n *infrav1.NetworkSpec) *ManagedControlPlaneScopeBuilder { 2173 b.customizers = append(b.customizers, func(p *scope.ManagedControlPlaneScopeParams) { 2174 p.ControlPlane.Spec.NetworkSpec = *n 2175 }) 2176 2177 return b 2178 } 2179 2180 func (b *ManagedControlPlaneScopeBuilder) WithEKSClusterName(name string) *ManagedControlPlaneScopeBuilder { 2181 b.customizers = append(b.customizers, func(p *scope.ManagedControlPlaneScopeParams) { 2182 p.ControlPlane.Spec.EKSClusterName = name 2183 }) 2184 2185 return b 2186 } 2187 2188 func (b *ManagedControlPlaneScopeBuilder) Build() (scope.NetworkScope, error) { 2189 scheme := runtime.NewScheme() 2190 _ = infrav1.AddToScheme(scheme) 2191 _ = ekscontrolplanev1.AddToScheme(scheme) 2192 client := fake.NewClientBuilder().WithScheme(scheme).Build() 2193 2194 param := &scope.ManagedControlPlaneScopeParams{ 2195 Client: client, 2196 Cluster: &clusterv1.Cluster{ 2197 ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, 2198 }, 2199 ControlPlane: &ekscontrolplanev1.AWSManagedControlPlane{ 2200 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 2201 Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{}, 2202 }, 2203 } 2204 2205 for _, customizer := range b.customizers { 2206 customizer(param) 2207 } 2208 2209 return scope.NewManagedControlPlaneScope(*param) 2210 }