]>
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 | ||
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 ¬ify_type, const std::string ¬ify_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 |