]> git.proxmox.com Git - ceph.git/blame - ceph/src/mgr/MgrPyModule.cc
update sources to v12.1.0
[ceph.git] / ceph / src / mgr / MgrPyModule.cc
CommitLineData
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
28std::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
55namespace {
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 82MgrPyModule::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
117MgrPyModule::~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
150int 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
199int 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
223void 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
240void MgrPyModule::notify(const std::string &notify_type, const std::string &notify_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
263void 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
291int 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
329int 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