]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr: evaluate `can_run` method on modules
authorJohn Spray <john.spray@redhat.com>
Thu, 16 Nov 2017 21:30:39 +0000 (16:30 -0500)
committerJohn Spray <john.spray@redhat.com>
Wed, 24 Jan 2018 18:08:20 +0000 (13:08 -0500)
...and transmit the result to the monitor in
our beacon.

Fixes: http://tracker.ceph.com/issues/21502
Signed-off-by: John Spray <john.spray@redhat.com>
doc/mgr/plugins.rst
src/messages/MMgrBeacon.h
src/mgr/MgrStandby.cc
src/mgr/PyModule.cc
src/mgr/PyModule.h
src/mgr/PyModuleRegistry.h
src/mgr/PyModuleRunner.cc
src/mon/MgrMap.h
src/pybind/mgr/influx/module.py
src/pybind/mgr/mgr_module.py

index 5069fa2a0780f73c6cb957b68152b790510d5152..9e73b7a58cdf20991fe3cb74e703329b73168686 100644 (file)
@@ -124,6 +124,19 @@ the monitor cluster, use this function:
 
 .. automethod:: MgrModule.have_mon_connection
 
+Reporting if your module cannot run
+-----------------------------------
+
+If your module cannot be run for any reason (such as a missing dependency),
+then you can report that by implementing the ``can_run`` function.
+
+.. automethod:: MgrModule.can_run
+
+Note that this will only work properly if your module can always be imported:
+if you are importing a dependency that may be absent, then do it in a
+try/except block so that your module can be loaded far enough to use
+``can_run`` even if the dependency is absent.
+
 Sending commands
 ----------------
 
index 54d569cba87492d49e7dfcf65eb0c45b5a9101a1..660941d056d6f1470aa4ecead67ca29450919c34 100644 (file)
 #include "include/types.h"
 
 
+
 class MMgrBeacon : public PaxosServiceMessage {
 
-  static const int HEAD_VERSION = 6;
+  static const int HEAD_VERSION = 7;
   static const int COMPAT_VERSION = 1;
 
+  public:
+
+  class ModuleInfo
+  {
+    public:
+    std::string name;
+    bool can_run = true;
+    std::string error_string;
+
+    // We do not include the module's `failed` field in the beacon,
+    // because it is exposed via health checks.
+    void encode(bufferlist &bl) const {
+      ENCODE_START(1, 1, bl);
+      ::encode(name, bl);
+      ::encode(can_run, bl);
+      ::encode(error_string, bl);
+      ENCODE_FINISH(bl);
+    }
+
+    void decode(bufferlist::iterator &bl) {
+      DECODE_START(1, bl);
+      ::decode(name, bl);
+      ::decode(can_run, bl);
+      ::decode(error_string, bl);
+      DECODE_FINISH(bl);
+    }
+  };
+
 protected:
   uint64_t gid;
   entity_addr_t server_addr;
   bool available;
   std::string name;
   uuid_d fsid;
-  std::set<std::string> available_modules;
-  map<string,string> metadata; ///< misc metadata about this osd
 
   // From active daemon to populate MgrMap::services
   std::map<std::string, std::string> services;
@@ -41,6 +68,11 @@ protected:
   // Only populated during activation
   std::vector<MonCommand> command_descs;
 
+  // Information about the modules found locally on this daemon
+  std::vector<ModuleInfo> modules;
+
+  map<string,string> metadata; ///< misc metadata about this osd
+
 public:
   MMgrBeacon()
     : PaxosServiceMessage(MSG_MGR_BEACON, 0, HEAD_VERSION, COMPAT_VERSION),
@@ -50,11 +82,11 @@ public:
 
   MMgrBeacon(const uuid_d& fsid_, uint64_t gid_, const std::string &name_,
              entity_addr_t server_addr_, bool available_,
-            const std::set<std::string>& module_list,
-            map<string,string>&& metadata)
+            std::vector<ModuleInfo>&& modules_,
+            map<string,string>&& metadata_)
     : PaxosServiceMessage(MSG_MGR_BEACON, 0, HEAD_VERSION, COMPAT_VERSION),
       gid(gid_), server_addr(server_addr_), available(available_), name(name_),
-      fsid(fsid_), available_modules(module_list), metadata(std::move(metadata))
+      fsid(fsid_), modules(std::move(modules_)), metadata(std::move(metadata_))
   {
   }
 
@@ -63,7 +95,7 @@ public:
   bool get_available() const { return available; }
   const std::string& get_name() const { return name; }
   const uuid_d& get_fsid() const { return fsid; }
-  std::set<std::string>& get_available_modules() { return available_modules; }
+  std::vector<ModuleInfo>& get_modules() { return modules; }
   const std::map<std::string,std::string>& get_metadata() const {
     return metadata;
   }
