From f08fe63761d4186d49212b1d2382b1a5a2b8a62f Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Tue, 9 Nov 2010 18:53:01 +0000 Subject: [PATCH] New API: add-domain This new API allows you to add the disks from a libvirt domain. In guestfish you can use the 'domain' command to access the API, eg: > domain Fedora14 libvirturi:qemu:///system 1 The returned number is the number of disks that were added. Also here is a proposed (but commented out) low-level API which would allow you to add a domain from a virDomainPtr. However there are several problems with this API -- see discussion on the list: https://www.redhat.com/archives/libguestfs/2010-November/thread.html#00028 --- TODO | 8 -- generator/generator_actions.ml | 61 ++++++++ po/POTFILES.in | 1 + regressions/Makefile.am | 1 + regressions/test-add-domain.sh | 79 ++++++++++ src/Makefile.am | 7 +- src/virt.c | 319 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 467 insertions(+), 9 deletions(-) create mode 100755 regressions/test-add-domain.sh create mode 100644 src/virt.c diff --git a/TODO b/TODO index 0f933fa..2301c27 100644 --- a/TODO +++ b/TODO @@ -347,11 +347,3 @@ Eric Sandeen pointed out the blktrace tool which is a better way of capturing traces than using patched qemu (see contrib/visualize-alignment). We would still use the same visualization tools in conjunction with blktrace traces. - -Add-domain command ------------------- - -guestfs_add_domain (g, "libvirt-dom"); - -However this would need to not depend on libvirt, eg. loading it -on demand. diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index a0a337b..12fda29 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -1058,6 +1058,67 @@ Please read L for more details."); This returns the internal QEMU command line. 'debug' commands are not part of the formal API and can be removed or changed at any time."); + ("add_domain", (RInt "nrdisks", [String "dom"], [String "libvirturi"; Bool "readonly"; String "iface"]), -1, [FishAlias "domain"], + [], + "add the disk(s) from a named libvirt domain", + "\ +This function adds the disk(s) attached to the named libvirt +domain C. It works by connecting to libvirt, requesting +the domain and domain XML from libvirt, parsing it for disks, +and calling C on each one. + +The number of disks added is returned. This operation is atomic: +if an error is returned, then no disks are added. + +This function does some minimal checks to make sure the libvirt +domain is not running (unless C is true). In a future +version we will try to acquire the libvirt lock on each disk. + +Disks must be accessible locally. This often means that adding disks +from a remote libvirt connection (see L) +will fail unless those disks are accessible via the same device path +locally too. + +The optional C parameter sets the libvirt URI +(see L). If this is not set then +we connect to the default libvirt URI (or one set through an +environment variable, see the libvirt documentation for full +details). If you are using the C API directly then it is more +flexible to create the libvirt connection object yourself, get +the domain object, and call C. + +The other optional parameters are passed directly through to +C."); + +(* +This interface is not quite baked yet. -- RWMJ 2010-11-11 + ("add_libvirt_dom", (RInt "nrdisks", [Pointer ("virDomainPtr", "dom")], [Bool "readonly"; String "iface"]), -1, [NotInFish], + [], + "add the disk(s) from a libvirt domain", + "\ +This function adds the disk(s) attached to the libvirt domain C. +It works by requesting the domain XML from libvirt, parsing it for +disks, and calling C on each one. + +In the C API we declare C, but really it has type +C. This is so we don't need Elibvirt.hE. + +The number of disks added is returned. This operation is atomic: +if an error is returned, then no disks are added. + +This function does some minimal checks to make sure the libvirt +domain is not running (unless C is true). In a future +version we will try to acquire the libvirt lock on each disk. + +Disks must be accessible locally. This often means that adding disks +from a remote libvirt connection (see L) +will fail unless those disks are accessible via the same device path +locally too. + +The optional parameters are passed directly through to +C."); +*) + ] (* daemon_functions are any functions which cause some action diff --git a/po/POTFILES.in b/po/POTFILES.in index e8d9587..d76904d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -131,6 +131,7 @@ src/inspect.c src/launch.c src/listfs.c src/proto.c +src/virt.c test-tool/helper.c test-tool/test-tool.c tools/virt-cat.pl diff --git a/regressions/Makefile.am b/regressions/Makefile.am index c9156f9..10c5c48 100644 --- a/regressions/Makefile.am +++ b/regressions/Makefile.am @@ -32,6 +32,7 @@ TESTS = \ rhbz576879.sh \ rhbz578407.sh \ rhbz580246.sh \ + test-add-domain.sh \ test-cancellation-download-librarycancels.sh \ test-cancellation-upload-daemoncancels.sh \ test-copy.sh \ diff --git a/regressions/test-add-domain.sh b/regressions/test-add-domain.sh new file mode 100755 index 0000000..d124b62 --- /dev/null +++ b/regressions/test-add-domain.sh @@ -0,0 +1,79 @@ +#!/bin/bash - +# libguestfs +# Copyright (C) 2010 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# Test add-domain command. + +set -e + +rm -f test1.img test2.img test3.img test.xml test.out + +cwd="$(pwd)" + +truncate -s 1M test1.img test2.img test3.img + +# Libvirt test XML, see libvirt.git/examples/xml/test/testnode.xml +cat > test.xml < + + guest + + hvm + + + 524288 + + + + + + + + + + + + + + + + + + +EOF + +../fish/guestfish >test.out <test.out < + +#include +#include +#include + +#ifdef HAVE_LIBVIRT +#include +#include +#endif + +#ifdef HAVE_LIBXML2 +#include +#include +#include +#endif + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" +#include "guestfs_protocol.h" + +#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2) + +static void init_libxml2 (void) __attribute__((constructor)); + +static void +init_libxml2 (void) +{ + /* I am told that you don't really need to call virInitialize ... */ + + xmlInitParser (); + LIBXML_TEST_VERSION; +} + +struct guestfs___add_libvirt_dom_argv { + uint64_t bitmask; +#define GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK (UINT64_C(1)<<0) + int readonly; +#define GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK (UINT64_C(1)<<1) + const char *iface; +}; + +static int guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs); + +int +guestfs__add_domain (guestfs_h *g, const char *domain_name, + const struct guestfs_add_domain_argv *optargs) +{ + virErrorPtr err; + virConnectPtr conn = NULL; + virDomainPtr dom = NULL; + int r = -1; + const char *libvirturi; + int readonly; + const char *iface; + struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 }; + + libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK + ? optargs->libvirturi : NULL; + readonly = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLY_BITMASK + ? optargs->readonly : 0; + iface = optargs->bitmask & GUESTFS_ADD_DOMAIN_IFACE_BITMASK + ? optargs->iface : NULL; + + /* Connect to libvirt, find the domain. */ + conn = virConnectOpenReadOnly (libvirturi); + if (!conn) { + err = virGetLastError (); + error (g, _("could not connect to libvirt (code %d, domain %d): %s"), + err->code, err->domain, err->message); + goto cleanup; + } + + dom = virDomainLookupByName (conn, domain_name); + if (!dom) { + err = virGetLastError (); + error (g, _("no libvirt domain called '%s': %s"), + domain_name, err->message); + goto cleanup; + } + + if (readonly) { + optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK; + optargs2.readonly = readonly; + } + if (iface) { + optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK; + optargs2.iface = iface; + } + + r = guestfs___add_libvirt_dom (g, dom, &optargs2); + + cleanup: + if (dom) virDomainFree (dom); + if (conn) virConnectClose (conn); + + return r; +} + +/* This was proposed as an external API, but it's not quite baked yet. */ +static int +guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, + const struct guestfs___add_libvirt_dom_argv *optargs) +{ + int r = -1, nr_added = 0, i; + virErrorPtr err; + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + char *xml = NULL; + int readonly; + const char *iface; + int cmdline_pos; + + cmdline_pos = guestfs___checkpoint_cmdline (g); + + readonly = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK + ? optargs->readonly : 0; + iface = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK + ? optargs->iface : NULL; + + if (!readonly) { + virDomainInfo info; + if (virDomainGetInfo (dom, &info) == -1) { + err = virGetLastError (); + error (g, _("error getting domain info: %s"), err->message); + goto cleanup; + } + if (info.state != VIR_DOMAIN_SHUTOFF) { + error (g, _("error: domain is a live virtual machine.\nYou must use readonly access because write access to a running virtual machine\ncan cause disk corruption.")); + goto cleanup; + } + } + + /* Domain XML. */ + xml = virDomainGetXMLDesc (dom, 0); + + if (!xml) { + err = virGetLastError (); + error (g, _("error reading libvirt XML information: %s"), + err->message); + goto cleanup; + } + + /* Now the horrible task of parsing out the fields we need from the XML. + * http://www.xmlsoft.org/examples/xpath1.c + */ + doc = xmlParseMemory (xml, strlen (xml)); + if (doc == NULL) { + error (g, _("unable to parse XML information returned by libvirt")); + goto cleanup; + } + + xpathCtx = xmlXPathNewContext (doc); + if (xpathCtx == NULL) { + error (g, _("unable to create new XPath context")); + goto cleanup; + } + + /* This gives us a set of all the nodes. */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx); + if (xpathObj == NULL) { + error (g, _("unable to evaluate XPath expression")); + goto cleanup; + } + + xmlNodeSetPtr nodes = xpathObj->nodesetval; + for (i = 0; i < nodes->nodeNr; ++i) { + xmlXPathObjectPtr xptype; + + /* Change the context to the current node. + * DV advises to reset this before each search since older versions of + * libxml2 might overwrite it. + */ + xpathCtx->node = nodes->nodeTab[i]; + + /* Filename can be in or attribute. + * Check the attribute first to find out which one. + */ + xptype = xmlXPathEvalExpression (BAD_CAST "./@type", xpathCtx); + if (xptype == NULL || + xptype->nodesetval == NULL || + xptype->nodesetval->nodeNr == 0) { + xmlXPathFreeObject (xptype); + continue; /* no type attribute, skip it */ + } + assert (xptype->nodesetval->nodeTab[0]); + assert (xptype->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); + xmlAttrPtr attr = (xmlAttrPtr) xptype->nodesetval->nodeTab[0]; + char *type = (char *) xmlNodeListGetString (doc, attr->children, 1); + + xmlXPathObjectPtr xpfilename; + + if (STREQ (type, "file")) { /* type = "file" so look at source/@file */ + free (type); + + xpathCtx->node = nodes->nodeTab[i]; + xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@file", xpathCtx); + if (xpfilename == NULL || + xpfilename->nodesetval == NULL || + xpfilename->nodesetval->nodeNr == 0) { + xmlXPathFreeObject (xpfilename); + continue; /* disk filename not found, skip this */ + } + } else if (STREQ (type, "block")) { /* type = "block", use source/@dev */ + free (type); + + xpathCtx->node = nodes->nodeTab[i]; + xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@dev", xpathCtx); + if (xpfilename == NULL || + xpfilename->nodesetval == NULL || + xpfilename->nodesetval->nodeNr == 0) { + xmlXPathFreeObject (xpfilename); + continue; /* disk filename not found, skip this */ + } + } else { + free (type); + continue; /* type <> "file" or "block", skip it */ + } + + assert (xpfilename->nodesetval->nodeTab[0]); + assert (xpfilename->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); + attr = (xmlAttrPtr) xpfilename->nodesetval->nodeTab[0]; + char *filename = (char *) xmlNodeListGetString (doc, attr->children, 1); + + /* Get the disk format (may not be set). */ + xmlXPathObjectPtr xpformat; + + xpathCtx->node = nodes->nodeTab[i]; + xpformat = xmlXPathEvalExpression (BAD_CAST "./driver/@type", xpathCtx); + char *format = NULL; + if (xpformat != NULL && + xpformat->nodesetval && + xpformat->nodesetval->nodeNr > 0) { + assert (xpformat->nodesetval->nodeTab[0]); + assert (xpformat->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); + attr = (xmlAttrPtr) xpformat->nodesetval->nodeTab[0]; + format = (char *) xmlNodeListGetString (doc, attr->children, 1); + } + + /* Add the disk, with optional format. */ + struct guestfs_add_drive_opts_argv optargs2 = { .bitmask = 0 }; + if (readonly) { + optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK; + optargs2.readonly = readonly; + } + if (format) { + optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK; + optargs2.format = format; + } + if (iface) { + optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK; + optargs2.iface = iface; + } + + int t = guestfs__add_drive_opts (g, filename, &optargs2); + + xmlFree (filename); + xmlFree (format); + xmlXPathFreeObject (xpfilename); + xmlXPathFreeObject (xpformat); + + if (t == -1) + goto cleanup; + + nr_added++; + } + + if (nr_added == 0) { + error (g, _("libvirt domain has no disks")); + goto cleanup; + } + + /* Successful. */ + r = nr_added; + + cleanup: + if (r == -1) guestfs___rollback_cmdline (g, cmdline_pos); + free (xml); + if (xpathObj) xmlXPathFreeObject (xpathObj); + if (xpathCtx) xmlXPathFreeContext (xpathCtx); + if (doc) xmlFreeDoc (doc); + + return r; +} + +#else /* no libvirt or libxml2 at compile time */ + +#define NOT_IMPL(r) \ + error (g, _("add-domain API not available since this version of libguestfs was compiled without libvirt or libxml2")); \ + return r + +int +guestfs__add_domain (guestfs_h *g, const char *dom, + const struct guestfs_add_domain_argv *optargs) +{ + NOT_IMPL(-1); +} + +#endif /* no libvirt or libxml2 at compile time */ -- 1.8.3.1