Skip to content

Commit 8f307f0

Browse files
authored
feat: simplify acls calls
reduce number of calls to LB API move ACL related functions to a file
1 parent 76a9003 commit 8f307f0

2 files changed

Lines changed: 147 additions & 139 deletions

File tree

scaleway/acl.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"slices"
8+
"strings"
9+
10+
scwlb "github.com/scaleway/scaleway-sdk-go/api/lb/v1"
11+
"github.com/scaleway/scaleway-sdk-go/scw"
12+
v1 "k8s.io/api/core/v1"
13+
"k8s.io/klog/v2"
14+
)
15+
16+
const MaxEntriesPerACL = 60
17+
18+
func (l *loadbalancers) reconcileACLs(ctx context.Context, loadbalancer *scwlb.LB, frontend *scwlb.Frontend, service *v1.Service, nodes []*v1.Node) error {
19+
// List ACLs for the frontend
20+
aclName := makeACLPrefix(frontend)
21+
aclsResp, err := l.api.ListACLs(&scwlb.ZonedAPIListACLsRequest{
22+
Zone: loadbalancer.Zone,
23+
FrontendID: frontend.ID,
24+
Name: &aclName,
25+
}, scw.WithAllPages(), scw.WithContext(ctx))
26+
if err != nil {
27+
return fmt.Errorf("failed to list ACLs for frontend: %s port: %d loadbalancer: %s err: %v", frontend.ID, frontend.InboundPort, loadbalancer.ID, err)
28+
}
29+
30+
svcAcls := makeACLSpecs(service, nodes, frontend)
31+
if !aclsEquals(aclsResp.ACLs, svcAcls) {
32+
klog.Infof("set ACLs for frontend: %s port: %d loadbalancer: %s", frontend.ID, frontend.InboundPort, loadbalancer.ID)
33+
if _, err := l.api.SetACLs(&scwlb.ZonedAPISetACLsRequest{
34+
Zone: loadbalancer.Zone,
35+
FrontendID: frontend.ID,
36+
ACLs: svcAcls,
37+
}, scw.WithContext(ctx)); err != nil {
38+
return fmt.Errorf("failed setting ACLs for frontend: %s port: %d loadbalancer: %s err: %v", frontend.ID, frontend.InboundPort, loadbalancer.ID, err)
39+
}
40+
}
41+
42+
return nil
43+
}
44+
45+
// makeACLPrefix returns the ACL prefix for rules
46+
func makeACLPrefix(frontend *scwlb.Frontend) string {
47+
if frontend == nil {
48+
return "lb-source-range"
49+
}
50+
return fmt.Sprintf("%s-lb-source-range", frontend.ID)
51+
}
52+
53+
// makeACLSpecs converts a service frontend definition to acl specifications
54+
func makeACLSpecs(service *v1.Service, nodes []*v1.Node, frontend *scwlb.Frontend) []*scwlb.ACLSpec {
55+
if len(service.Spec.LoadBalancerSourceRanges) == 0 {
56+
return []*scwlb.ACLSpec{}
57+
}
58+
59+
sourceRanges := make([]string, 0, len(service.Spec.LoadBalancerSourceRanges))
60+
for _, sourceRange := range service.Spec.LoadBalancerSourceRanges {
61+
if _, _, err := net.ParseCIDR(sourceRange); err != nil {
62+
klog.Warningf("ignoring invalid CIDR %s in LoadBalancerSourceRanges for service %s/%s: %v", sourceRange, service.Namespace, service.Name, err)
63+
continue
64+
}
65+
66+
if strings.Contains(sourceRange, ":") {
67+
sourceRange = strings.TrimSuffix(sourceRange, "/128")
68+
} else {
69+
sourceRange = strings.TrimSuffix(sourceRange, "/32")
70+
}
71+
72+
sourceRanges = append(sourceRanges, sourceRange)
73+
}
74+
75+
aclPrefix := makeACLPrefix(frontend)
76+
whitelist := extractNodesInternalIps(nodes)
77+
whitelist = append(whitelist, extractNodesExternalIps(nodes)...)
78+
whitelist = append(whitelist, sourceRanges...)
79+
80+
slices.Sort(whitelist)
81+
82+
subnetsChunks := chunkArray(whitelist, MaxEntriesPerACL)
83+
acls := make([]*scwlb.ACLSpec, len(subnetsChunks)+1)
84+
85+
for idx, subnets := range subnetsChunks {
86+
acls[idx] = &scwlb.ACLSpec{
87+
Name: fmt.Sprintf("%s-%d", aclPrefix, idx),
88+
Action: &scwlb.ACLAction{
89+
Type: scwlb.ACLActionTypeAllow,
90+
},
91+
Index: int32(idx),
92+
Match: &scwlb.ACLMatch{
93+
IPSubnet: scw.StringSlicePtr(subnets),
94+
},
95+
}
96+
}
97+
98+
acls[len(acls)-1] = &scwlb.ACLSpec{
99+
Name: fmt.Sprintf("%s-end", aclPrefix),
100+
Action: &scwlb.ACLAction{
101+
Type: scwlb.ACLActionTypeDeny,
102+
},
103+
Index: int32(len(acls) - 1),
104+
Match: &scwlb.ACLMatch{
105+
IPSubnet: scw.StringSlicePtr([]string{"0.0.0.0/0", "::/0"}),
106+
},
107+
}
108+
109+
return acls
110+
}
111+
112+
// aclsEquals returns true if both acl lists are equal
113+
func aclsEquals(got []*scwlb.ACL, want []*scwlb.ACLSpec) bool {
114+
if len(got) != len(want) {
115+
return false
116+
}
117+
118+
slices.SortStableFunc(got, func(a, b *scwlb.ACL) int { return int(a.Index - b.Index) })
119+
slices.SortStableFunc(want, func(a, b *scwlb.ACLSpec) int { return int(a.Index - b.Index) })
120+
for idx := range want {
121+
if want[idx].Name != got[idx].Name {
122+
return false
123+
}
124+
if want[idx].Index != got[idx].Index {
125+
return false
126+
}
127+
if (want[idx].Action == nil) != (got[idx].Action == nil) {
128+
return false
129+
}
130+
if want[idx].Action != nil && want[idx].Action.Type != got[idx].Action.Type {
131+
return false
132+
}
133+
if (want[idx].Match == nil) != (got[idx].Match == nil) {
134+
return false
135+
}
136+
if want[idx].Match != nil && !stringPtrArrayEqual(want[idx].Match.IPSubnet, got[idx].Match.IPSubnet) {
137+
return false
138+
}
139+
if want[idx].Match != nil && want[idx].Match.Invert != got[idx].Match.Invert {
140+
return false
141+
}
142+
}
143+
144+
return true
145+
}

