+++ /dev/null
-:orphan:
-
-=====================================================
- ceph-rest-api -- ceph RESTlike administration server
-=====================================================
-
-.. program:: ceph-rest-api
-
-Synopsis
-========
-
-| **ceph-rest-api** [ -c *conffile* ] [--cluster *clustername* ] [ -n *name* ] [-i *id* ]
-
-
-Description
-===========
-
-**ceph-rest-api** is a WSGI application that can run as a
-standalone web service or run under a web server that supports
-WSGI. It provides much of the functionality of the **ceph**
-command-line tool through an HTTP-accessible interface.
-
-Options
-=======
-
-.. option:: -c/--conf conffile
-
- names the ceph.conf file to use for configuration. If -c is not
- specified, the default depends on the state of the --cluster option
- (default 'ceph'; see below). The configuration file is searched
- for in this order:
-
- * $CEPH_CONF
- * /etc/ceph/${cluster}.conf
- * ~/.ceph/${cluster}.conf
- * ${cluster}.conf (in the current directory)
-
- so you can also pass this option in the environment as CEPH_CONF.
-
-.. option:: --cluster clustername
-
- set *clustername* for use in the $cluster metavariable, for
- locating the ceph.conf file. The default is 'ceph'.
-
-.. option:: -n/--name name
-
- specifies the client 'name', which is used to find the
- client-specific configuration options in the config file, and
- also is the name used for authentication when connecting
- to the cluster (the entity name appearing in 'ceph auth ls' output,
- for example). The default is 'client.restapi'.
-
-.. option:: -i/--id id
-
- specifies the client 'id', which will form the clientname
- as 'client.<id>' if clientname is not set. If -n/-name is
- set, that takes precedence.
-
- Also, global Ceph options are supported.
-
-
-Configuration parameters
-========================
-
-Supported configuration parameters include:
-
-* **keyring** the keyring file holding the key for 'clientname'
-* **public addr** ip:port to listen on (default 0.0.0.0:5000)
-* **log file** (usual Ceph default)
-* **restapi base url** the base URL to answer requests on (default /api/v0.1)
-* **restapi log level** critical, error, warning, info, debug (default warning)
-
-Configuration parameters are searched in the standard order:
-first in the section named '<clientname>', then 'client', then 'global'.
-
-<clientname> is either supplied by -n/--name, "client.<id>" where
-<id> is supplied by -i/--id, or 'client.restapi' if neither option
-is present.
-
-A single-threaded server will run on **public addr** if the ceph-rest-api
-executed directly; otherwise, configuration is specified by the enclosing
-WSGI web server.
-
-Commands
-========
-
-Commands are submitted with HTTP GET requests (for commands that
-primarily return data) or PUT (for commands that affect cluster state).
-HEAD and OPTIONS are also supported. Standard HTTP status codes
-are returned.
-
-For commands that return bulk data, the request can include
-Accept: application/json or Accept: application/xml to select the
-desired structured output, or you may use a .json or .xml addition
-to the requested PATH. Parameters are supplied as query parameters
-in the request; for parameters that take more than one value, repeat
-the key=val construct. For instance, to remove OSDs 2 and 3,
-send a PUT request to ``osd/rm?ids=2&ids=3``.
-
-Discovery
-=========
-
-Human-readable discovery of supported commands and parameters, along
-with a small description of each command, is provided when the requested
-path is incomplete/partially matching. Requesting / will redirect to
-the value of **restapi base url**, and that path will give a full list
-of all known commands.
-For example, requesting ``api/vX.X/mon`` will return the list of API calls for
-monitors - ``api/vX.X/osd`` will return the list of API calls for OSD and so on.
-
-The command set is very similar to the commands
-supported by the **ceph** tool. One notable exception is that the
-``ceph pg <pgid> <command>`` style of commands is supported here
-as ``tell/<pgid>/command?args``.
-
-Deployment as WSGI application
-==============================
-
-When deploying as WSGI application (say, with Apache/mod_wsgi,
-or nginx/uwsgi, or gunicorn, etc.), use the ``ceph_rest_api.py`` module
-(``ceph-rest-api`` is a thin layer around this module). The standalone web
-server is of course not used, so address/port configuration is done in
-the WSGI server. Use a python .wsgi module or the equivalent to call
-``app = generate_app(conf, cluster, clientname, clientid, args)`` where:
-
-* conf is as -c/--conf above
-* cluster is as --cluster above
-* clientname, -n/--name
-* clientid, -i/--id, and
-* args are any other generic Ceph arguments
-
-When app is returned, it will have attributes 'ceph_addr' and 'ceph_port'
-set to what the address and port are in the Ceph configuration;
-those may be used for the server, or ignored.
-
-Any errors reading configuration or connecting to the cluster cause an
-exception to be raised; see your WSGI server documentation for how to
-see those messages in case of problem.
-
-Availability
-============
-
-**ceph-rest-api** is part of Ceph, a massively scalable, open-source, distributed storage system. Please refer to the Ceph documentation at
-http://ceph.com/docs for more information.
-
-
-See also
-========
-
-:doc:`ceph <ceph>`\(8)
+++ /dev/null
-"""
-Rest Api
-"""
-import logging
-import contextlib
-import time
-
-from teuthology import misc as teuthology
-from teuthology import contextutil
-from teuthology.orchestra import run
-from teuthology.orchestra.daemon import DaemonGroup
-
-log = logging.getLogger(__name__)
-
-
-@contextlib.contextmanager
-def run_rest_api_daemon(ctx, api_clients):
- """
- Wrapper starts the rest api daemons
- """
- if not hasattr(ctx, 'daemons'):
- ctx.daemons = DaemonGroup()
- remotes = ctx.cluster.only(teuthology.is_type('client')).remotes
- for rems, roles in remotes.iteritems():
- for whole_id_ in roles:
- if whole_id_ in api_clients:
- id_ = whole_id_[len('clients'):]
- run_cmd = [
- 'sudo',
- 'daemon-helper',
- 'kill',
- 'ceph-rest-api',
- '-n',
- 'client.rest{id}'.format(id=id_), ]
- cl_rest_id = 'client.rest{id}'.format(id=id_)
- ctx.daemons.add_daemon(rems, 'restapi',
- cl_rest_id,
- args=run_cmd,
- logger=log.getChild(cl_rest_id),
- stdin=run.PIPE,
- wait=False,
- )
- for i in range(1, 12):
- log.info('testing for ceph-rest-api try {0}'.format(i))
- run_cmd = [
- 'wget',
- '-O',
- '/dev/null',
- '-q',
- 'http://localhost:5000/api/v0.1/status'
- ]
- proc = rems.run(
- args=run_cmd,
- check_status=False
- )
- if proc.exitstatus == 0:
- break
- time.sleep(5)
- if proc.exitstatus != 0:
- raise RuntimeError('Cannot contact ceph-rest-api')
- try:
- yield
-
- finally:
- """
- TO DO: destroy daemons started -- modify iter_daemons_of_role
- """
- teuthology.stop_daemons_of_type(ctx, 'restapi')
-
-@contextlib.contextmanager
-def task(ctx, config):
- """
- Start up rest-api.
-
- To start on on all clients::
-
- tasks:
- - ceph:
- - rest-api:
-
- To only run on certain clients::
-
- tasks:
- - ceph:
- - rest-api: [client.0, client.3]
-
- or
-
- tasks:
- - ceph:
- - rest-api:
- client.0:
- client.3:
-
- The general flow of things here is:
- 1. Find clients on which rest-api is supposed to run (api_clients)
- 2. Generate keyring values
- 3. Start up ceph-rest-api daemons
- On cleanup:
- 4. Stop the daemons
- 5. Delete keyring value files.
- """
- api_clients = []
- remotes = ctx.cluster.only(teuthology.is_type('client')).remotes
- log.info(remotes)
- if config == None:
- api_clients = ['client.{id}'.format(id=id_)
- for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
- else:
- api_clients = config
- log.info(api_clients)
- testdir = teuthology.get_testdir(ctx)
- coverage_dir = '{tdir}/archive/coverage'.format(tdir=testdir)
- for rems, roles in remotes.iteritems():
- for whole_id_ in roles:
- if whole_id_ in api_clients:
- id_ = whole_id_[len('client.'):]
- keyring = '/etc/ceph/ceph.client.rest{id}.keyring'.format(
- id=id_)
- rems.run(
- args=[
- 'sudo',
- 'adjust-ulimits',
- 'ceph-coverage',
- coverage_dir,
- 'ceph-authtool',
- '--create-keyring',
- '--gen-key',
- '--name=client.rest{id}'.format(id=id_),
- '--set-uid=0',
- '--cap', 'mon', 'allow *',
- '--cap', 'osd', 'allow *',
- '--cap', 'mds', 'allow',
- keyring,
- run.Raw('&&'),
- 'sudo',
- 'chmod',
- '0644',
- keyring,
- ],
- )
- rems.run(
- args=[
- 'sudo',
- 'sh',
- '-c',
- run.Raw("'"),
- "echo",
- '[client.rest{id}]'.format(id=id_),
- run.Raw('>>'),
- "/etc/ceph/ceph.conf",
- run.Raw("'")
- ]
- )
- rems.run(
- args=[
- 'sudo',
- 'sh',
- '-c',
- run.Raw("'"),
- 'echo',
- 'restapi',
- 'keyring',
- '=',
- '/etc/ceph/ceph.client.rest{id}.keyring'.format(id=id_),
- run.Raw('>>'),
- '/etc/ceph/ceph.conf',
- run.Raw("'"),
- ]
- )
- rems.run(
- args=[
- 'sudo',
- 'ceph',
- 'auth',
- 'import',
- '-i',
- '/etc/ceph/ceph.client.rest{id}.keyring'.format(id=id_),
- ]
- )
- with contextutil.nested(
- lambda: run_rest_api_daemon(ctx=ctx, api_clients=api_clients),):
- yield
-
+++ /dev/null
-# vim: ts=4 sw=4 smarttab expandtab
-
-import errno
-import json
-import logging
-import logging.handlers
-import os
-import rados
-import textwrap
-import xml.etree.ElementTree
-import xml.sax.saxutils
-
-import flask
-from ceph_argparse import \
- ArgumentError, CephPgid, CephOsdName, CephChoices, CephPrefix, \
- concise_sig, descsort, parse_funcsig, parse_json_funcsigs, \
- validate, json_command
-
-#
-# Globals and defaults
-#
-
-DEFAULT_ADDR = '::'
-DEFAULT_PORT = '5000'
-DEFAULT_ID = 'restapi'
-
-DEFAULT_BASEURL = '/api/v0.1'
-DEFAULT_LOG_LEVEL = 'warning'
-DEFAULT_LOGDIR = '/var/log/ceph'
-# default client name will be 'client.<DEFAULT_ID>'
-
-# network failure could keep the underlying json_command() waiting forever,
-# set a timeout, so it bails out on timeout.
-DEFAULT_TIMEOUT = 20
-# and retry in that case.
-DEFAULT_TRIES = 5
-
-# 'app' must be global for decorators, etc.
-APPNAME = '__main__'
-app = flask.Flask(APPNAME)
-
-LOGLEVELS = {
- 'critical': logging.CRITICAL,
- 'error': logging.ERROR,
- 'warning': logging.WARNING,
- 'info': logging.INFO,
- 'debug': logging.DEBUG,
-}
-
-
-def find_up_osd(app):
- '''
- Find an up OSD. Return the last one that's up.
- Returns id as an int.
- '''
- ret, outbuf, outs = json_command(app.ceph_cluster, prefix="osd dump",
- argdict=dict(format='json'))
- if ret:
- raise EnvironmentError(ret, 'Can\'t get osd dump output')
- try:
- osddump = json.loads(outbuf)
- except:
- raise EnvironmentError(errno.EINVAL, 'Invalid JSON back from osd dump')
- osds = [osd['osd'] for osd in osddump['osds'] if osd['up']]
- if not osds:
- return None
- return int(osds[-1])
-
-
-METHOD_DICT = {'r': ['GET'], 'w': ['PUT', 'DELETE']}
-
-
-def api_setup(app, conf, cluster, clientname, clientid, args):
- '''
- This is done globally, and cluster connection kept open for
- the lifetime of the daemon. librados should assure that even
- if the cluster goes away and comes back, our connection remains.
-
- Initialize the running instance. Open the cluster, get the command
- signatures, module, perms, and help; stuff them away in the app.ceph_urls
- dict. Also save app.ceph_sigdict for help() handling.
- '''
- def get_command_descriptions(cluster, target=('mon', '')):
- ret, outbuf, outs = json_command(cluster, target,
- prefix='get_command_descriptions',
- timeout=30)
- if ret:
- err = "Can't get command descriptions: {0}".format(outs)
- app.logger.error(err)
- raise EnvironmentError(ret, err)
-
- try:
- sigdict = parse_json_funcsigs(outbuf, 'rest')
- except Exception as e:
- err = "Can't parse command descriptions: {}".format(e)
- app.logger.error(err)
- raise EnvironmentError(err)
- return sigdict
-
- app.ceph_cluster = cluster or 'ceph'
- app.ceph_urls = {}
- app.ceph_sigdict = {}
- app.ceph_baseurl = ''
-
- conf = conf or ''
- cluster = cluster or 'ceph'
- clientid = clientid or DEFAULT_ID
- clientname = clientname or 'client.' + clientid
-
- app.ceph_cluster = rados.Rados(name=clientname, conffile=conf)
- app.ceph_cluster.conf_parse_argv(args)
- app.ceph_cluster.connect()
-
- app.ceph_baseurl = app.ceph_cluster.conf_get('restapi_base_url') \
- or DEFAULT_BASEURL
- if app.ceph_baseurl.endswith('/'):
- app.ceph_baseurl = app.ceph_baseurl[:-1]
- addr = app.ceph_cluster.conf_get('public_addr') or DEFAULT_ADDR
-
- if addr == '-':
- addr = DEFAULT_ADDR
- port = int(DEFAULT_PORT)
- else:
- # remove the type prefix from the conf value if any
- for t in ('legacy:', 'msgr2:'):
- if addr.startswith(t):
- addr = addr[len(t):]
- break
- # remove any nonce from the conf value
- addr = addr.split('/')[0]
- addr, port = addr.rsplit(':', 1)
- port = int(port) or int(DEFAULT_PORT)
-
- loglevel = app.ceph_cluster.conf_get('restapi_log_level') \
- or DEFAULT_LOG_LEVEL
- # ceph has a default log file for daemons only; clients (like this)
- # default to "". Override that for this particular client.
- logfile = app.ceph_cluster.conf_get('log_file')
- if not logfile:
- logfile = os.path.join(
- DEFAULT_LOGDIR,
- '{cluster}-{clientname}.{pid}.log'.format(
- cluster=cluster,
- clientname=clientname,
- pid=os.getpid()
- )
- )
- app.logger.addHandler(logging.handlers.WatchedFileHandler(logfile))
- app.logger.setLevel(LOGLEVELS[loglevel.lower()])
- for h in app.logger.handlers:
- h.setFormatter(logging.Formatter(
- '%(asctime)s %(name)s %(levelname)s: %(message)s'))
-
- app.ceph_sigdict = get_command_descriptions(app.ceph_cluster)
-
- osdid = find_up_osd(app)
- if osdid is not None:
- osd_sigdict = get_command_descriptions(app.ceph_cluster,
- target=('osd', int(osdid)))
-
- # shift osd_sigdict keys up to fit at the end of the mon's app.ceph_sigdict
- maxkey = sorted(app.ceph_sigdict.keys())[-1]
- maxkey = int(maxkey.replace('cmd', ''))
- osdkey = maxkey + 1
- for k, v in osd_sigdict.iteritems():
- newv = v
- newv['flavor'] = 'tell'
- globk = 'cmd' + str(osdkey)
- app.ceph_sigdict[globk] = newv
- osdkey += 1
-
- # app.ceph_sigdict maps "cmdNNN" to a dict containing:
- # 'sig', an array of argdescs
- # 'help', the helptext
- # 'module', the Ceph module this command relates to
- # 'perm', a 'rwx*' string representing required permissions, and also
- # a hint as to whether this is a GET or POST/PUT operation
- # 'avail', a comma-separated list of strings of consumers that should
- # display this command (filtered by parse_json_funcsigs() above)
- app.ceph_urls = {}
- for cmdnum, cmddict in app.ceph_sigdict.iteritems():
- cmdsig = cmddict['sig']
- flavor = cmddict.get('flavor', 'mon')
- url, params = generate_url_and_params(app, cmdsig, flavor)
- perm = cmddict['perm']
- for k in METHOD_DICT.iterkeys():
- if k in perm:
- methods = METHOD_DICT[k]
- urldict = {'paramsig': params,
- 'help': cmddict['help'],
- 'module': cmddict['module'],
- 'perm': perm,
- 'flavor': flavor,
- 'methods': methods, }
-
- # app.ceph_urls contains a list of urldicts (usually only one long)
- if url not in app.ceph_urls:
- app.ceph_urls[url] = [urldict]
- else:
- # If more than one, need to make union of methods of all.
- # Method must be checked in handler
- methodset = set(methods)
- for old_urldict in app.ceph_urls[url]:
- methodset |= set(old_urldict['methods'])
- methods = list(methodset)
- app.ceph_urls[url].append(urldict)
-
- # add, or re-add, rule with all methods and urldicts
- app.add_url_rule(url, url, handler, methods=methods)
- url += '.<fmt>'
- app.add_url_rule(url, url, handler, methods=methods)
-
- app.logger.debug("urls added: %d", len(app.ceph_urls))
-
- app.add_url_rule('/<path:catchall_path>', '/<path:catchall_path>',
- handler, methods=['GET', 'PUT'])
- return addr, port
-
-
-def generate_url_and_params(app, sig, flavor):
- '''
- Digest command signature from cluster; generate an absolute
- (including app.ceph_baseurl) endpoint from all the prefix words,
- and a list of non-prefix param descs
- '''
-
- url = ''
- params = []
- # the OSD command descriptors don't include the 'tell <target>', so
- # tack it onto the front of sig
- if flavor == 'tell':
- tellsig = parse_funcsig(['tell',
- {'name': 'target', 'type': 'CephOsdName'}])
- sig = tellsig + sig
-
- for desc in sig:
- # prefixes go in the URL path
- if desc.t == CephPrefix:
- url += '/' + desc.instance.prefix
- else:
- # tell/<target> is a weird case; the URL includes what
- # would everywhere else be a parameter
- if flavor == 'tell' and ((desc.t, desc.name) ==
- (CephOsdName, 'target')):
- url += '/<target>'
- else:
- params.append(desc)
-
- return app.ceph_baseurl + url, params
-
-
-#
-# end setup (import-time) functions, begin request-time functions
-#
-def concise_sig_for_uri(sig, flavor):
- '''
- Return a generic description of how one would send a REST request for sig
- '''
- prefix = []
- args = []
- ret = ''
- if flavor == 'tell':
- ret = 'tell/<osdid-or-pgid>/'
- for d in sig:
- if d.t == CephPrefix:
- prefix.append(d.instance.prefix)
- else:
- args.append(d.name + '=' + str(d))
- ret += '/'.join(prefix)
- if args:
- ret += '?' + '&'.join(args)
- return ret
-
-
-def show_human_help(prefix):
- '''
- Dump table showing commands matching prefix
- '''
- # XXX There ought to be a better discovery mechanism than an HTML table
- s = '<html><body><table border=1><th>Possible commands:</th><th>Method</th><th>Description</th>'
-
- permmap = {'r': 'GET', 'rw': 'PUT', 'rx': 'GET', 'rwx': 'PUT'}
- line = ''
- for cmdsig in sorted(app.ceph_sigdict.itervalues(), cmp=descsort):
- concise = concise_sig(cmdsig['sig'])
- flavor = cmdsig.get('flavor', 'mon')
- if flavor == 'tell':
- concise = 'tell/<target>/' + concise
- if concise.startswith(prefix):
- line = ['<tr><td>']
- wrapped_sig = textwrap.wrap(
- concise_sig_for_uri(cmdsig['sig'], flavor), 40
- )
- for sigline in wrapped_sig:
- line.append(flask.escape(sigline) + '\n')
- line.append('</td><td>')
- line.append(permmap[cmdsig['perm']])
- line.append('</td><td>')
- line.append(flask.escape(cmdsig['help']))
- line.append('</td></tr>\n')
- s += ''.join(line)
-
- s += '</table></body></html>'
- if line:
- return s
- else:
- return ''
-
-
-@app.before_request
-def log_request():
- '''
- For every request, log it. XXX Probably overkill for production
- '''
- app.logger.info(flask.request.url + " from " + flask.request.remote_addr + " " + flask.request.user_agent.string)
- app.logger.debug("Accept: %s", flask.request.accept_mimetypes.values())
-
-
-@app.route('/')
-def root_redir():
- return flask.redirect(app.ceph_baseurl)
-
-
-def make_response(fmt, output, statusmsg, errorcode):
- '''
- If formatted output, cobble up a response object that contains the
- output and status wrapped in enclosing objects; if nonformatted, just
- use output+status. Return HTTP status errorcode in any event.
- '''
- response = output
- if fmt:
- if 'json' in fmt:
- try:
- native_output = json.loads(output or '[]')
- response = json.dumps({"output": native_output,
- "status": statusmsg})
- except:
- return flask.make_response("Error decoding JSON from " +
- output, 500)
- elif 'xml' in fmt:
- # XXX
- # one is tempted to do this with xml.etree, but figuring out how
- # to 'un-XML' the XML-dumped output so it can be reassembled into
- # a piece of the tree here is beyond me right now.
- # ET = xml.etree.ElementTree
- # resp_elem = ET.Element('response')
- # o = ET.SubElement(resp_elem, 'output')
- # o.text = output
- # s = ET.SubElement(resp_elem, 'status')
- # s.text = statusmsg
- # response = ET.tostring(resp_elem)
- response = '''
-<response>
- <output>
- {0}
- </output>
- <status>
- {1}
- </status>
-</response>'''.format(response, xml.sax.saxutils.escape(statusmsg))
- else:
- if not 200 <= errorcode < 300:
- response = response + '\n' + statusmsg + '\n'
-
- return flask.make_response(response, errorcode)
-
-
-def handler(catchall_path=None, fmt=None, target=None):
- '''
- Main endpoint handler; generic for every endpoint, including catchall.
- Handles the catchall, anything with <.fmt>, anything with embedded
- <target>. Partial match or ?help cause the HTML-table
- "show_human_help" output.
- '''
-
- ep = catchall_path or flask.request.endpoint
- ep = ep.replace('.<fmt>', '')
-
- if ep[0] != '/':
- ep = '/' + ep
-
- # demand that endpoint begin with app.ceph_baseurl
- if not ep.startswith(app.ceph_baseurl):
- return make_response(fmt, '', 'Page not found', 404)
-
- rel_ep = ep[len(app.ceph_baseurl) + 1:]
-
- # Extensions override Accept: headers override defaults
- if not fmt:
- if 'application/json' in flask.request.accept_mimetypes.values():
- fmt = 'json'
- elif 'application/xml' in flask.request.accept_mimetypes.values():
- fmt = 'xml'
-
- prefix = ''
- pgid = None
- cmdtarget = 'mon', ''
-
- if target:
- # got tell/<target>; validate osdid or pgid
- name = CephOsdName()
- pgidobj = CephPgid()
- try:
- name.valid(target)
- except ArgumentError:
- # try pgid
- try:
- pgidobj.valid(target)
- except ArgumentError:
- return flask.make_response("invalid osdid or pgid", 400)
- else:
- # it's a pgid
- pgid = pgidobj.val
- cmdtarget = 'pg', pgid
- else:
- # it's an osd
- cmdtarget = name.nametype, name.nameid
-
- # prefix does not include tell/<target>/
- prefix = ' '.join(rel_ep.split('/')[2:]).strip()
- else:
- # non-target command: prefix is entire path
- prefix = ' '.join(rel_ep.split('/')).strip()
-
- # show "match as much as you gave me" help for unknown endpoints
- if ep not in app.ceph_urls:
- helptext = show_human_help(prefix)
- if helptext:
- resp = flask.make_response(helptext, 400)
- resp.headers['Content-Type'] = 'text/html'
- return resp
- else:
- return make_response(fmt, '', 'Invalid endpoint ' + ep, 400)
-
- found = None
- exc = ''
- for urldict in app.ceph_urls[ep]:
- if flask.request.method not in urldict['methods']:
- continue
- paramsig = urldict['paramsig']
-
- # allow '?help' for any specifically-known endpoint
- if 'help' in flask.request.args:
- response = flask.make_response('{0}: {1}'.
- format(prefix +
- concise_sig(paramsig),
- urldict['help']))
- response.headers['Content-Type'] = 'text/plain'
- return response
-
- # if there are parameters for this endpoint, process them
- if paramsig:
- args = {}
- for k, l in flask.request.args.iterlists():
- if len(l) == 1:
- args[k] = l[0]
- else:
- args[k] = l
-
- # is this a valid set of params?
- try:
- argdict = validate(args, paramsig)
- found = urldict
- break
- except Exception as e:
- exc += str(e)
- continue
- else:
- if flask.request.args:
- continue
- found = urldict
- argdict = {}
- break
-
- if not found:
- return make_response(fmt, '', exc + '\n', 400)
-
- argdict['format'] = fmt or 'plain'
- argdict['module'] = found['module']
- argdict['perm'] = found['perm']
- if pgid:
- argdict['pgid'] = pgid
-
- if not cmdtarget:
- cmdtarget = ('mon', '')
-
- app.logger.debug('sending command prefix %s argdict %s', prefix, argdict)
-
- for _ in range(DEFAULT_TRIES):
- ret, outbuf, outs = json_command(app.ceph_cluster, prefix=prefix,
- target=cmdtarget,
- inbuf=flask.request.data,
- argdict=argdict,
- timeout=DEFAULT_TIMEOUT)
- if ret != -errno.EINTR:
- break
- else:
- return make_response(fmt, '',
- 'Timedout: {0} ({1})'.format(outs, ret), 504)
- if ret:
- return make_response(fmt, '', 'Error: {0} ({1})'.format(outs, ret), 400)
-
- response = make_response(fmt, outbuf, outs or 'OK', 200)
- if fmt:
- contenttype = 'application/' + fmt.replace('-pretty', '')
- else:
- contenttype = 'text/plain'
- response.headers['Content-Type'] = contenttype
- return response
-
-
-#
-# Main entry point from wrapper/WSGI server: call with cmdline args,
-# get back the WSGI app entry point
-#
-def generate_app(conf, cluster, clientname, clientid, args):
- addr, port = api_setup(app, conf, cluster, clientname, clientid, args)
- app.ceph_addr = addr
- app.ceph_port = port
- return app