diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index ea6c03f84a9..f81797af95d 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -121,7 +121,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.Platform, c.ControlPlane, c.IsSingleNodeOpenShift(), field.NewPath("controlPlane"))...) } else { allErrs = append(allErrs, field.Required(field.NewPath("controlPlane"), "controlPlane is required")) } @@ -596,13 +596,22 @@ func validateNetworkingClusterNetworkMTU(c *types.InstallConfig, fldPath *field. return allErrs } -func validateControlPlane(platform *types.Platform, pool *types.MachinePool, fldPath *field.Path) field.ErrorList { +func validateControlPlane(platform *types.Platform, pool *types.MachinePool, isSingleNodeOpenShift bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if pool.Name != types.MachinePoolControlPlaneRoleName { allErrs = append(allErrs, field.NotSupported(fldPath.Child("name"), pool.Name, []string{types.MachinePoolControlPlaneRoleName})) } - if pool.Replicas != nil && *pool.Replicas == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "number of control plane replicas must be positive")) + if pool.Replicas == nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "number of control plane replicas must be provided")) + } else { + switch { + case *pool.Replicas == 0: + allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "number of control plane replicas must be positive")) + case *pool.Replicas == 1 && !supportedSingleNodePlatform(isSingleNodeOpenShift, platform): + allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "Single Node OpenShift(SNO) not supported by this install method for this platform")) + case *pool.Replicas < 3: + allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), pool.Replicas, "number of control plane replicas must be >= 3")) + } } allErrs = append(allErrs, ValidateMachinePool(platform, pool, fldPath)...) return allErrs @@ -1345,3 +1354,17 @@ func validateReleaseArchitecture(controlPlanePool *types.MachinePool, computePoo return allErrs } + +// supportedSingleNodePlatform indicates if the IPI Installer can be used to install SNO on +// a platform. +func supportedSingleNodePlatform(isSingleNodeOpenShift bool, p *types.Platform) bool { + if p.AWS != nil || p.GCP != nil || p.Azure != nil { + // Single node OpenShift installations are supported without `bootstrapInPlace` being configured + return true + } + if isSingleNodeOpenShift && (p.BareMetal != nil || p.None != nil) { + // Single node OpenShift installations are supported with `bootstrapInPlace` being configured + return true + } + return false +}