Skip to content

Commit

Permalink
[feat] adding vpcref to linodemachine (#553)
Browse files Browse the repository at this point in the history
* feat: adding vpcref to linodemachine which is prioritized over linodecluster vpcref

* Update e2e test

* Add envtest test cases for VPCref
  • Loading branch information
komer3 authored Oct 29, 2024
1 parent ab23ac2 commit 12ba433
Show file tree
Hide file tree
Showing 22 changed files with 625 additions and 10 deletions.
1 change: 1 addition & 0 deletions api/v1alpha1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/v1alpha2/linodemachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ type LinodeMachineSpec struct {
// +optional
// FirewallRef is a reference to a firewall object. This makes the linode use the specified firewall.
FirewallRef *corev1.ObjectReference `json:"firewallRef,omitempty"`
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// +optional
// VPCRef is a reference to a LinodeVPC resource. If specified, this takes precedence over
// the cluster-level VPC configuration for multi-region support.
VPCRef *corev1.ObjectReference `json:"vpcRef,omitempty"`
}

// InstanceDisk defines a list of disks to use for an instance
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,55 @@ spec:
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
vpcRef:
description: |-
VPCRef is a reference to a LinodeVPC resource. If specified, this takes precedence over
the cluster-level VPC configuration for multi-region support.
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: |-
If referring to a piece of an object instead of an entire object, this string
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within a pod, this would take on a value like:
"spec.containers{name}" (where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]" (container with
index 2 in this pod). This syntax is chosen only to have some well-defined way of
referencing a part of an object.
TODO: this design is not final and this field is subject to change in the future.
type: string
kind:
description: |-
Kind of the referent.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
namespace:
description: |-
Namespace of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
type: string
resourceVersion:
description: |-
Specific resourceVersion to which this reference is made, if any.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
type: string
uid:
description: |-
UID of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
type: string
type: object
x-kubernetes-map-type: atomic
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
required:
- region
- type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,55 @@ spec:
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
vpcRef:
description: |-
VPCRef is a reference to a LinodeVPC resource. If specified, this takes precedence over
the cluster-level VPC configuration for multi-region support.
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: |-
If referring to a piece of an object instead of an entire object, this string
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within a pod, this would take on a value like:
"spec.containers{name}" (where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]" (container with
index 2 in this pod). This syntax is chosen only to have some well-defined way of
referencing a part of an object.
TODO: this design is not final and this field is subject to change in the future.
type: string
kind:
description: |-
Kind of the referent.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
namespace:
description: |-
Namespace of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
type: string
resourceVersion:
description: |-
Specific resourceVersion to which this reference is made, if any.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
type: string
uid:
description: |-
UID of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
type: string
type: object
x-kubernetes-map-type: atomic
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
required:
- region
- type
Expand Down
39 changes: 39 additions & 0 deletions controller/linodemachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@ func (r *LinodeMachineReconciler) reconcileCreate(
}
}

// Should we check if the VPC ref in LinodeCluster is ready? Or is it enough to check if the VPC exists?
if vpcRef := getVPCRefFromScope(machineScope); vpcRef != nil {
if !reconciler.ConditionTrue(machineScope.LinodeMachine, ConditionPreflightLinodeVPCReady) && machineScope.LinodeMachine.Spec.ProviderID == nil {
res, err := r.reconcilePreflightVPC(ctx, logger, machineScope, vpcRef)
if err != nil || !res.IsZero() {
return res, err
}
}
}

