From bd95749296fb90cc512f2157d598329978ae7246 Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Thu, 20 Apr 2023 16:37:53 +0200 Subject: [PATCH] Add Nutanix support for CPMSO This adds basic support for Nutanix control plane machines for the purpose of self-healing control plane nodes. We would enhance this at a later point to add failure domain support. --- .../controller.go | 7 + .../controller_test.go | 328 +++++++++++++++++- .../nutanix.go | 67 ++++ .../controlplanemachinesetgenerator/utils.go | 2 + .../machine/v1beta1/providerconfig/nutanix.go | 54 +++ .../v1beta1/providerconfig/providerconfig.go | 24 +- test/e2e/framework/framework.go | 50 +++ 7 files changed, 526 insertions(+), 6 deletions(-) create mode 100644 pkg/controllers/controlplanemachinesetgenerator/nutanix.go create mode 100644 pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/nutanix.go diff --git a/pkg/controllers/controlplanemachinesetgenerator/controller.go b/pkg/controllers/controlplanemachinesetgenerator/controller.go index e5da6d464..ab309c6ae 100644 --- a/pkg/controllers/controlplanemachinesetgenerator/controller.go +++ b/pkg/controllers/controlplanemachinesetgenerator/controller.go @@ -210,6 +210,8 @@ func (r *ControlPlaneMachineSetGeneratorReconciler) reconcile(ctx context.Contex } // generateControlPlaneMachineSet generates a control plane machine set based on the current cluster state. +// +//nolint:cyclop func (r *ControlPlaneMachineSetGeneratorReconciler) generateControlPlaneMachineSet(logger logr.Logger, platformType configv1.PlatformType, machines []machinev1beta1.Machine, machineSets []machinev1beta1.MachineSet) (*machinev1.ControlPlaneMachineSet, error) { var ( @@ -233,6 +235,11 @@ func (r *ControlPlaneMachineSetGeneratorReconciler) generateControlPlaneMachineS if err != nil { return nil, fmt.Errorf("unable to generate control plane machine set spec: %w", err) } + case configv1.NutanixPlatformType: + cpmsSpecApplyConfig, err = generateControlPlaneMachineSetNutanixSpec(machines) + if err != nil { + return nil, fmt.Errorf("unable to generate control plane machine set spec: %w", err) + } default: logger.V(1).WithValues("platform", platformType).Info(unsupportedPlatform) return nil, errUnsupportedPlatform diff --git a/pkg/controllers/controlplanemachinesetgenerator/controller_test.go b/pkg/controllers/controlplanemachinesetgenerator/controller_test.go index 0e431f322..96e750cb0 100644 --- a/pkg/controllers/controlplanemachinesetgenerator/controller_test.go +++ b/pkg/controllers/controlplanemachinesetgenerator/controller_test.go @@ -18,11 +18,11 @@ package controlplanemachinesetgenerator import ( "context" + "fmt" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - configv1 "github.com/openshift/api/config/v1" machinev1 "github.com/openshift/api/machine/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" @@ -31,13 +31,17 @@ import ( corev1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/core/v1" machinev1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/machine/v1" machinev1beta1resourcebuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/machine/v1beta1" - "github.com/openshift/cluster-control-plane-machine-set-operator/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/envtest/komega" "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/openshift/cluster-control-plane-machine-set-operator/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig" ) var _ = Describe("controlplanemachinesetgenerator controller on AWS", func() { @@ -1870,3 +1874,321 @@ var _ = Describe("controlplanemachinesetgenerator controller on GCP", func() { }) }) }) + +type nutanixMachineProviderSpecBuilder struct{} + +func (n nutanixMachineProviderSpecBuilder) BuildRawExtension() *runtime.RawExtension { + nmpc := &machinev1.NutanixMachineProviderConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: machinev1.GroupVersion.String(), + Kind: "NutanixMachineProviderConfig", + }, + UserDataSecret: &corev1.LocalObjectReference{Name: "nutanix-user-data"}, + CredentialsSecret: &corev1.LocalObjectReference{Name: "nutanix-credentials"}, + Image: machinev1.NutanixResourceIdentifier{ + Type: machinev1.NutanixIdentifierName, + Name: pointer.String("rhcos"), + }, + Subnets: []machinev1.NutanixResourceIdentifier{{Type: machinev1.NutanixIdentifierName, Name: pointer.String("default-net")}}, + VCPUsPerSocket: int32(1), + VCPUSockets: int32(4), + MemorySize: resource.MustParse(fmt.Sprintf("%dMi", 8096)), + Cluster: machinev1.NutanixResourceIdentifier{ + Type: machinev1.NutanixIdentifierUUID, + UUID: pointer.String("7244448a-7fde-400d-bf2e-bd8521459859"), + }, + SystemDiskSize: resource.MustParse(fmt.Sprintf("%dGi", 120)), + } + + raw, err := json.Marshal(nmpc) + if err != nil { + // As we are building the input to json.Marshal, this should never happen. + panic(err) + } + + return &runtime.RawExtension{Raw: raw} +} + +var _ = Describe("controlplanemachinesetgenerator controller on Nutanix", func() { + var mgrCancel context.CancelFunc + var mgrDone chan struct{} + var mgr manager.Manager + var reconciler *ControlPlaneMachineSetGeneratorReconciler + + var namespaceName string + var cpms *machinev1.ControlPlaneMachineSet + var machine0, machine1, machine2 *machinev1beta1.Machine + var machineSet0, machineSet1, machineSet2, machineSet3, machineSet4 *machinev1beta1.MachineSet + + startManager := func(mgr *manager.Manager) (context.CancelFunc, chan struct{}) { + mgrCtx, mgrCancel := context.WithCancel(context.Background()) + mgrDone := make(chan struct{}) + + go func() { + defer GinkgoRecover() + defer close(mgrDone) + + Expect((*mgr).Start(mgrCtx)).To(Succeed()) + }() + + return mgrCancel, mgrDone + } + + stopManager := func() { + mgrCancel() + // Wait for the mgrDone to be closed, which will happen once the mgr has stopped + <-mgrDone + } + + create3MachineSets := func() { + machineSetBuilder := machinev1beta1resourcebuilder.MachineSet().WithNamespace(namespaceName) + machineSet0 = machineSetBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithGenerateName("machineset-1-").Build() + machineSet1 = machineSetBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithGenerateName("machineset-2-").Build() + machineSet2 = machineSetBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithGenerateName("machineset-3-").Build() + + Expect(k8sClient.Create(ctx, machineSet0)).To(Succeed()) + Expect(k8sClient.Create(ctx, machineSet1)).To(Succeed()) + Expect(k8sClient.Create(ctx, machineSet2)).To(Succeed()) + } + + create5MachineSets := func() { + create3MachineSets() + + machineSetBuilder := machinev1beta1resourcebuilder.MachineSet().WithNamespace(namespaceName) + machineSet3 = machineSetBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithGenerateName("machineset-3-").Build() + machineSet4 = machineSetBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithGenerateName("machineset-4-").Build() + + Expect(k8sClient.Create(ctx, machineSet3)).To(Succeed()) + Expect(k8sClient.Create(ctx, machineSet4)).To(Succeed()) + } + + create3CPMachines := func() *[]machinev1beta1.Machine { + // Create 3 control plane machines with differing Provider Specs, + // so then we can reliably check which machine Provider Spec is picked for the ControlPlaneMachineSet. + machineBuilder := machinev1beta1resourcebuilder.Machine().AsMaster().WithNamespace(namespaceName) + machine0 = machineBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithName("master-0").Build() + machine1 = machineBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithName("master-1").Build() + machine2 = machineBuilder.WithProviderSpecBuilder(nutanixMachineProviderSpecBuilder{}).WithName("master-2").Build() + + // Create Machines with some wait time between them + // to achieve staggered CreationTimestamp(s). + Expect(k8sClient.Create(ctx, machine0)).To(Succeed()) + Expect(k8sClient.Create(ctx, machine1)).To(Succeed()) + Expect(k8sClient.Create(ctx, machine2)).To(Succeed()) + + return &[]machinev1beta1.Machine{*machine0, *machine1, *machine2} + } + + BeforeEach(func() { + Expect(k8sClient).NotTo(BeNil()) + By("Setting up a namespace for the test") + ns := corev1resourcebuilder.Namespace().WithGenerateName("control-plane-machine-set-controller-").Build() + Expect(k8sClient.Create(ctx, ns)).To(Succeed()) + namespaceName = ns.GetName() + + By("Setting up a new infrastructure for the test") + // Create infrastructure object. + infra := configv1resourcebuilder.Infrastructure().WithName(infrastructureName).Build() + infra.Status.ControlPlaneTopology = configv1.HighlyAvailableTopologyMode + infra.Status.InfrastructureTopology = configv1.HighlyAvailableTopologyMode + infra.Status.PlatformStatus = &configv1.PlatformStatus{ + Type: configv1.NutanixPlatformType, + Nutanix: &configv1.NutanixPlatformStatus{}, + } + infraStatus := infra.Status.DeepCopy() + Expect(k8sClient.Create(ctx, infra)).To(Succeed()) + // Update Infrastructure Status. + Eventually(komega.UpdateStatus(infra, func() { + infra.Status = *infraStatus + })).Should(Succeed()) + + By("Setting up a manager and controller") + var err error + mgr, err = ctrl.NewManager(cfg, ctrl.Options{ + Scheme: testScheme, + MetricsBindAddress: "0", + Port: testEnv.WebhookInstallOptions.LocalServingPort, + Host: testEnv.WebhookInstallOptions.LocalServingHost, + CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir, + }) + Expect(err).ToNot(HaveOccurred(), "Manager should be able to be created") + reconciler = &ControlPlaneMachineSetGeneratorReconciler{ + Client: mgr.GetClient(), + Namespace: namespaceName, + } + Expect(reconciler.SetupWithManager(mgr)).To(Succeed(), "Reconciler should be able to setup with manager") + }) + + AfterEach(func() { + testutils.CleanupResources(Default, ctx, cfg, k8sClient, namespaceName, + &corev1.Node{}, + &machinev1beta1.Machine{}, + &configv1.Infrastructure{}, + &machinev1beta1.MachineSet{}, + &machinev1.ControlPlaneMachineSet{}, + ) + }) + + JustBeforeEach(func() { + By("Starting the manager") + mgrCancel, mgrDone = startManager(&mgr) + }) + + JustAfterEach(func() { + By("Stopping the manager") + stopManager() + }) + + Context("when a Control Plane Machine Set doesn't exist", func() { + BeforeEach(func() { + cpms = &machinev1.ControlPlaneMachineSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterControlPlaneMachineSetName, + Namespace: namespaceName, + }, + } + }) + + Context("with 5 Machine Sets", func() { + BeforeEach(func() { + By("Creating MachineSets") + create5MachineSets() + }) + + Context("with 3 existing control plane machines", func() { + BeforeEach(func() { + By("Creating Control Plane Machines") + create3CPMachines() + }) + + It("should create the ControlPlaneMachineSet with the expected fields", func() { + By("Checking the Control Plane Machine Set has been created") + Eventually(komega.Get(cpms)).Should(Succeed()) + Expect(cpms.Spec.State).To(Equal(machinev1.ControlPlaneMachineSetStateInactive)) + Expect(*cpms.Spec.Replicas).To(Equal(int32(3))) + }) + + It("should create the ControlPlaneMachineSet with the provider spec matching the youngest machine provider spec", func() { + By("Checking the Control Plane Machine Set has been created") + Eventually(komega.Get(cpms)).Should(Succeed()) + // In this case expect the machine Provider Spec of the youngest machine to be used here. + // In this case it should be `machine-2` given that's the one we created last. + cpmsProviderSpec, err := providerconfig.NewProviderConfigFromMachineSpec(cpms.Spec.Template.OpenShiftMachineV1Beta1Machine.Spec) + Expect(err).To(BeNil()) + + machineProviderSpec, err := providerconfig.NewProviderConfigFromMachineSpec(machine2.Spec) + Expect(err).To(BeNil()) + + machineProviderConfig := machineProviderSpec.Generic() + + Expect(cpmsProviderSpec.Generic()).To(Equal(machineProviderConfig)) + }) + }) + }) + + Context("with 3 Machine Sets", func() { + BeforeEach(func() { + By("Creating MachineSets") + create3MachineSets() + }) + + Context("with 3 existing control plane machines", func() { + BeforeEach(func() { + By("Creating Control Plane Machines") + create3CPMachines() + }) + + It("should create the ControlPlaneMachineSet with the expected fields", func() { + By("Checking the Control Plane Machine Set has been created") + Eventually(komega.Get(cpms)).Should(Succeed()) + Expect(cpms.Spec.State).To(Equal(machinev1.ControlPlaneMachineSetStateInactive)) + Expect(*cpms.Spec.Replicas).To(Equal(int32(3))) + }) + + It("should create the ControlPlaneMachineSet with the provider spec matching the youngest machine provider spec", func() { + By("Checking the Control Plane Machine Set has been created") + Eventually(komega.Get(cpms)).Should(Succeed()) + // In this case expect the machine Provider Spec of the youngest machine to be used here. + // In this case it should be `machine-2` given that's the one we created last. + cpmsProviderSpec, err := providerconfig.NewProviderConfigFromMachineSpec(cpms.Spec.Template.OpenShiftMachineV1Beta1Machine.Spec) + Expect(err).To(BeNil()) + + machineProviderSpec, err := providerconfig.NewProviderConfigFromMachineSpec(machine2.Spec) + Expect(err).To(BeNil()) + + // Remove from the machine Provider Spec the fields that won't be + // present on the ControlPlaneMachineSet Provider Spec. + machineProviderConfig := machineProviderSpec.Generic() + + Expect(cpmsProviderSpec.Generic()).To(Equal(machineProviderConfig)) + }) + }) + }) + + Context("with only 1 existing control plane machine", func() { + var logger testutils.TestLogger + isSupportedControlPlaneMachinesNumber := false + + BeforeEach(func() { + By("Creating 1 Control Plane Machine") + machineBuilder := machinev1beta1resourcebuilder.Machine().AsMaster().WithNamespace(namespaceName) + machine2 = machineBuilder.WithName("master-2").Build() + Expect(k8sClient.Create(ctx, machine2)).To(Succeed()) + machines := []machinev1beta1.Machine{*machine2} + + By("Invoking the check on whether the number of control plane machines in the cluster is supported") + logger = testutils.NewTestLogger() + isSupportedControlPlaneMachinesNumber = reconciler.isSupportedControlPlaneMachinesNumber(logger.Logger(), machines) + }) + + It("should have not created the ControlPlaneMachineSet", func() { + Consistently(komega.Get(cpms)).Should(MatchError("controlplanemachinesets.machine.openshift.io \"" + clusterControlPlaneMachineSetName + "\" not found")) + }) + + It("should detect the cluster has an unsupported number of control plane machines", func() { + Expect(isSupportedControlPlaneMachinesNumber).To(BeFalse()) + }) + + It("sets an appropriate log line", func() { + Eventually(logger.Entries()).Should(ConsistOf( + testutils.LogEntry{ + Level: 1, + KeysAndValues: []interface{}{"count", 1}, + Message: unsupportedNumberOfControlPlaneMachines, + }, + )) + }) + }) + + Context("with an unsupported platform", func() { + var logger testutils.TestLogger + BeforeEach(func() { + By("Creating MachineSets") + create5MachineSets() + + By("Creating Control Plane Machines") + machines := create3CPMachines() + + logger = testutils.NewTestLogger() + generatedCPMS, err := reconciler.generateControlPlaneMachineSet(logger.Logger(), configv1.NonePlatformType, *machines, nil) + Expect(generatedCPMS).To(BeNil()) + Expect(err).To(MatchError(errUnsupportedPlatform)) + }) + + It("should have not created the ControlPlaneMachineSet", func() { + Consistently(komega.Get(cpms)).Should(MatchError("controlplanemachinesets.machine.openshift.io \"" + clusterControlPlaneMachineSetName + "\" not found")) + }) + + It("sets an appropriate log line", func() { + Eventually(logger.Entries()).Should(ConsistOf( + testutils.LogEntry{ + Level: 1, + KeysAndValues: []interface{}{"platform", configv1.NonePlatformType}, + Message: unsupportedPlatform, + }, + )) + }) + + }) + }) +}) diff --git a/pkg/controllers/controlplanemachinesetgenerator/nutanix.go b/pkg/controllers/controlplanemachinesetgenerator/nutanix.go new file mode 100644 index 000000000..662b2da56 --- /dev/null +++ b/pkg/controllers/controlplanemachinesetgenerator/nutanix.go @@ -0,0 +1,67 @@ +/* +Copyright 2022 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controlplanemachinesetgenerator + +import ( + "fmt" + + machinev1beta1 "github.com/openshift/api/machine/v1beta1" + machinev1builder "github.com/openshift/client-go/machine/applyconfigurations/machine/v1" + machinev1beta1builder "github.com/openshift/client-go/machine/applyconfigurations/machine/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/openshift/cluster-control-plane-machine-set-operator/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig" +) + +// generateControlPlaneMachineSetNutanixSpec generates a Nutanix flavored ControlPlaneMachineSet Spec. +func generateControlPlaneMachineSetNutanixSpec(machines []machinev1beta1.Machine) (machinev1builder.ControlPlaneMachineSetSpecApplyConfiguration, error) { + controlPlaneMachineSetMachineSpecApplyConfig, err := buildControlPlaneMachineSetNutanixMachineSpec(machines) + if err != nil { + return machinev1builder.ControlPlaneMachineSetSpecApplyConfiguration{}, fmt.Errorf("failed to build ControlPlaneMachineSet's Nutanix spec: %w", err) + } + + // We want to work with the newest machine. + controlPlaneMachineSetApplyConfigSpec := genericControlPlaneMachineSetSpec(replicas, machines[0].ObjectMeta.Labels[clusterIDLabelKey]) + controlPlaneMachineSetApplyConfigSpec.Template.OpenShiftMachineV1Beta1Machine.Spec = controlPlaneMachineSetMachineSpecApplyConfig + + return controlPlaneMachineSetApplyConfigSpec, nil +} + +// buildControlPlaneMachineSetNutanixMachineSpec builds a Nutanix flavored MachineSpec for the ControlPlaneMachineSet. +func buildControlPlaneMachineSetNutanixMachineSpec(machines []machinev1beta1.Machine) (*machinev1beta1builder.MachineSpecApplyConfiguration, error) { + // The machines slice is sorted by the creation time. + // We want to get the provider config for the newest machine. + providerConfig, err := providerconfig.NewProviderConfigFromMachineSpec(machines[0].Spec) + if err != nil { + return nil, fmt.Errorf("failed to extract machine's providerSpec: %w", err) + } + + rawBytes, err := providerConfig.RawConfig() + if err != nil { + return nil, fmt.Errorf("error marshalling providerSpec: %w", err) + } + + re := runtime.RawExtension{ + Raw: rawBytes, + } + + msac := &machinev1beta1builder.MachineSpecApplyConfiguration{ + ProviderSpec: &machinev1beta1builder.ProviderSpecApplyConfiguration{Value: &re}, + } + + return msac, nil +} diff --git a/pkg/controllers/controlplanemachinesetgenerator/utils.go b/pkg/controllers/controlplanemachinesetgenerator/utils.go index 3ccc0d153..ce51a18e1 100644 --- a/pkg/controllers/controlplanemachinesetgenerator/utils.go +++ b/pkg/controllers/controlplanemachinesetgenerator/utils.go @@ -60,6 +60,8 @@ func sortMachineSetsByCreationTimeAscending(machineSets []machinev1beta1.Machine } // genericControlPlaneMachineSetSpec returns a generic ControlPlaneMachineSet spec, without provider specific details. +// +//nolint:unparam func genericControlPlaneMachineSetSpec(replicas int32, clusterID string) machinev1builder.ControlPlaneMachineSetSpecApplyConfiguration { labels := map[string]string{ clusterIDLabelKey: clusterID, diff --git a/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/nutanix.go b/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/nutanix.go new file mode 100644 index 000000000..eaefdbe33 --- /dev/null +++ b/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/nutanix.go @@ -0,0 +1,54 @@ +/* +Copyright 2023 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package providerconfig + +import ( + "encoding/json" + "fmt" + + configv1 "github.com/openshift/api/config/v1" + machinev1 "github.com/openshift/api/machine/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// NutanixProviderConfig is a wrapper around machinev1.NutanixMachineProviderConfig. +type NutanixProviderConfig struct { + providerConfig machinev1.NutanixMachineProviderConfig +} + +// Config returns the stored NutanixMachineProviderConfig. +func (n NutanixProviderConfig) Config() machinev1.NutanixMachineProviderConfig { + return n.providerConfig +} + +func newNutanixProviderConfig(raw *runtime.RawExtension) (ProviderConfig, error) { + nutanixMachineProviderconfig := machinev1.NutanixMachineProviderConfig{} + if err := json.Unmarshal(raw.Raw, &nutanixMachineProviderconfig); err != nil { + return providerConfig{}, fmt.Errorf("unable to unmarshal provider config: %w", err) + } + + npc := NutanixProviderConfig{ + providerConfig: nutanixMachineProviderconfig, + } + + config := providerConfig{ + platformType: configv1.NutanixPlatformType, + nutanix: npc, + } + + return config, nil +} diff --git a/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/providerconfig.go b/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/providerconfig.go index 4d2c865f2..bdadd588d 100644 --- a/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/providerconfig.go +++ b/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig/providerconfig.go @@ -79,6 +79,9 @@ type ProviderConfig interface { // GCP returns the GCPProviderConfig if the platform type is GCP. GCP() GCPProviderConfig + // Nutanix returns the NutanixProviderConfig if the platform type is Nutanix. + Nutanix() NutanixProviderConfig + // Generic returns the GenericProviderConfig if we are on a platform that is using generic provider abstraction. Generic() GenericProviderConfig } @@ -115,6 +118,8 @@ func newProviderConfigFromProviderSpec(providerSpec machinev1beta1.ProviderSpec, return newAzureProviderConfig(providerSpec.Value) case configv1.GCPPlatformType: return newGCPProviderConfig(providerSpec.Value) + case configv1.NutanixPlatformType: + return newNutanixProviderConfig(providerSpec.Value) case configv1.NonePlatformType: return nil, fmt.Errorf("%w: %s", errUnsupportedPlatformType, platformType) default: @@ -128,6 +133,7 @@ type providerConfig struct { aws AWSProviderConfig azure AzureProviderConfig gcp GCPProviderConfig + nutanix NutanixProviderConfig generic GenericProviderConfig } @@ -189,6 +195,8 @@ func (p providerConfig) Diff(other ProviderConfig) ([]string, error) { return deep.Equal(p.azure.providerConfig, other.Azure().providerConfig), nil case configv1.GCPPlatformType: return deep.Equal(p.gcp.providerConfig, other.GCP().providerConfig), nil + case configv1.NutanixPlatformType: + return deep.Equal(p.nutanix.providerConfig, other.Nutanix().providerConfig), nil case configv1.NonePlatformType: return nil, errUnsupportedPlatformType default: @@ -213,6 +221,8 @@ func (p providerConfig) Equal(other ProviderConfig) (bool, error) { return reflect.DeepEqual(p.azure.providerConfig, other.Azure().providerConfig), nil case configv1.GCPPlatformType: return reflect.DeepEqual(p.gcp.providerConfig, other.GCP().providerConfig), nil + case configv1.NutanixPlatformType: + return reflect.DeepEqual(p.nutanix.providerConfig, other.Nutanix().providerConfig), nil case configv1.NonePlatformType: return false, errUnsupportedPlatformType default: @@ -234,6 +244,8 @@ func (p providerConfig) RawConfig() ([]byte, error) { rawConfig, err = json.Marshal(p.azure.providerConfig) case configv1.GCPPlatformType: rawConfig, err = json.Marshal(p.gcp.providerConfig) + case configv1.NutanixPlatformType: + rawConfig, err = json.Marshal(p.nutanix.providerConfig) case configv1.NonePlatformType: return nil, errUnsupportedPlatformType default: @@ -267,6 +279,11 @@ func (p providerConfig) GCP() GCPProviderConfig { return p.gcp } +// Nutanix returns the NutanixProviderConfig if the platform type is Nutanix. +func (p providerConfig) Nutanix() NutanixProviderConfig { + return p.nutanix +} + // Generic returns the GenericProviderConfig if the platform type is generic. func (p providerConfig) Generic() GenericProviderConfig { return p.generic @@ -276,9 +293,10 @@ func (p providerConfig) Generic() GenericProviderConfig { // When platform is unknown, it returns "UnknownPlatform". func getPlatformTypeFromProviderSpecKind(kind string) configv1.PlatformType { var providerSpecKindToPlatformType = map[string]configv1.PlatformType{ - "AWSMachineProviderConfig": configv1.AWSPlatformType, - "AzureMachineProviderSpec": configv1.AzurePlatformType, - "GCPMachineProviderSpec": configv1.GCPPlatformType, + "AWSMachineProviderConfig": configv1.AWSPlatformType, + "AzureMachineProviderSpec": configv1.AzurePlatformType, + "GCPMachineProviderSpec": configv1.GCPPlatformType, + "NutanixMachineProviderConfig": configv1.NutanixPlatformType, } platformType, ok := providerSpecKindToPlatformType[kind] diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index a80f519fd..d0f5eb895 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -222,6 +222,8 @@ func (f *framework) IncreaseProviderSpecInstanceSize(rawProviderSpec *runtime.Ra return increaseAzureInstanceSize(rawProviderSpec, providerConfig) case configv1.GCPPlatformType: return increaseGCPInstanceSize(rawProviderSpec, providerConfig) + case configv1.NutanixPlatformType: + return increaseNutanixInstanceSize(rawProviderSpec, providerConfig) default: return fmt.Errorf("%w: %s", errUnsupportedPlatform, f.platform) } @@ -246,6 +248,8 @@ func (f *framework) UpdateDefaultedValueFromCPMS(rawProviderSpec *runtime.RawExt return updateCredentialsSecretNameAWS(providerConfig) case configv1.GCPPlatformType: return updateCredentialsSecretNameGCP(providerConfig) + case configv1.NutanixPlatformType: + return updateCredentialsSecretNameNutanix(providerConfig) default: return nil, fmt.Errorf("%w: %s", errUnsupportedPlatform, f.platform) } @@ -296,6 +300,21 @@ func updateCredentialsSecretNameGCP(providerConfig providerconfig.ProviderConfig }, nil } +// updateCredentialsSecretNameNutanix updates the credentialSecret field from the ControlPlaneMachineSet. +func updateCredentialsSecretNameNutanix(providerConfig providerconfig.ProviderConfig) (*runtime.RawExtension, error) { + cfg := providerConfig.Nutanix().Config() + cfg.CredentialsSecret = nil + + rawBytes, err := json.Marshal(cfg) + if err != nil { + return nil, fmt.Errorf("error marshalling nutanix providerSpec: %w", err) + } + + return &runtime.RawExtension{ + Raw: rawBytes, + }, nil +} + // ConvertToControlPlaneMachineSetProviderSpec converts a control plane machine provider spec // to a raw, control plane machine set suitable provider spec. func (f *framework) ConvertToControlPlaneMachineSetProviderSpec(providerSpec machinev1beta1.ProviderSpec) (*runtime.RawExtension, error) { @@ -313,6 +332,8 @@ func (f *framework) ConvertToControlPlaneMachineSetProviderSpec(providerSpec mac return convertAzureProviderConfigToControlPlaneMachineSetProviderSpec(providerConfig) case configv1.GCPPlatformType: return convertGCPProviderConfigToControlPlaneMachineSetProviderSpec(providerConfig) + case configv1.NutanixPlatformType: + return convertNutanixProviderConfigToControlPlaneMachineSetProviderSpec(providerConfig) default: return nil, fmt.Errorf("%w: %s", errUnsupportedPlatform, f.platform) } @@ -367,6 +388,21 @@ func convertAzureProviderConfigToControlPlaneMachineSetProviderSpec(providerConf }, nil } +// convertNutanixProviderConfigToControlPlaneMachineSetProviderSpec converts a Nutanix providerConfig into a +// raw control plane machine set provider spec. +func convertNutanixProviderConfigToControlPlaneMachineSetProviderSpec(providerConfig providerconfig.ProviderConfig) (*runtime.RawExtension, error) { + nutanixProviderConfig := providerConfig.Nutanix().Config() + + rawBytes, err := json.Marshal(nutanixProviderConfig) + if err != nil { + return nil, fmt.Errorf("error marshalling nutanix providerSpec: %w", err) + } + + return &runtime.RawExtension{ + Raw: rawBytes, + }, nil +} + // loadClient returns a new controller-runtime client. func loadClient(sch *runtime.Scheme) (runtimeclient.Client, error) { cfg, err := config.GetConfig() @@ -429,6 +465,8 @@ func getPlatformSupportLevel(k8sClient runtimeclient.Client) (PlatformSupportLev return Manual, platformType, nil case configv1.GCPPlatformType: return Manual, platformType, nil + case configv1.NutanixPlatformType: + return Manual, platformType, nil default: return Unsupported, platformType, nil } @@ -563,6 +601,18 @@ func increaseGCPInstanceSize(rawProviderSpec *runtime.RawExtension, providerConf return nil } +// increateNutanixInstanceSize increases the instance size of the instance on the providerSpec for an Nutanix providerSpec. +func increaseNutanixInstanceSize(rawProviderSpec *runtime.RawExtension, providerConfig providerconfig.ProviderConfig) error { + cfg := providerConfig.Nutanix().Config() + cfg.VCPUSockets++ + + if err := setProviderSpecValue(rawProviderSpec, cfg); err != nil { + return fmt.Errorf("failed to set provider spec value: %w", err) + } + + return nil +} + // nextGCPVMSize returns the next GCP machine size in the series. // The Machine sizes being used are in format -standard-. func nextGCPMachineSize(current string) (string, error) {