[libvirt] [libvirt-glib] Add virStorageVolResize()

Eric Blake eblake at redhat.com
Fri Jan 27 23:17:30 UTC 2012


On 01/26/2012 10:29 PM, Zeeshan Ali (Khattak) wrote:
> From: "Zeeshan Ali (Khattak)" <zeeshanak at gnome.org>
> 
> Add a new function to allow changing of capacity of storage volumes.
> ---
>  include/libvirt/libvirt.h.in     |    5 ++
>  src/driver.h                     |    5 ++
>  src/libvirt.c                    |   50 ++++++++++++++++++
>  src/libvirt_public.syms          |    1 +

This part forms the public API,

>  src/remote/remote_driver.c       |    1 +
>  src/remote/remote_protocol.x     |    9 +++-
>  src/remote_protocol-structs      |    6 ++

This forwards the public API over the wire,

>  src/storage/storage_backend.h    |    6 ++
>  src/storage/storage_backend_fs.c |   18 +++++++
>  src/storage/storage_driver.c     |   83 ++++++++++++++++++++++++++++++
>  src/util/storage_file.c          |  105 ++++++++++++++++++++++++++++++++++++++
>  src/util/storage_file.h          |    4 ++

this implements things,

>  tools/virsh.c                    |   53 +++++++++++++++++++

and this exposes the implementation to the user (which can also be done
at the same time as the public api, if it is simple enough).  And it's
missing tools/virsh.pod for documentation.

I think, in the interest of hitting 0.9.10 freeze deadlines, that we
want to commit to the API, but that your implementation still has some
issues to be worked out.  So, what I'm going to do is split this into
three patches, then just review _and commit_ the first two, while
leaving the implementation for a v2 patch, once you've fixed things to
address Dan's comments (such as, if you're going to resize a qcow2
image, you have to do it with qemu-img and not by raw poking in the
header of the file; as well as implementing a sparse vs. pre-allocation
flag).

The rest of this mail is a review of part 1:

>  13 files changed, 345 insertions(+), 1 deletions(-)
> 
> diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
> index e99cd00..b169592 100644
> --- a/include/libvirt/libvirt.h.in
> +++ b/include/libvirt/libvirt.h.in
> @@ -2386,6 +2386,11 @@ char *                  virStorageVolGetXMLDesc         (virStorageVolPtr pool,
>  
>  char *                  virStorageVolGetPath            (virStorageVolPtr vol);
>  
> +int                     virStorageVolResize             (virStorageVolPtr vol,
> +                                                         unsigned long long capacity,
> +                                                         unsigned int flags);

I think this makes sense.  We normally want to resize capacity; which
may or may not also increase allocation.  We may also want to resize
allocation without affecting capacity (basically, turn a thin disk into
a full one), but when you throw things like qcow2 in the mix (allocation
may be less than capacity when things are sparse, or may be greater than
capacity due to the size of the headers managing the disk), I don't
think you can ever directly target the allocation parameter.  And the
presence of flags will let us add flags if we ever change our minds and
need to add even more features (such as async operation, where we then
send an event when things complete).

> +++ b/src/libvirt.c
> @@ -12927,6 +12927,56 @@ error:
>      return NULL;
>  }
>  
> +/**
> + * virStorageVolResize:
> + * @vol: pointer to storage volume
> + * @capacity: new capacity

In what units?  virDomainBlockResize used kbytes as the unit, but I
think that's not as nice as doing things in raw bytes.

> + * @flags: extra flags; not used yet, so callers should always pass 0

Let's go ahead and define some useful flags now, so that we can give
further details on the semantics.  You don't have to implement them all
right away, but I'd rather have things called out in advance.  For now,
I'm going to document things as having all expansions be all NUL bytes,
although we can always add a flag in the future to allow the resize to
use unspecified bytes if that can make an operation faster.

> + *
> + * Changes the capacity of the storage volume @vol to @capacity. The new
> + * capacity must not exceed the sum of current capacity of the volume and
> + * remainining free space of its parent pool. Also the new capacity must

s/remainining/remaining/

> + * be greater than or equal to current allocation of the volume.

I think this is too strong - downsizing _might_ be reasonable, even
though it is generally a form of data loss and thus must not be default,
so let's control it by a flag.

Allowing a delta might be nice, and that says we should pass a signed
value (shouldn't be an issue - if you can show me anyone with a single
storage volume with 2^63 bytes, let alone 2^64, I'd be impressed with
their setup!).

Hmm, I wonder if we also want to support a percentage growth mode (I
want my disk to be made 20% bigger than it currently is, no matter its
current size); but that seems difficult to do without a floating point
parameter; so at this point, I'm inclined to say that the user must call
virStorageVolGetInfo, do the percentage growth themselves, then call the
API.

> + *
> + * Returns 0 on success, or -1 on error.
> + */
> +int
> +virStorageVolResize(virStorageVolPtr vol,
> +                    unsigned long long capacity,
> +                    unsigned int flags)
> +{
> +    virConnectPtr conn;
> +    VIR_DEBUG("vol=%p", vol);
> +
> +    virResetLastError();
> +
> +    if (!VIR_IS_STORAGE_VOL(vol)) {
> +        virLibStorageVolError(VIR_ERR_INVALID_STORAGE_VOL, __FUNCTION__);
> +        virDispatchError(NULL);
> +        return -1;
> +    }
> +
> +    conn = vol->conn;
> +
> +    if (conn->flags & VIR_CONNECT_RO) {
> +       virLibConnError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);
> +       goto error;
> +    }

