Skip to content

Commit cb51ece

Browse files
committed
add local zone support
1 parent 012e132 commit cb51ece

File tree

7 files changed

+1413
-222
lines changed

7 files changed

+1413
-222
lines changed

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ func main() {
102102
nodeENIResolver := networking.NewDefaultNodeENIInfoResolver(cloud.EC2(), ctrl.Log)
103103
sgManager := networking.NewDefaultSecurityGroupManager(cloud.EC2(), ctrl.Log)
104104
sgReconciler := networking.NewDefaultSecurityGroupReconciler(sgManager, ctrl.Log)
105-
subnetResolver := networking.NewDefaultSubnetsResolver(cloud.EC2(), cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log.WithName("subnets-resolver"))
105+
azInfoProvider := networking.NewDefaultAZInfoProvider(cloud.EC2(), ctrl.Log.WithName("az-info-provider"))
106+
subnetResolver := networking.NewDefaultSubnetsResolver(azInfoProvider, cloud.EC2(), cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log.WithName("subnets-resolver"))
106107
tgbResManager := targetgroupbinding.NewDefaultResourceManager(mgr.GetClient(), cloud.ELBV2(),
107108
podInfoRepo, podENIResolver, nodeENIResolver, sgManager, sgReconciler, cloud.VpcID(), controllerCFG.ClusterName, mgr.GetEventRecorderFor("targetGroupBinding"), ctrl.Log)
108109
ingGroupReconciler := ingress.NewGroupReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("ingress"),

pkg/networking/az_info_provider.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package networking
2+
3+
import (
4+
"context"
5+
awssdk "github.com/aws/aws-sdk-go/aws"
6+
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
7+
"github.com/go-logr/logr"
8+
"github.com/pkg/errors"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
10+
"sync"
11+
)
12+
13+
// AZInfoProvider is responsible for provide AZ info.
14+
type AZInfoProvider interface {
15+
FetchAZInfos(ctx context.Context, availabilityZoneIDs []string) (map[string]ec2sdk.AvailabilityZone, error)
16+
}
17+
18+
// NewDefaultAZInfoProvider constructs new defaultAZInfoProvider.
19+
func NewDefaultAZInfoProvider(ec2Client services.EC2, logger logr.Logger) *defaultAZInfoProvider {
20+
return &defaultAZInfoProvider{
21+
ec2Client: ec2Client,
22+
azInfoCache: make(map[string]ec2sdk.AvailabilityZone),
23+
azInfoCacheMutex: sync.RWMutex{},
24+
logger: logger,
25+
}
26+
}
27+
28+
var _ AZInfoProvider = &defaultAZInfoProvider{}
29+
30+
// default implementation for AZInfoProvider.
31+
// AZ info for each zone is cached indefinitely.
32+
type defaultAZInfoProvider struct {
33+
ec2Client services.EC2
34+
azInfoCache map[string]ec2sdk.AvailabilityZone
35+
azInfoCacheMutex sync.RWMutex
36+
37+
logger logr.Logger
38+
}
39+
40+
func (p *defaultAZInfoProvider) FetchAZInfos(ctx context.Context, availabilityZoneIDs []string) (map[string]ec2sdk.AvailabilityZone, error) {
41+
azInfoByAZID := p.fetchAZInfosFromCache(availabilityZoneIDs)
42+
azIDsWithoutAZInfo := computeAZIDsWithoutAZInfo(availabilityZoneIDs, azInfoByAZID)
43+
if len(azIDsWithoutAZInfo) == 0 {
44+
return azInfoByAZID, nil
45+
}
46+
47+
azInfoByAZIDViaAWS, err := p.fetchAZInfosFromAWS(ctx, azIDsWithoutAZInfo)
48+
if err != nil {
49+
return nil, err
50+
}
51+
p.saveAZInfosToCache(azInfoByAZIDViaAWS)
52+
for azID, azInfo := range azInfoByAZIDViaAWS {
53+
azInfoByAZID[azID] = azInfo
54+
}
55+
56+
azIDsWithoutAZInfo = computeAZIDsWithoutAZInfo(availabilityZoneIDs, azInfoByAZID)
57+
if len(azIDsWithoutAZInfo) > 0 {
58+
// NOTE: this branch should never be triggered as fetchAZInfosFromAWS will error out first if some AZ is not found.
59+
// however, we still add this check to not depend on this specific AWS API behavior.
60+
return nil, errors.Errorf("cannot resolve AZ info for AZs: %v", azIDsWithoutAZInfo)
61+
}
62+
return azInfoByAZID, nil
63+
}
64+
65+
func (p *defaultAZInfoProvider) fetchAZInfosFromCache(availabilityZoneIDs []string) map[string]ec2sdk.AvailabilityZone {
66+
p.azInfoCacheMutex.RLock()
67+
defer p.azInfoCacheMutex.RUnlock()
68+
69+
azInfoByAZID := make(map[string]ec2sdk.AvailabilityZone)
70+
for _, azID := range availabilityZoneIDs {
71+
if azInfo, exists := p.azInfoCache[azID]; exists {
72+
azInfoByAZID[azID] = azInfo
73+
}
74+
}
75+
return azInfoByAZID
76+
}
77+
78+
func (p *defaultAZInfoProvider) saveAZInfosToCache(azInfoByAZID map[string]ec2sdk.AvailabilityZone) {
79+
p.azInfoCacheMutex.Lock()
80+
defer p.azInfoCacheMutex.Unlock()
81+
82+
for azID, azInfo := range azInfoByAZID {
83+
p.azInfoCache[azID] = azInfo
84+
}
85+
}
86+
87+
// fetchAZInfosFromAWS will fetch AZ info from AWS API.
88+
// the availabilityZoneIDs shouldn't be empty.
89+
func (p *defaultAZInfoProvider) fetchAZInfosFromAWS(ctx context.Context, availabilityZoneIDs []string) (map[string]ec2sdk.AvailabilityZone, error) {
90+
req := &ec2sdk.DescribeAvailabilityZonesInput{
91+
ZoneIds: awssdk.StringSlice(availabilityZoneIDs),
92+
}
93+
resp, err := p.ec2Client.DescribeAvailabilityZonesWithContext(ctx, req)
94+
if err != nil {
95+
return nil, err
96+
}
97+
azInfoByAZID := make(map[string]ec2sdk.AvailabilityZone)
98+
for _, azInfo := range resp.AvailabilityZones {
99+
azInfoByAZID[awssdk.StringValue(azInfo.ZoneId)] = *azInfo
100+
}
101+
return azInfoByAZID, nil
102+
}
103+
104+
// computeAZIDsWithoutAZInfo computes az IDs that don't have az Info.
105+
func computeAZIDsWithoutAZInfo(availabilityZoneIDs []string, azInfoByAZID map[string]ec2sdk.AvailabilityZone) []string {
106+
azIDsWithoutAZInfo := make([]string, 0, len(availabilityZoneIDs)-len(azInfoByAZID))
107+
for _, azID := range availabilityZoneIDs {
108+
if _, ok := azInfoByAZID[azID]; !ok {
109+
azIDsWithoutAZInfo = append(azIDsWithoutAZInfo, azID)
110+
}
111+
}
112+
return azIDsWithoutAZInfo
113+
}

pkg/networking/az_info_provider_mocks.go

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)