xref: /openbmc/qemu/tests/qemu-iotests/219 (revision bdebdc712b06ba82e103d617c335830682cde242)
1#!/usr/bin/env python
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.verify_image_format(supported_fmts=['qcow2'])
25
26def pause_wait(vm, job_id):
27    with iotests.Timeout(3, "Timeout waiting for job to pause"):
28        while True:
29            result = vm.qmp('query-jobs')
30            for job in result['return']:
31                if job['id'] == job_id and job['status'] in ['paused', 'standby']:
32                    return job
33
34# Test that block-job-pause/resume and job-pause/resume can be mixed
35def test_pause_resume(vm):
36    for pause_cmd, pause_arg in [('block-job-pause', 'device'),
37                                 ('job-pause', 'id')]:
38        for resume_cmd, resume_arg in [('block-job-resume', 'device'),
39                                       ('job-resume', 'id')]:
40            iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd))
41
42            iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'}))
43            pause_wait(vm, 'job0')
44            iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
45            iotests.log(vm.qmp('query-jobs'))
46
47            iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'}))
48            iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
49            iotests.log(vm.qmp('query-jobs'))
50
51def test_job_lifecycle(vm, job, job_args, has_ready=False):
52    iotests.log('')
53    iotests.log('')
54    iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' %
55                (job,
56                 job_args.get('auto-finalize', True),
57                 job_args.get('auto-dismiss', True)))
58    iotests.log(vm.qmp(job, job_id='job0', **job_args))
59
60    # Depending on the storage, the first request may or may not have completed
61    # yet, so filter out the progress. Later query-job calls don't need the
62    # filtering because the progress is made deterministic by the block job
63    # speed
64    result = vm.qmp('query-jobs')
65    for j in result['return']:
66        del j['current-progress']
67    iotests.log(result)
68
69    # undefined -> created -> running
70    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
71    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
72
73    # RUNNING state:
74    # pause/resume should work, complete/finalize/dismiss should error out
75    iotests.log('')
76    iotests.log('Pause/resume in RUNNING')
77    test_pause_resume(vm)
78
79    iotests.log(vm.qmp('job-complete', id='job0'))
80    iotests.log(vm.qmp('job-finalize', id='job0'))
81    iotests.log(vm.qmp('job-dismiss', id='job0'))
82
83    iotests.log(vm.qmp('block-job-complete', device='job0'))
84    iotests.log(vm.qmp('block-job-finalize', id='job0'))
85    iotests.log(vm.qmp('block-job-dismiss', id='job0'))
86
87    # Let the job complete (or transition to READY if it supports that)
88    iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0))
89    if has_ready:
90        iotests.log('')
91        iotests.log('Waiting for READY state...')
92        vm.event_wait('BLOCK_JOB_READY')
93        iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
94        iotests.log(vm.qmp('query-jobs'))
95
96        # READY state:
97        # pause/resume/complete should work, finalize/dismiss should error out
98        iotests.log('')
99        iotests.log('Pause/resume in READY')
100        test_pause_resume(vm)
101
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-finalize', id='job0'))
106        iotests.log(vm.qmp('block-job-dismiss', id='job0'))
107
108        # Transition to WAITING
109        iotests.log(vm.qmp('job-complete', id='job0'))
110
111    # Move to WAITING and PENDING state
112    iotests.log('')
113    iotests.log('Waiting for PENDING state...')
114    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
115    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
116
117    if not job_args.get('auto-finalize', True):
118        # PENDING state:
119        # finalize should work, pause/complete/dismiss should error out
120        iotests.log(vm.qmp('query-jobs'))
121
122        iotests.log(vm.qmp('job-pause', id='job0'))
123        iotests.log(vm.qmp('job-complete', id='job0'))
124        iotests.log(vm.qmp('job-dismiss', id='job0'))
125
126        iotests.log(vm.qmp('block-job-pause', device='job0'))
127        iotests.log(vm.qmp('block-job-complete', device='job0'))
128        iotests.log(vm.qmp('block-job-dismiss', id='job0'))
129
130        # Transition to CONCLUDED
131        iotests.log(vm.qmp('job-finalize', id='job0'))
132
133
134    # Move to CONCLUDED state
135    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
136
137    if not job_args.get('auto-dismiss', True):
138        # CONCLUDED state:
139        # dismiss should work, pause/complete/finalize should error out
140        iotests.log(vm.qmp('query-jobs'))
141
142        iotests.log(vm.qmp('job-pause', id='job0'))
143        iotests.log(vm.qmp('job-complete', id='job0'))
144        iotests.log(vm.qmp('job-finalize', id='job0'))
145
146        iotests.log(vm.qmp('block-job-pause', device='job0'))
147        iotests.log(vm.qmp('block-job-complete', device='job0'))
148        iotests.log(vm.qmp('block-job-finalize', id='job0'))
149
150        # Transition to NULL
151        iotests.log(vm.qmp('job-dismiss', id='job0'))
152
153    # Move to NULL state
154    iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
155    iotests.log(vm.qmp('query-jobs'))
156
157
158with iotests.FilePath('disk.img') as disk_path, \
159     iotests.FilePath('copy.img') as copy_path, \
160     iotests.VM() as vm:
161
162    img_size = '4M'
163    iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size)
164    iotests.qemu_io('-c', 'write 0 %s' % (img_size),
165                    '-f', iotests.imgfmt, disk_path)
166
167    iotests.log('Launching VM...')
168    vm.add_blockdev(vm.qmp_to_opts({
169        'driver': iotests.imgfmt,
170        'node-name': 'drive0-node',
171        'file': {
172            'driver': 'file',
173            'filename': disk_path,
174        },
175    }))
176    vm.launch()
177
178    # In order to keep things deterministic (especially progress in query-job,
179    # but related to this also automatic state transitions like job
180    # completion), but still get pause points often enough to avoid making this
181    # test very slow, it's important to have the right ratio between speed and
182    # buf_size.
183    #
184    # For backup, buf_size is hard-coded to the source image cluster size (64k),
185    # so we'll pick the same for mirror. The slice time, i.e. the granularity
186    # of the rate limiting is 100ms. With a speed of 256k per second, we can
187    # get four pause points per second. This gives us 250ms per iteration,
188    # which should be enough to stay deterministic.
189
190    test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={
191        'device': 'drive0-node',
192        'target': copy_path,
193        'sync': 'full',
194        'speed': 262144,
195        'buf_size': 65536,
196    })
197
198    for auto_finalize in [True, False]:
199        for auto_dismiss in [True, False]:
200            test_job_lifecycle(vm, 'drive-backup', job_args={
201                'device': 'drive0-node',
202                'target': copy_path,
203                'sync': 'full',
204                'speed': 262144,
205                'auto-finalize': auto_finalize,
206                'auto-dismiss': auto_dismiss,
207            })
208
209    vm.shutdown()
210