diff options
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; } |