[libvirt] KVM migration patch
Daniel P. Berrange
berrange at redhat.com
Tue Jun 3 13:21:20 UTC 2008
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 :|
More information about the libvir-list
mailing list