From dff1c68f6e38a7ca2d7cbad92e47f914bd6e1e1c Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Thu, 25 Jul 2019 15:42:17 -0400 Subject: [PATCH] RESTify the VM functions and enable debug mode --- client-api/api_lib/pvcapi.py | 7 + client-api/pvc-api.py | 388 +++++++++++++++------------------ client-api/pvc-api.sample.yaml | 2 + docs/manuals/api.md | 12 +- 4 files changed, 190 insertions(+), 219 deletions(-) diff --git a/client-api/api_lib/pvcapi.py b/client-api/api_lib/pvcapi.py index 5a13c329..ae2b4c0d 100755 --- a/client-api/api_lib/pvcapi.py +++ b/client-api/api_lib/pvcapi.py @@ -117,6 +117,13 @@ def node_ready(node): # # VM functions # +def vm_is_migrated(vm): + """ + Determine if a VM is migrated or not + """ + zk_conn = pvc_common.startZKConnection(config['coordinators']) + return pvc_vm.is_migrated(zk_conn, vm) + def vm_list(node=None, state=None, limit=None, is_fuzzy=True): """ Return a list of VMs with limit LIMIT. diff --git a/client-api/pvc-api.py b/client-api/pvc-api.py index e7c89fdc..9118507b 100755 --- a/client-api/pvc-api.py +++ b/client-api/pvc-api.py @@ -29,9 +29,6 @@ import gevent.pywsgi import api_lib.pvcapi as pvcapi -api = flask.Flask(__name__) -api.config['DEBUG'] = True - # Parse the configuration file try: pvc_config_file = os.environ['PVC_CONFIG_FILE'] @@ -52,6 +49,7 @@ except Exception as e: try: # Create the config object config = { + 'debug': o_config['pvc']['debug'], 'coordinators': o_config['pvc']['coordinators'], 'listen_address': o_config['pvc']['api']['listen_address'], 'listen_port': int(o_config['pvc']['api']['listen_port']), @@ -69,6 +67,11 @@ except Exception as e: print('ERROR: {}.'.format(e)) exit(1) +api = flask.Flask(__name__) + +if config['debug']: + api.config['DEBUG'] = True + if config['auth_enabled']: api.config["SECRET_KEY"] = config['auth_secret_key'] @@ -148,245 +151,202 @@ def api_node(): @api.route('/api/v1/node/', methods=['GET']) @authenticator -def api_node_info(node): +def api_node_root(node): """ Return information about node NODE. """ # Same as specifying /node?limit=NODE return pvcapi.node_list(node) -@api.route('/api/v1/node//secondary', methods=['POST']) -@authenticator -def api_node_secondary(node): - """ - Take NODE out of primary router mode. - """ - return pvcapi.node_secondary(node) -@api.route('/api/v1/node//primary', methods=['POST']) +@api.route('/api/v1/node//coordinator-state', methods=['GET', 'POST']) @authenticator -def api_node_primary(node): +def api_node_coordinator_state(node): """ - Set NODE to primary router mode. + Manage NODE coordinator state. """ - return pvcapi.node_primary(node) -@api.route('/api/v1/node//flush', methods=['POST']) -@authenticator -def api_node_flush(node): - """ - Flush NODE of running VMs. - """ - return pvcapi.node_flush(node) + if flask.request.method == 'GET': + return "Test", 200 -@api.route('/api/v1/node//unflush', methods=['POST']) -@api.route('/api/v1/node//ready', methods=['POST']) + if flask.request.method == 'POST': + if not coordinator-state in flask.request.values: + flask.abort(400) + new_state = flask.request.values['coordinator-state'] + if new_state == 'primary': + return pvcapi.node_primary(node) + if new_state == 'secondary': + return pvcapi.node_secondary(node) + flask.abort(400) + +@api.route('/api/v1/node//domain-state', methods=['GET', 'POST']) @authenticator -def api_node_ready(node): +def api_node_domain_state(node): """ - Restore NODE to active service. + Manage NODE domain state. """ - return pvcapi.node_ready(node) + + if flask.request.method == 'GET': + return "Test", 200 + + if flask.request.method == 'POST': + if not domain-state in flask.request.values: + flask.abort(400) + new_state = flask.request.values['domain-state'] + if new_state == 'ready': + return pvcapi.node_ready(node) + if new_state == 'flush': + return pvcapi.node_flush(node) + flask.abort(400) # # VM endpoints # -@api.route('/api/v1/vm', methods=['GET']) +@api.route('/api/v1/vm', methods=['GET', 'POST']) @authenticator def api_vm(): - """ - Return a list of VMs with limit LIMIT. - """ - # Get node limit - if 'node' in flask.request.values: - node = flask.request.values['node'] - else: - node = None + if flask.request.method == 'GET': + # Get node limit + if 'node' in flask.request.values: + node = flask.request.values['node'] + else: + node = None + + # Get state limit + if 'state' in flask.request.values: + state = flask.request.values['state'] + else: + state = None + + # Get name limit + if 'limit' in flask.request.values: + limit = flask.request.values['limit'] + else: + limit = None + + return pvcapi.vm_list(node, state, limit) - # Get state limit - if 'state' in flask.request.values: - state = flask.request.values['state'] - else: - state = None + if flask.request.method == 'POST': + # Get XML data + if 'xml' in flask.request.values: + libvirt_xml = flask.request.values['xml'] + else: + return flask.jsonify({"message":"ERROR: A Libvirt XML document must be specified."}), 400 + + # Get node name + if 'node' in flask.request.values: + node = flask.request.values['node'] + else: + node = None + + # Get target selector + if 'selector' in flask.request.values: + selector = flask.request.values['selector'] + else: + selector = None + + return pvcapi.vm_define(vm, libvirt_xml, node, selector) - # Get name limit - if 'limit' in flask.request.values: - limit = flask.request.values['limit'] - else: - limit = None - - return pvcapi.vm_list(node, state, limit) - -@api.route('/api/v1/vm/', methods=['GET']) +@api.route('/api/v1/vm/', methods=['GET', 'POST', 'PUT', 'DELETE']) @authenticator -def api_vm_info(vm): - """ - Get information about a virtual machine named VM. - """ - # Same as specifying /vm?limit=VM - return pvcapi.vm_list(None, None, vm, is_fuzzy=False) +def api_vm_root(vm): + if flask.request.method == 'GET': + # Same as specifying /vm?limit=VM + return pvcapi.vm_list(None, None, vm, is_fuzzy=False) -# TODO: #22 -#@api.route('/api/v1/vm//add', methods=['POST']) + if flask.request.method == 'POST': + # TODO: #22 + flask.abort(501) + + if flask.request.method == 'PUT': + libvirt_xml = flask.request.data + + if 'restart' in flask.request.values and flask.request.values['restart']: + flag_restart = True + else: + flag_restart = False + + return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) + + if flask.request.method == 'DELETE': + if 'delete_disks' in flask.request.values and flask.request.values['delete_disks']: + return pvcapi.vm_remove(vm) + else: + return pvcapi.vm_undefine(vm) + +#@api.route('/api/v1/vm//dump', methods=['GET']) #@authenticator -#def api_vm_add(vm): +#def api_vm_dump(vm): # """ -# Add a virtual machine named VM. +# Dump the Libvirt XML configuration of a virtual machine named VM. # """ -# return pvcapi.vm_add() +# return pvcapi.vm_dump(vm) -@api.route('/api/v1/vm//define', methods=['POST']) +@api.route('/api/v1/vm//state', methods=['GET', 'POST']) @authenticator -def api_vm_define(vm): - """ - Define a virtual machine named VM from Libvirt XML. - """ - # Get XML data - if 'xml' in flask.request.values: - libvirt_xml = flask.request.values['xml'] - else: - return flask.jsonify({"message":"ERROR: A Libvirt XML document must be specified."}), 520 +def api_vm_state(vm): + if flask.request.method == 'GET': + return "Test", 200 - # Get node name - if 'node' in flask.request.values: - node = flask.request.values['node'] - else: - node = None + if flask.request.method == 'POST': + if not state in flask.request.values: + flask.abort(400) + new_state = flask.request.values['state'] + if new_state == 'start': + return pvcapi.vm_start(vm) + if new_state == 'shutdown': + return pvcapi.vm_shutdown(vm) + if new_state == 'stop': + return pvcapi.vm_stop(vm) + if new_state == 'restart': + return pvcapi.vm_restart(vm) + flask.abort(400) - # Get target selector - if 'selector' in flask.request.values: - selector = flask.request.values['selector'] - else: - selector = None - - return pvcapi.vm_define(vm, libvirt_xml, node, selector) - -@api.route('/api/v1/vm//modify', methods=['POST']) +@api.route('/api/v1/vm//node', methods=['GET', 'POST']) @authenticator -def api_vm_modify(vm): - """ - Modify an existing virtual machine named VM from Libvirt XML. - """ - # Get XML from the POST body - libvirt_xml = flask.request.data +def api_vm_node(vm): + if flask.request.method == 'GET': + return "Test", 200 + + if flask.request.method == 'POST': + if 'action' in flask.request.values: + action = flask.request.values['action'] + else: + flask.abort(400) + + # Get node name + if 'node' in flask.request.values: + node = flask.request.values['node'] + else: + node = None + # Get target selector + if 'selector' in flask.request.values: + selector = flask.request.values['selector'] + else: + selector = None + # Get permanent flag + if 'permanent' in flask.request.values and flask.request.values['permanent']: + flag_permanent = True + else: + flag_permanent = False + # Get force flag + if 'force' in flask.request.values and flask.request.values['force']: + flag_force = True + else: + flag_force = False - # Get node name - if 'flag_restart' in flask.request.values: - flag_restart = flask.request.values['flag_restart'] - else: - flag_restart = None + # Check if VM is presently migrated + is_migrated = pvcapi.vm_is_migrated(vm) - return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) + if action == 'migrate' and not flag_permanent: + return pvcapi.vm_migrate(vm, node, selector, flag_force) + if action == 'migrate' and flag_permanent: + return pvcapi.vm_move(vm, node, selector) + if action == 'unmigrate' and is_migrated: + return pvcapi.vm_unmigrate(vm) -@api.route('/api/v1/vm//undefine', methods=['POST']) -@authenticator -def api_vm_undefine(vm): - """ - Undefine a virtual machine named VM. - """ - return pvcapi.vm_undefine(vm) + flask.abort(400) -@api.route('/api/v1/vm//remove', methods=['POST']) -@authenticator -def api_vm_remove(vm): - """ - Remove a virtual machine named VM including all disks. - """ - return pvcapi.vm_remove(vm) - -@api.route('/api/v1/vm//dump', methods=['GET']) -@authenticator -def api_vm_dump(vm): - """ - Dump the Libvirt XML configuration of a virtual machine named VM. - """ - return pvcapi.vm_dump(vm) - -@api.route('/api/v1/vm//start', methods=['POST']) -@authenticator -def api_vm_start(vm): - """ - Start a virtual machine named VM. - """ - return pvcapi.vm_start(vm) - -@api.route('/api/v1/vm//restart', methods=['POST']) -@authenticator -def api_vm_restart(vm): - """ - Restart a virtual machine named VM. - """ - return pvcapi.vm_restart(vm) - -@api.route('/api/v1/vm//shutdown', methods=['POST']) -@authenticator -def api_vm_shutdown(vm): - """ - Shutdown a virtual machine named VM. - """ - return pvcapi.vm_shutdown(vm) - -@api.route('/api/v1/vm//stop', methods=['POST']) -@authenticator -def api_vm_stop(vm): - """ - Forcibly stop a virtual machine named VM. - """ - return pvcapi.vm_stop(vm) - -@api.route('/api/v1/vm//move', methods=['POST']) -@authenticator -def api_vm_move(vm): - """ - Move a virtual machine named VM to another node. - """ - # Get node name - if 'node' in flask.request.values: - node = flask.request.values['node'] - else: - node = None - - # Get target selector - if 'selector' in flask.request.values: - selector = flask.request.values['selector'] - else: - selector = None - - return pvcapi.vm_move(vm, node, selector) - -@api.route('/api/v1/vm//migrate', methods=['POST']) -@authenticator -def api_vm_migrate(vm): - """ - Temporarily migrate a virtual machine named VM to another node. - """ - # Get node name - if 'node' in flask.request.values: - node = flask.request.values['node'] - else: - node = None - - # Get target selector - if 'selector' in flask.request.values: - selector = flask.request.values['selector'] - else: - selector = None - - # Get target selector - if 'flag_force' in flask.request.values: - flag_force = True - else: - flag_force = False - - return pvcapi.vm_migrate(vm, node, selector, flag_force) - -@api.route('/api/v1/vm//unmigrate', methods=['POST']) -@authenticator -def api_vm_unmigrate(vm): - """ - Unmigrate a migrated virtual machine named VM. - """ - return pvcapi.vm_move(vm) # # Network endpoints @@ -982,13 +942,15 @@ def api_ceph_volume_snapshot_remove(pool, volume, snapshot): # # Entrypoint # -if config['ssl_enabled']: - # Run the WSGI server with SSL - http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api, - keyfile=config['ssl_key_file'], certfile=config['ssl_cert_file']) +if config['debug']: + api.run(config['listen_address'], config['listen_port']) else: - # Run the ?WSGI server without SSL - http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) + if config['ssl_enabled']: + # Run the WSGI server with SSL + http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api, keyfile=config['ssl_key_file'], certfile=config['ssl_cert_file']) + else: + # Run the ?WSGI server without SSL + http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) -print('Starting PyWSGI server at {}:{} with SSL={}, Authentication={}'.format(config['listen_address'], config['listen_port'], config['ssl_enabled'], config['auth_enabled'])) -http_server.serve_forever() + print('Starting PyWSGI server at {}:{} with SSL={}, Authentication={}'.format(config['listen_address'], config['listen_port'], config['ssl_enabled'], config['auth_enabled'])) + http_server.serve_forever() diff --git a/client-api/pvc-api.sample.yaml b/client-api/pvc-api.sample.yaml index 13e72eb1..a44f0078 100644 --- a/client-api/pvc-api.sample.yaml +++ b/client-api/pvc-api.sample.yaml @@ -8,6 +8,8 @@ # Copy this example to /etc/pvc/pvc-api.conf and edit to your needs pvc: + # debug: Enable/disable API debug mode + debug: True # coordinators: The list of cluster coordinator hostnames coordinators: - pvc-hv1 diff --git a/docs/manuals/api.md b/docs/manuals/api.md index 0aac52bc..dd690879 100644 --- a/docs/manuals/api.md +++ b/docs/manuals/api.md @@ -214,20 +214,20 @@ Valid `state` values are: `start`, `shutdown`, `stop`, `restart` Return the current host node and previous node, if applicable, for ``. ###### `POST` - * Mandatory values: N/A - * Optional values: `node`, `selector`, `permanent` + * Mandatory values: `action` + * Optional values: `node`, `selector`, `permanent`, `force` -Change the current host node for ``, using live migration if possible, and using `shutdown` then `start` if not. +Change the current host node for `` by `action`, using live migration if possible, and using `shutdown` then `start` if not. `action` must be either `migrate` or `unmigrate`. If `node` is specified and is valid, the VM will be assigned to `node` instead of automatically determining the target node. If `node` is specified and not valid, auto-selection occurrs instead. -If `node` is not specified and the VM is in migrated state, returns the VM to its previous `node`. - If `selector` is specified and no specific and valid `node` is specified, the automatic node determination will use `selector` to determine the optimal node instead of the default for the cluster. Valid `selector` values are: `mem`: the node with the least allocated VM memory; `vcpus`: the node with the least allocated VM vCPUs; `load`: the node with the least current load average; `vms`: the node with the least number of provisioned VMs. -If `permanent` is specified, the PVC system will not track the previous node and the VM will not be considered migrated. +If `permanent` is specified, the PVC system will not track the previous node and the VM will not be considered migrated. This is equivalent to the `pvc vm move` CLI command. + +If `force` is specified, and the VM has been previously migrated, force through a new migration to the selected target and do not update the previous node value. ### Network endpoints