From 4e620d591fe98106bedb7e6d4251ab72f87a8f7f Mon Sep 17 00:00:00 2001 From: Tarun Chinmai Sekar <70169773+tchinmai7@users.noreply.github.com> Date: Sat, 27 Jul 2024 18:24:00 -0700 Subject: [PATCH] [feat] Add PlacementGroupRef to linodeMachine (#418) No flavor as of now, can be tested manually by inserting into the manifest for a cluster --- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate metadata: name: schinmai-test-control-plane namespace: default spec: template: spec: authorizedKeys: null image: linode/ubuntu22.04 interfaces: - purpose: public placementGroupRef: apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodePlacementGroup name: schinmai-test region: us-ord type: g6-standard-2 --- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodePlacementGroup metadata: name: schinmai-test namespace: default spec: region: us-ord --- --- .github/workflows/e2e-test.yaml | 2 +- api/v1alpha1/conversion.go | 5 + api/v1alpha1/linodemachine_conversion_test.go | 5 + api/v1alpha1/zz_generated.conversion.go | 64 +++++-- api/v1alpha2/linodemachine_types.go | 5 + api/v1alpha2/linodeplacementgroup_types.go | 7 +- api/v1alpha2/linodeplacementgroup_webhook.go | 2 +- api/v1alpha2/zz_generated.deepcopy.go | 5 + ...cture.cluster.x-k8s.io_linodemachines.yaml | 48 ++++++ ...uster.x-k8s.io_linodemachinetemplates.yaml | 49 ++++++ ...luster.x-k8s.io_linodeplacementgroups.yaml | 29 ++-- ...apicontract_in_linodeclustertemplates.yaml | 2 +- .../capicontract_in_linodemachines.yaml | 2 +- ...apicontract_in_linodemachinetemplates.yaml | 2 +- .../patches/capicontract_in_linodevpcs.yaml | 2 +- .../linodemachine_controller_helpers.go | 39 +++++ controller/linodemachine_controller_test.go | 160 ++++++++++++++++++ .../linodeplacementgroup_controller_test.go | 5 +- .../chainsaw-test.yaml | 8 +- go.mod | 2 +- go.sum | 4 +- .../clusterclass-kubeadm/kustomization.yaml | 4 +- .../kubeadm/etcd-disk/kustomization.yaml | 2 +- .../kubeadm/full-vpcless/kustomization.yaml | 2 +- .../flavors/kubeadm/full/kustomization.yaml | 2 +- .../flavors/rke2/etcd-disk/kustomization.yaml | 2 +- .../rke2/full-vpcless/kustomization.yaml | 2 +- .../flavors/rke2/full/kustomization.yaml | 2 +- 28 files changed, 410 insertions(+), 53 deletions(-) diff --git a/.github/workflows/e2e-test.yaml b/.github/workflows/e2e-test.yaml index 89a2a2873..639444566 100644 --- a/.github/workflows/e2e-test.yaml +++ b/.github/workflows/e2e-test.yaml @@ -28,7 +28,7 @@ on: - linodemachine - linodeobj - linodevpc - # - linodeplacementgroup + - linodeplacementgroup - all e2e-flags: type: string diff --git a/api/v1alpha1/conversion.go b/api/v1alpha1/conversion.go index 6d3134912..d64d33d7d 100644 --- a/api/v1alpha1/conversion.go +++ b/api/v1alpha1/conversion.go @@ -38,3 +38,8 @@ func Convert_v1alpha2_NetworkSpec_To_v1alpha1_NetworkSpec(in *infrastructurev1al out.NodeBalancerID = in.NodeBalancerID return nil } + +func Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in *infrastructurev1alpha2.LinodeMachineSpec, out *LinodeMachineSpec, s conversion.Scope) error { + // Ok to use the auto-generated conversion function, it simply drops the PlacementGroupRef, and copies everything else + return autoConvert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in, out, s) +} diff --git a/api/v1alpha1/linodemachine_conversion_test.go b/api/v1alpha1/linodemachine_conversion_test.go index f9e128fc3..7578ce9fa 100644 --- a/api/v1alpha1/linodemachine_conversion_test.go +++ b/api/v1alpha1/linodemachine_conversion_test.go @@ -206,6 +206,11 @@ func TestLinodeMachineConvertFrom(t *testing.T) { Namespace: "default", Name: "cred-secret", }, + PlacementGroupRef: &corev1.ObjectReference{ + Kind: "LinodePlacementGroup", + Name: "test-placement-group", + Namespace: "default", + }, }, Status: infrav1alpha2.LinodeMachineStatus{}, } diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index b89d186ac..6e892f4aa 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -174,11 +174,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha2.LinodeMachineSpec)(nil), (*LinodeMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(a.(*v1alpha2.LinodeMachineSpec), b.(*LinodeMachineSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*LinodeMachineStatus)(nil), (*v1alpha2.LinodeMachineStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_LinodeMachineStatus_To_v1alpha2_LinodeMachineStatus(a.(*LinodeMachineStatus), b.(*v1alpha2.LinodeMachineStatus), scope) }); err != nil { @@ -294,6 +289,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1alpha2.LinodeMachineSpec)(nil), (*LinodeMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(a.(*v1alpha2.LinodeMachineSpec), b.(*LinodeMachineSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1alpha2.NetworkSpec)(nil), (*NetworkSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_NetworkSpec_To_v1alpha1_NetworkSpec(a.(*v1alpha2.NetworkSpec), b.(*NetworkSpec), scope) }); err != nil { @@ -662,7 +662,17 @@ func Convert_v1alpha2_LinodeMachine_To_v1alpha1_LinodeMachine(in *v1alpha2.Linod func autoConvert_v1alpha1_LinodeMachineList_To_v1alpha2_LinodeMachineList(in *LinodeMachineList, out *v1alpha2.LinodeMachineList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1alpha2.LinodeMachine)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1alpha2.LinodeMachine, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_LinodeMachine_To_v1alpha2_LinodeMachine(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -673,7 +683,17 @@ func Convert_v1alpha1_LinodeMachineList_To_v1alpha2_LinodeMachineList(in *Linode func autoConvert_v1alpha2_LinodeMachineList_To_v1alpha1_LinodeMachineList(in *v1alpha2.LinodeMachineList, out *LinodeMachineList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]LinodeMachine)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinodeMachine, len(*in)) + for i := range *in { + if err := Convert_v1alpha2_LinodeMachine_To_v1alpha1_LinodeMachine(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -728,14 +748,10 @@ func autoConvert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in *v1 out.OSDisk = (*InstanceDisk)(unsafe.Pointer(in.OSDisk)) out.DataDisks = *(*map[string]*InstanceDisk)(unsafe.Pointer(&in.DataDisks)) out.CredentialsRef = (*v1.SecretReference)(unsafe.Pointer(in.CredentialsRef)) + // WARNING: in.PlacementGroupRef requires manual conversion: does not exist in peer-type return nil } -// Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec is an autogenerated conversion function. -func Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in *v1alpha2.LinodeMachineSpec, out *LinodeMachineSpec, s conversion.Scope) error { - return autoConvert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in, out, s) -} - func autoConvert_v1alpha1_LinodeMachineStatus_To_v1alpha2_LinodeMachineStatus(in *LinodeMachineStatus, out *v1alpha2.LinodeMachineStatus, s conversion.Scope) error { out.Ready = in.Ready out.Addresses = *(*[]v1beta1.MachineAddress)(unsafe.Pointer(&in.Addresses)) @@ -794,7 +810,17 @@ func Convert_v1alpha2_LinodeMachineTemplate_To_v1alpha1_LinodeMachineTemplate(in func autoConvert_v1alpha1_LinodeMachineTemplateList_To_v1alpha2_LinodeMachineTemplateList(in *LinodeMachineTemplateList, out *v1alpha2.LinodeMachineTemplateList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1alpha2.LinodeMachineTemplate)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1alpha2.LinodeMachineTemplate, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_LinodeMachineTemplate_To_v1alpha2_LinodeMachineTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -805,7 +831,17 @@ func Convert_v1alpha1_LinodeMachineTemplateList_To_v1alpha2_LinodeMachineTemplat func autoConvert_v1alpha2_LinodeMachineTemplateList_To_v1alpha1_LinodeMachineTemplateList(in *v1alpha2.LinodeMachineTemplateList, out *LinodeMachineTemplateList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]LinodeMachineTemplate)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinodeMachineTemplate, len(*in)) + for i := range *in { + if err := Convert_v1alpha2_LinodeMachineTemplate_To_v1alpha1_LinodeMachineTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } diff --git a/api/v1alpha2/linodemachine_types.go b/api/v1alpha2/linodemachine_types.go index 285cb856b..305632634 100644 --- a/api/v1alpha2/linodemachine_types.go +++ b/api/v1alpha2/linodemachine_types.go @@ -83,6 +83,11 @@ type LinodeMachineSpec struct { // 3. Controller // +optional CredentialsRef *corev1.SecretReference `json:"credentialsRef,omitempty"` + + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // +optional + // PlacementGroupRef is a reference to a placement group object. This makes the linode to be launched in that specific group. + PlacementGroupRef *corev1.ObjectReference `json:"placementGroupRef,omitempty"` } // InstanceDisk defines a list of disks to use for an instance diff --git a/api/v1alpha2/linodeplacementgroup_types.go b/api/v1alpha2/linodeplacementgroup_types.go index 770894274..9b3dfbe97 100644 --- a/api/v1alpha2/linodeplacementgroup_types.go +++ b/api/v1alpha2/linodeplacementgroup_types.go @@ -35,15 +35,16 @@ type LinodePlacementGroupSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" Region string `json:"region"` // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - // +kubebuilder:default=true + // +kubebuilder:default="strict" + // +kubebuilder:validation:Enum=strict;flexible // +optional - IsStrict bool `json:"isStrict"` + PlacementGroupPolicy string `json:"placementGroupPolicy"` // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +kubebuilder:default="anti_affinity:local" // +kubebuilder:validation:Enum="anti_affinity:local" // +optional - AffinityType string `json:"affinityType"` + PlacementGroupType string `json:"placementGroupType"` // TODO: add affinity as a type when available // CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this PlacementGroup. If not diff --git a/api/v1alpha2/linodeplacementgroup_webhook.go b/api/v1alpha2/linodeplacementgroup_webhook.go index 3e826e763..605a8d98e 100644 --- a/api/v1alpha2/linodeplacementgroup_webhook.go +++ b/api/v1alpha2/linodeplacementgroup_webhook.go @@ -109,7 +109,7 @@ func (r *LinodePlacementGroup) validateLinodePlacementGroupSpec(ctx context.Cont if err := validatePlacementGroupLabel(r.Name, field.NewPath("metadata").Child("name")); err != nil { errs = append(errs, err) } - // isStrict is immutable, no need to verify again. + // PlacementGroupPolicy is immutable, no need to verify again. if len(errs) == 0 { return nil } diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 61fc97c61..5ac2b6cf8 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -422,6 +422,11 @@ func (in *LinodeMachineSpec) DeepCopyInto(out *LinodeMachineSpec) { *out = new(v1.SecretReference) **out = **in } + if in.PlacementGroupRef != nil { + in, out := &in.PlacementGroupRef, &out.PlacementGroupRef + *out = new(v1.ObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeMachineSpec. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml index 9ce4d8503..bab1a2301 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml @@ -597,6 +597,54 @@ spec: required: - size type: object + placementGroupRef: + description: PlacementGroupRef is a reference to a placement group + object. This makes the linode to be launched in that specific group. + 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 privateIP: type: boolean x-kubernetes-validations: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml index 17ebc48fc..61dea0dfb 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml @@ -464,6 +464,55 @@ spec: required: - size type: object + placementGroupRef: + description: PlacementGroupRef is a reference to a placement + group object. This makes the linode to be launched in that + specific group. + 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 privateIP: type: boolean x-kubernetes-validations: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeplacementgroups.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeplacementgroups.yaml index bed1b81cf..e78980ea2 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeplacementgroups.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeplacementgroups.yaml @@ -51,14 +51,6 @@ spec: spec: description: LinodePlacementGroupSpec defines the desired state of LinodePlacementGroup properties: - affinityType: - default: anti_affinity:local - enum: - - anti_affinity:local - type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf credentialsRef: description: |- CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this PlacementGroup. If not @@ -74,14 +66,25 @@ spec: type: string type: object x-kubernetes-map-type: atomic - isStrict: - default: true - type: boolean + pgID: + type: integer + placementGroupPolicy: + default: strict + enum: + - strict + - flexible + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + placementGroupType: + default: anti_affinity:local + enum: + - anti_affinity:local + type: string x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf - pgID: - type: integer region: type: string x-kubernetes-validations: diff --git a/config/crd/patches/capicontract_in_linodeclustertemplates.yaml b/config/crd/patches/capicontract_in_linodeclustertemplates.yaml index 54b35826c..5193fd283 100644 --- a/config/crd/patches/capicontract_in_linodeclustertemplates.yaml +++ b/config/crd/patches/capicontract_in_linodeclustertemplates.yaml @@ -3,4 +3,4 @@ kind: CustomResourceDefinition metadata: name: linodeclustertemplates.infrastructure.cluster.x-k8s.io labels: - cluster.x-k8s.io/v1beta1: v1alpha1 + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/config/crd/patches/capicontract_in_linodemachines.yaml b/config/crd/patches/capicontract_in_linodemachines.yaml index 2fa76546b..69a48a608 100644 --- a/config/crd/patches/capicontract_in_linodemachines.yaml +++ b/config/crd/patches/capicontract_in_linodemachines.yaml @@ -3,4 +3,4 @@ kind: CustomResourceDefinition metadata: name: linodemachines.infrastructure.cluster.x-k8s.io labels: - cluster.x-k8s.io/v1beta1: v1alpha1 + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/config/crd/patches/capicontract_in_linodemachinetemplates.yaml b/config/crd/patches/capicontract_in_linodemachinetemplates.yaml index 1c8ba1b05..9049e1867 100644 --- a/config/crd/patches/capicontract_in_linodemachinetemplates.yaml +++ b/config/crd/patches/capicontract_in_linodemachinetemplates.yaml @@ -3,4 +3,4 @@ kind: CustomResourceDefinition metadata: name: linodemachinetemplates.infrastructure.cluster.x-k8s.io labels: - cluster.x-k8s.io/v1beta1: v1alpha1 + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/config/crd/patches/capicontract_in_linodevpcs.yaml b/config/crd/patches/capicontract_in_linodevpcs.yaml index 186394177..3bf7fbc8a 100644 --- a/config/crd/patches/capicontract_in_linodevpcs.yaml +++ b/config/crd/patches/capicontract_in_linodevpcs.yaml @@ -3,4 +3,4 @@ kind: CustomResourceDefinition metadata: name: linodevpcs.infrastructure.cluster.x-k8s.io labels: - cluster.x-k8s.io/v1beta1: v1alpha1 + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/controller/linodemachine_controller_helpers.go b/controller/linodemachine_controller_helpers.go index b29f92fb8..addb386b8 100644 --- a/controller/linodemachine_controller_helpers.go +++ b/controller/linodemachine_controller_helpers.go @@ -108,6 +108,17 @@ func (r *LinodeMachineReconciler) newCreateConfig(ctx context.Context, machineSc createConfig.Interfaces = slices.Insert(createConfig.Interfaces, 0, *iface) } + if machineScope.LinodeMachine.Spec.PlacementGroupRef != nil { + pgID, err := r.getPlacementGroupID(ctx, machineScope, logger) + if err != nil { + logger.Error(err, "Failed to get Placement Group config") + return nil, err + } + createConfig.PlacementGroup = &linodego.InstanceCreatePlacementGroupOptions{ + ID: pgID, + } + } + return createConfig, nil } @@ -296,6 +307,34 @@ func (r *LinodeMachineReconciler) requestsForCluster(ctx context.Context, namesp return result, nil } +func (r *LinodeMachineReconciler) getPlacementGroupID(ctx context.Context, machineScope *scope.MachineScope, logger logr.Logger) (int, error) { + name := machineScope.LinodeMachine.Spec.PlacementGroupRef.Name + namespace := machineScope.LinodeMachine.Spec.PlacementGroupRef.Namespace + if namespace == "" { + namespace = machineScope.LinodeMachine.Namespace + } + + logger = logger.WithValues("placementGroupName", name, "placementGroupNamespace", namespace) + + linodePlacementGroup := infrav1alpha2.LinodePlacementGroup{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + } + objectKey := client.ObjectKeyFromObject(&linodePlacementGroup) + err := r.Get(ctx, objectKey, &linodePlacementGroup) + if err != nil { + logger.Error(err, "Failed to fetch LinodePlacementGroup") + return -1, err + } else if !linodePlacementGroup.Status.Ready || linodePlacementGroup.Spec.PGID == nil { + logger.Info("LinodePlacementGroup is not ready") + return -1, errors.New("placement group is not ready") + } + + return *linodePlacementGroup.Spec.PGID, nil +} + func (r *LinodeMachineReconciler) getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope, logger logr.Logger) (*linodego.InstanceConfigInterfaceCreateOptions, error) { name := machineScope.LinodeCluster.Spec.VPCRef.Name namespace := machineScope.LinodeCluster.Spec.VPCRef.Namespace diff --git a/controller/linodemachine_controller_test.go b/controller/linodemachine_controller_test.go index cbe29a148..7af1b3ebb 100644 --- a/controller/linodemachine_controller_test.go +++ b/controller/linodemachine_controller_test.go @@ -1094,3 +1094,163 @@ var _ = Describe("machine-delete", Ordered, Label("machine", "machine-delete"), ), ) }) + +var _ = Describe("machine in PlacementGroup", Label("machine", "placementGroup"), func() { + var machine clusterv1.Machine + var linodeMachine infrav1alpha2.LinodeMachine + var secret corev1.Secret + var reconciler *LinodeMachineReconciler + var lpgReconciler *LinodePlacementGroupReconciler + var linodePlacementGroup infrav1alpha2.LinodePlacementGroup + + 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, + }, + }, + } + + 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"), + }, + }, + } + + linodePlacementGroup = infrav1alpha2.LinodePlacementGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pg", + Namespace: defaultNamespace, + UID: "5123122", + }, + Spec: infrav1alpha2.LinodePlacementGroupSpec{ + PGID: ptr.To(1), + Region: "us-ord", + PlacementGroupPolicy: "strict", + PlacementGroupType: "anti_affinity:local", + }, + Status: infrav1alpha2.LinodePlacementGroupStatus{ + Ready: true, + }, + } + Expect(k8sClient.Create(ctx, &linodePlacementGroup)).To(Succeed()) + + linodeMachine = infrav1alpha2.LinodeMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mock", + Namespace: defaultNamespace, + UID: "12345", + }, + Spec: infrav1alpha2.LinodeMachineSpec{ + InstanceID: ptr.To(0), + Type: "g6-nanode-1", + Image: rutil.DefaultMachineControllerLinodeImage, + PlacementGroupRef: &corev1.ObjectReference{ + Namespace: defaultNamespace, + Name: "test-pg", + }, + }, + } + + lpgReconciler = &LinodePlacementGroupReconciler{ + Recorder: recorder, + Client: k8sClient, + } + + reconciler = &LinodeMachineReconciler{ + 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()) + + mockCtrl.Finish() + for len(recorder.Events) > 0 { + <-recorder.Events + } + }) + + It("creates a instance in a PlacementGroup", func(ctx SpecContext) { + 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) + + helper, err := patch.NewHelper(&linodePlacementGroup, k8sClient) + Expect(err).NotTo(HaveOccurred()) + + _, err = lpgReconciler.reconcile(ctx, logger, &scope.PlacementGroupScope{ + PatchHelper: helper, + Client: k8sClient, + LinodeClient: mockLinodeClient, + LinodePlacementGroup: &linodePlacementGroup, + }) + + Expect(err).NotTo(HaveOccurred()) + + mScope := scope.MachineScope{ + Client: k8sClient, + LinodeClient: mockLinodeClient, + LinodeDomainsClient: mockLinodeClient, + Cluster: &cluster, + Machine: &machine, + LinodeCluster: &linodeCluster, + LinodeMachine: &linodeMachine, + } + + createOpts, err := reconciler.newCreateConfig(ctx, &mScope, []string{}, logger) + Expect(err).NotTo(HaveOccurred()) + Expect(createOpts).NotTo(BeNil()) + Expect(createOpts.PlacementGroup.ID).To(Equal(1)) + }) + +}) diff --git a/controller/linodeplacementgroup_controller_test.go b/controller/linodeplacementgroup_controller_test.go index 698fafe3d..401f128ad 100644 --- a/controller/linodeplacementgroup_controller_test.go +++ b/controller/linodeplacementgroup_controller_test.go @@ -46,8 +46,9 @@ var _ = Describe("lifecycle", Ordered, Label("placementgroup", "lifecycle"), fun Namespace: "default", }, Spec: infrav1alpha2.LinodePlacementGroupSpec{ - Region: "us-ord", - AffinityType: "anti_affinity:local", + Region: "us-ord", + PlacementGroupType: "anti_affinity:local", + PlacementGroupPolicy: "strict", }, } diff --git a/e2e/linodeplacementgroup-controller/minimal-linodeplacementgroup/chainsaw-test.yaml b/e2e/linodeplacementgroup-controller/minimal-linodeplacementgroup/chainsaw-test.yaml index 4a863d72c..65d588c81 100644 --- a/e2e/linodeplacementgroup-controller/minimal-linodeplacementgroup/chainsaw-test.yaml +++ b/e2e/linodeplacementgroup-controller/minimal-linodeplacementgroup/chainsaw-test.yaml @@ -4,10 +4,10 @@ kind: Test metadata: name: minimal-linodeplacementgroup # Label to trigger the test on every PR - # labels: - # # all: - # # quick: - # # linodeplacementgroup: + labels: + all: + quick: + linodeplacementgroup: spec: bindings: # A short identifier for the E2E test run diff --git a/go.mod b/go.mod index 47f205d2d..32f60d500 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 - github.com/linode/linodego v1.37.0 + github.com/linode/linodego v1.38.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index a6e9b2fe1..67c1054d0 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/linode/linodego v1.37.0 h1:B/2Spzv9jYXzKA+p+GD8fVCNJ7Wuw6P91ZDD9eCkkso= -github.com/linode/linodego v1.37.0/go.mod h1:L7GXKFD3PoN2xSEtFc04wIXP5WK65O10jYQx0PQISWQ= +github.com/linode/linodego v1.38.0 h1:wP3oW9OhGc6vhze8NPf2knbwH4TzSbrjzuCd9okjbTY= +github.com/linode/linodego v1.38.0/go.mod h1:L7GXKFD3PoN2xSEtFc04wIXP5WK65O10jYQx0PQISWQ= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= diff --git a/templates/flavors/clusterclass-kubeadm/kustomization.yaml b/templates/flavors/clusterclass-kubeadm/kustomization.yaml index 80bb2f85f..cdce66846 100644 --- a/templates/flavors/clusterclass-kubeadm/kustomization.yaml +++ b/templates/flavors/clusterclass-kubeadm/kustomization.yaml @@ -14,7 +14,7 @@ patches: - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*md-0 options: @@ -27,7 +27,7 @@ patches: - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*control-plane options: diff --git a/templates/flavors/kubeadm/etcd-disk/kustomization.yaml b/templates/flavors/kubeadm/etcd-disk/kustomization.yaml index 55327e901..00f77a364 100644 --- a/templates/flavors/kubeadm/etcd-disk/kustomization.yaml +++ b/templates/flavors/kubeadm/etcd-disk/kustomization.yaml @@ -7,7 +7,7 @@ resources: patches: - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |- diff --git a/templates/flavors/kubeadm/full-vpcless/kustomization.yaml b/templates/flavors/kubeadm/full-vpcless/kustomization.yaml index d528bd03d..743ea222f 100644 --- a/templates/flavors/kubeadm/full-vpcless/kustomization.yaml +++ b/templates/flavors/kubeadm/full-vpcless/kustomization.yaml @@ -113,7 +113,7 @@ patches: cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: ${WORKER_MACHINE_MAX:-"10"} - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |- diff --git a/templates/flavors/kubeadm/full/kustomization.yaml b/templates/flavors/kubeadm/full/kustomization.yaml index 9a05e63bf..0f266b374 100644 --- a/templates/flavors/kubeadm/full/kustomization.yaml +++ b/templates/flavors/kubeadm/full/kustomization.yaml @@ -10,7 +10,7 @@ resources: patches: - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |- diff --git a/templates/flavors/rke2/etcd-disk/kustomization.yaml b/templates/flavors/rke2/etcd-disk/kustomization.yaml index f78ffb7cd..c6038b091 100644 --- a/templates/flavors/rke2/etcd-disk/kustomization.yaml +++ b/templates/flavors/rke2/etcd-disk/kustomization.yaml @@ -7,7 +7,7 @@ resources: patches: - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |- diff --git a/templates/flavors/rke2/full-vpcless/kustomization.yaml b/templates/flavors/rke2/full-vpcless/kustomization.yaml index bd6699c90..fa5fd1a5e 100644 --- a/templates/flavors/rke2/full-vpcless/kustomization.yaml +++ b/templates/flavors/rke2/full-vpcless/kustomization.yaml @@ -34,7 +34,7 @@ patches: cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: ${WORKER_MACHINE_MAX:-"10"} - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |- diff --git a/templates/flavors/rke2/full/kustomization.yaml b/templates/flavors/rke2/full/kustomization.yaml index 388f45788..a61cc233a 100644 --- a/templates/flavors/rke2/full/kustomization.yaml +++ b/templates/flavors/rke2/full/kustomization.yaml @@ -34,7 +34,7 @@ patches: cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: ${WORKER_MACHINE_MAX:-"10"} - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeMachineTemplate name: .*-control-plane patch: |-