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:

><fs> 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
This commit is contained in:
Richard Jones
2010-11-09 18:53:01 +00:00
committed by Richard W.M. Jones
parent 4ada0a7815
commit f08fe63761
7 changed files with 467 additions and 9 deletions

8
TODO
View File

@@ -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.

View File

@@ -1058,6 +1058,67 @@ Please read L<guestfs(3)/INSPECTION> 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<dom>. It works by connecting to libvirt, requesting
the domain and domain XML from libvirt, parsing it for disks,
and calling C<guestfs_add_drive_opts> 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<readonly> 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<http://libvirt.org/remote.html>)
will fail unless those disks are accessible via the same device path
locally too.
The optional C<libvirturi> parameter sets the libvirt URI
(see L<http://libvirt.org/uri.html>). 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<guestfs_add_libvirt_dom>.
The other optional parameters are passed directly through to
C<guestfs_add_drive_opts>.");
(*
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<dom>.
It works by requesting the domain XML from libvirt, parsing it for
disks, and calling C<guestfs_add_drive_opts> on each one.
In the C API we declare C<void *dom>, but really it has type
C<virDomainPtr dom>. This is so we don't need E<lt>libvirt.hE<gt>.
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<readonly> 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<http://libvirt.org/remote.html>)
will fail unless those disks are accessible via the same device path
locally too.
The optional parameters are passed directly through to
C<guestfs_add_drive_opts>.");
*)
]
(* daemon_functions are any functions which cause some action

View File

@@ -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

View File

@@ -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 \

79
regressions/test-add-domain.sh Executable file
View File

@@ -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 <<EOF
<node>
<domain type="test">
<name>guest</name>
<os>
<type>hvm</type>
<boot dev='hd'/>
</os>
<memory>524288</memory>
<devices>
<disk type="file">
<source file="$cwd/test1.img"/>
<target dev="hda"/>
</disk>
<disk type="file">
<driver name="qemu" type="raw"/>
<source file="$cwd/test2.img"/>
<target dev="hdb"/>
</disk>
<disk type="file">
<driver name="qemu" type="qcow2"/>
<source file="$cwd/test3.img"/>
<target dev="hdc"/>
</disk>
</devices>
</domain>
</node>
EOF
../fish/guestfish >test.out <<EOF
domain guest libvirturi:test://$cwd/test.xml readonly:true
debug-cmdline
EOF
grep -sq "test1.img.*snapshot=on" test.out
! grep -sq "test1.img.*format" test.out
grep -sq "test2.img.*snapshot=on.*format=raw" test.out
grep -sq "test3.img.*snapshot=on.*format=qcow2" test.out
# Test atomicity.
rm test3.img
../fish/guestfish >test.out <<EOF
-domain guest libvirturi:test://$cwd/test.xml readonly:true
debug-cmdline
EOF
! grep -sq "test1.img" test.out
! grep -sq "test2.img" test.out
! grep -sq "test3.img" test.out
rm -f test1.img test2.img test3.img test.xml test.out

View File

@@ -133,9 +133,13 @@ libguestfs_la_SOURCES = \
launch.c \
listfs.c \
proto.c \
virt.c \
libguestfs.syms
libguestfs_la_LIBADD = $(HIVEX_LIBS) $(AUGEAS_LIBS) $(PCRE_LIBS) $(MAGIC_LIBS) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la
libguestfs_la_LIBADD = \
$(HIVEX_LIBS) $(AUGEAS_LIBS) $(PCRE_LIBS) $(MAGIC_LIBS) \
$(LIBVIRT_LIBS) $(LIBXML2_LIBS) \
$(LTLIBTHREAD) ../gnulib/lib/libgnu.la
# Make libguestfs include the convenience libraries.
noinst_LTLIBRARIES = liberrnostring.la libprotocol.la
@@ -144,6 +148,7 @@ libguestfs_la_LIBADD += liberrnostring.la libprotocol.la
libguestfs_la_CFLAGS = \
-DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \
$(HIVEX_CFLAGS) $(AUGEAS_CFLAGS) $(PCRE_CFLAGS) \
$(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \
$(WARN_CFLAGS) $(WERROR_CFLAGS)
libguestfs_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib

319
src/virt.c Normal file
View File

@@ -0,0 +1,319 @@
/* 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#ifdef HAVE_LIBVIRT
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#endif
#ifdef HAVE_LIBXML2
#include <libxml/xpath.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#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 <disk> 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 <disk> 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 <source dev=..> or <source file=..> attribute.
* Check the <disk type=..> 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 */