]> git.proxmox.com Git - ceph.git/blame - ceph/src/mgr/ActivePyModule.cc
import 15.2.0 Octopus source
[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
3efd9988
FG
28int 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
54void ActivePyModule::notify(const std::string &notify_type, const std::string &notify_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
82void 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 115bool 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
128PyObject *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
170void 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
188int 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
250void 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
259bool 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}