1#!/usr/bin/env python3 2# group: rw auto quick 3# 4# Test cases for NBD multi-conn advertisement 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 21import os 22from contextlib import contextmanager 23import iotests 24from iotests import qemu_img_create, qemu_io 25 26 27disk = os.path.join(iotests.test_dir, 'disk') 28size = '4M' 29nbd_sock = os.path.join(iotests.sock_dir, 'nbd_sock') 30nbd_uri = 'nbd+unix:///{}?socket=' + nbd_sock 31 32 33@contextmanager 34def open_nbd(export_name): 35 h = nbd.NBD() 36 try: 37 h.connect_uri(nbd_uri.format(export_name)) 38 yield h 39 finally: 40 h.shutdown() 41 42class TestNbdMulticonn(iotests.QMPTestCase): 43 def setUp(self): 44 qemu_img_create('-f', iotests.imgfmt, disk, size) 45 qemu_io('-c', 'w -P 1 0 2M', '-c', 'w -P 2 2M 2M', disk) 46 47 self.vm = iotests.VM() 48 self.vm.launch() 49 result = self.vm.qmp('blockdev-add', { 50 'driver': 'qcow2', 51 'node-name': 'n', 52 'file': {'driver': 'file', 'filename': disk} 53 }) 54 self.assert_qmp(result, 'return', {}) 55 56 def tearDown(self): 57 self.vm.shutdown() 58 os.remove(disk) 59 try: 60 os.remove(nbd_sock) 61 except OSError: 62 pass 63 64 @contextmanager 65 def run_server(self, max_connections=None): 66 args = { 67 'addr': { 68 'type': 'unix', 69 'data': {'path': nbd_sock} 70 } 71 } 72 if max_connections is not None: 73 args['max-connections'] = max_connections 74 75 result = self.vm.qmp('nbd-server-start', args) 76 self.assert_qmp(result, 'return', {}) 77 yield 78 79 result = self.vm.qmp('nbd-server-stop') 80 self.assert_qmp(result, 'return', {}) 81 82 def add_export(self, name, writable=None): 83 args = { 84 'type': 'nbd', 85 'id': name, 86 'node-name': 'n', 87 'name': name, 88 } 89 if writable is not None: 90 args['writable'] = writable 91 92 result = self.vm.qmp('block-export-add', args) 93 self.assert_qmp(result, 'return', {}) 94 95 def test_default_settings(self): 96 with self.run_server(): 97 self.add_export('r') 98 self.add_export('w', writable=True) 99 with open_nbd('r') as h: 100 self.assertTrue(h.can_multi_conn()) 101 with open_nbd('w') as h: 102 self.assertTrue(h.can_multi_conn()) 103 104 def test_limited_connections(self): 105 with self.run_server(max_connections=1): 106 self.add_export('r') 107 self.add_export('w', writable=True) 108 with open_nbd('r') as h: 109 self.assertFalse(h.can_multi_conn()) 110 with open_nbd('w') as h: 111 self.assertFalse(h.can_multi_conn()) 112 113 def test_parallel_writes(self): 114 with self.run_server(): 115 self.add_export('w', writable=True) 116 117 clients = [nbd.NBD() for _ in range(3)] 118 for c in clients: 119 c.connect_uri(nbd_uri.format('w')) 120 self.assertTrue(c.can_multi_conn()) 121 122 initial_data = clients[0].pread(1024 * 1024, 0) 123 self.assertEqual(initial_data, b'\x01' * 1024 * 1024) 124 125 updated_data = b'\x03' * 1024 * 1024 126 clients[1].pwrite(updated_data, 0) 127 clients[2].flush() 128 current_data = clients[0].pread(1024 * 1024, 0) 129 130 self.assertEqual(updated_data, current_data) 131 132 for i in range(3): 133 clients[i].shutdown() 134 135 136if __name__ == '__main__': 137 try: 138 # Easier to use libnbd than to try and set up parallel 139 # 'qemu-nbd --list' or 'qemu-io' processes, but not all systems 140 # have libnbd installed. 141 import nbd # type: ignore 142 143 iotests.main(supported_fmts=['qcow2']) 144 except ImportError: 145 iotests.notrun('libnbd not installed') 146