qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

219 (9243B)


      1 #!/usr/bin/env python3
      2 # group: rw
      3 #
      4 # Copyright (C) 2018 Red Hat, Inc.
      5 #
      6 # This program is free software; you can redistribute it and/or modify
      7 # it under the terms of the GNU General Public License as published by
      8 # the Free Software Foundation; either version 2 of the License, or
      9 # (at your option) any later version.
     10 #
     11 # This program is distributed in the hope that it will be useful,
     12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 # GNU General Public License for more details.
     15 #
     16 # You should have received a copy of the GNU General Public License
     17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 #
     19 # Creator/Owner: Kevin Wolf <kwolf@redhat.com>
     20 #
     21 # Check using the job-* QMP commands with block jobs
     22 
     23 import iotests
     24 
     25 iotests.script_initialize(supported_fmts=['qcow2'])
     26 
     27 img_size = 4 * 1024 * 1024
     28 
     29 def pause_wait(vm, job_id):
     30     with iotests.Timeout(3, "Timeout waiting for job to pause"):
     31         while True:
     32             result = vm.qmp('query-jobs')
     33             for job in result['return']:
     34                 if job['id'] == job_id and job['status'] in ['paused', 'standby']:
     35                     return job
     36 
     37 # Test that block-job-pause/resume and job-pause/resume can be mixed
     38 def test_pause_resume(vm):
     39     for pause_cmd, pause_arg in [('block-job-pause', 'device'),
     40                                  ('job-pause', 'id')]:
     41         for resume_cmd, resume_arg in [('block-job-resume', 'device'),
     42                                        ('job-resume', 'id')]:
     43             iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd))
     44 
     45             iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'}))
     46             pause_wait(vm, 'job0')
     47             iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
     48             result = vm.qmp('query-jobs')
     49             iotests.log(result)
     50 
     51             old_progress = result['return'][0]['current-progress']
     52             total_progress = result['return'][0]['total-progress']
     53 
     54             iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'}))
     55             iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
     56             if old_progress < total_progress:
     57                 # Wait for the job to advance
     58                 while result['return'][0]['current-progress'] == old_progress:
     59                     result = vm.qmp('query-jobs')
     60                 iotests.log(result)
     61             else:
     62                 # Already reached the end, so the job cannot advance
     63                 # any further; therefore, the query-jobs result can be
     64                 # logged immediately
     65                 iotests.log(vm.qmp('query-jobs'))
     66 
     67 def test_job_lifecycle(vm, job, job_args, has_ready=False, is_mirror=False):
     68     global img_size
     69 
     70     iotests.log('')
     71     iotests.log('')
     72     iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' %
     73                 (job,
     74                  job_args.get('auto-finalize', True),
     75                  job_args.get('auto-dismiss', True)))
     76     iotests.log(vm.qmp(job, job_id='job0', **job_args))
     77 
     78     # Depending on the storage, the first request may or may not have completed
     79     # yet (and the total progress may not have been fully determined yet), so
     80     # filter out the progress. Later query-job calls don't need the filtering
     81     # because the progress is made deterministic by the block job speed
     82     result = vm.qmp('query-jobs')
     83     for j in result['return']:
     84         j['current-progress'] = 'FILTERED'
     85         j['total-progress'] = 'FILTERED'
     86     iotests.log(result)
     87 
     88     # undefined -> created -> running
     89     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
     90     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
     91 
     92     # Wait for total-progress to stabilize
     93     while vm.qmp('query-jobs')['return'][0]['total-progress'] < img_size:
     94         pass
     95 
     96     # RUNNING state:
     97     # pause/resume should work, complete/finalize/dismiss should error out
     98     iotests.log('')
     99     iotests.log('Pause/resume in RUNNING')
    100     test_pause_resume(vm)
    101 
    102     iotests.log(vm.qmp('job-complete', id='job0'))
    103     iotests.log(vm.qmp('job-finalize', id='job0'))
    104     iotests.log(vm.qmp('job-dismiss', id='job0'))
    105 
    106     iotests.log(vm.qmp('block-job-complete', device='job0'))
    107     iotests.log(vm.qmp('block-job-finalize', id='job0'))
    108     iotests.log(vm.qmp('block-job-dismiss', id='job0'))
    109 
    110     # Let the job complete (or transition to READY if it supports that)
    111     iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0))
    112     if has_ready:
    113         iotests.log('')
    114         iotests.log('Waiting for READY state...')
    115         vm.event_wait('BLOCK_JOB_READY')
    116         iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    117         iotests.log(vm.qmp('query-jobs'))
    118 
    119         # READY state:
    120         # pause/resume/complete should work, finalize/dismiss should error out
    121         iotests.log('')
    122         iotests.log('Pause/resume in READY')
    123         test_pause_resume(vm)
    124 
    125         iotests.log(vm.qmp('job-finalize', id='job0'))
    126         iotests.log(vm.qmp('job-dismiss', id='job0'))
    127 
    128         iotests.log(vm.qmp('block-job-finalize', id='job0'))
    129         iotests.log(vm.qmp('block-job-dismiss', id='job0'))
    130 
    131         # Transition to WAITING
    132         iotests.log(vm.qmp('job-complete', id='job0'))
    133 
    134     # Move to WAITING and PENDING state
    135     iotests.log('')
    136     iotests.log('Waiting for PENDING state...')
    137     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    138     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    139     if is_mirror:
    140         iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    141         iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    142 
    143     if not job_args.get('auto-finalize', True):
    144         # PENDING state:
    145         # finalize should work, pause/complete/dismiss should error out
    146         iotests.log(vm.qmp('query-jobs'))
    147 
    148         iotests.log(vm.qmp('job-pause', id='job0'))
    149         iotests.log(vm.qmp('job-complete', id='job0'))
    150         iotests.log(vm.qmp('job-dismiss', id='job0'))
    151 
    152         iotests.log(vm.qmp('block-job-pause', device='job0'))
    153         iotests.log(vm.qmp('block-job-complete', device='job0'))
    154         iotests.log(vm.qmp('block-job-dismiss', id='job0'))
    155 
    156         # Transition to CONCLUDED
    157         iotests.log(vm.qmp('job-finalize', id='job0'))
    158 
    159 
    160     # Move to CONCLUDED state
    161     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    162 
    163     if not job_args.get('auto-dismiss', True):
    164         # CONCLUDED state:
    165         # dismiss should work, pause/complete/finalize should error out
    166         iotests.log(vm.qmp('query-jobs'))
    167 
    168         iotests.log(vm.qmp('job-pause', id='job0'))
    169         iotests.log(vm.qmp('job-complete', id='job0'))
    170         iotests.log(vm.qmp('job-finalize', id='job0'))
    171 
    172         iotests.log(vm.qmp('block-job-pause', device='job0'))
    173         iotests.log(vm.qmp('block-job-complete', device='job0'))
    174         iotests.log(vm.qmp('block-job-finalize', id='job0'))
    175 
    176         # Transition to NULL
    177         iotests.log(vm.qmp('job-dismiss', id='job0'))
    178 
    179     # Move to NULL state
    180     iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
    181     iotests.log(vm.qmp('query-jobs'))
    182 
    183 
    184 with iotests.FilePath('disk.img') as disk_path, \
    185      iotests.FilePath('copy.img') as copy_path, \
    186      iotests.VM() as vm:
    187 
    188     iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, str(img_size))
    189     iotests.qemu_io('-c', 'write 0 %i' % (img_size),
    190                     '-f', iotests.imgfmt, disk_path)
    191 
    192     iotests.log('Launching VM...')
    193     vm.add_blockdev(vm.qmp_to_opts({
    194         'driver': iotests.imgfmt,
    195         'node-name': 'drive0-node',
    196         'file': {
    197             'driver': 'file',
    198             'filename': disk_path,
    199         },
    200     }))
    201     vm.launch()
    202 
    203     # In order to keep things deterministic (especially progress in query-job,
    204     # but related to this also automatic state transitions like job
    205     # completion), but still get pause points often enough to avoid making this
    206     # test very slow, it's important to have the right ratio between speed and
    207     # copy-chunk-size.
    208     #
    209     # Chose 64k copy-chunk-size both for mirror (by buf_size) and backup (by
    210     # x-max-chunk). The slice time, i.e. the granularity of the rate limiting
    211     # is 100ms. With a speed of 256k per second, we can get four pause points
    212     # per second. This gives us 250ms per iteration, which should be enough to
    213     # stay deterministic.
    214 
    215     test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={
    216         'device': 'drive0-node',
    217         'target': copy_path,
    218         'sync': 'full',
    219         'speed': 262144,
    220         'buf_size': 65536,
    221     })
    222 
    223     for auto_finalize in [True, False]:
    224         for auto_dismiss in [True, False]:
    225             test_job_lifecycle(vm, 'drive-backup', is_mirror=True, job_args={
    226                 'device': 'drive0-node',
    227                 'target': copy_path,
    228                 'sync': 'full',
    229                 'speed': 262144,
    230                 'x-perf': {'max-chunk': 65536},
    231                 'auto-finalize': auto_finalize,
    232                 'auto-dismiss': auto_dismiss,
    233             })
    234 
    235     vm.shutdown()