
On Tue, Jun 03, 2008 at 09:17:01AM +0100, Richard W.M. Jones wrote:
Index: qemud/remote.c =================================================================== RCS file: /data/cvs/libvirt/qemud/remote.c,v retrieving revision 1.35 diff -u -p -r1.35 remote.c --- qemud/remote.c 23 May 2008 08:24:44 -0000 1.35 +++ qemud/remote.c 3 Jun 2008 08:17:52 -0000 @@ -1346,6 +1346,66 @@ remoteDispatchDomainMigrateFinish (struc }
static int +remoteDispatchDomainMigratePrepare2 (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_domain_migrate_prepare2_args *args, + remote_domain_migrate_prepare2_ret *ret) +{ + int r; + char *cookie = NULL; + int cookielen = 0; + char *uri_in; + char **uri_out; + char *dname; + CHECK_CONN (client); + + uri_in = args->uri_in == NULL ? NULL : *args->uri_in; + dname = args->dname == NULL ? NULL : *args->dname; + + /* Wacky world of XDR ... */ + uri_out = calloc (1, sizeof (*uri_out));
This ought to be updated to use if (VIR_ALLOC(uri_out)) < 0) { remoteDispatchSendError(client, req, VIR_ERR_NO_MEMORY, NULL); return -2; }
+ + r = __virDomainMigratePrepare2 (client->conn, &cookie, &cookielen, + uri_in, uri_out, + args->flags, dname, args->resource, + args->dom_xml); + if (r == -1) return -1; + + /* remoteDispatchClientRequest will free cookie, uri_out and + * the string if there is one. + */ + ret->cookie.cookie_len = cookielen; + ret->cookie.cookie_val = cookie; + ret->uri_out = *uri_out == NULL ? NULL : uri_out;
This will leak uri_out in the 1st half of the conditional
+static int +remoteDispatchDomainMigrateFinish2 (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_domain_migrate_finish2_args *args, + remote_domain_migrate_finish2_ret *ret) +{ + virDomainPtr ddom; + CHECK_CONN (client); + + ddom = __virDomainMigrateFinish2 (client->conn, args->dname, + args->cookie.cookie_val, + args->cookie.cookie_len, + args->uri, + args->flags, + args->retcode); + if (ddom == NULL) return -1; + + make_nonnull_domain (&ret->ddom, ddom);
Need to call virDomainFree(ddom) after this.
diff -u -p -r1.143 libvirt.c --- src/libvirt.c 29 May 2008 19:23:17 -0000 1.143 +++ src/libvirt.c 3 Jun 2008 08:17:57 -0000 + else /* if (version == 2) */ { + /* In version 2 of the protocol, the prepare step is slightly + * different. We fetch the domain XML of the source domain + * and pass it to Prepare2. + */ + if (!conn->driver->domainDumpXML) { + virLibConnError (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__); + return NULL; + } + dom_xml = conn->driver->domainDumpXML (domain, + VIR_DOMAIN_XML_SECURE); + + if (!dom_xml) + return NULL; + + ret = dconn->driver->domainMigratePrepare2 + (dconn, &cookie, &cookielen, uri, &uri_out, flags, dname, + bandwidth, dom_xml); + free (dom_xml);
Can use VIR_FREE now
@@ -2190,18 +2230,28 @@ virDomainMigrate (virDomainPtr domain, ret = conn->driver->domainMigratePerform (domain, cookie, cookielen, uri, flags, dname, bandwidth);
- if (ret == -1) goto done; - - /* Get the destination domain and return it or error. - * 'domain' no longer actually exists at this point (or so we hope), but - * we still use the object in memory in order to get the name. - */ - dname = dname ? dname : domain->name; - if (dconn->driver->domainMigrateFinish) - ddomain = dconn->driver->domainMigrateFinish - (dconn, dname, cookie, cookielen, uri, flags); - else - ddomain = virDomainLookupByName (dconn, dname); + if (version == 1) { + if (ret == -1) goto done; + /* Get the destination domain and return it or error. + * 'domain' no longer actually exists at this point + * (or so we hope), but we still use the object in memory + * in order to get the name. + */ + dname = dname ? dname : domain->name; + if (dconn->driver->domainMigrateFinish) + ddomain = dconn->driver->domainMigrateFinish + (dconn, dname, cookie, cookielen, uri, flags); + else + ddomain = virDomainLookupByName (dconn, dname);
So this code suggests that domainMigrateFinish is optional in v1 of the migration protocol...
+ } else /* if (version == 2) */ { + /* In version 2 of the migration protocol, we pass the + * status code from the sender to the destination host, + * so it can do any cleanup if the migration failed. + */ + dname = dname ? dname : domain->name; + ddomain = dconn->driver->domainMigrateFinish2 + (dconn, dname, cookie, cookielen, uri, flags, ret); + }
And compulsory in v2 ? Is that the correct understanding ? What if we wanted to make Xen Driver use v2, but it doesn't require the finish method ? I guess we could just no-op it.
+/* Migration support. */ + +/* Prepare is the first step, and it runs on the destination host. + * + * This starts an empty VM listening on a TCP port. +*/ +static int +qemudDomainMigratePrepare2 (virConnectPtr dconn, + char **cookie ATTRIBUTE_UNUSED, + int *cookielen ATTRIBUTE_UNUSED, + const char *uri_in, + char **uri_out, + unsigned long flags ATTRIBUTE_UNUSED, + const char *dname, + unsigned long resource ATTRIBUTE_UNUSED, + const char *dom_xml) +{ + static int port = 0; + struct qemud_driver *driver = (struct qemud_driver *)dconn->privateData; + struct qemud_vm_def *def; + struct qemud_vm *vm; + int this_port; + char hostname [HOST_NAME_MAX+1]; + const char *p; + const int uri_length_max = + 6 /* "tcp://" */ + + sizeof (hostname) /* hostname */ + + 1 + 5 + 1 /* :port\0 */; + + if (!dom_xml) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("no domain XML passed")); + return -1; + } + + /* The URI passed in may be NULL or a string "tcp://somehostname:port". + * + * If the URI passed in is NULL then we allocate a port number + * from our pool of port numbers and return a URI of + * "tcp://ourhostname:port". + * + * If the URI passed in is not NULL then we try to parse out the + * port number and use that (note that the hostname is assumed + * to be a correct hostname which refers to the target machine). + */ + if (uri_in == NULL) { + this_port = QEMUD_MIGRATION_FIRST_PORT + port++; + if (port == QEMUD_MIGRATION_NUM_PORTS) port = 0;
This is a little fragile - although the pool of 64 numbers is unlikely to wrap around and collide in 'regular' use, if some client of libvirt were mis-behaving its conceivable that they could issue a large number of migrate requests and cause a collsion. I think we ought to explicitly track the ports in use (and auto-expire if client crashes part way through).
+ /* Caller frees */ + *uri_out = malloc (uri_length_max);
Can use VIR_ALLOC_N(*uri_out, uri_length_max) here
+ } else { + /* Check the URI starts with "tcp://". We will escape the + * URI when passing it to the qemu monitor, so bad + * characters in hostname part don't matter. + */ + if (!STREQLEN (uri_in, "tcp://", 6)) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_INVALID_ARG, + "%s", _("only tcp URIs are supported for KVM migrations")); + return -1; + } + + /* Get the port number. */ + p = strrchr (uri_in, ':'); + p++; /* definitely has a ':' in it, see above */
I think this should use the libxml2 URI parsing code instead.
+ /* Parse the domain XML. */ + if (!(def = qemudParseVMDef (dconn, driver, dom_xml, NULL))) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("failed to parse XML")); + return -1; + } + + /* Target domain name, maybe renamed. */ + dname = dname ? dname : def->name; + + /* Ensure the name and UUID don't already exist in an active VM */ + vm = qemudFindVMByUUID (driver, def->uuid); + if (!vm) vm = qemudFindVMByName (driver, dname); + if (vm) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + _("domain is already active as '%s'"), vm->def->name); + return -1; + } + + if (!(vm = qemudAssignVMDef (dconn, driver, def))) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("failed to assign new VM")); + qemudFreeVMDef (def); + return -1; + } + + /* Start the QEMU daemon, with the same command-line arguments plus + * -incoming tcp://0:port + */ + snprintf (vm->migrateFrom, sizeof (vm->migrateFrom), + "tcp://0:%d", this_port); + if (qemudStartVMDaemon (dconn, driver, vm) < 0) { + qemudReportError (dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("failed to start listening VM")); + if (!vm->configFile[0]) + qemudRemoveInactiveVM(driver, vm); + return -1; + }
I don't think we're handling persistent config files correctly in this code. There's a couple of scenarios we need to deal with Source: transient Target: none -> transient Source: transient Target: transient, running -> error Source: transient Target: persistent, inactive -> migrate, update active config Source: transient Target: persistent, running -> error Source: persistent Target: none -> migrate & save persistent config on success Source: persistent Target: transient, running -> error Source: persistent Target: persistent, inactive -> migrate, update active config Source: persistent Target: persistent, running -> error 5B5BIn particular, if the target already has a persistent config we need to be careful not to remove than config upon migrate failure. We also need to make sure that if we create a persistent config, that SaveVMDef is called to actually flush it to disk.
+/* Perform is the second step, and it runs on the source host. */ +static int +qemudDomainMigratePerform (virDomainPtr dom, + const char *cookie ATTRIBUTE_UNUSED, + int cookielen ATTRIBUTE_UNUSED, + const char *uri, + unsigned long flags ATTRIBUTE_UNUSED, + const char *dname ATTRIBUTE_UNUSED, + unsigned long resource) +{ + struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData; + struct qemud_vm *vm = qemudFindVMByID (driver, dom->id); + char *safe_uri; + char cmd[HOST_NAME_MAX+50]; + char *info; + + if (!vm) { + qemudReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching id %d"), dom->id); + return -1; + } + + if (!qemudIsActiveVM (vm)) { + qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("domain is not running")); + return -1; + } + + if (resource > 0) { + /* Issue migrate_set_speed command. Don't worry if it fails. */ + snprintf (cmd, sizeof cmd, "migrate_set_speed %lum", resource); + qemudMonitorCommand (driver, vm, cmd, &info); + + DEBUG ("migrate_set_speed reply: %s", info); + free (info); + } + + /* Issue the migrate command. */ + safe_uri = qemudEscapeMonitorArg (uri); + if (!safe_uri) { + qemudReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + return -1; + } + snprintf (cmd, sizeof cmd, "migrate \"%s\"", safe_uri); + free (safe_uri); + + if (qemudMonitorCommand (driver, vm, cmd, &info) < 0) { + qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("migrate operation failed")); + return -1; + } + + DEBUG ("migrate reply: %s", info); + free (info); + + /* Clean up the source domain. */ + qemudShutdownVMDaemon (dom->conn, driver, vm); + if (!vm->configFile[0]) + qemudRemoveInactiveVM (driver, vm); + + return 0; +}
Need to use VIR_FREE() in various places here. Dan, -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|