On Fri, 6 Jan 2012 14:01:32 +0000 Stefan Hajnoczi <stefa...@linux.vnet.ibm.com> wrote:
> Add the block_stream command, which starts copy backing file contents > into the image file. Also add the BLOCK_JOB_COMPLETED QMP event which > is emitted when image streaming completes. Later patches add control > over the background copy speed, cancelation, and querying running > streaming operations. > > Signed-off-by: Stefan Hajnoczi <stefa...@linux.vnet.ibm.com> > --- > QMP/qmp-events.txt | 29 ++++++++++++++++++++++ > blockdev.c | 67 > ++++++++++++++++++++++++++++++++++++++++++++++++++++ > hmp-commands.hx | 13 ++++++++++ > hmp.c | 11 ++++++++ > hmp.h | 1 + > monitor.c | 3 ++ > monitor.h | 1 + > qapi-schema.json | 32 ++++++++++++++++++++++++ > qerror.c | 4 +++ > qerror.h | 3 ++ > qmp-commands.hx | 6 ++++ > trace-events | 4 +++ > 12 files changed, 174 insertions(+), 0 deletions(-) > > diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt > index af586ec..a80e604 100644 > --- a/QMP/qmp-events.txt > +++ b/QMP/qmp-events.txt > @@ -264,3 +264,32 @@ Example: > > Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is > followed respectively by the RESET, SHUTDOWN, or STOP events. > + > + > +BLOCK_JOB_COMPLETED > +------------------- > + > +Emitted when a block job has completed. > + > +Data: > + > +- "type": Job type ("stream" for image streaming, json-string) > +- "device": Device name (json-string) > +- "len": Maximum progress value (json-int) > +- "offset": Current progress value (json-int) > + On success this is equal to len. > + On failure this is less than len. > +- "speed": Rate limit, bytes per second (json-int) > +- "error": Error message (json-string) "error" is optional, so it should be "(json-string, optional)" > + Only present on failure. This field contains a human-readable > + error message. There are no semantics other than that > streaming > + has failed and clients should not try to interpret the error > + string. > + > +Example: > + > +{ "event": "BLOCK_JOB_COMPLETED", > + "data": { "type": "stream", "device": "virtio-disk0", > + "len": 10737418240, "offset": 10737418240, > + "speed": 0 }, > + "timestamp": { "seconds": 1267061043, "microseconds": 959568 } } > diff --git a/blockdev.c b/blockdev.c > index 6d78b36..ba973b0 100644 > --- a/blockdev.c > +++ b/blockdev.c > @@ -13,9 +13,11 @@ > #include "qerror.h" > #include "qemu-option.h" > #include "qemu-config.h" > +#include "qemu-objects.h" > #include "sysemu.h" > #include "block_int.h" > #include "qmp-commands.h" > +#include "trace.h" > > static QTAILQ_HEAD(drivelist, DriveInfo) drives = > QTAILQ_HEAD_INITIALIZER(drives); > > @@ -880,3 +882,68 @@ void qmp_block_resize(const char *device, int64_t size, > Error **errp) > return; > } > } > + > +static QObject *qobject_from_block_job(BlockJob *job) > +{ > + return qobject_from_jsonf("{ 'type': %s," > + "'device': %s," > + "'len': %" PRId64 "," > + "'offset': %" PRId64 "," > + "'speed': %" PRId64 " }", > + job->job_type->job_type, > + bdrv_get_device_name(job->bs), > + job->len, > + job->offset, > + job->speed); > +} > + > +static void block_stream_cb(void *opaque, int ret) > +{ > + BlockDriverState *bs = opaque; > + QObject *obj; > + > + trace_block_stream_cb(bs, bs->job, ret); > + > + assert(bs->job); > + obj = qobject_from_block_job(bs->job); > + if (ret < 0) { > + QDict *dict = qobject_to_qdict(obj); > + qdict_put(dict, "error", qstring_from_str(strerror(-ret))); > + } > + > + monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj); > + qobject_decref(obj); > +} > + > +void qmp_block_stream(const char *device, bool has_base, > + const char *base, Error **errp) > +{ > + BlockDriverState *bs; > + int ret; > + > + bs = bdrv_find(device); > + if (!bs) { > + error_set(errp, QERR_DEVICE_NOT_FOUND, device); > + return; > + } > + > + /* Base device not supported */ > + if (base) { > + error_set(errp, QERR_NOT_SUPPORTED); > + return; > + } > + > + ret = stream_start(bs, NULL, block_stream_cb, bs); > + if (ret < 0) { > + switch (ret) { > + case -EBUSY: > + error_set(errp, QERR_DEVICE_IN_USE, device); > + return; > + default: > + error_set(errp, QERR_NOT_SUPPORTED); > + return; > + } > + } > + > + trace_qmp_block_stream(bs, bs->job); > +} > diff --git a/hmp-commands.hx b/hmp-commands.hx > index 14838b7..8d9dbd6 100644 > --- a/hmp-commands.hx > +++ b/hmp-commands.hx > @@ -69,6 +69,19 @@ but should be used with extreme caution. Note that this > command only > resizes image files, it can not resize block devices like LVM volumes. > ETEXI > > + { > + .name = "block_stream", > + .args_type = "device:B,base:s?", > + .params = "device [base]", > + .help = "copy data from a backing file into a block device", > + .mhandler.cmd = hmp_block_stream, > + }, > + > +STEXI > +@item block_stream > +@findex block_stream > +Copy data from a backing file into a block device. > +ETEXI > > { > .name = "eject", > diff --git a/hmp.c b/hmp.c > index e7659d5..b6e5913 100644 > --- a/hmp.c > +++ b/hmp.c > @@ -679,3 +679,14 @@ void hmp_migrate_set_speed(Monitor *mon, const QDict > *qdict) > int64_t value = qdict_get_int(qdict, "value"); > qmp_migrate_set_speed(value, NULL); > } > + > +void hmp_block_stream(Monitor *mon, const QDict *qdict) > +{ > + Error *error = NULL; > + const char *device = qdict_get_str(qdict, "device"); > + const char *base = qdict_get_try_str(qdict, "base"); > + > + qmp_block_stream(device, base != NULL, base, &error); > + > + hmp_handle_error(mon, &error); > +} > diff --git a/hmp.h b/hmp.h > index 093242d..b55c295 100644 > --- a/hmp.h > +++ b/hmp.h > @@ -49,5 +49,6 @@ void hmp_snapshot_blkdev(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); > +void hmp_block_stream(Monitor *mon, const QDict *qdict); > > #endif > diff --git a/monitor.c b/monitor.c > index 7334401..bb42580 100644 > --- a/monitor.c > +++ b/monitor.c > @@ -479,6 +479,9 @@ void monitor_protocol_event(MonitorEvent event, QObject > *data) > case QEVENT_SPICE_DISCONNECTED: > event_name = "SPICE_DISCONNECTED"; > break; > + case QEVENT_BLOCK_JOB_COMPLETED: > + event_name = "BLOCK_JOB_COMPLETED"; > + break; > default: > abort(); > break; > diff --git a/monitor.h b/monitor.h > index cfa2f67..7324236 100644 > --- a/monitor.h > +++ b/monitor.h > @@ -35,6 +35,7 @@ typedef enum MonitorEvent { > QEVENT_SPICE_CONNECTED, > QEVENT_SPICE_INITIALIZED, > QEVENT_SPICE_DISCONNECTED, > + QEVENT_BLOCK_JOB_COMPLETED, > QEVENT_MAX, > } MonitorEvent; > > diff --git a/qapi-schema.json b/qapi-schema.json > index 44cf764..2b1cc8c 100644 > --- a/qapi-schema.json > +++ b/qapi-schema.json > @@ -1275,3 +1275,35 @@ > { 'command': 'qom-set', > 'data': { 'path': 'str', 'property': 'str', 'value': 'visitor' }, > 'gen': 'no' } > + > +## > +# @block_stream: > +# > +# Copy data from a backing file into a block device. > +# > +# The block streaming operation is performed in the background until the > entire > +# backing file has been copied. This command returns immediately once > streaming > +# has started. The status of ongoing block streaming operations can be > checked > +# with query-block-jobs. The operation can be stopped before it has > completed > +# using the block_job_cancel command. > +# > +# If a base file is specified then sectors are not copied from that base > file and > +# its backing chain. When streaming completes the image file will have the > base > +# file as its backing file. This can be used to stream a subset of the > backing > +# file chain instead of flattening the entire image. > +# > +# On successful completion the image file is updated to drop the backing file > +# and the BLOCK_JOB_COMPLETED event is emitted. > +# > +# @device: the device name > +# > +# @base: the common backing file name @base is optional, so it should be documented like this: @base: #optional the common backing file name > +# > +# Returns: Nothing on success > +# If streaming is already active on this device, DeviceInUse > +# If @device is does not exist, DeviceNotFound > +# If image streaming is not supported by this device, NotSupported > +# > +# Since: 1.1 > +## > +{ 'command': 'block_stream', 'data': { 'device': 'str', '*base': 'str' } } > diff --git a/qerror.c b/qerror.c > index 9a75d06..feb3d35 100644 > --- a/qerror.c > +++ b/qerror.c > @@ -182,6 +182,10 @@ static const QErrorStringTable qerror_table[] = { > .desc = "No '%(bus)' bus found for device '%(device)'", > }, > { > + .error_fmt = QERR_NOT_SUPPORTED, > + .desc = "Not supported", > + }, > + { > .error_fmt = QERR_OPEN_FILE_FAILED, > .desc = "Could not open '%(filename)'", > }, > diff --git a/qerror.h b/qerror.h > index efda232..095ba9d 100644 > --- a/qerror.h > +++ b/qerror.h > @@ -153,6 +153,9 @@ QError *qobject_to_qerror(const QObject *obj); > #define QERR_NO_BUS_FOR_DEVICE \ > "{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }" > > +#define QERR_NOT_SUPPORTED \ > + "{ 'class': 'NotSupported', 'data': {} }" > + > #define QERR_OPEN_FILE_FAILED \ > "{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }" > > diff --git a/qmp-commands.hx b/qmp-commands.hx > index 7e3f4b9..b9ebb76 100644 > --- a/qmp-commands.hx > +++ b/qmp-commands.hx > @@ -655,6 +655,12 @@ Example: > EQMP > > { > + .name = "block_stream", > + .args_type = "device:B,base:s?", > + .mhandler.cmd_new = qmp_marshal_input_block_stream, > + }, > + > + { > .name = "blockdev-snapshot-sync", > .args_type = "device:B,snapshot-file:s,format:s?", > .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync, > diff --git a/trace-events b/trace-events > index c5368fa..6ff0d43 100644 > --- a/trace-events > +++ b/trace-events > @@ -74,6 +74,10 @@ bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int > nb_sectors, int64_t clus > stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int > is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d" > stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p > base %p s %p co %p opaque %p" > > +# blockdev.c > +block_stream_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 > virtio_blk_req_complete(void *req, int status) "req %p status %d" > virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"