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>
345 lines
16 KiB
Python
Executable File
345 lines
16 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):
|
|
self.assert_no_active_block_jobs()
|
|
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("virtio-scsi-pci")
|
|
self.vm.add_device("scsi-hd,id=scsi0,drive=drive0")
|
|
self.vm.launch()
|
|
|
|
def tearDown(self):
|
|
self.vm.shutdown()
|
|
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_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_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)
|
|
|
|
# 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
|
|
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(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'])
|