]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
tools: cleanup: rip out ceph-rest-api
authorNathan Cutler <ncutler@suse.com>
Wed, 6 Sep 2017 18:09:44 +0000 (20:09 +0200)
committerNathan Cutler <ncutler@suse.com>
Mon, 5 Mar 2018 21:01:51 +0000 (22:01 +0100)
Obsoleted by the mgr REST API.

Fixes: http://tracker.ceph.com/issues/21264
Signed-off-by: Nathan Cutler <ncutler@suse.com>
12 files changed:
ceph.spec.in
debian/ceph-mon.install
debian/control
doc/man/8/CMakeLists.txt
doc/man/8/ceph-rest-api.rst [deleted file]
doc/man_index.rst
doc/rados/man/index.rst
qa/tasks/rest_api.py [deleted file]
src/CMakeLists.txt
src/ceph-rest-api [deleted file]
src/pybind/CMakeLists.txt
src/pybind/ceph_rest_api.py [deleted file]

index 7560cdfc98f14251972f241ce2bc963bdcc139cf..5583e8168448aed99acd8b0efb0988740930e09d 100644 (file)
@@ -333,13 +333,6 @@ Summary:   Ceph Monitor Daemon
 Group:         System/Filesystems
 %endif
 Requires:      ceph-base = %{_epoch_prefix}%{version}-%{release}
-# For ceph-rest-api
-%if 0%{?fedora} || 0%{?rhel}
-Requires:      python%{_python_buildid}-flask
-%endif
-%if 0%{?suse_version}
-Requires:      python%{_python_buildid}-Flask
-%endif
 %description mon
 ceph-mon is the cluster monitor daemon for the Ceph distributed file
 system. One or more instances of ceph-mon form a Paxos part-time
@@ -1319,16 +1312,8 @@ fi
 
 %files mon
 %{_bindir}/ceph-mon
-%{_bindir}/ceph-rest-api
 %{_bindir}/ceph-monstore-tool
 %{_mandir}/man8/ceph-mon.8*
-%{_mandir}/man8/ceph-rest-api.8*
-%if 0%{with python2}
-%{python_sitelib}/ceph_rest_api.py*
-%else
-%{python3_sitelib}/ceph_rest_api.py
-%{python3_sitelib}/__pycache__/ceph_rest_api.cpython*.py*
-%endif
 %{_unitdir}/ceph-mon@.service
 %{_unitdir}/ceph-mon.target
 %attr(750,ceph,ceph) %dir %{_localstatedir}/lib/ceph/mon
index a9785bddbb16a28a76a5b157d163020fb87c1eb1..ff429f17a6af4fefbc5d716c946890ce39812295 100644 (file)
@@ -1,7 +1,4 @@
 lib/systemd/system/ceph-mon*
 usr/bin/ceph-mon
 usr/bin/ceph-monstore-tool
-usr/bin/ceph-rest-api
-usr/lib/python*/dist-packages/ceph_rest_api.py*
 usr/share/man/man8/ceph-mon.8
-usr/share/man/man8/ceph-rest-api.8
index 8422873d7d2d94dd0cbff0367199d381f46afc9b..0c9ec39afe01568ed520009f753834dd95061309 100644 (file)
@@ -203,7 +203,6 @@ Description: debugging symbols for ceph-mgr
 Package: ceph-mon
 Architecture: linux-any
 Depends: ceph-base (= ${binary:Version}),
-         python-flask,
          ${misc:Depends},
          ${shlibs:Depends},
 Recommends: ceph-common,
index 84e7640962083d0e56fdc1246ff24eefc2d3347e..d559f4683b09396634d67a332f39598f22e7f425 100644 (file)
@@ -13,8 +13,7 @@ set(server_srcs
   crushtool.rst
   ceph-run.rst
   mount.ceph.rst
-  ceph-create-keys.rst
-  ceph-rest-api.rst)
+  ceph-create-keys.rst)
 if(WITH_TESTS)
 list(APPEND server_srcs
   ceph-debugpack.rst)
diff --git a/doc/man/8/ceph-rest-api.rst b/doc/man/8/ceph-rest-api.rst
deleted file mode 100644 (file)
index 9864a9b..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-: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)
index fbd0585cf821323f1c27ef18807336da7255b465..c9fcdfe60270f8d580c55345cf2796863a2b68ec 100644 (file)
@@ -22,7 +22,6 @@
    man/8/ceph-osd
    man/8/ceph-post-file
    man/8/ceph-rbdnamer
