xemu/tests/qemu-iotests/040
Thomas Huth 9442bebe6e iotests: Add more "skip_if_unsupported" statements to the python tests
The python code already contains a possibility to skip tests if the
corresponding driver is not available in the qemu binary - use it
in more spots to avoid that the tests are failing if the driver has
been disabled.

While we're at it, we can now also remove some of the old checks that
were using iotests.supports_quorum() - and which were apparently not
working as expected since the tests aborted instead of being skipped
when "quorum" was missing in the QEMU binary.

Signed-off-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
2020-01-27 17:19:53 +01:00

436 lines
20 KiB
Python
Executable File

#!/usr/bin/env python
#
# Tests for image block commit.
#
# Copyright (C) 2012 IBM, Corp.
# 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/>.
#
# Test for live block commit
# Derived from Image Streaming Test 030
import time
import os
import iotests
from iotests import qemu_img, qemu_io
import struct
import errno
backing_img = os.path.join(iotests.test_dir, 'backing.img')
mid_img = os.path.join(iotests.test_dir, 'mid.img')
test_img = os.path.join(iotests.test_dir, 'test.img')
class ImageCommitTestCase(iotests.QMPTestCase):
'''Abstract base class for image commit test cases'''
def wait_for_complete(self, need_ready=False):
completed = False
ready = False
while not completed:
for event in self.vm.get_qmp_events(wait=True):
if event['event'] == 'BLOCK_JOB_COMPLETED':
self.assert_qmp_absent(event, 'data/error')
self.assert_qmp(event, 'data/type', 'commit')
self.assert_qmp(event, 'data/device', 'drive0')
self.assert_qmp(event, 'data/offset', event['data']['len'])
if need_ready:
self.assertTrue(ready, "Expecting BLOCK_JOB_COMPLETED event")
completed = True
elif event['event'] == 'BLOCK_JOB_READY':
ready = True
self.assert_qmp(event, 'data/type', 'commit')
self.assert_qmp(event, 'data/device', 'drive0')
self.vm.qmp('block-job-complete', device='drive0')
self.assert_no_active_block_jobs()
self.vm.shutdown()
def run_commit_test(self, top, base, need_ready=False, node_names=False):
self.assert_no_active_block_jobs()
if node_names:
result = self.vm.qmp('block-commit', device='drive0', top_node=top, base_node=base)
else:
result = self.vm.qmp('block-commit', device='drive0', top=top, base=base)
self.assert_qmp(result, 'return', {})
self.wait_for_complete(need_ready)
def run_default_commit_test(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0')
self.assert_qmp(result, 'return', {})
self.wait_for_complete()
class TestSingleDrive(ImageCommitTestCase):
# Need some space after the copied data so that throttling is effective in
# tests that use it rather than just completing the job immediately
image_len = 2 * 1024 * 1024
test_len = 1 * 1024 * 256
def setUp(self):
iotests.create_image(backing_img, self.image_len)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img)
qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img)
self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=mid,backing.backing.node-name=base", interface="none")
self.vm.add_device(iotests.get_virtio_scsi_device())
self.vm.add_device("scsi-hd,id=scsi0,drive=drive0")
self.vm.launch()
self.has_quit = False
def tearDown(self):
self.vm.shutdown(has_quit=self.has_quit)
os.remove(test_img)
os.remove(mid_img)
os.remove(backing_img)
def test_commit(self):
self.run_commit_test(mid_img, backing_img)
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
def test_commit_node(self):
self.run_commit_test("mid", "base", node_names=True)
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
@iotests.skip_if_unsupported(['throttle'])
def test_commit_with_filter_and_quit(self):
result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg')
self.assert_qmp(result, 'return', {})
# Add a filter outside of the backing chain
result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
self.assert_qmp(result, 'return', {})
result = self.vm.qmp('block-commit', device='drive0')
self.assert_qmp(result, 'return', {})
# Quit immediately, thus forcing a simultaneous cancel of the
# block job and a bdrv_drain_all()
result = self.vm.qmp('quit')
self.assert_qmp(result, 'return', {})
self.has_quit = True
# Same as above, but this time we add the filter after starting the job
@iotests.skip_if_unsupported(['throttle'])
def test_commit_plus_filter_and_quit(self):
result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg')
self.assert_qmp(result, 'return', {})
result = self.vm.qmp('block-commit', device='drive0')
self.assert_qmp(result, 'return', {})
# Add a filter outside of the backing chain
result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
self.assert_qmp(result, 'return', {})
# Quit immediately, thus forcing a simultaneous cancel of the
# block job and a bdrv_drain_all()
result = self.vm.qmp('quit')
self.assert_qmp(result, 'return', {})
self.has_quit = True
def test_device_not_found(self):
result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % mid_img)
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
def test_top_same_base(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % backing_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % backing_img)
def test_top_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % backing_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
def test_base_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % mid_img, base='badfile')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found')
def test_top_node_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile")
def test_base_node_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile")
def test_top_path_and_node(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', top='%s' % mid_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "'top-node' and 'top' are mutually exclusive")
def test_base_path_and_node(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', base='%s' % backing_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "'base-node' and 'base' are mutually exclusive")
def test_top_is_active(self):
self.run_commit_test(test_img, backing_img, need_ready=True)
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
def test_top_is_default_active(self):
self.run_default_commit_test()
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
def test_top_and_base_reversed(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % mid_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % mid_img)
def test_top_and_base_node_reversed(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top_node='base', base_node='top')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "'top' is not in this backing file chain")
def test_top_node_in_wrong_chain(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('blockdev-add', driver='null-co', node_name='null')
self.assert_qmp(result, 'return', {})
result = self.vm.qmp('block-commit', device='drive0', top_node='null', base_node='base')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', "'null' is not in this backing file chain")
# When the job is running on a BB that is automatically deleted on hot
# unplug, the job is cancelled when the device disappears
def test_hot_unplug(self):
if self.image_len == 0:
return
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top=mid_img,
base=backing_img, speed=(self.image_len // 4))
self.assert_qmp(result, 'return', {})
result = self.vm.qmp('device_del', id='scsi0')
self.assert_qmp(result, 'return', {})
cancelled = False
deleted = False
while not cancelled or not deleted:
for event in self.vm.get_qmp_events(wait=True):
if event['event'] == 'DEVICE_DELETED':
self.assert_qmp(event, 'data/device', 'scsi0')
deleted = True
elif event['event'] == 'BLOCK_JOB_CANCELLED':
self.assert_qmp(event, 'data/device', 'drive0')
cancelled = True
elif event['event'] == 'JOB_STATUS_CHANGE':
self.assert_qmp(event, 'data/id', 'drive0')
else:
self.fail("Unexpected event %s" % (event['event']))
self.assert_no_active_block_jobs()
# Tests that the insertion of the commit_top filter node doesn't make a
# difference to query-blockstat
def test_implicit_node(self):
if self.image_len == 0:
return
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top=mid_img,
base=backing_img, speed=(self.image_len // 4))
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', mid_img)
self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 2)
self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', mid_img)
self.assert_qmp(result, 'return[0]/inserted/image/backing-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', 'mid')
self.assert_qmp(result, 'return[0]/backing/backing/node-name', 'base')
self.cancel_and_wait()
self.assert_no_active_block_jobs()
class TestRelativePaths(ImageCommitTestCase):
image_len = 1 * 1024 * 1024
test_len = 1 * 1024 * 256
dir1 = "dir1"
dir2 = "dir2/"
dir3 = "dir2/dir3/"
test_img = os.path.join(iotests.test_dir, dir3, 'test.img')
mid_img = "../mid.img"
backing_img = "../dir1/backing.img"
backing_img_abs = os.path.join(iotests.test_dir, dir1, 'backing.img')
mid_img_abs = os.path.join(iotests.test_dir, dir2, 'mid.img')
def setUp(self):
try:
os.mkdir(os.path.join(iotests.test_dir, self.dir1))
os.mkdir(os.path.join(iotests.test_dir, self.dir2))
os.mkdir(os.path.join(iotests.test_dir, self.dir3))
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
iotests.create_image(self.backing_img_abs, TestRelativePaths.image_len)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.backing_img_abs, self.mid_img_abs)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.mid_img_abs, self.test_img)
qemu_img('rebase', '-u', '-b', self.backing_img, self.mid_img_abs)
qemu_img('rebase', '-u', '-b', self.mid_img, self.test_img)
qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', self.backing_img_abs)
qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', self.mid_img_abs)
self.vm = iotests.VM().add_drive(self.test_img)
self.vm.launch()
def tearDown(self):
self.vm.shutdown()
os.remove(self.test_img)
os.remove(self.mid_img_abs)
os.remove(self.backing_img_abs)
try:
os.rmdir(os.path.join(iotests.test_dir, self.dir1))
os.rmdir(os.path.join(iotests.test_dir, self.dir3))
os.rmdir(os.path.join(iotests.test_dir, self.dir2))
except OSError as exception:
if exception.errno != errno.EEXIST and exception.errno != errno.ENOTEMPTY:
raise
def test_commit(self):
self.run_commit_test(self.mid_img, self.backing_img)
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed"))
def test_device_not_found(self):
result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % self.mid_img)
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
def test_top_same_base(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='%s' % self.mid_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img)
def test_top_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % self.backing_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
def test_base_invalid(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='badfile')
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found')
def test_top_is_active(self):
self.run_commit_test(self.test_img, self.backing_img)
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed"))
self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed"))
def test_top_and_base_reversed(self):
self.assert_no_active_block_jobs()
result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.backing_img, base='%s' % self.mid_img)
self.assert_qmp(result, 'error/class', 'GenericError')
self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img)
class TestSetSpeed(ImageCommitTestCase):
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, mid_img)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 0 512', test_img)
qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img)
self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
self.vm.launch()
def tearDown(self):
self.vm.shutdown()
os.remove(test_img)
os.remove(mid_img)
os.remove(backing_img)
def test_set_speed(self):
self.assert_no_active_block_jobs()
self.vm.pause_drive('drive0')
result = self.vm.qmp('block-commit', device='drive0', top=mid_img, speed=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', 1024 * 1024)
self.cancel_and_wait(resume=True)
class TestActiveZeroLengthImage(TestSingleDrive):
image_len = 0
class TestReopenOverlay(ImageCommitTestCase):
image_len = 1024 * 1024
img0 = os.path.join(iotests.test_dir, '0.img')
img1 = os.path.join(iotests.test_dir, '1.img')
img2 = os.path.join(iotests.test_dir, '2.img')
img3 = os.path.join(iotests.test_dir, '3.img')
def setUp(self):
iotests.create_image(self.img0, self.image_len)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img0, self.img1)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img1, self.img2)
qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img2, self.img3)
qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1)
self.vm = iotests.VM().add_drive(self.img3)
self.vm.launch()
def tearDown(self):
self.vm.shutdown()
os.remove(self.img0)
os.remove(self.img1)
os.remove(self.img2)
os.remove(self.img3)
# This tests what happens when the overlay image of the 'top' node
# needs to be reopened in read-write mode in order to update the
# backing image string.
def test_reopen_overlay(self):
self.run_commit_test(self.img1, self.img0)
if __name__ == '__main__':
iotests.main(supported_fmts=['qcow2', 'qed'],
supported_protocols=['file'])