Compare commits

...

4 Commits

Author SHA1 Message Date
Joshua Boniface 1cf8706a52 Up timeout when setting VM state
Ensures the API won't time out immediately especially during a
wait-flagged or disable action.
2021-11-06 04:15:10 -04:00
Joshua Boniface dd8f07526f Use positive check rather than negative
Ensure the VM is start before doing shutdown/stop, rather than being
stopped. Prevents overwrite of existing disable state and other
weirdness.
2021-11-06 04:08:33 -04:00
Joshua Boniface 5a5e5da663 Add disable forcing to CLI
References #148
2021-11-06 04:02:50 -04:00
Joshua Boniface 739b60b91e Perform automatic shutdown/stop on VM disable
Instead of requiring the VM to already be stopped, instead allow disable
state changes to perform a shutdown first. Also add a force option which
will do a hard stop instead of a shutdown.

References #148
2021-11-06 03:57:24 -04:00
7 changed files with 59 additions and 22 deletions

View File

@ -1868,6 +1868,7 @@ class API_VM_State(Resource):
"helptext": "A valid state must be specified", "helptext": "A valid state must be specified",
"required": True, "required": True,
}, },
{"name": "force"},
{"name": "wait"}, {"name": "wait"},
] ]
) )
@ -1890,6 +1891,10 @@ class API_VM_State(Resource):
- stop - stop
- restart - restart
- disable - disable
- in: query
name: force
type: boolean
description: Whether to force stop instead of shutdown VM during disable
- in: query - in: query
name: wait name: wait
type: boolean type: boolean
@ -1907,6 +1912,7 @@ class API_VM_State(Resource):
id: Message id: Message
""" """
state = reqargs.get("state", None) state = reqargs.get("state", None)
force = bool(strtobool(reqargs.get("force", "false")))
wait = bool(strtobool(reqargs.get("wait", "false"))) wait = bool(strtobool(reqargs.get("wait", "false")))
if state == "start": if state == "start":
@ -1918,7 +1924,7 @@ class API_VM_State(Resource):
if state == "restart": if state == "restart":
return api_helper.vm_restart(vm, wait) return api_helper.vm_restart(vm, wait)
if state == "disable": if state == "disable":
return api_helper.vm_disable(vm) return api_helper.vm_disable(vm, force)
abort(400) abort(400)

View File

@ -719,11 +719,11 @@ def vm_start(zkhandler, name):
@ZKConnection(config) @ZKConnection(config)
def vm_restart(zkhandler, name, wait): def vm_restart(zkhandler, name, wait=False):
""" """
Restart a VM in the PVC cluster. Restart a VM in the PVC cluster.
""" """
retflag, retdata = pvc_vm.restart_vm(zkhandler, name, wait) retflag, retdata = pvc_vm.restart_vm(zkhandler, name, wait=wait)
if retflag: if retflag:
retcode = 200 retcode = 200
@ -767,11 +767,11 @@ def vm_stop(zkhandler, name):
@ZKConnection(config) @ZKConnection(config)
def vm_disable(zkhandler, name): def vm_disable(zkhandler, name, force=False):
""" """
Disable a (stopped) 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) retflag, retdata = pvc_vm.disable_vm(zkhandler, name, force=force)
if retflag: if retflag:
retcode = 200 retcode = 200

View File

@ -116,16 +116,20 @@ class ErrorResponse(requests.Response):
def call_api( def call_api(
config, operation, request_uri, headers={}, params=None, data=None, files=None config,
operation,
request_uri,
headers={},
params=None,
data=None,
files=None,
timeout=3,
): ):
# Craft the URI # Craft the URI
uri = "{}://{}{}{}".format( uri = "{}://{}{}{}".format(
config["api_scheme"], config["api_host"], config["api_prefix"], request_uri config["api_scheme"], config["api_host"], config["api_prefix"], request_uri
) )
# Default timeout is 3 seconds
timeout = 3
# Craft the authentication header if required # Craft the authentication header if required
if config["api_key"]: if config["api_key"]:
headers["X-Api-Key"] = config["api_key"] headers["X-Api-Key"] = config["api_key"]

View File

