[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