diff mbox

[v4,05/10] block: Accept node-name arguments for block-commit

Message ID 5da6a0b4d7cf0177bb5bf8e2e74a49769d90120c.1401886089.git.jcody@redhat.com
State New
Headers show

Commit Message

Jeff Cody June 4, 2014, 1:51 p.m. UTC
This modifies the block operation block-commit so that it will
accept node-name arguments for either 'top' or 'base' BDS.

The filename and node-name are mutually exclusive to each other;
i.e.:
    "top" and "top-node-name" are mutually exclusive (enforced)
    "base" and "base-node-name" are mutually exclusive (enforced)

The "device" argument now becomes optional as well, because with
a node-name we can identify the block device chain.  It is only
optional if a node-name is not specified.

The preferred and recommended method now of using block-commit
is to specify the BDS to operate on via their node-name arguments.

This also adds an explicit check that base_bs is in the chain of
top_bs, because if they are referenced by their unique node-name
arguments, the discovery process does not intrinsically verify
they are in the same chain.

Signed-off-by: Jeff Cody <jcody@redhat.com>
---
 blockdev.c       | 55 +++++++++++++++++++++++++++++++++++++++++++++++--------
 qapi-schema.json | 37 +++++++++++++++++++++++++++----------
 qmp-commands.hx  | 31 +++++++++++++++++++++++--------
 3 files changed, 97 insertions(+), 26 deletions(-)

Comments

Eric Blake June 4, 2014, 9:38 p.m. UTC | #1
On 06/04/2014 07:51 AM, Jeff Cody wrote:
> This modifies the block operation block-commit so that it will
> accept node-name arguments for either 'top' or 'base' BDS.
> 
> The filename and node-name are mutually exclusive to each other;
> i.e.:
>     "top" and "top-node-name" are mutually exclusive (enforced)
>     "base" and "base-node-name" are mutually exclusive (enforced)
> 
> The "device" argument now becomes optional as well, because with
> a node-name we can identify the block device chain.  It is only
> optional if a node-name is not specified.

Stale paragraph; just delete it.  [We may later want to add patches to
make device optional, if we can figure out a solution for what to do
when a BDS has more than one overlay; but that doesn't need to stall
this series]

> 
> The preferred and recommended method now of using block-commit
> is to specify the BDS to operate on via their node-name arguments.
> 
> This also adds an explicit check that base_bs is in the chain of
> top_bs, because if they are referenced by their unique node-name
> arguments, the discovery process does not intrinsically verify
> they are in the same chain.
> 
> Signed-off-by: Jeff Cody <jcody@redhat.com>
> ---
>  blockdev.c       | 55 +++++++++++++++++++++++++++++++++++++++++++++++--------
>  qapi-schema.json | 37 +++++++++++++++++++++++++++----------
>  qmp-commands.hx  | 31 +++++++++++++++++++++++--------
>  3 files changed, 97 insertions(+), 26 deletions(-)
> 

> -    bs = bdrv_find(device);
> -    if (!bs) {
> -        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
> +    /* argument combination validation */
> +    if (has_base && has_base_node_name) {
> +        error_setg(errp, "'base' and 'base-node-name' are mutually exclusive");
> +        return;
> +    }

An alternative would have been to validate that both 'base' and
'base-node-name' resolve to the same BDS, but I like the simplicity of
erroring out here.

> +
> +    if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) {
> +        return;
> +    }

[unrelated to this commit - are we sure that the semantics of the commit
blocker are correct? If we implement BDS reuse among multiple chains,
consider what happens if I have:
       / sn1 <- sn2
base <
       \ file3

Even if there is no other block job operating on sn2 or its chain, we
really want to block an attempt to merge file3 into base, as modifying
base would corrupt both sn1 and sn2.  That is, a BDS being a common
backing between more than one chain should automatically behave as an
op-blocker for commits - where the block is what you are committing
into, not where you are committing from.]

Reviewed-by: Eric Blake <eblake@redhat.com>
Jeff Cody June 5, 2014, 12:57 a.m. UTC | #2
On Wed, Jun 04, 2014 at 03:38:55PM -0600, Eric Blake wrote:
> On 06/04/2014 07:51 AM, Jeff Cody wrote:
> > This modifies the block operation block-commit so that it will
> > accept node-name arguments for either 'top' or 'base' BDS.
> > 
> > The filename and node-name are mutually exclusive to each other;
> > i.e.:
> >     "top" and "top-node-name" are mutually exclusive (enforced)
> >     "base" and "base-node-name" are mutually exclusive (enforced)
> > 
> > The "device" argument now becomes optional as well, because with
> > a node-name we can identify the block device chain.  It is only
> > optional if a node-name is not specified.
> 
> Stale paragraph; just delete it.  [We may later want to add patches to
> make device optional, if we can figure out a solution for what to do
> when a BDS has more than one overlay; but that doesn't need to stall
> this series]

Argh, yes.  Forgot to update the commit message here, thanks.  If a v5
is needed, I'll delete it, otherwise perhaps a kind maintainer could
fix it up for me before applying :)