@ -369,7 +369,7 @@ def vm_remove(config, vm, delete_disks=False):
return retstatus, response.json().get("message", "") return retstatus, response.json().get("message", "")
def vm_state(config, vm, target_state, wait=False): def vm_state(config, vm, target_state, force=False, wait=False):
""" """
Modify the current state of VM Modify the current state of VM
@ -377,8 +377,14 @@ def vm_state(config, vm, target_state, wait=False):
API arguments: state={state}, wait={wait} API arguments: state={state}, wait={wait}
API schema: {"message":"{data}"} API schema: {"message":"{data}"}
""" """
params = {"state": target_state, "wait": str(wait).lower()} params = {
response = call_api(config, "post", "/vm/{vm}/state".format(vm=vm), params=params) "state": target_state,
"force": str(force).lower(),
"wait": str(wait).lower(),
}
response = call_api(
config, "post", "/vm/{vm}/state".format(vm=vm), params=params, timeout=120
)
if response.status_code == 200: if response.status_code == 200:
retstatus = True retstatus = True

View File

@ -1308,15 +1308,22 @@ def vm_stop(domain, confirm_flag):
############################################################################### ###############################################################################
@click.command(name="disable", short_help="Mark a virtual machine as disabled.") @click.command(name="disable", short_help="Mark a virtual machine as disabled.")
@click.argument("domain") @click.argument("domain")
@click.option(
"--force",
"force",
is_flag=True,
default=False,
help="Forcibly stop the VM instead of waiting for shutdown.",
)
@cluster_req @cluster_req
def vm_disable(domain): def vm_disable(domain, force):
""" """
Prevent stopped virtual machine DOMAIN from being counted towards cluster health status. DOMAIN may be a UUID or name. Shut down virtual machine DOMAIN and mark it as disabled. DOMAIN may be a UUID or name.
Use this option for VM that are stopped intentionally or long-term and which should not impact cluster health if stopped. A VM can be started directly from disable state. 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.
""" """
retcode, retmsg = pvc_vm.vm_state(config, domain, "disable") retcode, retmsg = pvc_vm.vm_state(config, domain, "disable", force=force)
cleanup(retcode, retmsg) cleanup(retcode, retmsg)

View File

@ -837,21 +837,29 @@ def stop_vm(zkhandler, domain):
return True, 'Forcibly stopping VM "{}".'.format(domain) return True, 'Forcibly stopping VM "{}".'.format(domain)
def disable_vm(zkhandler, domain): def disable_vm(zkhandler, domain, force=False):
# Validate that VM exists in cluster # Validate that VM exists in cluster
dom_uuid = getDomainUUID(zkhandler, domain) dom_uuid = getDomainUUID(zkhandler, domain)
if not dom_uuid: if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed # Get state and perform a shutdown/stop if VM is online
current_state = zkhandler.read(("domain.state", dom_uuid)) current_state = zkhandler.read(("domain.state", dom_uuid))
if current_state != "stop": if current_state in ["start"]:
return False, 'ERROR: VM "{}" must be stopped before disabling!'.format(domain) if force:
change_state(zkhandler, dom_uuid, "stop")
# Wait for the command to be registered by the node
time.sleep(0.5)
else:
change_state(zkhandler, dom_uuid, "shutdown")
# Wait for the shutdown to complete
while zkhandler.read(("domain.state", dom_uuid)) != "stop":
time.sleep(0.5)
# Set the VM to disable # Set the VM to disable
change_state(zkhandler, dom_uuid, "disable") change_state(zkhandler, dom_uuid, "disable")
return True, 'Marked VM "{}" as disable.'.format(domain) return True, 'Disabled VM "{}".'.format(domain)
def update_vm_sriov_nics(zkhandler, dom_uuid, source_node, target_node): def update_vm_sriov_nics(zkhandler, dom_uuid, source_node, target_node):

View File

@ -6680,6 +6680,12 @@
"required": true, "required": true,
"type": "string" "type": "string"
}, },
{
"description": "Whether to force stop instead of shutdown VM during disable",
"in": "query",
"name": "force",
"type": "boolean"
},
{ {
"description": "Whether to block waiting for the state change to complete", "description": "Whether to block waiting for the state change to complete",
"in": "query", "in": "query",