diff --git a/.hooks/pre-commit b/.hooks/pre-commit new file mode 100755 index 00000000..957a0411 --- /dev/null +++ b/.hooks/pre-commit @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +pushd $( git rev-parse --show-toplevel ) &>/dev/null + +ex=0 + +# Linting +./lint +if [[ $? -ne 0 ]]; then + echo "Aborting commit due to linting errors." + ex=1 +fi + +echo +popd &>/dev/null +exit $ex diff --git a/api-daemon/migrations/env.py b/api-daemon/migrations/env.py index 23663ff2..8f39ee5c 100644 --- a/api-daemon/migrations/env.py +++ b/api-daemon/migrations/env.py @@ -2,6 +2,7 @@ from __future__ import with_statement from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig +from flask import current_app import logging # this is the Alembic Config object, which provides @@ -17,7 +18,6 @@ logger = logging.getLogger('alembic.env') # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -from flask import current_app config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) target_metadata = current_app.extensions['migrate'].db.metadata @@ -81,6 +81,7 @@ def run_migrations_online(): finally: connection.close() + if context.is_offline_mode(): run_migrations_offline() else: diff --git a/api-daemon/migrations/versions/2d1daa722a0a_pvc_version_0_6.py b/api-daemon/migrations/versions/2d1daa722a0a_pvc_version_0_6.py index 8a0da4bd..57ebf58f 100644 --- a/api-daemon/migrations/versions/2d1daa722a0a_pvc_version_0_6.py +++ b/api-daemon/migrations/versions/2d1daa722a0a_pvc_version_0_6.py @@ -1,7 +1,7 @@ """PVC version 0.6 Revision ID: 2d1daa722a0a -Revises: +Revises: Create Date: 2020-02-15 23:14:14.733134 """ diff --git a/api-daemon/provisioner/examples/debootstrap_script.py b/api-daemon/provisioner/examples/debootstrap_script.py index abd0a3ff..7ce216f3 100644 --- a/api-daemon/provisioner/examples/debootstrap_script.py +++ b/api-daemon/provisioner/examples/debootstrap_script.py @@ -50,15 +50,15 @@ def install(**kwargs): # failures of these gracefully, should administrators forget to specify them. try: deb_release = kwargs['deb_release'] - except: + except Exception: deb_release = "stable" try: deb_mirror = kwargs['deb_mirror'] - except: + except Exception: deb_mirror = "http://ftp.debian.org/debian" try: deb_packages = kwargs['deb_packages'].split(',') - except: + except Exception: deb_packages = ["linux-image-amd64", "grub-pc", "cloud-init", "python3-cffi-backend", "wget"] # We need to know our root disk @@ -205,7 +205,7 @@ GRUB_DISABLE_LINUX_UUID=false os.system( "grub-install --force /dev/rbd/{}/{}_{}".format(root_disk['pool'], vm_name, root_disk['disk_id']) ) - os.system( + os.system( "update-grub" ) # Set a really dumb root password [TEMPORARY] diff --git a/api-daemon/provisioner/examples/dummy_script.py b/api-daemon/provisioner/examples/dummy_script.py index 08d234ba..caa594a5 100644 --- a/api-daemon/provisioner/examples/dummy_script.py +++ b/api-daemon/provisioner/examples/dummy_script.py @@ -30,8 +30,6 @@ # This script will run under root privileges as the provisioner does. Be careful # with that. -import os - # Installation function - performs a debootstrap install of a Debian system # Note that the only arguments are keyword arguments. def install(**kwargs): diff --git a/api-daemon/pvcapid-manage.py b/api-daemon/pvcapid-manage.py index 0de43342..75040553 100755 --- a/api-daemon/pvcapid-manage.py +++ b/api-daemon/pvcapid-manage.py @@ -20,16 +20,16 @@ # ############################################################################### -import os from flask_migrate import Migrate, MigrateCommand from flask_script import Manager -from pvcapid.flaskapi import app, db, config +from pvcapid.flaskapi import app, db +from pvcapid.models import * # noqa F401,F403 migrate = Migrate(app, db) manager = Manager(app) -manager.add_command('db', MigrateCommand) +manager.add_command('db', MigrateCommand) if __name__ == '__main__': manager.run() diff --git a/api-daemon/pvcapid.py b/api-daemon/pvcapid.py index 2caaf148..423ace3f 100755 --- a/api-daemon/pvcapid.py +++ b/api-daemon/pvcapid.py @@ -20,4 +20,4 @@ # ############################################################################### -import pvcapid.Daemon +import pvcapid.Daemon # noqa: F401 diff --git a/api-daemon/pvcapid/Daemon.py b/api-daemon/pvcapid/Daemon.py index daebf054..9d32afd3 100755 --- a/api-daemon/pvcapid/Daemon.py +++ b/api-daemon/pvcapid/Daemon.py @@ -29,7 +29,7 @@ import pvcapid.flaskapi as pvc_api if pvc_api.config['ssl_enabled']: context = (pvc_api.config['ssl_cert_file'], pvc_api.config['ssl_key_file']) else: - context=None + context = None print('Starting PVC API daemon at {}:{} with SSL={}, Authentication={}'.format(pvc_api.config['listen_address'], pvc_api.config['listen_port'], pvc_api.config['ssl_enabled'], pvc_api.config['auth_enabled'])) pvc_api.app.run(pvc_api.config['listen_address'], pvc_api.config['listen_port'], threaded=True, ssl_context=context) diff --git a/api-daemon/pvcapid/benchmark.py b/api-daemon/pvcapid/benchmark.py index 1c622dfa..89ee3ad8 100755 --- a/api-daemon/pvcapid/benchmark.py +++ b/api-daemon/pvcapid/benchmark.py @@ -20,25 +20,16 @@ # ############################################################################### -import flask -import json import psycopg2 import psycopg2.extras -import os -import re -import time -import shlex -import subprocess from distutils.util import strtobool as dustrtobool import daemon_lib.common as pvc_common -import daemon_lib.node as pvc_node import daemon_lib.ceph as pvc_ceph -import pvcapid.libvirt_schema as libvirt_schema +config = None # Set in this namespace by flaskapi -from pvcapid.ova import list_ova def strtobool(stringv): if stringv is None: @@ -47,9 +38,10 @@ def strtobool(stringv): return bool(stringv) try: return bool(dustrtobool(stringv)) - except: + except Exception: return False + # # Exceptions (used by Celery tasks) # @@ -76,6 +68,7 @@ class BenchmarkError(Exception): # Common functions # + # Database connections def open_database(config): conn = psycopg2.connect( @@ -88,12 +81,14 @@ def open_database(config): cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) return conn, cur + def close_database(conn, cur, failed=False): if not failed: conn.commit() cur.close() conn.close() + def list_benchmarks(job=None): if job is not None: query = "SELECT * FROM {} WHERE job = %s;".format('storage_benchmarks') @@ -117,7 +112,8 @@ def list_benchmarks(job=None): if data: return data, 200 else: - return { 'message': 'No benchmark found.' }, 404 + return {'message': 'No benchmark found.'}, 404 + def run_benchmark(self, pool): # Runtime imports @@ -134,17 +130,16 @@ def run_benchmark(self, pool): # Phase 0 - connect to databases try: db_conn, db_cur = open_database(config) - except: + except Exception: print('FATAL - failed to connect to Postgres') raise Exception try: zk_conn = pvc_common.startZKConnection(config['coordinators']) - except: + except Exception: print('FATAL - failed to connect to Zookeeper') raise Exception - print("Storing running status for job '{}' in database".format(cur_time)) try: query = "INSERT INTO storage_benchmarks (job, result) VALUES (%s, %s);" @@ -242,12 +237,11 @@ def run_benchmark(self, pool): --bs={bs} \ --readwrite={rw} """.format( - pool=pool, - volume=volume, - test=test, - bs=test_matrix[test]['bs'], - rw=test_matrix[test]['rw'] - ) + pool=pool, + volume=volume, + test=test, + bs=test_matrix[test]['bs'], + rw=test_matrix[test]['rw']) retcode, stdout, stderr = pvc_common.run_os_command(fio_cmd) if retcode: @@ -324,19 +318,19 @@ def run_benchmark(self, pool): # 7: IOPS # 8: runtime (msec) # Total latency - # 37: min - # 38: max + # 37: min + # 38: max # 39: mean # 40: stdev # Bandwidth - # 41: min - # 42: max + # 41: min + # 42: max # 44: mean # 45: stdev # 46: # samples # IOPS - # 47: min - # 48: max + # 47: min + # 48: max # 49: mean # 50: stdev # 51: # samples @@ -405,7 +399,7 @@ def run_benchmark(self, pool): # 96: mean # 97: stdev # 98: # samples - # CPU + # CPU # 146: user # 147: system # 148: ctx switches @@ -446,7 +440,7 @@ def run_benchmark(self, pool): "minfault": results[150] } } - + # Phase 3 - cleanup self.update_state(state='RUNNING', meta={'current': 3, 'total': 3, 'status': 'Cleaning up and storing results'}) time.sleep(1) @@ -469,4 +463,4 @@ def run_benchmark(self, pool): close_database(db_conn, db_cur) pvc_common.stopZKConnection(zk_conn) - return { 'status': "Storage benchmark '{}' completed successfully.", 'current': 3, 'total': 3 } + return {'status': "Storage benchmark '{}' completed successfully.", 'current': 3, 'total': 3} diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py index c243b2b4..41df23de 100755 --- a/api-daemon/pvcapid/flaskapi.py +++ b/api-daemon/pvcapid/flaskapi.py @@ -20,22 +20,14 @@ # ############################################################################### -import json import yaml import os - -import gevent.pywsgi - import flask from distutils.util import strtobool as dustrtobool - from functools import wraps - from flask_restful import Resource, Api, reqparse, abort - from celery import Celery -from celery.task.control import inspect import pvcapid.helper as api_helper import pvcapid.provisioner as api_provisioner @@ -46,6 +38,7 @@ from flask_sqlalchemy import SQLAlchemy API_VERSION = 1.0 + def strtobool(stringv): if stringv is None: return False @@ -53,13 +46,14 @@ def strtobool(stringv): return bool(stringv) try: return bool(dustrtobool(stringv)) - except: + except Exception: return False + # Parse the configuration file try: pvc_config_file = os.environ['PVC_CONFIG_FILE'] -except: +except Exception: print('Error: The "PVC_CONFIG_FILE" environment variable must be set before starting pvcapid.') exit(1) @@ -133,9 +127,6 @@ if config['auth_enabled']: # Create SQLAlchemy database db = SQLAlchemy(app) -# Import database models -from pvcapid.models import * - # Create Flask blueprint blueprint = flask.Blueprint('api', __name__, url_prefix='/api/v1') @@ -147,6 +138,7 @@ app.register_blueprint(blueprint) celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL']) celery.conf.update(app.config) + # # Custom decorators # @@ -155,9 +147,11 @@ celery.conf.update(app.config) class RequestParser(object): def __init__(self, reqargs): self.reqargs = reqargs + def __call__(self, function): if not callable(function): return + @wraps(function) def wrapped_function(*args, **kwargs): parser = reqparse.RequestParser() @@ -169,13 +163,14 @@ class RequestParser(object): action=reqarg.get('action', None), choices=reqarg.get('choices', ()), help=reqarg.get('helptext', None), - location=['args','form'] + location=['args', 'form'] ) reqargs = parser.parse_args() kwargs['reqargs'] = reqargs return function(*args, **kwargs) return wrapped_function + # Authentication decorator function def Authenticator(function): @wraps(function) @@ -191,9 +186,9 @@ def Authenticator(function): if any(token for token in config['auth_tokens'] if flask.request.headers.get('X-Api-Key') == token.get('token')): return function(*args, **kwargs) else: - return { "message": "X-Api-Key Authentication failed." }, 401 + return {"message": "X-Api-Key Authentication failed."}, 401 # All authentications failed - return { "message": "X-Api-Key Authentication required." }, 401 + return {"message": "X-Api-Key Authentication required."}, 401 return authenticate @@ -204,6 +199,7 @@ def Authenticator(function): def create_vm(self, vm_name, profile_name, define_vm=True, start_vm=True, script_run_args=[]): return api_provisioner.create_vm(self, vm_name, profile_name, define_vm=define_vm, start_vm=start_vm, script_run_args=script_run_args) + @celery.task(bind=True) def run_benchmark(self, pool): return api_benchmark.run_benchmark(self, pool) @@ -233,27 +229,33 @@ class API_Root(Resource): description: A text message example: "PVC API version 1.0" """ - return { "message": "PVC API version {}".format(API_VERSION) } + return {"message": "PVC API version {}".format(API_VERSION)} + + api.add_resource(API_Root, '/') + # /doc - NOTE: Until flask_swagger is packaged for Debian this must be disabled -#class API_Doc(Resource): -# def get(self): -# """ -# Provide the Swagger API documentation -# --- -# tags: -# - root -# responses: -# 200: -# description: OK -# """ -# swagger_data = swagger(pvc_api.app) -# swagger_data['info']['version'] = API_VERSION -# swagger_data['info']['title'] = "PVC Client and Provisioner API" -# swagger_data['host'] = "{}:{}".format(config['listen_address'], config['listen_port']) -# return swagger_data -#api.add_resource(API_Doc, '/doc') +# class API_Doc(Resource): +# def get(self): +# """ +# Provide the Swagger API documentation +# --- +# tags: +# - root +# responses: +# 200: +# description: OK +# """ +# swagger_data = swagger(pvc_api.app) +# swagger_data['info']['version'] = API_VERSION +# swagger_data['info']['title'] = "PVC Client and Provisioner API" +# swagger_data['host'] = "{}:{}".format(config['listen_address'], config['listen_port']) +# return swagger_data +# +# +# api.add_resource(API_Doc, '/doc') + # /login class API_Login(Resource): @@ -291,11 +293,14 @@ class API_Login(Resource): if any(token for token in config['auth_tokens'] if flask.request.values['token'] in token['token']): flask.session['token'] = flask.request.form['token'] - return { "message": "Authentication successful" }, 200 + return {"message": "Authentication successful"}, 200 else: - { "message": "Authentication failed" }, 401 + {"message": "Authentication failed"}, 401 + + api.add_resource(API_Login, '/login') + # /logout class API_Logout(Resource): def post(self): @@ -317,9 +322,12 @@ class API_Logout(Resource): return flask.redirect(Api.url_for(api, API_Root)) flask.session.pop('token', None) - return { "message": "Deauthentication successful" }, 200 + return {"message": "Deauthentication successful"}, 200 + + api.add_resource(API_Logout, '/logout') + # /initialize class API_Initialize(Resource): @Authenticator @@ -344,11 +352,14 @@ class API_Initialize(Resource): description: Bad request """ if api_helper.initialize_cluster(): - return { "message": "Successfully initialized a new PVC cluster" }, 200 + return {"message": "Successfully initialized a new PVC cluster"}, 200 else: - return { "message": "PVC cluster already initialized" }, 400 + return {"message": "PVC cluster already initialized"}, 400 + + api.add_resource(API_Initialize, '/initialize') + # /status class API_Status(Resource): @Authenticator @@ -429,7 +440,7 @@ class API_Status(Resource): return api_helper.cluster_status() @RequestParser([ - { 'name': 'state', 'choices': ('true', 'false'), 'required': True, 'helpmsg': "A valid state must be specified." } + {'name': 'state', 'choices': ('true', 'false'), 'required': True, 'helpmsg': "A valid state must be specified."} ]) @Authenticator def post(self, reqargs): @@ -458,6 +469,7 @@ class API_Status(Resource): """ return api_helper.cluster_maintenance(reqargs.get('state', 'false')) + api.add_resource(API_Status, '/status') @@ -468,10 +480,10 @@ api.add_resource(API_Status, '/status') # /node class API_Node_Root(Resource): @RequestParser([ - { 'name': 'limit' }, - { 'name': 'daemon_state' }, - { 'name': 'coordinator_state' }, - { 'name': 'domain_state' } + {'name': 'limit'}, + {'name': 'daemon_state'}, + {'name': 'coordinator_state'}, + {'name': 'domain_state'} ]) @Authenticator def get(self, reqargs): @@ -581,8 +593,11 @@ class API_Node_Root(Resource): coordinator_state=reqargs.get('coordinator_state', None), domain_state=reqargs.get('domain_state', None) ) + + api.add_resource(API_Node_Root, '/node') + # /node/ class API_Node_Element(Resource): @Authenticator @@ -604,8 +619,11 @@ class API_Node_Element(Resource): id: Message """ return api_helper.node_list(node, is_fuzzy=False) + + api.add_resource(API_Node_Element, '/node/') + # /node//daemon-state class API_Node_DaemonState(Resource): @Authenticator @@ -635,8 +653,11 @@ class API_Node_DaemonState(Resource): id: Message """ return api_helper.node_daemon_state(node) + + api.add_resource(API_Node_DaemonState, '/node//daemon-state') + # /node//coordinator-state class API_Node_CoordinatorState(Resource): @Authenticator @@ -668,7 +689,7 @@ class API_Node_CoordinatorState(Resource): return api_helper.node_coordinator_state(node) @RequestParser([ - { 'name': 'state', 'choices': ('primary', 'secondary'), 'helptext': "A valid state must be specified", 'required': True } + {'name': 'state', 'choices': ('primary', 'secondary'), 'helptext': "A valid state must be specified", 'required': True} ]) @Authenticator def post(self, node, reqargs): @@ -703,8 +724,11 @@ class API_Node_CoordinatorState(Resource): if reqargs['state'] == 'secondary': return api_helper.node_secondary(node) abort(400) + + api.add_resource(API_Node_CoordinatorState, '/node//coordinator-state') + # /node//domain-state class API_Node_DomainState(Resource): @Authenticator @@ -736,8 +760,8 @@ class API_Node_DomainState(Resource): return api_helper.node_domain_state(node) @RequestParser([ - { 'name': 'state', 'choices': ('ready', 'flush'), 'helptext': "A valid state must be specified", 'required': True }, - { 'name': 'wait' } + {'name': 'state', 'choices': ('ready', 'flush'), 'helptext': "A valid state must be specified", 'required': True}, + {'name': 'wait'} ]) @Authenticator def post(self, node, reqargs): @@ -776,6 +800,8 @@ class API_Node_DomainState(Resource): if reqargs['state'] == 'ready': return api_helper.node_ready(node, bool(strtobool(reqargs.get('wait', 'false')))) abort(400) + + api.add_resource(API_Node_DomainState, '/node//domain-state') @@ -786,9 +812,9 @@ api.add_resource(API_Node_DomainState, '/node//domain-state') # /vm class API_VM_Root(Resource): @RequestParser([ - { 'name': 'limit' }, - { 'name': 'node' }, - { 'name': 'state' }, + {'name': 'limit'}, + {'name': 'node'}, + {'name': 'state'}, ]) @Authenticator def get(self, reqargs): @@ -1035,12 +1061,12 @@ class API_VM_Root(Resource): ) @RequestParser([ - { 'name': 'limit' }, - { 'name': 'node' }, - { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, - { 'name': 'autostart' }, - { 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" }, - { 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" }, + {'name': 'limit'}, + {'name': 'node'}, + {'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified"}, + {'name': 'autostart'}, + {'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified"}, + {'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified"}, ]) @Authenticator def post(self, reqargs): @@ -1111,8 +1137,11 @@ class API_VM_Root(Resource): bool(strtobool(reqargs.get('autostart', 'false'))), reqargs.get('migration_method', 'none') ) + + api.add_resource(API_VM_Root, '/vm') + # /vm/ class API_VM_Element(Resource): @Authenticator @@ -1136,12 +1165,12 @@ class API_VM_Element(Resource): return api_helper.vm_list(None, None, vm, is_fuzzy=False) @RequestParser([ - { 'name': 'limit' }, - { 'name': 'node' }, - { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, - { 'name': 'autostart' }, - { 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" }, - { 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" }, + {'name': 'limit'}, + {'name': 'node'}, + {'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified"}, + {'name': 'autostart'}, + {'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified"}, + {'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified"}, ]) @Authenticator def post(self, vm, reqargs): @@ -1216,8 +1245,8 @@ class API_VM_Element(Resource): ) @RequestParser([ - { 'name': 'restart' }, - { 'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified" }, + {'name': 'restart'}, + {'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified"}, ]) @Authenticator def put(self, vm, reqargs): @@ -1255,7 +1284,7 @@ class API_VM_Element(Resource): ) @RequestParser([ - { 'name': 'delete_disks' }, + {'name': 'delete_disks'}, ]) @Authenticator def delete(self, vm, reqargs): @@ -1291,8 +1320,11 @@ class API_VM_Element(Resource): return api_helper.vm_remove(vm) else: return api_helper.vm_undefine(vm) + + api.add_resource(API_VM_Element, '/vm/') + # /vm//meta class API_VM_Metadata(Resource): @Authenticator @@ -1335,11 +1367,11 @@ class API_VM_Metadata(Resource): return api_helper.get_vm_meta(vm) @RequestParser([ - { 'name': 'limit' }, - { 'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified" }, - { 'name': 'autostart' }, - { 'name': 'profile' }, - { 'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified" }, + {'name': 'limit'}, + {'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms'), 'helptext': "A valid selector must be specified"}, + {'name': 'autostart'}, + {'name': 'profile'}, + {'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified"}, ]) @Authenticator def post(self, vm, reqargs): @@ -1404,8 +1436,11 @@ class API_VM_Metadata(Resource): reqargs.get('profile', None), reqargs.get('migration_method', None) ) + + api.add_resource(API_VM_Metadata, '/vm//meta') + # /vm//state') + # /vm//node class API_VM_Node(Resource): @Authenticator @@ -1525,11 +1563,11 @@ class API_VM_Node(Resource): return api_helper.vm_node(vm) @RequestParser([ - { 'name': 'action', 'choices': ('migrate', 'unmigrate', 'move'), 'helptext': "A valid action must be specified", 'required': True }, - { 'name': 'node' }, - { 'name': 'force' }, - { 'name': 'wait' }, - { 'name': 'force_live' } + {'name': 'action', 'choices': ('migrate', 'unmigrate', 'move'), 'helptext': "A valid action must be specified", 'required': True}, + {'name': 'node'}, + {'name': 'force'}, + {'name': 'wait'}, + {'name': 'force_live'} ]) @Authenticator def post(self, vm, reqargs): @@ -1589,8 +1627,11 @@ class API_VM_Node(Resource): if action == 'unmigrate': return api_helper.vm_unmigrate(vm, wait, force_live) abort(400) + + api.add_resource(API_VM_Node, '/vm//node') + # /vm//locks class API_VM_Locks(Resource): @Authenticator @@ -1613,12 +1654,15 @@ class API_VM_Locks(Resource): id: Message """ return api_helper.vm_flush_locks(vm) + + api.add_resource(API_VM_Locks, '/vm//locks') + # /vm//console') @@ -1666,7 +1712,7 @@ api.add_resource(API_VM_Console, '/vm//console') # /network class API_Network_Root(Resource): @RequestParser([ - { 'name': 'limit' } + {'name': 'limit'} ]) @Authenticator def get(self, reqargs): @@ -1749,18 +1795,18 @@ class API_Network_Root(Resource): return api_helper.net_list(reqargs.get('limit', None)) @RequestParser([ - { 'name': 'vni', 'required': True }, - { 'name': 'description', 'required': True }, - { 'name': 'nettype', 'choices': ('managed', 'bridged'), 'helptext': 'A valid nettype must be specified', 'required': True }, - { 'name': 'domain' }, - { 'name': 'name_servers' }, - { 'name': 'ip4_network' }, - { 'name': 'ip4_gateway' }, - { 'name': 'ip6_network' }, - { 'name': 'ip6_gateway' }, - { 'name': 'dhcp4' }, - { 'name': 'dhcp4_start' }, - { 'name': 'dhcp4_end' } + {'name': 'vni', 'required': True}, + {'name': 'description', 'required': True}, + {'name': 'nettype', 'choices': ('managed', 'bridged'), 'helptext': 'A valid nettype must be specified', 'required': True}, + {'name': 'domain'}, + {'name': 'name_servers'}, + {'name': 'ip4_network'}, + {'name': 'ip4_gateway'}, + {'name': 'ip6_network'}, + {'name': 'ip6_gateway'}, + {'name': 'dhcp4'}, + {'name': 'dhcp4_start'}, + {'name': 'dhcp4_end'} ]) @Authenticator def post(self, reqargs): @@ -1854,8 +1900,11 @@ class API_Network_Root(Resource): reqargs.get('dhcp4_start', None), reqargs.get('dhcp4_end', None), ) + + api.add_resource(API_Network_Root, '/network') + # /network/ class API_Network_Element(Resource): @Authenticator @@ -1879,17 +1928,17 @@ class API_Network_Element(Resource): return api_helper.net_list(vni, is_fuzzy=False) @RequestParser([ - { 'name': 'description', 'required': True }, - { 'name': 'nettype', 'choices': ('managed', 'bridged'), 'helptext': 'A valid nettype must be specified', 'required': True }, - { 'name': 'domain' }, - { 'name': 'name_servers' }, - { 'name': 'ip4_network' }, - { 'name': 'ip4_gateway' }, - { 'name': 'ip6_network' }, - { 'name': 'ip6_gateway' }, - { 'name': 'dhcp4' }, - { 'name': 'dhcp4_start' }, - { 'name': 'dhcp4_end' } + {'name': 'description', 'required': True}, + {'name': 'nettype', 'choices': ('managed', 'bridged'), 'helptext': 'A valid nettype must be specified', 'required': True}, + {'name': 'domain'}, + {'name': 'name_servers'}, + {'name': 'ip4_network'}, + {'name': 'ip4_gateway'}, + {'name': 'ip6_network'}, + {'name': 'ip6_gateway'}, + {'name': 'dhcp4'}, + {'name': 'dhcp4_start'}, + {'name': 'dhcp4_end'} ]) @Authenticator def post(self, vni, reqargs): @@ -1980,16 +2029,16 @@ class API_Network_Element(Resource): ) @RequestParser([ - { 'name': 'description' }, - { 'name': 'domain' }, - { 'name': 'name_servers' }, - { 'name': 'ip4_network' }, - { 'name': 'ip4_gateway' }, - { 'name': 'ip6_network' }, - { 'name': 'ip6_gateway' }, - { 'name': 'dhcp4' }, - { 'name': 'dhcp4_start' }, - { 'name': 'dhcp4_end' } + {'name': 'description'}, + {'name': 'domain'}, + {'name': 'name_servers'}, + {'name': 'ip4_network'}, + {'name': 'ip4_gateway'}, + {'name': 'ip6_network'}, + {'name': 'ip6_gateway'}, + {'name': 'dhcp4'}, + {'name': 'dhcp4_start'}, + {'name': 'dhcp4_end'} ]) @Authenticator def put(self, vni, reqargs): @@ -2100,13 +2149,16 @@ class API_Network_Element(Resource): id: Message """ return api_helper.net_remove(vni) + + api.add_resource(API_Network_Element, '/network/') + # /network//lease class API_Network_Lease_Root(Resource): @RequestParser([ - { 'name': 'limit' }, - { 'name': 'static' } + {'name': 'limit'}, + {'name': 'static'} ]) @Authenticator def get(self, vni, reqargs): @@ -2169,9 +2221,9 @@ class API_Network_Lease_Root(Resource): ) @RequestParser([ - { 'name': 'macaddress', 'required': True }, - { 'name': 'ipaddress', 'required': True }, - { 'name': 'hostname' } + {'name': 'macaddress', 'required': True}, + {'name': 'ipaddress', 'required': True}, + {'name': 'hostname'} ]) @Authenticator def post(self, vni, reqargs): @@ -2219,8 +2271,11 @@ class API_Network_Lease_Root(Resource): reqargs.get('macaddress', None), reqargs.get('hostname', None) ) + + api.add_resource(API_Network_Lease_Root, '/network//lease') + # /network//lease/{mac} class API_Network_Lease_Element(Resource): @Authenticator @@ -2272,11 +2327,11 @@ class API_Network_Lease_Element(Resource): ) @RequestParser([ - { 'name': 'ipaddress', 'required': True }, - { 'name': 'hostname' } + {'name': 'ipaddress', 'required': True}, + {'name': 'hostname'} ]) @Authenticator - def post(self, vni, mac): + def post(self, vni, mac, reqargs): """ Create a new static DHCP lease {mac} in network {vni} --- @@ -2345,13 +2400,16 @@ class API_Network_Lease_Element(Resource): vni, mac ) + + api.add_resource(API_Network_Lease_Element, '/network//lease/') + # /network//acl class API_Network_ACL_Root(Resource): @RequestParser([ - { 'name': 'limit' }, - { 'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified." } + {'name': 'limit'}, + {'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified."} ]) @Authenticator def get(self, vni, reqargs): @@ -2413,10 +2471,10 @@ class API_Network_ACL_Root(Resource): ) @RequestParser([ - { 'name': 'description', 'required': True, 'helpmsg': "A whitespace-free description must be specified." }, - { 'name': 'rule', 'required': True, 'helpmsg': "A rule must be specified." }, - { 'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified." }, - { 'name': 'order' } + {'name': 'description', 'required': True, 'helpmsg': "A whitespace-free description must be specified."}, + {'name': 'rule', 'required': True, 'helpmsg': "A rule must be specified."}, + {'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified."}, + {'name': 'order'} ]) @Authenticator def post(self, vni, reqargs): @@ -2467,8 +2525,11 @@ class API_Network_ACL_Root(Resource): reqargs.get('rule', None), reqargs.get('order', None) ) + + api.add_resource(API_Network_ACL_Root, '/network//acl') + # /network//acl/ class API_Network_ACL_Element(Resource): @Authenticator @@ -2502,9 +2563,9 @@ class API_Network_ACL_Element(Resource): ) @RequestParser([ - { 'name': 'rule', 'required': True, 'helpmsg': "A rule must be specified." }, - { 'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified." }, - { 'name': 'order' } + {'name': 'rule', 'required': True, 'helpmsg': "A rule must be specified."}, + {'name': 'direction', 'choices': ('in', 'out'), 'helpmsg': "A valid direction must be specified."}, + {'name': 'order'} ]) @Authenticator def post(self, vni, description, reqargs): @@ -2574,6 +2635,8 @@ class API_Network_ACL_Element(Resource): vni, description ) + + api.add_resource(API_Network_ACL_Element, '/network//acl/') @@ -2590,15 +2653,21 @@ class API_Storage_Root(Resource): @Authenticator def get(self): pass + + api.add_resource(API_Storage_Root, '/storage') + # /storage/ceph class API_Storage_Ceph_Root(Resource): @Authenticator def get(self): pass + + api.add_resource(API_Storage_Ceph_Root, '/storage/ceph') + # /storage/ceph/status class API_Storage_Ceph_Status(Resource): @Authenticator @@ -2625,8 +2694,11 @@ class API_Storage_Ceph_Status(Resource): description: The raw output data """ return api_helper.ceph_status() + + api.add_resource(API_Storage_Ceph_Status, '/storage/ceph/status') + # /storage/ceph/utilization class API_Storage_Ceph_Utilization(Resource): @Authenticator @@ -2653,12 +2725,15 @@ class API_Storage_Ceph_Utilization(Resource): description: The raw output data """ return api_helper.ceph_util() + + api.add_resource(API_Storage_Ceph_Utilization, '/storage/ceph/utilization') + # /storage/ceph/benchmark class API_Storage_Ceph_Benchmark(Resource): @RequestParser([ - { 'name': 'job' } + {'name': 'job'} ]) @Authenticator def get(self, reqargs): @@ -2780,7 +2855,7 @@ class API_Storage_Ceph_Benchmark(Resource): return api_benchmark.list_benchmarks(reqargs.get('job', None)) @RequestParser([ - { 'name': 'pool', 'required': True, 'helpmsg': "A valid pool must be specified." }, + {'name': 'pool', 'required': True, 'helpmsg': "A valid pool must be specified."}, ]) @Authenticator def post(self, reqargs): @@ -2805,19 +2880,22 @@ class API_Storage_Ceph_Benchmark(Resource): # Verify that the pool is valid _list, code = api_helper.ceph_pool_list(reqargs.get('pool', None), is_fuzzy=False) if code != 200: - return { 'message': 'Pool "{}" is not valid.'.format(reqargs.get('pool')) }, 400 + return {'message': 'Pool "{}" is not valid.'.format(reqargs.get('pool'))}, 400 task = run_benchmark.delay( reqargs.get('pool', None) ) - return { "task_id": task.id }, 202, { 'Location': Api.url_for(api, API_Storage_Ceph_Benchmark, task_id=task.id) } + return {"task_id": task.id}, 202, {'Location': Api.url_for(api, API_Storage_Ceph_Benchmark, task_id=task.id)} + + api.add_resource(API_Storage_Ceph_Benchmark, '/storage/ceph/benchmark') + # /storage/ceph/option class API_Storage_Ceph_Option(Resource): @RequestParser([ - { 'name': 'option', 'required': True, 'helpmsg': "A valid option must be specified." }, - { 'name': 'action', 'required': True, 'choices': ('set', 'unset'), 'helpmsg': "A valid action must be specified." }, + {'name': 'option', 'required': True, 'helpmsg': "A valid option must be specified."}, + {'name': 'action', 'required': True, 'choices': ('set', 'unset'), 'helpmsg': "A valid action must be specified."}, ]) @Authenticator def post(self, reqargs): @@ -2857,12 +2935,15 @@ class API_Storage_Ceph_Option(Resource): if reqargs.get('action') == 'unset': return api_helper.ceph_osd_unset(reqargs.get('option')) abort(400) + + api.add_resource(API_Storage_Ceph_Option, '/storage/ceph/option') + # /storage/ceph/osd class API_Storage_Ceph_OSD_Root(Resource): @RequestParser([ - { 'name': 'limit' }, + {'name': 'limit'}, ]) @Authenticator def get(self, reqargs): @@ -2955,9 +3036,9 @@ class API_Storage_Ceph_OSD_Root(Resource): ) @RequestParser([ - { 'name': 'node', 'required': True, 'helpmsg': "A valid node must be specified." }, - { 'name': 'device', 'required': True, 'helpmsg': "A valid device must be specified." }, - { 'name': 'weight', 'required': True, 'helpmsg': "An OSD weight must be specified." }, + {'name': 'node', 'required': True, 'helpmsg': "A valid node must be specified."}, + {'name': 'device', 'required': True, 'helpmsg': "A valid device must be specified."}, + {'name': 'weight', 'required': True, 'helpmsg': "An OSD weight must be specified."}, ]) @Authenticator def post(self, reqargs): @@ -3000,8 +3081,11 @@ class API_Storage_Ceph_OSD_Root(Resource): reqargs.get('device', None), reqargs.get('weight', None) ) + + api.add_resource(API_Storage_Ceph_OSD_Root, '/storage/ceph/osd') + # /storage/ceph/osd/ class API_Storage_Ceph_OSD_Element(Resource): @Authenticator @@ -3022,7 +3106,7 @@ class API_Storage_Ceph_OSD_Element(Resource): ) @RequestParser([ - { 'name': 'yes-i-really-mean-it', 'required': True, 'helpmsg': "Please confirm that 'yes-i-really-mean-it'." } + {'name': 'yes-i-really-mean-it', 'required': True, 'helpmsg': "Please confirm that 'yes-i-really-mean-it'."} ]) @Authenticator def delete(self, osdid, reqargs): @@ -3059,8 +3143,11 @@ class API_Storage_Ceph_OSD_Element(Resource): return api_helper.ceph_osd_remove( osdid ) + + api.add_resource(API_Storage_Ceph_OSD_Element, '/storage/ceph/osd/') + # /storage/ceph/osd//state class API_Storage_Ceph_OSD_State(Resource): @Authenticator @@ -3085,7 +3172,7 @@ class API_Storage_Ceph_OSD_State(Resource): ) @RequestParser([ - { 'name': 'state', 'choices': ('in', 'out'), 'required': True, 'helpmsg': "A valid state must be specified." }, + {'name': 'state', 'choices': ('in', 'out'), 'required': True, 'helpmsg': "A valid state must be specified."}, ]) @Authenticator def post(self, osdid, reqargs): @@ -3116,12 +3203,15 @@ class API_Storage_Ceph_OSD_State(Resource): osdid ) abort(400) + + api.add_resource(API_Storage_Ceph_OSD_State, '/storage/ceph/osd//state') + # /storage/ceph/pool class API_Storage_Ceph_Pool_Root(Resource): @RequestParser([ - { 'name': 'limit' } + {'name': 'limit'} ]) @Authenticator def get(self, reqargs): @@ -3202,9 +3292,9 @@ class API_Storage_Ceph_Pool_Root(Resource): ) @RequestParser([ - { 'name': 'pool', 'required': True, 'helpmsg': "A pool name must be specified." }, - { 'name': 'pgs', 'required': True, 'helpmsg': "A placement group count must be specified." }, - { 'name': 'replcfg', 'required': True, 'helpmsg': "A valid replication configuration must be specified." } + {'name': 'pool', 'required': True, 'helpmsg': "A pool name must be specified."}, + {'name': 'pgs', 'required': True, 'helpmsg': "A placement group count must be specified."}, + {'name': 'replcfg', 'required': True, 'helpmsg': "A valid replication configuration must be specified."} ]) @Authenticator def post(self, reqargs): @@ -3246,8 +3336,11 @@ class API_Storage_Ceph_Pool_Root(Resource): reqargs.get('pgs', None), reqargs.get('replcfg', None) ) + + api.add_resource(API_Storage_Ceph_Pool_Root, '/storage/ceph/pool') + # /storage/ceph/pool/ class API_Storage_Ceph_Pool_Element(Resource): @Authenticator @@ -3268,17 +3361,17 @@ class API_Storage_Ceph_Pool_Element(Resource): type: object id: Message """ - return api_helper,ceph_pool_list( + return api_helper, api_helper.ceph_pool_list( pool, is_fuzzy=False ) @RequestParser([ - { 'name': 'pgs', 'required': True, 'helpmsg': "A placement group count must be specified." }, - { 'name': 'replcfg', 'required': True, 'helpmsg': "A valid replication configuration must be specified." } + {'name': 'pgs', 'required': True, 'helpmsg': "A placement group count must be specified."}, + {'name': 'replcfg', 'required': True, 'helpmsg': "A valid replication configuration must be specified."} ]) @Authenticator - def post(self, pool): + def post(self, pool, reqargs): """ Create a new Ceph pool {pool} --- @@ -3319,7 +3412,7 @@ class API_Storage_Ceph_Pool_Element(Resource): ) @RequestParser([ - { 'name': 'yes-i-really-mean-it', 'required': True, 'helpmsg': "Please confirm that 'yes-i-really-mean-it'." } + {'name': 'yes-i-really-mean-it', 'required': True, 'helpmsg': "Please confirm that 'yes-i-really-mean-it'."} ]) @Authenticator def delete(self, pool, reqargs): @@ -3355,13 +3448,16 @@ class API_Storage_Ceph_Pool_Element(Resource): return api_helper.ceph_pool_remove( pool ) + + api.add_resource(API_Storage_Ceph_Pool_Element, '/storage/ceph/pool/') + # /storage/ceph/volume class API_Storage_Ceph_Volume_Root(Resource): @RequestParser([ - { 'name': 'limit' }, - { 'name': 'pool' } + {'name': 'limit'}, + {'name': 'pool'} ]) @Authenticator def get(self, reqargs): @@ -3460,9 +3556,9 @@ class API_Storage_Ceph_Volume_Root(Resource): ) @RequestParser([ - { 'name': 'volume', 'required': True, 'helpmsg': "A volume name must be specified." }, - { 'name': 'pool', 'required': True, 'helpmsg': "A valid pool name must be specified." }, - { 'name': 'size', 'required': True, 'helpmsg': "A volume size in bytes (or with k/M/G/T suffix) must be specified." } + {'name': 'volume', 'required': True, 'helpmsg': "A volume name must be specified."}, + {'name': 'pool', 'required': True, 'helpmsg': "A valid pool name must be specified."}, + {'name': 'size', 'required': True, 'helpmsg': "A volume size in bytes (or with k/M/G/T suffix) must be specified."} ]) @Authenticator def post(self, reqargs): @@ -3504,8 +3600,11 @@ class API_Storage_Ceph_Volume_Root(Resource): reqargs.get('volume', None), reqargs.get('size', None) ) + + api.add_resource(API_Storage_Ceph_Volume_Root, '/storage/ceph/volume') + # /storage/ceph/volume// class API_Storage_Ceph_Volume_Element(Resource): @Authenticator @@ -3533,7 +3632,7 @@ class API_Storage_Ceph_Volume_Element(Resource): ) @RequestParser([ - { 'name': 'size', 'required': True, 'helpmsg': "A volume size in bytes (or with k/M/G/T suffix) must be specified." } + {'name': 'size', 'required': True, 'helpmsg': "A volume size in bytes (or with k/M/G/T suffix) must be specified."} ]) @Authenticator def post(self, pool, volume, reqargs): @@ -3572,8 +3671,8 @@ class API_Storage_Ceph_Volume_Element(Resource): ) @RequestParser([ - { 'name': 'new_size' }, - { 'name': 'new_name' } + {'name': 'new_size'}, + {'name': 'new_name'} ]) @Authenticator def put(self, pool, volume, reqargs): @@ -3611,7 +3710,7 @@ class API_Storage_Ceph_Volume_Element(Resource): id: Message """ if reqargs.get('new_size', None) and reqargs.get('new_name', None): - return { "message": "Can only perform one modification at once" }, 400 + return {"message": "Can only perform one modification at once"}, 400 if reqargs.get('new_size', None): return api_helper.ceph_volume_resize( @@ -3625,7 +3724,7 @@ class API_Storage_Ceph_Volume_Element(Resource): volume, reqargs.get('new_name') ) - return { "message": "At least one modification must be specified" }, 400 + return {"message": "At least one modification must be specified"}, 400 @Authenticator def delete(self, pool, volume): @@ -3651,12 +3750,15 @@ class API_Storage_Ceph_Volume_Element(Resource): pool, volume ) + + api.add_resource(API_Storage_Ceph_Volume_Element, '/storage/ceph/volume//') + # /storage/ceph/volume///clone class API_Storage_Ceph_Volume_Element_Clone(Resource): @RequestParser([ - { 'name': 'new_volume', 'required': True, 'helpmsg': "A new volume name must be specified." } + {'name': 'new_volume', 'required': True, 'helpmsg': "A new volume name must be specified."} ]) @Authenticator def post(self, pool, volume, reqargs): @@ -3693,12 +3795,15 @@ class API_Storage_Ceph_Volume_Element_Clone(Resource): reqargs.get('new_volume', None), volume ) + + api.add_resource(API_Storage_Ceph_Volume_Element_Clone, '/storage/ceph/volume///clone') + # /storage/ceph/volume///upload class API_Storage_Ceph_Volume_Element_Upload(Resource): @RequestParser([ - { 'name': 'image_format', 'required': True, 'helpmsg': "A source image format must be specified." } + {'name': 'image_format', 'required': True, 'helpmsg': "A source image format must be specified."} ]) @Authenticator def post(self, pool, volume, reqargs): @@ -3744,14 +3849,17 @@ class API_Storage_Ceph_Volume_Element_Upload(Resource): volume, reqargs.get('image_format', None) ) + + api.add_resource(API_Storage_Ceph_Volume_Element_Upload, '/storage/ceph/volume///upload') + # /storage/ceph/snapshot class API_Storage_Ceph_Snapshot_Root(Resource): @RequestParser([ - { 'name': 'pool' }, - { 'name': 'volume' }, - { 'name': 'limit' }, + {'name': 'pool'}, + {'name': 'volume'}, + {'name': 'limit'}, ]) @Authenticator def get(self, reqargs): @@ -3805,9 +3913,9 @@ class API_Storage_Ceph_Snapshot_Root(Resource): ) @RequestParser([ - { 'name': 'snapshot', 'required': True, 'helpmsg': "A snapshot name must be specified." }, - { 'name': 'volume', 'required': True, 'helpmsg': "A volume name must be specified." }, - { 'name': 'pool', 'required': True, 'helpmsg': "A pool name must be specified." } + {'name': 'snapshot', 'required': True, 'helpmsg': "A snapshot name must be specified."}, + {'name': 'volume', 'required': True, 'helpmsg': "A volume name must be specified."}, + {'name': 'pool', 'required': True, 'helpmsg': "A pool name must be specified."} ]) @Authenticator def post(self, reqargs): @@ -3849,8 +3957,11 @@ class API_Storage_Ceph_Snapshot_Root(Resource): reqargs.get('volume', None), reqargs.get('snapshot', None) ) + + api.add_resource(API_Storage_Ceph_Snapshot_Root, '/storage/ceph/snapshot') + # /storage/ceph/snapshot/// class API_Storage_Ceph_Snapshot_Element(Resource): @Authenticator @@ -3925,7 +4036,7 @@ class API_Storage_Ceph_Snapshot_Element(Resource): ) @RequestParser([ - { 'name': 'new_name', 'required': True, 'helpmsg': "A new name must be specified." } + {'name': 'new_name', 'required': True, 'helpmsg': "A new name must be specified."} ]) @Authenticator def put(self, pool, volume, snapshot, reqargs): @@ -3989,6 +4100,8 @@ class API_Storage_Ceph_Snapshot_Element(Resource): volume, snapshot ) + + api.add_resource(API_Storage_Ceph_Snapshot_Element, '/storage/ceph/snapshot///') @@ -4004,12 +4117,15 @@ class API_Provisioner_Root(Resource): Unused endpoint """ abort(404) + + api.add_resource(API_Provisioner_Root, '/provisioner') + # /provisioner/template class API_Provisioner_Template_Root(Resource): @RequestParser([ - { 'name': 'limit' } + {'name': 'limit'} ]) @Authenticator def get(self, reqargs): @@ -4050,12 +4166,15 @@ class API_Provisioner_Template_Root(Resource): return api_provisioner.template_list( reqargs.get('limit', None) ) + + api.add_resource(API_Provisioner_Template_Root, '/provisioner/template') + # /provisioner/template/system class API_Provisioner_Template_System_Root(Resource): @RequestParser([ - { 'name': 'limit' } + {'name': 'limit'} ]) @Authenticator def get(self, reqargs): @@ -4121,16 +4240,16 @@ class API_Provisioner_Template_System_Root(Resource): ) @RequestParser([ - { 'name': 'name', 'required': True, 'helpmsg': "A name must be specified." }, - { 'name': 'vcpus', 'required': True, 'helpmsg': "A vcpus value must be specified." }, - { 'name': 'vram', 'required': True, 'helpmsg': "A vram value in MB must be specified." }, - { 'name': 'serial', 'required': True, 'helpmsg': "A serial value must be specified." }, - { 'name': 'vnc', 'required': True, 'helpmsg': "A vnc value must be specified." }, - { 'name': 'vnc_bind' }, - { 'name': 'node_limit' }, - { 'name': 'node_selector' }, - { 'name': 'node_autostart' }, - { 'name': 'migration_method' } + {'name': 'name', 'required': True, 'helpmsg': "A name must be specified."}, + {'name': 'vcpus', 'required': True, 'helpmsg': "A vcpus value must be specified."}, + {'name': 'vram', 'required': True, 'helpmsg': "A vram value in MB must be specified."}, + {'name': 'serial', 'required': True, 'helpmsg': "A serial value must be specified."}, + {'name': 'vnc', 'required': True, 'helpmsg': "A vnc value must be specified."}, + {'name': 'vnc_bind'}, + {'name': 'node_limit'}, + {'name': 'node_selector'}, + {'name': 'node_autostart'}, + {'name': 'migration_method'} ]) @Authenticator def post(self, reqargs): @@ -4205,12 +4324,12 @@ class API_Provisioner_Template_System_Root(Resource): # Validate arguments try: vcpus = int(reqargs.get('vcpus')) - except: - return { "message": "A vcpus value must be an integer" }, 400 + except Exception: + return {"message": "A vcpus value must be an integer"}, 400 try: vram = int(reqargs.get('vram')) - except: - return { "message": "A vram value must be an integer" }, 400 + except Exception: + return {"message": "A vram value must be an integer"}, 400 # Cast boolean arguments if bool(strtobool(reqargs.get('serial', 'false'))): serial = True @@ -4239,8 +4358,11 @@ class API_Provisioner_Template_System_Root(Resource): node_autostart, reqargs.get('migration_method', None), ) + + api.add_resource(API_Provisioner_Template_System_Root, '/provisioner/template/system') + # /provisioner/template/system/