Philippe Mathieu-Daudé | 903cb1b | 2020-01-30 17:32:23 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 2 | # group: rw |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 3 | # |
| 4 | # Test cases for blockdev + IOThread interactions |
| 5 | # |
| 6 | # Copyright (C) 2019 Red Hat, Inc. |
| 7 | # |
| 8 | # This program is free software; you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License as published by |
| 10 | # the Free Software Foundation; either version 2 of the License, or |
| 11 | # (at your option) any later version. |
| 12 | # |
| 13 | # This program is distributed in the hope that it will be useful, |
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | # GNU General Public License for more details. |
| 17 | # |
| 18 | # You should have received a copy of the GNU General Public License |
| 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | # |
| 21 | |
| 22 | import os |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 23 | import time |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 24 | import iotests |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 25 | from iotests import qemu_img, QemuStorageDaemon |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 26 | |
| 27 | image_len = 64 * 1024 * 1024 |
| 28 | |
| 29 | # Test for RHBZ#1782175 |
| 30 | class TestDirtyBitmapIOThread(iotests.QMPTestCase): |
| 31 | drive0_img = os.path.join(iotests.test_dir, 'drive0.img') |
| 32 | images = { 'drive0': drive0_img } |
| 33 | |
| 34 | def setUp(self): |
| 35 | for name in self.images: |
| 36 | qemu_img('create', '-f', iotests.imgfmt, |
| 37 | self.images[name], str(image_len)) |
| 38 | |
| 39 | self.vm = iotests.VM() |
| 40 | self.vm.add_object('iothread,id=iothread0') |
| 41 | |
| 42 | for name in self.images: |
| 43 | self.vm.add_blockdev('driver=file,filename=%s,node-name=file_%s' |
| 44 | % (self.images[name], name)) |
| 45 | self.vm.add_blockdev('driver=qcow2,file=file_%s,node-name=%s' |
| 46 | % (name, name)) |
| 47 | |
| 48 | self.vm.launch() |
| 49 | self.vm.qmp('x-blockdev-set-iothread', |
| 50 | node_name='drive0', iothread='iothread0', |
| 51 | force=True) |
| 52 | |
| 53 | def tearDown(self): |
| 54 | self.vm.shutdown() |
| 55 | for name in self.images: |
| 56 | os.remove(self.images[name]) |
| 57 | |
| 58 | def test_add_dirty_bitmap(self): |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 59 | self.vm.cmd( |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 60 | 'block-dirty-bitmap-add', |
| 61 | node='drive0', |
| 62 | name='bitmap1', |
| 63 | persistent=True, |
| 64 | ) |
| 65 | |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 66 | |
| 67 | # Test for RHBZ#1746217 & RHBZ#1773517 |
| 68 | class TestNBDMirrorIOThread(iotests.QMPTestCase): |
| 69 | nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock') |
| 70 | drive0_img = os.path.join(iotests.test_dir, 'drive0.img') |
| 71 | mirror_img = os.path.join(iotests.test_dir, 'mirror.img') |
| 72 | images = { 'drive0': drive0_img, 'mirror': mirror_img } |
| 73 | |
| 74 | def setUp(self): |
| 75 | for name in self.images: |
| 76 | qemu_img('create', '-f', iotests.imgfmt, |
| 77 | self.images[name], str(image_len)) |
| 78 | |
| 79 | self.vm_src = iotests.VM(path_suffix='src') |
| 80 | self.vm_src.add_object('iothread,id=iothread0') |
| 81 | self.vm_src.add_blockdev('driver=file,filename=%s,node-name=file0' |
| 82 | % (self.drive0_img)) |
| 83 | self.vm_src.add_blockdev('driver=qcow2,file=file0,node-name=drive0') |
| 84 | self.vm_src.launch() |
| 85 | self.vm_src.qmp('x-blockdev-set-iothread', |
| 86 | node_name='drive0', iothread='iothread0', |
| 87 | force=True) |
| 88 | |
| 89 | self.vm_tgt = iotests.VM(path_suffix='tgt') |
| 90 | self.vm_tgt.add_object('iothread,id=iothread0') |
| 91 | self.vm_tgt.add_blockdev('driver=file,filename=%s,node-name=file0' |
| 92 | % (self.mirror_img)) |
| 93 | self.vm_tgt.add_blockdev('driver=qcow2,file=file0,node-name=drive0') |
| 94 | self.vm_tgt.launch() |
| 95 | self.vm_tgt.qmp('x-blockdev-set-iothread', |
| 96 | node_name='drive0', iothread='iothread0', |
| 97 | force=True) |
| 98 | |
| 99 | def tearDown(self): |
| 100 | self.vm_src.shutdown() |
| 101 | self.vm_tgt.shutdown() |
| 102 | for name in self.images: |
| 103 | os.remove(self.images[name]) |
| 104 | |
| 105 | def test_nbd_mirror(self): |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 106 | self.vm_tgt.cmd( |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 107 | 'nbd-server-start', |
| 108 | addr={ |
| 109 | 'type': 'unix', |
| 110 | 'data': { 'path': self.nbd_sock } |
| 111 | } |
| 112 | ) |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 113 | |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 114 | self.vm_tgt.cmd( |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 115 | 'nbd-server-add', |
| 116 | device='drive0', |
| 117 | writable=True |
| 118 | ) |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 119 | |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 120 | self.vm_src.cmd( |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 121 | 'drive-mirror', |
| 122 | device='drive0', |
| 123 | target='nbd+unix:///drive0?socket=' + self.nbd_sock, |
| 124 | sync='full', |
| 125 | mode='existing', |
| 126 | speed=64*1024*1024, |
| 127 | job_id='j1' |
| 128 | ) |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 129 | |
| 130 | self.vm_src.event_wait(name="BLOCK_JOB_READY") |
| 131 | |
| 132 | |
| 133 | # Test for RHBZ#1779036 |
| 134 | class TestExternalSnapshotAbort(iotests.QMPTestCase): |
| 135 | drive0_img = os.path.join(iotests.test_dir, 'drive0.img') |
| 136 | snapshot_img = os.path.join(iotests.test_dir, 'snapshot.img') |
| 137 | images = { 'drive0': drive0_img, 'snapshot': snapshot_img } |
| 138 | |
| 139 | def setUp(self): |
| 140 | for name in self.images: |
| 141 | qemu_img('create', '-f', iotests.imgfmt, |
| 142 | self.images[name], str(image_len)) |
| 143 | |
| 144 | self.vm = iotests.VM() |
| 145 | self.vm.add_object('iothread,id=iothread0') |
| 146 | self.vm.add_blockdev('driver=file,filename=%s,node-name=file0' |
| 147 | % (self.drive0_img)) |
| 148 | self.vm.add_blockdev('driver=qcow2,file=file0,node-name=drive0') |
| 149 | self.vm.launch() |
| 150 | self.vm.qmp('x-blockdev-set-iothread', |
| 151 | node_name='drive0', iothread='iothread0', |
| 152 | force=True) |
| 153 | |
| 154 | def tearDown(self): |
| 155 | self.vm.shutdown() |
| 156 | for name in self.images: |
| 157 | os.remove(self.images[name]) |
| 158 | |
| 159 | def test_external_snapshot_abort(self): |
| 160 | # Use a two actions transaction with a bogus values on the second |
| 161 | # one to trigger an abort of the transaction. |
| 162 | result = self.vm.qmp('transaction', actions=[ |
| 163 | { |
| 164 | 'type': 'blockdev-snapshot-sync', |
| 165 | 'data': { 'node-name': 'drive0', |
| 166 | 'snapshot-file': self.snapshot_img, |
| 167 | 'snapshot-node-name': 'snap1', |
| 168 | 'mode': 'absolute-paths', |
| 169 | 'format': 'qcow2' } |
| 170 | }, |
| 171 | { |
| 172 | 'type': 'blockdev-snapshot-sync', |
| 173 | 'data': { 'node-name': 'drive0', |
| 174 | 'snapshot-file': '/fakesnapshot', |
| 175 | 'snapshot-node-name': 'snap2', |
| 176 | 'mode': 'absolute-paths', |
| 177 | 'format': 'qcow2' } |
| 178 | }, |
| 179 | ]) |
| 180 | |
| 181 | # Crashes on failure, we expect this error. |
| 182 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 183 | |
| 184 | |
| 185 | # Test for RHBZ#1782111 |
| 186 | class TestBlockdevBackupAbort(iotests.QMPTestCase): |
| 187 | drive0_img = os.path.join(iotests.test_dir, 'drive0.img') |
| 188 | drive1_img = os.path.join(iotests.test_dir, 'drive1.img') |
| 189 | snap0_img = os.path.join(iotests.test_dir, 'snap0.img') |
| 190 | snap1_img = os.path.join(iotests.test_dir, 'snap1.img') |
| 191 | images = { 'drive0': drive0_img, |
| 192 | 'drive1': drive1_img, |
| 193 | 'snap0': snap0_img, |
| 194 | 'snap1': snap1_img } |
| 195 | |
| 196 | def setUp(self): |
| 197 | for name in self.images: |
| 198 | qemu_img('create', '-f', iotests.imgfmt, |
| 199 | self.images[name], str(image_len)) |
| 200 | |
| 201 | self.vm = iotests.VM() |
| 202 | self.vm.add_object('iothread,id=iothread0') |
| 203 | self.vm.add_device('virtio-scsi,iothread=iothread0') |
| 204 | |
| 205 | for name in self.images: |
| 206 | self.vm.add_blockdev('driver=file,filename=%s,node-name=file_%s' |
| 207 | % (self.images[name], name)) |
| 208 | self.vm.add_blockdev('driver=qcow2,file=file_%s,node-name=%s' |
| 209 | % (name, name)) |
| 210 | |
| 211 | self.vm.add_device('scsi-hd,drive=drive0') |
| 212 | self.vm.add_device('scsi-hd,drive=drive1') |
| 213 | self.vm.launch() |
| 214 | |
| 215 | def tearDown(self): |
| 216 | self.vm.shutdown() |
| 217 | for name in self.images: |
| 218 | os.remove(self.images[name]) |
| 219 | |
| 220 | def test_blockdev_backup_abort(self): |
| 221 | # Use a two actions transaction with a bogus values on the second |
| 222 | # one to trigger an abort of the transaction. |
| 223 | result = self.vm.qmp('transaction', actions=[ |
| 224 | { |
| 225 | 'type': 'blockdev-backup', |
| 226 | 'data': { 'device': 'drive0', |
| 227 | 'target': 'snap0', |
| 228 | 'sync': 'full', |
| 229 | 'job-id': 'j1' } |
| 230 | }, |
| 231 | { |
| 232 | 'type': 'blockdev-backup', |
| 233 | 'data': { 'device': 'drive1', |
| 234 | 'target': 'snap1', |
| 235 | 'sync': 'full' } |
| 236 | }, |
| 237 | ]) |
| 238 | |
| 239 | # Hangs on failure, we expect this error. |
| 240 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 241 | |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 242 | # Test for RHBZ#2033626 |
| 243 | class TestYieldingAndTimers(iotests.QMPTestCase): |
| 244 | sock = os.path.join(iotests.sock_dir, 'nbd.sock') |
| 245 | qsd = None |
| 246 | |
| 247 | def setUp(self): |
| 248 | self.create_nbd_export() |
| 249 | |
| 250 | # Simple VM with an NBD block device connected to the NBD export |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 251 | # provided by the QSD, and an (initially unused) iothread |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 252 | self.vm = iotests.VM() |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 253 | self.vm.add_object('iothread,id=iothr') |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 254 | self.vm.add_blockdev('nbd,node-name=nbd,server.type=unix,' + |
| 255 | f'server.path={self.sock},export=exp,' + |
| 256 | 'reconnect-delay=1,open-timeout=1') |
| 257 | |
| 258 | self.vm.launch() |
| 259 | |
| 260 | def tearDown(self): |
| 261 | self.stop_nbd_export() |
| 262 | self.vm.shutdown() |
| 263 | |
| 264 | def test_timers_with_blockdev_del(self): |
| 265 | # The NBD BDS will have had an active open timer, because setUp() gave |
| 266 | # a positive value for @open-timeout. It should be gone once the BDS |
| 267 | # has been opened. |
| 268 | # (But there used to be a bug where it remained active, which will |
| 269 | # become important below.) |
| 270 | |
| 271 | # Stop and restart the NBD server, and do some I/O on the client to |
| 272 | # trigger a reconnect and start the reconnect delay timer |
| 273 | self.stop_nbd_export() |
| 274 | self.create_nbd_export() |
| 275 | |
| 276 | result = self.vm.qmp('human-monitor-command', |
| 277 | command_line='qemu-io nbd "write 0 512"') |
| 278 | self.assert_qmp(result, 'return', '') |
| 279 | |
| 280 | # Reconnect is done, so the reconnect delay timer should be gone. |
| 281 | # (This is similar to how the open timer should be gone after open, |
| 282 | # and similarly there used to be a bug where it was not gone.) |
| 283 | |
| 284 | # Delete the BDS to see whether both timers are gone. If they are not, |
| 285 | # they will remain active, fire later, and then access freed data. |
| 286 | # (Or, with "block/nbd: Assert there are no timers when closed" |
| 287 | # applied, the assertions added in that patch will fail.) |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 288 | self.vm.cmd('blockdev-del', node_name='nbd') |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 289 | |
| 290 | # Give the timers some time to fire (both have a timeout of 1 s). |
| 291 | # (Sleeping in an iotest may ring some alarm bells, but note that if |
| 292 | # the timing is off here, the test will just always pass. If we kill |
| 293 | # the VM too early, then we just kill the timers before they can fire, |
| 294 | # thus not see the error, and so the test will pass.) |
| 295 | time.sleep(2) |
| 296 | |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 297 | def test_yield_in_iothread(self): |
| 298 | # Move the NBD node to the I/O thread; the NBD block driver should |
| 299 | # attach the connection's QIOChannel to that thread's AioContext, too |
Vladimir Sementsov-Ogievskiy | b6aed19 | 2023-10-06 18:41:25 +0300 | [diff] [blame] | 300 | self.vm.cmd('x-blockdev-set-iothread', |
| 301 | node_name='nbd', iothread='iothr') |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 302 | |
| 303 | # Do some I/O that will be throttled by the QSD, so that the network |
| 304 | # connection hopefully will yield here. When it is resumed, it must |
| 305 | # then be resumed in the I/O thread's AioContext. |
| 306 | result = self.vm.qmp('human-monitor-command', |
| 307 | command_line='qemu-io nbd "read 0 128K"') |
| 308 | self.assert_qmp(result, 'return', '') |
| 309 | |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 310 | def create_nbd_export(self): |
| 311 | assert self.qsd is None |
| 312 | |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 313 | # Export a throttled null-co BDS: Reads are throttled (max 64 kB/s), |
| 314 | # writes are not. |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 315 | self.qsd = QemuStorageDaemon( |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 316 | '--object', |
| 317 | 'throttle-group,id=thrgr,x-bps-read=65536,x-bps-read-max=65536', |
| 318 | |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 319 | '--blockdev', |
| 320 | 'null-co,node-name=null,read-zeroes=true', |
| 321 | |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 322 | '--blockdev', |
| 323 | 'throttle,node-name=thr,file=null,throttle-group=thrgr', |
| 324 | |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 325 | '--nbd-server', |
| 326 | f'addr.type=unix,addr.path={self.sock}', |
| 327 | |
| 328 | '--export', |
Hanna Reitz | 8cfbe92 | 2022-02-04 12:10:12 +0100 | [diff] [blame] | 329 | 'nbd,id=exp,node-name=thr,name=exp,writable=true' |
Hanna Reitz | eaf1e85 | 2022-02-04 12:10:10 +0100 | [diff] [blame] | 330 | ) |
| 331 | |
| 332 | def stop_nbd_export(self): |
| 333 | self.qsd.stop() |
| 334 | self.qsd = None |
| 335 | |
Sergio Lopez | 9b8c59e | 2020-01-08 15:31:38 +0100 | [diff] [blame] | 336 | if __name__ == '__main__': |
| 337 | iotests.main(supported_fmts=['qcow2'], |
Vladimir Sementsov-Ogievskiy | b30b807 | 2021-12-23 17:01:28 +0100 | [diff] [blame] | 338 | supported_protocols=['file'], |
| 339 | unsupported_imgopts=['compat']) |