Patchwork [RFC,23/36] qmp: add drive-mirror command

login
register
mail settings
Submitter Paolo Bonzini
Date June 15, 2012, 3:05 p.m.
Message ID <1339772759-31004-24-git-send-email-pbonzini@redhat.com>
Download mbox | patch
Permalink /patch/165192/
State New
Headers show

Comments

Paolo Bonzini - June 15, 2012, 3:05 p.m.
This adds the monitor commands that start the mirroring job.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 hmp-commands.hx  |   21 ++++++++++
 hmp.c            |   28 +++++++++++++
 hmp.h            |    1 +
 qapi-schema.json |   33 +++++++++++++++
 qmp-commands.hx  |   41 +++++++++++++++++++
 trace-events     |    2 +-
 7 files changed, 242 insertions(+), 4 deletions(-)
Eric Blake - June 15, 2012, 8:12 p.m.
On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
> This adds the monitor commands that start the mirroring job.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  hmp-commands.hx  |   21 ++++++++++
>  hmp.c            |   28 +++++++++++++
>  hmp.h            |    1 +
>  qapi-schema.json |   33 +++++++++++++++
>  qmp-commands.hx  |   41 +++++++++++++++++++
>  trace-events     |    2 +-
>  7 files changed, 242 insertions(+), 4 deletions(-)
> 

>  ##
> +# @drive-mirror
> +#
> +# Start mirroring a block device's writes to a new destination.
> +#
> +# @device:  the name of the device whose writes should be mirrored.
> +#
> +# @target: the target of the new image. If the file exists, or if it
> +#          is a device, the existing file/device will be used as the new
> +#          destination.  If it does not exist, a new file will be created.
> +#
> +# @format: #optional the format of the new destination, default is the
> +#          format of the source
> +#
> +# @mode: #optional whether and how QEMU should create a new image, default is
> +# 'absolute-paths'.

Indentation.

> +#
> +# @sync: what parts of the disk image should be copied to the destination
> +#        (all the disk, only the sectors allocated in the topmost image, or
> +#        only new I/O).

Document @peed:.

> +#
> +# Returns: nothing on success
> +#          If @device is not a valid block device, DeviceNotFound
> +#          If @target can't be opened, OpenFileFailed
> +#          If @format is invalid, InvalidBlockFormat
> +#
> +# Since 1.1

1.2

