Add the _ceph_get_module_option_ex and _ceph_set_module_option_ex methods to the BaseMgrModule. This allows a module to get/set options from other modules, e.g. allow the Dashboard to set the telemetry module settings, either the other module is not enabled.
Fixes: https://tracker.ceph.com/issues/37722
Signed-off-by: Volker Theile <vtheile@suse.com>
// For ::config_prefix
#include "PyModule.h"
+#include "PyModuleRegistry.h"
#include "ActivePyModules.h"
#include "DaemonServer.h"
DaemonStateIndex &ds, ClusterState &cs,
MonClient &mc, LogChannelRef clog_,
LogChannelRef audit_clog_, Objecter &objecter_,
- Client &client_, Finisher &f, DaemonServer &server)
+ Client &client_, Finisher &f, DaemonServer &server,
+ PyModuleRegistry &pmr)
: module_config(module_config_), daemon_state(ds), cluster_state(cs),
monc(mc), clog(clog_), audit_clog(audit_clog_), objecter(objecter_),
client(client_), finisher(f),
- server(server), lock("ActivePyModules")
+ server(server), py_module_registry(pmr), lock("ActivePyModules")
{
store_cache = std::move(store_data);
}
}
}
+PyObject *ActivePyModules::get_typed_config(
+ const std::string &module_name,
+ const std::string &key) const
+{
+ if (!py_module_registry.module_exists(module_name)) {
+ derr << "Module '" << module_name << "' is not available" << dendl;
+ Py_RETURN_NONE;
+ }
+
+ std::string value;
+ bool found = get_config(module_name, key, &value);
+ if (found) {
+ PyModuleRef module = py_module_registry.get_module(module_name);
+ dout(10) << __func__ << " " << key << " found: " << value << dendl;
+ return module->get_typed_option_value(key, value);
+ }
+
+ dout(4) << __func__ << " " << key << " not found " << dendl;
+ Py_RETURN_NONE;
+}
+
PyObject *ActivePyModules::get_store_prefix(const std::string &module_name,
const std::string &prefix) const
{
class health_check_map_t;
class DaemonServer;
+class PyModuleRegistry;
class ActivePyModules
{
Client &client;
Finisher &finisher;
DaemonServer &server;
+ PyModuleRegistry &py_module_registry;
mutable Mutex lock{"ActivePyModules::lock"};
std::map<std::string, std::string> store_data,
DaemonStateIndex &ds, ClusterState &cs, MonClient &mc,
LogChannelRef clog_, LogChannelRef audit_clog_, Objecter &objecter_, Client &client_,
- Finisher &f, DaemonServer &server);
+ Finisher &f, DaemonServer &server, PyModuleRegistry &pmr);
~ActivePyModules();
void set_config(const std::string &module_name,
const std::string &key, const boost::optional<std::string> &val);
+ PyObject *get_typed_config(const std::string &module_name,
+ const std::string &key) const;
+
void set_health_checks(const std::string& module_name,
health_check_map_t&& checks);
void get_health_checks(health_check_map_t *checks);
void cluster_log(const std::string &channel, clog_type prio,
const std::string &message);
};
-
}
static PyObject*
-ceph_get_module_option(BaseMgrModule *self, PyObject *args)
+ceph_get_module_option_ex(BaseMgrModule *self, PyObject *args)
{
+ char *module = nullptr;
char *what = nullptr;
- if (!PyArg_ParseTuple(args, "s:ceph_get_module_option", &what)) {
+ if (!PyArg_ParseTuple(args, "ss:ceph_get_module_option_ex", &module, &what)) {
derr << "Invalid args!" << dendl;
return nullptr;
}
-
PyThreadState *tstate = PyEval_SaveThread();
- std::string value;
- bool found = self->py_modules->get_config(self->this_module->get_name(),
- what, &value);
-
+ auto pResult = self->py_modules->get_typed_config(module, what);
PyEval_RestoreThread(tstate);
+ return pResult;
+}
- if (found) {
- dout(10) << __func__ << " " << what << " found: " << value.c_str() << dendl;
- return self->this_module->py_module->get_typed_option_value(what, value);
- } else {
- dout(4) << __func__ << " " << what << " not found " << dendl;
- Py_RETURN_NONE;
+static PyObject*
+ceph_get_module_option(BaseMgrModule *self, PyObject *args)
+{
+ char *key = nullptr;
+ if (!PyArg_ParseTuple(args, "s:ceph_get_module_option", &key)) {
+ derr << "Invalid args!" << dendl;
+ return nullptr;
}
+ auto pKey = PyString_FromString(key);
+ auto pModule = PyString_FromString(self->this_module->get_name().c_str());
+ auto pArgs = PyTuple_Pack(2, pModule, pKey);
+ Py_DECREF(pKey);
+ Py_DECREF(pModule);
+ auto pResult = ceph_get_module_option_ex(self, pArgs);
+ Py_DECREF(pArgs);
+ return pResult;
}
static PyObject*
}
static PyObject*
-ceph_set_module_option(BaseMgrModule *self, PyObject *args)
+ceph_set_module_option_ex(BaseMgrModule *self, PyObject *args)
{
+ char *module = nullptr;
char *key = nullptr;
char *value = nullptr;
- if (!PyArg_ParseTuple(args, "sz:ceph_set_module_option", &key, &value)) {
+ if (!PyArg_ParseTuple(args, "ssz:ceph_set_module_option_ex",
+ &module, &key, &value)) {
+ derr << "Invalid args!" << dendl;
return nullptr;
}
boost::optional<string> val;
if (value) {
val = value;
}
- self->py_modules->set_config(self->this_module->get_name(), key, val);
+ self->py_modules->set_config(module, key, val);
Py_RETURN_NONE;
}
+static PyObject*
+ceph_set_module_option(BaseMgrModule *self, PyObject *args)
+{
+ char *key = nullptr;
+ char *value = nullptr;
+ if (!PyArg_ParseTuple(args, "sz:ceph_set_module_option", &key, &value)) {
+ derr << "Invalid args!" << dendl;
+ return nullptr;
+ }
+ auto pModule = PyString_FromString(self->this_module->get_name().c_str());
+ auto pKey = PyString_FromString(key);
+ auto pValue = PyString_FromString(value);
+ auto pArgs = PyTuple_Pack(3, pModule, pKey, pValue);
+ Py_DECREF(pValue);
+ Py_DECREF(pKey);
+ Py_DECREF(pModule);
+ auto pResult = ceph_set_module_option_ex(self, pArgs);
+ Py_DECREF(pArgs);
+ return pResult;
+}
static PyObject*
ceph_store_get(BaseMgrModule *self, PyObject *args)
{"_ceph_get_module_option", (PyCFunction)ceph_get_module_option, METH_VARARGS,
"Get a module configuration option value"},
+ {"_ceph_get_module_option_ex", (PyCFunction)ceph_get_module_option_ex, METH_VARARGS,
+ "Get a module configuration option value from the specified module"},
+
{"_ceph_get_store_prefix", (PyCFunction)ceph_store_get_prefix, METH_VARARGS,
"Get all KV store values with a given prefix"},
{"_ceph_set_module_option", (PyCFunction)ceph_set_module_option, METH_VARARGS,
"Set a module configuration option value"},
+ {"_ceph_set_module_option_ex", (PyCFunction)ceph_set_module_option_ex, METH_VARARGS,
+ "Set a module configuration option value for the specified module"},
+
{"_ceph_get_store", (PyCFunction)ceph_store_get, METH_VARARGS,
"Get a stored field"},
active_modules.reset(new ActivePyModules(
module_config, kv_store, ds, cs, mc,
- clog_, audit_clog_, objecter_, client_, f, server));
+ clog_, audit_clog_, objecter_, client_, f, server,
+ *this));
for (const auto &i : modules) {
// Anything we're skipping because of !can_run will be flagged
return modules.at(module_name);
}
+ bool module_exists(const std::string &module_name) const
+ {
+ std::lock_guard l(lock);
+ auto mod_iter = modules.find(module_name);
+ return mod_iter != modules.end();
+ }
+
/**
* Pass through command to the named module for execution.
*
self._validate_module_option(key)
return self._get_module_option(key, default)
+ def get_module_option_ex(self, module, key, default=None):
+ """
+ Retrieve the value of a persistent configuration setting
+ for the specified module.
+
+ :param str module: The name of the module, e.g. 'dashboard'
+ or 'telemetry'.
+ :param str key: The configuration key, e.g. 'server_addr'.
+ :param str,None default: The default value to use when the
+ returned value is ``None``. Defaults to ``None``.
+ :return: str,int,bool,float,None
+ """
+ if module == self.module_name:
+ self._validate_module_option(key)
+ r = self._ceph_get_module_option_ex(module, key)
+ return default if r is None else r
+
def get_store_prefix(self, key_prefix):
"""
Retrieve a dict of KV store keys to values, where the keys
self._validate_module_option(key)
return self._set_module_option(key, val)
+ def set_module_option_ex(self, module, key, val):
+ """
+ Set the value of a persistent configuration setting
+ for the specified module.
+
+ :param str module:
+ :param str key:
+ :param str val:
+ """
+ if module == self.module_name:
+ self._validate_module_option(key)
+ return self._ceph_set_module_option_ex(module, key, str(val))
+
def set_localized_module_option(self, key, val):
"""
Set localized configuration for this ceph-mgr instance
# The test code in qa/ relies on these options existing -- they
# are of course not really used for anything in the module
MODULE_OPTIONS = [
- {'name': 'testkey'},
- {'name': 'testlkey'},
- {'name': 'testnewline'}
+ {'name': 'testkey'},
+ {'name': 'testlkey'},
+ {'name': 'testnewline'},
+ {'name': 'roption1'},
+ {'name': 'roption2', 'type': 'str', 'default': 'xyz'},
+ {'name': 'rwoption1'},
+ {'name': 'rwoption2', 'type': 'int'},
+ {'name': 'rwoption3', 'type': 'float'},
+ {'name': 'rwoption4', 'type': 'str'},
+ {'name': 'rwoption5', 'type': 'bool'}
]
COMMANDS = [
self.set_localized_module_option("testkey", "testvalue")
assert self.get_localized_module_option("testkey") == "testvalue"
+ # Use default value.
+ assert self.get_module_option("roption1") is None
+ assert self.get_module_option("roption1", "foobar") == "foobar"
+ assert self.get_module_option("roption2") == "xyz"
+ assert self.get_module_option("roption2", "foobar") == "xyz"
+
+ # Option type is not defined => return as string.
+ self.set_module_option("rwoption1", 8080)
+ value = self.get_module_option("rwoption1")
+ assert isinstance(value, str)
+ assert value == "8080"
+
+ # Option type is defined => return as integer.
+ self.set_module_option("rwoption2", 10)
+ value = self.get_module_option("rwoption2")
+ assert isinstance(value, int)
+ assert value == 10
+
+ # Option type is defined => return as float.
+ self.set_module_option("rwoption3", 1.5)
+ value = self.get_module_option("rwoption3")
+ assert isinstance(value, float)
+ assert value == 1.5
+
+ # Option type is defined => return as string.
+ self.set_module_option("rwoption4", "foo")
+ value = self.get_module_option("rwoption4")
+ assert isinstance(value, str)
+ assert value == "foo"
+
+ # Option type is defined => return as bool.
+ self.set_module_option("rwoption5", False)
+ value = self.get_module_option("rwoption5")
+ assert isinstance(value, bool)
+ assert value is False
+
+ # Specified module does not exist => return None.
+ assert self.get_module_option_ex("foo", "bar") is None
+
+ # Specified key does not exist => return None.
+ assert self.get_module_option_ex("dashboard", "bar") is None
+
+ self.set_module_option_ex("telemetry", "contact", "test@test.com")
+ assert self.get_module_option_ex("telemetry", "contact") == "test@test.com"
+
+ # No option default value, so use the specified one.
+ assert self.get_module_option_ex("dashboard", "password") is None
+ assert self.get_module_option_ex("dashboard", "password", "foobar") == "foobar"
+
+ # Option type is not defined => return as string.
+ self.set_module_option_ex("dashboard", "server_port", 8080)
+ value = self.get_module_option_ex("dashboard", "server_port")
+ assert isinstance(value, str)
+ assert value == "8080"
+
+ # Option type is defined => return as integer.
+ self.set_module_option_ex("telemetry", "interval", 60)
+ value = self.get_module_option_ex("telemetry", "interval")
+ assert isinstance(value, int)
+ assert value == 60
+
+ # Option type is defined => return as bool.
+ self.set_module_option_ex("telemetry", "leaderboard", True)
+ value = self.get_module_option_ex("telemetry", "leaderboard")
+ assert isinstance(value, bool)
+ assert value is True
+
def _self_test_store(self):
existing_keys = set(self.get_store_prefix("test").keys())
self.set_store("testkey", "testvalue")