1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2017 John Spray <john.spray@redhat.com>
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.
14 #include "BaseMgrModule.h"
15 #include "BaseMgrStandbyModule.h"
17 #include "MgrContext.h"
22 #include "common/debug.h"
23 #include "common/errno.h"
24 #define dout_context g_ceph_context
25 #define dout_subsys ceph_subsys_mgr
28 #define dout_prefix *_dout << "mgr[py] "
30 // definition for non-const static member
31 std::string
PyModule::mgr_store_prefix
= "mgr/";
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()
46 using namespace boost::python
;
47 using namespace boost
;
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"));
56 object
format_exception_only(traceback
.attr("format_exception_only"));
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__");
64 ss
<< PyUnicode_AsUTF8(name_attr
) << ": " << PyUnicode_AsUTF8(val
);
65 Py_XDECREF(name_attr
);
66 ss
<< "\nError processing exception object: " << peek_pyerror();
70 object
format_exception(traceback
.attr("format_exception"));
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__");
78 ss
<< PyUnicode_AsUTF8(name_attr
) << ": " << PyUnicode_AsUTF8(val
);
79 Py_XDECREF(name_attr
);
80 ss
<< "\nError processing exception object: " << peek_pyerror();
84 formatted
= str("").join(formatted_list
);
85 return extract
<std::string
>(formatted
);
89 * Get the single-line exception message, without clearing any
92 std::string
peek_pyerror()
94 PyObject
*ptype
, *pvalue
, *ptraceback
;
95 PyErr_Fetch(&ptype
, &pvalue
, &ptraceback
);
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
);
108 PyObject
* log_write(PyObject
*, PyObject
* args
) {
110 if (PyArg_ParseTuple(args
, "s", &m
)) {
111 auto len
= strlen(m
);
112 if (len
&& m
[len
-1] == '\n') {
115 dout(4) << m
<< dendl
;
120 PyObject
* log_flush(PyObject
*, PyObject
*){
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}
130 static PyModuleDef ceph_logger_module
= {
131 PyModuleDef_HEAD_INIT
,
139 PyModuleConfig::PyModuleConfig() = default;
141 PyModuleConfig::PyModuleConfig(PyModuleConfig
&mconfig
)
142 : config(mconfig
.config
)
145 PyModuleConfig::~PyModuleConfig() = default;
148 void PyModuleConfig::set_config(
150 const std::string
&module_name
,
151 const std::string
&key
, const boost::optional
<std::string
>& val
)
153 const std::string global_key
= "mgr/" + module_name
+ "/" + key
;
156 std::ostringstream cmd_json
;
158 jf
.open_object_section("cmd");
160 jf
.dump_string("prefix", "config set");
161 jf
.dump_string("value", *val
);
163 jf
.dump_string("prefix", "config rm");
165 jf
.dump_string("who", "mgr");
166 jf
.dump_string("name", global_key
);
169 set_cmd
.run(monc
, cmd_json
.str());
173 if (set_cmd
.r
== 0) {
174 std::lock_guard
l(lock
);
176 config
[global_key
] = *val
;
178 config
.erase(global_key
);
182 dout(0) << "`config set mgr " << global_key
<< " " << val
<< "` failed: "
183 << cpp_strerror(set_cmd
.r
) << dendl
;
185 dout(0) << "`config rm mgr " << global_key
<< "` failed: "
186 << cpp_strerror(set_cmd
.r
) << dendl
;
188 dout(0) << "mon returned " << set_cmd
.r
<< ": " << set_cmd
.outs
<< dendl
;
192 std::string
PyModule::get_site_packages()
194 std::stringstream site_packages
;
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
);
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
);
206 auto n
= PyList_Size(site_packages_list
);
207 for (Py_ssize_t i
= 0; i
< n
; ++i
) {
209 site_packages
<< ":";
211 site_packages
<< PyUnicode_AsUTF8(PyList_GetItem(site_packages_list
, i
));
214 Py_DECREF(site_packages_list
);
215 Py_DECREF(site_packages_fn
);
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 :-/
221 auto site_packages_fn
= PyObject_GetAttrString(site_module
, "addsitepackages");
222 ceph_assert(site_packages_fn
);
224 auto known_paths
= PySet_New(nullptr);
225 auto pArgs
= PyTuple_Pack(1, known_paths
);
226 PyObject_CallObject(site_packages_fn
, pArgs
);
228 Py_DECREF(known_paths
);
229 Py_DECREF(site_packages_fn
);
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
);
236 dout(1) << "sys.path:" << dendl
;
237 auto n
= PyList_Size(sys_path
);
239 for (Py_ssize_t i
= 0; i
< n
; ++i
) {
240 dout(1) << " " << PyUnicode_AsUTF8(PyList_GetItem(sys_path
, i
)) << dendl
;
244 site_packages
<< ":";
246 site_packages
<< PyUnicode_AsUTF8(PyList_GetItem(sys_path
, i
));
250 Py_DECREF(sys_module
);
253 Py_DECREF(site_module
);
255 return site_packages
.str();
258 PyObject
* PyModule::init_ceph_logger()
260 auto py_logger
= PyModule_Create(&ceph_logger_module
);
261 PySys_SetObject("stderr", py_logger
);
262 PySys_SetObject("stdout", py_logger
);
266 PyObject
* PyModule::init_ceph_module()
268 static PyMethodDef module_methods
[] = {
269 {nullptr, nullptr, 0, nullptr}
271 static PyModuleDef ceph_module_def
= {
272 PyModuleDef_HEAD_INIT
,
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
}}
291 for (auto [name
, type
] : classes
) {
292 type
->tp_new
= PyType_GenericNew
;
293 if (PyType_Ready(type
) < 0) {
298 PyModule_AddObject(ceph_module
, name
, (PyObject
*)type
);
303 int PyModule::load(PyThreadState
*pMainThreadState
)
305 ceph_assert(pMainThreadState
!= nullptr);
307 // Configure sub-interpreter
309 SafeThreadState
sts(pMainThreadState
);
312 auto thread_state
= Py_NewInterpreter();
313 if (thread_state
== nullptr) {
314 derr
<< "Failed to create python sub-interpreter for '" << module_name
<< '"' << dendl
;
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
;
331 // Environment is all good, import the external module
333 Gil
gil(pMyThreadState
);
336 r
= load_subclass_of("MgrModule", &pClass
);
338 derr
<< "Class not found in module '" << module_name
<< "'" << dendl
;
344 derr
<< "Missing or invalid COMMANDS attribute in module '"
345 << module_name
<< "'" << dendl
;
346 error_string
= "Missing or invalid COMMANDS attribute";
350 register_options(pClass
);
353 derr
<< "Missing or invalid MODULE_OPTIONS attribute in module '"
354 << module_name
<< "'" << dendl
;
355 error_string
= "Missing or invalid MODULE_OPTIONS attribute";
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...
364 r
= load_subclass_of("MgrStandbyModule", &pStandbyClass
);
366 dout(4) << "Standby mode available in module '" << module_name
368 register_options(pStandbyClass
);
370 dout(4) << "Standby mode not provided by module '" << module_name
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";
388 can_run
= (pCanRun
== Py_True
);
390 error_string
= PyUnicode_AsUTF8(can_run_str
);
391 dout(4) << "Module " << get_name()
392 << " reported that it cannot run: "
393 << error_string
<< dendl
;
397 derr
<< "Module " << get_name()
398 << " returned wrong type in can_run" << dendl
;
399 error_string
= "wrong type returned from can_run";
403 Py_DECREF(pCanRunTuple
);
405 derr
<< "Exception calling can_run on " << get_name() << dendl
;
406 derr
<< handle_pyerror() << dendl
;
413 int PyModule::walk_dict_list(
414 const std::string
&attr_name
,
415 std::function
<int(PyObject
*)> fn
)
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
;
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
;
431 // Invoke fn on each item in the list
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);
438 if (!PyDict_Check(command
)) {
439 derr
<< "Module " << get_name() << " has non-dict entry "
440 << "in " << attr_name
<< " list" << dendl
;
449 Py_DECREF(command_list
);
454 int PyModule::register_options(PyObject
*cls
)
456 PyObject
*pRegCmd
= PyObject_CallMethod(
458 const_cast<char*>("_register_options"), const_cast<char*>("(s)"),
459 module_name
.c_str());
460 if (pRegCmd
!= nullptr) {
463 derr
<< "Exception calling _register_options on " << get_name()
465 derr
<< handle_pyerror() << dendl
;
470 int PyModule::load_commands()
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) {
478 derr
<< "Exception calling _register_commands on " << get_name()
480 derr
<< handle_pyerror() << dendl
;
483 int r
= walk_dict_list("COMMANDS", [this](PyObject
*pCommand
) -> int {
484 ModuleCommand command
;
486 PyObject
*pCmd
= PyDict_GetItemString(pCommand
, "cmd");
487 ceph_assert(pCmd
!= nullptr);
488 command
.cmdstring
= PyUnicode_AsUTF8(pCmd
);
490 dout(20) << "loaded command " << command
.cmdstring
<< dendl
;
492 PyObject
*pDesc
= PyDict_GetItemString(pCommand
, "desc");
493 ceph_assert(pDesc
!= nullptr);
494 command
.helpstring
= PyUnicode_AsUTF8(pDesc
);
496 PyObject
*pPerm
= PyDict_GetItemString(pCommand
, "perm");
497 ceph_assert(pPerm
!= nullptr);
498 command
.perm
= PyUnicode_AsUTF8(pPerm
);
500 command
.polling
= false;
501 if (PyObject
*pPoll
= PyDict_GetItemString(pCommand
, "poll");
502 pPoll
&& PyObject_IsTrue(pPoll
)) {
503 command
.polling
= true;
506 command
.module_name
= module_name
;
508 commands
.push_back(std::move(command
));
513 dout(10) << "loaded " << commands
.size() << " commands" << dendl
;
518 int PyModule::load_options()
520 int r
= walk_dict_list("MODULE_OPTIONS", [this](PyObject
*pOption
) -> int {
521 MgrMap::ModuleOption option
;
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
);
535 p
= PyDict_GetItemString(pOption
, "desc");
536 if (p
&& PyObject_TypeCheck(p
, &PyUnicode_Type
)) {
537 option
.desc
= PyUnicode_AsUTF8(p
);
539 p
= PyDict_GetItemString(pOption
, "long_desc");
540 if (p
&& PyObject_TypeCheck(p
, &PyUnicode_Type
)) {
541 option
.long_desc
= PyUnicode_AsUTF8(p
);
543 p
= PyDict_GetItemString(pOption
, "default");
545 auto q
= PyObject_Str(p
);
546 option
.default_value
= PyUnicode_AsUTF8(q
);
549 p
= PyDict_GetItemString(pOption
, "min");
551 auto q
= PyObject_Str(p
);
552 option
.min
= PyUnicode_AsUTF8(q
);
555 p
= PyDict_GetItemString(pOption
, "max");
557 auto q
= PyObject_Str(p
);
558 option
.max
= PyUnicode_AsUTF8(q
);
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
);
566 auto r
= PyObject_Str(q
);
567 option
.enum_allowed
.insert(PyUnicode_AsUTF8(r
));
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
));
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
));
590 p
= PyDict_GetItemString(pOption
, "runtime");
591 if (p
&& PyObject_TypeCheck(p
, &PyBool_Type
)) {
593 option
.flags
|= Option::FLAG_RUNTIME
;
596 option
.flags
&= ~Option::FLAG_RUNTIME
;
599 dout(20) << "loaded module option " << option
.name
<< dendl
;
600 options
[option
.name
] = std::move(option
);
604 dout(10) << "loaded " << options
.size() << " options" << dendl
;
609 bool PyModule::is_option(const std::string
&option_name
)
611 std::lock_guard
l(lock
);
612 return options
.count(option_name
) > 0;
615 PyObject
*PyModule::get_typed_option_value(const std::string
& name
,
616 const std::string
& value
)
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
);
624 return PyUnicode_FromString(value
.c_str());
627 int PyModule::load_subclass_of(const char* base_class
, PyObject
** py_class
)
629 // load the base class
630 PyObject
*mgr_module
= PyImport_ImportModule("mgr_module");
632 error_string
= peek_pyerror();
633 derr
<< "Module not found: 'mgr_module'" << dendl
;
634 derr
<< handle_pyerror() << dendl
;
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
;
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
;
654 auto locals
= PyModule_GetDict(plugin_module
);
655 Py_DECREF(plugin_module
);
656 PyObject
*key
, *value
;
659 while (PyDict_Next(locals
, &pos
, &key
, &value
)) {
660 if (!PyType_Check(value
)) {
663 if (!PyObject_IsSubclass(value
, mgr_module_type
)) {
666 if (PyObject_RichCompareBool(value
, mgr_module_type
, Py_EQ
)) {
669 auto class_name
= PyUnicode_AsUTF8(key
);
671 derr
<< __func__
<< ": ignoring '"
672 << module_name
<< "." << class_name
<< "'"
673 << ": only one '" << base_class
674 << "' class is loaded from each plugin" << dendl
;
678 dout(4) << __func__
<< ": found class: '"
679 << module_name
<< "." << class_name
<< "'" << dendl
;
681 Py_DECREF(mgr_module_type
);
683 return *py_class
? 0 : -EINVAL
;
686 PyModule::~PyModule()
688 if (pMyThreadState
.ts
!= nullptr) {
689 Gil
gil(pMyThreadState
, true);
691 Py_XDECREF(pStandbyClass
);