Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / mgr / ActivePyModule.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) 2016 John Spray <john.spray@redhat.com>
7  *
8  * This is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License version 2.1, as published by the Free Software
11  * Foundation.  See file COPYING.
12  */
13
14 #include "BaseMgrModule.h"
15
16 #include "PyFormatter.h"
17
18 #include "common/debug.h"
19
20 #include "ActivePyModule.h"
21
22 //XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
23 #include <boost/python.hpp>
24 #include "include/assert.h"  // boost clobbers this
25
26
27 #define dout_context g_ceph_context
28 #define dout_subsys ceph_subsys_mgr
29 #undef dout_prefix
30 #define dout_prefix *_dout << "mgr " << __func__ << " "
31
32 // decode a Python exception into a string
33 std::string handle_pyerror()
34 {
35     using namespace boost::python;
36     using namespace boost;
37
38     PyObject *exc, *val, *tb;
39     object formatted_list, formatted;
40     PyErr_Fetch(&exc, &val, &tb);
41     handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
42     object traceback(import("traceback"));
43     if (!tb) {
44         object format_exception_only(traceback.attr("format_exception_only"));
45         formatted_list = format_exception_only(hexc, hval);
46     } else {
47         object format_exception(traceback.attr("format_exception"));
48         formatted_list = format_exception(hexc,hval, htb);
49     }
50     formatted = str("").join(formatted_list);
51     return extract<std::string>(formatted);
52 }
53
54 int ActivePyModule::load(ActivePyModules *py_modules)
55 {
56   assert(py_modules);
57   Gil gil(pMyThreadState, true);
58
59   // We tell the module how we name it, so that it can be consistent
60   // with us in logging etc.
61   auto pThisPtr = PyCapsule_New(this, nullptr, nullptr);
62   auto pPyModules = PyCapsule_New(py_modules, nullptr, nullptr);
63   auto pModuleName = PyString_FromString(module_name.c_str());
64   auto pArgs = PyTuple_Pack(3, pModuleName, pPyModules, pThisPtr);
65
66   pClassInstance = PyObject_CallObject(pClass, pArgs);
67   Py_DECREF(pClass);
68   Py_DECREF(pModuleName);
69   Py_DECREF(pArgs);
70   if (pClassInstance == nullptr) {
71     derr << "Failed to construct class in '" << module_name << "'" << dendl;
72     derr << handle_pyerror() << dendl;
73     return -EINVAL;
74   } else {
75     dout(1) << "Constructed class from module: " << module_name << dendl;
76   }
77
78   return load_commands();
79 }
80
81 void ActivePyModule::notify(const std::string &notify_type, const std::string &notify_id)
82 {
83   assert(pClassInstance != nullptr);
84
85   Gil gil(pMyThreadState, true);
86
87   // Execute
88   auto pValue = PyObject_CallMethod(pClassInstance,
89        const_cast<char*>("notify"), const_cast<char*>("(ss)"),
90        notify_type.c_str(), notify_id.c_str());
91
92   if (pValue != NULL) {
93     Py_DECREF(pValue);
94   } else {
95     derr << module_name << ".notify:" << dendl;
96     derr << handle_pyerror() << dendl;
97     // FIXME: callers can't be expected to handle a python module
98     // that has spontaneously broken, but Mgr() should provide
99     // a hook to unload misbehaving modules when they have an
100     // error somewhere like this
101   }
102 }
103
104 void ActivePyModule::notify_clog(const LogEntry &log_entry)
105 {
106   assert(pClassInstance != nullptr);
107
108   Gil gil(pMyThreadState, true);
109
110   // Construct python-ized LogEntry
111   PyFormatter f;
112   log_entry.dump(&f);
113   auto py_log_entry = f.get();
114
115   // Execute
116   auto pValue = PyObject_CallMethod(pClassInstance,
117        const_cast<char*>("notify"), const_cast<char*>("(sN)"),
118        "clog", py_log_entry);
119
120   if (pValue != NULL) {
121     Py_DECREF(pValue);
122   } else {
123     derr << module_name << ".notify_clog:" << dendl;
124     derr << handle_pyerror() << dendl;
125     // FIXME: callers can't be expected to handle a python module
126     // that has spontaneously broken, but Mgr() should provide
127     // a hook to unload misbehaving modules when they have an
128     // error somewhere like this
129   }
130 }
131
132 int ActivePyModule::load_commands()
133 {
134   // Don't need a Gil here -- this is called from ActivePyModule::load(),
135   // which already has one.
136   PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS");
137   if (command_list == nullptr) {
138     // Even modules that don't define command should still have the COMMANDS
139     // from the MgrModule definition.  Something is wrong!
140     derr << "Module " << get_name() << " has missing COMMANDS member" << dendl;
141     return -EINVAL;
142   }
143   if (!PyObject_TypeCheck(command_list, &PyList_Type)) {
144     // Relatively easy mistake for human to make, e.g. defining COMMANDS
145     // as a {} instead of a []
146     derr << "Module " << get_name() << " has COMMANDS member of wrong type ("
147             "should be a list)" << dendl;
148     return -EINVAL;
149   }
150   const size_t list_size = PyList_Size(command_list);
151   for (size_t i = 0; i < list_size; ++i) {
152     PyObject *command = PyList_GetItem(command_list, i);
153     assert(command != nullptr);
154
155     ModuleCommand item;
156
157     PyObject *pCmd = PyDict_GetItemString(command, "cmd");
158     assert(pCmd != nullptr);
159     item.cmdstring = PyString_AsString(pCmd);
160
161     dout(20) << "loaded command " << item.cmdstring << dendl;
162
163     PyObject *pDesc = PyDict_GetItemString(command, "desc");
164     assert(pDesc != nullptr);
165     item.helpstring = PyString_AsString(pDesc);
166
167     PyObject *pPerm = PyDict_GetItemString(command, "perm");
168     assert(pPerm != nullptr);
169     item.perm = PyString_AsString(pPerm);
170
171     item.handler = this;
172
173     commands.push_back(item);
174   }
175   Py_DECREF(command_list);
176
177   dout(10) << "loaded " << commands.size() << " commands" << dendl;
178
179   return 0;
180 }
181
182 int ActivePyModule::handle_command(
183   const cmdmap_t &cmdmap,
184   std::stringstream *ds,
185   std::stringstream *ss)
186 {
187   assert(ss != nullptr);
188   assert(ds != nullptr);
189
190   Gil gil(pMyThreadState, true);
191
192   PyFormatter f;
193   cmdmap_dump(cmdmap, &f);
194   PyObject *py_cmd = f.get();
195
196   auto pResult = PyObject_CallMethod(pClassInstance,
197       const_cast<char*>("handle_command"), const_cast<char*>("(O)"), py_cmd);
198
199   Py_DECREF(py_cmd);
200
201   int r = 0;
202   if (pResult != NULL) {
203     if (PyTuple_Size(pResult) != 3) {
204       r = -EINVAL;
205     } else {
206       r = PyInt_AsLong(PyTuple_GetItem(pResult, 0));
207       *ds << PyString_AsString(PyTuple_GetItem(pResult, 1));
208       *ss << PyString_AsString(PyTuple_GetItem(pResult, 2));
209     }
210
211     Py_DECREF(pResult);
212   } else {
213     *ds << "";
214     *ss << handle_pyerror();
215     r = -EINVAL;
216   }
217
218   return r;
219 }
220
221 void ActivePyModule::get_health_checks(health_check_map_t *checks)
222 {
223   checks->merge(health_checks);
224 }
225