Skip to content

Commit

Permalink
add support for configuring the VPC interface
Browse files Browse the repository at this point in the history
  • Loading branch information
eljohnson92 committed Sep 18, 2024
1 parent 708b0ac commit f3c6ecd
Show file tree
Hide file tree
Showing 2 changed files with 280 additions and 5 deletions.
17 changes: 12 additions & 5 deletions controller/linodemachine_controller_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,16 @@ func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, logg

// if vpc, attach additional interface as eth0 to linode
if machineScope.LinodeCluster.Spec.VPCRef != nil {
iface, err := getVPCInterfaceConfig(ctx, machineScope, logger)
iface, err := getVPCInterfaceConfig(ctx, machineScope, createConfig.Interfaces, logger)
if err != nil {
logger.Error(err, "Failed to get VPC interface config")

return nil, err
}

// add VPC interface as first interface
createConfig.Interfaces = slices.Insert(createConfig.Interfaces, 0, *iface)
if iface != nil {
// add VPC interface as first interface
createConfig.Interfaces = slices.Insert(createConfig.Interfaces, 0, *iface)
}
}

if machineScope.LinodeMachine.Spec.PlacementGroupRef != nil {
Expand Down Expand Up @@ -333,7 +334,7 @@ func getFirewallID(ctx context.Context, machineScope *scope.MachineScope, logger
return *linodeFirewall.Spec.FirewallID, nil
}

func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope, logger logr.Logger) (*linodego.InstanceConfigInterfaceCreateOptions, error) {
func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope, interfaces []linodego.InstanceConfigInterfaceCreateOptions, logger logr.Logger) (*linodego.InstanceConfigInterfaceCreateOptions, error) {
name := machineScope.LinodeCluster.Spec.VPCRef.Name
namespace := machineScope.LinodeCluster.Spec.VPCRef.Namespace
if namespace == "" {
Expand Down Expand Up @@ -381,6 +382,12 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
})

subnetID = vpc.Subnets[0].ID
for i, netInterface := range interfaces {
if netInterface.Purpose == linodego.InterfacePurposeVPC {
interfaces[i].SubnetID = &subnetID
return nil, nil //nolint:nilnil // it is important we don't return an interface if a VPC interface already exists
}
}

return &linodego.InstanceConfigInterfaceCreateOptions{
Purpose: linodego.InterfacePurposeVPC,
Expand Down
268 changes: 268 additions & 0 deletions controller/linodemachine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1566,3 +1566,271 @@ var _ = Describe("machine in PlacementGroup", Label("machine", "placementGroup")
Expect(createOpts.FirewallID).To(Equal(2))
})
})

var _ = Describe("machine in VPC", Label("machine", "VPC"), Ordered, func() {
var machine clusterv1.Machine
var secret corev1.Secret
var lvpcReconciler *LinodeVPCReconciler
var linodeVPC infrav1alpha2.LinodeVPC

var mockCtrl *gomock.Controller
var testLogs *bytes.Buffer
var logger logr.Logger

cluster := clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "mock",
Namespace: defaultNamespace,
},
}

linodeCluster := infrav1alpha2.LinodeCluster{
Spec: infrav1alpha2.LinodeClusterSpec{
Region: "us-ord",
Network: infrav1alpha2.NetworkSpec{
LoadBalancerType: "dns",
DNSRootDomain: "lkedevs.net",
DNSUniqueIdentifier: "abc123",
DNSTTLSec: 30,
},
VPCRef: &corev1.ObjectReference{
Namespace: "default",
Kind: "LinodeVPC",
Name: "test-cluster",
},
},
}

recorder := record.NewFakeRecorder(10)

BeforeEach(func(ctx SpecContext) {
secret = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "bootstrap-secret",
Namespace: defaultNamespace,
},
Data: map[string][]byte{
"value": []byte("userdata"),
},
}
Expect(k8sClient.Create(ctx, &secret)).To(Succeed())

machine = clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Namespace: defaultNamespace,
Labels: make(map[string]string),
},
Spec: clusterv1.MachineSpec{
Bootstrap: clusterv1.Bootstrap{
DataSecretName: ptr.To("bootstrap-secret"),
},
},
}

linodeVPC = infrav1alpha2.LinodeVPC{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: defaultNamespace,
UID: "5123122",
},
Spec: infrav1alpha2.LinodeVPCSpec{
VPCID: ptr.To(1),
Region: "us-ord",
Subnets: []infrav1alpha2.VPCSubnetCreateOptions{},
},
Status: infrav1alpha2.LinodeVPCStatus{
Ready: true,
},
}
Expect(k8sClient.Create(ctx, &linodeVPC)).To(Succeed())

lvpcReconciler = &LinodeVPCReconciler{
Recorder: recorder,
Client: k8sClient,
}

mockCtrl = gomock.NewController(GinkgoT())
testLogs = &bytes.Buffer{}
logger = zap.New(
zap.WriteTo(GinkgoWriter),
zap.WriteTo(testLogs),
zap.UseDevMode(true),
)
})

AfterEach(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, &secret)).To(Succeed())
var currentVPC infrav1alpha2.LinodeVPC
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&linodeVPC), &currentVPC)).To(Succeed())
currentVPC.Finalizers = nil

Expect(k8sClient.Update(ctx, &currentVPC)).To(Succeed())

Expect(k8sClient.Delete(ctx, &currentVPC)).To(Succeed())

mockCtrl.Finish()
for len(recorder.Events) > 0 {
<-recorder.Events
}
})

