Extend the QEMU migration cookie structure to allow information
about the destination host graphics setup to be passed by to
the source host. This will enable seamless migration of any
connected graphics clients
* src/qemu/qemu_migration.c: Add graphics info to migration
cookies
* daemon/libvirtd.c: Always initialize gnutls to enable
x509 cert parsing in QEMU
---
cfg.mk | 1 +
daemon/libvirtd.c | 8 +-
src/qemu/qemu_migration.c | 264 +++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 261 insertions(+), 12 deletions(-)
diff --git a/cfg.mk b/cfg.mk
index 92974a9..f414ea6 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -73,6 +73,7 @@ useless_free_options = \
--name=VIR_FREE \
--name=qemuCapsFree \
--name=qemuMigrationCookieFree \
+ --name=qemuMigrationCookieGraphicsFree \
--name=sexpr_free \
--name=virBitmapFree \
--name=virCPUDefFree \
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c
index 42cbe5d..ae04078 100644
--- a/daemon/libvirtd.c
+++ b/daemon/libvirtd.c
@@ -317,9 +317,6 @@ remoteInitializeGnuTLS (void)
{
int err;
- /* Initialise GnuTLS. */
- gnutls_global_init ();
-
err = gnutls_certificate_allocate_credentials (&x509_cred);
if (err) {
VIR_ERROR(_("gnutls_certificate_allocate_credentials: %s"),
@@ -3310,6 +3307,11 @@ int main(int argc, char **argv) {
goto error;
}
+ /* Initialise GnuTLS. Required even if we don't use TLS
+ * for libvirtd, because QEMU driver needs to be able to
+ * parse x590 certificates for seemless migration */
+ gnutls_global_init();
+
if (!(server = qemudInitialize())) {
ret = VIR_DAEMON_ERR_INIT;
goto error;
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 98773c0..3bc7ed8 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -47,6 +47,20 @@
#define timeval_to_ms(tv) (((tv).tv_sec * 1000ull) + ((tv).tv_usec / 1000))
+enum qemuMigrationCookieFlags {
+ QEMU_MIGRATION_COOKIE_GRAPHICS = (1 << 0),
+};
+
+typedef struct _qemuMigrationCookieGraphics qemuMigrationCookieGraphics;
+typedef qemuMigrationCookieGraphics *qemuMigrationCookieGraphicsPtr;
+struct _qemuMigrationCookieGraphics {
+ int type;
+ int port;
+ int tlsPort;
+ char *listen;
+ char *tlsSubject;
+};
+
typedef struct _qemuMigrationCookie qemuMigrationCookie;
typedef qemuMigrationCookie *qemuMigrationCookiePtr;
struct _qemuMigrationCookie {
@@ -59,20 +73,142 @@ struct _qemuMigrationCookie {
/* Guest properties */
unsigned char uuid[VIR_UUID_BUFLEN];
char *name;
+
+ /* If (flags & QEMU_MIGRATION_COOKIE_GRAPHICS) */
+ qemuMigrationCookieGraphicsPtr graphics;
};
+static void qemuMigrationCookieGraphicsFree(qemuMigrationCookieGraphicsPtr grap)
+{
+ if (!grap)
+ return;
+ VIR_FREE(grap->listen);
+ VIR_FREE(grap->tlsSubject);
+ VIR_FREE(grap);
+}
+
static void qemuMigrationCookieFree(qemuMigrationCookiePtr mig)
{
if (!mig)
return;
+ if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS)
+ qemuMigrationCookieGraphicsFree(mig->graphics);
+
VIR_FREE(mig->hostname);
VIR_FREE(mig->name);
VIR_FREE(mig);
}
+static char *
+qemuDomainExtractTLSSubject(const char *certdir)
+{
+ char *certfile = NULL;
+ char *subject = NULL;
+ char *pemdata = NULL;
+ gnutls_datum_t pemdatum;
+ gnutls_x509_crt_t cert;
+ int ret;
+ size_t subjectlen;
+
+ if (virAsprintf(&certfile, "%s/server-cert.pem", certdir) < 0)
+ goto no_memory;
+
+ if (virFileReadAll(certfile, 8192, &pemdata) < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to read server cert %s"), certfile);
+ goto error;
+ }
+
+ ret = gnutls_x509_crt_init(&cert);
+ if (ret < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ _("cannot initialize cert object: %s"),
+ gnutls_strerror(ret));
+ goto error;
+ }
+
+ pemdatum.data = (unsigned char *)pemdata;
+ pemdatum.size = strlen(pemdata);
+
+ ret = gnutls_x509_crt_import(cert, &pemdatum, GNUTLS_X509_FMT_PEM);
+ if (ret < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ _("cannot load cert data from %s: %s"),
+ certfile, gnutls_strerror(ret));
+ goto error;
+ }
+
+ subjectlen = 1024;
+ if (VIR_ALLOC_N(subject, subjectlen+1) < 0)
+ goto no_memory;
+
+ gnutls_x509_crt_get_dn(cert, subject, &subjectlen);
+ subject[subjectlen] = '\0';
+
+ VIR_FREE(certfile);
+ VIR_FREE(pemdata);
+
+ return subject;
+
+no_memory:
+ virReportOOMError();
+error:
+ VIR_FREE(certfile);
+ VIR_FREE(pemdata);
+ return NULL;
+}
+
+
+static qemuMigrationCookieGraphicsPtr
+qemuMigrationCookieGraphicsAlloc(struct qemud_driver *driver,
+ virDomainGraphicsDefPtr def)
+{
+ qemuMigrationCookieGraphicsPtr mig = NULL;
+ const char *listenAddr;
+
+ if (VIR_ALLOC(mig) < 0)
+ goto no_memory;
+
+ mig->type = def->type;
+ if (mig->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
+ mig->port = def->data.vnc.port;
+ listenAddr = def->data.vnc.listenAddr;
+ if (!listenAddr)
+ listenAddr = driver->vncListen;
+
+ if (driver->vncTLS &&
+ !(mig->tlsSubject =
qemuDomainExtractTLSSubject(driver->vncTLSx509certdir)))
+ goto error;
+ } else {
+ mig->port = def->data.spice.port;
+ if (driver->spiceTLS)
+ mig->tlsPort = def->data.spice.tlsPort;
+ else
+ mig->tlsPort = -1;
+ listenAddr = def->data.spice.listenAddr;
+ if (!listenAddr)
+ listenAddr = driver->spiceListen;
+
+ if (driver->spiceTLS &&
+ !(mig->tlsSubject =
qemuDomainExtractTLSSubject(driver->spiceTLSx509certdir)))
+ goto error;
+ }
+ if (!(mig->listen = strdup(listenAddr)))
+ goto no_memory;
+
+ return mig;
+
+no_memory:
+ virReportOOMError();
+error:
+ qemuMigrationCookieGraphicsFree(mig);
+ return NULL;
+}
+
+
static qemuMigrationCookiePtr
qemuMigrationCookieNew(virDomainObjPtr dom)
{
@@ -103,6 +239,47 @@ error:
}
+static int
+qemuMigrationCookieAddGraphics(qemuMigrationCookiePtr mig,
+ struct qemud_driver *driver,
+ virDomainObjPtr dom)
+{
+ if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Migration graphics data already present"));
+ return -1;
+ }
+
+ if (dom->def->ngraphics == 1 &&
+ (dom->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC ||
+ dom->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE)
&&
+ !(mig->graphics = qemuMigrationCookieGraphicsAlloc(driver,
dom->def->graphics[0])))
+ return -1;
+
+ mig->flags |= QEMU_MIGRATION_COOKIE_GRAPHICS;
+
+ return 0;
+}
+
+
+static void qemuMigrationCookieGraphicsXMLFormat(virBufferPtr buf,
+ qemuMigrationCookieGraphicsPtr grap)
+{
+ virBufferVSprintf(buf, " <graphics type='%s' port='%d'
listen='%s'",
+ virDomainGraphicsTypeToString(grap->type),
+ grap->port, grap->listen);
+ if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE)
+ virBufferVSprintf(buf, " tlsPort='%d'", grap->tlsPort);
+ if (grap->tlsSubject) {
+ virBufferVSprintf(buf, ">\n");
+ virBufferEscapeString(buf, " <cert info='subject'
value='%s'/>\n", grap->tlsSubject);
+ virBufferVSprintf(buf, " </graphics>\n");
+ } else {
+ virBufferVSprintf(buf, "/>\n");
+ }
+}
+
+
static void qemuMigrationCookieXMLFormat(virBufferPtr buf,
qemuMigrationCookiePtr mig)
{
@@ -117,6 +294,10 @@ static void qemuMigrationCookieXMLFormat(virBufferPtr buf,
virBufferVSprintf(buf, " <uuid>%s</uuid>\n", uuidstr);
virBufferEscapeString(buf, " <hostname>%s</hostname>\n",
mig->hostname);
virBufferVSprintf(buf, " <hostuuid>%s</hostuuid>\n",
hostuuidstr);
+
+ if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS)
+ qemuMigrationCookieGraphicsXMLFormat(buf, mig->graphics);
+
virBufferAddLit(buf, "</qemu-migration>\n");
}
@@ -136,10 +317,61 @@ static char *qemuMigrationCookieXMLFormatStr(qemuMigrationCookiePtr
mig)
}
+static qemuMigrationCookieGraphicsPtr
+qemuMigrationCookieGraphicsXMLParse(xmlXPathContextPtr ctxt)
+{
+ qemuMigrationCookieGraphicsPtr grap;
+ char *tmp;
+
+ if (VIR_ALLOC(grap) < 0)
+ goto no_memory;
+
+ if (!(tmp = virXPathString("string(./graphics/@type)", ctxt))) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing type attribute in migration
data"));
+ goto error;
+ }
+ if ((grap->type = virDomainGraphicsTypeFromString(tmp)) < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown graphics type %s"), tmp);
+ VIR_FREE(tmp);
+ goto error;
+ }
+ if (virXPathInt("string(./graphics/@port)", ctxt, &grap->port) <
0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing port attribute in migration
data"));
+ goto error;
+ }
+ if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
+ if (virXPathInt("string(./graphics/@tlsPort)", ctxt,
&grap->tlsPort) < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing tlsPort attribute in
migration data"));
+ goto error;
+ }
+ }
+ if (!(grap->listen = virXPathString("string(./graphics/@listen)",
ctxt))) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing listen attribute in migration
data"));
+ goto error;
+ }
+ /* Optional */
+ grap->tlsSubject = virXPathString("string(./graphics/cert[
info='subject']/@value)", ctxt);
+
+
+ return grap;
+
+no_memory:
+ virReportOOMError();
+error:
+ qemuMigrationCookieGraphicsFree(grap);
+ return NULL;
+}
+
+
static int
qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig,
xmlXPathContextPtr ctxt,
- int flags ATTRIBUTE_UNUSED)
+ int flags)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
char *tmp;
@@ -206,6 +438,11 @@ qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig,
}
VIR_FREE(tmp);
+ if ((flags & QEMU_MIGRATION_COOKIE_GRAPHICS) &&
+ virXPathBoolean("count(./graphics) > 0", ctxt) &&
+ (!(mig->graphics = qemuMigrationCookieGraphicsXMLParse(ctxt))))
+ goto error;
+
return 0;
error:
@@ -247,11 +484,11 @@ cleanup:
static int
qemuMigrationBakeCookie(qemuMigrationCookiePtr mig,
- struct qemud_driver *driver ATTRIBUTE_UNUSED,
- virDomainObjPtr dom ATTRIBUTE_UNUSED,
+ struct qemud_driver *driver,
+ virDomainObjPtr dom,
char **cookieout,
int *cookieoutlen,
- int flags ATTRIBUTE_UNUSED)
+ int flags)
{
if (!cookieout || !cookieoutlen) {
qemuReportError(VIR_ERR_INVALID_ARG, "%s",
@@ -261,6 +498,10 @@ qemuMigrationBakeCookie(qemuMigrationCookiePtr mig,
*cookieoutlen = 0;
+ if (flags & QEMU_MIGRATION_COOKIE_GRAPHICS &&
+ qemuMigrationCookieAddGraphics(mig, driver, dom) < 0)
+ return -1;
+
if (!(*cookieout = qemuMigrationCookieXMLFormatStr(mig)))
return -1;
@@ -560,7 +801,8 @@ qemuMigrationPrepareTunnel(struct qemud_driver *driver,
def = NULL;
priv = vm->privateData;
- if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen, 0)))
+ if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen,
+ QEMU_MIGRATION_COOKIE_GRAPHICS)))
goto cleanup;
if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
@@ -622,7 +864,8 @@ qemuMigrationPrepareTunnel(struct qemud_driver *driver,
VIR_DOMAIN_EVENT_STARTED,
VIR_DOMAIN_EVENT_STARTED_MIGRATED);
- if (qemuMigrationBakeCookie(mig, driver, vm, cookieout, cookieoutlen, 0) < 0) {
+ if (qemuMigrationBakeCookie(mig, driver, vm, cookieout, cookieoutlen,
+ QEMU_MIGRATION_COOKIE_GRAPHICS) < 0) {
/* We could tear down the whole guest here, but
* cookie data is (so far) non-critical, so that
* seems a little harsh. We'll just warn for now..
@@ -797,7 +1040,8 @@ qemuMigrationPrepareDirect(struct qemud_driver *driver,
def = NULL;
priv = vm->privateData;
- if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen, 0)))
+ if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen,
+ QEMU_MIGRATION_COOKIE_GRAPHICS)))
goto cleanup;
if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
@@ -825,7 +1069,8 @@ qemuMigrationPrepareDirect(struct qemud_driver *driver,
goto endjob;
}
- if (qemuMigrationBakeCookie(mig, driver, vm, cookieout, cookieoutlen, 0) < 0) {
+ if (qemuMigrationBakeCookie(mig, driver, vm, cookieout, cookieoutlen,
+ QEMU_MIGRATION_COOKIE_GRAPHICS) < 0) {
/* We could tear down the whole guest here, but
* cookie data is (so far) non-critical, so that
* seems a little harsh. We'll just warn for now..
@@ -890,7 +1135,8 @@ static int doNativeMigrate(struct qemud_driver *driver,
unsigned int background_flags = QEMU_MONITOR_MIGRATE_BACKGROUND;
qemuMigrationCookiePtr mig = NULL;
- if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen, 0)))
+ if (!(mig = qemuMigrationEatCookie(vm, cookiein, cookieinlen,
+ QEMU_MIGRATION_COOKIE_GRAPHICS)))
goto cleanup;
/* Issue the migrate command. */
--
1.7.4.4