At present, this just loads Gofer plugins in the same way that goferd would
and allows their methods to be called over QMF. Additional features of
Gofer (basically all out-of-band data that is normally passed to the
plugin) have not yet been implemented, so the only data that is passed is
the class/method name and the arguments.
---
matahari.spec.in | 58 +++++++
src/CMakeLists.txt | 6 +
src/gofer/.gitignore | 2
src/gofer/CMakeLists.txt | 20 ++
src/gofer/gofer-qmf.cpp | 199 +++++++++++++++++++++++
src/gofer/mhgofer_test.py | 59 +++++++
src/gofer/schema.xml | 15 ++
src/include/matahari/gofer.h | 100 ++++++++++++
src/lib/CMakeLists.txt | 6 +
src/lib/gofer-python.c | 362 ++++++++++++++++++++++++++++++++++++++++++
src/lib/gofer.c | 153 ++++++++++++++++++
src/lib/gofer_private.h | 35 ++++
12 files changed, 1014 insertions(+), 1 deletions(-)
create mode 100644 src/gofer/.gitignore
create mode 100644 src/gofer/CMakeLists.txt
create mode 100644 src/gofer/gofer-qmf.cpp
create mode 100644 src/gofer/mhgofer_test.py
create mode 100644 src/gofer/schema.xml
create mode 100644 src/include/matahari/gofer.h
create mode 100644 src/lib/gofer-python.c
create mode 100644 src/lib/gofer.c
create mode 100644 src/lib/gofer_private.h
diff --git a/matahari.spec.in b/matahari.spec.in
index c40f9fb..2ccd894 100644
--- a/matahari.spec.in
+++ b/matahari.spec.in
@@ -41,6 +41,7 @@ Requires: pcre
Requires: %{name}-core = %{version}-%{release}
Requires: %{name}-consoles = %{version}-%{release}
Requires: %{name}-broker = %{version}-%{release}
+Requires: %{name}-gofer = %{version}-%{release}
Requires: %{name}-host = %{version}-%{release}
Requires: %{name}-network = %{version}-%{release}
Requires: %{name}-service = %{version}-%{release}
@@ -50,6 +51,7 @@ BuildRequires: cmake
BuildRequires: libuuid-devel
BuildRequires: gcc-c++
BuildRequires: pcre-devel
+BuildRequires: python-devel
BuildRequires: glib2-devel
BuildRequires: sigar-devel
BuildRequires: libcurl-devel
@@ -156,6 +158,20 @@ Requires: kstart
%description agent-lib
C++ library containing the base class for Matahari agents
+%package gofer
+License: GPLv2+
+Summary: QMF agent for Gofer plugins
+Group: Applications/System
+Requires: %{name}-lib = %{version}-%{release}
+Requires: %{name}-agent-lib = %{version}-%{release}
+Requires: gofer
+Requires(post): chkconfig
+Requires(preun):chkconfig
+Requires(preun):initscripts
+
+%description gofer
+QMF agent for dispatching RMI calls to Gofer plugins
+
%package host
License: GPLv2+
Summary: QMF agent for remote hosts
@@ -256,6 +272,7 @@ make DESTDIR=%{buildroot} -f Makefile install
%if %{systemd}
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-service
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-network
+rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-gofer
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-host
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-sysconfig
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-sysconfig-console
@@ -273,6 +290,29 @@ rm -f
$RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-vios-proxy-guest
/sbin/ldconfig
%if %{with qmf}
+#== Gofer
+
+%post gofer
+%if %{systemd}
+systemctl --system daemon-reload
+%else
+/sbin/chkconfig --add matahari-gofer
+%endif
+/sbin/service matahari-gofer condrestart
+
+%preun gofer
+if [ $1 = 0 ]; then
+ /sbin/service matahari-gofer stop >/dev/null 2>&1 || :
+%if !%{systemd}
+ chkconfig --del matahari-gofer
+%endif
+fi
+
+%postun gofer
+if [ "$1" -ge "1" ]; then
+ /sbin/service matahari-gofer condrestart >/dev/null 2>&1 || :
+fi
+
#== Host
%post host
@@ -458,6 +498,7 @@ test "x%{buildroot}" != "x" && rm -rf
%{buildroot}
%files lib
%defattr(644, root, root, 755)
%{_libdir}/libmcommon.so.*
+%{_libdir}/libmgofer.so.*
%{_libdir}/libmhost.so.*
%{_libdir}/libmnetwork.so.*
%{_libdir}/libmservice.so.*
@@ -496,6 +537,22 @@ test "x%{buildroot}" != "x" && rm -rf
%{buildroot}
%{_datadir}/polkit-1/actions/org.matahariproject.Network.policy
%endif
+%files gofer
+%defattr(644, root, root, 755)
+%doc AUTHORS COPYING
+
+%if %{systemd}
+%{_unitdir}/matahari-gofer.service
+%endif
+
+%if %{with qmf}
+%if !%{systemd}
+%attr(755, root, root) %{_initddir}/matahari-gofer
+%endif
+%attr(755, root, root) %{_sbindir}/matahari-qmf-goferd
+%{_mandir}/man8/matahari-qmf-goferd.8*
+%endif
+
%files host
%defattr(644, root, root, 755)
%doc AUTHORS COPYING
@@ -649,6 +706,7 @@ test "x%{buildroot}" != "x" && rm -rf
%{buildroot}
%{_includedir}/matahari/utilities.h
%{_includedir}/matahari/errors.h
%{_includedir}/matahari/dnssrv.h
+%{_includedir}/matahari/gofer.h
%{_includedir}/matahari/host.h
%{_includedir}/matahari/network.h
%{_includedir}/matahari/sysconfig.h
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 14a4bd5..9eea413 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -108,6 +108,10 @@ include_directories(${glib_INCLUDE_DIRS})
SET(CMAKE_REQUIRED_LIBRARIES ${glib_LIBRARIES})
check_function_exists (g_list_free_full HAVE_G_LIST_FREE_FULL)
+# Python
+pkg_check_modules(python REQUIRED python)
+include_directories(${python_INCLUDE_DIRS})
+
# cURL
if(NOT WIN32)
find_library(CURL curl)
@@ -182,6 +186,7 @@ endif(WITH-QMF)
### Subdirectories
add_subdirectory(broker)
add_subdirectory(lib)
+add_subdirectory(gofer)
add_subdirectory(host)
add_subdirectory(network)
add_subdirectory(service)
@@ -196,6 +201,7 @@ install(FILES include/matahari/logging.h DESTINATION
include/matahari)
install(FILES include/matahari/utilities.h DESTINATION include/matahari)
install(FILES include/matahari/errors.h DESTINATION include/matahari)
install(FILES include/matahari/dnssrv.h DESTINATION include/matahari)
+install(FILES include/matahari/gofer.h DESTINATION include/matahari)
install(FILES include/matahari/host.h DESTINATION include/matahari)
install(FILES include/matahari/network.h DESTINATION include/matahari)
install(FILES include/matahari/sysconfig.h DESTINATION include/matahari)
diff --git a/src/gofer/.gitignore b/src/gofer/.gitignore
new file mode 100644
index 0000000..52e4e61
--- /dev/null
+++ b/src/gofer/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+*.pyo
diff --git a/src/gofer/CMakeLists.txt b/src/gofer/CMakeLists.txt
new file mode 100644
index 0000000..adf953c
--- /dev/null
+++ b/src/gofer/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(BASE "gofer")
+set(BASE_LIB "m${BASE}")
+set(QMF_AGENT "matahari-qmf-${BASE}d")
+
+# QMF daemon
+if(WITH-QMF)
+ set(SCHEMA_SOURCES
${CMAKE_CURRENT_BINARY_DIR}/qmf/org/matahariproject/gofer/QmfPackage.cpp)
+ generate_qmf_schemas(${CMAKE_CURRENT_SOURCE_DIR}/schema.xml ${SCHEMA_SOURCES})
+ include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+ add_executable(${QMF_AGENT} ${BASE}-qmf.cpp ${SCHEMA_SOURCES})
+ target_link_libraries(${QMF_AGENT} ${BASE_LIB} mcommon_qmf)
+
+ create_manpage(${QMF_AGENT} ${AGENT_MAN_SECTION} ${AGENT_MAN_DESC})
+ create_service_scripts(${BASE})
+
+ install(TARGETS ${QMF_AGENT} DESTINATION sbin)
+
+endif(WITH-QMF)
+
diff --git a/src/gofer/gofer-qmf.cpp b/src/gofer/gofer-qmf.cpp
new file mode 100644
index 0000000..82b1489
--- /dev/null
+++ b/src/gofer/gofer-qmf.cpp
@@ -0,0 +1,199 @@
+/* matahari-gofer.cpp - Copyright (C) 2009 Red Hat, Inc.
+ * Written by Zane Bitter <zbitter(a)redhat.com>
+ *
+ * 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.1 of the License, or (at your option) any later version.
+ *
+ * This software 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 library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/**
+ * \file
+ * \brief Gofer QMF Agent
+ */
+
+#include "config.h"
+
+#include <set>
+#include "matahari/agent.h"
+#include "matahari/object.h"
+#include <qmf/Data.h>
+#include "qmf/org/matahariproject/gofer/QmfPackage.h"
+
+extern "C" {
+#include "matahari/gofer.h"
+#include "matahari/logging.h"
+#include "matahari/errors.h"
+}
+
+
+class GoferAgent;
+
+class GoferPlugin: public MatahariObject
+{
+public:
+ GoferPlugin(GoferAgent& agent, mh_gofer_plugin_t& plugin);
+ ~GoferPlugin();
+
+ bool invoke(qmf::AgentSession& session, qmf::AgentEvent& event);
+
+private:
+ GoferAgent& _agent;
+ std::string _name;
+ mh_gofer_plugin_t _plugin;
+};
+
+
+class GoferAgent : public MatahariAgent
+{
+public:
+ virtual ~GoferAgent();
+
+ virtual int setup(qmf::AgentSession session);
+ virtual gboolean invoke(qmf::AgentSession session, qmf::AgentEvent event,
+ gpointer user_data);
+
+private:
+ friend class GoferPlugin;
+ qmf::org::matahariproject::gofer::PackageDefinition _package;
+ ObjectManager *_objectManager;
+
+ typedef std::map<std::string, GoferPlugin*> PluginMap;
+ PluginMap _plugins;
+
+ void addPlugin(mh_gofer_plugin_t& plugin);
+};
+
+
+int
+main(int argc, char **argv)
+{
+ mh_gofer_init(1, &argv[0]);
+
+ GoferAgent *agent = new GoferAgent();
+ int rc = agent->init(argc, argv, "gofer");
+ if (rc == 0) {
+ agent->run();
+ }
+
+ mh_gofer_deinit();
+ return rc;
+}
+
+gboolean
+GoferAgent::invoke(qmf::AgentSession session, qmf::AgentEvent event,
+ gpointer user_data)
+{
+ if (!_objectManager->invoke(session, event)) {
+ session.raiseException(event, mh_result_to_str(MH_RES_NOT_IMPLEMENTED));
+ }
+
+ return TRUE;
+}
+
+void GoferAgent::addPlugin(mh_gofer_plugin_t& plugin)
+{
+ _plugins[std::string(plugin.name)] = new GoferPlugin(*this, plugin);
+}
+
+int
+GoferAgent::setup(qmf::AgentSession session)
+{
+ int num_plugins = 0;
+ mh_gofer_plugin_t *plugins = NULL;
+
+ _package.configure(session);
+ _objectManager = new ObjectManager(session);
+
+ if (mh_gofer_load_plugins(&num_plugins, &plugins)) {
+ return 1;
+ }
+
+ for (int i = 0; i < num_plugins; i++) {
+ addPlugin(plugins[i]);
+ }
+ return 0;
+}
+
+GoferAgent::~GoferAgent()
+{
+ delete _objectManager;
+}
+
+
+GoferPlugin::GoferPlugin(GoferAgent& agent, mh_gofer_plugin_t& plugin):
+ MatahariObject(agent._package.data_Plugin),
+ _agent(agent),
+ _name(plugin.name),
+ _plugin(plugin)
+{
+ mh_info("Creating Plugin %s", _name.c_str());
+
+ _instance.setProperty("name", _name);
+ *_agent._objectManager += this;
+}
+
+GoferPlugin::~GoferPlugin()
+{
+ *_agent._objectManager -= this;
+}
+
+bool
+GoferPlugin::invoke(qmf::AgentSession& session, qmf::AgentEvent& event)
+{
+ if (event.getType() != qmf::AGENT_METHOD) {
+ return true;
+ }
+
+ const std::string& methodName(event.getMethodName());
+ qpid::types::Variant::Map& args = event.getArguments();
+
+ if (methodName == "invoke") {
+ mh_gofer_rmi_t call;
+ mh_gofer_rmi_create(&call,
+ args["classname"].asString().c_str(),
+ args["method"].asString().c_str());
+
+ qpid::types::Variant::List arglist(args["args"].asList());
+ for (qpid::types::Variant::List::iterator iter = arglist.begin();
+ iter != arglist.end();
+ iter++) {
+ mh_gofer_rmi_add_arg(call, iter->asString().c_str());
+ }
+ qpid::types::Variant::Map argmap(args["kwargs"].asMap());
+ for (qpid::types::Variant::Map::iterator iter = argmap.begin();
+ iter != argmap.end();
+ iter++) {
+ mh_gofer_rmi_add_kw_arg(call,
+ iter->first.c_str(),
+ iter->second.asString().c_str());
+ }
+ char *output;
+ enum mh_result result = mh_gofer_invoke(&_plugin, call, &output);
+
+ mh_gofer_rmi_destroy(call);
+
+ if (result != MH_RES_SUCCESS) {
+ session.raiseException(event, mh_result_to_str(result));
+ return true;
+ }
+
+ event.addReturnArgument("result", output);
+ free(output);
+ } else {
+ return false;
+ }
+
+ session.methodSuccess(event);
+ return true;
+}
+
diff --git a/src/gofer/mhgofer_test.py b/src/gofer/mhgofer_test.py
new file mode 100644
index 0000000..ae39ded
--- /dev/null
+++ b/src/gofer/mhgofer_test.py
@@ -0,0 +1,59 @@
+import unittest
+import json
+import qmf.console
+
+class GoferTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls._session = qmf.console.Session()
+ cls._broker = cls._session.addBroker('amqp://localhost:49000')
+
+ @classmethod
+ def tearDownClass(cls):
+ cls._session.delBroker(cls._broker)
+ cls._session.close()
+
+ def get_plugin(self, name):
+ p = self._session.getObjects(_class='Plugin',
+ _package='org.matahariproject.gofer',
+ name=name)
+ self.assertTrue(p)
+ return p[0]
+
+ def test_hello(self):
+ builtin = self.get_plugin('builtin')
+ self.assertIsNotNone(builtin)
+ result = builtin.invoke('Admin', 'hello', [], {})
+ self.assertEqual(result.status, 0)
+ self.assertIn('result', result.outArgs)
+ resJSON = result.outArgs['result']
+ resDict = json.loads(resJSON)
+ self.assertIn('retval', resDict)
+ retval = resDict['retval']
+ self.assertTrue(retval.startswith('Hello'))
+
+ def test_help(self):
+ builtin = self.get_plugin('builtin')
+ self.assertIsNotNone(builtin)
+ result = builtin.invoke('Admin', 'help', [], {})
+ self.assertEqual(result.status, 0)
+ self.assertIn('result', result.outArgs)
+ resJSON = result.outArgs['result']
+ resDict = json.loads(resJSON)
+ self.assertIn('retval', resDict)
+ retval = resDict['retval']
+ self.assertTrue(retval.startswith('Plugins:'))
+
+ def test_args_invalid(self):
+ builtin = self.get_plugin('builtin')
+ self.assertIsNotNone(builtin)
+ result = builtin.invoke('Admin', 'hello', ['1',
'2'], {'3': '4'})
+ self.assertEqual(result.status, 0)
+ self.assertIn('result', result.outArgs)
+ resJSON = result.outArgs['result']
+ resDict = json.loads(resJSON)
+ self.assertNotIn('retval', resDict)
+ self.assertIn('exval', resDict)
+ self.assertIn('xclass', resDict)
+ self.assertEqual(resDict['xclass'], 'TypeError')
diff --git a/src/gofer/schema.xml b/src/gofer/schema.xml
new file mode 100644
index 0000000..54fbe3c
--- /dev/null
+++ b/src/gofer/schema.xml
@@ -0,0 +1,15 @@
+<schema package="org.matahariproject.gofer">
+
+ <class name="Plugin">
+ <property name="name" type="sstr" access="RO"
/>
+
+ <method name="invoke" desc="Make a remote procedure
call">
+ <arg name="classname" dir="I" type="sstr"
/>
+ <arg name="method" dir="I" type="sstr"
/>
+ <arg name="args" dir="I" type="list"
/>
+ <arg name="kwargs" dir="I" type="map" />
+ <arg name="result" dir="O" type="lstr"
/>
+ </method>
+ </class>
+
+</schema>
diff --git a/src/include/matahari/gofer.h b/src/include/matahari/gofer.h
new file mode 100644
index 0000000..e6258b1
--- /dev/null
+++ b/src/include/matahari/gofer.h
@@ -0,0 +1,100 @@
+
+#ifndef __MH_GOFER_H__
+#define __MH_GOFER_H__
+
+#include "matahari/errors.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mh_gofer_plugin_impl;
+
+typedef struct {
+ char *name;
+ unsigned int type;
+ struct mh_gofer_plugin_impl *impl;
+} mh_gofer_plugin_t;
+
+struct mh_gofer_rmi;
+typedef struct mh_gofer_rmi* mh_gofer_rmi_t;
+
+
+/**
+ * Initialise the Gofer library with the given CLI options to goferd.
+ */
+void
+mh_gofer_init(int argc, char **argv);
+
+/**
+ * De-initialise the Gofer library and clean up.
+ */
+void
+mh_gofer_deinit(void);
+
+/**
+ * Load the available Gofer plugins.
+ * \param count pointer in which to store the plugin count. Should be
+ * initialised to 0.
+ * \param plugins pointer in which to store the list of plugins. Should be
+ * initialised to NULL.
+ * \return zero on success, non-zero otherwise.
+ */
+int
+mh_gofer_load_plugins(int *count, mh_gofer_plugin_t **plugins);
+
+/**
+ * Create a Remote Method Invocation.
+ * This should be freed with mh_gofer_rmi_destroy() when done.
+ * \param pcall pointer in which to store the RMI data.
+ * \param classname the name of the class containing the method to call.
+ * \param method the name of the method to invoke.
+ * \return zero on success, non-zero otherwise.
+ */
+int
+mh_gofer_rmi_create(mh_gofer_rmi_t *pcall,
+ const char *classname,
+ const char *method);
+
+/**
+ * Clean up a Remote Method Invocation.
+ * \param call the data to clean up.
+ */
+int
+mh_gofer_rmi_destroy(mh_gofer_rmi_t call);
+
+/**
+ * Add a positional argument to the argument list for the RMI.
+ * \param call the RMI data.
+ * \param arg the argument to add.
+ */
+int
+mh_gofer_rmi_add_arg(mh_gofer_rmi_t call, const char *arg);
+
+/**
+ * Add a keyword argument to the keyword argument map for the RMI.
+ * \param call the RMI data.
+ * \param key the argument keyword.
+ * \param arg the argument to add.
+ */
+int
+mh_gofer_rmi_add_kw_arg(mh_gofer_rmi_t call, const char *key, const char *arg);
+
+/**
+ * Invoke a remote method call.
+ * \param plugin the plugin to invoke the method from.
+ * \param call the RMI data.
+ * \param output pointer in which to store the result string (in JSON format).
+ * This needs to be freed with free().
+ * \return the return status code.
+ */
+enum mh_result
+mh_gofer_invoke(const mh_gofer_plugin_t *plugin,
+ const struct mh_gofer_rmi *call,
+ char **output);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 67cad82..9fd38ba 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -1,6 +1,6 @@
# Library
-set(LIBS mcommon mhost mnetwork mservice msysconfig)
+set(LIBS mcommon mgofer mhost mnetwork mservice msysconfig)
include_directories(${pcre_INCLUDE_DIRS})
# See
http://sourceware.org/autobook/autobook/autobook_91.html for
@@ -14,6 +14,10 @@ if(HAVE_RESOLV_H)
target_link_libraries(mcommon resolv)
endif(HAVE_RESOLV_H)
+add_library (mgofer SHARED gofer.c gofer-python.c)
+set_target_properties(mgofer PROPERTIES SOVERSION 1.0.0)
+target_link_libraries(mgofer mcommon ${python_LIBRARIES})
+
add_library (mhost SHARED host.c host_${VARIANT}.c)
set_target_properties(mhost PROPERTIES SOVERSION 1.0.0)
target_link_libraries(mhost ${uuid_LIBRARIES} ${pcre_LIBRARIES} mcommon ${SIGAR}
${glib_LIBRARIES})
diff --git a/src/lib/gofer-python.c b/src/lib/gofer-python.c
new file mode 100644
index 0000000..ce81723
--- /dev/null
+++ b/src/lib/gofer-python.c
@@ -0,0 +1,362 @@
+
+#include <Python.h>
+#include <stdio.h>
+
+#include "matahari/gofer.h"
+#include "gofer_private.h"
+#include "matahari/logging.h"
+#include "matahari/errors.h"
+
+
+#define PLUGIN_MODULE "gofer.agent.plugin"
+#define MESSAGING_MODULE "gofer.messaging"
+#define DISPATCHER_MODULE "gofer.rmi.dispatcher"
+
+
+struct mh_gofer_plugin_impl {
+ PyObject *plugin;
+};
+
+
+static void
+init(int argc, char **argv)
+{
+ if (!Py_IsInitialized()) {
+ Py_Initialize();
+ }
+ PySys_SetArgvEx(argc, argv, 0);
+}
+
+static void
+deinit(void)
+{
+ if (Py_IsInitialized()) {
+ Py_Finalize();
+ }
+}
+
+/* Equivalent to the Python code:
+ * def get_py_class_instance(package, classname, args):
+ * module = builtins.__import__(package)
+ * classobject = getattr(module, classname)
+ * instance = classobject(*args)
+ * return instance
+ */
+static PyObject *
+get_py_class_instance(const char *package, const char *classname,
+ PyObject *args)
+{
+ PyObject *module, *classobject, *instance;
+
+ module = PyImport_ImportModule(package);
+ if (!module) {
+ mh_err("Error importing module %s", package);
+ PyErr_Print();
+ return NULL;
+ }
+
+ classobject = PyObject_GetAttrString(module, classname);
+ if (!classobject) {
+ mh_err("Error importing %s class", classname);
+ PyErr_Print();
+ Py_DECREF(module);
+ return NULL;
+ }
+
+ instance = PyObject_CallObject(classobject, args);
+ if (!instance) {
+ mh_err("Error instantiating %s object", classname);
+ PyErr_Print();
+ }
+
+ Py_DECREF(classobject);
+ Py_DECREF(module);
+ return instance;
+}
+
+/* Equivalent to the Python code:
+ * def load_plugin_list():
+ * loader = gofer.agent.plugin.PluginLoader()
+ * plugins = loader.load()
+ * return plugins
+ */
+static PyObject *
+load_plugin_list(void)
+{
+ PyObject *loader, *plugins;
+
+ loader = get_py_class_instance(PLUGIN_MODULE, "PluginLoader", NULL);
+ if (loader == NULL) {
+ mh_err("Failed to create plugin loader");
+ PyErr_Print();
+ return NULL;
+ }
+
+ plugins = PyObject_CallMethod(loader, "load", NULL);
+ if (plugins == NULL) {
+ mh_err("Failed to import Gofer Python module");
+ PyErr_Print();
+ }
+
+ Py_DECREF(loader);
+ return plugins;
+}
+
+static int
+load_plugins(int *count, mh_gofer_plugin_t **plugins)
+{
+ PyObject *pluginList;
+ Py_ssize_t length, i;
+
+ if (!Py_IsInitialized()) {
+ mh_err("Python not initialised");
+ return 1;
+ }
+
+ pluginList = load_plugin_list();
+ if (!pluginList) {
+ return 1;
+ }
+
+ if (!PySequence_Check(pluginList)) {
+ mh_err("Plugin list is not a sequence");
+ return 1;
+ }
+
+ length = PySequence_Length(pluginList);
+ if (length < 0) {
+ mh_err("Error getting length of plugin list");
+ return 1;
+ }
+
+ for (i = 0; i < length; i++) {
+ mh_gofer_plugin_t *tmp_list;
+ mh_gofer_plugin_t new_plugin;
+ struct mh_gofer_plugin_impl *new_impl;
+
+ PyObject *plugin = PySequence_GetItem(pluginList, i);
+ if (!plugin) {
+ mh_err("Error retrieving plugin module");
+ PyErr_Print();
+ continue;
+ }
+
+ // plugin is an instance of the gofer.agent.plugin.Plugin class
+ {
+ char *name_str;
+ PyObject *name = PyObject_GetAttrString(plugin, "name");
+
+ if (!name) {
+ mh_err("Failed to get plugin name");
+ PyErr_Print();
+ Py_DECREF(plugin);
+ continue;
+ }
+
+ name_str = PyString_AsString(name);
+ if (!name_str) {
+ mh_err("Module name is not a string");
+ PyErr_Print();
+ Py_DECREF(name);
+ Py_DECREF(plugin);
+ continue;
+ }
+ new_plugin.name = strdup(name_str);
+ Py_DECREF(name);
+ }
+
+ tmp_list = realloc(*plugins, sizeof(mh_gofer_plugin_t) * (*count + 1));
+ if (!tmp_list) {
+ mh_err("Out of Memory");
+ return 1;
+ }
+ *plugins = tmp_list;
+
+ new_impl = malloc(sizeof(*new_impl));
+ if (!new_impl) {
+ mh_err("Out of Memory");
+ return 1;
+ }
+ memset(new_impl, 0, sizeof(*new_impl));
+ new_impl->plugin = plugin;
+
+ new_plugin.type = MH_GOFER_PYTHON;
+ new_plugin.impl = new_impl;
+
+ memcpy(&tmp_list[(*count)++], &new_plugin, sizeof(new_plugin));
+ }
+
+ Py_DECREF(pluginList);
+
+ return 0;
+}
+
+/* Equivalent to the Python code:
+ * def get_classes(plugin):
+ * dispatcher = plugin.dispatcher
+ * return dispatcher.classes()
+ */
+static PyObject *
+get_classes(PyObject *plugin)
+{
+ PyObject *dispatcher, *classes;
+
+ dispatcher = PyObject_GetAttrString(plugin, "dispatcher");
+ if (!dispatcher) {
+ mh_err("Error getting plugin dispatcher");
+ PyErr_Print();
+ return NULL;
+ }
+
+ classes = PyObject_GetAttrString(dispatcher, "classes");
+ if (!classes) {
+ mh_err("Error getting plugin class list");
+ PyErr_Print();
+ }
+
+ Py_DECREF(dispatcher);
+
+ return classes;
+}
+
+/* Equivalent to the Python code:
+ * def get_rmi(plugin, envelope):
+ * classes = get_classes(plugin)
+ * return gofer.rmi.dispatcher.RMI(envelope, classes)
+ */
+static PyObject *
+get_rmi(PyObject *plugin, PyObject *envelope)
+{
+ PyObject *classes, *args, *rmi;
+
+ classes = get_classes(plugin);
+ if (!classes) {
+ Py_DECREF(envelope);
+ return NULL;
+ }
+
+ args = PyTuple_Pack(2, envelope, classes);
+ if (!args) {
+ mh_err("Error creating argument list");
+ PyErr_Print();
+ Py_DECREF(envelope);
+ Py_DECREF(classes);
+ return NULL;
+ }
+
+ rmi = get_py_class_instance(DISPATCHER_MODULE, "RMI", args);
+ if (!rmi) {
+ mh_err("Error creating RMI object");
+ PyErr_Print();
+ }
+
+ Py_DECREF(envelope);
+ Py_DECREF(classes);
+ Py_DECREF(args);
+
+ return rmi;
+}
+
+static PyObject*
+get_method_call_envelope(const struct mh_gofer_rmi *call)
+{
+ PyObject *envelope, *ret;
+
+ char *json = NULL;
+ asprintf(&json, "{\n"
+ "\"classname\": \"%s\",\n"
+ "\"method\": \"%s\",\n"
+ "\"args\": [%s],\n"
+ "\"kws\": {%s}\n"
+ "}",
+ call->classname, call->method,
+ call->args.str ?: "",
+ call->kwargs.str ?: "");
+
+ if (!json) {
+ return NULL;
+ }
+
+ envelope = get_py_class_instance(MESSAGING_MODULE, "Envelope", NULL);
+ if (!envelope) {
+ return NULL;
+ }
+
+ ret = PyObject_CallMethod(envelope, "load", "(s)", json);
+ free(json);
+ if (!ret) {
+ mh_err("Error loading JSON");
+ PyErr_Print();
+ Py_DECREF(envelope);
+ return NULL;
+ }
+ Py_DECREF(ret);
+
+ return envelope;
+}
+
+static enum mh_result
+invoke(const struct mh_gofer_plugin_impl *plugin,
+ const struct mh_gofer_rmi *call,
+ char **output)
+{
+ PyObject *envelope, *rmi, *result;
+
+ mh_debug("Got method invocation \"%s\"", call->method);
+
+ *output = NULL;
+
+ envelope = get_method_call_envelope(call);
+ if (!envelope) {
+ return MH_RES_INVALID_ARGS;
+ }
+
+ rmi = get_rmi(plugin->plugin, envelope);
+ if (!rmi) {
+ Py_DECREF(envelope);
+ return MH_RES_INVALID_ARGS;
+ }
+
+ // result = get_rmi(plugin, get_method_call_envelope(call))()
+ result = PyObject_CallObject(rmi, NULL);
+ if (!result) {
+ mh_err("Error invoking remote method");
+ PyErr_Print();
+ Py_DECREF(rmi);
+ Py_DECREF(envelope);
+ return MH_RES_BACKEND_ERROR;
+ }
+
+ mh_debug("Remote method invocation successful");
+
+ Py_DECREF(rmi);
+ Py_DECREF(envelope);
+
+ // jsonResult = str(result)
+ {
+ char *result_str;
+ PyObject *jsonResult = PyObject_Str(result);
+ Py_DECREF(result);
+
+ if (!jsonResult) {
+ return MH_RES_BACKEND_ERROR;
+ }
+ result_str = PyString_AsString(jsonResult);
+ if (!result_str) {
+ Py_DECREF(jsonResult);
+ return MH_RES_BACKEND_ERROR;
+ }
+ *output = strdup(result_str);
+ Py_DECREF(jsonResult);
+ }
+
+ return MH_RES_SUCCESS;
+}
+
+
+struct mh_gofer_api mh_gofer_python_api = {
+ init,
+ deinit,
+ load_plugins,
+ invoke,
+};
diff --git a/src/lib/gofer.c b/src/lib/gofer.c
new file mode 100644
index 0000000..69fab46
--- /dev/null
+++ b/src/lib/gofer.c
@@ -0,0 +1,153 @@
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "matahari/gofer.h"
+#include "gofer_private.h"
+
+
+#define API_IN_RANGE(INDEX) ((INDEX) >= 0 && \
+ (INDEX) < (sizeof(apis) / sizeof(*apis)))
+
+#define API_DEFINED(INDEX) (API_IN_RANGE(INDEX) && (apis[INDEX] != NULL))
+
+#define FOREACH_API(P_API, API) \
+ for ((P_API) = &apis[0], (API) = *(P_API); \
+ API_IN_RANGE((P_API) - apis); \
+ (API) = *++(P_API)) \
+ if ((API) != NULL)
+
+
+const struct mh_gofer_api * const apis[] = {
+ [MH_GOFER_PYTHON] = &mh_gofer_python_api,
+};
+
+
+void
+mh_gofer_init(int argc, char **argv)
+{
+ const struct mh_gofer_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->init(argc, argv);
+ }
+}
+
+void
+mh_gofer_deinit(void)
+{
+ const struct mh_gofer_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->deinit();
+ }
+}
+
+int
+mh_gofer_load_plugins(int *count, mh_gofer_plugin_t **plugins)
+{
+ const struct mh_gofer_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->load_plugins(count, plugins);
+ }
+
+ return 0;
+}
+
+int
+mh_gofer_rmi_create(mh_gofer_rmi_t *pcall,
+ const char *classname,
+ const char *method)
+{
+ *pcall = malloc(sizeof(struct mh_gofer_rmi));
+ if (!*pcall) {
+ return -1;
+ }
+
+ memset(*pcall, 0, sizeof(struct mh_gofer_rmi));
+ (*pcall)->classname = strdup(classname);
+ (*pcall)->method = strdup(method);
+
+ return 0;
+}
+
+int
+mh_gofer_rmi_destroy(mh_gofer_rmi_t call)
+{
+ free(call->classname);
+ call->classname = NULL;
+
+ free(call->method);
+ call->method = NULL;
+
+ free(call->args.str);
+ call->args.str = NULL;
+ call->args.len = 0;
+
+ free(call->kwargs.str);
+ call->kwargs.str = NULL;
+ call->kwargs.len = 0;
+
+ free(call);
+ return 0;
+}
+
+#define JSON_SEP ", "
+
+static int
+add_arg(struct argstring *argstring, const char *arg, size_t len)
+{
+ char *s = realloc(argstring->str, argstring->len + len + sizeof(JSON_SEP));
+ if (!s) {
+ return 1;
+ }
+
+ if (argstring->len) {
+ strcpy(&s[argstring->len], JSON_SEP);
+ argstring->len += strlen(JSON_SEP);
+ }
+
+ memcpy(&s[argstring->len], arg, len + 1);
+
+ argstring->str = s;
+ argstring->len += len;
+
+ return 0;
+}
+
+int
+mh_gofer_rmi_add_arg(mh_gofer_rmi_t call, const char *arg)
+{
+ return add_arg(&call->args, arg, strlen(arg));
+}
+
+int
+mh_gofer_rmi_add_kw_arg(mh_gofer_rmi_t call, const char *key, const char *arg)
+{
+ int ret = 1;
+ size_t len = strlen(key) + strlen(arg) + 4;
+ char *entry = malloc(len + 1);
+
+ if (entry) {
+ sprintf(entry, "\"%s\": %s", key, arg);
+ ret = add_arg(&call->kwargs, entry, len);
+ free(entry);
+ }
+
+ return ret;
+}
+
+
+enum mh_result
+mh_gofer_invoke(const mh_gofer_plugin_t *plugin,
+ const struct mh_gofer_rmi *call,
+ char **output)
+{
+ if (!API_DEFINED(plugin->type)) {
+ return MH_RES_BACKEND_ERROR;
+ }
+
+ return apis[plugin->type]->invoke(plugin->impl, call, output);
+}
diff --git a/src/lib/gofer_private.h b/src/lib/gofer_private.h
new file mode 100644
index 0000000..0167789
--- /dev/null
+++ b/src/lib/gofer_private.h
@@ -0,0 +1,35 @@
+
+#ifndef __MH_GOFER_PRIVATE_H__
+#define __MH_GOFER_PRIVATE_H__
+
+#include "matahari/gofer.h"
+
+enum mh_gofer_plugin_type {
+ MH_GOFER_INVALID,
+ MH_GOFER_PYTHON,
+};
+
+struct mh_gofer_api {
+ void (*init)(int, char**);
+ void (*deinit)(void);
+ int (*load_plugins)(int*, mh_gofer_plugin_t**);
+ enum mh_result (*invoke)(const struct mh_gofer_plugin_impl*,
+ const struct mh_gofer_rmi*,
+ char **output);
+};
+
+extern struct mh_gofer_api mh_gofer_python_api;
+
+
+struct argstring {
+ char *str;
+ size_t len;
+};
+
+struct mh_gofer_rmi {
+ char *classname;
+ char *method;
+ struct argstring args, kwargs;
+};
+
+#endif