1#!/usr/bin/env python3
2# group: rw quick
3#
4# This test covers what happens when a mirror block job is cancelled
5# in various phases of its existence.
6#
7# Note that this test only checks the emitted events (i.e.
8# BLOCK_JOB_COMPLETED vs. BLOCK_JOB_CANCELLED), it does not compare
9# whether the target is in sync with the source when the
10# BLOCK_JOB_COMPLETED event occurs.  This is covered by other tests
11# (such as 041).
12#
13# Copyright (C) 2018 Red Hat, Inc.
14#
15# This program is free software; you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation; either version 2 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program.  If not, see <http://www.gnu.org/licenses/>.
27#
28# Creator/Owner: Max Reitz <mreitz@redhat.com>
29
30import iotests
31from iotests import log, qemu_img, qemu_io_silent
32
33iotests.script_initialize(supported_fmts=['qcow2', 'raw'])
34
35
36# Launches the VM, adds two null-co nodes (source and target), and
37# starts a blockdev-mirror job on them.
38#
39# Either both or none of speed and buf_size must be given.
40
41def start_mirror(vm, speed=None, buf_size=None):
42    vm.launch()
43
44    ret = vm.qmp('blockdev-add',
45                     node_name='source',
46                     driver='null-co',
47                     size=1048576)
48    assert ret['return'] == {}
49
50    ret = vm.qmp('blockdev-add',
51                     node_name='target',
52                     driver='null-co',
53                     size=1048576)
54    assert ret['return'] == {}
55
56    if speed is not None:
57        ret = vm.qmp('blockdev-mirror',
58                         job_id='mirror',
59                         device='source',
60                         target='target',
61                         sync='full',
62                         speed=speed,
63                         buf_size=buf_size)
64    else:
65        ret = vm.qmp('blockdev-mirror',
66                         job_id='mirror',
67                         device='source',
68                         target='target',
69                         sync='full')
70
71    assert ret['return'] == {}
72
73
74log('')
75log('=== Cancel mirror job before convergence ===')
76log('')
77
78log('--- force=false ---')
79log('')
80
81with iotests.VM() as vm:
82    # Low speed so it does not converge
83    start_mirror(vm, 65536, 65536)
84
85    log('Cancelling job')
86    log(vm.qmp('block-job-cancel', device='mirror', force=False))
87
88    log(vm.event_wait('BLOCK_JOB_CANCELLED'),
89        filters=[iotests.filter_qmp_event])
90
91log('')
92log('--- force=true ---')
93log('')
94
95with iotests.VM() as vm:
96    # Low speed so it does not converge
97    start_mirror(vm, 65536, 65536)
98
99    log('Cancelling job')
100    log(vm.qmp('block-job-cancel', device='mirror', force=True))
101
102    log(vm.event_wait('BLOCK_JOB_CANCELLED'),
103        filters=[iotests.filter_qmp_event])
104
105
106log('')
107log('=== Cancel mirror job after convergence ===')
108log('')
109
110log('--- force=false ---')
111log('')
112
113with iotests.VM() as vm:
114    start_mirror(vm)
115
116    log(vm.event_wait('BLOCK_JOB_READY'),
117        filters=[iotests.filter_qmp_event])
118
119    log('Cancelling job')
120    log(vm.qmp('block-job-cancel', device='mirror', force=False))
121
122    log(vm.event_wait('BLOCK_JOB_COMPLETED'),
123        filters=[iotests.filter_qmp_event])
124
125log('')
126log('--- force=true ---')
127log('')
128
129with iotests.VM() as vm:
130    start_mirror(vm)
131
132    log(vm.event_wait('BLOCK_JOB_READY'),
133        filters=[iotests.filter_qmp_event])
134
135    log('Cancelling job')
136    log(vm.qmp('block-job-cancel', device='mirror', force=True))
137
138    log(vm.event_wait('BLOCK_JOB_CANCELLED'),
139        filters=[iotests.filter_qmp_event])
140
141log('')
142log('=== Cancel mirror job from throttled node by quitting ===')
143log('')
144
145with iotests.VM() as vm, \
146     iotests.FilePath('src.img') as src_img_path:
147
148    assert qemu_img('create', '-f', iotests.imgfmt, src_img_path, '64M') == 0
149    assert qemu_io_silent('-f', iotests.imgfmt, src_img_path,
150                          '-c', 'write -P 42 0M 64M') == 0
151
152    vm.launch()
153
154    ret = vm.qmp('object-add', qom_type='throttle-group', id='tg',
155                 limits={'bps-read': 4096})
156    assert ret['return'] == {}
157
158    ret = vm.qmp('blockdev-add',
159                 node_name='source',
160                 driver=iotests.imgfmt,
161                 file={
162                     'driver': 'file',
163                     'filename': src_img_path
164                 })
165    assert ret['return'] == {}
166
167    ret = vm.qmp('blockdev-add',
168                 node_name='throttled-source',
169                 driver='throttle',
170                 throttle_group='tg',
171                 file='source')
172    assert ret['return'] == {}
173
174    ret = vm.qmp('blockdev-add',
175                 node_name='target',
176                 driver='null-co',
177                 size=(64 * 1048576))
178    assert ret['return'] == {}
179
180    ret = vm.qmp('blockdev-mirror',
181                 job_id='mirror',
182                 device='throttled-source',
183                 target='target',
184                 sync='full')
185    assert ret['return'] == {}
186
187    log(vm.qmp('quit'))
188
189    with iotests.Timeout(5, 'Timeout waiting for VM to quit'):
190        vm.shutdown()
191