| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| import marvin |
| from marvin.cloudstackTestCase import * |
| from marvin.cloudstackAPI import * |
| from marvin.lib.utils import * |
| from marvin.lib.base import * |
| from marvin.lib.common import * |
| from nose.plugins.attrib import attr |
| |
| from ipmisim.ipmisim import IpmiServerContext, IpmiServer, ThreadedIpmiServer |
| |
| import random |
| import socket |
| import sys |
| import thread |
| import time |
| |
| |
| class TestHostHA(cloudstackTestCase): |
| """ Test host-ha business logic using Simulator |
| """ |
| |
| def setUp(self): |
| self.apiclient = self.testClient.getApiClient() |
| self.hypervisor = self.testClient.getHypervisorInfo() |
| self.dbclient = self.testClient.getDbConnection() |
| self.services = self.testClient.getParsedTestDataConfig() |
| self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__ |
| self.fakeMsId = random.randint(10000, 99999) * random.randint(10, 20) |
| self.host = None |
| |
| # Cleanup any existing configs |
| self.dbclient.execute("delete from ha_config where resource_type='Host'") |
| |
| # use random port for ipmisim |
| s = socket.socket() |
| s.bind(('', 0)) |
| self.serverPort = s.getsockname()[1] |
| s.close() |
| |
| # Get a host to run tests against |
| self.host = self.getHost() |
| |
| self.cleanup = [] |
| |
| |
| def tearDown(self): |
| try: |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.host = None |
| self.dbclient.execute("delete from mshost_peer where peer_runid=%s" % self.getFakeMsRunId()) |
| self.dbclient.execute("delete from mshost where runid=%s" % self.getFakeMsRunId()) |
| self.dbclient.execute("delete from cluster_details where name='resourceHAEnabled'") |
| self.dbclient.execute("delete from data_center_details where name='resourceHAEnabled'") |
| self.dbclient.execute("delete from ha_config where resource_type='Host'") |
| self.dbclient.execute("update host set resource_state='Enabled' where type='Routing' and resource_state='Maintenance'") |
| cleanup_resources(self.apiclient, self.cleanup) |
| except Exception as e: |
| raise Exception("Warning: Exception during cleanup : %s" % e) |
| |
| |
| def getFakeMsId(self): |
| return self.fakeMsId |
| |
| |
| def getFakeMsRunId(self): |
| return self.fakeMsId * 1000 |
| |
| |
| def getHost(self, hostId=None): |
| if self.host and hostId is None: |
| return self.host |
| |
| response = list_hosts( |
| self.apiclient, |
| type='Routing', |
| hypervisor='Simulator', |
| resourcestate='Enabled', |
| id=hostId |
| ) |
| |
| if response and len(response) > 0: |
| random.shuffle(response) |
| self.host = response[0] |
| return self.host |
| raise self.skipTest("No suitable hosts found, skipping host-ha test") |
| |
| |
| def getHostHaConfigCmd(self, provider='simulatorhaprovider'): |
| cmd = configureHAForHost.configureHAForHostCmd() |
| cmd.provider = provider |
| cmd.hostid = self.getHost().id |
| return cmd |
| |
| |
| def getHostHaEnableCmd(self): |
| cmd = enableHAForHost.enableHAForHostCmd() |
| cmd.hostid = self.getHost().id |
| return cmd |
| |
| |
| def getHostHaDisableCmd(self): |
| cmd = disableHAForHost.disableHAForHostCmd() |
| cmd.hostid = self.getHost().id |
| return cmd |
| |
| |
| def getListHostHAResources(self): |
| cmd = listHostHAResources.listHostHAResourcesCmd() |
| cmd.hostid = self.getHost().id |
| return cmd |
| |
| |
| def configureAndEnableHostHa(self, initialize=True): |
| 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.apiclient.configureHAForHost(self.getHostHaConfigCmd()) |
| cmd = self.getHostHaDisableCmd() |
| cmd.hostid = hostId |
| response = self.apiclient.disableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, False) |
| |
| |
| def enableHostHa(self, hostId): |
| cmd = self.getHostHaEnableCmd() |
| cmd.hostid = hostId |
| response = self.apiclient.enableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, True) |
| |
| |
| def configureSimulatorHAProviderState(self, health, activity, recover, fence): |
| cmd = configureSimulatorHAProviderState.configureSimulatorHAProviderStateCmd() |
| cmd.hostid = self.getHost().id |
| cmd.health = health |
| cmd.activity = activity |
| cmd.recover = recover |
| cmd.fence = fence |
| response = self.apiclient.configureSimulatorHAProviderState(cmd) |
| self.assertEqual(response.success, True) |
| |
| |
| def getSimulatorHAStateTransitions(self, hostId): |
| cmd = listSimulatorHAStateTransitions.listSimulatorHAStateTransitionsCmd() |
| cmd.hostid = hostId |
| return self.apiclient.listSimulatorHAStateTransitions(cmd) |
| |
| |
| def checkSyncToState(self, state, interval=5000): |
| def checkForStateSync(expectedState): |
| response = self.getHost(hostId=self.getHost().id).hostha |
| print("checkForStateSync:: response=%s, expected=%s" % (response, expectedState)) |
| return response.hastate == expectedState, None |
| |
| sync_interval = 1 + int(interval) / 1000 |
| res, _ = wait_until(sync_interval, 100, checkForStateSync, state) |
| if not res: |
| self.fail("Failed to get host.hastate synced to expected state:" + state) |
| response = self.getHost(hostId=self.getHost().id).hostha |
| self.assertEqual(response.hastate, state) |
| |
| |
| def getNonConfiguredHaHost(self): |
| response = list_hosts( |
| self.apiclient, |
| type='Routing' |
| ) |
| for host in response: |
| if host.haprovider is None: |
| return host |
| else: |
| return None |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_hostha_enable_feature_without_setting_provider(self): |
| """ |
| Tests Enable HA without setting the provider, Exception is thrown |
| """ |
| host = self.getNonConfiguredHaHost() |
| |
| if host is None: |
| cloudstackTestCase.skipTest(self, "There is no non configured hosts. Skipping test.") |
| |
| cmd = self.getHostHaEnableCmd() |
| cmd.hostid = host.id |
| |
| try: |
| response = self.apiclient.enableHAForHost(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_list_providers(self): |
| """ |
| Tests default ha providers list |
| """ |
| cmd = listHostHAProviders.listHostHAProvidersCmd() |
| |
| cmd.hypervisor = 'Simulator' |
| response = self.apiclient.listHostHAProviders(cmd)[0] |
| self.assertEqual(response.haprovider, 'SimulatorHAProvider') |
| |
| cmd.hypervisor = 'KVM' |
| response = self.apiclient.listHostHAProviders(cmd)[0] |
| self.assertEqual(response.haprovider, 'KVMHAProvider') |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_hostha_configure_invalid_provider(self): |
| """ |
| Tests host-ha configuration with invalid driver |
| """ |
| cmd = self.getHostHaConfigCmd() |
| cmd.provider = 'randomDriverThatDoesNotExist' |
| try: |
| response = self.apiclient.configureHAForHost(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_hostha_configure_default_driver(self): |
| """ |
| Tests host-ha configuration with valid data |
| """ |
| cmd = self.getHostHaConfigCmd() |
| response = self.apiclient.configureHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haprovider, cmd.provider.lower()) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_enable_feature_invalid(self): |
| """ |
| Tests ha feature enable command with invalid options |
| """ |
| cmd = self.getHostHaEnableCmd() |
| cmd.hostid = -1 |
| try: |
| response = self.apiclient.enableHAForHost(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| try: |
| cmd = enableHAForCluster.enableHAForClusterCmd() |
| response = self.apiclient.enableHAForCluster(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| try: |
| cmd = enableHAForZone.enableHAForZoneCmd() |
| response = self.apiclient.enableHAForZone(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_disable_feature_invalid(self): |
| """ |
| Tests ha feature disable command with invalid options |
| """ |
| cmd = self.getHostHaDisableCmd() |
| cmd.hostid = -1 |
| try: |
| response = self.apiclient.disableHAForHost(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| try: |
| cmd = disableHAForCluster.disableHAForClusterCmd() |
| response = self.apiclient.disableHAForCluster(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| try: |
| cmd = disableHAForZone.disableHAForZoneCmd() |
| response = self.apiclient.disableHAForZone(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_hostha_enable_feature_valid(self): |
| """ |
| Tests host-ha enable feature with valid options |
| """ |
| self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) |
| cmd = self.getHostHaEnableCmd() |
| response = self.apiclient.enableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, True) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_hostha_disable_feature_valid(self): |
| """ |
| Tests host-ha disable feature with valid options |
| """ |
| self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) |
| cmd = self.getHostHaDisableCmd() |
| response = self.apiclient.disableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, False) |
| |
| response = self.getHost(hostId=cmd.hostid).hostha |
| self.assertEqual(response.hastate, 'Disabled') |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_configure_enabledisable_across_clusterzones(self): |
| """ |
| Tests ha enable/disable feature at cluster and zone level |
| Zone > Cluster > Host |
| """ |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureAndEnableHostHa() |
| |
| self.checkSyncToState('Available') |
| response = self.getHost(hostId=host.id).hostha |
| self.assertTrue(response.hastate == 'Available') |
| |
| # Disable at host level |
| cmd = disableHAForHost.disableHAForHostCmd() |
| cmd.hostid = host.id |
| response = self.apiclient.disableHAForHost(cmd) |
| |
| # Disable at cluster level |
| cmd = disableHAForCluster.disableHAForClusterCmd() |
| cmd.clusterid = host.clusterid |
| response = self.apiclient.disableHAForCluster(cmd) |
| |
| # Disable at zone level |
| cmd = disableHAForZone.disableHAForZoneCmd() |
| cmd.zoneid = host.zoneid |
| response = self.apiclient.disableHAForZone(cmd) |
| |
| # HA state check |
| response = self.getHost(hostId=host.id).hostha |
| self.assertTrue(response.hastate == 'Disabled') |
| |
| # Check ha-state check and sync |
| self.dbclient.execute("update ha_config set ha_state='Available' where enabled='1' and resource_type='Host'") |
| self.checkSyncToState('Disabled') |
| |
| # Enable at zone level |
| cmd = enableHAForZone.enableHAForZoneCmd() |
| cmd.zoneid = host.zoneid |
| response = self.apiclient.enableHAForZone(cmd) |
| |
| # Enable at cluster level |
| cmd = enableHAForCluster.enableHAForClusterCmd() |
| cmd.clusterid = host.clusterid |
| response = self.apiclient.enableHAForCluster(cmd) |
| |
| # Enable at host level |
| cmd = enableHAForHost.enableHAForHostCmd() |
| cmd.hostid = host.id |
| response = self.apiclient.enableHAForHost(cmd) |
| |
| # Check state sync |
| self.checkSyncToState('Available') |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_multiple_mgmt_server_ownership(self): |
| """ |
| Tests ha resource ownership expiry across multi-mgmt server |
| """ |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureSimulatorHAProviderState(True, True, True, False) |
| self.configureAndEnableHostHa(False) |
| |
| cloudstackVersion = Configurations.listCapabilities(self.apiclient).cloudstackversion |
| |
| currentMsHosts = [] |
| mshosts = self.dbclient.execute( |
| "select msid from mshost where version='%s' and removed is NULL and state='Up'" % (cloudstackVersion)) |
| if len(mshosts) > 0: |
| currentMsHosts = map(lambda row: row[0], mshosts) |
| |
| # Inject fake ms host |
| self.dbclient.execute( |
| "insert into mshost (msid,runid,name,state,version,service_ip,service_port,last_update) values (%s,%s,'ha-marvin-fakebox', 'Down', '%s', '127.0.0.1', '22', NOW())" % ( |
| self.getFakeMsId(), self.getFakeMsRunId(), cloudstackVersion)) |
| |
| # Pass ownership to the fake ms id |
| self.dbclient.execute( |
| "update ha_config set mgmt_server_id=%d where resource_type='Host' and enabled=1 and provider='simulatorhaprovider'" % self.getFakeMsId()) |
| |
| pingInterval = float(list_configurations( |
| self.apiclient, |
| name='ping.interval' |
| )[0].value) |
| |
| pingTimeout = float(list_configurations( |
| self.apiclient, |
| name='ping.timeout' |
| )[0].value) |
| |
| def removeFakeMgmtServer(fakeMsRunId): |
| rows = self.dbclient.execute("select * from mshost_peer where peer_runid=%s" % fakeMsRunId) |
| if len(rows) > 0: |
| self.debug("Mgmt server is now trying to contact the fake mgmt server") |
| self.dbclient.execute("update mshost set removed=now() where runid=%s" % fakeMsRunId) |
| self.dbclient.execute("update mshost_peer set peer_state='Down' where peer_runid=%s" % fakeMsRunId) |
| return True, None |
| return False, None |
| |
| def checkHaOwnershipExpiry(fakeMsId): |
| rows = self.dbclient.execute( |
| "select mgmt_server_id from ha_config where resource_type='Host' and enabled=1 and provider='simulatorhaprovider'") |
| if len(rows) > 0 and rows[0][0] != fakeMsId: |
| self.debug("HA resource ownership expired as node was detected to be gone") |
| return True, None |
| return False, None |
| |
| retry_interval = 1 + (pingInterval * pingTimeout / 10) |
| |
| res, _ = wait_until(retry_interval, 20, removeFakeMgmtServer, self.getFakeMsRunId()) |
| if not res: |
| self.fail("Management server failed to turn down or remove fake mgmt server") |
| |
| res, _ = wait_until(retry_interval, 100, checkHaOwnershipExpiry, self.getFakeMsId()) |
| if not res: |
| self.fail("Management server failed to expire ownership of fenced peer") |
| |
| self.debug("Testing ha background sync should claim new ownership") |
| self.checkSyncToState('Available') |
| |
| result = self.dbclient.execute( |
| "select mgmt_server_id from ha_config where resource_type='Host' and enabled=1 and provider='simulatorhaprovider'") |
| newOwnerId = result[0][0] |
| self.assertTrue(newOwnerId in currentMsHosts) |
| |
| |
| def checkFSMTransition(self, transition, event, haState, prevHaState, hasActiviyCounter, hasRecoveryCounter): |
| print("checkFSMTransition:: transition=%s, event=%s, state=%s" % (transition, event, haState)) |
| self.assertEqual(transition.event, event) |
| self.assertEqual(transition.hastate, haState) |
| self.assertEqual(transition.prevhastate, prevHaState) |
| |
| if hasActiviyCounter is None: |
| pass |
| elif hasActiviyCounter: |
| self.assertTrue(transition.activitycounter > 0) |
| else: |
| self.assertEqual(transition.activitycounter, 0) |
| |
| if hasRecoveryCounter is None: |
| pass |
| elif hasRecoveryCounter: |
| self.assertTrue(transition.recoverycounter > 0) |
| else: |
| self.assertEqual(transition.recoverycounter, 0) |
| |
| |
| def findFSMTransitionToState(self, state, host): |
| transitions = self.getSimulatorHAStateTransitions(host.id) |
| if not transitions: |
| print("findFSMTransition:: no transitions returned") |
| return False, (None, None, None) |
| |
| previousTransition = None |
| stateTransition = None |
| nextTransition = None |
| for transition in transitions: |
| if stateTransition: |
| nextTransition = transition |
| break |
| if transition.hastate == state: |
| stateTransition = transition |
| if not stateTransition: |
| previousTransition = transition |
| |
| print("findFSMTransition:: prev=%s, cur=%s, next=%s, find state=%s" % (previousTransition, stateTransition, nextTransition, state)) |
| if stateTransition: |
| return True, (previousTransition, stateTransition, nextTransition,) |
| return False, (previousTransition, stateTransition, nextTransition,) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_verify_fsm_available(self): |
| """ |
| Tests ha FSM transitions for valid healthy host |
| Simulates health check passing |
| """ |
| |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureSimulatorHAProviderState(True, True, True, False) |
| self.configureAndEnableHostHa(False) |
| |
| res, (_, T, _) = wait_until(3, 20, self.findFSMTransitionToState, 'available', host) |
| if not res: |
| self.fail("FSM did not transition to available state") |
| |
| self.checkFSMTransition(T, 'enabled', 'available', 'disabled', False, False) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_verify_fsm_degraded(self): |
| """ |
| Tests ha FSM transitions leading to degraded state |
| Simulates health check failures with activity checks passing |
| FSM transitions should happen indefinitely between: |
| Available->Suspect<->Checking->Degraded->Available |
| """ |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureSimulatorHAProviderState(False, True, True, False) |
| self.configureAndEnableHostHa(False) |
| |
| # Initial health check failure |
| res, (_, T, _) = wait_until(3, 50, self.findFSMTransitionToState, 'suspect', host) |
| if not res: |
| self.fail("FSM did not transition to suspect state") |
| |
| self.checkFSMTransition(T, 'healthcheckfailed', 'suspect', 'available', False, False) |
| |
| # Check transition to Degraded |
| res, (prevT, T, _) = wait_until(3, 100, self.findFSMTransitionToState, 'degraded', host) |
| if not res: |
| self.fail("FSM did not transition to degraded state") |
| |
| if prevT: |
| self.checkFSMTransition(prevT, 'performactivitycheck', 'checking', 'suspect', True, False) |
| self.checkFSMTransition(T, 'activitycheckfailureunderthresholdratio', 'degraded', 'checking', True, False) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_verify_fsm_recovering(self): |
| """ |
| Tests ha FSM transitions leading to recovering |
| Simulates both health and activity check failures |
| FSM transitions should happen indefinitely between: |
| Available->Suspect<->Checking->Recovering->Recovered<-retry-loop->->Fencing |
| """ |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureSimulatorHAProviderState(False, False, True, False) |
| self.configureAndEnableHostHa(False) |
| |
| # Initial health check failure |
| res, (_, T, _) = wait_until(3, 50, self.findFSMTransitionToState, 'suspect', host) |
| if not res: |
| self.fail("FSM did not transition to suspect state") |
| |
| self.checkFSMTransition(T, 'healthcheckfailed', 'suspect', 'available', False, False) |
| |
| # Check transition to recovering |
| res, (prevT, T, _) = wait_until(3, 100, self.findFSMTransitionToState, 'recovering', host) |
| if not res: |
| self.fail("FSM did not transition to recovering state") |
| |
| if prevT: |
| self.checkFSMTransition(prevT, 'performactivitycheck', 'checking', 'suspect', True, False) |
| self.checkFSMTransition(T, 'activitycheckfailureoverthresholdratio', 'recovering', 'checking', True, False) |
| |
| # Check transition to fencing due to recovery attempts exceeded |
| res, (_, T, _) = wait_until(3, 100, self.findFSMTransitionToState, 'fencing', host) |
| if not res: |
| self.fail("FSM did not transition to fencing state") |
| |
| self.checkFSMTransition(T, 'recoveryoperationthresholdexceeded', 'fencing', 'recovering', None, True) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_ha_verify_fsm_fenced(self): |
| """ |
| Tests ha FSM transitions for failures leading to fenced state |
| FSM transitions should happen indefinitely between: |
| Available->Suspect<->Checking->Recovering<-fail recovery->->Fencing->Fenced |
| """ |
| host = self.getHost() |
| self.configureAndDisableHostHa(host.id) |
| self.configureSimulatorHAProviderState(False, False, False, True) |
| self.configureAndEnableHostHa(False) |
| |
| # Check for transition to fenced |
| res, (prevT, T, _) = wait_until(3, 100, self.findFSMTransitionToState, 'fenced', host) |
| if not res: |
| self.fail("FSM did not transition to fenced state") |
| |
| self.checkFSMTransition(prevT, 'recoveryoperationthresholdexceeded', 'fencing', 'recovering', False, True) |
| self.checkFSMTransition(T, 'fenced', 'fenced', 'fencing', False, False) |
| |
| # Simulate manual recovery of host and cancel maintenance mode |
| self.configureSimulatorHAProviderState(True, True, True, False) |
| cancelCmd = cancelHostMaintenance.cancelHostMaintenanceCmd() |
| cancelCmd.id = host.id |
| self.apiclient.cancelHostMaintenance(cancelCmd) |
| |
| # Check for transition to available after manual recovery |
| res, (prevT, T, _) = wait_until(3, 50, self.findFSMTransitionToState, 'available', host) |
| if not res: |
| self.fail("FSM did not transition to available state") |
| |
| self.checkFSMTransition(T, 'eligible', 'available', 'ineligible', False, False) |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_configure_ha_provider_invalid(self): |
| """ |
| Tests configure HA Provider with invalid provider options |
| """ |
| |
| # Enable ha for host |
| self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) |
| cmd = self.getHostHaEnableCmd() |
| response = self.apiclient.enableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, True) |
| |
| host = self.getHost(response.hostid) |
| |
| # Setup wrong configuration for the host |
| conf_ha_cmd = configureHAForHost.configureHAForHostCmd() |
| if host.hypervisor.lower() in "simulator": |
| conf_ha_cmd.provider = "kvmhaprovider" |
| if host.hypervisor.lower() in "kvm": |
| conf_ha_cmd.provider = "simulatorhaprovider" |
| |
| conf_ha_cmd.hostid = cmd.hostid |
| |
| # Call the configure HA provider API with not supported provider for HA |
| try: |
| self.apiclient.configureHAForHost(conf_ha_cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |
| |
| |
| @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") |
| def test_configure_ha_provider_valid(self): |
| """ |
| Tests configure HA Provider with valid provider options |
| """ |
| |
| # Enable ha for host |
| self.apiclient.configureHAForHost(self.getHostHaConfigCmd()) |
| cmd = self.getHostHaEnableCmd() |
| response = self.apiclient.enableHAForHost(cmd) |
| self.assertEqual(response.hostid, cmd.hostid) |
| self.assertEqual(response.haenable, True) |
| |
| host = self.getHost(response.hostid) |
| |
| |
| # Setup wrong configuration for the host |
| conf_ha_cmd = configureHAForHost.configureHAForHostCmd() |
| if host.hypervisor.lower() in "kvm": |
| conf_ha_cmd.provider = "kvmhaprovider" |
| if host.hypervisor.lower() in "simulator": |
| conf_ha_cmd.provider = "simulatorhaprovider" |
| |
| conf_ha_cmd.hostid = cmd.hostid |
| |
| # Call the configure HA provider API with not supported provider for HA |
| response = self.apiclient.configureHAForHost(conf_ha_cmd) |
| |
| # Check the response contains the set provider and hostID |
| self.assertEqual(response.haprovider, conf_ha_cmd.provider) |
| self.assertEqual(response.hostid, conf_ha_cmd.hostid) |
| |
| |
| def getHaProvider(self, host): |
| cmd = listHostHAProviders.listHostHAProvidersCmd() |
| cmd.hypervisor = host.hypervisor |
| response = self.apiclient.listHostHAProviders(cmd) |
| return response[0].haprovider |
| |
| |
| def configureHaProvider(self): |
| cmd = self.getHostHaConfigCmd(self.getHaProvider(self.getHost())) |
| return self.apiclient.configureHAForHost(cmd) |
| |
| |
| @attr(tags=["advanced", |
| "advancedns", |
| "smoke", |
| "basic", |
| "sg"], |
| required_hardware="false") |
| def test_list_ha_for_host(self): |
| """ |
| Test that verifies the listHAForHost API |
| """ |
| self.configureHaProvider() |
| db_count = self.dbclient.execute("SELECT count(*) FROM cloud.ha_config") |
| |
| cmd = self.getListHostHAResources() |
| del cmd.hostid |
| response = self.apiclient.listHostHAResources(cmd) |
| |
| self.assertEqual(db_count[0][0], len(response)) |
| |
| |
| @attr(tags=["advanced", |
| "advancedns", |
| "smoke", |
| "basic", |
| "sg"], |
| required_hardware="false") |
| def test_list_ha_for_host_valid(self): |
| """ |
| Valid test for listing a specific host HA resources |
| """ |
| |
| self.configureHaProvider() |
| cmd = self.getListHostHAResources() |
| response = self.apiclient.listHostHAResources(cmd) |
| self.assertEqual(response[0].hostid, cmd.hostid) |
| |
| |
| @attr(tags=["advanced", |
| "advancedns", |
| "smoke", |
| "basic", |
| "sg"], |
| required_hardware="false") |
| def test_list_ha_for_host_invalid(self): |
| """ |
| Test that listHostHAResources is returning exception when called with invalid data |
| """ |
| |
| self.configureHaProvider() |
| cmd = self.getListHostHAResources() |
| cmd.hostid = "someinvalidvalue" |
| |
| try: |
| response = self.apiclient.listHostHAResources(cmd) |
| except Exception: |
| pass |
| else: |
| self.fail("Expected an exception to be thrown, failing") |