Change in vdsm[master]: [WIP] vdsm API and libvdsm

agl at us.ibm.com agl at us.ibm.com
Mon Aug 27 22:04:44 UTC 2012


Adam Litke has uploaded a new change for review.

Change subject: [WIP] vdsm API and libvdsm
......................................................................

[WIP] vdsm API and libvdsm

In this release you can actually execute real API calls against a remote server.
See the test programs for examples in C and Python.

Known issues:
- Unions are untested and probably broken
- Optional value parameters (int, bool, enum, etc) do not work from python
- Incorrect GIR generation for functions returning arrays (see gir-fixes.patch)
- Need to convert parameters/return values in Bridge when the schema types
  differ from the internal types (ie. IntStr)

Changes since V1:
- API is generated for the full schema
- Server-side support
- RPM packaging (vdsm-jsonrpc (for server) and vdsm-api (for client))


This work-in-progress patch demonstrates the mechanics of generating a working
C API and remote transport from a schema definition.  The "C" API is written in
Vala to more easily take advantage of GObjects and reduce the amount of
boilerplate code that we need to generate and maintain.  The API has a static
part and a dynamically generated part.  I generate the dynamic part by using the
same technique employed by qemu for generating the qapi bindings (python format
strings).  The build system produces libvdsm and a C test program.  An included
python program demonstrates how the automatic python bindings work.  A test
server is included to enable the test programs to function.  We use JSON for
internal object storage and also as the wire format.

Thank you Saggi for sharing some great ideas (many of which are
incorporated into this patch).

TODO:
 - Fix issues in the main schema
   * Return values must be anonymous, singular types
   * Remove 'IntStr' and friends in favor of native representation
 - Support generation for the entire schema
   * Mapping types
   * Enums with non-C string values
   * Finish array support
   * Generate the command sub-classes (VM, Volume, StorageDomain, etc)
 - Connect the server to the real vdsm API
   * Functions must map from the new schema to the old implementation
 - Validate argument and return types on the server side
 - Probably a few more things I haven't yet thought of

Signed-off-by: Adam Litke <agl at us.ibm.com>
Change-Id: If6bd34700b86aa84c7e289f02c0e9f2ac6fcba63
---
M Makefile.am
M build-aux/gitlog-to-changelog
M configure.ac
M vdsm.spec.in
M vdsm/clientIF.py
M vdsm/config.py.in
A vdsm_api/BindingJsonRpc.py
A vdsm_api/Bridge.py
A vdsm_api/Makefile.am
A vdsm_api/generate.py
A vdsm_api/gir-fixes.patch
A vdsm_api/libvdsm-base.vala
A vdsm_api/schema.json
A vdsm_api/test.c
A vdsm_api/test.py
A vdsm_api/test_serv.py
M vdsm_api/vdsmapi-schema.json
17 files changed, 4,308 insertions(+), 59 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/16/7516/1

diff --git a/Makefile.am b/Makefile.am
index 17b5a85..d52964b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,10 +18,12 @@
 # Refer to the README and COPYING files for full details of the license
 #
 
-SUBDIRS = vdsm vdsm_cli vds_bootstrap vdsm_reg vdsm_hooks tests vdsm-tool
+SUBDIRS = vdsm vdsm_cli vds_bootstrap vdsm_reg vdsm_hooks tests vdsm-tool vdsm_api
 
 include $(top_srcdir)/build-aux/Makefile.subs
 
+ACLOCAL_AMFLAGS = -I m4
+
 # This is an *exception*, we ship also vdsm.spec so it's possible to build the
 # rpm from the tarball.
 EXTRA_DIST = \
diff --git a/build-aux/gitlog-to-changelog b/build-aux/gitlog-to-changelog
old mode 100755
new mode 100644
diff --git a/configure.ac b/configure.ac
index d4b6815..65d4ada 100644
--- a/configure.ac
+++ b/configure.ac
@@ -44,6 +44,37 @@
 AC_PROG_LN_S
 AM_PATH_PYTHON([2.6])
 
