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
13 changes: 13 additions & 0 deletions pkg/types/validation/installconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
utilsnet "k8s.io/utils/net"
"k8s.io/utils/ptr"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/api/features"
Expand Down Expand Up @@ -797,6 +798,7 @@ func validateComputeEdge(platform *types.Platform, pName string, fldPath *field.

func validateCompute(platform *types.Platform, control *types.MachinePool, pools []types.MachinePool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
controlReplicas, computeReplicas := int64(0), int64(0)
// Multi Arch is enabled by default for AWS and GCP, these are also the only
// two valid platforms for multi arch installations.
isMultiArchEnabled := platform.AWS != nil || platform.GCP != nil
Expand All @@ -805,6 +807,7 @@ func validateCompute(platform *types.Platform, control *types.MachinePool, pools
poolFldPath := fldPath.Index(i)
switch p.Name {
case types.MachinePoolComputeRoleName:
computeReplicas += ptr.Deref(p.Replicas, 0)
case types.MachinePoolEdgeRoleName:
allErrs = append(allErrs, validateComputeEdge(platform, p.Name, poolFldPath, poolFldPath)...)
default:
Expand All @@ -824,6 +827,16 @@ func validateCompute(platform *types.Platform, control *types.MachinePool, pools
allErrs = append(allErrs, field.Invalid(poolFldPath.Child("fencing"), p.Fencing, "fencing is only valid for control plane"))
}
}

if control != nil {
controlReplicas = ptr.Deref(control.Replicas, 3)
}

// Validate that exactly 1 worker node is allowed only when a single control plane node is provisioned on platforms None or External
if computeReplicas == 1 && controlReplicas != 1 && !(platform.None != nil || platform.External != nil) {
Comment on lines +835 to +836
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate that exactly 1 worker node is allowed only when a single control plane node is provisioned on platforms None or External

Regarding the comment, it hints that only SNO and platform none/external is allowed. But from #10042 (comment) and official doc, either SNO or none/external platform is allowed, which also matches the current code implementation.

I guess the comment is incorrect?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Validate that exactly 1 worker node is allowed only when a single control plane node is provisioned on platforms None or External
if computeReplicas == 1 && controlReplicas != 1 && !(platform.None != nil || platform.External != nil) {
// Validate that exactly 1 worker node is allowed only in either the following scenarios:
// 1. A single control plane node (SNO) is provisioned.
// 2. Platforms are None or External.
if computeReplicas == 1 {
isSNO := controlReplicas == 1
isAllowedPlatform := platform.None != nil || platform.External != nil
if !isSNO && !isAllowedPlatform {

nit: It seems a bit more verbose, but improves readability? WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1+1 is actually not a valid topology on day 1. Assisted is not capable of creating an SNO with worker nodes.
But 3+1 is actually a valid topology, the bug report notwithstanding.

allErrs = append(allErrs, field.Invalid(fldPath, computeReplicas, "exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or 2 workers for multi-node clusters"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say

Suggested change
allErrs = append(allErrs, field.Invalid(fldPath, computeReplicas, "exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or 2 workers for multi-node clusters"))
allErrs = append(allErrs, field.Invalid(fldPath, computeReplicas, "exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or at least 2 workers for multi-node clusters"))

in this error msg?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 as users might mistake it to exactly 2. We should probably use the correct field path:

Suggested change
allErrs = append(allErrs, field.Invalid(fldPath, computeReplicas, "exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or 2 workers for multi-node clusters"))
allErrs = append(allErrs, field.Invalid(fldPath.Child("replicas"), computeReplicas, "exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or at least 2 workers for multi-node clusters"))

}

return allErrs
}

Expand Down
85 changes: 80 additions & 5 deletions pkg/types/validation/installconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/pointer"
utilsslice "k8s.io/utils/strings/slices"

configv1 "github.com/openshift/api/config/v1"
Expand All @@ -31,6 +30,12 @@ import (

const TechPreviewNoUpgrade = "TechPreviewNoUpgrade"

func validControlPlane() *types.MachinePool {
mp := validMachinePool("master")
mp.Replicas = ptr.Int64(3)
return mp
}

func validInstallConfig() *types.InstallConfig {
return &types.InstallConfig{
TypeMeta: metav1.TypeMeta{
Expand All @@ -41,8 +46,8 @@ func validInstallConfig() *types.InstallConfig {
},
BaseDomain: "test-domain",
Networking: validIPv4NetworkingConfig(),
ControlPlane: validMachinePool("master"),
Compute: []types.MachinePool{*validMachinePool("worker")},
ControlPlane: validControlPlane(),
Compute: []types.MachinePool{func() types.MachinePool { p := *validMachinePool("worker"); p.Replicas = ptr.Int64(0); return p }()},
Platform: types.Platform{
AWS: validAWSPlatform(),
},
Expand Down Expand Up @@ -801,7 +806,7 @@ func TestValidateInstallConfig(t *testing.T) {
name: "control plane with 0 replicas",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = pointer.Int64Ptr(0)
c.ControlPlane.Replicas = ptr.Int64(0)
return c
}(),
expectedError: `^controlPlane.replicas: Invalid value: 0: number of control plane replicas must be positive$`,
Expand Down Expand Up @@ -850,7 +855,69 @@ func TestValidateInstallConfig(t *testing.T) {
c.Compute = []types.MachinePool{
func() types.MachinePool {
p := *validMachinePool("worker")
p.Replicas = pointer.Int64Ptr(0)
p.Replicas = ptr.Int64(0)
return p
}(),
}
return c
}(),
},
{
name: "one compute replica, one control plane, AWS",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
c := validInstallConfig()
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(1)

The default now is 3 right? As in line 33-37. So we need to set it 1, which makes this case valid (i.e. expectedError to be nil)?

c.Compute = []types.MachinePool{
func() types.MachinePool {
p := *validMachinePool("worker")
p.Replicas = ptr.Int64(1)
return p
}(),
}
return c
}(),
expectedError: `compute: Invalid value: 1: exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or 2 workers for multi-node clusters`,
},
{
name: "one compute replicas, 3 control plane, AWS",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(3)
c.Compute = []types.MachinePool{
func() types.MachinePool {
p := *validMachinePool("worker")
p.Replicas = ptr.Int64(1)
return p
}(),
}
return c
}(),
expectedError: `compute: Invalid value: 1: exactly 1 worker node is not allowed for this platform and configuration. Use 0 workers for single-node clusters or 2 workers for multi-node clusters`,
},
{
name: "one compute replicas, one control plane, None/External",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.Platform = types.Platform{
None: &none.Platform{},
}
c.Compute = []types.MachinePool{
func() types.MachinePool {
p := *validMachinePool("worker")
p.Replicas = ptr.Int64(1)
return p
}(),
}
return c
}(),
},
{
name: "two compute replicas allowed",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.Compute = []types.MachinePool{
func() types.MachinePool {
p := *validMachinePool("worker")
p.Replicas = ptr.Int64(2)
return p
}(),
}
Expand Down Expand Up @@ -910,6 +977,7 @@ func TestValidateInstallConfig(t *testing.T) {
name: "valid baremetal platform",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(1)
c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11"}
c.Capabilities.AdditionalEnabledCapabilities = append(c.Capabilities.AdditionalEnabledCapabilities, configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityOperatorLifecycleManager)
c.Platform = types.Platform{
Expand Down Expand Up @@ -1672,6 +1740,7 @@ func TestValidateInstallConfig(t *testing.T) {
name: "invalidly set cloud credentials mode",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(1)
c.Platform = types.Platform{BareMetal: validBareMetalPlatform()}
c.Capabilities = &types.Capabilities{BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetCurrent}
c.CredentialsMode = types.PassthroughCredentialsMode
Expand Down Expand Up @@ -1859,6 +1928,7 @@ func TestValidateInstallConfig(t *testing.T) {
name: "apivip_v4_not_in_machinenetwork_cidr_usermanaged_loadbalancer",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(1)
c.FeatureSet = configv1.TechPreviewNoUpgrade
c.Networking.MachineNetwork = []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")},
Expand Down Expand Up @@ -2160,6 +2230,7 @@ func TestValidateInstallConfig(t *testing.T) {
name: "identical_apivip_ingressvip_usermanaged_loadbalancer",
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.ControlPlane.Replicas = ptr.Int64(1)
c.FeatureSet = configv1.TechPreviewNoUpgrade
c.Platform = types.Platform{
BareMetal: validBareMetalPlatform(),
Expand Down Expand Up @@ -2548,6 +2619,7 @@ func TestValidateInstallConfig(t *testing.T) {
c := validInstallConfig()
c.BareMetal = validBareMetalPlatform()
c.AWS = nil
c.ControlPlane.Replicas = ptr.Int64(1)
c.Capabilities = &types.Capabilities{
BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetCurrent,
}
Expand All @@ -2560,6 +2632,7 @@ func TestValidateInstallConfig(t *testing.T) {
c := validInstallConfig()
c.BareMetal = validBareMetalPlatform()
c.AWS = nil
c.ControlPlane.Replicas = ptr.Int64(1)
c.Capabilities = &types.Capabilities{
BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone,
AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityBaremetal, configv1.ClusterVersionCapabilityMachineAPI, configv1.ClusterVersionCapabilityIngress},
Expand All @@ -2584,6 +2657,7 @@ func TestValidateInstallConfig(t *testing.T) {
c := validInstallConfig()
c.Platform.AWS = nil
c.Platform.None = &none.Platform{}
c.ControlPlane.Replicas = ptr.Int64(1)
c.Capabilities = &types.Capabilities{
BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone,
AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityIngress},
Expand All @@ -2596,6 +2670,7 @@ func TestValidateInstallConfig(t *testing.T) {
installConfig: func() *types.InstallConfig {
c := validInstallConfig()
c.Platform.AWS = nil
c.ControlPlane.Replicas = ptr.Int64(1)
c.Platform.BareMetal = validBareMetalPlatform()
c.Capabilities = &types.Capabilities{
BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone,
Expand Down