Skip to content

Commit 4bda9bd

Browse files
Rahul Sharmaeljohnson92
authored andcommitted
allow specifying ipv6 slaac public ranges
1 parent f3b5e58 commit 4bda9bd

File tree

7 files changed

+138
-33
lines changed

7 files changed

+138
-33
lines changed

api/v1alpha2/linodemachine_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ type LinodeMachineSpec struct {
112112
// +optional
113113
VPCID *int `json:"vpcID,omitempty"`
114114

115+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
116+
// EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
117+
// This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
118+
// Defaults to false.
119+
// +optional
120+
EnableSLAAC *bool `json:"enableSLAAC,omitempty"`
121+
122+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
123+
// IsPublicIPv6 is an option to enable public IPv6 for the instance.
124+
// If set to true, the instance will have a publicly routable IPv6 range.
125+
IsPublicIPv6 *bool `json:"isPublicIPv6,omitempty"`
126+
115127
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
116128
// +optional
117129
// NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.

api/v1alpha2/zz_generated.deepcopy.go

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

config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,15 @@ spec:
163163
x-kubernetes-validations:
164164
- message: Value is immutable
165165
rule: self == oldSelf
166+
enableSLAAC:
167+
description: |-
168+
EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
169+
This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
170+
Defaults to false.
171+
type: boolean
172+
x-kubernetes-validations:
173+
- message: Value is immutable
174+
rule: self == oldSelf
166175
firewallID:
167176
type: integer
168177
x-kubernetes-validations:
@@ -264,6 +273,14 @@ spec:
264273
x-kubernetes-validations:
265274
- message: Value is immutable
266275
rule: self == oldSelf
276+
isPublicIPv6:
277+
description: |-
278+
IsPublicIPv6 is an option to enable public IPv6 for the instance.
279+
If set to true, the instance will have a publicly routable IPv6 range.
280+
type: boolean
281+
x-kubernetes-validations:
282+
- message: Value is immutable
283+
rule: self == oldSelf
267284
networkHelper:
268285
description: |-
269286
NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.

config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ spec:
153153
x-kubernetes-validations:
154154
- message: Value is immutable
155155
rule: self == oldSelf
156+
enableSLAAC:
157+
description: |-
158+
EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
159+
This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
160+
Defaults to false.
161+
type: boolean
162+
x-kubernetes-validations:
163+
- message: Value is immutable
164+
rule: self == oldSelf
156165
firewallID:
157166
type: integer
158167
x-kubernetes-validations:
@@ -256,6 +265,14 @@ spec:
256265
x-kubernetes-validations:
257266
- message: Value is immutable
258267
rule: self == oldSelf
268+
isPublicIPv6:
269+
description: |-
270+
IsPublicIPv6 is an option to enable public IPv6 for the instance.
271+
If set to true, the instance will have a publicly routable IPv6 range.
272+
type: boolean
273+
x-kubernetes-validations:
274+
- message: Value is immutable
275+
rule: self == oldSelf
259276
networkHelper:
260277
description: |-
261278
NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.

docs/src/reference/out.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,8 @@ _Appears in:_
621621
| `firewallRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectreference-v1-core)_ | FirewallRef is a reference to a firewall object. This makes the linode use the specified firewall. | | |
622622
| `vpcRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectreference-v1-core)_ | VPCRef is a reference to a LinodeVPC resource. If specified, this takes precedence over<br />the cluster-level VPC configuration for multi-region support. | | |
623623
| `vpcID` _integer_ | VPCID is the ID of an existing VPC in Linode. This allows using a VPC that is not managed by CAPL. | | |
624+
| `enableSLAAC` _boolean_ | EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.<br />This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.<br />Defaults to false. | | |
625+
| `isPublicIPv6` _boolean_ | IsPublicIPv6 is an option to enable public IPv6 for the instance.<br />If set to true, the instance will have a publicly routable IPv6 range. | | |
624626
| `networkHelper` _boolean_ | NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.<br />You can use this to enable/disable the network helper for a specific instance.<br />For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking<br />Defaults to true. | | |
625627

626628

internal/controller/linodemachine_controller_helpers.go

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,12 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
463463

464464
subnetName := machineScope.LinodeCluster.Spec.Network.SubnetName // name of subnet to use
465465

