mirror of
https://github.com/openshift/installer.git
synced 2026-02-05 15:47:14 +01:00
Merge pull request #9521 from mshitrit/fencing-config-platform-none
OCPEDGE-1505: Enhance Platform none with Fencing Credentials
This commit is contained in:
@@ -57,6 +57,33 @@ spec:
|
||||
- ""
|
||||
- amd64
|
||||
type: string
|
||||
fencing:
|
||||
description: |-
|
||||
Fencing stores the information about a baremetal host's management controller.
|
||||
Fencing may only be set for control plane nodes.
|
||||
properties:
|
||||
credentials:
|
||||
description: Credentials stores the information about a baremetal
|
||||
host's management controller.
|
||||
items:
|
||||
description: Credential stores the information about a baremetal
|
||||
host's management controller.
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
hostName:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
hyperthreading:
|
||||
default: Enabled
|
||||
description: |-
|
||||
@@ -1293,6 +1320,33 @@ spec:
|
||||
- ""
|
||||
- amd64
|
||||
type: string
|
||||
fencing:
|
||||
description: |-
|
||||
Fencing stores the information about a baremetal host's management controller.
|
||||
Fencing may only be set for control plane nodes.
|
||||
properties:
|
||||
credentials:
|
||||
description: Credentials stores the information about a baremetal
|
||||
host's management controller.
|
||||
items:
|
||||
description: Credential stores the information about a baremetal
|
||||
host's management controller.
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
hostName:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
hyperthreading:
|
||||
default: Enabled
|
||||
description: |-
|
||||
@@ -2468,6 +2522,33 @@ spec:
|
||||
- ""
|
||||
- amd64
|
||||
type: string
|
||||
fencing:
|
||||
description: |-
|
||||
Fencing stores the information about a baremetal host's management controller.
|
||||
Fencing may only be set for control plane nodes.
|
||||
properties:
|
||||
credentials:
|
||||
description: Credentials stores the information about a baremetal
|
||||
host's management controller.
|
||||
items:
|
||||
description: Credential stores the information about a baremetal
|
||||
host's management controller.
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
hostName:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
hyperthreading:
|
||||
default: Enabled
|
||||
description: |-
|
||||
|
||||
2
go.mod
2
go.mod
@@ -44,6 +44,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.6
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6
|
||||
github.com/aws/smithy-go v1.22.3
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e
|
||||
github.com/clarketm/json v1.14.1
|
||||
github.com/containers/image/v5 v5.31.0
|
||||
@@ -181,7 +182,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
|
||||
github.com/aws/smithy-go v1.22.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
|
||||
@@ -474,7 +474,7 @@ platform:
|
||||
pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}"
|
||||
`,
|
||||
expectedFound: false,
|
||||
expectedError: "invalid install-config configuration: ControlPlane.Replicas: Invalid value: 2: ControlPlane.Replicas can only be set to 5, 4, 3, or 1. Found 2",
|
||||
expectedError: "invalid install-config configuration: [controlPlane.fencing.credentials: Forbidden: there should be exactly two fencing credentials to support the two node cluster, instead 0 credentials were found, ControlPlane.Replicas: Invalid value: 2: ControlPlane.Replicas can only be set to 5, 4, 3, or 1. Found 2]",
|
||||
},
|
||||
{
|
||||
name: "invalid platform for SNO cluster",
|
||||
|
||||
@@ -101,7 +101,7 @@ platform:
|
||||
pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}"
|
||||
`,
|
||||
expectedFound: false,
|
||||
expectedError: "invalid install-config configuration: ControlPlane.Replicas: Required value: Only Single Node OpenShift (SNO) is supported, total number of ControlPlane.Replicas must be 1. Found 2",
|
||||
expectedError: "invalid install-config configuration: [controlPlane.fencing.credentials: Forbidden: there should be exactly two fencing credentials to support the two node cluster, instead 0 credentials were found, ControlPlane.Replicas: Required value: Only Single Node OpenShift (SNO) is supported, total number of ControlPlane.Replicas must be 1. Found 2]",
|
||||
},
|
||||
{
|
||||
name: "invalid number of MachineNetworks",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/openshift/installer/pkg/ipnet"
|
||||
"github.com/openshift/installer/pkg/types"
|
||||
"github.com/openshift/installer/pkg/types/baremetal"
|
||||
"github.com/openshift/installer/pkg/types/common"
|
||||
"github.com/openshift/installer/pkg/validate"
|
||||
)
|
||||
|
||||
@@ -180,53 +181,7 @@ func validateDHCPRange(p *baremetal.Platform, fldPath *field.Path) (allErrs fiel
|
||||
|
||||
// validateHostsBase validates the hosts based on a filtering function
|
||||
func validateHostsBase(hosts []*baremetal.Host, fldPath *field.Path, filter validator.FilterFunc) field.ErrorList {
|
||||
hostErrs := field.ErrorList{}
|
||||
|
||||
values := make(map[string]map[interface{}]struct{})
|
||||
|
||||
//Initialize a new validator and register a custom validation rule for the tag `uniqueField`
|
||||
validate := validator.New()
|
||||
validate.RegisterValidation("uniqueField", func(fl validator.FieldLevel) bool {
|
||||
valueFound := false
|
||||
fieldName := fl.Parent().Type().Name() + "." + fl.FieldName()
|
||||
fieldValue := fl.Field().Interface()
|
||||
|
||||
if fl.Field().Type().Comparable() {
|
||||
if _, present := values[fieldName]; !present {
|
||||
values[fieldName] = make(map[interface{}]struct{})
|
||||
}
|
||||
|
||||
fieldValues := values[fieldName]
|
||||
if _, valueFound = fieldValues[fieldValue]; !valueFound {
|
||||
fieldValues[fieldValue] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Sprintf("Cannot apply validation rule 'uniqueField' on field %s", fl.FieldName()))
|
||||
}
|
||||
|
||||
return !valueFound
|
||||
})
|
||||
|
||||
//Apply validations and translate errors
|
||||
fldPath = fldPath.Child("hosts")
|
||||
|
||||
for idx, host := range hosts {
|
||||
err := validate.StructFiltered(host, filter)
|
||||
if err != nil {
|
||||
hostType := reflect.TypeOf(hosts).Elem().Elem().Name()
|
||||
for _, err := range err.(validator.ValidationErrors) {
|
||||
childName := fldPath.Index(idx).Child(err.Namespace()[len(hostType)+1:])
|
||||
switch err.Tag() {
|
||||
case "required":
|
||||
hostErrs = append(hostErrs, field.Required(childName, "missing "+err.Field()))
|
||||
case "uniqueField":
|
||||
hostErrs = append(hostErrs, field.Duplicate(childName, err.Value()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hostErrs
|
||||
return common.ValidateUniqueAndRequiredFields(hosts, fldPath, filter, "hosts")
|
||||
}
|
||||
|
||||
// filterHostsBMC is a function to control whether to filter BMC details of Hosts
|
||||
|
||||
67
pkg/types/common/common.go
Normal file
67
pkg/types/common/common.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ValidateUniqueAndRequiredFields validated unique fields are indeed unique and that required fields exist on a generic element.
|
||||
func ValidateUniqueAndRequiredFields[T any](elements []T, fldPath *field.Path, filter validator.FilterFunc, fieldName string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
|
||||
values := make(map[string]map[interface{}]struct{})
|
||||
|
||||
// Initialize a new validator and register a custom validation rule for the tag `uniqueField`.
|
||||
validate := validator.New()
|
||||
if err := validate.RegisterValidation("uniqueField", func(fl validator.FieldLevel) bool {
|
||||
valueFound := false
|
||||
fieldName := fl.Parent().Type().Name() + "." + fl.FieldName()
|
||||
fieldValue := fl.Field().Interface()
|
||||
|
||||
if fl.Field().Type().Comparable() {
|
||||
if _, present := values[fieldName]; !present {
|
||||
values[fieldName] = make(map[interface{}]struct{})
|
||||
}
|
||||
|
||||
fieldValues := values[fieldName]
|
||||
if _, valueFound = fieldValues[fieldValue]; !valueFound {
|
||||
fieldValues[fieldValue] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Sprintf("Cannot apply validation rule 'uniqueField' on field %s", fl.FieldName()))
|
||||
}
|
||||
|
||||
return !valueFound
|
||||
}); err != nil {
|
||||
logrus.Error("Unexpected error registering validation", err)
|
||||
}
|
||||
|
||||
// Apply validations and translate errors.
|
||||
|
||||
fldPath = fldPath.Child(fieldName)
|
||||
|
||||
for idx, element := range elements {
|
||||
err := validate.StructFiltered(element, filter)
|
||||
if err != nil {
|
||||
elementType := reflect.TypeOf(elements).Elem().Elem().Name()
|
||||
var validationErrs validator.ValidationErrors
|
||||
if errors.As(err, &validationErrs) {
|
||||
for _, fieldErr := range validationErrs {
|
||||
childName := fldPath.Index(idx).Child(fieldErr.Namespace()[len(elementType)+1:])
|
||||
switch fieldErr.Tag() {
|
||||
case "required":
|
||||
errs = append(errs, field.Required(childName, "missing "+fieldErr.Field()))
|
||||
case "uniqueField":
|
||||
errs = append(errs, field.Duplicate(childName, fieldErr.Value()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
21
pkg/types/defaults/validation/featuregates.go
Normal file
21
pkg/types/defaults/validation/featuregates.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"github.com/openshift/api/features"
|
||||
"github.com/openshift/installer/pkg/types"
|
||||
"github.com/openshift/installer/pkg/types/featuregates"
|
||||
)
|
||||
|
||||
// GatedFeatures determines all of the install config fields that should
|
||||
// be validated to ensure that the proper featuregate is enabled when the field is used.
|
||||
func GatedFeatures(c *types.InstallConfig) []featuregates.GatedInstallConfigFeature {
|
||||
return []featuregates.GatedInstallConfigFeature{
|
||||
{
|
||||
FeatureGateName: features.FeatureGateDualReplica,
|
||||
Condition: c.ControlPlane != nil && c.ControlPlane.Fencing != nil,
|
||||
Field: field.NewPath("platform", "none", "fencingCredentials"),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,11 @@ type MachinePool struct {
|
||||
// +kubebuilder:default=amd64
|
||||
// +optional
|
||||
Architecture Architecture `json:"architecture,omitempty"`
|
||||
|
||||
// Fencing stores the information about a baremetal host's management controller.
|
||||
// Fencing may only be set for control plane nodes.
|
||||
// +optional
|
||||
Fencing *Fencing `json:"fencing,omitempty"`
|
||||
}
|
||||
|
||||
// MachinePoolPlatform is the platform-specific configuration for a machine
|
||||
@@ -145,3 +150,18 @@ func (p *MachinePoolPlatform) Name() string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Fencing stores the information about a baremetal host's management controller.
|
||||
type Fencing struct {
|
||||
// Credentials stores the information about a baremetal host's management controller.
|
||||
// +optional
|
||||
Credentials []*Credential `json:"credentials,omitempty"`
|
||||
}
|
||||
|
||||
// Credential stores the information about a baremetal host's management controller.
|
||||
type Credential struct {
|
||||
HostName string `json:"hostName,omitempty" validate:"required,uniqueField"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Address string `json:"address" validate:"required,uniqueField"`
|
||||
}
|
||||
|
||||
@@ -223,6 +223,33 @@ func TestFeatureGates(t *testing.T) {
|
||||
return c
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "FencingCredentials is not allowed with Feature Gates disabled",
|
||||
installConfig: func() *types.InstallConfig {
|
||||
c := validInstallConfig()
|
||||
c.ControlPlane.Fencing = &types.Fencing{Credentials: []*types.Credential{{HostName: "host1"}, {HostName: "host2"}}}
|
||||
return c
|
||||
}(),
|
||||
expected: `^platform.none.fencingCredentials: Forbidden: this field is protected by the DualReplica feature gate which must be enabled through either the TechPreviewNoUpgrade or CustomNoUpgrade feature set$`,
|
||||
},
|
||||
{
|
||||
name: "FencingCredentials is allowed with TechPreviewNoUpgrade Feature Set",
|
||||
installConfig: func() *types.InstallConfig {
|
||||
c := validInstallConfig()
|
||||
c.FeatureSet = v1.TechPreviewNoUpgrade
|
||||
c.ControlPlane.Fencing = &types.Fencing{Credentials: []*types.Credential{{HostName: "host1"}, {HostName: "host2"}}}
|
||||
return c
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "FencingCredentials is allowed with DevPreviewNoUpgrade Feature Set",
|
||||
installConfig: func() *types.InstallConfig {
|
||||
c := validInstallConfig()
|
||||
c.FeatureSet = v1.DevPreviewNoUpgrade
|
||||
c.ControlPlane.Fencing = &types.Fencing{Credentials: []*types.Credential{{HostName: "host1"}, {HostName: "host2"}}}
|
||||
return c
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
azurevalidation "github.com/openshift/installer/pkg/types/azure/validation"
|
||||
"github.com/openshift/installer/pkg/types/baremetal"
|
||||
baremetalvalidation "github.com/openshift/installer/pkg/types/baremetal/validation"
|
||||
"github.com/openshift/installer/pkg/types/common"
|
||||
defaultsvalidation "github.com/openshift/installer/pkg/types/defaults/validation"
|
||||
"github.com/openshift/installer/pkg/types/external"
|
||||
"github.com/openshift/installer/pkg/types/featuregates"
|
||||
"github.com/openshift/installer/pkg/types/gcp"
|
||||
@@ -121,7 +123,7 @@ func ValidateInstallConfig(c *types.InstallConfig, usingAgentMethod bool) field.
|
||||
}
|
||||
allErrs = append(allErrs, validatePlatform(&c.Platform, usingAgentMethod, field.NewPath("platform"), c.Networking, c)...)
|
||||
if c.ControlPlane != nil {
|
||||
allErrs = append(allErrs, validateControlPlane(&c.Platform, c.ControlPlane, field.NewPath("controlPlane"))...)
|
||||
allErrs = append(allErrs, validateControlPlane(c, field.NewPath("controlPlane"))...)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(field.NewPath("controlPlane"), "controlPlane is required"))
|
||||
}
|
||||
@@ -744,8 +746,10 @@ func validateOVNIPv4InternalJoinSubnet(n *types.Networking, fldPath *field.Path)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateControlPlane(platform *types.Platform, pool *types.MachinePool, fldPath *field.Path) field.ErrorList {
|
||||
func validateControlPlane(installConfig *types.InstallConfig, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
platform := &installConfig.Platform
|
||||
pool := installConfig.ControlPlane
|
||||
if pool.Name != types.MachinePoolControlPlaneRoleName {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("name"), pool.Name, []string{types.MachinePoolControlPlaneRoleName}))
|
||||
}
|
||||
@@ -753,6 +757,7 @@ func validateControlPlane(platform *types.Platform, pool *types.MachinePool, fld
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "number of control plane replicas must be positive"))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateMachinePool(platform, pool, fldPath)...)
|
||||
allErrs = append(allErrs, validateFencingCredentials(installConfig)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@@ -804,6 +809,10 @@ func validateCompute(platform *types.Platform, control *types.MachinePool, pools
|
||||
allErrs = append(allErrs, field.Invalid(poolFldPath.Child("architecture"), p.Architecture, "heteregeneous multi-arch is not supported; compute pool architecture must match control plane"))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateMachinePool(platform, &p, poolFldPath)...)
|
||||
|
||||
if p.Fencing != nil {
|
||||
allErrs = append(allErrs, field.Invalid(poolFldPath.Child("fencing"), p.Fencing, "fencing is only valid for control plane"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@@ -1466,6 +1475,10 @@ func validateGatedFeatures(c *types.InstallConfig) field.ErrorList {
|
||||
gatedFeatures = append(gatedFeatures, azurevalidation.GatedFeatures(c)...)
|
||||
}
|
||||
|
||||
if c.ControlPlane != nil {
|
||||
gatedFeatures = append(gatedFeatures, defaultsvalidation.GatedFeatures(c)...)
|
||||
}
|
||||
|
||||
fg := c.EnabledFeatureGates()
|
||||
errMsgTemplate := "this field is protected by the %s feature gate which must be enabled through either the TechPreviewNoUpgrade or CustomNoUpgrade feature set"
|
||||
|
||||
@@ -1544,3 +1557,39 @@ func isV4NodeSubnetLargeEnough(cn []types.ClusterNetworkEntry, nodeSubnet *ipnet
|
||||
// reserve one IP for the gw, one IP for network and one for broadcasting
|
||||
return maxNodesNum < (1<<(addrLen-intSubnetMask) - 3), nil
|
||||
}
|
||||
|
||||
// validateCredentialsNumber in case fencing credentials exists validates there are exactly 2.
|
||||
func validateCredentialsNumber(controlPlane *types.MachinePool, fencing *types.Fencing, fldPath *field.Path) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
if controlPlane == nil || controlPlane.Replicas == nil {
|
||||
// invalid use case covered by a different validation.
|
||||
return errs
|
||||
}
|
||||
numOfCpReplicas := *controlPlane.Replicas
|
||||
var numOfCredentials int
|
||||
if fencing != nil {
|
||||
numOfCredentials = len(fencing.Credentials)
|
||||
}
|
||||
if numOfCpReplicas == 2 {
|
||||
if numOfCredentials != 2 {
|
||||
errs = append(errs, field.Forbidden(fldPath, fmt.Sprintf("there should be exactly two fencing credentials to support the two node cluster, instead %d credentials were found", numOfCredentials)))
|
||||
}
|
||||
} else {
|
||||
if numOfCredentials != 0 {
|
||||
errs = append(errs, field.Forbidden(fldPath, fmt.Sprintf("there should not be any fencing credentials configured for a non dual replica control plane (Two Nodes Fencing) cluster, instead %d credentials were found", numOfCredentials)))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateFencingCredentials(installConfig *types.InstallConfig) (errors field.ErrorList) {
|
||||
fldPath := field.NewPath("controlPlane", "fencing")
|
||||
fencingCredentials := installConfig.ControlPlane.Fencing
|
||||
allErrs := field.ErrorList{}
|
||||
if fencingCredentials != nil {
|
||||
allErrs = append(allErrs, common.ValidateUniqueAndRequiredFields(fencingCredentials.Credentials, fldPath, func([]byte) bool { return false }, "credentials")...)
|
||||
}
|
||||
allErrs = append(allErrs, validateCredentialsNumber(installConfig.ControlPlane, fencingCredentials, fldPath.Child("credentials"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
@@ -2797,3 +2798,255 @@ func TestValidateReleaseArchitecture(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateTNF(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
config *types.InstallConfig
|
||||
machinePool *types.MachinePool
|
||||
checkCompute bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
config: installConfig().CpReplicas(3).build(),
|
||||
name: "valid_empty_credentials",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1(), c2())).
|
||||
CpReplicas(2).
|
||||
build(),
|
||||
name: "valid_two_credentials",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1(), c2(), c3())).
|
||||
CpReplicas(2).build(),
|
||||
name: "invalid_number_of_credentials_for_dual_replica",
|
||||
expected: "controlPlane.fencing.credentials: Forbidden: there should be exactly two fencing credentials to support the two node cluster, instead 3 credentials were found",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1(), c2())).
|
||||
CpReplicas(3).build(),
|
||||
name: "invalid_number_of_credentials_for_non_dual_replica",
|
||||
expected: "controlPlane.fencing.credentials: Forbidden: there should not be any fencing credentials configured for a non dual replica control plane \\(Two Nodes Fencing\\) cluster, instead 2 credentials were found",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(
|
||||
c1().BMCAddress("ipmi://192.168.111.1"),
|
||||
c2().BMCAddress("ipmi://192.168.111.1"))).
|
||||
CpReplicas(2).build(),
|
||||
name: "duplicate_bmc_address",
|
||||
expected: "controlPlane.fencing.credentials\\[1\\].Address: Duplicate value: \"ipmi://192.168.111.1\"",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1().BMCAddress(""), c2())).
|
||||
CpReplicas(2).build(),
|
||||
name: "bmc_address_required",
|
||||
expected: "controlPlane.fencing.credentials\\[0\\].Address: Required value: missing Address",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1(), c2().BMCUsername(""))).
|
||||
CpReplicas(2).build(),
|
||||
name: "bmc_username_required",
|
||||
expected: "controlPlane.fencing.credentials\\[1\\].Username: Required value: missing Username",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1().BMCPassword(""), c2())).
|
||||
CpReplicas(2).build(),
|
||||
name: "bmc_password_required",
|
||||
expected: "controlPlane.fencing.credentials\\[0\\].Password: Required value: missing Password",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Credential(c1().HostName(""), c2())).
|
||||
CpReplicas(2).build(),
|
||||
name: "host_name_required",
|
||||
expected: "controlPlane.fencing.credentials\\[0\\].HostName: Required value: missing HostName",
|
||||
},
|
||||
{
|
||||
config: installConfig().
|
||||
MachinePoolCP(machinePool().
|
||||
Architecture(types.ArchitectureAMD64).
|
||||
Credential(c1(), c2())).
|
||||
MachinePoolCompute(
|
||||
machinePool().Name("worker").
|
||||
Hyperthreading(types.HyperthreadingDisabled).
|
||||
Architecture(types.ArchitectureAMD64).
|
||||
Replicas(ptr.Int64(3)).
|
||||
Credential(c1())).
|
||||
CpReplicas(2).build(),
|
||||
name: "host_name_required",
|
||||
checkCompute: true,
|
||||
expected: `compute\[\d+\]\.fencing: Invalid value: types\.Fencing\{Credentials:\[\]\*types\.Credential\{\(\*types\.Credential\)\(\S+\)\}\}: fencing is only valid for control plane`,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Build default wrapping installConfig
|
||||
var err error
|
||||
if tc.checkCompute {
|
||||
err = validateCompute(&tc.config.Platform, tc.config.ControlPlane, tc.config.Compute, field.NewPath("compute"), false).ToAggregate()
|
||||
} else {
|
||||
err = validateFencingCredentials(tc.config).ToAggregate()
|
||||
}
|
||||
|
||||
if tc.expected == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Regexp(t, tc.expected, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type credentialBuilder struct {
|
||||
types.Credential
|
||||
}
|
||||
|
||||
func (hb *credentialBuilder) build() *types.Credential {
|
||||
return &hb.Credential
|
||||
}
|
||||
|
||||
func c1() *credentialBuilder {
|
||||
return &credentialBuilder{
|
||||
types.Credential{
|
||||
HostName: "host1",
|
||||
Username: "root",
|
||||
Password: "password",
|
||||
Address: "ipmi://192.168.111.1",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func c2() *credentialBuilder {
|
||||
return &credentialBuilder{
|
||||
types.Credential{
|
||||
HostName: "host2",
|
||||
Username: "root",
|
||||
Password: "password",
|
||||
Address: "ipmi://192.168.111.2",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func c3() *credentialBuilder {
|
||||
return &credentialBuilder{
|
||||
types.Credential{
|
||||
HostName: "host3",
|
||||
Username: "root",
|
||||
Password: "password",
|
||||
Address: "ipmi://192.168.111.3",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (hb *credentialBuilder) HostName(value string) *credentialBuilder {
|
||||
hb.Credential.HostName = value
|
||||
return hb
|
||||
}
|
||||
|
||||
func (hb *credentialBuilder) BMCAddress(value string) *credentialBuilder {
|
||||
hb.Credential.Address = value
|
||||
return hb
|
||||
}
|
||||
|
||||
func (hb *credentialBuilder) BMCUsername(value string) *credentialBuilder {
|
||||
hb.Credential.Username = value
|
||||
return hb
|
||||
}
|
||||
|
||||
func (hb *credentialBuilder) BMCPassword(value string) *credentialBuilder {
|
||||
hb.Credential.Password = value
|
||||
return hb
|
||||
}
|
||||
|
||||
type machinePoolBuilder struct {
|
||||
types.MachinePool
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) build() *types.MachinePool {
|
||||
return &pb.MachinePool
|
||||
}
|
||||
|
||||
func machinePool() *machinePoolBuilder {
|
||||
return &machinePoolBuilder{
|
||||
types.MachinePool{}}
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) Name(name string) *machinePoolBuilder {
|
||||
pb.MachinePool.Name = name
|
||||
return pb
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) Replicas(replicas *int64) *machinePoolBuilder {
|
||||
pb.MachinePool.Replicas = replicas
|
||||
return pb
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) Architecture(architecture types.Architecture) *machinePoolBuilder {
|
||||
pb.MachinePool.Architecture = architecture
|
||||
return pb
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) Hyperthreading(hyperthreading types.HyperthreadingMode) *machinePoolBuilder {
|
||||
pb.MachinePool.Hyperthreading = hyperthreading
|
||||
return pb
|
||||
}
|
||||
|
||||
func (pb *machinePoolBuilder) Credential(builders ...*credentialBuilder) *machinePoolBuilder {
|
||||
pb.MachinePool.Fencing = &types.Fencing{}
|
||||
for _, builder := range builders {
|
||||
pb.MachinePool.Fencing.Credentials = append(pb.MachinePool.Fencing.Credentials, builder.build())
|
||||
}
|
||||
return pb
|
||||
}
|
||||
|
||||
type installConfigBuilder struct {
|
||||
types.InstallConfig
|
||||
}
|
||||
|
||||
func installConfig() *installConfigBuilder {
|
||||
return &installConfigBuilder{
|
||||
InstallConfig: types.InstallConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
func (icb *installConfigBuilder) CpReplicas(numOfCpReplicas int64) *installConfigBuilder {
|
||||
if icb.InstallConfig.ControlPlane == nil {
|
||||
icb.InstallConfig.ControlPlane = &types.MachinePool{}
|
||||
}
|
||||
icb.InstallConfig.ControlPlane.Replicas = &numOfCpReplicas
|
||||
return icb
|
||||
}
|
||||
|
||||
func (icb *installConfigBuilder) MachinePoolCP(builder *machinePoolBuilder) *installConfigBuilder {
|
||||
icb.InstallConfig.ControlPlane = builder.build()
|
||||
return icb
|
||||
}
|
||||
|
||||
func (icb *installConfigBuilder) MachinePoolCompute(builders ...*machinePoolBuilder) *installConfigBuilder {
|
||||
for _, builder := range builders {
|
||||
icb.InstallConfig.Compute = append(icb.InstallConfig.Compute, *builder.build())
|
||||
}
|
||||
return icb
|
||||
}
|
||||
|
||||
func (icb *installConfigBuilder) build() *types.InstallConfig {
|
||||
return &icb.InstallConfig
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user