> 
> > 
> > The preferred and recommended method now of using block-commit
> > is to specify the BDS to operate on via their node-name arguments.
> > 
> > This also adds an explicit check that base_bs is in the chain of
> > top_bs, because if they are referenced by their unique node-name
> > arguments, the discovery process does not intrinsically verify
> > they are in the same chain.
> > 
> > Signed-off-by: Jeff Cody <jcody@redhat.com>
> > ---
> >  blockdev.c       | 55 +++++++++++++++++++++++++++++++++++++++++++++++--------
> >  qapi-schema.json | 37 +++++++++++++++++++++++++++----------
> >  qmp-commands.hx  | 31 +++++++++++++++++++++++--------
> >  3 files changed, 97 insertions(+), 26 deletions(-)
> > 
> 
> > -    bs = bdrv_find(device);
> > -    if (!bs) {
> > -        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
> > +    /* argument combination validation */
> > +    if (has_base && has_base_node_name) {
> > +        error_setg(errp, "'base' and 'base-node-name' are mutually exclusive");
> > +        return;
> > +    }
> 
> An alternative would have been to validate that both 'base' and
> 'base-node-name' resolve to the same BDS, but I like the simplicity of
> erroring out here.
> 
> > +
> > +    if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) {
> > +        return;
> > +    }
> 
> [unrelated to this commit - are we sure that the semantics of the commit
> blocker are correct? If we implement BDS reuse among multiple chains,
> consider what happens if I have:
>        / sn1 <- sn2
> base <
>        \ file3
> 
> Even if there is no other block job operating on sn2 or its chain, we
> really want to block an attempt to merge file3 into base, as modifying
> base would corrupt both sn1 and sn2.  That is, a BDS being a common
> backing between more than one chain should automatically behave as an
> op-blocker for commits - where the block is what you are committing
> into, not where you are committing from.]


No, the commit blocker likely won't catch everything.  But that is
because there are additional enhancements the blocker code needs (it
is ultimately intended to block on an individual BDS, but in reality
currently operates on the active layer BDS for the whole chain).

For every block job, the appropriate blockers need to be set on each
BDS affected by the job (intermediate images, overlay images in some
cases, the target, etc..).

It currently functions like the older in_use flag (blocks a chain as
referenced by the active layer), but the groundwork is in place for
truly independent BDS blockers to come later.

(For more discussion, see the qemu-devel list replies to:
[PATCH v20 04/15] block: Move op_blocker check from block_job_create to its caller)

> 
> Reviewed-by: Eric Blake <eblake@redhat.com>
>

Thanks!
diff mbox

Patch

