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