]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
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) 2017 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 | #include "BaseMgrModule.h" | |
15 | #include "BaseMgrStandbyModule.h" | |
16 | #include "PyOSDMap.h" | |
17 | #include "MgrContext.h" | |
9f95a23c | 18 | #include "PyUtil.h" |
11fdf7f2 TL |
19 | |
20 | #include "PyModule.h" | |
21 | ||
20effc67 TL |
22 | #include "include/stringify.h" |
23 | #include "common/BackTrace.h" | |
24 | #include "global/signal_handler.h" | |
25 | ||
11fdf7f2 TL |
26 | #include "common/debug.h" |
27 | #include "common/errno.h" | |
28 | #define dout_context g_ceph_context | |
29 | #define dout_subsys ceph_subsys_mgr | |
30 | ||
31 | #undef dout_prefix | |
32 | #define dout_prefix *_dout << "mgr[py] " | |
33 | ||
34 | // definition for non-const static member | |
f67539c2 | 35 | std::string PyModule::mgr_store_prefix = "mgr/"; |
11fdf7f2 TL |
36 | |
37 | // Courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text | |
f67539c2 TL |
38 | #define BOOST_BIND_GLOBAL_PLACEHOLDERS |
39 | // Boost apparently can't be bothered to fix its own usage of its own | |
40 | // deprecated features. | |
41 | #include <boost/python/extract.hpp> | |
42 | #include <boost/python/import.hpp> | |
43 | #include <boost/python/object.hpp> | |
44 | #undef BOOST_BIND_GLOBAL_PLACEHOLDERS | |
11fdf7f2 TL |
45 | #include <boost/algorithm/string/predicate.hpp> |
46 | #include "include/ceph_assert.h" // boost clobbers this | |
20effc67 TL |
47 | |
48 | ||
49 | using std::string; | |
50 | using std::wstring; | |
51 | ||
11fdf7f2 | 52 | // decode a Python exception into a string |
20effc67 TL |
53 | std::string handle_pyerror( |
54 | bool crash_dump, | |
55 | std::string module, | |
56 | std::string caller) | |
11fdf7f2 | 57 | { |
20effc67 TL |
58 | using namespace boost::python; |
59 | using namespace boost; | |
60 | ||
61 | PyObject *exc, *val, *tb; | |
62 | object formatted_list, formatted; | |
63 | PyErr_Fetch(&exc, &val, &tb); | |
64 | PyErr_NormalizeException(&exc, &val, &tb); | |
65 | handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb)); | |
66 | ||
67 | object traceback(import("traceback")); | |
68 | if (!tb) { | |
69 | object format_exception_only(traceback.attr("format_exception_only")); | |
70 | try { | |
71 | formatted_list = format_exception_only(hexc, hval); | |
72 | } catch (error_already_set const &) { | |
73 | // error while processing exception object | |
74 | // returning only the exception string value | |
75 | PyObject *name_attr = PyObject_GetAttrString(exc, "__name__"); | |
76 | std::stringstream ss; | |
77 | ss << PyUnicode_AsUTF8(name_attr) << ": " << PyUnicode_AsUTF8(val); | |
78 | Py_XDECREF(name_attr); | |
79 | ss << "\nError processing exception object: " << peek_pyerror(); | |
80 | return ss.str(); | |
81 | } | |
82 | } else { | |
83 | object format_exception(traceback.attr("format_exception")); | |
84 | try { | |
85 | formatted_list = format_exception(hexc, hval, htb); | |
86 | } catch (error_already_set const &) { | |
87 | // error while processing exception object | |
88 | // returning only the exception string value | |
89 | PyObject *name_attr = PyObject_GetAttrString(exc, "__name__"); | |
90 | std::stringstream ss; | |
91 | ss << PyUnicode_AsUTF8(name_attr) << ": " << PyUnicode_AsUTF8(val); | |
92 | Py_XDECREF(name_attr); | |
93 | ss << "\nError processing exception object: " << peek_pyerror(); | |
94 | return ss.str(); | |
95 | } | |
96 | } | |
97 | formatted = str("").join(formatted_list); | |
98 | ||
99 | if (!module.empty()) { | |
100 | std::list<std::string> bt_strings; | |
101 | std::map<std::string, std::string> extra; | |
102 | ||
103 | extra["mgr_module"] = module; | |
104 | extra["mgr_module_caller"] = caller; | |
105 | PyObject *name_attr = PyObject_GetAttrString(exc, "__name__"); | |
106 | extra["mgr_python_exception"] = stringify(PyUnicode_AsUTF8(name_attr)); | |
107 | Py_XDECREF(name_attr); | |
108 | ||
109 | PyObject *l = get_managed_object(formatted_list, boost::python::tag); | |
110 | if (PyList_Check(l)) { | |
111 | // skip first line, which is: "Traceback (most recent call last):\n" | |
112 | for (unsigned i = 1; i < PyList_Size(l); ++i) { | |
113 | PyObject *val = PyList_GET_ITEM(l, i); | |
114 | std::string s = PyUnicode_AsUTF8(val); | |
115 | s.resize(s.size() - 1); // strip off newline character | |
116 | bt_strings.push_back(s); | |
117 | } | |
11fdf7f2 | 118 | } |
20effc67 TL |
119 | PyBackTrace bt(bt_strings); |
120 | ||
121 | char crash_path[PATH_MAX]; | |
122 | generate_crash_dump(crash_path, bt, &extra); | |
123 | } | |
124 | ||
125 | return extract<std::string>(formatted); | |
11fdf7f2 TL |
126 | } |
127 | ||
128 | /** | |
129 | * Get the single-line exception message, without clearing any | |
130 | * exception state. | |
131 | */ | |
132 | std::string peek_pyerror() | |
133 | { | |
134 | PyObject *ptype, *pvalue, *ptraceback; | |
135 | PyErr_Fetch(&ptype, &pvalue, &ptraceback); | |
136 | ceph_assert(ptype); | |
137 | ceph_assert(pvalue); | |
138 | PyObject *pvalue_str = PyObject_Str(pvalue); | |
9f95a23c | 139 | std::string exc_msg = PyUnicode_AsUTF8(pvalue_str); |
11fdf7f2 TL |
140 | Py_DECREF(pvalue_str); |
141 | PyErr_Restore(ptype, pvalue, ptraceback); | |
142 | ||
143 | return exc_msg; | |
144 | } | |
145 | ||
146 | ||
147 | namespace { | |
148 | PyObject* log_write(PyObject*, PyObject* args) { | |
149 | char* m = nullptr; | |
150 | if (PyArg_ParseTuple(args, "s", &m)) { | |
151 | auto len = strlen(m); | |
152 | if (len && m[len-1] == '\n') { | |
153 | m[len-1] = '\0'; | |
154 | } | |
155 | dout(4) << m << dendl; | |
156 | } | |
157 | Py_RETURN_NONE; | |
158 | } | |
159 | ||
160 | PyObject* log_flush(PyObject*, PyObject*){ | |
161 | Py_RETURN_NONE; | |
162 | } | |
163 | ||
164 | static PyMethodDef log_methods[] = { | |
165 | {"write", log_write, METH_VARARGS, "write stdout and stderr"}, | |
166 | {"flush", log_flush, METH_VARARGS, "flush"}, | |
167 | {nullptr, nullptr, 0, nullptr} | |
168 | }; | |
169 | ||
11fdf7f2 TL |
170 | static PyModuleDef ceph_logger_module = { |
171 | PyModuleDef_HEAD_INIT, | |
172 | "ceph_logger", | |
173 | nullptr, | |
174 | -1, | |
175 | log_methods, | |
176 | }; | |
11fdf7f2 TL |
177 | } |
178 | ||
179 | PyModuleConfig::PyModuleConfig() = default; | |
180 | ||
181 | PyModuleConfig::PyModuleConfig(PyModuleConfig &mconfig) | |
182 | : config(mconfig.config) | |
183 | {} | |
184 | ||
185 | PyModuleConfig::~PyModuleConfig() = default; | |
186 | ||
187 | ||
20effc67 | 188 | std::pair<int, std::string> PyModuleConfig::set_config( |
11fdf7f2 TL |
189 | MonClient *monc, |
190 | const std::string &module_name, | |
20effc67 | 191 | const std::string &key, const std::optional<std::string>& val) |
11fdf7f2 | 192 | { |
f67539c2 | 193 | const std::string global_key = "mgr/" + module_name + "/" + key; |
11fdf7f2 TL |
194 | Command set_cmd; |
195 | { | |
196 | std::ostringstream cmd_json; | |
197 | JSONFormatter jf; | |
198 | jf.open_object_section("cmd"); | |
199 | if (val) { | |
200 | jf.dump_string("prefix", "config set"); | |
11fdf7f2 TL |
201 | jf.dump_string("value", *val); |
202 | } else { | |
203 | jf.dump_string("prefix", "config rm"); | |
11fdf7f2 | 204 | } |
9f95a23c TL |
205 | jf.dump_string("who", "mgr"); |
206 | jf.dump_string("name", global_key); | |
11fdf7f2 TL |
207 | jf.close_section(); |
208 | jf.flush(cmd_json); | |
209 | set_cmd.run(monc, cmd_json.str()); | |
210 | } | |
211 | set_cmd.wait(); | |
212 | ||
9f95a23c TL |
213 | if (set_cmd.r == 0) { |
214 | std::lock_guard l(lock); | |
215 | if (val) { | |
216 | config[global_key] = *val; | |
217 | } else { | |
218 | config.erase(global_key); | |
219 | } | |
20effc67 | 220 | return {0, ""}; |
9f95a23c TL |
221 | } else { |
222 | if (val) { | |
223 | dout(0) << "`config set mgr " << global_key << " " << val << "` failed: " | |
224 | << cpp_strerror(set_cmd.r) << dendl; | |
225 | } else { | |
226 | dout(0) << "`config rm mgr " << global_key << "` failed: " | |
227 | << cpp_strerror(set_cmd.r) << dendl; | |
228 | } | |
11fdf7f2 | 229 | dout(0) << "mon returned " << set_cmd.r << ": " << set_cmd.outs << dendl; |
20effc67 | 230 | return {set_cmd.r, set_cmd.outs}; |
11fdf7f2 TL |
231 | } |
232 | } | |
233 | ||
234 | std::string PyModule::get_site_packages() | |
235 | { | |
236 | std::stringstream site_packages; | |
237 | ||
238 | // CPython doesn't auto-add site-packages dirs to sys.path for us, | |
239 | // but it does provide a module that we can ask for them. | |
240 | auto site_module = PyImport_ImportModule("site"); | |
241 | ceph_assert(site_module); | |
242 | ||
243 | auto site_packages_fn = PyObject_GetAttrString(site_module, "getsitepackages"); | |
244 | if (site_packages_fn != nullptr) { | |
245 | auto site_packages_list = PyObject_CallObject(site_packages_fn, nullptr); | |
246 | ceph_assert(site_packages_list); | |
247 | ||
248 | auto n = PyList_Size(site_packages_list); | |
249 | for (Py_ssize_t i = 0; i < n; ++i) { | |
250 | if (i != 0) { | |
251 | site_packages << ":"; | |
252 | } | |
9f95a23c | 253 | site_packages << PyUnicode_AsUTF8(PyList_GetItem(site_packages_list, i)); |
11fdf7f2 TL |
254 | } |
255 | ||
256 | Py_DECREF(site_packages_list); | |
257 | Py_DECREF(site_packages_fn); | |
258 | } else { | |
259 | // Fall back to generating our own site-packages paths by imitating | |
260 | // what the standard site.py does. This is annoying but it lets us | |
261 | // run inside virtualenvs :-/ | |
262 | ||
263 | auto site_packages_fn = PyObject_GetAttrString(site_module, "addsitepackages"); | |
264 | ceph_assert(site_packages_fn); | |
265 | ||
266 | auto known_paths = PySet_New(nullptr); | |
267 | auto pArgs = PyTuple_Pack(1, known_paths); | |
268 | PyObject_CallObject(site_packages_fn, pArgs); | |
269 | Py_DECREF(pArgs); | |
270 | Py_DECREF(known_paths); | |
271 | Py_DECREF(site_packages_fn); | |
272 | ||
273 | auto sys_module = PyImport_ImportModule("sys"); | |
274 | ceph_assert(sys_module); | |
275 | auto sys_path = PyObject_GetAttrString(sys_module, "path"); | |
276 | ceph_assert(sys_path); | |
277 | ||
278 | dout(1) << "sys.path:" << dendl; | |
279 | auto n = PyList_Size(sys_path); | |
280 | bool first = true; | |
281 | for (Py_ssize_t i = 0; i < n; ++i) { | |
9f95a23c | 282 | dout(1) << " " << PyUnicode_AsUTF8(PyList_GetItem(sys_path, i)) << dendl; |
11fdf7f2 TL |
283 | if (first) { |
284 | first = false; | |
285 | } else { | |
286 | site_packages << ":"; | |
287 | } | |
9f95a23c | 288 | site_packages << PyUnicode_AsUTF8(PyList_GetItem(sys_path, i)); |
11fdf7f2 TL |
289 | } |
290 | ||
291 | Py_DECREF(sys_path); | |
292 | Py_DECREF(sys_module); | |
293 | } | |
294 | ||
295 | Py_DECREF(site_module); | |
296 | ||
297 | return site_packages.str(); | |
298 | } | |
299 | ||
11fdf7f2 TL |
300 | PyObject* PyModule::init_ceph_logger() |
301 | { | |
302 | auto py_logger = PyModule_Create(&ceph_logger_module); | |
303 | PySys_SetObject("stderr", py_logger); | |
304 | PySys_SetObject("stdout", py_logger); | |
305 | return py_logger; | |
306 | } | |
11fdf7f2 | 307 | |
11fdf7f2 | 308 | PyObject* PyModule::init_ceph_module() |
11fdf7f2 TL |
309 | { |
310 | static PyMethodDef module_methods[] = { | |
311 | {nullptr, nullptr, 0, nullptr} | |
312 | }; | |
11fdf7f2 TL |
313 | static PyModuleDef ceph_module_def = { |
314 | PyModuleDef_HEAD_INIT, | |
315 | "ceph_module", | |
316 | nullptr, | |
317 | -1, | |
318 | module_methods, | |
319 | nullptr, | |
320 | nullptr, | |
321 | nullptr, | |
322 | nullptr | |
323 | }; | |
324 | PyObject *ceph_module = PyModule_Create(&ceph_module_def); | |
11fdf7f2 TL |
325 | ceph_assert(ceph_module != nullptr); |
326 | std::map<const char*, PyTypeObject*> classes{ | |
327 | {{"BaseMgrModule", &BaseMgrModuleType}, | |
328 | {"BaseMgrStandbyModule", &BaseMgrStandbyModuleType}, | |
329 | {"BasePyOSDMap", &BasePyOSDMapType}, | |
330 | {"BasePyOSDMapIncremental", &BasePyOSDMapIncrementalType}, | |
331 | {"BasePyCRUSH", &BasePyCRUSHType}} | |
332 | }; | |
333 | for (auto [name, type] : classes) { | |
334 | type->tp_new = PyType_GenericNew; | |
335 | if (PyType_Ready(type) < 0) { | |
336 | ceph_abort(); | |
337 | } | |
338 | Py_INCREF(type); | |
339 | ||
340 | PyModule_AddObject(ceph_module, name, (PyObject *)type); | |
341 | } | |
11fdf7f2 | 342 | return ceph_module; |
11fdf7f2 TL |
343 | } |
344 | ||
345 | int PyModule::load(PyThreadState *pMainThreadState) | |
346 | { | |
347 | ceph_assert(pMainThreadState != nullptr); | |
348 | ||
349 | // Configure sub-interpreter | |
350 | { | |
351 | SafeThreadState sts(pMainThreadState); | |
352 | Gil gil(sts); | |
353 | ||
354 | auto thread_state = Py_NewInterpreter(); | |
355 | if (thread_state == nullptr) { | |
356 | derr << "Failed to create python sub-interpreter for '" << module_name << '"' << dendl; | |
357 | return -EINVAL; | |
358 | } else { | |
359 | pMyThreadState.set(thread_state); | |
360 | // Some python modules do not cope with an unpopulated argv, so lets | |
361 | // fake one. This step also picks up site-packages into sys.path. | |
11fdf7f2 TL |
362 | const wchar_t *argv[] = {L"ceph-mgr"}; |
363 | PySys_SetArgv(1, (wchar_t**)argv); | |
11fdf7f2 | 364 | // Configure sys.path to include mgr_module_path |
f67539c2 TL |
365 | string paths = (g_conf().get_val<std::string>("mgr_module_path") + ':' + |
366 | get_site_packages() + ':'); | |
367 | wstring sys_path(wstring(begin(paths), end(paths)) + Py_GetPath()); | |
11fdf7f2 TL |
368 | PySys_SetPath(const_cast<wchar_t*>(sys_path.c_str())); |
369 | dout(10) << "Computed sys.path '" | |
370 | << string(begin(sys_path), end(sys_path)) << "'" << dendl; | |
11fdf7f2 TL |
371 | } |
372 | } | |
373 | // Environment is all good, import the external module | |
374 | { | |
375 | Gil gil(pMyThreadState); | |
376 | ||
377 | int r; | |
378 | r = load_subclass_of("MgrModule", &pClass); | |
379 | if (r) { | |
380 | derr << "Class not found in module '" << module_name << "'" << dendl; | |
381 | return r; | |
382 | } | |
383 | ||
384 | r = load_commands(); | |
385 | if (r != 0) { | |
386 | derr << "Missing or invalid COMMANDS attribute in module '" | |
387 | << module_name << "'" << dendl; | |
388 | error_string = "Missing or invalid COMMANDS attribute"; | |
389 | return r; | |
390 | } | |
391 | ||
9f95a23c | 392 | register_options(pClass); |
11fdf7f2 TL |
393 | r = load_options(); |
394 | if (r != 0) { | |
395 | derr << "Missing or invalid MODULE_OPTIONS attribute in module '" | |
396 | << module_name << "'" << dendl; | |
397 | error_string = "Missing or invalid MODULE_OPTIONS attribute"; | |
398 | return r; | |
399 | } | |
400 | ||
20effc67 TL |
401 | load_notify_types(); |
402 | ||
11fdf7f2 TL |
403 | // We've imported the module and found a MgrModule subclass, at this |
404 | // point the module is considered loaded. It might still not be | |
405 | // runnable though, can_run populated later... | |
406 | loaded = true; | |
407 | ||
408 | r = load_subclass_of("MgrStandbyModule", &pStandbyClass); | |
409 | if (!r) { | |
410 | dout(4) << "Standby mode available in module '" << module_name | |
411 | << "'" << dendl; | |
9f95a23c | 412 | register_options(pStandbyClass); |
11fdf7f2 TL |
413 | } else { |
414 | dout(4) << "Standby mode not provided by module '" << module_name | |
415 | << "'" << dendl; | |
416 | } | |
417 | ||
418 | // Populate can_run by interrogating the module's callback that | |
419 | // may check for dependencies etc | |
420 | PyObject *pCanRunTuple = PyObject_CallMethod(pClass, | |
421 | const_cast<char*>("can_run"), const_cast<char*>("()")); | |
422 | if (pCanRunTuple != nullptr) { | |
423 | if (PyTuple_Check(pCanRunTuple) && PyTuple_Size(pCanRunTuple) == 2) { | |
424 | PyObject *pCanRun = PyTuple_GetItem(pCanRunTuple, 0); | |
425 | PyObject *can_run_str = PyTuple_GetItem(pCanRunTuple, 1); | |
9f95a23c | 426 | if (!PyBool_Check(pCanRun) || !PyUnicode_Check(can_run_str)) { |
11fdf7f2 TL |
427 | derr << "Module " << get_name() |
428 | << " returned wrong type in can_run" << dendl; | |
429 | error_string = "wrong type returned from can_run"; | |
430 | can_run = false; | |
431 | } else { | |
432 | can_run = (pCanRun == Py_True); | |
433 | if (!can_run) { | |
9f95a23c | 434 | error_string = PyUnicode_AsUTF8(can_run_str); |
11fdf7f2 TL |
435 | dout(4) << "Module " << get_name() |
436 | << " reported that it cannot run: " | |
437 | << error_string << dendl; | |
438 | } | |
439 | } | |
440 | } else { | |
441 | derr << "Module " << get_name() | |
442 | << " returned wrong type in can_run" << dendl; | |
443 | error_string = "wrong type returned from can_run"; | |
444 | can_run = false; | |
445 | } | |
446 | ||
447 | Py_DECREF(pCanRunTuple); | |
448 | } else { | |
449 | derr << "Exception calling can_run on " << get_name() << dendl; | |
20effc67 | 450 | derr << handle_pyerror(true, get_name(), "PyModule::load") << dendl; |
11fdf7f2 TL |
451 | can_run = false; |
452 | } | |
453 | } | |
454 | return 0; | |
455 | } | |
456 | ||
457 | int PyModule::walk_dict_list( | |
458 | const std::string &attr_name, | |
459 | std::function<int(PyObject*)> fn) | |
460 | { | |
461 | PyObject *command_list = PyObject_GetAttrString(pClass, attr_name.c_str()); | |
462 | if (command_list == nullptr) { | |
463 | derr << "Module " << get_name() << " has missing " << attr_name | |
464 | << " member" << dendl; | |
465 | return -EINVAL; | |
466 | } | |
467 | if (!PyObject_TypeCheck(command_list, &PyList_Type)) { | |
468 | // Relatively easy mistake for human to make, e.g. defining COMMANDS | |
469 | // as a {} instead of a [] | |
470 | derr << "Module " << get_name() << " has " << attr_name | |
471 | << " member of wrong type (should be a list)" << dendl; | |
472 | return -EINVAL; | |
473 | } | |
474 | ||
475 | // Invoke fn on each item in the list | |
476 | int r = 0; | |
477 | const size_t list_size = PyList_Size(command_list); | |
478 | for (size_t i = 0; i < list_size; ++i) { | |
479 | PyObject *command = PyList_GetItem(command_list, i); | |
480 | ceph_assert(command != nullptr); | |
481 | ||
482 | if (!PyDict_Check(command)) { | |
483 | derr << "Module " << get_name() << " has non-dict entry " | |
484 | << "in " << attr_name << " list" << dendl; | |
485 | return -EINVAL; | |
486 | } | |
487 | ||
488 | r = fn(command); | |
489 | if (r != 0) { | |
490 | break; | |
491 | } | |
492 | } | |
493 | Py_DECREF(command_list); | |
494 | ||
495 | return r; | |
496 | } | |
497 | ||
9f95a23c TL |
498 | int PyModule::register_options(PyObject *cls) |
499 | { | |
500 | PyObject *pRegCmd = PyObject_CallMethod( | |
501 | cls, | |
502 | const_cast<char*>("_register_options"), const_cast<char*>("(s)"), | |
503 | module_name.c_str()); | |
504 | if (pRegCmd != nullptr) { | |
505 | Py_DECREF(pRegCmd); | |
506 | } else { | |
507 | derr << "Exception calling _register_options on " << get_name() | |
508 | << dendl; | |
20effc67 TL |
509 | derr << handle_pyerror(true, module_name, "PyModule::register_options") << dendl; |
510 | } | |
511 | return 0; | |
512 | } | |
513 | ||
514 | int PyModule::load_notify_types() | |
515 | { | |
516 | PyObject *ls = PyObject_GetAttrString(pClass, "NOTIFY_TYPES"); | |
517 | if (ls == nullptr) { | |
518 | derr << "Module " << get_name() << " has missing NOTIFY_TYPES member" << dendl; | |
519 | return -EINVAL; | |
520 | } | |
521 | if (!PyObject_TypeCheck(ls, &PyList_Type)) { | |
522 | // Relatively easy mistake for human to make, e.g. defining COMMANDS | |
523 | // as a {} instead of a [] | |
524 | derr << "Module " << get_name() << " has NOTIFY_TYPES that is not a list" << dendl; | |
525 | return -EINVAL; | |
9f95a23c | 526 | } |
20effc67 TL |
527 | |
528 | const size_t list_size = PyList_Size(ls); | |
529 | for (size_t i = 0; i < list_size; ++i) { | |
530 | PyObject *notify_type = PyList_GetItem(ls, i); | |
531 | ceph_assert(notify_type != nullptr); | |
532 | ||
533 | if (!PyObject_TypeCheck(notify_type, &PyUnicode_Type)) { | |
534 | derr << "Module " << get_name() << " has non-string entry in NOTIFY_TYPES list" | |
535 | << dendl; | |
536 | return -EINVAL; | |
537 | } | |
538 | ||
539 | notify_types.insert(PyUnicode_AsUTF8(notify_type)); | |
540 | } | |
541 | Py_DECREF(ls); | |
542 | dout(10) << "Module " << get_name() << " notify_types " << notify_types << dendl; | |
543 | ||
9f95a23c TL |
544 | return 0; |
545 | } | |
546 | ||
11fdf7f2 TL |
547 | int PyModule::load_commands() |
548 | { | |
549 | PyObject *pRegCmd = PyObject_CallMethod(pClass, | |
9f95a23c TL |
550 | const_cast<char*>("_register_commands"), const_cast<char*>("(s)"), |
551 | module_name.c_str()); | |
11fdf7f2 TL |
552 | if (pRegCmd != nullptr) { |
553 | Py_DECREF(pRegCmd); | |
554 | } else { | |
555 | derr << "Exception calling _register_commands on " << get_name() | |
556 | << dendl; | |
20effc67 | 557 | derr << handle_pyerror(true, module_name, "PyModule::load_commands") << dendl; |
11fdf7f2 TL |
558 | } |
559 | ||
560 | int r = walk_dict_list("COMMANDS", [this](PyObject *pCommand) -> int { | |
561 | ModuleCommand command; | |
562 | ||
563 | PyObject *pCmd = PyDict_GetItemString(pCommand, "cmd"); | |
564 | ceph_assert(pCmd != nullptr); | |
9f95a23c | 565 | command.cmdstring = PyUnicode_AsUTF8(pCmd); |
11fdf7f2 TL |
566 | |
567 | dout(20) << "loaded command " << command.cmdstring << dendl; | |
568 | ||
569 | PyObject *pDesc = PyDict_GetItemString(pCommand, "desc"); | |
570 | ceph_assert(pDesc != nullptr); | |
9f95a23c | 571 | command.helpstring = PyUnicode_AsUTF8(pDesc); |
11fdf7f2 TL |
572 | |
573 | PyObject *pPerm = PyDict_GetItemString(pCommand, "perm"); | |
574 | ceph_assert(pPerm != nullptr); | |
9f95a23c | 575 | command.perm = PyUnicode_AsUTF8(pPerm); |
11fdf7f2 TL |
576 | |
577 | command.polling = false; | |
f67539c2 TL |
578 | if (PyObject *pPoll = PyDict_GetItemString(pCommand, "poll"); |
579 | pPoll && PyObject_IsTrue(pPoll)) { | |
580 | command.polling = true; | |
11fdf7f2 TL |
581 | } |
582 | ||
583 | command.module_name = module_name; | |
584 | ||
585 | commands.push_back(std::move(command)); | |
586 | ||
587 | return 0; | |
588 | }); | |
589 | ||
590 | dout(10) << "loaded " << commands.size() << " commands" << dendl; | |
591 | ||
592 | return r; | |
593 | } | |
594 | ||
595 | int PyModule::load_options() | |
596 | { | |
597 | int r = walk_dict_list("MODULE_OPTIONS", [this](PyObject *pOption) -> int { | |
598 | MgrMap::ModuleOption option; | |
599 | PyObject *p; | |
600 | p = PyDict_GetItemString(pOption, "name"); | |
601 | ceph_assert(p != nullptr); | |
9f95a23c | 602 | option.name = PyUnicode_AsUTF8(p); |
11fdf7f2 TL |
603 | option.type = Option::TYPE_STR; |
604 | p = PyDict_GetItemString(pOption, "type"); | |
9f95a23c TL |
605 | if (p && PyObject_TypeCheck(p, &PyUnicode_Type)) { |
606 | std::string s = PyUnicode_AsUTF8(p); | |
11fdf7f2 TL |
607 | int t = Option::str_to_type(s); |
608 | if (t >= 0) { | |
609 | option.type = t; | |
610 | } | |
611 | } | |
612 | p = PyDict_GetItemString(pOption, "desc"); | |
9f95a23c TL |
613 | if (p && PyObject_TypeCheck(p, &PyUnicode_Type)) { |
614 | option.desc = PyUnicode_AsUTF8(p); | |
11fdf7f2 TL |
615 | } |
616 | p = PyDict_GetItemString(pOption, "long_desc"); | |
9f95a23c TL |
617 | if (p && PyObject_TypeCheck(p, &PyUnicode_Type)) { |
618 | option.long_desc = PyUnicode_AsUTF8(p); | |
11fdf7f2 TL |
619 | } |
620 | p = PyDict_GetItemString(pOption, "default"); | |
621 | if (p) { | |
622 | auto q = PyObject_Str(p); | |
9f95a23c | 623 | option.default_value = PyUnicode_AsUTF8(q); |
11fdf7f2 TL |
624 | Py_DECREF(q); |
625 | } | |
626 | p = PyDict_GetItemString(pOption, "min"); | |
627 | if (p) { | |
628 | auto q = PyObject_Str(p); | |
9f95a23c | 629 | option.min = PyUnicode_AsUTF8(q); |
11fdf7f2 TL |
630 | Py_DECREF(q); |
631 | } | |
632 | p = PyDict_GetItemString(pOption, "max"); | |
633 | if (p) { | |
634 | auto q = PyObject_Str(p); | |
9f95a23c | 635 | option.max = PyUnicode_AsUTF8(q); |
11fdf7f2 TL |
636 | Py_DECREF(q); |
637 | } | |
638 | p = PyDict_GetItemString(pOption, "enum_allowed"); | |
639 | if (p && PyObject_TypeCheck(p, &PyList_Type)) { | |
20effc67 | 640 | for (Py_ssize_t i = 0; i < PyList_Size(p); ++i) { |
11fdf7f2 TL |
641 | auto q = PyList_GetItem(p, i); |
642 | if (q) { | |
643 | auto r = PyObject_Str(q); | |
9f95a23c | 644 | option.enum_allowed.insert(PyUnicode_AsUTF8(r)); |
11fdf7f2 TL |
645 | Py_DECREF(r); |
646 | } | |
647 | } | |
648 | } | |
649 | p = PyDict_GetItemString(pOption, "see_also"); | |
650 | if (p && PyObject_TypeCheck(p, &PyList_Type)) { | |
20effc67 | 651 | for (Py_ssize_t i = 0; i < PyList_Size(p); ++i) { |
11fdf7f2 | 652 | auto q = PyList_GetItem(p, i); |
9f95a23c TL |
653 | if (q && PyObject_TypeCheck(q, &PyUnicode_Type)) { |
654 | option.see_also.insert(PyUnicode_AsUTF8(q)); | |
11fdf7f2 TL |
655 | } |
656 | } | |
657 | } | |
658 | p = PyDict_GetItemString(pOption, "tags"); | |
659 | if (p && PyObject_TypeCheck(p, &PyList_Type)) { | |
20effc67 | 660 | for (Py_ssize_t i = 0; i < PyList_Size(p); ++i) { |
11fdf7f2 | 661 | auto q = PyList_GetItem(p, i); |
9f95a23c TL |
662 | if (q && PyObject_TypeCheck(q, &PyUnicode_Type)) { |
663 | option.tags.insert(PyUnicode_AsUTF8(q)); | |
11fdf7f2 TL |
664 | } |
665 | } | |
666 | } | |
667 | p = PyDict_GetItemString(pOption, "runtime"); | |
668 | if (p && PyObject_TypeCheck(p, &PyBool_Type)) { | |
669 | if (p == Py_True) { | |
670 | option.flags |= Option::FLAG_RUNTIME; | |
671 | } | |
672 | if (p == Py_False) { | |
673 | option.flags &= ~Option::FLAG_RUNTIME; | |
674 | } | |
675 | } | |
676 | dout(20) << "loaded module option " << option.name << dendl; | |
677 | options[option.name] = std::move(option); | |
678 | return 0; | |
679 | }); | |
680 | ||
681 | dout(10) << "loaded " << options.size() << " options" << dendl; | |
682 | ||
683 | return r; | |
684 | } | |
685 | ||
686 | bool PyModule::is_option(const std::string &option_name) | |
687 | { | |
688 | std::lock_guard l(lock); | |
689 | return options.count(option_name) > 0; | |
690 | } | |
691 | ||
692 | PyObject *PyModule::get_typed_option_value(const std::string& name, | |
693 | const std::string& value) | |
694 | { | |
695 | // we don't need to hold a lock here because these MODULE_OPTIONS | |
696 | // are set up exactly once during startup. | |
697 | auto p = options.find(name); | |
698 | if (p != options.end()) { | |
9f95a23c | 699 | return get_python_typed_option_value((Option::type_t)p->second.type, value); |
11fdf7f2 | 700 | } |
9f95a23c | 701 | return PyUnicode_FromString(value.c_str()); |
11fdf7f2 TL |
702 | } |
703 | ||
704 | int PyModule::load_subclass_of(const char* base_class, PyObject** py_class) | |
705 | { | |
706 | // load the base class | |
707 | PyObject *mgr_module = PyImport_ImportModule("mgr_module"); | |
708 | if (!mgr_module) { | |
709 | error_string = peek_pyerror(); | |
710 | derr << "Module not found: 'mgr_module'" << dendl; | |
20effc67 | 711 | derr << handle_pyerror(true, module_name, "PyModule::load_subclass_of") << dendl; |
11fdf7f2 TL |
712 | return -EINVAL; |
713 | } | |
714 | auto mgr_module_type = PyObject_GetAttrString(mgr_module, base_class); | |
715 | Py_DECREF(mgr_module); | |
716 | if (!mgr_module_type) { | |
717 | error_string = peek_pyerror(); | |
718 | derr << "Unable to import MgrModule from mgr_module" << dendl; | |
20effc67 | 719 | derr << handle_pyerror(true, module_name, "PyModule::load_subclass_of") << dendl; |
11fdf7f2 TL |
720 | return -EINVAL; |
721 | } | |
722 | ||
723 | // find the sub class | |
724 | PyObject *plugin_module = PyImport_ImportModule(module_name.c_str()); | |
725 | if (!plugin_module) { | |
726 | error_string = peek_pyerror(); | |
727 | derr << "Module not found: '" << module_name << "'" << dendl; | |
20effc67 | 728 | derr << handle_pyerror(true, module_name, "PyModule::load_subclass_of") << dendl; |
11fdf7f2 TL |
729 | return -ENOENT; |
730 | } | |
731 | auto locals = PyModule_GetDict(plugin_module); | |
732 | Py_DECREF(plugin_module); | |
733 | PyObject *key, *value; | |
734 | Py_ssize_t pos = 0; | |
735 | *py_class = nullptr; | |
736 | while (PyDict_Next(locals, &pos, &key, &value)) { | |
737 | if (!PyType_Check(value)) { | |
738 | continue; | |
739 | } | |
740 | if (!PyObject_IsSubclass(value, mgr_module_type)) { | |
741 | continue; | |
742 | } | |
743 | if (PyObject_RichCompareBool(value, mgr_module_type, Py_EQ)) { | |
744 | continue; | |
745 | } | |
9f95a23c | 746 | auto class_name = PyUnicode_AsUTF8(key); |
11fdf7f2 TL |
747 | if (*py_class) { |
748 | derr << __func__ << ": ignoring '" | |
749 | << module_name << "." << class_name << "'" | |
750 | << ": only one '" << base_class | |
751 | << "' class is loaded from each plugin" << dendl; | |
752 | continue; | |
753 | } | |
754 | *py_class = value; | |
755 | dout(4) << __func__ << ": found class: '" | |
756 | << module_name << "." << class_name << "'" << dendl; | |
757 | } | |
758 | Py_DECREF(mgr_module_type); | |
759 | ||
760 | return *py_class ? 0 : -EINVAL; | |
761 | } | |
762 | ||
763 | PyModule::~PyModule() | |
764 | { | |
765 | if (pMyThreadState.ts != nullptr) { | |
766 | Gil gil(pMyThreadState, true); | |
767 | Py_XDECREF(pClass); | |
768 | Py_XDECREF(pStandbyClass); | |
769 | } | |
770 | } | |
771 |