> +##
> +{ 'command': 'drive-mirror',
> +  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
> +            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
> +            '*speed': 'int' } }
> +
> +##
>  # @migrate_cancel
>  #
>  # Cancel the current executing migration process.
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index e2d77b6..cccea2f 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -832,6 +832,47 @@ Example:
>  EQMP
>  
>      {
> +        .name       = "drive-mirror",
> +        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",

sync: twice? and no speed: or mode:?


> +SQMP
> +drive-mirror
> +------------
> +
> +Start mirroring a block device's writes to a new destination. target
> +specifies the target of the new image. If the file exists, or if it is
> +a device, it will be used as the new destination for writes. If does not
> +exist, a new file will be created. format specifies the format of the
> +mirror image, default is to probe if mode='existing', else qcow2.

default is qcow2?  Earlier in this patch you said the same format as the
source.
Paolo Bonzini - July 11, 2012, 4:23 p.m.
Il 15/06/2012 22:12, Eric Blake ha scritto:
> On 06/15/2012 09:05 AM, Paolo Bonzini wrote:
>> > This adds the monitor commands that start the mirroring job.
>> > 
>> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> > ---
>> >  blockdev.c       |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>> >  hmp-commands.hx  |   21 ++++++++++
>> >  hmp.c            |   28 +++++++++++++
>> >  hmp.h            |    1 +
>> >  qapi-schema.json |   33 +++++++++++++++
>> >  qmp-commands.hx  |   41 +++++++++++++++++++
>> >  trace-events     |    2 +-
>> >  7 files changed, 242 insertions(+), 4 deletions(-)
>> > 
>> >  ##
>> > +# @drive-mirror
>> > +#
>> > +# Start mirroring a block device's writes to a new destination.
>> > +#
>> > +# @device:  the name of the device whose writes should be mirrored.
>> > +#
>> > +# @target: the target of the new image. If the file exists, or if it
>> > +#          is a device, the existing file/device will be used as the new
>> > +#          destination.  If it does not exist, a new file will be created.
>> > +#
>> > +# @format: #optional the format of the new destination, default is the
>> > +#          format of the source
>> > +#
>> > +# @mode: #optional whether and how QEMU should create a new image, default is
>> > +# 'absolute-paths'.
> Indentation.
> 
>> > +#
>> > +# @sync: what parts of the disk image should be copied to the destination
>> > +#        (all the disk, only the sectors allocated in the topmost image, or
>> > +#        only new I/O).
> Document @peed:.
> 
>> > +#
>> > +# Returns: nothing on success
>> > +#          If @device is not a valid block device, DeviceNotFound
>> > +#          If @target can't be opened, OpenFileFailed
>> > +#          If @format is invalid, InvalidBlockFormat
>> > +#
>> > +# Since 1.1
> 1.2
> 
>> > +##
>> > +{ 'command': 'drive-mirror',
>> > +  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> > +            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> > +            '*speed': 'int' } }
>> > +
>> > +##
>> >  # @migrate_cancel
>> >  #
>> >  # Cancel the current executing migration process.
>> > diff --git a/qmp-commands.hx b/qmp-commands.hx
>> > index e2d77b6..cccea2f 100644
>> > --- a/qmp-commands.hx
>> > +++ b/qmp-commands.hx
>> > @@ -832,6 +832,47 @@ Example:
>> >  EQMP
>> >  
>> >      {
>> > +        .name       = "drive-mirror",
>> > +        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",
> sync: twice? and no speed: or mode:?
> 
> 
>> > +SQMP
>> > +drive-mirror
>> > +------------
>> > +
>> > +Start mirroring a block device's writes to a new destination. target
>> > +specifies the target of the new image. If the file exists, or if it is
>> > +a device, it will be used as the new destination for writes. If does not
>> > +exist, a new file will be created. format specifies the format of the
>> > +mirror image, default is to probe if mode='existing', else qcow2.
> default is qcow2?  Earlier in this patch you said the same format as the
> source.

Indeed, thanks for pointing out all the inconsistencies.

Paolo

Patch

diff --git a/blockdev.c b/blockdev.c
index adb21b9..29ecadc 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -21,6 +21,8 @@ 
 #include "trace.h"
 #include "arch_init.h"
 
+static void block_job_cb(void *opaque, int ret);
+
 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
 
 static const char *const if_name[IF_COUNT] = {
@@ -829,6 +831,118 @@  exit:
     return;
 }
 
+void qmp_drive_mirror(const char *device, const char *target,
+                      bool has_format, const char *format,
+                      enum MirrorSyncMode sync,
+                      bool has_mode, enum NewImageMode mode,
+                      bool has_speed, int64_t speed, Error **errp)
+{
+    BlockDriverState *bs;
+    BlockDriverState *source, *target_bs;
+    BlockDriver *proto_drv;
+    BlockDriver *drv = NULL;
+    Error *local_err = NULL;
+    int flags;
+    uint64_t size;
+    int ret;
+
+    if (!has_mode) {
+        mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+    }
+
+    bs = bdrv_find(device);
+    if (!bs) {
+        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+        return;
+    }
+
+    if (!has_format) {
+        format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name;
+    }
+    if (format) {
+        drv = bdrv_find_format(format);
+        if (!drv) {
+            error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+            return;
+        }
+    }
+
+    if (!bdrv_is_inserted(bs)) {
+        error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
+        return;
+    }
+
+    if (bdrv_in_use(bs)) {
+        error_set(errp, QERR_DEVICE_IN_USE, device);
+        return;
+    }
+
+    flags = bs->open_flags | BDRV_O_RDWR;
+    source = bs->backing_hd;
+    if (!source && sync == MIRROR_SYNC_MODE_TOP) {
+        sync = MIRROR_SYNC_MODE_FULL;
+    }
+
+    proto_drv = bdrv_find_protocol(target);
+    if (!proto_drv) {
+        error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+        return;
+    }
+
+    if (sync == MIRROR_SYNC_MODE_FULL && mode != NEW_IMAGE_MODE_EXISTING) {
+        /* create new image w/o backing file */
+        assert(format && drv);
+        bdrv_get_geometry(bs, &size);
+        size *= 512;
+        ret = bdrv_img_create(target, format,
+                              NULL, NULL, NULL, size, flags);
+    } else {
+        switch (mode) {
+        case NEW_IMAGE_MODE_EXISTING:
+            ret = 0;
+            break;
+        case NEW_IMAGE_MODE_ABSOLUTE_PATHS:
+            /* create new image with backing file */
+            ret = bdrv_img_create(target, format,
+                                  source->filename,
+                                  source->drv->format_name,
+                                  NULL, -1, flags);
+            break;
+        default:
+            abort();
+        }
+    }
+
+    if (ret) {
+        error_set(errp, QERR_OPEN_FILE_FAILED, target);
+        return;
+    }
+
+    /* ### TODO check for cluster size vs. dirty bitmap granularity */
+
+    target_bs = bdrv_new("");
+    ret = bdrv_open(target_bs, target, flags | BDRV_O_NO_BACKING, drv);
+
+    if (ret < 0) {
+        bdrv_delete(target_bs);
+        error_set(errp, QERR_OPEN_FILE_FAILED, target);
+        return;
+    }
+
+    mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err);
+    if (local_err != NULL) {
+        bdrv_delete(target_bs);
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    /* Grab a reference so hotplug does not delete the BlockDriverState from
+     * underneath us.
+     */
+    drive_get_ref(drive_get_by_blockdev(bs));
+}
+
+
 
 static void eject_device(BlockDriverState *bs, int force, Error **errp)
 {
@@ -1067,12 +1181,12 @@  static QObject *qobject_from_block_job(BlockJob *job)
                               job->speed);
 }
 
