Revision | f3d0bec9f80e4ed7796fffa834ba0a53f2094f7f (tree) |
---|---|
Zeit | 2019-06-14 22:46:13 |
Autor | Peter Maydell <peter.maydell@lina...> |
Commiter | Peter Maydell |
Merge remote-tracking branch 'remotes/maxreitz/tags/pull-block-2019-06-14' into staging
Block patches:
- Allow blockdev-backup from nodes that are not in qemu's main AIO
- Add salvaging mode to qemu-img convert
- Minor fixes to tests, documentation, and for less Valgrind annoyance
# gpg: Signature made Fri 14 Jun 2019 14:38:11 BST
# gpg: using RSA key 91BEB60A30DB3E8857D11829F407DB0061D5CF40
# gpg: issuer "mreitz@redhat.com"
# gpg: Good signature from "Max Reitz <mreitz@redhat.com>" [full]
# Primary key fingerprint: 91BE B60A 30DB 3E88 57D1 1829 F407 DB00 61D5 CF40
* remotes/maxreitz/tags/pull-block-2019-06-14:
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
@@ -75,6 +75,7 @@ typedef struct BlkdebugRule { | ||
75 | 75 | int state; |
76 | 76 | union { |
77 | 77 | struct { |
78 | + uint64_t iotype_mask; | |
78 | 79 | int error; |
79 | 80 | int immediately; |
80 | 81 | int once; |
@@ -91,6 +92,9 @@ typedef struct BlkdebugRule { | ||
91 | 92 | QSIMPLEQ_ENTRY(BlkdebugRule) active_next; |
92 | 93 | } BlkdebugRule; |
93 | 94 | |
95 | +QEMU_BUILD_BUG_MSG(BLKDEBUG_IO_TYPE__MAX > 64, | |
96 | + "BlkdebugIOType mask does not fit into an uint64_t"); | |
97 | + | |
94 | 98 | static QemuOptsList inject_error_opts = { |
95 | 99 | .name = "inject-error", |
96 | 100 | .head = QTAILQ_HEAD_INITIALIZER(inject_error_opts.head), |
@@ -104,6 +108,10 @@ static QemuOptsList inject_error_opts = { | ||
104 | 108 | .type = QEMU_OPT_NUMBER, |
105 | 109 | }, |
106 | 110 | { |
111 | + .name = "iotype", | |
112 | + .type = QEMU_OPT_STRING, | |
113 | + }, | |
114 | + { | |
107 | 115 | .name = "errno", |
108 | 116 | .type = QEMU_OPT_NUMBER, |
109 | 117 | }, |
@@ -162,6 +170,8 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp) | ||
162 | 170 | int event; |
163 | 171 | struct BlkdebugRule *rule; |
164 | 172 | int64_t sector; |
173 | + BlkdebugIOType iotype; | |
174 | + Error *local_error = NULL; | |
165 | 175 | |
166 | 176 | /* Find the right event for the rule */ |
167 | 177 | event_name = qemu_opt_get(opts, "event"); |
@@ -192,6 +202,26 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp) | ||
192 | 202 | sector = qemu_opt_get_number(opts, "sector", -1); |
193 | 203 | rule->options.inject.offset = |
194 | 204 | sector == -1 ? -1 : sector * BDRV_SECTOR_SIZE; |
205 | + | |
206 | + iotype = qapi_enum_parse(&BlkdebugIOType_lookup, | |
207 | + qemu_opt_get(opts, "iotype"), | |
208 | + BLKDEBUG_IO_TYPE__MAX, &local_error); | |
209 | + if (local_error) { | |
210 | + error_propagate(errp, local_error); | |
211 | + return -1; | |
212 | + } | |
213 | + if (iotype != BLKDEBUG_IO_TYPE__MAX) { | |
214 | + rule->options.inject.iotype_mask = (1ull << iotype); | |
215 | + } else { | |
216 | + /* Apply the default */ | |
217 | + rule->options.inject.iotype_mask = | |
218 | + (1ull << BLKDEBUG_IO_TYPE_READ) | |
219 | + | (1ull << BLKDEBUG_IO_TYPE_WRITE) | |
220 | + | (1ull << BLKDEBUG_IO_TYPE_WRITE_ZEROES) | |
221 | + | (1ull << BLKDEBUG_IO_TYPE_DISCARD) | |
222 | + | (1ull << BLKDEBUG_IO_TYPE_FLUSH); | |
223 | + } | |
224 | + | |
195 | 225 | break; |
196 | 226 | |
197 | 227 | case ACTION_SET_STATE: |
@@ -461,6 +491,8 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags, | ||
461 | 491 | goto out; |
462 | 492 | } |
463 | 493 | |
494 | + bdrv_debug_event(bs, BLKDBG_NONE); | |
495 | + | |
464 | 496 | ret = 0; |
465 | 497 | out: |
466 | 498 | if (ret < 0) { |
@@ -470,7 +502,8 @@ out: | ||
470 | 502 | return ret; |
471 | 503 | } |
472 | 504 | |
473 | -static int rule_check(BlockDriverState *bs, uint64_t offset, uint64_t bytes) | |
505 | +static int rule_check(BlockDriverState *bs, uint64_t offset, uint64_t bytes, | |
506 | + BlkdebugIOType iotype) | |
474 | 507 | { |
475 | 508 | BDRVBlkdebugState *s = bs->opaque; |
476 | 509 | BlkdebugRule *rule = NULL; |
@@ -480,9 +513,10 @@ static int rule_check(BlockDriverState *bs, uint64_t offset, uint64_t bytes) | ||
480 | 513 | QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) { |
481 | 514 | uint64_t inject_offset = rule->options.inject.offset; |
482 | 515 | |
483 | - if (inject_offset == -1 || | |
484 | - (bytes && inject_offset >= offset && | |
485 | - inject_offset < offset + bytes)) | |
516 | + if ((inject_offset == -1 || | |
517 | + (bytes && inject_offset >= offset && | |
518 | + inject_offset < offset + bytes)) && | |
519 | + (rule->options.inject.iotype_mask & (1ull << iotype))) | |
486 | 520 | { |
487 | 521 | break; |
488 | 522 | } |
@@ -521,7 +555,7 @@ blkdebug_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes, | ||
521 | 555 | assert(bytes <= bs->bl.max_transfer); |
522 | 556 | } |
523 | 557 | |
524 | - err = rule_check(bs, offset, bytes); | |
558 | + err = rule_check(bs, offset, bytes, BLKDEBUG_IO_TYPE_READ); | |
525 | 559 | if (err) { |
526 | 560 | return err; |
527 | 561 | } |
@@ -542,7 +576,7 @@ blkdebug_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, | ||
542 | 576 | assert(bytes <= bs->bl.max_transfer); |
543 | 577 | } |
544 | 578 | |
545 | - err = rule_check(bs, offset, bytes); | |
579 | + err = rule_check(bs, offset, bytes, BLKDEBUG_IO_TYPE_WRITE); | |
546 | 580 | if (err) { |
547 | 581 | return err; |
548 | 582 | } |
@@ -552,7 +586,7 @@ blkdebug_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, | ||
552 | 586 | |
553 | 587 | static int blkdebug_co_flush(BlockDriverState *bs) |
554 | 588 | { |
555 | - int err = rule_check(bs, 0, 0); | |
589 | + int err = rule_check(bs, 0, 0, BLKDEBUG_IO_TYPE_FLUSH); | |
556 | 590 | |
557 | 591 | if (err) { |
558 | 592 | return err; |
@@ -586,7 +620,7 @@ static int coroutine_fn blkdebug_co_pwrite_zeroes(BlockDriverState *bs, | ||
586 | 620 | assert(bytes <= bs->bl.max_pwrite_zeroes); |
587 | 621 | } |
588 | 622 | |
589 | - err = rule_check(bs, offset, bytes); | |
623 | + err = rule_check(bs, offset, bytes, BLKDEBUG_IO_TYPE_WRITE_ZEROES); | |
590 | 624 | if (err) { |
591 | 625 | return err; |
592 | 626 | } |
@@ -620,7 +654,7 @@ static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs, | ||
620 | 654 | assert(bytes <= bs->bl.max_pdiscard); |
621 | 655 | } |
622 | 656 | |
623 | - err = rule_check(bs, offset, bytes); | |
657 | + err = rule_check(bs, offset, bytes, BLKDEBUG_IO_TYPE_DISCARD); | |
624 | 658 | if (err) { |
625 | 659 | return err; |
626 | 660 | } |
@@ -636,7 +670,15 @@ static int coroutine_fn blkdebug_co_block_status(BlockDriverState *bs, | ||
636 | 670 | int64_t *map, |
637 | 671 | BlockDriverState **file) |
638 | 672 | { |
673 | + int err; | |
674 | + | |
639 | 675 | assert(QEMU_IS_ALIGNED(offset | bytes, bs->bl.request_alignment)); |
676 | + | |
677 | + err = rule_check(bs, offset, bytes, BLKDEBUG_IO_TYPE_BLOCK_STATUS); | |
678 | + if (err) { | |
679 | + return err; | |
680 | + } | |
681 | + | |
640 | 682 | return bdrv_co_block_status_from_file(bs, want_zero, offset, bytes, |
641 | 683 | pnum, map, file); |
642 | 684 | } |
@@ -1608,13 +1608,13 @@ static void external_snapshot_prepare(BlkActionState *common, | ||
1608 | 1608 | s->has_snapshot_node_name ? s->snapshot_node_name : NULL; |
1609 | 1609 | |
1610 | 1610 | if (node_name && !snapshot_node_name) { |
1611 | - error_setg(errp, "New snapshot node name missing"); | |
1611 | + error_setg(errp, "New overlay node name missing"); | |
1612 | 1612 | goto out; |
1613 | 1613 | } |
1614 | 1614 | |
1615 | 1615 | if (snapshot_node_name && |
1616 | 1616 | bdrv_lookup_bs(snapshot_node_name, snapshot_node_name, NULL)) { |
1617 | - error_setg(errp, "New snapshot node name already in use"); | |
1617 | + error_setg(errp, "New overlay node name already in use"); | |
1618 | 1618 | goto out; |
1619 | 1619 | } |
1620 | 1620 |
@@ -1656,7 +1656,7 @@ static void external_snapshot_prepare(BlkActionState *common, | ||
1656 | 1656 | } |
1657 | 1657 | |
1658 | 1658 | if (bdrv_has_blk(state->new_bs)) { |
1659 | - error_setg(errp, "The snapshot is already in use"); | |
1659 | + error_setg(errp, "The overlay is already in use"); | |
1660 | 1660 | goto out; |
1661 | 1661 | } |
1662 | 1662 |
@@ -1666,12 +1666,12 @@ static void external_snapshot_prepare(BlkActionState *common, | ||
1666 | 1666 | } |
1667 | 1667 | |
1668 | 1668 | if (state->new_bs->backing != NULL) { |
1669 | - error_setg(errp, "The snapshot already has a backing image"); | |
1669 | + error_setg(errp, "The overlay already has a backing image"); | |
1670 | 1670 | goto out; |
1671 | 1671 | } |
1672 | 1672 | |
1673 | 1673 | if (!state->new_bs->drv->supports_backing) { |
1674 | - error_setg(errp, "The snapshot does not support backing images"); | |
1674 | + error_setg(errp, "The overlay does not support backing images"); | |
1675 | 1675 | goto out; |
1676 | 1676 | } |
1677 | 1677 |
@@ -1876,10 +1876,6 @@ static void blockdev_backup_prepare(BlkActionState *common, Error **errp) | ||
1876 | 1876 | } |
1877 | 1877 | |
1878 | 1878 | aio_context = bdrv_get_aio_context(bs); |
1879 | - if (aio_context != bdrv_get_aio_context(target)) { | |
1880 | - error_setg(errp, "Backup between two IO threads is not implemented"); | |
1881 | - return; | |
1882 | - } | |
1883 | 1879 | aio_context_acquire(aio_context); |
1884 | 1880 | state->bs = bs; |
1885 | 1881 |
@@ -2648,6 +2648,7 @@ static void fdctrl_realize_common(DeviceState *dev, FDCtrl *fdctrl, | ||
2648 | 2648 | |
2649 | 2649 | FLOPPY_DPRINTF("init controller\n"); |
2650 | 2650 | fdctrl->fifo = qemu_memalign(512, FD_SECTOR_LEN); |
2651 | + memset(fdctrl->fifo, 0, FD_SECTOR_LEN); | |
2651 | 2652 | fdctrl->fifo_size = 512; |
2652 | 2653 | fdctrl->result_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, |
2653 | 2654 | fdctrl_result_timer, fdctrl); |
@@ -402,42 +402,75 @@ class QEMUMachine(object): | ||
402 | 402 | self._qmp.clear_events() |
403 | 403 | return events |
404 | 404 | |
405 | - def event_wait(self, name, timeout=60.0, match=None): | |
405 | + @staticmethod | |
406 | + def event_match(event, match=None): | |
406 | 407 | """ |
407 | - Wait for specified timeout on named event in QMP; optionally filter | |
408 | - results by match. | |
408 | + Check if an event matches optional match criteria. | |
409 | + | |
410 | + The match criteria takes the form of a matching subdict. The event is | |
411 | + checked to be a superset of the subdict, recursively, with matching | |
412 | + values whenever the subdict values are not None. | |
409 | 413 | |
410 | - The 'match' is checked to be a recursive subset of the 'event'; skips | |
411 | - branch processing on match's value None | |
412 | - {"foo": {"bar": 1}} matches {"foo": None} | |
413 | - {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}} | |
414 | + This has a limitation that you cannot explicitly check for None values. | |
415 | + | |
416 | + Examples, with the subdict queries on the left: | |
417 | + - None matches any object. | |
418 | + - {"foo": None} matches {"foo": {"bar": 1}} | |
419 | + - {"foo": None} matches {"foo": 5} | |
420 | + - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} | |
421 | + - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} | |
414 | 422 | """ |
415 | - def event_match(event, match=None): | |
416 | - if match is None: | |
417 | - return True | |
423 | + if match is None: | |
424 | + return True | |
418 | 425 | |
426 | + try: | |
419 | 427 | for key in match: |
420 | 428 | if key in event: |
421 | - if isinstance(event[key], dict): | |
422 | - if not event_match(event[key], match[key]): | |
423 | - return False | |
424 | - elif event[key] != match[key]: | |
429 | + if not QEMUMachine.event_match(event[key], match[key]): | |
425 | 430 | return False |
426 | 431 | else: |
427 | 432 | return False |
428 | - | |
429 | 433 | return True |
434 | + except TypeError: | |
435 | + # either match or event wasn't iterable (not a dict) | |
436 | + return match == event | |
437 | + | |
438 | + def event_wait(self, name, timeout=60.0, match=None): | |
439 | + """ | |
440 | + event_wait waits for and returns a named event from QMP with a timeout. | |
441 | + | |
442 | + name: The event to wait for. | |
443 | + timeout: QEMUMonitorProtocol.pull_event timeout parameter. | |
444 | + match: Optional match criteria. See event_match for details. | |
445 | + """ | |
446 | + return self.events_wait([(name, match)], timeout) | |
447 | + | |
448 | + def events_wait(self, events, timeout=60.0): | |
449 | + """ | |
450 | + events_wait waits for and returns a named event from QMP with a timeout. | |
451 | + | |
452 | + events: a sequence of (name, match_criteria) tuples. | |
453 | + The match criteria are optional and may be None. | |
454 | + See event_match for details. | |
455 | + timeout: QEMUMonitorProtocol.pull_event timeout parameter. | |
456 | + """ | |
457 | + def _match(event): | |
458 | + for name, match in events: | |
459 | + if (event['event'] == name and | |
460 | + self.event_match(event, match)): | |
461 | + return True | |
462 | + return False | |
430 | 463 | |
431 | 464 | # Search cached events |
432 | 465 | for event in self._events: |
433 | - if (event['event'] == name) and event_match(event, match): | |
466 | + if _match(event): | |
434 | 467 | self._events.remove(event) |
435 | 468 | return event |
436 | 469 | |
437 | 470 | # Poll for new events |
438 | 471 | while True: |
439 | 472 | event = self._qmp.pull_event(wait=timeout) |
440 | - if (event['event'] == name) and event_match(event, match): | |
473 | + if _match(event): | |
441 | 474 | return event |
442 | 475 | self._events.append(event) |
443 | 476 |
@@ -1279,17 +1279,17 @@ | ||
1279 | 1279 | # |
1280 | 1280 | # Either @device or @node-name must be set but not both. |
1281 | 1281 | # |
1282 | -# @device: the name of the device to generate the snapshot from. | |
1282 | +# @device: the name of the device to take a snapshot of. | |
1283 | 1283 | # |
1284 | 1284 | # @node-name: graph node name to generate the snapshot from (Since 2.0) |
1285 | 1285 | # |
1286 | -# @snapshot-file: the target of the new image. If the file exists, or | |
1287 | -# if it is a device, the snapshot will be created in the existing | |
1288 | -# file/device. Otherwise, a new file will be created. | |
1286 | +# @snapshot-file: the target of the new overlay image. If the file | |
1287 | +# exists, or if it is a device, the overlay will be created in the | |
1288 | +# existing file/device. Otherwise, a new file will be created. | |
1289 | 1289 | # |
1290 | 1290 | # @snapshot-node-name: the graph node name of the new image (Since 2.0) |
1291 | 1291 | # |
1292 | -# @format: the format of the snapshot image, default is 'qcow2'. | |
1292 | +# @format: the format of the overlay image, default is 'qcow2'. | |
1293 | 1293 | # |
1294 | 1294 | # @mode: whether and how QEMU should create a new image, default is |
1295 | 1295 | # 'absolute-paths'. |
@@ -1302,10 +1302,10 @@ | ||
1302 | 1302 | ## |
1303 | 1303 | # @BlockdevSnapshot: |
1304 | 1304 | # |
1305 | -# @node: device or node name that will have a snapshot created. | |
1305 | +# @node: device or node name that will have a snapshot taken. | |
1306 | 1306 | # |
1307 | 1307 | # @overlay: reference to the existing block device that will become |
1308 | -# the overlay of @node, as part of creating the snapshot. | |
1308 | +# the overlay of @node, as part of taking the snapshot. | |
1309 | 1309 | # It must not have a current backing file (this can be |
1310 | 1310 | # achieved by passing "backing": null to blockdev-add). |
1311 | 1311 | # |
@@ -1443,7 +1443,7 @@ | ||
1443 | 1443 | ## |
1444 | 1444 | # @blockdev-snapshot-sync: |
1445 | 1445 | # |
1446 | -# Generates a synchronous snapshot of a block device. | |
1446 | +# Takes a synchronous snapshot of a block device. | |
1447 | 1447 | # |
1448 | 1448 | # For the arguments, see the documentation of BlockdevSnapshotSync. |
1449 | 1449 | # |
@@ -1469,9 +1469,9 @@ | ||
1469 | 1469 | ## |
1470 | 1470 | # @blockdev-snapshot: |
1471 | 1471 | # |
1472 | -# Generates a snapshot of a block device. | |
1472 | +# Takes a snapshot of a block device. | |
1473 | 1473 | # |
1474 | -# Create a snapshot, by installing 'node' as the backing image of | |
1474 | +# Take a snapshot, by installing 'node' as the backing image of | |
1475 | 1475 | # 'overlay'. Additionally, if 'node' is associated with a block |
1476 | 1476 | # device, the block device changes to using 'overlay' as its new active |
1477 | 1477 | # image. |
@@ -3244,6 +3244,8 @@ | ||
3244 | 3244 | # |
3245 | 3245 | # @cluster_alloc_space: an allocation of file space for a cluster (since 4.1) |
3246 | 3246 | # |
3247 | +# @none: triggers once at creation of the blkdebug node (since 4.1) | |
3248 | +# | |
3247 | 3249 | # Since: 2.9 |
3248 | 3250 | ## |
3249 | 3251 | { 'enum': 'BlkdebugEvent', 'prefix': 'BLKDBG', |
@@ -3262,7 +3264,30 @@ | ||
3262 | 3264 | 'pwritev_rmw_tail', 'pwritev_rmw_after_tail', 'pwritev', |
3263 | 3265 | 'pwritev_zero', 'pwritev_done', 'empty_image_prepare', |
3264 | 3266 | 'l1_shrink_write_table', 'l1_shrink_free_l2_clusters', |
3265 | - 'cor_write', 'cluster_alloc_space'] } | |
3267 | + 'cor_write', 'cluster_alloc_space', 'none'] } | |
3268 | + | |
3269 | +## | |
3270 | +# @BlkdebugIOType: | |
3271 | +# | |
3272 | +# Kinds of I/O that blkdebug can inject errors in. | |
3273 | +# | |
3274 | +# @read: .bdrv_co_preadv() | |
3275 | +# | |
3276 | +# @write: .bdrv_co_pwritev() | |
3277 | +# | |
3278 | +# @write-zeroes: .bdrv_co_pwrite_zeroes() | |
3279 | +# | |
3280 | +# @discard: .bdrv_co_pdiscard() | |
3281 | +# | |
3282 | +# @flush: .bdrv_co_flush_to_disk() | |
3283 | +# | |
3284 | +# @block-status: .bdrv_co_block_status() | |
3285 | +# | |
3286 | +# Since: 4.1 | |
3287 | +## | |
3288 | +{ 'enum': 'BlkdebugIOType', 'prefix': 'BLKDEBUG_IO_TYPE', | |
3289 | + 'data': [ 'read', 'write', 'write-zeroes', 'discard', 'flush', | |
3290 | + 'block-status' ] } | |
3266 | 3291 | |
3267 | 3292 | ## |
3268 | 3293 | # @BlkdebugInjectErrorOptions: |
@@ -3274,6 +3299,11 @@ | ||
3274 | 3299 | # @state: the state identifier blkdebug needs to be in to |
3275 | 3300 | # actually trigger the event; defaults to "any" |
3276 | 3301 | # |
3302 | +# @iotype: the type of I/O operations on which this error should | |
3303 | +# be injected; defaults to "all read, write, | |
3304 | +# write-zeroes, discard, and flush operations" | |
3305 | +# (since: 4.1) | |
3306 | +# | |
3277 | 3307 | # @errno: error identifier (errno) to be returned; defaults to |
3278 | 3308 | # EIO |
3279 | 3309 | # |
@@ -3291,6 +3321,7 @@ | ||
3291 | 3321 | { 'struct': 'BlkdebugInjectErrorOptions', |
3292 | 3322 | 'data': { 'event': 'BlkdebugEvent', |
3293 | 3323 | '*state': 'int', |
3324 | + '*iotype': 'BlkdebugIOType', | |
3294 | 3325 | '*errno': 'int', |
3295 | 3326 | '*sector': 'int', |
3296 | 3327 | '*once': 'bool', |
@@ -44,9 +44,9 @@ STEXI | ||
44 | 44 | ETEXI |
45 | 45 | |
46 | 46 | DEF("convert", img_convert, |
47 | - "convert [--object objectdef] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] filename [filename2 [...]] output_filename") | |
47 | + "convert [--object objectdef] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename") | |
48 | 48 | STEXI |
49 | -@item convert [--object @var{objectdef}] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-B @var{backing_file}] [-o @var{options}] [-l @var{snapshot_param}] [-S @var{sparse_size}] [-m @var{num_coroutines}] [-W] @var{filename} [@var{filename2} [...]] @var{output_filename} | |
49 | +@item convert [--object @var{objectdef}] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-B @var{backing_file}] [-o @var{options}] [-l @var{snapshot_param}] [-S @var{sparse_size}] [-m @var{num_coroutines}] [-W] [--salvage] @var{filename} [@var{filename2} [...]] @var{output_filename} | |
50 | 50 | ETEXI |
51 | 51 | |
52 | 52 | DEF("create", img_create, |
@@ -69,6 +69,7 @@ enum { | ||
69 | 69 | OPTION_SIZE = 264, |
70 | 70 | OPTION_PREALLOCATION = 265, |
71 | 71 | OPTION_SHRINK = 266, |
72 | + OPTION_SALVAGE = 267, | |
72 | 73 | }; |
73 | 74 | |
74 | 75 | typedef enum OutputFormat { |
@@ -1581,6 +1582,8 @@ typedef struct ImgConvertState { | ||
1581 | 1582 | int64_t target_backing_sectors; /* negative if unknown */ |
1582 | 1583 | bool wr_in_order; |
1583 | 1584 | bool copy_range; |
1585 | + bool salvage; | |
1586 | + bool quiet; | |
1584 | 1587 | int min_sparse; |
1585 | 1588 | int alignment; |
1586 | 1589 | size_t cluster_sectors; |
@@ -1627,25 +1630,44 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) | ||
1627 | 1630 | } |
1628 | 1631 | |
1629 | 1632 | if (s->sector_next_status <= sector_num) { |
1630 | - int64_t count = n * BDRV_SECTOR_SIZE; | |
1633 | + uint64_t offset = (sector_num - src_cur_offset) * BDRV_SECTOR_SIZE; | |
1634 | + int64_t count; | |
1631 | 1635 | |
1632 | - if (s->target_has_backing) { | |
1636 | + do { | |
1637 | + count = n * BDRV_SECTOR_SIZE; | |
1638 | + | |
1639 | + if (s->target_has_backing) { | |
1640 | + ret = bdrv_block_status(blk_bs(s->src[src_cur]), offset, | |
1641 | + count, &count, NULL, NULL); | |
1642 | + } else { | |
1643 | + ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL, | |
1644 | + offset, count, &count, NULL, | |
1645 | + NULL); | |
1646 | + } | |
1647 | + | |
1648 | + if (ret < 0) { | |
1649 | + if (s->salvage) { | |
1650 | + if (n == 1) { | |
1651 | + if (!s->quiet) { | |
1652 | + warn_report("error while reading block status at " | |
1653 | + "offset %" PRIu64 ": %s", offset, | |
1654 | + strerror(-ret)); | |
1655 | + } | |
1656 | + /* Just try to read the data, then */ | |
1657 | + ret = BDRV_BLOCK_DATA; | |
1658 | + count = BDRV_SECTOR_SIZE; | |
1659 | + } else { | |
1660 | + /* Retry on a shorter range */ | |
1661 | + n = DIV_ROUND_UP(n, 4); | |
1662 | + } | |
1663 | + } else { | |
1664 | + error_report("error while reading block status at offset " | |
1665 | + "%" PRIu64 ": %s", offset, strerror(-ret)); | |
1666 | + return ret; | |
1667 | + } | |
1668 | + } | |
1669 | + } while (ret < 0); | |
1633 | 1670 | |
1634 | - ret = bdrv_block_status(blk_bs(s->src[src_cur]), | |
1635 | - (sector_num - src_cur_offset) * | |
1636 | - BDRV_SECTOR_SIZE, | |
1637 | - count, &count, NULL, NULL); | |
1638 | - } else { | |
1639 | - ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL, | |
1640 | - (sector_num - src_cur_offset) * | |
1641 | - BDRV_SECTOR_SIZE, | |
1642 | - count, &count, NULL, NULL); | |
1643 | - } | |
1644 | - if (ret < 0) { | |
1645 | - error_report("error while reading block status of sector %" PRId64 | |
1646 | - ": %s", sector_num, strerror(-ret)); | |
1647 | - return ret; | |
1648 | - } | |
1649 | 1671 | n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE); |
1650 | 1672 | |
1651 | 1673 | if (ret & BDRV_BLOCK_ZERO) { |
@@ -1682,6 +1704,7 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) | ||
1682 | 1704 | static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num, |
1683 | 1705 | int nb_sectors, uint8_t *buf) |
1684 | 1706 | { |
1707 | + uint64_t single_read_until = 0; | |
1685 | 1708 | int n, ret; |
1686 | 1709 | |
1687 | 1710 | assert(nb_sectors <= s->buf_sectors); |
@@ -1689,6 +1712,7 @@ static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num, | ||
1689 | 1712 | BlockBackend *blk; |
1690 | 1713 | int src_cur; |
1691 | 1714 | int64_t bs_sectors, src_cur_offset; |
1715 | + uint64_t offset; | |
1692 | 1716 | |
1693 | 1717 | /* In the case of compression with multiple source files, we can get a |
1694 | 1718 | * nb_sectors that spreads into the next part. So we must be able to |
@@ -1697,13 +1721,29 @@ static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num, | ||
1697 | 1721 | blk = s->src[src_cur]; |
1698 | 1722 | bs_sectors = s->src_sectors[src_cur]; |
1699 | 1723 | |
1724 | + offset = (sector_num - src_cur_offset) << BDRV_SECTOR_BITS; | |
1725 | + | |
1700 | 1726 | n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset)); |
1727 | + if (single_read_until > offset) { | |
1728 | + n = 1; | |
1729 | + } | |
1701 | 1730 | |
1702 | - ret = blk_co_pread( | |
1703 | - blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS, | |
1704 | - n << BDRV_SECTOR_BITS, buf, 0); | |
1731 | + ret = blk_co_pread(blk, offset, n << BDRV_SECTOR_BITS, buf, 0); | |
1705 | 1732 | if (ret < 0) { |
1706 | - return ret; | |
1733 | + if (s->salvage) { | |
1734 | + if (n > 1) { | |
1735 | + single_read_until = offset + (n << BDRV_SECTOR_BITS); | |
1736 | + continue; | |
1737 | + } else { | |
1738 | + if (!s->quiet) { | |
1739 | + warn_report("error while reading offset %" PRIu64 | |
1740 | + ": %s", offset, strerror(-ret)); | |
1741 | + } | |
1742 | + memset(buf, 0, BDRV_SECTOR_SIZE); | |
1743 | + } | |
1744 | + } else { | |
1745 | + return ret; | |
1746 | + } | |
1707 | 1747 | } |
1708 | 1748 | |
1709 | 1749 | sector_num += n; |
@@ -2012,7 +2052,7 @@ static int img_convert(int argc, char **argv) | ||
2012 | 2052 | QDict *open_opts = NULL; |
2013 | 2053 | char *options = NULL; |
2014 | 2054 | Error *local_err = NULL; |
2015 | - bool writethrough, src_writethrough, quiet = false, image_opts = false, | |
2055 | + bool writethrough, src_writethrough, image_opts = false, | |
2016 | 2056 | skip_create = false, progress = false, tgt_image_opts = false; |
2017 | 2057 | int64_t ret = -EINVAL; |
2018 | 2058 | bool force_share = false; |
@@ -2034,6 +2074,7 @@ static int img_convert(int argc, char **argv) | ||
2034 | 2074 | {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, |
2035 | 2075 | {"force-share", no_argument, 0, 'U'}, |
2036 | 2076 | {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, |
2077 | + {"salvage", no_argument, 0, OPTION_SALVAGE}, | |
2037 | 2078 | {0, 0, 0, 0} |
2038 | 2079 | }; |
2039 | 2080 | c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU", |
@@ -2120,7 +2161,7 @@ static int img_convert(int argc, char **argv) | ||
2120 | 2161 | src_cache = optarg; |
2121 | 2162 | break; |
2122 | 2163 | case 'q': |
2123 | - quiet = true; | |
2164 | + s.quiet = true; | |
2124 | 2165 | break; |
2125 | 2166 | case 'n': |
2126 | 2167 | skip_create = true; |
@@ -2151,6 +2192,9 @@ static int img_convert(int argc, char **argv) | ||
2151 | 2192 | case OPTION_IMAGE_OPTS: |
2152 | 2193 | image_opts = true; |
2153 | 2194 | break; |
2195 | + case OPTION_SALVAGE: | |
2196 | + s.salvage = true; | |
2197 | + break; | |
2154 | 2198 | case OPTION_TARGET_IMAGE_OPTS: |
2155 | 2199 | tgt_image_opts = true; |
2156 | 2200 | break; |
@@ -2177,6 +2221,11 @@ static int img_convert(int argc, char **argv) | ||
2177 | 2221 | goto fail_getopt; |
2178 | 2222 | } |
2179 | 2223 | |
2224 | + if (s.copy_range && s.salvage) { | |
2225 | + error_report("Cannot use copy offloading in salvaging mode"); | |
2226 | + goto fail_getopt; | |
2227 | + } | |
2228 | + | |
2180 | 2229 | if (tgt_image_opts && !skip_create) { |
2181 | 2230 | error_report("--target-image-opts requires use of -n flag"); |
2182 | 2231 | goto fail_getopt; |
@@ -2209,7 +2258,7 @@ static int img_convert(int argc, char **argv) | ||
2209 | 2258 | } |
2210 | 2259 | |
2211 | 2260 | /* Initialize before goto out */ |
2212 | - if (quiet) { | |
2261 | + if (s.quiet) { | |
2213 | 2262 | progress = false; |
2214 | 2263 | } |
2215 | 2264 | qemu_progress_init(progress, 1.0); |
@@ -2220,7 +2269,7 @@ static int img_convert(int argc, char **argv) | ||
2220 | 2269 | |
2221 | 2270 | for (bs_i = 0; bs_i < s.src_num; bs_i++) { |
2222 | 2271 | s.src[bs_i] = img_open(image_opts, argv[optind + bs_i], |
2223 | - fmt, src_flags, src_writethrough, quiet, | |
2272 | + fmt, src_flags, src_writethrough, s.quiet, | |
2224 | 2273 | force_share); |
2225 | 2274 | if (!s.src[bs_i]) { |
2226 | 2275 | ret = -1; |
@@ -2383,7 +2432,7 @@ static int img_convert(int argc, char **argv) | ||
2383 | 2432 | |
2384 | 2433 | if (skip_create) { |
2385 | 2434 | s.target = img_open(tgt_image_opts, out_filename, out_fmt, |
2386 | - flags, writethrough, quiet, false); | |
2435 | + flags, writethrough, s.quiet, false); | |
2387 | 2436 | } else { |
2388 | 2437 | /* TODO ultimately we should allow --target-image-opts |
2389 | 2438 | * to be used even when -n is not given. |
@@ -2391,7 +2440,7 @@ static int img_convert(int argc, char **argv) | ||
2391 | 2440 | * to allow filenames in option syntax |
2392 | 2441 | */ |
2393 | 2442 | s.target = img_open_file(out_filename, open_opts, out_fmt, |
2394 | - flags, writethrough, quiet, false); | |
2443 | + flags, writethrough, s.quiet, false); | |
2395 | 2444 | open_opts = NULL; /* blk_new_open will have freed it */ |
2396 | 2445 | } |
2397 | 2446 | if (!s.target) { |
@@ -3350,6 +3399,7 @@ static int img_rebase(int argc, char **argv) | ||
3350 | 3399 | out_baseimg, |
3351 | 3400 | &local_err); |
3352 | 3401 | if (local_err) { |
3402 | + qobject_unref(options); | |
3353 | 3403 | error_reportf_err(local_err, |
3354 | 3404 | "Could not resolve backing filename: "); |
3355 | 3405 | ret = -1; |
@@ -3362,7 +3412,9 @@ static int img_rebase(int argc, char **argv) | ||
3362 | 3412 | */ |
3363 | 3413 | prefix_chain_bs = bdrv_find_backing_image(bs, out_real_path); |
3364 | 3414 | if (prefix_chain_bs) { |
3415 | + qobject_unref(options); | |
3365 | 3416 | g_free(out_real_path); |
3417 | + | |
3366 | 3418 | blk_new_backing = blk_new(qemu_get_aio_context(), |
3367 | 3419 | BLK_PERM_CONSISTENT_READ, |
3368 | 3420 | BLK_PERM_ALL); |
@@ -175,6 +175,10 @@ improve performance if the data is remote, such as with NFS or iSCSI backends, | ||
175 | 175 | but will not automatically sparsify zero sectors, and may result in a fully |
176 | 176 | allocated target image depending on the host support for getting allocation |
177 | 177 | information. |
178 | +@item --salvage | |
179 | +Try to ignore I/O errors when reading. Unless in quiet mode (@code{-q}), errors | |
180 | +will still be printed. Areas that cannot be read from the source will be | |
181 | +treated as containing only zeroes. | |
178 | 182 | @end table |
179 | 183 | |
180 | 184 | Parameters to dd subcommand: |
@@ -162,6 +162,7 @@ echo === convert: -C and other options === | ||
162 | 162 | run_qemu_img convert -C -S 4k -O $IMGFMT "$TEST_IMG" "$TEST_IMG".target |
163 | 163 | run_qemu_img convert -C -S 8k -O $IMGFMT "$TEST_IMG" "$TEST_IMG".target |
164 | 164 | run_qemu_img convert -C -c -O $IMGFMT "$TEST_IMG" "$TEST_IMG".target |
165 | +run_qemu_img convert -C --salvage -O $IMGFMT "$TEST_IMG" "$TEST_IMG".target | |
165 | 166 | |
166 | 167 | echo |
167 | 168 | echo === amend: Options specified more than once === |
@@ -567,6 +567,9 @@ qemu-img: Cannot enable copy offloading when -S is used | ||
567 | 567 | Testing: convert -C -c -O qcow2 TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.target |
568 | 568 | qemu-img: Cannot enable copy offloading when -c is used |
569 | 569 | |
570 | +Testing: convert -C --salvage -O qcow2 TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.target | |
571 | +qemu-img: Cannot use copy offloading in salvaging mode | |
572 | + | |
570 | 573 | === amend: Options specified more than once === |
571 | 574 | |
572 | 575 | Testing: amend -f foo -f qcow2 -o lazy_refcounts=on TEST_DIR/t.qcow2 |
@@ -64,13 +64,13 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/ | ||
64 | 64 | |
65 | 65 | === Invalid command - cannot create a snapshot using a file BDS === |
66 | 66 | |
67 | -{"error": {"class": "GenericError", "desc": "The snapshot does not support backing images"}} | |
67 | +{"error": {"class": "GenericError", "desc": "The overlay does not support backing images"}} | |
68 | 68 | |
69 | 69 | === Invalid command - snapshot node used as active layer === |
70 | 70 | |
71 | -{"error": {"class": "GenericError", "desc": "The snapshot is already in use"}} | |
72 | -{"error": {"class": "GenericError", "desc": "The snapshot is already in use"}} | |
73 | -{"error": {"class": "GenericError", "desc": "The snapshot is already in use"}} | |
71 | +{"error": {"class": "GenericError", "desc": "The overlay is already in use"}} | |
72 | +{"error": {"class": "GenericError", "desc": "The overlay is already in use"}} | |
73 | +{"error": {"class": "GenericError", "desc": "The overlay is already in use"}} | |
74 | 74 | |
75 | 75 | === Invalid command - snapshot node used as backing hd === |
76 | 76 |
@@ -81,7 +81,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/ | ||
81 | 81 | Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 |
82 | 82 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base |
83 | 83 | {"return": {}} |
84 | -{"error": {"class": "GenericError", "desc": "The snapshot already has a backing image"}} | |
84 | +{"error": {"class": "GenericError", "desc": "The overlay already has a backing image"}} | |
85 | 85 | |
86 | 86 | === Invalid command - The node does not exist === |
87 | 87 |
@@ -28,10 +28,25 @@ status=1 # failure is the default! | ||
28 | 28 | |
29 | 29 | _cleanup() |
30 | 30 | { |
31 | - _cleanup_test_img | |
31 | + _cleanup_test_img | |
32 | + rm -f "$TEST_DIR/empty" | |
32 | 33 | } |
33 | 34 | trap "_cleanup; exit \$status" 0 1 2 3 15 |
34 | 35 | |
36 | +# Some file systems sometimes allocate extra blocks independently of | |
37 | +# the file size. This function hides the resulting difference in the | |
38 | +# stat -c '%b' output. | |
39 | +# Parameter 1: Number of blocks an empty file occupies | |
40 | +# Parameter 2: Image size in bytes | |
41 | +_filter_blocks() | |
42 | +{ | |
43 | + extra_blocks=$1 | |
44 | + img_size=$2 | |
45 | + | |
46 | + sed -e "s/blocks=$extra_blocks\\(\$\\|[^0-9]\\)/nothing allocated/" \ | |
47 | + -e "s/blocks=$((extra_blocks + img_size / 512))\\(\$\\|[^0-9]\\)/everything allocated/" | |
48 | +} | |
49 | + | |
35 | 50 | # get standard environment, filters and checks |
36 | 51 | . ./common.rc |
37 | 52 | . ./common.filter |
@@ -40,18 +55,21 @@ _supported_fmt raw | ||
40 | 55 | _supported_proto file |
41 | 56 | _supported_os Linux |
42 | 57 | |
43 | -size=1m | |
58 | +size=$((1 * 1024 * 1024)) | |
59 | + | |
60 | +touch "$TEST_DIR/empty" | |
61 | +extra_blocks=$(stat -c '%b' "$TEST_DIR/empty") | |
44 | 62 | |
45 | 63 | echo |
46 | 64 | echo "== creating image with default preallocation ==" |
47 | 65 | _make_test_img $size | _filter_imgfmt |
48 | -stat -c "size=%s, blocks=%b" $TEST_IMG | |
66 | +stat -c "size=%s, blocks=%b" $TEST_IMG | _filter_blocks $extra_blocks $size | |
49 | 67 | |
50 | 68 | for mode in off full falloc; do |
51 | 69 | echo |
52 | 70 | echo "== creating image with preallocation $mode ==" |
53 | 71 | IMGOPTS=preallocation=$mode _make_test_img $size | _filter_imgfmt |
54 | - stat -c "size=%s, blocks=%b" $TEST_IMG | |
72 | + stat -c "size=%s, blocks=%b" $TEST_IMG | _filter_blocks $extra_blocks $size | |
55 | 73 | done |
56 | 74 | |
57 | 75 | # success, all done |
@@ -2,17 +2,17 @@ QA output created by 175 | ||
2 | 2 | |
3 | 3 | == creating image with default preallocation == |
4 | 4 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 |
5 | -size=1048576, blocks=0 | |
5 | +size=1048576, nothing allocated | |
6 | 6 | |
7 | 7 | == creating image with preallocation off == |
8 | 8 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 preallocation=off |
9 | -size=1048576, blocks=0 | |
9 | +size=1048576, nothing allocated | |
10 | 10 | |
11 | 11 | == creating image with preallocation full == |
12 | 12 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 preallocation=full |
13 | -size=1048576, blocks=2048 | |
13 | +size=1048576, everything allocated | |
14 | 14 | |
15 | 15 | == creating image with preallocation falloc == |
16 | 16 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 preallocation=falloc |
17 | -size=1048576, blocks=2048 | |
17 | +size=1048576, everything allocated | |
18 | 18 | *** done |
@@ -23,6 +23,8 @@ import iotests | ||
23 | 23 | |
24 | 24 | iotests.verify_image_format(supported_fmts=['qcow2']) |
25 | 25 | |
26 | +img_size = 4 * 1024 * 1024 | |
27 | + | |
26 | 28 | def pause_wait(vm, job_id): |
27 | 29 | with iotests.Timeout(3, "Timeout waiting for job to pause"): |
28 | 30 | while True: |
@@ -62,6 +64,8 @@ def test_pause_resume(vm): | ||
62 | 64 | iotests.log(vm.qmp('query-jobs')) |
63 | 65 | |
64 | 66 | def test_job_lifecycle(vm, job, job_args, has_ready=False): |
67 | + global img_size | |
68 | + | |
65 | 69 | iotests.log('') |
66 | 70 | iotests.log('') |
67 | 71 | iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' % |
@@ -84,6 +88,10 @@ def test_job_lifecycle(vm, job, job_args, has_ready=False): | ||
84 | 88 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) |
85 | 89 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) |
86 | 90 | |
91 | + # Wait for total-progress to stabilize | |
92 | + while vm.qmp('query-jobs')['return'][0]['total-progress'] < img_size: | |
93 | + pass | |
94 | + | |
87 | 95 | # RUNNING state: |
88 | 96 | # pause/resume should work, complete/finalize/dismiss should error out |
89 | 97 | iotests.log('') |
@@ -173,9 +181,8 @@ with iotests.FilePath('disk.img') as disk_path, \ | ||
173 | 181 | iotests.FilePath('copy.img') as copy_path, \ |
174 | 182 | iotests.VM() as vm: |
175 | 183 | |
176 | - img_size = '4M' | |
177 | - iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size) | |
178 | - iotests.qemu_io('-c', 'write 0 %s' % (img_size), | |
184 | + iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, str(img_size)) | |
185 | + iotests.qemu_io('-c', 'write 0 %i' % (img_size), | |
179 | 186 | '-f', iotests.imgfmt, disk_path) |
180 | 187 | |
181 | 188 | iotests.log('Launching VM...') |
@@ -0,0 +1,170 @@ | ||
1 | +#!/usr/bin/env bash | |
2 | +# | |
3 | +# Test qemu-img convert --salvage | |
4 | +# | |
5 | +# Copyright (C) 2019 Red Hat, Inc. | |
6 | +# | |
7 | +# This program is free software; you can redistribute it and/or modify | |
8 | +# it under the terms of the GNU General Public License as published by | |
9 | +# the Free Software Foundation; either version 2 of the License, or | |
10 | +# (at your option) any later version. | |
11 | +# | |
12 | +# This program is distributed in the hope that it will be useful, | |
13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +# GNU General Public License for more details. | |
16 | +# | |
17 | +# You should have received a copy of the GNU General Public License | |
18 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | +# | |
20 | + | |
21 | +# creator | |
22 | +owner=mreitz@redhat.com | |
23 | + | |
24 | +seq=$(basename $0) | |
25 | +echo "QA output created by $seq" | |
26 | + | |
27 | +status=1 # failure is the default! | |
28 | + | |
29 | +_cleanup() | |
30 | +{ | |
31 | + _cleanup_test_img | |
32 | +} | |
33 | +trap "_cleanup; exit \$status" 0 1 2 3 15 | |
34 | + | |
35 | +# get standard environment, filters and checks | |
36 | +. ./common.rc | |
37 | +. ./common.filter | |
38 | +. ./common.qemu | |
39 | + | |
40 | +_supported_fmt generic | |
41 | +_supported_proto file | |
42 | +_supported_os Linux | |
43 | + | |
44 | +if [ "$IMGOPTSSYNTAX" = "true" ]; then | |
45 | + # We use json:{} filenames here, so we cannot work with additional options. | |
46 | + _unsupported_fmt $IMGFMT | |
47 | +else | |
48 | + # With VDI, the output is ordered differently. Just disable it. | |
49 | + _unsupported_fmt vdi | |
50 | +fi | |
51 | + | |
52 | + | |
53 | +TEST_IMG="$TEST_IMG.orig" _make_test_img 64M | |
54 | + | |
55 | +$QEMU_IO -c 'write -P 42 0 64M' "$TEST_IMG.orig" | _filter_qemu_io | |
56 | + | |
57 | + | |
58 | +sector_size=512 | |
59 | + | |
60 | +# Offsets on which to fail block-status. Keep in ascending order so | |
61 | +# the indexing done by _filter_offsets will appear in ascending order | |
62 | +# in the output as well. | |
63 | +status_fail_offsets="$((16 * 1024 * 1024 + 8192)) | |
64 | + $((33 * 1024 * 1024 + 512))" | |
65 | + | |
66 | +# Offsets on which to fail reads. Keep in ascending order for the | |
67 | +# same reason. | |
68 | +# The second element is shared with $status_fail_offsets on purpose. | |
69 | +# Starting with the third element, we test what happens when a | |
70 | +# continuous range of sectors is inaccessible. | |
71 | +read_fail_offsets="$((32 * 1024 * 1024 - 65536)) | |
72 | + $((33 * 1024 * 1024 + 512)) | |
73 | + $(seq $((34 * 1024 * 1024)) $sector_size \ | |
74 | + $((34 * 1024 * 1024 + 4096 - $sector_size)))" | |
75 | + | |
76 | + | |
77 | +# blkdebug must be above the format layer so it can intercept all | |
78 | +# block-status events | |
79 | +source_img="json:{'driver': 'blkdebug', | |
80 | + 'image': { | |
81 | + 'driver': '$IMGFMT', | |
82 | + 'file': { | |
83 | + 'driver': 'file', | |
84 | + 'filename': '$TEST_IMG.orig' | |
85 | + } | |
86 | + }, | |
87 | + 'inject-error': [" | |
88 | + | |
89 | +for ofs in $status_fail_offsets | |
90 | +do | |
91 | + source_img+="{ 'event': 'none', | |
92 | + 'iotype': 'block-status', | |
93 | + 'errno': 5, | |
94 | + 'sector': $((ofs / sector_size)) }," | |
95 | +done | |
96 | + | |
97 | +for ofs in $read_fail_offsets | |
98 | +do | |
99 | + source_img+="{ 'event': 'none', | |
100 | + 'iotype': 'read', | |
101 | + 'errno': 5, | |
102 | + 'sector': $((ofs / sector_size)) }," | |
103 | +done | |
104 | + | |
105 | +# Remove the trailing comma and terminate @inject-error and json:{} | |
106 | +source_img="${source_img%,} ] }" | |
107 | + | |
108 | + | |
109 | +echo | |
110 | + | |
111 | + | |
112 | +_filter_offsets() { | |
113 | + filters= | |
114 | + | |
115 | + index=0 | |
116 | + for ofs in $1 | |
117 | + do | |
118 | + filters+=" -e s/$ofs/status_fail_offset_$index/" | |
119 | + index=$((index + 1)) | |
120 | + done | |
121 | + | |
122 | + index=0 | |
123 | + for ofs in $2 | |
124 | + do | |
125 | + filters+=" -e s/$ofs/read_fail_offset_$index/" | |
126 | + index=$((index + 1)) | |
127 | + done | |
128 | + | |
129 | + sed $filters | |
130 | +} | |
131 | + | |
132 | +# While determining the number of allocated sectors in the input | |
133 | +# image, we should see one block status warning per element of | |
134 | +# $status_fail_offsets. | |
135 | +# | |
136 | +# Then, the image is read. Since the block status is queried in | |
137 | +# basically the same way, the same warnings as in the previous step | |
138 | +# should reappear. Interleaved with those we should see a read | |
139 | +# warning per element of $read_fail_offsets. | |
140 | +# Note that $read_fail_offsets and $status_fail_offsets share an | |
141 | +# element (read_fail_offset_1 == status_fail_offset_1), so | |
142 | +# "status_fail_offset_1" in the output is the same as | |
143 | +# "read_fail_offset_1". | |
144 | +$QEMU_IMG convert --salvage "$source_img" "$TEST_IMG" 2>&1 \ | |
145 | + | _filter_offsets "$status_fail_offsets" "$read_fail_offsets" | |
146 | + | |
147 | +echo | |
148 | + | |
149 | +# The offsets where the block status could not be determined should | |
150 | +# have been treated as containing data and thus should be correct in | |
151 | +# the output image. | |
152 | +# The offsets where reading failed altogether should be 0. Make them | |
153 | +# 0 in the input image, too, so we can compare both images. | |
154 | +for ofs in $read_fail_offsets | |
155 | +do | |
156 | + $QEMU_IO -c "write -z $ofs $sector_size" "$TEST_IMG.orig" \ | |
157 | + | _filter_qemu_io \ | |
158 | + | _filter_offsets '' "$read_fail_offsets" | |
159 | +done | |
160 | + | |
161 | +echo | |
162 | + | |
163 | +# These should be equal now. | |
164 | +$QEMU_IMG compare "$TEST_IMG.orig" "$TEST_IMG" | |
165 | + | |
166 | + | |
167 | +# success, all done | |
168 | +echo "*** done" | |
169 | +rm -f $seq.full | |
170 | +status=0 |
@@ -0,0 +1,43 @@ | ||
1 | +QA output created by 251 | |
2 | +Formatting 'TEST_DIR/t.IMGFMT.orig', fmt=IMGFMT size=67108864 | |
3 | +wrote 67108864/67108864 bytes at offset 0 | |
4 | +64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
5 | + | |
6 | +qemu-img: warning: error while reading block status at offset status_fail_offset_0: Input/output error | |
7 | +qemu-img: warning: error while reading block status at offset status_fail_offset_1: Input/output error | |
8 | +qemu-img: warning: error while reading block status at offset status_fail_offset_0: Input/output error | |
9 | +qemu-img: warning: error while reading offset read_fail_offset_0: Input/output error | |
10 | +qemu-img: warning: error while reading block status at offset status_fail_offset_1: Input/output error | |
11 | +qemu-img: warning: error while reading offset status_fail_offset_1: Input/output error | |
12 | +qemu-img: warning: error while reading offset read_fail_offset_2: Input/output error | |
13 | +qemu-img: warning: error while reading offset read_fail_offset_3: Input/output error | |
14 | +qemu-img: warning: error while reading offset read_fail_offset_4: Input/output error | |
15 | +qemu-img: warning: error while reading offset read_fail_offset_5: Input/output error | |
16 | +qemu-img: warning: error while reading offset read_fail_offset_6: Input/output error | |
17 | +qemu-img: warning: error while reading offset read_fail_offset_7: Input/output error | |
18 | +qemu-img: warning: error while reading offset read_fail_offset_8: Input/output error | |
19 | +qemu-img: warning: error while reading offset read_fail_offset_9: Input/output error | |
20 | + | |
21 | +wrote 512/512 bytes at offset read_fail_offset_0 | |
22 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
23 | +wrote 512/512 bytes at offset read_fail_offset_1 | |
24 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
25 | +wrote 512/512 bytes at offset read_fail_offset_2 | |
26 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
27 | +wrote 512/512 bytes at offset read_fail_offset_3 | |
28 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
29 | +wrote 512/512 bytes at offset read_fail_offset_4 | |
30 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
31 | +wrote 512/512 bytes at offset read_fail_offset_5 | |
32 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
33 | +wrote 512/512 bytes at offset read_fail_offset_6 | |
34 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
35 | +wrote 512/512 bytes at offset read_fail_offset_7 | |
36 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
37 | +wrote 512/512 bytes at offset read_fail_offset_8 | |
38 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
39 | +wrote 512/512 bytes at offset read_fail_offset_9 | |
40 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | |
41 | + | |
42 | +Images are identical. | |
43 | +*** done |
@@ -21,6 +21,8 @@ | ||
21 | 21 | import iotests |
22 | 22 | from iotests import qemu_img_create, file_path, log |
23 | 23 | |
24 | +iotests.verify_image_format(supported_fmts=['qcow2']) | |
25 | + | |
24 | 26 | disk, top = file_path('disk', 'top') |
25 | 27 | size = 1024 * 1024 |
26 | 28 |
@@ -0,0 +1,122 @@ | ||
1 | +#!/usr/bin/env python | |
2 | +# | |
3 | +# Test incremental/backup across iothread contexts | |
4 | +# | |
5 | +# Copyright (c) 2019 John Snow for Red Hat, Inc. | |
6 | +# | |
7 | +# This program is free software; you can redistribute it and/or modify | |
8 | +# it under the terms of the GNU General Public License as published by | |
9 | +# the Free Software Foundation; either version 2 of the License, or | |
10 | +# (at your option) any later version. | |
11 | +# | |
12 | +# This program is distributed in the hope that it will be useful, | |
13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +# GNU General Public License for more details. | |
16 | +# | |
17 | +# You should have received a copy of the GNU General Public License | |
18 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | +# | |
20 | +# owner=jsnow@redhat.com | |
21 | + | |
22 | +import os | |
23 | +import iotests | |
24 | +from iotests import log | |
25 | + | |
26 | +iotests.verify_image_format(supported_fmts=['qcow2']) | |
27 | +size = 64 * 1024 * 1024 | |
28 | + | |
29 | +with iotests.FilePath('img0') as img0_path, \ | |
30 | + iotests.FilePath('img1') as img1_path, \ | |
31 | + iotests.FilePath('img0-full') as img0_full_path, \ | |
32 | + iotests.FilePath('img1-full') as img1_full_path, \ | |
33 | + iotests.FilePath('img0-incr') as img0_incr_path, \ | |
34 | + iotests.FilePath('img1-incr') as img1_incr_path, \ | |
35 | + iotests.VM() as vm: | |
36 | + | |
37 | + def create_target(filepath, name, size): | |
38 | + basename = os.path.basename(filepath) | |
39 | + nodename = "file_{}".format(basename) | |
40 | + log(vm.command('blockdev-create', job_id='job1', | |
41 | + options={ | |
42 | + 'driver': 'file', | |
43 | + 'filename': filepath, | |
44 | + 'size': 0, | |
45 | + })) | |
46 | + vm.run_job('job1') | |
47 | + log(vm.command('blockdev-add', driver='file', | |
48 | + node_name=nodename, filename=filepath)) | |
49 | + log(vm.command('blockdev-create', job_id='job2', | |
50 | + options={ | |
51 | + 'driver': iotests.imgfmt, | |
52 | + 'file': nodename, | |
53 | + 'size': size, | |
54 | + })) | |
55 | + vm.run_job('job2') | |
56 | + log(vm.command('blockdev-add', driver=iotests.imgfmt, | |
57 | + node_name=name, | |
58 | + file=nodename)) | |
59 | + | |
60 | + log('--- Preparing images & VM ---\n') | |
61 | + vm.add_object('iothread,id=iothread0') | |
62 | + vm.add_object('iothread,id=iothread1') | |
63 | + vm.add_device('virtio-scsi-pci,id=scsi0,iothread=iothread0') | |
64 | + vm.add_device('virtio-scsi-pci,id=scsi1,iothread=iothread1') | |
65 | + iotests.qemu_img_create('-f', iotests.imgfmt, img0_path, str(size)) | |
66 | + iotests.qemu_img_create('-f', iotests.imgfmt, img1_path, str(size)) | |
67 | + vm.add_drive(img0_path, interface='none') | |
68 | + vm.add_device('scsi-hd,id=device0,drive=drive0,bus=scsi0.0') | |
69 | + vm.add_drive(img1_path, interface='none') | |
70 | + vm.add_device('scsi-hd,id=device1,drive=drive1,bus=scsi1.0') | |
71 | + | |
72 | + log('--- Starting VM ---\n') | |
73 | + vm.launch() | |
74 | + | |
75 | + log('--- Create Targets & Full Backups ---\n') | |
76 | + create_target(img0_full_path, 'img0-full', size) | |
77 | + create_target(img1_full_path, 'img1-full', size) | |
78 | + ret = vm.qmp_log('transaction', indent=2, actions=[ | |
79 | + { 'type': 'block-dirty-bitmap-add', | |
80 | + 'data': { 'node': 'drive0', 'name': 'bitmap0' }}, | |
81 | + { 'type': 'block-dirty-bitmap-add', | |
82 | + 'data': { 'node': 'drive1', 'name': 'bitmap1' }}, | |
83 | + { 'type': 'blockdev-backup', | |
84 | + 'data': { 'device': 'drive0', | |
85 | + 'target': 'img0-full', | |
86 | + 'sync': 'full', | |
87 | + 'job-id': 'j0' }}, | |
88 | + { 'type': 'blockdev-backup', | |
89 | + 'data': { 'device': 'drive1', | |
90 | + 'target': 'img1-full', | |
91 | + 'sync': 'full', | |
92 | + 'job-id': 'j1' }} | |
93 | + ]) | |
94 | + if "error" in ret: | |
95 | + raise Exception(ret['error']['desc']) | |
96 | + vm.run_job('j0', auto_dismiss=True) | |
97 | + vm.run_job('j1', auto_dismiss=True) | |
98 | + | |
99 | + log('\n--- Create Targets & Incremental Backups ---\n') | |
100 | + create_target(img0_incr_path, 'img0-incr', size) | |
101 | + create_target(img1_incr_path, 'img1-incr', size) | |
102 | + ret = vm.qmp_log('transaction', indent=2, actions=[ | |
103 | + { 'type': 'blockdev-backup', | |
104 | + 'data': { 'device': 'drive0', | |
105 | + 'target': 'img0-incr', | |
106 | + 'sync': 'incremental', | |
107 | + 'bitmap': 'bitmap0', | |
108 | + 'job-id': 'j2' }}, | |
109 | + { 'type': 'blockdev-backup', | |
110 | + 'data': { 'device': 'drive1', | |
111 | + 'target': 'img1-incr', | |
112 | + 'sync': 'incremental', | |
113 | + 'bitmap': 'bitmap1', | |
114 | + 'job-id': 'j3' }} | |
115 | + ]) | |
116 | + if "error" in ret: | |
117 | + raise Exception(ret['error']['desc']) | |
118 | + vm.run_job('j2', auto_dismiss=True) | |
119 | + vm.run_job('j3', auto_dismiss=True) | |
120 | + | |
121 | + log('\n--- Done ---') | |
122 | + vm.shutdown() |
@@ -0,0 +1,119 @@ | ||
1 | +--- Preparing images & VM --- | |
2 | + | |
3 | +--- Starting VM --- | |
4 | + | |
5 | +--- Create Targets & Full Backups --- | |
6 | + | |
7 | +{} | |
8 | +{"execute": "job-dismiss", "arguments": {"id": "job1"}} | |
9 | +{"return": {}} | |
10 | +{} | |
11 | +{} | |
12 | +{"execute": "job-dismiss", "arguments": {"id": "job2"}} | |
13 | +{"return": {}} | |
14 | +{} | |
15 | +{} | |
16 | +{"execute": "job-dismiss", "arguments": {"id": "job1"}} | |
17 | +{"return": {}} | |
18 | +{} | |
19 | +{} | |
20 | +{"execute": "job-dismiss", "arguments": {"id": "job2"}} | |
21 | +{"return": {}} | |
22 | +{} | |
23 | +{ | |
24 | + "execute": "transaction", | |
25 | + "arguments": { | |
26 | + "actions": [ | |
27 | + { | |
28 | + "data": { | |
29 | + "name": "bitmap0", | |
30 | + "node": "drive0" | |
31 | + }, | |
32 | + "type": "block-dirty-bitmap-add" | |
33 | + }, | |
34 | + { | |
35 | + "data": { | |
36 | + "name": "bitmap1", | |
37 | + "node": "drive1" | |
38 | + }, | |
39 | + "type": "block-dirty-bitmap-add" | |
40 | + }, | |
41 | + { | |
42 | + "data": { | |
43 | + "device": "drive0", | |
44 | + "job-id": "j0", | |
45 | + "sync": "full", | |
46 | + "target": "img0-full" | |
47 | + }, | |
48 | + "type": "blockdev-backup" | |
49 | + }, | |
50 | + { | |
51 | + "data": { | |
52 | + "device": "drive1", | |
53 | + "job-id": "j1", | |
54 | + "sync": "full", | |
55 | + "target": "img1-full" | |
56 | + }, | |
57 | + "type": "blockdev-backup" | |
58 | + } | |
59 | + ] | |
60 | + } | |
61 | +} | |
62 | +{ | |
63 | + "return": {} | |
64 | +} | |
65 | +{"data": {"device": "j0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} | |
66 | +{"data": {"device": "j1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} | |
67 | + | |
68 | +--- Create Targets & Incremental Backups --- | |
69 | + | |
70 | +{} | |
71 | +{"execute": "job-dismiss", "arguments": {"id": "job1"}} | |
72 | +{"return": {}} | |
73 | +{} | |
74 | +{} | |
75 | +{"execute": "job-dismiss", "arguments": {"id": "job2"}} | |
76 | +{"return": {}} | |
77 | +{} | |
78 | +{} | |
79 | +{"execute": "job-dismiss", "arguments": {"id": "job1"}} | |
80 | +{"return": {}} | |
81 | +{} | |
82 | +{} | |
83 | +{"execute": "job-dismiss", "arguments": {"id": "job2"}} | |
84 | +{"return": {}} | |
85 | +{} | |
86 | +{ | |
87 | + "execute": "transaction", | |
88 | + "arguments": { | |
89 | + "actions": [ | |
90 | + { | |
91 | + "data": { | |
92 | + "bitmap": "bitmap0", | |
93 | + "device": "drive0", | |
94 | + "job-id": "j2", | |
95 | + "sync": "incremental", | |
96 | + "target": "img0-incr" | |
97 | + }, | |
98 | + "type": "blockdev-backup" | |
99 | + }, | |
100 | + { | |
101 | + "data": { | |
102 | + "bitmap": "bitmap1", | |
103 | + "device": "drive1", | |
104 | + "job-id": "j3", | |
105 | + "sync": "incremental", | |
106 | + "target": "img1-incr" | |
107 | + }, | |
108 | + "type": "blockdev-backup" | |
109 | + } | |
110 | + ] | |
111 | + } | |
112 | +} | |
113 | +{ | |
114 | + "return": {} | |
115 | +} | |
116 | +{"data": {"device": "j2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} | |
117 | +{"data": {"device": "j3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} | |
118 | + | |
119 | +--- Done --- |
@@ -263,7 +263,9 @@ | ||
263 | 263 | 248 rw quick |
264 | 264 | 249 rw auto quick |
265 | 265 | 250 rw auto quick |
266 | +251 rw auto quick | |
266 | 267 | 252 rw auto backing quick |
267 | 268 | 253 rw auto quick |
268 | 269 | 254 rw auto backing quick |
269 | 270 | 255 rw auto quick |
271 | +256 rw auto quick |
@@ -524,7 +524,7 @@ class VM(qtest.QEMUQtestMachine): | ||
524 | 524 | output_list += [key + '=' + obj[key]] |
525 | 525 | return ','.join(output_list) |
526 | 526 | |
527 | - def get_qmp_events_filtered(self, wait=True): | |
527 | + def get_qmp_events_filtered(self, wait=60.0): | |
528 | 528 | result = [] |
529 | 529 | for ev in self.get_qmp_events(wait=wait): |
530 | 530 | result.append(filter_qmp_event(ev)) |
@@ -542,28 +542,38 @@ class VM(qtest.QEMUQtestMachine): | ||
542 | 542 | |
543 | 543 | # Returns None on success, and an error string on failure |
544 | 544 | def run_job(self, job, auto_finalize=True, auto_dismiss=False, |
545 | - pre_finalize=None): | |
545 | + pre_finalize=None, wait=60.0): | |
546 | + match_device = {'data': {'device': job}} | |
547 | + match_id = {'data': {'id': job}} | |
548 | + events = [ | |
549 | + ('BLOCK_JOB_COMPLETED', match_device), | |
550 | + ('BLOCK_JOB_CANCELLED', match_device), | |
551 | + ('BLOCK_JOB_ERROR', match_device), | |
552 | + ('BLOCK_JOB_READY', match_device), | |
553 | + ('BLOCK_JOB_PENDING', match_id), | |
554 | + ('JOB_STATUS_CHANGE', match_id) | |
555 | + ] | |
546 | 556 | error = None |
547 | 557 | while True: |
548 | - for ev in self.get_qmp_events_filtered(wait=True): | |
549 | - if ev['event'] == 'JOB_STATUS_CHANGE': | |
550 | - status = ev['data']['status'] | |
551 | - if status == 'aborting': | |
552 | - result = self.qmp('query-jobs') | |
553 | - for j in result['return']: | |
554 | - if j['id'] == job: | |
555 | - error = j['error'] | |
556 | - log('Job failed: %s' % (j['error'])) | |
557 | - elif status == 'pending' and not auto_finalize: | |
558 | - if pre_finalize: | |
559 | - pre_finalize() | |
560 | - self.qmp_log('job-finalize', id=job) | |
561 | - elif status == 'concluded' and not auto_dismiss: | |
562 | - self.qmp_log('job-dismiss', id=job) | |
563 | - elif status == 'null': | |
564 | - return error | |
565 | - else: | |
566 | - log(ev) | |
558 | + ev = filter_qmp_event(self.events_wait(events)) | |
559 | + if ev['event'] != 'JOB_STATUS_CHANGE': | |
560 | + log(ev) | |
561 | + continue | |
562 | + status = ev['data']['status'] | |
563 | + if status == 'aborting': | |
564 | + result = self.qmp('query-jobs') | |
565 | + for j in result['return']: | |
566 | + if j['id'] == job: | |
567 | + error = j['error'] | |
568 | + log('Job failed: %s' % (j['error'])) | |
569 | + elif status == 'pending' and not auto_finalize: | |
570 | + if pre_finalize: | |
571 | + pre_finalize() | |
572 | + self.qmp_log('job-finalize', id=job) | |
573 | + elif status == 'concluded' and not auto_dismiss: | |
574 | + self.qmp_log('job-dismiss', id=job) | |
575 | + elif status == 'null': | |
576 | + return error | |
567 | 577 | |
568 | 578 | def node_info(self, node_name): |
569 | 579 | nodes = self.qmp('query-named-block-nodes') |
@@ -650,7 +660,7 @@ class QMPTestCase(unittest.TestCase): | ||
650 | 660 | self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])), |
651 | 661 | self.vm.flatten_qmp_object(reference)) |
652 | 662 | |
653 | - def cancel_and_wait(self, drive='drive0', force=False, resume=False): | |
663 | + def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0): | |
654 | 664 | '''Cancel a block job and wait for it to finish, returning the event''' |
655 | 665 | result = self.vm.qmp('block-job-cancel', device=drive, force=force) |
656 | 666 | self.assert_qmp(result, 'return', {}) |
@@ -661,7 +671,7 @@ class QMPTestCase(unittest.TestCase): | ||
661 | 671 | cancelled = False |
662 | 672 | result = None |
663 | 673 | while not cancelled: |
664 | - for event in self.vm.get_qmp_events(wait=True): | |
674 | + for event in self.vm.get_qmp_events(wait=wait): | |
665 | 675 | if event['event'] == 'BLOCK_JOB_COMPLETED' or \ |
666 | 676 | event['event'] == 'BLOCK_JOB_CANCELLED': |
667 | 677 | self.assert_qmp(event, 'data/device', drive) |
@@ -674,10 +684,10 @@ class QMPTestCase(unittest.TestCase): | ||
674 | 684 | self.assert_no_active_block_jobs() |
675 | 685 | return result |
676 | 686 | |
677 | - def wait_until_completed(self, drive='drive0', check_offset=True): | |
687 | + def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0): | |
678 | 688 | '''Wait for a block job to finish, returning the event''' |
679 | 689 | while True: |
680 | - for event in self.vm.get_qmp_events(wait=True): | |
690 | + for event in self.vm.get_qmp_events(wait=wait): | |
681 | 691 | if event['event'] == 'BLOCK_JOB_COMPLETED': |
682 | 692 | self.assert_qmp(event, 'data/device', drive) |
683 | 693 | self.assert_qmp_absent(event, 'data/error') |