diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index ea6c03f84a9..78c452a1525 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,20 @@ 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 { + 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 equal 3")) + } } allErrs = append(allErrs, ValidateMachinePool(platform, pool, fldPath)...) return allErrs @@ -1345,3 +1352,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(bootstrapInPlace bool, p *types.Platform) bool { + if p.AWS != nil || p.GCP != nil || p.Azure != nil { + // Single node OpenShift installations supported without `bootstrapInPlace` + return true + } + if bootstrapInPlace && p.None != nil { + // Single node OpenShift installations supported with `bootstrapInPlace` + return true + } + return false +}