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 }