scaleway/loadbalancers.go

Lines changed: 2 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"errors"
2222
"fmt"
2323
"maps"
24-
"net"
2524
"os"
2625
"reflect"
2726
"slices"
@@ -40,8 +39,6 @@ import (
4039
"github.com/scaleway/scaleway-sdk-go/scw"
4140
)
4241

43-
const MaxEntriesPerACL = 60
44-
4542
type loadbalancers struct {
4643
api LoadBalancerAPI
4744
ipam IPAMAPI
@@ -741,40 +738,8 @@ func (l *loadbalancers) updateLoadBalancer(ctx context.Context, loadbalancer *sc
741738
frontend = f
742739
}
743740

744-
// List ACLs for the frontend
745-
aclName := makeACLPrefix(frontend)
746-
aclsResp, err := l.api.ListACLs(&scwlb.ZonedAPIListACLsRequest{
747-
Zone: loadbalancer.Zone,
748-
FrontendID: frontend.ID,
749-
Name: &aclName,
750-
}, scw.WithAllPages(), scw.WithContext(ctx))
751-
if err != nil {
752-
return fmt.Errorf("failed to list ACLs for frontend: %s port: %d loadbalancer: %s err: %v", frontend.ID, frontend.InboundPort, loadbalancer.ID, err)
753-
}
754-
755-
svcAcls := makeACLSpecs(service, nodes, frontend)
756-
if !aclsEquals(aclsResp.ACLs, svcAcls) {
757-
// Replace ACLs
758-
klog.Infof("remove all ACLs from frontend: %s port: %d loadbalancer: %s", frontend.ID, frontend.InboundPort, loadbalancer.ID)
759-
for _, acl := range aclsResp.ACLs {
760-
if err := l.api.DeleteACL(&scwlb.ZonedAPIDeleteACLRequest{
761-
Zone: loadbalancer.Zone,
762-
ACLID: acl.ID,
763-
}); err != nil {
764-
return fmt.Errorf("failed removing ACL %s from frontend: %s port: %d loadbalancer: %s err: %v", acl.Name, frontend.ID, frontend.InboundPort, loadbalancer.ID, err)
765-
}
766-
}
767-
768-
klog.Infof("create all ACLs for frontend: %s port: %d loadbalancer: %s", frontend.ID, frontend.InboundPort, loadbalancer.ID)
769-
for _, acl := range svcAcls {
770-
if _, err := l.api.SetACLs(&scwlb.ZonedAPISetACLsRequest{
771-
Zone: loadbalancer.Zone,
772-
FrontendID: frontend.ID,
773-
ACLs: svcAcls,
774-
}); err != nil {
775-
return fmt.Errorf("failed creating ACL %s for frontend: %s port: %d loadbalancer: %s err: %v", acl.Name, frontend.ID, frontend.InboundPort, loadbalancer.ID, err)
776-
}
777-
}
741+
if err := l.reconcileACLs(ctx, loadbalancer, frontend, service, nodes); err != nil {
742+
return err
778743
}
779744
}
780745

@@ -1692,41 +1657,6 @@ func compareBackends(got []*scwlb.Backend, want map[int32]*scwlb.Backend, filter
16921657
}
16931658
}
16941659

