sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/network/natgateways_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  	"context"
    21  	"testing"
    22  
    23  	"github.com/aws/aws-sdk-go/aws"
    24  	"github.com/aws/aws-sdk-go/service/ec2"
    25  	"github.com/golang/mock/gomock"
    26  	. "github.com/onsi/gomega"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  
    31  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors"
    33  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    34  	"sigs.k8s.io/cluster-api-provider-aws/test/mocks"
    35  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    36  )
    37  
    38  const (
    39  	ElasticIPAllocationID = "elastic-ip-allocation-id"
    40  )
    41  
    42  func TestReconcileNatGateways(t *testing.T) {
    43  	mockCtrl := gomock.NewController(t)
    44  	defer mockCtrl.Finish()
    45  
    46  	testCases := []struct {
    47  		name   string
    48  		input  []infrav1.SubnetSpec
    49  		expect func(m *mocks.MockEC2APIMockRecorder)
    50  	}{
    51  		{
    52  			name: "single private subnet exists, should create no NAT gateway",
    53  			input: []infrav1.SubnetSpec{
    54  				{
    55  					ID:               "subnet-1",
    56  					AvailabilityZone: "us-east-1a",
    57  					CidrBlock:        "10.0.10.0/24",
    58  					IsPublic:         false,
    59  				},
    60  			},
    61  			expect: func(m *mocks.MockEC2APIMockRecorder) {
    62  				m.CreateNatGateway(gomock.Any()).Times(0)
    63  			},
    64  		},
    65  		{
    66  			name: "no private subnet exists, should create no NAT gateway",
    67  			input: []infrav1.SubnetSpec{
    68  				{
    69  					ID:               "subnet-1",
    70  					AvailabilityZone: "us-east-1a",
    71  					CidrBlock:        "10.0.10.0/24",
    72  					IsPublic:         true,
    73  				},
    74  			},
    75  			expect: func(m *mocks.MockEC2APIMockRecorder) {
    76  				m.DescribeNatGatewaysPages(gomock.Any(), gomock.Any()).Times(0)
    77  				m.CreateNatGateway(gomock.Any()).Times(0)
    78  			},
    79  		},
    80  		{
    81  			name: "public & private subnet exists, should create 1 NAT gateway",
    82  			input: []infrav1.SubnetSpec{
    83  				{
    84  					ID:               "subnet-1",
    85  					AvailabilityZone: "us-east-1a",
    86  					CidrBlock:        "10.0.10.0/24",
    87  					IsPublic:         true,
    88  				},
    89  				{
    90  					ID:               "subnet-2",
    91  					AvailabilityZone: "us-east-1a",
    92  					CidrBlock:        "10.0.12.0/24",
    93  					IsPublic:         false,
    94  				},
    95  			},
    96  			expect: func(m *mocks.MockEC2APIMockRecorder) {
    97  				m.DescribeNatGatewaysPages(
    98  					gomock.Eq(&ec2.DescribeNatGatewaysInput{
    99  						Filter: []*ec2.Filter{
   100  							{
   101  								Name:   aws.String("vpc-id"),
   102  								Values: []*string{aws.String(subnetsVPCID)},
   103  							},
   104  							{
   105  								Name:   aws.String("state"),
   106  								Values: []*string{aws.String("pending"), aws.String("available")},
   107  							},
   108  						},
   109  					}),
   110  					gomock.Any()).Return(nil)
   111  
   112  				m.DescribeAddresses(gomock.Any()).
   113  					Return(&ec2.DescribeAddressesOutput{}, nil)
   114  
   115  				m.AllocateAddress(&ec2.AllocateAddressInput{
   116  					Domain: aws.String("vpc"),
   117  					TagSpecifications: []*ec2.TagSpecification{
   118  						{
   119  							ResourceType: aws.String("elastic-ip"),
   120  							Tags: []*ec2.Tag{
   121  								{
   122  									Key:   aws.String("Name"),
   123  									Value: aws.String("test-cluster-eip-apiserver"),
   124  								},
   125  								{
   126  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
   127  									Value: aws.String("owned"),
   128  								},
   129  								{
   130  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
   131  									Value: aws.String("apiserver"),
   132  								},
   133  							},
   134  						},
   135  					},
   136  				}).Return(&ec2.AllocateAddressOutput{
   137  					AllocationId: aws.String(ElasticIPAllocationID),
   138  				}, nil)
   139  
   140  				m.CreateNatGateway(&ec2.CreateNatGatewayInput{
   141  					AllocationId: aws.String(ElasticIPAllocationID),
   142  					SubnetId:     aws.String("subnet-1"),
   143  					TagSpecifications: []*ec2.TagSpecification{
   144  						{
   145  							ResourceType: aws.String("natgateway"),
   146  							Tags: []*ec2.Tag{
   147  								{
   148  									Key:   aws.String("Name"),
   149  									Value: aws.String("test-cluster-nat"),
   150  								},
   151  								{
   152  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
   153  									Value: aws.String("owned"),
   154  								},
   155  								{
   156  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
   157  									Value: aws.String("common"),
   158  								},
   159  							},
   160  						},
   161  					},
   162  				},
   163  				).Return(&ec2.CreateNatGatewayOutput{
   164  					NatGateway: &ec2.NatGateway{
   165  						NatGatewayId: aws.String("natgateway"),
   166  						SubnetId:     aws.String("subnet-1"),
   167  					},
   168  				}, nil)
   169  
   170  				m.WaitUntilNatGatewayAvailable(&ec2.DescribeNatGatewaysInput{
   171  					NatGatewayIds: []*string{aws.String("natgateway")},
   172  				}).Return(nil)
   173  			},
   174  		},
   175  		{
   176  			name: "two public & 1 private subnet, and one NAT gateway exists",
   177  			input: []infrav1.SubnetSpec{
   178  				{
   179  					ID:               "subnet-1",
   180  					AvailabilityZone: "us-east-1a",
   181  					CidrBlock:        "10.0.10.0/24",
   182  					IsPublic:         true,
   183  				},
   184  				{
   185  					ID:               "subnet-2",
   186  					AvailabilityZone: "us-east-1a",
   187  					CidrBlock:        "10.0.12.0/24",
   188  					IsPublic:         false,
   189  				},
   190  				{
   191  					ID:               "subnet-3",
   192  					AvailabilityZone: "us-east-1b",
   193  					CidrBlock:        "10.0.13.0/24",
   194  					IsPublic:         true,
   195  				},
   196  			},
   197  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   198  				m.DescribeNatGatewaysPages(
   199  					gomock.Eq(&ec2.DescribeNatGatewaysInput{
   200  						Filter: []*ec2.Filter{
   201  							{
   202  								Name:   aws.String("vpc-id"),
   203  								Values: []*string{aws.String(subnetsVPCID)},
   204  							},
   205  							{
   206  								Name:   aws.String("state"),
   207  								Values: []*string{aws.String("pending"), aws.String("available")},
   208  							},
   209  						},
   210  					}),
   211  					gomock.Any()).Do(func(_, y interface{}) {
   212  					funct := y.(func(page *ec2.DescribeNatGatewaysOutput, lastPage bool) bool)
   213  					funct(&ec2.DescribeNatGatewaysOutput{NatGateways: []*ec2.NatGateway{{
   214  						NatGatewayId: aws.String("gateway"),
   215  						SubnetId:     aws.String("subnet-1"),
   216  					}}}, true)
   217  				}).Return(nil)
   218  
   219  				m.DescribeAddresses(gomock.Any()).
   220  					Return(&ec2.DescribeAddressesOutput{}, nil)
   221  
   222  				m.AllocateAddress(&ec2.AllocateAddressInput{
   223  					Domain: aws.String("vpc"),
   224  					TagSpecifications: []*ec2.TagSpecification{
   225  						{
   226  							ResourceType: aws.String("elastic-ip"),
   227  							Tags: []*ec2.Tag{
   228  								{
   229  									Key:   aws.String("Name"),
   230  									Value: aws.String("test-cluster-eip-apiserver"),
   231  								},
   232  								{
   233  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
   234  									Value: aws.String("owned"),
   235  								},
   236  								{
   237  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
   238  									Value: aws.String("apiserver"),
   239  								},
   240  							},
   241  						},
   242  					},
   243  				}).Return(&ec2.AllocateAddressOutput{
   244  					AllocationId: aws.String(ElasticIPAllocationID),
   245  				}, nil)
   246  
   247  				m.CreateNatGateway(&ec2.CreateNatGatewayInput{
   248  					AllocationId: aws.String(ElasticIPAllocationID),
   249  					SubnetId:     aws.String("subnet-3"),
   250  					TagSpecifications: []*ec2.TagSpecification{
   251  						{
   252  							ResourceType: aws.String("natgateway"),
   253  							Tags: []*ec2.Tag{
   254  								{
   255  									Key:   aws.String("Name"),
   256  									Value: aws.String("test-cluster-nat"),
   257  								},
   258  								{
   259  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
   260  									Value: aws.String("owned"),
   261  								},
   262  								{
   263  									Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
   264  									Value: aws.String("common"),
   265  								},
   266  							},
   267  						},
   268  					},
   269  				}).Return(&ec2.CreateNatGatewayOutput{
   270  					NatGateway: &ec2.NatGateway{
   271  						NatGatewayId: aws.String("natgateway"),
   272  						SubnetId:     aws.String("subnet-3"),
   273  					},
   274  				}, nil)
   275  
   276  				m.WaitUntilNatGatewayAvailable(&ec2.DescribeNatGatewaysInput{
   277  					NatGatewayIds: []*string{aws.String("natgateway")},
   278  				}).Return(nil)
   279  
   280  				m.CreateTags(gomock.AssignableToTypeOf(&ec2.CreateTagsInput{})).
   281  					Return(nil, nil).Times(1)
   282  			},
   283  		},
   284  		{
   285  			name: "public & private subnet, and one NAT gateway exists",
   286  			input: []infrav1.SubnetSpec{
   287  				{
   288  					ID:               "subnet-1",
   289  					AvailabilityZone: "us-east-1a",
   290  					CidrBlock:        "10.0.10.0/24",
   291  					IsPublic:         true,
   292  				},
   293  				{
   294  					ID:               "subnet-2",
   295  					AvailabilityZone: "us-east-1a",
   296  					CidrBlock:        "10.0.12.0/24",
   297  					IsPublic:         false,
   298  				},
   299  			},
   300  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   301  				m.DescribeNatGatewaysPages(
   302  					gomock.Eq(&ec2.DescribeNatGatewaysInput{
   303  						Filter: []*ec2.Filter{
   304  							{
   305  								Name:   aws.String("vpc-id"),
   306  								Values: []*string{aws.String(subnetsVPCID)},
   307  							},
   308  							{
   309  								Name:   aws.String("state"),
   310  								Values: []*string{aws.String("pending"), aws.String("available")},
   311  							},
   312  						},
   313  					}),
   314  					gomock.Any()).Do(func(_, y interface{}) {
   315  					funct := y.(func(page *ec2.DescribeNatGatewaysOutput, lastPage bool) bool)
   316  					funct(&ec2.DescribeNatGatewaysOutput{NatGateways: []*ec2.NatGateway{{
   317  						NatGatewayId: aws.String("gateway"),
   318  						SubnetId:     aws.String("subnet-1"),
   319  						Tags: []*ec2.Tag{
   320  							{
   321  								Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/role"),
   322  								Value: aws.String("common"),
   323  							},
   324  							{
   325  								Key:   aws.String("Name"),
   326  								Value: aws.String("test-cluster-nat"),
   327  							},
   328  							{
   329  								Key:   aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
   330  								Value: aws.String("owned"),
   331  							},
   332  						},
   333  					}}}, true)
   334  				}).Return(nil)
   335  
   336  				m.DescribeAddresses(gomock.Any()).Times(0)
   337  				m.AllocateAddress(gomock.Any()).Times(0)
   338  				m.CreateNatGateway(gomock.Any()).Times(0)
   339  			},
   340  		},
   341  		{
   342  			name: "public & private subnet declared, but don't exist yet",
   343  			input: []infrav1.SubnetSpec{
   344  				{
   345  					ID:               "",
   346  					AvailabilityZone: "us-east-1a",
   347  					CidrBlock:        "10.0.10.0/24",
   348  					IsPublic:         true,
   349  				},
   350  				{
   351  					ID:               "",
   352  					AvailabilityZone: "us-east-1a",
   353  					CidrBlock:        "10.0.12.0/24",
   354  					IsPublic:         false,
   355  				},
   356  			},
   357  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   358  				m.DescribeNatGatewaysPages(gomock.Any(), gomock.Any()).
   359  					Return(nil).
   360  					Times(1)
   361  			},
   362  		},
   363  	}
   364  
   365  	for _, tc := range testCases {
   366  		t.Run(tc.name, func(t *testing.T) {
   367  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
   368  			scheme := runtime.NewScheme()
   369  			_ = infrav1.AddToScheme(scheme)
   370  			awsCluster := &infrav1.AWSCluster{
   371  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   372  				Spec: infrav1.AWSClusterSpec{
   373  					NetworkSpec: infrav1.NetworkSpec{
   374  						VPC: infrav1.VPCSpec{
   375  							ID: subnetsVPCID,
   376  							Tags: infrav1.Tags{
   377  								infrav1.ClusterTagKey("test-cluster"): "owned",
   378  							},
   379  						},
   380  						Subnets: tc.input,
   381  					},
   382  				},
   383  			}
   384  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   385  			ctx := context.TODO()
   386  			client.Create(ctx, awsCluster)
   387  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   388  				Cluster: &clusterv1.Cluster{
   389  					ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"},
   390  				},
   391  				AWSCluster: awsCluster,
   392  				Client:     client,
   393  			})
   394  			if err != nil {
   395  				t.Fatalf("Failed to create test context: %v", err)
   396  			}
   397  
   398  			tc.expect(ec2Mock.EXPECT())
   399  
   400  			s := NewService(clusterScope)
   401  			s.EC2Client = ec2Mock
   402  
   403  			if err := s.reconcileNatGateways(); err != nil {
   404  				t.Fatalf("got an unexpected error: %v", err)
   405  			}
   406  		})
   407  	}
   408  }
   409  
   410  func TestDeleteNatGateways(t *testing.T) {
   411  	mockCtrl := gomock.NewController(t)
   412  	defer mockCtrl.Finish()
   413  
   414  	testCases := []struct {
   415  		name           string
   416  		input          []infrav1.SubnetSpec
   417  		isUnmanagedVPC bool
   418  		expect         func(m *mocks.MockEC2APIMockRecorder)
   419  		wantErr        bool
   420  	}{
   421  		{
   422  			name:           "Should skip deletion if vpc is unmanaged",
   423  			isUnmanagedVPC: true,
   424  		},
   425  		{
   426  			name: "Should skip deletion if no private subnet is present",
   427  			input: []infrav1.SubnetSpec{
   428  				{
   429  					ID:               "subnet-1",
   430  					AvailabilityZone: "us-east-1a",
   431  					CidrBlock:        "10.0.10.0/24",
   432  					IsPublic:         true,
   433  				},
   434  			},
   435  		},
   436  		{
   437  			name: "Should skip deletion if no public subnet is present",
   438  			input: []infrav1.SubnetSpec{
   439  				{
   440  					ID:               "subnet-1",
   441  					AvailabilityZone: "us-east-1a",
   442  					CidrBlock:        "10.0.10.0/24",
   443  					IsPublic:         false,
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name: "Should skip deletion if no existing natgateway present",
   449  			input: []infrav1.SubnetSpec{
   450  				{
   451  					ID:               "subnet-1",
   452  					AvailabilityZone: "us-east-1a",
   453  					CidrBlock:        "10.0.10.0/24",
   454  					IsPublic:         true,
   455  				},
   456  				{
   457  					ID:               "subnet-2",
   458  					AvailabilityZone: "us-east-1a",
   459  					CidrBlock:        "10.0.12.0/24",
   460  					IsPublic:         false,
   461  				},
   462  			},
   463  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   464  				m.DescribeNatGatewaysPages(
   465  					gomock.Eq(&ec2.DescribeNatGatewaysInput{
   466  						Filter: []*ec2.Filter{
   467  							{
   468  								Name:   aws.String("vpc-id"),
   469  								Values: []*string{aws.String("managed-vpc")},
   470  							},
   471  							{
   472  								Name:   aws.String("state"),
   473  								Values: []*string{aws.String("pending"), aws.String("available")},
   474  							},
   475  						},
   476  					}),
   477  					gomock.Any()).Return(nil)
   478  			},
   479  		},
   480  		{
   481  			name: "Should successfully delete natgateways",
   482  			input: []infrav1.SubnetSpec{
   483  				{
   484  					ID:               "subnet-1",
   485  					AvailabilityZone: "us-east-1a",
   486  					CidrBlock:        "10.0.10.0/24",
   487  					IsPublic:         true,
   488  				},
   489  				{
   490  					ID:               "subnet-2",
   491  					AvailabilityZone: "us-east-1a",
   492  					CidrBlock:        "10.0.12.0/24",
   493  					IsPublic:         false,
   494  				},
   495  			},
   496  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   497  				m.DescribeNatGatewaysPages(
   498  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}),
   499  					gomock.Any()).Do(mockDescribeNatGatewaysOutput).Return(nil)
   500  
   501  				m.DeleteNatGateway(gomock.Eq(&ec2.DeleteNatGatewayInput{
   502  					NatGatewayId: aws.String("natgateway"),
   503  				})).Return(&ec2.DeleteNatGatewayOutput{}, nil)
   504  
   505  				m.DescribeNatGateways(gomock.Eq(&ec2.DescribeNatGatewaysInput{
   506  					NatGatewayIds: []*string{aws.String("natgateway")},
   507  				})).Return(&ec2.DescribeNatGatewaysOutput{
   508  					NatGateways: []*ec2.NatGateway{
   509  						{
   510  							State: aws.String("available"),
   511  						},
   512  					},
   513  				}, nil)
   514  				m.DescribeNatGateways(gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{})).Return(&ec2.DescribeNatGatewaysOutput{
   515  					NatGateways: []*ec2.NatGateway{
   516  						{
   517  							State: aws.String("deleted"),
   518  						},
   519  					},
   520  				}, nil)
   521  			},
   522  		},
   523  		{
   524  			name: "Should return error if natgateway has unknown state",
   525  			input: []infrav1.SubnetSpec{
   526  				{
   527  					ID:               "subnet-1",
   528  					AvailabilityZone: "us-east-1a",
   529  					CidrBlock:        "10.0.10.0/24",
   530  					IsPublic:         true,
   531  				},
   532  				{
   533  					ID:               "",
   534  					AvailabilityZone: "us-east-1a",
   535  					CidrBlock:        "10.0.12.0/24",
   536  					IsPublic:         true,
   537  				},
   538  				{
   539  					ID:               "subnet-3",
   540  					AvailabilityZone: "us-east-1a",
   541  					CidrBlock:        "10.0.14.0/24",
   542  					IsPublic:         false,
   543  				},
   544  			},
   545  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   546  				m.DescribeNatGatewaysPages(
   547  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}), gomock.Any()).Do(mockDescribeNatGatewaysOutput).Return(nil)
   548  
   549  				m.DeleteNatGateway(gomock.Eq(&ec2.DeleteNatGatewayInput{
   550  					NatGatewayId: aws.String("natgateway"),
   551  				})).Return(&ec2.DeleteNatGatewayOutput{}, nil)
   552  
   553  				m.DescribeNatGateways(gomock.Eq(&ec2.DescribeNatGatewaysInput{
   554  					NatGatewayIds: []*string{aws.String("natgateway")},
   555  				})).Return(&ec2.DescribeNatGatewaysOutput{
   556  					NatGateways: []*ec2.NatGateway{
   557  						{
   558  							State: aws.String("unknown"),
   559  						},
   560  					},
   561  				}, nil)
   562  			},
   563  			wantErr: true,
   564  		},
   565  		{
   566  			name: "Should return error if describe natgateway output and error, both are nil",
   567  			input: []infrav1.SubnetSpec{
   568  				{
   569  					ID:               "subnet-1",
   570  					AvailabilityZone: "us-east-1a",
   571  					CidrBlock:        "10.0.10.0/24",
   572  					IsPublic:         true,
   573  				},
   574  				{
   575  					ID:               "subnet-2",
   576  					AvailabilityZone: "us-east-1a",
   577  					CidrBlock:        "10.0.14.0/24",
   578  					IsPublic:         false,
   579  				},
   580  			},
   581  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   582  				m.DescribeNatGatewaysPages(
   583  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}), gomock.Any()).Do(mockDescribeNatGatewaysOutput).Return(nil)
   584  
   585  				m.DeleteNatGateway(gomock.Eq(&ec2.DeleteNatGatewayInput{
   586  					NatGatewayId: aws.String("natgateway"),
   587  				})).Return(&ec2.DeleteNatGatewayOutput{}, nil)
   588  
   589  				m.DescribeNatGateways(gomock.Eq(&ec2.DescribeNatGatewaysInput{
   590  					NatGatewayIds: []*string{aws.String("natgateway")},
   591  				})).Return(nil, nil)
   592  			},
   593  			wantErr: true,
   594  		},
   595  		{
   596  			name: "Should return error if describe natgateway pages fails",
   597  			input: []infrav1.SubnetSpec{
   598  				{
   599  					ID:               "subnet-1",
   600  					AvailabilityZone: "us-east-1a",
   601  					CidrBlock:        "10.0.10.0/24",
   602  					IsPublic:         true,
   603  				},
   604  				{
   605  					ID:               "subnet-2",
   606  					AvailabilityZone: "us-east-1a",
   607  					CidrBlock:        "10.0.14.0/24",
   608  					IsPublic:         false,
   609  				},
   610  			},
   611  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   612  				m.DescribeNatGatewaysPages(
   613  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}), gomock.Any()).Return(awserrors.NewFailedDependency("failed dependency"))
   614  			},
   615  			wantErr: true,
   616  		},
   617  		{
   618  			name: "Should return error if delete natgateway fails",
   619  			input: []infrav1.SubnetSpec{
   620  				{
   621  					ID:               "subnet-1",
   622  					AvailabilityZone: "us-east-1a",
   623  					CidrBlock:        "10.0.10.0/24",
   624  					IsPublic:         true,
   625  				},
   626  				{
   627  					ID:               "subnet-2",
   628  					AvailabilityZone: "us-east-1a",
   629  					CidrBlock:        "10.0.14.0/24",
   630  					IsPublic:         false,
   631  				},
   632  			},
   633  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   634  				m.DescribeNatGatewaysPages(
   635  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}), gomock.Any()).Do(mockDescribeNatGatewaysOutput).Return(nil)
   636  
   637  				m.DeleteNatGateway(gomock.Eq(&ec2.DeleteNatGatewayInput{
   638  					NatGatewayId: aws.String("natgateway"),
   639  				})).Return(nil, awserrors.NewFailedDependency("failed dependency"))
   640  			},
   641  			wantErr: true,
   642  		},
   643  		{
   644  			name: "Should return error if describe natgateway fails",
   645  			input: []infrav1.SubnetSpec{
   646  				{
   647  					ID:               "subnet-1",
   648  					AvailabilityZone: "us-east-1a",
   649  					CidrBlock:        "10.0.10.0/24",
   650  					IsPublic:         true,
   651  				},
   652  				{
   653  					ID:               "subnet-2",
   654  					AvailabilityZone: "us-east-1a",
   655  					CidrBlock:        "10.0.14.0/24",
   656  					IsPublic:         false,
   657  				},
   658  			},
   659  			expect: func(m *mocks.MockEC2APIMockRecorder) {
   660  				m.DescribeNatGatewaysPages(
   661  					gomock.AssignableToTypeOf(&ec2.DescribeNatGatewaysInput{}), gomock.Any()).Do(mockDescribeNatGatewaysOutput).Return(nil)
   662  
   663  				m.DeleteNatGateway(gomock.Eq(&ec2.DeleteNatGatewayInput{
   664  					NatGatewayId: aws.String("natgateway"),
   665  				})).Return(&ec2.DeleteNatGatewayOutput{}, nil)
   666  
   667  				m.DescribeNatGateways(gomock.Eq(&ec2.DescribeNatGatewaysInput{
   668  					NatGatewayIds: []*string{aws.String("natgateway")},
   669  				})).Return(nil, awserrors.NewNotFound("not found"))
   670  			},
   671  			wantErr: true,
   672  		},
   673  	}
   674  
   675  	for _, tc := range testCases {
   676  		t.Run(tc.name, func(t *testing.T) {
   677  			g := NewWithT(t)
   678  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
   679  			scheme := runtime.NewScheme()
   680  			_ = infrav1.AddToScheme(scheme)
   681  			awsCluster := &infrav1.AWSCluster{
   682  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   683  				Spec: infrav1.AWSClusterSpec{
   684  					NetworkSpec: infrav1.NetworkSpec{
   685  						VPC: infrav1.VPCSpec{
   686  							ID: "managed-vpc",
   687  							Tags: infrav1.Tags{
   688  								infrav1.ClusterTagKey("test-cluster"): "owned",
   689  							},
   690  						},
   691  						Subnets: tc.input,
   692  					},
   693  				},
   694  			}
   695  			if tc.isUnmanagedVPC {
   696  				awsCluster.Spec.NetworkSpec.VPC.Tags = infrav1.Tags{}
   697  			}
   698  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   699  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   700  				Cluster: &clusterv1.Cluster{
   701  					ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"},
   702  				},
   703  				AWSCluster: awsCluster,
   704  				Client:     client,
   705  			})
   706  			g.Expect(err).NotTo(HaveOccurred())
   707  			if tc.expect != nil {
   708  				tc.expect(ec2Mock.EXPECT())
   709  			}
   710  
   711  			s := NewService(clusterScope)
   712  			s.EC2Client = ec2Mock
   713  
   714  			err = s.deleteNatGateways()
   715  			if tc.wantErr {
   716  				g.Expect(err).To(HaveOccurred())
   717  				return
   718  			}
   719  			g.Expect(err).NotTo(HaveOccurred())
   720  		})
   721  	}
   722  }
   723  
   724  var mockDescribeNatGatewaysOutput = func(_, y interface{}) {
   725  	funct := y.(func(page *ec2.DescribeNatGatewaysOutput, lastPage bool) bool)
   726  	funct(&ec2.DescribeNatGatewaysOutput{NatGateways: []*ec2.NatGateway{{
   727  		NatGatewayId: aws.String("natgateway"),
   728  		SubnetId:     aws.String("subnet-1"),
   729  	}}}, true)
   730  }