qmp: add block_stream command

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 <stefanha@linux.vnet.ibm.com>
Acked-by: Luiz Capitulino <lcapitulino@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2012-01-18 14:40:46 +00:00 committed by Kevin Wolf
parent 5094a6c016
commit 12bd451fe0
12 changed files with 173 additions and 0 deletions

View file

@ -264,3 +264,32 @@ Example:
Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
followed respectively by the RESET, SHUTDOWN, or STOP events. 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, 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 } }

View file

@ -13,9 +13,11 @@
#include "qerror.h" #include "qerror.h"
#include "qemu-option.h" #include "qemu-option.h"
#include "qemu-config.h" #include "qemu-config.h"
#include "qemu-objects.h"
#include "sysemu.h" #include "sysemu.h"
#include "block_int.h" #include "block_int.h"
#include "qmp-commands.h" #include "qmp-commands.h"
#include "trace.h"
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
@ -897,3 +899,68 @@ void qmp_block_resize(const char *device, int64_t size, Error **errp)
break; break;
} }
} }
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);
}

View file

@ -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. resizes image files, it can not resize block devices like LVM volumes.
ETEXI 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", .name = "eject",

11
hmp.c
View file

@ -783,3 +783,14 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
qdict_get_int(qdict, "iops_wr"), &err); qdict_get_int(qdict, "iops_wr"), &err);
hmp_handle_error(mon, &err); hmp_handle_error(mon, &err);
} }
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);
}

1
hmp.h
View file

@ -54,5 +54,6 @@ void hmp_expire_password(Monitor *mon, const QDict *qdict);
void hmp_eject(Monitor *mon, const QDict *qdict); void hmp_eject(Monitor *mon, const QDict *qdict);
void hmp_change(Monitor *mon, const QDict *qdict); void hmp_change(Monitor *mon, const QDict *qdict);
void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
void hmp_block_stream(Monitor *mon, const QDict *qdict);
#endif #endif

View file

@ -479,6 +479,9 @@ void monitor_protocol_event(MonitorEvent event, QObject *data)
case QEVENT_SPICE_DISCONNECTED: case QEVENT_SPICE_DISCONNECTED:
event_name = "SPICE_DISCONNECTED"; event_name = "SPICE_DISCONNECTED";
break; break;
case QEVENT_BLOCK_JOB_COMPLETED:
event_name = "BLOCK_JOB_COMPLETED";
break;
default: default:
abort(); abort();
break; break;

View file

@ -36,6 +36,7 @@ typedef enum MonitorEvent {
QEVENT_SPICE_CONNECTED, QEVENT_SPICE_CONNECTED,
QEVENT_SPICE_INITIALIZED, QEVENT_SPICE_INITIALIZED,
QEVENT_SPICE_DISCONNECTED, QEVENT_SPICE_DISCONNECTED,
QEVENT_BLOCK_JOB_COMPLETED,
QEVENT_MAX, QEVENT_MAX,
} MonitorEvent; } MonitorEvent;

View file

@ -1434,3 +1434,34 @@
{ 'command': 'block_set_io_throttle', { 'command': 'block_set_io_throttle',
'data': { 'device': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', 'data': { 'device': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } } 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } }
# @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: #optional the common backing file name
#
# Returns: Nothing on success
# If streaming is already active on this device, DeviceInUse
# If @device 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' } }

View file

@ -196,6 +196,10 @@ static const QErrorStringTable qerror_table[] = {
.error_fmt = QERR_NO_BUS_FOR_DEVICE, .error_fmt = QERR_NO_BUS_FOR_DEVICE,
.desc = "No '%(bus)' bus found for device '%(device)'", .desc = "No '%(bus)' bus found for device '%(device)'",
}, },
{
.error_fmt = QERR_NOT_SUPPORTED,
.desc = "Not supported",
},
{ {
.error_fmt = QERR_OPEN_FILE_FAILED, .error_fmt = QERR_OPEN_FILE_FAILED,
.desc = "Could not open '%(filename)'", .desc = "Could not open '%(filename)'",

View file

@ -168,6 +168,9 @@ QError *qobject_to_qerror(const QObject *obj);
#define QERR_NO_BUS_FOR_DEVICE \ #define QERR_NO_BUS_FOR_DEVICE \
"{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }" "{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }"
#define QERR_NOT_SUPPORTED \
"{ 'class': 'NotSupported', 'data': {} }"
#define QERR_OPEN_FILE_FAILED \ #define QERR_OPEN_FILE_FAILED \
"{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }" "{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }"

View file

@ -648,6 +648,12 @@ Example:
EQMP EQMP
{
.name = "block_stream",
.args_type = "device:B,base:s?",
.mhandler.cmd_new = qmp_marshal_input_block_stream,
},
{ {
.name = "blockdev-snapshot-sync", .name = "blockdev-snapshot-sync",
.args_type = "device:B,snapshot-file:s,format:s?", .args_type = "device:B,snapshot-file:s,format:s?",

View file

@ -74,6 +74,10 @@ bdrv_co_do_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t c
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_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" 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 # hw/virtio-blk.c
virtio_blk_req_complete(void *req, int status) "req %p status %d" virtio_blk_req_complete(void *req, int status) "req %p status %d"
virtio_blk_rw_complete(void *req, int ret) "req %p ret %d" virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"