Add Celery queueing for VM creation function

Also define this function and provide the planned workflow.
This commit is contained in:
2019-12-03 23:39:13 -05:00
parent 4a7c6db9b2
commit c6986aa5b8
5 changed files with 236 additions and 38 deletions

View File

@ -28,6 +28,8 @@ import uu
import gevent.pywsgi
import celery as Celery
import provisioner_lib.provisioner as pvcprovisioner
# Parse the configuration file
@ -51,6 +53,7 @@ try:
# Create the config object
config = {
'debug': o_config['pvc']['debug'],
'coordinators': o_config['pvc']['coordinators'],
'listen_address': o_config['pvc']['provisioner']['listen_address'],
'listen_port': int(o_config['pvc']['provisioner']['listen_port']),
'auth_enabled': o_config['pvc']['provisioner']['authentication']['enabled'],
@ -63,7 +66,10 @@ try:
'database_port': int(o_config['pvc']['provisioner']['database']['port']),
'database_name': o_config['pvc']['provisioner']['database']['name'],
'database_user': o_config['pvc']['provisioner']['database']['user'],
'database_password': o_config['pvc']['provisioner']['database']['pass']
'database_password': o_config['pvc']['provisioner']['database']['pass'],
'queue_host': o_config['pvc']['provisioner']['queue']['host'],
'queue_port': o_config['pvc']['provisioner']['queue']['port'],
'queue_path': o_config['pvc']['provisioner']['queue']['path'],
}
# Set the config object in the pvcapi namespace
@ -82,6 +88,8 @@ except Exception as e:
exit(1)
api = flask.Flask(__name__)
api.config['CELERY_BROKER_URL'] = 'redis://{}:{}{}'.format(config['queue_host'], config['queue_port'], config['queue_path'])
api.config['CELERY_RESULT_BACKEND'] = 'redis://{}:{}{}'.format(config['queue_host'], config['queue_port'], config['queue_path'])
if config['debug']:
api.config['DEBUG'] = True
@ -89,6 +97,17 @@ if config['debug']:
if config['auth_enabled']:
api.config["SECRET_KEY"] = config['auth_secret_key']
print(api.name)
celery = Celery.Celery(api.name, broker=api.config['CELERY_BROKER_URL'])
celery.conf.update(api.config)
#
# Job functions
#
@celery.task(bind=True)
def create_vm(self, vm_name, profile_name):
return pvcprovisioner.create_vm(self, vm_name, profile_name)
# Authentication decorator function
def authenticator(function):
def authenticate(*args, **kwargs):
@ -561,6 +580,10 @@ def api_template_storage_disk_root(template):
* type: Disk identifier in 'sdX' or 'vdX' format, unique within template
* optional: false
* requires: N/A
?pool: The storage pool in which to store the disk.
* type: Storage Pool name
* optional: false
* requires: N/A
?disk_size: The disk size in GB.
* type: integer, Gigabytes (GB)
* optional: false
@ -591,6 +614,11 @@ def api_template_storage_disk_root(template):
else:
return flask.jsonify({"message": "A disk ID in sdX/vdX format must be specified."}), 400
if 'pool' in flask.request.values:
pool = flask.request.values['pool']
else:
return flask.jsonify({"message": "A pool name must be specified."}), 400
if 'disk_size' in flask.request.values:
disk_size = flask.request.values['disk_size']
else:
@ -606,7 +634,7 @@ def api_template_storage_disk_root(template):
else:
mountpoint = None
return pvcprovisioner.create_template_storage_element(template, disk_id, disk_size, filesystem, mountpoint)
return pvcprovisioner.create_template_storage_element(template, pool, disk_id, disk_size, filesystem, mountpoint)
if flask.request.method == 'DELETE':
if 'disk_id' in flask.request.values:
@ -625,6 +653,10 @@ def api_template_storage_disk_element(template, disk_id):
GET: Show details of disk <disk_id> storage template <template>.
POST: Add new storage VNI <vni> to storage template <template>.
?pool: The storage pool in which to store the disk.
* type: Storage Pool name
* optional: false
* requires: N/A
?disk_size: The disk size in GB.
* type: integer, Gigabytes (GB)
* optional: false
@ -650,6 +682,11 @@ def api_template_storage_disk_element(template, disk_id):
return flask.jsonify({"message": "Found no disk with ID {} in storage template {}".format(disk_id, template)}), 404
if flask.request.method == 'POST':
if 'pool' in flask.request.values:
pool = flask.request.values['pool']
else:
return flask.jsonify({"message": "A pool name must be specified."}), 400
if 'disk_size' in flask.request.values:
disk_size = flask.request.values['disk_size']
else:
@ -665,7 +702,7 @@ def api_template_storage_disk_element(template, disk_id):
else:
mountpoint = None
return pvcprovisioner.create_template_storage_element(template, disk_id, disk_size, mountpoint, filesystem)
return pvcprovisioner.create_template_storage_element(template, pool, disk_id, disk_size, mountpoint, filesystem)
if flask.request.method == 'DELETE':
return pvcprovisioner.delete_template_storage_element(template, disk_id)
@ -787,6 +824,10 @@ def api_profile_root():
* type: text
* optional: false
* requires: N/A
?arg: An arbitrary key=value argument for use by the provisioning script.
* type: key-value pair, multiple
* optional: true
* requires: N/A
"""
if flask.request.method == 'GET':
# Get name limit
@ -828,7 +869,12 @@ def api_profile_root():
else:
return flask.jsonify({"message": "A script must be specified."}), 400
return pvcprovisioner.create_profile(name, system_template, network_template, storage_template, script)
if 'arg' in flask.request.values:
arguments = flask.request.values.getlist('arg')
else:
arguments = None
return pvcprovisioner.create_profile(name, system_template, network_template, storage_template, script, arguments)
@api.route('/api/v1/profile/<profile>', methods=['GET', 'POST', 'DELETE'])
@authenticator
@ -901,32 +947,96 @@ def api_create_root():
/create - Create new VM on the cluster.
POST: Create new VM.
?name: The name of the VM.
* type: text
* optional: false
* requires: N/A
?profile: The profile name of the VM.
* type: text
* optional: flase
* requires: N/A
"""
pass
if 'name' in flask.request.values:
name = flask.request.values['name']
else:
return flask.jsonify({"message": "A VM name must be specified."}), 400
if 'profile' in flask.request.values:
profile = flask.request.values['profile']
else:
return flask.jsonify({"message": "A VM profile must be specified."}), 400
print("starting task")
task = create_vm.delay(name, profile)
print(task.id)
return flask.jsonify({"task_id": task.id}), 202, {'Location': flask.url_for('api_status_root', task_id=task.id)}
@api.route('/api/v1/status/<task_id>', methods=['GET'])
@authenticator
def api_status_root(task_id):
"""
/status - Report on VM creation status.
GET: Get status of the VM provisioning.
?task: The task ID returned from the '/create' endpoint.
* type: text
* optional: flase
* requires: N/A
"""
task = create_vm.AsyncResult(task_id)
if task.state == 'PENDING':
# job did not start yet
response = {
'state': task.state,
'current': 0,
'total': 1,
'status': 'Pending...'
}
elif task.state != 'FAILURE':
# job is still running
response = {
'state': task.state,
'current': task.info.get('current', 0),
'total': task.info.get('total', 1),
'status': task.info.get('status', '')
}
if 'result' in task.info:
response['result'] = task.info['result']
else:
# something went wrong in the background job
response = {
'state': task.state,
'current': 1,
'total': 1,
'status': str(task.info), # this is the exception raised
}
return flask.jsonify(response)
#
# Entrypoint
#
if config['debug']:
# Run in Flask standard mode
api.run(config['listen_address'], config['listen_port'])
else:
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 __name__ == '__main__':
if config['debug']:
# Run in Flask standard mode
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()