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

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package network
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/aws/aws-sdk-go/aws"
    23  	"github.com/aws/aws-sdk-go/service/ec2"
    24  	"github.com/pkg/errors"
    25  
    26  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    27  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/awserrors"
    28  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/filter"
    29  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait"
    30  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/tags"
    31  	"sigs.k8s.io/cluster-api-provider-aws/pkg/record"
    32  )
    33  
    34  func (s *Service) getOrAllocateAddresses(num int, role string) (eips []string, err error) {
    35  	out, err := s.describeAddresses(role)
    36  	if err != nil {
    37  		record.Eventf(s.scope.InfraCluster(), "FailedDescribeAddresses", "Failed to query addresses for role %q: %v", role, err)
    38  		return nil, errors.Wrap(err, "failed to query addresses")
    39  	}
    40  
    41  	for _, address := range out.Addresses {
    42  		if address.AssociationId == nil {
    43  			eips = append(eips, aws.StringValue(address.AllocationId))
    44  		}
    45  	}
    46  
    47  	for len(eips) < num {
    48  		ip, err := s.allocateAddress(role)
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  		eips = append(eips, ip)
    53  	}
    54  
    55  	return eips, nil
    56  }
    57  
    58  func (s *Service) allocateAddress(role string) (string, error) {
    59  	tagSpecifications := tags.BuildParamsToTagSpecification(ec2.ResourceTypeElasticIp, s.getEIPTagParams(role))
    60  	out, err := s.EC2Client.AllocateAddress(&ec2.AllocateAddressInput{
    61  		Domain: aws.String("vpc"),
    62  		TagSpecifications: []*ec2.TagSpecification{
    63  			tagSpecifications,
    64  		},
    65  	})
    66  	if err != nil {
    67  		record.Warnf(s.scope.InfraCluster(), "FailedAllocateEIP", "Failed to allocate Elastic IP for %q: %v", role, err)
    68  		return "", errors.Wrap(err, "failed to allocate Elastic IP")
    69  	}
    70  
    71  	return aws.StringValue(out.AllocationId), nil
    72  }
    73  
    74  func (s *Service) describeAddresses(role string) (*ec2.DescribeAddressesOutput, error) {
    75  	x := []*ec2.Filter{filter.EC2.Cluster(s.scope.Name())}
    76  	if role != "" {
    77  		x = append(x, filter.EC2.ProviderRole(role))
    78  	}
    79  
    80  	return s.EC2Client.DescribeAddresses(&ec2.DescribeAddressesInput{
    81  		Filters: x,
    82  	})
    83  }
    84  
    85  func (s *Service) disassociateAddress(ip *ec2.Address) error {
    86  	err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
    87  		_, err := s.EC2Client.DisassociateAddress(&ec2.DisassociateAddressInput{
    88  			AssociationId: ip.AssociationId,
    89  		})
    90  		if err != nil {
    91  			cause, _ := awserrors.Code(errors.Cause(err))
    92  			if cause != awserrors.AssociationIDNotFound {
    93  				return false, err
    94  			}
    95  		}
    96  		return true, nil
    97  	}, awserrors.AuthFailure)
    98  	if err != nil {
    99  		record.Warnf(s.scope.InfraCluster(), "FailedDisassociateEIP", "Failed to disassociate Elastic IP %q: %v", *ip.AllocationId, err)
   100  		return errors.Wrapf(err, "failed to disassociate Elastic IP %q", *ip.AllocationId)
   101  	}
   102  	return nil
   103  }
   104  
   105  func (s *Service) releaseAddresses() error {
   106  	out, err := s.EC2Client.DescribeAddresses(&ec2.DescribeAddressesInput{
   107  		Filters: []*ec2.Filter{filter.EC2.Cluster(s.scope.Name())},
   108  	})
   109  	if err != nil {
   110  		return errors.Wrapf(err, "failed to describe elastic IPs %q", err)
   111  	}
   112  	if out == nil {
   113  		return nil
   114  	}
   115  	for i := range out.Addresses {
   116  		ip := out.Addresses[i]
   117  		if ip.AssociationId != nil {
   118  			if _, err := s.EC2Client.DisassociateAddress(&ec2.DisassociateAddressInput{
   119  				AssociationId: ip.AssociationId,
   120  			}); err != nil {
   121  				record.Warnf(s.scope.InfraCluster(), "FailedDisassociateEIP", "Failed to disassociate Elastic IP %q: %v", *ip.AllocationId, err)
   122  				return errors.Errorf("failed to disassociate Elastic IP %q with allocation ID %q: Still associated with association ID %q", *ip.PublicIp, *ip.AllocationId, *ip.AssociationId)
   123  			}
   124  		}
   125  
   126  		if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
   127  			_, err := s.EC2Client.ReleaseAddress(&ec2.ReleaseAddressInput{AllocationId: ip.AllocationId})
   128  			if err != nil {
   129  				if ip.AssociationId != nil {
   130  					if s.disassociateAddress(ip) != nil {
   131  						return false, err
   132  					}
   133  				}
   134  				return false, err
   135  			}
   136  			return true, nil
   137  		}, awserrors.AuthFailure, awserrors.InUseIPAddress); err != nil {
   138  			record.Warnf(s.scope.InfraCluster(), "FailedReleaseEIP", "Failed to disassociate Elastic IP %q: %v", *ip.AllocationId, err)
   139  			return errors.Wrapf(err, "failed to release ElasticIP %q", *ip.AllocationId)
   140  		}
   141  
   142  		s.scope.Info("released ElasticIP", "eip", *ip.PublicIp, "allocation-id", *ip.AllocationId)
   143  	}
   144  	return nil
   145  }
   146  
   147  func (s *Service) getEIPTagParams(role string) infrav1.BuildParams {
   148  	name := fmt.Sprintf("%s-eip-%s", s.scope.Name(), role)
   149  
   150  	return infrav1.BuildParams{
   151  		ClusterName: s.scope.Name(),
   152  		Lifecycle:   infrav1.ResourceLifecycleOwned,
   153  		Name:        aws.String(name),
   154  		Role:        aws.String(role),
   155  		Additional:  s.scope.AdditionalTags(),
   156  	}
   157  }