]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/mgr/PyModuleRegistry.cc
import ceph 15.2.14
[ceph.git] / ceph / src / mgr / PyModuleRegistry.cc
index 5cd1d844e38c5a749e35b7a6590ba8228856794c..7ee218dc06a0b6a8651630ada923cca5b9d9e414 100644 (file)
  * Foundation.  See file COPYING.
  */
 
+#include "PyModuleRegistry.h"
+
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error std::filesystem not available!
+#endif
 
 #include "include/stringify.h"
 #include "common/errno.h"
-#include "common/backport14.h"
+#include "common/split.h"
 
 #include "BaseMgrModule.h"
 #include "PyOSDMap.h"
 #include "BaseMgrStandbyModule.h"
 #include "Gil.h"
+#include "MgrContext.h"
+#include "mgr/mgr_commands.h"
 
 #include "ActivePyModules.h"
 
-#include "PyModuleRegistry.h"
-
-// definition for non-const static member
-std::string PyModuleRegistry::config_prefix;
-
-
-
 #define dout_context g_ceph_context
 #define dout_subsys ceph_subsys_mgr
 
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr[py] "
 
-namespace {
-  PyObject* log_write(PyObject*, PyObject* args) {
-    char* m = nullptr;
-    if (PyArg_ParseTuple(args, "s", &m)) {
-      auto len = strlen(m);
-      if (len && m[len-1] == '\n') {
-       m[len-1] = '\0';
-      }
-      dout(4) << m << dendl;
-    }
-    Py_RETURN_NONE;
-  }
-
-  PyObject* log_flush(PyObject*, PyObject*){
-    Py_RETURN_NONE;
-  }
-
-  static PyMethodDef log_methods[] = {
-    {"write", log_write, METH_VARARGS, "write stdout and stderr"},
-    {"flush", log_flush, METH_VARARGS, "flush"},
-    {nullptr, nullptr, 0, nullptr}
-  };
-}
-
-#undef dout_prefix
-#define dout_prefix *_dout << "mgr " << __func__ << " "
-
-
-
-std::string PyModule::get_site_packages()
-{
-  std::stringstream site_packages;
-
-  // CPython doesn't auto-add site-packages dirs to sys.path for us,
-  // but it does provide a module that we can ask for them.
-  auto site_module = PyImport_ImportModule("site");
-  assert(site_module);
-
-  auto site_packages_fn = PyObject_GetAttrString(site_module, "getsitepackages");
-  if (site_packages_fn != nullptr) {
-    auto site_packages_list = PyObject_CallObject(site_packages_fn, nullptr);
-    assert(site_packages_list);
-
-    auto n = PyList_Size(site_packages_list);
-    for (Py_ssize_t i = 0; i < n; ++i) {
-      if (i != 0) {
-        site_packages << ":";
-      }
-      site_packages << PyString_AsString(PyList_GetItem(site_packages_list, i));
-    }
-
-    Py_DECREF(site_packages_list);
-    Py_DECREF(site_packages_fn);
-  } else {
-    // Fall back to generating our own site-packages paths by imitating
-    // what the standard site.py does.  This is annoying but it lets us
-    // run inside virtualenvs :-/
-
-    auto site_packages_fn = PyObject_GetAttrString(site_module, "addsitepackages");
-    assert(site_packages_fn);
-
-    auto known_paths = PySet_New(nullptr);
-    auto pArgs = PyTuple_Pack(1, known_paths);
-    PyObject_CallObject(site_packages_fn, pArgs);
-    Py_DECREF(pArgs);
-    Py_DECREF(known_paths);
-    Py_DECREF(site_packages_fn);
-
-    auto sys_module = PyImport_ImportModule("sys");
-    assert(sys_module);
-    auto sys_path = PyObject_GetAttrString(sys_module, "path");
-    assert(sys_path);
-
-    dout(1) << "sys.path:" << dendl;
-    auto n = PyList_Size(sys_path);
-    bool first = true;
-    for (Py_ssize_t i = 0; i < n; ++i) {
-      dout(1) << "  " << PyString_AsString(PyList_GetItem(sys_path, i)) << dendl;
-      if (first) {
-        first = false;
-      } else {
-        site_packages << ":";
-      }
-      site_packages << PyString_AsString(PyList_GetItem(sys_path, i));
-    }
-
-    Py_DECREF(sys_path);
-    Py_DECREF(sys_module);
-  }
 
-  Py_DECREF(site_module);
 
-  return site_packages.str();
-}
-
-int PyModuleRegistry::init(const MgrMap &map)
+void PyModuleRegistry::init()
 {
-  Mutex::Locker locker(lock);
-
-  // Don't try and init me if you don't really have a map
-  assert(map.epoch > 0);
-
-  mgr_map = map;
-
-  // namespace in config-key prefixed by "mgr/"
-  config_prefix = std::string(g_conf->name.get_type_str()) + "/";
+  std::lock_guard locker(lock);
 
   // Set up global python interpreter
-  Py_SetProgramName(const_cast<char*>(PYTHON_EXECUTABLE));
+#if PY_MAJOR_VERSION >= 3
+#define WCHAR(s) L ## #s
+  Py_SetProgramName(const_cast<wchar_t*>(WCHAR(MGR_PYTHON_EXECUTABLE)));
+#undef WCHAR
+#else
+  Py_SetProgramName(const_cast<char*>(MGR_PYTHON_EXECUTABLE));
+#endif
+  // Add more modules
+  if (g_conf().get_val<bool>("daemonize")) {
+    PyImport_AppendInittab("ceph_logger", PyModule::init_ceph_logger);
+  }
+  PyImport_AppendInittab("ceph_module", PyModule::init_ceph_module);
   Py_InitializeEx(0);
 
   // Let CPython know that we will be calling it back from other
@@ -156,14 +72,19 @@ int PyModuleRegistry::init(const MgrMap &map)
   // Drop the GIL and remember the main thread state (current
   // thread state becomes NULL)
   pMainThreadState = PyEval_SaveThread();
-  assert(pMainThreadState != nullptr);
+  ceph_assert(pMainThreadState != nullptr);
 
   std::list<std::string> failed_modules;
 
+  const std::string module_path = g_conf().get_val<std::string>("mgr_module_path");
+  std::set<std::string> module_names = probe_modules(module_path);
   // Load python code
-  for (const auto& module_name : mgr_map.modules) {
+  for (const auto& module_name : module_names) {
     dout(1) << "Loading python module '" << module_name << "'" << dendl;
-    auto mod = ceph::make_unique<PyModule>(module_name);
+
+    // Everything starts disabled, set enabled flag on module
+    // when we see first MgrMap
+    auto mod = std::make_shared<PyModule>(module_name);
     int r = mod->load(pMainThreadState);
     if (r != 0) {
       // Don't use handle_pyerror() here; we don't have the GIL
@@ -172,159 +93,82 @@ int PyModuleRegistry::init(const MgrMap &map)
         << cpp_strerror(r) << dendl;
       failed_modules.push_back(module_name);
       // Don't drop out here, load the other modules
-    } else {
-      // Success!
-      modules[module_name] = std::move(mod);
     }
-  }
 
+    // Record the module even if the load failed, so that we can
+    // report its loading error
+    modules[module_name] = std::move(mod);
+  }
+  if (module_names.empty()) {
+    clog->error() << "No ceph-mgr modules found in " << module_path;
+  }
   if (!failed_modules.empty()) {
     clog->error() << "Failed to load ceph-mgr modules: " << joinify(
         failed_modules.begin(), failed_modules.end(), std::string(", "));
   }
-
-  return 0;
 }
 
