sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/elb/loadbalancer.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 elb
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/aws/arn"
    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/google/go-cmp/cmp"
    31  	"github.com/pkg/errors"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  
    34  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors"
    36  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/converters"
    37  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
    38  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait"
    39  	"sigs.k8s.io/cluster-api-provider-aws/pkg/hash"
    40  	"sigs.k8s.io/cluster-api-provider-aws/pkg/record"
    41  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    42  	"sigs.k8s.io/cluster-api/util/conditions"
    43  )
    44  
    45  // ResourceGroups are filtered by ARN identifier: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-syntax
    46  // this is the identifier for classic ELBs: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_elasticloadbalancing.html#elasticloadbalancing-resources-for-iam-policies
    47  const elbResourceType = "elasticloadbalancing:loadbalancer"
    48  
    49  // maxELBsDescribeTagsRequest is the maximum number of loadbalancers for the DescribeTags API call
    50  // see: https://docs.aws.amazon.com/elasticloadbalancing/2012-06-01/APIReference/API_DescribeTags.html
    51  const maxELBsDescribeTagsRequest = 20
    52  
    53  // ReconcileLoadbalancers reconciles the load balancers for the given cluster.
    54  func (s *Service) ReconcileLoadbalancers() error {
    55  	s.scope.V(2).Info("Reconciling load balancers")
    56  
    57  	// If ELB scheme is set to Internet-facing due to an API bug in versions > v0.6.6 and v0.7.0, change it to internet-facing and patch.
    58  	if s.scope.ControlPlaneLoadBalancerScheme().String() == infrav1.ClassicELBSchemeIncorrectInternetFacing.String() {
    59  		s.scope.ControlPlaneLoadBalancer().Scheme = &infrav1.ClassicELBSchemeInternetFacing
    60  		if err := s.scope.PatchObject(); err != nil {
    61  			return err
    62  		}
    63  		s.scope.V(4).Info("Patched control plane load balancer scheme")
    64  	}
    65  
    66  	// Generate a default control plane load balancer name. The load balancer name cannot be
    67  	// generated by the defaulting webhook, because it is derived from the cluster name, and that
    68  	// name is undefined at defaulting time when generateName is used.
    69  	name, err := ELBName(s.scope)
    70  	if err != nil {
    71  		return errors.Wrap(err, "failed to get control plane load balancer name")
    72  	}
    73  
    74  	// Get default api server spec.
    75  	spec, err := s.getAPIServerClassicELBSpec(name)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	apiELB, err := s.describeClassicELB(spec.Name)
    81  	switch {
    82  	case IsNotFound(err) && s.scope.ControlPlaneEndpoint().IsValid():
    83  		// if elb is not found and owner cluster ControlPlaneEndpoint is already populated, then we should not recreate the elb.
    84  		return errors.Wrapf(err, "no loadbalancer exists for the AWSCluster %s, the cluster has become unrecoverable and should be deleted manually", s.scope.InfraClusterName())
    85  	case IsNotFound(err):
    86  		apiELB, err = s.createClassicELB(spec)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		s.scope.V(2).Info("Created new classic load balancer for apiserver", "api-server-elb-name", apiELB.Name)
    91  	case err != nil:
    92  		// Failed to describe the classic ELB
    93  		return err
    94  	}
    95  
    96  	if apiELB.IsManaged(s.scope.Name()) {
    97  		if !cmp.Equal(spec.Attributes, apiELB.Attributes) {
    98  			err := s.configureAttributes(apiELB.Name, spec.Attributes)
    99  			if err != nil {
   100  				return err
   101  			}
   102  		}
   103  
   104  		if err := s.reconcileELBTags(apiELB, spec.Tags); err != nil {
   105  			return errors.Wrapf(err, "failed to reconcile tags for apiserver load balancer %q", apiELB.Name)
   106  		}
   107  
   108  		// Reconcile the subnets and availability zones from the spec
   109  		// and the ones currently attached to the load balancer.
   110  		if len(apiELB.SubnetIDs) != len(spec.SubnetIDs) {
   111  			_, err := s.ELBClient.AttachLoadBalancerToSubnets(&elb.AttachLoadBalancerToSubnetsInput{
   112  				LoadBalancerName: &apiELB.Name,
   113  				Subnets:          aws.StringSlice(spec.SubnetIDs),
   114  			})
   115  			if err != nil {
   116  				return errors.Wrapf(err, "failed to attach apiserver load balancer %q to subnets", apiELB.Name)
   117  			}
   118  		}
   119  		if len(apiELB.AvailabilityZones) != len(spec.AvailabilityZones) {
   120  			apiELB.AvailabilityZones = spec.AvailabilityZones
   121  		}
   122  
   123  		// Reconcile the security groups from the spec and the ones currently attached to the load balancer
   124  		if !sets.NewString(apiELB.SecurityGroupIDs...).Equal(sets.NewString(spec.SecurityGroupIDs...)) {
   125  			_, err := s.ELBClient.ApplySecurityGroupsToLoadBalancer(&elb.ApplySecurityGroupsToLoadBalancerInput{
   126  				LoadBalancerName: &apiELB.Name,
   127  				SecurityGroups:   aws.StringSlice(spec.SecurityGroupIDs),
   128  			})
   129  			if err != nil {
   130  				return errors.Wrapf(err, "failed to apply security groups to load balancer %q", apiELB.Name)
   131  			}
   132  		}
   133  	} else {
   134  		s.scope.V(4).Info("Unmanaged control plane load balancer, skipping load balancer configuration", "api-server-elb", apiELB)
   135  	}
   136  
   137  	// TODO(vincepri): check if anything has changed and reconcile as necessary.
   138  	apiELB.DeepCopyInto(&s.scope.Network().APIServerELB)
   139  	s.scope.V(4).Info("Control plane load balancer", "api-server-elb", apiELB)
   140  
   141  	s.scope.V(2).Info("Reconcile load balancers completed successfully")
   142  	return nil
   143  }
   144  
   145  func (s *Service) deleteAPIServerELB() error {
   146  	s.scope.V(2).Info("Deleting control plane load balancer")
   147  
   148  	elbName, err := ELBName(s.scope)
   149  	if err != nil {
   150  		return errors.Wrap(err, "failed to get control plane load balancer name")
   151  	}
   152  
   153  	conditions.MarkFalse(s.scope.InfraCluster(), infrav1.LoadBalancerReadyCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
   154  	if err := s.scope.PatchObject(); err != nil {
   155  		return err
   156  	}
   157  
   158  	apiELB, err := s.describeClassicELB(elbName)
   159  	if IsNotFound(err) {
   160  		s.scope.V(2).Info("Control plane load balancer not found, skipping deletion")
   161  		conditions.MarkFalse(s.scope.InfraCluster(), infrav1.LoadBalancerReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
   162  		return nil
   163  	}
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	if apiELB.IsUnmanaged(s.scope.Name()) {
   169  		s.scope.V(2).Info("Found unmanaged classic load balancer for apiserver, skipping deletion", "api-server-elb-name", apiELB.Name)
   170  		conditions.MarkFalse(s.scope.InfraCluster(), infrav1.LoadBalancerReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
   171  		return nil
   172  	}
   173  
   174  	s.scope.V(3).Info("deleting load balancer", "name", elbName)
   175  	if err := s.deleteClassicELB(elbName); err != nil {
   176  		conditions.MarkFalse(s.scope.InfraCluster(), infrav1.LoadBalancerReadyCondition, "DeletingFailed", clusterv1.ConditionSeverityWarning, err.Error())
   177  		return err
   178  	}
   179  
   180  	if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (done bool, err error) {
   181  		_, err = s.describeClassicELB(elbName)
   182  		done = IsNotFound(err)
   183  		return done, nil
   184  	}); err != nil {
   185  		return errors.Wrapf(err, "failed to wait for %q load balancer deletion", s.scope.Name())
   186  	}
   187  
   188  	conditions.MarkFalse(s.scope.InfraCluster(), infrav1.LoadBalancerReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
   189  	s.scope.Info("Deleted control plane load balancer", "name", elbName)
   190  	return nil
   191  }
   192  
   193  // deleteAWSCloudProviderELBs deletes ELBs owned by the AWS Cloud Provider. For every
   194  // LoadBalancer-type Service on the cluster, there is one ELB. If the Service is deleted before the
   195  // cluster is deleted, its ELB is deleted; the ELBs found in this function will typically be for
   196  // Services that were not deleted before the cluster was deleted.
   197  func (s *Service) deleteAWSCloudProviderELBs() error {
   198  	s.scope.V(2).Info("Deleting AWS cloud provider load balancers (created for LoadBalancer-type Services)")
   199  
   200  	elbs, err := s.listAWSCloudProviderOwnedELBs()
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	for _, elb := range elbs {
   206  		s.scope.V(3).Info("Deleting AWS cloud provider load balancer", "arn", elb)
   207  		if err := s.deleteClassicELB(elb); err != nil {
   208  			return err
   209  		}
   210  	}
   211  
   212  	if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (done bool, err error) {
   213  		elbs, err := s.listAWSCloudProviderOwnedELBs()
   214  		if err != nil {
   215  			return false, err
   216  		}
   217  		done = len(elbs) == 0
   218  		return done, nil
   219  	}); err != nil {
   220  		return errors.Wrapf(err, "failed to wait for %q load balancer deletions", s.scope.Name())
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  // DeleteLoadbalancers deletes the load balancers for the given cluster.
   227  func (s *Service) DeleteLoadbalancers() error {
   228  	s.scope.V(2).Info("Deleting load balancers")
   229  
   230  	if err := s.deleteAPIServerELB(); err != nil {
   231  		return errors.Wrap(err, "failed to delete control plane load balancer")
   232  	}
   233  
   234  	if err := s.deleteAWSCloudProviderELBs(); err != nil {
   235  		return errors.Wrap(err, "failed to delete AWS cloud provider load balancer(s)")
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  // IsInstanceRegisteredWithAPIServerELB returns true if the instance is already registered with the APIServer ELB.
   242  func (s *Service) IsInstanceRegisteredWithAPIServerELB(i *infrav1.Instance) (bool, error) {
   243  	name, err := ELBName(s.scope)
   244  	if err != nil {
   245  		return false, errors.Wrap(err, "failed to get control plane load balancer name")
   246  	}
   247  
   248  	input := &elb.DescribeLoadBalancersInput{
   249  		LoadBalancerNames: []*string{aws.String(name)},
   250  	}
   251  
   252  	output, err := s.ELBClient.DescribeLoadBalancers(input)
   253  	if err != nil {
   254  		return false, errors.Wrapf(err, "error describing ELB %q", name)
   255  	}
   256  	if len(output.LoadBalancerDescriptions) != 1 {
   257  		return false, errors.Errorf("expected 1 ELB description for %q, got %d", name, len(output.LoadBalancerDescriptions))
   258  	}
   259  
   260  	for _, registeredInstance := range output.LoadBalancerDescriptions[0].Instances {
   261  		if aws.StringValue(registeredInstance.InstanceId) == i.ID {
   262  			return true, nil
   263  		}
   264  	}
   265  
   266  	return false, nil
   267  }
   268  
   269  // RegisterInstanceWithAPIServerELB registers an instance with a classic ELB.
   270  func (s *Service) RegisterInstanceWithAPIServerELB(i *infrav1.Instance) error {
   271  	name, err := ELBName(s.scope)
   272  	if err != nil {
   273  		return errors.Wrap(err, "failed to get control plane load balancer name")
   274  	}
   275  	out, err := s.describeClassicELB(name)
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	// Validate that the subnets associated with the load balancer has the instance AZ.
   281  	subnet := s.scope.Subnets().FindByID(i.SubnetID)
   282  	if subnet == nil {
   283  		return errors.Errorf("failed to attach load balancer subnets, could not find subnet %q description in AWSCluster", i.SubnetID)
   284  	}
   285  	instanceAZ := subnet.AvailabilityZone
   286  
   287  	var subnets infrav1.Subnets
   288  	if s.scope.ControlPlaneLoadBalancer() != nil && len(s.scope.ControlPlaneLoadBalancer().Subnets) > 0 {
   289  		subnets, err = s.getControlPlaneLoadBalancerSubnets()
   290  		if err != nil {
   291  			return err
   292  		}
   293  	} else {
   294  		subnets = s.scope.Subnets()
   295  	}
   296  
   297  	found := false
   298  	for _, subnetID := range out.SubnetIDs {
   299  		if subnet := subnets.FindByID(subnetID); subnet != nil && instanceAZ == subnet.AvailabilityZone {
   300  			found = true
   301  			break
   302  		}
   303  	}
   304  	if !found {
   305  		return errors.Errorf("failed to register instance with APIServer ELB %q: instance is in availability zone %q, no public subnets attached to the ELB in the same zone", name, instanceAZ)
   306  	}
   307  
   308  	input := &elb.RegisterInstancesWithLoadBalancerInput{
   309  		Instances:        []*elb.Instance{{InstanceId: aws.String(i.ID)}},
   310  		LoadBalancerName: aws.String(name),
   311  	}
   312  
   313  	_, err = s.ELBClient.RegisterInstancesWithLoadBalancer(input)
   314  	return err
   315  }
   316  
   317  // getControlPlaneLoadBalancerSubnets retrieves ControlPlaneLoadBalancer subnets information.
   318  func (s *Service) getControlPlaneLoadBalancerSubnets() (infrav1.Subnets, error) {
   319  	var subnets infrav1.Subnets
   320  
   321  	input := &ec2.DescribeSubnetsInput{
   322  		SubnetIds: aws.StringSlice(s.scope.ControlPlaneLoadBalancer().Subnets),
   323  	}
   324  	res, err := s.EC2Client.DescribeSubnets(input)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	for _, sn := range res.Subnets {
   330  		lbSn := infrav1.SubnetSpec{
   331  			AvailabilityZone: *sn.AvailabilityZone,
   332  			ID:               *sn.SubnetId,
   333  		}
   334  		subnets = append(subnets, lbSn)
   335  	}
   336  
   337  	return subnets, nil
   338  }
   339  
   340  // DeregisterInstanceFromAPIServerELB de-registers an instance from a classic ELB.
   341  func (s *Service) DeregisterInstanceFromAPIServerELB(i *infrav1.Instance) error {
   342  	name, err := ELBName(s.scope)
   343  	if err != nil {
   344  		return errors.Wrap(err, "failed to get control plane load balancer name")
   345  	}
   346  
   347  	input := &elb.DeregisterInstancesFromLoadBalancerInput{
   348  		Instances:        []*elb.Instance{{InstanceId: aws.String(i.ID)}},
   349  		LoadBalancerName: aws.String(name),
   350  	}
   351  
   352  	_, err = s.ELBClient.DeregisterInstancesFromLoadBalancer(input)
   353  	if err != nil {
   354  		if aerr, ok := err.(awserr.Error); ok {
   355  			switch aerr.Code() {
   356  			case elb.ErrCodeAccessPointNotFoundException, elb.ErrCodeInvalidEndPointException:
   357  				// Ignoring LoadBalancerNotFound and InvalidInstance when deregistering
   358  				return nil
   359  			default:
   360  				return err
   361  			}
   362  		}
   363  	}
   364  	return err
   365  }
   366  
   367  // ELBName returns the user-defined API Server ELB name, or a generated default if the user has not defined the ELB
   368  // name.
   369  func ELBName(s scope.ELBScope) (string, error) {
   370  	if userDefinedName := s.ControlPlaneLoadBalancerName(); userDefinedName != nil {
   371  		return *userDefinedName, nil
   372  	}
   373  	name, err := GenerateELBName(s.Name())
   374  	if err != nil {
   375  		return "", fmt.Errorf("failed to generate name: %w", err)
   376  	}
   377  	return name, nil
   378  }
   379  
   380  // GenerateELBName generates a formatted ELB name via either
   381  // concatenating the cluster name to the "-apiserver" suffix
   382  // or computing a hash for clusters with names above 32 characters.
   383  //
   384  // WARNING If this function's output is changed, a controller using the
   385  // new function will fail to generate the load balancer of an existing
   386  // cluster whose load balancer name was generated using the old
   387  // function.
   388  func GenerateELBName(clusterName string) (string, error) {
   389  	standardELBName := generateStandardELBName(clusterName)
   390  	if len(standardELBName) <= 32 {
   391  		return standardELBName, nil
   392  	}
   393  
   394  	elbName, err := generateHashedELBName(clusterName)
   395  	if err != nil {
   396  		return "", err
   397  	}
   398  
   399  	return elbName, nil
   400  }
   401  
   402  // generateStandardELBName generates a formatted ELB name based on cluster
   403  // and ELB name.
   404  func generateStandardELBName(clusterName string) string {
   405  	elbCompatibleClusterName := strings.ReplaceAll(clusterName, ".", "-")
   406  	return fmt.Sprintf("%s-%s", elbCompatibleClusterName, infrav1.APIServerRoleTagValue)
   407  }
   408  
   409  // generateHashedELBName generates a 32-character hashed name based on cluster
   410  // and ELB name.
   411  func generateHashedELBName(clusterName string) (string, error) {
   412  	// hashSize = 32 - length of "k8s" - length of "-" = 28
   413  	shortName, err := hash.Base36TruncatedHash(clusterName, 28)
   414  	if err != nil {
   415  		return "", errors.Wrap(err, "unable to create ELB name")
   416  	}
   417  
   418  	return fmt.Sprintf("%s-%s", shortName, "k8s"), nil
   419  }
   420  
   421  func (s *Service) getAPIServerClassicELBSpec(elbName string) (*infrav1.ClassicELB, error) {
   422  	securityGroupIDs := []string{}
   423  	controlPlaneLoadBalancer := s.scope.ControlPlaneLoadBalancer()
   424  	if controlPlaneLoadBalancer != nil && len(controlPlaneLoadBalancer.AdditionalSecurityGroups) != 0 {
   425  		securityGroupIDs = append(securityGroupIDs, controlPlaneLoadBalancer.AdditionalSecurityGroups...)
   426  	}
   427  	securityGroupIDs = append(securityGroupIDs, s.scope.SecurityGroups()[infrav1.SecurityGroupAPIServerLB].ID)
   428  
   429  	res := &infrav1.ClassicELB{
   430  		Name:   elbName,
   431  		Scheme: s.scope.ControlPlaneLoadBalancerScheme(),
   432  		Listeners: []infrav1.ClassicELBListener{
   433  			{
   434  				Protocol:         infrav1.ClassicELBProtocolTCP,
   435  				Port:             int64(s.scope.APIServerPort()),
   436  				InstanceProtocol: infrav1.ClassicELBProtocolTCP,
   437  				InstancePort:     6443,
   438  			},
   439  		},
   440  		HealthCheck: &infrav1.ClassicELBHealthCheck{
   441  			Target:             fmt.Sprintf("%v:%d", s.getHealthCheckELBProtocol(), 6443),
   442  			Interval:           10 * time.Second,
   443  			Timeout:            5 * time.Second,
   444  			HealthyThreshold:   5,
   445  			UnhealthyThreshold: 3,
   446  		},
   447  		SecurityGroupIDs: securityGroupIDs,
   448  		Attributes: infrav1.ClassicELBAttributes{
   449  			IdleTimeout: 10 * time.Minute,
   450  		},
   451  	}
   452  
   453  	if s.scope.ControlPlaneLoadBalancer() != nil {
   454  		res.Attributes.CrossZoneLoadBalancing = s.scope.ControlPlaneLoadBalancer().CrossZoneLoadBalancing
   455  	}
   456  
   457  	res.Tags = infrav1.Build(infrav1.BuildParams{
   458  		ClusterName: s.scope.Name(),
   459  		Lifecycle:   infrav1.ResourceLifecycleOwned,
   460  		Name:        aws.String(elbName),
   461  		Role:        aws.String(infrav1.APIServerRoleTagValue),
   462  		Additional:  s.scope.AdditionalTags(),
   463  	})
   464  
   465  	// If subnet IDs have been specified for this load balancer
   466  	if s.scope.ControlPlaneLoadBalancer() != nil && len(s.scope.ControlPlaneLoadBalancer().Subnets) > 0 {
   467  		// This set of subnets may not match the subnets specified on the Cluster, so we may not have already discovered them
   468  		// We need to call out to AWS to describe them just in case
   469  		input := &ec2.DescribeSubnetsInput{
   470  			SubnetIds: aws.StringSlice(s.scope.ControlPlaneLoadBalancer().Subnets),
   471  		}
   472  		out, err := s.EC2Client.DescribeSubnets(input)
   473  		if err != nil {
   474  			return nil, err
   475  		}
   476  		for _, sn := range out.Subnets {
   477  			res.AvailabilityZones = append(res.AvailabilityZones, *sn.AvailabilityZone)
   478  			res.SubnetIDs = append(res.SubnetIDs, *sn.SubnetId)
   479  		}
   480  	} else {
   481  		// The load balancer APIs require us to only attach one subnet for each AZ.
   482  		subnets := s.scope.Subnets().FilterPrivate()
   483  
   484  		if s.scope.ControlPlaneLoadBalancerScheme() == infrav1.ClassicELBSchemeInternetFacing {
   485  			subnets = s.scope.Subnets().FilterPublic()
   486  		}
   487  
   488  	subnetLoop:
   489  		for _, sn := range subnets {
   490  			for _, az := range res.AvailabilityZones {
   491  				if sn.AvailabilityZone == az {
   492  					// If we already attached another subnet in the same AZ, there is no need to
   493  					// add this subnet to the list of the ELB's subnets.
   494  					continue subnetLoop
   495  				}
   496  			}
   497  			res.AvailabilityZones = append(res.AvailabilityZones, sn.AvailabilityZone)
   498  			res.SubnetIDs = append(res.SubnetIDs, sn.ID)
   499  		}
   500  	}
   501  
   502  	return res, nil
   503  }
   504  
   505  func (s *Service) createClassicELB(spec *infrav1.ClassicELB) (*infrav1.ClassicELB, error) {
   506  	input := &elb.CreateLoadBalancerInput{
   507  		LoadBalancerName: aws.String(spec.Name),
   508  		Subnets:          aws.StringSlice(spec.SubnetIDs),
   509  		SecurityGroups:   aws.StringSlice(spec.SecurityGroupIDs),
   510  		Scheme:           aws.String(string(spec.Scheme)),
   511  		Tags:             converters.MapToELBTags(spec.Tags),
   512  	}
   513  
   514  	for _, ln := range spec.Listeners {
   515  		input.Listeners = append(input.Listeners, &elb.Listener{
   516  			Protocol:         aws.String(string(ln.Protocol)),
   517  			LoadBalancerPort: aws.Int64(ln.Port),
   518  			InstanceProtocol: aws.String(string(ln.InstanceProtocol)),
   519  			InstancePort:     aws.Int64(ln.InstancePort),
   520  		})
   521  	}
   522  
   523  	out, err := s.ELBClient.CreateLoadBalancer(input)
   524  	if err != nil {
   525  		return nil, errors.Wrapf(err, "failed to create classic load balancer: %v", spec)
   526  	}
   527  
   528  	if spec.HealthCheck != nil {
   529  		if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
   530  			if _, err := s.ELBClient.ConfigureHealthCheck(&elb.ConfigureHealthCheckInput{
   531  				LoadBalancerName: aws.String(spec.Name),
   532  				HealthCheck: &elb.HealthCheck{
   533  					Target:             aws.String(spec.HealthCheck.Target),
   534  					Interval:           aws.Int64(int64(spec.HealthCheck.Interval.Seconds())),
   535  					Timeout:            aws.Int64(int64(spec.HealthCheck.Timeout.Seconds())),
   536  					HealthyThreshold:   aws.Int64(spec.HealthCheck.HealthyThreshold),
   537  					UnhealthyThreshold: aws.Int64(spec.HealthCheck.UnhealthyThreshold),
   538  				},
   539  			}); err != nil {
   540  				return false, err
   541  			}
   542  			return true, nil
   543  		}, awserrors.LoadBalancerNotFound); err != nil {
   544  			return nil, errors.Wrapf(err, "failed to configure health check for classic load balancer: %v", spec)
   545  		}
   546  	}
   547  
   548  	s.scope.Info("Created classic load balancer", "dns-name", *out.DNSName)
   549  
   550  	res := spec.DeepCopy()
   551  	res.DNSName = *out.DNSName
   552  	return res, nil
   553  }
   554  
   555  func (s *Service) configureAttributes(name string, attributes infrav1.ClassicELBAttributes) error {
   556  	attrs := &elb.ModifyLoadBalancerAttributesInput{
   557  		LoadBalancerName: aws.String(name),
   558  		LoadBalancerAttributes: &elb.LoadBalancerAttributes{
   559  			CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{
   560  				Enabled: aws.Bool(attributes.CrossZoneLoadBalancing),
   561  			},
   562  		},
   563  	}
   564  
   565  	if attributes.IdleTimeout > 0 {
   566  		attrs.LoadBalancerAttributes.ConnectionSettings = &elb.ConnectionSettings{
   567  			IdleTimeout: aws.Int64(int64(attributes.IdleTimeout.Seconds())),
   568  		}
   569  	}
   570  
   571  	if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
   572  		if _, err := s.ELBClient.ModifyLoadBalancerAttributes(attrs); err != nil {
   573  			return false, err
   574  		}
   575  		return true, nil
   576  	}, awserrors.LoadBalancerNotFound); err != nil {
   577  		return errors.Wrapf(err, "failed to configure attributes for classic load balancer: %v", name)
   578  	}
   579  
   580  	return nil
   581  }
   582  
   583  func (s *Service) deleteClassicELB(name string) error {
   584  	input := &elb.DeleteLoadBalancerInput{
   585  		LoadBalancerName: aws.String(name),
   586  	}
   587  
   588  	if _, err := s.ELBClient.DeleteLoadBalancer(input); err != nil {
   589  		return err
   590  	}
   591  
   592  	s.scope.Info("Deleted AWS cloud provider load balancers")
   593  	return nil
   594  }
   595  
   596  func (s *Service) listByTag(tag string) ([]string, error) {
   597  	input := rgapi.GetResourcesInput{
   598  		ResourceTypeFilters: aws.StringSlice([]string{elbResourceType}),
   599  		TagFilters: []*rgapi.TagFilter{
   600  			{
   601  				Key:    aws.String(tag),
   602  				Values: aws.StringSlice([]string{string(infrav1.ResourceLifecycleOwned)}),
   603  			},
   604  		},
   605  	}
   606  
   607  	names := []string{}
   608  
   609  	err := s.ResourceTaggingClient.GetResourcesPages(&input, func(r *rgapi.GetResourcesOutput, last bool) bool {
   610  		for _, tagmapping := range r.ResourceTagMappingList {
   611  			if tagmapping.ResourceARN != nil {
   612  				parsedARN, err := arn.Parse(*tagmapping.ResourceARN)
   613  				if err != nil {
   614  					s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag)
   615  					continue
   616  				}
   617  				if strings.Contains(parsedARN.Resource, "loadbalancer/net/") {
   618  					s.scope.Info("ignoring nlb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag)
   619  					continue
   620  				}
   621  				if strings.Contains(parsedARN.Resource, "loadbalancer/app/") {
   622  					s.scope.Info("ignoring alb created by service, consider enabling garbage collection", "arn", *tagmapping.ResourceARN, "tag", tag)
   623  					continue
   624  				}
   625  				name := strings.ReplaceAll(parsedARN.Resource, "loadbalancer/", "")
   626  				if name == "" {
   627  					s.scope.Info("failed to parse ARN", "arn", *tagmapping.ResourceARN, "tag", tag)
   628  					continue
   629  				}
   630  				names = append(names, name)
   631  			}
   632  		}
   633  		return true
   634  	})
   635  	if err != nil {
   636  		record.Eventf(s.scope.InfraCluster(), "FailedListELBsByTag", "Failed to list %s ELB by Tags: %v", s.scope.Name(), err)
   637  		return nil, errors.Wrapf(err, "failed to list %s ELBs by tag group", s.scope.Name())
   638  	}
   639  
   640  	return names, nil
   641  }
   642  
   643  func (s *Service) filterByOwnedTag(tagKey string) ([]string, error) {
   644  	var names []string
   645  	err := s.ELBClient.DescribeLoadBalancersPages(&elb.DescribeLoadBalancersInput{}, func(r *elb.DescribeLoadBalancersOutput, last bool) bool {
   646  		for _, lb := range r.LoadBalancerDescriptions {
   647  			names = append(names, *lb.LoadBalancerName)
   648  		}
   649  		return true
   650  	})
   651  	if err != nil {
   652  		return nil, err
   653  	}
   654  
   655  	if len(names) == 0 {
   656  		return nil, nil
   657  	}
   658  
   659  	var ownedElbs []string
   660  	lbChunks := chunkELBs(names)
   661  	for _, chunk := range lbChunks {
   662  		output, err := s.ELBClient.DescribeTags(&elb.DescribeTagsInput{LoadBalancerNames: aws.StringSlice(chunk)})
   663  		if err != nil {
   664  			return nil, err
   665  		}
   666  		for _, tagDesc := range output.TagDescriptions {
   667  			for _, tag := range tagDesc.Tags {
   668  				if *tag.Key == tagKey && *tag.Value == string(infrav1.ResourceLifecycleOwned) {
   669  					ownedElbs = append(ownedElbs, *tagDesc.LoadBalancerName)
   670  				}
   671  			}
   672  		}
   673  	}
   674  
   675  	return ownedElbs, nil
   676  }
   677  
   678  func (s *Service) listAWSCloudProviderOwnedELBs() ([]string, error) {
   679  	// k8s.io/cluster/<name>, created by k/k cloud provider
   680  	serviceTag := infrav1.ClusterAWSCloudProviderTagKey(s.scope.Name())
   681  	arns, err := s.listByTag(serviceTag)
   682  	if err != nil {
   683  		// retry by listing all ELBs as listByTag will fail in air-gapped environments
   684  		arns, err = s.filterByOwnedTag(serviceTag)
   685  		if err != nil {
   686  			return nil, err
   687  		}
   688  	}
   689  
   690  	return arns, nil
   691  }
   692  
   693  func (s *Service) describeClassicELB(name string) (*infrav1.ClassicELB, error) {
   694  	input := &elb.DescribeLoadBalancersInput{
   695  		LoadBalancerNames: aws.StringSlice([]string{name}),
   696  	}
   697  
   698  	out, err := s.ELBClient.DescribeLoadBalancers(input)
   699  	if err != nil {
   700  		if aerr, ok := err.(awserr.Error); ok {
   701  			switch aerr.Code() {
   702  			case elb.ErrCodeAccessPointNotFoundException:
   703  				return nil, NewNotFound(fmt.Sprintf("no classic load balancer found with name: %q", name))
   704  			case elb.ErrCodeDependencyThrottleException:
   705  				return nil, errors.Wrap(err, "too many requests made to the ELB service")
   706  			default:
   707  				return nil, errors.Wrap(err, "unexpected aws error")
   708  			}
   709  		} else {
   710  			return nil, errors.Wrapf(err, "failed to describe classic load balancer: %s", name)
   711  		}
   712  	}
   713  
   714  	if out != nil && len(out.LoadBalancerDescriptions) == 0 {
   715  		return nil, NewNotFound(fmt.Sprintf("no classic load balancer found with name %q", name))
   716  	}
   717  
   718  	if s.scope.VPC().ID != "" && s.scope.VPC().ID != *out.LoadBalancerDescriptions[0].VPCId {
   719  		return nil, errors.Errorf(
   720  			"ELB names must be unique within a region: %q ELB already exists in this region in VPC %q",
   721  			name, *out.LoadBalancerDescriptions[0].VPCId)
   722  	}
   723  
   724  	if s.scope.ControlPlaneLoadBalancer() != nil &&
   725  		s.scope.ControlPlaneLoadBalancer().Scheme != nil &&
   726  		string(*s.scope.ControlPlaneLoadBalancer().Scheme) != aws.StringValue(out.LoadBalancerDescriptions[0].Scheme) {
   727  		return nil, errors.Errorf(
   728  			"ELB names must be unique within a region: %q ELB already exists in this region with a different scheme %q",
   729  			name, *out.LoadBalancerDescriptions[0].Scheme)
   730  	}
   731  
   732  	outAtt, err := s.ELBClient.DescribeLoadBalancerAttributes(&elb.DescribeLoadBalancerAttributesInput{
   733  		LoadBalancerName: aws.String(name),
   734  	})
   735  	if err != nil {
   736  		return nil, errors.Wrapf(err, "failed to describe classic load balancer %q attributes", name)
   737  	}
   738  
   739  	tags, err := s.describeClassicELBTags(name)
   740  	if err != nil {
   741  		return nil, errors.Wrapf(err, "failed to describe classic load balancer tags")
   742  	}
   743  
   744  	return fromSDKTypeToClassicELB(out.LoadBalancerDescriptions[0], outAtt.LoadBalancerAttributes, tags), nil
   745  }
   746  
   747  func (s *Service) describeClassicELBTags(name string) ([]*elb.Tag, error) {
   748  	output, err := s.ELBClient.DescribeTags(&elb.DescribeTagsInput{
   749  		LoadBalancerNames: []*string{aws.String(name)},
   750  	})
   751  	if err != nil {
   752  		return nil, err
   753  	}
   754  
   755  	if len(output.TagDescriptions) == 0 {
   756  		return nil, errors.Errorf("no tag information returned for load balancer %q", name)
   757  	}
   758  
   759  	return output.TagDescriptions[0].Tags, nil
   760  }
   761  
   762  func (s *Service) reconcileELBTags(lb *infrav1.ClassicELB, desiredTags map[string]string) error {
   763  	addTagsInput := &elb.AddTagsInput{
   764  		LoadBalancerNames: []*string{aws.String(lb.Name)},
   765  	}
   766  
   767  	removeTagsInput := &elb.RemoveTagsInput{
   768  		LoadBalancerNames: []*string{aws.String(lb.Name)},
   769  	}
   770  
   771  	currentTags := infrav1.Tags(lb.Tags)
   772  
   773  	for k, v := range desiredTags {
   774  		if val, ok := currentTags[k]; !ok || val != v {
   775  			s.scope.V(4).Info("adding tag to load balancer", "elb-name", lb.Name, "key", k, "value", v)
   776  			addTagsInput.Tags = append(addTagsInput.Tags, &elb.Tag{Key: aws.String(k), Value: aws.String(v)})
   777  		}
   778  	}
   779  
   780  	for k := range currentTags {
   781  		if _, ok := desiredTags[k]; !ok {
   782  			s.scope.V(4).Info("removing tag from load balancer", "elb-name", lb.Name, "key", k)
   783  			removeTagsInput.Tags = append(removeTagsInput.Tags, &elb.TagKeyOnly{Key: aws.String(k)})
   784  		}
   785  	}
   786  
   787  	if len(addTagsInput.Tags) > 0 {
   788  		if _, err := s.ELBClient.AddTags(addTagsInput); err != nil {
   789  			return err
   790  		}
   791  	}
   792  
   793  	if len(removeTagsInput.Tags) > 0 {
   794  		if _, err := s.ELBClient.RemoveTags(removeTagsInput); err != nil {
   795  			return err
   796  		}
   797  	}
   798  
   799  	return nil
   800  }
   801  
   802  func (s *Service) getHealthCheckELBProtocol() *infrav1.ClassicELBProtocol {
   803  	controlPlaneELB := s.scope.ControlPlaneLoadBalancer()
   804  	if controlPlaneELB != nil && controlPlaneELB.HealthCheckProtocol != nil {
   805  		return controlPlaneELB.HealthCheckProtocol
   806  	}
   807  	return &infrav1.ClassicELBProtocolSSL
   808  }
   809  
   810  func fromSDKTypeToClassicELB(v *elb.LoadBalancerDescription, attrs *elb.LoadBalancerAttributes, tags []*elb.Tag) *infrav1.ClassicELB {
   811  	res := &infrav1.ClassicELB{
   812  		Name:             aws.StringValue(v.LoadBalancerName),
   813  		Scheme:           infrav1.ClassicELBScheme(*v.Scheme),
   814  		SubnetIDs:        aws.StringValueSlice(v.Subnets),
   815  		SecurityGroupIDs: aws.StringValueSlice(v.SecurityGroups),
   816  		DNSName:          aws.StringValue(v.DNSName),
   817  		Tags:             converters.ELBTagsToMap(tags),
   818  	}
   819  
   820  	if attrs.ConnectionSettings != nil && attrs.ConnectionSettings.IdleTimeout != nil {
   821  		res.Attributes.IdleTimeout = time.Duration(*attrs.ConnectionSettings.IdleTimeout) * time.Second
   822  	}
   823  
   824  	res.Attributes.CrossZoneLoadBalancing = aws.BoolValue(attrs.CrossZoneLoadBalancing.Enabled)
   825  
   826  	return res
   827  }
   828  
   829  func chunkELBs(names []string) [][]string {
   830  	var chunked [][]string
   831  	for i := 0; i < len(names); i += maxELBsDescribeTagsRequest {
   832  		end := i + maxELBsDescribeTagsRequest
   833  		if end > len(names) {
   834  			end = len(names)
   835  		}
   836  		chunked = append(chunked, names[i:end])
   837  	}
   838  	return chunked
   839  }