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 }