sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/elb/loadbalancer_test.go (about)

     1  /*
     2  Copyright 2019 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 elb
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/aws/aws-sdk-go/aws"
    26  	"github.com/aws/aws-sdk-go/aws/awserr"
    27  	"github.com/aws/aws-sdk-go/service/ec2"
    28  	"github.com/aws/aws-sdk-go/service/elb"
    29  	rgapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
    30  	"github.com/golang/mock/gomock"
    31  	. "github.com/onsi/gomega"
    32  	"github.com/pkg/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/utils/pointer"
    36  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  
    38  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    39  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    40  	"sigs.k8s.io/cluster-api-provider-aws/test/mocks"
    41  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    42  	"sigs.k8s.io/cluster-api/util/conditions"
    43  )
    44  
    45  func TestELBName(t *testing.T) {
    46  	tests := []struct {
    47  		name       string
    48  		awsCluster infrav1.AWSCluster
    49  		expected   string
    50  	}{
    51  		{
    52  			name: "name is not defined by user, so generate the default",
    53  			awsCluster: infrav1.AWSCluster{
    54  				ObjectMeta: metav1.ObjectMeta{
    55  					Name:      "example",
    56  					Namespace: metav1.NamespaceDefault,
    57  				},
    58  			},
    59  			expected: "example-apiserver",
    60  		},
    61  		{
    62  			name: "name is defined by user, so use it",
    63  			awsCluster: infrav1.AWSCluster{
    64  				ObjectMeta: metav1.ObjectMeta{
    65  					Name:      "example",
    66  					Namespace: metav1.NamespaceDefault,
    67  				},
    68  				Spec: infrav1.AWSClusterSpec{
    69  					ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
    70  						Name: pointer.String("myapiserver"),
    71  					},
    72  				},
    73  			},
    74  			expected: "myapiserver",
    75  		},
    76  	}
    77  	for _, tt := range tests {
    78  		t.Run(tt.name, func(t *testing.T) {
    79  			scheme := runtime.NewScheme()
    80  			_ = infrav1.AddToScheme(scheme)
    81  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
    82  
    83  			scope, err := scope.NewClusterScope(scope.ClusterScopeParams{
    84  				Client: client,
    85  				Cluster: &clusterv1.Cluster{
    86  					ObjectMeta: metav1.ObjectMeta{
    87  						Name:      tt.awsCluster.Name,
    88  						Namespace: tt.awsCluster.Namespace,
    89  					},
    90  				},
    91  				AWSCluster: &tt.awsCluster,
    92  			})
    93  			if err != nil {
    94  				t.Fatalf("failed to create scope: %s", err)
    95  			}
    96  
    97  			elbName, err := ELBName(scope)
    98  			if err != nil {
    99  				t.Fatalf("unable to get ELB name: %v", err)
   100  			}
   101  			if elbName != tt.expected {
   102  				t.Fatalf("expected ELB name: %v, got name: %v", tt.expected, elbName)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  func TestGenerateELBName(t *testing.T) {
   109  	tests := []struct {
   110  		name     string
   111  		expected string
   112  	}{
   113  		{
   114  			name:     "test",
   115  			expected: "test-apiserver",
   116  		},
   117  		{
   118  			name:     "0123456789012345678901",
   119  			expected: "0123456789012345678901-apiserver",
   120  		},
   121  		{
   122  			name:     "01234567890123456789012",
   123  			expected: "26o3cjil5at5qn27vukn5x09b3ql-k8s",
   124  		},
   125  		{
   126  			name:     "anotherverylongtoolongname",
   127  			expected: "t8gnrbbifaaf5d0k4xmwui3xwvip-k8s",
   128  		},
   129  		{
   130  			name:     "anotherverylongtoolongnameanotherverylongtoolongname",
   131  			expected: "tph1huzox1f10z9ow1inrootjws8-k8s",
   132  		},
   133  	}
   134  	for _, tt := range tests {
   135  		t.Run(tt.name, func(t *testing.T) {
   136  			elbName, err := GenerateELBName(tt.name)
   137  			if err != nil {
   138  				t.Error(err)
   139  			}
   140  
   141  			if elbName != tt.expected {
   142  				t.Errorf("expected ELB name: %v, got name: %v", tt.expected, elbName)
   143  			}
   144  
   145  			if len(elbName) > 32 {
   146  				t.Errorf("ELB name too long: %v vs. %s", len(elbName), "32")
   147  			}
   148  		})
   149  	}
   150  }
   151  
   152  func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
   153  	tests := []struct {
   154  		name   string
   155  		lb     *infrav1.AWSLoadBalancerSpec
   156  		mocks  func(m *mocks.MockEC2APIMockRecorder)
   157  		expect func(t *testing.T, g *WithT, res *infrav1.ClassicELB)
   158  	}{
   159  		{
   160  			name:  "nil load balancer config",
   161  			lb:    nil,
   162  			mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   163  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   164  				t.Helper()
   165  				if res.Attributes.CrossZoneLoadBalancing {
   166  					t.Error("Expected load balancer not to have cross-zone load balancing enabled")
   167  				}
   168  			},
   169  		},
   170  		{
   171  			name: "load balancer config with cross zone enabled",
   172  			lb: &infrav1.AWSLoadBalancerSpec{
   173  				CrossZoneLoadBalancing: true,
   174  			},
   175  			mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   176  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   177  				t.Helper()
   178  				if !res.Attributes.CrossZoneLoadBalancing {
   179  					t.Error("Expected load balancer to have cross-zone load balancing enabled")
   180  				}
   181  			},
   182  		},
   183  		{
   184  			name: "load balancer config with subnets specified",
   185  			lb: &infrav1.AWSLoadBalancerSpec{
   186  				Subnets: []string{"subnet-1", "subnet-2"},
   187  			},
   188  			mocks: func(m *mocks.MockEC2APIMockRecorder) {
   189  				m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{
   190  					SubnetIds: []*string{
   191  						aws.String("subnet-1"),
   192  						aws.String("subnet-2"),
   193  					},
   194  				})).
   195  					Return(&ec2.DescribeSubnetsOutput{
   196  						Subnets: []*ec2.Subnet{
   197  							{
   198  								SubnetId:         aws.String("subnet-1"),
   199  								AvailabilityZone: aws.String("us-east-1a"),
   200  							},
   201  							{
   202  								SubnetId:         aws.String("subnet-2"),
   203  								AvailabilityZone: aws.String("us-east-1b"),
   204  							},
   205  						},
   206  					}, nil)
   207  			},
   208  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   209  				t.Helper()
   210  				if len(res.SubnetIDs) != 2 {
   211  					t.Errorf("Expected load balancer to be configured for 2 subnets, got %v", len(res.SubnetIDs))
   212  				}
   213  				if len(res.AvailabilityZones) != 2 {
   214  					t.Errorf("Expected load balancer to be configured for 2 availability zones, got %v", len(res.AvailabilityZones))
   215  				}
   216  			},
   217  		},
   218  		{
   219  			name: "load balancer config with additional security groups specified",
   220  			lb: &infrav1.AWSLoadBalancerSpec{
   221  				AdditionalSecurityGroups: []string{"sg-00001", "sg-00002"},
   222  			},
   223  			mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   224  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   225  				t.Helper()
   226  				if len(res.SecurityGroupIDs) != 3 {
   227  					t.Errorf("Expected load balancer to be configured for 3 security groups, got %v", len(res.SecurityGroupIDs))
   228  				}
   229  			},
   230  		},
   231  		{
   232  			name: "Should create load balancer spec if elb health check protocol specified in config",
   233  			lb: &infrav1.AWSLoadBalancerSpec{
   234  				HealthCheckProtocol: &infrav1.ClassicELBProtocolTCP,
   235  			},
   236  			mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   237  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   238  				t.Helper()
   239  				expectedTarget := fmt.Sprintf("%v:%d", infrav1.ClassicELBProtocolTCP, 6443)
   240  				g.Expect(expectedTarget, res.HealthCheck.Target)
   241  			},
   242  		},
   243  		{
   244  			name:  "Should create load balancer spec with default elb health check protocol",
   245  			lb:    &infrav1.AWSLoadBalancerSpec{},
   246  			mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   247  			expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
   248  				t.Helper()
   249  				expectedTarget := fmt.Sprintf("%v:%d", infrav1.ClassicELBProtocolTCP, 6443)
   250  				g.Expect(expectedTarget, res.HealthCheck.Target)
   251  			},
   252  		},
   253  	}
   254  
   255  	for _, tc := range tests {
   256  		t.Run(tc.name, func(t *testing.T) {
   257  			g := NewWithT(t)
   258  			mockCtrl := gomock.NewController(t)
   259  			defer mockCtrl.Finish()
   260  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
   261  
   262  			scheme := runtime.NewScheme()
   263  			_ = infrav1.AddToScheme(scheme)
   264  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   265  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   266  				Client: client,
   267  				Cluster: &clusterv1.Cluster{
   268  					ObjectMeta: metav1.ObjectMeta{
   269  						Namespace: "foo",
   270  						Name:      "bar",
   271  					},
   272  				},
   273  				AWSCluster: &infrav1.AWSCluster{
   274  					ObjectMeta: metav1.ObjectMeta{Name: "test"},
   275  					Spec: infrav1.AWSClusterSpec{
   276  						ControlPlaneLoadBalancer: tc.lb,
   277  					},
   278  				},
   279  			})
   280  			if err != nil {
   281  				t.Fatal(err)
   282  			}
   283  
   284  			tc.mocks(ec2Mock.EXPECT())
   285  
   286  			s := &Service{
   287  				scope:     clusterScope,
   288  				EC2Client: ec2Mock,
   289  			}
   290  
   291  			spec, err := s.getAPIServerClassicELBSpec(clusterScope.Name())
   292  			if err != nil {
   293  				t.Fatal(err)
   294  			}
   295  
   296  			tc.expect(t, g, spec)
   297  		})
   298  	}
   299  }
   300  
   301  func TestRegisterInstanceWithAPIServerELB(t *testing.T) {
   302  	const (
   303  		namespace       = "foo"
   304  		clusterName     = "bar"
   305  		clusterSubnetID = "subnet-1"
   306  		elbName         = "bar-apiserver"
   307  		elbSubnetID     = "elb-subnet"
   308  		instanceID      = "test-instance"
   309  		az              = "us-west-1a"
   310  		differentAZ     = "us-east-2c"
   311  	)
   312  
   313  	tests := []struct {
   314  		name        string
   315  		awsCluster  *infrav1.AWSCluster
   316  		elbAPIMocks func(m *mocks.MockELBAPIMockRecorder)
   317  		ec2Mocks    func(m *mocks.MockEC2APIMockRecorder)
   318  		check       func(t *testing.T, err error)
   319  	}{
   320  		{
   321  			name: "no load balancer subnets specified",
   322  			awsCluster: &infrav1.AWSCluster{
   323  				ObjectMeta: metav1.ObjectMeta{Name: clusterName},
   324  				Spec: infrav1.AWSClusterSpec{
   325  					ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
   326  						Name: aws.String(elbName),
   327  					},
   328  					NetworkSpec: infrav1.NetworkSpec{
   329  						Subnets: infrav1.Subnets{{
   330  							ID:               clusterSubnetID,
   331  							AvailabilityZone: az,
   332  						}},
   333  					},
   334  				},
   335  			},
   336  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   337  				m.DescribeLoadBalancers(gomock.Eq(&elb.DescribeLoadBalancersInput{
   338  					LoadBalancerNames: aws.StringSlice([]string{elbName}),
   339  				})).
   340  					Return(&elb.DescribeLoadBalancersOutput{
   341  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   342  							{
   343  								LoadBalancerName: aws.String(elbName),
   344  								Scheme:           aws.String(string(infrav1.ClassicELBSchemeInternetFacing)),
   345  								Subnets:          []*string{aws.String(clusterSubnetID)},
   346  							},
   347  						},
   348  					}, nil)
   349  				m.DescribeLoadBalancerAttributes(gomock.Eq(&elb.DescribeLoadBalancerAttributesInput{
   350  					LoadBalancerName: aws.String(elbName),
   351  				})).
   352  					Return(&elb.DescribeLoadBalancerAttributesOutput{
   353  						LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   354  							CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   355  								Enabled: aws.Bool(false),
   356  							},
   357  						},
   358  					}, nil)
   359  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   360  					&elb.DescribeTagsOutput{
   361  						TagDescriptions: []*elb.TagDescription{
   362  							{
   363  								LoadBalancerName: aws.String(elbName),
   364  								Tags: []*elb.Tag{{
   365  									Key:   aws.String(infrav1.ClusterTagKey(clusterName)),
   366  									Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   367  								}},
   368  							},
   369  						},
   370  					}, nil)
   371  
   372  				m.RegisterInstancesWithLoadBalancer(gomock.Eq(&elb.RegisterInstancesWithLoadBalancerInput{
   373  					Instances:        []*elb.Instance{{InstanceId: aws.String(instanceID)}},
   374  					LoadBalancerName: aws.String(elbName),
   375  				})).
   376  					Return(&elb.RegisterInstancesWithLoadBalancerOutput{
   377  						Instances: []*elb.Instance{{InstanceId: aws.String(instanceID)}},
   378  					}, nil)
   379  			},
   380  			ec2Mocks: func(m *mocks.MockEC2APIMockRecorder) {},
   381  			check: func(t *testing.T, err error) {
   382  				t.Helper()
   383  				if err != nil {
   384  					t.Fatalf("did not expect error: %v", err)
   385  				}
   386  			},
   387  		},
   388  		{
   389  			name: "load balancer subnets specified in the same az from the instance",
   390  			awsCluster: &infrav1.AWSCluster{
   391  				ObjectMeta: metav1.ObjectMeta{Name: clusterName},
   392  				Spec: infrav1.AWSClusterSpec{
   393  					NetworkSpec: infrav1.NetworkSpec{
   394  						Subnets: infrav1.Subnets{{
   395  							ID:               clusterSubnetID,
   396  							AvailabilityZone: az,
   397  						}},
   398  					},
   399  					ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
   400  						Name:    aws.String("bar-apiserver"),
   401  						Subnets: []string{elbSubnetID},
   402  					},
   403  				},
   404  			},
   405  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   406  				m.DescribeLoadBalancers(gomock.Eq(&elb.DescribeLoadBalancersInput{
   407  					LoadBalancerNames: aws.StringSlice([]string{elbName}),
   408  				})).
   409  					Return(&elb.DescribeLoadBalancersOutput{
   410  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   411  							{
   412  								Scheme:            aws.String(string(infrav1.ClassicELBSchemeInternetFacing)),
   413  								Subnets:           []*string{aws.String(elbSubnetID)},
   414  								AvailabilityZones: []*string{aws.String(az)},
   415  							},
   416  						},
   417  					}, nil)
   418  				m.DescribeLoadBalancerAttributes(gomock.Eq(&elb.DescribeLoadBalancerAttributesInput{
   419  					LoadBalancerName: aws.String(elbName),
   420  				})).
   421  					Return(&elb.DescribeLoadBalancerAttributesOutput{
   422  						LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   423  							CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   424  								Enabled: aws.Bool(false),
   425  							},
   426  						},
   427  					}, nil)
   428  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   429  					&elb.DescribeTagsOutput{
   430  						TagDescriptions: []*elb.TagDescription{
   431  							{
   432  								LoadBalancerName: aws.String(elbName),
   433  								Tags: []*elb.Tag{{
   434  									Key:   aws.String(infrav1.ClusterTagKey(clusterName)),
   435  									Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   436  								}},
   437  							},
   438  						},
   439  					}, nil)
   440  
   441  				m.RegisterInstancesWithLoadBalancer(gomock.Eq(&elb.RegisterInstancesWithLoadBalancerInput{
   442  					Instances:        []*elb.Instance{{InstanceId: aws.String(instanceID)}},
   443  					LoadBalancerName: aws.String(elbName),
   444  				})).
   445  					Return(&elb.RegisterInstancesWithLoadBalancerOutput{
   446  						Instances: []*elb.Instance{{InstanceId: aws.String(instanceID)}},
   447  					}, nil)
   448  			},
   449  			ec2Mocks: func(m *mocks.MockEC2APIMockRecorder) {
   450  				m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{
   451  					SubnetIds: []*string{
   452  						aws.String(elbSubnetID),
   453  					},
   454  				})).
   455  					Return(&ec2.DescribeSubnetsOutput{
   456  						Subnets: []*ec2.Subnet{
   457  							{
   458  								SubnetId:         aws.String(elbSubnetID),
   459  								AvailabilityZone: aws.String(az),
   460  							},
   461  						},
   462  					}, nil)
   463  			},
   464  			check: func(t *testing.T, err error) {
   465  				t.Helper()
   466  				if err != nil {
   467  					t.Fatalf("did not expect error: %v", err)
   468  				}
   469  			},
   470  		},
   471  		{
   472  			name: "load balancer subnets specified in a different az from the instance",
   473  			awsCluster: &infrav1.AWSCluster{
   474  				ObjectMeta: metav1.ObjectMeta{Name: clusterName},
   475  				Spec: infrav1.AWSClusterSpec{
   476  					NetworkSpec: infrav1.NetworkSpec{
   477  						Subnets: infrav1.Subnets{{
   478  							ID:               clusterSubnetID,
   479  							AvailabilityZone: az,
   480  						}},
   481  					},
   482  					ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
   483  						Name:    aws.String(elbName),
   484  						Subnets: []string{elbSubnetID},
   485  					},
   486  				},
   487  			},
   488  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   489  				m.DescribeLoadBalancers(gomock.Eq(&elb.DescribeLoadBalancersInput{
   490  					LoadBalancerNames: aws.StringSlice([]string{elbName}),
   491  				})).
   492  					Return(&elb.DescribeLoadBalancersOutput{
   493  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   494  							{
   495  								Scheme:            aws.String(string(infrav1.ClassicELBSchemeInternetFacing)),
   496  								Subnets:           []*string{aws.String(elbSubnetID)},
   497  								AvailabilityZones: []*string{aws.String(differentAZ)},
   498  							},
   499  						},
   500  					}, nil)
   501  				m.DescribeLoadBalancerAttributes(gomock.Eq(&elb.DescribeLoadBalancerAttributesInput{
   502  					LoadBalancerName: aws.String(elbName),
   503  				})).
   504  					Return(&elb.DescribeLoadBalancerAttributesOutput{
   505  						LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   506  							CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   507  								Enabled: aws.Bool(false),
   508  							},
   509  						},
   510  					}, nil)
   511  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   512  					&elb.DescribeTagsOutput{
   513  						TagDescriptions: []*elb.TagDescription{
   514  							{
   515  								LoadBalancerName: aws.String(elbName),
   516  								Tags: []*elb.Tag{{
   517  									Key:   aws.String(infrav1.ClusterTagKey(clusterName)),
   518  									Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   519  								}},
   520  							},
   521  						},
   522  					}, nil)
   523  			},
   524  			ec2Mocks: func(m *mocks.MockEC2APIMockRecorder) {
   525  				m.DescribeSubnets(gomock.Eq(&ec2.DescribeSubnetsInput{
   526  					SubnetIds: []*string{
   527  						aws.String(elbSubnetID),
   528  					},
   529  				})).
   530  					Return(&ec2.DescribeSubnetsOutput{
   531  						Subnets: []*ec2.Subnet{
   532  							{
   533  								SubnetId:         aws.String(elbSubnetID),
   534  								AvailabilityZone: aws.String(differentAZ),
   535  							},
   536  						},
   537  					}, nil)
   538  			},
   539  			check: func(t *testing.T, err error) {
   540  				t.Helper()
   541  				expectedErrMsg := "failed to register instance with APIServer ELB \"bar-apiserver\": instance is in availability zone \"us-west-1a\", no public subnets attached to the ELB in the same zone"
   542  				if err == nil {
   543  					t.Fatalf("Expected error, but got nil")
   544  				}
   545  
   546  				if !strings.Contains(err.Error(), expectedErrMsg) {
   547  					t.Fatalf("Expected error: %s\nInstead got: %s", expectedErrMsg, err.Error())
   548  				}
   549  			},
   550  		},
   551  	}
   552  
   553  	for _, tc := range tests {
   554  		t.Run(tc.name, func(t *testing.T) {
   555  			mockCtrl := gomock.NewController(t)
   556  			defer mockCtrl.Finish()
   557  			elbAPIMocks := mocks.NewMockELBAPI(mockCtrl)
   558  			ec2Mock := mocks.NewMockEC2API(mockCtrl)
   559  
   560  			scheme, err := setupScheme()
   561  			if err != nil {
   562  				t.Fatal(err)
   563  			}
   564  
   565  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   566  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   567  				Client: client,
   568  				Cluster: &clusterv1.Cluster{
   569  					ObjectMeta: metav1.ObjectMeta{
   570  						Namespace: namespace,
   571  						Name:      clusterName,
   572  					},
   573  				},
   574  				AWSCluster: tc.awsCluster,
   575  			})
   576  			if err != nil {
   577  				t.Fatal(err)
   578  			}
   579  
   580  			instance := &infrav1.Instance{
   581  				ID:       instanceID,
   582  				SubnetID: clusterSubnetID,
   583  			}
   584  
   585  			tc.elbAPIMocks(elbAPIMocks.EXPECT())
   586  			tc.ec2Mocks(ec2Mock.EXPECT())
   587  
   588  			s := &Service{
   589  				scope:     clusterScope,
   590  				EC2Client: ec2Mock,
   591  				ELBClient: elbAPIMocks,
   592  			}
   593  
   594  			err = s.RegisterInstanceWithAPIServerELB(instance)
   595  			tc.check(t, err)
   596  		})
   597  	}
   598  }
   599  
   600  func TestDeleteAPIServerELB(t *testing.T) {
   601  	clusterName := "bar" //nolint:goconst // does not need to be a package-level const
   602  	elbName := "bar-apiserver"
   603  	tests := []struct {
   604  		name             string
   605  		elbAPIMocks      func(m *mocks.MockELBAPIMockRecorder)
   606  		verifyAWSCluster func(*infrav1.AWSCluster)
   607  	}{
   608  		{
   609  			name: "if control plane ELB is not found, do nothing",
   610  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   611  				m.DescribeLoadBalancers(gomock.Eq(&elb.DescribeLoadBalancersInput{
   612  					LoadBalancerNames: aws.StringSlice([]string{elbName}),
   613  				})).Return(nil, awserr.New(elb.ErrCodeAccessPointNotFoundException, "", nil))
   614  			},
   615  			verifyAWSCluster: func(awsCluster *infrav1.AWSCluster) {
   616  				loadBalancerConditionReady := conditions.IsTrue(awsCluster, infrav1.LoadBalancerReadyCondition)
   617  				if loadBalancerConditionReady {
   618  					t.Fatalf("Expected LoadBalancerReady condition to be False, but was True")
   619  				}
   620  				loadBalancerConditionReason := conditions.GetReason(awsCluster, infrav1.LoadBalancerReadyCondition)
   621  				if loadBalancerConditionReason != clusterv1.DeletedReason {
   622  					t.Fatalf("Expected LoadBalancerReady condition reason to be Deleted, but was %s", loadBalancerConditionReason)
   623  				}
   624  			},
   625  		},
   626  		{
   627  			name: "if control plane ELB is found, and it is not managed, do nothing",
   628  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   629  				m.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   630  					&elb.DescribeLoadBalancersOutput{
   631  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   632  							{
   633  								LoadBalancerName: aws.String(elbName),
   634  								Scheme:           aws.String(string(infrav1.ClassicELBSchemeInternetFacing)),
   635  							},
   636  						},
   637  					},
   638  					nil,
   639  				)
   640  
   641  				m.DescribeLoadBalancerAttributes(&elb.DescribeLoadBalancerAttributesInput{LoadBalancerName: aws.String(elbName)}).Return(
   642  					&elb.DescribeLoadBalancerAttributesOutput{
   643  						LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   644  							CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   645  								Enabled: aws.Bool(false),
   646  							},
   647  						},
   648  					},
   649  					nil,
   650  				)
   651  
   652  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   653  					&elb.DescribeTagsOutput{
   654  						TagDescriptions: []*elb.TagDescription{
   655  							{
   656  								LoadBalancerName: aws.String(elbName),
   657  								Tags:             []*elb.Tag{},
   658  							},
   659  						},
   660  					},
   661  					nil,
   662  				)
   663  			},
   664  			verifyAWSCluster: func(awsCluster *infrav1.AWSCluster) {
   665  				loadBalancerConditionReady := conditions.IsTrue(awsCluster, infrav1.LoadBalancerReadyCondition)
   666  				if loadBalancerConditionReady {
   667  					t.Fatalf("Expected LoadBalancerReady condition to be False, but was True")
   668  				}
   669  				loadBalancerConditionReason := conditions.GetReason(awsCluster, infrav1.LoadBalancerReadyCondition)
   670  				if loadBalancerConditionReason != clusterv1.DeletedReason {
   671  					t.Fatalf("Expected LoadBalancerReady condition reason to be Deleted, but was %s", loadBalancerConditionReason)
   672  				}
   673  			},
   674  		},
   675  		{
   676  			name: "if control plane ELB is found, and it is managed, delete the ELB",
   677  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   678  				m.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   679  					&elb.DescribeLoadBalancersOutput{
   680  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   681  							{
   682  								LoadBalancerName: aws.String(elbName),
   683  								Scheme:           aws.String(string(infrav1.ClassicELBSchemeInternetFacing)),
   684  							},
   685  						},
   686  					},
   687  					nil,
   688  				)
   689  
   690  				m.DescribeLoadBalancerAttributes(&elb.DescribeLoadBalancerAttributesInput{LoadBalancerName: aws.String(elbName)}).Return(
   691  					&elb.DescribeLoadBalancerAttributesOutput{
   692  						LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   693  							CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   694  								Enabled: aws.Bool(false),
   695  							},
   696  						},
   697  					},
   698  					nil,
   699  				)
   700  
   701  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   702  					&elb.DescribeTagsOutput{
   703  						TagDescriptions: []*elb.TagDescription{
   704  							{
   705  								LoadBalancerName: aws.String(elbName),
   706  								Tags: []*elb.Tag{{
   707  									Key:   aws.String(infrav1.ClusterTagKey(clusterName)),
   708  									Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   709  								}},
   710  							},
   711  						},
   712  					},
   713  					nil,
   714  				)
   715  
   716  				m.DeleteLoadBalancer(&elb.DeleteLoadBalancerInput{LoadBalancerName: aws.String(elbName)}).Return(
   717  					&elb.DeleteLoadBalancerOutput{}, nil)
   718  
   719  				m.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(elbName)}}).Return(
   720  					&elb.DescribeLoadBalancersOutput{
   721  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{},
   722  					},
   723  					nil,
   724  				)
   725  			},
   726  			verifyAWSCluster: func(awsCluster *infrav1.AWSCluster) {
   727  				loadBalancerConditionReady := conditions.IsTrue(awsCluster, infrav1.LoadBalancerReadyCondition)
   728  				if loadBalancerConditionReady {
   729  					t.Fatalf("Expected LoadBalancerReady condition to be False, but was True")
   730  				}
   731  				loadBalancerConditionReason := conditions.GetReason(awsCluster, infrav1.LoadBalancerReadyCondition)
   732  				if loadBalancerConditionReason != clusterv1.DeletedReason {
   733  					t.Fatalf("Expected LoadBalancerReady condition reason to be Deleted, but was %s", loadBalancerConditionReason)
   734  				}
   735  			},
   736  		},
   737  	}
   738  
   739  	for _, tc := range tests {
   740  		t.Run(tc.name, func(t *testing.T) {
   741  			mockCtrl := gomock.NewController(t)
   742  			defer mockCtrl.Finish()
   743  			rgapiMock := mocks.NewMockResourceGroupsTaggingAPIAPI(mockCtrl)
   744  			elbapiMock := mocks.NewMockELBAPI(mockCtrl)
   745  
   746  			scheme, err := setupScheme()
   747  			if err != nil {
   748  				t.Fatal(err)
   749  			}
   750  
   751  			awsCluster := &infrav1.AWSCluster{
   752  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   753  				Spec: infrav1.AWSClusterSpec{
   754  					ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
   755  						Name: aws.String(elbName),
   756  					},
   757  				},
   758  			}
   759  
   760  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   761  			ctx := context.TODO()
   762  			client.Create(ctx, awsCluster)
   763  
   764  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   765  				Cluster: &clusterv1.Cluster{
   766  					ObjectMeta: metav1.ObjectMeta{
   767  						Namespace: "foo",
   768  						Name:      clusterName,
   769  					},
   770  				},
   771  				AWSCluster: awsCluster,
   772  				Client:     client,
   773  			})
   774  			if err != nil {
   775  				t.Fatal(err)
   776  			}
   777  
   778  			tc.elbAPIMocks(elbapiMock.EXPECT())
   779  
   780  			s := &Service{
   781  				scope:                 clusterScope,
   782  				ResourceTaggingClient: rgapiMock,
   783  				ELBClient:             elbapiMock,
   784  			}
   785  
   786  			err = s.deleteAPIServerELB()
   787  			if err != nil {
   788  				t.Fatal(err)
   789  			}
   790  
   791  			tc.verifyAWSCluster(awsCluster)
   792  		})
   793  	}
   794  }
   795  
   796  func TestDeleteAWSCloudProviderELBs(t *testing.T) {
   797  	clusterName := "bar"
   798  	tests := []struct {
   799  		name                  string
   800  		rgAPIMocks            func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder)
   801  		elbAPIMocks           func(m *mocks.MockELBAPIMockRecorder)
   802  		postDeleteRGAPIMocks  func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder)
   803  		postDeleteElbAPIMocks func(m *mocks.MockELBAPIMockRecorder)
   804  	}{
   805  		{
   806  			name: "discover ELBs with Resource Groups Tagging API and then delete successfully",
   807  			rgAPIMocks: func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder) {
   808  				m.GetResourcesPages(&rgapi.GetResourcesInput{
   809  					ResourceTypeFilters: aws.StringSlice([]string{elbResourceType}),
   810  					TagFilters: []*rgapi.TagFilter{
   811  						{
   812  							Key:    aws.String(infrav1.ClusterAWSCloudProviderTagKey(clusterName)),
   813  							Values: aws.StringSlice([]string{string(infrav1.ResourceLifecycleOwned)}),
   814  						},
   815  					},
   816  				}, gomock.Any()).Do(func(_, y interface{}) {
   817  					funct := y.(func(output *rgapi.GetResourcesOutput, lastPage bool) bool)
   818  					funct(&rgapi.GetResourcesOutput{
   819  						ResourceTagMappingList: []*rgapi.ResourceTagMapping{
   820  							{
   821  								ResourceARN: aws.String("arn:aws:elasticloadbalancing:eu-west-2:1234567890:loadbalancer/lb-service-name"),
   822  								Tags: []*rgapi.Tag{{
   823  									Key:   aws.String(infrav1.ClusterAWSCloudProviderTagKey(clusterName)),
   824  									Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   825  								}},
   826  							},
   827  						},
   828  					}, true)
   829  				}).Return(nil)
   830  			},
   831  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   832  				m.DeleteLoadBalancer(gomock.Eq(&elb.DeleteLoadBalancerInput{LoadBalancerName: aws.String("lb-service-name")})).Return(nil, nil)
   833  			},
   834  			postDeleteRGAPIMocks: func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder) {
   835  				m.GetResourcesPages(&rgapi.GetResourcesInput{
   836  					ResourceTypeFilters: aws.StringSlice([]string{elbResourceType}),
   837  					TagFilters: []*rgapi.TagFilter{
   838  						{
   839  							Key:    aws.String(infrav1.ClusterAWSCloudProviderTagKey(clusterName)),
   840  							Values: aws.StringSlice([]string{string(infrav1.ResourceLifecycleOwned)}),
   841  						},
   842  					},
   843  				}, gomock.Any()).Do(func(_, y interface{}) {
   844  					funct := y.(func(output *rgapi.GetResourcesOutput, lastPage bool) bool)
   845  					funct(&rgapi.GetResourcesOutput{
   846  						ResourceTagMappingList: []*rgapi.ResourceTagMapping{},
   847  					}, true)
   848  				}).Return(nil)
   849  			},
   850  		},
   851  		{
   852  			name: "fall back to ELB API when Resource Groups Tagging API fails and then delete successfully",
   853  			rgAPIMocks: func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder) {
   854  				m.GetResourcesPages(gomock.Any(), gomock.Any()).Return(errors.Errorf("connection failure")).AnyTimes()
   855  			},
   856  			elbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   857  				m.DescribeLoadBalancersPages(gomock.Any(), gomock.Any()).Do(func(_, y interface{}) {
   858  					funct := y.(func(output *elb.DescribeLoadBalancersOutput, lastPage bool) bool)
   859  					funct(&elb.DescribeLoadBalancersOutput{
   860  						LoadBalancerDescriptions: []*elb.LoadBalancerDescription{
   861  							{
   862  								LoadBalancerName: aws.String("lb-service-name"),
   863  							},
   864  							{
   865  								LoadBalancerName: aws.String("another-service-not-owned"),
   866  							},
   867  							{
   868  								LoadBalancerName: aws.String("service-without-tags"),
   869  							},
   870  						},
   871  					}, true)
   872  				}).Return(nil)
   873  				m.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: []*string{aws.String("lb-service-name"), aws.String("another-service-not-owned"), aws.String("service-without-tags")}}).Return(&elb.DescribeTagsOutput{
   874  					TagDescriptions: []*elb.TagDescription{
   875  						{
   876  							LoadBalancerName: aws.String("lb-service-name"),
   877  							Tags: []*elb.Tag{{
   878  								Key:   aws.String(infrav1.ClusterAWSCloudProviderTagKey(clusterName)),
   879  								Value: aws.String(string(infrav1.ResourceLifecycleOwned)),
   880  							}},
   881  						},
   882  						{
   883  							LoadBalancerName: aws.String("another-service-not-owned"),
   884  							Tags: []*elb.Tag{{
   885  								Key:   aws.String("some-tag-key"),
   886  								Value: aws.String("some-tag-value"),
   887  							}},
   888  						},
   889  						{
   890  							LoadBalancerName: aws.String("service-without-tags"),
   891  							Tags:             []*elb.Tag{},
   892  						},
   893  					},
   894  				}, nil)
   895  				m.DeleteLoadBalancer(gomock.Eq(&elb.DeleteLoadBalancerInput{LoadBalancerName: aws.String("lb-service-name")})).Return(nil, nil)
   896  			},
   897  			postDeleteElbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   898  				m.DescribeLoadBalancersPages(gomock.Any(), gomock.Any()).Return(nil)
   899  			},
   900  		},
   901  	}
   902  
   903  	for _, tc := range tests {
   904  		t.Run(tc.name, func(t *testing.T) {
   905  			mockCtrl := gomock.NewController(t)
   906  			defer mockCtrl.Finish()
   907  			rgapiMock := mocks.NewMockResourceGroupsTaggingAPIAPI(mockCtrl)
   908  			elbapiMock := mocks.NewMockELBAPI(mockCtrl)
   909  
   910  			scheme, err := setupScheme()
   911  			if err != nil {
   912  				t.Fatal(err)
   913  			}
   914  
   915  			awsCluster := &infrav1.AWSCluster{
   916  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   917  				Spec:       infrav1.AWSClusterSpec{},
   918  			}
   919  
   920  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
   921  			ctx := context.TODO()
   922  			client.Create(ctx, awsCluster)
   923  
   924  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
   925  				Cluster: &clusterv1.Cluster{
   926  					ObjectMeta: metav1.ObjectMeta{
   927  						Namespace: "foo",
   928  						Name:      clusterName,
   929  					},
   930  				},
   931  				AWSCluster: awsCluster,
   932  				Client:     client,
   933  			})
   934  			if err != nil {
   935  				t.Fatal(err)
   936  			}
   937  
   938  			tc.rgAPIMocks(rgapiMock.EXPECT())
   939  			tc.elbAPIMocks(elbapiMock.EXPECT())
   940  			if tc.postDeleteElbAPIMocks != nil {
   941  				tc.postDeleteElbAPIMocks(elbapiMock.EXPECT())
   942  			}
   943  			if tc.postDeleteRGAPIMocks != nil {
   944  				tc.postDeleteRGAPIMocks(rgapiMock.EXPECT())
   945  			}
   946  
   947  			s := &Service{
   948  				scope:                 clusterScope,
   949  				ResourceTaggingClient: rgapiMock,
   950  				ELBClient:             elbapiMock,
   951  			}
   952  
   953  			err = s.deleteAWSCloudProviderELBs()
   954  			if err != nil {
   955  				t.Fatal(err)
   956  			}
   957  		})
   958  	}
   959  }
   960  
   961  func TestDescribeLoadbalancers(t *testing.T) {
   962  	clusterName := "bar"
   963  	tests := []struct {
   964  		name                string
   965  		lbName              string
   966  		rgAPIMocks          func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder)
   967  		DescribeElbAPIMocks func(m *mocks.MockELBAPIMockRecorder)
   968  	}{
   969  		{
   970  			name:   "Error if existing loadbalancer with same name doesn't have same scheme",
   971  			lbName: "bar-apiserver",
   972  			rgAPIMocks: func(m *mocks.MockResourceGroupsTaggingAPIAPIMockRecorder) {
   973  				m.GetResourcesPages(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
   974  			},
   975  			DescribeElbAPIMocks: func(m *mocks.MockELBAPIMockRecorder) {
   976  				m.DescribeLoadBalancers(gomock.Eq(&elb.DescribeLoadBalancersInput{
   977  					LoadBalancerNames: aws.StringSlice([]string{"bar-apiserver"}),
   978  				})).Return(&elb.DescribeLoadBalancersOutput{LoadBalancerDescriptions: []*elb.LoadBalancerDescription{{Scheme: pointer.StringPtr(string(infrav1.ClassicELBSchemeInternal))}}}, nil)
   979  			},
   980  		},
   981  	}
   982  
   983  	for _, tc := range tests {
   984  		t.Run(tc.name, func(t *testing.T) {
   985  			mockCtrl := gomock.NewController(t)
   986  			defer mockCtrl.Finish()
   987  			rgapiMock := mocks.NewMockResourceGroupsTaggingAPIAPI(mockCtrl)
   988  			elbapiMock := mocks.NewMockELBAPI(mockCtrl)
   989  
   990  			scheme, err := setupScheme()
   991  			if err != nil {
   992  				t.Fatal(err)
   993  			}
   994  			awsCluster := &infrav1.AWSCluster{
   995  				ObjectMeta: metav1.ObjectMeta{Name: "test"},
   996  				Spec: infrav1.AWSClusterSpec{ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
   997  					Scheme: &infrav1.ClassicELBSchemeInternetFacing,
   998  				}},
   999  			}
  1000  
  1001  			client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1002  			ctx := context.TODO()
  1003  			client.Create(ctx, awsCluster)
  1004  
  1005  			clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{
  1006  				Cluster: &clusterv1.Cluster{
  1007  					ObjectMeta: metav1.ObjectMeta{
  1008  						Namespace: "foo",
  1009  						Name:      clusterName,
  1010  					},
  1011  				},
  1012  				AWSCluster: awsCluster,
  1013  				Client:     client,
  1014  			})
  1015  			if err != nil {
  1016  				t.Fatal(err)
  1017  			}
  1018  
  1019  			tc.rgAPIMocks(rgapiMock.EXPECT())
  1020  			tc.DescribeElbAPIMocks(elbapiMock.EXPECT())
  1021  
  1022  			s := &Service{
  1023  				scope:                 clusterScope,
  1024  				ResourceTaggingClient: rgapiMock,
  1025  				ELBClient:             elbapiMock,
  1026  			}
  1027  
  1028  			_, err = s.describeClassicELB(tc.lbName)
  1029  			if err == nil {
  1030  				t.Fatal(err)
  1031  			}
  1032  		})
  1033  	}
  1034  }
  1035  
  1036  func TestChunkELBs(t *testing.T) {
  1037  	base := "loadbalancer"
  1038  	var names []string
  1039  	for i := 0; i < 25; i++ {
  1040  		names = append(names, fmt.Sprintf("%s+%d", base, i))
  1041  	}
  1042  	tests := []struct {
  1043  		testName              string
  1044  		names                 []string
  1045  		expectedChunkArrayLen int
  1046  	}{
  1047  		{
  1048  			testName:              "When the user has more the 20 ELBs",
  1049  			names:                 names,
  1050  			expectedChunkArrayLen: 2,
  1051  		}, {
  1052  			testName:              "When the user has less than 20 ELBs",
  1053  			names:                 []string{"loadBalancer-00"},
  1054  			expectedChunkArrayLen: 1,
  1055  		},
  1056  	}
  1057  	for _, tc := range tests {
  1058  		t.Run(tc.testName, func(t *testing.T) {
  1059  			ans := chunkELBs(tc.names)
  1060  			if len(ans) != tc.expectedChunkArrayLen {
  1061  				t.Errorf("got %d, want %d", len(ans), tc.expectedChunkArrayLen)
  1062  			}
  1063  		})
  1064  	}
  1065  }
  1066  
  1067  func setupScheme() (*runtime.Scheme, error) {
  1068  	scheme := runtime.NewScheme()
  1069  	if err := clusterv1.AddToScheme(scheme); err != nil {
  1070  		return nil, err
  1071  	}
  1072  	if err := infrav1.AddToScheme(scheme); err != nil {
  1073  		return nil, err
  1074  	}
  1075  	return scheme, nil
  1076  }