RESTify the VM functions and enable debug mode

This commit is contained in:
Joshua Boniface 2019-07-25 15:42:17 -04:00
parent bcd48648b2
commit dff1c68f6e
4 changed files with 190 additions and 219 deletions

View File

@ -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.

View File

@ -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,245 +151,202 @@ 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.
""" """
return pvcapi.node_primary(node)
@api.route('/api/v1/node/<node>/flush', methods=['POST']) if flask.request.method == 'GET':
@authenticator return "Test", 200
def api_node_flush(node):
"""
Flush NODE of running VMs.
"""
return pvcapi.node_flush(node)
@api.route('/api/v1/node/<node>/unflush', methods=['POST']) if flask.request.method == 'POST':
@api.route('/api/v1/node/<node>/ready', methods=['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/<node>/domain-state', methods=['GET', 'POST'])
@authenticator @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 # 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
""" if 'node' in flask.request.values:
# Get node limit node = flask.request.values['node']
if 'node' in flask.request.values: else:
node = flask.request.values['node'] node = None
else:
node = None
# Get state limit # Get state limit
if 'state' in flask.request.values: if 'state' in flask.request.values:
state = flask.request.values['state'] state = flask.request.values['state']
else: else:
state = None state = None
# Get name limit # Get name limit
if 'limit' in flask.request.values: if 'limit' in flask.request.values:
limit = flask.request.values['limit'] limit = flask.request.values['limit']
else: else:
limit = None limit = None
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':
# 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)
@api.route('/api/v1/vm/<vm>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@authenticator @authenticator
def api_vm_info(vm): def api_vm_root(vm):
""" if flask.request.method == 'GET':
Get information about a virtual machine named VM. # Same as specifying /vm?limit=VM
""" return pvcapi.vm_list(None, None, vm, is_fuzzy=False)
# Same as specifying /vm?limit=VM
return pvcapi.vm_list(None, None, vm, is_fuzzy=False)
# TODO: #22 if flask.request.method == 'POST':
#@api.route('/api/v1/vm/<vm>/add', methods=['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/<vm>/dump', methods=['GET'])
#@authenticator #@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/<vm>/define', methods=['POST']) @api.route('/api/v1/vm/<vm>/state', methods=['GET', 'POST'])
@authenticator @authenticator
def api_vm_define(vm): def api_vm_state(vm):
""" if flask.request.method == 'GET':
Define a virtual machine named VM from Libvirt XML. return "Test", 200
"""
# 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
# Get node name if flask.request.method == 'POST':
if 'node' in flask.request.values: if not state in flask.request.values:
node = flask.request.values['node'] flask.abort(400)
else: new_state = flask.request.values['state']
node = None 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 @api.route('/api/v1/vm/<vm>/node', methods=['GET', 'POST'])
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/<vm>/modify', methods=['POST'])
@authenticator @authenticator
def api_vm_modify(vm): def api_vm_node(vm):
""" if flask.request.method == 'GET':
Modify an existing virtual machine named VM from Libvirt XML. return "Test", 200
"""
# Get XML from the POST body
libvirt_xml = flask.request.data
# Get node name if flask.request.method == 'POST':
if 'flag_restart' in flask.request.values: if 'action' in flask.request.values:
flag_restart = flask.request.values['flag_restart'] action = flask.request.values['action']
else: else:
flag_restart = None flask.abort(400)
return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) # 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
@api.route('/api/v1/vm/<vm>/undefine', methods=['POST']) # Check if VM is presently migrated
@authenticator is_migrated = pvcapi.vm_is_migrated(vm)
def api_vm_undefine(vm):
"""
Undefine a virtual machine named VM.
"""
return pvcapi.vm_undefine(vm)
@api.route('/api/v1/vm/<vm>/remove', methods=['POST']) if action == 'migrate' and not flag_permanent:
@authenticator return pvcapi.vm_migrate(vm, node, selector, flag_force)
def api_vm_remove(vm): if action == 'migrate' and flag_permanent:
""" return pvcapi.vm_move(vm, node, selector)
Remove a virtual machine named VM including all disks. if action == 'unmigrate' and is_migrated:
""" return pvcapi.vm_unmigrate(vm)
return pvcapi.vm_remove(vm)
@api.route('/api/v1/vm/<vm>/dump', methods=['GET']) flask.abort(400)
@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/<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/<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)
@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)
@api.route('/api/v1/vm/<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/<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/<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,13 +942,15 @@ def api_ceph_volume_snapshot_remove(pool, volume, snapshot):
# #
# Entrypoint # Entrypoint
# #
if config['ssl_enabled']: if config['debug']:
# Run the WSGI server with SSL api.run(config['listen_address'], config['listen_port'])
http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api,
keyfile=config['ssl_key_file'], certfile=config['ssl_cert_file'])
else: else:
# Run the ?WSGI server without SSL if config['ssl_enabled']:
http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) # 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'])) 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() http_server.serve_forever()

View File

@ -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

View File

@ -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