-   man/8/ceph-rest-api
    man/8/ceph-run
    man/8/ceph-syn
    man/8/ceph
index abeb88b1d38c7dc7a7e737a902ecd1bfcc2a9ad2..8cca2cf565e6f489823adf193f74cd1254e0788a 100644 (file)
@@ -10,7 +10,6 @@
    ../../man/8/ceph-volume-systemd.rst
    ../../man/8/ceph.rst
    ../../man/8/ceph-deploy.rst
-   ../../man/8/ceph-rest-api.rst
    ../../man/8/ceph-authtool.rst
    ../../man/8/ceph-clsinfo.rst
    ../../man/8/ceph-conf.rst
diff --git a/qa/tasks/rest_api.py b/qa/tasks/rest_api.py
deleted file mode 100644 (file)
index e86f77e..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-"""
-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
-
index e399af3dce2776ecf8e77cbf819461badf398e3a..33a8de9e4078cfd187235f1f51702f5cabadf1a8 100644 (file)
@@ -939,7 +939,6 @@ install(PROGRAMS
   ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ceph
   ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ceph-post-file
   ${CMAKE_SOURCE_DIR}/src/ceph-run
-  ${CMAKE_SOURCE_DIR}/src/ceph-rest-api
   ${CMAKE_SOURCE_DIR}/src/ceph-clsinfo
   DESTINATION bin)
 install(PROGRAMS
diff --git a/src/ceph-rest-api b/src/ceph-rest-api
deleted file mode 100755 (executable)
index d185a80..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python
-# vim: ts=4 sw=4 smarttab expandtab
-
-import argparse
-import inspect
-import os
-import sys
-
-# Make life easier on developers
-
-MYPATH = os.path.abspath(__file__)
-MYDIR = os.path.dirname(MYPATH)
-DEVMODEMSG = '*** DEVELOPER MODE: setting PYTHONPATH and LD_LIBRARY_PATH'
-
-def parse_args():
-    parser = argparse.ArgumentParser(description="Ceph REST API webapp")
-    parser.add_argument('-c', '--conf', help='Ceph configuration file',
-                        default='/etc/ceph/ceph.conf')
-    parser.add_argument('--cluster', help='Ceph cluster name')
-    parser.add_argument('-n', '--name', help='Ceph client name')
-    parser.add_argument('-i', '--id', help='Ceph client id', default='admin')
-
-    return parser.parse_known_args()
-
-# main
-
-parsed_args, rest = parse_args()
-
-# import now that env vars are available to imported module
-
-try:
-    import ceph_rest_api
-except EnvironmentError as e:
-    print >> sys.stderr, "Error importing ceph_rest_api: ", str(e)
-    sys.exit(1)
-
-# let other exceptions generate traceback
-
-app = ceph_rest_api.generate_app(
-    parsed_args.conf,
-    parsed_args.cluster,
-    parsed_args.name,
-    parsed_args.id,
-    rest,
-)
-
-files = [os.path.split(fr[1])[-1] for fr in inspect.stack()]
-if 'pdb.py' in files:
-    app.run(host=app.ceph_addr, port=app.ceph_port,
-            debug=True, use_reloader=False, use_debugger=False)
-else:
-    if __name__ == '__main__':
-        app.run(host=app.ceph_addr, port=app.ceph_port)
-    else:
-        application = app
index 9a37726f9962a6f6f9310fb0148b21436629d766..f9679d8f7957e17c21a821cd7a307400420f9c5a 100644 (file)
@@ -61,10 +61,6 @@ foreach(python_version ${py_vers})
     DESTINATION ${PYTHON${PYTHON_VERSION}_INSTDIR})
 endforeach()
 
-install(FILES
-  ${CMAKE_CURRENT_SOURCE_DIR}/ceph_rest_api.py
-  DESTINATION ${PYTHON${PYTHON_VERSION}_INSTDIR})
-
 if(WITH_MGR)
   if(NOT WITH_PYTHON2 AND MGR_PYTHON_VERSION_MAJOR EQUAL 2)
     message(FATAL_ERROR "mgr plugins require python2 binding")
diff --git a/src/pybind/ceph_rest_api.py b/src/pybind/ceph_rest_api.py
deleted file mode 100644 (file)
index 85895f1..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-# 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