RESTify the VM functions and enable debug mode
This commit is contained in:
		| @@ -117,6 +117,13 @@ def node_ready(node): | |||||||
| # | # | ||||||
| # VM functions | # 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): | def vm_list(node=None, state=None, limit=None, is_fuzzy=True): | ||||||
|     """ |     """ | ||||||
|     Return a list of VMs with limit LIMIT. |     Return a list of VMs with limit LIMIT. | ||||||
|   | |||||||
| @@ -29,9 +29,6 @@ import gevent.pywsgi | |||||||
|  |  | ||||||
| import api_lib.pvcapi as pvcapi | import api_lib.pvcapi as pvcapi | ||||||
|  |  | ||||||
| api = flask.Flask(__name__) |  | ||||||
| api.config['DEBUG'] = True |  | ||||||
|  |  | ||||||
| # Parse the configuration file | # Parse the configuration file | ||||||
| try: | try: | ||||||
|     pvc_config_file = os.environ['PVC_CONFIG_FILE'] |     pvc_config_file = os.environ['PVC_CONFIG_FILE'] | ||||||
| @@ -52,6 +49,7 @@ except Exception as e: | |||||||
| try: | try: | ||||||
|     # Create the config object |     # Create the config object | ||||||
|     config = { |     config = { | ||||||
|  |         'debug': o_config['pvc']['debug'], | ||||||
|         'coordinators': o_config['pvc']['coordinators'], |         'coordinators': o_config['pvc']['coordinators'], | ||||||
|         'listen_address': o_config['pvc']['api']['listen_address'], |         'listen_address': o_config['pvc']['api']['listen_address'], | ||||||
|         'listen_port': int(o_config['pvc']['api']['listen_port']), |         'listen_port': int(o_config['pvc']['api']['listen_port']), | ||||||
| @@ -69,6 +67,11 @@ except Exception as e: | |||||||
|     print('ERROR: {}.'.format(e)) |     print('ERROR: {}.'.format(e)) | ||||||
|     exit(1) |     exit(1) | ||||||
|  |  | ||||||
|  | api = flask.Flask(__name__) | ||||||
|  |  | ||||||
|  | if config['debug']: | ||||||
|  |     api.config['DEBUG'] = True | ||||||
|  |  | ||||||
| if config['auth_enabled']: | if config['auth_enabled']: | ||||||
|     api.config["SECRET_KEY"] = config['auth_secret_key'] |     api.config["SECRET_KEY"] = config['auth_secret_key'] | ||||||
|  |  | ||||||
| @@ -148,55 +151,61 @@ def api_node(): | |||||||
|  |  | ||||||
| @api.route('/api/v1/node/<node>', methods=['GET']) | @api.route('/api/v1/node/<node>', methods=['GET']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_node_info(node): | def api_node_root(node): | ||||||
|     """ |     """ | ||||||
|     Return information about node NODE. |     Return information about node NODE. | ||||||
|     """ |     """ | ||||||
|     # Same as specifying /node?limit=NODE |     # Same as specifying /node?limit=NODE | ||||||
|     return pvcapi.node_list(node) |     return pvcapi.node_list(node) | ||||||
|  |  | ||||||
| @api.route('/api/v1/node/<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/<node>/primary', methods=['POST']) | @api.route('/api/v1/node/<node>/coordinator-state', methods=['GET', 'POST']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_node_primary(node): | def api_node_coordinator_state(node): | ||||||
|     """ |     """ | ||||||
|     Set NODE to primary router mode. |     Manage NODE coordinator state. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  |     if flask.request.method == 'GET': | ||||||
|  |         return "Test", 200 | ||||||
|  |  | ||||||
|  |     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) |             return pvcapi.node_primary(node) | ||||||
|  |         if new_state == 'secondary': | ||||||
|  |             return pvcapi.node_secondary(node) | ||||||
|  |         flask.abort(400) | ||||||
|  |  | ||||||
| @api.route('/api/v1/node/<node>/flush', methods=['POST']) | @api.route('/api/v1/node/<node>/domain-state', methods=['GET', 'POST']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_node_flush(node): | def api_node_domain_state(node): | ||||||
|     """ |     """ | ||||||
|     Flush NODE of running VMs. |     Manage NODE domain state. | ||||||
|     """ |     """ | ||||||
|     return pvcapi.node_flush(node) |  | ||||||
|  |  | ||||||
| @api.route('/api/v1/node/<node>/unflush', methods=['POST']) |     if flask.request.method == 'GET': | ||||||
| @api.route('/api/v1/node/<node>/ready', methods=['POST']) |         return "Test", 200 | ||||||
| @authenticator |  | ||||||
| def api_node_ready(node): |     if flask.request.method == 'POST': | ||||||
|     """ |         if not domain-state in flask.request.values: | ||||||
|     Restore NODE to active service. |             flask.abort(400) | ||||||
|     """ |         new_state = flask.request.values['domain-state'] | ||||||
|  |         if new_state == 'ready': | ||||||
|             return pvcapi.node_ready(node) |             return pvcapi.node_ready(node) | ||||||
|  |         if new_state == 'flush': | ||||||
|  |             return pvcapi.node_flush(node) | ||||||
|  |         flask.abort(400) | ||||||
|  |  | ||||||
| # | # | ||||||
| # VM endpoints | # VM endpoints | ||||||
| # | # | ||||||
| @api.route('/api/v1/vm', methods=['GET']) | @api.route('/api/v1/vm', methods=['GET', 'POST']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_vm(): | def api_vm(): | ||||||
|     """ |     if flask.request.method == 'GET': | ||||||
|     Return a list of VMs with limit LIMIT. |  | ||||||
|     """ |  | ||||||
|         # Get node limit |         # Get node limit | ||||||
|         if 'node' in flask.request.values: |         if 'node' in flask.request.values: | ||||||
|             node = flask.request.values['node'] |             node = flask.request.values['node'] | ||||||
| @@ -217,35 +226,12 @@ def api_vm(): | |||||||
|      |      | ||||||
|         return pvcapi.vm_list(node, state, limit) |         return pvcapi.vm_list(node, state, limit) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>', methods=['GET']) |     if flask.request.method == 'POST': | ||||||
| @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) |  | ||||||
|  |  | ||||||
| # TODO: #22 |  | ||||||
| #@api.route('/api/v1/vm/<vm>/add', methods=['POST']) |  | ||||||
| #@authenticator |  | ||||||
| #def api_vm_add(vm): |  | ||||||
| #    """ |  | ||||||
| #    Add a virtual machine named VM. |  | ||||||
| #    """ |  | ||||||
| #    return pvcapi.vm_add() |  | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/define', methods=['POST']) |  | ||||||
| @authenticator |  | ||||||
| def api_vm_define(vm): |  | ||||||
|     """ |  | ||||||
|     Define a virtual machine named VM from Libvirt XML. |  | ||||||
|     """ |  | ||||||
|         # Get XML data |         # Get XML data | ||||||
|         if 'xml' in flask.request.values: |         if 'xml' in flask.request.values: | ||||||
|             libvirt_xml = flask.request.values['xml'] |             libvirt_xml = flask.request.values['xml'] | ||||||
|         else: |         else: | ||||||
|         return flask.jsonify({"message":"ERROR: A Libvirt XML document must be specified."}), 520 |             return flask.jsonify({"message":"ERROR: A Libvirt XML document must be specified."}), 400 | ||||||
|      |      | ||||||
|         # Get node name |         # Get node name | ||||||
|         if 'node' in flask.request.values: |         if 'node' in flask.request.values: | ||||||
| @@ -261,132 +247,106 @@ def api_vm_define(vm): | |||||||
|      |      | ||||||
|         return pvcapi.vm_define(vm, libvirt_xml, node, selector) |         return pvcapi.vm_define(vm, libvirt_xml, node, selector) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/modify', methods=['POST']) | @api.route('/api/v1/vm/<vm>', methods=['GET', 'POST', 'PUT', 'DELETE']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_vm_modify(vm): | def api_vm_root(vm): | ||||||
|     """ |     if flask.request.method == 'GET': | ||||||
|     Modify an existing virtual machine named VM from Libvirt XML. |         # Same as specifying /vm?limit=VM | ||||||
|     """ |         return pvcapi.vm_list(None, None, vm, is_fuzzy=False) | ||||||
|     # Get XML from the POST body |  | ||||||
|  |     if flask.request.method == 'POST': | ||||||
|  |         # TODO: #22 | ||||||
|  |         flask.abort(501) | ||||||
|  |  | ||||||
|  |     if flask.request.method == 'PUT': | ||||||
|         libvirt_xml = flask.request.data |         libvirt_xml = flask.request.data | ||||||
|      |      | ||||||
|     # Get node name |         if 'restart' in flask.request.values and flask.request.values['restart']: | ||||||
|     if 'flag_restart' in flask.request.values: |             flag_restart = True | ||||||
|         flag_restart = flask.request.values['flag_restart'] |  | ||||||
|         else: |         else: | ||||||
|         flag_restart = None |             flag_restart = False | ||||||
|      |      | ||||||
|         return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) |         return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/undefine', methods=['POST']) |     if flask.request.method == 'DELETE': | ||||||
| @authenticator |         if 'delete_disks' in flask.request.values and flask.request.values['delete_disks']: | ||||||
| def api_vm_undefine(vm): |             return pvcapi.vm_remove(vm) | ||||||
|     """ |         else: | ||||||
|     Undefine a virtual machine named VM. |  | ||||||
|     """ |  | ||||||
|             return pvcapi.vm_undefine(vm) |             return pvcapi.vm_undefine(vm) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/remove', methods=['POST']) | #@api.route('/api/v1/vm/<vm>/dump', methods=['GET']) | ||||||
| @authenticator | #@authenticator | ||||||
| def api_vm_remove(vm): | #def api_vm_dump(vm): | ||||||
|     """ | #    """ | ||||||
|     Remove a virtual machine named VM including all disks. | #    Dump the Libvirt XML configuration of a virtual machine named VM. | ||||||
|     """ | #    """ | ||||||
|     return pvcapi.vm_remove(vm) | #    return pvcapi.vm_dump(vm) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/dump', methods=['GET']) | @api.route('/api/v1/vm/<vm>/state', methods=['GET', 'POST']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_vm_dump(vm): | def api_vm_state(vm): | ||||||
|     """ |     if flask.request.method == 'GET': | ||||||
|     Dump the Libvirt XML configuration of a virtual machine named VM. |         return "Test", 200 | ||||||
|     """ |  | ||||||
|     return pvcapi.vm_dump(vm) |  | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/start', methods=['POST']) |     if flask.request.method == 'POST': | ||||||
| @authenticator |         if not state in flask.request.values: | ||||||
| def api_vm_start(vm): |             flask.abort(400) | ||||||
|     """ |         new_state = flask.request.values['state'] | ||||||
|     Start a virtual machine named VM. |         if new_state == 'start': | ||||||
|     """ |  | ||||||
|             return pvcapi.vm_start(vm) |             return pvcapi.vm_start(vm) | ||||||
|  |         if new_state == 'shutdown': | ||||||
| @api.route('/api/v1/vm/<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/<vm>/shutdown', methods=['POST']) |  | ||||||
| @authenticator |  | ||||||
| def api_vm_shutdown(vm): |  | ||||||
|     """ |  | ||||||
|     Shutdown a virtual machine named VM. |  | ||||||
|     """ |  | ||||||
|             return pvcapi.vm_shutdown(vm) |             return pvcapi.vm_shutdown(vm) | ||||||
|  |         if new_state == 'stop': | ||||||
| @api.route('/api/v1/vm/<vm>/stop', methods=['POST']) |  | ||||||
| @authenticator |  | ||||||
| def api_vm_stop(vm): |  | ||||||
|     """ |  | ||||||
|     Forcibly stop a virtual machine named VM. |  | ||||||
|     """ |  | ||||||
|             return pvcapi.vm_stop(vm) |             return pvcapi.vm_stop(vm) | ||||||
|  |         if new_state == 'restart': | ||||||
|  |             return pvcapi.vm_restart(vm) | ||||||
|  |         flask.abort(400) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/move', methods=['POST']) | @api.route('/api/v1/vm/<vm>/node', methods=['GET', 'POST']) | ||||||
| @authenticator | @authenticator | ||||||
| def api_vm_move(vm): | def api_vm_node(vm): | ||||||
|     """ |     if flask.request.method == 'GET': | ||||||
|     Move a virtual machine named VM to another node. |         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 |         # Get node name | ||||||
|         if 'node' in flask.request.values: |         if 'node' in flask.request.values: | ||||||
|             node = flask.request.values['node'] |             node = flask.request.values['node'] | ||||||
|         else: |         else: | ||||||
|             node = None |             node = None | ||||||
|  |  | ||||||
|         # Get target selector |         # Get target selector | ||||||
|         if 'selector' in flask.request.values: |         if 'selector' in flask.request.values: | ||||||
|             selector = flask.request.values['selector'] |             selector = flask.request.values['selector'] | ||||||
|         else: |         else: | ||||||
|             selector = None |             selector = None | ||||||
|  |         # Get permanent flag | ||||||
|     return pvcapi.vm_move(vm, node, selector) |         if 'permanent' in flask.request.values and flask.request.values['permanent']: | ||||||
|  |             flag_permanent = True | ||||||
| @api.route('/api/v1/vm/<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: |         else: | ||||||
|         node = None |             flag_permanent = False | ||||||
|  |         # Get force flag | ||||||
|     # Get target selector |         if 'force' in flask.request.values and flask.request.values['force']: | ||||||
|     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 |             flag_force = True | ||||||
|         else: |         else: | ||||||
|             flag_force = False |             flag_force = False | ||||||
|  |  | ||||||
|     return pvcapi.vm_migrate(vm, node, selector, flag_force) |         # Check if VM is presently migrated | ||||||
|  |         is_migrated = pvcapi.vm_is_migrated(vm) | ||||||
|  |  | ||||||
|  |         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) | ||||||
|  |  | ||||||
|  |         flask.abort(400) | ||||||
|  |  | ||||||
| @api.route('/api/v1/vm/<vm>/unmigrate', methods=['POST']) |  | ||||||
| @authenticator |  | ||||||
| def api_vm_unmigrate(vm): |  | ||||||
|     """ |  | ||||||
|     Unmigrate a migrated virtual machine named VM. |  | ||||||
|     """ |  | ||||||
|     return pvcapi.vm_move(vm) |  | ||||||
|  |  | ||||||
| # | # | ||||||
| # Network endpoints | # Network endpoints | ||||||
| @@ -982,10 +942,12 @@ def api_ceph_volume_snapshot_remove(pool, volume, snapshot): | |||||||
| # | # | ||||||
| # Entrypoint | # Entrypoint | ||||||
| # | # | ||||||
|  | if config['debug']: | ||||||
|  |     api.run(config['listen_address'], config['listen_port']) | ||||||
|  | else: | ||||||
|     if config['ssl_enabled']: |     if config['ssl_enabled']: | ||||||
|         # Run the WSGI server with SSL |         # Run the WSGI server with SSL | ||||||
|     http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api, |         http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api,                                           keyfile=config['ssl_key_file'], certfile=config['ssl_cert_file']) | ||||||
|                                        keyfile=config['ssl_key_file'], certfile=config['ssl_cert_file']) |  | ||||||
|     else: |     else: | ||||||
|         # Run the ?WSGI server without SSL |         # Run the ?WSGI server without SSL | ||||||
|         http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) |         http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| # Copy this example to /etc/pvc/pvc-api.conf and edit to your needs | # Copy this example to /etc/pvc/pvc-api.conf and edit to your needs | ||||||
|  |  | ||||||
| pvc: | pvc: | ||||||
|  |     # debug: Enable/disable API debug mode | ||||||
|  |     debug: True | ||||||
|     # coordinators: The list of cluster coordinator hostnames |     # coordinators: The list of cluster coordinator hostnames | ||||||
|     coordinators: |     coordinators: | ||||||
|       - pvc-hv1 |       - pvc-hv1 | ||||||
|   | |||||||
| @@ -214,20 +214,20 @@ Valid `state` values are: `start`, `shutdown`, `stop`, `restart` | |||||||
| Return the current host node and previous node, if applicable, for `<vm>`. | Return the current host node and previous node, if applicable, for `<vm>`. | ||||||
|  |  | ||||||
| ###### `POST` | ###### `POST` | ||||||
|  * Mandatory values: N/A |  * Mandatory values: `action` | ||||||
|  * Optional values: `node`, `selector`, `permanent` |  * Optional values: `node`, `selector`, `permanent`, `force` | ||||||
|  |  | ||||||
| Change the current host node for `<vm>`, using live migration if possible, and using `shutdown` then `start` if not. | Change the current host node for `<vm>` 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 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. | 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. | 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 | ### Network endpoints | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user