diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py index 9ccb7411..a79d0986 100755 --- a/api-daemon/pvcapid/flaskapi.py +++ b/api-daemon/pvcapid/flaskapi.py @@ -2645,7 +2645,7 @@ class API_VM_State(Resource): - in: query name: force type: boolean - description: Whether to force stop instead of shutdown VM during disable + description: For "disable", force stop instead of shutdown and/or force mirror state; for "start" or "stop", force mirror state. - in: query name: wait type: boolean @@ -2667,15 +2667,15 @@ class API_VM_State(Resource): wait = bool(strtobool(reqargs.get("wait", "false"))) if state == "start": - return api_helper.vm_start(vm) + return api_helper.vm_start(vm, force=force) if state == "shutdown": return api_helper.vm_shutdown(vm, wait) if state == "stop": - return api_helper.vm_stop(vm) + return api_helper.vm_stop(vm, force=force) if state == "restart": return api_helper.vm_restart(vm, wait) if state == "disable": - return api_helper.vm_disable(vm, force) + return api_helper.vm_disable(vm, force=force) abort(400) diff --git a/api-daemon/pvcapid/helper.py b/api-daemon/pvcapid/helper.py index 0f992b7b..5c877fc8 100755 --- a/api-daemon/pvcapid/helper.py +++ b/api-daemon/pvcapid/helper.py @@ -1153,11 +1153,11 @@ def vm_remove(zkhandler, name): @ZKConnection(config) -def vm_start(zkhandler, name): +def vm_start(zkhandler, name, force=False): """ Start a VM in the PVC cluster. """ - retflag, retdata = pvc_vm.start_vm(zkhandler, name) + retflag, retdata = pvc_vm.start_vm(zkhandler, name, force=force) if retflag: retcode = 200 @@ -1201,11 +1201,11 @@ def vm_shutdown(zkhandler, name, wait): @ZKConnection(config) -def vm_stop(zkhandler, name): +def vm_stop(zkhandler, name, force=False): """ Forcibly stop a VM in the PVC cluster. """ - retflag, retdata = pvc_vm.stop_vm(zkhandler, name) + retflag, retdata = pvc_vm.stop_vm(zkhandler, name, force=force) if retflag: retcode = 200 @@ -1219,7 +1219,7 @@ def vm_stop(zkhandler, name): @ZKConnection(config) def vm_disable(zkhandler, name, force=False): """ - Disable (shutdown or force stop if required)a VM in the PVC cluster. + Disable (shutdown or force stop if required) a VM in the PVC cluster. """ retflag, retdata = pvc_vm.disable_vm(zkhandler, name, force=force) diff --git a/client-cli/pvc/cli/cli.py b/client-cli/pvc/cli/cli.py index 068c061e..bfb6b845 100644 --- a/client-cli/pvc/cli/cli.py +++ b/client-cli/pvc/cli/cli.py @@ -1517,12 +1517,21 @@ def cli_vm_remove(domain): @click.command(name="start", short_help="Start up a defined virtual machine.") @connection_req @click.argument("domain") -def cli_vm_start(domain): +@click.option( + "--force", + "force_flag", + is_flag=True, + default=False, + help="Force a snapshot mirror state change.", +) +def cli_vm_start(domain, force_flag): """ Start virtual machine DOMAIN on its configured node. DOMAIN may be a UUID or name. + + If the VM is a snapshot mirror, "--force" allows a manual state change to the mirror. """ - retcode, retmsg = pvc.lib.vm.vm_state(CLI_CONFIG, domain, "start") + retcode, retmsg = pvc.lib.vm.vm_state(CLI_CONFIG, domain, "start", force=force_flag) finish(retcode, retmsg) @@ -1582,13 +1591,22 @@ def cli_vm_shutdown(domain, wait): @click.command(name="stop", short_help="Forcibly halt a running virtual machine.") @connection_req @click.argument("domain") +@click.option( + "--force", + "force_flag", + is_flag=True, + default=False, + help="Force a snapshot mirror state change.", +) @confirm_opt("Forcibly stop virtual machine {domain}") -def cli_vm_stop(domain): +def cli_vm_stop(domain, force_flag): """ Forcibly halt (destroy) running virtual machine DOMAIN. DOMAIN may be a UUID or name. + + If the VM is a snapshot mirror, "--force" allows a manual state change to the mirror. """ - retcode, retmsg = pvc.lib.vm.vm_state(CLI_CONFIG, domain, "stop") + retcode, retmsg = pvc.lib.vm.vm_state(CLI_CONFIG, domain, "stop", force=force_flag) finish(retcode, retmsg) @@ -1603,14 +1621,14 @@ def cli_vm_stop(domain): "force_flag", is_flag=True, default=False, - help="Forcibly stop the VM instead of waiting for shutdown.", + help="Forcibly stop VM without shutdown and/or force a snapshot mirror state change.", ) @confirm_opt("Shut down and disable virtual machine {domain}") def cli_vm_disable(domain, force_flag): """ Shut down virtual machine DOMAIN and mark it as disabled. DOMAIN may be a UUID or name. - Disabled VMs will not be counted towards a degraded cluster health status, unlike stopped VMs. Use this option for a VM that will remain off for an extended period. + If "--force" is specified, and the VM is running, it will be forcibly stopped instead of waiting for a graceful ACPI shutdown. If the VM is a snapshot mirror, "--force" allows a manual state change to the mirror. """ retcode, retmsg = pvc.lib.vm.vm_state( diff --git a/client-cli/pvc/lib/vm.py b/client-cli/pvc/lib/vm.py index 2180c0e6..1ebc1e59 100644 --- a/client-cli/pvc/lib/vm.py +++ b/client-cli/pvc/lib/vm.py @@ -383,8 +383,8 @@ def vm_state(config, vm, target_state, force=False, wait=False): """ params = { "state": target_state, - "force": str(force).lower(), - "wait": str(wait).lower(), + "force": force, + "wait": wait, } response = call_api(config, "post", "/vm/{vm}/state".format(vm=vm), params=params) diff --git a/daemon-common/vm.py b/daemon-common/vm.py index f4f2c80e..6aefeef3 100644 --- a/daemon-common/vm.py +++ b/daemon-common/vm.py @@ -696,12 +696,19 @@ def remove_vm(zkhandler, domain): return True, 'Removed VM "{}" and its disks from the cluster.'.format(domain) -def start_vm(zkhandler, domain): +def start_vm(zkhandler, domain, force=False): # Validate that VM exists in cluster dom_uuid = getDomainUUID(zkhandler, domain) if not dom_uuid: return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) + current_vm_state = zkhandler.read(("domain.state", dom_uuid)) + if current_vm_state in ["mirror"] and not force: + return ( + False, + 'ERROR: VM "{}" is a snapshot mirror and start not forced!'.format(domain), + ) + # Set the VM to start change_state(zkhandler, dom_uuid, "start") @@ -756,12 +763,18 @@ def shutdown_vm(zkhandler, domain, wait=False): return True, retmsg -def stop_vm(zkhandler, domain): +def stop_vm(zkhandler, domain, force=False): # Validate that VM exists in cluster dom_uuid = getDomainUUID(zkhandler, domain) if not dom_uuid: return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) + current_vm_state = zkhandler.read(("domain.state", dom_uuid)) + if current_vm_state in ["mirror"] and not force: + return False, 'ERROR: VM "{}" is a snapshot mirror and stop not forced!'.format( + domain + ) + # Set the VM to stop change_state(zkhandler, dom_uuid, "stop") @@ -775,8 +788,15 @@ def disable_vm(zkhandler, domain, force=False): return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) # Get state and perform a shutdown/stop if VM is online - current_state = zkhandler.read(("domain.state", dom_uuid)) - if current_state in ["start"]: + current_vm_state = zkhandler.read(("domain.state", dom_uuid)) + if current_vm_state in ["mirror"] and not force: + return ( + False, + 'ERROR: VM "{}" is a snapshot mirror and disable not forced!'.format( + domain + ), + ) + elif current_vm_state in ["start"]: if force: change_state(zkhandler, dom_uuid, "stop") # Wait for the command to be registered by the node