I have included some fairly minor implementation comments inline. I'll
respond to the other message with some general comments ...
On 12/07/2011 01:17 PM, Zane Bitter wrote:
---
matahari.spec.in | 56 ++++++
src/CMakeLists.txt | 2
src/include/matahari/rpc.h | 122 ++++++++++++++
src/lib/CMakeLists.txt | 6 +
src/lib/rpc-python.c | 390 ++++++++++++++++++++++++++++++++++++++++++++
src/lib/rpc.c | 197 ++++++++++++++++++++++
src/lib/rpc_private.h | 56 ++++++
src/rpc/.gitignore | 2
src/rpc/CMakeLists.txt | 20 ++
src/rpc/plugins/example.py | 12 +
src/rpc/rpc-qmf.cpp | 210 ++++++++++++++++++++++++
src/rpc/rpc.py | 43 +++++
src/rpc/schema.xml | 19 ++
13 files changed, 1134 insertions(+), 1 deletions(-)
create mode 100644 src/include/matahari/rpc.h
create mode 100644 src/lib/rpc-python.c
create mode 100644 src/lib/rpc.c
create mode 100644 src/lib/rpc_private.h
create mode 100644 src/rpc/.gitignore
create mode 100644 src/rpc/CMakeLists.txt
create mode 100644 src/rpc/plugins/example.py
create mode 100644 src/rpc/rpc-qmf.cpp
create mode 100644 src/rpc/rpc.py
create mode 100644 src/rpc/schema.xml
diff --git a/matahari.spec.in b/matahari.spec.in
index 2ccd894..87653c1 100644
--- a/matahari.spec.in
+++ b/matahari.spec.in
@@ -44,6 +44,7 @@ Requires: %{name}-broker = %{version}-%{release}
Requires: %{name}-gofer = %{version}-%{release}
Requires: %{name}-host = %{version}-%{release}
Requires: %{name}-network = %{version}-%{release}
+Requires: %{name}-rpc = %{version}-%{release}
Requires: %{name}-service = %{version}-%{release}
Requires: %{name}-sysconfig = %{version}-%{release}
@@ -204,6 +205,19 @@ Obsoletes: matahari-net< %{version}-%{release}
%description network
QMF agent for viewing and controlling network devices
+%package rpc
+License: GPLv2+
+Summary: QMF agent for RPC plugins
+Group: Applications/System
+Requires: %{name}-lib = %{version}-%{release}
+Requires: %{name}-agent-lib = %{version}-%{release}
+Requires(post): chkconfig
+Requires(preun):chkconfig
+Requires(preun):initscripts
+
+%description rpc
+QMF agent for dispatching RPC calls to plugins
+
%package service
License: GPLv2+
Summary: QMF agent for system services
@@ -274,6 +288,7 @@ 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-rpc
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
rm -f $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/matahari-broker
@@ -359,6 +374,29 @@ if [ "$1" -ge "1" ]; then
/sbin/service matahari-network condrestart>/dev/null 2>&1 || :
fi
+#== RPC
+
+%post rpc
+%if %{systemd}
+systemctl --system daemon-reload
+%else
+/sbin/chkconfig --add matahari-rpc
+%endif
+/sbin/service matahari-rpc condrestart
+
+%preun rpc
+if [ $1 = 0 ]; then
+ /sbin/service matahari-rpc stop>/dev/null 2>&1 || :
+%if !%{systemd}
+ chkconfig --del matahari-rpc
+%endif
+fi
+
+%postun rpc
+if [ "$1" -ge "1" ]; then
+ /sbin/service matahari-rpc condrestart>/dev/null 2>&1 || :
+fi
+
#== Services
%post service
@@ -501,6 +539,7 @@ test "x%{buildroot}" != "x"&& rm -rf
%{buildroot}
%{_libdir}/libmgofer.so.*
%{_libdir}/libmhost.so.*
%{_libdir}/libmnetwork.so.*
+%{_libdir}/libmrpc.so.*
%{_libdir}/libmservice.so.*
%{_libdir}/libmsysconfig.so.*
%doc AUTHORS COPYING
@@ -577,6 +616,22 @@ test "x%{buildroot}" != "x"&& rm -rf
%{buildroot}
%{_datadir}/dbus-1/system-services/org.matahariproject.Host.service
%endif
+%files rpc
+%defattr(644, root, root, 755)
+%doc AUTHORS COPYING
+
+%if %{systemd}
+%{_unitdir}/matahari-rpc.service
+%endif
+
+%if %{with qmf}
+%if !%{systemd}
+%attr(755, root, root) %{_initddir}/matahari-rpc
+%endif
+%attr(755, root, root) %{_sbindir}/matahari-qmf-rpcd
+%{_mandir}/man8/matahari-qmf-rpcd.8*
+%endif
+
%files service
%defattr(644, root, root, 755)
%doc AUTHORS COPYING
@@ -709,6 +764,7 @@ test "x%{buildroot}" != "x"&& rm -rf
%{buildroot}
%{_includedir}/matahari/gofer.h
%{_includedir}/matahari/host.h
%{_includedir}/matahari/network.h
+%{_includedir}/matahari/rpc.h
%{_includedir}/matahari/sysconfig.h
%{_includedir}/matahari/services.h
%{_datadir}/cmake/Modules/FindMatahari.cmake
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9eea413..c8c651c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -189,6 +189,7 @@ add_subdirectory(lib)
add_subdirectory(gofer)
add_subdirectory(host)
add_subdirectory(network)
+add_subdirectory(rpc)
add_subdirectory(service)
add_subdirectory(sysconfig)
add_subdirectory(unittests)
@@ -204,6 +205,7 @@ 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/rpc.h DESTINATION include/matahari)
install(FILES include/matahari/sysconfig.h DESTINATION include/matahari)
install(FILES include/matahari/services.h DESTINATION include/matahari)
install(FILES cmake/modules/FindMatahari.cmake DESTINATION share/cmake/Modules)
diff --git a/src/include/matahari/rpc.h b/src/include/matahari/rpc.h
new file mode 100644
index 0000000..15c0696
--- /dev/null
+++ b/src/include/matahari/rpc.h
@@ -0,0 +1,122 @@
+/* rpc.h - Copyright (C) 2011 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
+ */
+
+#ifndef __MH_RPC_H__
+#define __MH_RPC_H__
+
+#include "matahari/errors.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mh_rpc_plugin_impl;
+
+typedef struct {
+ char *name;
+ unsigned int type;
+ struct mh_rpc_plugin_impl *impl;
+} mh_rpc_plugin_t;
+
+struct mh_rpc;
+typedef struct mh_rpc* mh_rpc_t;
+
+typedef struct {
+ char *result;
+ char *exc_type;
+ char *exc_value;
+ char *exc_traceback;
+} mh_rpc_result_t;
+
+
+/**
+ * Initialise the Gofer library with the given CLI options to rpcd.
+ */
s/Gofer/RPC/ in this file
+void
+mh_rpc_init(int argc, char **argv);
+
+/**
+ * De-initialise the Gofer library and clean up.
+ */
+void
+mh_rpc_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_rpc_load_plugins(int *count, mh_rpc_plugin_t **plugins);
How about make count unsigned?
+
+/**
+ * Create a Remote Method Invocation.
+ * This should be freed with mh_rpc_rmi_destroy() when done.
+ * \param pcall pointer in which to store the RMI data.
+ * \param procedure the name of the procedure to call.
+ * \return zero on success, non-zero otherwise.
+ */
+int
+mh_rpc_call_create(mh_rpc_t *pcall,
+ const char *procedure);
+
+/**
+ * Clean up a Remote Method Invocation.
+ * \param call the data to clean up.
+ */
+int
+mh_rpc_call_destroy(mh_rpc_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_rpc_call_add_arg(mh_rpc_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_rpc_call_add_kw_arg(mh_rpc_t call, const char *key, const char *arg);
+
+/**
+ * Invoke a remote procedure 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_rpc_invoke(const mh_rpc_plugin_t *plugin,
+ const struct mh_rpc *call,
+ mh_rpc_result_t *output);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 9fd38ba..d0a260c 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -1,6 +1,6 @@
# Library
-set(LIBS mcommon mgofer mhost mnetwork mservice msysconfig)
+set(LIBS mcommon mgofer mhost mnetwork mrpc mservice msysconfig)
include_directories(${pcre_INCLUDE_DIRS})
# See
http://sourceware.org/autobook/autobook/autobook_91.html for
@@ -26,6 +26,10 @@ add_library (mnetwork SHARED network.c network_${VARIANT}.c)
set_target_properties(mnetwork PROPERTIES SOVERSION 1.0.0)
target_link_libraries(mnetwork ${pcre_LIBRARIES} mcommon ${SIGAR} ${glib_LIBRARIES})
+add_library (mrpc SHARED rpc.c rpc-python.c)
+set_target_properties(mrpc PROPERTIES SOVERSION 1.0.0)
+target_link_libraries(mrpc mcommon ${python_LIBRARIES})
+
add_library (mservice SHARED services.c services_${VARIANT}.c)
set_target_properties(mservice PROPERTIES SOVERSION 1.0.0)
target_link_libraries(mservice ${pcre_LIBRARIES} mcommon ${SIGAR} ${glib_LIBRARIES})
I know after we talked on IRC that it's in another patch, but for the
record, the cmake additions to find the Python headers and lib need to
get merged into this patch.
diff --git a/src/lib/rpc-python.c b/src/lib/rpc-python.c
new file mode 100644
index 0000000..b9dac7d
--- /dev/null
+++ b/src/lib/rpc-python.c
@@ -0,0 +1,390 @@
+/* rpc-python.c - Copyright (C) 2011 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
+ */
+
+#include<Python.h>
+#include<stdio.h>
+
+#include "matahari/rpc.h"
+#include "rpc_private.h"
+#include "matahari/logging.h"
+#include "matahari/errors.h"
+
+
+struct mh_rpc_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();
+ }
+}
+
+/**
+ * Call a function in the rpc module.
+ *
+ * This function creates a new reference to the returned object.
+ *
+ * \param funcname the name of the function to call.
+ * \param args a sequence of arguments to pass to the function, or NULL if no
+ * arguments are required.
+ * \return the result of the function call.
+ */
+static PyObject *
+call_rpc_server_func(const char *funcname, PyObject *args)
+{
+ PyObject *rpc_mod, *func, *result;
+
+ rpc_mod = PyImport_ImportModule("rpc"); // TODO: move to matahari package
+ if (!rpc_mod) {
+ mh_err("Error importing Matahari RPC Python module");
+ PyErr_Print();
+ return NULL;
+ }
+
+ func = PyObject_GetAttrString(rpc_mod, funcname);
+ if (!func) {
+ mh_err("Error getting %s() function from Matahari RPC module",
funcname);
+ PyErr_Print();
+ Py_DECREF(rpc_mod);
+ return NULL;
+ }
+
+ result = PyObject_CallObject(func, args);
+ if (!result) {
+ mh_err("Error calling %s() in Matahari RPC module", funcname);
+ PyErr_Print();
+ }
+
+ Py_DECREF(func);
+ Py_DECREF(rpc_mod);
+ return result;
+}
+
+/**
+ * Get the name of a plugin as a C string. The string must later be released
+ * by calling free().
+ * \param plugin an rpc.Plugin object.
+ * \return a newly-malloc()ed string containing the plugin name, or NULL on
+ * failure.
+ */
+static char *
+get_plugin_name(PyObject *plugin)
+{
+ char *name_str, *dup_str;
+ PyObject *name = PyObject_GetAttrString(plugin, "name");
+ if (!name) {
+ mh_err("Failed to get plugin name");
+ PyErr_Print();
+ return NULL;
+ }
+
+ name_str = PyString_AsString(name);
+ if (!name_str) {
+ mh_err("Plugin name is not a string");
+ PyErr_Print();
+ Py_DECREF(name);
+ return NULL;
+ }
+
+ dup_str = strdup(name_str);
+ Py_DECREF(name);
+
+ return dup_str;
+}
+
+static int
+load_plugins(int *count, mh_rpc_plugin_t **plugins)
+{
+ PyObject *pluginList;
+ Py_ssize_t length, i;
+ int result = 1;
+
+ if (!Py_IsInitialized()) {
+ mh_err("Python not initialised");
+ return 1;
+ }
+
+ pluginList = call_rpc_server_func("plugins", NULL);
+ if (!pluginList) {
+ return 1;
+ }
+
+ if (!PySequence_Check(pluginList)) {
+ mh_err("Plugin list is not a sequence");
+ PyErr_Print();
+ return 1;
+ }
+
+ length = PySequence_Length(pluginList);
+ if (length< 0) {
+ mh_err("Error getting length of plugin list");
+ PyErr_Print();
+ return 1;
+ }
+
It looks like there is a reference leak of pluginList in the last two
error handling blocks.
+ for (i = 0; i< length; i++) {
+ mh_rpc_plugin_t *tmp_list;
+ mh_rpc_plugin_t new_plugin;
+ struct mh_rpc_plugin_impl *new_impl;
+
+ PyObject *plugin = PySequence_GetItem(pluginList, i);
+ if (!plugin) {
+ mh_err("Error retrieving plugin module");
+ PyErr_Print();
+ continue;
+ }
+
+ new_plugin.name = get_plugin_name(plugin);
+ if (!new_plugin.name) {
+ Py_DECREF(plugin);
+ continue;
+ }
+
+ tmp_list = realloc(*plugins, sizeof(mh_rpc_plugin_t) * (*count + 1));
+ if (!tmp_list) {
+ mh_err("Out of Memory");
+ Py_DECREF(plugin);
+ goto cleanup;
+ }
+ *plugins = tmp_list;
+
+ new_impl = malloc(sizeof(*new_impl));
+ if (!new_impl) {
+ mh_err("Out of Memory");
+ Py_DECREF(plugin);
+ goto cleanup;
+ }
+ memset(new_impl, 0, sizeof(*new_impl));
Could use calloc() here.
+ new_impl->plugin = plugin;
+
+ new_plugin.type = MH_RPC_PYTHON;
+ new_plugin.impl = new_impl;
+
+ memcpy(&tmp_list[(*count)++],&new_plugin, sizeof(new_plugin));
+ }
+
+ result = 0;
+
+cleanup:
+ Py_DECREF(pluginList);
+
+ return result;
+}
+
+/**
+ * Return a Python string for an argument value.
+ *
+ * This function creates a new reference to the returned object.
+ *
+ * \param arg the argument to convert.
+ * \return a Python string containg the argument value.
+ */
+static PyObject *
+get_arg_item(const struct rpc_arg* arg)
+{
+ return PyString_FromString(arg->val);
+}
+
+/**
+ * Return a (key, value) tuple for a keyword argument.
+ *
+ * This function creates a new reference to the returned object.
+ *
+ * \param arg the argument to convert.
+ * \return a Python tuple containg the key and value as Python strings.
+ */
+static PyObject *
+get_kwarg_item(const struct rpc_arg* arg)
+{
+ return Py_BuildValue("(ss)", arg->key, arg->val);
+}
+
+/**
+ * Return the list of arguments as a Python tuple.
+ *
+ * This function creates a new reference to the returned object.
+ *
+ * \param args the argument list.
+ * \param get_item callback for turning each argument into a Python object.
+ * \return a tuple containing the arguments converted to Python objects.
+ */
+static PyObject *
+get_arg_list(const struct rpc_arg_list *args,
+ PyObject *(*get_item)(const struct rpc_arg*))
+{
+ PyObject *arglist;
+ size_t i;
+
+ arglist = PyTuple_New(args->len);
+ if (!arglist) {
+ return NULL;
+ }
+
+ for (i = 0; i< args->len; i++) {
+ PyTuple_SetItem(arglist, i, get_item(&args->list[i]));
+ }
+
+ return arglist;
+}
+
+/**
+ * Convert the supplied Python string to a newly-allocated C string. This
+ * string must later be released with a call to free().
+ *
+ * This function consumes a reference to the input Python string.
+ *
+ * \param object the Python string object.
+ * \return pointer to a new C string containing the Python object's string.
+ */
+static char *
+py_to_c_string(PyObject *object)
+{
+ char *str;
+
+ if (!object) {
+ return NULL;
+ }
+
+ str = PyString_AsString(object);
+ if (!str) {
+ mh_err("Error getting string");
+ PyErr_Print();
+ Py_DECREF(object);
+ return NULL;
+ }
+ str = strdup(str);
+
+ Py_DECREF(object);
+ return str;
+}
+
+/**
+ * Fetch the latest Python exception and format it as strings suitable for
+ * returning in the RPC output.
+ * \param output the RPC result output.
+ * \return zero on success, non-zero on failure.
+ */
+static int
+encode_exception(mh_rpc_result_t *output)
+{
+ int ret = 1;
+ PyObject *exc_type, *exc_value, *exc_traceback, *tuple, *formatted;
+
+ PyErr_Fetch(&exc_type,&exc_value,&exc_traceback);
+
+ tuple = Py_BuildValue("(OOO)", exc_type, exc_value, exc_traceback);
+ if (!tuple) {
+ mh_err("Error collating exception data");
+ PyErr_Print();
+ goto cleanup_exc;
+ }
+
+ formatted = call_rpc_server_func("formatException", tuple);
+ if (!formatted) {
+ mh_err("Error formatting exception data");
+ PyErr_Print();
+ goto cleanup_tuple;
+ }
+
+ output->exc_type = py_to_c_string(PySequence_GetItem(formatted, 0));
+ output->exc_value = py_to_c_string(PySequence_GetItem(formatted, 1));
+ output->exc_traceback = py_to_c_string(PySequence_GetItem(formatted, 2));
+
+ ret = 0;
+
+ Py_DECREF(formatted);
+cleanup_tuple:
+ Py_DECREF(tuple);
+
+cleanup_exc:
+ Py_DECREF(exc_type);
+ Py_DECREF(exc_value);
+ Py_DECREF(exc_traceback);
+
+ return ret;
+}
+
+static enum mh_result
+invoke(const struct mh_rpc_plugin_impl *plugin,
+ const struct mh_rpc *call,
+ mh_rpc_result_t *output)
+{
+ PyObject *arg_list, *kwarg_list, *result;
+
+ memset(output, 0, sizeof(*output));
+
+ arg_list = get_arg_list(&call->args,&get_arg_item);
+ if (!arg_list) {
+ return MH_RES_BACKEND_ERROR;
+ }
+
+ kwarg_list = get_arg_list(&call->kwargs,&get_kwarg_item);
+ if (!kwarg_list) {
+ Py_DECREF(arg_list);
+ return MH_RES_BACKEND_ERROR;
+ }
+
+ result = PyObject_CallMethod(plugin->plugin, "call", "sOO",
+ call->procedure, arg_list, kwarg_list);
+ if (result) {
+ mh_debug("Remote procedure call successful");
+
+ Py_DECREF(kwarg_list);
+ Py_DECREF(arg_list);
+
+ output->result = py_to_c_string(result);
+ if (!output->result) {
+ return MH_RES_BACKEND_ERROR;
+ }
+ } else {
+ int ret;
+
+ mh_debug("Exception invoking remote procedure call");
+ ret = encode_exception(output);
+
+ Py_DECREF(kwarg_list);
+ Py_DECREF(arg_list);
+
+ if (ret) {
+ return MH_RES_BACKEND_ERROR;
+ }
+ }
+
+ return MH_RES_SUCCESS;
+}
+
+
+struct mh_rpc_api mh_rpc_python_api = {
+ init,
+ deinit,
+ load_plugins,
+ invoke,
+};
diff --git a/src/lib/rpc.c b/src/lib/rpc.c
new file mode 100644
index 0000000..f7add33
--- /dev/null
+++ b/src/lib/rpc.c
@@ -0,0 +1,197 @@
+/* rpc.c - Copyright (C) 2011 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
+ */
+
+#include<stdlib.h>
+#include<string.h>
+#include<stdio.h>
+
+#include "matahari/rpc.h"
+#include "rpc_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))
+
How about inline functions for these last 2 macros?
+#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_rpc_api * const apis[] = {
+ [MH_RPC_PYTHON] =&mh_rpc_python_api,
+};
+
+
+void
+mh_rpc_init(int argc, char **argv)
+{
+ const struct mh_rpc_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->init(argc, argv);
+ }
+}
+
+void
+mh_rpc_deinit(void)
+{
+ const struct mh_rpc_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->deinit();
+ }
+}
+
+int
+mh_rpc_load_plugins(int *count, mh_rpc_plugin_t **plugins)
+{
+ const struct mh_rpc_api * const *p_api, *api;
+
+ FOREACH_API(p_api, api) {
+ api->load_plugins(count, plugins);
+ }
+
+ return 0;
+}
+
+int
+mh_rpc_call_create(mh_rpc_t *pcall,
+ const char *procedure)
+{
+ *pcall = malloc(sizeof(**pcall));
+ if (!*pcall) {
+ return -1;
+ }
+
+ memset(*pcall, 0, sizeof(**pcall));
You could use calloc() here.
+ (*pcall)->procedure = strdup(procedure);
Should this function just clean up and return an error if this strdup()
fails?
+
+ return 0;
+}
+
+static void
+arg_list_destroy(struct rpc_arg_list *args)
+{
+ size_t i;
+
+ for (i = 0; i< args->len; i++) {
+ struct rpc_arg *arg =&args->list[i];
+ free(arg->key);
+ arg->key = NULL;
+ free(arg->val);
+ arg->val = NULL;
+ }
+
+ args->len = 0;
+ free(args->list);
+ args->list = NULL;
+}
+
+int
+mh_rpc_call_destroy(mh_rpc_t call)
+{
+ free(call->procedure);
+ call->procedure = NULL;
+
+ arg_list_destroy(&call->args);
+ arg_list_destroy(&call->kwargs);
+
+ free(call);
+ return 0;
+}
+
+#define JSON_SEP ", "
+
This doesn't appear to be used.
+static int
+add_arg(struct rpc_arg_list *args, char *key, char *val)
+{
+ struct rpc_arg *new_arg;
+ struct rpc_arg *new_list = realloc(args->list, args->len + 1);
+ if (!new_list) {
+ return -1;
+ }
+
+ args->list = new_list;
+ new_arg =&new_list[args->len++];
+
+ new_arg->key = key;
+ new_arg->val = val;
+
+ return 0;
+}
+
+int
+mh_rpc_call_add_arg(mh_rpc_t call, const char *arg)
+{
+ int ret = -1;
+ char *stored = strdup(arg);
+ if (!stored) {
+ return -1;
+ }
+
+ ret = add_arg(&call->args, NULL, stored);
+
+ if (ret) {
+ free(stored);
+ }
+
+ return ret;
+}
+
+int
+mh_rpc_call_add_kw_arg(mh_rpc_t call, const char *key, const char *arg)
+{
+ int ret = -1;
+ char *stored_arg, *stored_key;
+
+ stored_arg = strdup(arg);
+ if (!stored_arg) {
+ return -1;
+ }
+ stored_key = strdup(key);
+ if (!stored_key) {
+ goto cleanup_arg;
+ }
+
+ ret = add_arg(&call->kwargs, stored_key, stored_arg);
+
+ if (ret) {
+ free(stored_key);
+cleanup_arg:
+ free(stored_arg);
+ }
+
+ return ret;
+}
+
+
+enum mh_result
+mh_rpc_invoke(const mh_rpc_plugin_t *plugin,
+ const struct mh_rpc *call,
+ mh_rpc_result_t *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/rpc_private.h b/src/lib/rpc_private.h
new file mode 100644
index 0000000..6321ed9
--- /dev/null
+++ b/src/lib/rpc_private.h
@@ -0,0 +1,56 @@
+/* rpc-private.h - Copyright (C) 2011 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
+ */
+
+#ifndef __MH_RPC_PRIVATE_H__
+#define __MH_RPC_PRIVATE_H__
+
+#include "matahari/rpc.h"
+
+enum mh_rpc_plugin_type {
+ MH_RPC_INVALID,
+ MH_RPC_PYTHON,
+};
+
+struct mh_rpc_api {
+ void (*init)(int, char**);
+ void (*deinit)(void);
+ int (*load_plugins)(int*, mh_rpc_plugin_t**);
+ enum mh_result (*invoke)(const struct mh_rpc_plugin_impl*,
+ const struct mh_rpc*,
+ mh_rpc_result_t *output);
+};
+
+extern struct mh_rpc_api mh_rpc_python_api;
+
+
+struct rpc_arg {
+ char *key;
+ char *val;
+};
+
+struct rpc_arg_list {
+ struct rpc_arg *list;
+ size_t len;
+};
+
+struct mh_rpc {
+ char *procedure;
+ struct rpc_arg_list args, kwargs;
+};
+
+#endif
diff --git a/src/rpc/.gitignore b/src/rpc/.gitignore
new file mode 100644
index 0000000..52e4e61
--- /dev/null
+++ b/src/rpc/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+*.pyo
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt
new file mode 100644
index 0000000..a43be57
--- /dev/null
+++ b/src/rpc/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(BASE "rpc")
+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/rpc/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/rpc/plugins/example.py b/src/rpc/plugins/example.py
new file mode 100644
index 0000000..2e26b38
--- /dev/null
+++ b/src/rpc/plugins/example.py
@@ -0,0 +1,12 @@
+
+def testFunction():
+ return 'Result of the testFunction() function'
+
+class TestClass(object):
+ pass
+
+def testException():
+ raise Exception('Exception thrown by the testException() function')
+
+def showArgs(*args, **kwargs):
+ return {'args': args, 'kwargs': kwargs}
diff --git a/src/rpc/rpc-qmf.cpp b/src/rpc/rpc-qmf.cpp
new file mode 100644
index 0000000..e61a466
--- /dev/null
+++ b/src/rpc/rpc-qmf.cpp
@@ -0,0 +1,210 @@
+/* rpc-qmf.cpp - Copyright (C) 2011 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 RPC QMF Agent
+ */
+
+#include "config.h"
+
+#include<set>
+#include "matahari/agent.h"
+#include "matahari/object.h"
+#include<qmf/Data.h>
+#include "qmf/org/matahariproject/rpc/QmfPackage.h"
+
+#include "matahari/rpc.h"
+#include "matahari/logging.h"
+#include "matahari/errors.h"
+
+
+class RPCAgent;
+
+class RPCPlugin: public MatahariObject
+{
+public:
+ RPCPlugin(RPCAgent& agent, mh_rpc_plugin_t& plugin);
+ ~RPCPlugin();
+
+ bool invoke(qmf::AgentSession& session, qmf::AgentEvent& event);
+
+private:
+ RPCAgent& _agent;
+ std::string _name;
+ mh_rpc_plugin_t _plugin;
+};
+
+
+class RPCAgent : public MatahariAgent
+{
+public:
+ virtual ~RPCAgent();
+
+ virtual int setup(qmf::AgentSession session);
+ virtual gboolean invoke(qmf::AgentSession session, qmf::AgentEvent event,
+ gpointer user_data);
+
+private:
+ friend class RPCPlugin;
+ qmf::org::matahariproject::rpc::PackageDefinition _package;
+ ObjectManager *_objectManager;
+
+ typedef std::map<std::string, RPCPlugin*> PluginMap;
+ PluginMap _plugins;
+
+ void addPlugin(mh_rpc_plugin_t& plugin);
+};
+
+
+int
+main(int argc, char **argv)
+{
+ mh_rpc_init(1,&argv[0]);
+
+ RPCAgent *agent = new RPCAgent();
+ int rc = agent->init(argc, argv, "rpc");
+ if (rc == 0) {
+ agent->run();
+ }
+
+ mh_rpc_deinit();
+ return rc;
+}
+
+gboolean
+RPCAgent::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 RPCAgent::addPlugin(mh_rpc_plugin_t& plugin)
+{
+ _plugins[std::string(plugin.name)] = new RPCPlugin(*this, plugin);
+}
+
+int
+RPCAgent::setup(qmf::AgentSession session)
+{
+ int num_plugins = 0;
+ mh_rpc_plugin_t *plugins = NULL;
+
+ _package.configure(session);
+ _objectManager = new ObjectManager(session);
+
+ if (mh_rpc_load_plugins(&num_plugins,&plugins)) {
+ return 1;
+ }
+
+ for (int i = 0; i< num_plugins; i++) {
+ addPlugin(plugins[i]);
+ }
+ return 0;
+}
+
+RPCAgent::~RPCAgent()
+{
+ delete _objectManager;
Should you delete everything in _plugins here, too?
+}
+
+
+RPCPlugin::RPCPlugin(RPCAgent& agent, mh_rpc_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;
+}
+
+RPCPlugin::~RPCPlugin()
+{
+ *_agent._objectManager -= this;
+}
+
+bool
+RPCPlugin::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_rpc_t call;
+ mh_rpc_call_create(&call,
+ args["procedure"].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_rpc_call_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_rpc_call_add_kw_arg(call,
+ iter->first.c_str(),
+ iter->second.asString().c_str());
+ }
+ mh_rpc_result_t output;
+ enum mh_result result = mh_rpc_invoke(&_plugin, call,&output);
+
+ mh_rpc_call_destroy(call);
+
+ if (result != MH_RES_SUCCESS) {
+ session.raiseException(event, mh_result_to_str(result));
+ return true;
+ }
+
+ if (output.result) {
+ event.addReturnArgument("result", output.result);
+ free(output.result);
+
+ session.methodSuccess(event);
+ } else {
+ qmf::Data exception(_agent._package.data_PluginException);
+ exception.setProperty("type", output.exc_type);
+ free(output.exc_type);
+ exception.setProperty("value", output.exc_value);
+ free(output.exc_value);
+ exception.setProperty("traceback", output.exc_traceback);
+ free(output.exc_traceback);
+
+ session.raiseException(event, exception);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
diff --git a/src/rpc/rpc.py b/src/rpc/rpc.py
new file mode 100644
index 0000000..9fb56df
--- /dev/null
+++ b/src/rpc/rpc.py
@@ -0,0 +1,43 @@
+import sys, os
+import json
+import traceback
+
+(PLUGIN_PATH,) = ('.',) # TODO: choose proper install path
+
+class Plugin(object):
+ def __init__(self, module):
+ self.module = module
+ self.name = self.module.__name__
+
+ def procedures(self):
+ return [p.__name__ for p in self.module if callable(p)]
+
+ def call(self, procedure, args_json=[], kwargs_json=[]):
+ func = getattr(self.module, procedure)
+ args = [json.loads(a) for a in args_json]
+ kwargs = dict((k, json.loads(v)) for k,v in kwargs_json)
+ result = func(*args, **kwargs)
+ return json.dumps(result)
+
+def formatException(exc_type, exc_val, exc_tb):
+ return exc_type.__name__, str(exc_val),
''.join(traceback.format_tb(exc_tb))
+
+def plugins(path=PLUGIN_PATH):
+ old_path = sys.path
+ try:
+ sys.path = [path] + old_path
+ filenames = [os.path.splitext(f) for f in os.listdir(path)]
+ modnames = [n for n, e in filenames if e == '.py']
+ def getPlugin(name):
+ try:
+ mod = __import__(name, {}, {}, [], -1)
+ return Plugin(mod)
+ except ImportError:
+ return None
+ return filter(lambda p: p is not None, map(getPlugin, modnames))
+ finally:
+ sys.path = old_path
+
+
+if __name__ == '__main__':
+ print [p.name for p in plugins('src/rpc/plugins')]
diff --git a/src/rpc/schema.xml b/src/rpc/schema.xml
new file mode 100644
index 0000000..e8ddcd4
--- /dev/null
+++ b/src/rpc/schema.xml
@@ -0,0 +1,19 @@
+<schema package="org.matahariproject.rpc">
+
+<class name="Plugin">
+<property name="name" type="sstr" access="RO" />
+
+<method name="invoke" desc="Make a remote procedure call">
+<arg name="procedure" 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>
+
+<class name="PluginException">
+<property name="type" type="sstr" />
+<property name="value" type="lstr" />
+<property name="traceback" type="lstr" />
+</class>
+</schema>
_______________________________________________
Matahari mailing list
Matahari(a)lists.fedorahosted.org
https://fedorahosted.org/mailman/listinfo/matahari
--
Russell Bryant