// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2016 John Spray * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. */ #include "BaseMgrModule.h" #include "PyFormatter.h" #include "common/debug.h" #include "ActivePyModule.h" //XXX courtesy of http://stackoverflow.com/questions/1418015/how-to-get-python-exception-text #include #include "include/assert.h" // boost clobbers this #define dout_context g_ceph_context #define dout_subsys ceph_subsys_mgr #undef dout_prefix #define dout_prefix *_dout << "mgr " << __func__ << " " // decode a Python exception into a string std::string handle_pyerror() { using namespace boost::python; using namespace boost; PyObject *exc, *val, *tb; object formatted_list, formatted; PyErr_Fetch(&exc, &val, &tb); handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb)); object traceback(import("traceback")); if (!tb) { object format_exception_only(traceback.attr("format_exception_only")); formatted_list = format_exception_only(hexc, hval); } else { object format_exception(traceback.attr("format_exception")); formatted_list = format_exception(hexc,hval, htb); } formatted = str("").join(formatted_list); return extract(formatted); } int ActivePyModule::load(ActivePyModules *py_modules) { assert(py_modules); Gil gil(pMyThreadState, true); // We tell the module how we name it, so that it can be consistent // with us in logging etc. auto pThisPtr = PyCapsule_New(this, nullptr, nullptr); auto pPyModules = PyCapsule_New(py_modules, nullptr, nullptr); auto pModuleName = PyString_FromString(module_name.c_str()); auto pArgs = PyTuple_Pack(3, pModuleName, pPyModules, pThisPtr); pClassInstance = PyObject_CallObject(pClass, pArgs); Py_DECREF(pClass); Py_DECREF(pModuleName); Py_DECREF(pArgs); if (pClassInstance == nullptr) { derr << "Failed to construct class in '" << module_name << "'" << dendl; derr << handle_pyerror() << dendl; return -EINVAL; } else { dout(1) << "Constructed class from module: " << module_name << dendl; } return load_commands(); } void ActivePyModule::notify(const std::string ¬ify_type, const std::string ¬ify_id) { assert(pClassInstance != nullptr); Gil gil(pMyThreadState, true); // Execute auto pValue = PyObject_CallMethod(pClassInstance, const_cast("notify"), const_cast("(ss)"), notify_type.c_str(), notify_id.c_str()); if (pValue != NULL) { Py_DECREF(pValue); } else { derr << module_name << ".notify:" << dendl; derr << handle_pyerror() << dendl; // FIXME: callers can't be expected to handle a python module // that has spontaneously broken, but Mgr() should provide // a hook to unload misbehaving modules when they have an // error somewhere like this } } void ActivePyModule::notify_clog(const LogEntry &log_entry) { assert(pClassInstance != nullptr); Gil gil(pMyThreadState, true); // Construct python-ized LogEntry PyFormatter f; log_entry.dump(&f); auto py_log_entry = f.get(); // Execute auto pValue = PyObject_CallMethod(pClassInstance, const_cast("notify"), const_cast("(sN)"), "clog", py_log_entry); if (pValue != NULL) { Py_DECREF(pValue); } else { derr << module_name << ".notify_clog:" << dendl; derr << handle_pyerror() << dendl; // FIXME: callers can't be expected to handle a python module // that has spontaneously broken, but Mgr() should provide // a hook to unload misbehaving modules when they have an // error somewhere like this } } int ActivePyModule::load_commands() { // Don't need a Gil here -- this is called from ActivePyModule::load(), // which already has one. PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS"); if (command_list == nullptr) { // Even modules that don't define command should still have the COMMANDS // from the MgrModule definition. Something is wrong! derr << "Module " << get_name() << " has missing COMMANDS member" << dendl; return -EINVAL; } if (!PyObject_TypeCheck(command_list, &PyList_Type)) { // Relatively easy mistake for human to make, e.g. defining COMMANDS // as a {} instead of a [] derr << "Module " << get_name() << " has COMMANDS member of wrong type (" "should be a list)" << dendl; return -EINVAL; } const size_t list_size = PyList_Size(command_list); for (size_t i = 0; i < list_size; ++i) { PyObject *command = PyList_GetItem(command_list, i); assert(command != nullptr); ModuleCommand item; PyObject *pCmd = PyDict_GetItemString(command, "cmd"); assert(pCmd != nullptr); item.cmdstring = PyString_AsString(pCmd); dout(20) << "loaded command " << item.cmdstring << dendl; PyObject *pDesc = PyDict_GetItemString(command, "desc"); assert(pDesc != nullptr); item.helpstring = PyString_AsString(pDesc); PyObject *pPerm = PyDict_GetItemString(command, "perm"); assert(pPerm != nullptr); item.perm = PyString_AsString(pPerm); item.handler = this; commands.push_back(item); } Py_DECREF(command_list); dout(10) << "loaded " << commands.size() << " commands" << dendl; return 0; } int ActivePyModule::handle_command( const cmdmap_t &cmdmap, std::stringstream *ds, std::stringstream *ss) { assert(ss != nullptr); assert(ds != nullptr); Gil gil(pMyThreadState, true); PyFormatter f; cmdmap_dump(cmdmap, &f); PyObject *py_cmd = f.get(); auto pResult = PyObject_CallMethod(pClassInstance, const_cast("handle_command"), const_cast("(O)"), py_cmd); Py_DECREF(py_cmd); int r = 0; if (pResult != NULL) { if (PyTuple_Size(pResult) != 3) { r = -EINVAL; } else { r = PyInt_AsLong(PyTuple_GetItem(pResult, 0)); *ds << PyString_AsString(PyTuple_GetItem(pResult, 1)); *ss << PyString_AsString(PyTuple_GetItem(pResult, 2)); } Py_DECREF(pResult); } else { *ds << ""; *ss << handle_pyerror(); r = -EINVAL; } return r; } void ActivePyModule::get_health_checks(health_check_map_t *checks) { checks->merge(health_checks); }