Even though I'm proposing making capacity signed, we can do some sanity
checking on it to avoid negative values without a request to shrink.

> +++ b/src/libvirt_public.syms
> @@ -520,6 +520,7 @@ LIBVIRT_0.9.10 {
>      global:
>          virDomainShutdownFlags;
>          virStorageVolWipePattern;
> +	virStorageVolResize;

In addition to Dan's comment about TAB, I also like to sort these lines.

>  } LIBVIRT_0.9.9;
>  
>  # .... define new API here using predicted next version number ....

Skipping to the only other file I'd like in patch 1:

> +++ b/tools/virsh.c
> @@ -11279,6 +11279,58 @@ cmdVolInfo(vshControl *ctl, const vshCmd *cmd)
>      return ret;
>  }
>  
> +/*
> + * "vol-resize" command
> + */
> +static const vshCmdInfo info_vol_resize[] = {
> +    {"help", N_("resize a vol")},
> +    {"desc", N_("Resizes a storage vol.")},

I'll go ahead and add flag support.

> +    {NULL, NULL}
> +};
> +
> +static const vshCmdOptDef opts_vol_resize[] = {
> +    {"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
> +    {"capacity", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new capacity for the vol with optional k,M,G,T suffix")},

Long line.  Disk manufacturers would have us use multiples of 1000
rather than 1024 in parsing numbers; but at least you are consistent
with the rest of virsh.

> +    {"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
> +    {NULL, 0, 0, NULL}
> +};
> +
> +static bool
> +cmdVolResize(vshControl *ctl, const vshCmd *cmd)
> +{
> +    virStorageVolPtr vol;
> +    const char *capacityStr = NULL;
> +    unsigned long long capacity = 0;
> +    bool ret = true;

This should be false, so that...

> +
> +    if (!vshConnectionUsability(ctl, ctl->conn))
> +        return false;
> +
> +    if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", NULL)))
> +        return false;
> +
> +    if (vshCommandOptString(cmd, "capacity", &capacityStr) <= 0)
> +        goto cleanup;
> +    if (cmdVolSize(capacityStr, &capacity) < 0) {
> +        vshError(ctl, _("Malformed size %s"), capacityStr);
> +        goto cleanup;

this error message gives the right return value.

> +        vshError(ctl, "Failed to change size of volume '%s' to %s\n",

Missing translation marks.

Here's the delta I'm proposing; I'll repost this as a formal patch when
I finish reviewing part 2.

diff --git i/include/libvirt/libvirt.h.in w/include/libvirt/libvirt.h.in
index b169592..d26cbbd 100644
--- i/include/libvirt/libvirt.h.in
+++ w/include/libvirt/libvirt.h.in
@@ -2386,8 +2386,14 @@ char *                  virStorageVolGetXMLDesc
       (virStorageVolPtr pool,

 char *                  virStorageVolGetPath
(virStorageVolPtr vol);

+typedef enum {
+  VIR_STORAGE_VOL_RESIZE_ALLOCATE = 1 << 0, /* force allocation of new
size */
+  VIR_STORAGE_VOL_RESIZE_DELTA    = 1 << 1, /* size is relative to
current */
+  VIR_STORAGE_VOL_RESIZE_SHRINK   = 1 << 2, /* allow decrease in
capacity */
+} virStorageVolResizeFlags;
+
 int                     virStorageVolResize
(virStorageVolPtr vol,
-                                                         unsigned long
long capacity,
+                                                         long long
capacity,
                                                          unsigned int
flags);


diff --git i/src/driver.h w/src/driver.h
index c850926..485b578 100644
--- i/src/driver.h
+++ w/src/driver.h
@@ -1263,7 +1263,7 @@ typedef int
                                unsigned int flags);
 typedef int
         (*virDrvStorageVolResize) (virStorageVolPtr vol,
-                                   unsigned long long capacity,
+                                   long long capacity,
                                    unsigned int flags);

 typedef int
diff --git i/src/libvirt.c w/src/libvirt.c
index 44865e8..540d74a 100644
--- i/src/libvirt.c
+++ w/src/libvirt.c
@@ -12930,23 +12930,39 @@ error:
 /**
  * virStorageVolResize:
  * @vol: pointer to storage volume
- * @capacity: new capacity
- * @flags: extra flags; not used yet, so callers should always pass 0
- *
- * Changes the capacity of the storage volume @vol to @capacity. The new
- * capacity must not exceed the sum of current capacity of the volume and
- * remainining free space of its parent pool. Also the new capacity must
- * be greater than or equal to current allocation of the volume.
+ * @capacity: new capacity, in bytes
+ * @flags: bitwise-OR of virStorageVolResizeFlags
+ *
+ * Changes the capacity of the storage volume @vol to @capacity. The
+ * operation will fail if the new capacity requires allocation that would
+ * exceed the remaining free space in the parent pool.  The contents of
+ * the new capacity will appear as all zero bytes.
+ *
+ * Normally, the operation will attempt to affect capacity with a minimum
+ * impact on allocation (that is, the default operation favors a sparse
+ * resize).  If @flags contains VIR_STORAGE_VOL_RESIZE_ALLOCATE, then the
+ * operation will ensure that allocation is sufficient for the new
+ * capacity; this may make the operation take noticeably longer.
+ *
+ * Normally, the operation treats @capacity as the new size in bytes;
+ * but if @flags contains VIR_STORAGE_RESIZE_DELTA, then @capacity
+ * represents the size difference to add to the current size.  It is
+ * up to the storage pool implementation whether unaligned requests are
+ * rounded up to the next valid boundary, or rejected.
+ *
+ * Normally, this operation should only be used to enlarge capacity;
+ * but if @flags contains VIR_STORAGE_RESIZE_SHRINK, it is possible to
+ * attempt a reduction in capacity even though it might cause data loss.
  *
  * Returns 0 on success, or -1 on error.
  */
 int
 virStorageVolResize(virStorageVolPtr vol,
-                    unsigned long long capacity,
+                    long long capacity,
                     unsigned int flags)
 {
     virConnectPtr conn;
-    VIR_DEBUG("vol=%p", vol);
+    VIR_DEBUG("vol=%p capacity=%lld flags=%x", vol, capacity, flags);

     virResetLastError();

@@ -12963,6 +12979,16 @@ virStorageVolResize(virStorageVolPtr vol,
        goto error;
     }

+    /* Negative capacity is valid only with both delta and shrink;
+     * zero capacity is valid with either delta or shrink.  */
+    if ((capacity < 0 && !(flags & VIR_STORAGE_VOL_RESIZE_DELTA) &&
+         !(flags & VIR_STORAGE_VOL_RESIZE_SHRINK)) ||
+        (capacity == 0 && !((flags & VIR_STORAGE_VOL_RESIZE_DELTA) ||
+                            (flags & VIR_STORAGE_VOL_RESIZE_SHRINK)))) {
+        virLibStorageVolError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
     if (conn->storageDriver && conn->storageDriver->volResize) {
         int ret;
         ret = conn->storageDriver->volResize(vol, capacity, flags);
diff --git i/src/libvirt_public.syms w/src/libvirt_public.syms
index 67c113e..8bdb24b 100644
--- i/src/libvirt_public.syms
+++ w/src/libvirt_public.syms
@@ -519,8 +519,8 @@ LIBVIRT_0.9.9 {
 LIBVIRT_0.9.10 {
     global:
         virDomainShutdownFlags;
+        virStorageVolResize;
         virStorageVolWipePattern;
-	virStorageVolResize;
 } LIBVIRT_0.9.9;

 # .... define new API here using predicted next version number ....
diff --git i/tools/virsh.c w/tools/virsh.c
index 7f3a55c..634c0f8 100644
--- i/tools/virsh.c
+++ w/tools/virsh.c
@@ -11284,14 +11284,20 @@ cmdVolInfo(vshControl *ctl, const vshCmd *cmd)
  */
 static const vshCmdInfo info_vol_resize[] = {
     {"help", N_("resize a vol")},
-    {"desc", N_("Resizes a storage vol.")},
+    {"desc", N_("Resizes a storage volume.")},
     {NULL, NULL}
 };

 static const vshCmdOptDef opts_vol_resize[] = {
     {"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")},
-    {"capacity", VSH_OT_DATA, VSH_OFLAG_REQ, N_("new capacity for the
vol with optional k,M,G,T suffix")},
+    {"capacity", VSH_OT_DATA, VSH_OFLAG_REQ,
+     N_("new capacity for the vol with optional k,M,G,T suffix")},
     {"pool", VSH_OT_STRING, 0, N_("pool name or uuid")},
+    {"allocate", VSH_OT_BOOL, 0,
+     N_("allocate the new capacity, rather than leaving it sparse")},
+    {"delta", VSH_OT_BOOL, 0,
+     N_("use capacity as a delta to current size, rather than the new
size")},
+    {"shrink", VSH_OT_BOOL, 0, N_("allow the resize to shrink the
volume")},
     {NULL, 0, 0, NULL}
 };

@@ -11301,7 +11307,18 @@ cmdVolResize(vshControl *ctl, const vshCmd *cmd)
     virStorageVolPtr vol;
     const char *capacityStr = NULL;
     unsigned long long capacity = 0;
-    bool ret = true;
+    unsigned int flags = 0;
+    bool ret = false;
+    bool delta = false;
+
+    if (vshCommandOptBool(cmd, "allocate"))
+        flags |= VIR_STORAGE_VOL_RESIZE_ALLOCATE;
+    if (vshCommandOptBool(cmd, "delta")) {
+        delta = true;
+        flags |= VIR_STORAGE_VOL_RESIZE_DELTA;
+    }
+    if (vshCommandOptBool(cmd, "shrink"))
+        flags |= VIR_STORAGE_VOL_RESIZE_SHRINK;

     if (!vshConnectionUsability(ctl, ctl->conn))
         return false;
@@ -11311,17 +11328,29 @@ cmdVolResize(vshControl *ctl, const vshCmd *cmd)

     if (vshCommandOptString(cmd, "capacity", &capacityStr) <= 0)
         goto cleanup;
-    if (cmdVolSize(capacityStr, &capacity) < 0) {
-        vshError(ctl, _("Malformed size %s"), capacityStr);
-        goto cleanup;
+    if (delta && *capacityStr == '-') {
+        if (cmdVolSize(capacityStr + 1, &capacity) < 0) {
+            vshError(ctl, _("Malformed size %s"), capacityStr);
+            goto cleanup;
+        }
+        capacity = -capacity;
+    } else {
+        if (cmdVolSize(capacityStr, &capacity) < 0) {
+            vshError(ctl, _("Malformed size %s"), capacityStr);
+            goto cleanup;
+        }
     }

-    if (virStorageVolResize(vol, capacity, 0) == 0) {
-        vshPrint(ctl, "Size of volume '%s' successfully changed to %s\n",
+    if (virStorageVolResize(vol, capacity, flags) == 0) {
+        vshPrint(ctl,
+                 delta ? _("Size of volume '%s' successfully changed by
%s\n")
+                 : _("Size of volume '%s' successfully changed to %s\n"),
                  virStorageVolGetName(vol), capacityStr);
         ret = true;
     } else {
-        vshError(ctl, "Failed to change size of volume '%s' to %s\n",
+        vshError(ctl,
+                 delta ? _("Failed to change size of volume '%s' by %s\n")
+                 : _("Failed to change size of volume '%s' to %s\n"),
                  virStorageVolGetName(vol), capacityStr);
         ret = false;
     }
diff --git i/tools/virsh.pod w/tools/virsh.pod
index 8599f66..6622caf 100644
--- i/tools/virsh.pod
+++ w/tools/virsh.pod
@@ -2012,6 +2012,17 @@ I<--pool> I<pool-or-uuid> is the name or UUID of
the storage pool the volume
 is in. I<vol-name-or-path> is the name or path of the volume to return the
 volume key for.

+=item B<vol-resize> [I<--pool> I<pool-or-uuid>] I<vol-name-or-path>
+I<pool-or-uuid> I<capacity> [I<--allocate>] [I<--delta>] [I<--shrink>]
+
+Resize the capacity of the given volume, in bytes.
+I<--pool> I<pool-or-uuid> is the name or UUID of the storage pool the
volume
+is in. I<vol-name-or-key-or-path> is the name or key or path of the volume
+to resize.  The new capacity might be sparse unless I<--allocate> is
+specified.  Normally, I<capacity> is the new size, but if I<--delta>
+is present, then it is added to the existing size.  Attempts to shrink
+the volume will fail unless I<--shrink> is present.
+
 =back

 =head1 SECRET COMMMANDS


-- 
Eric Blake   eblake at redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 620 bytes
Desc: OpenPGP digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20120127/f486aa92/attachment-0001.sig>


More information about the libvir-list mailing list