1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2016 John Spray <john.spray@redhat.com>
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
14 #include "BaseMgrModule.h"
16 #include "PyFormatter.h"
18 #include "common/debug.h"
20 #include "ActivePyModule.h"
22 //XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
23 #include <boost/python.hpp>
24 #include "include/assert.h" // boost clobbers this
27 #define dout_context g_ceph_context
28 #define dout_subsys ceph_subsys_mgr
30 #define dout_prefix *_dout << "mgr " << __func__ << " "
32 // decode a Python exception into a string
33 std::string handle_pyerror()
35 using namespace boost::python;
36 using namespace boost;
38 PyObject *exc, *val, *tb;
39 object formatted_list, formatted;
40 PyErr_Fetch(&exc, &val, &tb);
41 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
42 object traceback(import("traceback"));
44 object format_exception_only(traceback.attr("format_exception_only"));
45 formatted_list = format_exception_only(hexc, hval);
47 object format_exception(traceback.attr("format_exception"));
48 formatted_list = format_exception(hexc,hval, htb);
50 formatted = str("").join(formatted_list);
51 return extract<std::string>(formatted);
54 int ActivePyModule::load(ActivePyModules *py_modules)
57 Gil gil(pMyThreadState, true);
59 // We tell the module how we name it, so that it can be consistent
60 // with us in logging etc.
61 auto pThisPtr = PyCapsule_New(this, nullptr, nullptr);
62 auto pPyModules = PyCapsule_New(py_modules, nullptr, nullptr);
63 auto pModuleName = PyString_FromString(module_name.c_str());
64 auto pArgs = PyTuple_Pack(3, pModuleName, pPyModules, pThisPtr);
66 pClassInstance = PyObject_CallObject(pClass, pArgs);
68 Py_DECREF(pModuleName);
70 if (pClassInstance == nullptr) {
71 derr << "Failed to construct class in '" << module_name << "'" << dendl;
72 derr << handle_pyerror() << dendl;
75 dout(1) << "Constructed class from module: " << module_name << dendl;
78 return load_commands();
81 void ActivePyModule::notify(const std::string ¬ify_type, const std::string ¬ify_id)
83 assert(pClassInstance != nullptr);
85 Gil gil(pMyThreadState, true);
88 auto pValue = PyObject_CallMethod(pClassInstance,
89 const_cast<char*>("notify"), const_cast<char*>("(ss)"),
90 notify_type.c_str(), notify_id.c_str());
95 derr << module_name << ".notify:" << dendl;
96 derr << handle_pyerror() << dendl;
97 // FIXME: callers can't be expected to handle a python module
98 // that has spontaneously broken, but Mgr() should provide
99 // a hook to unload misbehaving modules when they have an
100 // error somewhere like this
104 void ActivePyModule::notify_clog(const LogEntry &log_entry)
106 assert(pClassInstance != nullptr);
108 Gil gil(pMyThreadState, true);
110 // Construct python-ized LogEntry
113 auto py_log_entry = f.get();
116 auto pValue = PyObject_CallMethod(pClassInstance,
117 const_cast<char*>("notify"), const_cast<char*>("(sN)"),
118 "clog", py_log_entry);
120 if (pValue != NULL) {
123 derr << module_name << ".notify_clog:" << dendl;
124 derr << handle_pyerror() << dendl;
125 // FIXME: callers can't be expected to handle a python module
126 // that has spontaneously broken, but Mgr() should provide
127 // a hook to unload misbehaving modules when they have an
128 // error somewhere like this
132 int ActivePyModule::load_commands()
134 // Don't need a Gil here -- this is called from ActivePyModule::load(),
135 // which already has one.
136 PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS");
137 if (command_list == nullptr) {
138 // Even modules that don't define command should still have the COMMANDS
139 // from the MgrModule definition. Something is wrong!
140 derr << "Module " << get_name() << " has missing COMMANDS member" << dendl;
143 if (!PyObject_TypeCheck(command_list, &PyList_Type)) {
144 // Relatively easy mistake for human to make, e.g. defining COMMANDS
145 // as a {} instead of a []
146 derr << "Module " << get_name() << " has COMMANDS member of wrong type ("
147 "should be a list)" << dendl;
150 const size_t list_size = PyList_Size(command_list);
151 for (size_t i = 0; i < list_size; ++i) {
152 PyObject *command = PyList_GetItem(command_list, i);
153 assert(command != nullptr);
157 PyObject *pCmd = PyDict_GetItemString(command, "cmd");
158 assert(pCmd != nullptr);
159 item.cmdstring = PyString_AsString(pCmd);
161 dout(20) << "loaded command " << item.cmdstring << dendl;
163 PyObject *pDesc = PyDict_GetItemString(command, "desc");
164 assert(pDesc != nullptr);
165 item.helpstring = PyString_AsString(pDesc);
167 PyObject *pPerm = PyDict_GetItemString(command, "perm");
168 assert(pPerm != nullptr);
169 item.perm = PyString_AsString(pPerm);
173 commands.push_back(item);
175 Py_DECREF(command_list);
177 dout(10) << "loaded " << commands.size() << " commands" << dendl;
182 int ActivePyModule::handle_command(
183 const cmdmap_t &cmdmap,
184 std::stringstream *ds,
185 std::stringstream *ss)
187 assert(ss != nullptr);
188 assert(ds != nullptr);
190 Gil gil(pMyThreadState, true);
193 cmdmap_dump(cmdmap, &f);
194 PyObject *py_cmd = f.get();
196 auto pResult = PyObject_CallMethod(pClassInstance,
197 const_cast<char*>("handle_command"), const_cast<char*>("(O)"), py_cmd);
202 if (pResult != NULL) {
203 if (PyTuple_Size(pResult) != 3) {
206 r = PyInt_AsLong(PyTuple_GetItem(pResult, 0));
207 *ds << PyString_AsString(PyTuple_GetItem(pResult, 1));
208 *ss << PyString_AsString(PyTuple_GetItem(pResult, 2));
214 *ss << handle_pyerror();
221 void ActivePyModule::get_health_checks(health_check_map_t *checks)
223 checks->merge(health_checks);