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());
141 PyThreadState
*tstate
= PyEval_SaveThread();
143 if (std::string(type
) == "mon") {
144 self
->py_modules
->get_monc().start_mon_command(
150 } else if (std::string(type
) == "osd") {
152 uint64_t osd_id
= strict_strtoll(name
, 10, &err
);
155 string
msg("invalid osd_id: ");
156 msg
.append("\"").append(name
).append("\"");
157 PyEval_RestoreThread(tstate
);
158 PyErr_SetString(PyExc_ValueError
, msg
.c_str());
163 self
->py_modules
->get_objecter().osd_command(
171 } else if (std::string(type
) == "mds") {
172 int r
= self
->py_modules
->get_client().mds_command(
180 string
msg("failed to send command to mds: ");
181 msg
.append(cpp_strerror(r
));
182 PyEval_RestoreThread(tstate
);
183 PyErr_SetString(PyExc_RuntimeError
, msg
.c_str());
186 } else if (std::string(type
) == "pg") {
188 if (!pgid
.parse(name
)) {
190 string
msg("invalid pgid: ");
191 msg
.append("\"").append(name
).append("\"");
192 PyEval_RestoreThread(tstate
);
193 PyErr_SetString(PyExc_ValueError
, msg
.c_str());
198 self
->py_modules
->get_objecter().pg_command(
206 PyEval_RestoreThread(tstate
);
210 string
msg("unknown service type: ");
212 PyEval_RestoreThread(tstate
);
213 PyErr_SetString(PyExc_ValueError
, msg
.c_str());
217 PyEval_RestoreThread(tstate
);
222 ceph_set_health_checks(BaseMgrModule
*self
, PyObject
*args
)
224 PyObject
*checks
= NULL
;
225 if (!PyArg_ParseTuple(args
, "O:ceph_set_health_checks", &checks
)) {
228 if (!PyDict_Check(checks
)) {
229 derr
<< __func__
<< " arg not a dict" << dendl
;
232 PyObject
*checksls
= PyDict_Items(checks
);
233 health_check_map_t out_checks
;
234 for (int i
= 0; i
< PyList_Size(checksls
); ++i
) {
235 PyObject
*kv
= PyList_GET_ITEM(checksls
, i
);
236 char *check_name
= nullptr;
237 PyObject
*check_info
= nullptr;
238 if (!PyArg_ParseTuple(kv
, "sO:pair", &check_name
, &check_info
)) {
239 derr
<< __func__
<< " dict item " << i
240 << " not a size 2 tuple" << dendl
;
243 if (!PyDict_Check(check_info
)) {
244 derr
<< __func__
<< " item " << i
<< " " << check_name
245 << " value not a dict" << dendl
;
248 health_status_t severity
= HEALTH_OK
;
251 PyObject
*infols
= PyDict_Items(check_info
);
252 for (int j
= 0; j
< PyList_Size(infols
); ++j
) {
253 PyObject
*pair
= PyList_GET_ITEM(infols
, j
);
254 if (!PyTuple_Check(pair
)) {
255 derr
<< __func__
<< " item " << i
<< " pair " << j
256 << " not a tuple" << dendl
;
260 PyObject
*v
= nullptr;
261 if (!PyArg_ParseTuple(pair
, "sO:pair", &k
, &v
)) {
262 derr
<< __func__
<< " item " << i
<< " pair " << j
263 << " not a size 2 tuple" << dendl
;
267 if (ks
== "severity") {
268 if (!PyString_Check(v
)) {
269 derr
<< __func__
<< " check " << check_name
270 << " severity value not string" << dendl
;
273 string
vs(PyString_AsString(v
));
274 if (vs
== "warning") {
275 severity
= HEALTH_WARN
;
276 } else if (vs
== "error") {
277 severity
= HEALTH_ERR
;
279 } else if (ks
== "summary") {
280 if (!PyString_Check(v
)) {
281 derr
<< __func__
<< " check " << check_name
282 << " summary value not string" << dendl
;
285 summary
= PyString_AsString(v
);
286 } else if (ks
== "detail") {
287 if (!PyList_Check(v
)) {
288 derr
<< __func__
<< " check " << check_name
289 << " detail value not list" << dendl
;
292 for (int k
= 0; k
< PyList_Size(v
); ++k
) {
293 PyObject
*di
= PyList_GET_ITEM(v
, k
);
294 if (!PyString_Check(di
)) {
295 derr
<< __func__
<< " check " << check_name
296 << " detail item " << k
<< " not a string" << dendl
;
299 detail
.push_back(PyString_AsString(di
));
302 derr
<< __func__
<< " check " << check_name
303 << " unexpected key " << k
<< dendl
;
306 auto& d
= out_checks
.add(check_name
, severity
, summary
);
307 d
.detail
.swap(detail
);
310 JSONFormatter
jf(true);
311 dout(10) << "module " << self
->this_module
->get_name()
312 << " health checks:\n";
313 out_checks
.dump(&jf
);
317 PyThreadState
*tstate
= PyEval_SaveThread();
318 self
->py_modules
->set_health_checks(self
->this_module
->get_name(),
319 std::move(out_checks
));
320 PyEval_RestoreThread(tstate
);
327 ceph_state_get(BaseMgrModule
*self
, PyObject
*args
)
330 if (!PyArg_ParseTuple(args
, "s:ceph_state_get", &what
)) {
334 return self
->py_modules
->get_python(what
);
339 ceph_get_server(BaseMgrModule
*self
, PyObject
*args
)
341 char *hostname
= NULL
;
342 if (!PyArg_ParseTuple(args
, "z:ceph_get_server", &hostname
)) {
347 return self
->py_modules
->get_server_python(hostname
);
349 return self
->py_modules
->list_servers_python();
354 ceph_get_mgr_id(BaseMgrModule
*self
, PyObject
*args
)
356 return PyString_FromString(g_conf
->name
.get_id().c_str());
360 ceph_config_get(BaseMgrModule
*self
, PyObject
*args
)
362 char *what
= nullptr;
363 if (!PyArg_ParseTuple(args
, "s:ceph_config_get", &what
)) {
364 derr
<< "Invalid args!" << dendl
;
368 PyThreadState
*tstate
= PyEval_SaveThread();
370 bool found
= self
->py_modules
->get_config(self
->this_module
->get_name(),
373 PyEval_RestoreThread(tstate
);
376 dout(10) << "ceph_config_get " << what
<< " found: " << value
.c_str() << dendl
;
377 return PyString_FromString(value
.c_str());
379 dout(4) << "ceph_config_get " << what
<< " not found " << dendl
;
385 ceph_config_get_prefix(BaseMgrModule
*self
, PyObject
*args
)
387 char *prefix
= nullptr;
388 if (!PyArg_ParseTuple(args
, "s:ceph_config_get", &prefix
)) {
389 derr
<< "Invalid args!" << dendl
;
393 return self
->py_modules
->get_config_prefix(self
->this_module
->get_name(),
398 ceph_config_set(BaseMgrModule
*self
, PyObject
*args
)
401 char *value
= nullptr;
402 if (!PyArg_ParseTuple(args
, "sz:ceph_config_set", &key
, &value
)) {
405 boost::optional
<string
> val
;
409 self
->py_modules
->set_config(self
->this_module
->get_name(), key
, val
);
415 get_metadata(BaseMgrModule
*self
, PyObject
*args
)
417 char *svc_name
= NULL
;
419 if (!PyArg_ParseTuple(args
, "ss:get_metadata", &svc_name
, &svc_id
)) {
422 return self
->py_modules
->get_metadata_python(svc_name
, svc_id
);
426 get_daemon_status(BaseMgrModule
*self
, PyObject
*args
)
428 char *svc_name
= NULL
;
430 if (!PyArg_ParseTuple(args
, "ss:get_daemon_status", &svc_name
,
434 return self
->py_modules
->get_daemon_status_python(svc_name
, svc_id
);
438 ceph_log(BaseMgrModule
*self
, PyObject
*args
)
442 char *record
= nullptr;
443 if (!PyArg_ParseTuple(args
, "is:log", &level
, &record
)) {
447 assert(self
->this_module
);
449 self
->this_module
->log(level
, record
);
455 ceph_get_version(BaseMgrModule
*self
, PyObject
*args
)
457 return PyString_FromString(pretty_version_to_str().c_str());
461 ceph_get_context(BaseMgrModule
*self
, PyObject
*args
)
463 return self
->py_modules
->get_context();
467 get_counter(BaseMgrModule
*self
, PyObject
*args
)
469 char *svc_name
= nullptr;
470 char *svc_id
= nullptr;
471 char *counter_path
= nullptr;
472 if (!PyArg_ParseTuple(args
, "sss:get_counter", &svc_name
,
473 &svc_id
, &counter_path
)) {
476 return self
->py_modules
->get_counter_python(
477 svc_name
, svc_id
, counter_path
);
481 get_perf_schema(BaseMgrModule
*self
, PyObject
*args
)
483 char *type_str
= nullptr;
484 char *svc_id
= nullptr;
485 if (!PyArg_ParseTuple(args
, "ss:get_perf_schema", &type_str
,
490 return self
->py_modules
->get_perf_schema_python(type_str
, svc_id
);
494 ceph_get_osdmap(BaseMgrModule
*self
, PyObject
*args
)
496 return self
->py_modules
->get_osdmap();
500 ceph_set_uri(BaseMgrModule
*self
, PyObject
*args
)
502 char *svc_str
= nullptr;
503 if (!PyArg_ParseTuple(args
, "s:ceph_advertize_service",
508 // We call down into PyModules even though we have a MgrPyModule
509 // reference here, because MgrPyModule's fields are protected
510 // by PyModules' lock.
511 PyThreadState
*tstate
= PyEval_SaveThread();
512 self
->py_modules
->set_uri(self
->this_module
->get_name(), svc_str
);
513 PyEval_RestoreThread(tstate
);
519 ceph_have_mon_connection(BaseMgrModule
*self
, PyObject
*args
)
521 if (self
->py_modules
->get_monc().is_connected()) {
529 PyMethodDef BaseMgrModule_methods
[] = {
530 {"_ceph_get", (PyCFunction
)ceph_state_get
, METH_VARARGS
,
531 "Get a cluster object"},
533 {"_ceph_get_server", (PyCFunction
)ceph_get_server
, METH_VARARGS
,
534 "Get a server object"},
536 {"_ceph_get_metadata", (PyCFunction
)get_metadata
, METH_VARARGS
,
537 "Get a service's metadata"},
539 {"_ceph_get_daemon_status", (PyCFunction
)get_daemon_status
, METH_VARARGS
,
540 "Get a service's status"},
542 {"_ceph_send_command", (PyCFunction
)ceph_send_command
, METH_VARARGS
,
543 "Send a mon command"},
545 {"_ceph_set_health_checks", (PyCFunction
)ceph_set_health_checks
, METH_VARARGS
,
546 "Set health checks for this module"},
548 {"_ceph_get_mgr_id", (PyCFunction
)ceph_get_mgr_id
, METH_NOARGS
,
549 "Get the name of the Mgr daemon where we are running"},
551 {"_ceph_get_config", (PyCFunction
)ceph_config_get
, METH_VARARGS
,
552 "Get a configuration value"},
554 {"_ceph_get_config_prefix", (PyCFunction
)ceph_config_get_prefix
, METH_VARARGS
,
555 "Get all configuration values with a given prefix"},
557 {"_ceph_set_config", (PyCFunction
)ceph_config_set
, METH_VARARGS
,
558 "Set a configuration value"},
560 {"_ceph_get_counter", (PyCFunction
)get_counter
, METH_VARARGS
,
561 "Get a performance counter"},
563 {"_ceph_get_perf_schema", (PyCFunction
)get_perf_schema
, METH_VARARGS
,
564 "Get the performance counter schema"},
566 {"_ceph_log", (PyCFunction
)ceph_log
, METH_VARARGS
,
567 "Emit a (local) log message"},
569 {"_ceph_get_version", (PyCFunction
)ceph_get_version
, METH_VARARGS
,
570 "Get the ceph version of this process"},
572 {"_ceph_get_context", (PyCFunction
)ceph_get_context
, METH_NOARGS
,
573 "Get a CephContext* in a python capsule"},
575 {"_ceph_get_osdmap", (PyCFunction
)ceph_get_osdmap
, METH_NOARGS
,
576 "Get an OSDMap* in a python capsule"},
578 {"_ceph_set_uri", (PyCFunction
)ceph_set_uri
, METH_VARARGS
,
579 "Advertize a service URI served by this module"},
581 {"_ceph_have_mon_connection", (PyCFunction
)ceph_have_mon_connection
,
582 METH_NOARGS
, "Find out whether this mgr daemon currently has "
583 "a connection to a monitor"},
585 {NULL
, NULL
, 0, NULL
}
590 BaseMgrModule_new(PyTypeObject
*type
, PyObject
*args
, PyObject
*kwds
)
594 self
= (BaseMgrModule
*)type
->tp_alloc(type
, 0);
596 return (PyObject
*)self
;
600 BaseMgrModule_init(BaseMgrModule
*self
, PyObject
*args
, PyObject
*kwds
)
602 PyObject
*py_modules_capsule
= nullptr;
603 PyObject
*this_module_capsule
= nullptr;
604 static const char *kwlist
[] = {"py_modules", "this_module", NULL
};
606 if (! PyArg_ParseTupleAndKeywords(args
, kwds
, "OO",
607 const_cast<char**>(kwlist
),
609 &this_module_capsule
)) {
613 self
->py_modules
= (ActivePyModules
*)PyCapsule_GetPointer(
614 py_modules_capsule
, nullptr);
615 assert(self
->py_modules
);
616 self
->this_module
= (ActivePyModule
*)PyCapsule_GetPointer(
617 this_module_capsule
, nullptr);
618 assert(self
->this_module
);
623 PyTypeObject BaseMgrModuleType
= {
624 PyVarObject_HEAD_INIT(NULL
, 0)
625 "ceph_module.BaseMgrModule", /* tp_name */
626 sizeof(BaseMgrModule
), /* tp_basicsize */
634 0, /* tp_as_number */
635 0, /* tp_as_sequence */
636 0, /* tp_as_mapping */
642 0, /* tp_as_buffer */
643 Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_BASETYPE
, /* tp_flags */
644 "ceph-mgr Python Plugin", /* tp_doc */
647 0, /* tp_richcompare */
648 0, /* tp_weaklistoffset */
651 BaseMgrModule_methods
, /* tp_methods */
656 0, /* tp_descr_get */
657 0, /* tp_descr_set */
658 0, /* tp_dictoffset */
659 (initproc
)BaseMgrModule_init
, /* tp_init */
661 BaseMgrModule_new
, /* tp_new */