1903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3 29dd003a9SVladimir Sementsov-Ogievskiy# group: img 3b74fc7f7SMax Reitz# 4b74fc7f7SMax Reitz# Test case for NBD's blockdev-add interface 5b74fc7f7SMax Reitz# 6b74fc7f7SMax Reitz# Copyright (C) 2016 Red Hat, Inc. 7b74fc7f7SMax Reitz# 8b74fc7f7SMax Reitz# This program is free software; you can redistribute it and/or modify 9b74fc7f7SMax Reitz# it under the terms of the GNU General Public License as published by 10b74fc7f7SMax Reitz# the Free Software Foundation; either version 2 of the License, or 11b74fc7f7SMax Reitz# (at your option) any later version. 12b74fc7f7SMax Reitz# 13b74fc7f7SMax Reitz# This program is distributed in the hope that it will be useful, 14b74fc7f7SMax Reitz# but WITHOUT ANY WARRANTY; without even the implied warranty of 15b74fc7f7SMax Reitz# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16b74fc7f7SMax Reitz# GNU General Public License for more details. 17b74fc7f7SMax Reitz# 18b74fc7f7SMax Reitz# You should have received a copy of the GNU General Public License 19b74fc7f7SMax Reitz# along with this program. If not, see <http://www.gnu.org/licenses/>. 20b74fc7f7SMax Reitz# 21b74fc7f7SMax Reitz 22b74fc7f7SMax Reitzimport os 23908b3016SMax Reitzimport random 24b74fc7f7SMax Reitzimport socket 25b74fc7f7SMax Reitzimport stat 26b74fc7f7SMax Reitzimport time 27b74fc7f7SMax Reitzimport iotests 288dff69b9SAarushi Mehtafrom iotests import cachemode, aiomode, imgfmt, qemu_img, qemu_nbd, qemu_nbd_early_pipe 29b74fc7f7SMax Reitz 30908b3016SMax ReitzNBD_PORT_START = 32768 31908b3016SMax ReitzNBD_PORT_END = NBD_PORT_START + 1024 32908b3016SMax ReitzNBD_IPV6_PORT_START = NBD_PORT_END 33908b3016SMax ReitzNBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024 34b74fc7f7SMax Reitz 35b74fc7f7SMax Reitztest_img = os.path.join(iotests.test_dir, 'test.img') 36610dffaaSMax Reitzunix_socket = os.path.join(iotests.sock_dir, 'nbd.socket') 37b74fc7f7SMax Reitz 389445673eSMarkus Armbruster 399445673eSMarkus Armbrusterdef flatten_sock_addr(crumpled_address): 409445673eSMarkus Armbruster result = { 'type': crumpled_address['type'] } 419445673eSMarkus Armbruster result.update(crumpled_address['data']) 429445673eSMarkus Armbruster return result 439445673eSMarkus Armbruster 449445673eSMarkus Armbruster 45b74fc7f7SMax Reitzclass NBDBlockdevAddBase(iotests.QMPTestCase): 46549084eaSVladimir Sementsov-Ogievskiy def blockdev_add_options(self, address, export, node_name): 47549084eaSVladimir Sementsov-Ogievskiy options = { 'node-name': node_name, 48b74fc7f7SMax Reitz 'driver': 'raw', 49b74fc7f7SMax Reitz 'file': { 50b74fc7f7SMax Reitz 'driver': 'nbd', 511104d83cSEric Blake 'read-only': True, 52b74fc7f7SMax Reitz 'server': address 53b74fc7f7SMax Reitz } } 54b74fc7f7SMax Reitz if export is not None: 55b74fc7f7SMax Reitz options['file']['export'] = export 56b74fc7f7SMax Reitz return options 57b74fc7f7SMax Reitz 58549084eaSVladimir Sementsov-Ogievskiy def client_test(self, filename, address, export=None, 59549084eaSVladimir Sementsov-Ogievskiy node_name='nbd-blockdev', delete=True): 60549084eaSVladimir Sementsov-Ogievskiy bao = self.blockdev_add_options(address, export, node_name) 61*b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-add', bao) 62b74fc7f7SMax Reitz 63549084eaSVladimir Sementsov-Ogievskiy found = False 64b74fc7f7SMax Reitz result = self.vm.qmp('query-named-block-nodes') 65b74fc7f7SMax Reitz for node in result['return']: 66549084eaSVladimir Sementsov-Ogievskiy if node['node-name'] == node_name: 67549084eaSVladimir Sementsov-Ogievskiy found = True 68b74fc7f7SMax Reitz if isinstance(filename, str): 69b74fc7f7SMax Reitz self.assert_qmp(node, 'image/filename', filename) 70b74fc7f7SMax Reitz else: 71b74fc7f7SMax Reitz self.assert_json_filename_equal(node['image']['filename'], 72b74fc7f7SMax Reitz filename) 73b74fc7f7SMax Reitz break 74549084eaSVladimir Sementsov-Ogievskiy self.assertTrue(found) 75b74fc7f7SMax Reitz 76549084eaSVladimir Sementsov-Ogievskiy if delete: 77*b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-del', node_name=node_name) 78b74fc7f7SMax Reitz 79b74fc7f7SMax Reitz 80b74fc7f7SMax Reitzclass QemuNBD(NBDBlockdevAddBase): 81b74fc7f7SMax Reitz def setUp(self): 82b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 83b74fc7f7SMax Reitz self.vm = iotests.VM() 84b74fc7f7SMax Reitz self.vm.launch() 85b74fc7f7SMax Reitz 86b74fc7f7SMax Reitz def tearDown(self): 87b74fc7f7SMax Reitz self.vm.shutdown() 88b74fc7f7SMax Reitz os.remove(test_img) 89b74fc7f7SMax Reitz try: 90b74fc7f7SMax Reitz os.remove(unix_socket) 91b74fc7f7SMax Reitz except OSError: 92b74fc7f7SMax Reitz pass 93b74fc7f7SMax Reitz 94908b3016SMax Reitz def _try_server_up(self, *args): 956177b584SMax Reitz status, msg = qemu_nbd_early_pipe('-f', imgfmt, test_img, *args) 96908b3016SMax Reitz if status == 0: 97908b3016SMax Reitz return True 98908b3016SMax Reitz if 'Address already in use' in msg: 99908b3016SMax Reitz return False 100908b3016SMax Reitz self.fail(msg) 101908b3016SMax Reitz 102b74fc7f7SMax Reitz def _server_up(self, *args): 103908b3016SMax Reitz self.assertTrue(self._try_server_up(*args)) 104b74fc7f7SMax Reitz 105b74fc7f7SMax Reitz def test_inet(self): 106908b3016SMax Reitz while True: 107908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 108908b3016SMax Reitz if self._try_server_up('-b', 'localhost', '-p', str(nbd_port)): 109908b3016SMax Reitz break 110908b3016SMax Reitz 111b74fc7f7SMax Reitz address = { 'type': 'inet', 112b74fc7f7SMax Reitz 'data': { 113b74fc7f7SMax Reitz 'host': 'localhost', 114908b3016SMax Reitz 'port': str(nbd_port) 115b74fc7f7SMax Reitz } } 116908b3016SMax Reitz self.client_test('nbd://localhost:%i' % nbd_port, 1179445673eSMarkus Armbruster flatten_sock_addr(address)) 118b74fc7f7SMax Reitz 119b74fc7f7SMax Reitz def test_unix(self): 120b74fc7f7SMax Reitz self._server_up('-k', unix_socket) 121b74fc7f7SMax Reitz address = { 'type': 'unix', 122b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 1239445673eSMarkus Armbruster self.client_test('nbd+unix://?socket=' + unix_socket, 1249445673eSMarkus Armbruster flatten_sock_addr(address)) 125b74fc7f7SMax Reitz 126b74fc7f7SMax Reitz 127b74fc7f7SMax Reitzclass BuiltinNBD(NBDBlockdevAddBase): 128b74fc7f7SMax Reitz def setUp(self): 129b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 130b74fc7f7SMax Reitz self.vm = iotests.VM() 131b74fc7f7SMax Reitz self.vm.launch() 132b74fc7f7SMax Reitz self.server = iotests.VM('.server') 133b74fc7f7SMax Reitz self.server.add_drive_raw('if=none,id=nbd-export,' + 134b74fc7f7SMax Reitz 'file=%s,' % test_img + 135b74fc7f7SMax Reitz 'format=%s,' % imgfmt + 136b0c4cf21SMax Reitz 'cache=%s,' % cachemode + 1378dff69b9SAarushi Mehta 'aio=%s' % aiomode) 138b74fc7f7SMax Reitz self.server.launch() 139b74fc7f7SMax Reitz 140b74fc7f7SMax Reitz def tearDown(self): 141b74fc7f7SMax Reitz self.vm.shutdown() 142b74fc7f7SMax Reitz self.server.shutdown() 143b74fc7f7SMax Reitz os.remove(test_img) 144b74fc7f7SMax Reitz try: 145b74fc7f7SMax Reitz os.remove(unix_socket) 146b74fc7f7SMax Reitz except OSError: 147b74fc7f7SMax Reitz pass 148b74fc7f7SMax Reitz 149908b3016SMax Reitz # Returns False on EADDRINUSE; fails an assertion on other errors. 150908b3016SMax Reitz # Returns True on success. 151908b3016SMax Reitz def _try_server_up(self, address, export_name=None, export_name2=None): 152b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-start', addr=address) 153908b3016SMax Reitz if 'error' in result and \ 154908b3016SMax Reitz 'Address already in use' in result['error']['desc']: 155908b3016SMax Reitz return False 156b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 157b74fc7f7SMax Reitz 158549084eaSVladimir Sementsov-Ogievskiy if export_name is None: 159*b6aed193SVladimir Sementsov-Ogievskiy self.server.cmd('nbd-server-add', device='nbd-export') 160549084eaSVladimir Sementsov-Ogievskiy else: 161*b6aed193SVladimir Sementsov-Ogievskiy self.server.cmd('nbd-server-add', device='nbd-export', 162549084eaSVladimir Sementsov-Ogievskiy name=export_name) 163b74fc7f7SMax Reitz 164549084eaSVladimir Sementsov-Ogievskiy if export_name2 is not None: 165*b6aed193SVladimir Sementsov-Ogievskiy self.server.cmd('nbd-server-add', device='nbd-export', 166549084eaSVladimir Sementsov-Ogievskiy name=export_name2) 167549084eaSVladimir Sementsov-Ogievskiy 168908b3016SMax Reitz return True 169908b3016SMax Reitz 170908b3016SMax Reitz def _server_up(self, address, export_name=None, export_name2=None): 171908b3016SMax Reitz self.assertTrue(self._try_server_up(address, export_name, export_name2)) 172549084eaSVladimir Sementsov-Ogievskiy 173b74fc7f7SMax Reitz def _server_down(self): 174*b6aed193SVladimir Sementsov-Ogievskiy self.server.cmd('nbd-server-stop') 175b74fc7f7SMax Reitz 176549084eaSVladimir Sementsov-Ogievskiy def do_test_inet(self, export_name=None): 177908b3016SMax Reitz while True: 178908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 179b74fc7f7SMax Reitz address = { 'type': 'inet', 180b74fc7f7SMax Reitz 'data': { 181b74fc7f7SMax Reitz 'host': 'localhost', 182908b3016SMax Reitz 'port': str(nbd_port) 183b74fc7f7SMax Reitz } } 184908b3016SMax Reitz if self._try_server_up(address, export_name): 185908b3016SMax Reitz break 186908b3016SMax Reitz 187549084eaSVladimir Sementsov-Ogievskiy export_name = export_name or 'nbd-export' 188908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, export_name), 189549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), export_name) 190549084eaSVladimir Sementsov-Ogievskiy self._server_down() 191549084eaSVladimir Sementsov-Ogievskiy 192549084eaSVladimir Sementsov-Ogievskiy def test_inet_default_export_name(self): 193549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet() 194549084eaSVladimir Sementsov-Ogievskiy 195549084eaSVladimir Sementsov-Ogievskiy def test_inet_same_export_name(self): 196549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet('nbd-export') 197549084eaSVladimir Sementsov-Ogievskiy 198549084eaSVladimir Sementsov-Ogievskiy def test_inet_different_export_name(self): 199549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet('shadow') 200549084eaSVladimir Sementsov-Ogievskiy 201549084eaSVladimir Sementsov-Ogievskiy def test_inet_two_exports(self): 202908b3016SMax Reitz while True: 203908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 204549084eaSVladimir Sementsov-Ogievskiy address = { 'type': 'inet', 205549084eaSVladimir Sementsov-Ogievskiy 'data': { 206549084eaSVladimir Sementsov-Ogievskiy 'host': 'localhost', 207908b3016SMax Reitz 'port': str(nbd_port) 208549084eaSVladimir Sementsov-Ogievskiy } } 209908b3016SMax Reitz if self._try_server_up(address, 'exp1', 'exp2'): 210908b3016SMax Reitz break 211908b3016SMax Reitz 212908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp1'), 213549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), 'exp1', 'node1', False) 214908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp2'), 215549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), 'exp2', 'node2', False) 216*b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-del', node_name='node1') 217*b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-del', node_name='node2') 218b74fc7f7SMax Reitz self._server_down() 219b74fc7f7SMax Reitz 220b74fc7f7SMax Reitz def test_inet6(self): 221cf1cd117SFam Zheng try: 222cf1cd117SFam Zheng socket.getaddrinfo("::0", "0", socket.AF_INET6, 223cf1cd117SFam Zheng socket.SOCK_STREAM, socket.IPPROTO_TCP, 224cf1cd117SFam Zheng socket.AI_ADDRCONFIG | socket.AI_CANONNAME) 225cf1cd117SFam Zheng except socket.gaierror: 226cf1cd117SFam Zheng # IPv6 not available, skip 227cf1cd117SFam Zheng return 228908b3016SMax Reitz 229908b3016SMax Reitz while True: 230908b3016SMax Reitz nbd_port = random.randrange(NBD_IPV6_PORT_START, NBD_IPV6_PORT_END) 231b74fc7f7SMax Reitz address = { 'type': 'inet', 232b74fc7f7SMax Reitz 'data': { 233b74fc7f7SMax Reitz 'host': '::1', 234908b3016SMax Reitz 'port': str(nbd_port), 235b74fc7f7SMax Reitz 'ipv4': False, 236b74fc7f7SMax Reitz 'ipv6': True 237b74fc7f7SMax Reitz } } 238908b3016SMax Reitz if self._try_server_up(address): 239908b3016SMax Reitz break 240908b3016SMax Reitz 241b74fc7f7SMax Reitz filename = { 'driver': 'raw', 242b74fc7f7SMax Reitz 'file': { 243b74fc7f7SMax Reitz 'driver': 'nbd', 244b74fc7f7SMax Reitz 'export': 'nbd-export', 2459445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 246b74fc7f7SMax Reitz } } 2479445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 248b74fc7f7SMax Reitz self._server_down() 249b74fc7f7SMax Reitz 250b74fc7f7SMax Reitz def test_unix(self): 251b74fc7f7SMax Reitz address = { 'type': 'unix', 252b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 253b74fc7f7SMax Reitz self._server_up(address) 254b74fc7f7SMax Reitz self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket, 2559445673eSMarkus Armbruster flatten_sock_addr(address), 'nbd-export') 256b74fc7f7SMax Reitz self._server_down() 257b74fc7f7SMax Reitz 258b74fc7f7SMax Reitz def test_fd(self): 259b74fc7f7SMax Reitz self._server_up({ 'type': 'unix', 260b74fc7f7SMax Reitz 'data': { 'path': unix_socket } }) 261b74fc7f7SMax Reitz 262b74fc7f7SMax Reitz sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 263b74fc7f7SMax Reitz sockfd.connect(unix_socket) 264b74fc7f7SMax Reitz 265bf43b29dSMax Reitz result = self.vm.send_fd_scm(fd=sockfd.fileno()) 266b74fc7f7SMax Reitz self.assertEqual(result, 0, 'Failed to send socket FD') 267b74fc7f7SMax Reitz 268*b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('getfd', fdname='nbd-fifo') 269b74fc7f7SMax Reitz 270b74fc7f7SMax Reitz address = { 'type': 'fd', 271b74fc7f7SMax Reitz 'data': { 'str': 'nbd-fifo' } } 272b74fc7f7SMax Reitz filename = { 'driver': 'raw', 273b74fc7f7SMax Reitz 'file': { 274b74fc7f7SMax Reitz 'driver': 'nbd', 275b74fc7f7SMax Reitz 'export': 'nbd-export', 2769445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 277b74fc7f7SMax Reitz } } 2789445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 279b74fc7f7SMax Reitz 280b74fc7f7SMax Reitz self._server_down() 281b74fc7f7SMax Reitz 282b74fc7f7SMax Reitz 283b74fc7f7SMax Reitzif __name__ == '__main__': 2847c932a1dSMax Reitz iotests.main(supported_fmts=['raw'], 2857c932a1dSMax Reitz supported_protocols=['nbd']) 286