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  }