diff --git a/blockdev.c b/blockdev.c
index 58b526a..50d4890 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1915,12 +1915,15 @@  void qmp_block_stream(const char *device, bool has_base,
 
 void qmp_block_commit(const char *device,
                       bool has_base, const char *base,
+                      bool has_base_node_name, const char *base_node_name,
                       bool has_top, const char *top,
+                      bool has_top_node_name, const char *top_node_name,
                       bool has_speed, int64_t speed,
                       Error **errp)
 {
-    BlockDriverState *bs;
-    BlockDriverState *base_bs, *top_bs;
+    BlockDriverState *bs = NULL;
+    BlockDriverState *base_bs = NULL;
+    BlockDriverState *top_bs = NULL;
     Error *local_err = NULL;
     /* This will be part of the QMP command, if/when the
      * BlockdevOnError change for blkmirror makes it in
@@ -1934,20 +1937,33 @@  void qmp_block_commit(const char *device,
     /* drain all i/o before commits */
     bdrv_drain_all();
 
-    bs = bdrv_find(device);
-    if (!bs) {
-        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+    /* argument combination validation */
+    if (has_base && has_base_node_name) {
+        error_setg(errp, "'base' and 'base-node-name' are mutually exclusive");
+        return;
+    }
+    if (has_top && has_top_node_name) {
+        error_setg(errp, "'top' and 'top-node-name' are mutually exclusive");
         return;
     }
 
-    if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) {
+    /* device lookups */
+    bs = bdrv_find(device);
+    if (!bs) {
+        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
         return;
     }
 
     /* default top_bs is the active layer */
     top_bs = bs;
 
-    if (has_top && top) {
+    if (has_top_node_name) {
+        top_bs = bdrv_lookup_bs(NULL, top_node_name, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            return;
+        }
+    } else if (has_top && top) {
         if (strcmp(bs->filename, top) != 0) {
             top_bs = bdrv_find_backing_image(bs, top);
         }
@@ -1958,7 +1974,13 @@  void qmp_block_commit(const char *device,
         return;
     }
 
-    if (has_base && base) {
+    if (has_base_node_name) {
+        base_bs = bdrv_lookup_bs(NULL, base_node_name, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            return;
+        }
+    } else if (has_base && base) {
         base_bs = bdrv_find_backing_image(top_bs, base);
     } else {
         base_bs = bdrv_find_base(top_bs);
@@ -1969,6 +1991,23 @@  void qmp_block_commit(const char *device,
         return;
     }
 
+    /* Verify that 'base' is in the same chain as 'top' */
+    if (!bdrv_chain_contains(top_bs, base_bs)) {
+        error_setg(errp, "'base' and 'top' are not in the same chain");
+        return;
+    }
+
+    /* This should technically be caught in commit_start, but
+     * check here for validation completeness */
+    if (!bdrv_chain_contains(bs, top_bs)) {
+        error_setg(errp, "'%s' and 'top' are not in the same chain", device);
+        return;
+    }
+
+    if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) {
+        return;
+    }
+
     if (top_bs == bs) {
         commit_active_start(bs, base_bs, speed, on_error, block_job_cb,
                             bs, &local_err);
diff --git a/qapi-schema.json b/qapi-schema.json
index 97cf053..67a627f 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2097,14 +2097,29 @@ 
 # Live commit of data from overlay image nodes into backing nodes - i.e.,
 # writes data between 'top' and 'base' into 'base'.
 #
-# @device:  the name of the device
+# @device:                 The name of the device.
 #
-# @base:   #optional The file name of the backing image to write data into.
-#                    If not specified, this is the deepest backing image
+# For 'base', either @base or @base-node-name may be set but not both. If
+# neither is specified, this is the deepest backing image.
 #
-# @top:    #optional The file name of the backing image within the image chain,
-#                    which contains the topmost data to be committed down. If
-#                    not specified, this is the active layer.
+# @base:          #optional The file name of the backing image to write data
+#                           into.
+#
+# @base-node-name #optional The name of the block driver state node of the
+#                           backing image to write data into.
+#                           (Since 2.1)
+#
+# For 'top', either @top or @top-node-name must be set but not both. If
+# neither is specified, this is the active layer.
+#
+# @top:           #optional The file name of the backing image within the image
+#                           chain, which contains the topmost data to be
+#                           committed down.
+#
+# @top-node-name: #optional The block driver state node name of the backing
+#                           image within the image chain, which contains the
+#                           topmost data to be committed down.
+#                           (Since 2.1)
 #
 #                    If top == base, that is an error.
 #                    If top == active, the job will not be completed by itself,
@@ -2123,17 +2138,19 @@ 
 #
 # Returns: Nothing on success
 #          If commit or stream is already active on this device, DeviceInUse
-#          If @device does not exist, DeviceNotFound
+#          If @device does not exist or cannot be determined, DeviceNotFound
 #          If image commit is not supported by this device, NotSupported
-#          If @base or @top is invalid, a generic error is returned
+#          If @base, @top, @base-node-name, @top-node-name invalid, GenericError
 #          If @speed is invalid, InvalidParameter
+#          If both @base and @base-node-name are specified, GenericError
+#          If both @top and @top-node-name are specified, GenericError
 #
 # Since: 1.3
 #
 ##
 { 'command': 'block-commit',
-  'data': { 'device': 'str', '*base': 'str', '*top': 'str',
-            '*speed': 'int' } }
+  'data': { 'device': 'str', '*base': 'str', '*base-node-name': 'str',
+            '*top': 'str', '*top-node-name': 'str', '*speed': 'int' } }
 
 ##
 # @drive-backup
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 6b67d2f..c61f4cb 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -985,7 +985,7 @@  EQMP
 
     {
         .name       = "block-commit",
-        .args_type  = "device:B,base:s?,top:s?,speed:o?",
+        .args_type  = "device:B,base:s?,base-node-name:s?,top:s?,top-node-name:s?,speed:o?",
         .mhandler.cmd_new = qmp_marshal_input_block_commit,
     },
 
@@ -998,13 +998,28 @@  data between 'top' and 'base' into 'base'.
 
 Arguments:
 
-- "device": The device's ID, must be unique (json-string)
-- "base": The file name of the backing image to write data into.
-          If not specified, this is the deepest backing image
-          (json-string, optional)
-- "top":  The file name of the backing image within the image chain,
-          which contains the topmost data to be committed down. If
-          not specified, this is the active layer. (json-string, optional)
+- "device":         The device's ID, must be unique (json-string)
+
+For 'base', either base or base-node-name may be set but not both. If
+neither is specified, this is the deepest backing image
+
+- "base":           The file name of the backing image to write data into.
+                    (json-string, optional)
+- "base-node-name": The name of the block driver state node of the
+                    backing image to write data into.
+                    (json-string, optional) (Since 2.1)
+
+For 'top', either top or top-node-name must be set but not both. If
+neither is specified, this is the active layer
+
+- "top":            The file name of the backing image within the image chain,
+                    which contains the topmost data to be committed down.
+                    (json-string, optional)
+
+- "top-node-name":  The block driver state node name of the backing
+                    image within the image chain, which contains the
+                    topmost data to be committed down.
+                    (json-string, optional) (Since 2.1)
 
           If top == base, that is an error.
           If top == active, the job will not be completed by itself,