Implemented calling ScriptFunctions from Python.

This commit is contained in:
Gunnar Beutner 2013-02-15 16:34:33 +01:00
parent a022be9de2
commit 4998563a74
12 changed files with 340 additions and 6 deletions

View File

@ -25,7 +25,7 @@ boost::signal<void (const String&, const ScriptFunction::Ptr&)> ScriptFunction::
boost::signal<void (const String&)> 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<String, ScriptFunction::Ptr>& ScriptFunction::GetFunctions(void)
static map<String, ScriptFunction::Ptr> 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;
}

View File

@ -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<ScriptTask>& task, const vector<Value>& 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<String, ScriptFunction::Ptr>& GetFunctions(void);
static boost::signal<void (const String&, const ScriptFunction::Ptr&)> OnRegistered;
@ -53,6 +76,9 @@ public:
private:
Callback m_Callback;
int m_ArgumentCount;
vector<ScriptArgumentHint> m_ArgumentHints;
ScriptArgumentHint m_ReturnHint;
};
/**

View File

@ -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);

View File

@ -5,6 +5,8 @@ pkglib_LTLIBRARIES = \
libicinga.la
libicinga_la_SOURCES = \
api.cpp \
api.h \
checkresultmessage.cpp \
checkresultmessage.h \
cib.cpp \

36
lib/icinga/api.cpp Normal file
View File

@ -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<Value>& 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);
}

42
lib/icinga/api.h Normal file
View File

@ -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<Value>& arguments);
private:
API(void);
};
}
#endif /* API_H */

View File

@ -64,4 +64,6 @@ using boost::algorithm::is_any_of;
#include "cib.h"
#include "api.h"
#endif /* I2ICINGA_H */

View File

@ -19,6 +19,7 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="api.cpp" />
<ClCompile Include="checkresultmessage.cpp" />
<ClCompile Include="cib.cpp" />
<ClCompile Include="externalcommandprocessor.cpp" />
@ -45,6 +46,7 @@
<ClCompile Include="servicegroup.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="api.h" />
<ClInclude Include="checkresultmessage.h" />
<ClInclude Include="cib.h" />
<ClInclude Include="externalcommandprocessor.h" />

View File

@ -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);

View File

@ -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;

View File

@ -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>()) {
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<Value> 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<ScriptTask>(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<char *>(pfunc->m_ml->ml_name));
delete pfunc->m_ml;
}
(void) PyDict_DelItem(pdict, pname);
Py_DECREF(pname);
(void) PyThreadState_Swap(NULL);
}

View File

@ -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);
};
}