@@ -87,6 +119,18 @@ public:
     return command_descs;
   }
 
+  std::set<std::string> get_available_modules() const
+  {
+    std::set<std::string> result;
+    for (const auto &i : modules) {
+      result.insert(i.name);
+    }
+
+    return result;
+  }
+
+
+
 private:
   ~MMgrBeacon() override {}
 
@@ -103,15 +147,26 @@ public:
   void encode_payload(uint64_t features) override {
     using ceph::encode;
     paxos_encode();
+
     encode(server_addr, payload, features);
     encode(gid, payload);
     encode(available, payload);
     encode(name, payload);
     encode(fsid, payload);
-    encode(available_modules, payload);
+
+    // Fill out old-style list of module names (deprecated by
+    // later field of full ModuleInfos)
+    std::set<std::string> module_names;
+    for (const auto &info : modules) {
+      module_names.insert(info.name);
+    }
+    encode(module_names, payload);
+
     encode(command_descs, payload);
     encode(metadata, payload);
     encode(services, payload);
+
+    encode(modules, payload);
   }
   void decode_payload() override {
     bufferlist::iterator p = payload.begin();
@@ -124,7 +179,17 @@ public:
       decode(fsid, p);
     }
     if (header.version >= 3) {
-      decode(available_modules, p);
+      std::set<std::string> module_name_list;
+      decode(module_name_list, p);
+      // Only need to unpack this field if we won't have the full
+      // ModuleInfo structures added in v7
+      if (header.version < 7) {
+        for (const auto &i : module_name_list) {
+          ModuleInfo info;
+          info.name = i;
+          modules.push_back(std::move(info));
+        }
+      }
     }
     if (header.version >= 4) {
       decode(command_descs, p);
@@ -135,7 +200,13 @@ public:
     if (header.version >= 6) {
       decode(services, p);
     }
+    if (header.version >= 7) {
+      decode(modules, p);
+    }
   }
 };
 
+WRITE_CLASS_ENCODER(MMgrBeacon::ModuleInfo);
+
+
 #endif
index be0f6af1891d074721213c3298b9162cbcd08eae..91f7737813b600b8d44d66b4852ec9b744cf26b7 100644 (file)
@@ -151,8 +151,19 @@ void MgrStandby::send_beacon()
   assert(lock.is_locked_by_me());
   dout(1) << state_str() << dendl;
 
-  set<string> modules;
-  py_module_registry.list_modules(&modules);
+  std::list<PyModuleRef> modules;
+  py_module_registry.get_modules(&modules);
+
+  // Construct a list of the info about each loaded module
+  // which we will transmit to the monitor.
+  std::vector<MMgrBeacon::ModuleInfo> module_info;
+  for (const auto &module : modules) {
+    MMgrBeacon::ModuleInfo info;
+    info.name = module->get_name();
+    info.error_string = module->get_error_string();
+    info.can_run = module->get_can_run();
+    module_info.push_back(std::move(info));
+  }
 
   // Whether I think I am available (request MgrMonitor to set me
   // as available in the map)
@@ -170,7 +181,7 @@ void MgrStandby::send_beacon()
                                  g_conf->name.get_id(),
                                  addr,
                                  available,
-                                modules,
+                                std::move(module_info),
                                 std::move(metadata));
 
   if (available) {
index 1e1d2659a46a9e4961cc524b37d7d816af82e3d3..3d4eb7e90fc4a147a235b271552c96ebb073488e 100644 (file)
@@ -233,8 +233,37 @@ int PyModule::load(PyThreadState *pMainThreadState)
 
     // Populate can_run by interrogating the module's callback that
     // may check for dependencies etc
-    // TODO
+    PyObject *pCanRunTuple = PyObject_CallMethod(pClass,
+      const_cast<char*>("can_run"), const_cast<char*>("()"));
+    if (pCanRunTuple != nullptr) {
+      if (PyTuple_Check(pCanRunTuple) && PyTuple_Size(pCanRunTuple) == 2) {
+        PyObject *pCanRun = PyTuple_GetItem(pCanRunTuple, 0);
+        PyObject *can_run_str = PyTuple_GetItem(pCanRunTuple, 1);
+        if (!PyBool_Check(pCanRun) || !PyString_Check(can_run_str)) {
+          derr << "Module " << get_name()
+               << " returned wrong type in can_run" << dendl;
+          can_run = false;
+        } else {
+          can_run = (pCanRun == Py_True);
+          if (!can_run) {
+            error_string = PyString_AsString(can_run_str);
+            dout(4) << "Module " << get_name()
+                    << " reported that it cannot run: "
+                    << error_string << dendl;
+          }
+        }
+      } else {
+        derr << "Module " << get_name()
+             << " returned wrong type in can_run" << dendl;
+        can_run = false;
+      }
 
+      Py_DECREF(pCanRunTuple);
+    } else {
+      derr << "Exception calling can_run on " << get_name() << dendl;
+      derr << handle_pyerror() << dendl;
+      can_run = false;
+    }
   }
   return 0;
 }
