Skip to content

Commit eacc96b

Browse files
authored
feat: Validate the configured failure domain(s) exist and valid (#1208)
https://jira.nutanix.com/browse/NCN-108173: Validate the configured failure domain(s) exist and valid Added preflight check validation for the Nutanix failureDomains configured using Cluster.spec.topology. Make sure the referenced NutanixFailureDomains objects in the Cluster.spec.topology exist and their spec configuration is valid. Added unit tests and did the integration test. Signed-off-by: Yanhua Li <[email protected]>
1 parent 73bebbe commit eacc96b

File tree

7 files changed

+690
-3
lines changed

7 files changed

+690
-3
lines changed

cmd/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"sigs.k8s.io/controller-runtime/pkg/webhook"
2929
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
3030

31+
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
3132
caaphv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1"
3233
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers"
3334
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/server"
@@ -56,6 +57,7 @@ func main() {
5657
utilruntime.Must(crsv1.AddToScheme(clientScheme))
5758
utilruntime.Must(clusterv1.AddToScheme(clientScheme))
5859
utilruntime.Must(caaphv1.AddToScheme(clientScheme))
60+
utilruntime.Must(capxv1.AddToScheme(clientScheme))
5961

6062
webhookOptions := webhook.Options{
6163
Port: 9444,

pkg/webhook/preflight/nutanix/checker.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
var Checker = &nutanixChecker{
2121
configurationCheckFactory: newConfigurationCheck,
2222
credentialsCheckFactory: newCredentialsCheck,
23+
failureDomainCheckFactory: newFailureDomainChecks,
2324
vmImageChecksFactory: newVMImageChecks,
2425
vmImageKubernetesVersionChecksFactory: newVMImageKubernetesVersionChecks,
2526
storageContainerChecksFactory: newStorageContainerChecks,
@@ -36,6 +37,10 @@ type nutanixChecker struct {
3637
cd *checkDependencies,
3738
) preflight.Check
3839

40+
failureDomainCheckFactory func(
41+
cd *checkDependencies,
42+
) []preflight.Check
43+
3944
vmImageChecksFactory func(
4045
cd *checkDependencies,
4146
) []preflight.Check
@@ -78,6 +83,10 @@ func (n *nutanixChecker) Init(
7883
n.credentialsCheckFactory(ctx, newClient, cd),
7984
}
8085

86+
// The failure domains check need to run before the image, storage container checks that depends on whether
87+
// failure domains are configured correctly.
88+
checks = append(checks, n.failureDomainCheckFactory(cd)...)
89+
8190
checks = append(checks, n.vmImageChecksFactory(cd)...)
8291
checks = append(checks, n.vmImageKubernetesVersionChecksFactory(cd)...)
8392
checks = append(checks, n.storageContainerChecksFactory(cd)...)

pkg/webhook/preflight/nutanix/checker_test.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func TestNutanixChecker_Init(t *testing.T) {
4242
vmImageCheckCount int
4343
vmImageKubernetesVersionCheckCount int
4444
storageContainerCheckCount int
45+
failureDomainCheckCount int
4546
}{
4647
{
4748
name: "basic initialization with no configs",
@@ -53,6 +54,7 @@ func TestNutanixChecker_Init(t *testing.T) {
5354
vmImageCheckCount: 0,
5455
vmImageKubernetesVersionCheckCount: 0,
5556
storageContainerCheckCount: 0,
57+
failureDomainCheckCount: 0,
5658
},
5759
{
5860
name: "initialization with control plane config",
@@ -62,12 +64,13 @@ func TestNutanixChecker_Init(t *testing.T) {
6264
},
6365
},
6466
workerNodeConfigs: nil,
65-
expectedCheckCount: 5, //nolint:lll // config check, credentials check, 1 VM image check, 1 storage container check, 1 VM image Kubernetes version check
67+
expectedCheckCount: 6, //nolint:lll // config check, credentials check, 1 VM image check, 1 storage container check, 1 VM image Kubernetes version check, 1 failure domain check
6668
expectedFirstCheckName: "NutanixConfiguration",
6769
expectedSecondCheckName: "NutanixCredentials",
6870
vmImageCheckCount: 1,
6971
vmImageKubernetesVersionCheckCount: 1,
7072
storageContainerCheckCount: 1,
73+
failureDomainCheckCount: 1,
7174
},
7275
{
7376
name: "initialization with worker node configs",
@@ -80,12 +83,13 @@ func TestNutanixChecker_Init(t *testing.T) {
8083
Nutanix: &carenv1.NutanixWorkerNodeSpec{},
8184
},
8285
},
83-
expectedCheckCount: 8, //nolint:lll // config check, credentials check, 2 VM image checks, 2 storage container checks, 2 VM image Kubernetes version checks
86+
expectedCheckCount: 10, //nolint:lll // config check, credentials check, 2 VM image checks, 2 storage container checks, 2 VM image Kubernetes version checks, 2 failure domain checks
8487
expectedFirstCheckName: "NutanixConfiguration",
8588
expectedSecondCheckName: "NutanixCredentials",
8689
vmImageCheckCount: 2,
8790
vmImageKubernetesVersionCheckCount: 2,
8891
storageContainerCheckCount: 2,
92+
failureDomainCheckCount: 2,
8993
},
9094
{
9195
name: "initialization with both control plane and worker node configs",
@@ -99,12 +103,13 @@ func TestNutanixChecker_Init(t *testing.T) {
99103
Nutanix: &carenv1.NutanixWorkerNodeSpec{},
100104
},
101105
},
102-
expectedCheckCount: 8, //nolint:lll // config check, credentials check, 2 VM image checks (1 CP + 1 worker), 2 storage container checks (1 CP + 1 worker), 2 VM image Kubernetes version checks
106+
expectedCheckCount: 10, //nolint:lll // config check, credentials check, 2 VM image checks (1 CP + 1 worker), 2 storage container checks (1 CP + 1 worker), 2 VM image Kubernetes version checks, 2 failure domain checks
103107
expectedFirstCheckName: "NutanixConfiguration",
104108
expectedSecondCheckName: "NutanixCredentials",
105109
vmImageCheckCount: 2,
106110
vmImageKubernetesVersionCheckCount: 2,
107111
storageContainerCheckCount: 2,
112+
failureDomainCheckCount: 2,
108113
},
109114
}
110115

@@ -119,6 +124,7 @@ func TestNutanixChecker_Init(t *testing.T) {
119124
vmImageCheckCount := 0
120125
storageContainerCheckCount := 0
121126
vmImageKubernetesVersionCheckCount := 0
127+
failureDomainCheckCount := 0
122128

123129
checker.configurationCheckFactory = func(cd *checkDependencies) preflight.Check {
124130
configCheckCalled = true
@@ -188,6 +194,22 @@ func TestNutanixChecker_Init(t *testing.T) {
188194
return checks
189195
}
190196

197+
checker.failureDomainCheckFactory = func(cd *checkDependencies) []preflight.Check {
198+
checks := []preflight.Check{}
199+
for i := 0; i < tt.failureDomainCheckCount; i++ {
200+
failureDomainCheckCount++
201+
checks = append(checks,
202+
&mockCheck{
203+
name: fmt.Sprintf("NutanixFailureDomain-%d", i),
204+
result: preflight.CheckResult{
205+
Allowed: true,
206+
},
207+
},
208+
)
209+
}
210+
return checks
211+
}
212+
191213
// Call Init
192214
ctx := context.Background()
193215
checks := checker.Init(ctx, nil, &clusterv1.Cluster{
@@ -210,6 +232,12 @@ func TestNutanixChecker_Init(t *testing.T) {
210232
storageContainerCheckCount,
211233
"Wrong number of storage container checks",
212234
)
235+
assert.Equal(
236+
t,
237+
tt.failureDomainCheckCount,
238+
failureDomainCheckCount,
239+
"Wrong number of failure domain checks",
240+
)
213241

214242
// Verify the first two checks when we have results
215243
if len(checks) >= 2 {

pkg/webhook/preflight/nutanix/clients.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99

1010
clustermgmtv4 "github.com/nutanix/ntnx-api-golang-clients/clustermgmt-go-client/v4/models/clustermgmt/v4/config"
11+
netv4 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/networking/v4/config"
1112
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
1213

1314
prismgoclient "github.com/nutanix-cloud-native/prism-go-client"
@@ -48,6 +49,16 @@ type client interface {
4849
select_ *string,
4950
args ...map[string]interface{},
5051
) (*clustermgmtv4.ListStorageContainersApiResponse, error)
52+
GetSubnetById(id *string) (*netv4.GetSubnetApiResponse, error)
53+
ListSubnets(
54+
page_ *int,
55+
limit_ *int,
56+
filter_ *string,
57+
orderby_ *string,
58+
expand_ *string,
59+
select_ *string,
60+
args ...map[string]interface{},
61+
) (*netv4.ListSubnetsApiResponse, error)
5162
}
5263

5364
// clientWrapper implements the client interface and wraps both v3 and v4 clients.
@@ -163,3 +174,35 @@ func (c *clientWrapper) ListStorageContainers(
163174
}
164175
return resp, nil
165176
}
177+
178+
func (c *clientWrapper) GetSubnetById(id *string) (*netv4.GetSubnetApiResponse, error) {
179+
resp, err := c.v4client.SubnetsApiInstance.GetSubnetById(id)
180+
if err != nil {
181+
return nil, err
182+
}
183+
return resp, nil
184+
}
185+
186+
func (c *clientWrapper) ListSubnets(
187+
page_ *int,
188+
limit_ *int,
189+
filter_ *string,
190+
orderby_ *string,
191+
expand_ *string,
192+
select_ *string,
193+
args ...map[string]interface{},
194+
) (*netv4.ListSubnetsApiResponse, error) {
195+
resp, err := c.v4client.SubnetsApiInstance.ListSubnets(
196+
page_,
197+
limit_,
198+
filter_,
199+
orderby_,
200+
expand_,
201+
select_,
202+
args...,
203+
)
204+
if err != nil {
205+
return nil, err
206+
}
207+
return resp, nil
208+
}

pkg/webhook/preflight/nutanix/clients_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88

99
clustermgmtv4 "github.com/nutanix/ntnx-api-golang-clients/clustermgmt-go-client/v4/models/clustermgmt/v4/config"
10+
netv4 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/networking/v4/config"
1011
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
1112

1213
prismv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
@@ -57,6 +58,18 @@ type mocknclient struct {
5758
select_ *string,
5859
args ...map[string]interface{},
5960
) (*clustermgmtv4.ListStorageContainersApiResponse, error)
61+
62+
GetSubnetByIdFunc func(id *string) (*netv4.GetSubnetApiResponse, error)
63+
64+
ListSubnetsFunc func(
65+
page_ *int,
66+
limit_ *int,
67+
filter_ *string,
68+
orderby_ *string,
69+
expand_ *string,
70+
select_ *string,
71+
args ...map[string]interface{},
72+
) (*netv4.ListSubnetsApiResponse, error)
6073
}
6174

6275
func (m *mocknclient) GetCurrentLoggedInUser(ctx context.Context) (*prismv3.UserIntentResponse, error) {
@@ -94,3 +107,19 @@ func (m *mocknclient) ListStorageContainers(
94107
) (*clustermgmtv4.ListStorageContainersApiResponse, error) {
95108
return m.listStorageContainersFunc(page, limit, filter, orderby, select_, args...)
96109
}
110+
111+
func (m *mocknclient) GetSubnetById(id *string) (*netv4.GetSubnetApiResponse, error) {
112+
return m.GetSubnetByIdFunc(id)
113+
}
114+
115+
func (m *mocknclient) ListSubnets(
116+
page_ *int,
117+
limit_ *int,
118+
filter_ *string,
119+
orderby_ *string,
120+
expand_ *string,
121+
select_ *string,
122+
args ...map[string]interface{},
123+
) (*netv4.ListSubnetsApiResponse, error) {
124+
return m.ListSubnetsFunc(page_, limit_, filter_, orderby_, expand_, select_, args...)
125+
}

0 commit comments

Comments
 (0)