xref: /qemu/tests/qemu-iotests/219 (revision 2e8f72ac)
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
23import iotests
24
25iotests.script_initialize(supported_fmts=['qcow2'])
26
27img_size = 4 * 1024 * 1024
28
29def 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
38def 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
67def 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
184with 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    # buf_size.
208    #
209    # For backup, buf_size is hard-coded to the source image cluster size (64k),
210    # so we'll pick the same for mirror. The slice time, i.e. the granularity
211    # of the rate limiting is 100ms. With a speed of 256k per second, we can
212    # get four pause points per second. This gives us 250ms per iteration,
213    # which should be enough to 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                'auto-finalize': auto_finalize,
231                'auto-dismiss': auto_dismiss,
232            })
233
234    vm.shutdown()
235