cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

030 (41826B)


      1#!/usr/bin/env python3
      2# group: rw backing
      3#
      4# Tests for image streaming.
      5#
      6# Copyright (C) 2012 IBM Corp.
      7#
      8# This program is free software; you can redistribute it and/or modify
      9# it under the terms of the GNU General Public License as published by
     10# the Free Software Foundation; either version 2 of the License, or
     11# (at your option) any later version.
     12#
     13# This program is distributed in the hope that it will be useful,
     14# but WITHOUT ANY WARRANTY; without even the implied warranty of
     15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16# GNU General Public License for more details.
     17#
     18# You should have received a copy of the GNU General Public License
     19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
     20#
     21
     22import time
     23import os
     24import iotests
     25import unittest
     26from iotests import qemu_img, qemu_io
     27
     28backing_img = os.path.join(iotests.test_dir, 'backing.img')
     29mid_img = os.path.join(iotests.test_dir, 'mid.img')
     30test_img = os.path.join(iotests.test_dir, 'test.img')
     31
     32class TestSingleDrive(iotests.QMPTestCase):
     33    image_len = 1 * 1024 * 1024 # MB
     34
     35    def setUp(self):
     36        iotests.create_image(backing_img, TestSingleDrive.image_len)
     37        qemu_img('create', '-f', iotests.imgfmt,
     38                 '-o', 'backing_file=%s' % backing_img,
     39                 '-F', 'raw', mid_img)
     40        qemu_img('create', '-f', iotests.imgfmt,
     41                 '-o', 'backing_file=%s' % mid_img,
     42                 '-F', iotests.imgfmt, test_img)
     43        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img)
     44        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 524288 512', mid_img)
     45        self.vm = iotests.VM().add_drive("blkdebug::" + test_img,
     46                                         "backing.node-name=mid," +
     47                                         "backing.backing.node-name=base")
     48        self.vm.launch()
     49
     50    def tearDown(self):
     51        self.vm.shutdown()
     52        os.remove(test_img)
     53        os.remove(mid_img)
     54        os.remove(backing_img)
     55
     56    def test_stream(self):
     57        self.assert_no_active_block_jobs()
     58
     59        result = self.vm.qmp('block-stream', device='drive0')
     60        self.assert_qmp(result, 'return', {})
     61
     62        self.wait_until_completed()
     63
     64        self.assert_no_active_block_jobs()
     65        self.vm.shutdown()
     66
     67        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
     68                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
     69                         'image file map does not match backing file after streaming')
     70
     71    def test_stream_intermediate(self):
     72        self.assert_no_active_block_jobs()
     73
     74        self.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img),
     75                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img),
     76                            'image file map matches backing file before streaming')
     77
     78        result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid')
     79        self.assert_qmp(result, 'return', {})
     80
     81        self.wait_until_completed(drive='stream-mid')
     82
     83        self.assert_no_active_block_jobs()
     84        self.vm.shutdown()
     85
     86        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
     87                         qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
     88                         'image file map does not match backing file after streaming')
     89
     90    def test_stream_pause(self):
     91        self.assert_no_active_block_jobs()
     92
     93        self.vm.pause_drive('drive0')
     94        result = self.vm.qmp('block-stream', device='drive0')
     95        self.assert_qmp(result, 'return', {})
     96
     97        self.pause_job('drive0', wait=False)
     98        self.vm.resume_drive('drive0')
     99        self.pause_wait('drive0')
    100
    101        result = self.vm.qmp('query-block-jobs')
    102        offset = self.dictpath(result, 'return[0]/offset')
    103
    104        time.sleep(0.5)
    105        result = self.vm.qmp('query-block-jobs')
    106        self.assert_qmp(result, 'return[0]/offset', offset)
    107
    108        result = self.vm.qmp('block-job-resume', device='drive0')
    109        self.assert_qmp(result, 'return', {})
    110
    111        self.wait_until_completed()
    112
    113        self.assert_no_active_block_jobs()
    114        self.vm.shutdown()
    115
    116        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
    117                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
    118                         'image file map does not match backing file after streaming')
    119
    120    def test_stream_no_op(self):
    121        self.assert_no_active_block_jobs()
    122
    123        # The image map is empty before the operation
    124        empty_map = qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', test_img)
    125
    126        # This is a no-op: no data should ever be copied from the base image
    127        result = self.vm.qmp('block-stream', device='drive0', base=mid_img)
    128        self.assert_qmp(result, 'return', {})
    129
    130        self.wait_until_completed()
    131
    132        self.assert_no_active_block_jobs()
    133        self.vm.shutdown()
    134
    135        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
    136                         empty_map, 'image file map changed after a no-op')
    137
    138    def test_stream_partial(self):
    139        self.assert_no_active_block_jobs()
    140
    141        result = self.vm.qmp('block-stream', device='drive0', base=backing_img)
    142        self.assert_qmp(result, 'return', {})
    143
    144        self.wait_until_completed()
    145
    146        self.assert_no_active_block_jobs()
    147        self.vm.shutdown()
    148
    149        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
    150                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
    151                         'image file map does not match backing file after streaming')
    152
    153    def test_device_not_found(self):
    154        result = self.vm.qmp('block-stream', device='nonexistent')
    155        self.assert_qmp(result, 'error/desc',
    156            'Cannot find device=\'nonexistent\' nor node-name=\'nonexistent\'')
    157
    158    def test_job_id_missing(self):
    159        result = self.vm.qmp('block-stream', device='mid')
    160        self.assert_qmp(result, 'error/desc', "Invalid job ID ''")
    161
    162    def test_read_only(self):
    163        # Create a new file that we can attach (we need a read-only top)
    164        with iotests.FilePath('ro-top.img') as ro_top_path:
    165            qemu_img('create', '-f', iotests.imgfmt, ro_top_path,
    166                     str(self.image_len))
    167
    168            result = self.vm.qmp('blockdev-add',
    169                                 node_name='ro-top',
    170                                 driver=iotests.imgfmt,
    171                                 read_only=True,
    172                                 file={
    173                                     'driver': 'file',
    174                                     'filename': ro_top_path,
    175                                     'read-only': True
    176                                 },
    177                                 backing='mid')
    178            self.assert_qmp(result, 'return', {})
    179
    180            result = self.vm.qmp('block-stream', job_id='stream',
    181                                 device='ro-top', base_node='base')
    182            self.assert_qmp(result, 'error/desc', 'Block node is read-only')
    183
    184            result = self.vm.qmp('blockdev-del', node_name='ro-top')
    185            self.assert_qmp(result, 'return', {})
    186
    187
    188class TestParallelOps(iotests.QMPTestCase):
    189    num_ops = 4 # Number of parallel block-stream operations
    190    num_imgs = num_ops * 2 + 1
    191    image_len = num_ops * 4 * 1024 * 1024
    192    imgs = []
    193
    194    def setUp(self):
    195        opts = []
    196        self.imgs = []
    197
    198        # Initialize file names and command-line options
    199        for i in range(self.num_imgs):
    200            img_depth = self.num_imgs - i - 1
    201            opts.append("backing." * img_depth + "node-name=node%d" % i)
    202            self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i))
    203
    204        # Create all images
    205        iotests.create_image(self.imgs[0], self.image_len)
    206        for i in range(1, self.num_imgs):
    207            qemu_img('create', '-f', iotests.imgfmt,
    208                     '-o', 'backing_file=%s' % self.imgs[i-1],
    209                     '-F', 'raw' if i == 1 else iotests.imgfmt, self.imgs[i])
    210
    211        # Put data into the images we are copying data from
    212        odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1]
    213        for i in range(len(odd_img_indexes)):
    214            # Alternate between 2MB and 4MB.
    215            # This way jobs will not finish in the same order they were created
    216            num_mb = 2 + 2 * (i % 2)
    217            qemu_io('-f', iotests.imgfmt,
    218                    '-c', 'write -P 0xFF %dM %dM' % (i * 4, num_mb),
    219                    self.imgs[odd_img_indexes[i]])
    220
    221        # Attach the drive to the VM
    222        self.vm = iotests.VM()
    223        self.vm.add_drive(self.imgs[-1], ','.join(opts))
    224        self.vm.launch()
    225
    226    def tearDown(self):
    227        self.vm.shutdown()
    228        for img in self.imgs:
    229            os.remove(img)
    230
    231    # Test that it's possible to run several block-stream operations
    232    # in parallel in the same snapshot chain
    233    @unittest.skipIf(os.environ.get('QEMU_CHECK_BLOCK_AUTO'), 'disabled in CI')
    234    def test_stream_parallel(self):
    235        self.assert_no_active_block_jobs()
    236
    237        # Check that the maps don't match before the streaming operations
    238        for i in range(2, self.num_imgs, 2):
    239            self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]),
    240                                qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]),
    241                                'image file map matches backing file before streaming')
    242
    243        # Create all streaming jobs
    244        pending_jobs = []
    245        for i in range(2, self.num_imgs, 2):
    246            node_name = 'node%d' % i
    247            job_id = 'stream-%s' % node_name
    248            pending_jobs.append(job_id)
    249            result = self.vm.qmp('block-stream', device=node_name,
    250                                 job_id=job_id, bottom=f'node{i-1}',
    251                                 speed=1024)
    252            self.assert_qmp(result, 'return', {})
    253
    254        for job in pending_jobs:
    255            result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
    256            self.assert_qmp(result, 'return', {})
    257
    258        # Wait for all jobs to be finished.
    259        while len(pending_jobs) > 0:
    260            for event in self.vm.get_qmp_events(wait=True):
    261                if event['event'] == 'BLOCK_JOB_COMPLETED':
    262                    job_id = self.dictpath(event, 'data/device')
    263                    self.assertTrue(job_id in pending_jobs)
    264                    self.assert_qmp_absent(event, 'data/error')
    265                    pending_jobs.remove(job_id)
    266
    267        self.assert_no_active_block_jobs()
    268        self.vm.shutdown()
    269
    270        # Check that all maps match now
    271        for i in range(2, self.num_imgs, 2):
    272            self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]),
    273                             qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]),
    274                             'image file map does not match backing file after streaming')
    275
    276    # Test that it's not possible to perform two block-stream
    277    # operations if there are nodes involved in both.
    278    def test_overlapping_1(self):
    279        self.assert_no_active_block_jobs()
    280
    281        # Set a speed limit to make sure that this job blocks the rest
    282        result = self.vm.qmp('block-stream', device='node4',
    283                             job_id='stream-node4', base=self.imgs[1],
    284                             filter_node_name='stream-filter', speed=1024*1024)
    285        self.assert_qmp(result, 'return', {})
    286
    287        result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2])
    288        self.assert_qmp(result, 'error/desc',
    289            "Node 'stream-filter' is busy: block device is in use by block job: stream")
    290
    291        result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2])
    292        self.assert_qmp(result, 'error/desc',
    293            "Node 'node3' is busy: block device is in use by block job: stream")
    294
    295        result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2')
    296        self.assert_qmp(result, 'error/desc',
    297            "Node 'node4' is busy: block device is in use by block job: stream")
    298
    299        # block-commit should also fail if it touches nodes used by the stream job
    300        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4')
    301        self.assert_qmp(result, 'error/desc',
    302            "Node 'stream-filter' is busy: block device is in use by block job: stream")
    303
    304        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1')
    305        self.assert_qmp(result, 'error/desc',
    306            "Node 'node3' is busy: block device is in use by block job: stream")
    307
    308        # This fails because it needs to modify the backing string in node2, which is blocked
    309        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0')
    310        self.assert_qmp(result, 'error/desc',
    311            "Node 'node2' is busy: block device is in use by block job: stream")
    312
    313        result = self.vm.qmp('block-job-set-speed', device='stream-node4', speed=0)
    314        self.assert_qmp(result, 'return', {})
    315
    316        self.wait_until_completed(drive='stream-node4')
    317        self.assert_no_active_block_jobs()
    318
    319    # Similar to test_overlapping_1, but with block-commit
    320    # blocking the other jobs
    321    def test_overlapping_2(self):
    322        self.assertLessEqual(9, self.num_imgs)
    323        self.assert_no_active_block_jobs()
    324
    325        # Set a speed limit to make sure that this job blocks the rest
    326        result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024)
    327        self.assert_qmp(result, 'return', {})
    328
    329        result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3')
    330        self.assert_qmp(result, 'error/desc',
    331            "Node 'node3' is busy: block device is in use by block job: commit")
    332
    333        result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6')
    334        self.assert_qmp(result, 'error/desc',
    335            "Node 'node5' is busy: block device is in use by block job: commit")
    336
    337        result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4')
    338        self.assert_qmp(result, 'error/desc',
    339            "Node 'node4' is busy: block device is in use by block job: commit")
    340
    341        result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2')
    342        self.assert_qmp(result, 'error/desc',
    343            "Node 'node5' is busy: block device is in use by block job: commit")
    344
    345        # This fails because block-commit currently blocks the active layer even if it's not used
    346        result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0')
    347        self.assert_qmp(result, 'error/desc',
    348            "Node 'drive0' is busy: block device is in use by block job: commit")
    349
    350        result = self.vm.qmp('block-job-set-speed', device='commit-node3', speed=0)
    351        self.assert_qmp(result, 'return', {})
    352
    353        self.wait_until_completed(drive='commit-node3')
    354
    355    # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter.
    356    # Internally this uses a mirror block job, hence the separate test case.
    357    def test_overlapping_3(self):
    358        self.assertLessEqual(8, self.num_imgs)
    359        self.assert_no_active_block_jobs()
    360
    361        # Set a speed limit to make sure that this job blocks the rest
    362        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024)
    363        self.assert_qmp(result, 'return', {})
    364
    365        result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6')
    366        self.assert_qmp(result, 'error/desc',
    367            "Node 'node5' is busy: block device is in use by block job: commit")
    368
    369        result = self.vm.qmp('block-job-set-speed', device='commit-drive0', speed=0)
    370        self.assert_qmp(result, 'return', {})
    371
    372        event = self.vm.event_wait(name='BLOCK_JOB_READY')
    373        self.assert_qmp(event, 'data/device', 'commit-drive0')
    374        self.assert_qmp(event, 'data/type', 'commit')
    375        self.assert_qmp_absent(event, 'data/error')
    376
    377        result = self.vm.qmp('block-job-complete', device='commit-drive0')
    378        self.assert_qmp(result, 'return', {})
    379
    380        self.wait_until_completed(drive='commit-drive0')
    381
    382    # In this case the base node of the stream job is the same as the
    383    # top node of commit job. Since this results in the commit filter
    384    # node being part of the stream chain, this is not allowed.
    385    def test_overlapping_4(self):
    386        self.assert_no_active_block_jobs()
    387
    388        # Commit from node2 into node0
    389        result = self.vm.qmp('block-commit', device='drive0',
    390                             top=self.imgs[2], base=self.imgs[0],
    391                             filter_node_name='commit-filter', speed=1024*1024)
    392        self.assert_qmp(result, 'return', {})
    393
    394        # Stream from node2 into node4
    395        result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='node4')
    396        self.assert_qmp(result, 'error/desc',
    397            "Cannot freeze 'backing' link to 'commit-filter'")
    398
    399        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
    400        self.assert_qmp(result, 'return', {})
    401
    402        self.wait_until_completed()
    403        self.assert_no_active_block_jobs()
    404
    405    # In this case the base node of the stream job is the commit job's
    406    # filter node.  stream does not have a real dependency on its base
    407    # node, so even though commit removes it when it is done, there is
    408    # no conflict.
    409    def test_overlapping_5(self):
    410        self.assert_no_active_block_jobs()
    411
    412        # Commit from node2 into node0
    413        result = self.vm.qmp('block-commit', device='drive0',
    414                             top_node='node2', base_node='node0',
    415                             filter_node_name='commit-filter', speed=1024*1024)
    416        self.assert_qmp(result, 'return', {})
    417
    418        # Stream from node2 into node4
    419        result = self.vm.qmp('block-stream', device='node4',
    420                             base_node='commit-filter', job_id='node4')
    421        self.assert_qmp(result, 'return', {})
    422
    423        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
    424        self.assert_qmp(result, 'return', {})
    425
    426        self.vm.run_job(job='drive0', auto_dismiss=True)
    427        self.vm.run_job(job='node4', auto_dismiss=True)
    428        self.assert_no_active_block_jobs()
    429
    430    # Test a block-stream and a block-commit job in parallel
    431    # Here the stream job is supposed to finish quickly in order to reproduce
    432    # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507
    433    def test_stream_commit_1(self):
    434        self.assertLessEqual(8, self.num_imgs)
    435        self.assert_no_active_block_jobs()
    436
    437        # Stream from node0 into node2
    438        result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2')
    439        self.assert_qmp(result, 'return', {})
    440
    441        # Commit from the active layer into node3
    442        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3])
    443        self.assert_qmp(result, 'return', {})
    444
    445        # Wait for all jobs to be finished.
    446        pending_jobs = ['node2', 'drive0']
    447        while len(pending_jobs) > 0:
    448            for event in self.vm.get_qmp_events(wait=True):
    449                if event['event'] == 'BLOCK_JOB_COMPLETED':
    450                    node_name = self.dictpath(event, 'data/device')
    451                    self.assertTrue(node_name in pending_jobs)
    452                    self.assert_qmp_absent(event, 'data/error')
    453                    pending_jobs.remove(node_name)
    454                if event['event'] == 'BLOCK_JOB_READY':
    455                    self.assert_qmp(event, 'data/device', 'drive0')
    456                    self.assert_qmp(event, 'data/type', 'commit')
    457                    self.assert_qmp_absent(event, 'data/error')
    458                    self.assertTrue('drive0' in pending_jobs)
    459                    self.vm.qmp('block-job-complete', device='drive0')
    460
    461        self.assert_no_active_block_jobs()
    462
    463    # This is similar to test_stream_commit_1 but both jobs are slowed
    464    # down so they can run in parallel for a little while.
    465    def test_stream_commit_2(self):
    466        self.assertLessEqual(8, self.num_imgs)
    467        self.assert_no_active_block_jobs()
    468
    469        # Stream from node0 into node4
    470        result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024)
    471        self.assert_qmp(result, 'return', {})
    472
    473        # Commit from the active layer into node5
    474        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024)
    475        self.assert_qmp(result, 'return', {})
    476
    477        for job in ['drive0', 'node4']:
    478            result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
    479            self.assert_qmp(result, 'return', {})
    480
    481        # Wait for all jobs to be finished.
    482        pending_jobs = ['node4', 'drive0']
    483        while len(pending_jobs) > 0:
    484            for event in self.vm.get_qmp_events(wait=True):
    485                if event['event'] == 'BLOCK_JOB_COMPLETED':
    486                    node_name = self.dictpath(event, 'data/device')
    487                    self.assertTrue(node_name in pending_jobs)
    488                    self.assert_qmp_absent(event, 'data/error')
    489                    pending_jobs.remove(node_name)
    490                if event['event'] == 'BLOCK_JOB_READY':
    491                    self.assert_qmp(event, 'data/device', 'drive0')
    492                    self.assert_qmp(event, 'data/type', 'commit')
    493                    self.assert_qmp_absent(event, 'data/error')
    494                    self.assertTrue('drive0' in pending_jobs)
    495                    self.vm.qmp('block-job-complete', device='drive0')
    496
    497        self.assert_no_active_block_jobs()
    498
    499    # Test the base_node parameter
    500    def test_stream_base_node_name(self):
    501        self.assert_no_active_block_jobs()
    502
    503        self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]),
    504                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]),
    505                            'image file map matches backing file before streaming')
    506
    507        # Error: the base node does not exist
    508        result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream')
    509        self.assert_qmp(result, 'error/desc',
    510            'Cannot find device=\'\' nor node-name=\'none\'')
    511
    512        # Error: the base node is not a backing file of the top node
    513        result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream')
    514        self.assert_qmp(result, 'error/desc',
    515            "Node 'node6' is not a backing image of 'node4'")
    516
    517        # Error: the base node is the same as the top node
    518        result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream')
    519        self.assert_qmp(result, 'error/desc',
    520            "Node 'node4' is not a backing image of 'node4'")
    521
    522        # Error: cannot specify 'base' and 'base-node' at the same time
    523        result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream')
    524        self.assert_qmp(result, 'error/desc',
    525            "'base' and 'base-node' cannot be specified at the same time")
    526
    527        # Success: the base node is a backing file of the top node
    528        result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream')
    529        self.assert_qmp(result, 'return', {})
    530
    531        self.wait_until_completed(drive='stream')
    532
    533        self.assert_no_active_block_jobs()
    534        self.vm.shutdown()
    535
    536        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]),
    537                         qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]),
    538                         'image file map matches backing file after streaming')
    539
    540class TestQuorum(iotests.QMPTestCase):
    541    num_children = 3
    542    children = []
    543    backing = []
    544
    545    @iotests.skip_if_unsupported(['quorum'])
    546    def setUp(self):
    547        opts = ['driver=quorum', 'vote-threshold=2']
    548
    549        # Initialize file names and command-line options
    550        for i in range(self.num_children):
    551            child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i)
    552            backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i)
    553            self.children.append(child_img)
    554            self.backing.append(backing_img)
    555            qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
    556            qemu_io('-f', iotests.imgfmt,
    557                    '-c', 'write -P 0x55 0 1024', backing_img)
    558            qemu_img('create', '-f', iotests.imgfmt,
    559                     '-o', 'backing_file=%s' % backing_img,
    560                     '-F', iotests.imgfmt, child_img)
    561            opts.append("children.%d.file.filename=%s" % (i, child_img))
    562            opts.append("children.%d.node-name=node%d" % (i, i))
    563
    564        # Attach the drive to the VM
    565        self.vm = iotests.VM()
    566        self.vm.add_drive(path = None, opts = ','.join(opts))
    567        self.vm.launch()
    568
    569    def tearDown(self):
    570        self.vm.shutdown()
    571        for img in self.children:
    572            os.remove(img)
    573        for img in self.backing:
    574            os.remove(img)
    575
    576    def test_stream_quorum(self):
    577        self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]),
    578                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]),
    579                            'image file map matches backing file before streaming')
    580
    581        self.assert_no_active_block_jobs()
    582
    583        result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0')
    584        self.assert_qmp(result, 'return', {})
    585
    586        self.wait_until_completed(drive='stream-node0')
    587
    588        self.assert_no_active_block_jobs()
    589        self.vm.shutdown()
    590
    591        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]),
    592                         qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]),
    593                         'image file map does not match backing file after streaming')
    594
    595class TestSmallerBackingFile(iotests.QMPTestCase):
    596    backing_len = 1 * 1024 * 1024 # MB
    597    image_len = 2 * backing_len
    598
    599    def setUp(self):
    600        iotests.create_image(backing_img, self.backing_len)
    601        qemu_img('create', '-f', iotests.imgfmt,
    602                 '-o', 'backing_file=%s' % backing_img,
    603                 '-F', 'raw', test_img, str(self.image_len))
    604        self.vm = iotests.VM().add_drive(test_img)
    605        self.vm.launch()
    606
    607    # If this hangs, then you are missing a fix to complete streaming when the
    608    # end of the backing file is reached.
    609    def test_stream(self):
    610        self.assert_no_active_block_jobs()
    611
    612        result = self.vm.qmp('block-stream', device='drive0')
    613        self.assert_qmp(result, 'return', {})
    614
    615        self.wait_until_completed()
    616
    617        self.assert_no_active_block_jobs()
    618        self.vm.shutdown()
    619
    620class TestErrors(iotests.QMPTestCase):
    621    image_len = 2 * 1024 * 1024 # MB
    622
    623    # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
    624    STREAM_BUFFER_SIZE = 512 * 1024
    625
    626    def create_blkdebug_file(self, name, event, errno):
    627        file = open(name, 'w')
    628        file.write('''
    629[inject-error]
    630state = "1"
    631event = "%s"
    632errno = "%d"
    633immediately = "off"
    634once = "on"
    635sector = "%d"
    636
    637[set-state]
    638state = "1"
    639event = "%s"
    640new_state = "2"
    641
    642[set-state]
    643state = "2"
    644event = "%s"
    645new_state = "1"
    646''' % (event, errno, self.STREAM_BUFFER_SIZE // 512, event, event))
    647        file.close()
    648
    649class TestEIO(TestErrors):
    650    def setUp(self):
    651        self.blkdebug_file = backing_img + ".blkdebug"
    652        iotests.create_image(backing_img, TestErrors.image_len)
    653        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
    654        qemu_img('create', '-f', iotests.imgfmt,
    655                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
    656                       % (self.blkdebug_file, backing_img),
    657                 test_img)
    658        self.vm = iotests.VM().add_drive(test_img)
    659        self.vm.launch()
    660
    661    def tearDown(self):
    662        self.vm.shutdown()
    663        os.remove(test_img)
    664        os.remove(backing_img)
    665        os.remove(self.blkdebug_file)
    666
    667    def test_report(self):
    668        self.assert_no_active_block_jobs()
    669
    670        result = self.vm.qmp('block-stream', device='drive0')
    671        self.assert_qmp(result, 'return', {})
    672
    673        completed = False
    674        error = False
    675        while not completed:
    676            for event in self.vm.get_qmp_events(wait=True):
    677                if event['event'] == 'BLOCK_JOB_ERROR':
    678                    self.assert_qmp(event, 'data/device', 'drive0')
    679                    self.assert_qmp(event, 'data/operation', 'read')
    680                    error = True
    681                elif event['event'] == 'BLOCK_JOB_COMPLETED':
    682                    self.assertTrue(error, 'job completed unexpectedly')
    683                    self.assert_qmp(event, 'data/type', 'stream')
    684                    self.assert_qmp(event, 'data/device', 'drive0')
    685                    self.assert_qmp(event, 'data/error', 'Input/output error')
    686                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
    687                    self.assert_qmp(event, 'data/len', self.image_len)
    688                    completed = True
    689                elif event['event'] == 'JOB_STATUS_CHANGE':
    690                    self.assert_qmp(event, 'data/id', 'drive0')
    691
    692        self.assert_no_active_block_jobs()
    693        self.vm.shutdown()
    694
    695    def test_ignore(self):
    696        self.assert_no_active_block_jobs()
    697
    698        result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
    699        self.assert_qmp(result, 'return', {})
    700
    701        error = False
    702        completed = False
    703        while not completed:
    704            for event in self.vm.get_qmp_events(wait=True):
    705                if event['event'] == 'BLOCK_JOB_ERROR':
    706                    error = True
    707                    self.assert_qmp(event, 'data/device', 'drive0')
    708                    self.assert_qmp(event, 'data/operation', 'read')
    709                    result = self.vm.qmp('query-block-jobs')
    710                    if result == {'return': []}:
    711                        # Job finished too quickly
    712                        continue
    713                    self.assert_qmp(result, 'return[0]/paused', False)
    714                elif event['event'] == 'BLOCK_JOB_COMPLETED':
    715                    self.assertTrue(error, 'job completed unexpectedly')
    716                    self.assert_qmp(event, 'data/type', 'stream')
    717                    self.assert_qmp(event, 'data/device', 'drive0')
    718                    self.assert_qmp(event, 'data/error', 'Input/output error')
    719                    self.assert_qmp(event, 'data/offset', self.image_len)
    720                    self.assert_qmp(event, 'data/len', self.image_len)
    721                    completed = True
    722                elif event['event'] == 'JOB_STATUS_CHANGE':
    723                    self.assert_qmp(event, 'data/id', 'drive0')
    724
    725        self.assert_no_active_block_jobs()
    726        self.vm.shutdown()
    727
    728    def test_stop(self):
    729        self.assert_no_active_block_jobs()
    730
    731        result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
    732        self.assert_qmp(result, 'return', {})
    733
    734        error = False
    735        completed = False
    736        while not completed:
    737            for event in self.vm.get_qmp_events(wait=True):
    738                if event['event'] == 'BLOCK_JOB_ERROR':
    739                    error = True
    740                    self.assert_qmp(event, 'data/device', 'drive0')
    741                    self.assert_qmp(event, 'data/operation', 'read')
    742
    743                    result = self.vm.qmp('query-block-jobs')
    744                    self.assert_qmp(result, 'return[0]/paused', True)
    745                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
    746                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
    747
    748                    result = self.vm.qmp('block-job-resume', device='drive0')
    749                    self.assert_qmp(result, 'return', {})
    750
    751                    result = self.vm.qmp('query-block-jobs')
    752                    if result == {'return': []}:
    753                        # Race; likely already finished. Check.
    754                        continue
    755                    self.assert_qmp(result, 'return[0]/paused', False)
    756                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
    757                elif event['event'] == 'BLOCK_JOB_COMPLETED':
    758                    self.assertTrue(error, 'job completed unexpectedly')
    759                    self.assert_qmp(event, 'data/type', 'stream')
    760                    self.assert_qmp(event, 'data/device', 'drive0')
    761                    self.assert_qmp_absent(event, 'data/error')
    762                    self.assert_qmp(event, 'data/offset', self.image_len)
    763                    self.assert_qmp(event, 'data/len', self.image_len)
    764                    completed = True
    765                elif event['event'] == 'JOB_STATUS_CHANGE':
    766                    self.assert_qmp(event, 'data/id', 'drive0')
    767
    768        self.assert_no_active_block_jobs()
    769        self.vm.shutdown()
    770
    771    def test_enospc(self):
    772        self.assert_no_active_block_jobs()
    773
    774        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
    775        self.assert_qmp(result, 'return', {})
    776
    777        completed = False
    778        error = False
    779        while not completed:
    780            for event in self.vm.get_qmp_events(wait=True):
    781                if event['event'] == 'BLOCK_JOB_ERROR':
    782                    self.assert_qmp(event, 'data/device', 'drive0')
    783                    self.assert_qmp(event, 'data/operation', 'read')
    784                    error = True
    785                elif event['event'] == 'BLOCK_JOB_COMPLETED':
    786                    self.assertTrue(error, 'job completed unexpectedly')
    787                    self.assert_qmp(event, 'data/type', 'stream')
    788                    self.assert_qmp(event, 'data/device', 'drive0')
    789                    self.assert_qmp(event, 'data/error', 'Input/output error')
    790                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
    791                    self.assert_qmp(event, 'data/len', self.image_len)
    792                    completed = True
    793                elif event['event'] == 'JOB_STATUS_CHANGE':
    794                    self.assert_qmp(event, 'data/id', 'drive0')
    795
    796        self.assert_no_active_block_jobs()
    797        self.vm.shutdown()
    798
    799class TestENOSPC(TestErrors):
    800    def setUp(self):
    801        self.blkdebug_file = backing_img + ".blkdebug"
    802        iotests.create_image(backing_img, TestErrors.image_len)
    803        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28)
    804        qemu_img('create', '-f', iotests.imgfmt,
    805                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
    806                       % (self.blkdebug_file, backing_img),
    807                 test_img)
    808        self.vm = iotests.VM().add_drive(test_img)
    809        self.vm.launch()
    810
    811    def tearDown(self):
    812        self.vm.shutdown()
    813        os.remove(test_img)
    814        os.remove(backing_img)
    815        os.remove(self.blkdebug_file)
    816
    817    def test_enospc(self):
    818        self.assert_no_active_block_jobs()
    819
    820        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
    821        self.assert_qmp(result, 'return', {})
    822
    823        error = False
    824        completed = False
    825        while not completed:
    826            for event in self.vm.get_qmp_events(wait=True):
    827                if event['event'] == 'BLOCK_JOB_ERROR':
    828                    self.assert_qmp(event, 'data/device', 'drive0')
    829                    self.assert_qmp(event, 'data/operation', 'read')
    830                    error = True
    831
    832                    result = self.vm.qmp('query-block-jobs')
    833                    self.assert_qmp(result, 'return[0]/paused', True)
    834                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
    835                    self.assert_qmp(result, 'return[0]/io-status', 'nospace')
    836
    837                    result = self.vm.qmp('block-job-resume', device='drive0')
    838                    self.assert_qmp(result, 'return', {})
    839
    840                    result = self.vm.qmp('query-block-jobs')
    841                    if result == {'return': []}:
    842                        # Race; likely already finished. Check.
    843                        continue
    844                    self.assert_qmp(result, 'return[0]/paused', False)
    845                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
    846                elif event['event'] == 'BLOCK_JOB_COMPLETED':
    847                    self.assertTrue(error, 'job completed unexpectedly')
    848                    self.assert_qmp(event, 'data/type', 'stream')
    849                    self.assert_qmp(event, 'data/device', 'drive0')
    850                    self.assert_qmp_absent(event, 'data/error')
    851                    self.assert_qmp(event, 'data/offset', self.image_len)
    852                    self.assert_qmp(event, 'data/len', self.image_len)
    853                    completed = True
    854                elif event['event'] == 'JOB_STATUS_CHANGE':
    855                    self.assert_qmp(event, 'data/id', 'drive0')
    856
    857        self.assert_no_active_block_jobs()
    858        self.vm.shutdown()
    859
    860class TestStreamStop(iotests.QMPTestCase):
    861    image_len = 8 * 1024 * 1024 * 1024 # GB
    862
    863    def setUp(self):
    864        qemu_img('create', backing_img, str(TestStreamStop.image_len))
    865        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
    866        qemu_img('create', '-f', iotests.imgfmt,
    867                 '-o', 'backing_file=%s' % backing_img,
    868                 '-F', 'raw', test_img)
    869        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
    870        self.vm = iotests.VM().add_drive("blkdebug::" + test_img)
    871        self.vm.launch()
    872
    873    def tearDown(self):
    874        self.vm.shutdown()
    875        os.remove(test_img)
    876        os.remove(backing_img)
    877
    878    def test_stream_stop(self):
    879        self.assert_no_active_block_jobs()
    880
    881        self.vm.pause_drive('drive0')
    882        result = self.vm.qmp('block-stream', device='drive0')
    883        self.assert_qmp(result, 'return', {})
    884
    885        time.sleep(0.1)
    886        events = self.vm.get_qmp_events(wait=False)
    887        for e in events:
    888            self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE')
    889            self.assert_qmp(e, 'data/id', 'drive0')
    890
    891        self.cancel_and_wait(resume=True)
    892
    893class TestSetSpeed(iotests.QMPTestCase):
    894    image_len = 80 * 1024 * 1024 # MB
    895
    896    def setUp(self):
    897        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
    898        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
    899        qemu_img('create', '-f', iotests.imgfmt,
    900                 '-o', 'backing_file=%s' % backing_img,
    901                 '-F', 'raw', test_img)
    902        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
    903        self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
    904        self.vm.launch()
    905
    906    def tearDown(self):
    907        self.vm.shutdown()
    908        os.remove(test_img)
    909        os.remove(backing_img)
    910
    911    # This is a short performance test which is not run by default.
    912    # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
    913    def perf_test_throughput(self):
    914        self.assert_no_active_block_jobs()
    915
    916        result = self.vm.qmp('block-stream', device='drive0')
    917        self.assert_qmp(result, 'return', {})
    918
    919        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
    920        self.assert_qmp(result, 'return', {})
    921
    922        self.wait_until_completed()
    923
    924        self.assert_no_active_block_jobs()
    925
    926    def test_set_speed(self):
    927        self.assert_no_active_block_jobs()
    928
    929        self.vm.pause_drive('drive0')
    930        result = self.vm.qmp('block-stream', device='drive0')
    931        self.assert_qmp(result, 'return', {})
    932
    933        # Default speed is 0
    934        result = self.vm.qmp('query-block-jobs')
    935        self.assert_qmp(result, 'return[0]/device', 'drive0')
    936        self.assert_qmp(result, 'return[0]/speed', 0)
    937
    938        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
    939        self.assert_qmp(result, 'return', {})
    940
    941        # Ensure the speed we set was accepted
    942        result = self.vm.qmp('query-block-jobs')
    943        self.assert_qmp(result, 'return[0]/device', 'drive0')
    944        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
    945
    946        self.cancel_and_wait(resume=True)
    947        self.vm.pause_drive('drive0')
    948
    949        # Check setting speed in block-stream works
    950        result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
    951        self.assert_qmp(result, 'return', {})
    952
    953        result = self.vm.qmp('query-block-jobs')
    954        self.assert_qmp(result, 'return[0]/device', 'drive0')
    955        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
    956
    957        self.cancel_and_wait(resume=True)
    958
    959    def test_set_speed_invalid(self):
    960        self.assert_no_active_block_jobs()
    961
    962        result = self.vm.qmp('block-stream', device='drive0', speed=-1)
    963        self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
    964
    965        self.assert_no_active_block_jobs()
    966
    967        self.vm.pause_drive('drive0')
    968        result = self.vm.qmp('block-stream', device='drive0')
    969        self.assert_qmp(result, 'return', {})
    970
    971        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
    972        self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
    973
    974        self.cancel_and_wait(resume=True)
    975
    976if __name__ == '__main__':
    977    iotests.main(supported_fmts=['qcow2', 'qed'],
    978                 supported_protocols=['file'])