>From da5208bc5b08b6cd29c89ec84185755e80393241 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Tue, 9 Nov 2010 18:53:01 +0000 Subject: [PATCH 5/7] New APIs: add-domain and add-libvirt-dom. These two new APIs allow you to add the disks from a libvirt domain. The higher level add-domain API takes the name of the libvirt domain as a string and connects to libvirt itself. The lower level add-libvirt-dom API relies on the program to connect to libvirt and pass the virDomainPtr into the API call. In guestfish you can use the 'domain' command to access the higher level API, eg: > domain Fedora14 libvirturi:qemu:///system 1 The returned number is the number of disks that were added. --- TODO | 8 - generator/generator_actions.ml | 53 ++++++++ perl/typemap | 11 ++ po/POTFILES.in | 1 + regressions/Makefile.am | 1 + regressions/test-add-domain.sh | 79 +++++++++++ src/Makefile.am | 7 +- src/guestfs.h | 5 + src/virt.c | 287 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 443 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..6b77a00 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -1058,6 +1058,59 @@ 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_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. + +The number of disks added is returned. This operation is atomic: +if an error is returned, then no disks are added. + +If C is true then you can add any libvirt domain. Otherwise +this function checks that the domain is shut off. + +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."); + + ("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. + +If C is true then you can add any libvirt domain. Otherwise +this function checks that the domain is shut off. + +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."); + ] (* daemon_functions are any functions which cause some action diff --git a/perl/typemap b/perl/typemap index d978e60..223aee1 100644 --- a/perl/typemap +++ b/perl/typemap @@ -3,6 +3,7 @@ char * T_PV const char * T_PV guestfs_h * O_OBJECT_guestfs_h int64_t T_IV +virDomainPtr O_OBJECT_domain INPUT O_OBJECT_guestfs_h @@ -18,6 +19,16 @@ O_OBJECT_guestfs_h croak (\"${Package}::$func_name(): $var is not a blessed HV reference\"); } +# This comes from the Sys::Virt bindings. +INPUT +O_OBJECT_domain + if (sv_isobject ($arg) && (SvTYPE (SvRV ($arg)) == SVt_PVMG)) + $var = ($type)SvIV ((SV*) SvRV ($arg)); + else { + warn(\"${Package}::$func_name() -- $var is not a blessed SV reference\"); + XSRETURN_UNDEF; + } + OUTPUT O_OBJECT_guestfs_h sv_setiv ($arg, PTR2IV ($var)); 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 < */ +typedef struct _virDomain virDomain; +typedef virDomain *virDomainPtr; + #include #include #include diff --git a/src/virt.c b/src/virt.c new file mode 100644 index 0000000..5816f61 --- /dev/null +++ b/src/virt.c @@ -0,0 +1,287 @@ +/* libguestfs + * Copyright (C) 2010 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#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; +} + +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 = virConnGetLastError (conn); + 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; +} + +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; + virConnectPtr conn = virDomainGetConnect (dom); + 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 = virConnGetLastError (conn); + 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 = virConnGetLastError (conn); + 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 xpfilename; + xmlXPathObjectPtr xpformat; + + /* 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. */ + xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@dev", xpathCtx); + if (xpfilename == NULL || + xpfilename->nodesetval == NULL || + xpfilename->nodesetval->nodeNr == 0) { + xmlXPathFreeObject (xpfilename); + 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 */ + } + } + + assert (xpfilename->nodesetval->nodeTab[0]); + assert (xpfilename->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); + xmlAttrPtr attr = (xmlAttrPtr) xpfilename->nodesetval->nodeTab[0]; + char *filename = (char *) xmlNodeListGetString (doc, attr->children, 1); + + /* Get the disk format (may not be set). */ + 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 APIs 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); +} + +int +guestfs__add_libvirt_dom (guestfs_h *g, virDomainPtr dom, + const struct guestfs_add_libvirt_dom_argv *optargs) +{ + NOT_IMPL(-1); +} + +#endif /* no libvirt or libxml2 at compile time */ -- 1.7.3.2