index ae56e11167ed2970a43e487f9e8a802b4e84d5fa..d3c39f4abb33e2a7fff2663ea5aa42f1cf4c4a19 100644 (file)
@@ -45,6 +45,9 @@ private:
   // (e.g. throwing an exception from serve())
   bool failed = false;
 
+  // Populated if loaded, can_run or failed indicates a problem
+  std::string error_string;
+
 public:
   SafeThreadState pMyThreadState;
   PyObject *pClass = nullptr;
@@ -59,9 +62,27 @@ public:
 
   int load(PyThreadState *pMainThreadState);
 
+  /**
+   * Mark the module as failed, recording the reason in the error
+   * string.
+   */
+  void fail(const std::string &reason)
+  {
+    Mutex::Locker l(lock);
+    failed = true;
+    error_string = reason;
+  }
+
   bool is_enabled() const { Mutex::Locker l(lock) ; return enabled; }
-  const std::string &get_name() const
-    { Mutex::Locker l(lock) ; return module_name; }
+  const std::string &get_name() const {
+    Mutex::Locker l(lock) ; return module_name;
+  }
+  const std::string &get_error_string() const {
+    Mutex::Locker l(lock) ; return error_string;
+  }
+  bool get_can_run() const {
+    Mutex::Locker l(lock) ; return can_run;
+  }
 };
 
 typedef std::shared_ptr<PyModule> PyModuleRef;
index b821cb5c8acb4d17dded362ac59cdd72ffb36e4d..3eaa9f640926eac893a345afbd79990fc3608bc8 100644 (file)
@@ -59,6 +59,14 @@ public:
 
   void list_modules(std::set<std::string> *modules);
 
+  void get_modules(std::list<PyModuleRef> *modules_out)
+  {
+    Mutex::Locker l(lock);
+    for (const auto &i : modules) {
+      modules_out->push_back(i.second);
+    }
+  }
+
   PyModuleRegistry(LogChannelRef clog_)
     : clog(clog_)
   {}
index 9e5a0da32dd70a81e191056b7ea80387f229f62a..81d744735042c338f5c1d5299c811a7843eac853 100644 (file)
@@ -70,6 +70,9 @@ int PyModuleRunner::serve()
                   << ": " << exc_msg;
     derr << get_name() << ".serve:" << dendl;
     derr << handle_pyerror() << dendl;
+
+    py_module->fail(exc_msg);
+
     return -EINVAL;
   }
 
index 79d5b3b0c22147cb8966ebcdf5134561ffa52fd4..15d5c0cb29190dca5f010fa291132621f0910a6e 100644 (file)
@@ -28,7 +28,7 @@ public:
   std::set<std::string> available_modules;
 
   StandbyInfo(uint64_t gid_, const std::string &name_,
-             std::set<std::string>& am)
+             const std::set<std::string>& am)
     : gid(gid_), name(name_), available_modules(am)
   {}
 
index 890dc2360cdb3a5f7681feb6d315ae01e736e33d..dd288dc718f62e9da57686bfd588d7149573c924 100644 (file)
@@ -58,6 +58,13 @@ class Module(MgrModule):
     def get_fsid(self):
         return self.get('mon_map')['fsid']
 
+    @staticmethod
+    def can_run():
+        if InfluxDBClient is not None:
+            return True, ""
+        else:
+            return False, "influxdb python module not found"
+
     def get_latest(self, daemon_type, daemon_name, stat):
         data = self.get_counter(daemon_type, daemon_name, stat)[stat]
         if data:
index adb59a0cff071c6b316afc431f14fa0421657377..ea72df830a05759a122943c5e3456939987705d3 100644 (file)
@@ -633,3 +633,19 @@ class MgrModule(ceph_module.BaseMgrModule):
         self._rados.connect()
 
         return self._rados
+
+    @staticmethod
+    def can_run():
+        """
+        Implement this function to report whether the module's dependencies
+        are met.  For example, if the module needs to import a particular
+        dependency to work, then use a try/except around the import at
+        file scope, and then report here if the import failed.
+
+        This will be called in a blocking way from the C++ code, so do not
+        do any I/O that could block in this function.
+
+        :return a 2-tuple consisting of a boolean and explanatory string
+        """
+
+        return True, ""