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/dashboard: add TLS #21627

Merged
merged 10 commits into from May 2, 2018
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
11 changes: 7 additions & 4 deletions qa/tasks/mgr/dashboard/helper.py
Expand Up @@ -82,13 +82,16 @@ def _request(cls, url, method, data=None, params=None):
url = "{}{}".format(cls.base_uri, url)
log.info("request %s to %s", method, url)
if method == 'GET':
cls._resp = cls._session.get(url, params=params)
cls._resp = cls._session.get(url, params=params, verify=False)
elif method == 'POST':
cls._resp = cls._session.post(url, json=data, params=params)
cls._resp = cls._session.post(url, json=data, params=params,
verify=False)
elif method == 'DELETE':
cls._resp = cls._session.delete(url, json=data, params=params)
cls._resp = cls._session.delete(url, json=data, params=params,
verify=False)
elif method == 'PUT':
cls._resp = cls._session.put(url, json=data, params=params)
cls._resp = cls._session.put(url, json=data, params=params,
verify=False)
else:
assert False
try:
Expand Down
15 changes: 9 additions & 6 deletions qa/tasks/mgr/test_dashboard.py
Expand Up @@ -12,10 +12,15 @@
class TestDashboard(MgrTestCase):
MGRS_REQUIRED = 3

def test_standby(self):
def setUp(self):
super(TestDashboard, self).setUp()

self._assign_ports("dashboard", "server_port")
self._load_module("dashboard")
self.mgr_cluster.mon_manager.raw_cluster_cmd("dashboard",
"create-self-signed-cert")

def test_standby(self):
original_active = self.mgr_cluster.get_active_id()

original_uri = self._get_uri("dashboard")
Expand All @@ -30,14 +35,11 @@ def test_standby(self):

# The original active daemon should have come back up as a standby
# and be doing redirects to the new active daemon
r = requests.get(original_uri, allow_redirects=False)
r = requests.get(original_uri, allow_redirects=False, verify=False)
self.assertEqual(r.status_code, 303)
self.assertEqual(r.headers['Location'], failed_over_uri)

def test_urls(self):
self._assign_ports("dashboard", "server_port")
self._load_module("dashboard")

base_uri = self._get_uri("dashboard")

# This is a very simple smoke test to check that the dashboard can
Expand All @@ -51,7 +53,8 @@ def test_urls(self):
failures = []

for url in urls:
r = requests.get(base_uri + url, allow_redirects=False)
r = requests.get(base_uri + url, allow_redirects=False,
verify=False)
if r.status_code >= 300 and r.status_code < 400:
log.error("Unexpected redirect to: {0} (from {1})".format(
r.headers['Location'], base_uri))
Expand Down
4 changes: 3 additions & 1 deletion qa/tasks/mgr/test_module_selftest.py
Expand Up @@ -172,6 +172,8 @@ def test_selftest_command_spam(self):
# Use the dashboard to test that the mgr is still able to do its job
self._assign_ports("dashboard", "server_port")
self._load_module("dashboard")
self.mgr_cluster.mon_manager.raw_cluster_cmd("dashboard",
"create-self-signed-cert")

original_active = self.mgr_cluster.get_active_id()
original_standbys = self.mgr_cluster.get_standby_ids()
Expand All @@ -187,7 +189,7 @@ def test_selftest_command_spam(self):
for i in range(0, periods):
t1 = time.time()
# Check that an HTTP module remains responsive
r = requests.get(dashboard_uri)
r = requests.get(dashboard_uri, verify=False)
self.assertEqual(r.status_code, 200)

# Check that a native non-module command remains responsive
Expand Down
29 changes: 29 additions & 0 deletions src/mgr/BaseMgrStandbyModule.cc
Expand Up @@ -80,6 +80,32 @@ ceph_config_get(BaseMgrStandbyModule *self, PyObject *args)
}
}

static PyObject*
ceph_store_get(BaseMgrStandbyModule *self, PyObject *args)
{
char *what = nullptr;
if (!PyArg_ParseTuple(args, "s:ceph_store_get", &what)) {
derr << "Invalid args!" << dendl;
return nullptr;
}

// Drop GIL for blocking mon command execution
PyThreadState *tstate = PyEval_SaveThread();

std::string value;
bool found = self->this_module->get_store(what, &value);

PyEval_RestoreThread(tstate);

if (found) {
dout(10) << "ceph_store_get " << what << " found: " << value.c_str() << dendl;
return PyString_FromString(value.c_str());
} else {
dout(4) << "ceph_store_get " << what << " not found " << dendl;
Py_RETURN_NONE;
}
}

