Skip to content

Commit ceb4781

Browse files
committed
feat: CAREN support for NutanixFailureDomain
1 parent 5fbdaa3 commit ceb4781

File tree

17 files changed

+434
-135
lines changed

17 files changed

+434
-135
lines changed

api/v1alpha1/controlplane_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type DockerControlPlaneSpec struct {
3131
// NutanixControlPlaneSpec defines the desired state of the control plane for a Nutanix cluster.
3232
type NutanixControlPlaneSpec struct {
3333
// +kubebuilder:validation:Optional
34-
Nutanix *NutanixNodeSpec `json:"nutanix,omitempty"`
34+
Nutanix *NutanixControlPlaneNodeSpec `json:"nutanix,omitempty"`
3535

3636
GenericControlPlaneSpec `json:",inline"`
3737

api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@ spec:
329329
type: object
330330
nutanix:
331331
properties:
332+
failureDomains:
333+
description: |-
334+
failureDomains specifies a list of NutanixFailureDomains (by names)
335+
that the cluster uses to deploy its control-plane machines.
336+
items:
337+
type: string
338+
type: array
339+
x-kubernetes-list-type: set
332340
machineDetails:
333341
properties:
334342
additionalCategories:

api/v1alpha1/crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ spec:
3939
metadata:
4040
type: object
4141
spec:
42-
description: NutanixWorkerNodeConfigSpec defines the desired state of NutanixNodeSpec.
42+
description: NutanixWorkerNodeConfigSpec defines the desired state of NutanixWorkerNodeSpec.
4343
properties:
4444
nodeRegistration:
4545
default: {}

api/v1alpha1/nodeconfig_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ func (s NutanixWorkerNodeConfig) VariableSchema() clusterv1.VariableSchema { //n
9494
return nutanixNodeConfigVariableSchema
9595
}
9696

97-
// NutanixWorkerNodeConfigSpec defines the desired state of NutanixNodeSpec.
97+
// NutanixWorkerNodeConfigSpec defines the desired state of NutanixWorkerNodeSpec.
9898
type NutanixWorkerNodeConfigSpec struct {
9999
// +kubebuilder:validation:Optional
100-
Nutanix *NutanixNodeSpec `json:"nutanix,omitempty"`
100+
Nutanix *NutanixWorkerNodeSpec `json:"nutanix,omitempty"`
101101

102102
GenericNodeSpec `json:",inline"`
103103
}

api/v1alpha1/nutanix_node_types.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,17 @@ import (
99
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
1010
)
1111

12-
type NutanixNodeSpec struct {
12+
type NutanixControlPlaneNodeSpec struct {
13+
MachineDetails NutanixMachineDetails `json:"machineDetails"`
14+
15+
// failureDomains specifies a list of NutanixFailureDomains (by names)
16+
// that the cluster uses to deploy its control-plane machines.
17+
// +listType=set
18+
// +optional
19+
FailureDomains []string `json:"failureDomains,omitempty"`
20+
}
21+
22+
type NutanixWorkerNodeSpec struct {
1323
MachineDetails NutanixMachineDetails `json:"machineDetails"`
1424
}
1525

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 39 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/variables/aggregate_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ControlPlaneSpec struct {
3434

3535
Docker *carenv1.DockerNodeSpec `json:"docker,omitempty"`
3636

37-
Nutanix *carenv1.NutanixNodeSpec `json:"nutanix,omitempty"`
37+
Nutanix *carenv1.NutanixControlPlaneNodeSpec `json:"nutanix,omitempty"`
3838

3939
carenv1.GenericControlPlaneSpec `json:",inline"`
4040

@@ -46,7 +46,7 @@ type WorkerNodeConfigSpec struct {
4646

4747
Docker *carenv1.DockerNodeSpec `json:"docker,omitempty"`
4848

49-
Nutanix *carenv1.NutanixNodeSpec `json:"nutanix,omitempty"`
49+
Nutanix *carenv1.NutanixWorkerNodeSpec `json:"nutanix,omitempty"`
5050
}
5151

5252
type Addons struct {

pkg/handlers/generic/mutation/kubeproxymode/variables_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func TestVariableValidation_Nutanix(t *testing.T) {
153153
func minimalNutanixClusterConfigSpec() v1alpha1.NutanixClusterConfigSpec {
154154
return v1alpha1.NutanixClusterConfigSpec{
155155
ControlPlane: &v1alpha1.NutanixControlPlaneSpec{
156-
Nutanix: &v1alpha1.NutanixNodeSpec{
156+
Nutanix: &v1alpha1.NutanixControlPlaneNodeSpec{
157157
MachineDetails: v1alpha1.NutanixMachineDetails{
158158
BootType: capxv1.NutanixBootTypeLegacy,
159159
VCPUSockets: 2,
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package controlplanefailuredomains
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
corev1 "k8s.io/api/core/v1"
11+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
12+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
17+
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
21+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors"
22+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
23+
)
24+
25+
const (
26+
// VariableName is the external patch variable name.
27+
VariableName = "failureDomains"
28+
)
29+
30+
type nutanixControlPlaneFailureDomains struct {
31+
variableName string
32+
variableFieldPath []string
33+
}
34+
35+
func NewPatch() *nutanixControlPlaneFailureDomains {
36+
return newNutanixControlPlaneFailureDomains(
37+
v1alpha1.ClusterConfigVariableName,
38+
v1alpha1.ControlPlaneConfigVariableName,
39+
v1alpha1.NutanixVariableName,
40+
VariableName,
41+
)
42+
}
43+
44+
func newNutanixControlPlaneFailureDomains(
45+
variableName string,
46+
variableFieldPath ...string,
47+
) *nutanixControlPlaneFailureDomains {
48+
return &nutanixControlPlaneFailureDomains{
49+
variableName: variableName,
50+
variableFieldPath: variableFieldPath,
51+
}
52+
}
53+
54+
func (h *nutanixControlPlaneFailureDomains) Mutate(
55+
ctx context.Context,
56+
obj *unstructured.Unstructured,
57+
vars map[string]apiextensionsv1.JSON,
58+
holderRef runtimehooksv1.HolderReference,
59+
_ client.ObjectKey,
60+
_ mutation.ClusterGetter,
61+
) error {
62+
log := ctrl.LoggerFrom(ctx).WithValues(
63+
"holderRef", holderRef,
64+
"variableName", h.variableName,
65+
"variableFieldPath", h.variableFieldPath,
66+
)
67+
68+
controlPlaneFDsVar, err := variables.Get[[]string](
69+
vars,
70+
h.variableName,
71+
h.variableFieldPath...,
72+
)
73+
if err != nil {
74+
if variables.IsNotFoundError(err) {
75+
log.Info("ControlPlane nutanix failureDomains variable not defined", "error", err.Error())
76+
return nil
77+
}
78+
log.Error(err, "failed to get controlPlane nutanix failureDomains variable.")
79+
return err
80+
}
81+
82+
log = log.WithValues("variableValue", controlPlaneFDsVar)
83+
84+
if len(controlPlaneFDsVar) == 0 {
85+
return nil
86+
}
87+
88+
return patches.MutateIfApplicable(
89+
obj,
90+
vars,
91+
&holderRef,
92+
selectors.InfrastructureCluster(capxv1.GroupVersion.Version, "NutanixClusterTemplate"),
93+
log,
94+
func(obj *capxv1.NutanixClusterTemplate) error {
95+
log.WithValues(
96+
"patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(),
97+
"patchedObjectName", client.ObjectKeyFromObject(obj),
98+
).Info("setting controlPlaneFailureDomains in NutanixCluster spec")
99+
100+
fdRefs := []corev1.LocalObjectReference{}
101+
// use to check duplicates
102+
fdSet := map[string]bool{}
103+
for _, fd := range controlPlaneFDsVar {
104+
if _, ok := fdSet[fd]; ok {
105+
log.Error(fmt.Errorf("duplicate failureDomain configured: %s", fd), "")
106+
continue
107+
}
108+
fdSet[fd] = true
109+
fdRefs = append(fdRefs, corev1.LocalObjectReference{Name: fd})
110+
}
111+
112+
// set controlPlaneFailureDomains in NutanixCluster spec
113+
obj.Spec.Template.Spec.ControlPlaneFailureDomains = fdRefs
114+
log.Info("done setting controlPlaneFailureDomains in NutanixClusterTemplate spec",
115+
"NutanixClusterTemplate name", obj.Name,
116+
"controlPlaneFailureDomains", obj.Spec.Template.Spec.ControlPlaneFailureDomains,
117+
)
118+
119+
return nil
120+
},
121+
)
122+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package controlplanefailuredomains
5+
6+
import (
7+
"testing"
8+
9+
. "github.com/onsi/ginkgo/v2"
10+
"github.com/onsi/gomega"
11+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
12+
13+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
14+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
15+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest"
16+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request"
17+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers"
18+
)
19+
20+
func TestControlPlaneFailureDomainsPatch(t *testing.T) {
21+
gomega.RegisterFailHandler(Fail)
22+
RunSpecs(t, "ControlPlane nutanix failuredomains suite")
23+
}
24+
25+
var _ = Describe("Generate ControlPlane nutanix failuredomains patches", func() {
26+
patchGenerator := func() mutation.GeneratePatches {
27+
return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewPatch()).(mutation.GeneratePatches)
28+
}
29+
30+
testDefs := []capitest.PatchTestDef{
31+
{
32+
Name: "unset variable",
33+
},
34+
{
35+
Name: "FailureDomains set to valid values",
36+
Vars: []runtimehooksv1.Variable{
37+
capitest.VariableWithValue(
38+
v1alpha1.ClusterConfigVariableName,
39+
[]string{"fd-1", "fd-2", "fd-3"},
40+
v1alpha1.ControlPlaneConfigVariableName,
41+
v1alpha1.NutanixVariableName,
42+
VariableName,
43+
),
44+
},
45+
RequestItem: request.NewNutanixClusterTemplateRequestItem(""),
46+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{
47+
{
48+
Operation: "add",
49+
Path: "/spec/template/spec/controlPlaneFailureDomains",
50+
ValueMatcher: gomega.ConsistOf(
51+
gomega.HaveKeyWithValue("name", "fd-1"),
52+
gomega.HaveKeyWithValue("name", "fd-2"),
53+
gomega.HaveKeyWithValue("name", "fd-3"),
54+
),
55+
},
56+
},
57+
},
58+
{
59+
Name: "FailureDomains set to duplicate values",
60+
Vars: []runtimehooksv1.Variable{
61+
capitest.VariableWithValue(
62+
v1alpha1.ClusterConfigVariableName,
63+
[]string{"fd-1", "fd-2", "fd-1"},
64+
v1alpha1.ControlPlaneConfigVariableName,
65+
v1alpha1.NutanixVariableName,
66+
VariableName,
67+
),
68+
},
69+
RequestItem: request.NewNutanixClusterTemplateRequestItem(""),
70+
ExpectedPatchMatchers: []capitest.JSONPatchMatcher{
71+
{
72+
Operation: "add",
73+
Path: "/spec/template/spec/controlPlaneFailureDomains",
74+
ValueMatcher: gomega.ConsistOf(
75+
gomega.HaveKeyWithValue("name", "fd-1"),
76+
gomega.HaveKeyWithValue("name", "fd-2"),
77+
),
78+
},
79+
},
80+
},
81+
}
82+
83+
// create test node for each case
84+
for _, tt := range testDefs {
85+
It(tt.Name, func() {
86+
capitest.AssertGeneratePatches(
87+
GinkgoT(),
88+
patchGenerator,
89+
&tt,
90+
)
91+
})
92+
}
93+
})

0 commit comments

Comments
 (0)