466-
var ipv6RangeConfig []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range
466+
var ipv6Config *linodego.InstanceConfigInterfaceCreateOptionsIPv6
467467
if subnetName != "" {
468468
for _, subnet := range linodeVPC.Spec.Subnets {
469469
if subnet.Label == subnetName {
470470
subnetID = subnet.SubnetID
471-
ipv6RangeConfig = machineIPv6RangeConfig(len(subnet.IPv6))
471+
ipv6Config = getMachineIPv6Config(machineScope, len(subnet.IPv6))
472472
break
473473
}
474474
}
@@ -478,7 +478,7 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
478478
}
479479
} else {
480480
subnetID = linodeVPC.Spec.Subnets[0].SubnetID // get first subnet if nothing specified
481-
ipv6RangeConfig = machineIPv6RangeConfig(len(linodeVPC.Spec.Subnets[0].IPv6))
481+
ipv6Config = getMachineIPv6Config(machineScope, len(linodeVPC.Spec.Subnets[0].IPv6))
482482
}
483483

484484
if subnetID == 0 {
@@ -488,10 +488,9 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
488488
for i, netInterface := range interfaces {
489489
if netInterface.Purpose == linodego.InterfacePurposeVPC {
490490
interfaces[i].SubnetID = &subnetID
491-
if len(ipv6RangeConfig) > 0 {
492-
interfaces[i].IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
493-
Ranges: ipv6RangeConfig,
494-
}
491+
// If IPv6 range config is not empty, add it to the interface configuration
492+
if ipv6Config != nil {
493+
interfaces[i].IPv6 = ipv6Config
495494
}
496495
return nil, nil //nolint:nilnil // it is important we don't return an interface if a VPC interface already exists
497496
}
@@ -506,11 +505,9 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
506505
},
507506
}
508507

509-
// If IPv6 range config is not empty, add it to the interface configuration
510-
if len(ipv6RangeConfig) > 0 {
511-
vpcIntfCreateOpts.IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
512-
Ranges: ipv6RangeConfig,
513-
}
508+
// If IPv6 config is not empty, add it to the interface configuration
509+
if ipv6Config != nil {
510+
vpcIntfCreateOpts.IPv6 = ipv6Config
514511
}
515512

516513
return vpcIntfCreateOpts, nil
@@ -538,12 +535,12 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
538535
}
539536

540537
// If subnet name specified, find matching subnet; otherwise use first subnet
541-
var ipv6RangeConfig []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range
538+
var ipv6Config *linodego.InstanceConfigInterfaceCreateOptionsIPv6
542539
if subnetName != "" {
543540
for _, subnet := range vpc.Subnets {
544541
if subnet.Label == subnetName {
545542
subnetID = subnet.ID
546-
ipv6RangeConfig = machineIPv6RangeConfig(len(subnet.IPv6))
543+
ipv6Config = getMachineIPv6Config(machineScope, len(subnet.IPv6))
547544
break
548545
}
549546
}
@@ -552,17 +549,15 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
552549
}
553550
} else {
554551
subnetID = vpc.Subnets[0].ID
555-
ipv6RangeConfig = machineIPv6RangeConfig(len(vpc.Subnets[0].IPv6))
552+
ipv6Config = getMachineIPv6Config(machineScope, len(vpc.Subnets[0].IPv6))
556553
}
557554

558555
// Check if a VPC interface already exists
559556
for i, netInterface := range interfaces {
560557
if netInterface.Purpose == linodego.InterfacePurposeVPC {
561558
interfaces[i].SubnetID = &subnetID
562-
if len(ipv6RangeConfig) > 0 {
563-
interfaces[i].IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
564-
Ranges: ipv6RangeConfig,
565-
}
559+
if ipv6Config != nil {
560+
interfaces[i].IPv6 = ipv6Config
566561
}
567562
return nil, nil //nolint:nilnil // it is important we don't return an interface if a VPC interface already exists
568563
}
@@ -579,27 +574,45 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
579574
}
580575

581576
// If IPv6 range config is not empty, add it to the interface configuration
582-
if len(ipv6RangeConfig) > 0 {
583-
vpcIntfCreateOpts.IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
584-
Ranges: ipv6RangeConfig,
585-
}
577+
if ipv6Config != nil {
578+
vpcIntfCreateOpts.IPv6 = ipv6Config
586579
}
587580

588581
return vpcIntfCreateOpts, nil
589582
}
590583