It("creates a instance with vpc", func(ctx SpecContext) {
linodeMachine := infrav1alpha2.LinodeMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "mock",
Namespace: defaultNamespace,
UID: "12345",
},
Spec: infrav1alpha2.LinodeMachineSpec{
ProviderID: ptr.To("linode://0"),
Type: "g6-nanode-1",
Interfaces: []infrav1alpha2.InstanceConfigInterfaceCreateOptions{
{
Primary: true,
},
},
},
}
mockLinodeClient := mock.NewMockLinodeClient(mockCtrl)
getRegion := mockLinodeClient.EXPECT().
GetRegion(ctx, gomock.Any()).
Return(&linodego.Region{Capabilities: []string{linodego.CapabilityMetadata, infrav1alpha2.LinodePlacementGroupCapability}}, nil)
mockLinodeClient.EXPECT().
GetImage(ctx, gomock.Any()).
After(getRegion).
Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil)
mockLinodeClient.EXPECT().
ListVPCs(ctx, gomock.Any()).
Return([]linodego.VPC{}, nil)
mockLinodeClient.EXPECT().
CreateVPC(ctx, gomock.Any()).
Return(&linodego.VPC{ID: 1}, nil)
mockLinodeClient.EXPECT().
GetVPC(ctx, gomock.Any()).
Return(&linodego.VPC{ID: 1, Subnets: []linodego.VPCSubnet{{
ID: 1,
Label: "test",
IPv4: "10.0.0.0/24",
}}}, nil)
helper, err := patch.NewHelper(&linodeVPC, k8sClient)
Expect(err).NotTo(HaveOccurred())

_, err = lvpcReconciler.reconcile(ctx, logger, &scope.VPCScope{
PatchHelper: helper,
Client: k8sClient,
LinodeClient: mockLinodeClient,
LinodeVPC: &linodeVPC,
})

Expect(err).NotTo(HaveOccurred())

mScope := scope.MachineScope{
Client: k8sClient,
LinodeClient: mockLinodeClient,
Cluster: &cluster,
Machine: &machine,
LinodeCluster: &linodeCluster,
LinodeMachine: &linodeMachine,
}

patchHelper, err := patch.NewHelper(mScope.LinodeMachine, k8sClient)
Expect(err).NotTo(HaveOccurred())
mScope.PatchHelper = patchHelper

createOpts, err := newCreateConfig(ctx, &mScope, logger)
Expect(err).NotTo(HaveOccurred())
Expect(createOpts).NotTo(BeNil())
Expect(createOpts.Interfaces).To(Equal([]linodego.InstanceConfigInterfaceCreateOptions{
{
Purpose: linodego.InterfacePurposeVPC,
Primary: true,
SubnetID: ptr.To(1),
IPv4: &linodego.VPCIPv4{NAT1To1: ptr.To("any")},
},
{
Primary: true,
}}))
})
It("creates a instance with pre defined vpc interface", func(ctx SpecContext) {
linodeMachine := infrav1alpha2.LinodeMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "mock",
Namespace: defaultNamespace,
UID: "12345",
},
Spec: infrav1alpha2.LinodeMachineSpec{
ProviderID: ptr.To("linode://0"),
Type: "g6-nanode-1",
Interfaces: []infrav1alpha2.InstanceConfigInterfaceCreateOptions{
{
Purpose: linodego.InterfacePurposeVPC,
Primary: false,
},
{
Purpose: linodego.InterfacePurposePublic,
Primary: true,
},
},
},
}
mockLinodeClient := mock.NewMockLinodeClient(mockCtrl)
getRegion := mockLinodeClient.EXPECT().
GetRegion(ctx, gomock.Any()).
Return(&linodego.Region{Capabilities: []string{linodego.CapabilityMetadata, infrav1alpha2.LinodePlacementGroupCapability}}, nil)
mockLinodeClient.EXPECT().
GetImage(ctx, gomock.Any()).
After(getRegion).
Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil)
mockLinodeClient.EXPECT().
ListVPCs(ctx, gomock.Any()).
Return([]linodego.VPC{}, nil)
mockLinodeClient.EXPECT().
CreateVPC(ctx, gomock.Any()).
Return(&linodego.VPC{ID: 1}, nil)
mockLinodeClient.EXPECT().
GetVPC(ctx, gomock.Any()).
Return(&linodego.VPC{ID: 1, Subnets: []linodego.VPCSubnet{{
ID: 1,
Label: "test",
IPv4: "10.0.0.0/24",
}}}, nil)
helper, err := patch.NewHelper(&linodeVPC, k8sClient)
Expect(err).NotTo(HaveOccurred())

_, err = lvpcReconciler.reconcile(ctx, logger, &scope.VPCScope{
PatchHelper: helper,
Client: k8sClient,
LinodeClient: mockLinodeClient,
LinodeVPC: &linodeVPC,
})

Expect(err).NotTo(HaveOccurred())

mScope := scope.MachineScope{
Client: k8sClient,
LinodeClient: mockLinodeClient,
Cluster: &cluster,
Machine: &machine,
LinodeCluster: &linodeCluster,
LinodeMachine: &linodeMachine,
}

patchHelper, err := patch.NewHelper(mScope.LinodeMachine, k8sClient)
Expect(err).NotTo(HaveOccurred())
mScope.PatchHelper = patchHelper

createOpts, err := newCreateConfig(ctx, &mScope, logger)
Expect(err).NotTo(HaveOccurred())
Expect(createOpts).NotTo(BeNil())
Expect(createOpts.Interfaces).To(Equal([]linodego.InstanceConfigInterfaceCreateOptions{
{
Purpose: linodego.InterfacePurposeVPC,
Primary: false,
SubnetID: ptr.To(1),
},
{
Purpose: linodego.InterfacePurposePublic,
Primary: true,
}}))
})
})

0 comments on commit f3c6ecd

Please sign in to comment.