diff --git a/api/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go b/api/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go index 2a7d4ad667ff..4f19525b80b3 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go +++ b/api/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go @@ -125,6 +125,9 @@ const ( // FeatureSupportLevelIDOPENSHIFTAI captures enum value "OPENSHIFT_AI" FeatureSupportLevelIDOPENSHIFTAI FeatureSupportLevelID = "OPENSHIFT_AI" + + // FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE captures enum value "NON_STANDARD_HA_CONTROL_PLANE" + FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE FeatureSupportLevelID = "NON_STANDARD_HA_CONTROL_PLANE" ) // for schema @@ -132,7 +135,7 @@ var featureSupportLevelIdEnum []interface{} func init() { var res []FeatureSupportLevelID - if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI","NON_STANDARD_HA_CONTROL_PLANE"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/client/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go b/client/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go index 2a7d4ad667ff..4f19525b80b3 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go +++ b/client/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go @@ -125,6 +125,9 @@ const ( // FeatureSupportLevelIDOPENSHIFTAI captures enum value "OPENSHIFT_AI" FeatureSupportLevelIDOPENSHIFTAI FeatureSupportLevelID = "OPENSHIFT_AI" + + // FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE captures enum value "NON_STANDARD_HA_CONTROL_PLANE" + FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE FeatureSupportLevelID = "NON_STANDARD_HA_CONTROL_PLANE" ) // for schema @@ -132,7 +135,7 @@ var featureSupportLevelIdEnum []interface{} func init() { var res []FeatureSupportLevelID - if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI","NON_STANDARD_HA_CONTROL_PLANE"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/internal/bminventory/inventory.go b/internal/bminventory/inventory.go index 3f4ba96954ed..15018a524efe 100644 --- a/internal/bminventory/inventory.go +++ b/internal/bminventory/inventory.go @@ -334,7 +334,7 @@ func (b *bareMetalInventory) setDefaultRegisterClusterParams(ctx context.Context params.NewClusterParams.SchedulableMasters = swag.Bool(false) } - params.NewClusterParams.HighAvailabilityMode, params.NewClusterParams.ControlPlaneCount = common.GetDefaultHighAvailabilityAndMasterCountParams( + params.NewClusterParams.HighAvailabilityMode, params.NewClusterParams.ControlPlaneCount = getDefaultHighAvailabilityAndMasterCountParams( params.NewClusterParams.HighAvailabilityMode, params.NewClusterParams.ControlPlaneCount, ) @@ -1947,21 +1947,21 @@ func validateHighAvailabilityWithControlPlaneCount(highAvailabilityMode string, ) } - stretchedClustersNotSuported, err := common.BaseVersionLessThan(common.MinimumVersionForStretchedControlPlanesCluster, openshiftVersion) + nonStandardHAOCPControlPlaneNotSuported, err := common.BaseVersionLessThan(common.MinimumVersionForNonStandardHAOCPControlPlane, openshiftVersion) if err != nil { return err } if highAvailabilityMode == models.ClusterCreateParamsHighAvailabilityModeFull && controlPlaneCount != common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder && - stretchedClustersNotSuported { + nonStandardHAOCPControlPlaneNotSuported { return common.NewApiError( http.StatusBadRequest, fmt.Errorf( "there should be exactly %d dedicated control plane nodes for high availability mode %s in openshift version older than %s", common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder, highAvailabilityMode, - common.MinimumVersionForStretchedControlPlanesCluster, + common.MinimumVersionForNonStandardHAOCPControlPlane, ), ) } @@ -1976,7 +1976,7 @@ func validateHighAvailabilityWithControlPlaneCount(highAvailabilityMode string, common.MinMasterHostsNeededForInstallationInHaMode, common.MaxMasterHostsNeededForInstallationInHaModeOfOCP418OrNewer, highAvailabilityMode, - common.MinimumVersionForStretchedControlPlanesCluster, + common.MinimumVersionForNonStandardHAOCPControlPlane, ), ) } @@ -6663,3 +6663,32 @@ func (b *bareMetalInventory) HandleVerifyVipsResponse(ctx context.Context, host } return b.clusterApi.HandleVerifyVipsResponse(ctx, *host.ClusterID, stepReply) } + +func getDefaultHighAvailabilityAndMasterCountParams(highAvailabilityMode *string, controlPlaneCount *int64) (*string, *int64) { + // Both not set, multi node by default + if highAvailabilityMode == nil && controlPlaneCount == nil { + return swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), + swag.Int64(common.MinMasterHostsNeededForInstallationInHaMode) + } + + // only highAvailabilityMode set + if controlPlaneCount == nil { + if *highAvailabilityMode == models.ClusterHighAvailabilityModeNone { + return highAvailabilityMode, swag.Int64(common.AllowedNumberOfMasterHostsInNoneHaMode) + } + + return highAvailabilityMode, swag.Int64(common.MinMasterHostsNeededForInstallationInHaMode) + } + + // only controlPlaneCount set + if highAvailabilityMode == nil { + if *controlPlaneCount == common.AllowedNumberOfMasterHostsInNoneHaMode { + return swag.String(models.ClusterHighAvailabilityModeNone), controlPlaneCount + } + + return swag.String(models.ClusterHighAvailabilityModeFull), controlPlaneCount + } + + // both are set + return highAvailabilityMode, controlPlaneCount +} diff --git a/internal/bminventory/inventory_test.go b/internal/bminventory/inventory_test.go index e0cf9cf25bf7..68edca858694 100644 --- a/internal/bminventory/inventory_test.go +++ b/internal/bminventory/inventory_test.go @@ -7929,7 +7929,7 @@ var _ = Describe("V2UpdateCluster", func() { Expect(newCluster.ControlPlaneCount).To(BeEquivalentTo(4)) }) - It(fmt.Sprintf("descreasing to 3 control planes with OCP >= %s the value and multi-node", common.MinimumVersionForStretchedControlPlanesCluster), func() { + It(fmt.Sprintf("descreasing to 3 control planes with OCP >= %s the value and multi-node", common.MinimumVersionForNonStandardHAOCPControlPlane), func() { cluster := &common.Cluster{ Cluster: models.Cluster{ ID: &clusterID, @@ -7962,7 +7962,7 @@ var _ = Describe("V2UpdateCluster", func() { }) Context("should fail", func() { - It("update to invalid value, stretched clusters not supported", func() { + It("update to invalid value, non-standad HA OCP Control Plane not supported", func() { cluster := &common.Cluster{ Cluster: models.Cluster{ ID: &clusterID, @@ -7985,7 +7985,7 @@ var _ = Describe("V2UpdateCluster", func() { verifyApiErrorString(reply, http.StatusBadRequest, "there should be exactly 3 dedicated control plane nodes for high availability mode Full in openshift version older than 4.18") }) - It("update to invalid value, stretched clusters supported", func() { + It("update to invalid value, non-standad HA OCP Control Plane supported", func() { cluster := &common.Cluster{ Cluster: models.Cluster{ ID: &clusterID, @@ -8031,7 +8031,7 @@ var _ = Describe("V2UpdateCluster", func() { verifyApiErrorString(reply, http.StatusBadRequest, "single-node clusters must have a single control plane node") }) - It(fmt.Sprintf("update amount to != 3 when multi-node, OCP version < %s", common.MinimumVersionForStretchedControlPlanesCluster), func() { + It(fmt.Sprintf("update amount to != 3 when multi-node, OCP version < %s", common.MinimumVersionForNonStandardHAOCPControlPlane), func() { cluster := &common.Cluster{ Cluster: models.Cluster{ ID: &clusterID, @@ -8058,7 +8058,7 @@ var _ = Describe("V2UpdateCluster", func() { ) }) - It(fmt.Sprintf("update amount to != 3 when multi-node, OCP version >= %s", common.MinimumVersionForStretchedControlPlanesCluster), func() { + It(fmt.Sprintf("update amount to != 3 when multi-node, OCP version >= %s", common.MinimumVersionForNonStandardHAOCPControlPlane), func() { cluster := &common.Cluster{ Cluster: models.Cluster{ ID: &clusterID, @@ -15650,11 +15650,11 @@ location = "%s" Context("using defaults", func() { It("high_availability mode is set to Full", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, }) @@ -15670,11 +15670,11 @@ location = "%s" }) It("high_availability mode is set to None", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), }, }) @@ -15690,11 +15690,11 @@ location = "%s" }) It("control_plane_count is set to 3", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(3), }, }) @@ -15710,11 +15710,11 @@ location = "%s" }) It("control_plane_count is set to 1", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(1), }, }) @@ -15730,11 +15730,11 @@ location = "%s" }) It("not set", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), }, }) @@ -15750,12 +15750,12 @@ location = "%s" }) }) - It(fmt.Sprintf("setting 5 control planes, multi-node with OCP version >= %s", common.MinimumVersionForStretchedControlPlanesCluster), func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, common.MinimumVersionForStretchedControlPlanesCluster) + It(fmt.Sprintf("setting 5 control planes, multi-node with OCP version >= %s", common.MinimumVersionForNonStandardHAOCPControlPlane), func() { + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, common.MinimumVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(common.MinimumVersionForStretchedControlPlanesCluster), + OpenshiftVersion: swag.String(common.MinimumVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(5), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, @@ -15773,11 +15773,11 @@ location = "%s" }) It("setting 1 control plane, single-node", func() { - mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStretchedClusters) + mockClusterRegisterSuccessWithVersion(common.X86CPUArchitecture, testutils.ValidOCPVersionForNonStandardHAOCPControlPlane) reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(1), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), }, @@ -15796,10 +15796,10 @@ location = "%s" }) Context("should fail", func() { - It("setting 6 control planes, multi-node, stretched clusters not supported", func() { + It("setting 6 control planes, multi-node, non-standad HA OCP Control Plane not supported", func() { reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(6), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, @@ -15812,10 +15812,10 @@ location = "%s" ) }) - It("setting 6 control planes, multi-node, stretched clusters supported", func() { + It("setting 6 control planes, multi-node, non-standad HA OCP Control Plane supported", func() { reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(common.MinimumVersionForStretchedControlPlanesCluster), + OpenshiftVersion: swag.String(common.MinimumVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(6), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, @@ -15831,7 +15831,7 @@ location = "%s" It("setting 3 control planes, single-node", func() { reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(3), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), }, @@ -15847,7 +15847,7 @@ location = "%s" It("setting 1 control plane, mutli-node", func() { reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(1), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, @@ -15860,10 +15860,10 @@ location = "%s" ) }) - It("setting 4 control planes, multi-node, stretched clusters not supported", func() { + It("setting 4 control planes, multi-node, non-standad HA OCP Control Plane not supported", func() { reply := bm.V2RegisterCluster(ctx, installer.V2RegisterClusterParams{ NewClusterParams: &models.ClusterCreateParams{ - OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStretchedClusters), + OpenshiftVersion: swag.String(testutils.ValidOCPVersionForNonStandardHAOCPControlPlane), ControlPlaneCount: swag.Int64(4), HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), }, diff --git a/internal/cluster/cluster_test.go b/internal/cluster/cluster_test.go index 18f9fb0bc946..a3687943f0f9 100644 --- a/internal/cluster/cluster_test.go +++ b/internal/cluster/cluster_test.go @@ -196,7 +196,7 @@ var _ = Describe("TestClusterMonitoring", func() { PullSecretSet: true, MonitoredOperators: []*models.MonitoredOperator{&common.TestDefaultConfig.MonitoredOperator}, StatusUpdatedAt: strfmt.DateTime(time.Now()), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, TriggerMonitorTimestamp: time.Now(), } @@ -456,6 +456,7 @@ var _ = Describe("TestClusterMonitoring", func() { BeforeEach(func() { c = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ID: &id, Status: swag.String("insufficient"), @@ -468,7 +469,7 @@ var _ = Describe("TestClusterMonitoring", func() { PullSecretSet: true, StatusInfo: swag.String(StatusInfoInsufficient), NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, TriggerMonitorTimestamp: time.Now(), } @@ -544,6 +545,7 @@ var _ = Describe("TestClusterMonitoring", func() { Context("from ready state", func() { BeforeEach(func() { c = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ID: &id, Status: swag.String(models.ClusterStatusReady), @@ -556,7 +558,7 @@ var _ = Describe("TestClusterMonitoring", func() { BaseDNSDomain: "test.com", PullSecretSet: true, NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, TriggerMonitorTimestamp: time.Now(), } @@ -2253,19 +2255,21 @@ var _ = Describe("Majority groups", func() { apiVip := "1.2.3.5" ingressVip := "1.2.3.6" verificationSuccess := models.VipVerificationSucceeded - cluster = common.Cluster{Cluster: models.Cluster{ - ID: &id, - Status: swag.String(models.ClusterStatusReady), - ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, - ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, - MachineNetworks: common.TestIPv4Networking.MachineNetworks, - APIVips: []*models.APIVip{{IP: models.IP(apiVip), ClusterID: id, Verification: &verificationSuccess}}, - IngressVips: []*models.IngressVip{{IP: models.IP(ingressVip), ClusterID: id, Verification: &verificationSuccess}}, - BaseDNSDomain: "test.com", - PullSecretSet: true, - NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - }} + cluster = common.Cluster{ + ControlPlaneCount: 3, + Cluster: models.Cluster{ + ID: &id, + Status: swag.String(models.ClusterStatusReady), + ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, + ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, + MachineNetworks: common.TestIPv4Networking.MachineNetworks, + APIVips: []*models.APIVip{{IP: models.IP(apiVip), ClusterID: id, Verification: &verificationSuccess}}, + IngressVips: []*models.IngressVip{{IP: models.IP(ingressVip), ClusterID: id, Verification: &verificationSuccess}}, + BaseDNSDomain: "test.com", + PullSecretSet: true, + NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, + }} Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) mockMetricApi.EXPECT().MonitoredClusterCount(int64(1)).AnyTimes() @@ -2572,19 +2576,21 @@ var _ = Describe("ready_state", func() { id = strfmt.UUID(uuid.New().String()) apiVip := "1.2.3.5" ingressVip := "1.2.3.6" - cluster = common.Cluster{Cluster: models.Cluster{ - ID: &id, - Status: swag.String(models.ClusterStatusReady), - ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, - ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, - MachineNetworks: common.TestIPv4Networking.MachineNetworks, - APIVips: []*models.APIVip{{IP: models.IP(apiVip), ClusterID: id, Verification: common.VipVerificationPtr(models.VipVerificationSucceeded)}}, - IngressVips: []*models.IngressVip{{IP: models.IP(ingressVip), ClusterID: id, Verification: common.VipVerificationPtr(models.VipVerificationSucceeded)}}, - BaseDNSDomain: "test.com", - PullSecretSet: true, - NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - }} + cluster = common.Cluster{ + ControlPlaneCount: 3, + Cluster: models.Cluster{ + ID: &id, + Status: swag.String(models.ClusterStatusReady), + ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, + ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, + MachineNetworks: common.TestIPv4Networking.MachineNetworks, + APIVips: []*models.APIVip{{IP: models.IP(apiVip), ClusterID: id, Verification: common.VipVerificationPtr(models.VipVerificationSucceeded)}}, + IngressVips: []*models.IngressVip{{IP: models.IP(ingressVip), ClusterID: id, Verification: common.VipVerificationPtr(models.VipVerificationSucceeded)}}, + BaseDNSDomain: "test.com", + PullSecretSet: true, + NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, + }} Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) addInstallationRequirements(id, db) diff --git a/internal/cluster/transition_test.go b/internal/cluster/transition_test.go index b48b7693cdf8..452b9a7fd2bd 100644 --- a/internal/cluster/transition_test.go +++ b/internal/cluster/transition_test.go @@ -974,7 +974,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, too much masters - stretched masters cluster not available", + name: "pending-for-input to insufficient, too much masters - non-standard HA OCP Control Plane not available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1009,7 +1009,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, not enough masters - stretched masters cluster not available", + name: "pending-for-input to insufficient, not enough masters - non-standard HA OCP Control Plane not available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1042,7 +1042,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, too much masters - stretched masters cluster available", + name: "pending-for-input to insufficient, too much masters - non-standard HA OCP Control Plane available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1069,13 +1069,14 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { IsDNSDomainDefined: {status: ValidationSuccess, messagePattern: "The base domain is defined"}, IsPullSecretSet: {status: ValidationSuccess, messagePattern: "The pull secret is set"}, isNetworkTypeValid: {status: ValidationSuccess, messagePattern: "The cluster has a valid network type"}, - SufficientMastersCount: {status: ValidationFailure, messagePattern: fmt.Sprintf("The cluster must have exactly %d dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.", common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder)}, + SufficientMastersCount: {status: ValidationFailure, messagePattern: "The cluster must have exactly 5 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement."}, }), - errorExpected: false, - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + errorExpected: false, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, + controlPlaneCount: 5, }, { - name: "pending-for-input to insufficient, not enough masters - stretched masters cluster available", + name: "pending-for-input to insufficient, not enough masters - non-standard HA OCP Control Plane available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1086,6 +1087,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { hosts: []models.Host{ {ID: &hid1, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, {ID: &hid2, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, + {ID: &hid3, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, }, statusInfoChecker: makeValueChecker(StatusInfoInsufficient), validationsChecker: makeJsonChecker(map[ValidationID]validationCheckResult{ @@ -1099,13 +1101,14 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { IsDNSDomainDefined: {status: ValidationSuccess, messagePattern: "The base domain is defined"}, IsPullSecretSet: {status: ValidationSuccess, messagePattern: "The pull secret is set"}, isNetworkTypeValid: {status: ValidationSuccess, messagePattern: "The cluster has a valid network type"}, - SufficientMastersCount: {status: ValidationFailure, messagePattern: fmt.Sprintf("The cluster must have exactly %d dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.", common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder)}, + SufficientMastersCount: {status: ValidationFailure, messagePattern: "The cluster must have exactly 4 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement."}, }), - errorExpected: false, - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + errorExpected: false, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, + controlPlaneCount: 4, }, { - name: "pending-for-input to ready, sufficient amount of potential masters - stretched masters cluster not available", + name: "pending-for-input to ready, sufficient amount of potential masters - non-standard HA OCP Control Plane not available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusReady, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1135,7 +1138,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to ready, sufficient amount of potential masters - stretched masters cluster available", + name: "pending-for-input to ready, sufficient amount of potential masters - non-standard HA OCP Control Plane available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusReady, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -1165,7 +1168,7 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { SufficientMastersCount: {status: ValidationSuccess, messagePattern: "The cluster has the exact amount of dedicated control plane nodes."}, }), errorExpected: false, - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, controlPlaneCount: 5, }, { @@ -1528,7 +1531,11 @@ var _ = Describe("Refresh Cluster - No DHCP", func() { } if cluster.Cluster.OpenshiftVersion == "" { - cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStretchedClusters + cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStandardHAOCPControlPlane + } + + if cluster.ControlPlaneCount == 0 { + cluster.ControlPlaneCount = 3 } Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) @@ -1786,6 +1793,7 @@ var _ = Describe("Refresh Cluster - Same networks", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ APIVips: t.apiVips, ID: &clusterId, @@ -1799,7 +1807,7 @@ var _ = Describe("Refresh Cluster - Same networks", func() { ServiceNetworks: t.serviceNetworks, NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), StatusUpdatedAt: strfmt.DateTime(time.Now()), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) @@ -2037,6 +2045,7 @@ var _ = Describe("RefreshCluster - preparing for install", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ APIVips: t.apiVips, ID: &clusterId, @@ -2050,7 +2059,7 @@ var _ = Describe("RefreshCluster - preparing for install", func() { Status: t.lastInstallationPreparationStatus, Reason: t.lastInstallationPreparationReason, }, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) @@ -2146,6 +2155,7 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { vipDhcpAllocation bool networkType string sno bool + controlPlaneCount int64 }{ { name: "pending-for-input to pending-for-input", @@ -2289,7 +2299,8 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { networkPrefixValid: {status: ValidationFailure, messagePattern: "Host prefix, now 0, must be a positive integer"}, isNetworkTypeValid: {status: ValidationSuccess, messagePattern: "The cluster has a valid network type"}, }), - errorExpected: false, + errorExpected: false, + controlPlaneCount: 1, }, { name: "pending-for-input to insufficient - overlapping", @@ -2573,7 +2584,8 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { networkPrefixValid: {status: ValidationSuccess, messagePattern: "Cluster Network prefix is valid."}, isNetworkTypeValid: {status: ValidationFailure, messagePattern: regexp.QuoteMeta("High-availability mode 'None' (SNO) is not supported by OpenShiftSDN; use another network type instead")}, }), - errorExpected: false, + errorExpected: false, + controlPlaneCount: 1, }, } @@ -2581,6 +2593,7 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: t.controlPlaneCount, Cluster: models.Cluster{ APIVips: t.apiVips, ID: &clusterId, @@ -2595,9 +2608,14 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { UserManagedNetworking: &t.userManagedNetworking, NetworkType: &t.networkType, VipDhcpAllocation: &t.vipDhcpAllocation, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } + + if cluster.ControlPlaneCount == 0 { + cluster.ControlPlaneCount = 3 + } + if t.sno { ha := models.ClusterHighAvailabilityModeNone cluster.HighAvailabilityMode = &ha @@ -3050,6 +3068,7 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ APIVips: t.apiVips, ID: &clusterId, @@ -3063,7 +3082,7 @@ var _ = Describe("Refresh Cluster - Advanced networking validations", func() { BaseDNSDomain: "test.com", UserManagedNetworking: &t.userManagedNetworking, NetworkType: &t.networkType, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) @@ -3186,6 +3205,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { vipDhcpAllocation bool errorExpected bool openshiftVersion string + controlPlaneCount int64 }{ { name: "pending-for-input to pending-for-input", @@ -3222,7 +3242,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, too much masters - stretched masters cluster not available", + name: "pending-for-input to insufficient, too much masters - non-standard HA OCP Control Plane not available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -3256,7 +3276,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, not enough masters - stretched masters cluster not available", + name: "pending-for-input to insufficient, not enough masters - non-standard HA OCP Control Plane not available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -3288,7 +3308,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { errorExpected: false, }, { - name: "pending-for-input to insufficient, too much masters - stretched masters cluster available", + name: "pending-for-input to insufficient, too much masters - non-standard HA OCP Control Plane available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -3315,13 +3335,14 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { AllHostsAreReadyToInstall: {status: ValidationSuccess, messagePattern: "All hosts in the cluster are ready to install"}, IsDNSDomainDefined: {status: ValidationSuccess, messagePattern: "The base domain is defined"}, IsPullSecretSet: {status: ValidationSuccess, messagePattern: "The pull secret is set."}, - SufficientMastersCount: {status: ValidationFailure, messagePattern: fmt.Sprintf("The cluster must have exactly %d dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.", common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder)}, + SufficientMastersCount: {status: ValidationFailure, messagePattern: "The cluster must have exactly 5 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement."}, }), - errorExpected: false, - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + errorExpected: false, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, + controlPlaneCount: 5, }, { - name: "pending-for-input to insufficient, not enough masters - stretched masters cluster available", + name: "pending-for-input to insufficient, not enough masters - non-standard HA OCP Control Plane available", srcState: models.ClusterStatusPendingForInput, dstState: models.ClusterStatusInsufficient, machineNetworks: common.TestIPv4Networking.MachineNetworks, @@ -3332,6 +3353,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { hosts: []models.Host{ {ID: &hid1, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, {ID: &hid2, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, + {ID: &hid3, Status: swag.String(models.HostStatusKnown), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleMaster}, }, statusInfoChecker: makeValueChecker(StatusInfoInsufficient), validationsChecker: makeJsonChecker(map[ValidationID]validationCheckResult{ @@ -3344,10 +3366,11 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { AllHostsAreReadyToInstall: {status: ValidationSuccess, messagePattern: "All hosts in the cluster are ready to install"}, IsDNSDomainDefined: {status: ValidationSuccess, messagePattern: "The base domain is defined"}, IsPullSecretSet: {status: ValidationSuccess, messagePattern: "The pull secret is set."}, - SufficientMastersCount: {status: ValidationFailure, messagePattern: fmt.Sprintf("The cluster must have exactly %d dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.", common.AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder)}, + SufficientMastersCount: {status: ValidationFailure, messagePattern: "The cluster must have exactly 4 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement."}, }), - errorExpected: false, - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + errorExpected: false, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, + controlPlaneCount: 4, }, { name: "pending-for-input to insufficient - not all hosts are ready to install - not enough workers", @@ -3752,6 +3775,7 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: t.controlPlaneCount, Cluster: models.Cluster{ APIVips: t.apiVips, ID: &clusterId, @@ -3771,7 +3795,11 @@ var _ = Describe("Refresh Cluster - With DHCP", func() { } if cluster.Cluster.OpenshiftVersion == "" { - cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStretchedClusters + cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStandardHAOCPControlPlane + } + + if cluster.ControlPlaneCount == 0 { + cluster.ControlPlaneCount = 3 } if t.setMachineCidrUpdatedAt { @@ -3882,7 +3910,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { openshiftVersion string }{ { - name: "installing to installing - non stretched cluster", + name: "installing to installing - non non-standard HA OCP Control Plane", srcState: models.ClusterStatusInstalling, srcStatusInfo: statusInfoInstalling, dstState: models.ClusterStatusInstalling, @@ -3896,7 +3924,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { statusInfoChecker: makeValueChecker(statusInfoInstalling), }, { - name: "installing to installing - stretched cluster", + name: "installing to installing - non-standard HA OCP Control Plane", srcState: models.ClusterStatusInstalling, srcStatusInfo: statusInfoInstalling, dstState: models.ClusterStatusInstalling, @@ -3909,7 +3937,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { {ID: &hid6, Status: swag.String(models.ClusterStatusInstalling), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleWorker}, }, statusInfoChecker: makeValueChecker(statusInfoInstalling), - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, }, { name: "installing to installing-pending-user-action", @@ -4070,7 +4098,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { statusInfoChecker: makeValueChecker(statusInfoFinalizing), }, { - name: "installing to finalizing - non stretched cluster", + name: "installing to finalizing - non non-standard HA OCP Control Plane", srcState: models.ClusterStatusInstalling, srcStatusInfo: statusInfoInstalling, dstState: models.ClusterStatusFinalizing, @@ -4084,7 +4112,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { statusInfoChecker: makeValueChecker(statusInfoFinalizing), }, { - name: "installing to finalizing - stretched cluster", + name: "installing to finalizing - non-standard HA OCP Control Plane", srcState: models.ClusterStatusInstalling, srcStatusInfo: statusInfoInstalling, dstState: models.ClusterStatusFinalizing, @@ -4096,7 +4124,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { {ID: &hid5, Status: swag.String(models.HostStatusInstalled), Inventory: common.GenerateTestDefaultInventory(), Role: models.HostRoleWorker}, }, statusInfoChecker: makeValueChecker(statusInfoFinalizing), - openshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + openshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, }, { name: "installing to error - failing master", @@ -4194,6 +4222,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, @@ -4208,7 +4237,7 @@ var _ = Describe("Refresh Cluster - Installing Cases", func() { PullSecretSet: t.pullSecretSet, MonitoredOperators: t.operators, StatusUpdatedAt: strfmt.DateTime(time.Now()), - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } if t.openshiftVersion != "" { @@ -4686,6 +4715,7 @@ var _ = Describe("NTP refresh cluster", func() { t := tests[i] It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, @@ -4697,7 +4727,7 @@ var _ = Describe("NTP refresh cluster", func() { StatusInfo: &t.srcStatusInfo, BaseDNSDomain: "test.com", PullSecretSet: t.pullSecretSet, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), }, } @@ -4792,6 +4822,7 @@ var _ = Describe("Single node", func() { validationsChecker *validationsChecker setMachineCidrUpdatedAt bool errorExpected bool + controlPlaneCount int64 }{ { name: "non ha mode, too many nodes", @@ -4969,6 +5000,7 @@ var _ = Describe("Single node", func() { haMode := models.ClusterHighAvailabilityModeNone It(t.name, func() { cluster = common.Cluster{ + ControlPlaneCount: 1, Cluster: models.Cluster{ ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, ServiceNetworks: common.TestIPv4Networking.ServiceNetworks, @@ -4982,9 +5014,10 @@ var _ = Describe("Single node", func() { PullSecretSet: t.pullSecretSet, NetworkType: swag.String(models.ClusterNetworkTypeOVNKubernetes), HighAvailabilityMode: &haMode, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, }, } + if t.srcState == models.ClusterStatusPreparingForInstallation && t.dstState == models.ClusterStatusInstalling { cluster.Cluster.StatusUpdatedAt = strfmt.DateTime(time.Now()) cluster.LastInstallationPreparation = models.LastInstallationPreparation{ diff --git a/internal/cluster/validator.go b/internal/cluster/validator.go index 92260add4a87..5b55d1ba77f5 100644 --- a/internal/cluster/validator.go +++ b/internal/cluster/validator.go @@ -315,13 +315,6 @@ func (v *clusterValidator) SufficientMastersCount(c *clusterPreprocessContext) ( message string ) - // Might be the case for already existing records (default value). In this case high availablity mode is set, we will get - // the corresponding control planes count - if c.cluster.ControlPlaneCount == 0 { - _, count := common.GetDefaultHighAvailabilityAndMasterCountParams(c.cluster.HighAvailabilityMode, nil) - c.cluster.ControlPlaneCount = swag.Int64Value(count) - } - masters, workers, autoAssignHosts := common.GetHostsByEachRole(&c.cluster.Cluster, true) for _, h := range autoAssignHosts { //if allocated masters count is less than the desired count, find eligible hosts diff --git a/internal/cluster/validator_test.go b/internal/cluster/validator_test.go index 22ad864c2b34..3ed5c0ee50a9 100644 --- a/internal/cluster/validator_test.go +++ b/internal/cluster/validator_test.go @@ -692,37 +692,6 @@ var _ = Describe("SufficientMastersCount", func() { }) Context("pass validation", func() { - It("with matching counts, default ControlPlaneCount", func() { - mockHostAPI.EXPECT(). - IsValidMasterCandidate( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(true, nil).AnyTimes() - - preprocessContext := &clusterPreprocessContext{ - clusterId: clusterID, - cluster: &common.Cluster{Cluster: models.Cluster{ - ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), - Hosts: []*models.Host{ - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleMaster, - }, - }, - }}, - } - - status, message := validator.SufficientMastersCount(preprocessContext) - Expect(status).To(Equal(ValidationSuccess)) - Expect(message).To(Equal("The cluster has the exact amount of dedicated control plane nodes.")) - }) - It("with matching counts, set ControlPlaneCount", func() { mockHostAPI.EXPECT(). IsValidMasterCandidate( @@ -735,7 +704,7 @@ var _ = Describe("SufficientMastersCount", func() { ControlPlaneCount: 3, Cluster: models.Cluster{ ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), Hosts: []*models.Host{ { @@ -756,31 +725,6 @@ var _ = Describe("SufficientMastersCount", func() { Expect(message).To(Equal("The cluster has the exact amount of dedicated control plane nodes.")) }) - It("with SNO cluster, default controlPlaneCount", func() { - mockHostAPI.EXPECT(). - IsValidMasterCandidate( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(true, nil).AnyTimes() - - preprocessContext := &clusterPreprocessContext{ - clusterId: clusterID, - cluster: &common.Cluster{Cluster: models.Cluster{ - ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), - Hosts: []*models.Host{ - { - Role: models.HostRoleMaster, - }, - }, - }}, - } - - status, message := validator.SufficientMastersCount(preprocessContext) - Expect(status).To(Equal(ValidationSuccess)) - Expect(message).To(Equal("The cluster has the exact amount of dedicated control plane nodes.")) - }) - It("with SNO cluster, set controlPlaneCount", func() { mockHostAPI.EXPECT(). IsValidMasterCandidate( @@ -793,7 +737,7 @@ var _ = Describe("SufficientMastersCount", func() { ControlPlaneCount: 1, Cluster: models.Cluster{ ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), Hosts: []*models.Host{ { @@ -820,7 +764,7 @@ var _ = Describe("SufficientMastersCount", func() { ControlPlaneCount: 5, Cluster: models.Cluster{ ID: &clusterID, - OpenshiftVersion: common.MinimumVersionForStretchedControlPlanesCluster, + OpenshiftVersion: common.MinimumVersionForNonStandardHAOCPControlPlane, HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), Hosts: []*models.Host{ { @@ -849,7 +793,7 @@ var _ = Describe("SufficientMastersCount", func() { }) Context("fails validation", func() { - It("with multi node cluster, 5 masters but expected 3 by default", func() { + It("with multi node cluster, 5 masters but expected 3", func() { mockHostAPI.EXPECT(). IsValidMasterCandidate( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), @@ -858,9 +802,10 @@ var _ = Describe("SufficientMastersCount", func() { preprocessContext := &clusterPreprocessContext{ clusterId: clusterID, cluster: &common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), Hosts: []*models.Host{ { @@ -884,13 +829,10 @@ var _ = Describe("SufficientMastersCount", func() { status, message := validator.SufficientMastersCount(preprocessContext) Expect(status).To(Equal(ValidationFailure)) - Expect(message).To(Equal(fmt.Sprintf( - "The cluster must have exactly %d dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.", - common.MinMasterHostsNeededForInstallationInHaMode, - ))) + Expect(message).To(Equal("The cluster must have exactly 3 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.")) }) - It("with multi node cluster, 5 masters but expected 3", func() { + It("with SNO cluster, 2 masters 0 workers", func() { mockHostAPI.EXPECT(). IsValidMasterCandidate( gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), @@ -899,11 +841,11 @@ var _ = Describe("SufficientMastersCount", func() { preprocessContext := &clusterPreprocessContext{ clusterId: clusterID, cluster: &common.Cluster{ - ControlPlaneCount: 3, + ControlPlaneCount: 1, Cluster: models.Cluster{ ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), Hosts: []*models.Host{ { Role: models.HostRoleMaster, @@ -911,47 +853,10 @@ var _ = Describe("SufficientMastersCount", func() { { Role: models.HostRoleMaster, }, - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleMaster, - }, }, }}, } - status, message := validator.SufficientMastersCount(preprocessContext) - Expect(status).To(Equal(ValidationFailure)) - Expect(message).To(Equal("The cluster must have exactly 3 dedicated control plane nodes. Add or remove hosts, or change their roles configurations to meet the requirement.")) - }) - - It("with SNO cluster, 2 masters 0 workers", func() { - mockHostAPI.EXPECT(). - IsValidMasterCandidate( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(true, nil).AnyTimes() - - preprocessContext := &clusterPreprocessContext{ - clusterId: clusterID, - cluster: &common.Cluster{Cluster: models.Cluster{ - ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), - Hosts: []*models.Host{ - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleMaster, - }, - }, - }}, - } - status, message := validator.SufficientMastersCount(preprocessContext) Expect(status).To(Equal(ValidationFailure)) Expect(message).To(Equal("Single-node clusters must have a single control plane node and no workers.")) @@ -965,19 +870,21 @@ var _ = Describe("SufficientMastersCount", func() { preprocessContext := &clusterPreprocessContext{ clusterId: clusterID, - cluster: &common.Cluster{Cluster: models.Cluster{ - ID: &clusterID, - OpenshiftVersion: testing.ValidOCPVersionForNonStretchedClusters, - HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), - Hosts: []*models.Host{ - { - Role: models.HostRoleMaster, - }, - { - Role: models.HostRoleWorker, + cluster: &common.Cluster{ + ControlPlaneCount: 1, + Cluster: models.Cluster{ + ID: &clusterID, + OpenshiftVersion: testing.ValidOCPVersionForNonStandardHAOCPControlPlane, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), + Hosts: []*models.Host{ + { + Role: models.HostRoleMaster, + }, + { + Role: models.HostRoleWorker, + }, }, - }, - }}, + }}, } status, message := validator.SufficientMastersCount(preprocessContext) diff --git a/internal/common/common.go b/internal/common/common.go index de34e378ddec..07b994d2ea43 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -54,7 +54,7 @@ const ( AllowedNumberOfMasterHostsForInstallationInHaModeOfOCP417OrOlder = 3 AllowedNumberOfMasterHostsInNoneHaMode = 1 AllowedNumberOfWorkersInNoneHaMode = 0 - MinimumVersionForStretchedControlPlanesCluster = "4.18" + MinimumVersionForNonStandardHAOCPControlPlane = "4.18" MinimumNumberOfWorkersForNonSchedulableMastersClusterInHaMode = 2 ) @@ -688,35 +688,6 @@ func ShouldMastersBeSchedulable(cluster *models.Cluster) bool { return len(workers) < MinimumNumberOfWorkersForNonSchedulableMastersClusterInHaMode } -func GetDefaultHighAvailabilityAndMasterCountParams(highAvailabilityMode *string, controlPlaneCount *int64) (*string, *int64) { - // Both not set, multi node by default - if highAvailabilityMode == nil && controlPlaneCount == nil { - return swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), - swag.Int64(MinMasterHostsNeededForInstallationInHaMode) - } - - // only highAvailabilityMode set - if controlPlaneCount == nil { - if *highAvailabilityMode == models.ClusterHighAvailabilityModeNone { - return highAvailabilityMode, swag.Int64(AllowedNumberOfMasterHostsInNoneHaMode) - } else { - return highAvailabilityMode, swag.Int64(MinMasterHostsNeededForInstallationInHaMode) - } - } - - // only controlPlaneCount set - if highAvailabilityMode == nil { - if *controlPlaneCount == AllowedNumberOfMasterHostsInNoneHaMode { - return swag.String(models.ClusterHighAvailabilityModeNone), controlPlaneCount - } else { - return swag.String(models.ClusterHighAvailabilityModeFull), controlPlaneCount - } - } - - // both are set - return highAvailabilityMode, controlPlaneCount -} - func IsMirrorConfigurationSet(conf *MirrorRegistryConfiguration) bool { if conf == nil { return false diff --git a/internal/common/db.go b/internal/common/db.go index 959562656a69..b71fc98f4a27 100644 --- a/internal/common/db.go +++ b/internal/common/db.go @@ -85,7 +85,7 @@ type Cluster struct { // A JSON blob in which cluster UI settings will be stored. UISettings string `json:"ui_settings"` - // The amount of control planes which should be part of the cluster in high availability 'Full' mode. + // The amount of control planes which should be part of the cluster. ControlPlaneCount int64 `json:"control_plane_count"` // A JSON blob in which holds the cluster mirror registry if set diff --git a/internal/controller/controllers/clusterdeployments_controller.go b/internal/controller/controllers/clusterdeployments_controller.go index 6928b28d8f2a..c5d726099890 100644 --- a/internal/controller/controllers/clusterdeployments_controller.go +++ b/internal/controller/controllers/clusterdeployments_controller.go @@ -709,7 +709,7 @@ func (r *ClusterDeploymentsReconciler) isReadyForInstallation( expectedWorkerCount := clusterInstall.Spec.ProvisionRequirements.WorkerAgents expectedHostCount := expectedMasterCount + expectedWorkerCount - masterCountPrt, workerCountPtr, err := getHostSuggestedRoleCount(r.ClusterApi, *c.ID) + masterCountPtr, workerCountPtr, err := getHostSuggestedRoleCount(r.ClusterApi, *c.ID) if err != nil { // will be shown as a SpecSynced error log.WithError(err).Error("failed to fetch host suggested role count") @@ -718,7 +718,7 @@ func (r *ClusterDeploymentsReconciler) isReadyForInstallation( return approvedHosts == expectedHostCount && registered == expectedHostCount && - int(swag.Int64Value(masterCountPrt)) == expectedMasterCount && + int(swag.Int64Value(masterCountPtr)) == expectedMasterCount && int(swag.Int64Value(workerCountPtr)) == expectedWorkerCount && unsyncedHosts == 0, nil } diff --git a/internal/featuresupport/feature_support_level.go b/internal/featuresupport/feature_support_level.go index c55b0bf2d76d..3198fa9845f8 100644 --- a/internal/featuresupport/feature_support_level.go +++ b/internal/featuresupport/feature_support_level.go @@ -11,12 +11,13 @@ import ( var featuresList = map[models.FeatureSupportLevelID]SupportLevelFeature{ // Generic features - models.FeatureSupportLevelIDSNO: (&SnoFeature{}).New(), - models.FeatureSupportLevelIDCUSTOMMANIFEST: (&CustomManifestFeature{}).New(), - models.FeatureSupportLevelIDSINGLENODEEXPANSION: (&SingleNodeExpansionFeature{}).New(), - models.FeatureSupportLevelIDMINIMALISO: (&MinimalIso{}).New(), - models.FeatureSupportLevelIDFULLISO: (&FullIso{}).New(), - models.FeatureSupportLevelIDSKIPMCOREBOOT: &skipMcoReboot{}, + models.FeatureSupportLevelIDSNO: (&SnoFeature{}).New(), + models.FeatureSupportLevelIDCUSTOMMANIFEST: (&CustomManifestFeature{}).New(), + models.FeatureSupportLevelIDSINGLENODEEXPANSION: (&SingleNodeExpansionFeature{}).New(), + models.FeatureSupportLevelIDMINIMALISO: (&MinimalIso{}).New(), + models.FeatureSupportLevelIDFULLISO: (&FullIso{}).New(), + models.FeatureSupportLevelIDSKIPMCOREBOOT: &skipMcoReboot{}, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE: (&NonStandardHAControlPlane{}).New(), // Network features models.FeatureSupportLevelIDVIPAUTOALLOC: (&VipAutoAllocFeature{}).New(), diff --git a/internal/featuresupport/feature_support_test.go b/internal/featuresupport/feature_support_test.go index 7a31b8801c82..3722d1d36686 100644 --- a/internal/featuresupport/feature_support_test.go +++ b/internal/featuresupport/feature_support_test.go @@ -177,6 +177,70 @@ var _ = Describe("V2ListFeatureSupportLevels API", func() { ) }) + Context("Test non-standad HA OCP Control Plane", func() { + feature := models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE + arch := "DoesNotMatter" + + It("test feature availability", func() { + Expect(IsFeatureAvailable(feature, common.MinimumVersionForNonStandardHAOCPControlPlane, swag.String(arch))).To(BeTrue()) + Expect(IsFeatureAvailable(feature, "4.17", swag.String(arch))).To(BeFalse()) + }) + + DescribeTable("test feature compatability with other features", func(activeFeatures []SupportLevelFeature, shouldSucceed bool) { + activeFeatures = append(activeFeatures, &NonStandardHAControlPlane{}) + + if shouldSucceed { + Expect( + isFeaturesCompatibleWithFeatures( + common.MinimumVersionForNonStandardHAOCPControlPlane, + activeFeatures), + ).ToNot(HaveOccurred()) + } else { + Expect( + isFeaturesCompatibleWithFeatures( + common.MinimumVersionForNonStandardHAOCPControlPlane, + activeFeatures), + ).To(HaveOccurred()) + } + }, + Entry( + "platform baremetal", + []SupportLevelFeature{&BaremetalPlatformFeature{}}, + true, + ), + + Entry( + "external platform", + []SupportLevelFeature{&ExternalPlatformFeature{}}, + false, + ), + + Entry( + "nutanix platform", + []SupportLevelFeature{&NutanixIntegrationFeature{}}, + false, + ), + + Entry( + "vsphere platform", + []SupportLevelFeature{&VsphereIntegrationFeature{}}, + false, + ), + + Entry( + "none platform", + []SupportLevelFeature{&NonePlatformFeature{}}, + false, + ), + + Entry( + "odf operator", + []SupportLevelFeature{&OdfFeature{}}, + false, + ), + ) + }) + Context("Test MCE not supported under 4.10", func() { feature := models.FeatureSupportLevelIDMCE It(fmt.Sprintf("%s test", feature), func() { @@ -269,19 +333,19 @@ var _ = Describe("V2ListFeatureSupportLevels API", func() { When("GetFeatureSupportList 4.12 with Platform", func() { It(string(*filters.PlatformType)+" "+swag.StringValue(filters.ExternalPlatformName), func() { list := GetFeatureSupportList("dummy", nil, filters.PlatformType, filters.ExternalPlatformName) - Expect(len(list)).To(Equal(26)) + Expect(len(list)).To(Equal(27)) }) }) } It("GetFeatureSupportList 4.12", func() { list := GetFeatureSupportList("4.12", nil, nil, nil) - Expect(len(list)).To(Equal(31)) + Expect(len(list)).To(Equal(32)) }) It("GetFeatureSupportList 4.13", func() { list := GetFeatureSupportList("4.13", nil, nil, nil) - Expect(len(list)).To(Equal(31)) + Expect(len(list)).To(Equal(32)) }) It("GetCpuArchitectureSupportList 4.12", func() { diff --git a/internal/featuresupport/features_misc.go b/internal/featuresupport/features_misc.go index 9f0e285c1463..a3d77245c15b 100644 --- a/internal/featuresupport/features_misc.go +++ b/internal/featuresupport/features_misc.go @@ -300,3 +300,57 @@ func (f *skipMcoReboot) getFeatureActiveLevel(cluster *common.Cluster, infraEnv } return activeLevelActive } + +// Non-standard HA OCP Control Plane +type NonStandardHAControlPlane struct{} + +func (f *NonStandardHAControlPlane) New() SupportLevelFeature { + return &NonStandardHAControlPlane{} +} + +func (f *NonStandardHAControlPlane) getId() models.FeatureSupportLevelID { + return models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE +} + +func (f *NonStandardHAControlPlane) GetName() string { + return "Non-standard HA OCP Control Plane" +} + +func (f *NonStandardHAControlPlane) getSupportLevel(filters SupportLevelFilters) models.SupportLevel { + supported, err := common.BaseVersionGreaterOrEqual(common.MinimumVersionForNonStandardHAOCPControlPlane, filters.OpenshiftVersion) + if !supported || err != nil { + return models.SupportLevelUnavailable + } + + if filters.PlatformType != nil && *filters.PlatformType != models.PlatformTypeBaremetal { + return models.SupportLevelUnavailable + } + + return models.SupportLevelSupported +} + +func (f *NonStandardHAControlPlane) getIncompatibleFeatures(openshiftVersion string) *[]models.FeatureSupportLevelID { + return &[]models.FeatureSupportLevelID{ + models.FeatureSupportLevelIDODF, + + // only baremetal platform is supported + models.FeatureSupportLevelIDEXTERNALPLATFORM, + models.FeatureSupportLevelIDNUTANIXINTEGRATION, + models.FeatureSupportLevelIDVSPHEREINTEGRATION, + models.FeatureSupportLevelIDNONEPLATFORM, + models.FeatureSupportLevelIDEXTERNALPLATFORMOCI, + } +} + +func (f *NonStandardHAControlPlane) getIncompatibleArchitectures(openshiftVersion *string) *[]models.ArchitectureSupportLevelID { + return nil +} + +func (f *NonStandardHAControlPlane) getFeatureActiveLevel(cluster *common.Cluster, infraEnv *models.InfraEnv, + clusterUpdateParams *models.V2ClusterUpdateParams, infraenvUpdateParams *models.InfraEnvUpdateParams) featureActiveLevel { + if cluster != nil && cluster.ControlPlaneCount > 3 { + return activeLevelActive + } + + return activeLevelNotActive +} diff --git a/internal/featuresupport/features_olm_operators.go b/internal/featuresupport/features_olm_operators.go index 7caba37dcacb..042f8c298448 100644 --- a/internal/featuresupport/features_olm_operators.go +++ b/internal/featuresupport/features_olm_operators.go @@ -134,6 +134,7 @@ func (feature *OdfFeature) getIncompatibleFeatures(string) *[]models.FeatureSupp return &[]models.FeatureSupportLevelID{ models.FeatureSupportLevelIDSNO, models.FeatureSupportLevelIDLVM, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } } diff --git a/internal/featuresupport/features_platforms.go b/internal/featuresupport/features_platforms.go index eae4ad4cca8c..3cb5ba514899 100644 --- a/internal/featuresupport/features_platforms.go +++ b/internal/featuresupport/features_platforms.go @@ -125,6 +125,7 @@ func (feature *NonePlatformFeature) getIncompatibleFeatures(string) *[]models.Fe return &[]models.FeatureSupportLevelID{ models.FeatureSupportLevelIDVIPAUTOALLOC, models.FeatureSupportLevelIDCLUSTERMANAGEDNETWORKING, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } } @@ -183,6 +184,7 @@ func (feature *NutanixIntegrationFeature) getIncompatibleFeatures(string) *[]mod models.FeatureSupportLevelIDCNV, models.FeatureSupportLevelIDPLATFORMMANAGEDNETWORKING, models.FeatureSupportLevelIDMTV, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } } @@ -236,6 +238,7 @@ func (feature *VsphereIntegrationFeature) getIncompatibleFeatures(openshiftVersi models.FeatureSupportLevelIDPLATFORMMANAGEDNETWORKING, models.FeatureSupportLevelIDCNV, models.FeatureSupportLevelIDMTV, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } if isNotSupported, err := common.BaseVersionLessThan("4.13", openshiftVersion); isNotSupported || err != nil { @@ -289,6 +292,7 @@ func (feature *OciIntegrationFeature) getIncompatibleFeatures(string) *[]models. models.FeatureSupportLevelIDVIPAUTOALLOC, models.FeatureSupportLevelIDDUALSTACKVIPS, models.FeatureSupportLevelIDFULLISO, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } } @@ -338,6 +342,7 @@ func (feature *ExternalPlatformFeature) getIncompatibleFeatures(string) *[]model return &[]models.FeatureSupportLevelID{ models.FeatureSupportLevelIDCLUSTERMANAGEDNETWORKING, models.FeatureSupportLevelIDVIPAUTOALLOC, + models.FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE, } } diff --git a/internal/host/monitor.go b/internal/host/monitor.go index 767a3d0c0a37..74e515132d53 100644 --- a/internal/host/monitor.go +++ b/internal/host/monitor.go @@ -152,11 +152,6 @@ func (m *Manager) clusterHostMonitoring() int64 { } for _, c := range clusters { - expectedMasterCount := c.ControlPlaneCount - if c.ControlPlaneCount == 0 { - expectedMasterCount = common.MinMasterHostsNeededForInstallationInHaMode - } - inventoryCache := make(InventoryCache) sortedHosts, canRefreshRoles := SortHosts(c.Hosts) @@ -176,7 +171,7 @@ func (m *Manager) clusterHostMonitoring() int64 { //all the hosts in the cluster has inventory to avoid race condition //with the reset auto-assign mechanism. if canRefreshRoles { - err = m.refreshRoleInternal(ctx, host, m.db, false, swag.Int(int(expectedMasterCount))) + err = m.refreshRoleInternal(ctx, host, m.db, false, swag.Int(int(c.ControlPlaneCount))) if err != nil { log.WithError(err).Errorf("failed to refresh host %s role", *host.ID) } diff --git a/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records.go b/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records.go new file mode 100644 index 000000000000..58ac13d41fb1 --- /dev/null +++ b/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records.go @@ -0,0 +1,48 @@ +package migrations + +import ( + gormigrate "github.com/go-gormigrate/gormigrate/v2" + "github.com/openshift/assisted-service/internal/common" + "github.com/openshift/assisted-service/models" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +// split to 2 migrations, one for each query. + +func updateNewColumnControlPlaneCountValueForExistingClusterRecords() *gormigrate.Migration { + migrate := func(db *gorm.DB) error { + return db.Transaction(func(tx *gorm.DB) error { + err := tx.Model(&common.Cluster{}). + // control_plane_count value in existing records can be NULL or 0 (default). We want to set the value in both cases + Where(tx.Where("control_plane_count IS NULL OR control_plane_count = ?", 0)). + Where("high_availability_mode = ?", models.ClusterCreateParamsHighAvailabilityModeNone). + Update("control_plane_count", "1").Error + if err != nil { + return errors.Wrap(err, "failed to update control_plane_count value of existing SNO clusters to 1") + } + + err = tx.Model(&common.Cluster{}). + // control_plane_count value in existing records can be NULL or 0 (default). We want to set the value in both cases + Where(tx.Where("control_plane_count IS NULL OR control_plane_count = ?", 0)). + Where("high_availability_mode = ?", models.ClusterCreateParamsHighAvailabilityModeFull). + Update("control_plane_count", "3").Error + if err != nil { + return errors.Wrap(err, "failed to update control_plane_count value of existing multi-node clusters to 3") + } + + return nil + }) + } + + rollback := func(tx *gorm.DB) error { + // No rollback as we can't roll back only the modified records + return nil + } + + return &gormigrate.Migration{ + ID: "20241122160000", + Migrate: gormigrate.MigrateFunc(migrate), + Rollback: gormigrate.RollbackFunc(rollback), + } +} diff --git a/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records_test.go b/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records_test.go new file mode 100644 index 000000000000..d4fc856a8657 --- /dev/null +++ b/internal/migrations/20241122160000_update_new_column_control_plane_count_value_for_existing_cluster_records_test.go @@ -0,0 +1,121 @@ +package migrations + +import ( + gormigrate "github.com/go-gormigrate/gormigrate/v2" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/google/uuid" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/openshift/assisted-service/internal/common" + "github.com/openshift/assisted-service/models" + "gorm.io/gorm" +) + +var ( + id1 = strfmt.UUID(uuid.New().String()) + id2 = strfmt.UUID(uuid.New().String()) + id3 = strfmt.UUID(uuid.New().String()) + id4 = strfmt.UUID(uuid.New().String()) +) + +var _ = Describe("updateNewColumnControlPlaneCountValueForExistingSNOClusterRecords", func() { + var ( + db *gorm.DB + dbName string + migration *gormigrate.Migration = updateNewColumnControlPlaneCountValueForExistingClusterRecords() + ) + + BeforeEach(func() { + db, dbName = common.PrepareTestDB() + }) + + AfterEach(func() { + common.DeleteTestDB(db, dbName) + }) + + Context("succeeds", func() { + It("changing only records with control_plane_count = 0", func() { + clusters := []*common.Cluster{ + { + ControlPlaneCount: 0, + Cluster: models.Cluster{ + ID: &id1, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), + }, + }, + { + ControlPlaneCount: 1, + Cluster: models.Cluster{ + ID: &id2, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeNone), + }, + }, + { + ControlPlaneCount: 0, + Cluster: models.Cluster{ + ID: &id3, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), + }, + }, + { + ControlPlaneCount: 3, + Cluster: models.Cluster{ + ID: &id4, + HighAvailabilityMode: swag.String(models.ClusterCreateParamsHighAvailabilityModeFull), + }, + }, + } + + Expect(db.Create(&clusters).Error).To(Succeed()) + Expect(migrateToBefore(db, migration.ID)).To(Succeed()) + Expect(migrateTo(db, migration.ID)).To(Succeed()) + + var count int64 + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count = ?", 0).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(0)) + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count = ?", 1).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(2)) + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count = ?", 3).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(2)) + }) + }) + + It("changing only records with control_plane_count = NULL", func() { + Expect( + db.Exec( + "INSERT INTO clusters (id, control_plane_count, high_availability_mode) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)", + id1, nil, models.ClusterCreateParamsHighAvailabilityModeNone, + id2, 1, models.ClusterCreateParamsHighAvailabilityModeNone, + id3, 3, models.ClusterCreateParamsHighAvailabilityModeFull, + id4, nil, models.ClusterCreateParamsHighAvailabilityModeFull, + ).Error, + ).To(Succeed()) + Expect(migrateToBefore(db, migration.ID)).To(Succeed()) + Expect(migrateTo(db, migration.ID)).To(Succeed()) + + var count int64 + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count IS NULL").Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(0)) + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count = ?", 1).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(2)) + + Expect(db.Model(&common.Cluster{}).Where("control_plane_count = ?", 3).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(2)) + }) + + It("with no existing records", func() { + Expect(migrateToBefore(db, migration.ID)).To(Succeed()) + Expect(migrateTo(db, migration.ID)).To(Succeed()) + + var count int64 + + Expect(db.Model(&common.Cluster{}).Count(&count).Error).To(Succeed()) + Expect(count).To(BeEquivalentTo(0)) + }) +}) diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index 3c2e5f8d49cd..610b5a4a1ca6 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -45,6 +45,7 @@ func post() []*gormigrate.Migration { dropClusterApiVipAndIngressVip(), updateOciToExternalPlatformType(), dropClusterPlatformIsExternal(), + updateNewColumnControlPlaneCountValueForExistingClusterRecords(), } sort.SliceStable(postMigrations, func(i, j int) bool { return postMigrations[i].ID < postMigrations[j].ID }) diff --git a/internal/operators/odf/odf_operator.go b/internal/operators/odf/odf_operator.go index e30114b586dd..07eea9289f48 100644 --- a/internal/operators/odf/odf_operator.go +++ b/internal/operators/odf/odf_operator.go @@ -129,7 +129,7 @@ func (o *operator) getValidDiskCount(disks []*models.Disk, installationDiskID st // ValidateHost verifies whether this operator is valid for given host func (o *operator) ValidateHost(_ context.Context, cluster *common.Cluster, host *models.Host, additionalOperatorRequirements *models.ClusterHostRequirementsDetails) (api.ValidationResult, error) { - // temporary disabling ODF for stretched clusters until it will be clear how ODF will work in this scenario. + // temporary disabling ODF for non-standad HA OCP Control Plane until it will be clear how ODF will work in this scenario. // We pass host validation to avoid false validation messages. The cluster validation will fail masters, _, _ := common.GetHostsByEachRole(&cluster.Cluster, true) if masterCount := len(masters); masterCount > 3 { @@ -199,7 +199,7 @@ func (o *operator) GetMonitoredOperator() *models.MonitoredOperator { // GetHostRequirements provides operator's requirements towards the host func (o *operator) GetHostRequirements(_ context.Context, cluster *common.Cluster, host *models.Host) (*models.ClusterHostRequirementsDetails, error) { - // temporary disabling ODF for stretched clusters until it will be clear how ODF will work in this scenario. + // temporary disabling ODF for non-standad HA OCP Control Plane until it will be clear how ODF will work in this scenario. // We pass host validation to avoid false validation messages. The cluster validation will fail masters, _, _ := common.GetHostsByEachRole(&cluster.Cluster, true) if masterCount := len(masters); masterCount > 3 { diff --git a/internal/operators/odf/validation_test.go b/internal/operators/odf/validation_test.go index d8b63ba001ad..ca0feeefd3a1 100644 --- a/internal/operators/odf/validation_test.go +++ b/internal/operators/odf/validation_test.go @@ -877,6 +877,7 @@ var _ = Describe("Ocs Operator use-cases", func() { } cluster = common.Cluster{ + ControlPlaneCount: 3, Cluster: models.Cluster{ ID: &clusterId, ClusterNetworks: common.TestIPv4Networking.ClusterNetworks, @@ -895,7 +896,7 @@ var _ = Describe("Ocs Operator use-cases", func() { } if cluster.Cluster.OpenshiftVersion == "" { - cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStretchedClusters + cluster.Cluster.OpenshiftVersion = testing.ValidOCPVersionForNonStandardHAOCPControlPlane } Expect(db.Create(&cluster).Error).ShouldNot(HaveOccurred()) diff --git a/internal/operators/odf/validations.go b/internal/operators/odf/validations.go index 3897f96cfba7..9f307835fb86 100644 --- a/internal/operators/odf/validations.go +++ b/internal/operators/odf/validations.go @@ -34,7 +34,7 @@ type odfClusterResourcesInfo struct { func (o *operator) validateRequirements(cluster *models.Cluster) (api.ValidationStatus, string) { var status string - // temporary disabling ODF for stretched clusters until it will be clear how ODF will work in this scenario. + // temporary disabling ODF for non-standad HA OCP Control Plane until it will be clear how ODF will work in this scenario. masters, _, _ := common.GetHostsByEachRole(cluster, true) if masterCount := len(masters); masterCount > 3 { status = "There are currently more than 3 hosts designated to be control planes. ODF currently supports clusters with exactly three control plane nodes." diff --git a/internal/testing/common.go b/internal/testing/common.go index 87697fa07339..2cef0995c195 100644 --- a/internal/testing/common.go +++ b/internal/testing/common.go @@ -8,8 +8,8 @@ import ( "github.com/openshift/assisted-service/internal/common" ) -var ValidOCPVersionForNonStretchedClusters = func(majorMinorOCPVersion string) string { +var ValidOCPVersionForNonStandardHAOCPControlPlane = func(majorMinorOCPVersion string) string { splittedVersion := strings.Split(majorMinorOCPVersion, ".") intVersion, _ := strconv.Atoi(splittedVersion[1]) return fmt.Sprintf("%s.%d", splittedVersion[0], intVersion-1) -}(common.MinimumVersionForStretchedControlPlanesCluster) +}(common.MinimumVersionForNonStandardHAOCPControlPlane) diff --git a/models/feature_support_level_id.go b/models/feature_support_level_id.go index 2a7d4ad667ff..4f19525b80b3 100644 --- a/models/feature_support_level_id.go +++ b/models/feature_support_level_id.go @@ -125,6 +125,9 @@ const ( // FeatureSupportLevelIDOPENSHIFTAI captures enum value "OPENSHIFT_AI" FeatureSupportLevelIDOPENSHIFTAI FeatureSupportLevelID = "OPENSHIFT_AI" + + // FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE captures enum value "NON_STANDARD_HA_CONTROL_PLANE" + FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE FeatureSupportLevelID = "NON_STANDARD_HA_CONTROL_PLANE" ) // for schema @@ -132,7 +135,7 @@ var featureSupportLevelIdEnum []interface{} func init() { var res []FeatureSupportLevelID - if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI","NON_STANDARD_HA_CONTROL_PLANE"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index b33874b2983c..a382792290d7 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -7773,7 +7773,8 @@ func init() { "PIPELINES", "SERVICEMESH", "SERVERLESS", - "OPENSHIFT_AI" + "OPENSHIFT_AI", + "NON_STANDARD_HA_CONTROL_PLANE" ] }, "finalizing-stage": { @@ -18634,7 +18635,8 @@ func init() { "PIPELINES", "SERVICEMESH", "SERVERLESS", - "OPENSHIFT_AI" + "OPENSHIFT_AI", + "NON_STANDARD_HA_CONTROL_PLANE" ] }, "finalizing-stage": { diff --git a/subsystem/cluster_test.go b/subsystem/cluster_test.go index 6f7357b02cbd..7271aeeeb207 100644 --- a/subsystem/cluster_test.go +++ b/subsystem/cluster_test.go @@ -5171,7 +5171,7 @@ var _ = Describe("Verify install-config manifest", func() { ) }) -var _ = Describe("Verify role assignment for stretched control plane cluster", func() { +var _ = Describe("Verify role assignment for non-standard HA OCP Control Plane cluster", func() { var ctx = context.TODO() It("with 4 masters, 1 worker", func() { @@ -5179,7 +5179,7 @@ var _ = Describe("Verify role assignment for stretched control plane cluster", f Context: ctx, NewClusterParams: &models.ClusterCreateParams{ Name: swag.String("test-cluster"), - OpenshiftVersion: swag.String(common.MinimumVersionForStretchedControlPlanesCluster), + OpenshiftVersion: swag.String(common.MinimumVersionForNonStandardHAOCPControlPlane), PullSecret: swag.String(pullSecret), ControlPlaneCount: swag.Int64(4), }, diff --git a/swagger.yaml b/swagger.yaml index 6d368ec5d145..8ff45071bfc1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -4159,6 +4159,7 @@ definitions: - 'SERVICEMESH' - 'SERVERLESS' - 'OPENSHIFT_AI' + - 'NON_STANDARD_HA_CONTROL_PLANE' architecture-support-level-id: type: string diff --git a/vendor/github.com/openshift/assisted-service/api/common/common_types.go b/vendor/github.com/openshift/assisted-service/api/common/common_types.go index 6eff7525735c..19afd847d2e2 100644 --- a/vendor/github.com/openshift/assisted-service/api/common/common_types.go +++ b/vendor/github.com/openshift/assisted-service/api/common/common_types.go @@ -16,4 +16,3 @@ type ValidationsStatus map[string]ValidationResults // +kubebuilder:object:generate=true type ValidationResults []ValidationResult - diff --git a/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go b/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go index 2a7d4ad667ff..4f19525b80b3 100644 --- a/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go +++ b/vendor/github.com/openshift/assisted-service/models/feature_support_level_id.go @@ -125,6 +125,9 @@ const ( // FeatureSupportLevelIDOPENSHIFTAI captures enum value "OPENSHIFT_AI" FeatureSupportLevelIDOPENSHIFTAI FeatureSupportLevelID = "OPENSHIFT_AI" + + // FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE captures enum value "NON_STANDARD_HA_CONTROL_PLANE" + FeatureSupportLevelIDNONSTANDARDHACONTROLPLANE FeatureSupportLevelID = "NON_STANDARD_HA_CONTROL_PLANE" ) // for schema @@ -132,7 +135,7 @@ var featureSupportLevelIdEnum []interface{} func init() { var res []FeatureSupportLevelID - if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["SNO","VIP_AUTO_ALLOC","CUSTOM_MANIFEST","SINGLE_NODE_EXPANSION","LVM","ODF","LSO","CNV","MCE","MTV","NUTANIX_INTEGRATION","BAREMETAL_PLATFORM","NONE_PLATFORM","VSPHERE_INTEGRATION","DUAL_STACK_VIPS","CLUSTER_MANAGED_NETWORKING","USER_MANAGED_NETWORKING","MINIMAL_ISO","FULL_ISO","EXTERNAL_PLATFORM_OCI","DUAL_STACK","PLATFORM_MANAGED_NETWORKING","SKIP_MCO_REBOOT","EXTERNAL_PLATFORM","OVN_NETWORK_TYPE","SDN_NETWORK_TYPE","NODE_FEATURE_DISCOVERY","NVIDIA_GPU","PIPELINES","SERVICEMESH","SERVERLESS","OPENSHIFT_AI","NON_STANDARD_HA_CONTROL_PLANE"]`), &res); err != nil { panic(err) } for _, v := range res {