]> git.proxmox.com Git - ceph.git/blame - ceph/src/mgr/ActivePyModule.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / mgr / ActivePyModule.cc
CommitLineData
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
28using std::string;
29using namespace std::literals;
30
3efd9988
FG
31int 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
57void ActivePyModule::notify(const std::string &notify_type, const std::string &notify_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
85void 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 118bool 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
131PyObject *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
174void 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
192int 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
254void 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
263bool 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}