Skip to content

Commit b0bf52f

Browse files
authored
[feat] : add ipv6 ipam to CCM (#369)
* initial implementation of ipv6 ipam * fix linting errors * add tests for cloud_allocator * fix review comments * use ipv6 range allocated to node to calculate /112 for node ipam * use providerid to get linode id * address review comments * add flag to enable/disable ipv6 allocation logic * update linodego to upstream branch which contains ipv6 support * fix lint error * address review comments * use slaac ranges as well in evaluation * make sure disabling ipv6 for nodeipam works
1 parent f1b64ce commit b0bf52f

22 files changed

+1983
-191
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fmt:
8080
.PHONY: test
8181
# we say code is not worth testing unless it's formatted
8282
test: fmt codegen
83-
go test -v -coverpkg=./sentry,./cloud/linode/client,./cloud/linode/firewall,./cloud/linode -coverprofile ./coverage.out -cover ./sentry/... ./cloud/... $(TEST_ARGS)
83+
go test -v -coverpkg=./sentry,./cloud/linode/client,./cloud/linode/firewall,./cloud/linode,./cloud/nodeipam,./cloud/nodeipam/ipam -coverprofile ./coverage.out -cover ./sentry/... ./cloud/... $(TEST_ARGS)
8484

8585
.PHONY: build-linux
8686
build-linux: codegen

cloud/linode/client/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Client interface {
2626
GetInstance(context.Context, int) (*linodego.Instance, error)
2727
ListInstances(context.Context, *linodego.ListOptions) ([]linodego.Instance, error)
2828
CreateInstance(ctx context.Context, opts linodego.InstanceCreateOptions) (*linodego.Instance, error)
29+
ListInstanceConfigs(ctx context.Context, linodeID int, opts *linodego.ListOptions) ([]linodego.InstanceConfig, error)
2930

3031
GetInstanceIPAddresses(context.Context, int) (*linodego.InstanceIPAddressResponse, error)
3132
AddInstanceIPAddress(ctx context.Context, linodeID int, public bool) (*linodego.InstanceIP, error)

cloud/linode/client/client_with_metrics.go

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

cloud/linode/client/mocks/mock_client.go

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

cloud/linode/cloud.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var Options struct {
5656
GlobalStopChannel chan<- struct{}
5757
EnableIPv6ForLoadBalancers bool
5858
AllocateNodeCIDRs bool
59+
DisableIPv6NodeCIDRAllocation bool
5960
ClusterCIDRIPv4 string
6061
NodeCIDRMaskSizeIPv4 int
6162
NodeCIDRMaskSizeIPv6 int

cloud/linode/cloud_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func TestNewCloudRouteControllerDisabled(t *testing.T) {
2020
t.Setenv("LINODE_API_TOKEN", "dummyapitoken")
2121
t.Setenv("LINODE_REGION", "us-east")
2222
t.Setenv("LINODE_REQUEST_TIMEOUT_SECONDS", "10")
23+
t.Setenv("LINODE_URL", "https://api.linode.com/v4")
2324
Options.NodeBalancerPrefix = "ccm"
2425

2526
t.Run("should not fail if vpc is empty and routecontroller is disabled", func(t *testing.T) {
@@ -45,6 +46,7 @@ func TestNewCloud(t *testing.T) {
4546
t.Setenv("LINODE_REGION", "us-east")
4647
t.Setenv("LINODE_REQUEST_TIMEOUT_SECONDS", "10")
4748
t.Setenv("LINODE_ROUTES_CACHE_TTL_SECONDS", "60")
49+
t.Setenv("LINODE_URL", "https://api.linode.com/v4")
4850
Options.LinodeGoDebug = true
4951
Options.NodeBalancerPrefix = "ccm"
5052

cloud/linode/nodeipamcontroller.go

Lines changed: 24 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,24 @@ import (
2727
"k8s.io/apimachinery/pkg/util/wait"
2828
v1 "k8s.io/client-go/informers/core/v1"
2929
"k8s.io/client-go/kubernetes"
30-
cloudprovider "k8s.io/cloud-provider"
31-
nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam"
32-
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
3330
netutils "k8s.io/utils/net"
31+
32+
nodeipamcontroller "github.com/linode/linode-cloud-controller-manager/cloud/nodeipam"
33+
"github.com/linode/linode-cloud-controller-manager/cloud/nodeipam/ipam"
3434
)
3535

3636
const (
37-
maxAllowedNodeCIDRs = 2
37+
maxAllowedNodeCIDRsIPv4 = 1
3838
)
3939

4040
var (
4141
// defaultNodeMaskCIDRIPv4 is default mask size for IPv4 node cidr
4242
defaultNodeMaskCIDRIPv4 = 24
4343
// defaultNodeMaskCIDRIPv6 is default mask size for IPv6 node cidr
44-
defaultNodeMaskCIDRIPv6 = 64
44+
defaultNodeMaskCIDRIPv6 = 112
4545
)
4646

47-
func startNodeIpamController(stopCh <-chan struct{}, cloud cloudprovider.Interface, nodeInformer v1.NodeInformer, kubeclient kubernetes.Interface) error {
47+
func startNodeIpamController(stopCh <-chan struct{}, cloud *linodeCloud, nodeInformer v1.NodeInformer, kubeclient kubernetes.Interface) error {
4848
var serviceCIDR *net.IPNet
4949
var secondaryServiceCIDR *net.IPNet
5050

@@ -54,64 +54,39 @@ func startNodeIpamController(stopCh <-chan struct{}, cloud cloudprovider.Interfa
5454
}
5555

5656
// failure: bad cidrs in config
57-
clusterCIDRs, dualStack, err := processCIDRs(Options.ClusterCIDRIPv4)
57+
clusterCIDRs, err := processCIDRs(Options.ClusterCIDRIPv4)
5858
if err != nil {
5959
return fmt.Errorf("processCIDRs failed: %w", err)
6060
}
6161

62-
// failure: more than one cidr but they are not configured as dual stack
63-
if len(clusterCIDRs) > 1 && !dualStack {
64-
return fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
65-
}
66-
67-
// failure: more than cidrs is not allowed even with dual stack
68-
if len(clusterCIDRs) > maxAllowedNodeCIDRs {
69-
return fmt.Errorf("len of clusters is:%v > more than max allowed of %d", len(clusterCIDRs), maxAllowedNodeCIDRs)
70-
}
71-
72-
/* TODO: uncomment and fix if we want to support service cidr overlap with nodecidr
73-
// service cidr processing
74-
if len(strings.TrimSpace(nodeIPAMConfig.ServiceCIDR)) != 0 {
75-
_, serviceCIDR, err = netutils.ParseCIDRSloppy(nodeIPAMConfig.ServiceCIDR)
76-
if err != nil {
77-
klog.ErrorS(err, "Unsuccessful parsing of service CIDR", "CIDR", nodeIPAMConfig.ServiceCIDR)
78-
}
62+
if len(clusterCIDRs) == 0 {
63+
return fmt.Errorf("no clusterCIDR specified. Must specify --cluster-cidr if --allocate-node-cidrs is set")
7964
}
8065

81-
if len(strings.TrimSpace(nodeIPAMConfig.SecondaryServiceCIDR)) != 0 {
82-
_, secondaryServiceCIDR, err = netutils.ParseCIDRSloppy(nodeIPAMConfig.SecondaryServiceCIDR)
83-
if err != nil {
84-
klog.ErrorS(err, "Unsuccessful parsing of service CIDR", "CIDR", nodeIPAMConfig.SecondaryServiceCIDR)
85-
}
66+
if len(clusterCIDRs) > maxAllowedNodeCIDRsIPv4 {
67+
return fmt.Errorf("too many clusterCIDRs specified for ipv4, max allowed is %d", maxAllowedNodeCIDRsIPv4)
8668
}
8769

88-
// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
89-
if serviceCIDR != nil && secondaryServiceCIDR != nil {
90-
// should be dual stack (from different IPFamilies)
91-
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
92-
if err != nil {
93-
return nil, false, fmt.Errorf("failed to perform dualstack check on serviceCIDR and secondaryServiceCIDR error:%v", err)
94-
}
95-
if !dualstackServiceCIDR {
96-
return nil, false, fmt.Errorf("serviceCIDR and secondaryServiceCIDR are not dualstack (from different IPfamiles)")
97-
}
70+
if clusterCIDRs[0].IP.To4() == nil {
71+
return fmt.Errorf("clusterCIDR %s is not ipv4", clusterCIDRs[0].String())
9872
}
99-
*/
10073

101-
nodeCIDRMaskSizes := setNodeCIDRMaskSizes(clusterCIDRs)
74+
nodeCIDRMaskSizes := setNodeCIDRMaskSizes()
10275

10376
ctx := wait.ContextForChannel(stopCh)
10477

10578
nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
10679
ctx,
10780
nodeInformer,
10881
cloud,
82+
cloud.client,
10983
kubeclient,
11084
clusterCIDRs,
11185
serviceCIDR,
11286
secondaryServiceCIDR,
11387
nodeCIDRMaskSizes,
114-
ipam.RangeAllocatorType,
88+
ipam.CloudAllocatorType,
89+
Options.DisableIPv6NodeCIDRAllocation,
11590
)
11691
if err != nil {
11792
return err
@@ -121,47 +96,25 @@ func startNodeIpamController(stopCh <-chan struct{}, cloud cloudprovider.Interfa
12196
return nil
12297
}
12398

124-
// processCIDRs is a helper function that works on a comma separated cidrs and returns
125-
// a list of typed cidrs
126-
// a flag if cidrs represents a dual stack
127-
// error if failed to parse any of the cidrs
128-
func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
99+
// processCIDR is a helper function that works on cidr and returns a list of typed cidrs
100+
// error if failed to parse the cidr
101+
func processCIDRs(cidrsList string) ([]*net.IPNet, error) {
129102
cidrsSplit := strings.Split(strings.TrimSpace(cidrsList), ",")
130103

131104
cidrs, err := netutils.ParseCIDRs(cidrsSplit)
132105
if err != nil {
133-
return nil, false, err
106+
return nil, err
134107
}
135108

136-
// if cidrs has an error then the previous call will fail
137-
// safe to ignore error checking on next call
138-
dualstack, err := netutils.IsDualStackCIDRs(cidrs)
139-
if err != nil {
140-
return nil, false, fmt.Errorf("failed to perform dualstack check on cidrs: %w", err)
141-
}
142-
143-
return cidrs, dualstack, nil
109+
return cidrs, nil
144110
}
145111

146-
func setNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet) []int {
147-
sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
148-
nodeMaskCIDRs := make([]int, len(clusterCIDRs))
149-
150-
for idx, clusterCIDR := range clusterCIDRs {
151-
if netutils.IsIPv6CIDR(clusterCIDR) {
152-
nodeMaskCIDRs[idx] = maskSizeIPv6
153-
} else {
154-
nodeMaskCIDRs[idx] = maskSizeIPv4
155-
}
156-
}
157-
return nodeMaskCIDRs
158-
}
159-
112+
func setNodeCIDRMaskSizes() []int {
160113
if Options.NodeCIDRMaskSizeIPv4 != 0 {
161114
defaultNodeMaskCIDRIPv4 = Options.NodeCIDRMaskSizeIPv4
162115
}
163116
if Options.NodeCIDRMaskSizeIPv6 != 0 {
164117
defaultNodeMaskCIDRIPv6 = Options.NodeCIDRMaskSizeIPv6
165118
}
166-
return sortedSizes(defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6)
119+
return []int{defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6}
167120
}

0 commit comments

Comments
 (0)