+# Enable libtool for libvdsm
+LT_INIT
+AC_CONFIG_MACRO_DIR([m4])
+
+AC_PATH_PROG([VALAC], [valac], [])
+
+# GLib dependencies for libvdsm
+PKG_CHECK_MODULES([GLIB], [glib-2.0])
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+PKG_CHECK_MODULES([JSON_GLIB], [json-glib-1.0])
+AC_SUBST(JSON_GLIB_CFLAGS)
+AC_SUBST(JSON_GLIB_LIBS)
+
+PKG_CHECK_MODULES([GEE], [gee-0.8])
+AC_SUBST(GEE_CFLAGS)
+AC_SUBST(GEE_LIBS)
+
+PKG_CHECK_MODULES([GOBJECT], [gobject-2.0])
+AC_SUBST(GOBJECT_CFLAGS)
+AC_SUBST(GOBJECT_LIBS)
+
+PKG_CHECK_MODULES([GOBJECT_INTROSPECTION], [gobject-introspection-1.0], [enable_introspection=yes])
+AC_SUBST(GOBJECT_INTROSPECTION_CFLAGS)
+AC_SUBST(GOBJECT_INTROSPECTION_LIBS)
+
+AC_SUBST([G_IR_SCANNER], [$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)])
+AC_SUBST([G_IR_COMPILER], [$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)])
+
+
 # Checking if hooks enables
 AC_ARG_ENABLE([hooks],
 [  --enable-hooks    build hooks RPMs],
@@ -79,6 +110,9 @@
 AC_SUBST([vdsmpylibdir], ['${pyexecdir}/vdsm'])
 AC_SUBST([vdsmtooldir], ['${vdsmpylibdir}/tool'])
 AC_SUBST([vdsmtestsdir], ['${datarootdir}/vdsm/tests'])
+AC_SUBST([vdsmapidir], ['${datarootdir}/vdsm_api'])
+AC_SUBST([vdsmtypelibdir], ['${exec_prefix}/lib/girepository-1.0'])
+AC_SUBST([vdsmgirdir], ['${datarootdir}/gir-1.0'])
 
 # VDSM registration default paths
 AC_SUBST([vdsmregdir], ['${datarootdir}/vdsm-reg'])
@@ -120,6 +154,9 @@
 # Checking for python modules (sorted, please keep in order)
 AX_PYTHON_MODULE([ethtool], [fatal])
 AX_PYTHON_MODULE([libvirt], [fatal])
+
+# Checking for vala compiler
+AM_PROG_VALAC([ >=0.16.0])
 
 # External programs (sorted, please keep in order)
 AC_PATH_PROG([BLKID_PATH], [blkid], [/sbin/blkid])
@@ -186,6 +223,7 @@
 	tests/Makefile
 	tests/functional/Makefile
 	vds_bootstrap/Makefile
+	vdsm_api/Makefile
 	vdsm_cli/Makefile
 	vdsm_hooks/directlun/Makefile
 	vdsm_hooks/faqemu/Makefile
diff --git a/vdsm.spec.in b/vdsm.spec.in
index ce73f6e..0f9871f 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -128,6 +128,15 @@
 %description rest
 A REST interface for interacting with vdsmd.
 
+%package jsonrpc
+Summary:        VDSM API Server
+BuildArch:      noarch
+
+Requires: %{name}-python = %{version}-%{release}
+
+%description jsonrpc
+A Json-based RPC interface that serves as the transport for libvdsm.
+
 %package bootstrap
 Summary:        VDSM bootstrapping package
 BuildArch:      noarch
@@ -169,6 +178,12 @@
 
 %description tests
 A test suite for verifying the functionality of a running vdsm instance
+
+%package api
+Summary:        VDSM API
+
+%description api
+Library and utilities for clients to use the vdsm API
 
 %package hook-vhostmd
 Summary:        VDSM hook set for interaction with vhostmd
@@ -731,6 +746,16 @@
 %{_datadir}/%{vdsm_name}/tests/netmaskconversions
 %{_datadir}/%{vdsm_name}/tests/functional/*.py*
 
+%files api
+%{_includedir}/libvdsm-0.1/libvdsm/vdsm.h
+%{_exec_prefix}/lib/girepository-1.0/vdsm-0.1.typelib
+%{_datadir}/gir-1.0/vdsm-0.1.gir
+%{_libdir}/libvdsm-0.1.a
+%{_libdir}/libvdsm-0.1.la
+%{_libdir}/libvdsm-0.1.so
+%{_libdir}/libvdsm-0.1.so.0
+%{_libdir}/libvdsm-0.1.so.0.0.0
+
 %files hook-vhostmd
 %defattr(-, root, root, -)
 %doc COPYING
@@ -853,6 +878,10 @@
 %{_datadir}/%{vdsm_name}/rest/templates/api.xsd
 %{_datadir}/%{vdsm_name}/rest/templates/rsdl.xml
 
+%files jsonrpc
+%{_datadir}/%{vdsm_name}/BindingJsonRpc.py*
+%{_datadir}/%{vdsm_name}/Bridge.py*
+
 %files bootstrap
 %defattr(-, root, root, -)
 %doc COPYING
diff --git a/vdsm/clientIF.py b/vdsm/clientIF.py
index ab34eaa..b212da4 100644
--- a/vdsm/clientIF.py
+++ b/vdsm/clientIF.py
@@ -158,6 +158,14 @@
         self.bindings['rest'] = BindingREST(self, self.log, ip, rest_port,
                                             templatePath)
 
+    def _loadBindingJsonRpc(self):
+        from BindingJsonRpc import BindingJsonRpc
+        from Bridge import VdsmBridge
+        ip = self._getServerIP(config.get('addresses', 'management_ip'))
+        port = config.getint('addresses', 'json_port')
+        self.bindings['json'] = BindingJsonRpc(VdsmBridge(), self.log,
+                                                ip, port)
+
     def _prepareBindings(self):
         self.bindings = {}
         if config.getboolean('vars', 'xmlrpc_enable'):
@@ -172,6 +180,12 @@
             except ImportError:
                 self.log.warn('Unable to load the rest server module. '
                               'Please make sure it is installed.')
+        if config.getboolean('vars', 'jsonrpc_enable'):
+            try:
+                self._loadBindingJsonRpc()
+            except ImportError:
+                self.log.warn('Unable to load the json rpc server module. '
+                              'Please make sure it is installed.')
 
     def _prepareMOM(self):
         try:
diff --git a/vdsm/config.py.in b/vdsm/config.py.in
index 56cd28e..8571365 100644
--- a/vdsm/config.py.in
+++ b/vdsm/config.py.in
@@ -130,6 +130,8 @@
 
         ('rest_enable', 'true', 'Enable the REST server'),
 
+        ('jsonrpc_enable', 'true', 'Enable the JSON RPC server'),
+
         ('report_host_threads_as_cores', 'false',
             'Count each cpu hyperthread as an individual core'),
     ]),
@@ -245,6 +247,10 @@
             'Port on which the vdsmd REST server listens to network '
             'clients.'),
 
+        ('json_port', '4444',
+            'Port on which the vdsmd Json RPC server listens to network '
+            'clients.'),
+
         ('management_ip', '', None),
 
         ('guests_gateway_ip', '', None),
diff --git a/vdsm_api/BindingJsonRpc.py b/vdsm_api/BindingJsonRpc.py
new file mode 100644
index 0000000..a2b9c9f
--- /dev/null
+++ b/vdsm_api/BindingJsonRpc.py
@@ -0,0 +1,93 @@
+# VDSM JsonRPC Server
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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
+import threading
+import SocketServer
+import json
+
+import struct
+
+Size = struct.Struct("!Q")
+
+__log__ = None
+__bridge__ = None
+
+class BindingJsonRpc:
+    def __init__(self, bridge, log, ip, port):
+        self.bridge = bridge
+        self.log = log
+        self.serverPort = port
+        if not ip:
+            self.serverIP = '0.0.0.0'
+        else:
+            self.serverIP = ip
+        self._create_server()
+
+    def _create_server(self):
+        global __bridge__
+        global __log__
+        __bridge__ = self.bridge
+        __log__ = self.log
+        self.server = SocketServer.TCPServer((self.serverIP, self.serverPort),
+                                              JsonRpcTCPHandler)
+
+    def start(self):
+        def threaded_start():
+            self.server.serve_forever()
+        threading.Thread(target=threaded_start,
+                         name='JsonRpc').start()
+
+    def prepareForShutdown(self):
+        self.server.shutdown()
+
+class JsonRpcTCPHandler(SocketServer.StreamRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    It is instantiated once per connection to the server, and must
+    override the handle() method to implement communication to the
+    client.
+    """
+
+    def handle(self):
+        bridge = __bridge__
+        log = __log__
+        while True:
+            # self.request is the TCP socket connected to the client
+            try:
+                msgLen = Size.unpack(self.request.recv(Size.size))[0]
+                msg = json.loads(self.request.recv(msgLen))
+            except:
+                return
+
+            log.debug("{} wrote:".format(self.client_address[0]))
+            log.debug(msg)
+
+            try:
+                ret = bridge._dispatch(msg['methodName'], msg.get('args', {}))
+            except Exception:
+                log.error("Dispatch error", exc_info=True)
+                continue
+            log.debug("ret = %s", ret)
+            ret['id'] = msg['id']
+            msg = json.dumps(ret)
+            msg = msg.encode('utf-8')
+            msize = Size.pack(len(msg) - 1)
+            resp = msize + msg
+            log.debug(msg)
+
+            self.wfile.write(resp)
+            self.wfile.flush()
+
diff --git a/vdsm_api/Bridge.py b/vdsm_api/Bridge.py
new file mode 100644
index 0000000..f7e2a7f
--- /dev/null
+++ b/vdsm_api/Bridge.py
@@ -0,0 +1,850 @@
+# VDSM API Connector
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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
+
+#import Fixups
+try:
+    import API
+except ImportError:
+    pass # Needed for the test bridge
+
+class VdsmError(Exception):
+    def __init__(self, code, message):
+        self.code = code
+        self.message = message
+
+class MethodBridge:
+    #def __init__(self):
+    #    self.fixup = Fixups.Fixup()
+
+    def _dispatch(self, name, argobj):
+        method = name.replace('.', '_').lower()
+        result = None
+        error = {'code': 0, 'message': 'Success'}
+        #argobj = self.fixup.fix_call(name, argobj, 'in')
+        try:
+            fn = getattr(self, method)
+        except AttributeError:
+            error = {'code': 4,
+                     'message': "Operation '%s' not supported" % name}
+            return {'result': result, 'error': error}
+        try:
+            result = fn(argobj)
+        except VdsmError, e:
+            error = {'code': e.code, 'message': e.message}
+        #result = self.fixup.fix_call(name, result, 'out')
+        return {'result': result, 'error': error}
+
+    def get_args(self, argobj, arglist):
+        ret = ()
+        for arg in arglist:
+            ret += (argobj.get(arg),)
+        return ret
+
+    def check_error(self, response):
+        code = response['status']['code']
+        if code != 0:
+            message = response['status']['message']
+            raise VdsmError(code, message)
+
+    def get_result(self, response, member=None):
+        self.check_error(response)
+        if member is None:
+            return None
+        try:
+            return response[member]
+        except KeyError:
+            raise VdsmError(5, "Response is missing '%s' member" % member)
+
+class VdsmBridge(MethodBridge):
+    def host_getvms(self, argobj):
+        ret = API.Global().getVMList(False, [])
+        ret = self.get_result(ret, 'vmList')
+        return [v['vmId'] for v in ret]
+
+    def vm_getinfo(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        ret = API.Global().getVMList(True, [objargs[0]])
+        ret = self.get_result(ret, 'vmList')
+        return ret[0]
+
+    def host_addnetwork(self, argobj):
+        args = self.get_args(argobj,
+                                ['bridge', 'vlan', 'bond', 'nics', 'options'])
+        ret = API.Global().addNetwork(*args)
+        return self.get_result(ret)
+
+    def host_delnetwork(self, argobj):
+        args = self.get_args(argobj,
+                                ['bridge', 'vlan', 'bond', 'nics', 'options'])
+        ret = API.Global().delNetwork(*args)
+        return self.get_result(ret)
+
+    def host_editnetwork(self, argobj):
+        args = self.get_args(argobj, ['oldBridge', 'newBridge', 'vlan',
+                                'bond', 'nics', 'options'])
+        ret = API.Global().editNetwork(*args)
+        return self.get_result(ret)
+
+    def host_setupnetworks(self, argobj):
+        args = self.get_args(argobj, ['networks', 'bondings', 'options'])
+        ret = API.Global().setupNetworks(*args)
+        return self.get_result(ret)
+
+    def host_fencenode(self, argobj):
+        args = self.get_args(argobj, ['addr', 'port', 'agent', 'username',
+                                 'password', 'action', 'secure', 'options'])
+        ret = API.Global().fenceNode(*args)
+        return self.get_result(ret, 'power')
+
+    def host_getalltasksinfo(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().getAllTasksInfo(*args)
+        return self.get_result(ret, 'allTasksInfo')
+
+    def host_getalltasksstatuses(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().getAllTasksStatuses(*args)
+        return self.get_result(ret, 'allTasksStatus')
+
+    def host_getcapabilities(self, argobj):
+        """
+        Returns 2 values: info and netConfigDirty which must be merged into
+        a single value
+        """
+        args = self.get_args(argobj, [])
+        resp = API.Global().getCapabilities(*args)
+        ret_data = self.get_result(resp, 'info')
+        #ret_data['netConfigDirty'] = self.get_result(resp, 'netConfigDirty')
+        return ret_data
+
+    def host_getconnectedstoragepools(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().getConnectedStoragePools(*args)
+        return self.get_result(ret, 'poollist')
+
+    def host_getdeviceinfo(self, argobj):
+        args = self.get_args(argobj, ['guid'])
+        ret = API.Global().getDeviceInfo(*args)
+        return self.get_result(ret, 'info')
+
+    def host_getdevicelist(self, argobj):
+        args = self.get_args(argobj, ['storageType'])
+        ret = API.Global().getDeviceList(*args)
+        return self.get_result(ret, 'devList')
+
+    def host_getdevicesvisibility(self, argobj):
+        args = self.get_args(argobj, ['guidList'])
+        ret = API.Global().getDevicesVisibility(*args)
+        return self.get_result(ret, 'visibility')
+
+    def host_getlvmvolumegroups(self, argobj):
+        args = self.get_args(argobj, ['storageType'])
+        ret = API.Global().getLVMVolumeGroups(*args)
+        return self.get_result(ret, 'vglist')
+
+    def host_getstats(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().getStats(*args)
+        return self.get_result(ret, 'info')
+
+    def host_getstoragedomains(self, argobj):
+        args = self.get_args(argobj, ['storagepoolID', 'domainClass', 'storageType',
+                                 'remotePath'])
+        ret = API.Global().getStorageDomains(*args)
+        return self.get_result(ret, 'domlist')
+
+    def host_getstoragerepostats(self, argobj):
+        """
+        The returned dictionary doesn't separate the stats from the status code
+        so we need to rebuild the result.
+        """
+        args = self.get_args(argobj, [])
+        resp = API.Global().getStorageRepoStats(*args)
+        self.check_error(resp)
+        del resp['status']
+        return resp
+
+    def host_getvmlist(self, argobj):
+        args = self.get_args(argobj, ['fullStatus', 'vmList'])
+        ret = API.Global().getVMList(*args)
+        return self.get_result(ret, 'vmList')
+
+    def host_ping(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().ping(*args)
+        return self.get_result(ret)
+
+    def host_setloglevel(self, argobj):
+        args = self.get_args(argobj, ['level'])
+        ret = API.Global().setLogLevel(*args)
+        return self.get_result(ret)
+
+    def host_setsafenetworkconfig(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.Global().setSafeNetworkConfig(*args)
+        return self.get_result(ret)
+
+    def connectionrefs_acquire(self, argobj):
+        args = self.get_args(argobj, ['conRefArgs'])
+        ret = API.ConnectionRefs().acquire(*args)
+        return self.get_result(ret, 'results')
+
+    def connectionrefs_release(self, argobj):
+        args = self.get_args(argobj, ['refIDs'])
+        ret = API.ConnectionRefs().release(*args)
+        return self.get_result(ret, 'results')
+
+    def connectionrefs_statuses(self, argobj):
+        args = self.get_args(argobj, [])
+        ret = API.ConnectionRefs().statuses(*args)
+        return self.get_result(ret, 'connectionslist')
+
+    def iscsiconnection_discoversendtargets(self, argobj):
+        """
+        This returns two lists: 'targets' & 'fullTargets'.  Just return
+        the 'fullTargets' one because it supercedes the old 'targets'.
+        """
+        objargs = self.get_args(argobj['__obj__'],
+                           ['host', 'port', 'username', 'password'])
+        args = self.get_args(argobj, [])
+        ret = API.ISCSIConnection(*objargs).discoverSendTargets(*args)
+        return self.get_result(ret, 'fullTargets')
+
+    def image_delete(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                                 ['imageID', 'storagepoolID', 'storagedomainID'])
+        args = self.get_args(argobj, ['postZero', 'force'])
+        ret = API.Image(*objargs).delete(*args)
+        return self.get_result(ret, 'uuid')
+
+    def image_deletevolumes(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                                 ['imageID', 'storagepoolID', 'storagedomainID'])
+        args = self.get_args(argobj, ['volumeList', 'postZero', 'force'])
+        ret = API.Image(*objargs).deleteVolumes(*args)
+        return self.get_result(ret, 'uuid')
+
+    def image_getvolumes(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                                 ['imageID', 'storagepoolID', 'storagedomainID'])
+        args = self.get_args(argobj, [])
+        ret = API.Image(*objargs).getVolumes(*args)
+        return self.get_result(ret, 'uuidlist')
+
+    def image_mergesnapshots(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                                 ['imageID', 'storagepoolID', 'storagedomainID'])
+        args = self.get_args(argobj, ['ancestor', 'successor', 'postZero'])
+        ret = API.Image(*objargs).mergeSnapshots(*args)
+        return self.get_result(ret, 'uuid')
+
+    def image_move(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                                 ['imageID', 'storagepoolID', 'storagedomainID'])
+        args = self.get_args(argobj, ['dstSdUUID', 'operation', 'postZero',
+                                 'force'])
+        ret = API.Image(*objargs).move(*args)
+        return self.get_result(ret, 'uuid')
+
+    def lvmvolumegroup_create(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['lvmvolumegroupID'])
+        args = self.get_args(argobj, ['name', 'devlist', 'force'])
+        ret = API.LVMVolumeGroup(*objargs).create(*args)
+        return self.get_result(ret, 'uuid')
+
+    def lvmvolumegroup_getinfo(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['lvmvolumegroupID'])
+        args = self.get_args(argobj, [])
+        ret = API.LVMVolumeGroup(*objargs).getInfo(*args)
+        return self.get_result(ret, 'info')
+
+    def lvmvolumegroup_remove(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['lvmvolumegroupID'])
+        args = self.get_args(argobj, [])
+        ret = API.LVMVolumeGroup(*objargs).remove(*args)
+        return self.get_result(ret)
+
+    def storagedomain_activate(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StorageDomain(*objargs).activate(*args)
+        return self.get_result(ret)
+
+    def storagedomain_attach(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['storagepoolID'])
+        ret = API.StorageDomain(*objargs).attach(*args)
+        return self.get_result(ret)
+
+    def storagedomain_create(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['_type', 'typeArgs', 'name',
+                                       'domainClass', 'version'])
+        ret = API.StorageDomain(*objargs).create(*args)
+        return self.get_result(ret)
+
+    def storagedomain_deactivate(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['masterSdUUID', 'masterVersion'])
+        ret = API.StorageDomain(*objargs).deactivate(*args)
+        return self.get_result(ret)
+
+    def storagedomain_detach(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj,
+                                ['masterSdUUID', 'masterVersion', 'force'])
+        ret = API.StorageDomain(*objargs).detach(*args)
+        return self.get_result(ret)
+
+    def storagedomain_extend(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['devlist'])
+        ret = API.StorageDomain(*objargs).extend(*args)
+        return self.get_result(ret)
+
+    def storagedomain_format(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['autoDetach'])
+        ret = API.StorageDomain(*objargs).format(*args)
+        return self.get_result(ret)
+
+    def storagedomain_getfilelist(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['pattern'])
+        ret = API.StorageDomain(*objargs).getFileList(*args)
+        return self.get_result(ret, 'files')
+
+    def storagedomain_getimages(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StorageDomain(*objargs).getImages(*args)
+        return self.get_result(ret, 'imageslist')
+
+    def storagedomain_getinfo(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StorageDomain(*objargs).getInfo(*args)
+        return self.get_result(ret, 'info')
+
+    def storagedomain_getstats(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StorageDomain(*objargs).getStats(*args)
+        return self.get_result(ret, 'stats')
+
+    def storagedomain_getvolumes(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['imageID'])
+        ret = API.StorageDomain(*objargs).getVolumes(*args)
+        return self.get_result(ret, 'uuidlist')
+
+    def storagedomain_setdescription(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['description'])
+        ret = API.StorageDomain(*objargs).setDescription(*args)
+        return self.get_result(ret)
+
+    def storagedomain_uploadvolume(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, ['imageID', 'volumeID', 'srcPath', 'size',
+                                 'method'])
+        ret = API.StorageDomain(*objargs).uploadVolume(*args)
+        return self.get_result(ret)
+
+    def storagedomain_validate(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagedomainID', 'storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StorageDomain(*objargs).validate(*args)
+        return self.get_result(ret)
+
+    def storagepool_connect(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['hostID', 'scsiKey', 'masterSdUUID',
+                                 'masterVersion'])
+        ret = API.StoragePool(*objargs).connect(*args)
+        return self.get_result(ret)
+
+    def storagepool_connectstorageserver(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['domainType', 'connectionParams'])
+        ret = API.StoragePool(*objargs).connectStorageServer(*args)
+        return self.get_result(ret, 'statuslist')
+
+    def storagepool_create(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj,
+                        ['name', 'masterSdUUID', 'masterVersion', 'domainList',
+                         'lockRenewalIntervalSec', 'leaseTimeSec',
+                         'ioOpTimeoutSec', 'leaseRetries'])
+        ret = API.StoragePool(*objargs).create(*args)
+        return self.get_result(ret)
+
+    def storagepool_destroy(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['hostID', 'scsiKey'])
+        ret = API.StoragePool(*objargs).destroy(*args)
+        return self.get_result(ret)
+
+    def storagepool_disconnect(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['hostID', 'scsiKey', 'remove'])
+        ret = API.StoragePool(*objargs).disconnect(*args)
+        return self.get_result(ret)
+
+    def storagepool_disconnectstorageserver(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['domainType', 'connectionParams'])
+        ret = API.StoragePool(*objargs).disconnectStorageServer(*args)
+        return self.get_result(ret, 'statuslist')
+
+    def storagepool_fence(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StoragePool(*objargs).fence(*args)
+        return self.get_result(ret, 'spm_st')
+
+    def storagepool_getbackedupvmsinfo(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['storagedomainID', 'vmList'])
+        ret = API.StoragePool(*objargs).getBackedUpVmsInfo(*args)
+        return self.get_result(ret, 'vmlist')
+
+    def storagepool_getbackedupvmslist(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['storagedomainID'])
+        ret = API.StoragePool(*objargs).getBackedUpVmsList(*args)
+        return self.get_result(ret, 'vmlist')
+
+    def storagepool_getfloppylist(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StoragePool(*objargs).getFloppyList(*args)
+        return self.get_result(ret, 'isolist')
+
+    def storagepool_getdomainscontainingimage(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['imageID', 'onlyDataDomains'])
+        ret = API.StoragePool(*objargs).getDomainsContainingImage(*args)
+        return self.get_result(ret, 'domainslist')
+
+    def storagepool_getisolist(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, ['filenameExtension'])
+        ret = API.StoragePool(*objargs).getIsoList(*args)
+        return self.get_result(ret, 'isolist')
+
+    def storagepool_getspmstatus(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StoragePool(*objargs).getSpmStatus(*args)
+        return self.get_result(ret, 'spm_st')
+
+    def storagepool_getinfo(self, argobj):
+        """
+        The result contains two data structures which must be merged
+        """
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.get_args(argobj, [])
+        ret = API.StoragePool(*objargs).getInfo(*args)
+        ret_data = {}
+        ret_data['info'] = self.get_result(ret, 'info')
+        ret_data['dominfo'] = self.get_result(ret, 'dominfo')
+        return ret_data
+
+    def storagepool_movemultipleimages(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj,
+                                ['srcSdUUID', 'dstSdUUID', 'imgDict', 'force'])
+        ret = API.StoragePool(*objargs).moveMultipleImages(*args)
+        return self.get_result(ret)
+
+    def storagepool_reconstructmaster(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj,
+                        ['hostId', 'name', 'masterSdUUID', 'masterVersion',
+                         'domainDict', 'lockRenewalIntervalSec', 'leaseTimeSec',
+                         'ioOpTimeoutSec', 'leaseRetries'])
+        ret = API.StoragePool(*objargs).reconstructMaster(*args)
+        return self.get_result(ret)
+
+    def storagepool_refresh(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['masterSdUUID', 'masterVersion'])
+        ret = API.StoragePool(*objargs).refresh(*args)
+        return self.get_result(ret)
+
+    def storagepool_setdescription(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['description'])
+        ret = API.StoragePool(*objargs).setDescription(*args)
+        return self.get_result(ret)
+
+    def storagepool_spmstart(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj,
+                                   ['prevID', 'prevLver', 'enableScsiFencing',
+                                    'maxHostID', 'domVersion'])
+        ret = API.StoragePool(*objargs).spmStart(*args)
+        return self.get_result(ret, 'uuid')
+
+    def storagepool_spmstop(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.StoragePool(*objargs).spmStop(*args)
+        return self.get_result(ret)
+
+    def storagepool_upgrade(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['targetDomVersion'])
+        ret = API.StoragePool(*objargs).upgrade(*args)
+        return self.get_result(ret, 'upgradeStatus')
+
+    def storagepool_validatestorageserverconnection(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['domainType', 'connectionParams'])
+        ret = API.StoragePool(*objargs).validateStorageServerConnection(*args)
+        return self.get_result(ret, 'statuslist')
+
+    def storagepool_updatevms(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['vmList', 'storagedomainID'])
+        ret = API.StoragePool(*objargs).updateVMs(*args)
+        return self.get_result(ret)
+
+    def storagepool_removevm(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['storagepoolID'])
+        args = self.self.get_args(argobj, ['vmUUID', 'storagedomainID'])
+        ret = API.StoragePool(*objargs).removeVM(*args)
+        return self.get_result(ret)
+
+    def task_clear(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['taskID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.Task(*objargs).clear(*args)
+        return self.get_result(ret)
+
+    def task_getinfo(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['taskID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.Task(*objargs).getInfo(*args)
+        return self.get_result(ret, 'TaskInfo')
+
+    def task_getstatus(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['taskID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.Task(*objargs).getStatus(*args)
+        return self.get_result(ret, 'taskStatus')
+
+    def task_revert(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['taskID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.Task(*objargs).revert(*args)
+        return self.get_result(ret)
+
+    def task_stop(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['taskID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.Task(*objargs).stop(*args)
+        return self.get_result(ret)
+
+    def vm_changecd(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, ['driveSpec'])
+        ret = API.VM(*objargs).changeCD(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_changefloppy(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, ['driveSpec'])
+        ret = API.VM(*objargs).changeFloppy(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_cont(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.VM(*objargs).cont(*args)
+        return self.get_result(ret)
+
+    def vm_create(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, ['vmParams'])
+        ret = API.VM(*objargs).create(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_desktoplock(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, [])
+        ret = API.VM(*objargs).desktopLock(*args)
+        return self.get_result(ret)
+
+    def vm_desktoplogin(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, ['domain', 'username', 'password'])
+        ret = API.VM(*objargs).desktopLogin(*args)
+        return self.get_result(ret)
+
+    def vm_desktoplogoff(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.self.get_args(argobj, ['force'])
+        ret = API.VM(*objargs).desktopLogoff(*args)
+        return self.get_result(ret)
+
+    def vm_desktopsendhccommand(self, argobj):
+        objargs = self.self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['message'])
+        ret = API.VM(*objargs).desktopSendHcCommand(*args)
+        return self.get_result(ret)
+
+    def vm_destroy(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).destroy(*args)
+        return self.get_result(ret)
+
+    def vm_getmigrationstatus(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).getMigrationStatus(*args)
+        return self.get_result(ret)
+
+    def vm_getstats(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).getStats(*args)
+        return self.get_result(ret, 'statsList')
+
+    def vm_hibernate(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['hibernationVolHandle'])
+        ret = API.VM(*objargs).hibernate(*args)
+        return self.get_result(ret)
+
+    def vm_hotplugnic(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['params'])
+        ret = API.VM(*objargs).hotplugNic(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_hotunplugnic(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['params'])
+        ret = API.VM(*objargs).hotunplugNic(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_hotplugdisk(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['params'])
+        ret = API.VM(*objargs).hotplugDisk(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_hotunplugdisk(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['params'])
+        ret = API.VM(*objargs).hotunplugDisk(*args)
+        return self.get_result(ret, 'vmList')
+
+    def vm_migrate(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['params'])
+        ret = API.VM(*objargs).migrate(*args)
+        return self.get_result(ret)
+
+    def vm_migratecancel(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).migrateCancel(*args)
+        return self.get_result(ret)
+
+    def vm_migrationcreate(self, argobj):
+        """
+        Returns 2 values: migrationPort and params which must be merged into
+        a single value
+        """
+        args = self.get_args(argobj, [])
+        resp = API.Global().getCapabilities(*args)
+        ret_data = {}
+        ret_data['params'] = self.get_result(resp, 'params')
+        ret_data['migrationPort'] = self.get_result(resp, 'migrationPort')
+        return ret_data
+
+    def vm_monitorcommand(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['command'])
+        ret = API.VM(*objargs).monitorCommand(*args)
+        return self.get_result(ret)
+
+    def vm_pause(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).pause(*args)
+        return self.get_result(ret)
+
+    def vm_reset(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).reset(*args)
+        return self.get_result(ret)
+
+    def vm_sendkeys(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['keySequence'])
+        ret = API.VM(*objargs).sendKeys(*args)
+        return self.get_result(ret)
+
+    def vm_setticket(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj,
+                        ['password', 'ttl', 'existingConnAction', 'params'])
+        ret = API.VM(*objargs).setTicket(*args)
+        return self.get_result(ret)
+
+    def vm_shutdown(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['delay', 'message'])
+        ret = API.VM(*objargs).shutdown(*args)
+        return self.get_result(ret)
+
+    def vm_snapshot(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['snapDrives'])
+        ret = API.VM(*objargs).snapshot(*args)
+        return self.get_result(ret)
+
+    def vm_merge(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, ['mergeDrives'])
+        ret = API.VM(*objargs).merge(*args)
+        return self.get_result(ret)
+
+    def vm_mergestatus(self, argobj):
+        objargs = self.get_args(argobj['__obj__'], ['vmID'])
+        args = self.get_args(argobj, [])
+        ret = API.VM(*objargs).mergeStatus(*args)
+        return self.get_result(ret, 'mergeStatus')
+
+    def volume_copy(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['dstSdUUID', 'dstImgUUID', 'dstVolUUID',
+                                 'desc', 'volType', 'volFormat', 'preallocate',
+                                 'postZero', 'force'])
+        ret = API.Volume(*objargs).copy(*args)
+        return self.get_result(ret, 'uuid')
+
+    def volume_create(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['size', 'volFormat', 'preallocate',
+                                 'diskType', 'desc', 'srcImgUUID',
+                                 'srcVolUUID'])
+        ret = API.Volume(*objargs).create(*args)
+        return self.get_result(ret, 'uuid')
+
+    def volume_delete(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['postZero', 'force'])
+        ret = API.Volume(*objargs).delete(*args)
+        return self.get_result(ret, 'uuid')
+
+    def volume_extend(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['size', 'isShuttingDown'])
+        ret = API.Volume(*objargs).extend(*args)
+        return self.get_result(ret)
+
+    def volume_getinfo(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, [])
+        ret = API.Volume(*objargs).getInfo(*args)
+        return self.get_result(ret, 'info')
+
+    def volume_getpath(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, [])
+        ret = API.Volume(*objargs).getPath(*args)
+        return self.get_result(ret, 'path')
+
+    def volume_getsize(self, argobj):
+        """
+        This function returns 2 values: apparentsize and truesize.  They have
+        to be merged into a dictionary together
+        """
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, [])
+        ret_data = {}
+        resp = API.Volume(*objargs).getSize(*args)
+        ret_data['truesize'] = self.get_result(resp, 'truesize')
+        ret_data['apparentsize'] = self.get_result(resp, 'apparentsize')
+        return ret_data
+
+    def volume_prepare(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['rw'])
+        ret = API.Volume(*objargs).prepare(*args)
+        return self.get_result(ret)
+
+    def volume_refresh(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, [])
+        ret = API.Volume(*objargs).refresh(*args)
+        return self.get_result(ret)
+
+    def volume_setdescription(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['description'])
+        ret = API.Volume(*objargs).setDescription(*args)
+        return self.get_result(ret)
+
+    def volume_setlegality(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, ['legality'])
+        ret = API.Volume(*objargs).setLegality(*args)
+        return self.get_result(ret)
+
+    def volume_teardown(self, argobj):
+        objargs = self.get_args(argobj['__obj__'],
+                           ['volumeID', 'storagepoolID', 'storagedomainID', 'imageID'])
+        args = self.get_args(argobj, [])
+        ret = API.Volume(*objargs).tearDown(*args)
+        return self.get_result(ret)
+
+
+class TestBridge(MethodBridge):
+    def test_ping(self, argobj):
+        return {}
+
+    def test_pong(self, argobj):
+        return argobj['a']
+
+    def test_passobject(self, argobj):
+        argobj['a']['nics'][0] = 'Hello!'
+        return argobj
+
+    def test_maptest(self, argobj):
+        m = argobj['a']
+        ret = {}
+        for k,v in m.items():
+            ret[k] = str(v)
+        return ret
diff --git a/vdsm_api/Makefile.am b/vdsm_api/Makefile.am
new file mode 100644
index 0000000..2126725
--- /dev/null
+++ b/vdsm_api/Makefile.am
@@ -0,0 +1,94 @@
+lib_LTLIBRARIES = libvdsm-0.1.la
+libvdsm_0_1_ladir = $(includedir)/libvdsm-0.1/libvdsm
+libvdsm_0_1_la_HEADERS = \
+	vdsm.h \
+	$(NULL)
+
+libvdsm_0_1_la_SOURCES = \
+	libvdsm-base.vala \
+	generated.vala \
+	$(NULL)
+
+BUILT_SOURCES = \
+	generated.vala \
+	$(NULL)
+
+EXTRA_DIST = \
+	generate.py \
+	vdsmapi-schema.json \
+	$(NULL)
+
+dist_vdsmtypelib_DATA = \
+	vdsm-0.1.typelib \
+	$(NULL)
+
+dist_vdsmgir_DATA = \
+	vdsm-0.1.gir \
+	$(NULL)
+
+dist_vdsm_PYTHON = \
+	BindingJsonRpc.py \
+	Bridge.py \
+	$(NULL)
+
+libvdsm_0_1_la_LIBADD = \
+	$(GLIB_LIBS) \
+	$(GOBJECT_LIBS) \
+	$(JSON_GLIB_LIBS) \
+	$(GEE_LIBS) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Werror \
+	$(GLIB_CFLAGS) \
+	$(JSON_GLIB_CFLAGS) \
+	$(GEE_CFLAGS) \
+	$(GOBJECT_CFLAGS) \
+	$(GOBJECT_INTROSPECTION_CFLAGS) \
+	-I.
+	$(NULL)
+
+VALAFLAGS = \
+	--save-temps 			\
+	--pkg glib-2.0 			\
+	--target-glib=2.32		\
+	--pkg gio-2.0 			\
+	--pkg json-glib-1.0		\
+	--pkg gee-0.8			\
+	-g						\
+	-X -fPIC -X -shared		\
+	--library=libvdsm		\
+	--gir=vdsm-0.1.gir		\
+	-o libvdsm.so			\
+	-H vdsm.h \
+	--vapi=vdsm-0.1.vapi 	\
+	$(NULL)
+
+generated.vala: generate.py vdsmapi-schema.json
+	python generate.py
+
+vdsm-0.1.typelib: libvdsm-0.1.la vdsm-0.1.gir
+	$(G_IR_COMPILER) \
+		--shared-library=libvdsm-0.1.la \
+		--output=$@ \
+		vdsm-0.1.gir
+
+vdsm-0.1.gir: libvdsm-0.1.la
+
+c_test: test.c
+	$(CC) $^ -o $@ $(AM_CFLAGS)  ./.libs/libvdsm-0.1.a $(GLIB_LIBS) $(GOBJECT_LIBS) $(JSON_GLIB_LIBS) $(GEE_LIBS)
+
+GENERATED = \
+	generated.* \
+	libvdsm-base.c \
+	vdsm.h \
+	$(NULL)
+
+CLEANFILES = \
+	$(GENERATED) \
+	*.gir \
+	*.vapi \
+	*.typelib \
+	*.pyc \
+	*.html \
+	$(NULL)
diff --git a/vdsm_api/generate.py b/vdsm_api/generate.py
new file mode 100644
index 0000000..c330518
--- /dev/null
+++ b/vdsm_api/generate.py
@@ -0,0 +1,895 @@
+# VDSM API Code Generator
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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
+
+import sys
+import os
+from collections import OrderedDict
+sys.path.append("../vdsm_api")
+import vdsmapi
+
+
+def genindent(count):
+    ret = ""
+    for i in range(count):
+        ret += " "
+    return ret
+
+
+def push_indent(indent_amount=4):
+    global indent_level
+    indent_level += indent_amount
+
+
+def pop_indent(indent_amount=4):
+    global indent_level
+    indent_level -= indent_amount
+
+
+def cgen(code, **kwds):
+    indent = genindent(indent_level)
+    lines = code.split('\n')
+    lines = map(lambda x: indent + x, lines)
+    return '\n'.join(lines) % kwds + '\n'
+
+
+def mcgen(code, **kwds):
+    return cgen('\n'.join(code.split('\n')[1:-1]), **kwds)
+
+
+def parse_args(typeinfo):
+    for member in typeinfo:
+        argname = member
+        argentry = typeinfo[member]
+        optional = False
+        structured = False
+        if member.startswith('*'):
+            argname = member[1:]
+            optional = True
+        if isinstance(argentry, OrderedDict):
+            structured = True
+        yield (argname, argentry, optional, structured)
+
+def map_type(name):
+    return map_types[name]['value']
+
+def map_cast(name):
+    valType = map_type(name)
+    outType = vala_type(valType)
+    if valType in ('double', 'int', 'uint'):
+        outType += '?'
+    return 'HashTable<string, %s>' % outType
+
+def vala_type(name):
+    if name == 'str':
+        return 'string'
+    elif name == 'int':
+        return 'int64'
+    elif name == 'uint':
+        return 'uint64'
+    elif name == 'bool':
+        return 'bool'
+    elif name == 'float':
+        return 'double'
+    elif type(name) == list:
+        return '%s[]' % vala_type(name[0])
+    elif is_enum(name):
+        return name
+    elif is_object(name):
+        return name
+    elif is_command_class(name):
+        return name
+    elif is_map(name):
+        mapType = vala_type(map_type(name))
+        return 'HashTable<string, %s>' % mapType
+    elif is_alias(name):
+        return vala_type(aliased_types[name])
+    elif is_union(name):
+        return name
+    elif name == None or len(name) == 0:
+        return 'void'
+    else:
+        raise ValueError ("Unhandled type: %s", name)
+
+
+def type_as_function_name(name):
+    if name == 'str':
+        return 'string'
+    elif name == 'int':
+        return 'int'
+    elif name == 'uint':
+        return 'uint'
+    elif name == 'bool':
+        return 'boolean'
+    elif name == 'float':
+        return 'double'
+    elif type(name) == list:
+        return "%s_array" % type_as_function_name(name[0])
+    elif is_enum(name):
+        return 'enum'
+    elif is_object(name):# or is_union(name):
+        return 'object'
+    elif is_union(name):
+        return 'union'
+    elif is_map(name):
+        mapType = type_as_function_name(map_type(name))
+        return '%s_map' % mapType
+    elif is_alias(name):
+        return type_as_function_name(aliased_types[name])
+    else:
+        raise ValueError ("Unhandled type: %s", name)
+
+
+def vala_type_to_json_type(name):
+    if name in  ('int', 'uint'):
+        return 'int'
+    elif name == 'float':
+        return 'double'
+    elif name == 'str':
+        return 'string';
+    elif name == 'bool':
+        return 'boolean'
+    elif is_enum(name):
+        return 'int'
+    elif is_array(name):
+        return 'array'
+    elif is_object(name) or is_map(name):
+        return 'object'
+    elif is_command_class(name): # The object is identified by UUID
+        return 'string'
+    elif is_alias(name):
+        return vala_type_to_json_type(aliased_types[name])
+    elif is_union(name):
+        return 'object'
+    else:
+        raise ValueError ("Unhandled type: %s", name)
+
+
+def vala_var(name):
+    return name.replace('-', '_').lstrip("*")
+
+
+def vala_enum_val(name):
+    return vala_var(name).replace(' ', '_').upper()
+
+
+def vala_fun(name):
+    return vala_var(name).replace('.', '_')
+
+
+def is_enum(name):
+    global enum_types
+    return (name in enum_types)
+
+
+def is_object(name):
+    global object_types
+    return (name in object_types)
+
+
+def is_array(name):
+    return type(name) is list
+
+
+def is_map(name):
+    return not is_array(name) and (name in map_types)
+
+
+def is_alias(name):
+    return (name in aliased_types)
+
+
+def is_union(name):
+    return (name in union_types)
+
+
+def is_command_class(name):
+    return (name in class_types.keys())
+
+
+def generate_errors(f, errors):
+    ret = cgen('''
+public errordomain VdsmError {
+''')
+    push_indent()
+    for error in errors:
+        ret += mcgen('''
+%(error_name)s = %(error_code)i,
+''', error_name=error['name'], error_code=error['code'])
+    pop_indent()
+    ret += cgen('''}''')
+    f.write(ret)
+
+
+def generate_enum(f, e):
+    strings = ""
+    for s in e['data']:
+        strings += '"%s", ' % s
+    strings = strings[:-2]
+
+    ret = mcgen('''
+const string[] %(type)s_strings = { %(strings)s};
+''', type=e['enum'], strings=strings)
+
+    ret += mcgen('''
+public enum %(type)s {
+''', type=e['enum'])
+
+    idents = ', '.join(map(vala_enum_val, e['data'])) + ';'
+    for ident in idents.split():
+        ret += mcgen('''
+    %(ident)s
+''', ident=ident)
+
+    ret += mcgen('''
+    public unowned string to_string() {
+        return enum_to_string((string[])%(type)s_strings, this);
+    }
+
+    public static %(type)s from_string (string val) {
+        return (%(type)s) enum_from_string((string[])%(type)s_strings, val);
+    }
+}
+''', type=e['enum'])
+    f.write(ret)
+
+
+def generate_type_constructor_field_setter(argname, argtype, optional):
+    ret = ""
+    if optional:
+        ret += mcgen('''
+if (%(argname)s != null) {
+''', argname=argname)
+        push_indent()
+
+
+    ret += mcgen('''
+set_%(argname)s(%(argname)s);
+''', argname=argname)
+    if optional:
+        pop_indent()
+        ret += cgen('''}''')
+    return ret
+
+
+def marshall_arg_list(fields, retval=None):
+    def maybe_optional(argtype, optional):
+        if optional:
+            return argtype + '?'
+        return argtype
+
+    arglist = ""
+    for argname, argtype, optional, structured in parse_args(fields):
+        if is_map(argtype):
+            _type = maybe_optional(map_cast(argtype), optional)
+        else:
+            _type = maybe_optional(vala_type(argtype), optional)
+        arglist += "%s %s, " % (_type, vala_var(argname))
+
+    arglist = arglist[:-2] # strip the last comma
+
+    return arglist
+
+
+def generate_type_constructor(name, fields):
+    arglist = marshall_arg_list(fields)
+
+    ret = mcgen('''
+public %(name)s(%(args)s) {
+    base();
+''',  name=vala_fun(name), args=arglist)
+
+    push_indent()
+    for argname, argtype, optional, structured in parse_args(fields):
+        ret += generate_type_constructor_field_setter(argname, argtype,
+                                                      optional)
+    pop_indent()
+
+    ret += cgen('''}''')
+
+    # Add the json constructor as well
+    ret += mcgen('''
+public %(name)s.from_json_object(owned Json.Object? obj) {
+    base.from_json_object(obj);
+}
+''', name=vala_fun(name))
+
+    return ret
+
+
+def setter_value_cast(vartype):
+    if is_array(vartype):
+        return "%s[]" % setter_value_cast(vartype[0])
+    elif is_object(vartype):
+        return "BaseType"
+    elif is_union(vartype):
+        return "BaseUnion"
+    elif is_enum(vartype):
+        return "int64"
+    else:
+        return vala_type(vartype)
+
+
+def generate_enum_type_setter(varname, vartype):
+    return mcgen('''
+public void set_%(name)s(%(type)s value) {
+    set_string_member("%(name)s", value.to_string());
+}
+''', name=varname, type=vartype)
+
+def generate_type_setter(varname, vartype, setVar=False):
+    if is_enum(vartype):
+        return generate_enum_type_setter(varname, vartype)
+
+    argtype = vala_type(vartype)
+    fname = "set_%s_member" % type_as_function_name(vartype)
+
+    value = "(%s) value" % setter_value_cast(vartype)
+
+
+    if setVar:
+        varSetter = mcgen('''
+    this.%(varname)s = %(varname)s;''', varname=varname)
+    else:
+        varSetter = ''
+
+    return mcgen('''
+public void set_%(name)s(%(type)s value) {
+    %(fname)s("%(name)s", %(value)s);%(varSetter)s
+}
+''', fname=fname, name=varname, type=argtype, value=value, varSetter=varSetter)
+
+
+def generate_object_type_getter(varname, vartype):
+    # Note: Object getters may always return null even if the member is not
+    # optional
+    rettype = vala_type(vartype)
+
+    return mcgen('''
+public %(rettype)s? get_%(name)s() {
+    var res = get_object_member("%(name)s");
+    if (res == null) {
+        return null;
+    }
+    return new %(rettype)s.from_json_object(res.get_internal_object());
+}
+''', rettype=rettype, name=varname)
+
+def generate_enum_type_getter(varname, vartype, optional):
+    rettype = vala_type(vartype)
+    if optional:
+        rettype += '?'
+    return mcgen('''
+public %(rettype)s get_%(name)s() {
+    return %(enum)s.from_string(get_string_member("%(name)s"));
+}
+''', rettype=rettype, enum=vartype, name=varname)
+
+def generate_map_type_getter(varname, vartype, optional):
+    mapType = map_type(vartype)
+    valaType = vala_type(mapType)
+    jsonType = vala_type_to_json_type(mapType)
+    if mapType in ('double', 'int', 'uint'):
+        valaType += '?'
+    if is_command_class(valaType):
+        mapObj = get_command_class_constructor_call(valaType, 'elem')
+    elif jsonType == 'object':
+        mapObj = "new %s.from_json_object(elem)" % valaType
+    else:
+        mapObj = "elem"
+    rettype = map_cast(vartype)
+    rettype += '?'
+    return mcgen('''
+public %(rettype)s get_%(name)s() {
+    var res = _obj.get_object_member("%(name)s");
+    if (res == null) {
+        return null;
+    }
+    var ret = new HashTable<string, %(type)s>(str_hash, str_equal);
+    var members = res.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        var elem = res.get_%(jsonType)s_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), %(mapObj)s);
+    }
+    return ret;
+}
+''', rettype=rettype, name=varname, type=valaType, jsonType=jsonType, mapObj=mapObj)
+
+def generate_type_getter(varname, vartype, optional):
+    if is_object(vartype):
+        return generate_object_type_getter(varname, vartype)
+    elif is_enum(vartype):
+        return generate_enum_type_getter(varname, vartype, optional)
+    elif is_map(vartype):
+        return generate_map_type_getter(varname, vartype, optional)
+    else:
+        rettype = vala_type(vartype)
+        fname = "get_%s_member" % type_as_function_name(vartype)
+        if optional:
+            rettype += '?'
+        return mcgen('''
+public %(rettype)s get_%(name)s() {
+    return (%(rettype)s) %(fname)s("%(name)s");
+}
+''', fname=fname, name=varname, rettype=rettype)
+
+def generate_type_setters_and_getters(name, fields):
+    ret = ""
+    for argname, argtype, optional, structured in parse_args(fields):
+        ret += generate_type_setter(argname, argtype)
+        ret += generate_type_getter(argname, argtype, optional)
+    return ret
+
+
+def generate_type(f, symbol):
+    ret = mcgen('''
+public class %(name)s : BaseType {
+''', name=symbol['type'])
+    push_indent()
+
+    ret += generate_type_constructor(symbol['type'], symbol['data'])
+    ret += generate_type_setters_and_getters(symbol['type'], symbol['data'])
+
+    pop_indent()
+    ret += cgen('''}''')
+    f.write(ret)
+
+
+def generate_union(f, symbol):
+    ret = mcgen('''
+public class %(name)s : BaseUnion {
+''', name=symbol['union'])
+
+    ret += mcgen('''
+public %(name)s.from_json_object(owned Json.Object? obj) {
+    base.from_json_object(obj);
+}
+''', name=symbol['union'])
+
+    for field in symbol['data']:
+        if not is_object(field):
+            raise ValueError("%s: Unions may only contain object types",
+                                symbol['union'])
+        ret += mcgen('''
+    public %(type)s to_%(type)s() {
+        return new %(type)s.from_json_object(get_internal_object());
+    }
+''', type=field)
+
+    ret += cgen('''}''')
+    f.write(ret)
+
+def generate_handle_command_parameter(argname, argtype, optional):
+    ret = ""
+
+    fname = "set_%s_member" % vala_type_to_json_type(argtype)
+
+    # Get the real type in case it was aliased
+    if not is_array(argtype) and is_alias(argtype):
+        argtype = aliased_types[argtype]
+
+    if is_array(argtype):
+        json_type = vala_type_to_json_type(argtype[0])
+        if json_type == 'object':
+            cast = '(BaseType[])'
+        else:
+            cast = ''
+        arg = "json_array_from_%ss(%s%s)" % (json_type, cast, argname)
+    elif is_object(argtype) or is_union(argtype):
+        arg = "%s.get_internal_object()" % argname
+    elif is_map(argtype):
+        arg = "json_object_from_%s(%s)" % \
+                (type_as_function_name(argtype), argname)
+    #elif is_union(argtype):
+    #    arg = "json_object_from_union((BaseType)%s)" % argname
+    else:
+        arg = argname
+    if (optional):
+        ret += mcgen('''
+if (%(argname)s != null) {
+''', argname=argname)
+        push_indent()
+    ret += mcgen('''
+object.%(fname)s("%(argname)s", %(arg)s);
+''', argname=argname, fname=fname, type=vala_type(argtype), arg=arg)
+    if (optional):
+        pop_indent()
+        ret += cgen('''}''')
+    return ret
+
+
+def get_command_class_constructor_call(commandClass, id_field=None):
+    params = class_types[commandClass]['data']
+    paramStr = ''
+    for (argname, argentry, optional, structured) in parse_args(params):
+        paramStr += argname + ', '
+    paramStr = paramStr[:-2]
+
+    # If id_field is given, replace the xxID field in params with the indicated
+    # in-scope variable reference
+    if id_field is not None:
+        paramStr = paramStr.replace('%sID' % commandClass.lower(), id_field)
+
+    return "new %s(%s)" % (commandClass, paramStr)
+
+def generate_command_array_ret(rettype):
+    valaType = vala_type(rettype)
+    jsonType = vala_type_to_json_type(rettype)
+    if is_command_class(valaType):
+        arrayObj = get_command_class_constructor_call(valaType, 'elem')
+    elif jsonType == 'object':
+        arrayObj = "new %s.from_json_object(elem)" % valaType
+    else:
+        arrayObj = "elem"
+    code = mcgen('''
+    var array = res.get_array_member("result");
+    var length = array.get_length();
+    %(type)s[] ret = new %(type)s*[length];
+
+    for (int i = 0; i < length; i++) {
+        var elem = array.get_%(jsonType)s_element(i);
+        ret[i] = %(arrayObj)s;
+    }
+    return ret;
+''', type=valaType, jsonType=jsonType, arrayObj=arrayObj)
+    return code
+
+#
+# TODO: Merge this with generate_map_type_getter
+#
+def generate_command_map_ret(rettype):
+    valaType = vala_type(rettype)
+    if rettype in ('double', 'int', 'uint'):
+        valaType += '?'
+    jsonType = vala_type_to_json_type(rettype)
+    if is_command_class(valaType):
+        mapObj = get_command_class_constructor_call(valaType, 'elem')
+    elif jsonType == 'object':
+        mapObj = "new %s.from_json_object(elem)" % valaType
+    else:
+        mapObj = "elem"
+    code = mcgen('''
+    var map = res.get_object_member("result");
+    var ret = new HashTable<string, %(type)s>(str_hash, str_equal);
+    var members = map.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        var elem = map.get_%(jsonType)s_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), %(mapObj)s);
+    }
+    return ret;
+}''', type=valaType, jsonType=jsonType, mapObj=mapObj)
+    return code
+
+def generate_command_ret(rettype):
+    getter = None
+    if rettype != 'void':
+        valaType = vala_type(rettype)
+        getter = 'res.get_%s_member("result")' % \
+                    vala_type_to_json_type(rettype)
+    else:
+        valaType = 'void'
+
+    if is_object (valaType) or is_union (valaType):
+        retval = '(%s) new %s.from_json_object(%s)' % \
+                    (valaType, valaType, getter)
+    elif is_map (valaType):
+        retval = '(%s) %s_from_json(%s)' % \
+                (valaType, type_as_function_name(valaType), getter)
+    elif is_command_class(valaType):
+        retval = get_command_class_constructor_call(valaType, getter)
+    elif valaType == 'void':
+        retval = ''
+    else:
+        retval = '(%s) %s' % (valaType, getter)
+
+    return mcgen('''
+    return %(retval)s;
+''', retval=retval)
+
+
+def generate_command(f, command):
+    args = command.get('data',{})
+    rettype = command.get('returns', 'void')
+
+    name = command['command']['name']
+    # Overriding GObject method names must add the 'new' keyword
+    new = ''
+    if name in ('connect', 'disconnect'):
+        new = 'new'
+
+    arglist = marshall_arg_list(args, rettype)
+    if rettype != 'void':
+        if is_map(rettype):
+            retcast = map_cast(rettype)
+        else:
+            retcast = vala_type(rettype)
+    else:
+        retcast = 'void'
+
+    push_indent()
+    ret = mcgen('''
+public %(new)s %(retcast)s %(name)s(%(args)s) throws VdsmError {
+    var object = new Json.Object();
+    var base_type = (BaseType) this;
+''', new=new, retcast=retcast, name=name, args=arglist)
+
+    push_indent()
+    # Set parameters for this object
+    ret += cgen('''
+object.set_object_member("__obj__", base_type.get_internal_object());''')
+
+    # Set normal parmeters
+    for argname, argtype, optional, structured in parse_args(args):
+        ret += generate_handle_command_parameter(argname, argtype, optional)
+    pop_indent()
+
+    # Send command
+    methodname = "%s.%s" % (command['command']['class'], command['command']['name'])
+    if rettype == 'void':
+        return_stmt = 'return;'
+    else:
+        return_stmt = 'throw new VdsmError.INTERNAL_ERROR' \
+                        '("The response contained no data");'
+    ret += mcgen('''
+    var res = this.send_command("%(methodname)s", object);
+    response_check_error(res);
+    if (res.get_null_member("result"))
+        %(return_stmt)s
+''', methodname=methodname, return_stmt=return_stmt)
+
+    # return result
+    if is_array (rettype):
+        ret += generate_command_array_ret(rettype[0])
+    elif is_map (rettype):
+        ret += generate_command_map_ret(map_type(rettype))
+    else:
+        ret += generate_command_ret(rettype)
+    ret += cgen('''}''')
+    f.write(ret)
+    pop_indent()
+
+def generate_comand_class_constructor(name, fields):
+    arglist = marshall_arg_list(fields)
+
+    ret = mcgen('''
+public %(name)s(%(arglist)s) {
+''',  name=vala_fun(name), arglist=arglist)
+
+    push_indent()
+    ret += cgen('''
+base(conn);
+this.conn = conn;
+''')
+    for argname, argtype, optional, structured in parse_args(fields):
+        if argname == 'conn':
+            # We already dealt with 'conn'
+            continue
+        ret += generate_type_constructor_field_setter(argname, argtype,
+                                                              optional)
+    pop_indent()
+
+    ret += cgen('''}''')
+
+    return ret
+
+
+def generate_command_class(f, class_name, class_info):
+    ret = mcgen('''
+public class %(name)s : BaseCommandClass {
+''', name=class_name)
+
+    push_indent()
+
+    ##
+    # All classes have these implicit parameters:
+    #   conn - A vdsm Host object that contains the connection to the server
+    #   xxID - A UUID associated with this object.  It is prefixed with the
+    #          object type to ease automatic construction of other objects from
+    #          this one.
+    #
+    # XXX: This is broken!  Auto-construction of objects is not always correct.
+    # Sometimes we do not have all of the xxIDs we need to properly construct an
+    # object.  See StorageDomain.getImages for an example.
+    ###
+    ctor_args = OrderedDict()
+    ctor_args['conn'] = 'Host'
+    ctor_args['%sID' % class_name.lower()] = 'UUID'
+    ctor_args.update(class_info.get('data', {}))
+
+    for argname, argtype, optional, structured in parse_args(ctor_args):
+        ret += mcgen('''
+private %(argtype)s %(argname)s;
+''', argtype=vala_type(argtype), argname=argname)
+
+    ret += generate_comand_class_constructor(class_name, ctor_args)
+    # We don't want a getter/setter for 'conn'
+    del ctor_args['conn']
+    for argname, argtype, optional, structured in parse_args(ctor_args):
+        ret += generate_type_setter(argname, argtype, setVar=True)
+        ret += generate_type_getter(argname, argtype, optional)
+
+    pop_indent()
+    f.write(ret)
+
+    for c in class_info['commands']:
+        print "Command: %s" % c['command']['name']
+        generate_command(f, c)
+
+    f.write(cgen('''}'''))
+
+
+def generate_commands(f, command_list):
+    # Generate the VDSM base class separately because it is special
+    ret = cgen('''
+public class Host : BaseCommandClass {
+    private Host conn;
+    public Host(string hostname, uint16 port, bool sync_mode) {
+        RPC rpc = new RPC(hostname, port, sync_mode);
+        base.forHost(rpc);
+        conn = this;
+    }
+    public new void connect() throws VdsmError {
+        try {
+            this._rpc.connect_host();
+        } catch (GLib.Error e) {
+            throw new VdsmError.CONNECTION_ERROR(e.message);
+        }
+    }
+    public new void disconnect() {
+        this._rpc.disconnect_host();
+    }
+    public bool is_connected() {
+        return !this._rpc.is_closed();
+    }
+    public int get_fd() {
+        return this._rpc.get_fd();
+    }
+
+    public void process() {
+        this._rpc.process_incoming_messages();
+    }
+''')
+    f.write(ret)
+
+    # Generate the commands for the Host base class
+    for command in class_types['Host']['commands']:
+        print "%s.%s" % (command['command']['class'], command['command']['name'])
+        generate_command(f, command)
+
+    # Close out the VDSM base class
+    ret = cgen('''}''')
+    f.write(ret)
+
+    del class_types['Host']
+    for k, v in class_types.items():
+        print "Class: %s Data: %s" % (k, v.get('data'))
+        generate_command_class(f, k, v)
+
+
+def generate_file_top(f):
+    f.write(cgen('''
+/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
+
+/*
+ * Copyright (C) 2012 Adam Litke, IBM Corporation
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Refer to the README and COPYING files for full details of the license
+ */
+
+ namespace vdsm {
+'''))
+
+
+def generate_file_bottom(f):
+    f.write(cgen('''} /* namespace vdsm */'''))
+
+
+################################################################################
+indent_level = 0
+class_types = {}
+enum_types = []
+object_types = ['Host',]
+union_types = []
+map_types = {}
+aliased_types = {}
+
+
+errors = [
+    {'code': 1, 'name': 'PROTOCOL_ERROR', 'message': ''},
+    {'code': 2, 'name': 'CONNECTION_ERROR', 'message': ''},
+    {'code': 3, 'name': 'OPERATION_CANCELLED', 'message': ''},
+    {'code': 4, 'name': 'OPERATION_NOT_SUPPORTED', 'message': ''},
+    {'code': 5, 'name': 'INTERNAL_ERROR', 'message': ''},
+    {'code': 666, 'name': 'GENERAL_ERROR', 'message': ''},
+]
+
+
+schema = 'vdsmapi-schema.json'
+with open(schema) as f:
+    symbols = vdsmapi.parse_schema(f)
+
+
+for s in symbols:
+    if 'enum' in s:
+        enum_types.append(s['enum'])
+    elif 'type' in s and isinstance(s['data'], OrderedDict):
+        object_types.append(s['type'])
+    elif 'map' in s:
+        map_types[s['map']] = {'key': s['key'], 'value': s['value']}
+    elif 'alias' in s:
+        aliased_types[s['alias']] = s['data']
+    elif 'union' in s:
+        union_types.append(s['union'])
+
+try:
+    f = open("generated.vala", "w")
+    generate_file_top(f)
+    errors.extend([s for s in symbols if 'error' in s])
+    types = [s for s in symbols if 'type' in s]
+    unions = [s for s in symbols if 'union' in s]
+    enums = [s for s in symbols if 'enum' in s]
+    commands = [s for s in symbols if 'command' in s]
+    classes = [s for s in symbols if 'class' in s]
+    generate_errors(f, errors)
+
+    f.write("/* Enum types */\n")
+    for e in enums:
+        generate_enum(f, e)
+
+    f.write("/* Object types */\n")
+    for t in types:
+        print t['type']
+        generate_type(f, t)
+
+    f.write("/* Union types */\n")
+    for u in unions:
+        generate_union(f, u)
+
+    for cmd in commands:
+        cls = cmd['command']['class']
+        if cls not in class_types:
+            class_types[cls] = {}
+        if 'commands' not in class_types[cls]:
+            class_types[cls]['commands'] = [cmd,]
+        else:
+            class_types[cls]['commands'].append(cmd)
+    for cls in classes:
+        name = cls['class']
+        if 'data' in cls:
+            class_types[name]['data'] = cls['data']
+
+    f.write("/* Command Classes */\n")
+    generate_commands(f, commands)
+
+    generate_file_bottom(f)
+    f.close()
+except:
+    os.unlink("generated.vala")
+    raise
+
diff --git a/vdsm_api/gir-fixes.patch b/vdsm_api/gir-fixes.patch
new file mode 100644
index 0000000..881c5f2
--- /dev/null
+++ b/vdsm_api/gir-fixes.patch
@@ -0,0 +1,155 @@
+--- vdsm-0.1.gir.orig	2012-08-08 07:45:24.430402843 -0500
++++ vdsm-0.1.gir	2012-08-08 07:49:53.882407762 -0500
+@@ -919,7 +919,7 @@
+ 		</method>
+ 		<method name="get_boolean_array_member" c:identifier="vdsm_base_type_get_boolean_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="gboolean" c:type="gboolean"/>
+ 				</array>
+ 			</return-value>
+@@ -975,7 +975,7 @@
+ 		</method>
+ 		<method name="get_double_array_member" c:identifier="vdsm_base_type_get_double_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="gdouble" c:type="gdouble"/>
+ 				</array>
+ 			</return-value>
+@@ -1031,7 +1031,7 @@
+ 		</method>
+ 		<method name="get_int_array_member" c:identifier="vdsm_base_type_get_int_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="glong" c:type="glong"/>
+ 				</array>
+ 			</return-value>
+@@ -1087,7 +1087,7 @@
+ 		</method>
+ 		<method name="get_string_array_member" c:identifier="vdsm_base_type_get_string_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -1143,7 +1143,7 @@
+ 		</method>
+ 		<method name="get_object_array_member" c:identifier="vdsm_base_type_get_object_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="vdsm.BaseType" c:type="vdsmBaseType*"/>
+ 				</array>
+ 			</return-value>
+@@ -1222,7 +1222,7 @@
+ 		</method>
+ 		<method name="get_union_array_member" c:identifier="vdsm_base_type_get_union_array_member">
+ 			<return-value transfer-ownership="full" allow-none="1">
+-				<array length="0">
++				<array length="1">
+ 					<type name="vdsm.BaseUnion" c:type="vdsmBaseUnion*"/>
+ 				</array>
+ 			</return-value>
+@@ -13546,7 +13546,7 @@
+ 		</method>
+ 		<method name="getDeviceList" c:identifier="vdsm_api_getDeviceList" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="1">
+ 					<type name="vdsm.BlockDeviceInfo" c:type="vdsmBlockDeviceInfo*"/>
+ 				</array>
+ 			</return-value>
+@@ -13576,7 +13576,7 @@
+ 		</method>
+ 		<method name="getLVMVolumeGroups" c:identifier="vdsm_api_getLVMVolumeGroups" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="1">
+ 					<type name="vdsm.VolumeGroupInfo" c:type="vdsmVolumeGroupInfo*"/>
+ 				</array>
+ 			</return-value>
+@@ -13596,7 +13596,7 @@
+ 		</method>
+ 		<method name="getStorageDomains" c:identifier="vdsm_api_getStorageDomains" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="4">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -13625,7 +13625,7 @@
+ 		</method>
+ 		<method name="getVMList" c:identifier="vdsm_api_getVMList" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="3">
+ 					<type name="vdsm.VmDefinition" c:type="vdsmVmDefinition*"/>
+ 				</array>
+ 			</return-value>
+@@ -14762,7 +14762,7 @@
+ 		</method>
+ 		<method name="getVolumes" c:identifier="vdsm_storage_domain_getVolumes" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="1">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -14983,7 +14983,7 @@
+ 		</method>
+ 		<method name="connectStorageServer" c:identifier="vdsm_storage_pool_connectStorageServer" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="3">
+ 					<type name="vdsm.ConnectStorageServerStatus" c:type="vdsmConnectStorageServerStatus*"/>
+ 				</array>
+ 			</return-value>
+@@ -15071,7 +15071,7 @@
+ 		</method>
+ 		<method name="disconnectStorageServer" c:identifier="vdsm_storage_pool_disconnectStorageServer" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="3">
+ 					<type name="vdsm.ConnectStorageServerStatus" c:type="vdsmConnectStorageServerStatus*"/>
+ 				</array>
+ 			</return-value>
+@@ -15117,7 +15117,7 @@
+ 		</method>
+ 		<method name="getBackedUpVmsList" c:identifier="vdsm_storage_pool_getBackedUpVmsList" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="1">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -15144,7 +15144,7 @@
+ 		</method>
+ 		<method name="getDomainsContainingImage" c:identifier="vdsm_storage_pool_getDomainsContainingImage" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="2">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -15162,7 +15162,7 @@
+ 		</method>
+ 		<method name="getIsoList" c:identifier="vdsm_storage_pool_getIsoList" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="1">
+ 					<type name="utf8" c:type="gchar*"/>
+ 				</array>
+ 			</return-value>
+@@ -15297,7 +15297,7 @@
+ 		</method>
+ 		<method name="validateStorageServerConnection" c:identifier="vdsm_storage_pool_validateStorageServerConnection" throws="1">
+ 			<return-value transfer-ownership="full">
+-				<array length="0">
++				<array length="3">
+ 					<type name="vdsm.StorageServerConnectionValidateStatus" c:type="vdsmStorageServerConnectionValidateStatus*"/>
+ 				</array>
+ 			</return-value>
diff --git a/vdsm_api/libvdsm-base.vala b/vdsm_api/libvdsm-base.vala
new file mode 100644
index 0000000..ec8b368
--- /dev/null
+++ b/vdsm_api/libvdsm-base.vala
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 2012 Adam Litke, IBM Corporation
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Refer to the README and COPYING files for full details of the license
+ */
+using Json;
+using Gee;
+
+namespace vdsm {
+
+delegate void SyncProcess();
+
+private class Request : GLib.Object {
+    private Json.Object? _result;
+    private Mutex _mtx;
+    private Cond _cond;
+    public bool finished {get; private set;}
+
+    public Request() {
+        this._result = null;
+        this.finished = false;
+    }
+
+    public bool wait(SyncProcess? fn) {
+        this._mtx.lock();
+        try {
+            if (this.finished) {
+                return true;
+            }
+
+            if (fn != null) {
+                this._mtx.unlock();
+                fn();
+                this._mtx.lock();
+                return this.finished;
+            } else {
+                this._cond.wait(this._mtx);
+                return true;
+            }
+        } finally {
+            this._mtx.unlock();
+        }
+    }
+
+    public void set_result(owned Json.Object? res) {
+        this._mtx.lock();
+        if (this.finished) {
+            return;
+        }
+
+        this._result = res;
+        this.finished = true;
+        this._cond.broadcast();
+        this._mtx.unlock();
+    }
+
+    public Json.Object? get_result() {
+        this._mtx.lock();
+        try {
+            return this._result;
+        } finally {
+        this._mtx.unlock();
+        }
+    }
+}
+
+protected class RPC : GLib.Object {
+    public string hostname {get; private set;}
+    public uint16 port {get; private set;}
+    private uint64 max_message_len {get; set;}
+    private int _reqid;
+    private AbstractMap<int, Request> _pending_requests;
+    private SocketClient _sock;
+    private SocketConnection? _conn;
+    private DataOutputStream? _out;
+    private DataInputStream? _in;
+    private bool sync_mode;
+
+    [CCode(notify = false)]
+    public bool closed {
+        get {
+            return this.is_closed();
+        }
+    }
+
+    // TODO: Support TLS
+    public RPC(string hostname, uint16 port, bool sync_mode) {
+        this.hostname = hostname;
+        this.port = port;
+        this._sock = new SocketClient();
+        this._reqid = 0;
+        // TBD: Is 1MB enough?
+        this.max_message_len = (uint64) (Math.pow(2, 20));
+        this._pending_requests = new HashMap<int, Request>();
+        this.sync_mode = sync_mode;
+    }
+
+    public bool is_closed() {
+        lock(this._conn) {
+            if (this._conn == null) {
+                return true;
+            }
+
+            return this._conn.closed;
+        }
+    }
+
+
+    public void connect_host() throws GLib.Error {
+        var resolver = Resolver.get_default();
+        var addresses = resolver.lookup_by_name(hostname, null);
+        var address = addresses.nth_data (0);
+        var end_point = new InetSocketAddress(address, port);
+
+        lock(this._conn) {
+        lock(this._in) {
+        lock(this._out) {
+            if (this._conn != null) {
+                if (!this._conn.closed) {
+                    // Already connected
+                    return;
+                }
+            }
+
+            this._conn = this._sock.connect(end_point);
+            this._out = new DataOutputStream(this._conn.output_stream);
+            this._in = new DataInputStream(this._conn.input_stream);
+        }}}
+    }
+
+    public void disconnect_host() {
+        lock(this._pending_requests) {
+        lock(this._conn) {
+            foreach(var req in this._pending_requests) {
+                if (req == null) {
+                    continue;
+                }
+
+                if (req.finished) {
+                    continue;
+                }
+
+                req.set_result(null);
+            }
+
+            if (this._conn == null) {
+                // Not connected
+                return;
+            }
+
+            try {
+                this._conn.close();
+            } catch {
+                // Stream already closed, don't announce state change
+                return;
+            }
+
+        }}
+    }
+
+    private int get_request_id() {
+        lock(_reqid) {
+            _reqid++;
+            var id = _reqid;
+            return id;
+        }
+    }
+
+    private string _build_request(int64 id,
+            string methodName, Json.Object? args) {
+
+        var gen = new Json.Generator();
+        var root = new Json.Node(NodeType.OBJECT);
+        var object = new Json.Object();
+
+        root.set_object(object);
+        gen.set_root(root);
+        object.set_int_member("id", id);
+        object.set_string_member("methodName", methodName);
+        if (args == null) {
+            object.set_null_member("args");
+        } else {
+            object.set_object_member("args", args);
+        }
+
+        return gen.to_data(null);
+
+    }
+
+    public Json.Object? send_command(string methodName, Json.Object? args)
+                                        throws GLib.Error, GLib.IOError {
+        var id = this.get_request_id();
+        var json = this._build_request(id, methodName, args);
+
+        var req_token = new Request();
+        lock(this._pending_requests) {
+            this._pending_requests[id] = req_token;
+        }
+
+        try {
+            this._send_message(json);
+        } catch (GLib.IOError e) {
+            lock(this._pending_requests) {
+                this._pending_requests.unset(id);
+                req_token.set_result(null);
+            }
+
+            throw e;
+        }
+
+        if (this.sync_mode) {
+            req_token.wait(process_incoming_messages);
+        } else {
+            req_token.wait(null);
+        }
+        Json.Object? res = null;
+        lock(this._pending_requests) {
+            if (req_token.finished) {
+                res = req_token.get_result();
+            } else {
+                throw new VdsmError.OPERATION_CANCELLED("Operation was cancelled");
+            }
+
+            this._pending_requests.unset(id);
+        }
+
+        return res;
+    }
+
+    // Sends a complete message in a thread safe manner
+    private void _send_message(string msg) throws GLib.IOError, GLib.Error {
+        lock(this._out) {
+            if (this._out == null) {
+                throw new IOError.CLOSED("Not connected to host");
+            }
+
+            _out.put_uint64(msg.length);
+            _out.put_string(msg);
+            _out.flush();
+        }
+    }
+
+    // Reads a complete message in a thread safe manner
+    private string _read_message() throws GLib.Error, GLib.IOError {
+        lock(this._in) {
+            if (this._in == null) {
+                throw new IOError.CLOSED("Not connected to host");
+            }
+
+            var size = _in.read_uint64();
+            if (size > this.max_message_len) {
+                // FIXME: Return error instead of disconnect so the client
+                // knows what happened. Also, read the message but don't store
+                // or parse it so we don't go out of sync.
+                this.disconnect_host();
+                // TODO: Better error
+                throw new IOError.FAILED(
+                        "Requested message size is too large");
+            }
+            var bread = size;
+            var buff = new uint8[size + 1];
+            // Make sure buffer is null terminated
+            buff[size] = '\0';
+
+            var ok = _in.read_all(buff, out bread);
+            // The only reason read_all fails without an error is if the stream
+            // ended.
+            if (!ok) {
+                throw new IOError.CLOSED("Connection droped unexpectedly");
+            }
+            // Safe because we made sure it's null terminated
+            return (string) buff;
+        }
+    }
+
+    public void process_incoming_messages() {
+        Json.Object resp;
+        try {
+            var msg = _read_message();
+            resp = json_parse(msg);
+        } catch {
+            // If we didn't get a proper json object, we are either connected
+            // to something that isn't vdsm or we are out of sync.
+            this.disconnect_host();
+            return;
+        }
+
+        if (resp.get_null_member("id")) {
+            return;
+            // TODO: events
+        }
+        var reqId = (int) resp.get_int_member("id");
+        lock(this._pending_requests) {
+            if (this._pending_requests.has_key(reqId)) {
+                var req = this._pending_requests[reqId];
+                if (req != null) {
+                    req.set_result(resp);
+                }
+            }
+        }
+    }
+
+    public int get_fd() {
+        return this._conn.get_socket().fd;
+    }
+
+    public Json.Object json_parse(string json) throws GLib.Error {
+        var parser = new Json.Parser();
+        parser.load_from_data(json);
+        return parser.get_root().get_object();
+    }
+}
+
+public unowned string?
+enum_to_string(string[] types, int val)
+{
+    if (val < 0 || val >= types.length)
+        return null;
+
+    return types[val];
+}
+
+public int
+enum_from_string(string[] types, string val)
+{
+    int i;
+    if (val == null)
+        return -1;
+
+    for (i = 0 ; i < types.length ; i++)
+        if (types[i] == val)
+            return i;
+
+    return -1;
+}
+
+public class BaseCommandClass : vdsm.BaseType {
+    protected RPC _rpc;
+
+    public BaseCommandClass(vdsm.Host conn) {
+        base();
+        this._rpc = conn._rpc;
+    }
+
+    protected BaseCommandClass.forHost(RPC rpc) {
+        base();
+        this._rpc = rpc;
+    }
+
+    public Json.Object send_command(string methodname, Json.Object? args)
+                                        throws VdsmError {
+        try {
+            return this._rpc.send_command(methodname, args);
+        } catch {
+            throw new VdsmError.PROTOCOL_ERROR(
+                    "problem communicating with host");
+        }
+    }
+}
+
+VdsmError vdsm_error_from_json(Json.Object obj) {
+    var code = obj.get_int_member("code");
+    var msg = obj.get_string_member("message");
+    if (code == 1) {
+        return new VdsmError.PROTOCOL_ERROR(msg);
+    } else if (code == 2) {
+        return new VdsmError.CONNECTION_ERROR(msg);
+    } else if (code == 3) {
+        return new VdsmError.OPERATION_CANCELLED(msg);
+    } else {
+        return new VdsmError.GENERAL_ERROR(msg);
+    }
+}
+
+void response_check_error(Json.Object res) throws VdsmError {
+    var errObj = res.get_object_member("error");
+    if (errObj != null) {
+        if (errObj.get_int_member("code") != 0)
+            throw vdsm_error_from_json(errObj);
+    }
+}
+
+public class BaseUnion : GLib.Object {
+    protected Json.Object _obj;
+
+    public BaseUnion.from_json_object(owned Json.Object? obj) {
+        if (obj == null) {
+            this._obj = new Json.Object();
+        } else {
+            this._obj = obj;
+        }
+    }
+
+    public BaseUnion() {
+        this._obj = new Json.Object();
+    }
+
+    internal virtual Json.Object get_internal_object() {
+        return this._obj;
+    }
+}
+
+public class BaseType : GLib.Object {
+    protected Json.Object _obj;
+
+    private void debug_pretty_print() {
+        var node = new Json.Node(Json.NodeType.OBJECT);
+        node.set_object(get_internal_object());
+        var gen = new Json.Generator();
+        gen.set_root(node);
+        stdout.printf("%s\n", gen.to_data(null));
+    }
+
+    public BaseType.from_json_object(owned Json.Object? obj) {
+        base();
+        if (obj == null) {
+            this._obj = new Json.Object();
+        } else {
+            this._obj = obj;
+        }
+        //debug_pretty_print();
+    }
+
+    public BaseType() {
+        base();
+        this._obj = new Json.Object();
+    }
+
+    internal Json.Object get_internal_object() {
+        return this._obj;
+    }
+
+    public bool get_null_member(string member_name) {
+        return _obj.get_null_member(member_name);
+    }
+
+    public bool has_member(string member_name) {
+        return _obj.has_member(member_name);
+    }
+
+    /*
+     * Supported types: boolean, double, int, uint, string, BaseType (object),
+     *                  map, enum
+     *
+     * For each supported type we implement:
+     * - get:       Return the object from the internal JSON object
+     * - set:       Set the value in the internal JSON object
+     * - array_get: Retrurn an array of values from the internal JSON object
+     * - array_set: Set an array of valued in the internal JSON object
+     */
+
+    /*
+     * Boolean
+     */
+    public bool? get_boolean_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+
+        /* XXX: handle evil booleans from vdsm */
+        Json.Node node = _obj.get_member(member_name);
+        if (node.get_value_type() == typeof(string))
+            return parse_evil_bool(node.get_string());
+        else
+            return node.get_boolean();
+
+        /*
+         * This is what the method should look like with the hack removed
+        return _obj.get_boolean_member(member_name);
+         */
+    }
+
+    public void set_boolean_member(string member_name, bool value) {
+        _obj.set_boolean_member(member_name, value);
+    }
+
+    public bool[]? get_boolean_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        array = _obj.get_array_member(member_name);
+        return boolean_array_from_json(array);
+    }
+
+    public void set_boolean_array_member(string member_name, bool[] data) {
+        _obj.set_array_member(member_name, json_array_from_booleans(data));
+    }
+
+    private bool parse_evil_bool(string evil_bool) {
+        if (evil_bool == "True" || evil_bool == "true" ||
+            evil_bool == "on" || evil_bool == "yes")
+            return true;
+        else
+            return false;
+    }
+
+    /*
+     * Double
+     */
+    public double? get_double_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+
+        /* XXX: handle evil floats from vdsm */
+        Json.Node node = _obj.get_member(member_name);
+        if (node.get_value_type() == typeof(string))
+            return double.parse(node.get_string());
+        else
+            return node.get_double();
+
+        /*
+         * This is what the method should look like with the hack removed
+        return _obj.get_double_member(member_name);
+         */
+    }
+
+    public void set_double_member(string member_name, double value) {
+        _obj.set_double_member(member_name, value);
+    }
+
+    public double[]? get_double_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        array = _obj.get_array_member(member_name);
+        return double_array_from_json(array);
+    }
+
+    public void set_double_array_member(string member_name, double[] data) {
+        _obj.set_array_member(member_name, json_array_from_doubles(data));
+    }
+
+    /*
+     * Int
+     */
+    public int64? get_int_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+
+        /* XXX: handle evil ints from vdsm */
+        Json.Node node = _obj.get_member(member_name);
+        if (node.get_value_type() == typeof(string))
+            return int64.parse(node.get_string());
+        else
+            return (int64) node.get_int();
+
+        /*
+         * This is what the method should look like with the hack removed
+        return (int64) _obj.get_int_member(member_name);
+         */
+    }
+
+    public void set_int_member(string member_name, int64 value) {
+        _obj.set_int_member(member_name, value);
+    }
+
+    public int64[]? get_int_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        array = _obj.get_array_member(member_name);
+        return int_array_from_json(array);
+    }
+
+    public void set_int_array_member(string member_name, int64[] data) {
+        _obj.set_array_member(member_name, json_array_from_ints(data));
+    }
+
+    /*
+     * Uint
+     */
+    public uint64? get_uint_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+
+        /* XXX: handle evil ints from vdsm */
+        Json.Node node = _obj.get_member(member_name);
+        if (node.get_value_type() == typeof(string))
+            return uint64.parse(node.get_string());
+        else
+            return (uint64) node.get_int();
+
+        /*
+         * This is what the method should look like with the hack removed
+        return (uint64) _obj.get_int_member(member_name);
+         */
+    }
+
+    public void set_uint_member(string member_name, uint64 value) {
+        _obj.set_int_member(member_name, (int64)value);
+    }
+
+    public uint64[]? get_uint_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        array = _obj.get_array_member(member_name);
+        return uint_array_from_json(array);
+    }
+
+    public void set_uint_array_member(string member_name, uint64[] data) {
+        _obj.set_array_member(member_name, json_array_from_uints(data));
+    }
+
+    /*
+     * String
+     */
+    public string? get_string_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+        return _obj.get_string_member(member_name);
+    }
+
+    public void set_string_member(string member_name, string value) {
+        _obj.set_string_member(member_name, value);
+    }
+
+    public string[]? get_string_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        array = _obj.get_array_member(member_name);
+        return string_array_from_json(array);
+    }
+
+    public void set_string_array_member(string member_name, string[] data) {
+        _obj.set_array_member(member_name, json_array_from_strings(data));
+    }
+
+    public HashTable<string, string>?
+    get_string_map_member(string member_name) {
+        Json.Object object;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        object = _obj.get_object_member(member_name);
+        return string_map_from_json(object);
+    }
+
+    public void set_string_map_member(string member_name,
+                                       HashTable<string, string> map) {
+        _obj.set_object_member(member_name, json_object_from_string_map(map));
+    }
+
+    /*
+     * BaseType (object)
+     */
+    public BaseType? get_object_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+        return new BaseType.from_json_object(_obj.get_object_member(member_name));
+    }
+
+    public void set_object_member(string member_name, BaseType value) {
+        _obj.set_object_member(member_name, value.get_internal_object());
+    }
+
+    public BaseType[]? get_object_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+        array = _obj.get_array_member(member_name);
+        return object_array_from_json(array);
+    }
+
+    public void set_object_array_member(string member_name, BaseType[] data) {
+        _obj.set_array_member(member_name, json_array_from_objects(data));
+    }
+
+    public HashTable<string, BaseType>?
+    get_object_map_member(string member_name) {
+        Json.Object object;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        object = _obj.get_object_member(member_name);
+        return object_map_from_json(object);
+    }
+
+    public void set_object_map_member(string member_name,
+                                       HashTable<string, BaseType> map) {
+        _obj.set_object_member(member_name, json_object_from_object_map(map));
+    }
+
+    /*
+     * Union: Unions are like BaseObjects except they can contain one of several
+     * different types of objects.
+     */
+    public BaseUnion? get_union_member(string member_name) {
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+        return new BaseUnion.from_json_object(_obj.get_object_member(member_name));
+    }
+
+    public void set_union_member(string member_name, BaseUnion value) {
+        _obj.set_object_member(member_name, value.get_internal_object());
+    }
+
+    public BaseUnion[]? get_union_array_member(string member_name) {
+        Json.Array array;
+        if (!has_member(member_name) || get_null_member(member_name))
+                return null;
+        array = _obj.get_array_member(member_name);
+        return union_array_from_json(array);
+    }
+
+    public void set_union_array_member(string member_name, BaseUnion[] data) {
+        _obj.set_array_member(member_name, json_array_from_unions(data));
+    }
+
+    public HashTable<string, BaseUnion>?
+    get_union_map_member(string member_name) {
+        Json.Object object;
+        if (!has_member(member_name) || get_null_member(member_name))
+            return null;
+        object = _obj.get_object_member(member_name);
+        return union_map_from_json(object);
+    }
+
+    public void set_union_map_member(string member_name,
+                                       HashTable<string, BaseUnion> map) {
+        _obj.set_object_member(member_name, json_object_from_union_map(map));
+    }
+
+    /*
+     * Enum
+     */
+    public int64[]? get_enum_array_member(string member_name) {
+        /*
+         * XXX: We need an instance of the proper enum to be able to call the
+         * from_string method
+         */
+        return null;
+    }
+
+    public void set_enum_array_member(string member_name, int64[] data) {
+        /*
+         * XXX: We need to know the type to be able to convert the ints to
+         * strings
+         */
+    }
+
+    public HashTable<string, int64?>?
+    get_enum_map_member(string member_name) {
+        /*
+         * XXX: We need an instance of the proper enum to be able to call the
+         * from_string method
+         */
+        return null;
+    }
+
+    public void set_enum_map_member(string member_name,
+                                       HashTable<string, int64?> map) {
+        /*
+         * XXX: We need to know the type to be able to convert the ints to
+         * strings
+         */
+    }
+}
+
+/*
+ * Convert between Vala arrays and Json arrays for the supported types
+ */
+
+/*
+ * Boolean
+ */
+bool[] boolean_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    bool[] ret = new bool[length];
+
+    for (int i = 0; i < length; i++)
+        ret[i] = array.get_boolean_element(i);
+    return ret;
+}
+
+Json.Array json_array_from_booleans(bool[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++) {
+        array.add_boolean_element (data[i]);
+    }
+    return array;
+}
+
+HashTable<string, bool> boolean_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, bool>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        bool value = object.get_boolean_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), value);
+    }
+    return ret;
+}
+
+Json.Object json_object_from_boolean_map(HashTable<string, bool> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_boolean_member(key, map.get(key));
+    }
+    return ret;
+}
+
+/*
+ * Double
+ */
+double[] double_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    double[] ret = new double[length];
+
+    for (int i = 0; i < length; i++)
+        ret[i] = array.get_double_element(i);
+    return ret;
+}
+
+Json.Array json_array_from_doubles(double[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++) {
+        array.add_double_element (data[i]);
+    }
+    return array;
+}
+
+HashTable<string, double?> double_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, double?>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        double value = object.get_double_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), value);
+    }
+    return ret;
+}
+
+Json.Object json_object_from_double_map(HashTable<string, double?> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_double_member(key, map.get(key));
+    }
+    return ret;
+}
+
+/*
+ * Int
+ */
+int64[] int_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    int64[] ret = new int64[length];
+
+    for (int i = 0; i < length; i++)
+        ret[i] = (int64)array.get_int_element(i);
+    return ret;
+}
+
+Json.Array json_array_from_ints(int64[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++) {
+        array.add_int_element (data[i]);
+    }
+    return array;
+}
+
+HashTable<string, int64?> int_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, int64?>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        int64 value = object.get_int_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), value);
+    }
+    return ret;
+}
+
+Json.Object json_object_from_int_map(HashTable<string, int64?> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        var val = map.get(key);
+        ret.set_int_member(key, val);
+    }
+    return ret;
+}
+
+/*
+ * Uint
+ */
+uint64[] uint_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    uint64[] ret = new uint64[length];
+
+    for (int i = 0; i < length; i++)
+        ret[i] = (uint64)array.get_int_element(i);
+    return ret;
+}
+
+Json.Array json_array_from_uints(uint64[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++) {
+        array.add_int_element ((int64)data[i]);
+    }
+    return array;
+}
+
+HashTable<string, uint64?> uint_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, uint64?>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        uint64 value = object.get_int_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), value);
+    }
+    return ret;
+}
+
+Json.Object json_object_from_uint_map(HashTable<string, uint64?> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_int_member(key, (int64)map.get(key));
+    }
+    return ret;
+}
+
+/*
+ * String
+ */
+string[] string_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    string[] ret = new string[length];
+
+    for (int i = 0; i < length; i++)
+        ret[i] = array.get_string_element(i);
+    return ret;
+}
+
+Json.Array json_array_from_strings(string[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++) {
+        array.add_string_element(data[i]);
+    }
+    return array;
+}
+
+HashTable<string, string> string_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, string>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        string value = object.get_string_member(members.nth_data(i));
+        ret.insert(members.nth_data(i), value);
+    }
+    return ret;
+}
+
+Json.Object json_object_from_string_map(HashTable<string, string> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_string_member(key, map.get(key));
+    }
+    return ret;
+}
+
+/*
+ * BaseType (object)
+ */
+BaseType[] object_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    BaseType[] ret = new BaseType*[length];
+
+    for (int i = 0; i < length; i++) {
+        Json.Object jsonobj = array.get_object_element(i);
+        ret[i] = new BaseType.from_json_object(jsonobj);
+    }
+    return ret;
+}
+
+Json.Array json_array_from_objects(BaseType[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++)
+        array.add_object_element(data[i].get_internal_object());
+    return array;
+}
+
+HashTable<string, BaseType> object_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, BaseType>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        ret.insert(members.nth_data(i),
+                    new BaseType.from_json_object(object.get_object_member(members.nth_data(i))));
+    }
+    return ret;
+}
+
+Json.Object json_object_from_object_map(HashTable<string, BaseType> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_object_member(key,  map.get(key).get_internal_object());
+    }
+    return ret;
+}
+
+/*
+ * Unions
+ */
+BaseUnion[] union_array_from_json(Json.Array array) {
+    var length = array.get_length();
+    BaseUnion[] ret = new BaseUnion*[length];
+
+    for (int i = 0; i < length; i++) {
+        Json.Object jsonobj = array.get_object_element(i);
+        ret[i] = new BaseUnion.from_json_object(jsonobj);
+    }
+    return ret;
+}
+
+Json.Array json_array_from_unions(BaseUnion[] data) {
+    var array = new Json.Array();
+    for (uint i = 0; i < data.length; i++)
+        array.add_object_element(data[i].get_internal_object());
+    return array;
+}
+
+HashTable<string, BaseUnion> union_map_from_json(Json.Object object) {
+    var ret = new HashTable<string, BaseUnion>(str_hash, str_equal);
+    var members = object.get_members();
+    for (int i = 0; i < members.length(); i++) {
+        var value = object.get_object_member(members.nth_data(i));
+        ret.insert(members.nth_data(i),
+                new BaseUnion.from_json_object(value));
+    }
+    return ret;
+}
+
+Json.Object json_object_from_union_map(HashTable<string, BaseUnion> map) {
+    var ret = new Json.Object();
+    var members = map.get_keys();
+    for (int i = 0; i < members.length(); i++) {
+        var key = members.nth_data(i);
+        ret.set_object_member(key, map.get(key).get_internal_object());
+    }
+    return ret;
+}
+
+/*
+ * Enum
+ */
+Json.Object json_object_from_enum_map(HashTable<string, int64?> map) {
+    /* TODO */
+    return new Json.Object();
+}
+
+} /* namespace vdsm */
diff --git a/vdsm_api/schema.json b/vdsm_api/schema.json
new file mode 100644
index 0000000..5412bbe
--- /dev/null
+++ b/vdsm_api/schema.json
@@ -0,0 +1,721 @@
+#
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+#
+# API API Schema
+#
+
+##
+# @API.ping:
+#
+# Test connectivity to vdsm.
+#
+# Since: 4.10.0
+##
+{'command': {'class': 'API', 'name': 'ping'},
+ 'data': {}}
+
+#
+##
+# @API.pong:
+#
+# echoes a
+#
+# Since: 4.10.0
+##
+{'command': {'class': 'API', 'name': 'pong'},
+ 'data': {'a': 'int', '*b': 'str'}
+ 'returns': 'int'}
+
+#
+##
+# @API.fake:
+#
+# Will invoke exception
+#
+# Since: 4.10.0
+##
+{'command': {'class': 'API', 'name': 'fake'},
+ 'data': {'a': 'int', '*b': 'str'}
+ 'returns': 'str'}
+
+
+{'command': {'class': 'API', 'name': 'pass'},
+ 'data': {'a': 'SetupNetworkBondAttributes' },
+ 'returns': ['SetupNetworkBondAttributes']}
+
+{'map': 'IntMap', 'key': 'str', 'value': 'int'}
+{'map': 'StrMap', 'key': 'str', 'value': 'str'}
+
+
+{'command': {'class': 'API', 'name': 'maptest'},
+ 'data': {'a': 'IntMap'},
+ 'returns': 'StrMap'}
+
+##
+# @SetupNetworkBondAttributes:
+#
+# Configuration attributes for a single bonded network interface device.
+#
+# @nics:     An array of network device names to include in the bond
+#
+# @options:  #optional A string of space-separated <option>=<value> pairs
+#
+# @remove:   #optional If True, remove existing bond only
+#
+# Since: 4.10.0
+##
+{'type': 'SetupNetworkBondAttributes',
+ 'data': {'nics': ['str'], '*options': 'str', '*remove': 'bool'}}
+
+{'type': 'TestA',
+ 'data': {'foo': 'str', '*bar': 'int'}}
+
+{'type': 'TestB',
+ 'data': {'name': 'str', 'obj': 'TestA'}}
+
+##
+# @FenceNodePowerStatus:
+#
+# Indicates the power state of a remote host.
+#
+# @on:       The remote host is powered on
+#
+# @off:      The remote host is powered off
+#
+# @unknown:  The power status is not known
+#
+# Since: 4.10.0
+##
+{'enum': 'FenceNodePowerStatus', 'data': ['on', 'off', 'unknown']}
+
+##
+# @FenceNodeAction:
+#
+# Specifies the type of fencing operation to perform.
+#
+# @status:  Just fetch the current power status
+#
+# @on:      Turn on the remote host
+#
+# @off:     Shut down the remote host
+#
+# @reboot:  Reboot the remote host
+#
+# Since: 4.10.0
+##
+{'enum': 'FenceNodeAction', 'data': ['status', 'on', 'off', 'reboot']}
+
+##
+# @TaskState:
+#
+# An enumeration of possible task states.
+#
+# @unknown:   The state of the task is not known
+#
+# @init:      The task is initializing
+#
+# @running:   The task is currently executing
+#
+# @finished:  The task has finished executing
+#
+# @aborting:  The task has been interrupted by a user has encountered an error
+#
+# @cleaning:  The task has failed and recovery actions are underway
+#
+# Since: 4.10.0
+##
+{'enum': 'TaskState',
+ 'data': ['unknown', 'init', 'running', 'finished', 'aborting', 'cleaning']}
+
+##
+# @TaskResult:
+#
+# An enumeration of the possible final task results.
+#
+# @success:       The task was successful
+#
+# @cleanSuccess:  The task failed but was successfully recovered
+#
+# @cleanFailure:  The task failed and recovery also failed
+#
+# Since: 4.10.0
+##
+{'enum': 'TaskResult', 'data': ['success', 'cleanSuccess', 'cleanFailure']}
+
+##
+# @OSName:
+#
+# An enumeration of recognized operating system names.
+#
+# @unknown:          The operating system could not be detected
+#
+# @oVirt Node:       oVirt Node standalone hypervisor
+#
+# @RHEL:             Red Hat Enterprise Linux
+#
+# @Fedora:           Fedora Linux
+#
+# @RHEV Hypervisor:  Red Hat Enterprise Virtualization Hypervisor
+#
+# @Debian:           A Debian-based distribution (including Ubuntu)
+#
+# Since: 4.10.0
+# XXX: Extension: Spaces in enum values
+##
+{'enum': 'OSName',
+ 'data': ['unknown', 'oVirt Node', 'RHEL', 'Fedora', 'RHEV Hypervisor',
+          'Debian']}
+
+##
+# @SoftwarePackage:
+#
+# An enumeration of aliases for important software components.
+#
+# @kernel:        The Linux kernel
+#
+# @qemu-kvm:      Qemu virtual machine emulator
+#
+# @qemu-img:      Qemu VM disk image manipulation utility
+#
+# @vdsm:          Virtual Desktop and Server Manager (this software)
+#
+# @spice-server:  The server for SPICE protocol
+#
+# @libvirt:       Low level virtualization API
+#
+# Since: 4.10.0
+##
+{'enum': 'SoftwarePackage',
+ 'data': ['kernel', 'qemu-kvm', 'qemu-img', 'vdsm', 'spice-server', 'libvirt']}
+
+##
+# @VmType:
+#
+# Enumeration of supported virtual machine types.
+#
+# @kvm:  VM runs on Linux Kernel Virtual Machine
+#
+# Since: 4.10.0
+##
+{'enum': 'VmType', 'data': ['kvm']}
+
+##
+# @BlockDeviceType:
+#
+# Enumeration of possible block device types.
+#
+# @iSCSI:  The device is pure iSCSI
+#
+# @FCP:    The device is purely FibreChannel
+#
+# @MIXED:  The device consists of a mix of iSCSI and FibreChannel paths
+#
+# Since: 4.10.0
+##
+{'enum': 'BlockDeviceType', 'data': ['iSCSI', 'FCP', 'MIXED']}
+
+##
+# @BlockDevicePathState:
+#
+# Enumeration of possible states for a block device path.
+#
+# @active:  The path is active
+#
+# @failed:  The path has failed
+#
+# Since: 4.10.0
+##
+{'enum': 'BlockDevicePathState', 'data': ['active', 'failed']}
+
+##
+# @VolumeGroupState:
+#
+# An enumeration of possible Volume Group states.
+#
+# @OK:       All Physical Volumes are online
+#
+# @PARTIAL:  One or more Physical Volumes are not available
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupState', 'data': ['OK', 'PARTIAL']}
+
+##
+# @VolumeGroupAttributePermission:
+#
+# An enumeration of possible values for the permission attribute.
+#
+# @w:  The Volume Group is writable
+#
+# @r:  The Volume Group is read-only
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributePermission', 'data': ['w', 'r']}
+
+##
+# @VolumeGroupAttributeResizeable:
+#
+# An enumeration of possible values for the resizeable attribute.
+#
+# @-:  The Volume Group is not resizeable
+#
+# @z:  The Volume Group is resizeable
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributeResizeable', 'data': ['-', 'z']}
+
+##
+# @VolumeGroupAttributeExported:
+#
+# An enumeration of possible values for the exported attribute.
+#
+# @-:  The Volume Group has not been exported
+#
+# @x:  The Volume Group has been exported
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributeExported', 'data': ['-', 'x']}
+
+##
+# @VolumeGroupAttributePartial:
+#
+# An enumeration of possible values for the partial status attribute.
+#
+# @-:  The Volume Group can access all of its Physical Volumes
+#
+# @p:  The Volume Group cannot access some Physical Volumes
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributePartial', 'data': ['-', 'p']}
+
+##
+# @VolumeGroupAttributeAllocation:
+#
+# An enumeration of possible values for the allocation policy attribute.
+#
+# @c:  Indicates a contiguous allocation policy
+#
+# @n:  Indicates a normal allocation policy
+#
+# @i:  Indicates that the allocation policy is inherited
+#
+# @l:  Indicates a cling allocation policy
+#
+# @a:  Indicates an anywhere allocation policy
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributeAllocation', 'data': ['c', 'n', 'i', 'l', 'a']}
+
+##
+# @VolumeGroupAttributeClustered:
+#
+# An enumeration of possible values for the clustered attribute.
+#
+# @-:  Indicates that clustered locking is disabled
+#
+# @c:  Indicates that clustered locking is enabled
+#
+# Since: 4.10.0
+##
+{'enum': 'VolumeGroupAttributeClustered', 'data': ['-', 'c']}
+
+##
+# @NetworkInterfaceState:
+#
+# An enumeration of possible network interface states.
+#
+# @up:    The interface is active
+#
+# @down:  The interface is not active
+#
+# Since: 4.10.0
+##
+{'enum': 'NetworkInterfaceState', 'data': ['up', 'down']}
+
+##
+# @THPStates:
+#
+# An enumeration of possible states for the Transparent Huge Pages feature.
+#
+# @always:   All memory regions will be scanned
+#
+# @madvise:  Only memory regions indicated by a call to madvise will be scanned
+#
+# @never:    No memory regions will be scanned
+#
+# Since: 4.10.0
+##
+{'enum': 'THPStates', 'data': ['always', 'madvise', 'never']}
+
+##
+# @StorageDomainClass:
+#
+# An enumeration of Storage Domain classes.
+#
+# @Unknown:  The class is not known
+# @Data:     The Storage Domain is used for virtual machine disk images
+# @Iso:      The Storage Domain is used for storing ISO images
+# @Backup:   The Storage Domain is used for import and export of disk images
+#
+# Since: 4.10.0
+# XXX: Need to specify explicit values for the enum
+##
+{'enum': 'StorageDomainClass', 'data': ['Unknown', 'Data', 'Iso', 'Backup']}
+
+##
+# @StorageDomainType:
+#
+# An enumeration of Storage Domain types.
+#
+# @UNKNOWN:   The type is not known
+#
+# @NFS:       The Storage Domain uses Network File System based storage
+#
+# @FCP:       The Storage Domain uses FibreChannel based storage
+#
+# @ISCSI:     The Storage Domain uses iSCSI based storage
+#
+# @LOCALFS:   The Storage Domain uses storage on the local file system
+#
+# @CIFS:      The Storage Domain uses CIFS/SMB based storage
+#
+# @SHAREDFS:  The Storage Domain uses storage from a Linux VFS file system
+#
+# Since: 4.10.0
+# XXX: Need to specify explicit values for the enum
+##
+{'enum': 'StorageDomainType',
+ 'data': ['UNKNOWN', 'NFS', 'FCP', 'ISCSI', 'LOCALFS', 'CIFS', 'SHAREDFS']}
+
+##
+# @VmStatus:
+#
+# An enumeration of possible virtual machine statuses.
+#
+# @Down:                   The VM is powered off
+#
+# @Migration Destination:  The VM is migrating to this host
+#
+# @Migration Source:       The VM is migrating away from this host
+#
+# @Paused:                 The VM is paused
+#
+# @Powering down:          A shutdown command has been sent to the VM
+#
+# @RebootInProgress:       The VM is currently rebooting
+#
+# @Restoring state:        The VM is waking from hibernation
+#
+# @Saving State:           The VM is preparing for hibernation
+#
+# @Up:                     The VM is running
+#
+# @WaitForLaunch:          The VM is being created
+#
+# Since: 4.10.0
+##
+{'enum': 'VmStatus',
+ 'data': ['Down', 'Migration Destination', 'Migration Source', 'Paused',
+          'Powering down', 'RebootInProgress', 'Restoring state',
+          'Saving State', 'Up', 'WaitForLaunch']}
+
+##
+# @VmDisplayType:
+#
+# An enumeration of VM display modes.
+#
+# @vnc:  Graphical VM interaction is available via the VNC protocol
+#
+# @qxl:  Graphical VM interaction is available via SPICE
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDisplayType', 'data': ['vnc', 'qxl']}
+
+##
+# @VmDeviceType:
+#
+# An enumeration of VM device types.
+#
+# @disk:        A hard disk, floppy, or cdrom device
+#
+# @interface:   A network interface
+#
+# @video:       A video card
+#
+# @sound:       A sound card or PC speaker
+#
+# @controller:  An internal controller that usually provides a bus
+#
+# @balloon:     A memory balloon device
+#
+# @channel:     A host-guest communication channel
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDeviceType',
+ 'data': ['disk', 'interface', 'video', 'sound', 'controller', 'balloon',
+          'channel']}
+
+##
+# @VmDiskDeviceType:
+#
+# An enumeration of VM disk device types.
+#
+# @disk:    A hard disk drive
+#
+# @cdrom:   A CD-ROM drive (always readonly)
+#
+# @floppy:  A floppy disk drive
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDiskDeviceType', 'data': ['disk', 'cdrom', 'floppy']}
+
+##
+# @VmDiskDeviceInterface:
+#
+# An enumeration of VM disk device interfaces.
+#
+# @ide:     The device is connected via an emulated IDE bus
+#
+# @virtio:  The device uses a virtio para-virtualized interconnect
+#
+# @fdc:     The device is connected to an emulated floppy disk controller
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDiskDeviceInterface', 'data': ['ide', 'virtio', 'fdc']}
+
+##
+# @VmDiskDeviceFormat:
+#
+# An enumeration of VM disk device formats.
+#
+# @raw:  Raw data is written directly to backing storage
+#
+# @cow:  Data is written using the Qemu qcow format
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDiskDeviceFormat', 'data': ['raw', 'cow']}
+
+# PCI
+# IDE
+# virtio-serial
+##
+# @VmDeviceAddressType:
+#
+# An enumeration of VM device address types.
+#
+# @pci:            The address describes a location on an PCI bus
+#
+# @drive:          The address describes a location on an IDE bus
+#
+# @virtio-serial:  Describes a location on a virtio-serial controller
+#
+# Since: 4.10.0
+##
+{'enum': 'VmDeviceAddressType', 'data': ['pci', 'drive', 'virtio-serial']}
+
+##
+# @VmInterfaceDeviceType:
+#
+# An enumeration of VM network device types.
+#
+# @bridge:  The device is connected via a host bridge device
+#
+# Since: 4.10.0
+##
+{'enum': 'VmInterfaceDeviceType', 'data': ['bridge']}
+
+##
+# @VmInterfaceDeviceModel:
+#
+# An enumeration of VM network device models.
+#
+# @pv:        Alias for @virtio
+#
+# @virtio:    Paravirtual network interface
+#
+# @ne2k_pci:  Emulated Realtek(R) PCI NIC
+#
+# @i82551:    Emulated Intel(R) 82551ER NIC
+#
+# @i82557b:   Emulated Intel i82557B NIC
+#
+# @i82559er:  Emulated Intel i82559ER NIC
+#
+# @rtl8139:   Emulated rtl8139 NIC
+#
+# @e1000:     Emulated Intel(R) Gigabit Ethernet NIC
+#
+# @pcnet:     Emulated AMD(R) PC-Net II NIC
+#
+# Since: 4.10.0
+##
+{'enum': 'VmInterfaceDeviceModel',
+ 'data': ['pv', 'virtio', 'ne2k_pci', 'i82551', 'i82557b', 'i82559er',
+          'rtl8139', 'e1000', 'pcnet']}
+
+##
+# @VmVideoDeviceType:
+#
+# An enumeration of VM video device types.
+#
+# @cirrus:  An emulated Cirrus Logic GD5446 video card
+#
+# @std:     An emulated standard VESA 2.0 VBE video card
+#
+# @vmware:  A paravirtualized VGA video device from VMWare(R)
+#
+# @qxl:     A paravirtualized video device meant for use with SPICE
+#
+# @none:    No video device is emulated
+#
+# Since: 4.10.0
+##
+{'enum': 'VmVideoDeviceType',
+ 'data': ['cirrus', 'std', 'vmware', 'qxl', 'none']}
+
+##
+# @VmSoundDeviceType:
+#
+# An enumeration of VM sound device types.
+#
+# @ac97:    An emulated Intel 82801AA AC97 Audio card
+#
+# @pcspk:   An emulated PC speaker
+#
+# @sb16:    An emulated Creative Labs Sound Blaster 16 card
+#
+# @es1370:  An emulated ENSONIQ AudioPCI ES1370 card
+#
+# @hda:     An emulated Intel High-Definition Audio card
+#
+# @ich6:    An emulated Intel ICH6 card
+#
+# Since: 4.10.0
+##
+{'enum': 'VmSoundDeviceType',
+ 'data': ['ac97', 'pcspk', 'sb16', 'es1370', 'hda', 'ich6']}
+
+##
+# @VmControllerDeviceType:
+#
+# An enumeration of VM controller device types.
+#
+# @ide:            An IDE controller
+#
+# @fdc:            A floppy disk controller
+#
+# @scsi:           A SCSI controller
+#
+# @sata:           A Serial ATA controller
+#
+# @usb:            A USB controller
+#
+# @ccid:           A ccid (smart card) controller
+#
+# @virtio-serial:  A virtio-serial controller
+#
+# Since: 4.10.0
+##
+{'enum': 'VmControllerDeviceType',
+ 'data': ['ide', 'fdc', 'scsi', 'sata', 'usb', 'ccid', 'virtio-serial']}
+
+##
+# @VmBalloonDeviceType:
+#
+# An enumeration of VM balloon device types.
+#
+# @memballoon:  A memory balloon
+#
+# Since: 4.10.0
+##
+{'enum': 'VmBalloonDeviceType', 'data': ['memballoon']}
+
+##
+# @VmBalloonDeviceModel:
+#
+# An enumeration of VM balloon device models.
+#
+# @none:    Indicates that no device is present
+#
+# @virtio:  A balloon implemented using the virtio specification
+#
+# Since: 4.10.0
+##
+{'enum': 'VmBalloonDeviceModel', 'data': ['none', 'virtio']}
+
+##
+# @VmChannelDeviceType:
+#
+# An enumeration of VM channel device types.
+#
+# @unix:           The host end of the channel is a unix socket
+#
+# @spicevmc:       This channel is used for SPICE communication
+#
+# @virtio-serial:  This channel supports a virtio-serial connection
+#
+# Since: 4.10.0
+##
+{'enum': 'VmChannelDeviceType', 'data': ['unix', 'spicevmc']}
+
+##
+# @LoggingLevel:
+#
+# An enumeration of logging verbosity levels.
+#
+# @DEBUG:     Log everything (including debugging messages)
+#
+# @INFO:      Log informational messages and anything more severe
+#
+# @WARNING:   Log warning messages and anything more severe
+#
+# @ERROR:     Log error messages and anything more severe
+#
+# @CRITICAL:  Log only critical messages
+#
+# Since: 4.10.0
+# XXX: These need to map to specific integers
+##
+{'enum': 'LoggingLevel',
+ 'data': ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']}
+
+##
+# @IscsiCredentialsType:
+#
+# An enumeration of ISCSI login credentials types.
+#
+# @chap:  Challenge-Handshake Authentication Protocol
+#
+# Since: 4.10.0
+##
+{'enum': 'IscsiCredentialsType', 'data': ['chap']}
+
+
diff --git a/vdsm_api/test.c b/vdsm_api/test.c
new file mode 100644
index 0000000..74970b0
--- /dev/null
+++ b/vdsm_api/test.c
@@ -0,0 +1,131 @@
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "glib.h"
+#include "json-glib/json-glib.h"
+
+#include "vdsm.h"
+
+void check_error(const char *operation, GError **error, int exit_on_fail) {
+    if (*error) {
+        int code = (*error)->code;
+        printf ("%s failed: %s\n", operation, (*error)->message);
+        g_error_free (*error);
+        error = NULL;
+        if (exit_on_fail)
+            exit(code);
+    }
+}
+
+void test_capabilities(vdsmHost *host) {
+    printf ("test_capabilities()\n");
+
+    vdsmVdsmCapabilities *caps;
+    GError *error = NULL;
+
+    caps = vdsm_host_getCapabilities(host, &error);
+    check_error("API.getCapabilities", &error, 1);
+    int kvmEnabled = vdsm_vdsm_capabilities_get_kvmEnabled(caps);
+
+    printf ("\tI am talking to vdsm version: %s.%s\n",
+            vdsm_vdsm_capabilities_get_software_version(caps),
+            vdsm_vdsm_capabilities_get_software_revision(caps));
+    printf ("\tKVM enabled: %s\n", kvmEnabled? "True" : "False");
+    g_object_unref (caps);
+}
+
+void test_stats(vdsmHost *host) {
+    printf ("test_stats()\n");
+
+    vdsmHostStats *stats;
+    GError *error = NULL;
+
+    stats = vdsm_host_getStats(host, &error);
+    check_error("API.getStats", &error, 1);
+
+    printf("\tHost CPU Load: %f\n", vdsm_host_stats_get_cpuLoad(stats));
+    printf ("\tTransparent huge pages status: %s\n",
+            vdsm_thp_states_to_string(vdsm_host_stats_get_thpState(stats)));
+    g_object_unref (stats);
+}
+
+void test_vms(vdsmHost *host) {
+    printf ("test_vms()\n");
+
+    GError *error = NULL;
+    int i, length;
+    gboolean fullstatus = TRUE;
+    vdsmVM **ret;
+    ret = vdsm_host_getVMs(host, &length, &error);
+    check_error("Host.getVMs", &error, 1);
+
+    printf ("\tThere are %i active VMs\n", length);
+    for (i = 0; i < length; i++) {
+        vdsmVmFullStatus *status;
+        status = vdsm_vm_getInfo(ret[i], &error);
+        check_error("vmList.getInfo", &error, 1);
+        printf ("\t%s: %s\n", vdsm_vm_full_status_get_vmName(status),
+            vdsm_vm_status_to_string(vdsm_vm_full_status_get_status(status)));
+        g_object_unref (status);
+        g_object_unref (ret[i]);
+    }
+
+    g_free (ret);
+}
+
+void test_walk_images(vdsmStorageDomain *domain) {
+    GError *error = NULL;
+    int i, length;
+    vdsmImage **images;
+
+    images = vdsm_storage_domain_getImages(domain, &length, &error);
+    check_error("StorageDomain.getImages", &error, 1);
+    for (i = 0; i < length; i++) {
+        printf ("      Image: %s\n", vdsm_image_get_imageID(images[i]));
+        g_object_unref (images[i]);
+    }
+    g_free (images);
+}
+
+void test_walk_storagedomains(vdsmHost *host) {
+    printf ("test_walk_storagedomains()\n");
+    GError *error = NULL;
+    int i, length;
+    vdsmStorageDomain **domains;
+
+    domains = vdsm_host_getStorageDomains(host, NULL, NULL, NULL, NULL,
+                                          &length, &error);
+    check_error("Host.getStorageDomains", &error, 1);
+    for (i = 0; i < length; i++) {
+        vdsmStorageDomainInfo *domainInfo;
+        domainInfo = vdsm_storage_domain_getInfo(domains[i], &error);
+        check_error("StorageDomain.getInfo", &error, 1);
+        printf ("    Domain '%s':\n",
+                vdsm_storage_domain_info_get_name(domainInfo));
+        test_walk_images (domains[i]);
+        g_object_unref (domainInfo);
+        g_object_unref (domains[i]);
+    }
+
+    g_free (domains);
+}
+
+int main(int argc, char **argv) {
+    g_type_init();
+
+    g_assert (argc == 3);
+    char *addr = argv[1];
+    int port = atoi(argv[2]);
+
+    vdsmHost *host = vdsm_host_new (addr, port, TRUE);
+    vdsm_host_connect (host, NULL);
+
+    test_capabilities(host);
+    test_stats(host);
+    test_vms(host);
+    test_walk_storagedomains(host);
+
+    vdsm_host_disconnect (host);
+    g_object_unref (host);
+
+}
diff --git a/vdsm_api/test.py b/vdsm_api/test.py
new file mode 100644
index 0000000..e84aea5
--- /dev/null
+++ b/vdsm_api/test.py
@@ -0,0 +1,101 @@
+# VDSM API Python Test Client
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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
+
+from gi.repository import vdsm
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Json
+import select
+import threading
+
+# Don't forget to turn this on or the GIL will not be released on FFI calls.
+# I lurned this the hard way!!!
+GLib.threads_init()
+
+
+def manage_host(host):
+    while True:
+        select.select([host.get_fd()], [], [], 1)
+        if not host.is_connected():
+            return
+
+        host.process()
+
+        if not host.is_connected():
+            return
+
+def json_to_map(j):
+    parser = Json.Parser.new()
+    parser.load_from_data(j, len(j))
+    node = parser.get_root()
+    return node.dup_object()
+
+def map_to_json(m):
+    node = Json.Node.new(Json.NodeType.OBJECT)
+    node.set_object(m)
+    gen = Json.Generator.new()
+    gen.set_root(node)
+    return gen.to_data()[0]
+
+def test_capabilities(host):
+    print "test_capabilities()"
+    caps = host.getCapabilities()
+    print "\tI am talking to vdsm version: %s.%s" % \
+            (caps.get_software_version(), caps.get_software_revision())
+    print "\tKVM enabled: %s" % caps.get_kvmEnabled()
+
+def test_getstoragedomains(host):
+    print "test_getstoragedomains()"
+    sdList = host.getStorageDomains(None, 0, 0, None)
+    for sd in sdList:
+        info = sd.getInfo()
+        stats = sd.getStats()
+        print "\t%s (%s) has %lu space free out of %lu" % \
+            (info.get_name(), info.get_uuid(),
+             stats.get_diskfree(), stats.get_disktotal())
+
+def test_stats(host):
+    print "test_stats()"
+    stats = host.getStats()
+    print "\tHost CPU Load: %f" % stats.get_cpuLoad()
+    print "\tTransparent huge pages status: %s" % \
+        stats.get_thpState().value_nick
+    # XXX: bindings do not seem to support the to_string method for enums
+    # stats.get_thpState().to_string()
+
+def test_vms(host):
+    print "test_vms()"
+    vms = host.getVMs()
+    print "\tThere are %i active VMs" % len(vms)
+    #print vms
+    for vm in vms:
+        info = vm.getInfo()
+        print "\t%s: %s" % (info.get_vmName(),
+                               info.get_status().value_nick)
+
+
+host = vdsm.Host.new("127.0.0.1", 4444, False)
+host.connect()
+t = threading.Thread(target=manage_host, args=(host,))
+t.setDaemon(True)
+t.start()
+
+test_capabilities(host)
+test_getstoragedomains(host)
+test_stats(host)
+test_vms(host)
+
+host.disconnect()
diff --git a/vdsm_api/test_serv.py b/vdsm_api/test_serv.py
new file mode 100644
index 0000000..d542241
--- /dev/null
+++ b/vdsm_api/test_serv.py
@@ -0,0 +1,37 @@
+# VDSM API Test Server
+# Copyright (C) 2012 Adam Litke, IBM Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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
+import logging
+import time
+from BindingJsonRpc import BindingJsonRpc
+from Bridge import TestBridge
+
+def setup_logging():
+    FORMAT = "%(message)s"
+    logging.basicConfig(format=FORMAT)
+    logger = logging.getLogger('test')
+    logger.setLevel(logging.DEBUG)
+    return logger
+
+log = setup_logging()
+server = BindingJsonRpc(TestBridge(), log, '127.0.0.1', 4444)
+server.start()
+
+while True:
+    try:
+        time.sleep(1)
+    except KeyboardInterrupt:
+        server.prepareForShutdown()
+        break
diff --git a/vdsm_api/vdsmapi-schema.json b/vdsm_api/vdsmapi-schema.json
index 1fc7585..e576fb0 100644
--- a/vdsm_api/vdsmapi-schema.json
+++ b/vdsm_api/vdsmapi-schema.json
@@ -942,6 +942,9 @@
 {'command': {'class': 'Host', 'name': 'getConnectedStoragePools'},
  'returns': ['UUID']}
 
+{'command': {'class': 'Host', 'name': 'getStoragePools'},
+ 'returns': ['StoragePool']}
+
 ##
 # @BlockDeviceType:
 #
@@ -1559,13 +1562,13 @@
 #
 # Get a list of known Storage Domain UUIDs.
 #
-# @spUUID:       #optional Limit to Domains belonging to this Storage Pool
+# @storagepoolID:  #optional Limit to Domains belonging to this Storage Pool
 #
-# @domainClass:  #optional Limit to Domains of this @StorageDomainImageClass
+# @domainClass:    #optional Limit to Domains of this @StorageDomainImageClass
 #
-# @storageType:  #optional Limit to Domains of this @StorageDomainType
+# @storageType:    #optional Limit to Domains of this @StorageDomainType
 #
-# @remotePath:   #optional Limit to Domains having this remotePath
+# @remotePath:     #optional Limit to Domains having this remotePath
 #
 # Returns:
 # A list of known Storage Domains
@@ -1573,9 +1576,9 @@
 # Since: 4.10.0
 ##
 {'command': {'class': 'Host', 'name': 'getStorageDomains'},
- 'data': {'*spUUID': 'UUID', '*domainClass': 'StorageDomainImageClass',
+ 'data': {'*storagepoolID': 'UUID', '*domainClass': 'StorageDomainImageClass',
           '*storageType': 'StorageDomainType', '*remotePath': 'str'},
- 'returns': ['UUID']}
+ 'returns': ['StorageDomain']}
 
 ##
 # @StorageDomainStatsMap:
@@ -2370,8 +2373,14 @@
 # Since: 4.10.0
 ##
 {'command': {'class': 'Host', 'name': 'getVMList'},
- 'data': {'*fullStatus': 'bool', '*vmList': ['UUID']},
+ 'data': {'fullStatus': 'bool', 'vmList': ['UUID']},
  'returns': ['VmDefinition']}
+
+{'command': {'class': 'Host', 'name': 'getVMs'},
+ 'returns': ['VM']}
+
+{'command': {'class': 'VM', 'name': 'getInfo'},
+ 'returns': 'VmFullStatus'}
 
 ##
 # @Host.ping:
@@ -2748,18 +2757,19 @@
 #
 # Image API object.
 #
-# @conn:    A connected base API object
+# @conn:             A connected base API object
 #
-# @UUID:    The UUID of the Image
+# @imageID:          The UUID of the Image
 #
-# @spUUID:  The UUID of the Storage Pool associated with the Image
+# @storagepoolID:    The UUID of the Storage Pool associated with the Image
 #
-# @sdUUID:  The UUID of the Storage Domain associated with the Image
+# @storagedomainID:  The UUID of the Storage Domain associated with the Image
 #
 # Since: 4.10.0
 ##
 {'class': 'Image',
- 'data': {'conn': 'Host', 'UUID': 'UUID', 'spUUID': 'UUID', 'sdUUID': 'UUID'}}
+ 'data': {'conn': 'Host', 'imageID': 'UUID', 'storagepoolID': 'UUID',
+          'storagedomainID': 'UUID'}}
 
 ##
 # @Image.delete:
@@ -2805,12 +2815,13 @@
 # Get a list of Volumes associated with this Image.
 #
 # Returns:
-# A list of Image UUIDs
+# A list of Images
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'Image', 'name': 'getVolumes'},
- 'returns': ['UUID']}
+ 'returns': ['Volume']}
+
 
 ##
 # @Image.mergeSnapshots:
@@ -2878,11 +2889,13 @@
 #
 # @conn:  A connected base API object
 #
-# @UUID:  #optional Associate this object with an existing LVM Volume Group
+# @lvmvolumegroupID:  #optional Associate this object with an existing LVM
+#                     Volume Group
 #
 # Since: 4.10.0
 ##
-{'class': 'LVMVolumeGroup', 'data': {'conn': 'Host', 'UUID': 'UUID'}}
+{'class': 'LVMVolumeGroup',
+ 'data': {'conn': 'Host', 'lvmvolumegroupID': 'UUID'}}
 
 ##
 # @LVMVolumeGroup.create:
@@ -2935,16 +2948,17 @@
 #
 # StorageDomain API object.
 #
-# @conn:    A connected base API object
+# @conn:             A connected base API object
 #
-# @UUID:    Associate this object with a new or existing Storage Domain
+# @storagedomainID:  Associate this object with a new or existing Storage Domain
 #
-# @spUUID:  #optional The Storage Pool UUID if this Storage Domain is attached
+# @storagepoolID:    #optional The Storage Pool UUID if this Storage Domain is
+#                    attached
 #
 # Since: 4.10.0
 ##
 {'class': 'StorageDomain',
- 'data': {'conn': 'Host', 'UUID': 'UUID', '*spUUID': 'UUID'}}
+ 'data': {'conn': 'Host', 'storagedomainID': 'UUID', '*storagepoolID': 'UUID'}}
 
 ##
 # @StorageDomain.activate:
@@ -2960,12 +2974,12 @@
 #
 # Attach a Storage Domain to a Storage Pool.
 #
-# @spUUID:  The Storage Pool to which the Storage Domain should be attached
+# @storagepoolID:  The Storage Pool to which the Storage Domain should be attached
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StorageDomain', 'name': 'attach'},
- 'data': {'spUUID': 'UUID'}}
+ 'data': {'storagepoolID': 'UUID'}}
 
 ##
 # @StorageDomainCreateArgumentsBlock:
@@ -3145,12 +3159,13 @@
 # Get a list of Images associated with this Storage Domain.
 #
 # Returns:
-# An array of Image UUIDs
+# A list of Images
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StorageDomain', 'name': 'getImages'},
- 'returns': ['UUID']}
+ 'returns': ['Image']}
+
 
 ##
 # @StorageDomainRole:
@@ -3252,16 +3267,16 @@
 #
 # Get a list of Volumes contained within a Storage Domain.
 #
-# @imgUUID:  Limit results to Volumes associated with a single Image
+# @imageID:  Limit results to Volumes associated with a single Image
 #
 # Returns:
-# A list of Volume UUIDs
+# A list of Volumes
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StorageDomain', 'name': 'getVolumes'},
- 'data': {'imgUUID': 'UUID'},
- 'returns': ['UUID']}
+ 'data': {'imageID': 'UUID'},
+ 'returns': ['Volume']}
 
 ##
 # @StorageDomain.setDescription:
@@ -3293,9 +3308,9 @@
 #
 # Upload a Volume into a Storage Domain from a remote location.
 #
-# @imgUUID:  The UUID of the image that is associated with the Volume
+# @imageID:  The UUID of the image that is associated with the Volume
 #
-# @volUUID:  The UUID of an existing Volume where the data will be uploaded
+# @volumeID:  The UUID of an existing Volume where the data will be uploaded
 #
 # @srcPath:  The remote location of the Volume data.  Must be in a format that
 #            can be understood by the command indicated in @method.
@@ -3307,7 +3322,7 @@
 # Since: 4.10.0
 ##
 {'command': {'class': 'StorageDomain', 'name': 'uploadVolume'},
- 'data': {'imgUUID': 'UUID', 'volUUID': 'UUID', 'srcPath': 'str',
+ 'data': {'imageID': 'UUID', 'volumeID': 'UUID', 'srcPath': 'str',
           'size': 'int', 'method': 'UploadVolumeMethod'}}
 
 ##
@@ -3325,13 +3340,13 @@
 #
 # StoragePool API object.
 #
-# @conn:  A connected base API object
+# @conn:           A connected base API object
 #
-# @UUID:  Associate this object with a new or existing Storage Pool
+# @storagepoolID:  Associate this object with a new or existing Storage Pool
 #
 # Since: 4.10.0
 ##
-{'class': 'StoragePool', 'data': {'conn': 'Host', 'UUID': 'UUID'}}
+{'class': 'StoragePool', 'data': {'conn': 'Host', 'storagepoolID': 'UUID'}}
 
 ##
 # @StoragePool.connect:
@@ -3536,7 +3551,7 @@
 #
 # Get information about backed-up virtual machines from a Backup Storage Domain.
 #
-# @sdUUID:  The UUID of the Backup Storage Domain to check
+# @storagedomainID:  The UUID of the Backup Storage Domain to check
 #
 # @vmList:  Limit results to a list of VM UUIDs
 #
@@ -3549,7 +3564,7 @@
 #         previously injected it into the Backup Storage Domain.
 ##
 {'command': {'class': 'StoragePool', 'name': 'getBackedUpVmsInfo'},
- 'data': {'sdUUID': 'UUID', 'vmList': ['UUID']},
+ 'data': {'storagedomainID': 'UUID', 'vmList': ['UUID']},
  'returns': 'OVFMap'}
 
 ##
@@ -3557,7 +3572,7 @@
 #
 # Get a list of backed up virtual machines from a Backup Storage Domain.
 #
-# @sdUUID:  The UUID of the Backup Storage Domain to check
+# @storagedomainID:  The UUID of the Backup Storage Domain to check
 #
 # Returns:
 # A list of VM UUIDs
@@ -3565,7 +3580,7 @@
 # Since: 4.10.0
 ##
 {'command': {'class': 'StoragePool', 'name': 'getBackedUpVmsList'},
- 'data': {'sdUUID': 'UUID'},
+ 'data': {'storagedomainID': 'UUID'},
  'returns': ['UUID']}
 
 ##
@@ -3586,18 +3601,18 @@
 #
 # Get a list of Storage Domains that contain an Image.
 #
-# @imgUUID:          The UUID of the Image to search for
+# @imageID:          The UUID of the Image to search for
 #
 # @onlyDataDomains:  #optional Only include Data Storage Domains
 #
 # Returns:
-# A list of Storage Domain UUIDs that contain the Image
+# A list of Storage Domains that contain the Image
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StoragePool', 'name': 'getDomainsContainingImage'},
- 'data': {'imgUUID': 'UUID', '*onlyDataDomains': 'bool'},
- 'returns': ['UUID']}
+ 'data': {'imageID': 'UUID', '*onlyDataDomains': 'bool'},
+ 'returns': ['StorageDomain']}
 
 ##
 # @StoragePool.getIsoList:
@@ -4001,12 +4016,12 @@
 #
 # @vmList:  A list of virtual machine definitions to store
 #
-# @sdUUID:  The Storage Domain to use for storing the VM definitions
+# @storagedomainID:  The Storage Domain to use for storing the VM definitions
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StoragePool', 'name': 'updateVMs'},
- 'data': {'vmList': ['UpdateVmDefinition'], 'sdUUID': 'UUID'}}
+ 'data': {'vmList': ['UpdateVmDefinition'], 'storagedomainID': 'UUID'}}
 
 ##
 # @StoragePool.removeVM:
@@ -4015,12 +4030,12 @@
 #
 # @vmUUID:  Remove the saved definition of the VM with this UUID
 #
-# @sdUUID:  The Storage Domain where the VM is stored
+# @storagedomainID:  The Storage Domain where the VM is stored
 #
 # Since: 4.10.0
 ##
 {'command': {'class': 'StoragePool', 'name': 'removeVM'},
- 'data': {'vmUUID': 'UUID', 'sdUUID': 'UUID'}}
+ 'data': {'vmUUID': 'UUID', 'storagedomainID': 'UUID'}}
 
 ## Category: @Task #############################################################
 ##
@@ -4028,13 +4043,13 @@
 #
 # Task API object.
 #
-# @conn:  A connected base API object
+# @conn:    A connected base API object
 #
-# @UUID:  Associate this object with an existing Task
+# @taskID:  Associate this object with an existing Task
 #
 # Since: 4.10.0
 ##
-{'class': 'Task', 'data': {'conn': 'Host', 'UUID': 'UUID'}}
+{'class': 'Task', 'data': {'conn': 'Host', 'taskID': 'UUID'}}
 
 ##
 # @Task.clear:
@@ -4097,11 +4112,11 @@
 #
 # @conn:  A connected base API object
 #
-# @UUID:  Associate this object with an existing VM
+# @vmID:  Associate this object with an existing VM
 #
 # Since: 4.10.0
 ##
-{'class': 'VM', 'data': {'conn': 'Host', 'UUID': 'UUID'}}
+{'class': 'VM', 'data': {'conn': 'Host', 'vmID': 'UUID'}}
 
 ##
 # @DriveSpecVolume:
@@ -5109,21 +5124,21 @@
 #
 # Volume API object.
 #
-# @conn:     A connected base API object
+# @conn:             A connected base API object
 #
-# @UUID:     The UUID of the Volume
+# @volumeID:         The UUID of the Volume
 #
-# @spUUID:   The Storage Pool associated with @UUID
+# @storagepoolID:    The Storage Pool associated with @UUID
 #
-# @sdUUID:   The Storage Domain associated with @UUID
+# @storagedomainID:  The Storage Domain associated with @UUID
 #
-# @imgUUID:  The Image associated with @UUID
+# @imageID:          The Image associated with @UUID
 #
 # Since: 4.10.0
 ##
 {'class': 'Volume',
- 'data': {'conn': 'Host', 'UUID': 'UUID', 'spUUID': 'UUID', 'sdUUID': 'UUID',
-          'imgUUID': 'UUID'}}
+ 'data': {'conn': 'Host', 'volumeID': 'UUID', 'storagepoolID': 'UUID',
+          'storagedomainID': 'UUID', 'imageID': 'UUID'}}
 
 ##
 # @VolumeRole:


--
To view, visit http://gerrit.ovirt.org/7516
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If6bd34700b86aa84c7e289f02c0e9f2ac6fcba63
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Adam Litke <agl at us.ibm.com>


More information about the vdsm-patches mailing list