Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ helm-template:
#Verify template works when region and apiToken are passed, and when it is passed as reference.
@helm template foo deploy/chart --set apiToken="apiToken",region="us-east" > /dev/null
@helm template foo deploy/chart --set secretRef.apiTokenRef="apiToken",secretRef.name="api",secretRef.regionRef="us-east" > /dev/null
@bash hack/test-helm-networking.sh

.PHONY: serve-docs
serve-docs:
Expand All @@ -287,4 +288,3 @@ serve-docs:
build-docs:
# Build the documentation site the way GitHub Pages does
docker run --rm --volume "$(shell pwd):/srv/jekyll" $(DOCS_IMAGE) jekyll build

8 changes: 8 additions & 0 deletions cloud/linode/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ func setupNodeBalancerBackendSubnet(linodeClient client.Client) error {
if options.Options.NodeBalancerBackendIPv4SubnetID != 0 && options.Options.NodeBalancerBackendIPv4SubnetName != "" {
return fmt.Errorf("cannot have both --nodebalancer-backend-ipv4-subnet-id and --nodebalancer-backend-ipv4-subnet-name set")
}
if options.Options.NodeBalancerBackendIPv4ReservedRange != "" {
if err := validateNodeBalancerBackendIPv4Reservation(
options.Options.NodeBalancerBackendIPv4Subnet,
options.Options.NodeBalancerBackendIPv4ReservedRange,
); err != nil {
return err
}
}

switch {
case options.Options.DisableNodeBalancerVPCBackends:
Expand Down
47 changes: 47 additions & 0 deletions cloud/linode/fake_linode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"testing"

"github.com/linode/linodego/v2"

"github.com/linode/linode-cloud-controller-manager/cloud/linode/services"
)

const apiVersion = "v4"
Expand Down Expand Up @@ -43,6 +45,10 @@ type fakeRequest struct {

func newFake(t *testing.T) *fakeAPI {
t.Helper()
services.Mu.Lock()
services.VpcIDs = make(map[string]int)
services.SubnetIDs = make(map[string]int)
services.Mu.Unlock()

fake := &fakeAPI{
t: t,
Expand Down Expand Up @@ -127,6 +133,24 @@ func (f *fakeAPI) setupRoutes() {
_, _ = w.Write(rr)
})

f.mux.HandleFunc("GET /v4/vpcs/{vpcId}/subnets/{subnetId}", func(w http.ResponseWriter, r *http.Request) {
subnetID, err := strconv.Atoi(r.PathValue("subnetId"))
if err != nil {
f.t.Fatal(err)
}
subnet, ok := f.subnet[subnetID]
if !ok {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"errors":[{"reason":"Not Found"}]}`))
return
}
resp, err := json.Marshal(subnet)
if err != nil {
f.t.Fatal(err)
}
_, _ = w.Write(resp)
})

f.mux.HandleFunc("GET /v4/vpcs", func(w http.ResponseWriter, r *http.Request) {
res := 0
data := []linodego.VPC{}
Expand Down Expand Up @@ -367,6 +391,20 @@ func (f *fakeAPI) setupRoutes() {
}
f.nb[strconv.Itoa(nb.ID)] = &nb

for _, backendVPC := range nbco.BackendVPCs {
if backendVPC.IPv4Range == "" {
continue
}
subnet, ok := f.subnet[backendVPC.SubnetID]
if !ok {
f.t.Fatalf("subnet %d not found", backendVPC.SubnetID)
}
subnet.Nodebalancers = append(subnet.Nodebalancers, linodego.VPCSubnetNodebalancers{
ID: nb.ID,
Ipv4Range: backendVPC.IPv4Range,
})
}

for _, nbcco := range nbco.Configs {
if nbcco.Protocol == "https" {
if !strings.Contains(nbcco.SSLCert, "BEGIN CERTIFICATE") {
Expand Down Expand Up @@ -649,6 +687,15 @@ func (f *fakeAPI) setupRoutes() {
delete(f.nbn, k)
}
}
for _, subnet := range f.subnet {
nodebalancers := subnet.Nodebalancers[:0]
for _, nodeBalancer := range subnet.Nodebalancers {
if nodeBalancer.ID != nid {
nodebalancers = append(nodebalancers, nodeBalancer)
}
}
subnet.Nodebalancers = nodebalancers
}
})

f.mux.HandleFunc("DELETE /v4/nodebalancers/{nodeBalancerId}/configs/{configId}/nodes/{nodeId}", func(w http.ResponseWriter, r *http.Request) {
Expand Down
64 changes: 56 additions & 8 deletions cloud/linode/loadbalancers.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,31 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser
}
}

if options.Options.NodeBalancerBackendIPv4ReservedRange != "" {
vpcID, err := l.getVPCIDForSVC(ctx, service)
if err != nil {
return nil, fmt.Errorf("failed to resolve VPC for NodeBalancer backend allocation: %w", err)
}
subnet, err := l.client.GetVPCSubnet(ctx, vpcID, subnetID)
if err != nil {
return nil, fmt.Errorf("failed to get VPC subnet %d for NodeBalancer backend allocation: %w", subnetID, err)
}
backendIPv4Range, err := allocateNodeBalancerBackendIPv4Range(
options.Options.NodeBalancerBackendIPv4Subnet,
options.Options.NodeBalancerBackendIPv4ReservedRange,
subnet,
)
if err != nil {
return nil, err
}
return []linodego.NodeBalancerBackendVPCOptions{
{
SubnetID: subnetID,
IPv4Range: backendIPv4Range,
},
}, nil
}

// Precedence 2: If the user wants to overwrite the default VPC name or subnet name
// and have specified it in the annotations, use it to set subnetID
// and auto-allocate subnets from it for the NodeBalancer
Expand All @@ -816,7 +841,7 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser
}

// Precedence 4: If the user has specified a NodeBalancerBackendIPv4Subnet, use that
// and auto-allocate subnets from it for the NodeBalancer
// and auto-allocate subnets from it for the NodeBalancer.
if options.Options.NodeBalancerBackendIPv4Subnet != "" {
vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{
{
Expand Down Expand Up @@ -1120,7 +1145,7 @@ func (l *loadbalancers) getSubnetIDForSVC(ctx context.Context, service *v1.Servi
return subnetID, nil
}

specifiedVPCName, vpcOk := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]
_, vpcOk := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]
specifiedSubnetName, subnetOk := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetName]

// If no VPCName or SubnetName is specified in annotations, but NodeBalancerBackendIPv4SubnetID is set,
Expand All @@ -1129,11 +1154,7 @@ func (l *loadbalancers) getSubnetIDForSVC(ctx context.Context, service *v1.Servi
return options.Options.NodeBalancerBackendIPv4SubnetID, nil
}

vpcName := options.Options.VPCNames[0]
if vpcOk {
vpcName = specifiedVPCName
}
vpcID, err := services.GetVPCID(ctx, l.client, vpcName)
vpcID, err := l.getVPCIDForSVC(ctx, service)
if err != nil {
return 0, err
}
Expand All @@ -1147,6 +1168,18 @@ func (l *loadbalancers) getSubnetIDForSVC(ctx context.Context, service *v1.Servi
return services.GetSubnetID(ctx, l.client, vpcID, subnetName)
}

func (l *loadbalancers) getVPCIDForSVC(ctx context.Context, service *v1.Service) (int, error) {
if len(options.Options.VPCNames) == 0 {
return 0, fmt.Errorf("CCM not configured with VPC, cannot create NodeBalancer with specified annotation")
}

vpcName := options.Options.VPCNames[0]
if specifiedVPCName, ok := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]; ok {
vpcName = specifiedVPCName
}
return services.GetVPCID(ctx, l.client, vpcName)
}

// buildLoadBalancerRequest returns a linodego.NodeBalancer
// requests for service across nodes.
func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (*linodego.NodeBalancer, error) {
Expand Down Expand Up @@ -1584,6 +1617,19 @@ func validateNodeBalancerBackendIPv4Range(backendIPv4Range string) error {
if !withinCIDR {
return fmt.Errorf("IPv4 range %s is not within the subnet %s", backendIPv4Range, options.Options.NodeBalancerBackendIPv4Subnet)
}
if options.Options.NodeBalancerBackendIPv4ReservedRange != "" {
reserved, err := parseIPv4Prefix(options.Options.NodeBalancerBackendIPv4ReservedRange)
if err != nil {
return fmt.Errorf("invalid reserved NodeBalancer backend range: %w", err)
}
backend, err := parseIPv4Prefix(backendIPv4Range)
if err != nil {
return fmt.Errorf("invalid IPv4 range: %w", err)
}
if prefixesOverlap(backend, reserved) {
return fmt.Errorf("IPv4 range %s overlaps the reserved NodeBalancer backend range %s", backend, reserved)
}
}
return nil
}

Expand All @@ -1608,5 +1654,7 @@ func isCIDRWithinCIDR(outer, inner string) (bool, error) {
if err != nil {
return false, fmt.Errorf("invalid CIDR: %w", err)
}
return ipNet1.Contains(ipNet2.IP), nil
outerOnes, outerBits := ipNet1.Mask.Size()
innerOnes, innerBits := ipNet2.Mask.Size()
return outerBits == innerBits && outerOnes <= innerOnes && ipNet1.Contains(ipNet2.IP), nil
}
13 changes: 13 additions & 0 deletions cloud/linode/loadbalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5881,13 +5881,26 @@ func Test_validateNodeBalancerBackendIPv4Range(t *testing.T) {
args: args{backendIPv4Range: "10.100.0.0"},
wantErr: true,
},
{
name: "Reserved IPv4 range",
args: args{backendIPv4Range: "10.100.0.252/30"},
wantErr: true,
},
{
name: "Range extends outside backend subnet",
args: args{backendIPv4Range: "10.100.0.0/23"},
wantErr: true,
},
}

nbBackendSubnet := options.Options.NodeBalancerBackendIPv4Subnet
nbBackendReservedRange := options.Options.NodeBalancerBackendIPv4ReservedRange
defer func() {
options.Options.NodeBalancerBackendIPv4Subnet = nbBackendSubnet
options.Options.NodeBalancerBackendIPv4ReservedRange = nbBackendReservedRange
}()
options.Options.NodeBalancerBackendIPv4Subnet = "10.100.0.0/24"
options.Options.NodeBalancerBackendIPv4ReservedRange = "10.100.0.252/30"

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading
Loading