]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | ||
31f18b77 FG |
14 | #include "PyState.h" |
15 | #include "Gil.h" | |
7c673cae FG |
16 | |
17 | #include "PyFormatter.h" | |
18 | ||
19 | #include "common/debug.h" | |
20 | ||
21 | #include "MgrPyModule.h" | |
22 | ||
23 | //XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text | |
24 | #include <boost/python.hpp> | |
25 | #include "include/assert.h" // boost clobbers this | |
26 | ||
27 | // decode a Python exception into a string | |
28 | std::string handle_pyerror() | |
29 | { | |
30 | using namespace boost::python; | |
31 | using namespace boost; | |
32 | ||
33 | PyObject *exc, *val, *tb; | |
34 | object formatted_list, formatted; | |
35 | PyErr_Fetch(&exc, &val, &tb); | |
36 | handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb)); | |
37 | object traceback(import("traceback")); | |
38 | if (!tb) { | |
39 | object format_exception_only(traceback.attr("format_exception_only")); | |
40 | formatted_list = format_exception_only(hexc, hval); | |
41 | } else { | |
42 | object format_exception(traceback.attr("format_exception")); | |
43 | formatted_list = format_exception(hexc,hval, htb); | |
44 | } | |
45 | formatted = str("").join(formatted_list); | |
46 | return extract<std::string>(formatted); | |
47 | } | |
48 | ||
49 | #define dout_context g_ceph_context | |
50 | #define dout_subsys ceph_subsys_mgr | |
31f18b77 FG |
51 | |
52 | #undef dout_prefix | |
53 | #define dout_prefix *_dout << "mgr[py] " | |
54 | ||
55 | namespace { | |
56 | PyObject* log_write(PyObject*, PyObject* args) { | |
57 | char* m = nullptr; | |
58 | if (PyArg_ParseTuple(args, "s", &m)) { | |
59 | auto len = strlen(m); | |
60 | if (len && m[len-1] == '\n') { | |
61 | m[len-1] = '\0'; | |
62 | } | |
63 | dout(4) << m << dendl; | |
64 | } | |
65 | Py_RETURN_NONE; | |
66 | } | |
67 | ||
68 | PyObject* log_flush(PyObject*, PyObject*){ | |
69 | Py_RETURN_NONE; | |
70 | } | |
71 | ||
72 | static PyMethodDef log_methods[] = { | |
73 | {"write", log_write, METH_VARARGS, "write stdout and stderr"}, | |
74 | {"flush", log_flush, METH_VARARGS, "flush"}, | |
75 | {nullptr, nullptr, 0, nullptr} | |
76 | }; | |
77 | } | |
78 | ||
7c673cae FG |
79 | #undef dout_prefix |
80 | #define dout_prefix *_dout << "mgr " << __func__ << " " | |
81 | ||
31f18b77 | 82 | MgrPyModule::MgrPyModule(const std::string &module_name_, const std::string &sys_path, PyThreadState *main_ts_) |
7c673cae | 83 | : module_name(module_name_), |
31f18b77 FG |
84 | pClassInstance(nullptr), |
85 | pMainThreadState(main_ts_) | |
7c673cae | 86 | { |
31f18b77 FG |
87 | assert(pMainThreadState != nullptr); |
88 | ||
89 | Gil gil(pMainThreadState); | |
90 | ||
91 | pMyThreadState = Py_NewInterpreter(); | |
92 | if (pMyThreadState == nullptr) { | |
93 | derr << "Failed to create python sub-interpreter for '" << module_name << '"' << dendl; | |
94 | } else { | |
95 | // Some python modules do not cope with an unpopulated argv, so lets | |
96 | // fake one. This step also picks up site-packages into sys.path. | |
97 | const char *argv[] = {"ceph-mgr"}; | |
98 | PySys_SetArgv(1, (char**)argv); | |
99 | ||
100 | if (g_conf->daemonize) { | |
101 | auto py_logger = Py_InitModule("ceph_logger", log_methods); | |
102 | #if PY_MAJOR_VERSION >= 3 | |
103 | PySys_SetObject("stderr", py_logger); | |
104 | PySys_SetObject("stdout", py_logger); | |
105 | #else | |
106 | PySys_SetObject(const_cast<char*>("stderr"), py_logger); | |
107 | PySys_SetObject(const_cast<char*>("stdout"), py_logger); | |
108 | #endif | |
109 | } | |
110 | // Populate python namespace with callable hooks | |
111 | Py_InitModule("ceph_state", CephStateMethods); | |
7c673cae | 112 | |
31f18b77 FG |
113 | PySys_SetPath(const_cast<char*>(sys_path.c_str())); |
114 | } | |
115 | } | |
7c673cae | 116 | |
31f18b77 FG |
117 | MgrPyModule::~MgrPyModule() |
118 | { | |
119 | if (pMyThreadState != nullptr) { | |
120 | Gil gil(pMyThreadState); | |
121 | ||
122 | Py_XDECREF(pClassInstance); | |
123 | ||
124 | // | |
125 | // Ideally, now, we'd be able to do this: | |
126 | // | |
127 | // Py_EndInterpreter(pMyThreadState); | |
128 | // PyThreadState_Swap(pMainThreadState); | |
129 | // | |
130 | // Unfortunately, if the module has any other *python* threads active | |
131 | // at this point, Py_EndInterpreter() will abort with: | |
132 | // | |
133 | // Fatal Python error: Py_EndInterpreter: not the last thread | |
134 | // | |
135 | // This can happen when using CherryPy in a module, becuase CherryPy | |
136 | // runs an extra thread as a timeout monitor, which spends most of its | |
137 | // life inside a time.sleep(60). Unless you are very, very lucky with | |
138 | // the timing calling this destructor, that thread will still be stuck | |
139 | // in a sleep, and Py_EndInterpreter() will abort. | |
140 | // | |
141 | // This could of course also happen with a poorly written module which | |
142 | // made no attempt to clean up any additional threads it created. | |
143 | // | |
144 | // The safest thing to do is just not call Py_EndInterpreter(), and | |
145 | // let Py_Finalize() kill everything after all modules are shut down. | |
146 | // | |
147 | } | |
7c673cae FG |
148 | } |
149 | ||
150 | int MgrPyModule::load() | |
151 | { | |
31f18b77 FG |
152 | if (pMyThreadState == nullptr) { |
153 | derr << "No python sub-interpreter exists for module '" << module_name << "'" << dendl; | |
154 | return -EINVAL; | |
155 | } | |
156 | ||
157 | Gil gil(pMyThreadState); | |
158 | ||
7c673cae FG |
159 | // Load the module |
160 | PyObject *pName = PyString_FromString(module_name.c_str()); | |
161 | auto pModule = PyImport_Import(pName); | |
162 | Py_DECREF(pName); | |
163 | if (pModule == nullptr) { | |
164 | derr << "Module not found: '" << module_name << "'" << dendl; | |
31f18b77 | 165 | derr << handle_pyerror() << dendl; |
7c673cae FG |
166 | return -ENOENT; |
167 | } | |
168 | ||
169 | // Find the class | |
170 | // TODO: let them call it what they want instead of just 'Module' | |
171 | auto pClass = PyObject_GetAttrString(pModule, (const char*)"Module"); | |
172 | Py_DECREF(pModule); | |
173 | if (pClass == nullptr) { | |
174 | derr << "Class not found in module '" << module_name << "'" << dendl; | |
31f18b77 | 175 | derr << handle_pyerror() << dendl; |
7c673cae FG |
176 | return -EINVAL; |
177 | } | |
178 | ||
179 | ||
180 | // Just using the module name as the handle, replace with a | |
181 | // uuidish thing if needed | |
182 | auto pyHandle = PyString_FromString(module_name.c_str()); | |
183 | auto pArgs = PyTuple_Pack(1, pyHandle); | |
184 | pClassInstance = PyObject_CallObject(pClass, pArgs); | |
185 | Py_DECREF(pClass); | |
186 | Py_DECREF(pyHandle); | |
187 | Py_DECREF(pArgs); | |
188 | if (pClassInstance == nullptr) { | |
189 | derr << "Failed to construct class in '" << module_name << "'" << dendl; | |
31f18b77 | 190 | derr << handle_pyerror() << dendl; |
7c673cae FG |
191 | return -EINVAL; |
192 | } else { | |
193 | dout(1) << "Constructed class from module: " << module_name << dendl; | |
194 | } | |
195 | ||
196 | return load_commands(); | |
197 | } | |
198 | ||
199 | int MgrPyModule::serve() | |
200 | { | |
201 | assert(pClassInstance != nullptr); | |
202 | ||
31f18b77 FG |
203 | // This method is called from a separate OS thread (i.e. a thread not |
204 | // created by Python), so tell Gil to wrap this in a new thread state. | |
205 | Gil gil(pMyThreadState, true); | |
7c673cae FG |
206 | |
207 | auto pValue = PyObject_CallMethod(pClassInstance, | |
208 | const_cast<char*>("serve"), nullptr); | |
209 | ||
210 | int r = 0; | |
211 | if (pValue != NULL) { | |
212 | Py_DECREF(pValue); | |
213 | } else { | |
214 | derr << module_name << ".serve:" << dendl; | |
215 | derr << handle_pyerror() << dendl; | |
216 | return -EINVAL; | |
217 | } | |
218 | ||
7c673cae FG |
219 | return r; |
220 | } | |
221 | ||
222 | // FIXME: DRY wrt serve | |
223 | void MgrPyModule::shutdown() | |
224 | { | |
225 | assert(pClassInstance != nullptr); | |
226 | ||
31f18b77 | 227 | Gil gil(pMyThreadState); |
7c673cae FG |
228 | |
229 | auto pValue = PyObject_CallMethod(pClassInstance, | |
230 | const_cast<char*>("shutdown"), nullptr); | |
231 | ||
232 | if (pValue != NULL) { | |
233 | Py_DECREF(pValue); | |
234 | } else { | |
235 | derr << "Failed to invoke shutdown() on " << module_name << dendl; | |
236 | derr << handle_pyerror() << dendl; | |
237 | } | |
7c673cae FG |
238 | } |
239 | ||
240 | void MgrPyModule::notify(const std::string ¬ify_type, const std::string ¬ify_id) | |
241 | { | |
242 | assert(pClassInstance != nullptr); | |
243 | ||
31f18b77 | 244 | Gil gil(pMyThreadState); |
7c673cae FG |
245 | |
246 | // Execute | |
247 | auto pValue = PyObject_CallMethod(pClassInstance, | |
248 | const_cast<char*>("notify"), const_cast<char*>("(ss)"), | |
249 | notify_type.c_str(), notify_id.c_str()); | |
250 | ||
251 | if (pValue != NULL) { | |
252 | Py_DECREF(pValue); | |
253 | } else { | |
254 | derr << module_name << ".notify:" << dendl; | |
255 | derr << handle_pyerror() << dendl; | |
256 | // FIXME: callers can't be expected to handle a python module | |
257 | // that has spontaneously broken, but Mgr() should provide | |
258 | // a hook to unload misbehaving modules when they have an | |
259 | // error somewhere like this | |
260 | } | |
7c673cae FG |
261 | } |
262 | ||
263 | void MgrPyModule::notify_clog(const LogEntry &log_entry) | |
264 | { | |
265 | assert(pClassInstance != nullptr); | |
266 | ||
31f18b77 | 267 | Gil gil(pMyThreadState); |
7c673cae FG |
268 | |
269 | // Construct python-ized LogEntry | |
270 | PyFormatter f; | |
271 | log_entry.dump(&f); | |
272 | auto py_log_entry = f.get(); | |
273 | ||
274 | // Execute | |
275 | auto pValue = PyObject_CallMethod(pClassInstance, | |
276 | const_cast<char*>("notify"), const_cast<char*>("(sN)"), | |
277 | "clog", py_log_entry); | |
278 | ||
279 | if (pValue != NULL) { | |
280 | Py_DECREF(pValue); | |
281 | } else { | |
282 | derr << module_name << ".notify_clog:" << dendl; | |
283 | derr << handle_pyerror() << dendl; | |
284 | // FIXME: callers can't be expected to handle a python module | |
285 | // that has spontaneously broken, but Mgr() should provide | |
286 | // a hook to unload misbehaving modules when they have an | |
287 | // error somewhere like this | |
288 | } | |
7c673cae FG |
289 | } |
290 | ||
291 | int MgrPyModule::load_commands() | |
292 | { | |
31f18b77 FG |
293 | // Don't need a Gil here -- this is called from MgrPyModule::load(), |
294 | // which already has one. | |
7c673cae FG |
295 | PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS"); |
296 | assert(command_list != nullptr); | |
297 | const size_t list_size = PyList_Size(command_list); | |
298 | for (size_t i = 0; i < list_size; ++i) { | |
299 | PyObject *command = PyList_GetItem(command_list, i); | |
300 | assert(command != nullptr); | |
301 | ||
302 | ModuleCommand item; | |
303 | ||
304 | PyObject *pCmd = PyDict_GetItemString(command, "cmd"); | |
305 | assert(pCmd != nullptr); | |
306 | item.cmdstring = PyString_AsString(pCmd); | |
307 | ||
308 | dout(20) << "loaded command " << item.cmdstring << dendl; | |
309 | ||
310 | PyObject *pDesc = PyDict_GetItemString(command, "desc"); | |
311 | assert(pDesc != nullptr); | |
312 | item.helpstring = PyString_AsString(pDesc); | |
313 | ||
314 | PyObject *pPerm = PyDict_GetItemString(command, "perm"); | |
315 | assert(pPerm != nullptr); | |
316 | item.perm = PyString_AsString(pPerm); | |
317 | ||
318 | item.handler = this; | |
319 | ||
320 | commands.push_back(item); | |
321 | } | |
322 | Py_DECREF(command_list); | |
323 | ||
7c673cae FG |
324 | dout(10) << "loaded " << commands.size() << " commands" << dendl; |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
329 | int MgrPyModule::handle_command( | |
330 | const cmdmap_t &cmdmap, | |
331 | std::stringstream *ds, | |
332 | std::stringstream *ss) | |
333 | { | |
334 | assert(ss != nullptr); | |
335 | assert(ds != nullptr); | |
336 | ||
31f18b77 | 337 | Gil gil(pMyThreadState); |
7c673cae FG |
338 | |
339 | PyFormatter f; | |
340 | cmdmap_dump(cmdmap, &f); | |
341 | PyObject *py_cmd = f.get(); | |
342 | ||
343 | auto pResult = PyObject_CallMethod(pClassInstance, | |
344 | const_cast<char*>("handle_command"), const_cast<char*>("(O)"), py_cmd); | |
345 | ||
346 | Py_DECREF(py_cmd); | |
347 | ||
348 | int r = 0; | |
349 | if (pResult != NULL) { | |
350 | if (PyTuple_Size(pResult) != 3) { | |
351 | r = -EINVAL; | |
352 | } else { | |
353 | r = PyInt_AsLong(PyTuple_GetItem(pResult, 0)); | |
354 | *ds << PyString_AsString(PyTuple_GetItem(pResult, 1)); | |
355 | *ss << PyString_AsString(PyTuple_GetItem(pResult, 2)); | |
356 | } | |
357 | ||
358 | Py_DECREF(pResult); | |
359 | } else { | |
360 | *ds << ""; | |
361 | *ss << handle_pyerror(); | |
362 | r = -EINVAL; | |
363 | } | |
364 | ||
7c673cae FG |
365 | return r; |
366 | } | |
367 |