]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr: load all modules (not just active ones)
authorJohn Spray <john.spray@redhat.com>
Thu, 16 Nov 2017 11:45:45 +0000 (06:45 -0500)
committerJohn Spray <john.spray@redhat.com>
Wed, 24 Jan 2018 18:08:20 +0000 (13:08 -0500)
This is to enable us to learn more about the module
before it is enabled, such as whether its can_run method
return true.

We can also use this to enable loading a module's
commands before it is enabled, to give the user
a better response when they try to use a command
belong to a module that is not loaded.

Signed-off-by: John Spray <john.spray@redhat.com>
14 files changed:
src/CMakeLists.txt
src/mgr/ActivePyModule.cc
src/mgr/ActivePyModule.h
src/mgr/ActivePyModules.cc
src/mgr/ActivePyModules.h
src/mgr/MgrStandby.cc
src/mgr/PyModule.cc [new file with mode: 0644]
src/mgr/PyModule.h [new file with mode: 0644]
src/mgr/PyModuleRegistry.cc
src/mgr/PyModuleRegistry.h
src/mgr/PyModuleRunner.cc
src/mgr/PyModuleRunner.h
src/mgr/StandbyPyModules.cc
src/mgr/StandbyPyModules.h

index 8dbf6d1707375e7ea01f508a15a156c9690a27af..a672add19904b3d9cf3818af5d99eec4e380d7f3 100644 (file)
@@ -733,6 +733,7 @@ if (WITH_MGR)
       mgr/ActivePyModules.cc
       mgr/OSDHealthMetricCollector.cc
       mgr/StandbyPyModules.cc
+      mgr/PyModule.cc
       mgr/PyModuleRegistry.cc
       mgr/PyModuleRunner.cc
       mgr/PyFormatter.cc
index 90040af930d5a1741e09b8da0964a5926b3c9e59..311144086abb612c1daac36d319589e84199787a 100644 (file)
  * Foundation.  See file COPYING.
  */
 
-#include "BaseMgrModule.h"
-
 #include "PyFormatter.h"
 
 #include "common/debug.h"
 
 #include "ActivePyModule.h"
 
-//XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
-#include <boost/python.hpp>
-#include "include/assert.h"  // boost clobbers this
-
 
 #define dout_context g_ceph_context
 #define dout_subsys ceph_subsys_mgr
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr " << __func__ << " "
 
