]>
Commit | Line | Data |
---|---|---|
2451f725 HR |
1 | #!/usr/bin/env python3 |
2 | # group: rw quick | |
3 | # | |
4 | # Test what happens when errors occur to a mirror job after it has | |
5 | # been cancelled in the READY phase | |
6 | # | |
7 | # Copyright (C) 2021 Red Hat, Inc. | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify | |
10 | # it under the terms of the GNU General Public License as published by | |
11 | # the Free Software Foundation; either version 2 of the License, or | |
12 | # (at your option) any later version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, | |
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | # GNU General Public License for more details. | |
18 | # | |
19 | # You should have received a copy of the GNU General Public License | |
20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | # | |
22 | ||
23 | import os | |
24 | import iotests | |
25 | ||
26 | ||
27 | image_size = 1 * 1024 * 1024 | |
28 | source = os.path.join(iotests.test_dir, 'source.img') | |
29 | target = os.path.join(iotests.test_dir, 'target.img') | |
30 | ||
31 | ||
32 | class TestMirrorReadyCancelError(iotests.QMPTestCase): | |
33 | def setUp(self) -> None: | |
34 | assert iotests.qemu_img_create('-f', iotests.imgfmt, source, | |
35 | str(image_size)) == 0 | |
36 | assert iotests.qemu_img_create('-f', iotests.imgfmt, target, | |
37 | str(image_size)) == 0 | |
38 | ||
fc2c3996 HR |
39 | # Ensure that mirror will copy something before READY so the |
40 | # target format layer will forward the pre-READY flush to its | |
41 | # file child | |
42 | assert iotests.qemu_io_silent('-c', 'write -P 1 0 64k', source) == 0 | |
43 | ||
2451f725 HR |
44 | self.vm = iotests.VM() |
45 | self.vm.launch() | |
46 | ||
47 | def tearDown(self) -> None: | |
48 | self.vm.shutdown() | |
49 | os.remove(source) | |
50 | os.remove(target) | |
51 | ||
52 | def add_blockdevs(self, once: bool) -> None: | |
53 | res = self.vm.qmp('blockdev-add', | |
54 | **{'node-name': 'source', | |
55 | 'driver': iotests.imgfmt, | |
56 | 'file': { | |
57 | 'driver': 'file', | |
58 | 'filename': source | |
59 | }}) | |
60 | self.assert_qmp(res, 'return', {}) | |
61 | ||
62 | # blkdebug notes: | |
63 | # Enter state 2 on the first flush, which happens before the | |
64 | # job enters the READY state. The second flush will happen | |
65 | # when the job is about to complete, and we want that one to | |
66 | # fail. | |
67 | res = self.vm.qmp('blockdev-add', | |
68 | **{'node-name': 'target', | |
69 | 'driver': iotests.imgfmt, | |
70 | 'file': { | |
71 | 'driver': 'blkdebug', | |
72 | 'image': { | |
73 | 'driver': 'file', | |
74 | 'filename': target | |
75 | }, | |
76 | 'set-state': [{ | |
77 | 'event': 'flush_to_disk', | |
78 | 'state': 1, | |
79 | 'new_state': 2 | |
80 | }], | |
81 | 'inject-error': [{ | |
82 | 'event': 'flush_to_disk', | |
83 | 'once': once, | |
84 | 'immediately': True, | |
85 | 'state': 2 | |
86 | }]}}) | |
87 | self.assert_qmp(res, 'return', {}) | |
88 | ||
89 | def start_mirror(self) -> None: | |
90 | res = self.vm.qmp('blockdev-mirror', | |
91 | job_id='mirror', | |
92 | device='source', | |
93 | target='target', | |
94 | filter_node_name='mirror-top', | |
95 | sync='full', | |
96 | on_target_error='stop') | |
97 | self.assert_qmp(res, 'return', {}) | |
98 | ||
99 | def cancel_mirror_with_error(self) -> None: | |
100 | self.vm.event_wait('BLOCK_JOB_READY') | |
101 | ||
102 | # Write something so will not leave the job immediately, but | |
103 | # flush first (which will fail, thanks to blkdebug) | |
104 | res = self.vm.qmp('human-monitor-command', | |
fc2c3996 | 105 | command_line='qemu-io mirror-top "write -P 2 0 64k"') |
2451f725 HR |
106 | self.assert_qmp(res, 'return', '') |
107 | ||
108 | # Drain status change events | |
109 | while self.vm.event_wait('JOB_STATUS_CHANGE', timeout=0.0) is not None: | |
110 | pass | |
111 | ||
112 | res = self.vm.qmp('block-job-cancel', device='mirror') | |
113 | self.assert_qmp(res, 'return', {}) | |
114 | ||
115 | self.vm.event_wait('BLOCK_JOB_ERROR') | |
116 | ||
117 | def test_transient_error(self) -> None: | |
118 | self.add_blockdevs(True) | |
119 | self.start_mirror() | |
120 | self.cancel_mirror_with_error() | |
121 | ||
122 | while True: | |
123 | e = self.vm.event_wait('JOB_STATUS_CHANGE') | |
124 | if e['data']['status'] == 'standby': | |
125 | # Transient error, try again | |
126 | self.vm.qmp('block-job-resume', device='mirror') | |
127 | elif e['data']['status'] == 'null': | |
128 | break | |
129 | ||
130 | def test_persistent_error(self) -> None: | |
131 | self.add_blockdevs(False) | |
132 | self.start_mirror() | |
133 | self.cancel_mirror_with_error() | |
134 | ||
135 | while True: | |
136 | e = self.vm.event_wait('JOB_STATUS_CHANGE') | |
137 | if e['data']['status'] == 'standby': | |
138 | # Persistent error, no point in continuing | |
139 | self.vm.qmp('block-job-cancel', device='mirror', force=True) | |
140 | elif e['data']['status'] == 'null': | |
141 | break | |
142 | ||
143 | ||
144 | if __name__ == '__main__': | |
145 | # LUKS would require special key-secret handling in add_blockdevs() | |
146 | iotests.main(supported_fmts=['generic'], | |
147 | unsupported_fmts=['luks'], | |
148 | supported_protocols=['file']) |