[PATCH] libxl: add migration support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds initial migration support to the libxl driver,
using the VIR_DRV_FEATURE_MIGRATION_PARAMS family of migration
functions.

Signed-off-by: Jim Fehlig <jfehlig@xxxxxxxx>
---

V3 of patch to add migration support to the libxl driver.  V2 is here

https://www.redhat.com/archives/libvir-list/2014-March/msg00880.html

Patches 1-12 in the original series have been pushed, leaving only
this patch to complete the migration support.

This version adds a simple, extensible messaging protocol to coordinate
transfer of migration data between libxl hosts.  Michal Privoznik
noted the even simpler protocol in V2 was not extensible, which in the
end was a good point IMO so I've tried to address that with V3.

Comments welcome.  Thanks!

 po/POTFILES.in              |   1 +
 src/Makefile.am             |   3 +-
 src/libxl/libxl_conf.h      |   6 +
 src/libxl/libxl_domain.h    |   1 +
 src/libxl/libxl_driver.c    | 221 +++++++++++
 src/libxl/libxl_migration.c | 920 ++++++++++++++++++++++++++++++++++++++++++++
 src/libxl/libxl_migration.h |  78 ++++
 7 files changed, 1229 insertions(+), 1 deletion(-)

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 122b853..5618631 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -73,6 +73,7 @@ src/lxc/lxc_process.c
 src/libxl/libxl_domain.c
 src/libxl/libxl_driver.c
 src/libxl/libxl_conf.c
+src/libxl/libxl_migration.c
 src/network/bridge_driver.c
 src/network/bridge_driver_linux.c
 src/node_device/node_device_driver.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 21d56fc..f0dd4ae 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -702,7 +702,8 @@ XENAPI_DRIVER_SOURCES =						\
 LIBXL_DRIVER_SOURCES =						\
 		libxl/libxl_conf.c libxl/libxl_conf.h		\
 		libxl/libxl_domain.c libxl/libxl_domain.h       \
-		libxl/libxl_driver.c libxl/libxl_driver.h
+		libxl/libxl_driver.c libxl/libxl_driver.h       \
+		libxl/libxl_migration.c libxl/libxl_migration.h
 
 UML_DRIVER_SOURCES =						\
 		uml/uml_conf.c uml/uml_conf.h			\
diff --git a/src/libxl/libxl_conf.h b/src/libxl/libxl_conf.h
index 24e1720..b798567 100644
--- a/src/libxl/libxl_conf.h
+++ b/src/libxl/libxl_conf.h
@@ -43,6 +43,9 @@
 # define LIBXL_VNC_PORT_MIN  5900
 # define LIBXL_VNC_PORT_MAX  65535
 
+# define LIBXL_MIGRATION_PORT_MIN  49152
+# define LIBXL_MIGRATION_PORT_MAX  49216
+
 # define LIBXL_CONFIG_DIR SYSCONFDIR "/libvirt/libxl"
 # define LIBXL_AUTOSTART_DIR LIBXL_CONFIG_DIR "/autostart"
 # define LIBXL_STATE_DIR LOCALSTATEDIR "/run/libvirt/libxl"
@@ -115,6 +118,9 @@ struct _libxlDriverPrivate {
     /* Immutable pointer, self-locking APIs */
     virPortAllocatorPtr reservedVNCPorts;
 
+    /* Immutable pointer, self-locking APIs */
+    virPortAllocatorPtr migrationPorts;
+
     /* Immutable pointer, lockless APIs*/
     virSysinfoDefPtr hostsysinfo;
 };
diff --git a/src/libxl/libxl_domain.h b/src/libxl/libxl_domain.h
index 979ce2a..9d48049 100644
--- a/src/libxl/libxl_domain.h
+++ b/src/libxl/libxl_domain.h
@@ -69,6 +69,7 @@ struct _libxlDomainObjPrivate {
     virChrdevsPtr devs;
     libxl_evgen_domain_death *deathW;
     libxlDriverPrivatePtr driver;
+    unsigned short migrationPort;
 
     struct libxlDomainJobObj job;
 };
diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c
index a6ae8a1..acf68b8 100644
--- a/src/libxl/libxl_driver.c
+++ b/src/libxl/libxl_driver.c
@@ -45,6 +45,7 @@
 #include "libxl_domain.h"
 #include "libxl_driver.h"
 #include "libxl_conf.h"
+#include "libxl_migration.h"
 #include "xen_xm.h"
 #include "xen_sxpr.h"
 #include "virtypedparam.h"
@@ -209,6 +210,7 @@ libxlStateCleanup(void)
     virObjectUnref(libxl_driver->xmlopt);
     virObjectUnref(libxl_driver->domains);
     virObjectUnref(libxl_driver->reservedVNCPorts);
+    virObjectUnref(libxl_driver->migrationPorts);
 
     virObjectEventStateFree(libxl_driver->domainEventState);
     virSysinfoDefFree(libxl_driver->hostsysinfo);
@@ -296,6 +298,13 @@ libxlStateInitialize(bool privileged,
                               LIBXL_VNC_PORT_MAX)))
         goto error;
 
+    /* Allocate bitmap for migration port reservation */
+    if (!(libxl_driver->migrationPorts =
+          virPortAllocatorNew(_("migration"),
+                              LIBXL_MIGRATION_PORT_MIN,
+                              LIBXL_MIGRATION_PORT_MAX)))
+        goto error;
+
     if (!(libxl_driver->domains = virDomainObjListNew()))
         goto error;
 
