diff options
author | Richard W.M. Jones <rjones@redhat.com> | 2012-10-05 14:56:35 +0100 |
---|---|---|
committer | Richard W.M. Jones <rjones@redhat.com> | 2012-10-08 20:04:53 +0100 |
commit | 18b7f09f366d518050f467d0944c81c82fd5e39e (patch) | |
tree | 6f89801dec73c60eb05cc01160544d76782e454a /src | |
parent | 7786d56db8c22413949f98ef6b15fe0ea367d195 (diff) | |
download | libguestfs-18b7f09f366d518050f467d0944c81c82fd5e39e.tar.gz libguestfs-18b7f09f366d518050f467d0944c81c82fd5e39e.tar.xz libguestfs-18b7f09f366d518050f467d0944c81c82fd5e39e.zip |
Add support for hotplugging (adding disks) to the libvirt attach-method.
When libvirt is used, we can allow disks to be hotplugged.
guestfs_add_drive can be called after launch to hot-add a disk.
When a disk is hot-added, we first ask libvirt to add the disk to the
appliance, then we make an internal call into the appliance to get it
to wait for the disk to appear (ie. udev_settle ()).
Hot-added disks are tracked in the g->drives array.
This also adds a test.
Diffstat (limited to 'src')
-rw-r--r-- | src/MAX_PROC_NR | 2 | ||||
-rw-r--r-- | src/guestfs-internal.h | 3 | ||||
-rw-r--r-- | src/guestfs.pod | 38 | ||||
-rw-r--r-- | src/launch-libvirt.c | 99 | ||||
-rw-r--r-- | src/launch.c | 80 |
5 files changed, 202 insertions, 20 deletions
diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index 446dfcc5..5b0cffbc 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -369 +370 diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index ed9ada48..db9818c3 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -166,6 +166,9 @@ struct attach_ops { int (*get_pid) (guestfs_h *g); /* get-pid API. */ int (*max_disks) (guestfs_h *g); /* max-disks API. */ + + /* Hotplugging drives. */ + int (*hot_add_drive) (guestfs_h *g, struct drive *drv, size_t drv_index); }; extern struct attach_ops attach_ops_appliance; extern struct attach_ops attach_ops_libvirt; diff --git a/src/guestfs.pod b/src/guestfs.pod index 48d810b0..624e743e 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -143,8 +143,11 @@ one you added), etc. Once L</guestfs_launch> has been called you cannot add any more images. You can call L</guestfs_list_devices> to get a list of the device -names, in the order that you added them. See also L</BLOCK DEVICE -NAMING> below. +names, in the order that you added them. +See also L</BLOCK DEVICE NAMING> below. + +There are slightly different rules when hotplugging disks (in +libguestfs E<ge> 1.20). See L</HOTPLUGGING> below. =head2 MOUNTING @@ -601,6 +604,35 @@ Libguestfs on top of FUSE performs quite poorly. For best performance do not use it. Use ordinary libguestfs filesystem calls, upload, download etc. instead. +=head2 HOTPLUGGING + +In libguestfs E<ge> 1.20, you may add drives after calling +L</guestfs_launch>. There are some restrictions, see below. +This is called I<hotplugging>. + +Only a subset of the attach-method backends support hotplugging +(currently only the libvirt attach-method has support). It also +requires that you use libvirt E<ge> 0.10.3 and qemu E<ge> 1.2. + +To hot-add a disk, simply call L</guestfs_add_drive_opts> after +L</guestfs_launch>. It is mandatory to specify the C<label> parameter +so that the newly added disk has a predictable name. For example: + + if (guestfs_launch (g) == -1) + error ("launch failed"); + + if (guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_LABEL, "newdisk", + -1) == -1) + error ("hot-add of disk failed"); + + if (guestfs_part_disk ("/dev/disk/guestfs/newdisk", "mbr") == -1) + error ("partitioning of hot-added disk failed"); + +Backends that support hotplugging do not require that you add +E<ge> 1 disk before calling launch. When hotplugging is supported +you don't need to add any disks. + =head2 INSPECTION Libguestfs has APIs for inspecting an unknown disk image to find out @@ -2639,7 +2671,7 @@ The guest may be killed by L</guestfs_kill_subprocess>, or may die asynchronously at any time (eg. due to some internal error), and that causes the state to transition back to CONFIG. -Configuration commands for qemu such as L</guestfs_add_drive> can only +Configuration commands for qemu such as L</guestfs_set_path> can only be issued when in the CONFIG state. The API offers one call that goes from CONFIG through LAUNCHING to diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index aa1c39f2..3eab5676 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -135,14 +135,6 @@ launch_libvirt (guestfs_h *g, const char *libvirt_uri) struct drive *drv; size_t i; - /* At present you must add drives before starting the appliance. In - * future when we enable hotplugging you won't need to do this. - */ - if (!g->nr_drives) { - error (g, _("you must call guestfs_add_drive before guestfs_launch")); - return -1; - } - /* XXX: It should be possible to make this work. */ if (g->direct) { error (g, _("direct mode flag is not supported yet for libvirt attach method")); @@ -1376,6 +1368,89 @@ max_disks_libvirt (guestfs_h *g) return 255; } +static xmlChar *construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv, size_t drv_index); + +/* Hot-add a drive. Note the appliance is up when this is called. */ +static int +hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index) +{ + virConnectPtr conn = g->virt.connv; + virDomainPtr dom = g->virt.domv; + xmlChar *xml = NULL; + + if (!conn || !dom) { + /* This is essentially an internal error if it happens. */ + error (g, "%s: conn == NULL or dom == NULL", __func__); + return -1; + } + + /* Create overlay for read-only drive. This works around lack of + * support for <transient/> disks in libvirt. + */ + if (make_qcow2_overlay_for_drive (g, drv) == -1) + return -1; + + /* Create the XML for the new disk. */ + xml = construct_libvirt_xml_hot_add_disk (g, drv, drv_index); + if (xml == NULL) + return -1; + + /* Attach it. */ + if (virDomainAttachDeviceFlags (dom, (char *) xml, + VIR_DOMAIN_DEVICE_MODIFY_LIVE) == -1) { + libvirt_error (g, _("could not attach disk to libvirt domain")); + goto error; + } + + free (xml); + return 0; + + error: + free (xml); + return -1; +} + +static xmlChar * +construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv, + size_t drv_index) +{ + xmlChar *ret = NULL; + xmlBufferPtr xb = NULL; + xmlOutputBufferPtr ob; + xmlTextWriterPtr xo = NULL; + + XMLERROR (NULL, xb = xmlBufferCreate ()); + XMLERROR (NULL, ob = xmlOutputBufferCreateBuffer (xb, NULL)); + XMLERROR (NULL, xo = xmlNewTextWriter (ob)); + + XMLERROR (-1, xmlTextWriterSetIndent (xo, 1)); + XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST " ")); + XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL)); + + if (construct_libvirt_xml_disk (g, xo, drv, drv_index) == -1) + goto err; + + XMLERROR (-1, xmlTextWriterEndDocument (xo)); + XMLERROR (NULL, ret = xmlBufferDetach (xb)); /* caller frees ret */ + + debug (g, "hot-add disk XML:\n%s", ret); + + err: + if (xo) + xmlFreeTextWriter (xo); /* frees 'ob' too */ + if (xb) + xmlBufferFree (xb); + + return ret; +} + +struct attach_ops attach_ops_libvirt = { + .launch = launch_libvirt, + .shutdown = shutdown_libvirt, + .max_disks = max_disks_libvirt, + .hot_add_drive = hot_add_drive_libvirt, +}; + #else /* no libvirt or libxml2 at compile time */ #define NOT_IMPL(r) \ @@ -1403,10 +1478,16 @@ max_disks_libvirt (guestfs_h *g) NOT_IMPL (-1); } -#endif /* no libvirt or libxml2 at compile time */ +static int +hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index) +{ + NOT_IMPL (-1); +} struct attach_ops attach_ops_libvirt = { .launch = launch_libvirt, .shutdown = shutdown_libvirt, .max_disks = max_disks_libvirt, }; + +#endif /* no libvirt or libxml2 at compile time */ diff --git a/src/launch.c b/src/launch.c index 1b6cf4bd..a4e10fef 100644 --- a/src/launch.c +++ b/src/launch.c @@ -29,6 +29,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <errno.h> +#include <assert.h> #include "c-ctype.h" @@ -57,14 +58,29 @@ guestfs___rollback_drives (guestfs_h *g, size_t old_i) g->nr_drives = old_i; } +/* Add struct drive to the g->drives vector at the given index. */ +static void +add_drive_to_handle_at (guestfs_h *g, struct drive *d, size_t drv_index) +{ + if (drv_index >= g->nr_drives) { + g->drives = safe_realloc (g, g->drives, + sizeof (struct drive *) * (drv_index + 1)); + while (g->nr_drives <= drv_index) { + g->drives[g->nr_drives] = NULL; + g->nr_drives++; + } + } + + assert (g->drives[drv_index] == NULL); + + g->drives[drv_index] = d; +} + /* Add struct drive to the end of the g->drives vector in the handle. */ static void add_drive_to_handle (guestfs_h *g, struct drive *d) { - g->nr_drives++; - g->drives = safe_realloc (g, g->drives, - sizeof (struct drive *) * g->nr_drives); - g->drives[g->nr_drives-1] = d; + add_drive_to_handle_at (g, d, g->nr_drives); } static struct drive * @@ -207,6 +223,48 @@ valid_disk_label (const char *str) return 1; } +/* The low-level function that adds a drive. */ +static int +add_drive (guestfs_h *g, struct drive *drv) +{ + size_t i, drv_index; + + if (g->state == CONFIG) { + /* Not hotplugging, so just add it to the handle. */ + add_drive_to_handle (g, drv); + return 0; + } + + /* ... else, hotplugging case. */ + if (!g->attach_ops || !g->attach_ops->hot_add_drive) { + error (g, _("the current attach-method does not support hotplugging drives")); + return -1; + } + + if (!drv->disk_label) { + error (g, _("'label' is required when hotplugging drives")); + return -1; + } + + /* Get the first free index, or add it at the end. */ + drv_index = g->nr_drives; + for (i = 0; i < g->nr_drives; ++i) + if (g->drives[i] == NULL) + drv_index = i; + + /* Hot-add the drive. */ + if (g->attach_ops->hot_add_drive (g, drv, drv_index) == -1) + return -1; + + add_drive_to_handle_at (g, drv, drv_index); + + /* Call into the appliance to wait for the new drive to appear. */ + if (guestfs_internal_hot_add_drive (g, drv->disk_label) == -1) + return -1; + + return 0; +} + /* Traditionally you have been able to use /dev/null as a filename, as * many times as you like. Ancient KVM (RHEL 5) cannot handle adding * /dev/null readonly. qemu 1.2 + virtio-scsi segfaults when you use @@ -220,7 +278,7 @@ add_null_drive (guestfs_h *g, int readonly, const char *format, const char *iface, const char *name, const char *disk_label) { char *tmpfile = NULL; - int fd = -1; + int fd = -1, r; struct drive *drv; if (format && STRNEQ (format, "raw")) { @@ -252,8 +310,12 @@ add_null_drive (guestfs_h *g, int readonly, const char *format, } drv = create_drive_struct (g, tmpfile, readonly, format, iface, name, disk_label, 0); - add_drive_to_handle (g, drv); + r = add_drive (g, drv); free (tmpfile); + if (r == -1) { + free_drive_struct (drv); + return -1; + } return 0; @@ -328,7 +390,11 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, drv = create_drive_struct (g, filename, readonly, format, iface, name, disk_label, use_cache_none); - add_drive_to_handle (g, drv); + if (add_drive (g, drv) == -1) { + free_drive_struct (drv); + return -1; + } + return 0; } |