static PyObject*
ceph_get_active_uri(BaseMgrStandbyModule *self, PyObject *args)
{
Expand Down Expand Up @@ -110,6 +136,9 @@ PyMethodDef BaseMgrStandbyModule_methods[] = {
{"_ceph_get_config", (PyCFunction)ceph_config_get, METH_VARARGS,
"Get a configuration value"},

{"_ceph_get_store", (PyCFunction)ceph_store_get, METH_VARARGS,
"Get a KV store value"},

{"_ceph_get_active_uri", (PyCFunction)ceph_get_active_uri, METH_NOARGS,
"Get the URI of the active instance of this module, if any"},

Expand Down
2 changes: 1 addition & 1 deletion src/mgr/MgrStandby.cc
Expand Up @@ -400,7 +400,7 @@ void MgrStandby::handle_mgr_map(MMgrMap* mmap)
// I am the standby and someone else is active, start modules
// in standby mode to do redirects if needed
if (!py_module_registry.is_standby_running()) {
py_module_registry.standby_start();
py_module_registry.standby_start(monc);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/mgr/PyModuleRegistry.cc
Expand Up @@ -124,7 +124,7 @@ bool PyModuleRegistry::handle_mgr_map(const MgrMap &mgr_map_)



void PyModuleRegistry::standby_start()
void PyModuleRegistry::standby_start(MonClient &mc)
{
Mutex::Locker l(lock);
assert(active_modules == nullptr);
Expand All @@ -137,7 +137,7 @@ void PyModuleRegistry::standby_start()
dout(4) << "Starting modules in standby mode" << dendl;

standby_modules.reset(new StandbyPyModules(
mgr_map, module_config, clog));
mgr_map, module_config, clog, mc));

std::set<std::string> failed_modules;
for (const auto &i : modules) {
Expand Down
2 changes: 1 addition & 1 deletion src/mgr/PyModuleRegistry.h
Expand Up @@ -96,7 +96,7 @@ class PyModuleRegistry
const std::map<std::string, std::string> &kv_store,
MonClient &mc, LogChannelRef clog_, Objecter &objecter_,
Client &client_, Finisher &f);
void standby_start();
void standby_start(MonClient &mc);

bool is_standby_running() const
{
Expand Down
55 changes: 50 additions & 5 deletions src/mgr/StandbyPyModules.cc
Expand Up @@ -33,8 +33,10 @@

StandbyPyModules::StandbyPyModules(
const MgrMap &mgr_map_,
PyModuleConfig &module_config, LogChannelRef clog_)
: state(module_config),
PyModuleConfig &module_config,
LogChannelRef clog_,
MonClient &monc_)
: state(module_config, monc_),
clog(clog_)
{
state.set_mgr_map(mgr_map_);
Expand Down Expand Up @@ -123,9 +125,6 @@ int StandbyPyModule::load()
bool StandbyPyModule::get_config(const std::string &key,
std::string *value) const
{
PyThreadState *tstate = PyEval_SaveThread();
PyEval_RestoreThread(tstate);

const std::string global_key = PyModule::config_prefix
+ get_name() + "/" + key;

Expand All @@ -141,6 +140,52 @@ bool StandbyPyModule::get_config(const std::string &key,
});
}

bool StandbyPyModule::get_store(const std::string &key,
std::string *value) const
{

const std::string global_key = PyModule::config_prefix
+ get_name() + "/" + key;

dout(4) << __func__ << " key: " << global_key << dendl;

// Active modules use a cache of store values (kept up to date
// as writes pass through the active mgr), but standbys
// fetch values synchronously to get an up to date value.
// It's an acceptable cost because standby modules should not be
// doing a lot.

MonClient &monc = state.get_monc();

std::ostringstream cmd_json;
cmd_json << "{\"prefix\": \"config-key get\", \"key\": \""
<< global_key << "\"}";

bufferlist outbl;
std::string outs;
C_SaferCond c;
monc.start_mon_command(
{cmd_json.str()},
{},
&outbl,
&outs,
&c);

int r = c.wait();
if (r == -ENOENT) {
return false;
} else if (r != 0) {
// This is some internal error, not meaningful to python modules,
// so let them just see no value.
derr << __func__ << " error fetching store key '" << global_key << "': "
<< cpp_strerror(r) << " " << outs << dendl;
return false;
} else {
*value = outbl.to_str();
return true;
}
}

std::string StandbyPyModule::get_active_uri() const
{
std::string result;
Expand Down
13 changes: 10 additions & 3 deletions src/mgr/StandbyPyModules.h
Expand Up @@ -35,12 +35,13 @@ class StandbyPyModuleState

MgrMap mgr_map;
PyModuleConfig &module_config;
MonClient &monc;

public:


StandbyPyModuleState(PyModuleConfig &module_config_)
: module_config(module_config_)
StandbyPyModuleState(PyModuleConfig &module_config_, MonClient &monc_)
: module_config(module_config_), monc(monc_)
{}

void set_mgr_map(const MgrMap &mgr_map_)
Expand All @@ -50,6 +51,10 @@ class StandbyPyModuleState
mgr_map = mgr_map_;
}

// MonClient does all its own locking so we're happy to hand out
// references.
MonClient &get_monc() {return monc;};

template<typename Callback, typename...Args>
void with_mgr_map(Callback&& cb, Args&&...args) const
{
Expand Down Expand Up @@ -84,6 +89,7 @@ class StandbyPyModule : public PyModuleRunner
}

bool get_config(const std::string &key, std::string *value) const;
bool get_store(const std::string &key, std::string *value) const;
std::string get_active_uri() const;

int load();
Expand All @@ -104,7 +110,8 @@ class StandbyPyModules
StandbyPyModules(
const MgrMap &mgr_map_,
PyModuleConfig &module_config,
LogChannelRef clog_);
LogChannelRef clog_,
MonClient &monc);

int start_one(PyModuleRef py_module);

Expand Down
2 changes: 1 addition & 1 deletion src/pybind/mgr/dashboard/.pylintrc
Expand Up @@ -407,7 +407,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
ignored-modules=cherrypy,distutils

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
Expand Down