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