1695-
// aclsEquals returns true if both acl lists are equal
1696-
func aclsEquals(got []*scwlb.ACL, want []*scwlb.ACLSpec) bool {
1697-
if len(got) != len(want) {
1698-
return false
1699-
}
1700-
1701-
slices.SortStableFunc(got, func(a, b *scwlb.ACL) int { return int(a.Index - b.Index) })
1702-
slices.SortStableFunc(want, func(a, b *scwlb.ACLSpec) int { return int(a.Index - b.Index) })
1703-
for idx := range want {
1704-
if want[idx].Name != got[idx].Name {
1705-
return false
1706-
}
1707-
if want[idx].Index != got[idx].Index {
1708-
return false
1709-
}
1710-
if (want[idx].Action == nil) != (got[idx].Action == nil) {
1711-
return false
1712-
}
1713-
if want[idx].Action != nil && want[idx].Action.Type != got[idx].Action.Type {
1714-
return false
1715-
}
1716-
if (want[idx].Match == nil) != (got[idx].Match == nil) {
1717-
return false
1718-
}
1719-
if want[idx].Match != nil && !stringPtrArrayEqual(want[idx].Match.IPSubnet, got[idx].Match.IPSubnet) {
1720-
return false
1721-
}
1722-
if want[idx].Match != nil && want[idx].Match.Invert != got[idx].Match.Invert {
1723-
return false
1724-
}
1725-
}
1726-
1727-
return true
1728-
}
1729-
17301660
// createBackend creates a backend on the load balancer
17311661
func (l *loadbalancers) createBackend(ctx context.Context, loadbalancer *scwlb.LB, backend *scwlb.Backend) (*scwlb.Backend, error) {
17321662
b, err := l.api.CreateBackend(&scwlb.ZonedAPICreateBackendRequest{
@@ -1945,73 +1875,6 @@ func chunkArray(array []string, maxChunkSize int) [][]string {
19451875
return result
19461876
}
19471877

1948-
// makeACLPrefix returns the ACL prefix for rules
1949-
func makeACLPrefix(frontend *scwlb.Frontend) string {
1950-
if frontend == nil {
1951-
return "lb-source-range"
1952-
}
1953-
return fmt.Sprintf("%s-lb-source-range", frontend.ID)
1954-
}
1955-
1956-
// makeACLSpecs converts a service frontend definition to acl specifications
1957-
func makeACLSpecs(service *v1.Service, nodes []*v1.Node, frontend *scwlb.Frontend) []*scwlb.ACLSpec {
1958-
if len(service.Spec.LoadBalancerSourceRanges) == 0 {
1959-
return []*scwlb.ACLSpec{}
1960-
}
1961-
1962-
sourceRanges := make([]string, 0, len(service.Spec.LoadBalancerSourceRanges))
1963-
for _, sourceRange := range service.Spec.LoadBalancerSourceRanges {
1964-
if _, _, err := net.ParseCIDR(sourceRange); err != nil {
1965-
klog.Warningf("ignoring invalid CIDR %s in LoadBalancerSourceRanges for service %s/%s: %v", sourceRange, service.Namespace, service.Name, err)
1966-
continue
1967-
}
1968-
1969-
if strings.Contains(sourceRange, ":") {
1970-
sourceRange = strings.TrimSuffix(sourceRange, "/128")
1971-
} else {
1972-
sourceRange = strings.TrimSuffix(sourceRange, "/32")
1973-
}
1974-
1975-
sourceRanges = append(sourceRanges, sourceRange)
1976-
}
1977-
1978-
aclPrefix := makeACLPrefix(frontend)
1979-
whitelist := extractNodesInternalIps(nodes)
1980-
whitelist = append(whitelist, extractNodesExternalIps(nodes)...)
1981-
whitelist = append(whitelist, sourceRanges...)
1982-
1983-
slices.Sort(whitelist)
1984-
1985-
subnetsChunks := chunkArray(whitelist, MaxEntriesPerACL)
1986-
acls := make([]*scwlb.ACLSpec, len(subnetsChunks)+1)
1987-
1988-
for idx, subnets := range subnetsChunks {
1989-
acls[idx] = &scwlb.ACLSpec{
1990-
Name: fmt.Sprintf("%s-%d", aclPrefix, idx),
1991-
Action: &scwlb.ACLAction{
1992-
Type: scwlb.ACLActionTypeAllow,
1993-
},
1994-
Index: int32(idx),
1995-
Match: &scwlb.ACLMatch{
1996-
IPSubnet: scw.StringSlicePtr(subnets),
1997-
},
1998-
}
1999-
}
2000-
2001-
acls[len(acls)-1] = &scwlb.ACLSpec{
2002-
Name: fmt.Sprintf("%s-end", aclPrefix),
2003-
Action: &scwlb.ACLAction{
2004-
Type: scwlb.ACLActionTypeDeny,
2005-
},
2006-
Index: int32(len(acls) - 1),
2007-
Match: &scwlb.ACLMatch{
2008-
IPSubnet: scw.StringSlicePtr([]string{"0.0.0.0/0", "::/0"}),
2009-
},
2010-
}
2011-
2012-
return acls
2013-
}
2014-
20151878
func ptrInt32ToString(i *int32) string {
20161879
if i == nil {
20171880
return "<nil>"

0 commit comments

Comments
 (0)