-// decode a Python exception into a string
-std::string handle_pyerror()
-{
-    using namespace boost::python;
-    using namespace boost;
-
-    PyObject *exc, *val, *tb;
-    object formatted_list, formatted;
-    PyErr_Fetch(&exc, &val, &tb);
-    handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
-    object traceback(import("traceback"));
-    if (!tb) {
-        object format_exception_only(traceback.attr("format_exception_only"));
-        formatted_list = format_exception_only(hexc, hval);
-    } else {
-        object format_exception(traceback.attr("format_exception"));
-        formatted_list = format_exception(hexc,hval, htb);
-    }
-    formatted = str("").join(formatted_list);
-    return extract<std::string>(formatted);
-}
-
 int ActivePyModule::load(ActivePyModules *py_modules)
 {
   assert(py_modules);
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   // We tell the module how we name it, so that it can be consistent
   // with us in logging etc.
   auto pThisPtr = PyCapsule_New(this, nullptr, nullptr);
   auto pPyModules = PyCapsule_New(py_modules, nullptr, nullptr);
-  auto pModuleName = PyString_FromString(module_name.c_str());
+  auto pModuleName = PyString_FromString(get_name().c_str());
   auto pArgs = PyTuple_Pack(3, pModuleName, pPyModules, pThisPtr);
 
-  pClassInstance = PyObject_CallObject(pClass, pArgs);
-  Py_DECREF(pClass);
+  pClassInstance = PyObject_CallObject(py_module->pClass, pArgs);
   Py_DECREF(pModuleName);
   Py_DECREF(pArgs);
   if (pClassInstance == nullptr) {
-    derr << "Failed to construct class in '" << module_name << "'" << dendl;
+    derr << "Failed to construct class in '" << get_name() << "'" << dendl;
     derr << handle_pyerror() << dendl;
     return -EINVAL;
   } else {
-    dout(1) << "Constructed class from module: " << module_name << dendl;
+    dout(1) << "Constructed class from module: " << get_name() << dendl;
   }
 
   return load_commands();
@@ -82,7 +53,7 @@ void ActivePyModule::notify(const std::string &notify_type, const std::string &n
 {
   assert(pClassInstance != nullptr);
 
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   // Execute
   auto pValue = PyObject_CallMethod(pClassInstance,
@@ -92,7 +63,7 @@ void ActivePyModule::notify(const std::string &notify_type, const std::string &n
   if (pValue != NULL) {
     Py_DECREF(pValue);
   } else {
-    derr << module_name << ".notify:" << dendl;
+    derr << get_name() << ".notify:" << dendl;
     derr << handle_pyerror() << dendl;
     // FIXME: callers can't be expected to handle a python module
     // that has spontaneously broken, but Mgr() should provide
@@ -105,7 +76,7 @@ void ActivePyModule::notify_clog(const LogEntry &log_entry)
 {
   assert(pClassInstance != nullptr);
 
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   // Construct python-ized LogEntry
   PyFormatter f;
@@ -120,7 +91,7 @@ void ActivePyModule::notify_clog(const LogEntry &log_entry)
   if (pValue != NULL) {
     Py_DECREF(pValue);
   } else {
-    derr << module_name << ".notify_clog:" << dendl;
+    derr << get_name() << ".notify_clog:" << dendl;
     derr << handle_pyerror() << dendl;
     // FIXME: callers can't be expected to handle a python module
     // that has spontaneously broken, but Mgr() should provide
@@ -187,7 +158,7 @@ int ActivePyModule::handle_command(
   assert(ss != nullptr);
   assert(ds != nullptr);
 
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   PyFormatter f;
   cmdmap_dump(cmdmap, &f);
index 9dbf92e2c33ea4a13eff80409d101e92b82380b6..b475610fbb1de01d72f9c692770583a2c9619bff 100644 (file)
@@ -58,11 +58,9 @@ private:
   std::string uri;
 
 public:
-  ActivePyModule(const std::string &module_name_,
-      PyObject *pClass_,
-      const SafeThreadState &my_ts_,
+  ActivePyModule(PyModuleRef py_module_,
       LogChannelRef clog_)
-    : PyModuleRunner(module_name_, pClass_, my_ts_, clog_)
+    : PyModuleRunner(py_module_, clog_)
   {}
 
   int load(ActivePyModules *py_modules);
index 47eb0a1325b897e40552500c6afeed3dbd6b486d..76b54203ded6e06245ca342f5167a041a474a0cc 100644 (file)
@@ -12,7 +12,6 @@
  */
 
 // Include this first to get python headers earlier
-#include "BaseMgrModule.h"
 #include "Gil.h"
 
 #include "common/errno.h"
@@ -321,26 +320,23 @@ PyObject *ActivePyModules::get_python(const std::string &what)
   }
 }
 
-int ActivePyModules::start_one(std::string const &module_name,
-    PyObject *pClass, const SafeThreadState &pMyThreadState)
+int ActivePyModules::start_one(PyModuleRef py_module)
 {
   Mutex::Locker l(lock);
 
-  assert(modules.count(module_name) == 0);
+  assert(modules.count(py_module->get_name()) == 0);
 
-  modules[module_name].reset(new ActivePyModule(
-      module_name, pClass,
-      pMyThreadState, clog));
+  modules[py_module->get_name()].reset(new ActivePyModule(py_module, clog));
 
-  int r = modules[module_name]->load(this);
+  int r = modules[py_module->get_name()]->load(this);
   if (r != 0) {
     return r;
   } else {
-    dout(4) << "Starting thread for " << module_name << dendl;
+    dout(4) << "Starting thread for " << py_module->get_name() << dendl;
     // Giving Thread the module's module_name member as its
     // char* thread name: thread must not outlive module class lifetime.
-    modules[module_name]->thread.create(
-        modules[module_name]->get_name().c_str());
+    modules[py_module->get_name()]->thread.create(
+        py_module->get_name().c_str());
 
     return 0;
   }
index 21e6529a93343b0c66e225dafd9d50156467b794..6d0feedb4170d7f6444f9e5b749bf5db3df13c6c 100644 (file)
@@ -108,9 +108,7 @@ public:
   int init();
   void shutdown();
 
-  int start_one(std::string const &module_name,
-                PyObject *pClass,
-                const SafeThreadState &pMyThreadState);
+  int start_one(PyModuleRef py_module);
 
   void dump_server(const std::string &hostname,
                    const DaemonStateCollection &dmc,
index cd8ec2ca36c01fa2b8b8eea0325df1486a5b3879..be0f6af1891d074721213c3298b9162cbcd08eae 100644 (file)
@@ -152,7 +152,7 @@ void MgrStandby::send_beacon()
   dout(1) << state_str() << dendl;
 
   set<string> modules;
-  PyModuleRegistry::list_modules(&modules);
+  py_module_registry.list_modules(&modules);
 
   // Whether I think I am available (request MgrMonitor to set me
   // as available in the map)
diff --git a/src/mgr/PyModule.cc b/src/mgr/PyModule.cc
new file mode 100644 (file)
index 0000000..1e1d265
--- /dev/null
@@ -0,0 +1,306 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 John Spray <john.spray@redhat.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ */
+
+#include "BaseMgrModule.h"
+#include "BaseMgrStandbyModule.h"
+#include "PyOSDMap.h"
+
+#include "PyModule.h"
+
+#include "common/debug.h"
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_mgr
+
+#undef dout_prefix
+#define dout_prefix *_dout << "mgr[py] "
+
+// Courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
+#include <boost/python.hpp>
+#include "include/assert.h"  // boost clobbers this
+// decode a Python exception into a string
+std::string handle_pyerror()
+{
+    using namespace boost::python;
+    using namespace boost;
+
+    PyObject *exc, *val, *tb;
+    object formatted_list, formatted;
+    PyErr_Fetch(&exc, &val, &tb);
+    handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
+    object traceback(import("traceback"));
+    if (!tb) {
+        object format_exception_only(traceback.attr("format_exception_only"));
+        formatted_list = format_exception_only(hexc, hval);
+    } else {
+        object format_exception(traceback.attr("format_exception"));
+        formatted_list = format_exception(hexc,hval, htb);
+    }
+    formatted = str("").join(formatted_list);
+    return extract<std::string>(formatted);
+}
+
+
+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}
+  };
+}
+
+
+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 PyModule::load(PyThreadState *pMainThreadState)
+{
+  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);
+    int r;
+    r = load_subclass_of("MgrModule", &pClass);
+    if (r) {
+      derr << "Class not found in module '" << module_name << "'" << dendl;
+      return r;
+    }
+
+    // We've imported the module and found a MgrModule subclass, at this
+    // point the module is considered loaded.  It might still not be
+    // runnable though, can_run populated later...
+    loaded = true;
+
+    r = load_subclass_of("MgrStandbyModule", &pStandbyClass);
+    if (!r) {
+      dout(4) << "Standby mode available in module '" << module_name
+              << "'" << dendl;
+    } else {
+      dout(4) << "Standby mode not provided by module '" << module_name
+              << "'" << dendl;
+    }
+
+    // Populate can_run by interrogating the module's callback that
+    // may check for dependencies etc
+    // TODO
+
+  }
+  return 0;
+}
+
+int PyModule::load_subclass_of(const char* base_class, PyObject** py_class)
+{
+  // load the base class
+  PyObject *mgr_module = PyImport_ImportModule("mgr_module");
+  if (!mgr_module) {
+    derr << "Module not found: 'mgr_module'" << dendl;
+    derr << handle_pyerror() << dendl;
+    return -EINVAL;
+  }
+  auto mgr_module_type = PyObject_GetAttrString(mgr_module, base_class);
+  Py_DECREF(mgr_module);
+  if (!mgr_module_type) {
+    derr << "Unable to import MgrModule from mgr_module" << dendl;
+    derr << handle_pyerror() << dendl;
+    return -EINVAL;
+  }
+
+  // find the sub class
+  PyObject *plugin_module = PyImport_ImportModule(module_name.c_str());
+  if (!plugin_module) {
+    derr << "Module not found: '" << module_name << "'" << dendl;
+    derr << handle_pyerror() << dendl;
+    return -ENOENT;
+  }
+  auto locals = PyModule_GetDict(plugin_module);
+  Py_DECREF(plugin_module);
+  PyObject *key, *value;
+  Py_ssize_t pos = 0;
+  *py_class = nullptr;
+  while (PyDict_Next(locals, &pos, &key, &value)) {
+    if (!PyType_Check(value)) {
+      continue;
+    }
+    if (!PyObject_IsSubclass(value, mgr_module_type)) {
+      continue;
+    }
+    if (PyObject_RichCompareBool(value, mgr_module_type, Py_EQ)) {
+      continue;
+    }
+    auto class_name = PyString_AsString(key);
+    if (*py_class) {
+      derr << __func__ << ": ignoring '"
+          << module_name << "." << class_name << "'"
+          << ": only one '" << base_class
+          << "' class is loaded from each plugin" << dendl;
+      continue;
+    }
+    *py_class = value;
+    dout(4) << __func__ << ": found class: '"
+           << module_name << "." << class_name << "'" << dendl;
+  }
+  Py_DECREF(mgr_module_type);
+
+  return *py_class ? 0 : -EINVAL;
+}
+
+PyModule::~PyModule()
+{
+  if (pMyThreadState.ts != nullptr) {
+    Gil gil(pMyThreadState, true);
+    Py_XDECREF(pClass);
+    Py_XDECREF(pStandbyClass);
+  }
+}
+
diff --git a/src/mgr/PyModule.h b/src/mgr/PyModule.h
new file mode 100644 (file)
index 0000000..ae56e11
--- /dev/null
@@ -0,0 +1,68 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 John Spray <john.spray@redhat.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ */
+
+#pragma once
+
+#include "Python.h"
+#include "Gil.h"
+
+#include <string>
+#include "common/Mutex.h"
+#include <memory>
+
+std::string handle_pyerror();
+
+class PyModule
+{
+  mutable Mutex lock{"PyModule::lock"};
+private:
+  const std::string module_name;
+  std::string get_site_packages();
+  int load_subclass_of(const char* class_name, PyObject** py_class);
+
+  // Did the MgrMap identify this module as one that should run?
+  bool enabled = false;
+
+  // Did we successfully import this python module and look up symbols?
+  // (i.e. is it possible to instantiate a MgrModule subclass instance?)
+  bool loaded = false;
+
+  // Did the module identify itself as being able to run?
+  // (i.e. should we expect instantiating and calling serve() to work?)
+  bool can_run = false;
+
+  // Did the module encounter an unexpected error while running?
+  // (e.g. throwing an exception from serve())
+  bool failed = false;
+
+public:
+  SafeThreadState pMyThreadState;
+  PyObject *pClass = nullptr;
+  PyObject *pStandbyClass = nullptr;
+
+  PyModule(const std::string &module_name_, bool const enabled_)
+    : module_name(module_name_), enabled(enabled_)
+  {
+  }
+
+  ~PyModule();
+
+  int load(PyThreadState *pMainThreadState);
+
+  bool is_enabled() const { Mutex::Locker l(lock) ; return enabled; }
+  const std::string &get_name() const
+    { Mutex::Locker l(lock) ; return module_name; }
+};
+
+typedef std::shared_ptr<PyModule> PyModuleRef;
+
index a70e622be3e984cb87640e25964268d4acfdccc2..dcda1e72f84de5c888e69c3f54277378db57979f 100644 (file)
@@ -35,101 +35,11 @@ std::string PyModuleRegistry::config_prefix;
 #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)
 {
   Mutex::Locker locker(lock);
@@ -159,10 +69,15 @@ int PyModuleRegistry::init(const MgrMap &map)
 
   std::list<std::string> failed_modules;
 
+  std::set<std::string> module_names;
+  list_modules(&module_names);
   // 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 = std::make_unique<PyModule>(module_name);
+
+    bool enabled = (mgr_map.modules.count(module_name) > 0);
+
+    auto mod = std::make_shared<PyModule>(module_name, enabled);
     int r = mod->load(pMainThreadState);
     if (r != 0) {
       // Don't use handle_pyerror() here; we don't have the GIL
@@ -185,158 +100,6 @@ int PyModuleRegistry::init(const MgrMap &map)
   return 0;
 }
 
-
-int PyModule::load(PyThreadState *pMainThreadState)
-{
-  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);
-    int r;
-    r = load_subclass_of("MgrModule", &pClass);
-    if (r) {
-      derr << "Class not found in module '" << module_name << "'" << dendl;
-      return r;
-    }
-    r = load_subclass_of("MgrStandbyModule", &pStandbyClass);
-    if (!r) {
-      dout(4) << "Standby mode available in module '" << module_name
-              << "'" << dendl;
-    } else {
-      dout(4) << "Standby mode not provided by module '" << module_name
-              << "'" << dendl;
-    }
-  }
-  return 0;
-} 
-
-int PyModule::load_subclass_of(const char* base_class, PyObject** py_class)
-{
-  // load the base class
-  PyObject *mgr_module = PyImport_ImportModule("mgr_module");
-  if (!mgr_module) {
-    derr << "Module not found: 'mgr_module'" << dendl;
-    derr << handle_pyerror() << dendl;
-    return -EINVAL;
-  }
-  auto mgr_module_type = PyObject_GetAttrString(mgr_module, base_class);
-  Py_DECREF(mgr_module);
-  if (!mgr_module_type) {
-    derr << "Unable to import MgrModule from mgr_module" << dendl;
-    derr << handle_pyerror() << dendl;
-    return -EINVAL;
-  }
-
-  // find the sub class
-  PyObject *plugin_module = PyImport_ImportModule(module_name.c_str());
-  if (!plugin_module) {
-    derr << "Module not found: '" << module_name << "'" << dendl;
-    derr << handle_pyerror() << dendl;
-    return -ENOENT;
-  }
-  auto locals = PyModule_GetDict(plugin_module);
-  Py_DECREF(plugin_module);
-  PyObject *key, *value;
-  Py_ssize_t pos = 0;
-  *py_class = nullptr;
-  while (PyDict_Next(locals, &pos, &key, &value)) {
-    if (!PyType_Check(value)) {
-      continue;
-    }
-    if (!PyObject_IsSubclass(value, mgr_module_type)) {
-      continue;
-    }
-    if (PyObject_RichCompareBool(value, mgr_module_type, Py_EQ)) {
-      continue;
-    }
-    auto class_name = PyString_AsString(key);
-    if (*py_class) {
-      derr << __func__ << ": ignoring '"
-          << module_name << "." << class_name << "'"
-          << ": only one '" << base_class
-          << "' class is loaded from each plugin" << dendl;
-      continue;
-    }
-    *py_class = value;
-    dout(4) << __func__ << ": found class: '"
-           << module_name << "." << class_name << "'" << dendl;
-  }
-  Py_DECREF(mgr_module_type);
-
-  return *py_class ? 0 : -EINVAL;
-}
-
-PyModule::~PyModule()
-{
-  if (pMyThreadState.ts != nullptr) {
-    Gil gil(pMyThreadState, true);
-    Py_XDECREF(pClass);
-    Py_XDECREF(pStandbyClass);
-  }
-}
-
 void PyModuleRegistry::standby_start(MonClient *monc)
 {
   Mutex::Locker l(lock);
@@ -350,11 +113,13 @@ void PyModuleRegistry::standby_start(MonClient *monc)
 
   std::set<std::string> failed_modules;
   for (const auto &i : modules) {
+    if (!i.second->is_enabled()) {
+      continue;
+    }
+
     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);
+      int r = standby_modules->start_one(i.second);
       if (r != 0) {
         derr << "failed to start module '" << i.second->get_name()
              << "'" << dendl;;
@@ -396,10 +161,12 @@ void PyModuleRegistry::active_start(
               config_, ds, cs, mc, clog_, objecter_, client_, f));
 
   for (const auto &i : modules) {
+    if (!i.second->is_enabled()) {
+      continue;
+    }
+
     dout(4) << "Starting " << i.first << dendl;
-    int r = active_modules->start_one(i.first,
-            i.second->pClass,
-            i.second->pMyThreadState);
+    int r = active_modules->start_one(i.second);
     if (r != 0) {
       derr << "Failed to run module in active mode ('" << i.first << "')"
            << dendl;
index 4dec10081165404d5dfa512eed5438a41a23733c..b821cb5c8acb4d17dded362ac59cdd72ffb36e4d 100644 (file)
@@ -14,8 +14,8 @@
 
 #pragma once
 
-// Python.h comes first because otherwise it clobbers ceph's assert
-#include "Python.h"
+// First because it includes Python.h
+#include "PyModule.h"
 
 #include <string>
 #include <map>
 #include "ActivePyModules.h"
 #include "StandbyPyModules.h"
 
-class PyModule
-{
-private:
-  const std::string module_name;
-  std::string get_site_packages();
-  int load_subclass_of(const char* class_name, PyObject** py_class);
-public:
-  SafeThreadState pMyThreadState;
-  PyObject *pClass = nullptr;
-  PyObject *pStandbyClass = nullptr;
-
-  PyModule(const std::string &module_name_)
-    : module_name(module_name_)
-  {
-  }
-
-  ~PyModule();
-
-  int load(PyThreadState *pMainThreadState);
 
-  std::string get_name() const {
-    return module_name;
-  }
-};
 
 /**
  * This class is responsible for setting up the python runtime environment
  * and importing the python modules.
  *
  * It is *not* responsible for constructing instances of their BaseMgrModule
- * subclasses.
+ * subclasses: that is the job of ActiveMgrModule, which consumes the class
+ * references that we load here.
  */
 class PyModuleRegistry
 {
@@ -65,7 +43,7 @@ private:
 
   LogChannelRef clog;
 
-  std::map<std::string, std::unique_ptr<PyModule>> modules;
+  std::map<std::string, PyModuleRef> modules;
 
   std::unique_ptr<ActivePyModules> active_modules;
   std::unique_ptr<StandbyPyModules> standby_modules;
@@ -79,7 +57,7 @@ private:
 public:
   static std::string config_prefix;
 
-  static void list_modules(std::set<std::string> *modules);
+  void list_modules(std::set<std::string> *modules);
 
   PyModuleRegistry(LogChannelRef clog_)
     : clog(clog_)
index 881ad16b33fc6f38ec6cb8d43d2f75054e54793e..9e5a0da32dd70a81e191056b7ea80387f229f62a 100644 (file)
@@ -15,6 +15,8 @@
 // Python.h comes first because otherwise it clobbers ceph's assert
 #include "Python.h"
 
+#include "PyModule.h"
+
 #include "common/debug.h"
 #include "mgr/Gil.h"
 
 #define dout_subsys ceph_subsys_mgr
 
 
-std::string handle_pyerror();
-
 PyModuleRunner::~PyModuleRunner()
 {
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   if (pClassInstance) {
     Py_XDECREF(pClassInstance);
     pClassInstance = nullptr;
   }
-
-  Py_DECREF(pClass);
-  pClass = nullptr;
 }
 
 int PyModuleRunner::serve()
@@ -45,7 +42,7 @@ int PyModuleRunner::serve()
 
   // This method is called from a separate OS thread (i.e. a thread not
   // created by Python), so tell Gil to wrap this in a new thread state.
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   auto pValue = PyObject_CallMethod(pClassInstance,
       const_cast<char*>("serve"), nullptr);
@@ -68,10 +65,10 @@ int PyModuleRunner::serve()
     Py_DECREF(pvalue_str);
     PyErr_Restore(ptype, pvalue, ptraceback);
     
-    clog->error() << "Unhandled exception from module '" << module_name
+    clog->error() << "Unhandled exception from module '" << get_name()
                   << "' while running on mgr." << g_conf->name.get_id()
                   << ": " << exc_msg;
-    derr << module_name << ".serve:" << dendl;
+    derr << get_name() << ".serve:" << dendl;
     derr << handle_pyerror() << dendl;
     return -EINVAL;
   }
@@ -83,7 +80,7 @@ void PyModuleRunner::shutdown()
 {
   assert(pClassInstance != nullptr);
 
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   auto pValue = PyObject_CallMethod(pClassInstance,
       const_cast<char*>("shutdown"), nullptr);
@@ -91,7 +88,7 @@ void PyModuleRunner::shutdown()
   if (pValue != NULL) {
     Py_DECREF(pValue);
   } else {
-    derr << "Failed to invoke shutdown() on " << module_name << dendl;
+    derr << "Failed to invoke shutdown() on " << get_name() << dendl;
     derr << handle_pyerror() << dendl;
   }
 }
@@ -99,7 +96,7 @@ void PyModuleRunner::shutdown()
 void PyModuleRunner::log(int level, const std::string &record)
 {
 #undef dout_prefix
-#define dout_prefix *_dout << "mgr[" << module_name << "] "
+#define dout_prefix *_dout << "mgr[" << get_name() << "] "
   dout(level) << record << dendl;
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr " << __func__ << " "
index c4b861cf3b6e7597afd4a361b2ed94fc99fd0139..40b27d11f20668750f372b6d29525bdb085c245e 100644 (file)
@@ -18,6 +18,8 @@
 #include "common/LogClient.h"
 #include "mgr/Gil.h"
 
+#include "PyModule.h"
+
 /**
  * Implement the pattern of calling serve() on a module in a thread,
  * until shutdown() is called.
 class PyModuleRunner
 {
 protected:
-  const std::string module_name;
-
-  // Passed in by whoever loaded our python module and looked up
-  // the symbols in it.
-  PyObject *pClass = nullptr;
+  // Info about the module we're going to run
+  PyModuleRef py_module;
 
-  // Passed in by whoever created our subinterpreter for us
-  SafeThreadState pMyThreadState = nullptr;
-
-  // Populated when we construct our instance of pClass in load()
+  // Populated by descendent class
   PyObject *pClassInstance = nullptr;
 
   LogChannelRef clog;
@@ -50,32 +46,28 @@ protected:
     void *entry() override;
   };
 
+
 public:
   int serve();
   void shutdown();
   void log(int level, const std::string &record);
 
   PyModuleRunner(
-      const std::string &module_name_,
-      PyObject *pClass_,
-      const SafeThreadState &pMyThreadState_,
+      PyModuleRef py_module_,
       LogChannelRef clog_)
     : 
-      module_name(module_name_),
-      pClass(pClass_), pMyThreadState(pMyThreadState_),
+      py_module(py_module_),
       clog(clog_),
       thread(this)
   {
-    assert(pClass != nullptr);
-    assert(pMyThreadState.ts != nullptr);
-    assert(!module_name.empty());
+    assert(py_module != nullptr);
   }
 
   ~PyModuleRunner();
 
   PyModuleRunnerThread thread;
 
-  std::string const &get_name() const { return module_name; }
+  std::string const &get_name() const { return py_module->get_name(); }
 };
 
 
index 46c74e03e4dd4678caae42da0824660272e04a73..5430658bf10ef63f692d322bf0b62ba692d52ca1 100644 (file)
@@ -30,9 +30,6 @@
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr " << __func__ << " "
 
-// Declaration fulfilled by ActivePyModules
-std::string handle_pyerror();
-
 
 StandbyPyModules::StandbyPyModules(MonClient *monc_, const MgrMap &mgr_map_,
     LogChannelRef clog_)
@@ -77,17 +74,16 @@ void StandbyPyModules::shutdown()
   modules.clear();
 }
 
-int StandbyPyModules::start_one(std::string const &module_name,
-    PyObject *pClass, const SafeThreadState &pMyThreadState)
+int StandbyPyModules::start_one(PyModuleRef py_module)
 {
   Mutex::Locker l(lock);
+  const std::string &module_name = py_module->get_name();
 
   assert(modules.count(module_name) == 0);
 
   modules[module_name].reset(new StandbyPyModule(
       state,
-      module_name, pClass,
-      pMyThreadState, clog));
+      py_module, clog));
 
   if (modules.size() == 1) {
     load_config_thread.create("LoadConfig");
@@ -109,26 +105,26 @@ int StandbyPyModules::start_one(std::string const &module_name,
 
 int StandbyPyModule::load()
 {
-  Gil gil(pMyThreadState, true);
+  Gil gil(py_module->pMyThreadState, true);
 
   // We tell the module how we name it, so that it can be consistent
   // with us in logging etc.
   auto pThisPtr = PyCapsule_New(this, nullptr, nullptr);
   assert(pThisPtr != nullptr);
-  auto pModuleName = PyString_FromString(module_name.c_str());
+  auto pModuleName = PyString_FromString(get_name().c_str());
   assert(pModuleName != nullptr);
   auto pArgs = PyTuple_Pack(2, pModuleName, pThisPtr);
   Py_DECREF(pThisPtr);
   Py_DECREF(pModuleName);
 
-  pClassInstance = PyObject_CallObject(pClass, pArgs);
+  pClassInstance = PyObject_CallObject(py_module->pStandbyClass, pArgs);
   Py_DECREF(pArgs);
   if (pClassInstance == nullptr) {
-    derr << "Failed to construct class in '" << module_name << "'" << dendl;
+    derr << "Failed to construct class in '" << get_name() << "'" << dendl;
     derr << handle_pyerror() << dendl;
     return -EINVAL;
   } else {
-    dout(1) << "Constructed class from module: " << module_name << dendl;
+    dout(1) << "Constructed class from module: " << get_name() << dendl;
     return 0;
   }
 }
@@ -172,7 +168,7 @@ bool StandbyPyModule::get_config(const std::string &key,
   PyEval_RestoreThread(tstate);
 
   const std::string global_key = PyModuleRegistry::config_prefix
-    + module_name + "/" + key;
+    + get_name() + "/" + key;
 
   dout(4) << __func__ << "key: " << global_key << dendl;
 
@@ -190,7 +186,7 @@ std::string StandbyPyModule::get_active_uri() const
 {
   std::string result;
   state.with_mgr_map([&result, this](const MgrMap &mgr_map){
-    auto iter = mgr_map.services.find(module_name);
+    auto iter = mgr_map.services.find(get_name());
     if (iter != mgr_map.services.end()) {
       result = iter->second;
     }
index 85edb6cc6e77d08f7fc8572d1336eb5d6537d46c..a22887bdeae9de06a7e42ec451ae31e23a572145 100644 (file)
@@ -89,12 +89,10 @@ class StandbyPyModule : public PyModuleRunner
 
   StandbyPyModule(
       StandbyPyModuleState &state_,
-      const std::string &module_name_,
-      PyObject *pClass_,
-      const SafeThreadState &pMyThreadState_,
+      PyModuleRef py_module_,
       LogChannelRef clog_)
     :
-      PyModuleRunner(module_name_, pClass_, pMyThreadState_, clog_),
+      PyModuleRunner(py_module_, clog_),
       state(state_)
   {
   }
@@ -139,9 +137,7 @@ public:
       const MgrMap &mgr_map_,
       LogChannelRef clog_);
 
-  int start_one(std::string const &module_name,
-                PyObject *pClass,
-                const SafeThreadState &pMyThreadState);
+  int start_one(PyModuleRef py_module);
 
   void shutdown();