These patches contain the beginnings of discard (a.k.a. trim or unmap) support. This will allow us to change virt-sparsify to work on disk images in-place (instead of using slow & inefficient copying). The approach used is to add an optional 'discard' parameter to add-drive. It has 3 possible settings: - 'disable' : the default, no discard is done - 'besteffort' : try to enable it, but don't fail if it's not supported - 'enable' : enable it, and fail if it is not supported I'm not sure if we should wait for post-1.26 before adding discard support. It depends if the interface above is suitable or if we'll have to change it later. However I discussed how to support this at DevConf and this approach was generally agreed. Rich.
Richard W.M. Jones
2014-Mar-10 17:28 UTC
[Libguestfs] [PATCH 1/3] launch: libvirt: Move the is_kvm flag (derived from libvirt capabilities) to backend data struct.
This is just rearranging the data between structs. There should be no functional change. --- src/launch-libvirt.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index fd6e6d2..d86e2d2 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -106,6 +106,7 @@ struct backend_libvirt_data { char *selinux_imagelabel; bool selinux_norelabel_disks; char name[DOMAIN_NAME_LEN]; /* random name */ + bool is_kvm; /* false = qemu, true = kvm (from capabilities)*/ }; /* Parameters passed to construct_libvirt_xml and subfunctions. We @@ -122,12 +123,11 @@ struct libvirt_xml_params { char guestfsd_path[UNIX_PATH_MAX]; /* paths to sockets */ char console_path[UNIX_PATH_MAX]; bool enable_svirt; /* false if we decided to disable sVirt */ - bool is_kvm; /* false = qemu, true = kvm */ bool current_proc_is_root; /* true = euid is root */ }; -static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct libvirt_xml_params *params); -static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params); +static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct backend_libvirt_data *data); +static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct backend_libvirt_data *data, const struct libvirt_xml_params *params); static void debug_appliance_permissions (guestfs_h *g); static void debug_socket_permissions (guestfs_h *g); static void libvirt_error (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3))); @@ -299,7 +299,7 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri) if (g->verbose) guestfs___print_timestamped_message (g, "parsing capabilities XML"); - if (parse_capabilities (g, capabilities_xml, ¶ms) == -1) + if (parse_capabilities (g, capabilities_xml, data) == -1) goto cleanup; /* Locate and/or build the appliance. */ @@ -434,7 +434,7 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri) guestfs___drive_name (params.appliance_index, ¶ms.appliance_dev[7]); params.enable_svirt = ! is_custom_hv (g); - xml = construct_libvirt_xml (g, ¶ms); + xml = construct_libvirt_xml (g, data, ¶ms); if (!xml) goto cleanup; @@ -575,7 +575,7 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri) static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, - struct libvirt_xml_params *params) + struct backend_libvirt_data *data) { CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL; CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL; @@ -654,9 +654,9 @@ parse_capabilities (guestfs_h *g, const char *capabilities_xml, force_tcg = guestfs___get_backend_setting_bool (g, "force_tcg"); if (!force_tcg) - params->is_kvm = seen_kvm; + data->is_kvm = seen_kvm; else - params->is_kvm = 0; + data->is_kvm = 0; return 0; } @@ -779,10 +779,10 @@ debug_socket_permissions (guestfs_h *g) } } -static int construct_libvirt_xml_domain (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); +static int construct_libvirt_xml_domain (guestfs_h *g, const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); static int construct_libvirt_xml_name (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); -static int construct_libvirt_xml_cpu (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); -static int construct_libvirt_xml_boot (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); +static int construct_libvirt_xml_cpu (guestfs_h *g, const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); +static int construct_libvirt_xml_boot (guestfs_h *g, const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); static int construct_libvirt_xml_seclabel (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); static int construct_libvirt_xml_lifecycle (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); static int construct_libvirt_xml_devices (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); @@ -872,7 +872,9 @@ static int construct_libvirt_xml_appliance (guestfs_h *g, const struct libvirt_x __FILE__, __LINE__, (fn)); static xmlChar * -construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params) +construct_libvirt_xml (guestfs_h *g, + const struct backend_libvirt_data *data, + const struct libvirt_xml_params *params) { xmlChar *ret = NULL; CLEANUP_XMLBUFFERFREE xmlBufferPtr xb = NULL; @@ -905,7 +907,7 @@ construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params) return NULL; } - if (construct_libvirt_xml_domain (g, params, xo) == -1) + if (construct_libvirt_xml_domain (g, data, params, xo) == -1) return NULL; if (xmlTextWriterEndDocument (xo) == -1) { @@ -925,19 +927,20 @@ construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params) static int construct_libvirt_xml_domain (guestfs_h *g, + const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo) { start_element ("domain") { - attribute ("type", params->is_kvm ? "kvm" : "qemu"); + attribute ("type", data->is_kvm ? "kvm" : "qemu"); attribute_ns ("xmlns", "qemu", NULL, "http://libvirt.org/schemas/domain/qemu/1.0"); if (construct_libvirt_xml_name (g, params, xo) == -1) return -1; - if (construct_libvirt_xml_cpu (g, params, xo) == -1) + if (construct_libvirt_xml_cpu (g, data, params, xo) == -1) return -1; - if (construct_libvirt_xml_boot (g, params, xo) == -1) + if (construct_libvirt_xml_boot (g, data, params, xo) == -1) return -1; if (construct_libvirt_xml_seclabel (g, params, xo) == -1) return -1; @@ -968,6 +971,7 @@ construct_libvirt_xml_name (guestfs_h *g, /* CPU and memory features. */ static int construct_libvirt_xml_cpu (guestfs_h *g, + const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo) { @@ -987,7 +991,7 @@ construct_libvirt_xml_cpu (guestfs_h *g, * Only do this with KVM. It is broken in subtle ways on TCG, and * fairly pointless anyway. */ - if (params->is_kvm) { + if (data->is_kvm) { start_element ("cpu") { attribute ("mode", "host-passthrough"); start_element ("model") { @@ -1031,6 +1035,7 @@ construct_libvirt_xml_cpu (guestfs_h *g, /* Boot parameters. */ static int construct_libvirt_xml_boot (guestfs_h *g, + const struct backend_libvirt_data *data, const struct libvirt_xml_params *params, xmlTextWriterPtr xo) { @@ -1039,7 +1044,7 @@ construct_libvirt_xml_boot (guestfs_h *g, /* Linux kernel command line. */ flags = 0; - if (!params->is_kvm) + if (!data->is_kvm) flags |= APPLIANCE_COMMAND_LINE_IS_TCG; cmdline = guestfs___appliance_command_line (g, params->appliance_dev, flags); -- 1.8.5.3
Richard W.M. Jones
2014-Mar-10 17:28 UTC
[Libguestfs] [PATCH 2/3] New API parameter: Add discard parameter to guestfs_add_drive_opts.
This adds a discard parameter to the guestfs_add_drive_opts which approximately maps to the discard=ignore|unmap parameter supported by qemu. If discard is set to "enable" then we force discard=unmap (and try to fail if it is not possible). If discard is set to the more useful "besteffort" option, then we enable discard if possible. The default is "disable". --- generator/actions.ml | 32 +++++++++++++- src/drives.c | 107 +++++++++++++++++++++++++++++++---------------- src/guestfs-internal.h | 9 ++++ src/launch-direct.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++- src/launch-libvirt.c | 96 ++++++++++++++++++++++++++++++++++++++---- src/launch-uml.c | 6 +++ 6 files changed, 316 insertions(+), 44 deletions(-) diff --git a/generator/actions.ml b/generator/actions.ml index a6ef194..b25194c 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -1286,7 +1286,7 @@ not all belong to a single logical operating system { defaults with name = "add_drive"; - style = RErr, [String "filename"], [OBool "readonly"; OString "format"; OString "iface"; OString "name"; OString "label"; OString "protocol"; OStringList "server"; OString "username"; OString "secret"; OString "cachemode"]; + style = RErr, [String "filename"], [OBool "readonly"; OString "format"; OString "iface"; OString "name"; OString "label"; OString "protocol"; OStringList "server"; OString "username"; OString "secret"; OString "cachemode"; OString "discard"]; once_had_no_optargs = true; blocking = false; fish_alias = ["add"]; @@ -1504,6 +1504,36 @@ for scratch or temporary disks. =back +=item C<discard> + +Enable or disable discard (a.k.a. trim or unmap) support on this +drive. If enabled, operations such as C<guestfs_fstrim> will be able +to discard / make thin / punch holes in the underlying host file +or device. + +Possible discard settings are: + +=over 4 + +=item C<discard = \"disable\"> + +Disable discard support. This is the default. + +=item C<discard = \"enable\"> + +Enable discard support. Fail if discard is not possible. + +=item C<discard = \"besteffort\"> + +Enable discard support if possible, but don't fail if it is not +supported. + +Since not all backends and not all underlying systems support +discard, this is a good choice if you want to use discard if +possible, but don't mind if it doesn't work. + +=back + =back" }; { defaults with diff --git a/src/drives.c b/src/drives.c index 2c85b52..68e37f7 100644 --- a/src/drives.c +++ b/src/drives.c @@ -115,7 +115,8 @@ static struct drive * create_drive_file (guestfs_h *g, const char *path, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { struct drive *drv = safe_calloc (g, 1, sizeof *drv); @@ -128,6 +129,7 @@ create_drive_file (guestfs_h *g, const char *path, drv->name = name ? safe_strdup (g, name) : NULL; drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL; drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL; + drv->discard = discard; if (readonly) { if (create_overlay (g, drv) == -1) { @@ -151,7 +153,8 @@ create_drive_non_file (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { struct drive *drv = safe_calloc (g, 1, sizeof *drv); @@ -168,6 +171,7 @@ create_drive_non_file (guestfs_h *g, drv->name = name ? safe_strdup (g, name) : NULL; drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL; drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL; + drv->discard = discard; if (readonly) { if (create_overlay (g, drv) == -1) { @@ -191,7 +195,8 @@ create_drive_curl (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { if (secret != NULL) { error (g, _("curl: you cannot specify a secret with this protocol")); @@ -223,7 +228,7 @@ create_drive_curl (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static struct drive * @@ -233,7 +238,8 @@ create_drive_gluster (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { if (username != NULL) { error (g, _("gluster: you cannot specify a username with this protocol")); @@ -263,7 +269,7 @@ create_drive_gluster (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static int @@ -285,7 +291,8 @@ create_drive_nbd (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { if (username != NULL) { error (g, _("nbd: you cannot specify a username with this protocol")); @@ -308,7 +315,7 @@ create_drive_nbd (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static struct drive * @@ -318,7 +325,8 @@ create_drive_rbd (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { size_t i; @@ -348,7 +356,7 @@ create_drive_rbd (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static struct drive * @@ -358,7 +366,8 @@ create_drive_sheepdog (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { size_t i; @@ -397,7 +406,7 @@ create_drive_sheepdog (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static struct drive * @@ -407,7 +416,8 @@ create_drive_ssh (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { if (secret != NULL) { error (g, _("ssh: you cannot specify a secret with this protocol")); @@ -444,7 +454,7 @@ create_drive_ssh (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } static struct drive * @@ -454,7 +464,8 @@ create_drive_iscsi (guestfs_h *g, const char *username, const char *secret, bool readonly, const char *format, const char *iface, const char *name, - const char *disk_label, const char *cachemode) + const char *disk_label, const char *cachemode, + enum discard discard) { if (username != NULL) { error (g, _("iscsi: you cannot specify a username with this protocol")); @@ -491,7 +502,7 @@ create_drive_iscsi (guestfs_h *g, servers, nr_servers, exportname, username, secret, readonly, format, iface, name, disk_label, - cachemode); + cachemode, discard); } /* Traditionally you have been able to use /dev/null as a filename, as @@ -528,14 +539,15 @@ create_drive_dev_null (guestfs_h *g, bool readonly, const char *format, return NULL; return create_drive_file (g, tmpfile, readonly, format, iface, name, - disk_label, 0); + disk_label, NULL, discard_disable); } static struct drive * create_drive_dummy (guestfs_h *g) { /* A special drive struct that is used as a dummy slot for the appliance. */ - return create_drive_file (g, "", 0, NULL, NULL, NULL, NULL, 0); + return create_drive_file (g, "", 0, NULL, NULL, NULL, NULL, NULL, + discard_disable); } static void @@ -563,8 +575,8 @@ free_drive_struct (struct drive *drv) free (drv); } -static const char * -protocol_to_string (enum drive_protocol protocol) +const char * +guestfs___drive_protocol_to_string (enum drive_protocol protocol) { switch (protocol) { case drive_protocol_file: return "file"; @@ -590,12 +602,12 @@ static char * drive_to_string (guestfs_h *g, const struct drive *drv) { return safe_asprintf - (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s", + (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s%s", drv->src.u.path, drv->readonly ? " readonly" : "", drv->src.format ? " format=" : "", drv->src.format ? : "", - protocol_to_string (drv->src.protocol), + guestfs___drive_protocol_to_string (drv->src.protocol), drv->iface ? " iface=" : "", drv->iface ? : "", drv->name ? " name=" : "", @@ -603,7 +615,9 @@ drive_to_string (guestfs_h *g, const struct drive *drv) drv->disk_label ? " label=" : "", drv->disk_label ? : "", drv->cachemode ? " cache=" : "", - drv->cachemode ? : ""); + drv->cachemode ? : "", + drv->discard == discard_disable ? "" : + drv->discard == discard_enable ? " discard=enable" : " discard=besteffort"); } /* Add struct drive to the g->drives vector at the given index. */ @@ -824,6 +838,7 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, const char *username; const char *secret; const char *cachemode; + enum discard discard; struct drive *drv; size_t i, drv_index; @@ -852,6 +867,28 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, cachemode = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK ? optargs->cachemode : NULL; + if (optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK) { + if (STREQ (optargs->discard, "disable")) + discard = discard_disable; + else if (STREQ (optargs->discard, "enable")) + discard = discard_enable; + else if (STREQ (optargs->discard, "besteffort")) + discard = discard_besteffort; + else { + error (g, _("discard parameter must be 'disable', 'enable' or 'besteffort'")); + free_drive_servers (servers, nr_servers); + return -1; + } + } + else + discard = discard_disable; + + if (readonly && discard == discard_enable) { + error (g, _("discard support cannot be enabled on read-only drives")); + free_drive_servers (servers, nr_servers); + return -1; + } + if (format && !valid_format_iface (format)) { error (g, _("%s parameter is empty or contains disallowed characters"), "format"); @@ -904,7 +941,7 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, } drv = create_drive_file (g, filename, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } } else if (STREQ (protocol, "ftp")) { @@ -912,71 +949,71 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "ftps")) { drv = create_drive_curl (g, drive_protocol_ftps, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "gluster")) { drv = create_drive_gluster (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "http")) { drv = create_drive_curl (g, drive_protocol_http, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "https")) { drv = create_drive_curl (g, drive_protocol_https, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "iscsi")) { drv = create_drive_iscsi (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "nbd")) { drv = create_drive_nbd (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "rbd")) { drv = create_drive_rbd (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "sheepdog")) { drv = create_drive_sheepdog (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "ssh")) { drv = create_drive_ssh (g, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else if (STREQ (protocol, "tftp")) { drv = create_drive_curl (g, drive_protocol_tftp, servers, nr_servers, filename, username, secret, readonly, format, iface, name, - disk_label, cachemode); + disk_label, cachemode, discard); } else { error (g, _("unknown protocol '%s'"), protocol); diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 545146f..df481a7 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -231,6 +231,12 @@ struct drive_source { char *secret; }; +enum discard { + discard_disable = 0, + discard_enable = 1, + discard_besteffort = 2, +}; + /* There is one 'struct drive' per drive, including hot-plugged drives. */ struct drive { /* Original source of the drive, eg. file:..., http:... */ @@ -252,6 +258,7 @@ struct drive { char *name; char *disk_label; char *cachemode; + enum discard discard; }; /* Extra hv parameters (from guestfs_config). */ @@ -713,6 +720,7 @@ extern size_t guestfs___checkpoint_drives (guestfs_h *g); extern void guestfs___rollback_drives (guestfs_h *g, size_t); extern void guestfs___add_dummy_appliance_drive (guestfs_h *g); extern void guestfs___free_drives (guestfs_h *g); +extern const char *guestfs___drive_protocol_to_string (enum drive_protocol protocol); /* appliance.c */ extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **dtb, char **initrd, char **appliance); @@ -832,6 +840,7 @@ extern void guestfs___cleanup_cmd_close (struct command **); /* launch-direct.c */ extern char *guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src); +extern bool guestfs___discard_possible (guestfs_h *g, struct drive *drv, bool qemu15, bool qemu16); /* utils.c */ extern int guestfs___validate_guid (const char *); diff --git a/src/launch-direct.c b/src/launch-direct.c index 1e84061..584c4de 100644 --- a/src/launch-direct.c +++ b/src/launch-direct.c @@ -509,6 +509,29 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL; if (!drv->overlay) { + const char *discard_mode = ""; + int major = data->qemu_version_major, minor = data->qemu_version_minor; + + switch (drv->discard) { + case discard_disable: + /* Since the default is always discard=ignore, don't specify it + * on the command line. This also avoids unnecessary breakage + * with qemu < 1.5 which didn't have the option at all. + */ + break; + case discard_enable: + if (!guestfs___discard_possible (g, drv, major, minor)) + goto cleanup0; + /*FALLTHROUGH*/ + case discard_besteffort: + /* I believe from reading the code that this is always safe as + * long as qemu >= 1.5. + */ + if (major > 1 || (major == 1 && minor >= 5)) + discard_mode = ",discard=unmap"; + break; + } + /* Make the file= parameter. */ file = guestfs___drive_source_qemu_param (g, &drv->src); escaped_file = qemu_escape_param (g, file); @@ -517,10 +540,11 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) * the if=... at the end. */ param = safe_asprintf - (g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu", + (g, "file=%s%s,cache=%s%s%s%s%s%s,id=hd%zu", escaped_file, drv->readonly ? ",snapshot=on" : "", drv->cachemode ? drv->cachemode : "writeback", + discard_mode, drv->src.format ? ",format=" : "", drv->src.format ? drv->src.format : "", drv->disk_label ? ",serial=" : "", @@ -1370,6 +1394,90 @@ guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src) abort (); } +/* Test if discard is both supported by qemu AND possible with the + * underlying file or device. This returns 1 if discard is possible. + * It returns 0 if not possible and sets the error to the reason why. + * + * This function is called when the user set discard == "enable". + * + * qemu15: qemu >= 1.5. This was the first version that supported + * the discard option on -drive at all. + * + * qemu16: qemu >= 1.6. This was the first version that supported + * unmap on qcow2 backing files. + */ +bool +guestfs___discard_possible (guestfs_h *g, struct drive *drv, + bool qemu15, bool qemu16) +{ + if (!qemu15) { + error (g, _("discard cannot be enabled on this drive: " + "qemu < 1.5")); + return false; + } + + /* If it's an overlay, discard is not possible (on the underlying + * file). This has probably been caught earlier since we already + * checked that the drive is !readonly. Nevertheless ... + */ + if (drv->overlay) { + error (g, _("discard cannot be enabled on this drive: " + "the drive has a read-only overlay")); + return false; + } + + /* Look at the source format. */ + if (drv->src.format == NULL) { + /* We could autodetect the format, but we don't ... yet. XXX */ + error (g, _("discard cannot be enabled on this drive: " + "you have to specify the format of the file")); + return false; + } + else if (STREQ (drv->src.format, "raw")) + /* OK */ ; + else if (STREQ (drv->src.format, "qcow2")) { + if (!qemu16) { + error (g, _("discard cannot be enabled on this drive: " + "qemu < 1.6 cannot do discard on qcow2 files")); + return false; + } + } + else { + /* It's possible in future other formats will support discard, but + * currently (qemu 1.7) none of them do. + */ + error (g, _("discard cannot be enabled on this drive: " + "qemu does not support discard for '%s' format files"), + drv->src.format); + return false; + } + + switch (drv->src.protocol) { + /* Protocols which support discard. */ + case drive_protocol_file: + case drive_protocol_gluster: + case drive_protocol_iscsi: + case drive_protocol_nbd: + case drive_protocol_rbd: + case drive_protocol_sheepdog: /* XXX depends on server version */ + break; + + /* Protocols which don't support discard. */ + case drive_protocol_ftp: + case drive_protocol_ftps: + case drive_protocol_http: + case drive_protocol_https: + case drive_protocol_ssh: + case drive_protocol_tftp: + error (g, _("discard cannot be enabled on this drive: " + "protocol '%s' does not support discard"), + guestfs___drive_protocol_to_string (drv->src.protocol)); + return false; + } + + return true; +} + static int shutdown_direct (guestfs_h *g, void *datav, int check_for_errors) { diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index d86e2d2..1c46b57 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -107,6 +107,7 @@ struct backend_libvirt_data { bool selinux_norelabel_disks; char name[DOMAIN_NAME_LEN]; /* random name */ bool is_kvm; /* false = qemu, true = kvm (from capabilities)*/ + unsigned long qemu_version; /* qemu version (from libvirt) */ }; /* Parameters passed to construct_libvirt_xml and subfunctions. We @@ -131,6 +132,7 @@ static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct backend_libvir static void debug_appliance_permissions (guestfs_h *g); static void debug_socket_permissions (guestfs_h *g); static void libvirt_error (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3))); +static void libvirt_debug (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3))); static int is_custom_hv (guestfs_h *g); static int is_blk (const char *path); static void ignore_errors (void *ignore, virErrorPtr ignore2); @@ -283,6 +285,19 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri) */ virConnSetErrorFunc (conn, NULL, ignore_errors); + /* Get hypervisor (hopefully qemu) version. */ + if (virConnectGetVersion (conn, &data->qemu_version) == 0) { + debug (g, "qemu version (reported by libvirt) = %lu (%lu.%lu.%lu)", + data->qemu_version, + data->qemu_version / 1000000UL, + data->qemu_version / 1000UL % 1000UL, + data->qemu_version % 1000UL); + } + else { + libvirt_debug (g, "unable to read qemu version from libvirt"); + data->qemu_version = 0; + } + if (g->verbose) guestfs___print_timestamped_message (g, "get libvirt capabilities"); @@ -789,7 +804,7 @@ static int construct_libvirt_xml_devices (guestfs_h *g, const struct libvirt_xml static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo); static int construct_libvirt_xml_disk (guestfs_h *g, const struct backend_libvirt_data *data, xmlTextWriterPtr xo, struct drive *drv, size_t drv_index); static int construct_libvirt_xml_disk_target (guestfs_h *g, xmlTextWriterPtr xo, size_t drv_index); -static int construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, xmlTextWriterPtr xo, const char *format, const char *cachemode); +static int construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, const struct backend_libvirt_data *data, struct drive *drv, xmlTextWriterPtr xo, const char *format, const char *cachemode, enum discard discard); static int construct_libvirt_xml_disk_address (guestfs_h *g, xmlTextWriterPtr xo, size_t drv_index); static int construct_libvirt_xml_disk_source_hosts (guestfs_h *g, xmlTextWriterPtr xo, const struct drive_source *src); static int construct_libvirt_xml_disk_source_seclabel (guestfs_h *g, const struct backend_libvirt_data *data, xmlTextWriterPtr xo); @@ -1238,7 +1253,9 @@ construct_libvirt_xml_disk (guestfs_h *g, if (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1) return -1; - if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") + if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL, + xo, "qcow2", "unsafe", + discard_disable) == -1) return -1; } @@ -1375,8 +1392,9 @@ construct_libvirt_xml_disk (guestfs_h *g, return -1; } - if (construct_libvirt_xml_disk_driver_qemu (g, xo, format, - drv->cachemode ? : "writeback") + if (construct_libvirt_xml_disk_driver_qemu (g, data, drv, xo, format, + drv->cachemode ? : "writeback", + drv->discard) == -1) return -1; } @@ -1412,14 +1430,43 @@ construct_libvirt_xml_disk_target (guestfs_h *g, xmlTextWriterPtr xo, } static int -construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, xmlTextWriterPtr xo, +construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, + const struct backend_libvirt_data *data, + struct drive *drv, + xmlTextWriterPtr xo, const char *format, - const char *cachemode) + const char *cachemode, + enum discard discard) { + bool discard_unmap = false; + + switch (discard) { + case discard_disable: + /* Since the default is always discard=ignore, don't specify it + * in the XML. + */ + break; + case discard_enable: + if (!guestfs___discard_possible (g, drv, + data->qemu_version >= 1005000, + data->qemu_version >= 1006000)) + return -1; + /*FALLTHROUGH*/ + case discard_besteffort: + /* I believe from reading the code that this is always safe as + * long as qemu >= 1.5. + */ + if (data->qemu_version >= 1005000) + discard_unmap = true; + break; + } + start_element ("driver") { attribute ("name", "qemu"); attribute ("type", format); attribute ("cache", cachemode); + if (discard_unmap) + attribute ("discard", "unmap"); } end_element (); return 0; @@ -1504,7 +1551,9 @@ construct_libvirt_xml_appliance (guestfs_h *g, attribute ("bus", "scsi"); } end_element (); - if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") == -1) + if (construct_libvirt_xml_disk_driver_qemu (g, NULL, NULL, xo, + "qcow2", "unsafe", + discard_disable) == -1) return -1; if (construct_libvirt_xml_disk_address (g, xo, params->appliance_index) @@ -1664,6 +1713,39 @@ libvirt_error (guestfs_h *g, const char *fs, ...) free (msg); } +/* Same as 'libvirt_error' but calls debug instead. */ +static void +libvirt_debug (guestfs_h *g, const char *fs, ...) +{ + va_list args; + char *msg; + int len; + virErrorPtr err; + + if (!g->verbose) + return; + + va_start (args, fs); + len = vasprintf (&msg, fs, args); + va_end (args); + + if (len < 0) + msg = safe_asprintf (g, + _("%s: internal error forming error message"), + __func__); + + /* In all recent libvirt, this retrieves the thread-local error. */ + err = virGetLastError (); + if (err) + debug (g, "%s: %s [code=%d domain=%d]", + msg, err->message, err->code, err->domain); + else + debug (g, "%s", msg); + + /* NB. 'err' must not be freed! */ + free (msg); +} + #if HAVE_LIBSELINUX static void selinux_warning (guestfs_h *g, const char *func, diff --git a/src/launch-uml.c b/src/launch-uml.c index deda366..2a6ddaf 100644 --- a/src/launch-uml.c +++ b/src/launch-uml.c @@ -122,6 +122,12 @@ uml_supported (guestfs_h *g) _("uml backend does not support drives with 'label' parameter")); return false; } + /* Note that discard == "besteffort" is fine. */ + if (drv->discard == discard_enable) { + error (g, + _("uml backend does not support drives with 'discard' parameter set to 'enable'")); + return false; + } } return true; -- 1.8.5.3
Richard W.M. Jones
2014-Mar-10 17:28 UTC
[Libguestfs] [PATCH 3/3] tests: Add a test of discard support, using fstrim.
Test that fstrim actually works, end-to-end. --- Makefile.am | 1 + configure.ac | 1 + tests/discard/Makefile.am | 26 ++++++++++ tests/discard/test-discard.pl | 116 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 tests/discard/Makefile.am create mode 100755 tests/discard/test-discard.pl diff --git a/Makefile.am b/Makefile.am index 020628e..11b12c2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,7 @@ SUBDIRS += tests/events SUBDIRS += tests/parallel SUBDIRS += tests/create SUBDIRS += tests/disks +SUBDIRS += tests/discard SUBDIRS += tests/mountable SUBDIRS += tests/network SUBDIRS += tests/lvm diff --git a/configure.ac b/configure.ac index 35460e2..3785ea8 100644 --- a/configure.ac +++ b/configure.ac @@ -1673,6 +1673,7 @@ AC_CONFIG_FILES([Makefile tests/charsets/Makefile tests/create/Makefile tests/data/Makefile + tests/discard/Makefile tests/disks/Makefile tests/disks/test-qemu-drive-libvirt.xml tests/disk-labels/Makefile diff --git a/tests/discard/Makefile.am b/tests/discard/Makefile.am new file mode 100644 index 0000000..f159287 --- /dev/null +++ b/tests/discard/Makefile.am @@ -0,0 +1,26 @@ +# libguestfs +# Copyright (C) 2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +include $(top_srcdir)/subdir-rules.mk + +TESTS = \ + test-discard.pl + +TESTS_ENVIRONMENT = $(top_builddir)/run --test + +EXTRA_DIST = \ + $(TESTS) diff --git a/tests/discard/test-discard.pl b/tests/discard/test-discard.pl new file mode 100755 index 0000000..770049c --- /dev/null +++ b/tests/discard/test-discard.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl +# Copyright (C) 2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test that discard support works. + +use strict; +use warnings; + +use Sys::Guestfs; + +# Since we read error messages, we want to ensure they are printed +# in English, hence: +$ENV{"LANG"} = "C"; + +$| = 1; + +my $g = Sys::Guestfs->new (); + +# Discard is only supported when using qemu. +if ($g->get_backend () ne "libvirt" && + $g->get_backend () !~ /^libvirt:/ && + $g->get_backend () ne "direct") { + print "$0: skipped test because discard is only supported when using qemu\n"; + exit 77; +} + +# You can set this to "raw" or "qcow2". +my $format = "raw"; + +my $disk; +my @args; +if ($format eq "raw") { + $disk = "test-discard.img"; + @args = ( preallocation => "sparse" ); +} elsif ($format eq "qcow2") { + $disk = "test-discard.qcow2"; + @args = ( preallocation => "off", compat => "1.1" ); +} else { + die "$0: invalid disk format: $format\n"; +} + +# Create a disk and add it with discard enabled. This is allowed to +# fail, eg because qemu is too old, but libguestfs must tell us that +# it failed (since we're using 'enable', not 'besteffort'). +$g->disk_create ($disk, $format, 100*1024*1024, @args); +END { unlink ($disk); }; + +eval { + $g->add_drive ($disk, format => $format, readonly => 0, discard => "enable"); + $g->launch (); +}; +if ($@) { + if ($@ =~ /discard cannot be enabled on this drive/) { + # This is OK. Libguestfs says it's not possible to enable + # discard on this drive (eg. because qemu is too old). Print + # the reason and skip the test. + print "$0: skipped test: $@\n"; + exit 77; + } + die # propagate the unexpected error +} + +# Is fstrim available in the appliance? +unless ($g->feature_available (["fstrim"])) { + print "$0: skipped test because fstrim is not available\n"; + exit 77; +} + +# At this point we've got a disk which claims to support discard. +# Let's test that theory. + +my $orig_size = (stat ($disk))[12]; +print "original size:\t$orig_size (blocks)\n"; +#system "du -sh $disk"; + +# Write a filesystem onto the disk and fill it with data. + +$g->mkfs ("ext4", "/dev/sda"); +$g->mount_options ("discard", "/dev/sda", "/"); +$g->fill (33, 10000000, "/data"); +$g->sync (); + +my $full_size = (stat ($disk))[12]; +print "full size:\t$full_size (blocks)\n"; +#system "du -sh $disk"; + +die "surprising result: full size <= original size" + if $full_size <= $orig_size; + +# Remove the file and then try to trim the filesystem. + +$g->rm ("/data"); +$g->fstrim ("/"); +$g->sync (); +$g->close (); + +my $trimmed_size = (stat ($disk))[12]; +print "trimmed size:\t$trimmed_size (blocks)\n"; +#system "du -sh $disk"; + +die "looks like the fstrim operation did not work" + if $trimmed_size >= $full_size; -- 1.8.5.3
Pino Toscano
2014-Mar-10 17:50 UTC
Re: [Libguestfs] [PATCH 2/3] New API parameter: Add discard parameter to guestfs_add_drive_opts.
Hi, On Monday 10 March 2014 17:28:53 Richard W.M. Jones wrote:> index 1e84061..584c4de 100644 > --- a/src/launch-direct.c > +++ b/src/launch-direct.c > @@ -509,6 +509,29 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) > CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL; > > if (!drv->overlay) { > + const char *discard_mode = ""; > + int major = data->qemu_version_major, minor = data->qemu_version_minor; > + > + switch (drv->discard) { > + case discard_disable: > + /* Since the default is always discard=ignore, don't specify it > + * on the command line. This also avoids unnecessary breakage > + * with qemu < 1.5 which didn't have the option at all. > + */ > + break; > + case discard_enable: > + if (!guestfs___discard_possible (g, drv, major, minor))There is an argument mismatch in this call to guestfs___discard_possible (int's vs bool's). What about turn guestfs___discard_possible to just get the qemu version, and do the checks in it rather than asking to prefill the two "is qemu >= 1.5" and "is qemu >= 1.6" bool's? -- Pino Toscano
Pino Toscano
2014-Mar-10 17:59 UTC
Re: [Libguestfs] [PATCH 1/3] launch: libvirt: Move the is_kvm flag (derived from libvirt capabilities) to backend data struct.
Hi, On Monday 10 March 2014 17:28:52 Richard W.M. Jones wrote:> diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c > index fd6e6d2..d86e2d2 100644 > --- a/src/launch-libvirt.c > +++ b/src/launch-libvirt.c > @@ -106,6 +106,7 @@ struct backend_libvirt_data { > char *selinux_imagelabel; > bool selinux_norelabel_disks; > char name[DOMAIN_NAME_LEN]; /* random name */ > + bool is_kvm; /* false = qemu, true = kvm (from capabilities)*/ }; > > /* Parameters passed to construct_libvirt_xml and subfunctions. We > @@ -122,12 +123,11 @@ struct libvirt_xml_params { > char guestfsd_path[UNIX_PATH_MAX]; /* paths to sockets */ > char console_path[UNIX_PATH_MAX]; > bool enable_svirt; /* false if we decided to disable sVirt */ > - bool is_kvm; /* false = qemu, true = kvm */ > bool current_proc_is_root; /* true = euid is root */ > }; > > -static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct libvirt_xml_params *params); > -static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params); > +static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct backend_libvirt_data *data); > +static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct backend_libvirt_data *data, const struct libvirt_xml_params *params);Considering the libvirt_xml_params struct has already a 'struct backend_libvirt_data *data' member, I guess these (and the others) functions could just access params->data->is_kvm with no need to pass also backend_libvirt_data around, no? After all, construct_libvirt_xml_name does this already. -- Pino Toscano
Possibly Parallel Threads
- [PATCH v2 00/18] Add discard support.
- [PATCH 0/2] Don't use snapshot=on
- [PATCH v3 00/10] Add discard support.
- Re: [PATCH v2 03/18] New API parameter: Add discard parameter to guestfs_add_drive_opts.
- [RFC] lib: allow to specify physical/logical block size for disks