--- /dev/null
+import io
+import os
+import sys
+import contextlib
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive
+from jinja2 import Template
+from pcpp.preprocessor import Preprocessor
+from sphinx.util import logging
+from sphinx.util.console import bold
+
+logger = logging.getLogger(__name__)
+
+
+class Flags:
+ NOFORWARD = (1 << 0)
+ OBSOLETE = (1 << 1)
+ DEPRECATED = (1 << 2)
+ MGR = (1 << 3)
+ POLL = (1 << 4)
+ HIDDEN = (1 << 5)
+
+ VALS = {
+ NOFORWARD: 'no_forward',
+ OBSOLETE: 'obsolete',
+ DEPRECATED: 'deprecated',
+ MGR: 'mgr',
+ POLL: 'poll',
+ HIDDEN: 'hidden',
+ }
+
+ def __init__(self, fs):
+ self.fs = fs
+
+ def __contains__(self, other):
+ return other in str(self)
+
+ def __str__(self):
+ keys = Flags.VALS.keys()
+ es = {Flags.VALS[k] for k in keys if self.fs & k == k}
+ return ', '.join(sorted(es))
+
+ def __bool__(self):
+ return bool(str(self))
+
+
+class CmdParam(object):
+ t = {
+ 'CephInt': 'int',
+ 'CephString': 'str',
+ 'CephChoices': 'str',
+ 'CephPgid': 'str',
+ 'CephOsdName': 'str',
+ 'CephPoolname': 'str',
+ 'CephObjectname': 'str',
+ 'CephUUID': 'str',
+ 'CephEntityAddr': 'str',
+ 'CephIPAddr': 'str',
+ 'CephName': 'str',
+ 'CephBool': 'bool',
+ 'CephFloat': 'float',
+ 'CephFilepath': 'str',
+ }
+
+ bash_example = {
+ 'CephInt': '1',
+ 'CephString': 'string',
+ 'CephChoices': 'choice',
+ 'CephPgid': '0',
+ 'CephOsdName': 'osd.0',
+ 'CephPoolname': 'poolname',
+ 'CephObjectname': 'objectname',
+ 'CephUUID': 'uuid',
+ 'CephEntityAddr': 'entityaddr',
+ 'CephIPAddr': '0.0.0.0',
+ 'CephName': 'name',
+ 'CephBool': 'true',
+ 'CephFloat': '0.0',
+ 'CephFilepath': '/path/to/file',
+ }
+
+ def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None,
+ goodchars=None):
+ self.type = type
+ self.name = name
+ self.who = who
+ self.n = n == 'N'
+ self.req = req != 'false'
+ self.range = range.split('|') if range else []
+ self.strings = strings.split('|') if strings else []
+ self.goodchars = goodchars
+
+ assert who == None
+
+ def help(self):
+ advanced = []
+ if self.type != 'CephString':
+ advanced.append(self.type + ' ')
+ if self.range:
+ advanced.append('range= ``{}`` '.format('..'.join(self.range)))
+ if self.strings:
+ advanced.append('strings=({}) '.format(' '.join(self.strings)))
+ if self.goodchars:
+ advanced.append('goodchars= ``{}`` '.format(self.goodchars))
+ if self.n:
+ advanced.append('(can be repeated)')
+
+ advanced = advanced or ["(string)"]
+ return ' '.join(advanced)
+
+ def mk_example_value(self):
+ if self.type == 'CephChoices' and self.strings:
+ return self.strings[0]
+ if self.range:
+ return self.range[0]
+ return CmdParam.bash_example[self.type]
+
+ def mk_bash_example(self, simple):
+ val = self.mk_example_value()
+
+ if self.type == 'CephBool':
+ return '--' + self.name
+ if simple:
+ if self.type == "CephChoices" and self.strings:
+ return val
+ elif self.type == "CephString" and self.name != 'who':
+ return 'my_' + self.name
+ else:
+ return CmdParam.bash_example[self.type]
+ else:
+ return '--{}={}'.format(self.name, val)
+
+
+class CmdCommand(object):
+ def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None):
+ self.sig = [s for s in sig if isinstance(s, str)]
+ self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)],
+ key=lambda p: p.req, reverse=True)
+ self.help = desc
+ self.module = module
+ self.perm = perm
+ self.flags = Flags(flags)
+ self.needs_overload = False
+
+ def prefix(self):
+ return ' '.join(self.sig)
+
+ def is_reasonably_simple(self):
+ if len(self.params) > 3:
+ return False
+ if any(p.n for p in self.params):
+ return False
+ return True
+
+ def mk_bash_example(self):
+ simple = self.is_reasonably_simple()
+ line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params])
+ return line
+
+
+class Sig:
+ @staticmethod
+ def _param_to_sig(p):
+ try:
+ return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')}
+ except IndexError:
+ return p
+
+ @staticmethod
+ def from_cmd(cmd):
+ sig = cmd.split()
+ return [Sig._param_to_sig(s) or s for s in sig]
+
+
+TEMPLATE = '''
+.. This file is automatically generated. do not modify
+
+{% for command in commands %}
+
+{{ command.prefix() }}
+{{ command.prefix() | length * '^' }}
+
+{{ command.help | wordwrap(70)}}
+
+Example command:
+
+.. code-block:: bash
+
+ {{ command.mk_bash_example() }}
+{% if command.params %}
+Parameters:
+
+{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }}
+{% endfor %}{% endif %}
+Ceph Module:
+
+* *{{ command.module }}*
+
+Required Permissions:
+
+* *{{ command.perm }}*
+
+{% if command.flags %}Command Flags:
+
+* *{{ command.flags }}*
+{% endif %}
+{% endfor %}
+
+'''
+
+class CephMgrCommands(Directive):
+ """
+ extracts commands from specified mgr modules
+ """
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+
+ def _normalize_path(self, dirname):
+ my_dir = os.path.dirname(os.path.realpath(__file__))
+ src_dir = os.path.abspath(os.path.join(my_dir, '../..'))
+ return os.path.join(src_dir, dirname)
+
+ def _is_mgr_module(self, dirname, name):
+ if not os.path.isdir(os.path.join(dirname, name)):
+ return False
+ if not os.path.isfile(os.path.join(dirname, name, '__init__.py')):
+ return False
+ return name not in ['tests']
+
+ @contextlib.contextmanager
+ def mocked_modules(self):
+ # src/pybind/mgr/tests
+ from tests import mock
+ mock_imports = ['rados',
+ 'rbd',
+ 'cephfs',
+ 'dateutil',
+ 'dateutil.parser']
+ # make dashboard happy
+ mock_imports += ['ceph_argparse',
+ 'OpenSSL',
+ 'jwt',
+ 'bcrypt',
+ 'scipy',
+ 'jsonpatch',
+ 'rook.rook_client',
+ 'rook.rook_client.ceph',
+ 'cherrypy=3.2.3']
+
+ # make restful happy
+ mock_imports += ['pecan',
+ 'pecan.rest',
+ 'pecan.hooks',
+ 'werkzeug',
+ 'werkzeug.serving']
+
+ for m in mock_imports:
+ args = {}
+ parts = m.split('=', 1)
+ mocked = parts[0]
+ if len(parts) > 1:
+ args['__version__'] = parts[1]
+ sys.modules[mocked] = mock.Mock(**args)
+
+ try:
+ yield
+ finally:
+ for m in mock_imports:
+ mocked = m.split('=', 1)[0]
+ sys.modules.pop(mocked)
+
+ def _collect_module_commands(self, name):
+ with self.mocked_modules():
+ logger.info(bold(f"loading mgr module '{name}'..."))
+ mgr_mod = __import__(name, globals(), locals(), [], 0)
+ from tests import M
+ def subclass(x):
+ try:
+ return issubclass(x, M)
+ except TypeError:
+ return False
+ ms = [c for c in mgr_mod.__dict__.values()
+ if subclass(c) and 'Standby' not in c.__name__]
+ [m] = ms
+ assert isinstance(m.COMMANDS, list)
+ return m.COMMANDS
+
+ def _normalize_command(self, command):
+ if 'handler' in command:
+ del command['handler']
+ command['sig'] = Sig.from_cmd(command['cmd'])
+ del command['cmd']
+ command['flags'] = (1 << 3)
+ command['module'] = 'mgr'
+ return command
+
+ def _render_cmds(self, commands):
+ rendered = Template(TEMPLATE).render(commands=list(commands))
+ lines = rendered.split("\n")
+ assert lines
+ source = self.state_machine.input_lines.source(self.lineno -
+ self.state_machine.input_offset - 1)
+ self.state_machine.insert_input(lines, source)
+
+ def run(self):
+ module_path = self._normalize_path(self.arguments[0])
+ sys.path.insert(0, module_path)
+ os.environ['UNITTEST'] = 'true'
+ modules = [name for name in os.listdir(module_path)
+ if self._is_mgr_module(module_path, name)]
+ commands = sum([self._collect_module_commands(name) for name in modules], [])
+ cmds = [CmdCommand(**self._normalize_command(c)) for c in commands]
+ cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags]
+ cmds = sorted(cmds, key=lambda cmd: cmd.sig)
+ self._render_cmds(cmds)
+ return []
+
+class MyProcessor(Preprocessor):
+ def __init__(self):
+ super().__init__()
+ self.cmds = []
+ self.undef('__DATE__')
+ self.undef('__TIME__')
+ self.expand_linemacro = False
+ self.expand_filemacro = False
+ self.expand_countermacro = False
+ self.line_directive = '#line'
+ self.define("__PCPP_VERSION__ " + '')
+ self.define("__PCPP_ALWAYS_FALSE__ 0")
+ self.define("__PCPP_ALWAYS_TRUE__ 1")
+
+ def eval(self, src):
+ _cmds = []
+
+ NONE = 0
+ NOFORWARD = (1 << 0)
+ OBSOLETE = (1 << 1)
+ DEPRECATED = (1 << 2)
+ MGR = (1 << 3)
+ POLL = (1 << 4)
+ HIDDEN = (1 << 5)
+ TELL = (1 << 6)
+
+ def FLAG(a):
+ return a
+
+ def COMMAND(cmd, desc, module, perm):
+ _cmds.append({
+ 'cmd': cmd,
+ 'desc': desc,
+ 'module': module,
+ 'perm': perm
+ })
+
+ def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag):
+ _cmds.append({
+ 'cmd': cmd,
+ 'desc': desc,
+ 'module': module,
+ 'perm': perm,
+ 'flags': flag
+ })
+
+ self.parse(src)
+ out = io.StringIO()
+ self.write(out)
+ out.seek(0)
+ s = out.read()
+ exec(s, globals(), locals())
+ return _cmds
+
+
+class CephMonCommands(Directive):
+ """
+ extracts commands from specified header file
+ """
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+
+
+ def _src_dir(self):
+ my_dir = os.path.dirname(os.path.realpath(__file__))
+ return os.path.abspath(os.path.join(my_dir, '../..'))
+
+ def _parse_headers(self, headers):
+ src_dir = self._src_dir()
+ src = '\n'.join(f'#include "{src_dir}/{header}"' for header in headers)
+ return MyProcessor().eval(src)
+
+ def _normalize_command(self, command):
+ if 'handler' in command:
+ del command['handler']
+ command['sig'] = Sig.from_cmd(command['cmd'])
+ del command['cmd']
+ return command
+
+ def _render_cmds(self, commands):
+ rendered = Template(TEMPLATE).render(commands=list(commands))
+ lines = rendered.split("\n")
+ assert lines
+ source = self.state_machine.input_lines.source(self.lineno -
+ self.state_machine.input_offset - 1)
+ self.state_machine.insert_input(lines, source)
+
+ def run(self):
+ headers = self.arguments[0].split()
+ commands = self._parse_headers(headers)
+ cmds = [CmdCommand(**self._normalize_command(c)) for c in commands]
+ cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags]
+ cmds = sorted(cmds, key=lambda cmd: cmd.sig)
+ self._render_cmds(cmds)
+ return []
+
+
+def setup(app):
+ app.add_directive("ceph-mgr-commands", CephMgrCommands)
+ app.add_directive("ceph-mon-commands", CephMonCommands)
+
+ return {
+ 'version': '0.1',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
+++ /dev/null
-import os
-import json
-import sys
-from subprocess import check_output
-
-from jinja2 import Template
-
-
-class Flags:
- NOFORWARD = (1 << 0)
- OBSOLETE = (1 << 1)
- DEPRECATED = (1 << 2)
- MGR = (1 << 3)
- POLL = (1 << 4)
- HIDDEN = (1 << 5)
-
- VALS = {
- NOFORWARD: 'no_forward',
- OBSOLETE: 'obsolete',
- DEPRECATED: 'deprecated',
- MGR: 'mgr',
- POLL: 'poll',
- HIDDEN: 'hidden',
- }
-
- def __init__(self, fs):
- self.fs = fs
-
- def __contains__(self, other):
- return other in str(self)
-
- def __str__(self):
- keys = Flags.VALS.keys()
- es = {Flags.VALS[k] for k in keys if self.fs & k == k}
- return ', '.join(sorted(es))
-
- def __bool__(self):
- return bool(str(self))
-
-
-class CmdParam(object):
- t = {
- 'CephInt': 'int',
- 'CephString': 'str',
- 'CephChoices': 'str',
- 'CephPgid': 'str',
- 'CephOsdName': 'str',
- 'CephPoolname': 'str',
- 'CephObjectname': 'str',
- 'CephUUID': 'str',
- 'CephEntityAddr': 'str',
- 'CephIPAddr': 'str',
- 'CephName': 'str',
- 'CephBool': 'bool',
- 'CephFloat': 'float',
- 'CephFilepath': 'str',
- }
-
- bash_example = {
- 'CephInt': '1',
- 'CephString': 'string',
- 'CephChoices': 'choice',
- 'CephPgid': '0',
- 'CephOsdName': 'osd.0',
- 'CephPoolname': 'poolname',
- 'CephObjectname': 'objectname',
- 'CephUUID': 'uuid',
- 'CephEntityAddr': 'entityaddr',
- 'CephIPAddr': '0.0.0.0',
- 'CephName': 'name',
- 'CephBool': 'true',
- 'CephFloat': '0.0',
- 'CephFilepath': '/path/to/file',
- }
-
- def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None,
- goodchars=None):
- self.type = type
- self.name = name
- self.who = who
- self.n = n == 'N'
- self.req = req != 'false'
- self.range = range.split('|') if range else []
- self.strings = strings.split('|') if strings else []
- self.goodchars = goodchars
-
- assert who == None
-
- def help(self):
- advanced = []
- if self.type != 'CephString':
- advanced.append(self.type + ' ')
- if self.range:
- advanced.append('range= ``{}`` '.format('..'.join(self.range)))
- if self.strings:
- advanced.append('strings=({}) '.format(' '.join(self.strings)))
- if self.goodchars:
- advanced.append('goodchars= ``{}`` '.format(self.goodchars))
- if self.n:
- advanced.append('(can be repeated)')
-
- advanced = advanced or ["(string)"]
- return ' '.join(advanced)
-
- def mk_example_value(self):
- if self.type == 'CephChoices' and self.strings:
- return self.strings[0]
- if self.range:
- return self.range[0]
- return CmdParam.bash_example[self.type]
-
- def mk_bash_example(self, simple):
- val = self.mk_example_value()
-
- if self.type == 'CephBool':
- return '--' + self.name
- if simple:
- if self.type == "CephChoices" and self.strings:
- return val
- elif self.type == "CephString" and self.name != 'who':
- return 'my_' + self.name
- else:
- return CmdParam.bash_example[self.type]
- else:
- return '--{}={}'.format(self.name, val)
-
-
-class CmdCommand(object):
- def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None):
- self.sig = [s for s in sig if isinstance(s, str)]
- self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)],
- key=lambda p: p.req, reverse=True)
- self.help = desc
- self.module = module
- self.perm = perm
- self.flags = Flags(flags)
- self.needs_overload = False
-
- def prefix(self):
- return ' '.join(self.sig)
-
- def is_reasonably_simple(self):
- if len(self.params) > 3:
- return False
- if any(p.n for p in self.params):
- return False
- return True
-
- def mk_bash_example(self):
- simple = self.is_reasonably_simple()
- line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params])
- return line
-
-
-tpl = '''
-.. This file is automatically generated. do not modify
-
-{% for command in commands %}
-
-{{ command.prefix() }}
-{{ command.prefix() | length * '^' }}
-
-{{ command.help | wordwrap(70)}}
-
-Example command:
-
-.. code-block:: bash
-
- {{ command.mk_bash_example() }}
-{% if command.params %}
-Parameters:
-
-{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }}
-{% endfor %}{% endif %}
-Ceph Module:
-
-* *{{ command.module }}*
-
-Required Permissions:
-
-* *{{ command.perm }}*
-
-{% if command.flags %}Command Flags:
-
-* *{{ command.flags }}*
-{% endif %}
-{% endfor %}
-
-'''
-
-def mk_sigs(all):
- sigs = [CmdCommand(**e) for e in all]
- sigs = [s for s in sigs if 'hidden' not in s.flags]
- sigs = sorted(sigs, key=lambda f: f.sig)
-
-
- tm = Template(tpl)
- msg = tm.render(commands=list(sigs))
-
- print(msg)
-
-
-if __name__ == '__main__':
- script_dir = os.path.dirname(os.path.realpath(__file__))
- commands = json.loads(check_output([sys.executable, script_dir + '/../../src/script/gen_static_command_descriptions.py']))
- mk_sigs(commands)
+++ /dev/null
-"""
-Prints a statically compiled list of all commands.
-
-See
-
-* /admin/doc-requirements.txt
-* /doc/scripts/gen_mon_command_api.py
-
-Rational for putting this file here is to allow others to make use of this output.
-"""
-import json
-import os
-import io
-import sys
-
-from pcpp.preprocessor import Preprocessor, OutputDirective, Action
-
-os.environ['UNITTEST'] = 'true'
-
-script_dir = os.path.dirname(os.path.realpath(__file__))
-
-mgr_dir = os.path.abspath(script_dir + '/../../src/pybind/mgr')
-
-sys.path.insert(0, mgr_dir)
-
-from tests import mock, M
-
-
-def param_to_sig(p):
- try:
- return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')}
- except IndexError:
- return p
-
-def cmd_to_sig(cmd):
- sig = cmd.split()
- return [param_to_sig(s) or s for s in sig]
-
-
-def list_mgr_module(m_name):
- sys.modules['rados'] = mock.Mock()
- sys.modules['rbd'] = mock.Mock()
- sys.modules['cephfs'] = mock.Mock()
- sys.modules['dateutil'] = mock.Mock()
- sys.modules['dateutil.parser'] = mock.Mock()
-
- # make dashboard happy
- sys.modules['OpenSSL'] = mock.Mock()
- sys.modules['jwt'] = mock.Mock()
- sys.modules['bcrypt'] = mock.Mock()
-
- sys.modules['scipy'] = mock.Mock()
- sys.modules['jsonpatch'] = mock.Mock()
- sys.modules['rook.rook_client'] = mock.Mock()
- sys.modules['rook.rook_client.ceph'] = mock.Mock()
-
- sys.modules['cherrypy'] = mock.Mock(__version__="3.2.3")
-
- # make restful happy:
- sys.modules['pecan'] = mock.Mock()
- sys.modules['pecan.rest'] = mock.Mock()
- sys.modules['pecan.hooks'] = mock.Mock()
- sys.modules['werkzeug'] = mock.Mock()
- sys.modules['werkzeug.serving'] = mock.Mock()
-
- mgr_mod = __import__(m_name, globals(), locals(), [], 0)
-
- def subclass(x):
- try:
- return issubclass(x, M)
- except TypeError:
- return False
-
- ms = [c for c in mgr_mod.__dict__.values() if subclass(c) and 'Standby' not in c.__name__]
- [m] = ms
- assert isinstance(m.COMMANDS, list)
- return m.COMMANDS
-
-
-def from_mgr_modules():
- names = [name for name in os.listdir(mgr_dir)
- if os.path.isdir(os.path.join(mgr_dir, name)) and
- os.path.isfile(os.path.join(mgr_dir, name, '__init__.py')) and
- name not in ['tests']]
-
- comms = sum([list_mgr_module(name) for name in names], [])
- for c in comms:
- if 'handler' in c:
- del c['handler']
- c['sig'] = cmd_to_sig(c['cmd'])
- del c['cmd']
- c['flags'] = (1 << 3)
- c['module'] = 'mgr'
- return comms
-
-
-def from_mon_commands_h():
- input_str = """
- #include "{script_dir}/../mon/MonCommands.h"
- #include "{script_dir}/../mgr/MgrCommands.h"
- """.format(script_dir=script_dir)
-
- cmds = []
-
- class MyProcessor(Preprocessor):
- def __init__(self):
- super(MyProcessor, self).__init__()
- self.undef('__DATE__')
- self.undef('__TIME__')
- self.expand_linemacro = False
- self.expand_filemacro = False
- self.expand_countermacro = False
- self.line_directive = '#line'
- self.define("__PCPP_VERSION__ " + '')
- self.define("__PCPP_ALWAYS_FALSE__ 0")
- self.define("__PCPP_ALWAYS_TRUE__ 1")
- self.parse(input_str)
- out = io.StringIO()
- self.write(out)
- out.seek(0)
- s = out.read()
-
- NONE = 0
- NOFORWARD = (1 << 0)
- OBSOLETE = (1 << 1)
- DEPRECATED = (1 << 2)
- MGR = (1 << 3)
- POLL = (1 << 4)
- HIDDEN = (1 << 5)
- TELL = (1 << 6)
-
- def FLAG(a):
- return a
-
- def COMMAND(cmd, desc, module, perm):
- cmds.append({
- 'cmd': cmd,
- 'desc': desc,
- 'module': module,
- 'perm': perm
- })
-
- def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag):
- cmds.append({
- 'cmd': cmd,
- 'desc': desc,
- 'module': module,
- 'perm': perm,
- 'flags': flag
- })
-
- exec(s, globals(), locals())
-
- MyProcessor()
- for c in cmds:
- if 'handler' in c:
- del c['handler']
- c['sig'] = cmd_to_sig(c['cmd'])
- del c['cmd']
- return cmds
-
-
-def gen_commands_dicts():
- comms = from_mon_commands_h() + from_mgr_modules()
- comms = sorted(comms, key=lambda c: [e for e in c['sig'] if isinstance(e, str)])
- return comms
-
-if __name__ == '__main__':
- print(json.dumps(gen_commands_dicts(), indent=2, sort_keys=True))