if !reconciler.ConditionTrue(machineScope.LinodeMachine, ConditionPreflightMetadataSupportConfigured) && machineScope.LinodeMachine.Spec.ProviderID == nil {
res, err := r.reconcilePreflightMetadataSupportConfigure(ctx, logger, machineScope)
if err != nil || !res.IsZero() {
Expand Down Expand Up @@ -305,6 +315,35 @@ func (r *LinodeMachineReconciler) reconcileCreate(
return ctrl.Result{}, nil
}

func (r *LinodeMachineReconciler) reconcilePreflightVPC(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope, vpcRef *corev1.ObjectReference) (ctrl.Result, error) {
name := vpcRef.Name
namespace := vpcRef.Namespace
if namespace == "" {
namespace = machineScope.LinodeMachine.Namespace
}
linodeVPC := infrav1alpha2.LinodeVPC{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
if err := machineScope.Client.Get(ctx, client.ObjectKeyFromObject(&linodeVPC), &linodeVPC); err != nil {
logger.Error(err, "Failed to fetch LinodeVPC")
if reconciler.RecordDecayingCondition(machineScope.LinodeMachine,
ConditionPreflightLinodeVPCReady, string(cerrs.CreateClusterError), err.Error(),
reconciler.DefaultTimeout(r.ReconcileTimeout, reconciler.DefaultClusterControllerReconcileTimeout)) {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: reconciler.DefaultClusterControllerReconcileDelay}, nil
} else if !linodeVPC.Status.Ready {
logger.Info("LinodeVPC is not yet available")
return ctrl.Result{RequeueAfter: reconciler.DefaultClusterControllerReconcileDelay}, nil
}
r.Recorder.Event(machineScope.LinodeMachine, corev1.EventTypeNormal, string(clusterv1.ReadyCondition), "LinodeVPC is now available")
conditions.MarkTrue(machineScope.LinodeMachine, ConditionPreflightLinodeVPCReady)
return ctrl.Result{}, nil
}

func (r *LinodeMachineReconciler) reconcilePreflightLinodeFirewallCheck(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) (ctrl.Result, error) {
name := machineScope.LinodeMachine.Spec.FirewallRef.Name
namespace := machineScope.LinodeMachine.Spec.FirewallRef.Namespace
Expand Down
28 changes: 20 additions & 8 deletions controller/linodemachine_controller_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/go-logr/logr"
"github.com/google/uuid"
"github.com/linode/linodego"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -113,12 +114,11 @@ func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, logg

fillCreateConfig(createConfig, machineScope)

// if vpc is enabled, attach additional interface as eth0 to linode
if machineScope.LinodeCluster.Spec.VPCRef != nil {
iface, err := getVPCInterfaceConfig(ctx, machineScope, createConfig.Interfaces, logger)
// Check for VPC configuration - first machine level, then cluster level
if vpcRef := getVPCRefFromScope(machineScope); vpcRef != nil {
iface, err := getVPCInterfaceConfig(ctx, machineScope, createConfig.Interfaces, logger, vpcRef)
if err != nil {
logger.Error(err, "Failed to get VPC interface config")

return nil, err
}
if iface != nil {
Expand Down Expand Up @@ -380,13 +380,15 @@ func getVlanInterfaceConfig(ctx context.Context, machineScope *scope.MachineScop
}, nil
}

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
func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope, interfaces []linodego.InstanceConfigInterfaceCreateOptions, logger logr.Logger, vpcRef *corev1.ObjectReference) (*linodego.InstanceConfigInterfaceCreateOptions, error) {
// Get namespace from VPC ref or default to machine namespace
namespace := vpcRef.Namespace
if namespace == "" {
namespace = machineScope.LinodeCluster.Namespace
namespace = machineScope.LinodeMachine.Namespace
}

name := vpcRef.Name

logger = logger.WithValues("vpcName", name, "vpcNamespace", namespace)

linodeVPC := infrav1alpha2.LinodeVPC{
Expand Down Expand Up @@ -712,3 +714,13 @@ func createInstance(ctx context.Context, logger logr.Logger, machineScope *scope
inst, err := machineScope.LinodeClient.CreateInstance(ctx, *createOpts)
return inst, ctr.RetryAfter(), err
}

// getVPCRefFromScope returns the appropriate VPC reference based on priority:
// 1. Machine-level VPC reference
// 2. Cluster-level VPC reference
func getVPCRefFromScope(machineScope *scope.MachineScope) *corev1.ObjectReference {
if machineScope.LinodeMachine.Spec.VPCRef != nil {
return machineScope.LinodeMachine.Spec.VPCRef
}
return machineScope.LinodeCluster.Spec.VPCRef
}
Loading

0 comments on commit 12ba433

Please sign in to comment.