Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr: Allow modules to get/set other module options #25651

Merged
merged 1 commit into from Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/mgr/ActivePyModules.cc
Expand Up @@ -26,6 +26,7 @@

// For ::config_prefix
#include "PyModule.h"
#include "PyModuleRegistry.h"

#include "ActivePyModules.h"
#include "DaemonServer.h"
Expand All @@ -40,11 +41,12 @@ ActivePyModules::ActivePyModules(PyModuleConfig &module_config_,
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);
}
Expand Down Expand Up @@ -519,6 +521,27 @@ bool ActivePyModules::get_config(const std::string &module_name,
}
}

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
{
Expand Down
8 changes: 6 additions & 2 deletions src/mgr/ActivePyModules.h
Expand Up @@ -32,6 +32,7 @@

class health_check_map_t;
class DaemonServer;
class PyModuleRegistry;

class ActivePyModules
{
Expand All @@ -46,6 +47,7 @@ class ActivePyModules
Client &client;
Finisher &finisher;
DaemonServer &server;
PyModuleRegistry &py_module_registry;


mutable Mutex lock{"ActivePyModules::lock"};
Expand All @@ -55,7 +57,7 @@ class ActivePyModules
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();

Expand Down Expand Up @@ -110,6 +112,9 @@ class 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);
Expand Down Expand Up @@ -165,4 +170,3 @@ class ActivePyModules
void cluster_log(const std::string &channel, clog_type prio,
const std::string &message);
};

69 changes: 53 additions & 16 deletions src/mgr/BaseMgrModule.cc
Expand Up @@ -386,28 +386,36 @@ ceph_option_get(BaseMgrModule *self, PyObject *args)
}

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only suggestion here would be to add const-ness here to self and args, but as long as you're falling back to ceph_get_module_ex, you would need to do it in both places, and just had a look at C++ Python API and not many consts over there, so that'd be a hell.

{
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*
Expand All @@ -424,22 +432,45 @@ ceph_store_get_prefix(BaseMgrModule *self, PyObject *args)
}

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)
Expand Down Expand Up @@ -974,12 +1005,18 @@ PyMethodDef BaseMgrModule_methods[] = {
{"_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"},

Expand Down
3 changes: 2 additions & 1 deletion src/mgr/PyModuleRegistry.cc
Expand Up @@ -199,7 +199,8 @@ void PyModuleRegistry::active_start(

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
Expand Down
7 changes: 7 additions & 0 deletions src/mgr/PyModuleRegistry.h
Expand Up @@ -122,6 +122,13 @@ class PyModuleRegistry
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.
*
Expand Down
30 changes: 30 additions & 0 deletions src/pybind/mgr/mgr_module.py
Expand Up @@ -823,6 +823,23 @@ def get_module_option(self, key, default=None):
self._validate_module_option(key)
return self._get_module_option(key, default)

def get_module_option_ex(self, module, key, default=None):
sebastian-philipp marked this conversation as resolved.
Show resolved Hide resolved
"""
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
Expand Down Expand Up @@ -866,6 +883,19 @@ def set_module_option(self, key, val):
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
Expand Down
80 changes: 77 additions & 3 deletions src/pybind/mgr/selftest/module.py
Expand Up @@ -29,9 +29,16 @@ class Module(MgrModule):
# 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 = [
Expand Down Expand Up @@ -260,6 +267,73 @@ def _self_test_config(self):
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")
Expand Down