From 63216a4d3b42b4f04c8474e4226982c559719b4e Mon Sep 17 00:00:00 2001 From: Deepak Muley Date: Tue, 21 May 2024 09:19:43 -0700 Subject: [PATCH] feat: Added FailureDomains as mutation in caren --- ...ren.nutanix.com_nutanixclusterconfigs.yaml | 77 +++++++ api/v1alpha1/nutanix_clusterconfig_types.go | 44 ++++ api/v1alpha1/zz_generated.deepcopy.go | 55 ++++- .../customization/nutanix/failure-domains.md | 93 +++++++++ .../nutanix/mutation/failuredomains/inject.go | 126 +++++++++++ .../mutation/failuredomains/inject_test.go | 82 ++++++++ .../mutation/failuredomains/variables_test.go | 195 ++++++++++++++++++ .../nutanix/mutation/metapatch_handler.go | 2 + 8 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 docs/content/customization/nutanix/failure-domains.md create mode 100644 pkg/handlers/nutanix/mutation/failuredomains/inject.go create mode 100644 pkg/handlers/nutanix/mutation/failuredomains/inject_test.go create mode 100644 pkg/handlers/nutanix/mutation/failuredomains/variables_test.go diff --git a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml index a9c3a0106..6f54c8bc9 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml @@ -572,6 +572,83 @@ spec: - host - port type: object + failureDomains: + description: |- + failureDomains configures failure domains information for the Nutanix platform. + When set, the failure domains defined here may be used to spread Machines across + prism element clusters to improve fault tolerance of the cluster. + items: + description: NutanixFailureDomain configures failure domain information for Nutanix. + properties: + cluster: + description: |- + cluster is to identify the cluster (the Prism Element under management of the Prism Central), + in which the Machine's VM will be created. The cluster identifier (uuid or name) can be obtained + from the Prism Central console or using the prism_central API. + properties: + name: + description: name is the resource name in the PC + type: string + type: + description: Type is the identifier type to use for this resource. + enum: + - uuid + - name + type: string + uuid: + description: uuid is the UUID of the resource in the PC. + type: string + required: + - type + type: object + controlPlane: + description: indicates if a failure domain is suited for control plane nodes + type: boolean + name: + description: |- + name defines the unique name of a failure domain. + Name is required and must be at most 64 characters in length. + It must consist of only lower case alphanumeric characters and hyphens (-). + It must start and end with an alphanumeric character. + This value is arbitrary and is used to identify the failure domain within the platform. + maxLength: 64 + minLength: 1 + pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?' + type: string + subnets: + description: |- + subnets holds a list of identifiers (one or more) of the cluster's network subnets + for the Machine's VM to connect to. The subnet identifiers (uuid or name) can be + obtained from the Prism Central console or using the prism_central API. + items: + description: NutanixResourceIdentifier holds the identity of a Nutanix PC resource (cluster, image, subnet, etc.) + properties: + name: + description: name is the resource name in the PC + type: string + type: + description: Type is the identifier type to use for this resource. + enum: + - uuid + - name + type: string + uuid: + description: uuid is the UUID of the resource in the PC. + type: string + required: + - type + type: object + minItems: 1 + type: array + required: + - cluster + - name + - subnets + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map prismCentralEndpoint: description: Nutanix Prism Central endpoint configuration. properties: diff --git a/api/v1alpha1/nutanix_clusterconfig_types.go b/api/v1alpha1/nutanix_clusterconfig_types.go index b49ac398e..f4eea0507 100644 --- a/api/v1alpha1/nutanix_clusterconfig_types.go +++ b/api/v1alpha1/nutanix_clusterconfig_types.go @@ -7,6 +7,8 @@ import ( "fmt" "net/url" "strconv" + + capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" ) const ( @@ -23,6 +25,14 @@ type NutanixSpec struct { // Nutanix Prism Central endpoint configuration. // +kubebuilder:validation:Required PrismCentralEndpoint NutanixPrismCentralEndpointSpec `json:"prismCentralEndpoint"` + + // failureDomains configures failure domains information for the Nutanix platform. + // When set, the failure domains defined here may be used to spread Machines across + // prism element clusters to improve fault tolerance of the cluster. + // +listType=map + // +listMapKey=name + // +kubebuilder:validation:Optional + FailureDomains []NutanixFailureDomain `json:"failureDomains,omitempty"` } type NutanixPrismCentralEndpointSpec struct { @@ -76,3 +86,37 @@ func (s NutanixPrismCentralEndpointSpec) ParseURL() (string, int32, error) { return hostname, int32(port), nil } + +// NutanixFailureDomains is a list of FDs. +type NutanixFailureDomains []NutanixFailureDomain + +// NutanixFailureDomain configures failure domain information for Nutanix. +type NutanixFailureDomain struct { + // name defines the unique name of a failure domain. + // Name is required and must be at most 64 characters in length. + // It must consist of only lower case alphanumeric characters and hyphens (-). + // It must start and end with an alphanumeric character. + // This value is arbitrary and is used to identify the failure domain within the platform. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=64 + // +kubebuilder:validation:Pattern=`[a-z0-9]([-a-z0-9]*[a-z0-9])?` + Name string `json:"name"` + + // cluster is to identify the cluster (the Prism Element under management of the Prism Central), + // in which the Machine's VM will be created. The cluster identifier (uuid or name) can be obtained + // from the Prism Central console or using the prism_central API. + // +kubebuilder:validation:Required + Cluster capxv1.NutanixResourceIdentifier `json:"cluster"` + + // subnets holds a list of identifiers (one or more) of the cluster's network subnets + // for the Machine's VM to connect to. The subnet identifiers (uuid or name) can be + // obtained from the Prism Central console or using the prism_central API. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + Subnets []capxv1.NutanixResourceIdentifier `json:"subnets"` + + // indicates if a failure domain is suited for control plane nodes + // +kubebuilder:validation:Required + ControlPlane bool `json:"controlPlane,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1a27f596a..2d64528c6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1203,6 +1203,50 @@ func (in *NutanixClusterConfigSpec) DeepCopy() *NutanixClusterConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NutanixFailureDomain) DeepCopyInto(out *NutanixFailureDomain) { + *out = *in + in.Cluster.DeepCopyInto(&out.Cluster) + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]v1beta1.NutanixResourceIdentifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixFailureDomain. +func (in *NutanixFailureDomain) DeepCopy() *NutanixFailureDomain { + if in == nil { + return nil + } + out := new(NutanixFailureDomain) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in NutanixFailureDomains) DeepCopyInto(out *NutanixFailureDomains) { + { + in := &in + *out = make(NutanixFailureDomains, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixFailureDomains. +func (in NutanixFailureDomains) DeepCopy() NutanixFailureDomains { + if in == nil { + return nil + } + out := new(NutanixFailureDomains) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NutanixMachineDetails) DeepCopyInto(out *NutanixMachineDetails) { *out = *in @@ -1309,7 +1353,9 @@ func (in *NutanixNodeSpec) DeepCopy() *NutanixNodeSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NutanixPrismCentralEndpointCredentials) DeepCopyInto(out *NutanixPrismCentralEndpointCredentials) { +func (in *NutanixPrismCentralEndpointCredentials) DeepCopyInto( + out *NutanixPrismCentralEndpointCredentials, +) { *out = *in out.SecretRef = in.SecretRef } @@ -1345,6 +1391,13 @@ func (in *NutanixSpec) DeepCopyInto(out *NutanixSpec) { *out = *in in.ControlPlaneEndpoint.DeepCopyInto(&out.ControlPlaneEndpoint) out.PrismCentralEndpoint = in.PrismCentralEndpoint + if in.FailureDomains != nil { + in, out := &in.FailureDomains, &out.FailureDomains + *out = make([]NutanixFailureDomain, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixSpec. diff --git a/docs/content/customization/nutanix/failure-domains.md b/docs/content/customization/nutanix/failure-domains.md new file mode 100644 index 000000000..7e66f2bdd --- /dev/null +++ b/docs/content/customization/nutanix/failure-domains.md @@ -0,0 +1,93 @@ ++++ +title = "Failure Domains" ++++ + +Configure Failure Domains. Defines the Prism Element Cluster and subnets to use for creating Control Plane or Worker +node VMs of Kubernetes Cluster. + +## Examples + +### Configure one or more Failure Domains + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + nutanix: + failureDomains: + - cluster: + name: pe-cluster-name-1 + type: name + controlPlane: true + name: failure-domain-name-1 + subnets: + - name: subnet-name-1 + type: name + - cluster: + name: pe-cluster-name-2 + type: name + controlPlane: true + name: failure-domain-name-2 + subnets: + - name: subnet-name-2 + type: name + +``` + +Applying this configuration will result in the following value being set: + +- `NutanixCluster`: + +```yaml +spec: + template: + spec: + failureDomains: + - cluster: + name: pe-cluster-name-1 + type: name + controlPlane: true + name: failure-domain-name-1 + subnets: + - name: subnet-name-1 + type: name + - cluster: + name: pe-cluster-name-2 + type: name + controlPlane: true + name: failure-domain-name-2 + subnets: + - name: subnet-name-2 + type: name +``` + +Note: + +- Configuring Failure Domains is optional and if not configured then respective NutanixMachineTemplate's cluster and +subnets will be used to create respective control plane and worker nodes + +- Only one Failure Domain can be used per Machine Deployment. Worker nodes will be created on respective Prism Element +cluster and subnet of the respective failure domain. + +- Control plane nodes will be created on every failure domain's cluster which has ControlPlane boolean set to true. + +Following is the way to set failure Domain to each Machine Deployment + +- `NutanixCluster`: + +```yaml + workers: + machineDeployments: + - class: default-worker + name: md-0 + failureDomain: failure-domain-name-1 + - class: default-worker + name: md-1 + failureDomain: failure-domain-name-2 +``` diff --git a/pkg/handlers/nutanix/mutation/failuredomains/inject.go b/pkg/handlers/nutanix/mutation/failuredomains/inject.go new file mode 100644 index 000000000..dcad7a091 --- /dev/null +++ b/pkg/handlers/nutanix/mutation/failuredomains/inject.go @@ -0,0 +1,126 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package failuredomains + +import ( + "context" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" +) + +const ( + // VariableName is the external patch variable name. + VariableName = "failureDomains" +) + +type nutanixFailureDomains struct { + variableName string + variableFieldPath []string +} + +func NewPatch() *nutanixFailureDomains { + return newnutanixFailureDomains( + v1alpha1.ClusterConfigVariableName, + v1alpha1.NutanixVariableName, + VariableName, + ) +} + +func newnutanixFailureDomains( + variableName string, + variableFieldPath ...string, +) *nutanixFailureDomains { + return &nutanixFailureDomains{ + variableName: variableName, + variableFieldPath: variableFieldPath, + } +} + +func (h *nutanixFailureDomains) Mutate( + ctx context.Context, + obj *unstructured.Unstructured, + vars map[string]apiextensionsv1.JSON, + holderRef runtimehooksv1.HolderReference, + _ client.ObjectKey, + _ mutation.ClusterGetter, +) error { + log := ctrl.LoggerFrom(ctx).WithValues( + "holderRef", holderRef, + ) + + failureDomainsVar, err := variables.Get[v1alpha1.NutanixFailureDomains]( + vars, + h.variableName, + h.variableFieldPath..., + ) + if err != nil { + if variables.IsNotFoundError(err) { + log.V(5).Info("Nutanix failureDomains variable not defined") + return nil + } + return err + } + + log = log.WithValues( + "variableName", + h.variableName, + "variableFieldPath", + h.variableFieldPath, + "variableValue", + failureDomainsVar, + ) + + failureDomains := make([]capxv1.NutanixFailureDomain, 0, len(failureDomainsVar)) + for _, fd := range failureDomainsVar { + subnets := make([]capxv1.NutanixResourceIdentifier, 0, len(fd.Subnets)) + for _, subnet := range fd.Subnets { + subnets = append(subnets, capxv1.NutanixResourceIdentifier{ + Type: subnet.Type, + Name: subnet.Name, + UUID: subnet.UUID, + }) + } + cluster := capxv1.NutanixResourceIdentifier{ + Type: fd.Cluster.Type, + Name: fd.Cluster.Name, + UUID: fd.Cluster.UUID, + } + + failureDomains = append(failureDomains, capxv1.NutanixFailureDomain{ + Name: fd.Name, + Cluster: cluster, + Subnets: subnets, + ControlPlane: fd.ControlPlane, + }) + } + + return patches.MutateIfApplicable( + obj, + vars, + &holderRef, + selectors.InfrastructureCluster(capxv1.GroupVersion.Version, "NutanixClusterTemplate"), + log, + func(obj *capxv1.NutanixClusterTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", client.ObjectKeyFromObject(obj), + ).Info("setting controlPlaneEndpoint in NutanixCluster spec") + + obj.Spec.Template.Spec.FailureDomains = failureDomains + + return nil + }, + ) +} diff --git a/pkg/handlers/nutanix/mutation/failuredomains/inject_test.go b/pkg/handlers/nutanix/mutation/failuredomains/inject_test.go new file mode 100644 index 000000000..0c5b8a013 --- /dev/null +++ b/pkg/handlers/nutanix/mutation/failuredomains/inject_test.go @@ -0,0 +1,82 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package failuredomains + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +func TestFailureDomainsPatch(t *testing.T) { + gomega.RegisterFailHandler(Fail) + RunSpecs(t, "Nutanix FailureDomains suite") +} + +var _ = Describe("Generate Nutanix FailureDomains endpoint patches", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "FailureDomains set to valid information", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.NutanixFailureDomains{ + v1alpha1.NutanixFailureDomain{ + Name: "fd1", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + }, + v1alpha1.NutanixVariableName, + VariableName, + ), + }, + RequestItem: request.NewNutanixClusterTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "replace", + Path: "/spec/template/spec/failureDomains", + ValueMatcher: gomega.HaveLen(1), + }, + }, + }, + } + + // create test node for each case + for testIdx := range testDefs { + tt := testDefs[testIdx] + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/nutanix/mutation/failuredomains/variables_test.go b/pkg/handlers/nutanix/mutation/failuredomains/variables_test.go new file mode 100644 index 000000000..53e97820b --- /dev/null +++ b/pkg/handlers/nutanix/mutation/failuredomains/variables_test.go @@ -0,0 +1,195 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package failuredomains + +import ( + "fmt" + "testing" + + "k8s.io/utils/ptr" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + nutanixclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/clusterconfig" +) + +var testPrismCentralURL = fmt.Sprintf( + "https://prism-central.nutanix.com:%d", + v1alpha1.DefaultPrismCentralPort, +) + +var ( + testPEName string = "PE1" + testSubnetName string = "Subnet1" +) + +func TestVariableValidation(t *testing.T) { + capitest.ValidateDiscoverVariables( + t, + v1alpha1.ClusterConfigVariableName, + ptr.To(v1alpha1.NutanixClusterConfig{}.VariableSchema()), + true, + nutanixclusterconfig.NewVariable, + capitest.VariableTestDef{ + Name: "failure domains not provided", + Vals: v1alpha1.NutanixClusterConfigSpec{ + Nutanix: &v1alpha1.NutanixSpec{ + ControlPlaneEndpoint: v1alpha1.ControlPlaneEndpointSpec{ + Host: "10.20.100.10", + Port: 6443, + }, + // PrismCentralEndpoint is a required field and must always be set + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + URL: testPrismCentralURL, + Credentials: v1alpha1.NutanixPrismCentralEndpointCredentials{ + SecretRef: v1alpha1.LocalObjectReference{ + Name: "credentials", + }, + }, + }, + }, + }, + }, + capitest.VariableTestDef{ + Name: "1 failure domain provided", + Vals: v1alpha1.NutanixClusterConfigSpec{ + Nutanix: &v1alpha1.NutanixSpec{ + // ControlPlaneEndpoint is a required field and must always be set + ControlPlaneEndpoint: v1alpha1.ControlPlaneEndpointSpec{ + Host: "10.20.100.10", + Port: 6443, + }, + // PrismCentralEndpoint is a required field and must always be set + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + URL: testPrismCentralURL, + Credentials: v1alpha1.NutanixPrismCentralEndpointCredentials{ + SecretRef: v1alpha1.LocalObjectReference{ + Name: "credentials", + }, + }, + }, + FailureDomains: v1alpha1.NutanixFailureDomains{ + v1alpha1.NutanixFailureDomain{ + Name: "fd1", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + }, + }, + }, + }, + capitest.VariableTestDef{ + Name: "multiple failure domains provided", + Vals: v1alpha1.NutanixClusterConfigSpec{ + Nutanix: &v1alpha1.NutanixSpec{ + // ControlPlaneEndpoint is a required field and must always be set + ControlPlaneEndpoint: v1alpha1.ControlPlaneEndpointSpec{ + Host: "10.20.100.10", + Port: 6443, + }, + // PrismCentralEndpoint is a required field and must always be set + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + URL: testPrismCentralURL, + Credentials: v1alpha1.NutanixPrismCentralEndpointCredentials{ + SecretRef: v1alpha1.LocalObjectReference{ + Name: "credentials", + }, + }, + }, + FailureDomains: v1alpha1.NutanixFailureDomains{ + v1alpha1.NutanixFailureDomain{ + Name: "fd1", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + v1alpha1.NutanixFailureDomain{ + Name: "fd2", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + }, + }, + }, + }, + capitest.VariableTestDef{ + Name: "duplicate named failure domains provided", + Vals: v1alpha1.NutanixClusterConfigSpec{ + Nutanix: &v1alpha1.NutanixSpec{ + // ControlPlaneEndpoint is a required field and must always be set + ControlPlaneEndpoint: v1alpha1.ControlPlaneEndpointSpec{ + Host: "10.20.100.10", + Port: 6443, + }, + // PrismCentralEndpoint is a required field and must always be set + PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ + URL: testPrismCentralURL, + Credentials: v1alpha1.NutanixPrismCentralEndpointCredentials{ + SecretRef: v1alpha1.LocalObjectReference{ + Name: "credentials", + }, + }, + }, + FailureDomains: v1alpha1.NutanixFailureDomains{ + v1alpha1.NutanixFailureDomain{ + Name: "fd1", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + v1alpha1.NutanixFailureDomain{ + Name: "fd1", + Cluster: v1beta1.NutanixResourceIdentifier{ + Type: v1beta1.NutanixIdentifierName, + Name: &testPEName, + }, + Subnets: []v1beta1.NutanixResourceIdentifier{ + { + Type: v1beta1.NutanixIdentifierName, + Name: &testSubnetName, + }, + }, + ControlPlane: true, + }, + }, + }, + }, + }, + ) +} diff --git a/pkg/handlers/nutanix/mutation/metapatch_handler.go b/pkg/handlers/nutanix/mutation/metapatch_handler.go index dff362b3b..1685a4942 100644 --- a/pkg/handlers/nutanix/mutation/metapatch_handler.go +++ b/pkg/handlers/nutanix/mutation/metapatch_handler.go @@ -12,6 +12,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/controlplanevirtualip" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation/controlplaneendpoint" nutanixcontrolplanevirtualip "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation/controlplanevirtualip" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation/failuredomains" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation/machinedetails" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation/prismcentralendpoint" ) @@ -23,6 +24,7 @@ func MetaPatchHandler(mgr manager.Manager, cfg *controlplanevirtualip.Config) ha controlplaneendpoint.NewPatch(), nutanixcontrolplanevirtualip.NewPatch(mgr.GetClient(), cfg), prismcentralendpoint.NewPatch(), + failuredomains.NewPatch(), machinedetails.NewControlPlanePatch(), }, genericmutation.MetaMutators(mgr)...,