mirror of https://github.com/xqemu/xqemu.git
Block layer patches
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJWyzBdAAoJEH8JsnLIjy/WHCQP/RHythkdZ+4dxj476Nzi/NC+ Bvn/OrflSnBzilKcXy9wKJQzAVrXLEGcAh2Tb4Pq3TPp349ZhDUUfrP/V3R2x0dt RDydBj3G/OFQtVG/vo5mFJFdVdBB2jkju5oqT0AghGxICKOjvSvsN549ROm6y0xG MufvHRyfSHs7ZjDHfME0pxDqnA6DCttaD7LJEAswOAo1zsapNIWps6P0ooEiqHx5 8cm8yTAFGC9XV6btGjnenP2DRWNrOqaCPKqYozqV9SgDg+8vxrnoQU+Nta28+XsY wnBUiAhJFC4lwAxBq1mNv8DffjVSzNGWBIpWhriUf5KTAM30Lzgbk0nT7sG7iAMl 7B6sX/P1OvnUD2vv921AVKcVbExF1IokXkih0vP9l02UohTUuACMRCF9bqngCNnR vIkrHQEEt7U087IIyhfE1e7JnGlF7e1zxgG9bnbcONaBeSC4pOcdbPgCGJ5P6Syz ySSaLT5mo3TfbmOUIW8iqA/2tDukueO5C/KaVPJVDBJVzbc5jP0TEMwelQKFbGWU Z8Iv4eGwrYuvkX+ko7Jfe9IIczdjKqxqBJCfKxrrLO/TS6sUOny9IOc1yNh/kTW1 VkZs6VrLDX86+pTq3F0kFM01TsacyF+JMUcp/g70bbIhHgRMQDiPBvgV6Cvv3bjb IFJpxaVEquevqwbeI7sx =1sMO -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches # gpg: Signature made Mon 22 Feb 2016 15:59:25 GMT using RSA key ID C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" * remotes/kevin/tags/for-upstream: (34 commits) qemu-iotests: 140: make description slightly more verbose qemu-iotests: 140: don't use IDE device qemu-iotests: 067: ignore QMP events blockdev: unset inappropriate flags when changing medium MAINTAINERS: Add myself as maintainer of the throttling code docs: Document the throttling infrastructure qapi: Correct the name of the iops_rd parameter qemu-iotests: Extend iotest 093 to test bursts throttle: Test throttle_compute_wait() during bursts throttle: Check that burst_level leaks correctly qapi: Add burst length fields to BlockDeviceInfo qapi: Add burst length parameters to block_set_io_throttle throttle: Add command-line settings to define the burst periods throttle: Add support for burst periods throttle: Use throttle_config_init() to initialize ThrottleConfig throttle: Merge all functions that check the configuration into one throttle: Set always an average value when setting a maximum value throttle: Make throttle_is_valid() set errp throttle: Make throttle_max_is_missing_limit() set errp throttle: Make throttle_conflicting() set errp ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
8eb779e422
|
@ -1283,6 +1283,15 @@ S: Maintained
|
||||||
F: include/qemu/sockets.h
|
F: include/qemu/sockets.h
|
||||||
F: util/qemu-sockets.c
|
F: util/qemu-sockets.c
|
||||||
|
|
||||||
|
Throttling infrastructure
|
||||||
|
M: Alberto Garcia <berto@igalia.com>
|
||||||
|
S: Supported
|
||||||
|
F: block/throttle-groups.c
|
||||||
|
F: include/block/throttle-groups.h
|
||||||
|
F: include/qemu/throttle.h
|
||||||
|
F: util/throttle.c
|
||||||
|
L: qemu-block@nongnu.org
|
||||||
|
|
||||||
Usermode Emulation
|
Usermode Emulation
|
||||||
------------------
|
------------------
|
||||||
Overall
|
Overall
|
||||||
|
|
4
block.c
4
block.c
|
@ -1191,10 +1191,6 @@ static int bdrv_fill_options(QDict **options, const char *filename,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
|
||||||
*flags |= BDRV_O_INACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
block/qapi.c
20
block/qapi.c
|
@ -92,6 +92,26 @@ BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs, Error **errp)
|
||||||
info->has_iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
info->has_iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
||||||
info->iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
info->iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max;
|
||||||
|
|
||||||
|
info->has_bps_max_length = info->has_bps_max;
|
||||||
|
info->bps_max_length =
|
||||||
|
cfg.buckets[THROTTLE_BPS_TOTAL].burst_length;
|
||||||
|
info->has_bps_rd_max_length = info->has_bps_rd_max;
|
||||||
|
info->bps_rd_max_length =
|
||||||
|
cfg.buckets[THROTTLE_BPS_READ].burst_length;
|
||||||
|
info->has_bps_wr_max_length = info->has_bps_wr_max;
|
||||||
|
info->bps_wr_max_length =
|
||||||
|
cfg.buckets[THROTTLE_BPS_WRITE].burst_length;
|
||||||
|
|
||||||
|
info->has_iops_max_length = info->has_iops_max;
|
||||||
|
info->iops_max_length =
|
||||||
|
cfg.buckets[THROTTLE_OPS_TOTAL].burst_length;
|
||||||
|
info->has_iops_rd_max_length = info->has_iops_rd_max;
|
||||||
|
info->iops_rd_max_length =
|
||||||
|
cfg.buckets[THROTTLE_OPS_READ].burst_length;
|
||||||
|
info->has_iops_wr_max_length = info->has_iops_wr_max;
|
||||||
|
info->iops_wr_max_length =
|
||||||
|
cfg.buckets[THROTTLE_OPS_WRITE].burst_length;
|
||||||
|
|
||||||
info->has_iops_size = cfg.op_size;
|
info->has_iops_size = cfg.op_size;
|
||||||
info->iops_size = cfg.op_size;
|
info->iops_size = cfg.op_size;
|
||||||
|
|
||||||
|
|
|
@ -286,7 +286,8 @@ static void quorum_aio_cb(void *opaque, int ret)
|
||||||
|
|
||||||
if (acb->is_read && s->read_pattern == QUORUM_READ_PATTERN_FIFO) {
|
if (acb->is_read && s->read_pattern == QUORUM_READ_PATTERN_FIFO) {
|
||||||
/* We try to read next child in FIFO order if we fail to read */
|
/* We try to read next child in FIFO order if we fail to read */
|
||||||
if (ret < 0 && ++acb->child_iter < s->num_children) {
|
if (ret < 0 && (acb->child_iter + 1) < s->num_children) {
|
||||||
|
acb->child_iter++;
|
||||||
read_fifo_child(acb);
|
read_fifo_child(acb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
109
blockdev.c
109
blockdev.c
|
@ -343,29 +343,6 @@ static bool parse_stats_intervals(BlockAcctStats *stats, QList *intervals,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool check_throttle_config(ThrottleConfig *cfg, Error **errp)
|
|
||||||
{
|
|
||||||
if (throttle_conflicting(cfg)) {
|
|
||||||
error_setg(errp, "bps/iops/max total values and read/write values"
|
|
||||||
" cannot be used at the same time");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!throttle_is_valid(cfg)) {
|
|
||||||
error_setg(errp, "bps/iops/max values must be within [0, %lld]",
|
|
||||||
THROTTLE_VALUE_MAX);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (throttle_max_is_missing_limit(cfg)) {
|
|
||||||
error_setg(errp, "bps_max/iops_max require corresponding"
|
|
||||||
" bps/iops values");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum { MEDIA_DISK, MEDIA_CDROM } DriveMediaType;
|
typedef enum { MEDIA_DISK, MEDIA_CDROM } DriveMediaType;
|
||||||
|
|
||||||
/* All parameters but @opts are optional and may be set to NULL. */
|
/* All parameters but @opts are optional and may be set to NULL. */
|
||||||
|
@ -410,7 +387,7 @@ static void extract_common_blockdev_options(QemuOpts *opts, int *bdrv_flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throttle_cfg) {
|
if (throttle_cfg) {
|
||||||
memset(throttle_cfg, 0, sizeof(*throttle_cfg));
|
throttle_config_init(throttle_cfg);
|
||||||
throttle_cfg->buckets[THROTTLE_BPS_TOTAL].avg =
|
throttle_cfg->buckets[THROTTLE_BPS_TOTAL].avg =
|
||||||
qemu_opt_get_number(opts, "throttling.bps-total", 0);
|
qemu_opt_get_number(opts, "throttling.bps-total", 0);
|
||||||
throttle_cfg->buckets[THROTTLE_BPS_READ].avg =
|
throttle_cfg->buckets[THROTTLE_BPS_READ].avg =
|
||||||
|
@ -437,10 +414,23 @@ static void extract_common_blockdev_options(QemuOpts *opts, int *bdrv_flags,
|
||||||
throttle_cfg->buckets[THROTTLE_OPS_WRITE].max =
|
throttle_cfg->buckets[THROTTLE_OPS_WRITE].max =
|
||||||
qemu_opt_get_number(opts, "throttling.iops-write-max", 0);
|
qemu_opt_get_number(opts, "throttling.iops-write-max", 0);
|
||||||
|
|
||||||
|
throttle_cfg->buckets[THROTTLE_BPS_TOTAL].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.bps-total-max-length", 1);
|
||||||
|
throttle_cfg->buckets[THROTTLE_BPS_READ].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.bps-read-max-length", 1);
|
||||||
|
throttle_cfg->buckets[THROTTLE_BPS_WRITE].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.bps-write-max-length", 1);
|
||||||
|
throttle_cfg->buckets[THROTTLE_OPS_TOTAL].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.iops-total-max-length", 1);
|
||||||
|
throttle_cfg->buckets[THROTTLE_OPS_READ].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.iops-read-max-length", 1);
|
||||||
|
throttle_cfg->buckets[THROTTLE_OPS_WRITE].burst_length =
|
||||||
|
qemu_opt_get_number(opts, "throttling.iops-write-max-length", 1);
|
||||||
|
|
||||||
throttle_cfg->op_size =
|
throttle_cfg->op_size =
|
||||||
qemu_opt_get_number(opts, "throttling.iops-size", 0);
|
qemu_opt_get_number(opts, "throttling.iops-size", 0);
|
||||||
|
|
||||||
if (!check_throttle_config(throttle_cfg, errp)) {
|
if (!throttle_is_valid(throttle_cfg, errp)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,6 +600,10 @@ static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
|
||||||
qdict_put(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, qstring_from_str("on"));
|
qdict_put(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, qstring_from_str("on"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||||
|
bdrv_flags |= BDRV_O_INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
blk = blk_new_open(qemu_opts_id(opts), file, NULL, bs_opts, bdrv_flags,
|
blk = blk_new_open(qemu_opts_id(opts), file, NULL, bs_opts, bdrv_flags,
|
||||||
errp);
|
errp);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
|
@ -688,6 +682,10 @@ static BlockDriverState *bds_tree_init(QDict *bs_opts, Error **errp)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
||||||
|
bdrv_flags |= BDRV_O_INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
bs = NULL;
|
bs = NULL;
|
||||||
ret = bdrv_open(&bs, NULL, NULL, bs_opts, bdrv_flags, errp);
|
ret = bdrv_open(&bs, NULL, NULL, bs_opts, bdrv_flags, errp);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -2515,6 +2513,8 @@ void qmp_blockdev_change_medium(const char *device, const char *filename,
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_flags = blk_get_open_flags_from_root_state(blk);
|
bdrv_flags = blk_get_open_flags_from_root_state(blk);
|
||||||
|
bdrv_flags &= ~(BDRV_O_TEMPORARY | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING |
|
||||||
|
BDRV_O_PROTOCOL);
|
||||||
|
|
||||||
if (!has_read_only) {
|
if (!has_read_only) {
|
||||||
read_only = BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN;
|
read_only = BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN;
|
||||||
|
@ -2600,6 +2600,18 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd,
|
||||||
int64_t iops_rd_max,
|
int64_t iops_rd_max,
|
||||||
bool has_iops_wr_max,
|
bool has_iops_wr_max,
|
||||||
int64_t iops_wr_max,
|
int64_t iops_wr_max,
|
||||||
|
bool has_bps_max_length,
|
||||||
|
int64_t bps_max_length,
|
||||||
|
bool has_bps_rd_max_length,
|
||||||
|
int64_t bps_rd_max_length,
|
||||||
|
bool has_bps_wr_max_length,
|
||||||
|
int64_t bps_wr_max_length,
|
||||||
|
bool has_iops_max_length,
|
||||||
|
int64_t iops_max_length,
|
||||||
|
bool has_iops_rd_max_length,
|
||||||
|
int64_t iops_rd_max_length,
|
||||||
|
bool has_iops_wr_max_length,
|
||||||
|
int64_t iops_wr_max_length,
|
||||||
bool has_iops_size,
|
bool has_iops_size,
|
||||||
int64_t iops_size,
|
int64_t iops_size,
|
||||||
bool has_group,
|
bool has_group,
|
||||||
|
@ -2626,7 +2638,7 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
cfg.buckets[THROTTLE_BPS_TOTAL].avg = bps;
|
cfg.buckets[THROTTLE_BPS_TOTAL].avg = bps;
|
||||||
cfg.buckets[THROTTLE_BPS_READ].avg = bps_rd;
|
cfg.buckets[THROTTLE_BPS_READ].avg = bps_rd;
|
||||||
cfg.buckets[THROTTLE_BPS_WRITE].avg = bps_wr;
|
cfg.buckets[THROTTLE_BPS_WRITE].avg = bps_wr;
|
||||||
|
@ -2654,11 +2666,30 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd,
|
||||||
cfg.buckets[THROTTLE_OPS_WRITE].max = iops_wr_max;
|
cfg.buckets[THROTTLE_OPS_WRITE].max = iops_wr_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has_bps_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_BPS_TOTAL].burst_length = bps_max_length;
|
||||||
|
}
|
||||||
|
if (has_bps_rd_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_BPS_READ].burst_length = bps_rd_max_length;
|
||||||
|
}
|
||||||
|
if (has_bps_wr_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_BPS_WRITE].burst_length = bps_wr_max_length;
|
||||||
|
}
|
||||||
|
if (has_iops_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_OPS_TOTAL].burst_length = iops_max_length;
|
||||||
|
}
|
||||||
|
if (has_iops_rd_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_OPS_READ].burst_length = iops_rd_max_length;
|
||||||
|
}
|
||||||
|
if (has_iops_wr_max_length) {
|
||||||
|
cfg.buckets[THROTTLE_OPS_WRITE].burst_length = iops_wr_max_length;
|
||||||
|
}
|
||||||
|
|
||||||
if (has_iops_size) {
|
if (has_iops_size) {
|
||||||
cfg.op_size = iops_size;
|
cfg.op_size = iops_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!check_throttle_config(&cfg, errp)) {
|
if (!throttle_is_valid(&cfg, errp)) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4086,6 +4117,30 @@ QemuOptsList qemu_common_drive_opts = {
|
||||||
.name = "throttling.bps-write-max",
|
.name = "throttling.bps-write-max",
|
||||||
.type = QEMU_OPT_NUMBER,
|
.type = QEMU_OPT_NUMBER,
|
||||||
.help = "total bytes write burst",
|
.help = "total bytes write burst",
|
||||||
|
},{
|
||||||
|
.name = "throttling.iops-total-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the iops-total-max burst period, in seconds",
|
||||||
|
},{
|
||||||
|
.name = "throttling.iops-read-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the iops-read-max burst period, in seconds",
|
||||||
|
},{
|
||||||
|
.name = "throttling.iops-write-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the iops-write-max burst period, in seconds",
|
||||||
|
},{
|
||||||
|
.name = "throttling.bps-total-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the bps-total-max burst period, in seconds",
|
||||||
|
},{
|
||||||
|
.name = "throttling.bps-read-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the bps-read-max burst period, in seconds",
|
||||||
|
},{
|
||||||
|
.name = "throttling.bps-write-max-length",
|
||||||
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
.help = "length of the bps-write-max burst period, in seconds",
|
||||||
},{
|
},{
|
||||||
.name = "throttling.iops-size",
|
.name = "throttling.iops-size",
|
||||||
.type = QEMU_OPT_NUMBER,
|
.type = QEMU_OPT_NUMBER,
|
||||||
|
|
|
@ -103,7 +103,18 @@ in the description of a field.
|
||||||
write to an image with unknown auto-clear features if it
|
write to an image with unknown auto-clear features if it
|
||||||
clears the respective bits from this field first.
|
clears the respective bits from this field first.
|
||||||
|
|
||||||
Bits 0-63: Reserved (set to 0)
|
Bit 0: Bitmaps extension bit
|
||||||
|
This bit indicates consistency for the bitmaps
|
||||||
|
extension data.
|
||||||
|
|
||||||
|
It is an error if this bit is set without the
|
||||||
|
bitmaps extension present.
|
||||||
|
|
||||||
|
If the bitmaps extension is present but this
|
||||||
|
bit is unset, the bitmaps extension data must be
|
||||||
|
considered inconsistent.
|
||||||
|
|
||||||
|
Bits 1-63: Reserved (set to 0)
|
||||||
|
|
||||||
96 - 99: refcount_order
|
96 - 99: refcount_order
|
||||||
Describes the width of a reference count block entry (width
|
Describes the width of a reference count block entry (width
|
||||||
|
@ -123,6 +134,7 @@ be stored. Each extension has a structure like the following:
|
||||||
0x00000000 - End of the header extension area
|
0x00000000 - End of the header extension area
|
||||||
0xE2792ACA - Backing file format name
|
0xE2792ACA - Backing file format name
|
||||||
0x6803f857 - Feature name table
|
0x6803f857 - Feature name table
|
||||||
|
0x23852875 - Bitmaps extension
|
||||||
other - Unknown header extension, can be safely
|
other - Unknown header extension, can be safely
|
||||||
ignored
|
ignored
|
||||||
|
|
||||||
|
@ -166,6 +178,36 @@ the header extension data. Each entry look like this:
|
||||||
terminated if it has full length)
|
terminated if it has full length)
|
||||||
|
|
||||||
|
|
||||||
|
== Bitmaps extension ==
|
||||||
|
|
||||||
|
The bitmaps extension is an optional header extension. It provides the ability
|
||||||
|
to store bitmaps related to a virtual disk. For now, there is only one bitmap
|
||||||
|
type: the dirty tracking bitmap, which tracks virtual disk changes from some
|
||||||
|
point in time.
|
||||||
|
|
||||||
|
The data of the extension should be considered consistent only if the
|
||||||
|
corresponding auto-clear feature bit is set, see autoclear_features above.
|
||||||
|
|
||||||
|
The fields of the bitmaps extension are:
|
||||||
|
|
||||||
|
Byte 0 - 3: nb_bitmaps
|
||||||
|
The number of bitmaps contained in the image. Must be
|
||||||
|
greater than or equal to 1.
|
||||||
|
|
||||||
|
Note: Qemu currently only supports up to 65535 bitmaps per
|
||||||
|
image.
|
||||||
|
|
||||||
|
4 - 7: Reserved, must be zero.
|
||||||
|
|
||||||
|
8 - 15: bitmap_directory_size
|
||||||
|
Size of the bitmap directory in bytes. It is the cumulative
|
||||||
|
size of all (nb_bitmaps) bitmap headers.
|
||||||
|
|
||||||
|
16 - 23: bitmap_directory_offset
|
||||||
|
Offset into the image file at which the bitmap directory
|
||||||
|
starts. Must be aligned to a cluster boundary.
|
||||||
|
|
||||||
|
|
||||||
== Host cluster management ==
|
== Host cluster management ==
|
||||||
|
|
||||||
qcow2 manages the allocation of host clusters by maintaining a reference count
|
qcow2 manages the allocation of host clusters by maintaining a reference count
|
||||||
|
@ -360,3 +402,180 @@ Snapshot table entry:
|
||||||
|
|
||||||
variable: Padding to round up the snapshot table entry size to the
|
variable: Padding to round up the snapshot table entry size to the
|
||||||
next multiple of 8.
|
next multiple of 8.
|
||||||
|
|
||||||
|
|
||||||
|
== Bitmaps ==
|
||||||
|
|
||||||
|
As mentioned above, the bitmaps extension provides the ability to store bitmaps
|
||||||
|
related to a virtual disk. This section describes how these bitmaps are stored.
|
||||||
|
|
||||||
|
All stored bitmaps are related to the virtual disk stored in the same image, so
|
||||||
|
each bitmap size is equal to the virtual disk size.
|
||||||
|
|
||||||
|
Each bit of the bitmap is responsible for strictly defined range of the virtual
|
||||||
|
disk. For bit number bit_nr the corresponding range (in bytes) will be:
|
||||||
|
|
||||||
|
[bit_nr * bitmap_granularity .. (bit_nr + 1) * bitmap_granularity - 1]
|
||||||
|
|
||||||
|
Granularity is a property of the concrete bitmap, see below.
|
||||||
|
|
||||||
|
|
||||||
|
=== Bitmap directory ===
|
||||||
|
|
||||||
|
Each bitmap saved in the image is described in a bitmap directory entry. The
|
||||||
|
bitmap directory is a contiguous area in the image file, whose starting offset
|
||||||
|
and length are given by the header extension fields bitmap_directory_offset and
|
||||||
|
bitmap_directory_size. The entries of the bitmap directory have variable
|
||||||
|
length, depending on the lengths of the bitmap name and extra data. These
|
||||||
|
entries are also called bitmap headers.
|
||||||
|
|
||||||
|
Structure of a bitmap directory entry:
|
||||||
|
|
||||||
|
Byte 0 - 7: bitmap_table_offset
|
||||||
|
Offset into the image file at which the bitmap table
|
||||||
|
(described below) for the bitmap starts. Must be aligned to
|
||||||
|
a cluster boundary.
|
||||||
|
|
||||||
|
8 - 11: bitmap_table_size
|
||||||
|
Number of entries in the bitmap table of the bitmap.
|
||||||
|
|
||||||
|
12 - 15: flags
|
||||||
|
Bit
|
||||||
|
0: in_use
|
||||||
|
The bitmap was not saved correctly and may be
|
||||||
|
inconsistent.
|
||||||
|
|
||||||
|
1: auto
|
||||||
|
The bitmap must reflect all changes of the virtual
|
||||||
|
disk by any application that would write to this qcow2
|
||||||
|
file (including writes, snapshot switching, etc.). The
|
||||||
|
type of this bitmap must be 'dirty tracking bitmap'.
|
||||||
|
|
||||||
|
2: extra_data_compatible
|
||||||
|
This flags is meaningful when the extra data is
|
||||||
|
unknown to the software (currently any extra data is
|
||||||
|
unknown to Qemu).
|
||||||
|
If it is set, the bitmap may be used as expected, extra
|
||||||
|
data must be left as is.
|
||||||
|
If it is not set, the bitmap must not be used, but
|
||||||
|
both it and its extra data be left as is.
|
||||||
|
|
||||||
|
Bits 3 - 31 are reserved and must be 0.
|
||||||
|
|
||||||
|
16: type
|
||||||
|
This field describes the sort of the bitmap.
|
||||||
|
Values:
|
||||||
|
1: Dirty tracking bitmap
|
||||||
|
|
||||||
|
Values 0, 2 - 255 are reserved.
|
||||||
|
|
||||||
|
17: granularity_bits
|
||||||
|
Granularity bits. Valid values: 0 - 63.
|
||||||
|
|
||||||
|
Note: Qemu currently doesn't support granularity_bits
|
||||||
|
greater than 31.
|
||||||
|
|
||||||
|
Granularity is calculated as
|
||||||
|
granularity = 1 << granularity_bits
|
||||||
|
|
||||||
|
A bitmap's granularity is how many bytes of the image
|
||||||
|
accounts for one bit of the bitmap.
|
||||||
|
|
||||||
|
18 - 19: name_size
|
||||||
|
Size of the bitmap name. Must be non-zero.
|
||||||
|
|
||||||
|
Note: Qemu currently doesn't support values greater than
|
||||||
|
1023.
|
||||||
|
|
||||||
|
20 - 23: extra_data_size
|
||||||
|
Size of type-specific extra data.
|
||||||
|
|
||||||
|
For now, as no extra data is defined, extra_data_size is
|
||||||
|
reserved and should be zero. If it is non-zero the
|
||||||
|
behavior is defined by extra_data_compatible flag.
|
||||||
|
|
||||||
|
variable: extra_data
|
||||||
|
Extra data for the bitmap, occupying extra_data_size bytes.
|
||||||
|
Extra data must never contain references to clusters or in
|
||||||
|
some other way allocate additional clusters.
|
||||||
|
|
||||||
|
variable: name
|
||||||
|
The name of the bitmap (not null terminated), occupying
|
||||||
|
name_size bytes. Must be unique among all bitmap names
|
||||||
|
within the bitmaps extension.
|
||||||
|
|
||||||
|
variable: Padding to round up the bitmap directory entry size to the
|
||||||
|
next multiple of 8. All bytes of the padding must be zero.
|
||||||
|
|
||||||
|
|
||||||
|
=== Bitmap table ===
|
||||||
|
|
||||||
|
Each bitmap is stored using a one-level structure (as opposed to two-level
|
||||||
|
structures like for refcounts and guest clusters mapping) for the mapping of
|
||||||
|
bitmap data to host clusters. This structure is called the bitmap table.
|
||||||
|
|
||||||
|
Each bitmap table has a variable size (stored in the bitmap directory entry)
|
||||||
|
and may use multiple clusters, however, it must be contiguous in the image
|
||||||
|
file.
|
||||||
|
|
||||||
|
Structure of a bitmap table entry:
|
||||||
|
|
||||||
|
Bit 0: Reserved and must be zero if bits 9 - 55 are non-zero.
|
||||||
|
If bits 9 - 55 are zero:
|
||||||
|
0: Cluster should be read as all zeros.
|
||||||
|
1: Cluster should be read as all ones.
|
||||||
|
|
||||||
|
1 - 8: Reserved and must be zero.
|
||||||
|
|
||||||
|
9 - 55: Bits 9 - 55 of the host cluster offset. Must be aligned to
|
||||||
|
a cluster boundary. If the offset is 0, the cluster is
|
||||||
|
unallocated; in that case, bit 0 determines how this
|
||||||
|
cluster should be treated during reads.
|
||||||
|
|
||||||
|
56 - 63: Reserved and must be zero.
|
||||||
|
|
||||||
|
|
||||||
|
=== Bitmap data ===
|
||||||
|
|
||||||
|
As noted above, bitmap data is stored in separate clusters, described by the
|
||||||
|
bitmap table. Given an offset (in bytes) into the bitmap data, the offset into
|
||||||
|
the image file can be obtained as follows:
|
||||||
|
|
||||||
|
image_offset(bitmap_data_offset) =
|
||||||
|
bitmap_table[bitmap_data_offset / cluster_size] +
|
||||||
|
(bitmap_data_offset % cluster_size)
|
||||||
|
|
||||||
|
This offset is not defined if bits 9 - 55 of bitmap table entry are zero (see
|
||||||
|
above).
|
||||||
|
|
||||||
|
Given an offset byte_nr into the virtual disk and the bitmap's granularity, the
|
||||||
|
bit offset into the image file to the corresponding bit of the bitmap can be
|
||||||
|
calculated like this:
|
||||||
|
|
||||||
|
bit_offset(byte_nr) =
|
||||||
|
image_offset(byte_nr / granularity / 8) * 8 +
|
||||||
|
(byte_nr / granularity) % 8
|
||||||
|
|
||||||
|
If the size of the bitmap data is not a multiple of the cluster size then the
|
||||||
|
last cluster of the bitmap data contains some unused tail bits. These bits must
|
||||||
|
be zero.
|
||||||
|
|
||||||
|
|
||||||
|
=== Dirty tracking bitmaps ===
|
||||||
|
|
||||||
|
Bitmaps with 'type' field equal to one are dirty tracking bitmaps.
|
||||||
|
|
||||||
|
When the virtual disk is in use dirty tracking bitmap may be 'enabled' or
|
||||||
|
'disabled'. While the bitmap is 'enabled', all writes to the virtual disk
|
||||||
|
should be reflected in the bitmap. A set bit in the bitmap means that the
|
||||||
|
corresponding range of the virtual disk (see above) was written to while the
|
||||||
|
bitmap was 'enabled'. An unset bit means that this range was not written to.
|
||||||
|
|
||||||
|
The software doesn't have to sync the bitmap in the image file with its
|
||||||
|
representation in RAM after each write. Flag 'in_use' should be set while the
|
||||||
|
bitmap is not synced.
|
||||||
|
|
||||||
|
In the image file the 'enabled' state is reflected by the 'auto' flag. If this
|
||||||
|
flag is set, the software must consider the bitmap as 'enabled' and start
|
||||||
|
tracking virtual disk changes to this bitmap from the first write to the
|
||||||
|
virtual disk. If this flag is not set then the bitmap is disabled.
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
The QEMU throttling infrastructure
|
||||||
|
==================================
|
||||||
|
Copyright (C) 2016 Igalia, S.L.
|
||||||
|
Author: Alberto Garcia <berto@igalia.com>
|
||||||
|
|
||||||
|
This work is licensed under the terms of the GNU GPL, version 2 or
|
||||||
|
later. See the COPYING file in the top-level directory.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
QEMU includes a throttling module that can be used to set limits to
|
||||||
|
I/O operations. The code itself is generic and independent of the I/O
|
||||||
|
units, but it is currenly used to limit the number of bytes per second
|
||||||
|
and operations per second (IOPS) when performing disk I/O.
|
||||||
|
|
||||||
|
This document explains how to use the throttling code in QEMU, and how
|
||||||
|
it works internally. The implementation is in throttle.c.
|
||||||
|
|
||||||
|
|
||||||
|
Using throttling to limit disk I/O
|
||||||
|
----------------------------------
|
||||||
|
Two aspects of the disk I/O can be limited: the number of bytes per
|
||||||
|
second and the number of operations per second (IOPS). For each one of
|
||||||
|
them the user can set a global limit or separate limits for read and
|
||||||
|
write operations. This gives us a total of six different parameters.
|
||||||
|
|
||||||
|
I/O limits can be set using the throttling.* parameters of -drive, or
|
||||||
|
using the QMP 'block_set_io_throttle' command. These are the names of
|
||||||
|
the parameters for both cases:
|
||||||
|
|
||||||
|
|-----------------------+-----------------------|
|
||||||
|
| -drive | block_set_io_throttle |
|
||||||
|
|-----------------------+-----------------------|
|
||||||
|
| throttling.iops-total | iops |
|
||||||
|
| throttling.iops-read | iops_rd |
|
||||||
|
| throttling.iops-write | iops_wr |
|
||||||
|
| throttling.bps-total | bps |
|
||||||
|
| throttling.bps-read | bps_rd |
|
||||||
|
| throttling.bps-write | bps_wr |
|
||||||
|
|-----------------------+-----------------------|
|
||||||
|
|
||||||
|
It is possible to set limits for both IOPS and bps and the same time,
|
||||||
|
and for each case we can decide whether to have separate read and
|
||||||
|
write limits or not, but note that if iops-total is set then neither
|
||||||
|
iops-read nor iops-write can be set. The same applies to bps-total and
|
||||||
|
bps-read/write.
|
||||||
|
|
||||||
|
The default value of these parameters is 0, and it means 'unlimited'.
|
||||||
|
|
||||||
|
In its most basic usage, the user can add a drive to QEMU with a limit
|
||||||
|
of 100 IOPS with the following -drive line:
|
||||||
|
|
||||||
|
-drive file=hd0.qcow2,throttling.iops-total=100
|
||||||
|
|
||||||
|
We can do the same using QMP. In this case all these parameters are
|
||||||
|
mandatory, so we must set to 0 the ones that we don't want to limit:
|
||||||
|
|
||||||
|
{ "execute": "block_set_io_throttle",
|
||||||
|
"arguments": {
|
||||||
|
"device": "virtio0",
|
||||||
|
"iops": 100,
|
||||||
|
"iops_rd": 0,
|
||||||
|
"iops_wr": 0,
|
||||||
|
"bps": 0,
|
||||||
|
"bps_rd": 0,
|
||||||
|
"bps_wr": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
I/O bursts
|
||||||
|
----------
|
||||||
|
In addition to the basic limits we have just seen, QEMU allows the
|
||||||
|
user to do bursts of I/O for a configurable amount of time. A burst is
|
||||||
|
an amount of I/O that can exceed the basic limit. Bursts are useful to
|
||||||
|
allow better performance when there are peaks of activity (the OS
|
||||||
|
boots, a service needs to be restarted) while keeping the average
|
||||||
|
limits lower the rest of the time.
|
||||||
|
|
||||||
|
Two parameters control bursts: their length and the maximum amount of
|
||||||
|
I/O they allow. These two can be configured separately for each one of
|
||||||
|
the six basic parameters described in the previous section, but in
|
||||||
|
this section we'll use 'iops-total' as an example.
|
||||||
|
|
||||||
|
The I/O limit during bursts is set using 'iops-total-max', and the
|
||||||
|
maximum length (in seconds) is set with 'iops-total-max-length'. So if
|
||||||
|
we want to configure a drive with a basic limit of 100 IOPS and allow
|
||||||
|
bursts of 2000 IOPS for 60 seconds, we would do it like this (the line
|
||||||
|
is split for clarity):
|
||||||
|
|
||||||
|
-drive file=hd0.qcow2,
|
||||||
|
throttling.iops-total=100,
|
||||||
|
throttling.iops-total-max=2000,
|
||||||
|
throttling.iops-total-max-length=60
|
||||||
|
|
||||||
|
Or, with QMP:
|
||||||
|
|
||||||
|
{ "execute": "block_set_io_throttle",
|
||||||
|
"arguments": {
|
||||||
|
"device": "virtio0",
|
||||||
|
"iops": 100,
|
||||||
|
"iops_rd": 0,
|
||||||
|
"iops_wr": 0,
|
||||||
|
"bps": 0,
|
||||||
|
"bps_rd": 0,
|
||||||
|
"bps_wr": 0,
|
||||||
|
"iops_max": 2000,
|
||||||
|
"iops_max_length": 60,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
With this, the user can perform I/O on hd0.qcow2 at a rate of 2000
|
||||||
|
IOPS for 1 minute before it's throttled down to 100 IOPS.
|
||||||
|
|
||||||
|
The user will be able to do bursts again if there's a sufficiently
|
||||||
|
long period of time with unused I/O (see below for details).
|
||||||
|
|
||||||
|
The default value for 'iops-total-max' is 0 and it means that bursts
|
||||||
|
are not allowed. 'iops-total-max-length' can only be set if
|
||||||
|
'iops-total-max' is set as well, and its default value is 1 second.
|
||||||
|
|
||||||
|
Here's the complete list of parameters for configuring bursts:
|
||||||
|
|
||||||
|
|----------------------------------+-----------------------|
|
||||||
|
| -drive | block_set_io_throttle |
|
||||||
|
|----------------------------------+-----------------------|
|
||||||
|
| throttling.iops-total-max | iops_max |
|
||||||
|
| throttling.iops-total-max-length | iops_max_length |
|
||||||
|
| throttling.iops-read-max | iops_rd_max |
|
||||||
|
| throttling.iops-read-max-length | iops_rd_max_length |
|
||||||
|
| throttling.iops-write-max | iops_wr_max |
|
||||||
|
| throttling.iops-write-max-length | iops_wr_max_length |
|
||||||
|
| throttling.bps-total-max | bps_max |
|
||||||
|
| throttling.bps-total-max-length | bps_max_length |
|
||||||
|
| throttling.bps-read-max | bps_rd_max |
|
||||||
|
| throttling.bps-read-max-length | bps_rd_max_length |
|
||||||
|
| throttling.bps-write-max | bps_wr_max |
|
||||||
|
| throttling.bps-write-max-length | bps_wr_max_length |
|
||||||
|
|----------------------------------+-----------------------|
|
||||||
|
|
||||||
|
|
||||||
|
Controlling the size of I/O operations
|
||||||
|
--------------------------------------
|
||||||
|
When applying IOPS limits all I/O operations are treated equally
|
||||||
|
regardless of their size. This means that the user can take advantage
|
||||||
|
of this in order to circumvent the limits and submit one huge I/O
|
||||||
|
request instead of several smaller ones.
|
||||||
|
|
||||||
|
QEMU provides a setting called throttling.iops-size to prevent this
|
||||||
|
from happening. This setting specifies the size (in bytes) of an I/O
|
||||||
|
request for accounting purposes. Larger requests will be counted
|
||||||
|
proportionally to this size.
|
||||||
|
|
||||||
|
For example, if iops-size is set to 4096 then an 8KB request will be
|
||||||
|
counted as two, and a 6KB request will be counted as one and a
|
||||||
|
half. This only applies to requests larger than iops-size: smaller
|
||||||
|
requests will be always counted as one, no matter their size.
|
||||||
|
|
||||||
|
The default value of iops-size is 0 and it means that the size of the
|
||||||
|
requests is never taken into account when applying IOPS limits.
|
||||||
|
|
||||||
|
|
||||||
|
Applying I/O limits to groups of disks
|
||||||
|
--------------------------------------
|
||||||
|
In all the examples so far we have seen how to apply limits to the I/O
|
||||||
|
performed on individual drives, but QEMU allows grouping drives so
|
||||||
|
they all share the same limits.
|
||||||
|
|
||||||
|
The way it works is that each drive with I/O limits is assigned to a
|
||||||
|
group named using the throttling.group parameter. If this parameter is
|
||||||
|
not specified, then the device name (i.e. 'virtio0', 'ide0-hd0') will
|
||||||
|
be used as the group name.
|
||||||
|
|
||||||
|
Limits set using the throttling.* parameters discussed earlier in this
|
||||||
|
document apply to the combined I/O of all members of a group.
|
||||||
|
|
||||||
|
Consider this example:
|
||||||
|
|
||||||
|
-drive file=hd1.qcow2,throttling.iops-total=6000,throttling.group=foo
|
||||||
|
-drive file=hd2.qcow2,throttling.iops-total=6000,throttling.group=foo
|
||||||
|
-drive file=hd3.qcow2,throttling.iops-total=3000,throttling.group=bar
|
||||||
|
-drive file=hd4.qcow2,throttling.iops-total=6000,throttling.group=foo
|
||||||
|
-drive file=hd5.qcow2,throttling.iops-total=3000,throttling.group=bar
|
||||||
|
-drive file=hd6.qcow2,throttling.iops-total=5000
|
||||||
|
|
||||||
|
Here hd1, hd2 and hd4 are all members of a group named 'foo' with a
|
||||||
|
combined IOPS limit of 6000, and hd3 and hd5 are members of 'bar'. hd6
|
||||||
|
is left alone (technically it is part of a 1-member group).
|
||||||
|
|
||||||
|
Limits are applied in a round-robin fashion so if there are concurrent
|
||||||
|
I/O requests on several drives of the same group they will be
|
||||||
|
distributed evenly.
|
||||||
|
|
||||||
|
When I/O limits are applied to an existing drive using the QMP command
|
||||||
|
'block_set_io_throttle', the following things need to be taken into
|
||||||
|
account:
|
||||||
|
|
||||||
|
- I/O limits are shared within the same group, so new values will
|
||||||
|
affect all members and overwrite the previous settings. In other
|
||||||
|
words: if different limits are applied to members of the same
|
||||||
|
group, the last one wins.
|
||||||
|
|
||||||
|
- If 'group' is unset it is assumed to be the current group of that
|
||||||
|
drive. If the drive is not in a group yet, it will be added to a
|
||||||
|
group named after the device name.
|
||||||
|
|
||||||
|
- If 'group' is set then the drive will be moved to that group if
|
||||||
|
it was member of a different one. In this case the limits
|
||||||
|
specified in the parameters will be applied to the new group
|
||||||
|
only.
|
||||||
|
|
||||||
|
- I/O limits can be disabled by setting all of them to 0. In this
|
||||||
|
case the device will be removed from its group and the rest of
|
||||||
|
its members will not be affected. The 'group' parameter is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
|
||||||
|
The Leaky Bucket algorithm
|
||||||
|
--------------------------
|
||||||
|
I/O limits in QEMU are implemented using the leaky bucket algorithm
|
||||||
|
(specifically the "Leaky bucket as a meter" variant).
|
||||||
|
|
||||||
|
This algorithm uses the analogy of a bucket that leaks water
|
||||||
|
constantly. The water that gets into the bucket represents the I/O
|
||||||
|
that has been performed, and no more I/O is allowed once the bucket is
|
||||||
|
full.
|
||||||
|
|
||||||
|
To see the way this corresponds to the throttling parameters in QEMU,
|
||||||
|
consider the following values:
|
||||||
|
|
||||||
|
iops-total=100
|
||||||
|
iops-total-max=2000
|
||||||
|
iops-total-max-length=60
|
||||||
|
|
||||||
|
- Water leaks from the bucket at a rate of 100 IOPS.
|
||||||
|
- Water can be added to the bucket at a rate of 2000 IOPS.
|
||||||
|
- The size of the bucket is 2000 x 60 = 120000
|
||||||
|
- If 'iops-total-max-length' is unset then the bucket size is 100.
|
||||||
|
|
||||||
|
The bucket is initially empty, therefore water can be added until it's
|
||||||
|
full at a rate of 2000 IOPS (the burst rate). Once the bucket is full
|
||||||
|
we can only add as much water as it leaks, therefore the I/O rate is
|
||||||
|
reduced to 100 IOPS. If we add less water than it leaks then the
|
||||||
|
bucket will start to empty, allowing for bursts again.
|
||||||
|
|
||||||
|
Note that since water is leaking from the bucket even during bursts,
|
||||||
|
it will take a bit more than 60 seconds at 2000 IOPS to fill it
|
||||||
|
up. After those 60 seconds the bucket will have leaked 60 x 100 =
|
||||||
|
6000, allowing for 3 more seconds of I/O at 2000 IOPS.
|
||||||
|
|
||||||
|
Also, due to the way the algorithm works, longer burst can be done at
|
||||||
|
a lower I/O rate, e.g. 1000 IOPS during 120 seconds.
|
12
hmp.c
12
hmp.c
|
@ -1414,6 +1414,18 @@ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict)
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
|
false, /* no burst length via HMP */
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
false, /* No default I/O size */
|
false, /* No default I/O size */
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* QEMU throttling infrastructure
|
* QEMU throttling infrastructure
|
||||||
*
|
*
|
||||||
* Copyright (C) Nodalink, EURL. 2013-2014
|
* Copyright (C) Nodalink, EURL. 2013-2014
|
||||||
* Copyright (C) Igalia, S.L. 2015
|
* Copyright (C) Igalia, S.L. 2015-2016
|
||||||
*
|
*
|
||||||
* Authors:
|
* Authors:
|
||||||
* Benoît Canet <benoit.canet@nodalink.com>
|
* Benoît Canet <benoit.canet@nodalink.com>
|
||||||
|
@ -42,16 +42,47 @@ typedef enum {
|
||||||
} BucketType;
|
} BucketType;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The max parameter of the leaky bucket throttling algorithm can be used to
|
* This module implements I/O limits using the leaky bucket
|
||||||
* allow the guest to do bursts.
|
* algorithm. The code is independent of the I/O units, but it is
|
||||||
* The max value is a pool of I/O that the guest can use without being throttled
|
* currently used for bytes per second and operations per second.
|
||||||
* at all. Throttling is triggered once this pool is empty.
|
*
|
||||||
|
* Three parameters can be set by the user:
|
||||||
|
*
|
||||||
|
* - avg: the desired I/O limits in units per second.
|
||||||
|
* - max: the limit during bursts, also in units per second.
|
||||||
|
* - burst_length: the maximum length of the burst period, in seconds.
|
||||||
|
*
|
||||||
|
* Here's how it works:
|
||||||
|
*
|
||||||
|
* - The bucket level (number of performed I/O units) is kept in
|
||||||
|
* bkt.level and leaks at a rate of bkt.avg units per second.
|
||||||
|
*
|
||||||
|
* - The size of the bucket is bkt.max * bkt.burst_length. Once the
|
||||||
|
* bucket is full no more I/O is performed until the bucket leaks
|
||||||
|
* again. This is what makes the I/O rate bkt.avg.
|
||||||
|
*
|
||||||
|
* - The bkt.avg rate does not apply until the bucket is full,
|
||||||
|
* allowing the user to do bursts until then. The I/O limit during
|
||||||
|
* bursts is bkt.max. To enforce this limit we keep an additional
|
||||||
|
* bucket in bkt.burst_length that leaks at a rate of bkt.max units
|
||||||
|
* per second.
|
||||||
|
*
|
||||||
|
* - Because of all of the above, the user can perform I/O at a
|
||||||
|
* maximum of bkt.max units per second for at most bkt.burst_length
|
||||||
|
* seconds in a row. After that the bucket will be full and the I/O
|
||||||
|
* rate will go down to bkt.avg.
|
||||||
|
*
|
||||||
|
* - Since the bucket always leaks at a rate of bkt.avg, this also
|
||||||
|
* determines how much the user needs to wait before being able to
|
||||||
|
* do bursts again.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef struct LeakyBucket {
|
typedef struct LeakyBucket {
|
||||||
double avg; /* average goal in units per second */
|
double avg; /* average goal in units per second */
|
||||||
double max; /* leaky bucket max burst in units */
|
double max; /* leaky bucket max burst in units */
|
||||||
double level; /* bucket level in units */
|
double level; /* bucket level in units */
|
||||||
|
double burst_level; /* bucket level in units (for computing bursts) */
|
||||||
|
unsigned burst_length; /* max length of the burst period, in seconds */
|
||||||
} LeakyBucket;
|
} LeakyBucket;
|
||||||
|
|
||||||
/* The following structure is used to configure a ThrottleState
|
/* The following structure is used to configure a ThrottleState
|
||||||
|
@ -84,12 +115,6 @@ void throttle_leak_bucket(LeakyBucket *bkt, int64_t delta);
|
||||||
|
|
||||||
int64_t throttle_compute_wait(LeakyBucket *bkt);
|
int64_t throttle_compute_wait(LeakyBucket *bkt);
|
||||||
|
|
||||||
/* expose timer computation function for unit tests */
|
|
||||||
bool throttle_compute_timer(ThrottleState *ts,
|
|
||||||
bool is_write,
|
|
||||||
int64_t now,
|
|
||||||
int64_t *next_timestamp);
|
|
||||||
|
|
||||||
/* init/destroy cycle */
|
/* init/destroy cycle */
|
||||||
void throttle_init(ThrottleState *ts);
|
void throttle_init(ThrottleState *ts);
|
||||||
|
|
||||||
|
@ -112,11 +137,7 @@ bool throttle_timers_are_initialized(ThrottleTimers *tt);
|
||||||
/* configuration */
|
/* configuration */
|
||||||
bool throttle_enabled(ThrottleConfig *cfg);
|
bool throttle_enabled(ThrottleConfig *cfg);
|
||||||
|
|
||||||
bool throttle_conflicting(ThrottleConfig *cfg);
|
bool throttle_is_valid(ThrottleConfig *cfg, Error **errp);
|
||||||
|
|
||||||
bool throttle_is_valid(ThrottleConfig *cfg);
|
|
||||||
|
|
||||||
bool throttle_max_is_missing_limit(ThrottleConfig *cfg);
|
|
||||||
|
|
||||||
void throttle_config(ThrottleState *ts,
|
void throttle_config(ThrottleState *ts,
|
||||||
ThrottleTimers *tt,
|
ThrottleTimers *tt,
|
||||||
|
@ -124,6 +145,8 @@ void throttle_config(ThrottleState *ts,
|
||||||
|
|
||||||
void throttle_get_config(ThrottleState *ts, ThrottleConfig *cfg);
|
void throttle_get_config(ThrottleState *ts, ThrottleConfig *cfg);
|
||||||
|
|
||||||
|
void throttle_config_init(ThrottleConfig *cfg);
|
||||||
|
|
||||||
/* usage */
|
/* usage */
|
||||||
bool throttle_schedule_timer(ThrottleState *ts,
|
bool throttle_schedule_timer(ThrottleState *ts,
|
||||||
ThrottleTimers *tt,
|
ThrottleTimers *tt,
|
||||||
|
|
|
@ -786,6 +786,7 @@ static int block_load(QEMUFile *f, void *opaque, int version_id)
|
||||||
int64_t addr;
|
int64_t addr;
|
||||||
BlockDriverState *bs, *bs_prev = NULL;
|
BlockDriverState *bs, *bs_prev = NULL;
|
||||||
BlockBackend *blk;
|
BlockBackend *blk;
|
||||||
|
Error *local_err = NULL;
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
int64_t total_sectors = 0;
|
int64_t total_sectors = 0;
|
||||||
int nr_sectors;
|
int nr_sectors;
|
||||||
|
@ -824,6 +825,12 @@ static int block_load(QEMUFile *f, void *opaque, int version_id)
|
||||||
device_name);
|
device_name);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bdrv_invalidate_cache(bs, &local_err);
|
||||||
|
if (local_err) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_sectors - addr < BDRV_SECTORS_PER_DIRTY_CHUNK) {
|
if (total_sectors - addr < BDRV_SECTORS_PER_DIRTY_CHUNK) {
|
||||||
|
|
|
@ -273,17 +273,41 @@
|
||||||
#
|
#
|
||||||
# @image: the info of image used (since: 1.6)
|
# @image: the info of image used (since: 1.6)
|
||||||
#
|
#
|
||||||
# @bps_max: #optional total max in bytes (Since 1.7)
|
# @bps_max: #optional total throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @bps_rd_max: #optional read max in bytes (Since 1.7)
|
# @bps_rd_max: #optional read throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @bps_wr_max: #optional write max in bytes (Since 1.7)
|
# @bps_wr_max: #optional write throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_max: #optional total I/O operations max (Since 1.7)
|
# @iops_max: #optional total I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_rd_max: #optional read I/O operations max (Since 1.7)
|
# @iops_rd_max: #optional read I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_wr_max: #optional write I/O operations max (Since 1.7)
|
# @iops_wr_max: #optional write I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
|
#
|
||||||
|
# @bps_max_length: #optional maximum length of the @bps_max burst
|
||||||
|
# period, in seconds. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @bps_rd_max_length: #optional maximum length of the @bps_rd_max
|
||||||
|
# burst period, in seconds. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @bps_wr_max_length: #optional maximum length of the @bps_wr_max
|
||||||
|
# burst period, in seconds. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_max_length: #optional maximum length of the @iops burst
|
||||||
|
# period, in seconds. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_rd_max_length: #optional maximum length of the @iops_rd_max
|
||||||
|
# burst period, in seconds. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_wr_max_length: #optional maximum length of the @iops_wr_max
|
||||||
|
# burst period, in seconds. (Since 2.6)
|
||||||
#
|
#
|
||||||
# @iops_size: #optional an I/O size in bytes (Since 1.7)
|
# @iops_size: #optional an I/O size in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
|
@ -308,6 +332,9 @@
|
||||||
'*bps_max': 'int', '*bps_rd_max': 'int',
|
'*bps_max': 'int', '*bps_rd_max': 'int',
|
||||||
'*bps_wr_max': 'int', '*iops_max': 'int',
|
'*bps_wr_max': 'int', '*iops_max': 'int',
|
||||||
'*iops_rd_max': 'int', '*iops_wr_max': 'int',
|
'*iops_rd_max': 'int', '*iops_wr_max': 'int',
|
||||||
|
'*bps_max_length': 'int', '*bps_rd_max_length': 'int',
|
||||||
|
'*bps_wr_max_length': 'int', '*iops_max_length': 'int',
|
||||||
|
'*iops_rd_max_length': 'int', '*iops_wr_max_length': 'int',
|
||||||
'*iops_size': 'int', '*group': 'str', 'cache': 'BlockdevCacheInfo',
|
'*iops_size': 'int', '*group': 'str', 'cache': 'BlockdevCacheInfo',
|
||||||
'write_threshold': 'int' } }
|
'write_threshold': 'int' } }
|
||||||
|
|
||||||
|
@ -1294,21 +1321,57 @@
|
||||||
#
|
#
|
||||||
# @iops: total I/O operations per second
|
# @iops: total I/O operations per second
|
||||||
#
|
#
|
||||||
# @ops_rd: read I/O operations per second
|
# @iops_rd: read I/O operations per second
|
||||||
#
|
#
|
||||||
# @iops_wr: write I/O operations per second
|
# @iops_wr: write I/O operations per second
|
||||||
#
|
#
|
||||||
# @bps_max: #optional total max in bytes (Since 1.7)
|
# @bps_max: #optional total throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @bps_rd_max: #optional read max in bytes (Since 1.7)
|
# @bps_rd_max: #optional read throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @bps_wr_max: #optional write max in bytes (Since 1.7)
|
# @bps_wr_max: #optional write throughput limit during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_max: #optional total I/O operations max (Since 1.7)
|
# @iops_max: #optional total I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_rd_max: #optional read I/O operations max (Since 1.7)
|
# @iops_rd_max: #optional read I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
# @iops_wr_max: #optional write I/O operations max (Since 1.7)
|
# @iops_wr_max: #optional write I/O operations per second during bursts,
|
||||||
|
# in bytes (Since 1.7)
|
||||||
|
#
|
||||||
|
# @bps_max_length: #optional maximum length of the @bps_max burst
|
||||||
|
# period, in seconds. It must only
|
||||||
|
# be set if @bps_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @bps_rd_max_length: #optional maximum length of the @bps_rd_max
|
||||||
|
# burst period, in seconds. It must only
|
||||||
|
# be set if @bps_rd_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @bps_wr_max_length: #optional maximum length of the @bps_wr_max
|
||||||
|
# burst period, in seconds. It must only
|
||||||
|
# be set if @bps_wr_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_max_length: #optional maximum length of the @iops burst
|
||||||
|
# period, in seconds. It must only
|
||||||
|
# be set if @iops_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_rd_max_length: #optional maximum length of the @iops_rd_max
|
||||||
|
# burst period, in seconds. It must only
|
||||||
|
# be set if @iops_rd_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
|
#
|
||||||
|
# @iops_wr_max_length: #optional maximum length of the @iops_wr_max
|
||||||
|
# burst period, in seconds. It must only
|
||||||
|
# be set if @iops_wr_max is set as well.
|
||||||
|
# Defaults to 1. (Since 2.6)
|
||||||
#
|
#
|
||||||
# @iops_size: #optional an I/O size in bytes (Since 1.7)
|
# @iops_size: #optional an I/O size in bytes (Since 1.7)
|
||||||
#
|
#
|
||||||
|
@ -1325,6 +1388,9 @@
|
||||||
'*bps_max': 'int', '*bps_rd_max': 'int',
|
'*bps_max': 'int', '*bps_rd_max': 'int',
|
||||||
'*bps_wr_max': 'int', '*iops_max': 'int',
|
'*bps_wr_max': 'int', '*iops_max': 'int',
|
||||||
'*iops_rd_max': 'int', '*iops_wr_max': 'int',
|
'*iops_rd_max': 'int', '*iops_wr_max': 'int',
|
||||||
|
'*bps_max_length': 'int', '*bps_rd_max_length': 'int',
|
||||||
|
'*bps_wr_max_length': 'int', '*iops_max_length': 'int',
|
||||||
|
'*iops_rd_max_length': 'int', '*iops_wr_max_length': 'int',
|
||||||
'*iops_size': 'int', '*group': 'str' } }
|
'*iops_size': 'int', '*group': 'str' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -10,68 +10,68 @@ STEXI
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("check", img_check,
|
DEF("check", img_check,
|
||||||
"check [-q] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename")
|
"check [-q] [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item check [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename}
|
@item check [--object @var{objectdef}] [--image-opts] [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("create", img_create,
|
DEF("create", img_create,
|
||||||
"create [-q] [-f fmt] [-o options] filename [size]")
|
"create [-q] [--object objectdef] [--image-opts] [-f fmt] [-o options] filename [size]")
|
||||||
STEXI
|
STEXI
|
||||||
@item create [-q] [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}]
|
@item create [--object @var{objectdef}] [--image-opts] [-q] [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}]
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("commit", img_commit,
|
DEF("commit", img_commit,
|
||||||
"commit [-q] [-f fmt] [-t cache] [-b base] [-d] [-p] filename")
|
"commit [-q] [--object objectdef] [--image-opts] [-f fmt] [-t cache] [-b base] [-d] [-p] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item commit [-q] [-f @var{fmt}] [-t @var{cache}] [-b @var{base}] [-d] [-p] @var{filename}
|
@item commit [--object @var{objectdef}] [--image-opts] [-q] [-f @var{fmt}] [-t @var{cache}] [-b @var{base}] [-d] [-p] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("compare", img_compare,
|
DEF("compare", img_compare,
|
||||||
"compare [-f fmt] [-F fmt] [-T src_cache] [-p] [-q] [-s] filename1 filename2")
|
"compare [--object objectdef] [--image-opts] [-f fmt] [-F fmt] [-T src_cache] [-p] [-q] [-s] filename1 filename2")
|
||||||
STEXI
|
STEXI
|
||||||
@item compare [-f @var{fmt}] [-F @var{fmt}] [-T @var{src_cache}] [-p] [-q] [-s] @var{filename1} @var{filename2}
|
@item compare [--object @var{objectdef}] [--image-opts] [-f @var{fmt}] [-F @var{fmt}] [-T @var{src_cache}] [-p] [-q] [-s] @var{filename1} @var{filename2}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("convert", img_convert,
|
DEF("convert", img_convert,
|
||||||
"convert [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-o options] [-s snapshot_id_or_name] [-l snapshot_param] [-S sparse_size] filename [filename2 [...]] output_filename")
|
"convert [--object objectdef] [--image-opts] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-o options] [-s snapshot_id_or_name] [-l snapshot_param] [-S sparse_size] filename [filename2 [...]] output_filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item convert [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
|
@item convert [--object @var{objectdef}] [--image-opts] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("info", img_info,
|
DEF("info", img_info,
|
||||||
"info [-f fmt] [--output=ofmt] [--backing-chain] filename")
|
"info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
|
@item info [--object @var{objectdef}] [--image-opts] [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("map", img_map,
|
DEF("map", img_map,
|
||||||
"map [-f fmt] [--output=ofmt] filename")
|
"map [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item map [-f @var{fmt}] [--output=@var{ofmt}] @var{filename}
|
@item map [--object @var{objectdef}] [--image-opts] [-f @var{fmt}] [--output=@var{ofmt}] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("snapshot", img_snapshot,
|
DEF("snapshot", img_snapshot,
|
||||||
"snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
|
"snapshot [--object objectdef] [--image-opts] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item snapshot [-q] [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename}
|
@item snapshot [--object @var{objectdef}] [--image-opts] [-q] [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("rebase", img_rebase,
|
DEF("rebase", img_rebase,
|
||||||
"rebase [-q] [-f fmt] [-t cache] [-T src_cache] [-p] [-u] -b backing_file [-F backing_fmt] filename")
|
"rebase [--object objectdef] [--image-opts] [-q] [-f fmt] [-t cache] [-T src_cache] [-p] [-u] -b backing_file [-F backing_fmt] filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item rebase [-q] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename}
|
@item rebase [--object @var{objectdef}] [--image-opts] [-q] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("resize", img_resize,
|
DEF("resize", img_resize,
|
||||||
"resize [-q] filename [+ | -]size")
|
"resize [--object objectdef] [--image-opts] [-q] filename [+ | -]size")
|
||||||
STEXI
|
STEXI
|
||||||
@item resize [-q] @var{filename} [+ | -]@var{size}
|
@item resize [--object @var{objectdef}] [--image-opts] [-q] @var{filename} [+ | -]@var{size}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
DEF("amend", img_amend,
|
DEF("amend", img_amend,
|
||||||
"amend [-p] [-q] [-f fmt] [-t cache] -o options filename")
|
"amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] -o options filename")
|
||||||
STEXI
|
STEXI
|
||||||
@item amend [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
|
@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
|
||||||
@end table
|
@end table
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
491
qemu-img.c
491
qemu-img.c
|
@ -27,8 +27,10 @@
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "qapi/qmp/qjson.h"
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "qemu/config-file.h"
|
||||||
#include "qemu/option.h"
|
#include "qemu/option.h"
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
|
#include "qom/object_interfaces.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "sysemu/block-backend.h"
|
#include "sysemu/block-backend.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
|
@ -47,6 +49,8 @@ typedef struct img_cmd_t {
|
||||||
enum {
|
enum {
|
||||||
OPTION_OUTPUT = 256,
|
OPTION_OUTPUT = 256,
|
||||||
OPTION_BACKING_CHAIN = 257,
|
OPTION_BACKING_CHAIN = 257,
|
||||||
|
OPTION_OBJECT = 258,
|
||||||
|
OPTION_IMAGE_OPTS = 259,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum OutputFormat {
|
typedef enum OutputFormat {
|
||||||
|
@ -94,6 +98,10 @@ static void QEMU_NORETURN help(void)
|
||||||
"\n"
|
"\n"
|
||||||
"Command parameters:\n"
|
"Command parameters:\n"
|
||||||
" 'filename' is a disk image filename\n"
|
" 'filename' is a disk image filename\n"
|
||||||
|
" 'objectdef' is a QEMU user creatable object definition. See the qemu(1)\n"
|
||||||
|
" manual page for a description of the object properties. The most common\n"
|
||||||
|
" object type is a 'secret', which is used to supply passwords and/or\n"
|
||||||
|
" encryption keys.\n"
|
||||||
" 'fmt' is the disk image format. It is guessed automatically in most cases\n"
|
" 'fmt' is the disk image format. It is guessed automatically in most cases\n"
|
||||||
" 'cache' is the cache mode used to write the output disk image, the valid\n"
|
" 'cache' is the cache mode used to write the output disk image, the valid\n"
|
||||||
" options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n"
|
" options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n"
|
||||||
|
@ -154,6 +162,24 @@ static void QEMU_NORETURN help(void)
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QemuOptsList qemu_object_opts = {
|
||||||
|
.name = "object",
|
||||||
|
.implied_opt_name = "qom-type",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
|
||||||
|
.desc = {
|
||||||
|
{ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static QemuOptsList qemu_source_opts = {
|
||||||
|
.name = "source",
|
||||||
|
.implied_opt_name = "file",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_source_opts.head),
|
||||||
|
.desc = {
|
||||||
|
{ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
static int GCC_FMT_ATTR(2, 3) qprintf(bool quiet, const char *fmt, ...)
|
static int GCC_FMT_ATTR(2, 3) qprintf(bool quiet, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
@ -196,13 +222,56 @@ static int print_block_option_help(const char *filename, const char *fmt)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BlockBackend *img_open(const char *id, const char *filename,
|
|
||||||
const char *fmt, int flags,
|
static int img_open_password(BlockBackend *blk, const char *filename,
|
||||||
bool require_io, bool quiet)
|
bool require_io, bool quiet)
|
||||||
{
|
{
|
||||||
BlockBackend *blk;
|
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
char password[256];
|
char password[256];
|
||||||
|
|
||||||
|
bs = blk_bs(blk);
|
||||||
|
if (bdrv_is_encrypted(bs) && require_io) {
|
||||||
|
qprintf(quiet, "Disk image '%s' is encrypted.\n", filename);
|
||||||
|
if (qemu_read_password(password, sizeof(password)) < 0) {
|
||||||
|
error_report("No password given");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (bdrv_set_key(bs, password) < 0) {
|
||||||
|
error_report("invalid password");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static BlockBackend *img_open_opts(const char *id,
|
||||||
|
const char *optstr,
|
||||||
|
QemuOpts *opts, int flags,
|
||||||
|
bool require_io, bool quiet)
|
||||||
|
{
|
||||||
|
QDict *options;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
BlockBackend *blk;
|
||||||
|
options = qemu_opts_to_qdict(opts, NULL);
|
||||||
|
blk = blk_new_open(id, NULL, NULL, options, flags, &local_err);
|
||||||
|
if (!blk) {
|
||||||
|
error_reportf_err(local_err, "Could not open '%s'", optstr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (img_open_password(blk, optstr, require_io, quiet) < 0) {
|
||||||
|
blk_unref(blk);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return blk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockBackend *img_open_file(const char *id, const char *filename,
|
||||||
|
const char *fmt, int flags,
|
||||||
|
bool require_io, bool quiet)
|
||||||
|
{
|
||||||
|
BlockBackend *blk;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
QDict *options = NULL;
|
QDict *options = NULL;
|
||||||
|
|
||||||
|
@ -214,27 +283,43 @@ static BlockBackend *img_open(const char *id, const char *filename,
|
||||||
blk = blk_new_open(id, filename, NULL, options, flags, &local_err);
|
blk = blk_new_open(id, filename, NULL, options, flags, &local_err);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
error_reportf_err(local_err, "Could not open '%s': ", filename);
|
error_reportf_err(local_err, "Could not open '%s': ", filename);
|
||||||
goto fail;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bs = blk_bs(blk);
|
if (img_open_password(blk, filename, require_io, quiet) < 0) {
|
||||||
if (bdrv_is_encrypted(bs) && require_io) {
|
blk_unref(blk);
|
||||||
qprintf(quiet, "Disk image '%s' is encrypted.\n", filename);
|
return NULL;
|
||||||
if (qemu_read_password(password, sizeof(password)) < 0) {
|
|
||||||
error_report("No password given");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (bdrv_set_key(bs, password) < 0) {
|
|
||||||
error_report("invalid password");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return blk;
|
return blk;
|
||||||
fail:
|
|
||||||
blk_unref(blk);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static BlockBackend *img_open(const char *id,
|
||||||
|
bool image_opts,
|
||||||
|
const char *filename,
|
||||||
|
const char *fmt, int flags,
|
||||||
|
bool require_io, bool quiet)
|
||||||
|
{
|
||||||
|
BlockBackend *blk;
|
||||||
|
if (image_opts) {
|
||||||
|
QemuOpts *opts;
|
||||||
|
if (fmt) {
|
||||||
|
error_report("--image-opts and --format are mutually exclusive");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
|
||||||
|
filename, true);
|
||||||
|
if (!opts) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
blk = img_open_opts(id, filename, opts, flags, true, quiet);
|
||||||
|
} else {
|
||||||
|
blk = img_open_file(id, filename, fmt, flags, true, quiet);
|
||||||
|
}
|
||||||
|
return blk;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int add_old_style_options(const char *fmt, QemuOpts *opts,
|
static int add_old_style_options(const char *fmt, QemuOpts *opts,
|
||||||
const char *base_filename,
|
const char *base_filename,
|
||||||
const char *base_fmt)
|
const char *base_fmt)
|
||||||
|
@ -275,7 +360,13 @@ static int img_create(int argc, char **argv)
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "F:b:f:he6o:q");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "F:b:f:he6o:q",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -317,6 +408,14 @@ static int img_create(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,6 +431,13 @@ static int img_create(int argc, char **argv)
|
||||||
}
|
}
|
||||||
optind++;
|
optind++;
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get image size, if specified */
|
/* Get image size, if specified */
|
||||||
if (optind < argc) {
|
if (optind < argc) {
|
||||||
int64_t sval;
|
int64_t sval;
|
||||||
|
@ -489,6 +595,8 @@ static int img_check(int argc, char **argv)
|
||||||
int flags = BDRV_O_FLAGS | BDRV_O_CHECK;
|
int flags = BDRV_O_FLAGS | BDRV_O_CHECK;
|
||||||
ImageCheck *check;
|
ImageCheck *check;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
output = NULL;
|
output = NULL;
|
||||||
|
@ -500,6 +608,8 @@ static int img_check(int argc, char **argv)
|
||||||
{"format", required_argument, 0, 'f'},
|
{"format", required_argument, 0, 'f'},
|
||||||
{"repair", required_argument, 0, 'r'},
|
{"repair", required_argument, 0, 'r'},
|
||||||
{"output", required_argument, 0, OPTION_OUTPUT},
|
{"output", required_argument, 0, OPTION_OUTPUT},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
c = getopt_long(argc, argv, "hf:r:T:q",
|
c = getopt_long(argc, argv, "hf:r:T:q",
|
||||||
|
@ -536,6 +646,17 @@ static int img_check(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (optind != argc - 1) {
|
if (optind != argc - 1) {
|
||||||
|
@ -552,13 +673,20 @@ static int img_check(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
ret = bdrv_parse_cache_flags(cache, &flags);
|
ret = bdrv_parse_cache_flags(cache, &flags);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_report("Invalid source cache option: %s", cache);
|
error_report("Invalid source cache option: %s", cache);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt, flags, true, quiet);
|
blk = img_open("image", image_opts, filename, fmt, flags, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -670,12 +798,20 @@ static int img_commit(int argc, char **argv)
|
||||||
bool progress = false, quiet = false, drop = false;
|
bool progress = false, quiet = false, drop = false;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
CommonBlockJobCBInfo cbi;
|
CommonBlockJobCBInfo cbi;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
cache = BDRV_DEFAULT_CACHE;
|
cache = BDRV_DEFAULT_CACHE;
|
||||||
base = NULL;
|
base = NULL;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "f:ht:b:dpq");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "f:ht:b:dpq",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -704,6 +840,17 @@ static int img_commit(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,6 +864,13 @@ static int img_commit(int argc, char **argv)
|
||||||
}
|
}
|
||||||
filename = argv[optind++];
|
filename = argv[optind++];
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
flags = BDRV_O_RDWR | BDRV_O_UNMAP;
|
flags = BDRV_O_RDWR | BDRV_O_UNMAP;
|
||||||
ret = bdrv_parse_cache_flags(cache, &flags);
|
ret = bdrv_parse_cache_flags(cache, &flags);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -724,7 +878,7 @@ static int img_commit(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt, flags, true, quiet);
|
blk = img_open("image", image_opts, filename, fmt, flags, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -973,10 +1127,19 @@ static int img_compare(int argc, char **argv)
|
||||||
int64_t nb_sectors;
|
int64_t nb_sectors;
|
||||||
int c, pnum;
|
int c, pnum;
|
||||||
uint64_t progress_base;
|
uint64_t progress_base;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
cache = BDRV_DEFAULT_CACHE;
|
cache = BDRV_DEFAULT_CACHE;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
c = getopt(argc, argv, "hf:F:T:pqs");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "hf:F:T:pqs",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1003,6 +1166,18 @@ static int img_compare(int argc, char **argv)
|
||||||
case 's':
|
case 's':
|
||||||
strict = true;
|
strict = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
ret = 2;
|
||||||
|
goto out4;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1018,6 +1193,14 @@ static int img_compare(int argc, char **argv)
|
||||||
filename1 = argv[optind++];
|
filename1 = argv[optind++];
|
||||||
filename2 = argv[optind++];
|
filename2 = argv[optind++];
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
ret = 2;
|
||||||
|
goto out4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize before goto out */
|
/* Initialize before goto out */
|
||||||
qemu_progress_init(progress, 2.0);
|
qemu_progress_init(progress, 2.0);
|
||||||
|
|
||||||
|
@ -1029,18 +1212,18 @@ static int img_compare(int argc, char **argv)
|
||||||
goto out3;
|
goto out3;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk1 = img_open("image_1", filename1, fmt1, flags, true, quiet);
|
blk1 = img_open("image_1", image_opts, filename1, fmt1, flags, true, quiet);
|
||||||
if (!blk1) {
|
if (!blk1) {
|
||||||
ret = 2;
|
ret = 2;
|
||||||
goto out3;
|
goto out3;
|
||||||
}
|
}
|
||||||
bs1 = blk_bs(blk1);
|
|
||||||
|
|
||||||
blk2 = img_open("image_2", filename2, fmt2, flags, true, quiet);
|
blk2 = img_open("image_2", image_opts, filename2, fmt2, flags, true, quiet);
|
||||||
if (!blk2) {
|
if (!blk2) {
|
||||||
ret = 2;
|
ret = 2;
|
||||||
goto out2;
|
goto out2;
|
||||||
}
|
}
|
||||||
|
bs1 = blk_bs(blk1);
|
||||||
bs2 = blk_bs(blk2);
|
bs2 = blk_bs(blk2);
|
||||||
|
|
||||||
buf1 = blk_blockalign(blk1, IO_BUF_SIZE);
|
buf1 = blk_blockalign(blk1, IO_BUF_SIZE);
|
||||||
|
@ -1225,6 +1408,7 @@ out2:
|
||||||
blk_unref(blk1);
|
blk_unref(blk1);
|
||||||
out3:
|
out3:
|
||||||
qemu_progress_end();
|
qemu_progress_end();
|
||||||
|
out4:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1546,6 +1730,7 @@ static int img_convert(int argc, char **argv)
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
QemuOpts *sn_opts = NULL;
|
QemuOpts *sn_opts = NULL;
|
||||||
ImgConvertState state;
|
ImgConvertState state;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
out_fmt = "raw";
|
out_fmt = "raw";
|
||||||
|
@ -1555,7 +1740,14 @@ static int img_convert(int argc, char **argv)
|
||||||
compress = 0;
|
compress = 0;
|
||||||
skip_create = 0;
|
skip_create = 0;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1646,16 +1838,32 @@ static int img_convert(int argc, char **argv)
|
||||||
case 'n':
|
case 'n':
|
||||||
skip_create = 1;
|
skip_create = 1;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT:
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
goto fail_getopt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
goto fail_getopt;
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize before goto out */
|
/* Initialize before goto out */
|
||||||
if (quiet) {
|
if (quiet) {
|
||||||
progress = 0;
|
progress = 0;
|
||||||
}
|
}
|
||||||
qemu_progress_init(progress, 1.0);
|
qemu_progress_init(progress, 1.0);
|
||||||
|
|
||||||
|
|
||||||
bs_n = argc - optind - 1;
|
bs_n = argc - optind - 1;
|
||||||
out_filename = bs_n >= 1 ? argv[argc - 1] : NULL;
|
out_filename = bs_n >= 1 ? argv[argc - 1] : NULL;
|
||||||
|
|
||||||
|
@ -1693,8 +1901,8 @@ static int img_convert(int argc, char **argv)
|
||||||
for (bs_i = 0; bs_i < bs_n; bs_i++) {
|
for (bs_i = 0; bs_i < bs_n; bs_i++) {
|
||||||
char *id = bs_n > 1 ? g_strdup_printf("source_%d", bs_i)
|
char *id = bs_n > 1 ? g_strdup_printf("source_%d", bs_i)
|
||||||
: g_strdup("source");
|
: g_strdup("source");
|
||||||
blk[bs_i] = img_open(id, argv[optind + bs_i], fmt, src_flags,
|
blk[bs_i] = img_open(id, image_opts, argv[optind + bs_i],
|
||||||
true, quiet);
|
fmt, src_flags, true, quiet);
|
||||||
g_free(id);
|
g_free(id);
|
||||||
if (!blk[bs_i]) {
|
if (!blk[bs_i]) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
|
@ -1835,7 +2043,13 @@ static int img_convert(int argc, char **argv)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
out_blk = img_open("target", out_filename, out_fmt, flags, true, quiet);
|
/* XXX we should allow --image-opts to trigger use of
|
||||||
|
* img_open() here, but then we have trouble with
|
||||||
|
* the bdrv_create() call which takes different params.
|
||||||
|
* Not critical right now, so fix can wait...
|
||||||
|
*/
|
||||||
|
out_blk = img_open_file("target", out_filename,
|
||||||
|
out_fmt, flags, true, quiet);
|
||||||
if (!out_blk) {
|
if (!out_blk) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2001,7 +2215,8 @@ static gboolean str_equal_func(gconstpointer a, gconstpointer b)
|
||||||
* image file. If there was an error a message will have been printed to
|
* image file. If there was an error a message will have been printed to
|
||||||
* stderr.
|
* stderr.
|
||||||
*/
|
*/
|
||||||
static ImageInfoList *collect_image_info_list(const char *filename,
|
static ImageInfoList *collect_image_info_list(bool image_opts,
|
||||||
|
const char *filename,
|
||||||
const char *fmt,
|
const char *fmt,
|
||||||
bool chain)
|
bool chain)
|
||||||
{
|
{
|
||||||
|
@ -2025,8 +2240,9 @@ static ImageInfoList *collect_image_info_list(const char *filename,
|
||||||
}
|
}
|
||||||
g_hash_table_insert(filenames, (gpointer)filename, NULL);
|
g_hash_table_insert(filenames, (gpointer)filename, NULL);
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt,
|
blk = img_open("image", image_opts, filename, fmt,
|
||||||
BDRV_O_FLAGS | BDRV_O_NO_BACKING, false, false);
|
BDRV_O_FLAGS | BDRV_O_NO_BACKING,
|
||||||
|
false, false);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
@ -2077,6 +2293,8 @@ static int img_info(int argc, char **argv)
|
||||||
bool chain = false;
|
bool chain = false;
|
||||||
const char *filename, *fmt, *output;
|
const char *filename, *fmt, *output;
|
||||||
ImageInfoList *list;
|
ImageInfoList *list;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
output = NULL;
|
output = NULL;
|
||||||
|
@ -2087,6 +2305,8 @@ static int img_info(int argc, char **argv)
|
||||||
{"format", required_argument, 0, 'f'},
|
{"format", required_argument, 0, 'f'},
|
||||||
{"output", required_argument, 0, OPTION_OUTPUT},
|
{"output", required_argument, 0, OPTION_OUTPUT},
|
||||||
{"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN},
|
{"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
c = getopt_long(argc, argv, "f:h",
|
c = getopt_long(argc, argv, "f:h",
|
||||||
|
@ -2108,6 +2328,17 @@ static int img_info(int argc, char **argv)
|
||||||
case OPTION_BACKING_CHAIN:
|
case OPTION_BACKING_CHAIN:
|
||||||
chain = true;
|
chain = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (optind != argc - 1) {
|
if (optind != argc - 1) {
|
||||||
|
@ -2124,7 +2355,14 @@ static int img_info(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
list = collect_image_info_list(filename, fmt, chain);
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
list = collect_image_info_list(image_opts, filename, fmt, chain);
|
||||||
if (!list) {
|
if (!list) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -2194,6 +2432,7 @@ static int get_block_status(BlockDriverState *bs, int64_t sector_num,
|
||||||
int64_t ret;
|
int64_t ret;
|
||||||
int depth;
|
int depth;
|
||||||
BlockDriverState *file;
|
BlockDriverState *file;
|
||||||
|
bool has_offset;
|
||||||
|
|
||||||
/* As an optimization, we could cache the current range of unallocated
|
/* As an optimization, we could cache the current range of unallocated
|
||||||
* clusters in each file of the chain, and avoid querying the same
|
* clusters in each file of the chain, and avoid querying the same
|
||||||
|
@ -2220,17 +2459,20 @@ static int get_block_status(BlockDriverState *bs, int64_t sector_num,
|
||||||
depth++;
|
depth++;
|
||||||
}
|
}
|
||||||
|
|
||||||
e->start = sector_num * BDRV_SECTOR_SIZE;
|
has_offset = !!(ret & BDRV_BLOCK_OFFSET_VALID);
|
||||||
e->length = nb_sectors * BDRV_SECTOR_SIZE;
|
|
||||||
e->data = !!(ret & BDRV_BLOCK_DATA);
|
*e = (MapEntry) {
|
||||||
e->zero = !!(ret & BDRV_BLOCK_ZERO);
|
.start = sector_num * BDRV_SECTOR_SIZE,
|
||||||
e->offset = ret & BDRV_BLOCK_OFFSET_MASK;
|
.length = nb_sectors * BDRV_SECTOR_SIZE,
|
||||||
e->has_offset = !!(ret & BDRV_BLOCK_OFFSET_VALID);
|
.data = !!(ret & BDRV_BLOCK_DATA),
|
||||||
e->depth = depth;
|
.zero = !!(ret & BDRV_BLOCK_ZERO),
|
||||||
if (file && e->has_offset) {
|
.offset = ret & BDRV_BLOCK_OFFSET_MASK,
|
||||||
e->has_filename = true;
|
.has_offset = has_offset,
|
||||||
e->filename = file->filename;
|
.depth = depth,
|
||||||
}
|
.has_filename = file && has_offset,
|
||||||
|
.filename = file && has_offset ? file->filename : NULL,
|
||||||
|
};
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2265,6 +2507,8 @@ static int img_map(int argc, char **argv)
|
||||||
int64_t length;
|
int64_t length;
|
||||||
MapEntry curr = { .length = 0 }, next;
|
MapEntry curr = { .length = 0 }, next;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
output = NULL;
|
output = NULL;
|
||||||
|
@ -2274,6 +2518,8 @@ static int img_map(int argc, char **argv)
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
{"format", required_argument, 0, 'f'},
|
{"format", required_argument, 0, 'f'},
|
||||||
{"output", required_argument, 0, OPTION_OUTPUT},
|
{"output", required_argument, 0, OPTION_OUTPUT},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
c = getopt_long(argc, argv, "f:h",
|
c = getopt_long(argc, argv, "f:h",
|
||||||
|
@ -2292,6 +2538,17 @@ static int img_map(int argc, char **argv)
|
||||||
case OPTION_OUTPUT:
|
case OPTION_OUTPUT:
|
||||||
output = optarg;
|
output = optarg;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (optind != argc - 1) {
|
if (optind != argc - 1) {
|
||||||
|
@ -2308,7 +2565,15 @@ static int img_map(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt, BDRV_O_FLAGS, true, false);
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blk = img_open("image", image_opts, filename, fmt,
|
||||||
|
BDRV_O_FLAGS, true, false);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -2370,11 +2635,19 @@ static int img_snapshot(int argc, char **argv)
|
||||||
qemu_timeval tv;
|
qemu_timeval tv;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
bdrv_oflags = BDRV_O_FLAGS | BDRV_O_RDWR;
|
bdrv_oflags = BDRV_O_FLAGS | BDRV_O_RDWR;
|
||||||
/* Parse commandline parameters */
|
/* Parse commandline parameters */
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "la:c:d:hq");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "la:c:d:hq",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2418,6 +2691,17 @@ static int img_snapshot(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2426,8 +2710,16 @@ static int img_snapshot(int argc, char **argv)
|
||||||
}
|
}
|
||||||
filename = argv[optind++];
|
filename = argv[optind++];
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &err)) {
|
||||||
|
error_report_err(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Open the image */
|
/* Open the image */
|
||||||
blk = img_open("image", filename, NULL, bdrv_oflags, true, quiet);
|
blk = img_open("image", image_opts, filename, NULL,
|
||||||
|
bdrv_oflags, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -2491,6 +2783,7 @@ static int img_rebase(int argc, char **argv)
|
||||||
int progress = 0;
|
int progress = 0;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
/* Parse commandline parameters */
|
/* Parse commandline parameters */
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
|
@ -2499,7 +2792,14 @@ static int img_rebase(int argc, char **argv)
|
||||||
out_baseimg = NULL;
|
out_baseimg = NULL;
|
||||||
out_basefmt = NULL;
|
out_basefmt = NULL;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "hf:F:b:upt:T:q");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "hf:F:b:upt:T:q",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2532,6 +2832,17 @@ static int img_rebase(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2547,6 +2858,13 @@ static int img_rebase(int argc, char **argv)
|
||||||
}
|
}
|
||||||
filename = argv[optind++];
|
filename = argv[optind++];
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
qemu_progress_init(progress, 2.0);
|
qemu_progress_init(progress, 2.0);
|
||||||
qemu_progress_print(0, 100);
|
qemu_progress_print(0, 100);
|
||||||
|
|
||||||
|
@ -2570,7 +2888,7 @@ static int img_rebase(int argc, char **argv)
|
||||||
* Ignore the old backing file for unsafe rebase in case we want to correct
|
* Ignore the old backing file for unsafe rebase in case we want to correct
|
||||||
* the reference to a renamed or moved backing file.
|
* the reference to a renamed or moved backing file.
|
||||||
*/
|
*/
|
||||||
blk = img_open("image", filename, fmt, flags, true, quiet);
|
blk = img_open("image", image_opts, filename, fmt, flags, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2807,6 +3125,8 @@ static int img_resize(int argc, char **argv)
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
BlockBackend *blk = NULL;
|
BlockBackend *blk = NULL;
|
||||||
QemuOpts *param;
|
QemuOpts *param;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
|
||||||
static QemuOptsList resize_options = {
|
static QemuOptsList resize_options = {
|
||||||
.name = "resize_options",
|
.name = "resize_options",
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(resize_options.head),
|
.head = QTAILQ_HEAD_INITIALIZER(resize_options.head),
|
||||||
|
@ -2820,6 +3140,7 @@ static int img_resize(int argc, char **argv)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
/* Remove size from argv manually so that negative numbers are not treated
|
/* Remove size from argv manually so that negative numbers are not treated
|
||||||
* as options by getopt. */
|
* as options by getopt. */
|
||||||
|
@ -2833,7 +3154,14 @@ static int img_resize(int argc, char **argv)
|
||||||
/* Parse getopt arguments */
|
/* Parse getopt arguments */
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
c = getopt(argc, argv, "f:hq");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "f:hq",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2848,6 +3176,17 @@ static int img_resize(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *opts;
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (optind != argc - 1) {
|
if (optind != argc - 1) {
|
||||||
|
@ -2855,6 +3194,13 @@ static int img_resize(int argc, char **argv)
|
||||||
}
|
}
|
||||||
filename = argv[optind++];
|
filename = argv[optind++];
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Choose grow, shrink, or absolute resize mode */
|
/* Choose grow, shrink, or absolute resize mode */
|
||||||
switch (size[0]) {
|
switch (size[0]) {
|
||||||
case '+':
|
case '+':
|
||||||
|
@ -2882,8 +3228,8 @@ static int img_resize(int argc, char **argv)
|
||||||
n = qemu_opt_get_size(param, BLOCK_OPT_SIZE, 0);
|
n = qemu_opt_get_size(param, BLOCK_OPT_SIZE, 0);
|
||||||
qemu_opts_del(param);
|
qemu_opts_del(param);
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR,
|
blk = img_open("image", image_opts, filename, fmt,
|
||||||
true, quiet);
|
BDRV_O_FLAGS | BDRV_O_RDWR, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2942,10 +3288,19 @@ static int img_amend(int argc, char **argv)
|
||||||
bool quiet = false, progress = false;
|
bool quiet = false, progress = false;
|
||||||
BlockBackend *blk = NULL;
|
BlockBackend *blk = NULL;
|
||||||
BlockDriverState *bs = NULL;
|
BlockDriverState *bs = NULL;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
bool image_opts = false;
|
||||||
|
|
||||||
cache = BDRV_DEFAULT_CACHE;
|
cache = BDRV_DEFAULT_CACHE;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
c = getopt(argc, argv, "ho:f:t:pq");
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"object", required_argument, 0, OPTION_OBJECT},
|
||||||
|
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "ho:f:t:pq",
|
||||||
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2981,6 +3336,17 @@ static int img_amend(int argc, char **argv)
|
||||||
case 'q':
|
case 'q':
|
||||||
quiet = true;
|
quiet = true;
|
||||||
break;
|
break;
|
||||||
|
case OPTION_OBJECT:
|
||||||
|
opts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!opts) {
|
||||||
|
ret = -1;
|
||||||
|
goto out_no_progress;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
image_opts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2988,6 +3354,14 @@ static int img_amend(int argc, char **argv)
|
||||||
error_exit("Must specify options (-o)");
|
error_exit("Must specify options (-o)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_err)) {
|
||||||
|
error_report_err(local_err);
|
||||||
|
ret = -1;
|
||||||
|
goto out_no_progress;
|
||||||
|
}
|
||||||
|
|
||||||
if (quiet) {
|
if (quiet) {
|
||||||
progress = false;
|
progress = false;
|
||||||
}
|
}
|
||||||
|
@ -3014,7 +3388,7 @@ static int img_amend(int argc, char **argv)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
blk = img_open("image", filename, fmt, flags, true, quiet);
|
blk = img_open("image", image_opts, filename, fmt, flags, true, quiet);
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -3111,6 +3485,9 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
cmdname = argv[1];
|
cmdname = argv[1];
|
||||||
|
|
||||||
|
qemu_add_opts(&qemu_object_opts);
|
||||||
|
qemu_add_opts(&qemu_source_opts);
|
||||||
|
|
||||||
/* find the command */
|
/* find the command */
|
||||||
for (cmd = img_cmds; cmd->name != NULL; cmd++) {
|
for (cmd = img_cmds; cmd->name != NULL; cmd++) {
|
||||||
if (!strcmp(cmdname, cmd->name)) {
|
if (!strcmp(cmdname, cmd->name)) {
|
||||||
|
|
|
@ -24,6 +24,20 @@ Command parameters:
|
||||||
@table @var
|
@table @var
|
||||||
@item filename
|
@item filename
|
||||||
is a disk image filename
|
is a disk image filename
|
||||||
|
|
||||||
|
@item --object @var{objectdef}
|
||||||
|
|
||||||
|
is a QEMU user creatable object definition. See the @code{qemu(1)} manual
|
||||||
|
page for a description of the object properties. The most common object
|
||||||
|
type is a @code{secret}, which is used to supply passwords and/or encryption
|
||||||
|
keys.
|
||||||
|
|
||||||
|
@item --image-opts
|
||||||
|
|
||||||
|
Indicates that the @var{filename} parameter is to be interpreted as a
|
||||||
|
full option string, not a plain filename. This parameter is mutually
|
||||||
|
exclusive with the @var{-f} and @var{-F} parameters.
|
||||||
|
|
||||||
@item fmt
|
@item fmt
|
||||||
is the disk image format. It is guessed automatically in most cases. See below
|
is the disk image format. It is guessed automatically in most cases. See below
|
||||||
for a description of the supported disk formats.
|
for a description of the supported disk formats.
|
||||||
|
|
115
qemu-io.c
115
qemu-io.c
|
@ -18,6 +18,7 @@
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "qemu/readline.h"
|
#include "qemu/readline.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
|
#include "qom/object_interfaces.h"
|
||||||
#include "sysemu/block-backend.h"
|
#include "sysemu/block-backend.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "trace/control.h"
|
#include "trace/control.h"
|
||||||
|
@ -31,6 +32,7 @@ static BlockBackend *qemuio_blk;
|
||||||
/* qemu-io commands passed using -c */
|
/* qemu-io commands passed using -c */
|
||||||
static int ncmdline;
|
static int ncmdline;
|
||||||
static char **cmdline;
|
static char **cmdline;
|
||||||
|
static bool imageOpts;
|
||||||
|
|
||||||
static ReadLineState *readline_state;
|
static ReadLineState *readline_state;
|
||||||
|
|
||||||
|
@ -150,6 +152,10 @@ static int open_f(BlockBackend *blk, int argc, char **argv)
|
||||||
readonly = 1;
|
readonly = 1;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
|
if (imageOpts) {
|
||||||
|
printf("--image-opts and 'open -o' are mutually exclusive\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!qemu_opts_parse_noisily(&empty_opts, optarg, false)) {
|
if (!qemu_opts_parse_noisily(&empty_opts, optarg, false)) {
|
||||||
qemu_opts_reset(&empty_opts);
|
qemu_opts_reset(&empty_opts);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -165,6 +171,14 @@ static int open_f(BlockBackend *blk, int argc, char **argv)
|
||||||
flags |= BDRV_O_RDWR;
|
flags |= BDRV_O_RDWR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imageOpts && (optind == argc - 1)) {
|
||||||
|
if (!qemu_opts_parse_noisily(&empty_opts, argv[optind], false)) {
|
||||||
|
qemu_opts_reset(&empty_opts);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
optind++;
|
||||||
|
}
|
||||||
|
|
||||||
qopts = qemu_opts_find(&empty_opts, NULL);
|
qopts = qemu_opts_find(&empty_opts, NULL);
|
||||||
opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
|
opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
|
||||||
qemu_opts_reset(&empty_opts);
|
qemu_opts_reset(&empty_opts);
|
||||||
|
@ -200,6 +214,8 @@ static void usage(const char *name)
|
||||||
"Usage: %s [-h] [-V] [-rsnm] [-f FMT] [-c STRING] ... [file]\n"
|
"Usage: %s [-h] [-V] [-rsnm] [-f FMT] [-c STRING] ... [file]\n"
|
||||||
"QEMU Disk exerciser\n"
|
"QEMU Disk exerciser\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --object OBJECTDEF define an object such as 'secret' for\n"
|
||||||
|
" passwords and/or encryption keys\n"
|
||||||
" -c, --cmd STRING execute command with its arguments\n"
|
" -c, --cmd STRING execute command with its arguments\n"
|
||||||
" from the given string\n"
|
" from the given string\n"
|
||||||
" -f, --format FMT specifies the block driver to use\n"
|
" -f, --format FMT specifies the block driver to use\n"
|
||||||
|
@ -361,24 +377,51 @@ static void reenable_tty_echo(void)
|
||||||
qemu_set_tty_echo(STDIN_FILENO, true);
|
qemu_set_tty_echo(STDIN_FILENO, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
OPTION_OBJECT = 256,
|
||||||
|
OPTION_IMAGE_OPTS = 257,
|
||||||
|
};
|
||||||
|
|
||||||
|
static QemuOptsList qemu_object_opts = {
|
||||||
|
.name = "object",
|
||||||
|
.implied_opt_name = "qom-type",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
|
||||||
|
.desc = {
|
||||||
|
{ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static QemuOptsList file_opts = {
|
||||||
|
.name = "file",
|
||||||
|
.implied_opt_name = "file",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(file_opts.head),
|
||||||
|
.desc = {
|
||||||
|
/* no elements => accept any params */
|
||||||
|
{ /* end of list */ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int readonly = 0;
|
int readonly = 0;
|
||||||
const char *sopt = "hVc:d:f:rsnmgkt:T:";
|
const char *sopt = "hVc:d:f:rsnmgkt:T:";
|
||||||
const struct option lopt[] = {
|
const struct option lopt[] = {
|
||||||
{ "help", 0, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "version", 0, NULL, 'V' },
|
{ "version", no_argument, NULL, 'V' },
|
||||||
{ "offset", 1, NULL, 'o' },
|
{ "offset", required_argument, NULL, 'o' },
|
||||||
{ "cmd", 1, NULL, 'c' },
|
{ "cmd", required_argument, NULL, 'c' },
|
||||||
{ "format", 1, NULL, 'f' },
|
{ "format", required_argument, NULL, 'f' },
|
||||||
{ "read-only", 0, NULL, 'r' },
|
{ "read-only", no_argument, NULL, 'r' },
|
||||||
{ "snapshot", 0, NULL, 's' },
|
{ "snapshot", no_argument, NULL, 's' },
|
||||||
{ "nocache", 0, NULL, 'n' },
|
{ "nocache", no_argument, NULL, 'n' },
|
||||||
{ "misalign", 0, NULL, 'm' },
|
{ "misalign", no_argument, NULL, 'm' },
|
||||||
{ "native-aio", 0, NULL, 'k' },
|
{ "native-aio", no_argument, NULL, 'k' },
|
||||||
{ "discard", 1, NULL, 'd' },
|
{ "discard", required_argument, NULL, 'd' },
|
||||||
{ "cache", 1, NULL, 't' },
|
{ "cache", required_argument, NULL, 't' },
|
||||||
{ "trace", 1, NULL, 'T' },
|
{ "trace", required_argument, NULL, 'T' },
|
||||||
|
{ "object", required_argument, NULL, OPTION_OBJECT },
|
||||||
|
{ "image-opts", no_argument, NULL, OPTION_IMAGE_OPTS },
|
||||||
{ NULL, 0, NULL, 0 }
|
{ NULL, 0, NULL, 0 }
|
||||||
};
|
};
|
||||||
int c;
|
int c;
|
||||||
|
@ -386,6 +429,7 @@ int main(int argc, char **argv)
|
||||||
int flags = BDRV_O_UNMAP;
|
int flags = BDRV_O_UNMAP;
|
||||||
Error *local_error = NULL;
|
Error *local_error = NULL;
|
||||||
QDict *opts = NULL;
|
QDict *opts = NULL;
|
||||||
|
const char *format = NULL;
|
||||||
|
|
||||||
#ifdef CONFIG_POSIX
|
#ifdef CONFIG_POSIX
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
@ -395,6 +439,7 @@ int main(int argc, char **argv)
|
||||||
qemu_init_exec_dir(argv[0]);
|
qemu_init_exec_dir(argv[0]);
|
||||||
|
|
||||||
module_call_init(MODULE_INIT_QOM);
|
module_call_init(MODULE_INIT_QOM);
|
||||||
|
qemu_add_opts(&qemu_object_opts);
|
||||||
bdrv_init();
|
bdrv_init();
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, sopt, lopt, &opt_index)) != -1) {
|
while ((c = getopt_long(argc, argv, sopt, lopt, &opt_index)) != -1) {
|
||||||
|
@ -412,10 +457,7 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
if (!opts) {
|
format = optarg;
|
||||||
opts = qdict_new();
|
|
||||||
}
|
|
||||||
qdict_put(opts, "driver", qstring_from_str(optarg));
|
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
add_user_command(optarg);
|
add_user_command(optarg);
|
||||||
|
@ -446,6 +488,17 @@ int main(int argc, char **argv)
|
||||||
case 'h':
|
case 'h':
|
||||||
usage(progname);
|
usage(progname);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
case OPTION_OBJECT: {
|
||||||
|
QemuOpts *qopts;
|
||||||
|
qopts = qemu_opts_parse_noisily(&qemu_object_opts,
|
||||||
|
optarg, true);
|
||||||
|
if (!qopts) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OPTION_IMAGE_OPTS:
|
||||||
|
imageOpts = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
usage(progname);
|
usage(progname);
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -457,11 +510,23 @@ int main(int argc, char **argv)
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (format && imageOpts) {
|
||||||
|
error_report("--image-opts and -f are mutually exclusive");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (qemu_init_main_loop(&local_error)) {
|
if (qemu_init_main_loop(&local_error)) {
|
||||||
error_report_err(local_error);
|
error_report_err(local_error);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qemu_opts_foreach(&qemu_object_opts,
|
||||||
|
user_creatable_add_opts_foreach,
|
||||||
|
NULL, &local_error)) {
|
||||||
|
error_report_err(local_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
/* initialize commands */
|
/* initialize commands */
|
||||||
qemuio_add_command(&quit_cmd);
|
qemuio_add_command(&quit_cmd);
|
||||||
qemuio_add_command(&open_cmd);
|
qemuio_add_command(&open_cmd);
|
||||||
|
@ -482,7 +547,21 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((argc - optind) == 1) {
|
if ((argc - optind) == 1) {
|
||||||
openfile(argv[optind], flags, opts);
|
if (imageOpts) {
|
||||||
|
QemuOpts *qopts = NULL;
|
||||||
|
qopts = qemu_opts_parse_noisily(&file_opts, argv[optind], false);
|
||||||
|
if (!qopts) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
opts = qemu_opts_to_qdict(qopts, NULL);
|
||||||
|
openfile(NULL, flags, opts);
|
||||||
|
} else {
|
||||||
|
if (format) {
|
||||||
|
opts = qdict_new();
|
||||||
|
qdict_put(opts, "driver", qstring_from_str(format));
|
||||||
|
}
|
||||||
|
openfile(argv[optind], flags, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
command_loop();
|
command_loop();
|
||||||
|
|
||||||
|
|
104
qemu-nbd.c
104
qemu-nbd.c
|
@ -37,12 +37,13 @@
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#define SOCKET_PATH "/var/lock/qemu-nbd-%s"
|
#define SOCKET_PATH "/var/lock/qemu-nbd-%s"
|
||||||
#define QEMU_NBD_OPT_CACHE 1
|
#define QEMU_NBD_OPT_CACHE 256
|
||||||
#define QEMU_NBD_OPT_AIO 2
|
#define QEMU_NBD_OPT_AIO 257
|
||||||
#define QEMU_NBD_OPT_DISCARD 3
|
#define QEMU_NBD_OPT_DISCARD 258
|
||||||
#define QEMU_NBD_OPT_DETECT_ZEROES 4
|
#define QEMU_NBD_OPT_DETECT_ZEROES 259
|
||||||
#define QEMU_NBD_OPT_OBJECT 5
|
#define QEMU_NBD_OPT_OBJECT 260
|
||||||
#define QEMU_NBD_OPT_TLSCREDS 6
|
#define QEMU_NBD_OPT_TLSCREDS 261
|
||||||
|
#define QEMU_NBD_OPT_IMAGE_OPTS 262
|
||||||
|
|
||||||
static NBDExport *exp;
|
static NBDExport *exp;
|
||||||
static bool newproto;
|
static bool newproto;
|
||||||
|
@ -105,6 +106,7 @@ static void usage(const char *name)
|
||||||
" --aio=MODE set AIO mode (native or threads)\n"
|
" --aio=MODE set AIO mode (native or threads)\n"
|
||||||
" --discard=MODE set discard mode (ignore, unmap)\n"
|
" --discard=MODE set discard mode (ignore, unmap)\n"
|
||||||
" --detect-zeroes=MODE set detect-zeroes mode (off, on, unmap)\n"
|
" --detect-zeroes=MODE set detect-zeroes mode (off, on, unmap)\n"
|
||||||
|
" --image-opts treat FILE as a full set of image options\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Report bugs to <qemu-devel@nongnu.org>\n"
|
"Report bugs to <qemu-devel@nongnu.org>\n"
|
||||||
, name, NBD_DEFAULT_PORT, "DEVICE");
|
, name, NBD_DEFAULT_PORT, "DEVICE");
|
||||||
|
@ -394,6 +396,16 @@ static SocketAddress *nbd_build_socket_address(const char *sockpath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static QemuOptsList file_opts = {
|
||||||
|
.name = "file",
|
||||||
|
.implied_opt_name = "file",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(file_opts.head),
|
||||||
|
.desc = {
|
||||||
|
/* no elements => accept any params */
|
||||||
|
{ /* end of list */ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
static QemuOptsList qemu_object_opts = {
|
static QemuOptsList qemu_object_opts = {
|
||||||
.name = "object",
|
.name = "object",
|
||||||
.implied_opt_name = "qom-type",
|
.implied_opt_name = "qom-type",
|
||||||
|
@ -451,30 +463,32 @@ int main(int argc, char **argv)
|
||||||
const char *sn_id_or_name = NULL;
|
const char *sn_id_or_name = NULL;
|
||||||
const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:";
|
const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:";
|
||||||
struct option lopt[] = {
|
struct option lopt[] = {
|
||||||
{ "help", 0, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "version", 0, NULL, 'V' },
|
{ "version", no_argument, NULL, 'V' },
|
||||||
{ "bind", 1, NULL, 'b' },
|
{ "bind", required_argument, NULL, 'b' },
|
||||||
{ "port", 1, NULL, 'p' },
|
{ "port", required_argument, NULL, 'p' },
|
||||||
{ "socket", 1, NULL, 'k' },
|
{ "socket", required_argument, NULL, 'k' },
|
||||||
{ "offset", 1, NULL, 'o' },
|
{ "offset", required_argument, NULL, 'o' },
|
||||||
{ "read-only", 0, NULL, 'r' },
|
{ "read-only", no_argument, NULL, 'r' },
|
||||||
{ "partition", 1, NULL, 'P' },
|
{ "partition", required_argument, NULL, 'P' },
|
||||||
{ "connect", 1, NULL, 'c' },
|
{ "connect", required_argument, NULL, 'c' },
|
||||||
{ "disconnect", 0, NULL, 'd' },
|
{ "disconnect", no_argument, NULL, 'd' },
|
||||||
{ "snapshot", 0, NULL, 's' },
|
{ "snapshot", no_argument, NULL, 's' },
|
||||||
{ "load-snapshot", 1, NULL, 'l' },
|
{ "load-snapshot", required_argument, NULL, 'l' },
|
||||||
{ "nocache", 0, NULL, 'n' },
|
{ "nocache", no_argument, NULL, 'n' },
|
||||||
{ "cache", 1, NULL, QEMU_NBD_OPT_CACHE },
|
{ "cache", required_argument, NULL, QEMU_NBD_OPT_CACHE },
|
||||||
{ "aio", 1, NULL, QEMU_NBD_OPT_AIO },
|
{ "aio", required_argument, NULL, QEMU_NBD_OPT_AIO },
|
||||||
{ "discard", 1, NULL, QEMU_NBD_OPT_DISCARD },
|
{ "discard", required_argument, NULL, QEMU_NBD_OPT_DISCARD },
|
||||||
{ "detect-zeroes", 1, NULL, QEMU_NBD_OPT_DETECT_ZEROES },
|
{ "detect-zeroes", required_argument, NULL,
|
||||||
{ "shared", 1, NULL, 'e' },
|
QEMU_NBD_OPT_DETECT_ZEROES },
|
||||||
{ "format", 1, NULL, 'f' },
|
{ "shared", required_argument, NULL, 'e' },
|
||||||
{ "persistent", 0, NULL, 't' },
|
{ "format", required_argument, NULL, 'f' },
|
||||||
{ "verbose", 0, NULL, 'v' },
|
{ "persistent", no_argument, NULL, 't' },
|
||||||
{ "object", 1, NULL, QEMU_NBD_OPT_OBJECT },
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
{ "export-name", 1, NULL, 'x' },
|
{ "object", required_argument, NULL, QEMU_NBD_OPT_OBJECT },
|
||||||
{ "tls-creds", 1, NULL, QEMU_NBD_OPT_TLSCREDS },
|
{ "export-name", required_argument, NULL, 'x' },
|
||||||
|
{ "tls-creds", required_argument, NULL, QEMU_NBD_OPT_TLSCREDS },
|
||||||
|
{ "image-opts", no_argument, NULL, QEMU_NBD_OPT_IMAGE_OPTS },
|
||||||
{ NULL, 0, NULL, 0 }
|
{ NULL, 0, NULL, 0 }
|
||||||
};
|
};
|
||||||
int ch;
|
int ch;
|
||||||
|
@ -493,6 +507,7 @@ int main(int argc, char **argv)
|
||||||
QDict *options = NULL;
|
QDict *options = NULL;
|
||||||
const char *export_name = NULL;
|
const char *export_name = NULL;
|
||||||
const char *tlscredsid = NULL;
|
const char *tlscredsid = NULL;
|
||||||
|
bool imageOpts = false;
|
||||||
|
|
||||||
/* The client thread uses SIGTERM to interrupt the server. A signal
|
/* The client thread uses SIGTERM to interrupt the server. A signal
|
||||||
* handler ensures that "qemu-nbd -v -c" exits with a nice status code.
|
* handler ensures that "qemu-nbd -v -c" exits with a nice status code.
|
||||||
|
@ -672,6 +687,9 @@ int main(int argc, char **argv)
|
||||||
case QEMU_NBD_OPT_TLSCREDS:
|
case QEMU_NBD_OPT_TLSCREDS:
|
||||||
tlscredsid = optarg;
|
tlscredsid = optarg;
|
||||||
break;
|
break;
|
||||||
|
case QEMU_NBD_OPT_IMAGE_OPTS:
|
||||||
|
imageOpts = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,13 +818,29 @@ int main(int argc, char **argv)
|
||||||
bdrv_init();
|
bdrv_init();
|
||||||
atexit(bdrv_close_all);
|
atexit(bdrv_close_all);
|
||||||
|
|
||||||
if (fmt) {
|
srcpath = argv[optind];
|
||||||
options = qdict_new();
|
if (imageOpts) {
|
||||||
qdict_put(options, "driver", qstring_from_str(fmt));
|
QemuOpts *opts;
|
||||||
|
if (fmt) {
|
||||||
|
error_report("--image-opts and -f are mutually exclusive");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
opts = qemu_opts_parse_noisily(&file_opts, srcpath, true);
|
||||||
|
if (!opts) {
|
||||||
|
qemu_opts_reset(&file_opts);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
options = qemu_opts_to_qdict(opts, NULL);
|
||||||
|
qemu_opts_reset(&file_opts);
|
||||||
|
blk = blk_new_open("hda", NULL, NULL, options, flags, &local_err);
|
||||||
|
} else {
|
||||||
|
if (fmt) {
|
||||||
|
options = qdict_new();
|
||||||
|
qdict_put(options, "driver", qstring_from_str(fmt));
|
||||||
|
}
|
||||||
|
blk = blk_new_open("hda", srcpath, NULL, options, flags, &local_err);
|
||||||
}
|
}
|
||||||
|
|
||||||
srcpath = argv[optind];
|
|
||||||
blk = blk_new_open("hda", srcpath, NULL, options, flags, &local_err);
|
|
||||||
if (!blk) {
|
if (!blk) {
|
||||||
error_reportf_err(local_err, "Failed to blk_new_open '%s': ",
|
error_reportf_err(local_err, "Failed to blk_new_open '%s': ",
|
||||||
argv[optind]);
|
argv[optind]);
|
||||||
|
|
|
@ -13,7 +13,8 @@ Export a QEMU disk image using the NBD protocol.
|
||||||
@c man end
|
@c man end
|
||||||
|
|
||||||
@c man begin OPTIONS
|
@c man begin OPTIONS
|
||||||
@var{filename} is a disk image filename.
|
@var{filename} is a disk image filename, or a set of block
|
||||||
|
driver options if @var{--image-opts} is specified.
|
||||||
|
|
||||||
@var{dev} is an NBD device.
|
@var{dev} is an NBD device.
|
||||||
|
|
||||||
|
@ -33,6 +34,10 @@ The offset into the image
|
||||||
The interface to bind to (default @samp{0.0.0.0})
|
The interface to bind to (default @samp{0.0.0.0})
|
||||||
@item -k, --socket=@var{path}
|
@item -k, --socket=@var{path}
|
||||||
Use a unix socket with path @var{path}
|
Use a unix socket with path @var{path}
|
||||||
|
@item --image-opts
|
||||||
|
Treat @var{filename} as a set of image options, instead of a plain
|
||||||
|
filename. If this flag is specified, the @var{-f} flag should
|
||||||
|
not be used, instead the '@code{format=}' option should be set.
|
||||||
@item -f, --format=@var{fmt}
|
@item -f, --format=@var{fmt}
|
||||||
Force the use of the block driver for format @var{fmt} instead of
|
Force the use of the block driver for format @var{fmt} instead of
|
||||||
auto-detecting
|
auto-detecting
|
||||||
|
|
|
@ -2006,7 +2006,7 @@ EQMP
|
||||||
|
|
||||||
{
|
{
|
||||||
.name = "block_set_io_throttle",
|
.name = "block_set_io_throttle",
|
||||||
.args_type = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l,bps_max:l?,bps_rd_max:l?,bps_wr_max:l?,iops_max:l?,iops_rd_max:l?,iops_wr_max:l?,iops_size:l?,group:s?",
|
.args_type = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l,bps_max:l?,bps_rd_max:l?,bps_wr_max:l?,iops_max:l?,iops_rd_max:l?,iops_wr_max:l?,bps_max_length:l?,bps_rd_max_length:l?,bps_wr_max_length:l?,iops_max_length:l?,iops_rd_max_length:l?,iops_wr_max_length:l?,iops_size:l?,group:s?",
|
||||||
.mhandler.cmd_new = qmp_marshal_block_set_io_throttle,
|
.mhandler.cmd_new = qmp_marshal_block_set_io_throttle,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2025,14 +2025,20 @@ Arguments:
|
||||||
- "iops": total I/O operations per second (json-int)
|
- "iops": total I/O operations per second (json-int)
|
||||||
- "iops_rd": read I/O operations per second (json-int)
|
- "iops_rd": read I/O operations per second (json-int)
|
||||||
- "iops_wr": write I/O operations per second (json-int)
|
- "iops_wr": write I/O operations per second (json-int)
|
||||||
- "bps_max": total max in bytes (json-int)
|
- "bps_max": total throughput limit during bursts, in bytes (json-int, optional)
|
||||||
- "bps_rd_max": read max in bytes (json-int)
|
- "bps_rd_max": read throughput limit during bursts, in bytes (json-int, optional)
|
||||||
- "bps_wr_max": write max in bytes (json-int)
|
- "bps_wr_max": write throughput limit during bursts, in bytes (json-int, optional)
|
||||||
- "iops_max": total I/O operations max (json-int)
|
- "iops_max": total I/O operations per second during bursts (json-int, optional)
|
||||||
- "iops_rd_max": read I/O operations max (json-int)
|
- "iops_rd_max": read I/O operations per second during bursts (json-int, optional)
|
||||||
- "iops_wr_max": write I/O operations max (json-int)
|
- "iops_wr_max": write I/O operations per second during bursts (json-int, optional)
|
||||||
- "iops_size": I/O size in bytes when limiting (json-int)
|
- "bps_max_length": maximum length of the @bps_max burst period, in seconds (json-int, optional)
|
||||||
- "group": throttle group name (json-string)
|
- "bps_rd_max_length": maximum length of the @bps_rd_max burst period, in seconds (json-int, optional)
|
||||||
|
- "bps_wr_max_length": maximum length of the @bps_wr_max burst period, in seconds (json-int, optional)
|
||||||
|
- "iops_max_length": maximum length of the @iops_max burst period, in seconds (json-int, optional)
|
||||||
|
- "iops_rd_max_length": maximum length of the @iops_rd_max burst period, in seconds (json-int, optional)
|
||||||
|
- "iops_wr_max_length": maximum length of the @iops_wr_max burst period, in seconds (json-int, optional)
|
||||||
|
- "iops_size": I/O size in bytes when limiting (json-int, optional)
|
||||||
|
- "group": throttle group name (json-string, optional)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -2049,6 +2055,7 @@ Example:
|
||||||
"iops_max": 0,
|
"iops_max": 0,
|
||||||
"iops_rd_max": 0,
|
"iops_rd_max": 0,
|
||||||
"iops_wr_max": 0,
|
"iops_wr_max": 0,
|
||||||
|
"bps_max_length": 60,
|
||||||
"iops_size": 0 } }
|
"iops_size": 0 } }
|
||||||
<- { "return": {} }
|
<- { "return": {} }
|
||||||
|
|
||||||
|
|
|
@ -45,11 +45,20 @@ function do_run_qemu()
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Remove QMP events from (pretty-printed) output. Doesn't handle
|
||||||
|
# nested dicts correctly, but we don't get any of those in this test.
|
||||||
|
_filter_qmp_events()
|
||||||
|
{
|
||||||
|
tr '\n' '\t' | sed -e \
|
||||||
|
's/{\s*"timestamp":\s*{[^}]*},\s*"event":[^,}]*\(,\s*"data":\s*{[^}]*}\)\?\s*}\s*//g' \
|
||||||
|
| tr '\t' '\n'
|
||||||
|
}
|
||||||
|
|
||||||
function run_qemu()
|
function run_qemu()
|
||||||
{
|
{
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu \
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu \
|
||||||
| sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g' \
|
| sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g' \
|
||||||
| _filter_generated_node_ids
|
| _filter_generated_node_ids | _filter_qmp_events
|
||||||
}
|
}
|
||||||
|
|
||||||
size=128M
|
size=128M
|
||||||
|
|
|
@ -69,34 +69,6 @@ Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk -device virti
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"path": "/machine/peripheral/virtio0/virtio-backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"device": "virtio0",
|
|
||||||
"path": "/machine/peripheral/virtio0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "RESET"
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
"return": [
|
"return": [
|
||||||
]
|
]
|
||||||
|
@ -105,14 +77,6 @@ Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk -device virti
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "SHUTDOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=== -drive/device_add and device_del ===
|
=== -drive/device_add and device_del ===
|
||||||
|
|
||||||
|
@ -185,34 +149,6 @@ Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"path": "/machine/peripheral/virtio0/virtio-backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"device": "virtio0",
|
|
||||||
"path": "/machine/peripheral/virtio0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "RESET"
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
"return": [
|
"return": [
|
||||||
]
|
]
|
||||||
|
@ -221,14 +157,6 @@ Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "SHUTDOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=== drive_add/device_add and device_del ===
|
=== drive_add/device_add and device_del ===
|
||||||
|
|
||||||
|
@ -304,34 +232,6 @@ Testing:
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"path": "/machine/peripheral/virtio0/virtio-backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"device": "virtio0",
|
|
||||||
"path": "/machine/peripheral/virtio0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "RESET"
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
"return": [
|
"return": [
|
||||||
]
|
]
|
||||||
|
@ -340,14 +240,6 @@ Testing:
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "SHUTDOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
=== blockdev_add/device_add and device_del ===
|
=== blockdev_add/device_add and device_del ===
|
||||||
|
|
||||||
|
@ -424,34 +316,6 @@ Testing:
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"path": "/machine/peripheral/virtio0/virtio-backend"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "DEVICE_DELETED",
|
|
||||||
"data": {
|
|
||||||
"device": "virtio0",
|
|
||||||
"path": "/machine/peripheral/virtio0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "RESET"
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
"return": [
|
"return": [
|
||||||
{
|
{
|
||||||
|
@ -506,12 +370,4 @@ Testing:
|
||||||
"return": {
|
"return": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
"timestamp": {
|
|
||||||
"seconds": TIMESTAMP,
|
|
||||||
"microseconds": TIMESTAMP
|
|
||||||
},
|
|
||||||
"event": "SHUTDOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
*** done
|
*** done
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Tests for IO throttling
|
# Tests for IO throttling
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 Red Hat, Inc.
|
# Copyright (C) 2015 Red Hat, Inc.
|
||||||
# Copyright (C) 2015 Igalia, S.L.
|
# Copyright (C) 2015-2016 Igalia, S.L.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
import iotests
|
import iotests
|
||||||
|
|
||||||
|
nsec_per_sec = 1000000000
|
||||||
|
|
||||||
class ThrottleTestCase(iotests.QMPTestCase):
|
class ThrottleTestCase(iotests.QMPTestCase):
|
||||||
test_img = "null-aio://"
|
test_img = "null-aio://"
|
||||||
max_drives = 3
|
max_drives = 3
|
||||||
|
@ -42,16 +44,7 @@ class ThrottleTestCase(iotests.QMPTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
|
|
||||||
def do_test_throttle(self, ndrives, seconds, params):
|
def configure_throttle(self, ndrives, params):
|
||||||
def check_limit(limit, num):
|
|
||||||
# IO throttling algorithm is discrete, allow 10% error so the test
|
|
||||||
# is more robust
|
|
||||||
return limit == 0 or \
|
|
||||||
(num < seconds * limit * 1.1 / ndrives
|
|
||||||
and num > seconds * limit * 0.9 / ndrives)
|
|
||||||
|
|
||||||
nsec_per_sec = 1000000000
|
|
||||||
|
|
||||||
params['group'] = 'test'
|
params['group'] = 'test'
|
||||||
|
|
||||||
# Set the I/O throttling parameters to all drives
|
# Set the I/O throttling parameters to all drives
|
||||||
|
@ -60,13 +53,21 @@ class ThrottleTestCase(iotests.QMPTestCase):
|
||||||
result = self.vm.qmp("block_set_io_throttle", conv_keys=False, **params)
|
result = self.vm.qmp("block_set_io_throttle", conv_keys=False, **params)
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
def do_test_throttle(self, ndrives, seconds, params):
|
||||||
|
def check_limit(limit, num):
|
||||||
|
# IO throttling algorithm is discrete, allow 10% error so the test
|
||||||
|
# is more robust
|
||||||
|
return limit == 0 or \
|
||||||
|
(num < seconds * limit * 1.1 / ndrives
|
||||||
|
and num > seconds * limit * 0.9 / ndrives)
|
||||||
|
|
||||||
# Set vm clock to a known value
|
# Set vm clock to a known value
|
||||||
ns = seconds * nsec_per_sec
|
ns = seconds * nsec_per_sec
|
||||||
self.vm.qtest("clock_step %d" % ns)
|
self.vm.qtest("clock_step %d" % ns)
|
||||||
|
|
||||||
# Submit enough requests. They will drain bps_max and iops_max, but the
|
# Submit enough requests so the throttling mechanism kicks
|
||||||
# rest requests won't get executed until we advance the virtual clock
|
# in. The throttled requests won't be executed until we
|
||||||
# with qtest interface
|
# advance the virtual clock.
|
||||||
rq_size = 512
|
rq_size = 512
|
||||||
rd_nr = max(params['bps'] / rq_size / 2,
|
rd_nr = max(params['bps'] / rq_size / 2,
|
||||||
params['bps_rd'] / rq_size,
|
params['bps_rd'] / rq_size,
|
||||||
|
@ -142,8 +143,44 @@ class ThrottleTestCase(iotests.QMPTestCase):
|
||||||
for tk in params:
|
for tk in params:
|
||||||
limits = dict([(k, 0) for k in params])
|
limits = dict([(k, 0) for k in params])
|
||||||
limits[tk] = params[tk] * ndrives
|
limits[tk] = params[tk] * ndrives
|
||||||
|
self.configure_throttle(ndrives, limits)
|
||||||
self.do_test_throttle(ndrives, 5, limits)
|
self.do_test_throttle(ndrives, 5, limits)
|
||||||
|
|
||||||
|
def test_burst(self):
|
||||||
|
params = {"bps": 4096,
|
||||||
|
"bps_rd": 4096,
|
||||||
|
"bps_wr": 4096,
|
||||||
|
"iops": 10,
|
||||||
|
"iops_rd": 10,
|
||||||
|
"iops_wr": 10,
|
||||||
|
}
|
||||||
|
ndrives = 1
|
||||||
|
# Pick each out of all possible params and test
|
||||||
|
for tk in params:
|
||||||
|
rate = params[tk] * ndrives
|
||||||
|
burst_rate = rate * 7
|
||||||
|
burst_length = 4
|
||||||
|
|
||||||
|
# Configure the throttling settings
|
||||||
|
settings = dict([(k, 0) for k in params])
|
||||||
|
settings[tk] = rate
|
||||||
|
settings['%s_max' % tk] = burst_rate
|
||||||
|
settings['%s_max_length' % tk] = burst_length
|
||||||
|
self.configure_throttle(ndrives, settings)
|
||||||
|
|
||||||
|
# Wait for the bucket to empty so we can do bursts
|
||||||
|
wait_ns = nsec_per_sec * burst_length * burst_rate / rate
|
||||||
|
self.vm.qtest("clock_step %d" % wait_ns)
|
||||||
|
|
||||||
|
# Test I/O at the max burst rate
|
||||||
|
limits = dict([(k, 0) for k in params])
|
||||||
|
limits[tk] = burst_rate
|
||||||
|
self.do_test_throttle(ndrives, burst_length, limits)
|
||||||
|
|
||||||
|
# Now test I/O at the normal rate
|
||||||
|
limits[tk] = rate
|
||||||
|
self.do_test_throttle(ndrives, 5, limits)
|
||||||
|
|
||||||
class ThrottleTestCoroutine(ThrottleTestCase):
|
class ThrottleTestCoroutine(ThrottleTestCase):
|
||||||
test_img = "null-co://"
|
test_img = "null-co://"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
..
|
....
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Ran 2 tests
|
Ran 4 tests
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# Test case for ejecting a BB with an NBD server attached to it
|
# Test case for ejecting a BlockBackend with an NBD server attached to it
|
||||||
|
#
|
||||||
|
# Verify that the NBD server stops offering the drive when ejecting a
|
||||||
|
# BlockDriverState tree from a BlockBackend (that is, a medium from a
|
||||||
|
# drive) exposed via an NBD server.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 Red Hat, Inc.
|
# Copyright (C) 2016 Red Hat, Inc.
|
||||||
#
|
#
|
||||||
|
@ -49,7 +53,7 @@ _make_test_img 64k
|
||||||
$QEMU_IO -c 'write -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io
|
$QEMU_IO -c 'write -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io
|
||||||
|
|
||||||
keep_stderr=y \
|
keep_stderr=y \
|
||||||
_launch_qemu -drive if=ide,media=cdrom,id=drv,file="$TEST_IMG",format=$IMGFMT \
|
_launch_qemu -drive if=none,media=cdrom,id=drv,file="$TEST_IMG",format=$IMGFMT \
|
||||||
2> >(_filter_nbd)
|
2> >(_filter_nbd)
|
||||||
|
|
||||||
_send_qemu_cmd $QEMU_HANDLE \
|
_send_qemu_cmd $QEMU_HANDLE \
|
||||||
|
|
|
@ -7,7 +7,6 @@ wrote 65536/65536 bytes at offset 0
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
read 65536/65536 bytes at offset 0
|
read 65536/65536 bytes at offset 0
|
||||||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "drv", "tray-open": true}}
|
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: No export with name 'drv' available
|
can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: No export with name 'drv' available
|
||||||
no file open, try 'help open'
|
no file open, try 'help open'
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test the combination of -incoming and snapshot=on
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# creator
|
||||||
|
owner=kwolf@redhat.com
|
||||||
|
|
||||||
|
seq=`basename $0`
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
here=`pwd`
|
||||||
|
tmp=/tmp/$$
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
_cleanup_test_img
|
||||||
|
true
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
. ./common.rc
|
||||||
|
. ./common.filter
|
||||||
|
|
||||||
|
_supported_fmt generic
|
||||||
|
_supported_proto generic
|
||||||
|
_supported_os Linux
|
||||||
|
|
||||||
|
_make_test_img 1M
|
||||||
|
echo quit | $QEMU -nographic -hda "$TEST_IMG" -incoming 'exec:true' -snapshot -serial none -monitor stdio | _filter_qemu
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo "*** done"
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
|
@ -0,0 +1,5 @@
|
||||||
|
QA output created by 145
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
|
||||||
|
QEMU X.Y.Z monitor - type 'help' for more information
|
||||||
|
(qemu) q[K[Dqu[K[D[Dqui[K[D[D[Dquit[K
|
||||||
|
*** done
|
|
@ -147,3 +147,4 @@
|
||||||
142 auto
|
142 auto
|
||||||
143 auto quick
|
143 auto quick
|
||||||
144 rw auto quick
|
144 rw auto quick
|
||||||
|
145 auto quick
|
||||||
|
|
|
@ -35,6 +35,9 @@ static bool double_cmp(double x, double y)
|
||||||
/* tests for single bucket operations */
|
/* tests for single bucket operations */
|
||||||
static void test_leak_bucket(void)
|
static void test_leak_bucket(void)
|
||||||
{
|
{
|
||||||
|
throttle_config_init(&cfg);
|
||||||
|
bkt = cfg.buckets[THROTTLE_BPS_TOTAL];
|
||||||
|
|
||||||
/* set initial value */
|
/* set initial value */
|
||||||
bkt.avg = 150;
|
bkt.avg = 150;
|
||||||
bkt.max = 15;
|
bkt.max = 15;
|
||||||
|
@ -57,13 +60,33 @@ static void test_leak_bucket(void)
|
||||||
g_assert(bkt.avg == 150);
|
g_assert(bkt.avg == 150);
|
||||||
g_assert(bkt.max == 15);
|
g_assert(bkt.max == 15);
|
||||||
g_assert(double_cmp(bkt.level, 0));
|
g_assert(double_cmp(bkt.level, 0));
|
||||||
|
|
||||||
|
/* check that burst_level leaks correctly */
|
||||||
|
bkt.burst_level = 6;
|
||||||
|
bkt.max = 250;
|
||||||
|
bkt.burst_length = 2; /* otherwise burst_level will not leak */
|
||||||
|
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
|
||||||
|
g_assert(double_cmp(bkt.burst_level, 3.5));
|
||||||
|
|
||||||
|
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
|
||||||
|
g_assert(double_cmp(bkt.burst_level, 1));
|
||||||
|
|
||||||
|
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
|
||||||
|
g_assert(double_cmp(bkt.burst_level, 0));
|
||||||
|
|
||||||
|
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
|
||||||
|
g_assert(double_cmp(bkt.burst_level, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_compute_wait(void)
|
static void test_compute_wait(void)
|
||||||
{
|
{
|
||||||
|
unsigned i;
|
||||||
int64_t wait;
|
int64_t wait;
|
||||||
int64_t result;
|
int64_t result;
|
||||||
|
|
||||||
|
throttle_config_init(&cfg);
|
||||||
|
bkt = cfg.buckets[THROTTLE_BPS_TOTAL];
|
||||||
|
|
||||||
/* no operation limit set */
|
/* no operation limit set */
|
||||||
bkt.avg = 0;
|
bkt.avg = 0;
|
||||||
bkt.max = 15;
|
bkt.max = 15;
|
||||||
|
@ -93,6 +116,27 @@ static void test_compute_wait(void)
|
||||||
/* time required to do half an operation */
|
/* time required to do half an operation */
|
||||||
result = (int64_t) NANOSECONDS_PER_SECOND / 150 / 2;
|
result = (int64_t) NANOSECONDS_PER_SECOND / 150 / 2;
|
||||||
g_assert(wait == result);
|
g_assert(wait == result);
|
||||||
|
|
||||||
|
/* Perform I/O for 2.2 seconds at a rate of bkt.max */
|
||||||
|
bkt.burst_length = 2;
|
||||||
|
bkt.level = 0;
|
||||||
|
bkt.avg = 10;
|
||||||
|
bkt.max = 200;
|
||||||
|
for (i = 0; i < 22; i++) {
|
||||||
|
double units = bkt.max / 10;
|
||||||
|
bkt.level += units;
|
||||||
|
bkt.burst_level += units;
|
||||||
|
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 10);
|
||||||
|
wait = throttle_compute_wait(&bkt);
|
||||||
|
g_assert(double_cmp(bkt.burst_level, 0));
|
||||||
|
g_assert(double_cmp(bkt.level, (i + 1) * (bkt.max - bkt.avg) / 10));
|
||||||
|
/* We can do bursts for the 2 seconds we have configured in
|
||||||
|
* burst_length. We have 100 extra miliseconds of burst
|
||||||
|
* because bkt.level has been leaking during this time.
|
||||||
|
* After that, we have to wait. */
|
||||||
|
result = i < 21 ? 0 : 1.8 * NANOSECONDS_PER_SECOND;
|
||||||
|
g_assert(wait == result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* functions to test ThrottleState initialization/destroy methods */
|
/* functions to test ThrottleState initialization/destroy methods */
|
||||||
|
@ -222,6 +266,8 @@ static void set_cfg_value(bool is_max, int index, int value)
|
||||||
{
|
{
|
||||||
if (is_max) {
|
if (is_max) {
|
||||||
cfg.buckets[index].max = value;
|
cfg.buckets[index].max = value;
|
||||||
|
/* If max is set, avg should never be 0 */
|
||||||
|
cfg.buckets[index].avg = MAX(cfg.buckets[index].avg, 1);
|
||||||
} else {
|
} else {
|
||||||
cfg.buckets[index].avg = value;
|
cfg.buckets[index].avg = value;
|
||||||
}
|
}
|
||||||
|
@ -231,17 +277,17 @@ static void test_enabled(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
g_assert(!throttle_enabled(&cfg));
|
g_assert(!throttle_enabled(&cfg));
|
||||||
|
|
||||||
for (i = 0; i < BUCKETS_COUNT; i++) {
|
for (i = 0; i < BUCKETS_COUNT; i++) {
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(false, i, 150);
|
set_cfg_value(false, i, 150);
|
||||||
g_assert(throttle_enabled(&cfg));
|
g_assert(throttle_enabled(&cfg));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < BUCKETS_COUNT; i++) {
|
for (i = 0; i < BUCKETS_COUNT; i++) {
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(false, i, -150);
|
set_cfg_value(false, i, -150);
|
||||||
g_assert(!throttle_enabled(&cfg));
|
g_assert(!throttle_enabled(&cfg));
|
||||||
}
|
}
|
||||||
|
@ -254,32 +300,32 @@ static void test_conflicts_for_one_set(bool is_max,
|
||||||
int read,
|
int read,
|
||||||
int write)
|
int write)
|
||||||
{
|
{
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
g_assert(!throttle_conflicting(&cfg));
|
g_assert(throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
set_cfg_value(is_max, total, 1);
|
set_cfg_value(is_max, total, 1);
|
||||||
set_cfg_value(is_max, read, 1);
|
set_cfg_value(is_max, read, 1);
|
||||||
g_assert(throttle_conflicting(&cfg));
|
g_assert(!throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(is_max, total, 1);
|
set_cfg_value(is_max, total, 1);
|
||||||
set_cfg_value(is_max, write, 1);
|
set_cfg_value(is_max, write, 1);
|
||||||
g_assert(throttle_conflicting(&cfg));
|
g_assert(!throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(is_max, total, 1);
|
set_cfg_value(is_max, total, 1);
|
||||||
set_cfg_value(is_max, read, 1);
|
set_cfg_value(is_max, read, 1);
|
||||||
set_cfg_value(is_max, write, 1);
|
set_cfg_value(is_max, write, 1);
|
||||||
g_assert(throttle_conflicting(&cfg));
|
g_assert(!throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(is_max, total, 1);
|
set_cfg_value(is_max, total, 1);
|
||||||
g_assert(!throttle_conflicting(&cfg));
|
g_assert(throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(is_max, read, 1);
|
set_cfg_value(is_max, read, 1);
|
||||||
set_cfg_value(is_max, write, 1);
|
set_cfg_value(is_max, write, 1);
|
||||||
g_assert(!throttle_conflicting(&cfg));
|
g_assert(throttle_is_valid(&cfg, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_conflicting_config(void)
|
static void test_conflicting_config(void)
|
||||||
|
@ -313,9 +359,9 @@ static void test_is_valid_for_value(int value, bool should_be_valid)
|
||||||
int is_max, index;
|
int is_max, index;
|
||||||
for (is_max = 0; is_max < 2; is_max++) {
|
for (is_max = 0; is_max < 2; is_max++) {
|
||||||
for (index = 0; index < BUCKETS_COUNT; index++) {
|
for (index = 0; index < BUCKETS_COUNT; index++) {
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
set_cfg_value(is_max, index, value);
|
set_cfg_value(is_max, index, value);
|
||||||
g_assert(throttle_is_valid(&cfg) == should_be_valid);
|
g_assert(throttle_is_valid(&cfg, NULL) == should_be_valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,18 +381,18 @@ static void test_max_is_missing_limit(void)
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < BUCKETS_COUNT; i++) {
|
for (i = 0; i < BUCKETS_COUNT; i++) {
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
throttle_config_init(&cfg);
|
||||||
cfg.buckets[i].max = 100;
|
cfg.buckets[i].max = 100;
|
||||||
cfg.buckets[i].avg = 0;
|
cfg.buckets[i].avg = 0;
|
||||||
g_assert(throttle_max_is_missing_limit(&cfg));
|
g_assert(!throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
cfg.buckets[i].max = 0;
|
cfg.buckets[i].max = 0;
|
||||||
cfg.buckets[i].avg = 0;
|
cfg.buckets[i].avg = 0;
|
||||||
g_assert(!throttle_max_is_missing_limit(&cfg));
|
g_assert(throttle_is_valid(&cfg, NULL));
|
||||||
|
|
||||||
cfg.buckets[i].max = 0;
|
cfg.buckets[i].max = 0;
|
||||||
cfg.buckets[i].avg = 100;
|
cfg.buckets[i].avg = 100;
|
||||||
g_assert(!throttle_max_is_missing_limit(&cfg));
|
g_assert(throttle_is_valid(&cfg, NULL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +596,7 @@ static void test_groups(void)
|
||||||
g_assert(bdrv1->throttle_state == bdrv3->throttle_state);
|
g_assert(bdrv1->throttle_state == bdrv3->throttle_state);
|
||||||
|
|
||||||
/* Setting the config of a group member affects the whole group */
|
/* Setting the config of a group member affects the whole group */
|
||||||
memset(&cfg1, 0, sizeof(cfg1));
|
throttle_config_init(&cfg1);
|
||||||
cfg1.buckets[THROTTLE_BPS_READ].avg = 500000;
|
cfg1.buckets[THROTTLE_BPS_READ].avg = 500000;
|
||||||
cfg1.buckets[THROTTLE_BPS_WRITE].avg = 285000;
|
cfg1.buckets[THROTTLE_BPS_WRITE].avg = 285000;
|
||||||
cfg1.buckets[THROTTLE_OPS_READ].avg = 20000;
|
cfg1.buckets[THROTTLE_OPS_READ].avg = 20000;
|
||||||
|
|
140
util/throttle.c
140
util/throttle.c
|
@ -41,6 +41,14 @@ void throttle_leak_bucket(LeakyBucket *bkt, int64_t delta_ns)
|
||||||
|
|
||||||
/* make the bucket leak */
|
/* make the bucket leak */
|
||||||
bkt->level = MAX(bkt->level - leak, 0);
|
bkt->level = MAX(bkt->level - leak, 0);
|
||||||
|
|
||||||
|
/* if we allow bursts for more than one second we also need to
|
||||||
|
* keep track of bkt->burst_level so the bkt->max goal per second
|
||||||
|
* is attained */
|
||||||
|
if (bkt->burst_length > 1) {
|
||||||
|
leak = (bkt->max * (double) delta_ns) / NANOSECONDS_PER_SECOND;
|
||||||
|
bkt->burst_level = MAX(bkt->burst_level - leak, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calculate the time delta since last leak and make proportionals leaks
|
/* Calculate the time delta since last leak and make proportionals leaks
|
||||||
|
@ -91,13 +99,24 @@ int64_t throttle_compute_wait(LeakyBucket *bkt)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extra = bkt->level - bkt->max;
|
/* If the bucket is full then we have to wait */
|
||||||
|
extra = bkt->level - bkt->max * bkt->burst_length;
|
||||||
if (extra <= 0) {
|
if (extra > 0) {
|
||||||
return 0;
|
return throttle_do_compute_wait(bkt->avg, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
return throttle_do_compute_wait(bkt->avg, extra);
|
/* If the bucket is not full yet we have to make sure that we
|
||||||
|
* fulfill the goal of bkt->max units per second. */
|
||||||
|
if (bkt->burst_length > 1) {
|
||||||
|
/* We use 1/10 of the max value to smooth the throttling.
|
||||||
|
* See throttle_fix_bucket() for more details. */
|
||||||
|
extra = bkt->burst_level - bkt->max / 10;
|
||||||
|
if (extra > 0) {
|
||||||
|
return throttle_do_compute_wait(bkt->max, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function compute the time that must be waited while this IO
|
/* This function compute the time that must be waited while this IO
|
||||||
|
@ -137,10 +156,10 @@ static int64_t throttle_compute_wait_for(ThrottleState *ts,
|
||||||
* @next_timestamp: the resulting timer
|
* @next_timestamp: the resulting timer
|
||||||
* @ret: true if a timer must be set
|
* @ret: true if a timer must be set
|
||||||
*/
|
*/
|
||||||
bool throttle_compute_timer(ThrottleState *ts,
|
static bool throttle_compute_timer(ThrottleState *ts,
|
||||||
bool is_write,
|
bool is_write,
|
||||||
int64_t now,
|
int64_t now,
|
||||||
int64_t *next_timestamp)
|
int64_t *next_timestamp)
|
||||||
{
|
{
|
||||||
int64_t wait;
|
int64_t wait;
|
||||||
|
|
||||||
|
@ -171,10 +190,24 @@ void throttle_timers_attach_aio_context(ThrottleTimers *tt,
|
||||||
tt->write_timer_cb, tt->timer_opaque);
|
tt->write_timer_cb, tt->timer_opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the ThrottleConfig structure to a valid state
|
||||||
|
* @cfg: the config to initialize
|
||||||
|
*/
|
||||||
|
void throttle_config_init(ThrottleConfig *cfg)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
memset(cfg, 0, sizeof(*cfg));
|
||||||
|
for (i = 0; i < BUCKETS_COUNT; i++) {
|
||||||
|
cfg->buckets[i].burst_length = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* To be called first on the ThrottleState */
|
/* To be called first on the ThrottleState */
|
||||||
void throttle_init(ThrottleState *ts)
|
void throttle_init(ThrottleState *ts)
|
||||||
{
|
{
|
||||||
memset(ts, 0, sizeof(ThrottleState));
|
memset(ts, 0, sizeof(ThrottleState));
|
||||||
|
throttle_config_init(&ts->cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To be called first on the ThrottleTimers */
|
/* To be called first on the ThrottleTimers */
|
||||||
|
@ -248,13 +281,14 @@ bool throttle_enabled(ThrottleConfig *cfg)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return true if any two throttling parameters conflicts
|
/* check if a throttling configuration is valid
|
||||||
*
|
|
||||||
* @cfg: the throttling configuration to inspect
|
* @cfg: the throttling configuration to inspect
|
||||||
* @ret: true if any conflict detected else false
|
* @ret: true if valid else false
|
||||||
|
* @errp: error object
|
||||||
*/
|
*/
|
||||||
bool throttle_conflicting(ThrottleConfig *cfg)
|
bool throttle_is_valid(ThrottleConfig *cfg, Error **errp)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
bool bps_flag, ops_flag;
|
bool bps_flag, ops_flag;
|
||||||
bool bps_max_flag, ops_max_flag;
|
bool bps_max_flag, ops_max_flag;
|
||||||
|
|
||||||
|
@ -274,22 +308,35 @@ bool throttle_conflicting(ThrottleConfig *cfg)
|
||||||
(cfg->buckets[THROTTLE_OPS_READ].max ||
|
(cfg->buckets[THROTTLE_OPS_READ].max ||
|
||||||
cfg->buckets[THROTTLE_OPS_WRITE].max);
|
cfg->buckets[THROTTLE_OPS_WRITE].max);
|
||||||
|
|
||||||
return bps_flag || ops_flag || bps_max_flag || ops_max_flag;
|
if (bps_flag || ops_flag || bps_max_flag || ops_max_flag) {
|
||||||
}
|
error_setg(errp, "bps/iops/max total values and read/write values"
|
||||||
|
" cannot be used at the same time");
|
||||||
/* check if a throttling configuration is valid
|
return false;
|
||||||
* @cfg: the throttling configuration to inspect
|
}
|
||||||
* @ret: true if valid else false
|
|
||||||
*/
|
|
||||||
bool throttle_is_valid(ThrottleConfig *cfg)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < BUCKETS_COUNT; i++) {
|
for (i = 0; i < BUCKETS_COUNT; i++) {
|
||||||
if (cfg->buckets[i].avg < 0 ||
|
if (cfg->buckets[i].avg < 0 ||
|
||||||
cfg->buckets[i].max < 0 ||
|
cfg->buckets[i].max < 0 ||
|
||||||
cfg->buckets[i].avg > THROTTLE_VALUE_MAX ||
|
cfg->buckets[i].avg > THROTTLE_VALUE_MAX ||
|
||||||
cfg->buckets[i].max > THROTTLE_VALUE_MAX) {
|
cfg->buckets[i].max > THROTTLE_VALUE_MAX) {
|
||||||
|
error_setg(errp, "bps/iops/max values must be within [0, %lld]",
|
||||||
|
THROTTLE_VALUE_MAX);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg->buckets[i].burst_length) {
|
||||||
|
error_setg(errp, "the burst length cannot be 0");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg->buckets[i].burst_length > 1 && !cfg->buckets[i].max) {
|
||||||
|
error_setg(errp, "burst length set without burst rate");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg->buckets[i].max && !cfg->buckets[i].avg) {
|
||||||
|
error_setg(errp, "bps_max/iops_max require corresponding"
|
||||||
|
" bps/iops values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,28 +344,13 @@ bool throttle_is_valid(ThrottleConfig *cfg)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check if bps_max/iops_max is used without bps/iops
|
|
||||||
* @cfg: the throttling configuration to inspect
|
|
||||||
*/
|
|
||||||
bool throttle_max_is_missing_limit(ThrottleConfig *cfg)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < BUCKETS_COUNT; i++) {
|
|
||||||
if (cfg->buckets[i].max && !cfg->buckets[i].avg) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix bucket parameters */
|
/* fix bucket parameters */
|
||||||
static void throttle_fix_bucket(LeakyBucket *bkt)
|
static void throttle_fix_bucket(LeakyBucket *bkt)
|
||||||
{
|
{
|
||||||
double min;
|
double min;
|
||||||
|
|
||||||
/* zero bucket level */
|
/* zero bucket level */
|
||||||
bkt->level = 0;
|
bkt->level = bkt->burst_level = 0;
|
||||||
|
|
||||||
/* The following is done to cope with the Linux CFQ block scheduler
|
/* The following is done to cope with the Linux CFQ block scheduler
|
||||||
* which regroup reads and writes by block of 100ms in the guest.
|
* which regroup reads and writes by block of 100ms in the guest.
|
||||||
|
@ -421,22 +453,36 @@ bool throttle_schedule_timer(ThrottleState *ts,
|
||||||
*/
|
*/
|
||||||
void throttle_account(ThrottleState *ts, bool is_write, uint64_t size)
|
void throttle_account(ThrottleState *ts, bool is_write, uint64_t size)
|
||||||
{
|
{
|
||||||
|
const BucketType bucket_types_size[2][2] = {
|
||||||
|
{ THROTTLE_BPS_TOTAL, THROTTLE_BPS_READ },
|
||||||
|
{ THROTTLE_BPS_TOTAL, THROTTLE_BPS_WRITE }
|
||||||
|
};
|
||||||
|
const BucketType bucket_types_units[2][2] = {
|
||||||
|
{ THROTTLE_OPS_TOTAL, THROTTLE_OPS_READ },
|
||||||
|
{ THROTTLE_OPS_TOTAL, THROTTLE_OPS_WRITE }
|
||||||
|
};
|
||||||
double units = 1.0;
|
double units = 1.0;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
/* if cfg.op_size is defined and smaller than size we compute unit count */
|
/* if cfg.op_size is defined and smaller than size we compute unit count */
|
||||||
if (ts->cfg.op_size && size > ts->cfg.op_size) {
|
if (ts->cfg.op_size && size > ts->cfg.op_size) {
|
||||||
units = (double) size / ts->cfg.op_size;
|
units = (double) size / ts->cfg.op_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
ts->cfg.buckets[THROTTLE_BPS_TOTAL].level += size;
|
for (i = 0; i < 2; i++) {
|
||||||
ts->cfg.buckets[THROTTLE_OPS_TOTAL].level += units;
|
LeakyBucket *bkt;
|
||||||
|
|
||||||
if (is_write) {
|
bkt = &ts->cfg.buckets[bucket_types_size[is_write][i]];
|
||||||
ts->cfg.buckets[THROTTLE_BPS_WRITE].level += size;
|
bkt->level += size;
|
||||||
ts->cfg.buckets[THROTTLE_OPS_WRITE].level += units;
|
if (bkt->burst_length > 1) {
|
||||||
} else {
|
bkt->burst_level += size;
|
||||||
ts->cfg.buckets[THROTTLE_BPS_READ].level += size;
|
}
|
||||||
ts->cfg.buckets[THROTTLE_OPS_READ].level += units;
|
|
||||||
|
bkt = &ts->cfg.buckets[bucket_types_units[is_write][i]];
|
||||||
|
bkt->level += units;
|
||||||
|
if (bkt->burst_length > 1) {
|
||||||
|
bkt->burst_level += units;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue