Block patches for 4.1.0-rc4:

- Fix the backup block job when using copy offloading
 - Fix the mirror block job when using the write-blocking copy mode
 - Fix incremental backups after the image has been grown with the
   respective bitmap attached to it
 -----BEGIN PGP SIGNATURE-----
 
 iQFGBAABCAAwFiEEkb62CjDbPohX0Rgp9AfbAGHVz0AFAl1JaxMSHG1yZWl0ekBy
 ZWRoYXQuY29tAAoJEPQH2wBh1c9AdCUH/R/pYY0OC2fHYYJnp/UxLK2aKvNBVcab
 u/8mXaDE5D9lNXbvXQx4bb+8TTcoKlH30qYcj5eDQ6BuRwPXGrNzBlHIXINTXKnF
 HRSLRE5LDRhj/K3GTyFqxKNShR6sC1jpTLEYlcV2xDhTPJQg5Nk1CgsFB3bzHINc
 6HUr0wMZAQwt/j+UmiFDc2nQyUGy+uJDiZ5cmRHNPf+ptZaAExVoxrDYHmogpa3z
 /oZrdpxEfN1sEjuiwWyxw75LBwKKj9yjIqFiIV0Yqo/aLYDH7g9eOr8b914Qbaht
 yFFcUh4SEALxejXd4MllnFyCPGuygZn8amON9xYa2jStXmL4CziDax0=
 =oGaJ
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/maxreitz/tags/pull-block-2019-08-06' into staging

Block patches for 4.1.0-rc4:
- Fix the backup block job when using copy offloading
- Fix the mirror block job when using the write-blocking copy mode
- Fix incremental backups after the image has been grown with the
  respective bitmap attached to it

# gpg: Signature made Tue 06 Aug 2019 12:57:07 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-08-06:
  block/backup: disable copy_range for compressed backup
  iotests: Test unaligned blocking mirror write
  mirror: Only mirror granularity-aligned chunks
  iotests: Test incremental backup after truncation
  util/hbitmap: update orig_size on truncate
  iotests: Test backup job with two guest writes
  backup: Copy only dirty areas

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-08-06 13:40:31 +01:00
commit df1a7c99dd
9 changed files with 150 additions and 14 deletions

View File

