From 4998563a74ddb2bc44d97f541aa33a7d2194c3a1 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Fri, 15 Feb 2013 16:34:33 +0100 Subject: [PATCH] Implemented calling ScriptFunctions from Python. --- lib/base/scriptfunction.cpp | 42 +++++++- lib/base/scriptfunction.h | 26 +++++ lib/base/scriptlanguage.h | 3 + lib/icinga/Makefile.am | 2 + lib/icinga/api.cpp | 36 +++++++ lib/icinga/api.h | 42 ++++++++ lib/icinga/i2-icinga.h | 2 + lib/icinga/icinga.vcxproj | 2 + lib/python/pythoninterpreter.cpp | 4 +- lib/python/pythoninterpreter.h | 4 +- lib/python/pythonlanguage.cpp | 173 ++++++++++++++++++++++++++++++- lib/python/pythonlanguage.h | 10 ++ 12 files changed, 340 insertions(+), 6 deletions(-) create mode 100644 lib/icinga/api.cpp create mode 100644 lib/icinga/api.h diff --git a/lib/base/scriptfunction.cpp b/lib/base/scriptfunction.cpp index 3a8f5fafa..84a1f2a46 100644 --- a/lib/base/scriptfunction.cpp +++ b/lib/base/scriptfunction.cpp @@ -25,7 +25,7 @@ boost::signal ScriptFunction:: boost::signal ScriptFunction::OnUnregistered; ScriptFunction::ScriptFunction(const Callback& function) - : m_Callback(function) + : m_Callback(function), m_ArgumentCount(-1) { } void ScriptFunction::Register(const String& name, const ScriptFunction::Ptr& function) @@ -62,3 +62,43 @@ map& ScriptFunction::GetFunctions(void) static map functions; return functions; } + +void ScriptFunction::SetArgumentCount(int count) +{ + if (m_ArgumentCount >= 0) + m_ArgumentHints.resize(count); + + m_ArgumentCount = count; +} + +int ScriptFunction::GetArgumentCount(void) const +{ + return m_ArgumentCount; +} + +void ScriptFunction::SetArgumentHint(int index, const ScriptArgumentHint& hint) +{ + assert(index >= 0 && index < m_ArgumentCount); + + m_ArgumentHints[index] = hint; +} + +ScriptArgumentHint ScriptFunction::GetArgumentHint(int index) const +{ + if (m_ArgumentCount == -1 || index >= m_ArgumentCount) + return ScriptArgumentHint(); + + assert(index >= 0 && index < m_ArgumentHints.size()); + + return m_ArgumentHints[index]; +} + +void ScriptFunction::SetReturnHint(const ScriptArgumentHint& hint) +{ + m_ReturnHint = hint; +} + +ScriptArgumentHint ScriptFunction::GetReturnHint(void) const +{ + return m_ReturnHint; +} diff --git a/lib/base/scriptfunction.h b/lib/base/scriptfunction.h index 4597acde6..c10da40c8 100644 --- a/lib/base/scriptfunction.h +++ b/lib/base/scriptfunction.h @@ -25,6 +25,20 @@ namespace icinga class ScriptTask; +/** + * A type hint. + */ +struct ScriptArgumentHint +{ + bool RestrictType; + ValueType Type; + String Class; + + ScriptArgumentHint(void) + : RestrictType(false), Type(ValueEmpty), Class() + { } +}; + /** * A script function that can be used to execute a script task. * @@ -46,6 +60,15 @@ public: void Invoke(const shared_ptr& task, const vector& arguments); + void SetArgumentCount(int count); + int GetArgumentCount(void) const; + + void SetArgumentHint(int index, const ScriptArgumentHint& hint); + ScriptArgumentHint GetArgumentHint(int index) const; + + void SetReturnHint(const ScriptArgumentHint& hint); + ScriptArgumentHint GetReturnHint(void) const; + static map& GetFunctions(void); static boost::signal OnRegistered; @@ -53,6 +76,9 @@ public: private: Callback m_Callback; + int m_ArgumentCount; + vector m_ArgumentHints; + ScriptArgumentHint m_ReturnHint; }; /** diff --git a/lib/base/scriptlanguage.h b/lib/base/scriptlanguage.h index da6e30e53..4ab9ec912 100644 --- a/lib/base/scriptlanguage.h +++ b/lib/base/scriptlanguage.h @@ -40,6 +40,9 @@ public: virtual ScriptInterpreter::Ptr CreateInterpreter(const Script::Ptr& script) = 0; + void SubscribeFunction(const String& name); + void UnsubscribeFunction(const String& name); + protected: ScriptLanguage(void); diff --git a/lib/icinga/Makefile.am b/lib/icinga/Makefile.am index 2dff520ab..9e09a230c 100644 --- a/lib/icinga/Makefile.am +++ b/lib/icinga/Makefile.am @@ -5,6 +5,8 @@ pkglib_LTLIBRARIES = \ libicinga.la libicinga_la_SOURCES = \ + api.cpp \ + api.h \ checkresultmessage.cpp \ checkresultmessage.h \ cib.cpp \ diff --git a/lib/icinga/api.cpp b/lib/icinga/api.cpp new file mode 100644 index 000000000..7938094a9 --- /dev/null +++ b/lib/icinga/api.cpp @@ -0,0 +1,36 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "i2-icinga.h" + +using namespace icinga; + +REGISTER_SCRIPTFUNCTION("GetAnswerToEverything", &API::GetAnswerToEverything); + +void API::GetAnswerToEverything(const ScriptTask::Ptr& task, const vector& arguments) +{ + if (arguments.size() < 1) + BOOST_THROW_EXCEPTION(invalid_argument("Text argument required.")); + + String text = arguments[0]; + + Logger::Write(LogInformation, "icinga", "Hello from the Icinga 2 API: " + text); + + task->FinishResult(42); +} diff --git a/lib/icinga/api.h b/lib/icinga/api.h new file mode 100644 index 000000000..d37e79833 --- /dev/null +++ b/lib/icinga/api.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef API_H +#define API_H + +namespace icinga +{ + +/** + * A state change message for a service. + * + * @ingroup icinga + */ +class I2_ICINGA_API API +{ +public: + static void GetAnswerToEverything(const ScriptTask::Ptr& task, const vector& arguments); + +private: + API(void); +}; + +} + +#endif /* API_H */ diff --git a/lib/icinga/i2-icinga.h b/lib/icinga/i2-icinga.h index 5ca4070f6..d31ac7bd0 100644 --- a/lib/icinga/i2-icinga.h +++ b/lib/icinga/i2-icinga.h @@ -64,4 +64,6 @@ using boost::algorithm::is_any_of; #include "cib.h" +#include "api.h" + #endif /* I2ICINGA_H */ diff --git a/lib/icinga/icinga.vcxproj b/lib/icinga/icinga.vcxproj index 0f96cf6ca..a81ea70f7 100644 --- a/lib/icinga/icinga.vcxproj +++ b/lib/icinga/icinga.vcxproj @@ -19,6 +19,7 @@ + @@ -45,6 +46,7 @@ + diff --git a/lib/python/pythoninterpreter.cpp b/lib/python/pythoninterpreter.cpp index d4275c773..dde9cdbb3 100644 --- a/lib/python/pythoninterpreter.cpp +++ b/lib/python/pythoninterpreter.cpp @@ -51,7 +51,7 @@ PythonInterpreter::~PythonInterpreter(void) PyEval_ReleaseLock(); } -void PythonInterpreter::RegisterFunction(const String& name, PyObject *function) +void PythonInterpreter::RegisterPythonFunction(const String& name, PyObject *function) { SubscribeFunction(name); @@ -59,7 +59,7 @@ void PythonInterpreter::RegisterFunction(const String& name, PyObject *function) m_Functions[name] = function; } -void PythonInterpreter::UnregisterFunction(const String& name) +void PythonInterpreter::UnregisterPythonFunction(const String& name) { UnsubscribeFunction(name); diff --git a/lib/python/pythoninterpreter.h b/lib/python/pythoninterpreter.h index 07cc8322e..d683914e6 100644 --- a/lib/python/pythoninterpreter.h +++ b/lib/python/pythoninterpreter.h @@ -37,8 +37,8 @@ public: PythonInterpreter(const PythonLanguage::Ptr& language, const Script::Ptr& script); ~PythonInterpreter(void); - void RegisterFunction(const String& name, PyObject *function); - void UnregisterFunction(const String& name); + void RegisterPythonFunction(const String& name, PyObject *function); + void UnregisterPythonFunction(const String& name); protected: PythonLanguage::Ptr m_Language; diff --git a/lib/python/pythonlanguage.cpp b/lib/python/pythonlanguage.cpp index 4b54e7348..044e33d8c 100644 --- a/lib/python/pythonlanguage.cpp +++ b/lib/python/pythonlanguage.cpp @@ -37,9 +37,19 @@ PythonLanguage::PythonLanguage(void) m_MainThreadState = PyThreadState_Get(); - PyThreadState_Swap(NULL); + m_NativeModule = Py_InitModule("ire", NULL); + (void) PyThreadState_Swap(NULL); PyEval_ReleaseLock(); + + String name; + ScriptFunction::Ptr function; + BOOST_FOREACH(tie(name, function), ScriptFunction::GetFunctions()) { + RegisterNativeFunction(name, function); + } + + ScriptFunction::OnRegistered.connect(boost::bind(&PythonLanguage::RegisterNativeFunction, this, _1, _2)); + ScriptFunction::OnUnregistered.connect(boost::bind(&PythonLanguage::UnregisterNativeFunction, this, _1)); } PythonLanguage::~PythonLanguage(void) @@ -58,3 +68,164 @@ PyThreadState *PythonLanguage::GetMainThreadState(void) const { return m_MainThreadState; } + +PyObject *PythonLanguage::MarshalToPython(const Value& value, const ScriptArgumentHint& hint) +{ + String svalue; + + switch (value.GetType()) { + case ValueEmpty: + Py_INCREF(Py_None); + return Py_None; + + case ValueNumber: + return PyFloat_FromDouble(value); + + case ValueString: + svalue = value; + return PyString_FromString(svalue.CStr()); + + case ValueObject: + if (value.IsObjectType()) { + DynamicObject::Ptr dobj = value; + + String type = dobj->GetType()->GetName(); + String name = dobj->GetName(); + + PyObject *ptype = PyString_FromString(type.CStr()); + + if (ptype == NULL) + return NULL; + + PyObject *pname = PyString_FromString(name.CStr()); + + if (pname == NULL) { + Py_DECREF(ptype); + + return NULL; + } + + PyObject *result = PyTuple_New(2); + + if (result == NULL) { + Py_DECREF(ptype); + Py_DECREF(pname); + + return NULL; + } + + (void) PyTuple_SetItem(result, 0, ptype); + (void) PyTuple_SetItem(result, 1, pname); + + return result; + } + + Py_INCREF(Py_None); + return Py_None; + + default: + BOOST_THROW_EXCEPTION(invalid_argument("Unexpected variant type.")); + } +} + +Value PythonLanguage::MarshalFromPython(PyObject *value, const ScriptArgumentHint& hint) +{ + if (value == Py_None) { + return Empty; + } else if (PyTuple_Check(value)) { + // TODO: look up object + } else if (PyFloat_Check(value)) { + return PyFloat_AsDouble(value); + } else if (PyString_Check(value)) { + return PyString_AsString(value); + } else { + return Empty; + } +} + +PyObject *PythonLanguage::CallNativeFunction(PyObject *self, PyObject *args) +{ + assert(PyString_Check(self)); + + char *name = PyString_AsString(self); + + ScriptFunction::Ptr function = ScriptFunction::GetByName(name); + + vector arguments; + + if (args != NULL) { + if (PyTuple_Check(args)) { + for (Py_ssize_t i = 0; i < PyTuple_Size(args); i++) { + PyObject *arg = PyTuple_GetItem(args, i); + + arguments.push_back(MarshalFromPython(arg, function->GetArgumentHint(i))); + } + } else { + arguments.push_back(MarshalFromPython(args, function->GetArgumentHint(0))); + } + } + + ScriptTask::Ptr task = boost::make_shared(function, arguments); + task->Start(); + task->Wait(); + + try { + Value result = task->GetResult(); + + return MarshalToPython(result, function->GetReturnHint()); + } catch (const std::exception& ex) { + String message = diagnostic_information(ex); + PyErr_SetString(PyExc_RuntimeError, message.CStr()); + + return NULL; + } +} + +/** + * Registers a native function. + * + * @param name The name of the native function. + * @param function The function. + */ +void PythonLanguage::RegisterNativeFunction(const String& name, const ScriptFunction::Ptr& function) +{ + (void) PyThreadState_Swap(m_MainThreadState); + + PyObject *pname = PyString_FromString(name.CStr()); + + PyMethodDef *md = new PyMethodDef; + md->ml_name = strdup(name.CStr()); + md->ml_meth = &PythonLanguage::CallNativeFunction; + md->ml_flags = METH_VARARGS; + md->ml_doc = NULL; + + PyObject *pfunc = PyCFunction_NewEx(md, pname, m_NativeModule); + (void) PyModule_AddObject(m_NativeModule, name.CStr(), pfunc); + + (void) PyThreadState_Swap(NULL); +} + +/** + * Unregisters a native function. + * + * @param name The name of the native function. + */ +void PythonLanguage::UnregisterNativeFunction(const String& name) +{ + (void) PyThreadState_Swap(m_MainThreadState); + + PyObject *pdict = PyModule_GetDict(m_NativeModule); + PyObject *pname = PyString_FromString(name.CStr()); + PyCFunctionObject *pfunc = (PyCFunctionObject *)PyDict_GetItem(pdict, pname); + + if (pfunc && PyCFunction_Check(pfunc)) { + /* Eww. */ + free(const_cast(pfunc->m_ml->ml_name)); + delete pfunc->m_ml; + } + + (void) PyDict_DelItem(pdict, pname); + Py_DECREF(pname); + + (void) PyThreadState_Swap(NULL); +} diff --git a/lib/python/pythonlanguage.h b/lib/python/pythonlanguage.h index bf0a519eb..1fed98c44 100644 --- a/lib/python/pythonlanguage.h +++ b/lib/python/pythonlanguage.h @@ -40,8 +40,18 @@ public: virtual ScriptInterpreter::Ptr CreateInterpreter(const Script::Ptr& script); PyThreadState *GetMainThreadState(void) const; + private: PyThreadState *m_MainThreadState; + PyObject *m_NativeModule; + + void RegisterNativeFunction(const String& name, const ScriptFunction::Ptr& function); + void UnregisterNativeFunction(const String& name); + + static PyObject *CallNativeFunction(PyObject *self, PyObject *args); + + static PyObject *MarshalToPython(const Value& value, const ScriptArgumentHint& hint); + static Value MarshalFromPython(PyObject *value, const ScriptArgumentHint& hint); }; }