From c638bdeaee235e2f9f2c82a1360d3371db39ba31 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 6 Jul 2019 01:48:45 -0400 Subject: [PATCH] Add configuration file, authentication, pywsgi --- client-api/api_lib/pvcapi.py | 92 ++++++++++++------------ client-api/pvc-api.py | 124 ++++++++++++++++++++++++++++++--- client-api/pvc-api.sample.yaml | 25 +++++++ client-api/pvc-api.service | 2 +- debian/control | 2 +- debian/pvc-client-api.install | 1 + debian/pvc-client-api.postinst | 10 +++ debian/pvc-daemon.postinst | 3 + 8 files changed, 202 insertions(+), 57 deletions(-) create mode 100644 client-api/pvc-api.sample.yaml diff --git a/client-api/api_lib/pvcapi.py b/client-api/api_lib/pvcapi.py index 1a303680..bbf55103 100755 --- a/client-api/api_lib/pvcapi.py +++ b/client-api/api_lib/pvcapi.py @@ -29,8 +29,6 @@ import client_lib.vm as pvc_vm import client_lib.network as pvc_network import client_lib.ceph as pvc_ceph -zk_host = "hv1:2181,hv2:2181,hv3:2181" - # # Node functions # @@ -38,7 +36,7 @@ def node_list(limit=None): """ Return a list of nodes with limit LIMIT. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_node.get_list(zk_conn, limit) if retflag: retcode = 200 @@ -52,7 +50,7 @@ def node_secondary(node): """ Take NODE out of primary router mode. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_node.secondary_node(zk_conn, node) if retflag: retcode = 200 @@ -69,7 +67,7 @@ def node_primary(node): """ Set NODE to primary router mode. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_node.primary_node(zk_conn, node) if retflag: retcode = 200 @@ -86,7 +84,7 @@ def node_flush(node): """ Flush NODE of running VMs. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_node.flush_node(zk_conn, node, False) if retflag: retcode = 200 @@ -103,7 +101,7 @@ def node_ready(node): """ Restore NODE to active service. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_node.ready_node(zk_conn, node) if retflag: retcode = 200 @@ -123,7 +121,7 @@ def vm_list(node=None, state=None, limit=None, is_fuzzy=True): """ Return a list of VMs with limit LIMIT. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_vm.get_list(zk_conn, node, state, limit, is_fuzzy) if retflag: if retdata: @@ -150,7 +148,7 @@ def vm_define(name, xml, node, selector): """ Define a VM from Libvirt XML in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.define_vm(zk_conn, xml, node, selector) if retflag: retcode = 200 @@ -167,7 +165,7 @@ def vm_modify(name, restart, xml): """ Modify a VM Libvirt XML in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.modify_vm(zk_conn, name, restart, xml) if retflag: retcode = 200 @@ -184,7 +182,7 @@ def vm_undefine(name): """ Undefine a VM from the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.undefine_vm(zk_conn, name) if retflag: retcode = 200 @@ -201,7 +199,7 @@ def vm_remove(name): """ Remove a VM from the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.remove_vm(zk_conn, name) if retflag: retcode = 200 @@ -218,7 +216,7 @@ def vm_dump(name): """ Dump a VM Libvirt XML configuration. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_vm.dump_vm(zk_conn, name) if retflag: retcode = 200 @@ -232,7 +230,7 @@ def vm_start(name): """ Start a VM in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.start_vm(zk_conn, name) if retflag: retcode = 200 @@ -249,7 +247,7 @@ def vm_restart(name): """ Restart a VM in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.restart_vm(zk_conn, name) if retflag: retcode = 200 @@ -266,7 +264,7 @@ def vm_shutdown(name): """ Shutdown a VM in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.shutdown_vm(zk_conn, name) if retflag: retcode = 200 @@ -283,7 +281,7 @@ def vm_stop(name): """ Forcibly stop a VM in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.stop_vm(zk_conn, name) if retflag: retcode = 200 @@ -300,7 +298,7 @@ def vm_move(name, node, selector): """ Move a VM to another node. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.move_vm(zk_conn, name, node, selector) if retflag: retcode = 200 @@ -317,7 +315,7 @@ def vm_migrate(name, node, selector, flag_force): """ Temporarily migrate a VM to another node. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.migrate_vm(zk_conn, name, node, selector, flag_force) if retflag: retcode = 200 @@ -334,7 +332,7 @@ def vm_unmigrate(name): """ Unmigrate a migrated VM. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_vm.unmigrate_vm(zk_conn, name) if retflag: retcode = 200 @@ -354,7 +352,7 @@ def net_list(limit=None): """ Return a list of client networks with limit LIMIT. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_network.get_list(zk_conn, limit) if retflag: retcode = 200 @@ -370,7 +368,7 @@ def net_add(vni, description, nettype, domain, """ Add a virtual client network to the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.add_network(zk_conn, vni, description, nettype, domain, ip4_network, ip4_gateway, ip6_network, ip6_gateway, dhcp4_flag, dhcp4_start, dhcp4_end) @@ -391,7 +389,7 @@ def net_modify(vni, description, nettype, domain, """ Modify a virtual client network in the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.add_network(zk_conn, vni, description, nettype, domain, ip4_network, ip4_gateway, ip6_network, ip6_gateway, dhcp4_flag, dhcp4_start, dhcp4_end) @@ -410,7 +408,7 @@ def net_remove(description): """ Remove a virtual client network from the PVC cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.remove_network(zk_conn, description) if retflag: retcode = 200 @@ -427,7 +425,7 @@ def net_dhcp_list(network, limit=None, static=False): """ Return a list of DHCP leases in network NETWORK with limit LIMIT. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_network.get_list_dhcp(zk_conn, network, limit, static) if retflag: retcode = 200 @@ -441,7 +439,7 @@ def net_dhcp_add(network, ipaddress, macaddress, hostname): """ Add a static DHCP lease to a virtual client network. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.add_dhcp_reservation(zk_conn, network, ipaddress, macaddress, hostname) if retflag: retcode = 200 @@ -458,7 +456,7 @@ def net_dhcp_remove(network, macaddress): """ Remove a static DHCP lease from a virtual client network. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.remove_dhcp_reservation(zk_conn, network, macaddress) if retflag: retcode = 200 @@ -475,7 +473,7 @@ def net_acl_list(network, limit=None, direction=None): """ Return a list of network ACLs in network NETWORK with limit LIMIT. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.get_list_acl(zk_conn, network, limit, direction) if retflag: retcode = 200 @@ -492,7 +490,7 @@ def net_acl_add(network, direction, description, rule, order): """ Add an ACL to a virtual client network. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.add_acl(zk_conn, network, direction, description, rule, order) if retflag: retcode = 200 @@ -509,7 +507,7 @@ def net_acl_remove(network, direction, description): """ Remove an ACL from a virtual client network. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_network.remove_acl(zk_conn, network, description, direction) if retflag: retcode = 200 @@ -529,7 +527,7 @@ def ceph_status(): """ Get the current Ceph cluster status. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_ceph.get_status(zk_conn) if retflag: retcode = 200 @@ -543,7 +541,7 @@ def ceph_osd_list(limit=None): """ Get the list of OSDs in the Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_ceph.get_list_osd(zk_conn, limit) if retflag: retcode = 200 @@ -557,7 +555,7 @@ def ceph_osd_add(node, device, weight): """ Add a Ceph OSD to the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.add_osd(zk_conn, node, device, weight) if retflag: retcode = 200 @@ -574,7 +572,7 @@ def ceph_osd_remove(osd_id): """ Remove a Ceph OSD from the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.remove_osd(zk_conn, osd_id) if retflag: retcode = 200 @@ -591,7 +589,7 @@ def ceph_osd_in(osd_id): """ Set in a Ceph OSD in the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.in_osd(zk_conn, osd_id) if retflag: retcode = 200 @@ -608,7 +606,7 @@ def ceph_osd_out(osd_id): """ Set out a Ceph OSD in the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.out_osd(zk_conn, osd_id) if retflag: retcode = 200 @@ -625,7 +623,7 @@ def ceph_osd_set(option): """ Set options on a Ceph OSD in the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.set_osd(zk_conn, option) if retflag: retcode = 200 @@ -642,7 +640,7 @@ def ceph_osd_unset(option): """ Unset options on a Ceph OSD in the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.unset_osd(zk_conn, option) if retflag: retcode = 200 @@ -659,7 +657,7 @@ def ceph_pool_list(limit=None): """ Get the list of RBD pools in the Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_ceph.get_list_pool(zk_conn, limit) if retflag: retcode = 200 @@ -673,7 +671,7 @@ def ceph_pool_add(name, pgs): """ Add a Ceph RBD pool to the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.add_pool(zk_conn, name, pgs) if retflag: retcode = 200 @@ -690,7 +688,7 @@ def ceph_pool_remove(name): """ Remove a Ceph RBD pool to the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.remove_pool(zk_conn, name) if retflag: retcode = 200 @@ -707,7 +705,7 @@ def ceph_volume_list(pool=None, limit=None): """ Get the list of RBD volumes in the Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_ceph.get_list_volume(zk_conn, pool, limit) if retflag: retcode = 200 @@ -721,7 +719,7 @@ def ceph_volume_add(pool, name, size): """ Add a Ceph RBD volume to the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.add_volume(zk_conn, pool, name, size) if retflag: retcode = 200 @@ -738,7 +736,7 @@ def ceph_volume_remove(pool, name): """ Remove a Ceph RBD volume to the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.remove_volume(zk_conn, pool, name) if retflag: retcode = 200 @@ -755,7 +753,7 @@ def ceph_volume_snapshot_list(pool=None, volume=None, limit=None): """ Get the list of RBD volume snapshots in the Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retdata = pvc_ceph.get_list_snapshot(zk_conn, pool, volume, limit) if retflag: retcode = 200 @@ -770,7 +768,7 @@ def ceph_volume_snapshot_add(pool, volume, name): Add a Ceph RBD volume snapshot to the PVC Ceph storage cluster. """ return '', 200 - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.add_snapshot(zk_conn, pool, volume, name) if retflag: retcode = 200 @@ -787,7 +785,7 @@ def ceph_volume_snapshot_remove(pool, volume, name): """ Remove a Ceph RBD volume snapshot from the PVC Ceph storage cluster. """ - zk_conn = pvc_common.startZKConnection(zk_host) + zk_conn = pvc_common.startZKConnection(config['zookeeper_uri']) retflag, retmsg = pvc_ceph.remove_snapshot(zk_conn, pool, volume, name) if retflag: retcode = 200 diff --git a/client-api/pvc-api.py b/client-api/pvc-api.py index 1e891f85..15e19332 100755 --- a/client-api/pvc-api.py +++ b/client-api/pvc-api.py @@ -22,22 +22,71 @@ import flask import json +import yaml +import os + +import gevent.pywsgi import api_lib.pvcapi as pvcapi -zk_host = "hv1:2181,hv2:2181,hv3:2181" - api = flask.Flask(__name__) -api.config["DEBUG"] = True +api.config['DEBUG'] = True + +# Parse the configuration file +try: + pvc_config_file = os.environ['PVC_CONFIG_FILE'] +except: + print('Error: The "PVC_CONFIG_FILE" environment variable must be set before starting pvc-api.') + exit(1) + +# Read in the config +try: + with open(pvc_config_file, 'r') as cfgfile: + o_config = yaml.load(cfgfile) +except Exception as e: + print('ERROR: Failed to parse configuration file: {}'.format(e)) + exit(1) + +try: + # Create the config object + config = { + 'zookeeper_uri': o_config['pvc']['zookeeper']['uri'], + 'listen_address': o_config['pvc']['api']['listen_address'], + 'listen_port': int(o_config['pvc']['api']['listen_port']), + 'authentication_key': o_config['pvc']['api']['authentication']['key'] + } + # Set the config object in the pvcapi namespace + pvcapi.config = config +except Exception as e: + print('ERROR: {}.'.format(e)) + exit(1) + +def authenticator(function): + def authenticate(*args, **kwargs): + request_values = flask.request.values + if config['authentication_key']: + if 'key' in request_values: + if request_values['key'] == config['authentication_key']: + return function(*args, **kwargs) + else: + return flask.jsonify({"message":"Authentication required"}), 401 + else: + return flask.jsonify({"message":"Authentication required"}), 401 + else: + return function(*args, **kwargs) + authenticate.__name__ = function.__name__ + return authenticate @api.route('/api/v1', methods=['GET']) +@authenticator def api_root(): - return "PVC API version 1", 209 + return flask.jsonify({"message":"PVC API version 1"}), 209 # # Node endpoints # @api.route('/api/v1/node', methods=['GET']) +@authenticator def api_node(): """ Return a list of nodes with limit LIMIT. @@ -51,6 +100,7 @@ def api_node(): return pvcapi.node_list(limit) @api.route('/api/v1/node/', methods=['GET']) +@authenticator def api_node_info(node): """ Return information about node NODE. @@ -59,6 +109,7 @@ def api_node_info(node): return pvcapi.node_list(node) @api.route('/api/v1/node//secondary', methods=['POST']) +@authenticator def api_node_secondary(node): """ Take NODE out of primary router mode. @@ -66,6 +117,7 @@ def api_node_secondary(node): return pvcapi.node_secondary(node) @api.route('/api/v1/node//primary', methods=['POST']) +@authenticator def api_node_primary(node): """ Set NODE to primary router mode. @@ -73,6 +125,7 @@ def api_node_primary(node): return pvcapi.node_primary(node) @api.route('/api/v1/node//flush', methods=['POST']) +@authenticator def api_node_flush(node): """ Flush NODE of running VMs. @@ -81,6 +134,7 @@ def api_node_flush(node): @api.route('/api/v1/node//unflush', methods=['POST']) @api.route('/api/v1/node//ready', methods=['POST']) +@authenticator def api_node_ready(node): """ Restore NODE to active service. @@ -91,6 +145,7 @@ def api_node_ready(node): # VM endpoints # @api.route('/api/v1/vm', methods=['GET']) +@authenticator def api_vm(): """ Return a list of VMs with limit LIMIT. @@ -116,6 +171,7 @@ def api_vm(): return pvcapi.vm_list(node, state, limit) @api.route('/api/v1/vm/', methods=['GET']) +@authenticator def api_vm_info(vm): """ Get information about a virtual machine named VM. @@ -125,6 +181,7 @@ def api_vm_info(vm): # TODO: #22 #@api.route('/api/v1/vm//add', methods=['POST']) +#@authenticator #def api_vm_add(vm): # """ # Add a virtual machine named VM. @@ -132,12 +189,16 @@ def api_vm_info(vm): # return pvcapi.vm_add() @api.route('/api/v1/vm//define', methods=['POST']) +@authenticator def api_vm_define(vm): """ - Define a virtual machine named VM from Libvirt XML. Send only the Libvirt XML as data. + Define a virtual machine named VM from Libvirt XML. """ - # Get XML from the POST body - libvirt_xml = flask.request.data + # 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 'node' in flask.request.values: @@ -154,6 +215,7 @@ def api_vm_define(vm): return pvcapi.vm_define(vm, libvirt_xml, node, selector) @api.route('/api/v1/vm//modify', methods=['POST']) +@authenticator def api_vm_modify(vm): """ Modify an existing virtual machine named VM from Libvirt XML. @@ -170,6 +232,7 @@ def api_vm_modify(vm): return pvcapi.vm_modify(vm, flag_restart, libvirt_xml) @api.route('/api/v1/vm//undefine', methods=['POST']) +@authenticator def api_vm_undefine(vm): """ Undefine a virtual machine named VM. @@ -177,6 +240,7 @@ def api_vm_undefine(vm): return pvcapi.vm_undefine(vm) @api.route('/api/v1/vm//remove', methods=['POST']) +@authenticator def api_vm_remove(vm): """ Remove a virtual machine named VM including all disks. @@ -184,6 +248,7 @@ def api_vm_remove(vm): return pvcapi.vm_remove(vm) @api.route('/api/v1/vm//dump', methods=['GET']) +@authenticator def api_vm_dump(vm): """ Dump the Libvirt XML configuration of a virtual machine named VM. @@ -191,6 +256,7 @@ def api_vm_dump(vm): return pvcapi.vm_dump(vm) @api.route('/api/v1/vm//start', methods=['POST']) +@authenticator def api_vm_start(vm): """ Start a virtual machine named VM. @@ -198,6 +264,7 @@ def api_vm_start(vm): return pvcapi.vm_start(vm) @api.route('/api/v1/vm//restart', methods=['POST']) +@authenticator def api_vm_restart(vm): """ Restart a virtual machine named VM. @@ -205,6 +272,7 @@ def api_vm_restart(vm): return pvcapi.vm_restart(vm) @api.route('/api/v1/vm//shutdown', methods=['POST']) +@authenticator def api_vm_shutdown(vm): """ Shutdown a virtual machine named VM. @@ -212,6 +280,7 @@ def api_vm_shutdown(vm): return pvcapi.vm_shutdown(vm) @api.route('/api/v1/vm//stop', methods=['POST']) +@authenticator def api_vm_stop(vm): """ Forcibly stop a virtual machine named VM. @@ -219,6 +288,7 @@ def api_vm_stop(vm): return pvcapi.vm_stop(vm) @api.route('/api/v1/vm//move', methods=['POST']) +@authenticator def api_vm_move(vm): """ Move a virtual machine named VM to another node. @@ -238,6 +308,7 @@ def api_vm_move(vm): return pvcapi.vm_move(vm, node, selector) @api.route('/api/v1/vm//migrate', methods=['POST']) +@authenticator def api_vm_migrate(vm): """ Temporarily migrate a virtual machine named VM to another node. @@ -263,6 +334,7 @@ def api_vm_migrate(vm): return pvcapi.vm_migrate(vm, node, selector, flag_force) @api.route('/api/v1/vm//unmigrate', methods=['POST']) +@authenticator def api_vm_unmigrate(vm): """ Unmigrate a migrated virtual machine named VM. @@ -273,6 +345,7 @@ def api_vm_unmigrate(vm): # Network endpoints # @api.route('/api/v1/network', methods=['GET']) +@authenticator def api_net(): """ Return a list of virtual client networks with limit LIMIT. @@ -286,6 +359,7 @@ def api_net(): return pvcapi.net_list(limit) @api.route('/api/v1/network/', methods=['GET']) +@authenticator def api_net_info(network): """ Get information about a virtual client network with description NETWORK. @@ -294,6 +368,7 @@ def api_net_info(network): return pvcapi.net_list(network) @api.route('/api/v1/network//add', methods=['POST']) +@authenticator def api_net_add(network): """ Add a virtual client network with description NETWORK. @@ -365,6 +440,7 @@ def api_net_add(network): dhcp4_flag, dhcp4_start, dhcp4_end) @api.route('/api/v1/network//modify', methods=['POST']) +@authenticator def api_net_modify(network): """ Modify a virtual client network with description NETWORK. @@ -434,6 +510,7 @@ def api_net_modify(network): dhcp4_flag, dhcp4_start, dhcp4_end) @api.route('/api/v1/network//remove', methods=['POST']) +@authenticator def api_net_remove(network): """ Remove a virtual client network with description NETWORK. @@ -441,6 +518,7 @@ def api_net_remove(network): return pvcapi.net_remove(network) @api.route('/api/v1/network//dhcp', methods=['GET']) +@authenticator def api_net_dhcp(network): """ Return a list of DHCP leases in virtual client network with description NETWORK with limit LIMIT. @@ -460,6 +538,7 @@ def api_net_dhcp(network): return pvcapi.net_dhcp_list(network, limit. flag_static) @api.route('/api/v1/network//dhcp/', methods=['GET']) +@authenticator def api_net_dhcp_info(network, lease): """ Get information about a DHCP lease for MAC address LEASE in virtual client network with description NETWORK. @@ -468,6 +547,7 @@ def api_net_dhcp_info(network, lease): return pvcapi.net_dhcp_list(network, lease, False) @api.route('/api/v1/network//dhcp//add', methods=['POST']) +@authenticator def api_net_dhcp_add(network, lease): """ Add a static DHCP lease for MAC address LEASE to virtual client network with description NETWORK. @@ -487,6 +567,7 @@ def api_net_dhcp_add(network, lease): return pvcapi.net_dhcp_add(network, ipaddress, lease, hostname) @api.route('/api/v1/network//dhcp//remove', methods=['POST']) +@authenticator def api_net_dhcp_remove(network, lease): """ Remove a static DHCP lease for MAC address LEASE from virtual client network with description NETWORK. @@ -494,6 +575,7 @@ def api_net_dhcp_remove(network, lease): return pvcapi.net_dhcp_remove(network, lease) @api.route('/api/v1/network//acl', methods=['GET']) +@authenticator def api_net_acl(network): """ Return a list of network ACLs in network NETWORK with limit LIMIT. @@ -515,6 +597,7 @@ def api_net_acl(network): return pvcapi.net_acl_list(network, limit, direction) @api.route('/api/v1/network//acl/', methods=['GET']) +@authenticator def api_net_acl_info(network, acl): """ Get information about a network access control entry with description ACL in virtual client network with description NETWORK. @@ -523,6 +606,7 @@ def api_net_acl_info(network, acl): return pvcapi.net_acl_list(network, acl, None) @api.route('/api/v1/network//acl//add', methods=['POST']) +@authenticator def api_net_acl_add(network, acl): """ Add an access control list with description ACL to virtual client network with description NETWORK. @@ -550,6 +634,7 @@ def api_net_acl_add(network, acl): return pvcapi.net_acl_add(network, direction, acl, rule, order) @api.route('/api/v1/network//acl//remove', methods=['POST']) +@authenticator def api_net_acl_remove(network, acl): """ Remove an access control list with description ACL from virtual client network with description NETWORK. @@ -568,6 +653,7 @@ def api_net_acl_remove(network, acl): # Ceph endpoints # @api.route('/api/v1/ceph', methods=['GET']) +@authenticator def api_ceph(): """ Get the current Ceph cluster status. @@ -575,6 +661,7 @@ def api_ceph(): return pvcapi.ceph_status() @api.route('/api/v1/ceph/osd', methods=['GET']) +@authenticator def api_ceph_osd(): """ Get the list of OSDs in the Ceph storage cluster. @@ -588,6 +675,7 @@ def api_ceph_osd(): return pvcapi.ceph_osd_list(limit) @api.route('/api/v1/ceph/osd/set', methods=['POST']) +@authenticator def api_ceph_osd_set(): """ Set OSD option OPTION on the PVC Ceph storage cluster, e.g. 'noout' or 'noscrub'. @@ -601,6 +689,7 @@ def api_ceph_osd_set(): return pvcapi.ceph_osd_set(option) @api.route('/api/v1/ceph/osd/unset', methods=['POST']) +@authenticator def api_ceph_osd_unset(): """ Unset OSD option OPTION on the PVC Ceph storage cluster, e.g. 'noout' or 'noscrub'. @@ -614,6 +703,7 @@ def api_ceph_osd_unset(): return pvcapi.ceph_osd_unset(option) @api.route('/api/v1/ceph/osd/', methods=['GET']) +@authenticator def api_ceph_osd_info(osd): """ Get information about an OSD with ID OSD. @@ -622,6 +712,7 @@ def api_ceph_osd_info(osd): return pvcapi.ceph_osd_list(osd) @api.route('/api/v1/ceph/osd//add', methods=['POST']) +@authenticator def api_ceph_osd_add(node): """ Add a Ceph OSD to node NODE. @@ -641,6 +732,7 @@ def api_ceph_osd_add(node): return pvcapi.ceph_osd_add(node, device, weight) @api.route('/api/v1/ceph/osd//remove', methods=['POST']) +@authenticator def api_ceph_osd_remove(osd): """ Remove a Ceph OSD with ID OSD. @@ -652,6 +744,7 @@ def api_ceph_osd_remove(osd): return pvcapi.ceph_osd_remove(osd) @api.route('/api/v1/ceph/osd//in', methods=['POST']) +@authenticator def api_ceph_osd_in(osd): """ Set in a Ceph OSD with ID OSD. @@ -659,6 +752,7 @@ def api_ceph_osd_in(osd): return pvcapi.ceph_osd_in(osd) @api.route('/api/v1/ceph/osd//out', methods=['POST']) +@authenticator def api_ceph_osd_out(osd): """ Set out a Ceph OSD with ID OSD. @@ -666,6 +760,7 @@ def api_ceph_osd_out(osd): return pvcapi.ceph_osd_out(osd) @api.route('/api/v1/ceph/pool', methods=['GET']) +@authenticator def api_ceph_pool(): """ Get the list of RBD pools in the Ceph storage cluster. @@ -679,6 +774,7 @@ def api_ceph_pool(): return pvcapi.ceph_pool_list(limit) @api.route('/api/v1/ceph/pool/', methods=['GET']) +@authenticator def api_ceph_pool_info(pool): """ Get information about an RBD pool with name POOL. @@ -687,6 +783,7 @@ def api_ceph_pool_info(pool): return pvcapi.ceph_pool_list(pool) @api.route('/api/v1/ceph/pool//add', methods=['POST']) +@authenticator def api_ceph_pool_add(pool): """ Add a Ceph RBD pool with name POOL. @@ -701,6 +798,7 @@ def api_ceph_pool_add(pool): return pvcapi.ceph_pool_add(pool, pgs) @api.route('/api/v1/ceph/pool//remove', methods=['POST']) +@authenticator def api_ceph_pool_remove(pool): """ Remove a Ceph RBD pool with name POOL. @@ -712,6 +810,7 @@ def api_ceph_pool_remove(pool): return pvcapi.ceph_pool_remove(pool) @api.route('/api/v1/ceph/volume', methods=['GET']) +@authenticator def api_ceph_volume(): """ Get the list of RBD volumes in the Ceph storage cluster. @@ -731,6 +830,7 @@ def api_ceph_volume(): return pvcapi.ceph_volume_list(pool, limit) @api.route('/api/v1/ceph/volume//', methods=['GET']) +@authenticator def api_ceph_volume_info(pool, volume): """ Get information about an RBD volume with name VOLUME in RBD pool with name POOL. @@ -739,6 +839,7 @@ def api_ceph_volume_info(pool, volume): return pvcapi.ceph_osd_list(pool, osd) @api.route('/api/v1/ceph/volume///add', methods=['POST']) +@authenticator def api_ceph_volume_add(pool, volume): """ Add a Ceph RBD volume with name VOLUME to RBD pool with name POOL. @@ -752,6 +853,7 @@ def api_ceph_volume_add(pool, volume): return pvcapi.ceph_volume_add(pool, volume, size) @api.route('/api/v1/ceph/volume///remove', methods=['POST']) +@authenticator def api_ceph_volume_remove(pool, volume): """ Remove a Ceph RBD volume with name VOLUME from RBD pool with name POOL. @@ -759,6 +861,7 @@ def api_ceph_volume_remove(pool, volume): return pvcapi.ceph_volume_remove(pool, volume) @api.route('/api/v1/ceph/volume/snapshot', methods=['GET']) +@authenticator def api_ceph_volume_snapshot(): """ Get the list of RBD volume snapshots in the Ceph storage cluster. @@ -784,6 +887,7 @@ def api_ceph_volume_snapshot(): return pvcapi.ceph_volume_snapshot_list(pool, volume, limit) @api.route('/api/v1/ceph/volume/snapshot///', methods=['GET']) +@authenticator def api_ceph_volume_snapshot_info(pool, volume, snapshot): """ Get information about a snapshot with name SNAPSHOT of RBD volume with name VOLUME in RBD pool with name POOL. @@ -792,6 +896,7 @@ def api_ceph_volume_snapshot_info(pool, volume, snapshot): return pvcapi.ceph_snapshot_list(pool, volume, snapshot) @api.route('/api/v1/ceph/volume/snapshot////add', methods=['POST']) +@authenticator def api_ceph_volume_snapshot_add(pool, volume, snapshot): """ Add a Ceph RBD volume snapshot with name SNAPSHOT of RBD volume with name VOLUME in RBD pool with name POOL. @@ -799,6 +904,7 @@ def api_ceph_volume_snapshot_add(pool, volume, snapshot): return pvcapi.ceph_volume_snapshot_add(pool, volume, snapshot) @api.route('/api/v1/ceph/volume/snapshot////remove', methods=['POST']) +@authenticator def api_ceph_volume_snapshot_remove(pool, volume, snapshot): """ Remove a Ceph RBD volume snapshot with name SNAPSHOT from RBD volume with name VOLUME in RBD pool with name POOL. @@ -808,4 +914,6 @@ def api_ceph_volume_snapshot_remove(pool, volume, snapshot): # # Entrypoint # -api.run() +http_server = gevent.pywsgi.WSGIServer((config['listen_address'], config['listen_port']), api) +http_server.serve_forever() + diff --git a/client-api/pvc-api.sample.yaml b/client-api/pvc-api.sample.yaml new file mode 100644 index 00000000..2decbd0d --- /dev/null +++ b/client-api/pvc-api.sample.yaml @@ -0,0 +1,25 @@ +# pvc-api client configuration file example +# +# This configuration file specifies details for the PVC API client running on +# this machine. Default values are not supported; the values in this sample +# configuration are considered defaults and can be used as-is. +# +# Copy this example to /etc/pvc/pvc-api.conf and edit to your needs + +pvc: + # zookeeper: Configuration of the zookeeper connection + zookeeper: + # uri: Zookeeper URI specifying the PVCD coordinators to connect to + #uri: "pvchv1:2181,pvchv2:2181,pvchv3:2181" + uri: "hv1:2181,hv2:2181,hv3:2181" + # api: Configuration of the API listener + api: + # listen_address: IP address(es) to listen on; use 0.0.0.0 for all interfaces + listen_address: "127.0.0.1" + # listen_port: TCP port to listen on, usually 7370 + listen_port: "7370" + # authentication: Authentication and security settings + authentication: + # key: A secure key to authorize against the API; must be sent in the body + # arguments or in the URI of each requess; leave blank for no authentication + key: "test1234" diff --git a/client-api/pvc-api.service b/client-api/pvc-api.service index 55b0eb2c..64ef0971 100644 --- a/client-api/pvc-api.service +++ b/client-api/pvc-api.service @@ -8,7 +8,7 @@ After = network-online.target Type = simple WorkingDirectory = /usr/share/pvc Environment = PYTHONUNBUFFERED=true -Environment = PVC_CONFIG_FILE=/etc/pvc/pvc.yaml +Environment = PVC_CONFIG_FILE=/etc/pvc/pvc-api.yaml ExecStart = /usr/share/pvc/pvc-api.py Restart = on-failure diff --git a/debian/control b/debian/control index 7bac14cc..f3706722 100644 --- a/debian/control +++ b/debian/control @@ -33,7 +33,7 @@ Description: Parallel Virtual Cluster client (Python 3) Package: pvc-client-api Architecture: all -Depends: pvc-client-common, python3-yaml, python3-flask +Depends: pvc-client-common, python3-yaml, python3-flask, python3-gevent Description: Parallel Virtual Cluster client (Python 3) A KVM/Zookeeper/Ceph-based VM and private cloud manager . diff --git a/debian/pvc-client-api.install b/debian/pvc-client-api.install index d1b68c0a..0ca92451 100644 --- a/debian/pvc-client-api.install +++ b/debian/pvc-client-api.install @@ -1,3 +1,4 @@ client-api/pvc-api.py usr/share/pvc +client-api/pvc-api.sample.yaml etc/pvc client-api/api_lib usr/share/pvc client-api/pvc-api.service lib/systemd/system diff --git a/debian/pvc-client-api.postinst b/debian/pvc-client-api.postinst index 0152991b..4eda712f 100644 --- a/debian/pvc-client-api.postinst +++ b/debian/pvc-client-api.postinst @@ -2,3 +2,13 @@ # Install client binary to /usr/bin via symlink ln -s /usr/share/pvc/api.py /usr/bin/pvc-api + +# Reload systemd's view of the units +systemctl daemon-reload + +# Restart the daemon (or warn on first install) +if systemctl is-active --quiet pvc-api.service; then + systemctl restart pvc-api.service +else + echo "NOTE: The PVC client API daemon (pvc-api.service) has not been started; create a config file at /etc/pvc/pvc-api.yaml then start it." +fi diff --git a/debian/pvc-daemon.postinst b/debian/pvc-daemon.postinst index c3b4569d..7f1886a9 100644 --- a/debian/pvc-daemon.postinst +++ b/debian/pvc-daemon.postinst @@ -1,5 +1,8 @@ #!/bin/sh +# Reload systemd's view of the units +systemctl daemon-reload + # Enable the service and target systemctl enable /lib/systemd/system/pvcd.service systemctl enable /lib/systemd/system/pvcd.target