--- src/libxl/libxl_conf.h | 4 + src/libxl/libxl_driver.c | 641 ++++++++++++++++++++++++++++++++++++++++++++++ src/libxl/libxl_driver.h | 5 + 3 files changed, 650 insertions(+), 0 deletions(-) diff --git a/src/libxl/libxl_conf.h b/src/libxl/libxl_conf.h index 8ba0ee4..2041cc2 100644 --- a/src/libxl/libxl_conf.h +++ b/src/libxl/libxl_conf.h @@ -41,6 +41,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" @@ -109,6 +112,7 @@ struct _libxlDriverPrivate { /* Immutable pointer, self-locking APIs */ virPortAllocatorPtr reservedVNCPorts; + virPortAllocatorPtr reservedMigPorts; /* Immutable pointer, lockless APIs*/ virSysinfoDefPtr hostsysinfo; diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index e2a6d44..93b7153 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -32,6 +32,12 @@ #include <libxl_utils.h> #include <fcntl.h> #include <regex.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netdb.h> #include "internal.h" #include "virlog.h" @@ -52,6 +58,7 @@ #include "virsysinfo.h" #include "viraccessapicheck.h" #include "viratomic.h" +#include "rpc/virnetsocket.h" #define VIR_FROM_THIS VIR_FROM_LIBXL @@ -69,6 +76,20 @@ static libxlDriverPrivatePtr libxl_driver = NULL; +typedef struct _libxlMigrateReceiveArgs { + virConnectPtr conn; + virDomainObjPtr vm; + + /* for freeing listen sockets */ + virNetSocketPtr *socks; + size_t nsocks; +} libxlMigrateReceiveArgs; + +static const char libxlMigrateReceiverReady[]= + "libvirt libxl migration receiver ready, send binary domain data"; +static const char libxlMigrateReceiverFinish[]= + "domain received, ready to unpause"; + /* Function declarations */ static int libxlDomainManagedSaveLoad(virDomainObjPtr vm, @@ -836,6 +857,12 @@ libxlStateInitialize(bool privileged, LIBXL_VNC_PORT_MAX))) goto error; + /* Allocate bitmap for migration port reservation */ + if (!(libxl_driver->reservedMigPorts = + virPortAllocatorNew(LIBXL_MIGRATION_PORT_MIN, + LIBXL_MIGRATION_PORT_MAX))) + goto error; + if (!(libxl_driver->domains = virDomainObjListNew())) goto error; @@ -4175,11 +4202,620 @@ libxlConnectSupportsFeature(virConnectPtr conn, int feature) switch (feature) { case VIR_DRV_FEATURE_TYPED_PARAM_STRING: return 1; + case VIR_DRV_FEATURE_MIGRATION_V3: + return 1; default: return 0; } } +static int +libxlCheckMessageBanner(int fd, const char *banner, int banner_sz) +{ + char buf[banner_sz]; + int ret = 0; + + do { + ret = saferead(fd, buf, banner_sz); + } while (ret == -1 && errno == EAGAIN); + + if (ret != banner_sz || memcmp(buf, banner, banner_sz)) { + return -1; + } + + return 0; +} + +static char * +libxlDomainMigrateBegin3(virDomainPtr domain, + const char *xmlin, + char **cookieout ATTRIBUTE_UNUSED, + int *cookieoutlen ATTRIBUTE_UNUSED, + unsigned long flags, + const char *dname ATTRIBUTE_UNUSED, + unsigned long resource ATTRIBUTE_UNUSED) +{ + libxlDriverPrivatePtr driver = domain->conn->privateData; + libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver); + virDomainObjPtr vm; + virDomainDefPtr def = NULL; + char *xml = NULL; + + virCheckFlags(LIBXL_MIGRATION_FLAGS, NULL); + + vm = virDomainObjListFindByUUID(driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + virReportError(VIR_ERR_OPERATION_INVALID, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (virDomainMigrateBegin3EnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (xmlin) { + if (!(def = virDomainDefParseString(xmlin, cfg->caps, + driver->xmlopt, + 1 << VIR_DOMAIN_VIRT_XEN, + VIR_DOMAIN_XML_INACTIVE))) + goto cleanup; + + xml = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE); + } else { + xml = virDomainDefFormat(vm->def, VIR_DOMAIN_XML_SECURE); + } + +cleanup: + virDomainDefFree(def); + if (vm) + virObjectUnlock(vm); + virObjectUnref(cfg); + return xml; +} + +static void +doMigrateReceive(virNetSocketPtr sock, + int events ATTRIBUTE_UNUSED, + void *opaque) +{ + libxlMigrateReceiveArgs *data = opaque; + virConnectPtr conn = data->conn; + virDomainObjPtr vm = data->vm; + virNetSocketPtr *socks = data->socks; + size_t nsocks = data->nsocks; + libxlDriverPrivatePtr driver = conn->privateData; + virNetSocketPtr client_sock; + int recv_fd; + int len; + 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\n"); + recv_fd = virNetSocketDupFD(client_sock, true); + virObjectUnref(client_sock); + + len = sizeof(libxlMigrateReceiverReady); + if (safewrite(recv_fd, libxlMigrateReceiverReady, len) != len) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to write libxlMigrateReceiverReady")); + goto cleanup; + } + + virObjectLock(vm); + ret = libxlVmStart(driver, vm, false, recv_fd); + virObjectUnlock(vm); + + if (ret < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to restore domain with libxenlight")); + if (!vm->persistent) { + virDomainObjListRemove(driver->domains, vm); + vm = NULL; + } + goto cleanup; + } + + len = sizeof(libxlMigrateReceiverFinish); + if (safewrite(recv_fd, libxlMigrateReceiverFinish, len) != len) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to write libxlMigrateReceiverFinish")); + } + + /* Remove all listen socks from event handler, and close them. */ + if (nsocks) { + for (i = 0; i < nsocks; i++) { + virNetSocketUpdateIOCallback(socks[i], 0); + virNetSocketRemoveIOCallback(socks[i]); + virNetSocketClose(socks[i]); + virObjectUnref(socks[i]); + } + VIR_FREE(socks); + } + +cleanup: + VIR_FORCE_CLOSE(recv_fd); + VIR_FREE(opaque); + return; +} + +static int +doMigrateSend(libxlDriverPrivatePtr driver, + virDomainObjPtr vm, + unsigned long flags, + int sockfd) +{ + libxlDomainObjPrivatePtr priv; + libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver); + virDomainEventPtr event = NULL; + int live = 0; + int ret = -1; + + if (flags & VIR_MIGRATE_LIVE) + live = LIBXL_SUSPEND_LIVE; + + priv = vm->privateData; + + /* read fixed message from dest (ready to receive) */ + if (libxlCheckMessageBanner(sockfd, libxlMigrateReceiverReady, + sizeof(libxlMigrateReceiverReady))) + goto cleanup; + + if (libxl_domain_suspend(priv->ctx, vm->def->id, sockfd, live, NULL) != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to save domain '%d' with libxenlight"), + vm->def->id); + goto cleanup; + } + + /* read fixed message from dest (receive completed) */ + if (libxlCheckMessageBanner(sockfd, libxlMigrateReceiverFinish, + sizeof(libxlMigrateReceiverFinish))) { + 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 = virDomainEventNewFromObj(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 int +libxlDomainMigratePrepare3(virConnectPtr dconn, + const char *cookiein ATTRIBUTE_UNUSED, + int cookieinlen ATTRIBUTE_UNUSED, + char **cookieout ATTRIBUTE_UNUSED, + int *cookieoutlen ATTRIBUTE_UNUSED, + const char *uri_in, + char **uri_out, + unsigned long flags, + const char *dname, + unsigned long resource ATTRIBUTE_UNUSED, + const char *dom_xml) +{ + libxlDriverPrivatePtr driver = dconn->privateData; + libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver); + virDomainDefPtr def = NULL; + 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; + + virCheckFlags(LIBXL_MIGRATION_FLAGS, -1); + + libxlDriverLock(driver); + if (!dom_xml) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("no domain XML passed")); + goto cleanup; + } + def = virDomainDefParseString(dom_xml, cfg->caps, driver->xmlopt, + 1 << VIR_DOMAIN_VIRT_XEN, + VIR_DOMAIN_XML_INACTIVE); + + /* Target domain name, maybe renamed. */ + if (dname) { + if (VIR_STRDUP(def->name, dname) < 0) + goto cleanup; + } + + if (!(vm = virDomainObjListAdd(driver->domains, def, + driver->xmlopt, + VIR_DOMAIN_OBJ_LIST_ADD_LIVE | + VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, + NULL))) + goto cleanup; + + def = NULL; + + if (virDomainMigratePrepare3EnsureACL(dconn, vm->def) < 0) + goto cleanup; + + /* Create socket connection to receive migration data */ + if (!uri_in) { + hostname = virGetHostname(); + if (hostname == NULL) + goto cleanup; + + if (virPortAllocatorAcquire(driver->reservedMigPorts, &port) < 0) + goto cleanup; + + if (port == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Unable to find an unused migrate port")); + goto cleanup; + } + + if (virAsprintf(uri_out, "tcp://%s:%d", hostname, port) < 0) + goto cleanup; + } else { + if (!strstr(uri_in, "//")) { + /* 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->reservedMigPorts, &port) < 0) + goto cleanup; + + if (port == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to find an unused migrate port")); + 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, + doMigrateReceive, + args, + NULL) < 0) { + continue; + } + + virNetSocketUpdateIOCallback(socks[i], VIR_EVENT_HANDLE_READABLE); + nsocks_listen ++; + } + + if (!nsocks_listen) + goto cleanup; + + ret = 0; + goto end; + +cleanup: + if (nsocks) { + for (i = 0 ; i < nsocks ; i++) { + virNetSocketClose(socks[i]); + virObjectUnref(socks[i]); + } + VIR_FREE(socks); + } + +end: + virURIFree(uri); + if (vm) + virObjectUnlock(vm); + virObjectUnref(cfg); + libxlDriverUnlock(driver); + return ret; +} + +static int +libxlDomainMigratePerform3(virDomainPtr dom, + const char *xmlin ATTRIBUTE_UNUSED, + const char *cookiein ATTRIBUTE_UNUSED, + int cookieinlen ATTRIBUTE_UNUSED, + char **cookieout ATTRIBUTE_UNUSED, + int *cookieoutlen ATTRIBUTE_UNUSED, + const char *dconnuri ATTRIBUTE_UNUSED, + const char *uri, + unsigned long flags, + const char *dname ATTRIBUTE_UNUSED, + unsigned long resource ATTRIBUTE_UNUSED) +{ + libxlDriverPrivatePtr driver = dom->conn->privateData; + virDomainObjPtr vm; + char *hostname = NULL; + unsigned short port = 0; + char portstr[100]; + virURIPtr uri_p = NULL; + virNetSocketPtr sock; + int sockfd = -1; + int saved_errno = EINVAL; + int ret = -1; + + virCheckFlags(LIBXL_MIGRATION_FLAGS, -1); + + vm = virDomainObjListFindByUUID(driver->domains, dom->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(dom->uuid, uuidstr); + virReportError(VIR_ERR_OPERATION_INVALID, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (virDomainMigratePerform3EnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + /* parse dst host:port from uri */ + uri_p = virURIParse(uri); + if (uri_p == NULL || uri_p->server == NULL || uri_p->port == 0) + goto cleanup; + + hostname = uri_p->server; + port = uri_p->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 */ + ret = doMigrateSend(driver, vm, flags, sockfd); + +cleanup: + VIR_FORCE_CLOSE(sockfd); + virURIFree(uri_p); + if (vm) + virObjectUnlock(vm); + return ret; +} + +static virDomainPtr +libxlDomainMigrateFinish3(virConnectPtr dconn, + const char *dname, + const char *cookiein ATTRIBUTE_UNUSED, + int cookieinlen ATTRIBUTE_UNUSED, + char **cookieout ATTRIBUTE_UNUSED, + int *cookieoutlen ATTRIBUTE_UNUSED, + const char *dconnuri ATTRIBUTE_UNUSED, + const char *uri, + unsigned long flags, + int cancelled) +{ + libxlDriverPrivatePtr driver = dconn->privateData; + libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver); + unsigned short port = 0; + virURIPtr uri_p = NULL; + virDomainObjPtr vm = NULL; + virDomainPtr dom = NULL; + libxlDomainObjPrivatePtr priv; + virDomainEventPtr event = NULL; + int rc; + + virCheckFlags(LIBXL_MIGRATION_FLAGS, NULL); + + uri_p = virURIParse(uri); + if (uri_p == NULL || uri_p->port == 0) + VIR_DEBUG("Fail to parse port from URI"); + port = uri_p->port; + if (LIBXL_MIGRATION_PORT_MIN <= port && port < LIBXL_MIGRATION_PORT_MAX) { + if (virPortAllocatorRelease(driver->reservedMigPorts, port) < 0) + VIR_DEBUG("Could not mark port %d as unused", port); + } + + vm = virDomainObjListFindByName(driver->domains, dname); + if (!vm) + goto cleanup; + + if (virDomainMigrateFinish3EnsureACL(dconn, vm->def) < 0) + goto cleanup; + + if (!cancelled) { + if (!(flags & VIR_MIGRATE_PAUSED)) { + priv = vm->privateData; + rc = libxl_domain_unpause(priv->ctx, vm->def->id); + if (rc) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to unpause domain")); + goto error; + } + + virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_BOOTED); + if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) + goto error; + } + + dom = virGetDomain(dconn, vm->def->name, vm->def->uuid); + goto cleanup; + } + +error: + if (libxlVmReap(driver, vm, VIR_DOMAIN_SHUTOFF_SAVED)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to destroy domain '%d'"), vm->def->id); + goto cleanup; + } + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_SAVED); + if (!vm->persistent) { + virDomainObjListRemove(driver->domains, vm); + vm = NULL; + } + +cleanup: + virURIFree(uri_p); + if (vm) + virObjectUnlock(vm); + if (event) + libxlDomainEventQueue(driver, event); + virObjectUnref(cfg); + return dom; +} + +static int +libxlDomainMigrateConfirm3(virDomainPtr domain, + const char *cookiein ATTRIBUTE_UNUSED, + int cookieinlen ATTRIBUTE_UNUSED, + unsigned long flags, + int cancelled) +{ + libxlDriverPrivatePtr driver = domain->conn->privateData; + libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver); + virDomainObjPtr vm; + libxlDomainObjPrivatePtr priv; + virDomainEventPtr event = NULL; + int ret = -1; + + virCheckFlags(LIBXL_MIGRATION_FLAGS, -1); + + vm = virDomainObjListFindByUUID(driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (virDomainMigrateConfirm3EnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (cancelled) { + priv = vm->privateData; + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("migration failed, try to resume on our end")); + if (!libxl_domain_resume(priv->ctx, vm->def->id, 0, 0)) { + ret = 0; + } else { + VIR_DEBUG("Failed to resume domain '%d' with libxenlight", + vm->def->id); + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_MIGRATION); + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED); + if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0) + goto cleanup; + } + + goto cleanup; + } + + if (libxlVmReap(driver, vm, VIR_DOMAIN_SHUTOFF_SAVED)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to destroy domain '%d'"), vm->def->id); + goto cleanup; + } + + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_SAVED); + + if (flags & VIR_MIGRATE_UNDEFINE_SOURCE) + virDomainDeleteConfig(cfg->configDir, cfg->autostartDir, vm); + + if (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE)) { + virDomainObjListRemove(driver->domains, vm); + vm = NULL; + } + + VIR_DEBUG("Migration successful.\n"); + ret = 0; + +cleanup: + if (vm) + virObjectUnlock(vm); + if (event) + libxlDomainEventQueue(driver, event); + virObjectUnref(cfg); + return ret; +} + static virDriver libxlDriver = { .no = VIR_DRV_LIBXL, @@ -4249,6 +4885,11 @@ static virDriver libxlDriver = { #ifdef LIBXL_HAVE_DOMAIN_NODEAFFINITY .domainGetNumaParameters = libxlDomainGetNumaParameters, /* 1.1.1 */ #endif + .domainMigrateBegin3 = libxlDomainMigrateBegin3, /* 1.1.3 */ + .domainMigratePrepare3 = libxlDomainMigratePrepare3, /* 1.1.3 */ + .domainMigratePerform3 = libxlDomainMigratePerform3, /* 1.1.3 */ + .domainMigrateFinish3 = libxlDomainMigrateFinish3, /* 1.1.3 */ + .domainMigrateConfirm3 = libxlDomainMigrateConfirm3, /* 1.1.3 */ .nodeGetFreeMemory = libxlNodeGetFreeMemory, /* 0.9.0 */ .nodeGetCellsFreeMemory = libxlNodeGetCellsFreeMemory, /* 1.1.1 */ .connectDomainEventRegister = libxlConnectDomainEventRegister, /* 0.9.0 */ diff --git a/src/libxl/libxl_driver.h b/src/libxl/libxl_driver.h index a33d60c..25ac2b8 100644 --- a/src/libxl/libxl_driver.h +++ b/src/libxl/libxl_driver.h @@ -24,6 +24,11 @@ #ifndef LIBXL_DRIVER_H # define LIBXL_DRIVER_H +# define LIBXL_MIGRATION_FLAGS \ + (VIR_MIGRATE_LIVE | \ + VIR_MIGRATE_UNDEFINE_SOURCE | \ + VIR_MIGRATE_PAUSED) + int libxlRegister(void); #endif /* LIBXL_DRIVER_H */ -- 1.6.0.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list