From a5e9eb018947bacc212b8b480ee149f8d6bf76d0 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Fri, 21 Dec 2018 10:47:52 +0000 Subject: [PATCH] mgr/mgr_module: @CLICommand decorator for declaring CLI commands Signed-off-by: Ricardo Dias --- src/mgr/ActivePyModule.cc | 2 +- src/mgr/PyModule.cc | 10 +++++ src/pybind/mgr/mgr_module.py | 77 +++++++++++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/mgr/ActivePyModule.cc b/src/mgr/ActivePyModule.cc index 56c34803fd97f..b923862fb7bae 100644 --- a/src/mgr/ActivePyModule.cc +++ b/src/mgr/ActivePyModule.cc @@ -193,7 +193,7 @@ int ActivePyModule::handle_command( inbuf.copy(0, inbuf.length(), instr); auto pResult = PyObject_CallMethod(pClassInstance, - const_cast("handle_command"), const_cast("s#O"), + const_cast("_handle_command"), const_cast("s#O"), instr.c_str(), instr.length(), py_cmd); Py_DECREF(py_cmd); diff --git a/src/mgr/PyModule.cc b/src/mgr/PyModule.cc index 562c6d418381a..2ece13be84365 100644 --- a/src/mgr/PyModule.cc +++ b/src/mgr/PyModule.cc @@ -474,6 +474,16 @@ int PyModule::walk_dict_list( int PyModule::load_commands() { + PyObject *pRegCmd = PyObject_CallMethod(pClass, + const_cast("_register_commands"), const_cast("()")); + if (pRegCmd != nullptr) { + Py_DECREF(pRegCmd); + } else { + derr << "Exception calling _register_commands on " << get_name() + << dendl; + derr << handle_pyerror() << dendl; + } + int r = walk_dict_list("COMMANDS", [this](PyObject *pCommand) -> int { ModuleCommand command; diff --git a/src/pybind/mgr/mgr_module.py b/src/pybind/mgr/mgr_module.py index 4c3bc486014ea..5ee2e1b0fa400 100644 --- a/src/pybind/mgr/mgr_module.py +++ b/src/pybind/mgr/mgr_module.py @@ -290,6 +290,64 @@ class CRUSHMap(ceph_module.BasePyCRUSH): return dict(result) +class CLICommand(object): + COMMANDS = {} + + def __init__(self, prefix, args="", desc="", perm="rw"): + self.prefix = prefix + self.args = args + self.args_dict = {} + self.desc = desc + self.perm = perm + self.func = None + self._parse_args() + + def _parse_args(self): + if not self.args: + return + args = self.args.split(" ") + for arg in args: + arg_desc = arg.strip().split(",") + arg_d = {} + for kv in arg_desc: + k, v = kv.split("=") + if k != "name": + arg_d[k] = v + else: + self.args_dict[v] = arg_d + + def __call__(self, func): + self.func = func + self.COMMANDS[self.prefix] = self + return self.func + + def call(self, mgr, cmd_dict, inbuf): + kwargs = {} + for a, d in self.args_dict.items(): + if 'req' in d and d['req'] == "false" and a not in cmd_dict: + continue + kwargs[a.replace("-", "_")] = cmd_dict[a] + if inbuf: + kwargs['inbuf'] = inbuf + return self.func(mgr, **kwargs) + + @classmethod + def dump_cmd_list(cls): + return [{ + 'cmd': '{} {}'.format(cmd.prefix, cmd.args), + 'desc': cmd.desc, + 'perm': cmd.perm + } for _, cmd in cls.COMMANDS.items()] + + +def CLIReadCommand(prefix, args="", desc=""): + return CLICommand(prefix, args, desc, "r") + + +def CLIWriteCommand(prefix, args="", desc=""): + return CLICommand(prefix, args, desc, "w") + + class MgrStandbyModule(ceph_module.BaseMgrStandbyModule): """ Standby modules only implement a serve and shutdown method, they @@ -436,6 +494,10 @@ class MgrModule(ceph_module.BaseMgrModule): def __del__(self): unconfigure_logger(self, self.module_name) + @classmethod + def _register_commands(cls): + cls.COMMANDS.extend(CLICommand.dump_cmd_list()) + @property def log(self): return self._logger @@ -512,7 +574,7 @@ class MgrModule(ceph_module.BaseMgrModule): """ Called by the plugin to fetch named cluster-wide objects from ceph-mgr. - :param str data_name: Valid things to fetch are osd_crush_map_text, + :param str data_name: Valid things to fetch are osd_crush_map_text, osd_map, osd_map_tree, osd_map_crush, config, mon_map, fs_map, osd_metadata, pg_summary, io_rate, pg_dump, df, osd_stats, health, mon_status, devices, device . @@ -524,7 +586,7 @@ class MgrModule(ceph_module.BaseMgrModule): return self._ceph_get(data_name) def _stattype_to_str(self, stattype): - + typeonly = stattype & self.PERFCOUNTER_TYPE_MASK if typeonly == 0: return 'gauge' @@ -535,7 +597,7 @@ class MgrModule(ceph_module.BaseMgrModule): return 'counter' if typeonly == self.PERFCOUNTER_HISTOGRAM: return 'histogram' - + return '' def _perfvalue_to_value(self, stattype, value): @@ -549,7 +611,7 @@ class MgrModule(ceph_module.BaseMgrModule): if unit == self.NONE: return "/s" elif unit == self.BYTES: - return "B/s" + return "B/s" def to_pretty_iec(self, n): for bits, suffix in [(60, 'Ei'), (50, 'Pi'), (40, 'Ti'), (30, 'Gi'), @@ -762,6 +824,11 @@ class MgrModule(ceph_module.BaseMgrModule): """ self._ceph_set_health_checks(checks) + def _handle_command(self, inbuf, cmd): + if cmd['prefix'] not in CLICommand.COMMANDS: + return self.handle_command(inbuf, cmd) + return CLICommand.COMMANDS[cmd['prefix']].call(self, cmd, inbuf) + def handle_command(self, inbuf, cmd): """ Called by ceph-mgr to request the plugin to handle one @@ -797,7 +864,7 @@ class MgrModule(ceph_module.BaseMgrModule): def _validate_module_option(self, key): """ - Helper: don't allow get/set config callers to + Helper: don't allow get/set config callers to access config options that they didn't declare in their schema. """ -- 2.39.5