d3c8c67469
Commits0db832f
and6cdbceb
introduced the automatic insertion of filter nodes above the top layer of mirror and commit block jobs. The assumption made there was that since libvirt doesn't do node-level management of the block layer yet, it shouldn't be affected by added nodes. This is true as far as commands issued by libvirt are concerned. It only uses BlockBackend names to address nodes, so any operations it performs still operate on the root of the tree as intended. However, the assumption breaks down when you consider query commands, which return data for the wrong node now. These commands also return information on some child nodes (bs->file and/or bs->backing), which libvirt does make use of, and which refer to the wrong nodes, too. One of the consequences is that oVirt gets wrong information about the image size and stops the VM in response as long as a mirror or commit job is running: https://bugzilla.redhat.com/show_bug.cgi?id=1470634 This patch fixes the problem by hiding the implicit nodes created automatically by the mirror and commit block jobs in the output of query-block and BlockBackend-based query-blockstats as long as the user doesn't indicate that they are aware of those nodes by providing a node name for them in the QMP command to start the block job. The node-based commands query-named-block-nodes and query-blockstats with query-nodes=true still show all nodes, including implicit ones. This ensures that users that are capable of node-level management can still access the full information; users that only know BlockBackends won't use these commands. Cc: qemu-stable@nongnu.org Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com> Reviewed-by: Max Reitz <mreitz@redhat.com> Tested-by: Eric Blake <eblake@redhat.com>
1053 lines
41 KiB
Python
Executable File
1053 lines
41 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Tests for image mirroring.
|
|
#
|
|
# Copyright (C) 2012 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/>.
|
|
#
|
|
|
|
import time
|
|
import os
|
|
import iotests
|
|
from iotests import qemu_img, qemu_io
|
|
|
|
backing_img = os.path.join(iotests.test_dir, 'backing.img')
|
|
target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
|
|
test_img = os.path.join(iotests.test_dir, 'test.img')
|
|
target_img = os.path.join(iotests.test_dir, 'target.img')
|
|
|
|
quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img')
|
|
quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img')
|
|
quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img')
|
|
quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img')
|
|
quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img')
|
|
|
|
class TestSingleDrive(iotests.QMPTestCase):
|
|
image_len = 1 * 1024 * 1024 # MB
|
|
qmp_cmd = 'drive-mirror'
|
|
qmp_target = target_img
|
|
|
|
def setUp(self):
|
|
iotests.create_image(backing_img, self.image_len)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
|
|
self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=base")
|
|
if iotests.qemu_default_machine == 'pc':
|
|
self.vm.add_drive(None, 'media=cdrom', 'ide')
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
try:
|
|
os.remove(target_img)
|
|
except OSError:
|
|
pass
|
|
|
|
def test_complete(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_cancel(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.cancel_and_wait(force=True)
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
|
|
self.vm.shutdown()
|
|
|
|
def test_cancel_after_ready(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.wait_ready_and_cancel()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_pause(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('block-job-pause', device='drive0')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
time.sleep(1)
|
|
result = self.vm.qmp('query-block-jobs')
|
|
offset = self.dictpath(result, 'return[0]/offset')
|
|
|
|
time.sleep(1)
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/offset', offset)
|
|
|
|
result = self.vm.qmp('block-job-resume', device='drive0')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_small_buffer(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
# A small buffer is rounded up automatically
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
buf_size=4096, target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_small_buffer2(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d'
|
|
% (self.image_len, self.image_len), target_img)
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
buf_size=65536, mode='existing', target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_large_cluster(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
|
|
% (self.image_len, backing_img), target_img)
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
mode='existing', target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
# Tests that the insertion of the mirror_top filter node doesn't make a
|
|
# difference to query-block
|
|
def test_implicit_node(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
|
|
self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1)
|
|
self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img)
|
|
|
|
result = self.vm.qmp('query-blockstats')
|
|
self.assert_qmp(result, 'return[0]/node-name', 'top')
|
|
self.assert_qmp(result, 'return[0]/backing/node-name', 'base')
|
|
|
|
self.cancel_and_wait(force=True)
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
|
|
self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1)
|
|
self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
|
|
self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img)
|
|
|
|
result = self.vm.qmp('query-blockstats')
|
|
self.assert_qmp(result, 'return[0]/node-name', 'top')
|
|
self.assert_qmp(result, 'return[0]/backing/node-name', 'base')
|
|
|
|
self.vm.shutdown()
|
|
|
|
def test_medium_not_found(self):
|
|
if iotests.qemu_default_machine != 'pc':
|
|
return
|
|
|
|
result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_image_not_found(self):
|
|
result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
|
|
mode='existing', target=self.qmp_target)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_device_not_found(self):
|
|
result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full',
|
|
target=self.qmp_target)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
class TestSingleBlockdev(TestSingleDrive):
|
|
qmp_cmd = 'blockdev-mirror'
|
|
qmp_target = 'node1'
|
|
|
|
def setUp(self):
|
|
TestSingleDrive.setUp(self)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
|
|
args = {'driver': iotests.imgfmt,
|
|
'node-name': self.qmp_target,
|
|
'file': { 'filename': target_img, 'driver': 'file' } }
|
|
result = self.vm.qmp("blockdev-add", **args)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
test_large_cluster = None
|
|
test_image_not_found = None
|
|
test_small_buffer2 = None
|
|
|
|
class TestSingleDriveZeroLength(TestSingleDrive):
|
|
image_len = 0
|
|
test_small_buffer2 = None
|
|
test_large_cluster = None
|
|
|
|
class TestSingleBlockdevZeroLength(TestSingleBlockdev):
|
|
image_len = 0
|
|
|
|
class TestSingleDriveUnalignedLength(TestSingleDrive):
|
|
image_len = 1025 * 1024
|
|
test_small_buffer2 = None
|
|
test_large_cluster = None
|
|
|
|
class TestSingleBlockdevUnalignedLength(TestSingleBlockdev):
|
|
image_len = 1025 * 1024
|
|
|
|
class TestMirrorNoBacking(iotests.QMPTestCase):
|
|
image_len = 2 * 1024 * 1024 # MB
|
|
|
|
def setUp(self):
|
|
iotests.create_image(backing_img, TestMirrorNoBacking.image_len)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
try:
|
|
os.remove(target_backing_img)
|
|
except:
|
|
pass
|
|
os.remove(target_img)
|
|
|
|
def test_complete(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_cancel(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.wait_ready_and_cancel()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_large_cluster(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
# qemu-img create fails if the image is not there
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d'
|
|
%(TestMirrorNoBacking.image_len), target_backing_img)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
|
|
% (TestMirrorNoBacking.image_len, target_backing_img), target_img)
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
class TestMirrorResized(iotests.QMPTestCase):
|
|
backing_len = 1 * 1024 * 1024 # MB
|
|
image_len = 2 * 1024 * 1024 # MB
|
|
|
|
def setUp(self):
|
|
iotests.create_image(backing_img, TestMirrorResized.backing_len)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
|
|
qemu_img('resize', test_img, '2M')
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
try:
|
|
os.remove(target_img)
|
|
except OSError:
|
|
pass
|
|
|
|
def test_complete_top(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
|
|
target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_complete_full(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait()
|
|
result = self.vm.qmp('query-block')
|
|
self.assert_qmp(result, 'return[0]/inserted/file', target_img)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
class TestReadErrors(iotests.QMPTestCase):
|
|
image_len = 2 * 1024 * 1024 # MB
|
|
|
|
# this should be a multiple of twice the default granularity
|
|
# so that we hit this offset first in state 1
|
|
MIRROR_GRANULARITY = 1024 * 1024
|
|
|
|
def create_blkdebug_file(self, name, event, errno):
|
|
file = open(name, 'w')
|
|
file.write('''
|
|
[inject-error]
|
|
state = "1"
|
|
event = "%s"
|
|
errno = "%d"
|
|
immediately = "off"
|
|
once = "on"
|
|
sector = "%d"
|
|
|
|
[set-state]
|
|
state = "1"
|
|
event = "%s"
|
|
new_state = "2"
|
|
|
|
[set-state]
|
|
state = "2"
|
|
event = "%s"
|
|
new_state = "1"
|
|
''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
|
|
file.close()
|
|
|
|
def setUp(self):
|
|
self.blkdebug_file = backing_img + ".blkdebug"
|
|
iotests.create_image(backing_img, TestReadErrors.image_len)
|
|
self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
|
|
qemu_img('create', '-f', iotests.imgfmt,
|
|
'-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
|
|
% (self.blkdebug_file, backing_img),
|
|
test_img)
|
|
# Write something for tests that use sync='top'
|
|
qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536),
|
|
test_img)
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
os.remove(self.blkdebug_file)
|
|
|
|
def test_report_read(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
completed = False
|
|
error = False
|
|
while not completed:
|
|
for event in self.vm.get_qmp_events(wait=True):
|
|
if event['event'] == 'BLOCK_JOB_ERROR':
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'read')
|
|
error = True
|
|
elif event['event'] == 'BLOCK_JOB_READY':
|
|
self.assertTrue(False, 'job completed unexpectedly')
|
|
elif event['event'] == 'BLOCK_JOB_COMPLETED':
|
|
self.assertTrue(error, 'job completed unexpectedly')
|
|
self.assert_qmp(event, 'data/type', 'mirror')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/error', 'Input/output error')
|
|
completed = True
|
|
|
|
self.assert_no_active_block_jobs()
|
|
self.vm.shutdown()
|
|
|
|
def test_ignore_read(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img, on_source_error='ignore')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
event = self.vm.get_qmp_event(wait=True)
|
|
self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'read')
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', False)
|
|
self.complete_and_wait()
|
|
self.vm.shutdown()
|
|
|
|
def test_large_cluster(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
# Test COW into the target image. The first half of the
|
|
# cluster at MIRROR_GRANULARITY has to be copied from
|
|
# backing_img, even though sync='top'.
|
|
qemu_img('create', '-f', iotests.imgfmt, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img)
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
|
|
on_source_error='ignore',
|
|
mode='existing', target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
event = self.vm.get_qmp_event(wait=True)
|
|
self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'read')
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', False)
|
|
self.complete_and_wait()
|
|
self.vm.shutdown()
|
|
|
|
# Detach blkdebug to compare images successfully
|
|
qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, test_img)
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_stop_read(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img, on_source_error='stop')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
error = False
|
|
ready = False
|
|
while not ready:
|
|
for event in self.vm.get_qmp_events(wait=True):
|
|
if event['event'] == 'BLOCK_JOB_ERROR':
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'read')
|
|
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', True)
|
|
self.assert_qmp(result, 'return[0]/io-status', 'failed')
|
|
|
|
result = self.vm.qmp('block-job-resume', device='drive0')
|
|
self.assert_qmp(result, 'return', {})
|
|
error = True
|
|
elif event['event'] == 'BLOCK_JOB_READY':
|
|
self.assertTrue(error, 'job completed unexpectedly')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
ready = True
|
|
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', False)
|
|
self.assert_qmp(result, 'return[0]/io-status', 'ok')
|
|
|
|
self.complete_and_wait(wait_ready=False)
|
|
self.assert_no_active_block_jobs()
|
|
self.vm.shutdown()
|
|
|
|
class TestWriteErrors(iotests.QMPTestCase):
|
|
image_len = 2 * 1024 * 1024 # MB
|
|
|
|
# this should be a multiple of twice the default granularity
|
|
# so that we hit this offset first in state 1
|
|
MIRROR_GRANULARITY = 1024 * 1024
|
|
|
|
def create_blkdebug_file(self, name, event, errno):
|
|
file = open(name, 'w')
|
|
file.write('''
|
|
[inject-error]
|
|
state = "1"
|
|
event = "%s"
|
|
errno = "%d"
|
|
immediately = "off"
|
|
once = "on"
|
|
sector = "%d"
|
|
|
|
[set-state]
|
|
state = "1"
|
|
event = "%s"
|
|
new_state = "2"
|
|
|
|
[set-state]
|
|
state = "2"
|
|
event = "%s"
|
|
new_state = "1"
|
|
''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
|
|
file.close()
|
|
|
|
def setUp(self):
|
|
self.blkdebug_file = target_img + ".blkdebug"
|
|
iotests.create_image(backing_img, TestWriteErrors.image_len)
|
|
self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
|
|
qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
os.remove(self.blkdebug_file)
|
|
|
|
def test_report_write(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=self.target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
completed = False
|
|
error = False
|
|
while not completed:
|
|
for event in self.vm.get_qmp_events(wait=True):
|
|
if event['event'] == 'BLOCK_JOB_ERROR':
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'write')
|
|
error = True
|
|
elif event['event'] == 'BLOCK_JOB_READY':
|
|
self.assertTrue(False, 'job completed unexpectedly')
|
|
elif event['event'] == 'BLOCK_JOB_COMPLETED':
|
|
self.assertTrue(error, 'job completed unexpectedly')
|
|
self.assert_qmp(event, 'data/type', 'mirror')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/error', 'Input/output error')
|
|
completed = True
|
|
|
|
self.assert_no_active_block_jobs()
|
|
self.vm.shutdown()
|
|
|
|
def test_ignore_write(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=self.target_img,
|
|
on_target_error='ignore')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
event = self.vm.get_qmp_event(wait=True)
|
|
self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'write')
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', False)
|
|
self.complete_and_wait()
|
|
self.vm.shutdown()
|
|
|
|
def test_stop_write(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
mode='existing', target=self.target_img,
|
|
on_target_error='stop')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
error = False
|
|
ready = False
|
|
while not ready:
|
|
for event in self.vm.get_qmp_events(wait=True):
|
|
if event['event'] == 'BLOCK_JOB_ERROR':
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
self.assert_qmp(event, 'data/operation', 'write')
|
|
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', True)
|
|
self.assert_qmp(result, 'return[0]/io-status', 'failed')
|
|
|
|
result = self.vm.qmp('block-job-resume', device='drive0')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/paused', False)
|
|
self.assert_qmp(result, 'return[0]/io-status', 'ok')
|
|
error = True
|
|
elif event['event'] == 'BLOCK_JOB_READY':
|
|
self.assertTrue(error, 'job completed unexpectedly')
|
|
self.assert_qmp(event, 'data/device', 'drive0')
|
|
ready = True
|
|
|
|
self.complete_and_wait(wait_ready=False)
|
|
self.assert_no_active_block_jobs()
|
|
self.vm.shutdown()
|
|
|
|
class TestSetSpeed(iotests.QMPTestCase):
|
|
image_len = 80 * 1024 * 1024 # MB
|
|
|
|
def setUp(self):
|
|
qemu_img('create', backing_img, str(TestSetSpeed.image_len))
|
|
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(backing_img)
|
|
os.remove(target_img)
|
|
|
|
def test_set_speed(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
# Default speed is 0
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/device', 'drive0')
|
|
self.assert_qmp(result, 'return[0]/speed', 0)
|
|
|
|
result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
# Ensure the speed we set was accepted
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/device', 'drive0')
|
|
self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
|
|
|
|
self.wait_ready_and_cancel()
|
|
|
|
# Check setting speed in drive-mirror works
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img, speed=4*1024*1024)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/device', 'drive0')
|
|
self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
|
|
|
|
self.wait_ready_and_cancel()
|
|
|
|
def test_set_speed_invalid(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img, speed=-1)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
|
|
target=target_img)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
self.wait_ready_and_cancel()
|
|
|
|
class TestUnbackedSource(iotests.QMPTestCase):
|
|
image_len = 2 * 1024 * 1024 # MB
|
|
|
|
def setUp(self):
|
|
qemu_img('create', '-f', iotests.imgfmt, test_img,
|
|
str(TestUnbackedSource.image_len))
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
os.remove(test_img)
|
|
os.remove(target_img)
|
|
|
|
def test_absolute_paths_full(self):
|
|
self.assert_no_active_block_jobs()
|
|
result = self.vm.qmp('drive-mirror', device='drive0',
|
|
sync='full', target=target_img,
|
|
mode='absolute-paths')
|
|
self.assert_qmp(result, 'return', {})
|
|
self.complete_and_wait()
|
|
self.assert_no_active_block_jobs()
|
|
|
|
def test_absolute_paths_top(self):
|
|
self.assert_no_active_block_jobs()
|
|
result = self.vm.qmp('drive-mirror', device='drive0',
|
|
sync='top', target=target_img,
|
|
mode='absolute-paths')
|
|
self.assert_qmp(result, 'return', {})
|
|
self.complete_and_wait()
|
|
self.assert_no_active_block_jobs()
|
|
|
|
def test_absolute_paths_none(self):
|
|
self.assert_no_active_block_jobs()
|
|
result = self.vm.qmp('drive-mirror', device='drive0',
|
|
sync='none', target=target_img,
|
|
mode='absolute-paths')
|
|
self.assert_qmp(result, 'return', {})
|
|
self.complete_and_wait()
|
|
self.assert_no_active_block_jobs()
|
|
|
|
class TestGranularity(iotests.QMPTestCase):
|
|
image_len = 10 * 1024 * 1024 # MB
|
|
|
|
def setUp(self):
|
|
qemu_img('create', '-f', iotests.imgfmt, test_img,
|
|
str(TestGranularity.image_len))
|
|
qemu_io('-c', 'write 0 %d' % (self.image_len),
|
|
test_img)
|
|
self.vm = iotests.VM().add_drive(test_img)
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(test_img, target_img),
|
|
'target image does not match source after mirroring')
|
|
os.remove(test_img)
|
|
os.remove(target_img)
|
|
|
|
def test_granularity(self):
|
|
self.assert_no_active_block_jobs()
|
|
result = self.vm.qmp('drive-mirror', device='drive0',
|
|
sync='full', target=target_img,
|
|
mode='absolute-paths', granularity=8192)
|
|
self.assert_qmp(result, 'return', {})
|
|
event = self.vm.get_qmp_event(wait=60.0)
|
|
# Failures will manifest as COMPLETED/ERROR.
|
|
self.assert_qmp(event, 'event', 'BLOCK_JOB_READY')
|
|
self.complete_and_wait(drive='drive0', wait_ready=False)
|
|
self.assert_no_active_block_jobs()
|
|
|
|
class TestRepairQuorum(iotests.QMPTestCase):
|
|
""" This class test quorum file repair using drive-mirror.
|
|
It's mostly a fork of TestSingleDrive """
|
|
image_len = 1 * 1024 * 1024 # MB
|
|
IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ]
|
|
|
|
def setUp(self):
|
|
self.vm = iotests.VM()
|
|
|
|
if iotests.qemu_default_machine == 'pc':
|
|
self.vm.add_drive(None, 'media=cdrom', 'ide')
|
|
|
|
# Add each individual quorum images
|
|
for i in self.IMAGES:
|
|
qemu_img('create', '-f', iotests.imgfmt, i,
|
|
str(TestSingleDrive.image_len))
|
|
# Assign a node name to each quorum image in order to manipulate
|
|
# them
|
|
opts = "node-name=img%i" % self.IMAGES.index(i)
|
|
self.vm = self.vm.add_drive(i, opts)
|
|
|
|
self.vm.launch()
|
|
|
|
#assemble the quorum block device from the individual files
|
|
args = { "driver": "quorum", "node-name": "quorum0",
|
|
"vote-threshold": 2, "children": [ "img0", "img1", "img2" ] }
|
|
if iotests.supports_quorum():
|
|
result = self.vm.qmp("blockdev-add", **args)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
for i in self.IMAGES + [ quorum_repair_img ]:
|
|
# Do a try/except because the test may have deleted some images
|
|
try:
|
|
os.remove(i)
|
|
except OSError:
|
|
pass
|
|
|
|
def test_complete(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name="repair0", replaces="img1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait(drive="job0")
|
|
self.assert_has_block_node("repair0", quorum_repair_img)
|
|
# TODO: a better test requiring some QEMU infrastructure will be added
|
|
# to check that this file is really driven by quorum
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_cancel(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name="repair0", replaces="img1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.cancel_and_wait(drive="job0", force=True)
|
|
# here we check that the last registered quorum file has not been
|
|
# swapped out and unref
|
|
self.assert_has_block_node(None, quorum_img3)
|
|
self.vm.shutdown()
|
|
|
|
def test_cancel_after_ready(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name="repair0", replaces="img1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.wait_ready_and_cancel(drive="job0")
|
|
# here we check that the last registered quorum file has not been
|
|
# swapped out and unref
|
|
self.assert_has_block_node(None, quorum_img3)
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_pause(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name="repair0", replaces="img1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
result = self.vm.qmp('block-job-pause', device='job0')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
time.sleep(1)
|
|
result = self.vm.qmp('query-block-jobs')
|
|
offset = self.dictpath(result, 'return[0]/offset')
|
|
|
|
time.sleep(1)
|
|
result = self.vm.qmp('query-block-jobs')
|
|
self.assert_qmp(result, 'return[0]/offset', offset)
|
|
|
|
result = self.vm.qmp('block-job-resume', device='job0')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait(drive="job0")
|
|
self.vm.shutdown()
|
|
self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
|
|
'target image does not match source after mirroring')
|
|
|
|
def test_medium_not_found(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
if iotests.qemu_default_machine != 'pc':
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM
|
|
sync='full',
|
|
node_name='repair0',
|
|
replaces='img1',
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_image_not_found(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name='repair0', replaces='img1',
|
|
mode='existing', target=quorum_repair_img,
|
|
format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_device_not_found(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0',
|
|
device='nonexistent', sync='full',
|
|
node_name='repair0',
|
|
replaces='img1',
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_wrong_sync_mode(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0',
|
|
node_name='repair0',
|
|
replaces='img1',
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_no_node_name(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', replaces='img1',
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_nonexistent_replaces(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name='repair0', replaces='img77',
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_after_a_quorum_snapshot(self):
|
|
if not iotests.supports_quorum():
|
|
return
|
|
|
|
result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1',
|
|
snapshot_file=quorum_snapshot_file,
|
|
snapshot_node_name="snap1");
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name='repair0', replaces="img1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
|
|
sync='full', node_name='repair0', replaces="snap1",
|
|
target=quorum_repair_img, format=iotests.imgfmt)
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait('job0')
|
|
self.assert_has_block_node("repair0", quorum_repair_img)
|
|
# TODO: a better test requiring some QEMU infrastructure will be added
|
|
# to check that this file is really driven by quorum
|
|
self.vm.shutdown()
|
|
|
|
# Test mirroring with a source that does not have any parents (not even a
|
|
# BlockBackend)
|
|
class TestOrphanedSource(iotests.QMPTestCase):
|
|
def setUp(self):
|
|
blk0 = { 'node-name': 'src',
|
|
'driver': 'null-co' }
|
|
|
|
blk1 = { 'node-name': 'dest',
|
|
'driver': 'null-co' }
|
|
|
|
blk2 = { 'node-name': 'dest-ro',
|
|
'driver': 'null-co',
|
|
'read-only': 'on' }
|
|
|
|
self.vm = iotests.VM()
|
|
self.vm.add_blockdev(self.qmp_to_opts(blk0))
|
|
self.vm.add_blockdev(self.qmp_to_opts(blk1))
|
|
self.vm.add_blockdev(self.qmp_to_opts(blk2))
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
|
|
def test_no_job_id(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('blockdev-mirror', device='src', sync='full',
|
|
target='dest')
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
def test_success(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('blockdev-mirror', job_id='job', device='src',
|
|
sync='full', target='dest')
|
|
self.assert_qmp(result, 'return', {})
|
|
|
|
self.complete_and_wait('job')
|
|
|
|
def test_failing_permissions(self):
|
|
self.assert_no_active_block_jobs()
|
|
|
|
result = self.vm.qmp('blockdev-mirror', device='src', sync='full',
|
|
target='dest-ro')
|
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
|
|
if __name__ == '__main__':
|
|
iotests.main(supported_fmts=['qcow2', 'qed'])
|