Files
pvc/client-api/pvc-api.py
Joshua M. Boniface 9ccf3390ab Add storage prefix to Ceph commands
Add the "storage" prefix to all Ceph-based commands in both the CLI and
the API. This partially abstracts the storage subsystem from the Ceph
tool specifically, should future storage subsystems be added or changed.
The name "ceph" is still used due to the non-abstracted components of
the Ceph management, e.g. referencing Ceph-specific concepts like OSDs
or pools.
2019-07-10 15:14:17 -04:00

995 lines
30 KiB
Python
Executable File

#!/usr/bin/env python3
# api.py - PVC HTTP API interface
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
import flask
import json
import yaml
import os
import gevent.pywsgi
import api_lib.pvcapi as pvcapi
api = flask.Flask(__name__)
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)
print('Starting PVC API daemon')
# 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 = {
'coordinators': o_config['pvc']['coordinators'],
'listen_address': o_config['pvc']['api']['listen_address'],
'listen_port': int(o_config['pvc']['api']['listen_port']),
'auth_enabled': o_config['pvc']['api']['authentication']['enabled'],
'auth_secret_key': o_config['pvc']['api']['authentication']['secret_key'],
'auth_tokens': o_config['pvc']['api']['authentication']['tokens'],
'ssl_enabled': o_config['pvc']['api']['ssl']['enabled'],
'ssl_key_file': o_config['pvc']['api']['ssl']['key_file'],
'ssl_cert_file': o_config['pvc']['api']['ssl']['cert_file']
}
# Set the config object in the pvcapi namespace
pvcapi.config = config
except Exception as e:
print('ERROR: {}.'.format(e))
exit(1)
if config['auth_enabled']:
api.config["SECRET_KEY"] = config['auth_secret_key']
def authenticator(function):
def authenticate(*args, **kwargs):
# Check if authentication is enabled
if not config['auth_enabled']:
return function(*args, **kwargs)
else:
# Session-based authentication
if 'token' in flask.session:
return function(*args, **kwargs)
# Direct token-based authentication
if 'token' in flask.request.values:
if any(token for token in config['auth_tokens'] if flask.request.values['token'] == token['token']):
return function(*args, **kwargs)
else:
return flask.jsonify({"message":"Authentication failed"}), 401
return flask.jsonify({"message":"Authentication required"}), 401
authenticate.__name__ = function.__name__
return authenticate
@api.route('/api/v1', methods=['GET'])
def api_root():
return flask.jsonify({"message":"PVC API version 1"}), 209
@api.route('/api/v1/auth/login', methods=['GET', 'POST'])
def api_auth_login():
# Just return a 200 if auth is disabled
if not config['auth_enabled']:
return flask.jsonify({"message":"Authentication is disabled."}), 200
if flask.request.method == 'POST':
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 flask.redirect(flask.url_for('api_root'))
else:
return flask.jsonify({"message":"Authentication failed"}), 401
return '''
<form method="post">
<p>
Enter your authentication token:
<input type=text name=token style='width:24em'>
<input type=submit value=Login>
</p>
</form>
'''
@api.route('/api/v1/auth/logout', methods=['GET', 'POST'])
def api_auth_logout():
# Just return a 200 if auth is disabled
if not config['auth_enabled']:
return flask.jsonify({"message":"Authentication is disabled."}), 200
# remove the username from the session if it's there
flask.session.pop('token', None)
return flask.redirect(flask.url_for('api_root'))
#
# Node endpoints
#
@api.route('/api/v1/node', methods=['GET'])
@authenticator
def api_node():
"""
Return a list of nodes with limit LIMIT.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.node_list(limit)
@api.route('/api/v1/node/<node>', methods=['GET'])
@authenticator
def api_node_info(node):
"""
Return information about node NODE.
"""
# Same as specifying /node?limit=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'])
@authenticator
def api_node_primary(node):
"""
Set NODE to primary router mode.
"""
return pvcapi.node_primary(node)
@api.route('/api/v1/node/<node>/flush', methods=['POST'])
@authenticator
def api_node_flush(node):
"""
Flush NODE of running VMs.
"""
return pvcapi.node_flush(node)
@api.route('/api/v1/node/<node>/unflush', methods=['POST'])
@api.route('/api/v1/node/<node>/ready', methods=['POST'])
@authenticator
def api_node_ready(node):
"""
Restore NODE to active service.
"""
return pvcapi.node_ready(node)
#
# VM endpoints
#
@api.route('/api/v1/vm', methods=['GET'])
@authenticator
def api_vm():
"""
Return a list of VMs with limit LIMIT.
"""
# Get node limit
if 'node' in flask.request.values:
node = flask.request.values['node']
else:
node = None
# Get state limit
if 'state' in flask.request.values:
state = flask.request.values['state']
else:
state = None
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.vm_list(node, state, limit)
@api.route('/api/v1/vm/<vm>', methods=['GET'])
@authenticator
def api_vm_info(vm):
"""
Get information about a virtual machine named VM.
"""
# Same as specifying /vm?limit=VM
return pvcapi.vm_list(None, None, vm, is_fuzzy=False)
# TODO: #22
#@api.route('/api/v1/vm/<vm>/add', methods=['POST'])
#@authenticator
#def api_vm_add(vm):
# """
# Add a virtual machine named VM.
# """
# return pvcapi.vm_add()
@api.route('/api/v1/vm/<vm>/define', methods=['POST'])
@authenticator
def api_vm_define(vm):
"""
Define a virtual machine named VM from Libvirt XML.
"""
# Get XML data
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:
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>/modify', methods=['POST'])
@authenticator
def api_vm_modify(vm):
"""
Modify an existing virtual machine named VM from Libvirt XML.
"""
# Get XML from the POST body
libvirt_xml = flask.request.data
# Get node name
if 'flag_restart' in flask.request.values:
flag_restart = flask.request.values['flag_restart']
else:
flag_restart = None
return pvcapi.vm_modify(vm, flag_restart, libvirt_xml)
@api.route('/api/v1/vm/<vm>/undefine', methods=['POST'])
@authenticator
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'])
@authenticator
def api_vm_remove(vm):
"""
Remove a virtual machine named VM including all disks.
"""
return pvcapi.vm_remove(vm)
@api.route('/api/v1/vm/<vm>/dump', methods=['GET'])
@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
#
@api.route('/api/v1/network', methods=['GET'])
@authenticator
def api_net():
"""
Return a list of virtual client networks with limit LIMIT.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.net_list(limit)
@api.route('/api/v1/network/<network>', methods=['GET'])
@authenticator
def api_net_info(network):
"""
Get information about a virtual client network with description NETWORK.
"""
# Same as specifying /network?limit=NETWORK
return pvcapi.net_list(network)
@api.route('/api/v1/network/<network>/add', methods=['POST'])
@authenticator
def api_net_add(network):
"""
Add a virtual client network with description NETWORK.
"""
# Get network VNI
if 'vni' in flask.request.values:
vni = flask.request.values['vni']
else:
return flask.jsonify({"message":"ERROR: A VNI must be specified for the virtual network."}), 520
# Get network type
if 'nettype' in flask.request.values:
nettype = flask.request.values['nettype']
if not 'managed' in nettype and not 'bridged' in nettype:
return flask.jsonify({"message":"ERROR: A valid nettype must be specified: 'managed' or 'bridged'."}), 520
else:
return flask.jsonify({"message":"ERROR: A nettype must be specified for the virtual network."}), 520
# Get network domain
if 'domain' in flask.request.values:
domain = flask.request.values['domain']
else:
domain = None
# Get ipv4 network
if 'ip4_network' in flask.request.values:
ip4_network = flask.request.values['ip4_network']
else:
ip4_network = None
# Get ipv4 gateway
if 'ip4_gateway' in flask.request.values:
ip4_gateway = flask.request.values['ip4_gateway']
else:
ip4_gateway = None
# Get ipv6 network
if 'ip6_network' in flask.request.values:
ip6_network = flask.request.values['ip6_network']
else:
ip6_network = None
# Get ipv6 gateway
if 'ip6_gateway' in flask.request.values:
ip6_gateway = flask.request.values['ip6_gateway']
else:
ip6_gateway = None
# Get ipv4 DHCP flag
if 'flag_dhcp4' in flask.request.values:
dhcp4_flag = True
else:
dhcp4_flag = False
# Get ipv4 DHCP start
if 'dhcp4_start' in flask.request.values:
dhcp4_start = flask.request.values['dhcp4_start']
else:
dhcp4_start = None
# Get ipv4 DHCP end
if 'dhcp4_end' in flask.request.values:
dhcp4_end = flask.request.values['dhcp4_end']
else:
dhcp4_end = None
return pvcapi.net_add(vni, network, nettype, domain,
ip4_network, ip4_gateway, ip6_network, ip6_gateway,
dhcp4_flag, dhcp4_start, dhcp4_end)
@api.route('/api/v1/network/<network>/modify', methods=['POST'])
@authenticator
def api_net_modify(network):
"""
Modify a virtual client network with description NETWORK.
"""
# Get network VNI
if 'vni' in flask.request.values:
vni = flask.request.values['vni']
else:
vni = None
# Get network type
if 'nettype' in flask.request.values:
nettype = flask.request.values['nettype']
else:
vni = None
# Get network domain
if 'domain' in flask.request.values:
domain = flask.request.values['domain']
else:
domain = None
# Get ipv4 network
if 'ip4_network' in flask.request.values:
ip4_network = flask.request.values['ip4_network']
else:
ip4_network = None
# Get ipv4 gateway
if 'ip4_gateway' in flask.request.values:
ip4_gateway = flask.request.values['ip4_gateway']
else:
ip4_gateway = None
# Get ipv6 network
if 'ip6_network' in flask.request.values:
ip6_network = flask.request.values['ip6_network']
else:
ip6_network = None
# Get ipv6 gateway
if 'ip6_gateway' in flask.request.values:
ip6_gateway = flask.request.values['ip6_gateway']
else:
ip6_gateway = None
# Get ipv4 DHCP flag
if 'flag_dhcp4' in flask.request.values:
dhcp4_flag = True
else:
dhcp4_flag = False
# Get ipv4 DHCP start
if 'dhcp4_start' in flask.request.values:
dhcp4_start = flask.request.values['dhcp4_start']
else:
dhcp4_start = None
# Get ipv4 DHCP end
if 'dhcp4_end' in flask.request.values:
dhcp4_end = flask.request.values['dhcp4_end']
else:
dhcp4_end = None
return pvcapi.net_modify(vni, network, nettype, domain,
ip4_network, ip4_gateway, ip6_network, ip6_gateway,
dhcp4_flag, dhcp4_start, dhcp4_end)
@api.route('/api/v1/network/<network>/remove', methods=['POST'])
@authenticator
def api_net_remove(network):
"""
Remove a virtual client network with description NETWORK.
"""
return pvcapi.net_remove(network)
@api.route('/api/v1/network/<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.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
# Get static-only flag
if 'flag_static' in flask.request.values:
flag_static = True
else:
flag_static = False
return pvcapi.net_dhcp_list(network, limit. flag_static)
@api.route('/api/v1/network/<network>/dhcp/<lease>', 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.
"""
# Same as specifying /network?limit=NETWORK
return pvcapi.net_dhcp_list(network, lease, False)
@api.route('/api/v1/network/<network>/dhcp/<lease>/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.
"""
# Get lease ipaddress
if 'ipaddress' in flask.request.values:
ipaddress = flask.request.values['ipaddress']
else:
return flask.jsonify({"message":"ERROR: An IP address must be specified for the lease."}), 520
# Get lease hostname
if 'hostname' in flask.request.values:
hostname = flask.request.values['hostname']
else:
hostname = None
return pvcapi.net_dhcp_add(network, ipaddress, lease, hostname)
@api.route('/api/v1/network/<network>/dhcp/<lease>/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.
"""
return pvcapi.net_dhcp_remove(network, lease)
@api.route('/api/v1/network/<network>/acl', methods=['GET'])
@authenticator
def api_net_acl(network):
"""
Return a list of network ACLs in network NETWORK with limit LIMIT.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
# Get direction limit
if 'direction' in flask.request.values:
direction = flask.request.values['direction']
if not 'in' in direction and not 'out' in direction:
return flash.jsonify({"message":"ERROR: Direction must be either 'in' or 'out'; for both, do not specify a direction."}), 510
else:
direction = None
return pvcapi.net_acl_list(network, limit, direction)
@api.route('/api/v1/network/<network>/acl/<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.
"""
# Same as specifying /network?limit=NETWORK
return pvcapi.net_acl_list(network, acl, None)
@api.route('/api/v1/network/<network>/acl/<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.
"""
# Get rule direction
if 'direction' in flask.request.values:
direction = flask.request.values['limit']
if not 'in' in direction and not 'out' in direction:
return flask.jsonify({"message":"ERROR: Direction must be either 'in' or 'out'."}), 510
else:
return flask.jsonify({"message":"ERROR: A direction must be specified for the ACL."}), 510
# Get rule data
if 'rule' in flask.request.values:
rule = flask.request.values['rule']
else:
return flask.jsonify({"message":"ERROR: A valid NFT rule line must be specified for the ACL."}), 510
# Get order value
if 'order' in flask.request.values:
order = flask.request.values['order']
else:
order = None
return pvcapi.net_acl_add(network, direction, acl, rule, order)
@api.route('/api/v1/network/<network>/acl/<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.
"""
# Get rule direction
if 'direction' in flask.request.values:
direction = flask.request.values['limit']
if not 'in' in direction and not 'out' in direction:
return flask.jsonify({"message":"ERROR: Direction must be either 'in' or 'out'."}), 510
else:
return flask.jsonify({"message":"ERROR: A direction must be specified for the ACL."}), 510
return pvcapi.net_acl_remove(network, direction, acl)
#
# Storage (Ceph) endpoints
#
# Note: The prefix `/storage` allows future potential storage subsystems.
# Since Ceph is the only section not abstracted by PVC directly
# (i.e. it references Ceph-specific concepts), this makes more
# sense in the long-term.
#
@api.route('/api/v1/storage', methods=['GET'])
def api_storage():
"""
Manage the storage of the PVC cluster.
"""
return flask.jsonify({"message":"Manage the storage of the PVC cluster."}), 200
@api.route('/api/v1/storage/ceph', methods=['GET'])
@api.route('/api/v1/storage/ceph/status', methods=['GET'])
@authenticator
def api_ceph_status():
"""
Get the current Ceph cluster status.
"""
return pvcapi.ceph_status()
@api.route('/api/v1/storage/ceph/df', methods=['GET'])
@authenticator
def api_ceph_radosdf():
"""
Get the current Ceph cluster utilization.
"""
return pvcapi.ceph_radosdf()
@api.route('/api/v1/storage/ceph/osd', methods=['GET'])
@authenticator
def api_ceph_osd():
"""
Get the list of OSDs in the Ceph storage cluster.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.ceph_osd_list(limit)
@api.route('/api/v1/storage/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'.
"""
# Get OSD option
if 'option' in flask.request.options:
option = flask.request.options['option']
else:
return flask.jsonify({"message":"ERROR: An OSD option must be specified."}), 510
return pvcapi.ceph_osd_set(option)
@api.route('/api/v1/storage/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'.
"""
# Get OSD option
if 'option' in flask.request.options:
option = flask.request.options['option']
else:
return flask.jsonify({"message":"ERROR: An OSD option must be specified."}), 510
return pvcapi.ceph_osd_unset(option)
@api.route('/api/v1/storage/ceph/osd/<osd>', methods=['GET'])
@authenticator
def api_ceph_osd_info(osd):
"""
Get information about an OSD with ID OSD.
"""
# Same as specifying /osd?limit=OSD
return pvcapi.ceph_osd_list(osd)
@api.route('/api/v1/storage/ceph/osd/<node>/add', methods=['POST'])
@authenticator
def api_ceph_osd_add(node):
"""
Add a Ceph OSD to node NODE.
"""
# Get OSD device
if 'device' in flask.request.devices:
device = flask.request.devices['device']
else:
return flask.jsonify({"message":"ERROR: A block device must be specified."}), 510
# Get OSD weight
if 'weight' in flask.request.weights:
weight = flask.request.weights['weight']
else:
return flask.jsonify({"message":"ERROR: An OSD weight must be specified."}), 510
return pvcapi.ceph_osd_add(node, device, weight)
@api.route('/api/v1/storage/ceph/osd/<osd>/remove', methods=['POST'])
@authenticator
def api_ceph_osd_remove(osd):
"""
Remove a Ceph OSD with ID OSD.
"""
# Verify yes-i-really-mean-it flag
if not 'flag_yes_i_really_mean_it' in flask.request.values:
return flask.jsonify({"message":"ERROR: This command can have unintended consequences and should not be automated; if you're sure you know what you're doing, resend with the argument 'flag_yes_i_really_mean_it'."}), 599
return pvcapi.ceph_osd_remove(osd)
@api.route('/api/v1/storage/ceph/osd/<osd>/in', methods=['POST'])
@authenticator
def api_ceph_osd_in(osd):
"""
Set in a Ceph OSD with ID OSD.
"""
return pvcapi.ceph_osd_in(osd)
@api.route('/api/v1/storage/ceph/osd/<osd>/out', methods=['POST'])
@authenticator
def api_ceph_osd_out(osd):
"""
Set out a Ceph OSD with ID OSD.
"""
return pvcapi.ceph_osd_out(osd)
@api.route('/api/v1/storage/ceph/pool', methods=['GET'])
@authenticator
def api_ceph_pool():
"""
Get the list of RBD pools in the Ceph storage cluster.
"""
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.ceph_pool_list(limit)
@api.route('/api/v1/storage/ceph/pool/<pool>', methods=['GET'])
@authenticator
def api_ceph_pool_info(pool):
"""
Get information about an RBD pool with name POOL.
"""
# Same as specifying /pool?limit=POOL
return pvcapi.ceph_pool_list(pool)
@api.route('/api/v1/storage/ceph/pool/<pool>/add', methods=['POST'])
@authenticator
def api_ceph_pool_add(pool):
"""
Add a Ceph RBD pool with name POOL.
"""
# Get placement groups
if 'pgs' in flask.request.values:
pgs = flask.request.values['pgs']
else:
# We default to a very small number; DOCUMENT THIS
pgs = 128
return pvcapi.ceph_pool_add(pool, pgs)
@api.route('/api/v1/storage/ceph/pool/<pool>/remove', methods=['POST'])
@authenticator
def api_ceph_pool_remove(pool):
"""
Remove a Ceph RBD pool with name POOL.
"""
# Verify yes-i-really-mean-it flag
if not 'flag_yes_i_really_mean_it' in flask.request.values:
return flask.jsonify({"message":"ERROR: This command can have unintended consequences and should not be automated; if you're sure you know what you're doing, resend with the argument 'flag_yes_i_really_mean_it'."}), 599
return pvcapi.ceph_pool_remove(pool)
@api.route('/api/v1/storage/ceph/volume', methods=['GET'])
@authenticator
def api_ceph_volume():
"""
Get the list of RBD volumes in the Ceph storage cluster.
"""
# Get pool limit
if 'pool' in flask.request.values:
pool = flask.request.values['pool']
else:
pool = None
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.ceph_volume_list(pool, limit)
@api.route('/api/v1/storage/ceph/volume/<pool>/<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.
"""
# Same as specifying /volume?limit=VOLUME
return pvcapi.ceph_osd_list(pool, osd)
@api.route('/api/v1/storage/ceph/volume/<pool>/<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.
"""
# Get volume size
if 'size' in flask.request.values:
size = flask.request.values['size']
else:
return flask.jsonify({"message":"ERROR: A volume size in bytes (or with an M/G/T suffix) must be specified."}), 510
return pvcapi.ceph_volume_add(pool, volume, size)
@api.route('/api/v1/storage/ceph/volume/<pool>/<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.
"""
return pvcapi.ceph_volume_remove(pool, volume)
@api.route('/api/v1/storage/ceph/volume/snapshot', methods=['GET'])
@authenticator
def api_ceph_volume_snapshot():
"""
Get the list of RBD volume snapshots in the Ceph storage cluster.
"""
# Get pool limit
if 'pool' in flask.request.values:
pool = flask.request.values['pool']
else:
pool = None
# Get volume limit
if 'volume' in flask.request.values:
volume = flask.request.values['volume']
else:
volume = None
# Get name limit
if 'limit' in flask.request.values:
limit = flask.request.values['limit']
else:
limit = None
return pvcapi.ceph_volume_snapshot_list(pool, volume, limit)
@api.route('/api/v1/storage/ceph/volume/snapshot/<pool>/<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.
"""
# Same as specifying /snapshot?limit=VOLUME
return pvcapi.ceph_snapshot_list(pool, volume, snapshot)
@api.route('/api/v1/storage/ceph/volume/snapshot/<pool>/<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.
"""
return pvcapi.ceph_volume_snapshot_add(pool, volume, snapshot)
@api.route('/api/v1/storage/ceph/volume/snapshot/<pool>/<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.
"""
return pvcapi.ceph_volume_snapshot_remove(pool, volume, snapshot)
#
# Entrypoint
#
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()