@@ -295,6 +295,8 @@ def __init__(
295295 # self._did_start_work = False
296296 # Now, VM was rebooted when it was running at start, then stopped and powered on:
297297 # .power_state in [] and any(_did_nice_shutdown_work, _did_force_shutdown_work) and _was_start_tried
298+ self ._was_reboot_tried = False
299+ self ._was_reset_tried = False
298300
299301 @property
300302 def nic_list (self ):
@@ -853,40 +855,87 @@ def get_vm_and_boot_devices(cls, ansible_dict, rest_client):
853855 ]
854856 return vm , boot_devices_uuid , boot_devices_ansible
855857
856- def update_vm_power_state (self , module , rest_client , desired_power_action ):
858+ def update_vm_power_state (
859+ self , module , rest_client , desired_power_action , ignore_repeated_request : bool
860+ ):
857861 """Sets the power state to what is stored in self.power_state"""
862+
858863 # desired_power_action must be present in FROM_ANSIBLE_TO_HYPERCORE_POWER_ACTION's keys
864+ def assert_or_ignore_repeated_request (msg ):
865+ if ignore_repeated_request :
866+ # Do not start/stop/shutdown VM twice.
867+ # Normally we want to assert if a second start/stop/shutdown is tried.
868+ # But as very last module change it makes sense to push VM into requested power_state,
869+ # without knowing what did module already do with VM power_state.
870+ # So in this special case, allow a second call, and just silently ignore
871+ # it if VM power_state was already set to desired state.
872+ return
873+ else :
874+ raise AssertionError (msg )
875+
859876 if not self ._power_state :
860877 raise errors .ScaleComputingError ("No information about VM's power state." )
861878
862879 # keep a record what was done
863880 if desired_power_action == "start" :
864881 if self ._was_start_tried :
865- raise AssertionError ("VM _was_start_tried already set" )
882+ return assert_or_ignore_repeated_request (
883+ "VM _was_start_tried already set"
884+ )
866885 self ._was_start_tried = True
867886 if desired_power_action == "shutdown" :
868887 if self ._was_nice_shutdown_tried :
869- raise AssertionError ("VM _was_nice_shutdown_tried already set" )
888+ return assert_or_ignore_repeated_request (
889+ "VM _was_nice_shutdown_tried already set"
890+ )
870891 self ._was_nice_shutdown_tried = True
871892 if desired_power_action == "stop" :
872893 if self ._was_force_shutdown_tried :
873- raise AssertionError ("VM _was_force_shutdown_tried already set" )
894+ return assert_or_ignore_repeated_request (
895+ "VM _was_force_shutdown_tried already set"
896+ )
874897 self ._was_force_shutdown_tried = True
875- # reboot, reset are not included
876-
877- task_tag = rest_client .create_record (
878- "/rest/v1/VirDomain/action" ,
879- [
880- dict (
881- virDomainUUID = self .uuid ,
882- actionType = FROM_ANSIBLE_TO_HYPERCORE_POWER_ACTION [
883- desired_power_action
884- ],
885- cause = "INTERNAL" ,
898+ if desired_power_action == "reboot" :
899+ if self ._was_reboot_tried :
900+ return assert_or_ignore_repeated_request (
901+ "VM _was_reboot_tried already set"
886902 )
887- ],
888- module .check_mode ,
889- )
903+ self ._was_reboot_tried = True
904+ if desired_power_action == "reset" :
905+ if self ._was_reset_tried :
906+ return assert_or_ignore_repeated_request (
907+ "VM _was_reset_tried already set"
908+ )
909+ self ._was_reset_tried = True
910+
911+ try :
912+ task_tag = rest_client .create_record (
913+ "/rest/v1/VirDomain/action" ,
914+ [
915+ dict (
916+ virDomainUUID = self .uuid ,
917+ actionType = FROM_ANSIBLE_TO_HYPERCORE_POWER_ACTION [
918+ desired_power_action
919+ ],
920+ cause = "INTERNAL" ,
921+ )
922+ ],
923+ module .check_mode ,
924+ )
925+ except errors .UnexpectedAPIResponse as ex :
926+ if desired_power_action != "reset" :
927+ raise
928+ # We are allowed to send reset only if VM is in
929+ # RUNNING or SHUTDOWN (as in middle of shutting down, but not yet fully shutdown).
930+ # If VM is already shutoff, the request fails.
931+ # Ignore this special case.
932+ # The whole RESET is not even exposed on HyperCore UI,
933+ # maybe we should remove it from ansible.
934+ if ex .response_status != 500 :
935+ # the '500 b'{"error":"An internal error occurred"}'' is the one to ignore
936+ raise
937+ module .warn ("Ignoring failed VM RESET" )
938+ return
890939 TaskTag .wait_task (rest_client , task_tag )
891940
892941 @classmethod
@@ -936,7 +985,7 @@ def vm_shutdown_forced(self, module, rest_client, reboot=False):
936985 if vm_fresh_data ["state" ] in ["SHUTOFF" , "SHUTDOWN" ]:
937986 return True
938987 if module .params ["force_reboot" ] and self ._was_nice_shutdown_tried :
939- self .update_vm_power_state (module , rest_client , "stop" )
988+ self .update_vm_power_state (module , rest_client , "stop" , False )
940989 # force shutdown should always work. If not, we need to pool for state change.
941990 # Maybe we need to pool for state change anyway -
942991 # TaskTag might be finished before VM is really off.
@@ -959,7 +1008,7 @@ def wait_shutdown(self, module, rest_client):
9591008 # and module.params["shutdown_timeout"] # default is 300 anyway
9601009 and not self ._was_nice_shutdown_tried
9611010 ):
962- self .update_vm_power_state (module , rest_client , "shutdown" )
1011+ self .update_vm_power_state (module , rest_client , "shutdown" , False )
9631012 shutdown_timeout = module .params ["shutdown_timeout" ]
9641013 start = time ()
9651014 while 1 :
@@ -983,14 +1032,14 @@ def vm_power_up(self, module, rest_client):
9831032 # - VM was initially stopped and
9841033 # - module param power_state is omitted or contains "stop".
9851034 if self .was_vm_shutdown () and self ._initially_running :
986- self .update_vm_power_state (module , rest_client , "start" )
1035+ self .update_vm_power_state (module , rest_client , "start" , False )
9871036 return
9881037 # Also start VM if module power_state requires a power on.
9891038 # Field _power_action is set only if VM instance was created with from_ansible();
9901039 # it is None if VM instance was created with from_hypercore().
9911040 requested_power_action = module .params .get ("power_state" )
9921041 if requested_power_action == "start" :
993- self .update_vm_power_state (module , rest_client , "start" )
1042+ self .update_vm_power_state (module , rest_client , "start" , False )
9941043
9951044 def was_vm_shutdown (self ) -> bool :
9961045 """
@@ -1236,21 +1285,26 @@ def set_vm_params(cls, module, rest_client, vm, param_subset: List[str]):
12361285 endpoint = "{0}/{1}" .format ("/rest/v1/VirDomain" , vm .uuid )
12371286 task_tag = rest_client .update_record (endpoint , payload , module .check_mode )
12381287 TaskTag .wait_task (rest_client , task_tag )
1288+
1289+ # shutdown VM if it needs to be rebooted to apply NIC/disk changes
12391290 if ManageVMParams ._needs_reboot (
12401291 module , changed_parameters
12411292 ) and vm ._power_action not in ["stop" , "stopped" , "shutdown" ]:
12421293 vm .do_shutdown_steps (module , rest_client )
1294+
12431295 return (
12441296 True ,
12451297 dict (
12461298 before = ManageVMParams ._build_before_diff (vm , module ),
12471299 after = ManageVMParams ._build_after_diff (module , rest_client ),
12481300 ),
1301+ changed_parameters ,
12491302 )
12501303 else :
12511304 return (
12521305 False ,
12531306 dict (before = None , after = None ),
1307+ changed_parameters ,
12541308 )
12551309
12561310 @classmethod
0 commit comments