diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java index d7707e197d64..04fe128eb9c2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java @@ -38,6 +38,9 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; +import com.cloud.hypervisor.Hypervisor.HypervisorType; + import javax.inject.Inject; @@ -50,6 +53,9 @@ public final class ConfigureHAForHostCmd extends BaseAsyncCmd { @Inject private HAConfigManager haConfigManager; + @Inject + private OutOfBandManagementService outOfBandManagementService; + ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @@ -98,6 +104,12 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId()); } + if (host.getHypervisorType() == HypervisorType.KVM) { + if (!outOfBandManagementService.isOutOfBandManagementEnabled(host)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot configure HA on KVM host because Out-of-Band Management (OOBM) is not enabled."); + } + } + final boolean result = haConfigManager.configureHA(host.getId(), HAResource.ResourceType.Host, getHaProvider()); if (!result) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to configure HA provider for the host"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java index f54767225432..82a9f6554012 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java @@ -37,7 +37,8 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; - +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; import javax.inject.Inject; @APICommand(name = "enableHAForHost", description = "Enables HA for a host", @@ -49,6 +50,10 @@ public final class EnableHAForHostCmd extends BaseAsyncCmd { @Inject private HAConfigManager haConfigManager; + @Inject + private OutOfBandManagementService outOfBandManagementService; + + ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @@ -89,6 +94,15 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE if (host == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId()); } + + // --- YOUR NEW GUARDRAIL --- + if (host.getHypervisorType() == HypervisorType.KVM) { + if (!outOfBandManagementService.isOutOfBandManagementEnabled(host)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot enable HA on KVM host because Out-of-Band Management (OOBM) is not enabled."); + } + } + // -------------------------- + final boolean result = haConfigManager.enableHA(host.getId(), HAResource.ResourceType.Host); CallContext.current().setEventDetails("Host Id:" + host.getId() + " HA enabled: true"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java index 6c9b48ef28f7..6ae61b11de8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java @@ -38,6 +38,9 @@ import org.apache.cloudstack.api.response.OutOfBandManagementResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; +import org.apache.cloudstack.ha.HAConfigManager; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import org.apache.cloudstack.ha.HAResource; import javax.inject.Inject; @@ -46,6 +49,9 @@ since = "4.9.0", authorized = {RoleType.Admin}) public class DisableOutOfBandManagementForHostCmd extends BaseAsyncCmd { + @Inject + private HAConfigManager haConfigManager; + @Inject private OutOfBandManagementService outOfBandManagementService; @@ -61,13 +67,22 @@ public class DisableOutOfBandManagementForHostCmd extends BaseAsyncCmd { /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// - @Override - final public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { final Host host = _resourceService.getHost(getHostId()); if (host == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId()); } + if (host.getHypervisorType() == HypervisorType.KVM) { + java.util.List haConfigs = haConfigManager.listHAResources(host.getId(), HAResource.ResourceType.Host); + if (haConfigs != null && !haConfigs.isEmpty()) { + if (haConfigs.get(0).isEnabled()) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot disable Out-of-Band Management (OOBM) because HA is currently enabled on this KVM host. Please disable HA first."); + } + } + } + OutOfBandManagementResponse response = outOfBandManagementService.disableOutOfBandManagement(host); CallContext.current().setEventDetails("Host Id:" + host.getId() + " out-of-band management enabled: false"); diff --git a/test/integration/smoke/test_hostha_kvm.py b/test/integration/smoke/test_hostha_kvm.py index 9b131558dc24..21bdab147aa3 100644 --- a/test/integration/smoke/test_hostha_kvm.py +++ b/test/integration/smoke/test_hostha_kvm.py @@ -88,6 +88,7 @@ def setUp(self): self.configureAndDisableHostHa() self.cleanup = [self.service_offering] + self.configureAndEnableOobm() def updateConfiguration(self, name, value): cmd = updateConfiguration.updateConfigurationCmd() @@ -150,12 +151,13 @@ def getHostHaDisableCmd(self): return cmd def configureAndEnableHostHa(self): - #Adding sleep between configuring and enabling + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) response = self.apiclient.enableHAForHost(self.getHostHaEnableCmd()) self.assertEqual(response.haenable, True) - + def configureAndDisableHostHa(self): + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaDisableCmd() cmd.hostid = self.host.id @@ -229,6 +231,7 @@ def test_disable_oobm_ha_state_ineligible(self): self.logger.debug("Starting test_disable_oobm_ha_state_ineligible") # Enable ha for host + self.configureAndEnableOobm() self.configureAndEnableHostHa() # Disable OOBM @@ -252,6 +255,7 @@ def test_hostha_configure_default_driver(self): """ self.logger.debug("Starting test_hostha_configure_default_driver") + self.configureAndEnableOobm() cmd = self.getHostHaConfigCmd() response = self.apiclient.configureHAForHost(cmd) self.assertEqual(response.hostid, cmd.hostid) @@ -343,6 +347,7 @@ def test_remove_ha_provider_not_possible(self): # Enable HA + self.configureAndEnableOobm() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaEnableCmd() cmd.hostid = self.host.id @@ -403,6 +408,7 @@ def test_hostha_kvm_host_recovering(self): self.skipIfMSIsUnsupported() self.configureAndStartIpmiServer() self.assertIssueCommandState('ON', 'On') + self.configureAndEnableOobm() self.configureAndEnableHostHa() self.deployVM() @@ -443,6 +449,7 @@ def test_hostha_kvm_host_fencing(self): self.skipIfMSIsUnsupported() self.configureAndStartIpmiServer() self.assertIssueCommandState('ON', 'On') + self.configureAndEnableOobm() self.configureAndEnableHostHa() self.deployVM() diff --git a/test/integration/smoke/test_hostha_simulator.py b/test/integration/smoke/test_hostha_simulator.py index 20d77e82743e..662a17edd20f 100644 --- a/test/integration/smoke/test_hostha_simulator.py +++ b/test/integration/smoke/test_hostha_simulator.py @@ -126,17 +126,17 @@ def getListHostHAResources(self): cmd = listHostHAResources.listHostHAResourcesCmd() cmd.hostid = self.getHost().id return cmd - - + def configureAndEnableHostHa(self, initialize=True): + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) response = self.apiclient.enableHAForHost(self.getHostHaEnableCmd()) self.assertEqual(response.haenable, True) if initialize: self.configureSimulatorHAProviderState(True, True, True, False) - def configureAndDisableHostHa(self, hostId): + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaDisableCmd() cmd.hostid = hostId @@ -241,6 +241,7 @@ def test_hostha_configure_invalid_provider(self): cmd = self.getHostHaConfigCmd() cmd.provider = 'randomDriverThatDoesNotExist' try: + self.setupDummyOOBM() response = self.apiclient.configureHAForHost(cmd) except Exception: pass @@ -254,6 +255,7 @@ def test_hostha_configure_default_driver(self): Tests host-ha configuration with valid data """ cmd = self.getHostHaConfigCmd() + self.setupDummyOOBM() response = self.apiclient.configureHAForHost(cmd) self.assertEqual(response.hostid, cmd.hostid) self.assertEqual(response.haprovider, cmd.provider.lower()) @@ -323,6 +325,7 @@ def test_hostha_enable_feature_valid(self): """ Tests host-ha enable feature with valid options """ + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaEnableCmd() response = self.apiclient.enableHAForHost(cmd) @@ -646,6 +649,7 @@ def test_configure_ha_provider_invalid(self): """ # Enable ha for host + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaEnableCmd() response = self.apiclient.enableHAForHost(cmd) @@ -665,6 +669,7 @@ def test_configure_ha_provider_invalid(self): # Call the configure HA provider API with not supported provider for HA try: + self.setupDummyOOBM() self.apiclient.configureHAForHost(conf_ha_cmd) except Exception: pass @@ -679,6 +684,7 @@ def test_configure_ha_provider_valid(self): """ # Enable ha for host + self.setupDummyOOBM() self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) cmd = self.getHostHaEnableCmd() response = self.apiclient.enableHAForHost(cmd) @@ -698,6 +704,7 @@ def test_configure_ha_provider_valid(self): conf_ha_cmd.hostid = cmd.hostid # Call the configure HA provider API with not supported provider for HA + self.setupDummyOOBM() response = self.apiclient.configureHAForHost(conf_ha_cmd) # Check the response contains the set provider and hostID @@ -714,6 +721,7 @@ def getHaProvider(self, host): def configureHaProvider(self): cmd = self.getHostHaConfigCmd(self.getHaProvider(self.getHost())) + self.setupDummyOOBM() return self.apiclient.configureHAForHost(cmd) diff --git a/tools/marvin/marvin/cloudstackTestCase.py b/tools/marvin/marvin/cloudstackTestCase.py index 297031611e94..175d77042cfd 100644 --- a/tools/marvin/marvin/cloudstackTestCase.py +++ b/tools/marvin/marvin/cloudstackTestCase.py @@ -77,6 +77,27 @@ def tearDownClass(cls): except Exception as e: raise Exception("Warning: Exception during cleanup : %s" % e) + def setupDummyOOBM(self): + try: + from apache.cloudstack.api.command.admin import outOfBandManagement + except ImportError: + self.debug("OOBM module not available, skipping dummy setup.") + return + + conf_cls = outOfBandManagement.configureOutOfBandManagementForHost + oobm_cmd = conf_cls.configureOutOfBandManagementForHostCmd() + oobm_cmd.hostid = self.host.id + oobm_cmd.address = "10.1.1.1" + oobm_cmd.driver = "ipmitool" + oobm_cmd.username = "admin" + oobm_cmd.password = "password" + self.apiclient.configureOutOfBandManagementForHost(oobm_cmd) + + en_cls = outOfBandManagement.enableOutOfBandManagementForHost + enable_oobm_cmd = en_cls.enableOutOfBandManagementForHostCmd() + enable_oobm_cmd.hostid = self.host.id + self.apiclient.enableOutOfBandManagementForHost(enable_oobm_cmd) + def tearDown(self): self.debug("Cleaning up the resources") try: diff --git a/ui/src/config/section/infra/hosts.js b/ui/src/config/section/infra/hosts.js index f81d19b136df..dc189d2e25e7 100644 --- a/ui/src/config/section/infra/hosts.js +++ b/ui/src/config/section/infra/hosts.js @@ -195,6 +195,9 @@ export default { docHelp: 'adminguide/hosts.html#out-of-band-management', dataView: true, show: (record) => { + if (record.hypervisor === 'KVM' && record?.hostha?.haenable === true) { + return false + } return record?.outofbandmanagement?.enabled === true }, args: ['hostid'],