591-
// machineIPv6RangeConfig returns the IPv6 range configuration if subnet has IPv6 ranges.
584+
// getMachineIPv6Config returns the IPv6 configuration if subnet has IPv6 ranges.
592585
// For now, we support only a single IPv6 range for machine per subnet.
593-
// If this changes, we may need to adjust this logic.
594-
func machineIPv6RangeConfig(numIPv6RangesInSubnet int) []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range {
586+
// If SLAAC is enabled, we create an IPv6 configuration with the SLAAC range.
587+
// If SLAAC is not enabled, we create an IPv6 configuration with the default range.
588+
// If no IPv6 ranges are available in the subnet, it returns nil.
589+
// If `IsPublicIPv6` is set, it will be used to determine if the IPv6 range should be publicly routable or not.
590+
func getMachineIPv6Config(machineScope *scope.MachineScope, numIPv6RangesInSubnet int) *linodego.InstanceConfigInterfaceCreateOptionsIPv6 {
595591
if numIPv6RangesInSubnet == 0 {
596-
return nil // No IPv6 ranges available in subnet, return empty slice
592+
return nil // No IPv6 ranges available in subnet, return nil
597593
}
598-
return []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range{
599-
{
600-
Range: ptr.To(defaultNodeIPv6CIDRRange),
601-
},
594+
595+
intfOpts := &linodego.InstanceConfigInterfaceCreateOptionsIPv6{}
596+
if machineScope.LinodeMachine.Spec.IsPublicIPv6 != nil {
597+
// Set the public IPv6 flag based on the IsPublicIPv6 specification.
598+
intfOpts.IsPublic = machineScope.LinodeMachine.Spec.IsPublicIPv6
602599
}
600+
601+
if machineScope.LinodeMachine.Spec.EnableSLAAC != nil && *machineScope.LinodeMachine.Spec.EnableSLAAC {
602+
intfOpts.SLAAC = []linodego.InstanceConfigInterfaceCreateOptionsIPv6SLAAC{
603+
{
604+
Range: defaultNodeIPv6CIDRRange,
605+
},
606+
}
607+
} else {
608+
intfOpts.Ranges = []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range{
609+
{
610+
Range: ptr.To(defaultNodeIPv6CIDRRange),
611+
},
612+
}
613+
}
614+
615+
return intfOpts
603616
}
604617

605618
func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1alpha2.LinodeMachineSpec, machineTags []string) *linodego.InstanceCreateOptions {

internal/controller/linodemachine_controller_helpers_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,9 @@ func validateInterfaceExpectations(
409409
if expectInterface {
410410
require.NotNil(t, iface)
411411
require.Equal(t, linodego.InterfacePurposeVPC, iface.Purpose)
412-
if iface.IPv6 != nil {
412+
if iface.IPv6 != nil && iface.IPv6.SLAAC != nil {
413+
require.Equal(t, defaultNodeIPv6CIDRRange, iface.IPv6.SLAAC[0].Range)
414+
} else if iface.IPv6 != nil && iface.IPv6.Ranges != nil {
413415
require.Equal(t, defaultNodeIPv6CIDRRange, *iface.IPv6.Ranges[0].Range)
414416
}
415417
require.True(t, iface.Primary)
@@ -485,6 +487,35 @@ func TestGetVPCInterfaceConfigFromDirectID(t *testing.T) {
485487
expectInterface: true,
486488
expectSubnetID: 789, // Matching subnet ID
487489
},
490+
{
491+
name: "Success - Valid VPC with subnets and ipv6 ranges, specific subnet name",
492+
vpcID: 123,
493+
interfaces: []linodego.InstanceConfigInterfaceCreateOptions{},
494+
subnetName: "subnet-2",
495+
mockSetup: func(mockLinodeClient *mock.MockLinodeClient) {
496+
mockLinodeClient.EXPECT().GetVPC(gomock.Any(), 123).Return(&linodego.VPC{
497+
ID: 123,
498+
Subnets: []linodego.VPCSubnet{
499+
{
500+
ID: 456,
501+
Label: "subnet-1",
502+
},
503+
{
504+
ID: 789,
505+
Label: "subnet-2",
506+
IPv6: []linodego.VPCIPv6Range{
507+
{
508+
Range: "2001:0db8::/56",
509+
},
510+
},
511+
},
512+
},
513+
}, nil)
514+
},
515+
expectErr: false,
516+
expectInterface: true,
517+
expectSubnetID: 789, // Matching subnet ID
518+
},
488519
{
489520
name: "Success - VPC interface already exists",
490521
vpcID: 123,
@@ -1176,7 +1207,10 @@ func TestGetVPCInterfaceConfig(t *testing.T) {
11761207
ObjectMeta: metav1.ObjectMeta{
11771208
Namespace: "default",
11781209
},
1179-
Spec: infrav1alpha2.LinodeMachineSpec{},
1210+
Spec: infrav1alpha2.LinodeMachineSpec{
1211+
EnableSLAAC: ptr.To(true),
1212+
IsPublicIPv6: ptr.To(true),
1213+
},
11801214
},
11811215
LinodeCluster: &infrav1alpha2.LinodeCluster{
11821216
Spec: infrav1alpha2.LinodeClusterSpec{

0 commit comments

Comments
 (0)