@ -202,22 +202,31 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
cow_request_begin(&cow_request, job, start, end); cow_request_begin(&cow_request, job, start, end);
while (start < end) { while (start < end) {
int64_t dirty_end;
if (!hbitmap_get(job->copy_bitmap, start)) { if (!hbitmap_get(job->copy_bitmap, start)) {
trace_backup_do_cow_skip(job, start); trace_backup_do_cow_skip(job, start);
start += job->cluster_size; start += job->cluster_size;
continue; /* already copied */ continue; /* already copied */
} }
dirty_end = hbitmap_next_zero(job->copy_bitmap, start, (end - start));
if (dirty_end < 0) {
dirty_end = end;
}
trace_backup_do_cow_process(job, start); trace_backup_do_cow_process(job, start);
if (job->use_copy_range) { if (job->use_copy_range) {
ret = backup_cow_with_offload(job, start, end, is_write_notifier); ret = backup_cow_with_offload(job, start, dirty_end,
is_write_notifier);
if (ret < 0) { if (ret < 0) {
job->use_copy_range = false; job->use_copy_range = false;
} }
} }
if (!job->use_copy_range) { if (!job->use_copy_range) {
ret = backup_cow_with_bounce_buffer(job, start, end, is_write_notifier, ret = backup_cow_with_bounce_buffer(job, start, dirty_end,
is_write_notifier,
error_is_read, &bounce_buffer); error_is_read, &bounce_buffer);
} }
if (ret < 0) { if (ret < 0) {
@ -648,7 +657,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
job->cluster_size = cluster_size; job->cluster_size = cluster_size;
job->copy_bitmap = copy_bitmap; job->copy_bitmap = copy_bitmap;
copy_bitmap = NULL; copy_bitmap = NULL;
job->use_copy_range = true; job->use_copy_range = !compress; /* compression isn't supported for it */
job->copy_range_size = MIN_NON_ZERO(blk_get_max_transfer(job->common.blk), job->copy_range_size = MIN_NON_ZERO(blk_get_max_transfer(job->common.blk),
blk_get_max_transfer(job->target)); blk_get_max_transfer(job->target));
job->copy_range_size = MAX(job->cluster_size, job->copy_range_size = MAX(job->cluster_size,

View File

@ -1481,6 +1481,15 @@ static void bdrv_mirror_top_child_perm(BlockDriverState *bs, BdrvChild *c,
*nshared = BLK_PERM_ALL; *nshared = BLK_PERM_ALL;
} }
static void bdrv_mirror_top_refresh_limits(BlockDriverState *bs, Error **errp)
{
MirrorBDSOpaque *s = bs->opaque;
if (s && s->job && s->job->copy_mode == MIRROR_COPY_MODE_WRITE_BLOCKING) {
bs->bl.request_alignment = s->job->granularity;
}
}
/* Dummy node that provides consistent read to its users without requiring it /* Dummy node that provides consistent read to its users without requiring it
* from its backing file and that allows writes on the backing file chain. */ * from its backing file and that allows writes on the backing file chain. */
static BlockDriver bdrv_mirror_top = { static BlockDriver bdrv_mirror_top = {
@ -1493,6 +1502,7 @@ static BlockDriver bdrv_mirror_top = {
.bdrv_co_block_status = bdrv_co_block_status_from_backing, .bdrv_co_block_status = bdrv_co_block_status_from_backing,
.bdrv_refresh_filename = bdrv_mirror_top_refresh_filename, .bdrv_refresh_filename = bdrv_mirror_top_refresh_filename,
.bdrv_child_perm = bdrv_mirror_top_child_perm, .bdrv_child_perm = bdrv_mirror_top_child_perm,
.bdrv_refresh_limits = bdrv_mirror_top_refresh_limits,
}; };
static BlockJob *mirror_start_job( static BlockJob *mirror_start_job(
@ -1637,6 +1647,25 @@ static BlockJob *mirror_start_job(
s->should_complete = true; s->should_complete = true;
} }
/*
* Must be called before we start tracking writes, but after
*
* ((MirrorBlockJob *)
* ((MirrorBDSOpaque *)
* mirror_top_bs->opaque
* )->job
* )->copy_mode
*
* has the correct value.
* (We start tracking writes as of the following
* bdrv_create_dirty_bitmap() call.)
*/
bdrv_refresh_limits(mirror_top_bs, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto fail;
}
s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp); s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
if (!s->dirty_bitmap) { if (!s->dirty_bitmap) {
goto fail; goto fail;

View File

@ -133,6 +133,7 @@ class BackupTest(iotests.QMPTestCase):
self.vm = iotests.VM() self.vm = iotests.VM()
self.test_img = img_create('test') self.test_img = img_create('test')
self.dest_img = img_create('dest') self.dest_img = img_create('dest')
self.ref_img = img_create('ref')
self.vm.add_drive(self.test_img) self.vm.add_drive(self.test_img)
self.vm.launch() self.vm.launch()
@ -140,6 +141,7 @@ class BackupTest(iotests.QMPTestCase):
self.vm.shutdown() self.vm.shutdown()
try_remove(self.test_img) try_remove(self.test_img)
try_remove(self.dest_img) try_remove(self.dest_img)
try_remove(self.ref_img)
def hmp_io_writes(self, drive, patterns): def hmp_io_writes(self, drive, patterns):
for pattern in patterns: for pattern in patterns:
@ -177,6 +179,43 @@ class BackupTest(iotests.QMPTestCase):
self.assert_qmp(event, 'data/error', qerror) self.assert_qmp(event, 'data/error', qerror)
return False return False
def test_overlapping_writes(self):
# Write something to back up
self.hmp_io_writes('drive0', [('42', '0M', '2M')])
# Create a reference backup
self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
sync='full', target=self.ref_img,
auto_dismiss=False)
res = self.vm.qmp('block-job-dismiss', id='drive0')
self.assert_qmp(res, 'return', {})
# Now to the test backup: We simulate the following guest
# writes:
# (1) [1M + 64k, 1M + 128k): Afterwards, everything in that
# area should be in the target image, and we must not copy
# it again (because the source image has changed now)
# (64k is the job's cluster size)
# (2) [1M, 2M): The backup job must not get overeager. It
# must copy [1M, 1M + 64k) and [1M + 128k, 2M) separately,
# but not the area in between.
self.qmp_backup(device='drive0', format=iotests.imgfmt, sync='full',
target=self.dest_img, speed=1, auto_dismiss=False)
self.hmp_io_writes('drive0', [('23', '%ik' % (1024 + 64), '64k'),
('66', '1M', '1M')])
# Let the job complete
res = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
self.assert_qmp(res, 'return', {})
self.qmp_backup_wait('drive0')
res = self.vm.qmp('block-job-dismiss', id='drive0')
self.assert_qmp(res, 'return', {})
self.assertTrue(iotests.compare_images(self.ref_img, self.dest_img),
'target image does not match reference image')
def test_dismiss_false(self): def test_dismiss_false(self):
res = self.vm.qmp('query-block-jobs') res = self.vm.qmp('query-block-jobs')
self.assert_qmp(res, 'return', []) self.assert_qmp(res, 'return', [])

View File

@ -1,5 +1,5 @@
......... ..........
---------------------------------------------------------------------- ----------------------------------------------------------------------
Ran 9 tests Ran 10 tests
OK OK

View File

@ -212,24 +212,27 @@ class TestIncrementalBackupBase(iotests.QMPTestCase):
return bitmap return bitmap
def prepare_backup(self, bitmap=None, parent=None): def prepare_backup(self, bitmap=None, parent=None, **kwargs):
if bitmap is None: if bitmap is None:
bitmap = self.bitmaps[-1] bitmap = self.bitmaps[-1]
if parent is None: if parent is None:
parent, _ = bitmap.last_target() parent, _ = bitmap.last_target()
target, _ = bitmap.new_target() target, _ = bitmap.new_target()
self.img_create(target, bitmap.drive['fmt'], parent=parent) self.img_create(target, bitmap.drive['fmt'], parent=parent,
**kwargs)
return target return target
def create_incremental(self, bitmap=None, parent=None, def create_incremental(self, bitmap=None, parent=None,
parentFormat=None, validate=True): parentFormat=None, validate=True,
target=None):
if bitmap is None: if bitmap is None:
bitmap = self.bitmaps[-1] bitmap = self.bitmaps[-1]
if parent is None: if parent is None:
parent, _ = bitmap.last_target() parent, _ = bitmap.last_target()
if target is None:
target = self.prepare_backup(bitmap, parent) target = self.prepare_backup(bitmap, parent)
res = self.do_qmp_backup(job_id=bitmap.drive['id'], res = self.do_qmp_backup(job_id=bitmap.drive['id'],
device=bitmap.drive['id'], device=bitmap.drive['id'],
@ -572,6 +575,33 @@ class TestIncrementalBackup(TestIncrementalBackupBase):
'bitmap0', self.drives[0], 'bitmap0', self.drives[0],
granularity=64000) granularity=64000)
def test_growing_before_backup(self):
'''
Test: Add a bitmap, truncate the image, write past the old
end, do a backup.
Incremental backup should not ignore dirty bits past the old
image end.
'''
self.assert_no_active_block_jobs()
self.create_anchor_backup()
self.add_bitmap('bitmap0', self.drives[0])
res = self.vm.qmp('block_resize', device=self.drives[0]['id'],
size=(65 * 1048576))
self.assert_qmp(res, 'return', {})
# Dirty the image past the old end
self.vm.hmp_qemu_io(self.drives[0]['id'], 'write 64M 64k')
target = self.prepare_backup(size='65M')
self.create_incremental(target=target)
self.vm.shutdown()
self.check_backups()
class TestIncrementalBackupBlkdebug(TestIncrementalBackupBase): class TestIncrementalBackupBlkdebug(TestIncrementalBackupBase):
'''Incremental backup tests that utilize a BlkDebug filter on drive0.''' '''Incremental backup tests that utilize a BlkDebug filter on drive0.'''

View File

@ -1,5 +1,5 @@
............ .............
---------------------------------------------------------------------- ----------------------------------------------------------------------
Ran 12 tests Ran 13 tests
OK OK

View File

@ -114,6 +114,31 @@ class TestActiveMirror(iotests.QMPTestCase):
def testActiveIOFlushed(self): def testActiveIOFlushed(self):
self.doActiveIO(True) self.doActiveIO(True)
def testUnalignedActiveIO(self):
# Fill the source image
result = self.vm.hmp_qemu_io('source', 'write -P 1 0 2M')
# Start the block job (very slowly)
result = self.vm.qmp('blockdev-mirror',
job_id='mirror',
filter_node_name='mirror-node',
device='source-node',
target='target-node',
sync='full',
copy_mode='write-blocking',
buf_size=(1048576 // 4),
speed=1)
self.assert_qmp(result, 'return', {})
# Start an unaligned request to a dirty area
result = self.vm.hmp_qemu_io('source', 'write -P 2 %i 1' % (1048576 + 42))
# Let the job finish
result = self.vm.qmp('block-job-set-speed', device='mirror', speed=0)
self.assert_qmp(result, 'return', {})
self.complete_and_wait(drive='mirror')
self.potential_writes_in_flight = False
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,5 +1,5 @@
.. ...
---------------------------------------------------------------------- ----------------------------------------------------------------------
Ran 2 tests Ran 3 tests
OK OK

View File

@ -53,7 +53,9 @@
*/ */
struct HBitmap { struct HBitmap {
/* Size of the bitmap, as requested in hbitmap_alloc. */ /*
* Size of the bitmap, as requested in hbitmap_alloc or in hbitmap_truncate.
*/
uint64_t orig_size; uint64_t orig_size;
/* Number of total bits in the bottom level. */ /* Number of total bits in the bottom level. */
@ -732,6 +734,8 @@ void hbitmap_truncate(HBitmap *hb, uint64_t size)
uint64_t num_elements = size; uint64_t num_elements = size;
uint64_t old; uint64_t old;
hb->orig_size = size;
/* Size comes in as logical elements, adjust for granularity. */ /* Size comes in as logical elements, adjust for granularity. */
size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity; size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity;
assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE)); assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));