1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2016 John Spray <john.spray@redhat.com>
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.
15 * The interface we present to python code that runs within
16 * ceph-mgr. This is implemented as a Python class from which
17 * all modules must inherit -- access to the Ceph state is then
18 * available as methods on that object.
25 #include "mon/MonClient.h"
26 #include "common/errno.h"
27 #include "common/version.h"
29 #include "BaseMgrModule.h"
32 #define dout_context g_ceph_context
33 #define dout_subsys ceph_subsys_mgr
35 #define PLACEHOLDER ""
40 ActivePyModules *py_modules;
41 ActivePyModule *this_module;
44 class MonCommandCompletion : public Context
46 ActivePyModules *py_modules;
47 PyObject *python_completion;
48 const std::string tag;
49 SafeThreadState pThreadState;
56 ActivePyModules *py_modules_, PyObject* ev,
57 const std::string &tag_, PyThreadState *ts_)
58 : py_modules(py_modules_), python_completion(ev),
59 tag(tag_), pThreadState(ts_)
61 assert(python_completion != nullptr);
62 Py_INCREF(python_completion);
65 ~MonCommandCompletion() override
67 if (python_completion) {
68 // Usually do this in finish(): this path is only for if we're
69 // being destroyed without completing.
70 Gil gil(pThreadState, true);
71 Py_DECREF(python_completion);
72 python_completion = nullptr;
76 void finish(int r) override
78 assert(python_completion != nullptr);
80 dout(10) << "MonCommandCompletion::finish()" << dendl;
82 // Scoped so the Gil is released before calling notify_all()
83 // Create new thread state because this is called via the MonClient
84 // Finisher, not the PyModules finisher.
85 Gil gil(pThreadState, true);
87 auto set_fn = PyObject_GetAttrString(python_completion, "complete");
88 assert(set_fn != nullptr);
90 auto pyR = PyInt_FromLong(r);
91 auto pyOutBl = PyString_FromString(outbl.to_str().c_str());
92 auto pyOutS = PyString_FromString(outs.c_str());
93 auto args = PyTuple_Pack(3, pyR, pyOutBl, pyOutS);
98 auto rtn = PyObject_CallObject(set_fn, args);
105 Py_DECREF(python_completion);
106 python_completion = nullptr;
108 py_modules->notify_all("command", tag);
114 ceph_send_command(BaseMgrModule *self, PyObject *args)
116 // Like mon, osd, mds
117 char *type = nullptr;
119 // Like "23" for an OSD or "myid" for an MDS
120 char *name = nullptr;
122 char *cmd_json = nullptr;
124 PyObject *completion = nullptr;
125 if (!PyArg_ParseTuple(args, "Ossss:ceph_send_command",
126 &completion, &type, &name, &cmd_json, &tag)) {
130 auto set_fn = PyObject_GetAttrString(completion, "complete");
131 if (set_fn == nullptr) {
132 ceph_abort(); // TODO raise python exception instead
134 assert(PyCallable_Check(set_fn));
138 auto c = new MonCommandCompletion(self->py_modules,
139 completion, tag, PyThreadState_Get());
140 if (std::string(type) == "mon") {
141 self->py_modules->get_monc().start_mon_command(
147 } else if (std::string(type) == "osd") {
149 uint64_t osd_id = strict_strtoll(name, 10, &err);
152 string msg("invalid osd_id: ");
153 msg.append("\"").append(name).append("\"");
154 PyErr_SetString(PyExc_ValueError, msg.c_str());
159 self->py_modules->get_objecter().osd_command(
167 } else if (std::string(type) == "mds") {
168 int r = self->py_modules->get_client().mds_command(
176 string msg("failed to send command to mds: ");
177 msg.append(cpp_strerror(r));
178 PyErr_SetString(PyExc_RuntimeError, msg.c_str());
181 } else if (std::string(type) == "pg") {
183 if (!pgid.parse(name)) {
185 string msg("invalid pgid: ");
186 msg.append("\"").append(name).append("\"");
187 PyErr_SetString(PyExc_ValueError, msg.c_str());
192 self->py_modules->get_objecter().pg_command(
203 string msg("unknown service type: ");
205 PyErr_SetString(PyExc_ValueError, msg.c_str());
213 ceph_set_health_checks(BaseMgrModule *self, PyObject *args)
215 PyObject *checks = NULL;
216 if (!PyArg_ParseTuple(args, "O:ceph_set_health_checks", &checks)) {
219 if (!PyDict_Check(checks)) {
220 derr << __func__ << " arg not a dict" << dendl;
223 PyObject *checksls = PyDict_Items(checks);
224 health_check_map_t out_checks;
225 for (int i = 0; i < PyList_Size(checksls); ++i) {
226 PyObject *kv = PyList_GET_ITEM(checksls, i);
227 char *check_name = nullptr;
228 PyObject *check_info = nullptr;
229 if (!PyArg_ParseTuple(kv, "sO:pair", &check_name, &check_info)) {
230 derr << __func__ << " dict item " << i
231 << " not a size 2 tuple" << dendl;
234 if (!PyDict_Check(check_info)) {
235 derr << __func__ << " item " << i << " " << check_name
236 << " value not a dict" << dendl;
239 health_status_t severity = HEALTH_OK;
242 PyObject *infols = PyDict_Items(check_info);
243 for (int j = 0; j < PyList_Size(infols); ++j) {
244 PyObject *pair = PyList_GET_ITEM(infols, j);
245 if (!PyTuple_Check(pair)) {
246 derr << __func__ << " item " << i << " pair " << j
247 << " not a tuple" << dendl;
251 PyObject *v = nullptr;
252 if (!PyArg_ParseTuple(pair, "sO:pair", &k, &v)) {
253 derr << __func__ << " item " << i << " pair " << j
254 << " not a size 2 tuple" << dendl;
258 if (ks == "severity") {
259 if (!PyString_Check(v)) {
260 derr << __func__ << " check " << check_name
261 << " severity value not string" << dendl;
264 string vs(PyString_AsString(v));
265 if (vs == "warning") {
266 severity = HEALTH_WARN;
267 } else if (vs == "error") {
268 severity = HEALTH_ERR;
270 } else if (ks == "summary") {
271 if (!PyString_Check(v)) {
272 derr << __func__ << " check " << check_name
273 << " summary value not string" << dendl;
276 summary = PyString_AsString(v);
277 } else if (ks == "detail") {
278 if (!PyList_Check(v)) {
279 derr << __func__ << " check " << check_name
280 << " detail value not list" << dendl;
283 for (int k = 0; k < PyList_Size(v); ++k) {
284 PyObject *di = PyList_GET_ITEM(v, k);
285 if (!PyString_Check(di)) {
286 derr << __func__ << " check " << check_name
287 << " detail item " << k << " not a string" << dendl;
290 detail.push_back(PyString_AsString(di));
293 derr << __func__ << " check " << check_name
294 << " unexpected key " << k << dendl;
297 auto& d = out_checks.add(check_name, severity, summary);
298 d.detail.swap(detail);
301 JSONFormatter jf(true);
302 dout(10) << "module " << self->this_module->get_name()
303 << " health checks:\n";
304 out_checks.dump(&jf);
308 PyThreadState *tstate = PyEval_SaveThread();
309 self->py_modules->set_health_checks(self->this_module->get_name(),
310 std::move(out_checks));
311 PyEval_RestoreThread(tstate);
318 ceph_state_get(BaseMgrModule *self, PyObject *args)
321 if (!PyArg_ParseTuple(args, "s:ceph_state_get", &what)) {
325 return self->py_modules->get_python(what);
330 ceph_get_server(BaseMgrModule *self, PyObject *args)
332 char *hostname = NULL;
333 if (!PyArg_ParseTuple(args, "z:ceph_get_server", &hostname)) {
338 return self->py_modules->get_server_python(hostname);
340 return self->py_modules->list_servers_python();
345 ceph_get_mgr_id(BaseMgrModule *self, PyObject *args)
347 return PyString_FromString(g_conf->name.get_id().c_str());
351 ceph_config_get(BaseMgrModule *self, PyObject *args)
353 char *what = nullptr;
354 if (!PyArg_ParseTuple(args, "s:ceph_config_get", &what)) {
355 derr << "Invalid args!" << dendl;
360 bool found = self->py_modules->get_config(self->this_module->get_name(),
363 dout(10) << "ceph_config_get " << what << " found: " << value.c_str() << dendl;
364 return PyString_FromString(value.c_str());
366 dout(4) << "ceph_config_get " << what << " not found " << dendl;
372 ceph_config_get_prefix(BaseMgrModule *self, PyObject *args)
374 char *prefix = nullptr;
375 if (!PyArg_ParseTuple(args, "s:ceph_config_get", &prefix)) {
376 derr << "Invalid args!" << dendl;
380 return self->py_modules->get_config_prefix(self->this_module->get_name(),
385 ceph_config_set(BaseMgrModule *self, PyObject *args)
388 char *value = nullptr;
389 if (!PyArg_ParseTuple(args, "sz:ceph_config_set", &key, &value)) {
392 boost::optional<string> val;
396 self->py_modules->set_config(self->this_module->get_name(), key, val);
402 get_metadata(BaseMgrModule *self, PyObject *args)
404 char *svc_name = NULL;
406 if (!PyArg_ParseTuple(args, "ss:get_metadata", &svc_name, &svc_id)) {
409 return self->py_modules->get_metadata_python(svc_name, svc_id);
413 get_daemon_status(BaseMgrModule *self, PyObject *args)
415 char *svc_name = NULL;
417 if (!PyArg_ParseTuple(args, "ss:get_daemon_status", &svc_name,
421 return self->py_modules->get_daemon_status_python(svc_name, svc_id);
425 ceph_log(BaseMgrModule *self, PyObject *args)
429 char *record = nullptr;
430 if (!PyArg_ParseTuple(args, "is:log", &level, &record)) {
434 assert(self->this_module);
436 self->this_module->log(level, record);
442 ceph_get_version(BaseMgrModule *self, PyObject *args)
444 return PyString_FromString(pretty_version_to_str().c_str());
448 ceph_get_context(BaseMgrModule *self, PyObject *args)
450 return self->py_modules->get_context();
454 get_counter(BaseMgrModule *self, PyObject *args)
456 char *svc_name = nullptr;
457 char *svc_id = nullptr;
458 char *counter_path = nullptr;
459 if (!PyArg_ParseTuple(args, "sss:get_counter", &svc_name,
460 &svc_id, &counter_path)) {
463 return self->py_modules->get_counter_python(
464 svc_name, svc_id, counter_path);
468 get_perf_schema(BaseMgrModule *self, PyObject *args)
470 char *type_str = nullptr;
471 char *svc_id = nullptr;
472 if (!PyArg_ParseTuple(args, "ss:get_perf_schema", &type_str,
477 return self->py_modules->get_perf_schema_python(type_str, svc_id);
481 ceph_get_osdmap(BaseMgrModule *self, PyObject *args)
483 return self->py_modules->get_osdmap();
487 ceph_set_uri(BaseMgrModule *self, PyObject *args)
489 char *svc_str = nullptr;
490 if (!PyArg_ParseTuple(args, "s:ceph_advertize_service",
495 // We call down into PyModules even though we have a MgrPyModule
496 // reference here, because MgrPyModule's fields are protected
497 // by PyModules' lock.
498 PyThreadState *tstate = PyEval_SaveThread();
499 self->py_modules->set_uri(self->this_module->get_name(), svc_str);
500 PyEval_RestoreThread(tstate);
506 PyMethodDef BaseMgrModule_methods[] = {
507 {"_ceph_get", (PyCFunction)ceph_state_get, METH_VARARGS,
508 "Get a cluster object"},
510 {"_ceph_get_server", (PyCFunction)ceph_get_server, METH_VARARGS,
511 "Get a server object"},
513 {"_ceph_get_metadata", (PyCFunction)get_metadata, METH_VARARGS,
514 "Get a service's metadata"},
516 {"_ceph_get_daemon_status", (PyCFunction)get_daemon_status, METH_VARARGS,
517 "Get a service's status"},
519 {"_ceph_send_command", (PyCFunction)ceph_send_command, METH_VARARGS,
520 "Send a mon command"},
522 {"_ceph_set_health_checks", (PyCFunction)ceph_set_health_checks, METH_VARARGS,
523 "Set health checks for this module"},
525 {"_ceph_get_mgr_id", (PyCFunction)ceph_get_mgr_id, METH_NOARGS,
526 "Get the name of the Mgr daemon where we are running"},
528 {"_ceph_get_config", (PyCFunction)ceph_config_get, METH_VARARGS,
529 "Get a configuration value"},
531 {"_ceph_get_config_prefix", (PyCFunction)ceph_config_get_prefix, METH_VARARGS,
532 "Get all configuration values with a given prefix"},
534 {"_ceph_set_config", (PyCFunction)ceph_config_set, METH_VARARGS,
535 "Set a configuration value"},
537 {"_ceph_get_counter", (PyCFunction)get_counter, METH_VARARGS,
538 "Get a performance counter"},
540 {"_ceph_get_perf_schema", (PyCFunction)get_perf_schema, METH_VARARGS,
541 "Get the performance counter schema"},
543 {"_ceph_log", (PyCFunction)ceph_log, METH_VARARGS,
544 "Emit a (local) log message"},
546 {"_ceph_get_version", (PyCFunction)ceph_get_version, METH_VARARGS,
547 "Get the ceph version of this process"},
549 {"_ceph_get_context", (PyCFunction)ceph_get_context, METH_NOARGS,
550 "Get a CephContext* in a python capsule"},
552 {"_ceph_get_osdmap", (PyCFunction)ceph_get_osdmap, METH_NOARGS,
553 "Get an OSDMap* in a python capsule"},
555 {"_ceph_set_uri", (PyCFunction)ceph_set_uri, METH_VARARGS,
556 "Advertize a service URI served by this module"},
558 {NULL, NULL, 0, NULL}
563 BaseMgrModule_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
567 self = (BaseMgrModule *)type->tp_alloc(type, 0);
569 return (PyObject *)self;
573 BaseMgrModule_init(BaseMgrModule *self, PyObject *args, PyObject *kwds)
575 PyObject *py_modules_capsule = nullptr;
576 PyObject *this_module_capsule = nullptr;
577 static const char *kwlist[] = {"py_modules", "this_module", NULL};
579 if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO",
580 const_cast<char**>(kwlist),
582 &this_module_capsule)) {
586 self->py_modules = (ActivePyModules*)PyCapsule_GetPointer(
587 py_modules_capsule, nullptr);
588 assert(self->py_modules);
589 self->this_module = (ActivePyModule*)PyCapsule_GetPointer(
590 this_module_capsule, nullptr);
591 assert(self->this_module);
596 PyTypeObject BaseMgrModuleType = {
597 PyVarObject_HEAD_INIT(NULL, 0)
598 "ceph_module.BaseMgrModule", /* tp_name */
599 sizeof(BaseMgrModule), /* tp_basicsize */
607 0, /* tp_as_number */
608 0, /* tp_as_sequence */
609 0, /* tp_as_mapping */
615 0, /* tp_as_buffer */
616 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
617 "ceph-mgr Python Plugin", /* tp_doc */
620 0, /* tp_richcompare */
621 0, /* tp_weaklistoffset */
624 BaseMgrModule_methods, /* tp_methods */
629 0, /* tp_descr_get */
630 0, /* tp_descr_set */
631 0, /* tp_dictoffset */
632 (initproc)BaseMgrModule_init, /* tp_init */
634 BaseMgrModule_new, /* tp_new */