]> git.proxmox.com Git - ceph.git/blob - ceph/src/mgr/MgrPyModule.cc
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / mgr / MgrPyModule.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2016 John Spray <john.spray@redhat.com>
7 *
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.
12 */
13
14
15 #include "PyFormatter.h"
16
17 #include "common/debug.h"
18
19 #include "MgrPyModule.h"
20
21 //XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
22 #include <boost/python.hpp>
23 #include "include/assert.h" // boost clobbers this
24
25 // decode a Python exception into a string
26 std::string handle_pyerror()
27 {
28 using namespace boost::python;
29 using namespace boost;
30
31 PyObject *exc, *val, *tb;
32 object formatted_list, formatted;
33 PyErr_Fetch(&exc, &val, &tb);
34 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
35 object traceback(import("traceback"));
36 if (!tb) {
37 object format_exception_only(traceback.attr("format_exception_only"));
38 formatted_list = format_exception_only(hexc, hval);
39 } else {
40 object format_exception(traceback.attr("format_exception"));
41 formatted_list = format_exception(hexc,hval, htb);
42 }
43 formatted = str("").join(formatted_list);
44 return extract<std::string>(formatted);
45 }
46
47 #define dout_context g_ceph_context
48 #define dout_subsys ceph_subsys_mgr
49 #undef dout_prefix
50 #define dout_prefix *_dout << "mgr " << __func__ << " "
51
52 MgrPyModule::MgrPyModule(const std::string &module_name_)
53 : module_name(module_name_),
54 pClassInstance(nullptr)
55 {}
56
57 MgrPyModule::~MgrPyModule()
58 {
59 PyGILState_STATE gstate;
60 gstate = PyGILState_Ensure();
61
62 Py_XDECREF(pClassInstance);
63
64 PyGILState_Release(gstate);
65 }
66
67 int MgrPyModule::load()
68 {
69 // Load the module
70 PyObject *pName = PyString_FromString(module_name.c_str());
71 auto pModule = PyImport_Import(pName);
72 Py_DECREF(pName);
73 if (pModule == nullptr) {
74 derr << "Module not found: '" << module_name << "'" << dendl;
75 return -ENOENT;
76 }
77
78 // Find the class
79 // TODO: let them call it what they want instead of just 'Module'
80 auto pClass = PyObject_GetAttrString(pModule, (const char*)"Module");
81 Py_DECREF(pModule);
82 if (pClass == nullptr) {
83 derr << "Class not found in module '" << module_name << "'" << dendl;
84 return -EINVAL;
85 }
86
87
88 // Just using the module name as the handle, replace with a
89 // uuidish thing if needed
90 auto pyHandle = PyString_FromString(module_name.c_str());
91 auto pArgs = PyTuple_Pack(1, pyHandle);
92 pClassInstance = PyObject_CallObject(pClass, pArgs);
93 Py_DECREF(pClass);
94 Py_DECREF(pyHandle);
95 Py_DECREF(pArgs);
96 if (pClassInstance == nullptr) {
97 derr << "Failed to construct class in '" << module_name << "'" << dendl;
98 return -EINVAL;
99 } else {
100 dout(1) << "Constructed class from module: " << module_name << dendl;
101 }
102
103 return load_commands();
104 }
105
106 int MgrPyModule::serve()
107 {
108 assert(pClassInstance != nullptr);
109
110 PyGILState_STATE gstate;
111 gstate = PyGILState_Ensure();
112
113 auto pValue = PyObject_CallMethod(pClassInstance,
114 const_cast<char*>("serve"), nullptr);
115
116 int r = 0;
117 if (pValue != NULL) {
118 Py_DECREF(pValue);
119 } else {
120 derr << module_name << ".serve:" << dendl;
121 derr << handle_pyerror() << dendl;
122 return -EINVAL;
123 }
124
125 PyGILState_Release(gstate);
126
127 return r;
128 }
129
130 // FIXME: DRY wrt serve
131 void MgrPyModule::shutdown()
132 {
133 assert(pClassInstance != nullptr);
134
135 PyGILState_STATE gstate;
136 gstate = PyGILState_Ensure();
137
138 auto pValue = PyObject_CallMethod(pClassInstance,
139 const_cast<char*>("shutdown"), nullptr);
140
141 if (pValue != NULL) {
142 Py_DECREF(pValue);
143 } else {
144 derr << "Failed to invoke shutdown() on " << module_name << dendl;
145 derr << handle_pyerror() << dendl;
146 }
147
148 PyGILState_Release(gstate);
149 }
150
151 void MgrPyModule::notify(const std::string &notify_type, const std::string &notify_id)
152 {
153 assert(pClassInstance != nullptr);
154
155 PyGILState_STATE gstate;
156 gstate = PyGILState_Ensure();
157
158 // Execute
159 auto pValue = PyObject_CallMethod(pClassInstance,
160 const_cast<char*>("notify"), const_cast<char*>("(ss)"),
161 notify_type.c_str(), notify_id.c_str());
162
163 if (pValue != NULL) {
164 Py_DECREF(pValue);
165 } else {
166 derr << module_name << ".notify:" << dendl;
167 derr << handle_pyerror() << dendl;
168 // FIXME: callers can't be expected to handle a python module
169 // that has spontaneously broken, but Mgr() should provide
170 // a hook to unload misbehaving modules when they have an
171 // error somewhere like this
172 }
173
174 PyGILState_Release(gstate);
175 }
176
177 void MgrPyModule::notify_clog(const LogEntry &log_entry)
178 {
179 assert(pClassInstance != nullptr);
180
181 PyGILState_STATE gstate;
182 gstate = PyGILState_Ensure();
183
184 // Construct python-ized LogEntry
185 PyFormatter f;
186 log_entry.dump(&f);
187 auto py_log_entry = f.get();
188
189 // Execute
190 auto pValue = PyObject_CallMethod(pClassInstance,
191 const_cast<char*>("notify"), const_cast<char*>("(sN)"),
192 "clog", py_log_entry);
193
194 if (pValue != NULL) {
195 Py_DECREF(pValue);
196 } else {
197 derr << module_name << ".notify_clog:" << dendl;
198 derr << handle_pyerror() << dendl;
199 // FIXME: callers can't be expected to handle a python module
200 // that has spontaneously broken, but Mgr() should provide
201 // a hook to unload misbehaving modules when they have an
202 // error somewhere like this
203 }
204
205 PyGILState_Release(gstate);
206 }
207
208 int MgrPyModule::load_commands()
209 {
210 PyGILState_STATE gstate;
211 gstate = PyGILState_Ensure();
212
213 PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS");
214 assert(command_list != nullptr);
215 const size_t list_size = PyList_Size(command_list);
216 for (size_t i = 0; i < list_size; ++i) {
217 PyObject *command = PyList_GetItem(command_list, i);
218 assert(command != nullptr);
219
220 ModuleCommand item;
221
222 PyObject *pCmd = PyDict_GetItemString(command, "cmd");
223 assert(pCmd != nullptr);
224 item.cmdstring = PyString_AsString(pCmd);
225
226 dout(20) << "loaded command " << item.cmdstring << dendl;
227
228 PyObject *pDesc = PyDict_GetItemString(command, "desc");
229 assert(pDesc != nullptr);
230 item.helpstring = PyString_AsString(pDesc);
231
232 PyObject *pPerm = PyDict_GetItemString(command, "perm");
233 assert(pPerm != nullptr);
234 item.perm = PyString_AsString(pPerm);
235
236 item.handler = this;
237
238 commands.push_back(item);
239 }
240 Py_DECREF(command_list);
241
242 PyGILState_Release(gstate);
243
244 dout(10) << "loaded " << commands.size() << " commands" << dendl;
245
246 return 0;
247 }
248
249 int MgrPyModule::handle_command(
250 const cmdmap_t &cmdmap,
251 std::stringstream *ds,
252 std::stringstream *ss)
253 {
254 assert(ss != nullptr);
255 assert(ds != nullptr);
256
257 PyGILState_STATE gstate;
258 gstate = PyGILState_Ensure();
259
260 PyFormatter f;
261 cmdmap_dump(cmdmap, &f);
262 PyObject *py_cmd = f.get();
263
264 auto pResult = PyObject_CallMethod(pClassInstance,
265 const_cast<char*>("handle_command"), const_cast<char*>("(O)"), py_cmd);
266
267 Py_DECREF(py_cmd);
268
269 int r = 0;
270 if (pResult != NULL) {
271 if (PyTuple_Size(pResult) != 3) {
272 r = -EINVAL;
273 } else {
274 r = PyInt_AsLong(PyTuple_GetItem(pResult, 0));
275 *ds << PyString_AsString(PyTuple_GetItem(pResult, 1));
276 *ss << PyString_AsString(PyTuple_GetItem(pResult, 2));
277 }
278
279 Py_DECREF(pResult);
280 } else {
281 *ds << "";
282 *ss << handle_pyerror();
283 r = -EINVAL;
284 }
285
286 PyGILState_Release(gstate);
287
288 return r;
289 }
290