diff --git a/cmd/node-joiner/testdata/add-nodes-overrides.txt b/cmd/node-joiner/testdata/add-nodes-overrides.txt index 0b8de8f516..1ba80eced7 100644 --- a/cmd/node-joiner/testdata/add-nodes-overrides.txt +++ b/cmd/node-joiner/testdata/add-nodes-overrides.txt @@ -50,6 +50,7 @@ spec: status: agentLabelSelector: {} bootArtifacts: + discoveryIgnitionURL: "" initrd: "" ipxeScript: "" kernel: "" diff --git a/cmd/openshift-install/testdata/agent/image/assets/with_additional_ntp_sources.txt b/cmd/openshift-install/testdata/agent/image/assets/with_additional_ntp_sources.txt index 9e2ca55bdb..e3c4dff0de 100644 --- a/cmd/openshift-install/testdata/agent/image/assets/with_additional_ntp_sources.txt +++ b/cmd/openshift-install/testdata/agent/image/assets/with_additional_ntp_sources.txt @@ -74,6 +74,7 @@ spec: status: agentLabelSelector: {} bootArtifacts: + discoveryIgnitionURL: "" initrd: "" ipxeScript: "" kernel: "" diff --git a/cmd/openshift-install/testdata/agent/image/manifests/default_manifests.txt b/cmd/openshift-install/testdata/agent/image/manifests/default_manifests.txt index 4275248113..bda0098689 100644 --- a/cmd/openshift-install/testdata/agent/image/manifests/default_manifests.txt +++ b/cmd/openshift-install/testdata/agent/image/manifests/default_manifests.txt @@ -142,6 +142,7 @@ spec: status: agentLabelSelector: {} bootArtifacts: + discoveryIgnitionURL: "" initrd: "" ipxeScript: "" kernel: "" diff --git a/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/from-ztp-manifests.txt b/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/from-ztp-manifests.txt index 809fd67a0f..662863802c 100644 --- a/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/from-ztp-manifests.txt +++ b/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/from-ztp-manifests.txt @@ -81,6 +81,7 @@ spec: status: agentLabelSelector: {} bootArtifacts: + discoveryIgnitionURL: "" initrd: "" ipxeScript: "" kernel: "" diff --git a/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/interactive.txt b/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/interactive.txt index 1c8fec2803..f4990fad8c 100644 --- a/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/interactive.txt +++ b/cmd/openshift-install/testdata/agent/unconfigured-ignition/configurations/interactive.txt @@ -82,6 +82,7 @@ spec: status: agentLabelSelector: {} bootArtifacts: + discoveryIgnitionURL: "" initrd: "" ipxeScript: "" kernel: "" diff --git a/data/data/agent/files/usr/local/bin/start-cluster-installation.sh b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh index cc614c3cf1..7df66bacb4 100644 --- a/data/data/agent/files/usr/local/bin/start-cluster-installation.sh +++ b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh @@ -18,8 +18,9 @@ done printf '\nInfra env id is %s\n' "${INFRA_ENV_ID}" 1>&2 -total_required_nodes=$(( REQUIRED_MASTER_NODES + REQUIRED_WORKER_NODES )) +total_required_nodes=$(( REQUIRED_MASTER_NODES + REQUIRED_ARBITER_NODES + REQUIRED_WORKER_NODES )) echo "Number of required master nodes: ${REQUIRED_MASTER_NODES}" 1>&2 +echo "Number of required arbiter nodes: ${REQUIRED_ARBITER_NODES}" 1>&2 echo "Number of required worker nodes: ${REQUIRED_WORKER_NODES}" 1>&2 echo "Total number of required nodes: ${total_required_nodes}" 1>&2 diff --git a/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template index 491f95c41c..973c02cb8d 100644 --- a/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template +++ b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template @@ -9,9 +9,10 @@ DISK_ENCRYPTION_SUPPORT=true DUMMY_IGNITION=false ENABLE_SINGLE_NODE_DNSMASQ=true EPHEMERAL_INSTALLER_CLUSTER_TLS_CERTS_OVERRIDE_DIR=/opt/agent/tls -HW_VALIDATOR_REQUIREMENTS=[{"version":"default","master":{"cpu_cores":4,"ram_mib":16384,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":100,"packet_loss_percentage":0},"worker":{"cpu_cores":2,"ram_mib":8192,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":1000,"packet_loss_percentage":10},"sno":{"cpu_cores":8,"ram_mib":16384,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10}}] +HW_VALIDATOR_REQUIREMENTS=[{"version":"default","master":{"cpu_cores":4,"ram_mib":16384,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":100,"packet_loss_percentage":0},"arbiter":{"cpu_cores":2,"ram_mib":8192,"disk_size_gb":50,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":1000,"packet_loss_percentage":0},"worker":{"cpu_cores":2,"ram_mib":8192,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":1000,"packet_loss_percentage":10},"sno":{"cpu_cores":8,"ram_mib":16384,"disk_size_gb":100,"installation_disk_speed_threshold_ms":10}}] INSTALL_INVOKER=agent-installer IPV6_SUPPORT=true +TNA_CLUSTERS_SUPPORT=true LOG_LEVEL=debug NTP_DEFAULT_SERVER= PUBLIC_CONTAINER_REGISTRIES={{.PublicContainerRegistries}} diff --git a/data/data/agent/files/usr/local/share/start-cluster/start-cluster.env.template b/data/data/agent/files/usr/local/share/start-cluster/start-cluster.env.template index 285553d508..2cda3fca8e 100644 --- a/data/data/agent/files/usr/local/share/start-cluster/start-cluster.env.template +++ b/data/data/agent/files/usr/local/share/start-cluster/start-cluster.env.template @@ -1,2 +1,3 @@ REQUIRED_MASTER_NODES={{.ControlPlaneAgents}} +REQUIRED_ARBITER_NODES={{.ArbiterAgents}} REQUIRED_WORKER_NODES={{.WorkerAgents}} diff --git a/pkg/asset/agent/agentconfig/agenthosts.go b/pkg/asset/agent/agentconfig/agenthosts.go index 62ac429a7b..d92d975630 100644 --- a/pkg/asset/agent/agentconfig/agenthosts.go +++ b/pkg/asset/agent/agentconfig/agenthosts.go @@ -26,8 +26,9 @@ var ( ) const ( - masterRole string = "master" - workerRole string = "worker" + masterRole string = "master" + workerRole string = "worker" + arbiterRole string = "arbiter" ) type nmStateInterface struct { @@ -193,9 +194,9 @@ func (a *AgentHosts) validateHostRootDeviceHints(hostPath *field.Path, host agen func (a *AgentHosts) validateRoles(hostPath *field.Path, host agent.Host) field.ErrorList { var allErrs field.ErrorList - if len(host.Role) > 0 && host.Role != masterRole && host.Role != workerRole { + if len(host.Role) > 0 && host.Role != masterRole && host.Role != arbiterRole && host.Role != workerRole { allErrs = append(allErrs, field.NotSupported(hostPath.Child("role"), host.Role, - []string{masterRole, workerRole})) + []string{masterRole, workerRole, arbiterRole})) } return allErrs diff --git a/pkg/asset/agent/agentconfig/agenthosts_test.go b/pkg/asset/agent/agentconfig/agenthosts_test.go index a7d4bc9f58..3b5a0d2e04 100644 --- a/pkg/asset/agent/agentconfig/agenthosts_test.go +++ b/pkg/asset/agent/agentconfig/agenthosts_test.go @@ -171,7 +171,7 @@ func TestAgentHosts_Generate(t *testing.T) { &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, &joiner.AddNodesConfig{}, getInstallConfigSingleHost(), - getAgentConfigMultiHost(), + getAgentConfigMultiHost("worker"), }, expectedConfig: agentHosts().hosts( agentHost().name("test").role("master").interfaces(iface("enp3s1", "28:d2:44:d2:b2:1a")).deviceHint().networkConfig(agentNetworkConfigOne), @@ -240,7 +240,7 @@ func TestAgentHosts_Generate(t *testing.T) { getInstallConfigSingleHost(), getAgentConfigInvalidHostRole(), }, - expectedError: "invalid Hosts configuration: hosts[0].role: Unsupported value: \"invalid-role\": supported values: \"master\", \"worker\"", + expectedError: "invalid Hosts configuration: hosts[0].role: Unsupported value: \"invalid-role\": supported values: \"master\", \"worker\", \"arbiter\"", expectedConfig: nil, }, { @@ -332,6 +332,18 @@ func TestAgentHosts_Generate(t *testing.T) { agentHost().name("test").role("master").interfaces(iface("enp3s1", "28:d2:44:d2:b2:1a")).deviceHint().networkConfig(agentNetworkConfigEmbeddedRendezvousIPOne), agentHost().name("test-2").role("worker").interfaces(iface("enp3s1", "28:d2:44:d2:b2:1b")).networkConfig(agentNetworkConfigEmbeddedRendezvousIPTwo)), }, + { + name: "multi-host-from-agent-config-with-arbiter", + dependencies: []asset.Asset{ + &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, + &joiner.AddNodesConfig{}, + getInstallConfigSingleHost(), + getAgentConfigMultiHost("arbiter"), + }, + expectedConfig: agentHosts().hosts( + agentHost().name("test").role("master").interfaces(iface("enp3s1", "28:d2:44:d2:b2:1a")).deviceHint().networkConfig(agentNetworkConfigOne), + agentHost().name("test-2").role("arbiter").interfaces(iface("enp3s1", "28:d2:44:d2:b2:1b")).networkConfig(agentNetworkConfigTwo)), + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -439,12 +451,12 @@ func getAgentConfigSingleHost() *AgentConfig { return a } -func getAgentConfigMultiHost() *AgentConfig { +func getAgentConfigMultiHost(role string) *AgentConfig { a := getAgentConfigSingleHost() a.Config.Hosts[0].NetworkConfig.Raw = []byte(agentNetworkConfigOne) host := agent.Host{ Hostname: "test-2", - Role: "worker", + Role: role, Interfaces: []*aiv1beta1.Interface{ { Name: "enp3s1", @@ -460,7 +472,7 @@ func getAgentConfigMultiHost() *AgentConfig { } func getAgentConfigMultiHostEmbeddedRendezvousIP() *AgentConfig { - a := getAgentConfigMultiHost() + a := getAgentConfigMultiHost("worker") a.Config.RendezvousIP = "192.168.111.1" a.Config.Hosts[0].NetworkConfig.Raw = []byte(agentNetworkConfigEmbeddedRendezvousIPOne) a.Config.Hosts[1].NetworkConfig.Raw = []byte(agentNetworkConfigEmbeddedRendezvousIPTwo) @@ -536,7 +548,7 @@ func getAgentConfigMissingInterfaces() *AgentConfig { } func getAgentConfigInvalidRendezvousIP() *AgentConfig { - a := getAgentConfigMultiHost() + a := getAgentConfigMultiHost("worker") a.Config.RendezvousIP = "192.168.111.81" return a } diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go index 565cb151af..dddb4c362d 100644 --- a/pkg/asset/agent/image/ignition.go +++ b/pkg/asset/agent/image/ignition.go @@ -65,6 +65,7 @@ type agentTemplateData struct { ServiceProtocol string PullSecret string ControlPlaneAgents int + ArbiterAgents int WorkerAgents int ReleaseImages string ReleaseImage string @@ -155,6 +156,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err clusterName := "" imageTypeISO := "full-iso" numMasters := 0 + numArbiters := 0 numWorkers := 0 enabledServices := getDefaultEnabledServices() openshiftVersion := "" @@ -178,6 +180,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err } // Fetch the required number of master and worker nodes. numMasters = agentManifests.AgentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents + numArbiters = agentManifests.AgentClusterInstall.Spec.ProvisionRequirements.ArbiterAgents numWorkers = agentManifests.AgentClusterInstall.Spec.ProvisionRequirements.WorkerAgents // Enable specific install services enabledServices = append(enabledServices, "start-cluster-installation.service") @@ -202,6 +205,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err // is supported, so forcing the expected number of masters to zero, and assuming implcitly // that all the hosts defined are workers. numMasters = 0 + numArbiters = 0 numWorkers = len(addNodesConfig.Config.Hosts) // Enable add-nodes specific services @@ -290,7 +294,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err authConfig.AuthTokenExpiry, caBundleMount, len(registriesConfig.MirrorConfig) > 0, - numMasters, numWorkers, + numMasters, numArbiters, numWorkers, osImage, infraEnv.Spec.Proxy, ) @@ -413,13 +417,14 @@ func addBootstrapScripts(config *igntypes.Config, releaseImage string) (err erro func getTemplateData(name, pullSecret, releaseImageList, releaseImage, releaseImageMirror, publicContainerRegistries, imageTypeISO, infraEnvID, publicKey, authType, agentAuthToken, userAuthToken, watcherAuthToken, tokenExpiry, caBundleMount string, haveMirrorConfig bool, - numMasters, numWorkers int, + numMasters, numArbiters, numWorkers int, osImage *models.OsImage, proxy *v1beta1.Proxy) *agentTemplateData { return &agentTemplateData{ ServiceProtocol: "http", PullSecret: pullSecret, ControlPlaneAgents: numMasters, + ArbiterAgents: numArbiters, WorkerAgents: numWorkers, ReleaseImages: releaseImageList, ReleaseImage: releaseImage, diff --git a/pkg/asset/agent/image/ignition_test.go b/pkg/asset/agent/image/ignition_test.go index 504e9800d2..b09500ec80 100644 --- a/pkg/asset/agent/image/ignition_test.go +++ b/pkg/asset/agent/image/ignition_test.go @@ -62,6 +62,7 @@ func TestIgnition_getTemplateData(t *testing.T) { ProvisionRequirements: hiveext.ProvisionRequirements{ ControlPlaneAgents: 3, WorkerAgents: 5, + ArbiterAgents: 1, }, }, } @@ -96,11 +97,12 @@ func TestIgnition_getTemplateData(t *testing.T) { agentAuthToken := "agentAuthToken" userAuthToken := "userAuthToken" watcherAuthToken := "watcherAuthToken" - templateData := getTemplateData(clusterName, pullSecret, releaseImageList, releaseImage, releaseImageMirror, publicContainerRegistries, "minimal-iso", infraEnvID, publicKey, gencrypto.AuthType, agentAuthToken, userAuthToken, watcherAuthToken, "", "", haveMirrorConfig, agentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents, agentClusterInstall.Spec.ProvisionRequirements.WorkerAgents, osImage, proxy) + templateData := getTemplateData(clusterName, pullSecret, releaseImageList, releaseImage, releaseImageMirror, publicContainerRegistries, "minimal-iso", infraEnvID, publicKey, gencrypto.AuthType, agentAuthToken, userAuthToken, watcherAuthToken, "", "", haveMirrorConfig, agentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents, agentClusterInstall.Spec.ProvisionRequirements.ArbiterAgents, agentClusterInstall.Spec.ProvisionRequirements.WorkerAgents, osImage, proxy) assert.Equal(t, clusterName, templateData.ClusterName) assert.Equal(t, "http", templateData.ServiceProtocol) assert.Equal(t, pullSecret, templateData.PullSecret) assert.Equal(t, agentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents, templateData.ControlPlaneAgents) + assert.Equal(t, agentClusterInstall.Spec.ProvisionRequirements.ArbiterAgents, templateData.ArbiterAgents) assert.Equal(t, agentClusterInstall.Spec.ProvisionRequirements.WorkerAgents, templateData.WorkerAgents) assert.Equal(t, releaseImageList, templateData.ReleaseImages) assert.Equal(t, releaseImage, templateData.ReleaseImage) @@ -649,6 +651,7 @@ func buildIgnitionAssetDefaultDependencies(t *testing.T) []asset.Asset { ProvisionRequirements: hiveext.ProvisionRequirements{ ControlPlaneAgents: 3, WorkerAgents: 5, + ArbiterAgents: 1, }, }, }, diff --git a/pkg/asset/agent/installconfig.go b/pkg/asset/agent/installconfig.go index 2abbadf781..69da3df708 100644 --- a/pkg/asset/agent/installconfig.go +++ b/pkg/asset/agent/installconfig.go @@ -88,14 +88,10 @@ func (a *OptionalInstallConfig) validateInstallConfig(ctx context.Context, insta allErrs = append(allErrs, err...) } - if installConfig.FeatureSet != configv1.Default { - allErrs = append(allErrs, field.NotSupported(field.NewPath("featureSet"), installConfig.FeatureSet, []string{string(configv1.Default)})) - } - warnUnusedConfig(installConfig) - numMasters, numWorkers := GetReplicaCount(installConfig) - logrus.Infof(fmt.Sprintf("Configuration has %d master replicas and %d worker replicas", numMasters, numWorkers)) + numMasters, numArbiters, numWorkers := GetReplicaCount(installConfig) + logrus.Infof("Configuration has %d master replicas, %d arbiter replicas, and %d worker replicas", numMasters, numArbiters, numWorkers) if err := a.validateControlPlaneConfiguration(installConfig); err != nil { allErrs = append(allErrs, err...) @@ -258,6 +254,10 @@ func (a *OptionalInstallConfig) validateSNOConfiguration(installConfig *types.In fieldPath = field.NewPath("compute", "replicas") allErrs = append(allErrs, field.Forbidden(fieldPath, fmt.Sprintf("Total number of compute replicas must be 0 when controlPlane.replicas is 1 for platform %s or %s. Found %v", none.Name, external.Name, workers))) } + if installConfig.Arbiter != nil && installConfig.Arbiter.Replicas != nil && *installConfig.Arbiter.Replicas > 0 { + fieldPath = field.NewPath("arbiter", "replicas") + allErrs = append(allErrs, field.Forbidden(fieldPath, fmt.Sprintf("Total number of arbiter replicas must be 0 when controlPlane.replicas is 1 for Single Node Openshift (SNO) cluster. Found %d", *installConfig.Arbiter.Replicas))) + } } return allErrs } @@ -518,12 +518,16 @@ func warnUnusedConfig(installConfig *types.InstallConfig) { } } -// GetReplicaCount gets the configured master and worker replicas. -func GetReplicaCount(installConfig *types.InstallConfig) (numMasters, numWorkers int64) { +// GetReplicaCount gets the configured master, arbiter and worker replicas. +func GetReplicaCount(installConfig *types.InstallConfig) (numMasters, numArbiters, numWorkers int64) { numRequiredMasters := int64(0) if installConfig.ControlPlane != nil && installConfig.ControlPlane.Replicas != nil { numRequiredMasters += *installConfig.ControlPlane.Replicas } + numRequiredArbiters := int64(0) + if installConfig.Arbiter != nil && installConfig.Arbiter.Replicas != nil { + numRequiredArbiters += *installConfig.Arbiter.Replicas + } numRequiredWorkers := int64(0) for _, worker := range installConfig.Compute { @@ -532,5 +536,5 @@ func GetReplicaCount(installConfig *types.InstallConfig) (numMasters, numWorkers } } - return numRequiredMasters, numRequiredWorkers + return numRequiredMasters, numRequiredArbiters, numRequiredWorkers } diff --git a/pkg/asset/agent/manifests/agentclusterinstall.go b/pkg/asset/agent/manifests/agentclusterinstall.go index 2b392d7e1f..e33680f44d 100644 --- a/pkg/asset/agent/manifests/agentclusterinstall.go +++ b/pkg/asset/agent/manifests/agentclusterinstall.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/yaml" + configv1 "github.com/openshift/api/config/v1" operv1 "github.com/openshift/api/operator/v1" hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" @@ -122,6 +123,10 @@ type agentClusterInstallInstallConfigOverrides struct { CPUPartitioning types.CPUPartitioningMode `json:"cpuPartitioningMode,omitempty"` // Allow override of AdditionalTrustBundlePolicy AdditionalTrustBundlePolicy types.PolicyType `json:"additionalTrustBundlePolicy,omitempty"` + // Allow override of FeatureSet + FeatureSet configv1.FeatureSet `json:"featureSet,omitempty"` + // Allow override of FeatureGates + FeatureGates []string `json:"featureGates,omitempty"` } var _ asset.WritableAsset = (*AgentClusterInstall)(nil) @@ -167,6 +172,11 @@ func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Par numberOfWorkers = numberOfWorkers + int(*compute.Replicas) } + numberOfArbiters := 0 + if installConfig.Config.IsArbiterEnabled() { + numberOfArbiters = int(*installConfig.Config.Arbiter.Replicas) + } + clusterNetwork := []hiveext.ClusterNetworkEntry{} for _, cn := range installConfig.Config.Networking.ClusterNetwork { entry := hiveext.ClusterNetworkEntry{ @@ -213,6 +223,7 @@ func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Par SSHPublicKey: strings.Trim(installConfig.Config.SSHKey, "|\n\t"), ProvisionRequirements: hiveext.ProvisionRequirements{ ControlPlaneAgents: int(*installConfig.Config.ControlPlane.Replicas), + ArbiterAgents: numberOfArbiters, WorkerAgents: numberOfWorkers, }, PlatformType: agent.HivePlatformType(installConfig.Config.Platform), @@ -237,6 +248,16 @@ func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Par icOverrides.FIPS = installConfig.Config.FIPS } + if len(installConfig.Config.FeatureSet) > 0 { + icOverridden = true + icOverrides.FeatureSet = installConfig.Config.FeatureSet + } + + if len(installConfig.Config.FeatureGates) > 0 { + icOverridden = true + icOverrides.FeatureGates = installConfig.Config.FeatureGates + } + if installConfig.Config.Proxy != nil { rendezvousIP := "" if agentConfig.Config != nil { diff --git a/pkg/asset/agent/manifests/agentclusterinstall_test.go b/pkg/asset/agent/manifests/agentclusterinstall_test.go index a9ea2317a4..5585fc92ec 100644 --- a/pkg/asset/agent/manifests/agentclusterinstall_test.go +++ b/pkg/asset/agent/manifests/agentclusterinstall_test.go @@ -150,6 +150,11 @@ func TestAgentClusterInstall_Generate(t *testing.T) { installConfigOverrides: `{"additionalTrustBundlePolicy":"Always"}`, }) + installConfigWithArbiter := getValidOptionalInstallConfigArbiter() + goodArbiterACI := getGoodACI() + goodArbiterACI.Spec.ProvisionRequirements.ArbiterAgents = 1 + goodArbiterACI.Spec.ProvisionRequirements.WorkerAgents = 0 + cases := []struct { name string dependencies []asset.Asset @@ -306,6 +311,16 @@ func TestAgentClusterInstall_Generate(t *testing.T) { }, expectedConfig: goodTrustBundlePolicyACI, }, + { + name: "valid configuration with ArbiterAgents", + dependencies: []asset.Asset{ + &workflow.AgentWorkflow{Workflow: workflow.AgentWorkflowTypeInstall}, + installConfigWithArbiter, + &agentconfig.AgentHosts{}, + &agentconfig.AgentConfig{}, + }, + expectedConfig: goodArbiterACI, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -422,6 +437,86 @@ spec: }, expectedError: "", }, + { + name: "valid-config-file-with-arbiter", + data: ` +metadata: + name: test-agent-cluster-install + namespace: cluster0 +spec: + apiVIP: 192.168.111.5 + ingressVIP: 192.168.111.4 + diskEncryption: + enableOn: workers + mode: tpmv2 + platformType: BareMetal + clusterDeploymentRef: + name: ostest + imageSetRef: + name: openshift-v4.10.0 + networking: + machineNetwork: + - cidr: 10.10.11.0/24 + clusterNetwork: + - cidr: 10.128.0.0/14 + hostPrefix: 23 + serviceNetwork: + - 172.30.0.0/16 + networkType: OVNKubernetes + provisionRequirements: + controlPlaneAgents: 3 + workerAgents: 2 + arbiterAgents: 1 + sshPublicKey: | + ssh-rsa AAAAmyKey`, + expectedFound: true, + expectedConfig: &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-agent-cluster-install", + Namespace: "cluster0", + }, + Spec: hiveext.AgentClusterInstallSpec{ + APIVIP: "192.168.111.5", + IngressVIP: "192.168.111.4", + DiskEncryption: &hiveext.DiskEncryption{ + EnableOn: swag.String("workers"), + Mode: swag.String("tpmv2"), + }, + PlatformType: hiveext.BareMetalPlatformType, + ClusterDeploymentRef: corev1.LocalObjectReference{ + Name: "ostest", + }, + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: "openshift-v4.10.0", + }, + Networking: hiveext.Networking{ + MachineNetwork: []hiveext.MachineNetworkEntry{ + { + CIDR: "10.10.11.0/24", + }, + }, + ClusterNetwork: []hiveext.ClusterNetworkEntry{ + { + CIDR: "10.128.0.0/14", + HostPrefix: 23, + }, + }, + ServiceNetwork: []string{ + "172.30.0.0/16", + }, + NetworkType: "OVNKubernetes", + UserManagedNetworking: swag.Bool(false), + }, + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + WorkerAgents: 2, + ArbiterAgents: 1, + }, + SSHPublicKey: "ssh-rsa AAAAmyKey", + }, + }, + expectedError: "", + }, { name: "valid-config-file-external-oci-platform", data: ` diff --git a/pkg/asset/agent/manifests/nmstateconfig.go b/pkg/asset/agent/manifests/nmstateconfig.go index 8429d9479e..9f634bdd28 100644 --- a/pkg/asset/agent/manifests/nmstateconfig.go +++ b/pkg/asset/agent/manifests/nmstateconfig.go @@ -259,7 +259,7 @@ func GetNodeZeroIP(hosts []agenttype.Host, nmStateConfigs []*aiv1beta1.NMStateCo // Select first the configs from the hosts, if defined // Skip worker hosts (or without an explicit role assigned) for _, host := range hosts { - if host.Role != "master" { + if host.Role != "master" && host.Role != "arbiter" { continue } rawConfigs = append(rawConfigs, host.NetworkConfig.Raw) @@ -361,10 +361,11 @@ func buildMacInterfaceMap(nmStateConfig aiv1beta1.NMStateConfig) models.MacInter } func validateHostCount(installConfig *types.InstallConfig, agentHosts *agentconfig.AgentHosts) error { - numRequiredMasters, numRequiredWorkers := agent.GetReplicaCount(installConfig) + numRequiredMasters, numRequiredArbiters, numRequiredWorkers := agent.GetReplicaCount(installConfig) numMasters := int64(0) numWorkers := int64(0) + numArbiters := int64(0) // Check for hosts explicitly defined for _, host := range agentHosts.Hosts { switch host.Role { @@ -372,6 +373,8 @@ func validateHostCount(installConfig *types.InstallConfig, agentHosts *agentconf numMasters++ case "worker": numWorkers++ + case "arbiter": + numArbiters++ } } @@ -380,6 +383,8 @@ func validateHostCount(installConfig *types.InstallConfig, agentHosts *agentconf if host.Role == "" { if numMasters < numRequiredMasters { numMasters++ + } else if numArbiters < numRequiredArbiters { + numArbiters++ } else { numWorkers++ } @@ -393,6 +398,13 @@ func validateHostCount(installConfig *types.InstallConfig, agentHosts *agentconf return fmt.Errorf("the number of master hosts defined (%v) exceeds the configured ControlPlane replicas (%v)", numMasters, numRequiredMasters) } + if numArbiters != 0 && numArbiters < numRequiredArbiters { + logrus.Warnf("not enough arbiter hosts defined (%v) to support all the configured Arbiter replicas (%v)", numArbiters, numRequiredArbiters) + } + if numArbiters > numRequiredArbiters { + return fmt.Errorf("the number of arbiter hosts defined (%v) exceeds the configured Arbiter replicas (%v)", numArbiters, numRequiredArbiters) + } + if numWorkers != 0 && numWorkers < numRequiredWorkers { logrus.Warnf("not enough worker hosts defined (%v) to support all the configured Compute replicas (%v)", numWorkers, numRequiredWorkers) } diff --git a/pkg/asset/agent/manifests/util_test.go b/pkg/asset/agent/manifests/util_test.go index 7d09350d89..939b034e15 100644 --- a/pkg/asset/agent/manifests/util_test.go +++ b/pkg/asset/agent/manifests/util_test.go @@ -7,7 +7,7 @@ import ( "github.com/go-openapi/swag" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "sigs.k8s.io/yaml" hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" @@ -84,17 +84,17 @@ func getValidOptionalInstallConfig() *agent.OptionalInstallConfig { SSHKey: testSSHKey, ControlPlane: &types.MachinePool{ Name: "master", - Replicas: pointer.Int64Ptr(3), + Replicas: ptr.To(int64(3)), Platform: types.MachinePoolPlatform{}, }, Compute: []types.MachinePool{ { Name: "worker-machine-pool-1", - Replicas: pointer.Int64Ptr(2), + Replicas: ptr.To(int64(2)), }, { Name: "worker-machine-pool-2", - Replicas: pointer.Int64Ptr(3), + Replicas: ptr.To(int64(3)), }, }, Networking: &types.Networking{ @@ -145,17 +145,17 @@ func getValidOptionalInstallConfigDualStack() *agent.OptionalInstallConfig { SSHKey: testSSHKey, ControlPlane: &types.MachinePool{ Name: "master", - Replicas: pointer.Int64Ptr(3), + Replicas: ptr.To(int64(3)), Platform: types.MachinePoolPlatform{}, }, Compute: []types.MachinePool{ { Name: "worker-machine-pool-1", - Replicas: pointer.Int64Ptr(2), + Replicas: ptr.To(int64(2)), }, { Name: "worker-machine-pool-2", - Replicas: pointer.Int64Ptr(3), + Replicas: ptr.To(int64(3)), }, }, Networking: &types.Networking{ @@ -200,6 +200,21 @@ func getValidOptionalInstallConfigDualStackDualVIPs() *agent.OptionalInstallConf return installConfig } +func getValidOptionalInstallConfigArbiter() *agent.OptionalInstallConfig { + installConfig := getValidOptionalInstallConfig() + installConfig.Config.Compute = []types.MachinePool{ + { + Name: "workers", + Replicas: ptr.To(int64(0)), + }, + } + installConfig.Config.Arbiter = &types.MachinePool{ + Name: "arbiter", + Replicas: ptr.To(int64(1)), + } + return installConfig +} + // getProxyValidOptionalInstallConfig returns a valid optional install config for proxied installation func getProxyValidOptionalInstallConfig() *agent.OptionalInstallConfig { validIC := getValidOptionalInstallConfig()