]>
Commit | Line | Data |
---|---|---|
3efd9988 FG |
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 | ||
3efd9988 FG |
14 | #include "PyFormatter.h" |
15 | ||
16 | #include "common/debug.h" | |
92f5a8d4 | 17 | #include "mon/MonCommand.h" |
3efd9988 FG |
18 | |
19 | #include "ActivePyModule.h" | |
92f5a8d4 | 20 | #include "MgrSession.h" |
3efd9988 | 21 | |
3efd9988 FG |
22 | |
23 | #define dout_context g_ceph_context | |
24 | #define dout_subsys ceph_subsys_mgr | |
25 | #undef dout_prefix | |
26 | #define dout_prefix *_dout << "mgr " << __func__ << " " | |
27 | ||
20effc67 TL |
28 | using std::string; |
29 | using namespace std::literals; | |
30 | ||
3efd9988 FG |
31 | int ActivePyModule::load(ActivePyModules *py_modules) |
32 | { | |
11fdf7f2 TL |
33 | ceph_assert(py_modules); |
34 | Gil gil(py_module->pMyThreadState, true); | |
3efd9988 FG |
35 | |
36 | // We tell the module how we name it, so that it can be consistent | |
37 | // with us in logging etc. | |
38 | auto pThisPtr = PyCapsule_New(this, nullptr, nullptr); | |
39 | auto pPyModules = PyCapsule_New(py_modules, nullptr, nullptr); | |
9f95a23c | 40 | auto pModuleName = PyUnicode_FromString(get_name().c_str()); |
3efd9988 FG |
41 | auto pArgs = PyTuple_Pack(3, pModuleName, pPyModules, pThisPtr); |
42 | ||
11fdf7f2 | 43 | pClassInstance = PyObject_CallObject(py_module->pClass, pArgs); |
3efd9988 FG |
44 | Py_DECREF(pModuleName); |
45 | Py_DECREF(pArgs); | |
46 | if (pClassInstance == nullptr) { | |
11fdf7f2 | 47 | derr << "Failed to construct class in '" << get_name() << "'" << dendl; |
20effc67 | 48 | derr << handle_pyerror(true, get_name(), "ActivePyModule::load") << dendl; |
3efd9988 FG |
49 | return -EINVAL; |
50 | } else { | |
11fdf7f2 | 51 | dout(1) << "Constructed class from module: " << get_name() << dendl; |
3efd9988 FG |
52 | } |
53 | ||
11fdf7f2 | 54 | return 0; |
3efd9988 FG |
55 | } |
56 | ||
57 | void ActivePyModule::notify(const std::string ¬ify_type, const std::string ¬ify_id) | |
58 | { | |
9f95a23c TL |
59 | if (is_dead()) { |
60 | dout(5) << "cancelling notify " << notify_type << " " << notify_id << dendl; | |
61 | return; | |
62 | } | |
63 | ||
11fdf7f2 | 64 | ceph_assert(pClassInstance != nullptr); |
3efd9988 | 65 | |
11fdf7f2 | 66 | Gil gil(py_module->pMyThreadState, true); |
3efd9988 FG |
67 | |
68 | // Execute | |
69 | auto pValue = PyObject_CallMethod(pClassInstance, | |
70 | const_cast<char*>("notify"), const_cast<char*>("(ss)"), | |
71 | notify_type.c_str(), notify_id.c_str()); | |
72 | ||
73 | if (pValue != NULL) { | |
74 | Py_DECREF(pValue); | |
75 | } else { | |
11fdf7f2 | 76 | derr << get_name() << ".notify:" << dendl; |
20effc67 | 77 | derr << handle_pyerror(true, get_name(), "ActivePyModule::notify") << dendl; |
3efd9988 FG |
78 | // FIXME: callers can't be expected to handle a python module |
79 | // that has spontaneously broken, but Mgr() should provide | |
80 | // a hook to unload misbehaving modules when they have an | |
81 | // error somewhere like this | |
82 | } | |
83 | } | |
84 | ||
85 | void ActivePyModule::notify_clog(const LogEntry &log_entry) | |
86 | { | |
9f95a23c TL |
87 | if (is_dead()) { |
88 | dout(5) << "cancelling notify_clog" << dendl; | |
89 | return; | |
90 | } | |
91 | ||
11fdf7f2 | 92 | ceph_assert(pClassInstance != nullptr); |
3efd9988 | 93 | |
11fdf7f2 | 94 | Gil gil(py_module->pMyThreadState, true); |
3efd9988 FG |
95 | |
96 | // Construct python-ized LogEntry | |
97 | PyFormatter f; | |
98 | log_entry.dump(&f); | |
99 | auto py_log_entry = f.get(); | |
100 | ||
101 | // Execute | |
102 | auto pValue = PyObject_CallMethod(pClassInstance, | |
103 | const_cast<char*>("notify"), const_cast<char*>("(sN)"), | |
104 | "clog", py_log_entry); | |
105 | ||
106 | if (pValue != NULL) { | |
107 | Py_DECREF(pValue); | |
108 | } else { | |
11fdf7f2 | 109 | derr << get_name() << ".notify_clog:" << dendl; |
20effc67 | 110 | derr << handle_pyerror(true, get_name(), "ActivePyModule::notify_clog") << dendl; |
3efd9988 FG |
111 | // FIXME: callers can't be expected to handle a python module |
112 | // that has spontaneously broken, but Mgr() should provide | |
113 | // a hook to unload misbehaving modules when they have an | |
114 | // error somewhere like this | |
115 | } | |
116 | } | |
117 | ||
11fdf7f2 | 118 | bool ActivePyModule::method_exists(const std::string &method) const |
3efd9988 | 119 | { |
11fdf7f2 TL |
120 | Gil gil(py_module->pMyThreadState, true); |
121 | ||
122 | auto boundMethod = PyObject_GetAttrString(pClassInstance, method.c_str()); | |
123 | if (boundMethod == nullptr) { | |
124 | return false; | |
125 | } else { | |
126 | Py_DECREF(boundMethod); | |
127 | return true; | |
3efd9988 | 128 | } |
11fdf7f2 TL |
129 | } |
130 | ||
131 | PyObject *ActivePyModule::dispatch_remote( | |
132 | const std::string &method, | |
133 | PyObject *args, | |
134 | PyObject *kwargs, | |
135 | std::string *err) | |
136 | { | |
137 | ceph_assert(err != nullptr); | |
3efd9988 | 138 | |
11fdf7f2 TL |
139 | // Rather than serializing arguments, pass the CPython objects. |
140 | // Works because we happen to know that the subinterpreter | |
141 | // implementation shares a GIL, allocator, deallocator and GC state, so | |
142 | // it's okay to pass the objects between subinterpreters. | |
143 | // But in future this might involve serialization to support a CSP-aware | |
144 | // future Python interpreter a la PEP554 | |
3efd9988 | 145 | |
11fdf7f2 | 146 | Gil gil(py_module->pMyThreadState, true); |
3efd9988 | 147 | |
11fdf7f2 TL |
148 | // Fire the receiving method |
149 | auto boundMethod = PyObject_GetAttrString(pClassInstance, method.c_str()); | |
3efd9988 | 150 | |
11fdf7f2 TL |
151 | // Caller should have done method_exists check first! |
152 | ceph_assert(boundMethod != nullptr); | |
3efd9988 | 153 | |
11fdf7f2 TL |
154 | dout(20) << "Calling " << py_module->get_name() |
155 | << "." << method << "..." << dendl; | |
3efd9988 | 156 | |
11fdf7f2 TL |
157 | auto remoteResult = PyObject_Call(boundMethod, |
158 | args, kwargs); | |
159 | Py_DECREF(boundMethod); | |
3efd9988 | 160 | |
11fdf7f2 TL |
161 | if (remoteResult == nullptr) { |
162 | // Because the caller is in a different context, we can't let this | |
163 | // exception bubble up, need to re-raise it from the caller's | |
164 | // context later. | |
20effc67 TL |
165 | std::string caller = "ActivePyModule::dispatch_remote "s + method; |
166 | *err = handle_pyerror(true, get_name(), caller); | |
11fdf7f2 TL |
167 | } else { |
168 | dout(20) << "Success calling '" << method << "'" << dendl; | |
3efd9988 | 169 | } |
3efd9988 | 170 | |
11fdf7f2 TL |
171 | return remoteResult; |
172 | } | |
3efd9988 | 173 | |
11fdf7f2 TL |
174 | void ActivePyModule::config_notify() |
175 | { | |
9f95a23c TL |
176 | if (is_dead()) { |
177 | dout(5) << "cancelling config_notify" << dendl; | |
178 | return; | |
179 | } | |
180 | ||
11fdf7f2 | 181 | Gil gil(py_module->pMyThreadState, true); |
9f95a23c | 182 | dout(20) << "Calling " << py_module->get_name() << "._config_notify..." |
11fdf7f2 TL |
183 | << dendl; |
184 | auto remoteResult = PyObject_CallMethod(pClassInstance, | |
9f95a23c | 185 | const_cast<char*>("_config_notify"), |
11fdf7f2 TL |
186 | (char*)NULL); |
187 | if (remoteResult != nullptr) { | |
188 | Py_DECREF(remoteResult); | |
189 | } | |
3efd9988 FG |
190 | } |
191 | ||
192 | int ActivePyModule::handle_command( | |
92f5a8d4 TL |
193 | const ModuleCommand& module_command, |
194 | const MgrSession& session, | |
3efd9988 | 195 | const cmdmap_t &cmdmap, |
11fdf7f2 | 196 | const bufferlist &inbuf, |
3efd9988 FG |
197 | std::stringstream *ds, |
198 | std::stringstream *ss) | |
199 | { | |
11fdf7f2 TL |
200 | ceph_assert(ss != nullptr); |
201 | ceph_assert(ds != nullptr); | |
202 | ||
203 | if (pClassInstance == nullptr) { | |
204 | // Not the friendliest error string, but we could only | |
205 | // hit this in quite niche cases, if at all. | |
206 | *ss << "Module not instantiated"; | |
207 | return -EINVAL; | |
208 | } | |
3efd9988 | 209 | |
11fdf7f2 | 210 | Gil gil(py_module->pMyThreadState, true); |
3efd9988 FG |
211 | |
212 | PyFormatter f; | |
9f95a23c | 213 | TOPNSPC::common::cmdmap_dump(cmdmap, &f); |
3efd9988 | 214 | PyObject *py_cmd = f.get(); |
11fdf7f2 | 215 | string instr; |
9f95a23c | 216 | inbuf.begin().copy(inbuf.length(), instr); |
3efd9988 | 217 | |
92f5a8d4 TL |
218 | ceph_assert(m_session == nullptr); |
219 | m_command_perms = module_command.perm; | |
220 | m_session = &session; | |
221 | ||
3efd9988 | 222 | auto pResult = PyObject_CallMethod(pClassInstance, |
11fdf7f2 TL |
223 | const_cast<char*>("_handle_command"), const_cast<char*>("s#O"), |
224 | instr.c_str(), instr.length(), py_cmd); | |
3efd9988 | 225 | |
92f5a8d4 TL |
226 | m_command_perms.clear(); |
227 | m_session = nullptr; | |
3efd9988 FG |
228 | Py_DECREF(py_cmd); |
229 | ||
230 | int r = 0; | |
231 | if (pResult != NULL) { | |
232 | if (PyTuple_Size(pResult) != 3) { | |
11fdf7f2 TL |
233 | derr << "module '" << py_module->get_name() << "' command handler " |
234 | "returned wrong type!" << dendl; | |
3efd9988 FG |
235 | r = -EINVAL; |
236 | } else { | |
9f95a23c TL |
237 | r = PyLong_AsLong(PyTuple_GetItem(pResult, 0)); |
238 | *ds << PyUnicode_AsUTF8(PyTuple_GetItem(pResult, 1)); | |
239 | *ss << PyUnicode_AsUTF8(PyTuple_GetItem(pResult, 2)); | |
3efd9988 FG |
240 | } |
241 | ||
242 | Py_DECREF(pResult); | |
243 | } else { | |
11fdf7f2 TL |
244 | derr << "module '" << py_module->get_name() << "' command handler " |
245 | "threw exception: " << peek_pyerror() << dendl; | |
3efd9988 FG |
246 | *ds << ""; |
247 | *ss << handle_pyerror(); | |
248 | r = -EINVAL; | |
249 | } | |
250 | ||
251 | return r; | |
252 | } | |
253 | ||
254 | void ActivePyModule::get_health_checks(health_check_map_t *checks) | |
255 | { | |
9f95a23c TL |
256 | if (is_dead()) { |
257 | dout(5) << "cancelling get_health_checks" << dendl; | |
258 | return; | |
259 | } | |
3efd9988 FG |
260 | checks->merge(health_checks); |
261 | } | |
262 | ||
92f5a8d4 TL |
263 | bool ActivePyModule::is_authorized( |
264 | const std::map<std::string, std::string>& arguments) const { | |
265 | if (m_session == nullptr) { | |
266 | return false; | |
267 | } | |
268 | ||
269 | // No need to pass command prefix here since that would have already been | |
270 | // tested before command invokation. Instead, only test for service/module | |
271 | // arguments as defined by the module itself. | |
272 | MonCommand mon_command {"", "", "", m_command_perms}; | |
273 | return m_session->caps.is_capable(nullptr, m_session->entity_name, "py", | |
274 | py_module->get_name(), "", arguments, | |
275 | mon_command.requires_perm('r'), | |
276 | mon_command.requires_perm('w'), | |
277 | mon_command.requires_perm('x'), | |
278 | m_session->get_peer_addr()); | |
279 | } |