2727from distutils .util import strtobool
2828from marvin .sshClient import SshClient
2929
30+ import time
31+
3032_multiprocess_shared_ = False
3133MIN_VMS_FOR_TEST = 3
3234
@@ -43,23 +45,40 @@ def get_ssh_client(self, ip, username, password, retries=10):
4345
4446 return ssh_client
4547
46- def wait_until_host_is_in_state (self , hostid , resourcestate , interval = 3 , retries = 20 ):
48+ def wait_until_host_is_in_state (self , hostid , resourcestate , interval = 3 , retries = 20 , abort_states = None ):
49+ """
50+ Wait until the host reaches resourcestate. If abort_states is given and the host lands
51+ in one of those states instead, skip the test immediately rather than waiting out the
52+ full timeout: ErrorInPrepareForMaintenance/ErrorInMaintenance while waiting for
53+ "Maintenance" usually means the hypervisor refused to migrate a VM off this host (most
54+ commonly because the hosts in the cluster have incompatible CPUs), which is an
55+ environment limitation, not something this test can exercise meaningfully.
56+ """
4757 def check_resource_state ():
4858 response = Host .list (
4959 self .apiclient ,
5060 id = hostid
5161 )
5262 if isinstance (response , list ):
53- if response [0 ].resourcestate == resourcestate :
63+ current_state = response [0 ].resourcestate
64+ if current_state == resourcestate :
5465 self .logger .debug ('Host with id %s is in resource state = %s' % (hostid , resourcestate ))
5566 return True , None
56- else :
57- self .logger .debug ("Waiting for host " + hostid +
58- " to reach state " + resourcestate +
59- ", with current state " + response [0 ].resourcestate )
67+ if abort_states and current_state in abort_states :
68+ self .logger .debug ('Host with id %s entered abort state = %s' % (hostid , current_state ))
69+ return True , current_state
70+ self .logger .debug ("Waiting for host " + hostid +
71+ " to reach state " + resourcestate +
72+ ", with current state " + current_state )
6073 return False , None
6174
62- done , _ = wait_until (interval , retries , check_resource_state )
75+ done , abort_state = wait_until (interval , retries , check_resource_state )
76+ if abort_state :
77+ raise unittest .SkipTest (
78+ "Host %s entered resource state %s while waiting to reach %s -- the hypervisor "
79+ "rejected the VM migration needed for this test (commonly caused by incompatible "
80+ "CPUs between hosts in the cluster). Skipping this migration-dependent test."
81+ % (hostid , abort_state , resourcestate ))
6382 if not done :
6483 raise Exception ("Failed to wait for host %s to be on resource state %s" % (hostid , resourcestate ))
6584 return True
@@ -82,6 +101,20 @@ def cancel_host_maintenance(self, hostid):
82101 return res
83102
84103 def revert_host_state_on_failure (self , hostId ):
104+ # updateHost(allocationstate=Enable) only has a transition defined from the
105+ # Disabled resource state. If a migration failed while putting the host into
106+ # maintenance, the host is left in one of the maintenance-related error states
107+ # (PrepareForMaintenance, ErrorInPrepareForMaintenance, Maintenance,
108+ # ErrorInMaintenance) instead, and only cancelHostMaintenance (AdminCancelMaintenance)
109+ # can move it back to Enabled from there. Recover via whichever API actually applies.
110+ host = Host .list (self .apiclient , id = hostId )[0 ]
111+ if host .resourcestate == "Enabled" :
112+ return
113+ if host .resourcestate in ("PrepareForMaintenance" , "ErrorInPrepareForMaintenance" ,
114+ "Maintenance" , "ErrorInMaintenance" ):
115+ self .cancel_host_maintenance (hostId )
116+ self .wait_until_host_is_in_state (hostId , "Enabled" , 5 , 60 )
117+ return
85118 cmd = updateHost .updateHostCmd ()
86119 cmd .id = hostId
87120 cmd .allocationstate = "Enable"
@@ -245,7 +278,9 @@ def hostPrepareAndCancelMaintenance(self, target_host_id, other_host_id):
245278 self .prepare_host_for_maintenance (target_host_id )
246279 migrations_finished = wait_until (5 , 200 , self .migrationsFinished , target_host_id )
247280
248- self .wait_until_host_is_in_state (target_host_id , "Maintenance" , 5 , 200 )
281+ self .wait_until_host_is_in_state (
282+ target_host_id , "Maintenance" , 5 , 200 ,
283+ abort_states = ("ErrorInPrepareForMaintenance" , "ErrorInMaintenance" ))
249284
250285 vm_count_after_maintenance = self .noOfVMsOnHost (target_host_id )
251286
@@ -299,11 +334,15 @@ def test_01_cancel_host_maintenace_with_no_migration_jobs(self):
299334 else :
300335 raise unittest .SkipTest ("VMs are still migrating so reverse migration /maintenace skipped" )
301336
337+ except unittest .SkipTest :
338+ self .revert_host_state_on_failure (listHost [0 ].id )
339+ self .revert_host_state_on_failure (listHost [1 ].id )
340+ raise
302341 except Exception as e :
303342 self .revert_host_state_on_failure (listHost [0 ].id )
304343 self .revert_host_state_on_failure (listHost [1 ].id )
305344 self .logger .debug ("Exception {}" .format (e ))
306- self .fail ("Host maintenance test failed {}" .format (e [ 0 ] ))
345+ self .fail ("Host maintenance test failed {}" .format (str ( e ) ))
307346
308347
309348 @attr (
@@ -350,6 +389,8 @@ def test_02_cancel_host_maintenace_with_migration_jobs(self):
350389 self .logger .debug ("Creating vms = {}" .format (no_vm_req ))
351390 self .vmlist = self .createVMs (listHost [0 ].id , no_vm_req )
352391
392+ time .sleep (10 )
393+
353394 try :
354395 migrations_finished = self .hostPrepareAndCancelMaintenance (listHost [0 ].id , listHost [1 ].id )
355396
@@ -358,11 +399,15 @@ def test_02_cancel_host_maintenace_with_migration_jobs(self):
358399 else :
359400 raise unittest .SkipTest ("VMs are still migrating so reverse migration /maintenace skipped" )
360401
402+ except unittest .SkipTest :
403+ self .revert_host_state_on_failure (listHost [0 ].id )
404+ self .revert_host_state_on_failure (listHost [1 ].id )
405+ raise
361406 except Exception as e :
362407 self .revert_host_state_on_failure (listHost [0 ].id )
363408 self .revert_host_state_on_failure (listHost [1 ].id )
364409 self .logger .debug ("Exception {}" .format (e ))
365- self .fail ("Host maintenance test failed {}" .format (e [ 0 ] ))
410+ self .fail ("Host maintenance test failed {}" .format (str ( e ) ))
366411
367412 @attr (
368413 tags = [
@@ -414,6 +459,8 @@ def test_03_cancel_host_maintenace_with_migration_jobs_failure(self):
414459 self .logger .debug ("Creating vms = {}" .format (no_vm_req ))
415460 self .vmlist = self .createVMs (listHost [0 ].id , no_vm_req , "taggedsmall" )
416461
462+ time .sleep (10 )
463+
417464 # Attempt putting host in maintenance and check if ErrorInMaintenance state is reached
418465 self .prepare_host_for_maintenance (target_host_id )
419466 error_in_maintenance_reached = self .wait_until_host_is_in_state (target_host_id , "ErrorInMaintenance" , 5 , 300 )
@@ -431,7 +478,7 @@ def test_03_cancel_host_maintenace_with_migration_jobs_failure(self):
431478 self .revert_host_state_on_failure (listHost [1 ].id )
432479 Host .update (self .apiclient , id = target_host_id , hosttags = "" )
433480 self .logger .debug ("Exception {}" .format (e ))
434- self .fail ("Host maintenance test failed {}" .format (e [ 0 ] ))
481+ self .fail ("Host maintenance test failed {}" .format (str ( e ) ))
435482
436483
437484class TestHostMaintenanceAgents (TestHostMaintenanceBase ):
0 commit comments