Merge PR #41509 into master

* refs/pull/41509/head:
	common/cmdparse: fix CephBool validation for tell commands
	mgr/nfs: fix 'nfs export create' argument order
	common/cmdparse: emit proper json
	mon/MonCommands: add -- seperator to example
	qa/tasks/cephfs/test_nfs: fix export create test
	mgr: make mgr commands compat with pre-quincy mon
	doc/_ext/ceph_commands: handle non-positional args in docs
	mgr: fix reweight-by-utilization cephbool flag
	mon/MonCommands: convert some CephChoices to CephBool
	mgr/k8sevents: fix help strings
	pybind/mgr/mgr_module: fix help desc formatting
	mgr/orchestrator: clean up 'orch {daemon add,apply} rgw' args
	mgr/orchestrator: add end_positional to a few methods
	mgr/orchestrator: reformat a few methods
	pybind/ceph_argparse: stop parsing when we run out of positional args
	pybind/ceph_argparse: remove dead code
	pybind/mgr/mgr_module: infer non-positional args
	pybind/mgr/mgr_module: add separator for non-positional args
	command/cmdparse: use -- to separate positional from non-positional args
	pybind/ceph_argparse: adjust help text for non-positional args
	pybind/ceph_argparse: track a 'positional' property on cli args

Reviewed-by: Kefu Chai <kchai@redhat.com>
This commit is contained in:
Sage Weil 2021-06-07 10:02:52 -04:00
commit b18427da4b
17 changed files with 232 additions and 84 deletions

View File

