-
Notifications
You must be signed in to change notification settings - Fork 233
/
vpcinventory.go
133 lines (116 loc) · 4.33 KB
/
vpcinventory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package awsprivatelink
import (
"strings"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/openshift/hive/pkg/awsclient"
"k8s.io/apimachinery/pkg/util/sets"
hivev1 "github.com/openshift/hive/apis/hive/v1"
)
var (
errNoSupportedAZsInInventory = errors.New("no supported VPC in inventory which support the AZs of the service")
errNoVPCWithQuotaInInventory = errors.New("no supported VPC in inventory with available quota")
)
func (r *ReconcileAWSPrivateLink) chooseVPCForVPCEndpoint(awsClient awsclient.Client,
cd *hivev1.ClusterDeployment, vpcEndpointServiceName string,
logger log.FieldLogger) (*hivev1.AWSPrivateLinkInventory, error) {
serviceLog := logger.WithField("serviceName", vpcEndpointServiceName)
// Filter out the VPCs in cluster region.
candidates := filterVPCInventory(r.controllerconfig.DeepCopy().EndpointVPCInventory, toSupportedRegion(cd.Spec.Platform.AWS.Region))
if len(candidates) == 0 {
serviceLog.WithField("region", cd.Spec.Platform.AWS.Region).Error("no supported VPC in inventory")
return nil, errors.New("no supported VPC in inventory for the cluster")
}
// Figure out the AZs supported by the service.
servicesResp, err := awsClient.DescribeVpcEndpointServices(&ec2.DescribeVpcEndpointServicesInput{
ServiceNames: aws.StringSlice([]string{vpcEndpointServiceName}),
})
if err != nil {
serviceLog.WithError(err).Error("error getting VPC Endpoint Service in hub account")
return nil, err
}
// Filter candidates that don't have at least one subnet in supported AZs.
supportedAZSet := sets.NewString(aws.StringValueSlice(servicesResp.ServiceDetails[0].AvailabilityZones)...)
candidates = filterVPCInventory(candidates, toSupportedSubnets(supportedAZSet))
if len(candidates) == 0 {
logger.WithField("region", cd.Spec.Platform.AWS.Region).
WithField("requiredAZs", supportedAZSet.List()).
Error(errNoSupportedAZsInInventory.Error())
return nil, errNoSupportedAZsInInventory
}
// Figure out which VPCs have quota available for endpoints.
vpcs := make([]string, 0, len(candidates))
endpointsPerVPC := map[string]int{}
for _, cand := range candidates {
vpcs = append(vpcs, cand.VPCID)
endpointsPerVPC[cand.VPCID] = 0
}
endpointsResp, err := awsClient.DescribeVpcEndpoints(&ec2.DescribeVpcEndpointsInput{
Filters: []*ec2.Filter{{Name: aws.String("vpc-id"), Values: aws.StringSlice(vpcs)}},
})
if err != nil {
logger.WithField("vpcs", vpcs).WithError(err).Error("error getting VPC Endpoints in the selected VPCs")
return nil, err
}
for _, vEnd := range endpointsResp.VpcEndpoints {
vpcID := aws.StringValue(vEnd.VpcId)
endpointsPerVPC[vpcID] = endpointsPerVPC[vpcID] + 1
}
candidates = filterVPCInventory(candidates, toAvailableQuota(endpointsPerVPC))
if len(candidates) == 0 {
logger.WithField("vpcs", vpcs).Error(errNoVPCWithQuotaInInventory.Error())
return nil, errNoVPCWithQuotaInInventory
}
return &candidates[0], nil
}
type filterVPCInventoryFn func(*hivev1.AWSPrivateLinkInventory) bool
func filterVPCInventory(input []hivev1.AWSPrivateLinkInventory, fn filterVPCInventoryFn) []hivev1.AWSPrivateLinkInventory {
n := 0
for _, cand := range input {
if fn(&cand) {
input[n] = cand
n++
}
}
input = input[:n]
return input
}
func toSupportedRegion(region string) filterVPCInventoryFn {
return func(inv *hivev1.AWSPrivateLinkInventory) bool {
return strings.EqualFold(region, inv.Region)
}
}
func toSupportedSubnets(azs sets.String) filterVPCInventoryFn {
return func(inv *hivev1.AWSPrivateLinkInventory) bool {
n := 0
for _, subnet := range inv.Subnets {
if azs.Has(subnet.AvailabilityZone) {
inv.Subnets[n] = subnet
n++
}
}
inv.Subnets = inv.Subnets[:n]
if len(inv.Subnets) > 0 {
return true
}
return false
}
}
const (
// VPCEndpointPerVPCLimit is a limit on the maximum number of VPC endpoints that can
// be created in a VPC. This limit is used to filter out any VPCs in the inventory
// that are already at the this upper limit.
// The actual limit is 255, but we limit to lower limit.
VPCEndpointPerVPCLimit = 250
)
func toAvailableQuota(existingPerVPC map[string]int) filterVPCInventoryFn {
return func(inv *hivev1.AWSPrivateLinkInventory) bool {
avail := VPCEndpointPerVPCLimit - existingPerVPC[inv.VPCID]
if avail > 0 {
return true
}
return false
}
}