Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion pkg/types/nutanix/validation/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package validation
import (
"fmt"
"regexp"
"sort"
"strings"

"k8s.io/apimachinery/pkg/util/validation/field"

Expand Down Expand Up @@ -103,11 +105,20 @@ func ValidatePlatform(p *nutanix.Platform, fldPath *field.Path, c *types.Install
if err != nil {
allErrs = append(allErrs, field.InternalError(fldPath.Child("failureDomain", "name"), fmt.Errorf("fail to compile the pattern %q: %w", pattern, err)))
} else {
for _, fd := range p.FailureDomains {
fdNames := make(map[string]int)
fdTopologies := make(map[string]string)

for idx, fd := range p.FailureDomains {
if !rexp.MatchString(fd.Name) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("failureDomain", "name"), fd.Name, fmt.Sprintf("failureDomain name should match the pattern %q.", pattern)))
}

if prevIdx, exists := fdNames[fd.Name]; exists {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("failureDomains").Index(idx).Child("name"), fmt.Sprintf("failure domain name %q is already used by failureDomains[%d]", fd.Name, prevIdx)))
} else {
fdNames[fd.Name] = idx
}

if fd.PrismElement.UUID == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("failureDomain", "prismElement", "uuid"), "failureDomain prismElement uuid cannot be empty"))
}
Expand All @@ -117,6 +128,19 @@ func ValidatePlatform(p *nutanix.Platform, fldPath *field.Path, c *types.Install
allErrs = append(allErrs, errs...)
}

// Only check for duplicate topology after basic required-field validation,
// so users see actionable "required field" errors instead of misleading
// "identical topology" when fields are simply empty.
if fd.PrismElement.UUID != "" && len(fd.SubnetUUIDs) > 0 {
topoKey := nutanixFailureDomainTopologyKey(fd)
if prevName, exists := fdTopologies[topoKey]; exists {
allErrs = append(allErrs, field.Invalid(fldPath.Child("failureDomains").Index(idx), fd.Name,
fmt.Sprintf("failure domain %q has identical topology (same prismElement and subnets) as %q; this provides no additional fault tolerance", fd.Name, prevName)))
} else {
fdTopologies[topoKey] = fd.Name
}
}

for _, sc := range fd.StorageContainers {
if sc.ReferenceName == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("failureDomain", "storageContainers", "referenceName"), fmt.Sprintf("failureDomain %q: missing storageContainer referenceName", fd.Name)))
Expand Down Expand Up @@ -153,6 +177,15 @@ func validateLoadBalancer(lbType configv1.PlatformLoadBalancerType) bool {
}
}

// nutanixFailureDomainTopologyKey builds a comparable key from the infrastructure-defining
// fields of a failure domain: Prism Element UUID and sorted subnet UUIDs.
func nutanixFailureDomainTopologyKey(fd nutanix.FailureDomain) string {
subnets := make([]string, len(fd.SubnetUUIDs))
copy(subnets, fd.SubnetUUIDs)
sort.Strings(subnets)
return fmt.Sprintf("pe=%s;subnets=%s", fd.PrismElement.UUID, strings.Join(subnets, "\x00"))
}

// validateSubnets validates the input subnetUUIDs meet the configuration requirements.
func validateSubnets(fldPath *field.Path, subnetUUIDs []string) field.ErrorList {
var errs field.ErrorList
Expand Down
79 changes: 79 additions & 0 deletions pkg/types/nutanix/validation/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,85 @@ func TestValidatePlatform(t *testing.T) {
return p
}(),
},
{
name: "failureDomain with duplicate name",
platform: func() *nutanix.Platform {
p := validPlatform()
p.FailureDomains = []nutanix.FailureDomain{
{
Name: "fd-1",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid-1", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe-1", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid-1"},
},
{
Name: "fd-1",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid-2", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe-2", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid-2"},
},
}
return p
}(),
expectedError: `test-path\.failureDomains\[1\]\.name: Duplicate value: "failure domain name \\"fd-1\\" is already used by failureDomains\[0\]"`,
},
{
name: "failureDomain with duplicate topology same prismElement and subnet",
platform: func() *nutanix.Platform {
p := validPlatform()
p.FailureDomains = []nutanix.FailureDomain{
{
Name: "fd-1",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid"},
},
{
Name: "fd-2",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid"},
},
}
return p
}(),
expectedError: `test-path\.failureDomains\[1\]: Invalid value: "fd-2": failure domain "fd-2" has identical topology \(same prismElement and subnets\) as "fd-1"; this provides no additional fault tolerance`,
},
{
name: "valid failureDomain with different prismElements",
platform: func() *nutanix.Platform {
p := validPlatform()
p.FailureDomains = []nutanix.FailureDomain{
{
Name: "fd-1",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid-1", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe-1", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid"},
},
{
Name: "fd-2",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid-2", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe-2", Port: 9440}},
SubnetUUIDs: []string{"fd-subnet-uuid"},
},
}
return p
}(),
},
{
name: "failureDomain duplicate topology with same subnets in different order",
platform: func() *nutanix.Platform {
p := validPlatform()
p.FailureDomains = []nutanix.FailureDomain{
{
Name: "fd-1",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe", Port: 9440}},
SubnetUUIDs: []string{"subnet-a", "subnet-b"},
},
{
Name: "fd-2",
PrismElement: nutanix.PrismElement{UUID: "fd-pe-uuid", Endpoint: nutanix.PrismEndpoint{Address: "fd-pe", Port: 9440}},
SubnetUUIDs: []string{"subnet-b", "subnet-a"},
},
}
return p
}(),
expectedError: `test-path\.failureDomains\[1\]: Invalid value: "fd-2": failure domain "fd-2" has identical topology \(same prismElement and subnets\) as "fd-1"; this provides no additional fault tolerance`,
},
{
name: "valid failureDomain with multiple subnets for multi-NIC",
platform: func() *nutanix.Platform {
Expand Down