1#!/usr/bin/env python3 2# group: rw 3# 4# Test graph changes while I/O is happening 5# 6# Copyright (C) 2022 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 22import os 23from threading import Thread 24import iotests 25from iotests import imgfmt, qemu_img, qemu_img_create, qemu_io, \ 26 QMPTestCase, QemuStorageDaemon 27 28 29top = os.path.join(iotests.test_dir, 'top.img') 30nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock') 31 32 33def do_qemu_img_bench(count: int = 2000000) -> None: 34 """ 35 Do some I/O requests on `nbd_sock`. 36 """ 37 qemu_img('bench', '-f', 'raw', '-c', str(count), 38 f'nbd+unix:///node0?socket={nbd_sock}') 39 40 41class TestGraphChangesWhileIO(QMPTestCase): 42 def setUp(self) -> None: 43 # Create an overlay that can be added at runtime on top of the 44 # null-co block node that will receive I/O 45 qemu_img_create('-f', imgfmt, '-F', 'raw', '-b', 'null-co://', top) 46 47 # QSD instance with a null-co block node in an I/O thread, 48 # exported over NBD (on `nbd_sock`, export name "node0") 49 self.qsd = QemuStorageDaemon( 50 '--object', 'iothread,id=iothread0', 51 '--blockdev', 'null-co,node-name=node0,read-zeroes=true', 52 '--nbd-server', f'addr.type=unix,addr.path={nbd_sock}', 53 '--export', 'nbd,id=exp0,node-name=node0,iothread=iothread0,' + 54 'fixed-iothread=true,writable=true', 55 qmp=True 56 ) 57 58 def tearDown(self) -> None: 59 self.qsd.stop() 60 61 def test_blockdev_add_while_io(self) -> None: 62 # Run qemu-img bench in the background 63 bench_thr = Thread(target=do_qemu_img_bench) 64 bench_thr.start() 65 66 # While qemu-img bench is running, repeatedly add and remove an 67 # overlay to/from node0 68 while bench_thr.is_alive(): 69 self.qsd.cmd('blockdev-add', { 70 'driver': imgfmt, 71 'node-name': 'overlay', 72 'backing': 'node0', 73 'file': { 74 'driver': 'file', 75 'filename': top 76 } 77 }) 78 79 self.qsd.cmd('blockdev-del', { 80 'node-name': 'overlay' 81 }) 82 83 bench_thr.join() 84 85 def test_commit_while_io(self) -> None: 86 # Run qemu-img bench in the background 87 bench_thr = Thread(target=do_qemu_img_bench, args=(200000, )) 88 bench_thr.start() 89 90 qemu_io('-c', 'write 0 64k', top) 91 qemu_io('-c', 'write 128k 64k', top) 92 93 self.qsd.cmd('blockdev-add', { 94 'driver': imgfmt, 95 'node-name': 'overlay', 96 'backing': None, 97 'file': { 98 'driver': 'file', 99 'filename': top 100 } 101 }) 102 103 self.qsd.cmd('blockdev-snapshot', { 104 'node': 'node0', 105 'overlay': 'overlay', 106 }) 107 108 # While qemu-img bench is running, repeatedly commit overlay to node0 109 while bench_thr.is_alive(): 110 self.qsd.cmd('block-commit', { 111 'job-id': 'job0', 112 'device': 'overlay', 113 }) 114 115 self.qsd.cmd('block-job-cancel', { 116 'device': 'job0', 117 }) 118 119 cancelled = False 120 while not cancelled: 121 for event in self.qsd.get_qmp().get_events(wait=10.0): 122 if event['event'] != 'JOB_STATUS_CHANGE': 123 continue 124 if event['data']['status'] == 'null': 125 cancelled = True 126 127 bench_thr.join() 128 129if __name__ == '__main__': 130 # Format must support raw backing files 131 iotests.main(supported_fmts=['qcow', 'qcow2', 'qed'], 132 supported_protocols=['file']) 133