-
-int PyModule::load(PyThreadState *pMainThreadState)
+bool PyModuleRegistry::handle_mgr_map(const MgrMap &mgr_map_)
 {
-  assert(pMainThreadState != nullptr);
-
-  // Configure sub-interpreter and construct C++-generated python classes
-  {
-    SafeThreadState sts(pMainThreadState);
-    Gil gil(sts);
-
-    auto thread_state = Py_NewInterpreter();
-    if (thread_state == nullptr) {
-      derr << "Failed to create python sub-interpreter for '" << module_name << '"' << dendl;
-      return -EINVAL;
-    } else {
-      pMyThreadState.set(thread_state);
-      // Some python modules do not cope with an unpopulated argv, so lets
-      // fake one.  This step also picks up site-packages into sys.path.
-      const char *argv[] = {"ceph-mgr"};
-      PySys_SetArgv(1, (char**)argv);
-
-      if (g_conf->daemonize) {
-        auto py_logger = Py_InitModule("ceph_logger", log_methods);
-#if PY_MAJOR_VERSION >= 3
-        PySys_SetObject("stderr", py_logger);
-        PySys_SetObject("stdout", py_logger);
-#else
-        PySys_SetObject(const_cast<char*>("stderr"), py_logger);
-        PySys_SetObject(const_cast<char*>("stdout"), py_logger);
-#endif
-      }
-
-      // Configure sys.path to include mgr_module_path
-      std::string sys_path = std::string(Py_GetPath()) + ":" + get_site_packages()
-                             + ":" + g_conf->get_val<std::string>("mgr_module_path");
-      dout(10) << "Computed sys.path '" << sys_path << "'" << dendl;
-
-      PySys_SetPath(const_cast<char*>(sys_path.c_str()));
-    }
-
-    PyMethodDef ModuleMethods[] = {
-      {nullptr}
-    };
-
-    // Initialize module
-    PyObject *ceph_module = Py_InitModule("ceph_module", ModuleMethods);
-    assert(ceph_module != nullptr);
-
-    auto load_class = [ceph_module](const char *name, PyTypeObject *type)
-    {
-      type->tp_new = PyType_GenericNew;
-      if (PyType_Ready(type) < 0) {
-          assert(0);
-      }
-      Py_INCREF(type);
-
-      PyModule_AddObject(ceph_module, name, (PyObject *)type);
-    };
-
-    load_class("BaseMgrModule", &BaseMgrModuleType);
-    load_class("BaseMgrStandbyModule", &BaseMgrStandbyModuleType);
-    load_class("BasePyOSDMap", &BasePyOSDMapType);
-    load_class("BasePyOSDMapIncremental", &BasePyOSDMapIncrementalType);
-    load_class("BasePyCRUSH", &BasePyCRUSHType);
-  }
-
-  // Environment is all good, import the external module
-  {
-    Gil gil(pMyThreadState);
-
-    // Load the module
-    PyObject *pName = PyString_FromString(module_name.c_str());
-    auto pModule = PyImport_Import(pName);
-    Py_DECREF(pName);
-    if (pModule == nullptr) {
-      derr << "Module not found: '" << module_name << "'" << dendl;
-      derr << handle_pyerror() << dendl;
-      return -ENOENT;
+  std::lock_guard l(lock);
+
+  if (mgr_map.epoch == 0) {
+    mgr_map = mgr_map_;
+
+    // First time we see MgrMap, set the enabled flags on modules
+    // This should always happen before someone calls standby_start
+    // or active_start
+    for (const auto &[module_name, module] : modules) {
+      const bool enabled = (mgr_map.modules.count(module_name) > 0);
+      module->set_enabled(enabled);
+      const bool always_on = (mgr_map.get_always_on_modules().count(module_name) > 0);
+      module->set_always_on(always_on);
     }
 
-    // Find the class
-    // TODO: let them call it what they want instead of just 'Module'
-    pClass = PyObject_GetAttrString(pModule, (const char*)"Module");
-    if (pClass == nullptr) {
-      derr << "Class not found in module '" << module_name << "'" << dendl;
-      derr << handle_pyerror() << dendl;
-      return -EINVAL;
-    }
+    return false;
+  } else {
+    bool modules_changed = mgr_map_.modules != mgr_map.modules ||
+      mgr_map_.always_on_modules != mgr_map.always_on_modules;
+    mgr_map = mgr_map_;
 
-    pStandbyClass = PyObject_GetAttrString(pModule,
-                                           (const char*)"StandbyModule");
-    if (pStandbyClass) {
-      dout(4) << "Standby mode available in module '" << module_name
-              << "'" << dendl;
-    } else {
-      dout(4) << "Standby mode not provided by module '" << module_name
-              << "'" << dendl;
-      PyErr_Clear();
+    if (standby_modules != nullptr) {
+      standby_modules->handle_mgr_map(mgr_map_);
     }
 
-    Py_DECREF(pModule);
+    return modules_changed;
   }
+}
 
-  return 0;
-} 
 
-PyModule::~PyModule()
-{
-  if (pMyThreadState.ts != nullptr) {
-    Gil gil(pMyThreadState, true);
-    Py_XDECREF(pClass);
-    Py_XDECREF(pStandbyClass);
-  }
-}
 
-void PyModuleRegistry::standby_start(MonClient *monc)
+void PyModuleRegistry::standby_start(MonClient &mc, Finisher &f)
 {
-  Mutex::Locker l(lock);
-  assert(active_modules == nullptr);
-  assert(standby_modules == nullptr);
-  assert(is_initialized());
+  std::lock_guard l(lock);
+  ceph_assert(active_modules == nullptr);
+  ceph_assert(standby_modules == nullptr);
+
+  // Must have seen a MgrMap by this point, in order to know
+  // which modules should be enabled
+  ceph_assert(mgr_map.epoch > 0);
 
   dout(4) << "Starting modules in standby mode" << dendl;
 
-  standby_modules.reset(new StandbyPyModules(monc, mgr_map, clog));
+  standby_modules.reset(new StandbyPyModules(
+        mgr_map, module_config, clog, mc, f));
 
   std::set<std::string> failed_modules;
   for (const auto &i : modules) {
-    if (i.second->pStandbyClass) {
-      dout(4) << "starting module " << i.second->get_name() << dendl;
-      int r = standby_modules->start_one(i.first,
-              i.second->pStandbyClass,
-              i.second->pMyThreadState);
-      if (r != 0) {
-        derr << "failed to start module '" << i.second->get_name()
-             << "'" << dendl;;
+    if (!(i.second->is_enabled() && i.second->get_can_run())) {
+      // report always_on modules with a standby mode that won't run
+      if (i.second->is_always_on() && i.second->pStandbyClass) {
         failed_modules.insert(i.second->get_name());
-        // Continue trying to load any other modules
       }
+      continue;
+    }
+
+    if (i.second->pStandbyClass) {
+      dout(4) << "starting module " << i.second->get_name() << dendl;
+      standby_modules->start_one(i.second);
     } else {
       dout(4) << "skipping module '" << i.second->get_name() << "' because "
                  "it does not implement a standby mode" << dendl;
@@ -339,17 +183,21 @@ void PyModuleRegistry::standby_start(MonClient *monc)
 }
 
 void PyModuleRegistry::active_start(
-            PyModuleConfig &config_,
-            DaemonStateIndex &ds, ClusterState &cs, MonClient &mc,
-            LogChannelRef clog_, Objecter &objecter_, Client &client_,
-            Finisher &f)
+            DaemonStateIndex &ds, ClusterState &cs,
+            const std::map<std::string, std::string> &kv_store,
+            MonClient &mc, LogChannelRef clog_, LogChannelRef audit_clog_,
+            Objecter &objecter_, Client &client_, Finisher &f,
+            DaemonServer &server)
 {
-  Mutex::Locker locker(lock);
+  std::lock_guard locker(lock);
 
   dout(4) << "Starting modules in active mode" << dendl;
 
-  assert(active_modules == nullptr);
-  assert(is_initialized());
+  ceph_assert(active_modules == nullptr);
+
+  // Must have seen a MgrMap by this point, in order to know
+  // which modules should be enabled
+  ceph_assert(mgr_map.epoch > 0);
 
   if (standby_modules != nullptr) {
     standby_modules->shutdown();
@@ -357,23 +205,25 @@ void PyModuleRegistry::active_start(
   }
 
   active_modules.reset(new ActivePyModules(
-              config_, ds, cs, mc, clog_, objecter_, client_, f));
+              module_config, kv_store, ds, cs, mc,
+              clog_, audit_clog_, objecter_, client_, f, server,
+              *this));
 
   for (const auto &i : modules) {
-    dout(4) << "Starting " << i.first << dendl;
-    int r = active_modules->start_one(i.first,
-            i.second->pClass,
-            i.second->pMyThreadState);
-    if (r != 0) {
-      derr << "Failed to run module in active mode ('" << i.first << "')"
-           << dendl;
+    // Anything we're skipping because of !can_run will be flagged
+    // to the user separately via get_health_checks
+    if (!(i.second->is_enabled() && i.second->is_loaded())) {
+      continue;
     }
+
+    dout(4) << "Starting " << i.first << dendl;
+    active_modules->start_one(i.second);
   }
 }
 
 void PyModuleRegistry::active_shutdown()
 {
-  Mutex::Locker locker(lock);
+  std::lock_guard locker(lock);
 
   if (active_modules != nullptr) {
     active_modules->shutdown();
@@ -383,7 +233,7 @@ void PyModuleRegistry::active_shutdown()
 
 void PyModuleRegistry::shutdown()
 {
-  Mutex::Locker locker(lock);
+  std::lock_guard locker(lock);
 
   if (standby_modules != nullptr) {
     standby_modules->shutdown();
@@ -418,33 +268,262 @@ void PyModuleRegistry::shutdown()
   Py_Finalize();
 }
 
-static void _list_modules(
-  const std::string path,
-  std::set<std::string> *modules)
+std::set<std::string> PyModuleRegistry::probe_modules(const std::string &path) const
+{
+  const auto opt = g_conf().get_val<std::string>("mgr_disabled_modules");
+  const auto disabled_modules = ceph::split(opt);
+
+  std::set<std::string> modules;
+  for (const auto& entry: fs::directory_iterator(path)) {
+    if (!fs::is_directory(entry)) {
+      continue;
+    }
+    const std::string name = entry.path().filename();
+    if (std::count(disabled_modules.begin(), disabled_modules.end(), name)) {
+      dout(10) << "ignoring disabled module " << name << dendl;
+      continue;
+    }
+    auto module_path = entry.path() / "module.py";
+    if (fs::exists(module_path)) {
+      modules.emplace(name);
+    }
+  }
+  return modules;
+}
+
+int PyModuleRegistry::handle_command(
+  const ModuleCommand& module_command,
+  const MgrSession& session,
+  const cmdmap_t &cmdmap,
+  const bufferlist &inbuf,
+  std::stringstream *ds,
+  std::stringstream *ss)
+{
+  if (active_modules) {
+    return active_modules->handle_command(module_command, session, cmdmap,
+                                          inbuf, ds, ss);
+  } else {
+    // We do not expect to be called before active modules is up, but
+    // it's straightfoward to handle this case so let's do it.
+    return -EAGAIN;
+  }
+}
+
+std::vector<ModuleCommand> PyModuleRegistry::get_py_commands() const
+{
+  std::lock_guard l(lock);
+
+  std::vector<ModuleCommand> result;
+  for (const auto& i : modules) {
+    i.second->get_commands(&result);
+  }
+
+  return result;
+}
+
+std::vector<MonCommand> PyModuleRegistry::get_commands() const
 {
-  DIR *dir = opendir(path.c_str());
-  if (!dir) {
-    return;
+  std::vector<ModuleCommand> commands = get_py_commands();
+  std::vector<MonCommand> result;
+  for (auto &pyc: commands) {
+    uint64_t flags = MonCommand::FLAG_MGR;
+    if (pyc.polling) {
+      flags |= MonCommand::FLAG_POLL;
+    }
+    result.push_back({pyc.cmdstring, pyc.helpstring, "mgr",
+                        pyc.perm, flags});
   }
-  struct dirent *entry = NULL;
-  while ((entry = readdir(dir)) != NULL) {
-    string n(entry->d_name);
-    string fn = path + "/" + n;
-    struct stat st;
-    int r = ::stat(fn.c_str(), &st);
-    if (r == 0 && S_ISDIR(st.st_mode)) {
-      string initfn = fn + "/module.py";
-      r = ::stat(initfn.c_str(), &st);
-      if (r == 0) {
-       modules->insert(n);
+  return result;
+}
+
+void PyModuleRegistry::get_health_checks(health_check_map_t *checks)
+{
+  std::lock_guard l(lock);
+
+  // Only the active mgr reports module issues
+  if (active_modules) {
+    active_modules->get_health_checks(checks);
+
+    std::map<std::string, std::string> dependency_modules;
+    std::map<std::string, std::string> failed_modules;
+
+    /*
+     * Break up broken modules into two categories:
+     *  - can_run=false: the module is working fine but explicitly
+     *    telling you that a dependency is missing.  Advise the user to
+     *    read the message from the module and install what's missing.
+     *  - failed=true or loaded=false: something unexpected is broken,
+     *    either at runtime (from serve()) or at load time.  This indicates
+     *    a bug and the user should be guided to inspect the mgr log
+     *    to investigate and gather evidence.
+     */
+
+    for (const auto &i : modules) {
+      auto module = i.second;
+      if (module->is_enabled() && !module->get_can_run()) {
+        dependency_modules[module->get_name()] = module->get_error_string();
+      } else if ((module->is_enabled() && !module->is_loaded())
+              || (module->is_failed() && module->get_can_run())) {
+        // - Unloadable modules are only reported if they're enabled,
+        //   to avoid spamming users about modules they don't have the
+        //   dependencies installed for because they don't use it.
+        // - Failed modules are only reported if they passed the can_run
+        //   checks (to avoid outputting two health messages about a
+        //   module that said can_run=false but we tried running it anyway)
+        failed_modules[module->get_name()] = module->get_error_string();
+      }
+    }
+
+    // report failed always_on modules as health errors
+    for (const auto& name : mgr_map.get_always_on_modules()) {
+      if (active_modules->is_pending(name)) {
+       continue;
+      }
+      if (!active_modules->module_exists(name)) {
+        if (failed_modules.find(name) == failed_modules.end() &&
+            dependency_modules.find(name) == dependency_modules.end()) {
+          failed_modules[name] = "Not found or unloadable";
+        }
+      }
+    }
+
+    if (!dependency_modules.empty()) {
+      std::ostringstream ss;
+      if (dependency_modules.size() == 1) {
+        auto iter = dependency_modules.begin();
+        ss << "Module '" << iter->first << "' has failed dependency: "
+           << iter->second;
+      } else if (dependency_modules.size() > 1) {
+        ss << dependency_modules.size()
+          << " mgr modules have failed dependencies";
+      }
+      auto& d = checks->add("MGR_MODULE_DEPENDENCY", HEALTH_WARN, ss.str(),
+                           dependency_modules.size());
+      for (auto& i : dependency_modules) {
+       std::ostringstream ss;
+        ss << "Module '" << i.first << "' has failed dependency: " << i.second;
+       d.detail.push_back(ss.str());
+      }
+    }
+
+    if (!failed_modules.empty()) {
+      std::ostringstream ss;
+      if (failed_modules.size() == 1) {
+        auto iter = failed_modules.begin();
+        ss << "Module '" << iter->first << "' has failed: " << iter->second;
+      } else if (failed_modules.size() > 1) {
+        ss << failed_modules.size() << " mgr modules have failed";
+      }
+      auto& d = checks->add("MGR_MODULE_ERROR", HEALTH_ERR, ss.str(),
+                           failed_modules.size());
+      for (auto& i : failed_modules) {
+       std::ostringstream ss;
+        ss << "Module '" << i.first << "' has failed: " << i.second;
+       d.detail.push_back(ss.str());
       }
     }
   }
-  closedir(dir);
 }
 
-void PyModuleRegistry::list_modules(std::set<std::string> *modules)
+void PyModuleRegistry::handle_config(const std::string &k, const std::string &v)
 {
-  _list_modules(g_conf->get_val<std::string>("mgr_module_path"), modules);
+  std::lock_guard l(module_config.lock);
+
+  if (!v.empty()) {
+    // removing value to hide sensitive data going into mgr logs
+    // leaving this for debugging purposes
+    // dout(10) << "Loaded module_config entry " << k << ":" << v << dendl;
+    dout(10) << "Loaded module_config entry " << k << ":" << dendl;
+    module_config.config[k] = v;
+  } else {
+    module_config.config.erase(k);
+  }
+}
+
+void PyModuleRegistry::handle_config_notify()
+{
+  std::lock_guard l(lock);
+  if (active_modules) {
+    active_modules->config_notify();
+  }
+}
+
+void PyModuleRegistry::upgrade_config(
+    MonClient *monc,
+    const std::map<std::string, std::string> &old_config)
+{
+  // Only bother doing anything if we didn't already have
+  // some new-style config.
+  if (module_config.config.empty()) {
+    dout(1) << "Upgrading module configuration for Mimic" << dendl;
+    // Upgrade luminous->mimic: migrate config-key configuration
+    // into main configuration store
+    for (auto &i : old_config) {
+      auto last_slash = i.first.rfind('/');
+      const std::string module_name = i.first.substr(4, i.first.substr(4).find('/'));
+      const std::string key = i.first.substr(last_slash + 1);
+
+      const auto &value = i.second;
+
+      // Heuristic to skip things that look more like stores
+      // than configs.
+      bool is_config = true;
+      for (const auto &c : value) {
+        if (c == '\n' || c == '\r' || c < 0x20) {
+          is_config = false;
+          break;
+        }
+      }
+
+      if (value.size() > 256) {
+        is_config = false;
+      }
+
+      if (!is_config) {
+        dout(1) << "Not migrating config module:key "
+                << module_name << " : " << key << dendl;
+        continue;
+      }
+
+      // Check that the named module exists
+      auto module_iter = modules.find(module_name);
+      if (module_iter == modules.end()) {
+        dout(1) << "KV store contains data for unknown module '"
+                << module_name << "'" << dendl;
+        continue;
+      }
+      PyModuleRef module = module_iter->second;
+
+      // Parse option name out of key
+      std::string option_name;
+      auto slash_loc = key.find("/");
+      if (slash_loc != std::string::npos) {
+        if (key.size() > slash_loc + 1) {
+          // Localized option
+          option_name = key.substr(slash_loc + 1);
+        } else {
+          // Trailing slash: garbage.
+          derr << "Invalid mgr store key: '" << key << "'" << dendl;
+          continue;
+        }
+      } else {
+        option_name = key;
+      }
+
+      // Consult module schema to see if this is really
+      // a configuration value
+      if (!option_name.empty() && module->is_option(option_name)) {
+        module_config.set_config(monc, module_name, key, i.second);
+        dout(4) << "Rewrote configuration module:key "
+                << module_name << ":" << key << dendl;
+      } else {
+        dout(4) << "Leaving store module:key " << module_name
+                << ":" << key << " in store, not config" << dendl;
+      }
+    }
+  } else {
+    dout(10) << "Module configuration contains "
+             << module_config.config.size() << " keys" << dendl;
+  }
 }