@@ -4120,6 +4129,7 @@ libxlConnectSupportsFeature(virConnectPtr conn, int feature)
 
     switch (feature) {
     case VIR_DRV_FEATURE_TYPED_PARAM_STRING:
+    case VIR_DRV_FEATURE_MIGRATION_PARAMS:
         return 1;
     default:
         return 0;
@@ -4298,6 +4308,212 @@ libxlNodeDeviceReset(virNodeDevicePtr dev)
     return ret;
 }
 
+static char *
+libxlDomainMigrateBegin3Params(virDomainPtr domain,
+                               virTypedParameterPtr params,
+                               int nparams,
+                               char **cookieout ATTRIBUTE_UNUSED,
+                               int *cookieoutlen ATTRIBUTE_UNUSED,
+                               unsigned int flags)
+{
+    const char *xmlin = NULL;
+    virDomainObjPtr vm = NULL;
+
+    virCheckFlags(LIBXL_MIGRATION_FLAGS, NULL);
+    if (virTypedParamsValidate(params, nparams, LIBXL_MIGRATION_PARAMETERS) < 0)
+        return NULL;
+
+    if (virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_XML,
+                                &xmlin) < 0)
+        return NULL;
+
+    if (!(vm = libxlDomObjFromDomain(domain)))
+        return NULL;
+
+    if (virDomainMigrateBegin3ParamsEnsureACL(domain->conn, vm->def) < 0) {
+        virObjectUnlock(vm);
+        return NULL;
+    }
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_OPERATION_INVALID,
+                       "%s", _("domain is not running"));
+        virObjectUnlock(vm);
+        return NULL;
+    }
+
+    return libxlDomainMigrationBegin(domain->conn, vm, xmlin);
+}
+
+static int
+libxlDomainMigratePrepare3Params(virConnectPtr dconn,
+                                 virTypedParameterPtr params,
+                                 int nparams,
+                                 const char *cookiein ATTRIBUTE_UNUSED,
+                                 int cookieinlen ATTRIBUTE_UNUSED,
+                                 char **cookieout ATTRIBUTE_UNUSED,
+                                 int *cookieoutlen ATTRIBUTE_UNUSED,
+                                 char **uri_out,
+                                 unsigned int flags)
+{
+    libxlDriverPrivatePtr driver = dconn->privateData;
+    virDomainDefPtr def = NULL;
+    const char *dom_xml = NULL;
+    const char *dname = NULL;
+    const char *uri_in = NULL;
+
+    virCheckFlags(LIBXL_MIGRATION_FLAGS, -1);
+    if (virTypedParamsValidate(params, nparams, LIBXL_MIGRATION_PARAMETERS) < 0)
+        goto error;
+
+    if (virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_XML,
+                                &dom_xml) < 0 ||
+        virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_NAME,
+                                &dname) < 0 ||
+        virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_URI,
+                                &uri_in) < 0)
+
+        goto error;
+
+    if (!(def = libxlDomainMigrationPrepareDef(driver, dom_xml, dname)))
+        goto error;
+
+    if (virDomainMigratePrepare3ParamsEnsureACL(dconn, def) < 0)
+        goto error;
+
+    if (libxlDomainMigrationPrepare(dconn, def, uri_in, uri_out) < 0)
+        goto error;
+
+    return 0;
+
+ error:
+    virDomainDefFree(def);
+    return -1;
+}
+
+static int
+libxlDomainMigratePerform3Params(virDomainPtr dom,
+                                 const char *dconnuri,
+                                 virTypedParameterPtr params,
+                                 int nparams,
+                                 const char *cookiein ATTRIBUTE_UNUSED,
+                                 int cookieinlen ATTRIBUTE_UNUSED,
+                                 char **cookieout ATTRIBUTE_UNUSED,
+                                 int *cookieoutlen ATTRIBUTE_UNUSED,
+                                 unsigned int flags)
+{
+    libxlDriverPrivatePtr driver = dom->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    const char *dom_xml = NULL;
+    const char *dname = NULL;
+    const char *uri = NULL;
+    int ret = -1;
+
+    virCheckFlags(LIBXL_MIGRATION_FLAGS, -1);
+    if (virTypedParamsValidate(params, nparams, LIBXL_MIGRATION_PARAMETERS) < 0)
+        goto cleanup;
+
+    if (virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_XML,
+                                &dom_xml) < 0 ||
+        virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_NAME,
+                                &dname) < 0 ||
+        virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_URI,
+                                &uri) < 0)
+
+        goto cleanup;
+
+    if (!(vm = libxlDomObjFromDomain(dom)))
+        goto cleanup;
+
+    if (virDomainMigratePerform3ParamsEnsureACL(dom->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (libxlDomainMigrationPerform(driver, vm, dom_xml, dconnuri,
+                                    uri, dname, flags) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    if (vm)
+        virObjectUnlock(vm);
+    return ret;
+}
+
+static virDomainPtr
+libxlDomainMigrateFinish3Params(virConnectPtr dconn,
+                                virTypedParameterPtr params,
+                                int nparams,
+                                const char *cookiein ATTRIBUTE_UNUSED,
+                                int cookieinlen ATTRIBUTE_UNUSED,
+                                char **cookieout ATTRIBUTE_UNUSED,
+                                int *cookieoutlen ATTRIBUTE_UNUSED,
+                                unsigned int flags,
+                                int cancelled)
+{
+    libxlDriverPrivatePtr driver = dconn->privateData;
+    virDomainObjPtr vm = NULL;
+    const char *dname = NULL;
+
+    virCheckFlags(LIBXL_MIGRATION_FLAGS, NULL);
+    if (virTypedParamsValidate(params, nparams, LIBXL_MIGRATION_PARAMETERS) < 0)
+        return NULL;
+
+    if (virTypedParamsGetString(params, nparams,
+                                VIR_MIGRATE_PARAM_DEST_NAME,
+                                &dname) < 0)
+        return NULL;
+
+    if (!dname ||
+        !(vm = virDomainObjListFindByName(driver->domains, dname))) {
+        virReportError(VIR_ERR_NO_DOMAIN,
+                       _("no domain with matching name '%s'"),
+                       NULLSTR(dname));
+        return NULL;
+    }
+
+    if (virDomainMigrateFinish3ParamsEnsureACL(dconn, vm->def) < 0) {
+        virObjectUnlock(vm);
+        return NULL;
+    }
+
+    return libxlDomainMigrationFinish(dconn, vm, flags, cancelled);
+}
+
+static int
+libxlDomainMigrateConfirm3Params(virDomainPtr domain,
+                                 virTypedParameterPtr params,
+                                 int nparams,
+                                 const char *cookiein ATTRIBUTE_UNUSED,
+                                 int cookieinlen ATTRIBUTE_UNUSED,
+                                 unsigned int flags,
+                                 int cancelled)
+{
+    libxlDriverPrivatePtr driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+
+    virCheckFlags(LIBXL_MIGRATION_FLAGS, -1);
+    if (virTypedParamsValidate(params, nparams, LIBXL_MIGRATION_PARAMETERS) < 0)
+        return -1;
+
+    if (!(vm = libxlDomObjFromDomain(domain)))
+        return -1;
+
+    if (virDomainMigrateConfirm3ParamsEnsureACL(domain->conn, vm->def) < 0) {
+        virObjectUnlock(vm);
+        return -1;
+    }
+
+    return libxlDomainMigrationConfirm(driver, vm, flags, cancelled);
+}
+
 
 static virDriver libxlDriver = {
     .no = VIR_DRV_LIBXL,
@@ -4388,6 +4604,11 @@ static virDriver libxlDriver = {
     .nodeDeviceDetachFlags = libxlNodeDeviceDetachFlags, /* 1.2.3 */
     .nodeDeviceReAttach = libxlNodeDeviceReAttach, /* 1.2.3 */
     .nodeDeviceReset = libxlNodeDeviceReset, /* 1.2.3 */
+    .domainMigrateBegin3Params = libxlDomainMigrateBegin3Params, /* 1.2.3 */
+    .domainMigratePrepare3Params = libxlDomainMigratePrepare3Params, /* 1.2.3 */
+    .domainMigratePerform3Params = libxlDomainMigratePerform3Params, /* 1.2.3 */
+    .domainMigrateFinish3Params = libxlDomainMigrateFinish3Params, /* 1.2.3 */
+    .domainMigrateConfirm3Params = libxlDomainMigrateConfirm3Params, /* 1.2.3 */
 };
 
 static virStateDriver libxlStateDriver = {
diff --git a/src/libxl/libxl_migration.c b/src/libxl/libxl_migration.c
new file mode 100644
index 0000000..4b74ef4
--- /dev/null
+++ b/src/libxl/libxl_migration.c
@@ -0,0 +1,920 @@
+/*
+ * libxl_migration.c: methods for handling migration with libxenlight
+ *
+ * Copyright (C) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Jim Fehlig <jfehlig@xxxxxxxx>
+ *     Chunyan Liu <cyliu@xxxxxxxx>
+ */
+
+#include <config.h>
+
+#include "internal.h"
+#include "virlog.h"
+#include "virerror.h"
+#include "virconf.h"
+#include "datatypes.h"
+#include "virfile.h"
+#include "viralloc.h"
+#include "viruuid.h"
+#include "vircommand.h"
+#include "virstring.h"
+#include "rpc/virnetsocket.h"
+#include "libxl_domain.h"
+#include "libxl_driver.h"
+#include "libxl_conf.h"
+#include "libxl_migration.h"
+
+#define VIR_FROM_THIS VIR_FROM_LIBXL
+
+VIR_LOG_INIT("libxl.libxl_migration");
+
+#define LIBXL_MIGRATION_PROTO_VERSION 1
+
+typedef struct _libxlMigrateReceiveArgs {
+    virConnectPtr conn;
+    virDomainObjPtr vm;
+
+    /* for freeing listen sockets */
+    virNetSocketPtr *socks;
+    size_t nsocks;
+} libxlMigrateReceiveArgs;
+
+/*
+ * For future extensibility, a simple messaging protocol used to send migration
+ * data between libxl hosts.  The message is encapsulated in json and currently
+ * includes the following entries:
+ *
+ * {"libvirt-libxl-mig-msg" :
+ *   {"version" : $ver},
+ *   {"state" : $state},
+ *   {"status" : $status}
+ * }
+ *
+ * Possible $state values are "negotiate-version", "send-binary-data",
+ * "sending-binary-data", "received-binary-data", "done", and "error".
+ *
+ * Migration between source and destination libxl hosts is performed with the
+ * following message exchange:
+ *
+ * - src->dst: connect
+ * - dst->src: state="negotiate-version", version=LIBXL_MIGRATION_PROTO_VERSION
+ * - src->dst: state-"negotiate-version",
+ *             version=min(dst ver,  LIBXL_MIGRATION_PROTO_VERSION)
+ * - dst->src: state="send-binary-data", version=negotiatedversion
+ * - src->dst: state="sending-binary-data", version=negotiatedversion
+ * _ src->dst: binary migration data
+ * - dst->src: state="received-binary-data", version=negotiatedversion
+ * - src->dst: state="done", version=negotiatedversion
+ *
+ */
+
+static virJSONValuePtr
+libxlMigrationMsgNew(const char *state)
+{
+    virJSONValuePtr msg;
+    virJSONValuePtr body;
+
+    if (!(msg = virJSONValueNewObject()))
+        return NULL;
+
+    if (!(body = virJSONValueNewObject())) {
+        virJSONValueFree(msg);
+        return NULL;
+    }
+
+    virJSONValueObjectAppendNumberInt(body, "version",
+                                      LIBXL_MIGRATION_PROTO_VERSION);
+    virJSONValueObjectAppendString(body, "state", state);
+    virJSONValueObjectAppendNumberInt(body, "status", 0);
+
+    virJSONValueObjectAppend(msg, "libvirt-libxl-mig-msg", body);
+
+    return msg;
+}
+
+static bool
+libxlMigrationMsgIsState(virJSONValuePtr message, const char *state)
+{
+    virJSONValuePtr body;
+    const char *msg_state;
+
+    if (!(body = virJSONValueObjectGet(message, "libvirt-libxl-mig-msg")))
+        return false;
+
+    msg_state = virJSONValueObjectGetString(body, "state");
+    if (!msg_state || STRNEQ(msg_state, state))
+        return false;
+
+    return true;
+}
+
+static int
+libxlMigrationMsgSetState(virJSONValuePtr message, const char *state)
+{
+    virJSONValuePtr body;
+
+    if (!(body = virJSONValueObjectGet(message, "libvirt-libxl-mig-msg")))
+        return -1;
+
+    virJSONValueObjectRemoveKey(body, "state", NULL);
+    virJSONValueObjectAppendString(body, "state", state);
+
+    return 0;
+}
+
+
+static int
+libxlMigrationMsgSend(int fd, virJSONValuePtr message, int status)
+{
+    int ret = -1;
+    virJSONValuePtr body;
+    int len;
+    char *msgstr;
+
+    if (!(body = virJSONValueObjectGet(message, "libvirt-libxl-mig-msg")))
+        return -1;
+
+    virJSONValueObjectRemoveKey(body, "status", NULL);
+    virJSONValueObjectAppendNumberInt(body, "status", status);
+
+    if (!(msgstr = virJSONValueToString(message, false)))
+        return -1;
+
+    VIR_DEBUG("Sending migration message %s", msgstr);
+    len = strlen(msgstr) + 1;
+    if (safewrite(fd, msgstr, len) != len) {
+        virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                       _("Failed to send migration message"));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(msgstr);
+    return ret;
+}
+
+static virJSONValuePtr
+libxlMigrationMsgReceive(int fd)
+{
+    char buf[512];
+    int ret;
+    virJSONValuePtr msg = NULL;
+    virJSONValuePtr body;
+    int status;
+    size_t i;
+
+    for (i = 0; i < sizeof(buf);) {
+        ret = saferead(fd, &(buf[i]), 1);
+        if (ret == -1 && errno != EAGAIN)
+            goto error;
+        if (ret == 1) {
+            if (buf[i] == '\0')
+                break;
+            i++;
+        }
+    }
+
+    VIR_DEBUG("Received migration message %s", buf);
+    if (!(msg = virJSONValueFromString(buf)))
+        return NULL;
+
+    if (!(body = virJSONValueObjectGet(msg, "libvirt-libxl-mig-msg")))
+        goto error;
+
+    if (virJSONValueObjectGetNumberInt(body, "status", &status) < 0)
+        goto error;
+
+    if (status != 0)
+        goto error;
+
+    return msg;
+
+ error:
+    virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                   _("Failed to receive migration message"));
+    virJSONValueFree(msg);
+    return NULL;
+}
+
+static int
+libxlMigrationSrcNegotiate(int fd)
+{
+    int ret = -1;
+    virJSONValuePtr msg;
+    virJSONValuePtr body;
+    int ver;
+
+    if (!(msg = libxlMigrationMsgReceive(fd)))
+        return -1;
+
+    if (!libxlMigrationMsgIsState(msg, "negotiate-version"))
+        goto cleanup;
+    /*
+     * ACK by returning the message, with version set to
+     * min(dest ver, src ver)
+     */
+    if (!(body = virJSONValueObjectGet(msg, "libvirt-libxl-mig-msg")))
+        goto cleanup;
+
+    if (virJSONValueObjectGetNumberInt(body, "version", &ver) < 0)
+        goto cleanup;
+    if (ver > LIBXL_MIGRATION_PROTO_VERSION) {
+        virJSONValueObjectRemoveKey(body, "version", NULL);
+        virJSONValueObjectAppendNumberInt(body,
+                                          "version",
+                                          LIBXL_MIGRATION_PROTO_VERSION);
+    }
+
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(msg);
+    return ret;
+}
+
+static int
+libxlMigrationDstNegotiate(int fd, virJSONValuePtr *message)
+{
+    virJSONValuePtr msg;
+
+    if (!(msg = libxlMigrationMsgNew("negotiate-version")))
+            return -1;
+
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0) {
+        virJSONValueFree(msg);
+        return -1;
+    }
+
+    /*
+     * Receive ACK.  src set version=min(dst ver, highest src ver),
+     * which is the negotiated version.  Use this message with
+     * negotiated version throughout the transaction.
+     */
+    virJSONValueFree(msg);
+    if (!(msg = libxlMigrationMsgReceive(fd)))
+        return -1;
+
+    *message = msg;
+    return 0;
+}
+
+static int
+libxlMigrationSrcReady(int fd)
+{
+    int ret = -1;
+    virJSONValuePtr msg;
+
+    if (!(msg = libxlMigrationMsgReceive(fd)))
+        return -1;
+
+    if (!libxlMigrationMsgIsState(msg, "send-binary-data"))
+        goto cleanup;
+
+    /* ACK by returning the message with state sending */
+    libxlMigrationMsgSetState(msg, "sending-binary-data");
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(msg);
+    return ret;
+}
+
+static int
+libxlMigrationDstReady(int fd, virJSONValuePtr msg)
+{
+    int ret = -1;
+    virJSONValuePtr tmp = NULL;
+
+    libxlMigrationMsgSetState(msg, "send-binary-data");
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0)
+        return -1;
+
+    /* Receive ACK, state sending */
+    if (!(tmp = libxlMigrationMsgReceive(fd)))
+            return -1;
+    if (!libxlMigrationMsgIsState(tmp, "sending-binary-data"))
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(tmp);
+    return ret;
+}
+
+static int
+libxlMigrationSrcDone(int fd)
+{
+    int ret = -1;
+    virJSONValuePtr msg;
+
+    if (!(msg = libxlMigrationMsgReceive(fd)))
+        return -1;
+
+    if (!libxlMigrationMsgIsState(msg, "received-binary-data"))
+        goto cleanup;
+
+    /* ACK by returning the message with state done */
+    libxlMigrationMsgSetState(msg, "done");
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(msg);
+    return ret;
+}
+
+static int
+libxlMigrationDestDone(int fd, virJSONValuePtr msg)
+{
+    int ret = -1;
+    virJSONValuePtr tmp;
+
+    libxlMigrationMsgSetState(msg, "received-binary-data");
+    if (libxlMigrationMsgSend(fd, msg, 0) < 0)
+        return -1;
+
+    /* Receive ACK, state done */
+    if (!(tmp = libxlMigrationMsgReceive(fd)))
+        return -1;
+    if (!libxlMigrationMsgIsState(tmp, "done"))
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(tmp);
+    return ret;
+}
+
+static void
+libxlMigrationSrcFailed(int fd, int status)
+{
+    virJSONValuePtr msg;
+
+    if (!(msg = libxlMigrationMsgNew("error")))
+            return;
+    libxlMigrationMsgSend(fd, msg, status);
+    virJSONValueFree(msg);
+}
+
+static void
+libxlMigrationDstFailed(int fd, virJSONValuePtr msg, int status)
+{
+    libxlMigrationMsgSetState(msg, "error");
+    libxlMigrationMsgSend(fd, msg, status);
+}
+
+static void
+libxlDoMigrateReceive(virNetSocketPtr sock,
+                      int events ATTRIBUTE_UNUSED,
+                      void *opaque)
+{
+    libxlMigrateReceiveArgs *data = opaque;
+    virConnectPtr conn = data->conn;
+    virDomainObjPtr vm = data->vm;
+    libxlDomainObjPrivatePtr priv = vm->privateData;
+    virNetSocketPtr *socks = data->socks;
+    size_t nsocks = data->nsocks;
+    libxlDriverPrivatePtr driver = conn->privateData;
+    virNetSocketPtr client_sock;
+    virJSONValuePtr mig_msg = NULL;
+    int recvfd;
+    size_t i;
+    int ret;
+
+    virNetSocketAccept(sock, &client_sock);
+    if (client_sock == NULL) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("Fail to accept migration connection"));
+        goto cleanup;
+    }
+    VIR_DEBUG("Accepted migration connection\n");
+    recvfd = virNetSocketDupFD(client_sock, true);
+    virObjectUnref(client_sock);
+
+    if (libxlMigrationDstNegotiate(recvfd, &mig_msg) < 0)
+        goto cleanup;
+
+    if (libxlMigrationDstReady(recvfd, mig_msg) < 0)
+        goto cleanup;
+
+    virObjectLock(vm);
+    ret = libxlDomainStart(driver, vm, false, recvfd);
+    virObjectUnlock(vm);
+
+    if (ret < 0) {
+        libxlMigrationDstFailed(recvfd, mig_msg, ret);
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Failed to restore domain with libxenlight"));
+        if (!vm->persistent)
+            virDomainObjListRemove(driver->domains, vm);
+        goto cleanup;
+    }
+
+    if (libxlMigrationDestDone(recvfd, mig_msg) < 0) {
+        virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                       _("Failed to notify sender that migration completed"));
+        goto cleanup_dom;
+    }
+
+    /* Remove all listen socks from event handler, and close them. */
+    for (i = 0; i < nsocks; i++) {
+        virNetSocketUpdateIOCallback(socks[i], 0);
+        virNetSocketRemoveIOCallback(socks[i]);
+        virNetSocketClose(socks[i]);
+        virObjectUnref(socks[i]);
+    }
+    VIR_FREE(socks);
+    goto cleanup;
+
+ cleanup_dom:
+    libxl_domain_destroy(priv->ctx, vm->def->id, NULL);
+    vm->def->id = -1;
+    if (!vm->persistent)
+        virDomainObjListRemove(driver->domains, vm);
+
+ cleanup:
+    VIR_FORCE_CLOSE(recvfd);
+    virJSONValueFree(mig_msg);
+    VIR_FREE(opaque);
+    return;
+}
+
+static int
+libxlDoMigrateSend(libxlDriverPrivatePtr driver,
+                   virDomainObjPtr vm,
+                   unsigned long flags,
+                   int sockfd)
+{
+    libxlDomainObjPrivatePtr priv;
+    libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
+    virObjectEventPtr event = NULL;
+    int xl_flags = 0;
+    int ret = -1;
+
+    if (flags & VIR_MIGRATE_LIVE)
+        xl_flags = LIBXL_SUSPEND_LIVE;
+
+    priv = vm->privateData;
+
+    if (libxlMigrationSrcNegotiate(sockfd) < 0)
+        goto cleanup;
+
+    if (libxlMigrationSrcReady(sockfd) < 0)
+        goto cleanup;
+
+    if (libxl_domain_suspend(priv->ctx, vm->def->id,
+                             sockfd, xl_flags, NULL) != 0) {
+        libxlMigrationSrcFailed(sockfd, ret);
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Failed to save domain '%d' with libxenlight"),
+                       vm->def->id);
+        goto cleanup;
+    }
+
+    if (libxlMigrationSrcDone(sockfd) < 0) {
+        if (libxl_domain_resume(priv->ctx, vm->def->id, 0, 0) != 0) {
+            VIR_DEBUG("Failed to resume domain '%d' with libxenlight",
+                      vm->def->id);
+            virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
+                                 VIR_DOMAIN_PAUSED_MIGRATION);
+            event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED,
+                                             VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED);
+            if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0)
+                goto cleanup;
+        }
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (event)
+        libxlDomainEventQueue(driver, event);
+    virObjectUnref(cfg);
+    return ret;
+}
+
+static bool
+libxlDomainMigrationIsAllowed(virDomainDefPtr def)
+{
+    /* Migration is not allowed if definition contains any hostdevs */
+    if (def->nhostdevs > 0) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("domain has assigned host devices"));
+        return false;
+    }
+
+    return true;
+}
+
+char *
+libxlDomainMigrationBegin(virConnectPtr conn,
+                          virDomainObjPtr vm,
+                          const char *xmlin)
+{
+    libxlDriverPrivatePtr driver = conn->privateData;
+    libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
+    virDomainDefPtr tmpdef = NULL;
+    virDomainDefPtr def;
+    char *xml = NULL;
+
+    if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (xmlin) {
+        if (!(tmpdef = virDomainDefParseString(xmlin, cfg->caps,
+                                               driver->xmlopt,
+                                               1 << VIR_DOMAIN_VIRT_XEN,
+                                               VIR_DOMAIN_XML_INACTIVE)))
+            goto endjob;
+
+        def = tmpdef;
+    } else {
+        def = vm->def;
+    }
+
+    if (!libxlDomainMigrationIsAllowed(def))
+        goto endjob;
+
+    xml = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE);
+
+ cleanup:
+    if (vm)
+        virObjectUnlock(vm);
+
+    virDomainDefFree(tmpdef);
+    virObjectUnref(cfg);
+    return xml;
+
+ endjob:
+    if (!libxlDomainObjEndJob(driver, vm))
+        vm = NULL;
+    goto cleanup;
+}
+
+virDomainDefPtr
+libxlDomainMigrationPrepareDef(libxlDriverPrivatePtr driver,
+                               const char *dom_xml,
+                               const char *dname)
+{
+    libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
+    virDomainDefPtr def;
+    char *name = NULL;
+
+    if (!dom_xml) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("no domain XML passed"));
+        return NULL;
+    }
+
+    if (!(def = virDomainDefParseString(dom_xml, cfg->caps, driver->xmlopt,
+                                        1 << VIR_DOMAIN_VIRT_XEN,
+                                        VIR_DOMAIN_XML_INACTIVE)))
+        goto cleanup;
+
+    if (dname) {
+        name = def->name;
+        if (VIR_STRDUP(def->name, dname) < 0) {
+            virDomainDefFree(def);
+            def = NULL;
+        }
+    }
+
+ cleanup:
+    virObjectUnref(cfg);
+    VIR_FREE(name);
+    return def;
+}
+
+int
+libxlDomainMigrationPrepare(virConnectPtr dconn,
+                            virDomainDefPtr def,
+                            const char *uri_in,
+                            char **uri_out)
+{
+    libxlDriverPrivatePtr driver = dconn->privateData;
+    virDomainObjPtr vm = NULL;
+    char *hostname = NULL;
+    unsigned short port;
+    char portstr[100];
+    virURIPtr uri = NULL;
+    virNetSocketPtr *socks = NULL;
+    size_t nsocks = 0;
+    int nsocks_listen = 0;
+    libxlMigrateReceiveArgs *args;
+    size_t i;
+    int ret = -1;
+
+    if (!(vm = virDomainObjListAdd(driver->domains, def,
+                                   driver->xmlopt,
+                                   VIR_DOMAIN_OBJ_LIST_ADD_LIVE |
+                                   VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE,
+                                   NULL)))
+        goto cleanup;
+
+    /* Create socket connection to receive migration data */
+    if (!uri_in) {
+        if ((hostname = virGetHostname()) == NULL)
+            goto cleanup;
+
+        if (STRPREFIX(hostname, "localhost")) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("hostname on destination resolved to localhost,"
+                             " but migration requires an FQDN"));
+            goto cleanup;
+        }
+
+        if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0)
+            goto cleanup;
+
+        if (virAsprintf(uri_out, "tcp://%s:%d", hostname, port) < 0)
+            goto cleanup;
+    } else {
+        if (!(STRPREFIX(uri_in, "tcp://"))) {
+            /* not full URI, add prefix tcp:// */
+            char *tmp;
+            if (virAsprintf(&tmp, "tcp://%s", uri_in) < 0)
+                goto cleanup;
+            uri = virURIParse(tmp);
+            VIR_FREE(tmp);
+        } else {
+            uri = virURIParse(uri_in);
+        }
+
+        if (uri == NULL) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("unable to parse URI: %s"),
+                           uri_in);
+            goto cleanup;
+        }
+
+        if (uri->server == NULL) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("missing host in migration URI: %s"),
+                           uri_in);
+            goto cleanup;
+        } else {
+            hostname = uri->server;
+        }
+
+        if (uri->port == 0) {
+            if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0)
+                goto cleanup;
+
+        } else {
+            port = uri->port;
+        }
+
+        if (virAsprintf(uri_out, "tcp://%s:%d", hostname, port) < 0)
+            goto cleanup;
+    }
+
+    snprintf(portstr, sizeof(portstr), "%d", port);
+
+    if (virNetSocketNewListenTCP(hostname, portstr, &socks, &nsocks) < 0) {
+        virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                       _("Fail to create socket for incoming migration"));
+        goto cleanup;
+    }
+
+    if (VIR_ALLOC(args) < 0)
+        goto cleanup;
+
+    args->conn = dconn;
+    args->vm = vm;
+    args->socks = socks;
+    args->nsocks = nsocks;
+
+    for (i = 0; i < nsocks; i++) {
+        if (virNetSocketSetBlocking(socks[i], true) < 0)
+             continue;
+
+        if (virNetSocketListen(socks[i], 1) < 0)
+            continue;
+
+        if (virNetSocketAddIOCallback(socks[i],
+                                      0,
+                                      libxlDoMigrateReceive,
+                                      args,
+                                      NULL) < 0) {
+            continue;
+        }
+
+        virNetSocketUpdateIOCallback(socks[i], VIR_EVENT_HANDLE_READABLE);
+        nsocks_listen++;
+    }
+
+    if (!nsocks_listen)
+        goto cleanup;
+
+    ret = 0;
+    goto done;
+
+ cleanup:
+    for (i = 0; i < nsocks; i++) {
+        virNetSocketClose(socks[i]);
+        virObjectUnref(socks[i]);
+    }
+    VIR_FREE(socks);
+
+ done:
+    virURIFree(uri);
+    if (vm)
+        virObjectUnlock(vm);
+    return ret;
+}
+
+int
+libxlDomainMigrationPerform(libxlDriverPrivatePtr driver,
+                            virDomainObjPtr vm,
+                            const char *dom_xml ATTRIBUTE_UNUSED,
+                            const char *dconnuri ATTRIBUTE_UNUSED,
+                            const char *uri_str,
+                            const char *dname ATTRIBUTE_UNUSED,
+                            unsigned int flags)
+{
+    char *hostname = NULL;
+    unsigned short port = 0;
+    char portstr[100];
+    virURIPtr uri = NULL;
+    virNetSocketPtr sock;
+    int sockfd = -1;
+    int saved_errno = EINVAL;
+    int ret = -1;
+
+    /* parse dst host:port from uri */
+    uri = virURIParse(uri_str);
+    if (uri == NULL || uri->server == NULL || uri->port == 0)
+        goto cleanup;
+
+    hostname = uri->server;
+    port = uri->port;
+    snprintf(portstr, sizeof(portstr), "%d", port);
+
+    /* socket connect to dst host:port */
+    if (virNetSocketNewConnectTCP(hostname, portstr, &sock) < 0) {
+        virReportSystemError(saved_errno,
+                             _("unable to connect to '%s:%s'"),
+                             hostname, portstr);
+        goto cleanup;
+    }
+
+    if (virNetSocketSetBlocking(sock, true) < 0) {
+        virObjectUnref(sock);
+        goto cleanup;
+    }
+
+    sockfd = virNetSocketDupFD(sock, true);
+    virObjectUnref(sock);
+
+    /* suspend vm and send saved data to dst through socket fd */
+    virObjectUnlock(vm);
+    ret = libxlDoMigrateSend(driver, vm, flags, sockfd);
+    virObjectLock(vm);
+
+ cleanup:
+    VIR_FORCE_CLOSE(sockfd);
+    virURIFree(uri);
+    return ret;
+}
+
+virDomainPtr
+libxlDomainMigrationFinish(virConnectPtr dconn,
+                           virDomainObjPtr vm,
+                           unsigned int flags,
+                           int cancelled)
+{
+    libxlDriverPrivatePtr driver = dconn->privateData;
+    libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
+    libxlDomainObjPrivatePtr priv = vm->privateData;
+    virObjectEventPtr event = NULL;
+    virDomainPtr dom = NULL;
+
+    virPortAllocatorRelease(driver->migrationPorts, priv->migrationPort);
+    priv->migrationPort = 0;
+
+    if (cancelled)
+        goto cleanup;
+
+    if (!(flags & VIR_MIGRATE_PAUSED)) {
+        if (libxl_domain_unpause(priv->ctx, vm->def->id) != 0) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("Failed to unpause domain"));
+            goto cleanup;
+        }
+
+        virDomainObjSetState(vm, VIR_DOMAIN_RUNNING,
+                             VIR_DOMAIN_RUNNING_MIGRATED);
+        event = virDomainEventLifecycleNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_RESUMED,
+                                         VIR_DOMAIN_EVENT_RESUMED_MIGRATED);
+    } else {
+        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER);
+        event = virDomainEventLifecycleNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);
+    }
+
+    if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0)
+        goto cleanup;
+
+    dom = virGetDomain(dconn, vm->def->name, vm->def->uuid);
+
+    if (dom == NULL) {
+        libxl_domain_destroy(priv->ctx, vm->def->id, NULL);
+        libxlDomainCleanup(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED);
+        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                         VIR_DOMAIN_EVENT_STOPPED_FAILED);
+        libxlDomainEventQueue(driver, event);
+    }
+
+ cleanup:
+    if (event)
+        libxlDomainEventQueue(driver, event);
+    if (vm)
+        virObjectUnlock(vm);
+    virObjectUnref(cfg);
+    return dom;
+}
+
+int
+libxlDomainMigrationConfirm(libxlDriverPrivatePtr driver,
+                            virDomainObjPtr vm,
+                            unsigned int flags,
+                            int cancelled)
+{
+    libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
+    libxlDomainObjPrivatePtr priv = vm->privateData;
+    virObjectEventPtr event = NULL;
+    int ret = -1;
+
+    if (cancelled) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                   _("migration failed, attempting to resume on source host"));
+        if (libxl_domain_resume(priv->ctx, vm->def->id, 1, 0) == 0) {
+            ret = 0;
+        } else {
+            VIR_DEBUG("Unable to resume domain '%s' after failed migration",
+                      vm->def->name);
+            virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
+                                 VIR_DOMAIN_PAUSED_MIGRATION);
+            event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED,
+                                     VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED);
+            ignore_value(virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm));
+        }
+        goto cleanup;
+    }
+
+    libxl_domain_destroy(priv->ctx, vm->def->id, NULL);
+    libxlDomainCleanup(driver, vm, VIR_DOMAIN_SHUTOFF_MIGRATED);
+    event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                              VIR_DOMAIN_EVENT_STOPPED_MIGRATED);
+
+    VIR_DEBUG("Domain '%s' successfully migrated", vm->def->name);
+
+    if (flags & VIR_MIGRATE_UNDEFINE_SOURCE)
+        virDomainDeleteConfig(cfg->configDir, cfg->autostartDir, vm);
+
+    if (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE))
+        virDomainObjListRemove(driver->domains, vm);
+
+    ret = 0;
+
+ cleanup:
+    if (!libxlDomainObjEndJob(driver, vm))
+        vm = NULL;
+    if (event)
+        libxlDomainEventQueue(driver, event);
+    if (vm)
+        virObjectUnlock(vm);
+    virObjectUnref(cfg);
+    return ret;
+}
diff --git a/src/libxl/libxl_migration.h b/src/libxl/libxl_migration.h
new file mode 100644
index 0000000..63d8bdc
--- /dev/null
+++ b/src/libxl/libxl_migration.h
@@ -0,0 +1,78 @@
+/*
+ * libxl_migration.h: methods for handling migration with libxenlight
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *     Jim Fehlig <jfehlig@xxxxxxxx>
+ */
+
+#ifndef LIBXL_MIGRATION_H
+# define LIBXL_MIGRATION_H
+
+# include "libxl_conf.h"
+
+# define LIBXL_MIGRATION_FLAGS                  \
+    (VIR_MIGRATE_LIVE |                         \
+     VIR_MIGRATE_UNDEFINE_SOURCE |              \
+     VIR_MIGRATE_PAUSED)
+
+/* All supported migration parameters and their types. */
+# define LIBXL_MIGRATION_PARAMETERS                             \
+    VIR_MIGRATE_PARAM_URI,              VIR_TYPED_PARAM_STRING, \
+    VIR_MIGRATE_PARAM_DEST_NAME,        VIR_TYPED_PARAM_STRING, \
+    VIR_MIGRATE_PARAM_DEST_XML,         VIR_TYPED_PARAM_STRING, \
+    NULL
+
+char *
+libxlDomainMigrationBegin(virConnectPtr conn,
+                          virDomainObjPtr vm,
+                          const char *xmlin);
+
+virDomainDefPtr
+libxlDomainMigrationPrepareDef(libxlDriverPrivatePtr driver,
+                               const char *dom_xml,
+                               const char *dname);
+
+int
+libxlDomainMigrationPrepare(virConnectPtr dconn,
+                            virDomainDefPtr def,
+                            const char *uri_in,
+                            char **uri_out);
+
+int
+libxlDomainMigrationPerform(libxlDriverPrivatePtr driver,
+                            virDomainObjPtr vm,
+                            const char *dom_xml,
+                            const char *dconnuri,
+                            const char *uri_str,
+                            const char *dname,
+                            unsigned int flags);
+
+virDomainPtr
+libxlDomainMigrationFinish(virConnectPtr dconn,
+                           virDomainObjPtr vm,
+                           unsigned int flags,
+                           int cancelled);
+
+int
+libxlDomainMigrationConfirm(libxlDriverPrivatePtr driver,
+                            virDomainObjPtr vm,
+                            unsigned int flags,
+                            int cancelled);
+
+#endif /* LIBXL_DRIVER_H */
-- 
1.8.1.4

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list




[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]