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