@ -82,7 +82,7 @@ class CmdParam(object):
def __init__(self, type, name,
who=None, n=None, req=True, range=None, strings=None,
goodchars=None):
goodchars=None, positional=True):
self.type = type
self.name = name
self.who = who
@ -91,6 +91,7 @@ class CmdParam(object):
self.range = range.split('|') if range else []
self.strings = strings.split('|') if strings else []
self.goodchars = goodchars
self.positional = positional != 'false'
assert who == None
@ -200,7 +201,7 @@ TEMPLATE = '''
{%- if command.params %}
:Parameters:{% for param in command.params -%}
{{" -" | indent(12, not loop.first) }} **{{param.name}}**: {{ param.help() }}
{{" -" | indent(12, not loop.first) }} **{% if param.positional %}{{param.name}}{% else %}--{{param.name}}{% endif %}**: {{ param.help() }}
{% endfor %}
{% endif %}
:Ceph Module: {{ command.module }}

View File

@ -348,7 +348,8 @@ class TestNFS(MgrTestCase):
# Export-3 for subvolume with r only
self._cmd('fs', 'subvolume', 'create', self.fs_name, 'sub_vol')
fs_path = self._cmd('fs', 'subvolume', 'getpath', self.fs_name, 'sub_vol').strip()
self._create_export(export_id='3', extra_cmd=[self.pseudo_path+'2', '--readonly', fs_path])
self._create_export(export_id='3', extra_cmd=[self.pseudo_path+'2', '--readonly',
'--path', fs_path])
# Export-4 for subvolume
self._create_export(export_id='4', extra_cmd=[self.pseudo_path+'3', fs_path])
# Check if exports gets listed

View File

@ -138,13 +138,20 @@ dump_cmd_to_json(Formatter *f, uint64_t features, const string& cmd)
stringstream ss(cmd);
std::string word;
bool positional = true;
while (std::getline(ss, word, ' ')) {
if (word == "--") {
positional = false;
continue;
}
// if no , or =, must be a plain word to put out
if (word.find_first_of(",=") == string::npos) {
f->dump_string("arg", word);
continue;
}
// accumulate descriptor keywords in desckv
auto desckv = cmddesc_get_args(word);
// name the individual desc object based on the name key
@ -168,8 +175,20 @@ dump_cmd_to_json(Formatter *f, uint64_t features, const string& cmd)
}
// dump all the keys including name into the array
if (!positional) {
desckv["positional"] = "false";
}
for (auto [key, value] : desckv) {
f->dump_string(key, value);
if (key == "positional") {
if (!HAVE_FEATURE(features, SERVER_QUINCY)) {
continue;
}
f->dump_bool(key, value == "true" || value == "True");
} else if (key == "req" && HAVE_FEATURE(features, SERVER_QUINCY)) {
f->dump_bool(key, value == "true" || value == "True");
} else {
f->dump_string(key, value);
}
}
f->close_section(); // attribute object for individual desc
}
@ -550,6 +569,30 @@ bool validate_str_arg(std::string_view value,
}
}
bool validate_bool(CephContext *cct,
const cmdmap_t& cmdmap,
const arg_desc_t& desc,
const std::string_view name,
const std::string_view type,
std::ostream& os)
{
bool v;
try {
if (!cmd_getval(cmdmap, string(name), v)) {
if (auto req = desc.find("req");
req != end(desc) && req->second == "false") {
return true;
} else {
os << "missing required parameter: '" << name << "'";
return false;
}
}
return true;
} catch (const bad_cmd_get& e) {
return false;
}
}
template<bool is_vector,
typename T,
typename Value = std::conditional_t<is_vector,
@ -629,6 +672,9 @@ bool validate_cmd(CephContext* cct,
} else if (type == "CephFloat") {
return !validate_arg<false, double>(cct, cmdmap, arg_desc,
name, type, os);
} else if (type == "CephBool") {
return !validate_bool(cct, cmdmap, arg_desc,
name, type, os);
} else {
return !validate_arg<false, string>(cct, cmdmap, arg_desc,
name, type, os);
@ -671,4 +717,23 @@ bool cmd_getval(const cmdmap_t& cmdmap,
}
}
bool cmd_getval_compat_cephbool(
const cmdmap_t& cmdmap,
const std::string& k, bool& val)
{
try {
return cmd_getval(cmdmap, k, val);
} catch (bad_cmd_get& e) {
// try as legacy/compat CephChoices
std::string t;
if (!cmd_getval(cmdmap, k, t)) {
return false;
}
std::string expected = "--"s + k;
std::replace(expected.begin(), expected.end(), '_', '-');
val = (t == expected);
return true;
}
}
}

View File

@ -62,6 +62,10 @@ struct bad_cmd_get : public std::exception {
bool cmd_getval(const cmdmap_t& cmdmap,
std::string_view k, bool& val);
bool cmd_getval_compat_cephbool(
const cmdmap_t& cmdmap,
const std::string& k, bool& val);
template <typename T>
bool cmd_getval(const cmdmap_t& cmdmap,
std::string_view k, T& val)

View File

@ -1442,7 +1442,7 @@ bool DaemonServer::_handle_command(
return true;
}
bool no_increasing = false;
cmd_getval(cmdctx->cmdmap, "no_increasing", no_increasing);
cmd_getval_compat_cephbool(cmdctx->cmdmap, "no_increasing", no_increasing);
string out_str;
mempool::osdmap::map<int32_t, uint32_t> new_weights;
r = cluster_state.with_osdmap_and_pgmap([&](const OSDMap &osdmap, const PGMap& pgmap) {

View File

@ -111,7 +111,7 @@ COMMAND("osd reweight-by-utilization " \
"name=oload,type=CephInt,req=false " \
"name=max_change,type=CephFloat,req=false " \
"name=max_osds,type=CephInt,req=false " \
"name=no_increasing,type=CephChoices,strings=--no-increasing,req=false",\
"name=no_increasing,type=CephBool,req=false",\
"reweight OSDs by utilization [overload-percentage-for-consideration, default 120]", \
"osd", "rw")
COMMAND("osd test-reweight-by-utilization " \
@ -181,7 +181,7 @@ COMMAND("service status",
"dump service state", "service", "r")
COMMAND("config show " \
"name=who,type=CephString name=key,type=CephString,req=False",
"name=who,type=CephString name=key,type=CephString,req=false",
"Show running configuration",
"mgr", "r")
COMMAND("config show-with-defaults " \
@ -203,7 +203,7 @@ COMMAND("device ls-by-host name=host,type=CephString",
"mgr", "r")
COMMAND("device set-life-expectancy name=devid,type=CephString "\
"name=from,type=CephString "\
"name=to,type=CephString,req=False",
"name=to,type=CephString,req=false",
"Set predicted device life expectancy",
"mgr", "rw")
COMMAND("device rm-life-expectancy name=devid,type=CephString",

View File

@ -12,6 +12,7 @@
*/
#include <Python.h>
#include <boost/algorithm/string/replace.hpp>
#include "common/errno.h"
#include "common/signal.h"
@ -258,6 +259,12 @@ void MgrStandby::send_beacon()
std::vector<MonCommand> commands = mgr_commands;
std::vector<MonCommand> py_commands = py_module_registry.get_commands();
commands.insert(commands.end(), py_commands.begin(), py_commands.end());
if (monc.monmap.min_mon_release < ceph_release_t::quincy) {
dout(10) << " stripping out positional=false quincy-ism" << dendl;
for (auto& i : commands) {
boost::replace_all(i.cmdstring, ",positional=false", "");
}
}
m->set_command_descs(commands);
dout(4) << "going active, including " << m->get_command_descs().size()
<< " commands in beacon" << dendl;

View File

@ -1184,10 +1184,10 @@ bool MgrMonitor::prepare_command(MonOpRequestRef op)
ss << "module '" << module << "' is already enabled (always-on)";
goto out;
}
string force;
cmd_getval(cmdmap, "force", force);
bool force = false;
cmd_getval_compat_cephbool(cmdmap, "force", force);
if (!pending_map.all_support_module(module) &&
force != "--force") {
!force) {
ss << "all mgr daemons do not support module '" << module << "', pass "
<< "--force to force enablement";
r = -ENOENT;
@ -1195,7 +1195,7 @@ bool MgrMonitor::prepare_command(MonOpRequestRef op)
}
std::string can_run_error;
if (force != "--force" && !pending_map.can_run_module(module, &can_run_error)) {
if (!force && !pending_map.can_run_module(module, &can_run_error)) {
ss << "module '" << module << "' reports that it cannot run on the active "
"manager daemon: " << can_run_error << " (pass --force to force "
"enablement)";

View File

@ -77,7 +77,9 @@
*
* COMMAND("auth add "
* "name=entity,type=CephString "
* "name=caps,type=CephString,n=N,req=false",
* "name=caps,type=CephString,n=N,req=false "
* "-- "
* "name=some_option,type=CephString,req=false",
* "add auth info for <name> from input file, or random key "
* "if no input given, and/or any caps specified in the command")
*
@ -88,6 +90,12 @@
* enters auth add client.admin 'mon rwx' 'osd *'. The result will be a
* JSON object like {"prefix":"auth add", "entity":"client.admin",
* "caps":["mon rwx", "osd *"]}.
*
* The -- separates positional from non-positional (and, by implication,
* optional) arguments. Note that CephBool is assumed to be non-positional
* and will also implicitly mark that any following arguments are
* non-positional.
*
* Note that
* - string literals are accumulated into 'prefix'
* - n=1 descriptors are given normal string or int object values
@ -474,7 +482,7 @@ COMMAND_WITH_FLAG("mon remove "
"remove monitor named <name>", "mon", "rw",
FLAG(DEPRECATED))
COMMAND("mon feature ls "
"name=with_value,type=CephChoices,strings=--with-value,req=false",
"name=with_value,type=CephBool,req=false",
"list available mon map features to be set/unset",
"mon", "r")
COMMAND("mon feature set "
@ -750,7 +758,7 @@ COMMAND("osd crush rule rename "
"rename crush rule <srcname> to <dstname>",
"osd", "rw")
COMMAND("osd crush tree "
"name=shadow,type=CephChoices,strings=--show-shadow,req=false",
"name=show_shadow,type=CephBool,req=false",
"dump crush buckets and items in a tree view",
"osd", "r")
COMMAND("osd crush ls name=node,type=CephString,goodchars=[A-Za-z0-9-_.]",
@ -1166,7 +1174,7 @@ COMMAND("osd force_recovery_stretch_mode " \
COMMAND("osd tier add "
"name=pool,type=CephPoolname "
"name=tierpool,type=CephPoolname "
"name=force_nonempty,type=CephChoices,strings=--force-nonempty,req=false",
"name=force_nonempty,type=CephBool,req=false",
"add the tier <tierpool> (the second one) to base pool <pool> (the first one)",
"osd", "rw")
COMMAND("osd tier rm "
@ -1256,7 +1264,7 @@ COMMAND("mgr services",
"mgr", "r")
COMMAND("mgr module enable "
"name=module,type=CephString "
"name=force,type=CephChoices,strings=--force,req=false",
"name=force,type=CephBool,req=false",
"enable mgr module", "mgr", "rw")
COMMAND("mgr module disable "
"name=module,type=CephString",
@ -1286,7 +1294,7 @@ COMMAND("config rm"
"config", "rw")
COMMAND("config get "
"name=who,type=CephString "
"name=key,type=CephString,req=False",
"name=key,type=CephString,req=false",
"Show configuration option(s) for an entity",
"config", "r")
COMMAND("config dump",
@ -1302,7 +1310,7 @@ COMMAND("config ls",
COMMAND("config assimilate-conf",
"Assimilate options from a conf, and return a new, minimal conf file",
"config", "rw")
COMMAND("config log name=num,type=CephInt,req=False",
COMMAND("config log name=num,type=CephInt,req=false",
"Show recent history of config changes",
"config", "r")
COMMAND("config reset "
@ -1352,7 +1360,7 @@ COMMAND_WITH_FLAG("connection scores reset",
"mon", "rwx",
FLAG(TELL))
COMMAND_WITH_FLAG("sync_force "
"name=validate,type=CephChoices,strings=--yes-i-really-mean-it,req=false",
"name=yes_i_really_mean_it,type=CephBool,req=false",
"force sync of and clear monitor store",
"mon", "rw",
FLAG(TELL))

View File

@ -337,9 +337,15 @@ int Monitor::do_admin_command(
} else if (command == "quorum_status") {
_quorum_status(f, out);
} else if (command == "sync_force") {
string validate;
if ((!cmd_getval(cmdmap, "validate", validate)) ||
(validate != "--yes-i-really-mean-it")) {
bool validate = false;
if (!cmd_getval(cmdmap, "yes_i_really_mean_it", validate)) {
std::string v;
if (cmd_getval(cmdmap, "validate", v) &&
v == "--yes-i-really-mean-it") {
validate = true;
}
}
if (!validate) {
err << "are you SURE? this will mean the monitor store will be erased "
"the next time the monitor is restarted. pass "
"'--yes-i-really-mean-it' if you really do.";

View File

@ -395,11 +395,7 @@ bool MonmapMonitor::preprocess_command(MonOpRequestRef op)
} else if (prefix == "mon feature ls") {
bool list_with_value = false;
string with_value;
if (cmd_getval(cmdmap, "with_value", with_value) &&
with_value == "--with-value") {
list_with_value = true;
}
cmd_getval_compat_cephbool(cmdmap, "with_value", list_with_value);
MonMap *p = mon.monmap;

View File

@ -6652,9 +6652,14 @@ bool OSDMonitor::preprocess_command(MonOpRequestRef op)
rs << "\n";
rdata.append(rs.str());
} else if (prefix == "osd crush tree") {
string shadow;
cmd_getval(cmdmap, "shadow", shadow);
bool show_shadow = shadow == "--show-shadow";
bool show_shadow = false;
if (!cmd_getval_compat_cephbool(cmdmap, "show_shadow", show_shadow)) {
std::string shadow;
if (cmd_getval(cmdmap, "shadow", shadow) &&
shadow == "--show-shadow") {
show_shadow = true;
}
}
boost::scoped_ptr<Formatter> f(Formatter::create(format));
if (f) {
f->open_object_section("crush_tree");
@ -12951,11 +12956,11 @@ bool OSDMonitor::prepare_command_impl(MonOpRequestRef op,
}
// make sure new tier is empty
string force_nonempty;
cmd_getval(cmdmap, "force_nonempty", force_nonempty);
bool force_nonempty = false;
cmd_getval_compat_cephbool(cmdmap, "force_nonempty", force_nonempty);
const pool_stat_t *pstats = mon.mgrstatmon()->get_pool_stat(tierpool_id);
if (pstats && pstats->stats.sum.num_objects != 0 &&
force_nonempty != "--force-nonempty") {
!force_nonempty) {
ss << "tier pool '" << tierpoolstr << "' is not empty; --force-nonempty to force";
err = -ENOTEMPTY;
goto reply;
@ -12967,8 +12972,8 @@ bool OSDMonitor::prepare_command_impl(MonOpRequestRef op,
goto reply;
}
if ((!tp->removed_snaps.empty() || !tp->snaps.empty()) &&
((force_nonempty != "--force-nonempty") ||
(!g_conf()->mon_debug_unsafe_allow_tier_with_nonempty_snaps))) {
(!force_nonempty ||
!g_conf()->mon_debug_unsafe_allow_tier_with_nonempty_snaps)) {
ss << "tier pool '" << tierpoolstr << "' has snapshot state; it cannot be added as a tier without breaking the pool";
err = -ENOTEMPTY;
goto reply;

View File

@ -168,32 +168,34 @@ class CephArgtype(object):
return []
@staticmethod
def _compound_type_to_argdesc(tp, attrs):
def _compound_type_to_argdesc(tp, attrs, positional):
# generate argdesc from Sequence[T], Tuple[T,..] and Optional[T]
orig_type = get_origin(tp)
type_args = get_args(tp)
if orig_type in (abc.Sequence, Sequence, List, list):
assert len(type_args) == 1
attrs['n'] = 'N'
return CephArgtype.to_argdesc(type_args[0], attrs)
return CephArgtype.to_argdesc(type_args[0], attrs, positional=positional)
elif orig_type is Tuple:
assert len(type_args) >= 1
inner_tp = type_args[0]
assert type_args.count(inner_tp) == len(type_args), \
f'all elements in {tp} should be identical'
attrs['n'] = str(len(type_args))
return CephArgtype.to_argdesc(inner_tp, attrs)
return CephArgtype.to_argdesc(inner_tp, attrs, positional=positional)
elif get_origin(tp) is Union:
# should be Union[t, NoneType]
assert len(type_args) == 2 and isinstance(None, type_args[1])
return CephArgtype.to_argdesc(type_args[0], attrs, True)
return CephArgtype.to_argdesc(type_args[0], attrs, True, positional)
else:
raise ValueError(f"unknown type '{tp}': '{attrs}'")
@staticmethod
def to_argdesc(tp, attrs, has_default=False):
def to_argdesc(tp, attrs, has_default=False, positional=True):
if has_default:
attrs['req'] = 'false'
if not positional:
attrs['positional'] = 'false'
CEPH_ARG_TYPES = {
str: CephString,
int: CephInt,
@ -208,7 +210,7 @@ class CephArgtype(object):
elif isinstance(tp, type) and issubclass(tp, enum.Enum):
return CephChoices(tp=tp).argdesc(attrs)
else:
return CephArgtype._compound_type_to_argdesc(tp, attrs)
return CephArgtype._compound_type_to_argdesc(tp, attrs, positional)
def argdesc(self, attrs):
attrs['type'] = type(self).__name__
@ -758,7 +760,8 @@ class CephPrefix(CephArgtype):
class argdesc(object):
"""
argdesc(typename, name='name', n=numallowed|N,
req=False, helptext=helptext, **kwargs (type-specific))
req=False, positional=True,
helptext=helptext, **kwargs (type-specific))
validation rules:
typename: type(**kwargs) will be constructed
@ -767,6 +770,7 @@ class argdesc(object):
name is used for parse errors and for constructing JSON output
n is a numeric literal or 'n|N', meaning "at least one, but maybe more"
req=False means the argument need not be present in the list
positional=False means the argument name must be specified, e.g. "--myoption value"
helptext is the associated help for the command
anything else are arguments to pass to the type constructor.
@ -775,15 +779,19 @@ class argdesc(object):
valid() will later be called with input to validate against it,
and will store the validated value in self.instance.val for extraction.
"""
def __init__(self, t, name=None, n=1, req=True, **kwargs):
def __init__(self, t, name=None, n=1, req=True, positional=True, **kwargs):
if isinstance(t, basestring):
self.t = CephPrefix
self.typeargs = {'prefix': t}
self.req = True
self.positional = True
else:
self.t = t
self.typeargs = kwargs
self.req = req in (True, 'True', 'true')
self.positional = positional in (True, 'True', 'true')
if not positional:
assert not req
self.name = name
self.N = (n in ['n', 'N'])
@ -828,34 +836,61 @@ class argdesc(object):
like str(), but omit parameter names (except for CephString,
which really needs them)
"""
if self.t == CephBool:
chunk = "--{0}".format(self.name.replace("_", "-"))
elif self.t == CephPrefix:
chunk = str(self.instance)
elif self.t == CephChoices:
if self.name == 'format':
chunk = f'--{self.name} {{{str(self.instance)}}}'
else:
if self.positional:
if self.t == CephBool:
chunk = "--{0}".format(self.name.replace("_", "-"))
elif self.t == CephPrefix:
chunk = str(self.instance)
elif self.t == CephOsdName:
# it just so happens all CephOsdName commands are named 'id' anyway,
# so <id|osd.id> is perfect.
chunk = '<id|osd.id>'
elif self.t == CephName:
# CephName commands similarly only have one arg of the
# type, so <type.id> is good.
chunk = '<type.id>'
elif self.t == CephInt:
chunk = '<{0}:int>'.format(self.name)
elif self.t == CephFloat:
chunk = '<{0}:float>'.format(self.name)
elif self.t == CephChoices:
if self.name == 'format':
# this is for talking to legacy clusters only; new clusters
# should properly mark format args as non-positional.
chunk = f'--{self.name} {{{str(self.instance)}}}'
else:
chunk = f'<{self.name}:{self.instance}>'
elif self.t == CephOsdName:
# it just so happens all CephOsdName commands are named 'id' anyway,
# so <id|osd.id> is perfect.
chunk = '<id|osd.id>'
elif self.t == CephName:
# CephName commands similarly only have one arg of the
# type, so <type.id> is good.
chunk = '<type.id>'
elif self.t == CephInt:
chunk = '<{0}:int>'.format(self.name)
elif self.t == CephFloat:
chunk = '<{0}:float>'.format(self.name)
else:
chunk = '<{0}>'.format(self.name)
s = chunk
if self.N:
s += '...'
if not self.req:
s = '[' + s + ']'
else:
chunk = '<{0}>'.format(self.name)
s = chunk
if self.N:
s += '...'
if not self.req:
s = '[' + s + ']'
# non-positional
if self.t == CephBool:
chunk = "--{0}".format(self.name.replace("_", "-"))
elif self.t == CephPrefix:
chunk = str(self.instance)
elif self.t == CephChoices:
chunk = f'--{self.name} {{{str(self.instance)}}}'
elif self.t == CephOsdName:
chunk = f'--{self.name} <id|osd.id>'
elif self.t == CephName:
chunk = f'--{self.name} <type.id>'
elif self.t == CephInt:
chunk = f'--{self.name} <int>'
elif self.t == CephFloat:
chunk = f'--{self.name} <float>'
else:
chunk = f'--{self.name} <value>'
s = chunk
if self.N:
s += '...'
if not self.req: # req should *always* be false
s = '[' + s + ']'
return s
def complete(self, s):
@ -917,12 +952,13 @@ def parse_funcsig(sig: Sequence[Union[str, Dict[str, str]]]) -> List[argdesc]:
kwargs = dict()
for key, val in desc.items():
if key not in ['type', 'name', 'n', 'req']:
if key not in ['type', 'name', 'n', 'req', 'positional']:
kwargs[key] = val
newsig.append(argdesc(t,
name=desc.get('name', None),
n=desc.get('n', 1),
req=desc.get('req', True),
positional=desc.get('positional', True),
**kwargs))
return newsig
@ -1123,10 +1159,6 @@ def validate(args: List[str],
# argdesc for the keyword argument, if we find one
kwarg_desc = None
# Track whether we need to push value back onto
# myargs in the case that this isn't a valid k=v
consumed_next = False
# Try both styles of keyword argument
kwarg_match = re.match(KWARG_EQUALS, myarg)
if kwarg_match:
@ -1164,6 +1196,10 @@ def validate(args: List[str],
store_arg(kwarg_desc, d)
continue
if not desc.positional:
# No more positional args!
raise ArgumentValid(f"Unexpected argument '{myarg}'")
# Don't handle something as a positional argument if it
# has a leading "--" unless it's a CephChoices (used for
# "--yes-i-really-mean-it")

View File

@ -1026,12 +1026,12 @@ class Module(MgrModule):
},
{
"cmd": "k8sevents set-access name=key,type=CephString",
"desc": "Set kubernetes access credentials. <key> must be cacrt or token and use -i <filename> syntax.\ne.g. ceph k8sevents set-access cacrt -i /root/ca.crt",
"desc": "Set kubernetes access credentials. <key> must be cacrt or token and use -i <filename> syntax (e.g., ceph k8sevents set-access cacrt -i /root/ca.crt).",
"perm": "rw"
},
{
"cmd": "k8sevents set-config name=key,type=CephString name=value,type=CephString",
"desc": "Set kubernetes config paramters. <key> must be server or namespace.\ne.g. ceph k8sevents set-config server https://localhost:30433",
"desc": "Set kubernetes config paramters. <key> must be server or namespace (e.g., ceph k8sevents set-config server https://localhost:30433).",
"perm": "rw"
},
{

View File

@ -321,22 +321,35 @@ class CLICommand(object):
@staticmethod
def load_func_metadata(f: HandlerFuncType) -> Tuple[str, Dict[str, Any], int, str]:
desc = inspect.getdoc(f) or ''
desc = (inspect.getdoc(f) or '').replace('\n', ' ')
full_argspec = inspect.getfullargspec(f)
arg_spec = full_argspec.annotations
first_default = len(arg_spec)
if full_argspec.defaults:
first_default -= len(full_argspec.defaults)
args = []
positional = True
for index, arg in enumerate(full_argspec.args):
if arg in CLICommand.KNOWN_ARGS:
continue
if arg == '_end_positional_':
positional = False
continue
if (
arg == 'format'
or arg_spec[arg] is Optional[bool]
or arg_spec[arg] is bool
):
# implicit switch to non-positional on any
# Optional[bool] or the --format option
positional = False
assert arg in arg_spec, \
f"'{arg}' is not annotated for {f}: {full_argspec}"
has_default = index >= first_default
args.append(CephArgtype.to_argdesc(arg_spec[arg],
dict(name=arg),
has_default))
has_default,
positional))
return desc, arg_spec, first_default, ' '.join(args)
def store_func_metadata(self, f: HandlerFuncType) -> None:

View File

@ -28,8 +28,8 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
fsname: str,
clusterid: str,
binding: str,
readonly: bool = False,
path: str = '/') -> Tuple[int, str, str]:
path: str = '/',
readonly: bool = False) -> Tuple[int, str, str]:
"""Create a cephfs export"""
# TODO Extend export creation for rgw.
return self.export_mgr.create_export(fsal_type='cephfs', fs_name=fsname,

View File

@ -611,6 +611,7 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule,
@_cli_read_command('orch ps')
def _list_daemons(self,
hostname: Optional[str] = None,
_end_positional_: int = 0,
service_name: Optional[str] = None,
daemon_type: Optional[str] = None,
daemon_id: Optional[str] = None,
@ -900,9 +901,10 @@ Usage:
@_cli_write_command('orch daemon add rgw')
def _rgw_add(self,
svc_id: str,
placement: Optional[str] = None,
_end_positional_: int = 0,
port: Optional[int] = None,
ssl: bool = False,
placement: Optional[str] = None,
inbuf: Optional[str] = None) -> HandleCommandResult:
"""Start RGW daemon(s)"""
if inbuf:
@ -974,7 +976,9 @@ Usage:
return HandleCommandResult(stdout=completion.result_str())
@_cli_write_command('orch daemon redeploy')
def _daemon_action_redeploy(self, name: str, image: Optional[str] = None) -> HandleCommandResult:
def _daemon_action_redeploy(self,
name: str,
image: Optional[str] = None) -> HandleCommandResult:
"""Redeploy a daemon (with a specifc image)"""
if '.' not in name:
raise OrchestratorError('%s is not a valid daemon name' % name)
@ -1088,11 +1092,12 @@ Usage:
@_cli_write_command('orch apply rgw')
def _apply_rgw(self,
svc_id: str,
placement: Optional[str] = None,
_end_positional_: int = 0,
realm: Optional[str] = None,
zone: Optional[str] = None,
port: Optional[int] = None,
ssl: bool = False,
placement: Optional[str] = None,
dry_run: bool = False,
format: Format = Format.plain,
unmanaged: bool = False,
@ -1342,6 +1347,7 @@ Usage:
@_cli_write_command('orch upgrade start')
def _upgrade_start(self,
image: Optional[str] = None,
_end_positional_: int = 0,
ceph_version: Optional[str] = None) -> HandleCommandResult:
"""Initiate upgrade"""
self._upgrade_check_image_name(image, ceph_version)