From 4e4b17a8a8f50800da65b2a98856b46abbf8e8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dulko?= Date: Thu, 25 Aug 2022 17:20:27 +0200 Subject: [PATCH] OpenStack: Set minimum disk of a flavor to 100 GB Other platforms require at least 100 GB of disk size and we've updated openshift-docs to reflect that in OpenStack too. Seems like we forgot to update flavor validation code and docs in the installer. This commit fixes this. --- docs/user/openstack/README.md | 8 +- docs/user/openstack/deploy_sriov_workers.md | 2 +- .../openstack/validation/machinepool.go | 33 ++++--- .../openstack/validation/machinepool_test.go | 98 ++++++++++++++++--- 4 files changed, 111 insertions(+), 30 deletions(-) diff --git a/docs/user/openstack/README.md b/docs/user/openstack/README.md index 8a7f9911101..9b0a4d40c94 100644 --- a/docs/user/openstack/README.md +++ b/docs/user/openstack/README.md @@ -80,7 +80,7 @@ For a successful installation it is required: - Server Groups: 2, plus one per additional Availability zone in each machine-pool - RAM: 112 GB - vCPUs: 28 -- Volume Storage: 175 GB +- Volume Storage: 700 GB - Instances: 7 - Depending on the type of [image registry backend](#image-registry-requirements) either 1 Swift container or an additional 100 GB volume. - OpenStack resource tagging @@ -97,13 +97,13 @@ Once you configure the quota for your project, please ensure that the user for t ### Master Nodes -The default deployment stands up 3 master nodes, which is the minimum amount required for a cluster. For each master node you stand up, you will need 1 instance, and 1 port available in your quota. They should be assigned a flavor with at least 16 GB RAM, 4 vCPUs, and 25 GB Disk (or Root Volume). It is theoretically possible to run with a smaller flavor, but be aware that if it takes too long to stand up services, or certain essential services crash, the installer could time out, leading to a failed install. +The default deployment stands up 3 master nodes, which is the minimum amount required for a cluster. For each master node you stand up, you will need 1 instance, and 1 port available in your quota. They should be assigned a flavor with at least 16 GB RAM, 4 vCPUs, and 100 GB Disk (or Root Volume). It is theoretically possible to run with a smaller flavor, but be aware that if it takes too long to stand up services, or certain essential services crash, the installer could time out, leading to a failed install. The master nodes are placed in a single Server group with "soft anti-affinity" policy by default; the machines will therefore be created on separate hosts when possible. ### Worker Nodes -The default deployment stands up 3 worker nodes. Worker nodes host the applications you run on OpenShift. The flavor assigned to the worker nodes should have at least 2 vCPUs, 8 GB RAM and 25 GB Disk (or Root Volume). However, if you are experiencing `Out Of Memory` issues, or your installs are timing out, try increasing the size of your flavor to match the master nodes: 4 vCPUs and 16 GB RAM. +The default deployment stands up 3 worker nodes. Worker nodes host the applications you run on OpenShift. The flavor assigned to the worker nodes should have at least 2 vCPUs, 8 GB RAM and 100 GB Disk (or Root Volume). However, if you are experiencing `Out Of Memory` issues, or your installs are timing out, try increasing the size of your flavor to match the master nodes: 4 vCPUs and 16 GB RAM. The worker nodes are placed in a single Server group with "soft anti-affinity" policy by default; the machines will therefore be created on separate hosts when possible. @@ -111,7 +111,7 @@ See the [OpenShift documentation](https://docs.openshift.com/container-platform/ ### Bootstrap Node -The bootstrap node is a temporary node that is responsible for standing up the control plane on the masters. Only one bootstrap node will be stood up and it will be deprovisioned once the production control plane is ready. To do so, you need 1 instance, and 1 port. We recommend a flavor with a minimum of 16 GB RAM, 4 vCPUs, and 25 GB Disk (or Root Volume). +The bootstrap node is a temporary node that is responsible for standing up the control plane on the masters. Only one bootstrap node will be stood up and it will be deprovisioned once the production control plane is ready. To do so, you need 1 instance, and 1 port. We recommend a flavor with a minimum of 16 GB RAM, 4 vCPUs, and 100 GB Disk (or Root Volume). ### Image Registry Requirements diff --git a/docs/user/openstack/deploy_sriov_workers.md b/docs/user/openstack/deploy_sriov_workers.md index 2e023226c20..0ef13f05a33 100644 --- a/docs/user/openstack/deploy_sriov_workers.md +++ b/docs/user/openstack/deploy_sriov_workers.md @@ -20,7 +20,7 @@ in OpenShift, and that your tenant has access to them. Your OpenStack cluster mu - One instance from the RHOSP quota - One port attached to the machines subnet - One port for each SR-IOV Virtual Function -- A flavor with at least 16 GB memory, 4 vCPUs, and 25 GB storage space +- A flavor with at least 16 GB memory, 4 vCPUs, and 100 GB storage space For all clusters that use single-root input/output virtualization (SR-IOV), RHOSP compute nodes require a flavor that supports [huge pages][huge-pages]. Deploying worker nodes with SR-IOV networks is supported as a post-install operation for both IPI and UPI workflows. After you verify that your OpenStack cluster can support SR-IOV in OpenShift and you install an OpenShift cluster that meets the [minimum requirements](README.md#openstack-requirements), use the following steps and examples to create worker nodes with SR-IOV NICs. diff --git a/pkg/asset/installconfig/openstack/validation/machinepool.go b/pkg/asset/installconfig/openstack/validation/machinepool.go index 6ea072f8e91..e8b318972f6 100644 --- a/pkg/asset/installconfig/openstack/validation/machinepool.go +++ b/pkg/asset/installconfig/openstack/validation/machinepool.go @@ -3,6 +3,8 @@ package validation import ( "fmt" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" @@ -11,23 +13,26 @@ import ( ) type flavorRequirements struct { - RAM, VCPUs, Disk int + RAM, VCPUs, Disk, RecommendedDisk int } const ( - minimumStorage = 25 + minimumStorage = 25 + recommendedStorage = 100 ) var ( ctrlPlaneFlavorMinimums = flavorRequirements{ - RAM: 16384, - VCPUs: 4, - Disk: minimumStorage, + RAM: 16384, + VCPUs: 4, + Disk: minimumStorage, + RecommendedDisk: recommendedStorage, } computeFlavorMinimums = flavorRequirements{ - RAM: 8192, - VCPUs: 2, - Disk: minimumStorage, + RAM: 8192, + VCPUs: 2, + Disk: minimumStorage, + RecommendedDisk: recommendedStorage, } ) @@ -40,7 +45,9 @@ func ValidateMachinePool(p *openstack.MachinePool, ci *CloudInfo, controlPlane b if p.RootVolume != nil { allErrs = append(allErrs, validateVolumeTypes(p.RootVolume.Type, ci.VolumeTypes, fldPath.Child("rootVolume").Child("type"))...) if p.RootVolume.Size < minimumStorage { - allErrs = append(allErrs, field.Invalid(fldPath.Child("rootVolume").Child("size"), p.RootVolume.Size, fmt.Sprintf("Volume size must be greater than %d to use root volumes, had %d", minimumStorage, p.RootVolume.Size))) + allErrs = append(allErrs, field.Invalid(fldPath.Child("rootVolume").Child("size"), p.RootVolume.Size, fmt.Sprintf("Volume size must be greater than %d GB to use root volumes, had %d GB", minimumStorage, p.RootVolume.Size))) + } else if p.RootVolume.Size < recommendedStorage { + logrus.Warnf("Volume size is recommended to be greater than %d GB to use root volumes, had %d GB", recommendedStorage, p.RootVolume.Size) } allErrs = append(allErrs, validateZones(p.RootVolume.Zones, ci.VolumeZones, fldPath.Child("rootVolume").Child("zones"))...) @@ -149,8 +156,12 @@ func validateFlavor(flavorName string, ci *CloudInfo, req flavorRequirements, fl if flavor.VCPUs < req.VCPUs { errs = append(errs, fmt.Sprintf("Must have minimum of %d VCPUs, had %d", req.VCPUs, flavor.VCPUs)) } - if flavor.Disk < req.Disk && storage { - errs = append(errs, fmt.Sprintf("Must have minimum of %d GB Disk, had %d GB", req.Disk, flavor.Disk)) + if storage { + if flavor.Disk < req.Disk { + errs = append(errs, fmt.Sprintf("Must have minimum of %d GB Disk, had %d GB", req.Disk, flavor.Disk)) + } else if flavor.Disk < req.RecommendedDisk { + logrus.Warnf("Flavor does not meet the following recommended requirements: It is recommended to have %d GB Disk, had %d GB", req.RecommendedDisk, flavor.Disk) + } } if len(errs) == 0 { diff --git a/pkg/asset/installconfig/openstack/validation/machinepool_test.go b/pkg/asset/installconfig/openstack/validation/machinepool_test.go index 2016148b3dd..58a094777c9 100644 --- a/pkg/asset/installconfig/openstack/validation/machinepool_test.go +++ b/pkg/asset/installconfig/openstack/validation/machinepool_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/openshift/installer/pkg/types/openstack" + logrusTest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/util/validation/field" @@ -21,13 +22,16 @@ const ( invalidComputeFlavor = "invalid-compute-flavor" invalidCtrlPlaneFlavor = "invalid-control-plane-flavor" + warningComputeFlavor = "warning-compute-flavor" + warningCtrlPlaneFlavor = "warning-control-plane-flavor" baremetalFlavor = "baremetal-flavor" - volumeType = "performance" - invalidType = "invalid-type" - volumeSmallSize = 10 - volumeLargeSize = 25 + volumeType = "performance" + invalidType = "invalid-type" + volumeSmallSize = 10 + volumeMediumSize = 40 + volumeLargeSize = 100 ) func validMachinePool() *openstack.MachinePool { @@ -49,6 +53,18 @@ func invalidMachinePoolSmallVolume() *openstack.MachinePool { } } +func warningMachinePoolMediumVolume() *openstack.MachinePool { + return &openstack.MachinePool{ + FlavorName: validCtrlPlaneFlavor, + Zones: []string{""}, + RootVolume: &openstack.RootVolume{ + Type: volumeType, + Size: volumeMediumSize, + Zones: []string{""}, + }, + } +} + func validMachinePoolLargeVolume() *openstack.MachinePool { return &openstack.MachinePool{ FlavorName: validCtrlPlaneFlavor, @@ -68,7 +84,7 @@ func validMpoolCloudInfo() *CloudInfo { Flavor: flavors.Flavor{ Name: validCtrlPlaneFlavor, RAM: 16384, - Disk: 25, + Disk: 100, VCPUs: 4, }, }, @@ -76,7 +92,7 @@ func validMpoolCloudInfo() *CloudInfo { Flavor: flavors.Flavor{ Name: validComputeFlavor, RAM: 8192, - Disk: 25, + Disk: 100, VCPUs: 2, }, }, @@ -84,7 +100,7 @@ func validMpoolCloudInfo() *CloudInfo { Flavor: flavors.Flavor{ Name: invalidCtrlPlaneFlavor, RAM: 8192, // too low - Disk: 25, + Disk: 100, VCPUs: 2, // too low }, }, @@ -96,6 +112,22 @@ func validMpoolCloudInfo() *CloudInfo { VCPUs: 2, }, }, + warningCtrlPlaneFlavor: { + Flavor: flavors.Flavor{ + Name: warningCtrlPlaneFlavor, + RAM: 16384, + Disk: 40, // not recommended + VCPUs: 4, + }, + }, + warningComputeFlavor: { + Flavor: flavors.Flavor{ + Name: invalidComputeFlavor, + RAM: 8192, + Disk: 40, // not recommended + VCPUs: 2, + }, + }, baremetalFlavor: { Flavor: flavors.Flavor{ Name: baremetalFlavor, @@ -120,12 +152,13 @@ func validMpoolCloudInfo() *CloudInfo { func TestOpenStackMachinepoolValidation(t *testing.T) { cases := []struct { - name string - controlPlane bool // only matters for flavor - mpool *openstack.MachinePool - cloudInfo *CloudInfo - expectedError bool - expectedErrMsg string // NOTE: this is a REGEXP + name string + controlPlane bool // only matters for flavor + mpool *openstack.MachinePool + cloudInfo *CloudInfo + expectedError bool + expectedErrMsg string // NOTE: this is a REGEXP + expectedWarnMsg string //NOTE: this is a REGEXP }{ { name: "valid control plane", @@ -232,6 +265,28 @@ func TestOpenStackMachinepoolValidation(t *testing.T) { expectedError: true, expectedErrMsg: `compute\[0\].platform.openstack.type: Invalid value: "invalid-compute-flavor": Flavor did not meet the following minimum requirements: Must have minimum of 25 GB Disk, had 10 GB`, }, + { + name: "warning control plane flavorName", + controlPlane: true, + mpool: func() *openstack.MachinePool { + mp := validMachinePool() + mp.FlavorName = warningCtrlPlaneFlavor + return mp + }(), + cloudInfo: validMpoolCloudInfo(), + expectedWarnMsg: `Flavor does not meet the following recommended requirements: It is recommended to have 100 GB Disk, had 40 GB`, + }, + { + name: "warning compute flavorName", + controlPlane: false, + mpool: func() *openstack.MachinePool { + mp := validMachinePool() + mp.FlavorName = warningComputeFlavor + return mp + }(), + cloudInfo: validMpoolCloudInfo(), + expectedWarnMsg: `Flavor does not meet the following recommended requirements: It is recommended to have 100 GB Disk, had 40 GB`, + }, { name: "valid baremetal compute", controlPlane: false, @@ -254,7 +309,18 @@ func TestOpenStackMachinepoolValidation(t *testing.T) { }(), cloudInfo: validMpoolCloudInfo(), expectedError: true, - expectedErrMsg: "Volume size must be greater than 25 to use root volumes, had 10", + expectedErrMsg: "Volume size must be greater than 25 GB to use root volumes, had 10 GB", + }, + { + name: "volume not recommended", + controlPlane: false, + mpool: func() *openstack.MachinePool { + mp := warningMachinePoolMediumVolume() + mp.FlavorName = invalidCtrlPlaneFlavor + return mp + }(), + cloudInfo: validMpoolCloudInfo(), + expectedWarnMsg: "Volume size is recommended to be greater than 100 GB to use root volumes, had 40 GB", }, { name: "volume big enough", @@ -338,12 +404,16 @@ func TestOpenStackMachinepoolValidation(t *testing.T) { fieldPath = field.NewPath("compute").Index(0).Child("platform", "openstack") } + hook := logrusTest.NewGlobal() aggregatedErrors := ValidateMachinePool(tc.mpool, tc.cloudInfo, tc.controlPlane, fieldPath).ToAggregate() if tc.expectedError { assert.Regexp(t, tc.expectedErrMsg, aggregatedErrors) } else { assert.NoError(t, aggregatedErrors) } + if len(tc.expectedWarnMsg) > 0 { + assert.Regexp(t, tc.expectedWarnMsg, hook.LastEntry().Message) + } }) } }