-static void block_stream_cb(void *opaque, int ret)
+static void block_job_cb(void *opaque, int ret)
 {
     BlockDriverState *bs = opaque;
     QObject *obj;
 
-    trace_block_stream_cb(bs, bs->job, ret);
+    trace_block_job_cb(bs, bs->job, ret);
 
     assert(bs->job);
     obj = qobject_from_block_job(bs->job);
@@ -1119,7 +1233,7 @@  void qmp_block_stream(const char *device, bool has_base,
     }
 
     stream_start(bs, base_bs, base, has_speed ? speed : 0,
-                 on_error, block_stream_cb, bs, &local_err);
+                 on_error, block_job_cb, bs, &local_err);
     if (error_is_set(&local_err)) {
         error_propagate(errp, local_err);
         return;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 836564c..7ee8de8 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -958,6 +958,27 @@  Snapshot device, using snapshot file as target if provided
 ETEXI
 
     {
+        .name       = "drive_mirror",
+        .args_type  = "reuse:-n,full:-f,device:B,target:s,format:s?",
+        .params     = "[-n] [-f] device target [format]",
+        .help       = "initiates live storage\n\t\t\t"
+                      "migration for a device. The device's contents are\n\t\t\t"
+                      "copied to the new image file, including data that\n\t\t\t"
+                      "is written after the command is started.\n\t\t\t"
+                      "The -n flag requests QEMU to reuse the image found\n\t\t\t"
+                      "in new-image-file, instead of recreating it from scratch.\n\t\t\t"
+                      "The -f flag requests QEMU to copy the whole disk,\n\t\t\t"
+                      "so that the result does not need a backing file.\n\t\t\t",
+        .mhandler.cmd = hmp_drive_mirror,
+    },
+STEXI
+@item drive_mirror
+@findex drive_mirror
+Start mirroring a block device's writes to a new destination,
+using the specified target.
+ETEXI
+
+    {
         .name       = "drive_add",
         .args_type  = "pci_addr:s,opts:s",
         .params     = "[[<domain>:]<bus>:]<slot>\n"
diff --git a/hmp.c b/hmp.c
index b91fd32..2dfc477 100644
--- a/hmp.c
+++ b/hmp.c
@@ -693,6 +693,34 @@  void hmp_block_resize(Monitor *mon, const QDict *qdict)
     hmp_handle_error(mon, &errp);
 }
 
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
+{
+    const char *device = qdict_get_str(qdict, "device");
+    const char *filename = qdict_get_str(qdict, "target");
+    const char *format = qdict_get_try_str(qdict, "format");
+    int reuse = qdict_get_try_bool(qdict, "reuse", 0);
+    int full = qdict_get_try_bool(qdict, "full", 0);
+    enum NewImageMode mode;
+    Error *errp = NULL;
+
+    if (!filename) {
+        error_set(&errp, QERR_MISSING_PARAMETER, "target");
+        hmp_handle_error(mon, &errp);
+        return;
+    }
+
+    if (reuse) {
+        mode = NEW_IMAGE_MODE_EXISTING;
+    } else {
+        mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+    }
+
+    qmp_drive_mirror(device, filename, !!format, format,
+                     full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
+                     true, mode, false, 0, &errp);
+    hmp_handle_error(mon, &errp);
+}
+
 void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
 {
     const char *device = qdict_get_str(qdict, "device");
diff --git a/hmp.h b/hmp.h
index 6a16eec..4e86083 100644
--- a/hmp.h
+++ b/hmp.h
@@ -48,6 +48,7 @@  void hmp_block_passwd(Monitor *mon, const QDict *qdict);
 void hmp_balloon(Monitor *mon, const QDict *qdict);
 void hmp_block_resize(Monitor *mon, const QDict *qdict);
 void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict);
 void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 0d54c56..7b619e9 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1359,6 +1359,39 @@ 
   'returns': 'str' } 
 
 ##
