]> git.proxmox.com Git - ceph.git/blame - ceph/src/mgr/PyModule.cc
import ceph quincy 17.2.4
[ceph.git] / ceph / src / mgr / PyModule.cc
CommitLineData
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 35std::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
49using std::string;
50using std::wstring;
51
11fdf7f2 52// decode a Python exception into a string
20effc67
TL
53std::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 */
132std::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
147namespace {
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
179PyModuleConfig::PyModuleConfig() = default;
180
181PyModuleConfig::PyModuleConfig(PyModuleConfig &mconfig)
182 : config(mconfig.config)
183{}
184
185PyModuleConfig::~PyModuleConfig() = default;
186
187
20effc67 188std::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
234std::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
300PyObject* 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 308PyObject* 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
345int 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
457int 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
498int 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
514int 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
547int 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
595int 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
686bool PyModule::is_option(const std::string &option_name)
687{
688 std::lock_guard l(lock);
689 return options.count(option_name) > 0;
690}
691
692PyObject *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
704int 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
763PyModule::~PyModule()
764{
765 if (pMyThreadState.ts != nullptr) {
766 Gil gil(pMyThreadState, true);
767 Py_XDECREF(pClass);
768 Py_XDECREF(pStandbyClass);
769 }
770}
771