+# @drive-mirror
+#
+# Start mirroring a block device's writes to a new destination.
+#
+# @device:  the name of the device whose writes should be mirrored.
+#
+# @target: the target of the new image. If the file exists, or if it
+#          is a device, the existing file/device will be used as the new
+#          destination.  If it does not exist, a new file will be created.
+#
+# @format: #optional the format of the new destination, default is the
+#          format of the source
+#
+# @mode: #optional whether and how QEMU should create a new image, default is
+# 'absolute-paths'.
+#
+# @sync: what parts of the disk image should be copied to the destination
+#        (all the disk, only the sectors allocated in the topmost image, or
+#        only new I/O).
+#
+# Returns: nothing on success
+#          If @device is not a valid block device, DeviceNotFound
+#          If @target can't be opened, OpenFileFailed
+#          If @format is invalid, InvalidBlockFormat
+#
+# Since 1.1
+##
+{ 'command': 'drive-mirror',
+  'data': { 'device': 'str', 'target': 'str', '*format': 'str',
+            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
+            '*speed': 'int' } }
+
+##
 # @migrate_cancel
 #
 # Cancel the current executing migration process.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e2d77b6..cccea2f 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -832,6 +832,47 @@  Example:
 EQMP
 
     {
+        .name       = "drive-mirror",
+        .args_type  = "sync:s,device:B,target:s,sync:s?,format:s?",
+        .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
+    },
+
+SQMP
+drive-mirror
+------------
+
+Start mirroring a block device's writes to a new destination. target
+specifies the target of the new image. If the file exists, or if it is
+a device, it will be used as the new destination for writes. If does not
+exist, a new file will be created. format specifies the format of the
+mirror image, default is to probe if mode='existing', else qcow2.
+
+Arguments:
+
+- "device": device name to operate on (json-string)
+- "target": name of new image file (json-string)
+- "format": format of new image (json-string, optional)
+- "mode": how an image file should be created into the target
+  file/device (NewImageMode, optional, default 'absolute-paths')
+- "speed": maximum speed of the streaming job, in bytes per second
+  (json-int)
+- "sync": what parts of the disk image should be copied to the destination;
+  possibilities include "full" for all the disk, "top" for only the sectors
+  allocated in the topmost image, or "none" to only replicate new I/O
+  (MirrorSyncMode).
+
+
+Example:
+
+-> { "execute": "drive-mirror", "arguments": { "device": "ide-hd0",
+                                               "target": "/some/place/my-image",
+                                               "sync": "full",
+                                               "format": "qcow2" } }
+<- { "return": {} }
+
+EQMP
+
+    {
         .name       = "balloon",
         .args_type  = "value:M",
         .mhandler.cmd_new = qmp_marshal_input_balloon,
diff --git a/trace-events b/trace-events
index 2124105..306faf6 100644
--- a/trace-events
+++ b/trace-events
@@ -83,7 +83,7 @@  mirror_one_iteration(void *s, int64_t sector_num) "s %p sector_num %"PRId64
 qmp_block_job_cancel(void *job) "job %p"
 qmp_block_job_pause(void *job) "job %p"
 qmp_block_job_resume(void *job) "job %p"
-block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
+block_job_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
 qmp_block_stream(void *bs, void *job) "bs %p job %p"
 
 # hw/virtio-blk.c