1b74fc7f7SMax Reitz#!/usr/bin/env python 2b74fc7f7SMax Reitz# 3b74fc7f7SMax Reitz# Test case for NBD's blockdev-add interface 4b74fc7f7SMax Reitz# 5b74fc7f7SMax Reitz# Copyright (C) 2016 Red Hat, Inc. 6b74fc7f7SMax Reitz# 7b74fc7f7SMax Reitz# This program is free software; you can redistribute it and/or modify 8b74fc7f7SMax Reitz# it under the terms of the GNU General Public License as published by 9b74fc7f7SMax Reitz# the Free Software Foundation; either version 2 of the License, or 10b74fc7f7SMax Reitz# (at your option) any later version. 11b74fc7f7SMax Reitz# 12b74fc7f7SMax Reitz# This program is distributed in the hope that it will be useful, 13b74fc7f7SMax Reitz# but WITHOUT ANY WARRANTY; without even the implied warranty of 14b74fc7f7SMax Reitz# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15b74fc7f7SMax Reitz# GNU General Public License for more details. 16b74fc7f7SMax Reitz# 17b74fc7f7SMax Reitz# You should have received a copy of the GNU General Public License 18b74fc7f7SMax Reitz# along with this program. If not, see <http://www.gnu.org/licenses/>. 19b74fc7f7SMax Reitz# 20b74fc7f7SMax Reitz 21b74fc7f7SMax Reitzimport os 22908b3016SMax Reitzimport random 23b74fc7f7SMax Reitzimport socket 24b74fc7f7SMax Reitzimport stat 25b74fc7f7SMax Reitzimport time 26b74fc7f7SMax Reitzimport iotests 276177b584SMax Reitzfrom iotests import cachemode, imgfmt, qemu_img, qemu_nbd, qemu_nbd_early_pipe 28b74fc7f7SMax Reitz 29908b3016SMax ReitzNBD_PORT_START = 32768 30908b3016SMax ReitzNBD_PORT_END = NBD_PORT_START + 1024 31908b3016SMax ReitzNBD_IPV6_PORT_START = NBD_PORT_END 32908b3016SMax ReitzNBD_IPV6_PORT_END = NBD_IPV6_PORT_START + 1024 33b74fc7f7SMax Reitz 34b74fc7f7SMax Reitztest_img = os.path.join(iotests.test_dir, 'test.img') 35b74fc7f7SMax Reitzunix_socket = os.path.join(iotests.test_dir, 'nbd.socket') 36b74fc7f7SMax Reitz 379445673eSMarkus Armbruster 389445673eSMarkus Armbrusterdef flatten_sock_addr(crumpled_address): 399445673eSMarkus Armbruster result = { 'type': crumpled_address['type'] } 409445673eSMarkus Armbruster result.update(crumpled_address['data']) 419445673eSMarkus Armbruster return result 429445673eSMarkus Armbruster 439445673eSMarkus Armbruster 44b74fc7f7SMax Reitzclass NBDBlockdevAddBase(iotests.QMPTestCase): 45549084eaSVladimir Sementsov-Ogievskiy def blockdev_add_options(self, address, export, node_name): 46549084eaSVladimir Sementsov-Ogievskiy options = { 'node-name': node_name, 47b74fc7f7SMax Reitz 'driver': 'raw', 48b74fc7f7SMax Reitz 'file': { 49b74fc7f7SMax Reitz 'driver': 'nbd', 501104d83cSEric Blake 'read-only': True, 51b74fc7f7SMax Reitz 'server': address 52b74fc7f7SMax Reitz } } 53b74fc7f7SMax Reitz if export is not None: 54b74fc7f7SMax Reitz options['file']['export'] = export 55b74fc7f7SMax Reitz return options 56b74fc7f7SMax Reitz 57549084eaSVladimir Sementsov-Ogievskiy def client_test(self, filename, address, export=None, 58549084eaSVladimir Sementsov-Ogievskiy node_name='nbd-blockdev', delete=True): 59549084eaSVladimir Sementsov-Ogievskiy bao = self.blockdev_add_options(address, export, node_name) 60b74fc7f7SMax Reitz result = self.vm.qmp('blockdev-add', **bao) 61b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 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: 77549084eaSVladimir Sementsov-Ogievskiy result = self.vm.qmp('blockdev-del', node_name=node_name) 78b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 79b74fc7f7SMax Reitz 80b74fc7f7SMax Reitz 81b74fc7f7SMax Reitzclass QemuNBD(NBDBlockdevAddBase): 82b74fc7f7SMax Reitz def setUp(self): 83b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 84b74fc7f7SMax Reitz self.vm = iotests.VM() 85b74fc7f7SMax Reitz self.vm.launch() 86b74fc7f7SMax Reitz 87b74fc7f7SMax Reitz def tearDown(self): 88b74fc7f7SMax Reitz self.vm.shutdown() 89b74fc7f7SMax Reitz os.remove(test_img) 90b74fc7f7SMax Reitz try: 91b74fc7f7SMax Reitz os.remove(unix_socket) 92b74fc7f7SMax Reitz except OSError: 93b74fc7f7SMax Reitz pass 94b74fc7f7SMax Reitz 95908b3016SMax Reitz def _try_server_up(self, *args): 966177b584SMax Reitz status, msg = qemu_nbd_early_pipe('-f', imgfmt, test_img, *args) 97908b3016SMax Reitz if status == 0: 98908b3016SMax Reitz return True 99908b3016SMax Reitz if 'Address already in use' in msg: 100908b3016SMax Reitz return False 101908b3016SMax Reitz self.fail(msg) 102908b3016SMax Reitz 103b74fc7f7SMax Reitz def _server_up(self, *args): 104908b3016SMax Reitz self.assertTrue(self._try_server_up(*args)) 105b74fc7f7SMax Reitz 106b74fc7f7SMax Reitz def test_inet(self): 107908b3016SMax Reitz while True: 108908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 109908b3016SMax Reitz if self._try_server_up('-b', 'localhost', '-p', str(nbd_port)): 110908b3016SMax Reitz break 111908b3016SMax Reitz 112b74fc7f7SMax Reitz address = { 'type': 'inet', 113b74fc7f7SMax Reitz 'data': { 114b74fc7f7SMax Reitz 'host': 'localhost', 115908b3016SMax Reitz 'port': str(nbd_port) 116b74fc7f7SMax Reitz } } 117908b3016SMax Reitz self.client_test('nbd://localhost:%i' % nbd_port, 1189445673eSMarkus Armbruster flatten_sock_addr(address)) 119b74fc7f7SMax Reitz 120b74fc7f7SMax Reitz def test_unix(self): 121b74fc7f7SMax Reitz self._server_up('-k', unix_socket) 122b74fc7f7SMax Reitz address = { 'type': 'unix', 123b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 1249445673eSMarkus Armbruster self.client_test('nbd+unix://?socket=' + unix_socket, 1259445673eSMarkus Armbruster flatten_sock_addr(address)) 126b74fc7f7SMax Reitz 127b74fc7f7SMax Reitz 128b74fc7f7SMax Reitzclass BuiltinNBD(NBDBlockdevAddBase): 129b74fc7f7SMax Reitz def setUp(self): 130b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 131b74fc7f7SMax Reitz self.vm = iotests.VM() 132b74fc7f7SMax Reitz self.vm.launch() 133b74fc7f7SMax Reitz self.server = iotests.VM('.server') 134b74fc7f7SMax Reitz self.server.add_drive_raw('if=none,id=nbd-export,' + 135b74fc7f7SMax Reitz 'file=%s,' % test_img + 136b74fc7f7SMax Reitz 'format=%s,' % imgfmt + 137b74fc7f7SMax Reitz 'cache=%s' % cachemode) 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: 159b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-add', device='nbd-export') 160549084eaSVladimir Sementsov-Ogievskiy else: 161549084eaSVladimir Sementsov-Ogievskiy result = self.server.qmp('nbd-server-add', device='nbd-export', 162549084eaSVladimir Sementsov-Ogievskiy name=export_name) 163b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 164b74fc7f7SMax Reitz 165549084eaSVladimir Sementsov-Ogievskiy if export_name2 is not None: 166549084eaSVladimir Sementsov-Ogievskiy result = self.server.qmp('nbd-server-add', device='nbd-export', 167549084eaSVladimir Sementsov-Ogievskiy name=export_name2) 168549084eaSVladimir Sementsov-Ogievskiy self.assert_qmp(result, 'return', {}) 169549084eaSVladimir Sementsov-Ogievskiy 170908b3016SMax Reitz return True 171908b3016SMax Reitz 172908b3016SMax Reitz def _server_up(self, address, export_name=None, export_name2=None): 173908b3016SMax Reitz self.assertTrue(self._try_server_up(address, export_name, export_name2)) 174549084eaSVladimir Sementsov-Ogievskiy 175b74fc7f7SMax Reitz def _server_down(self): 176b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-stop') 177b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 178b74fc7f7SMax Reitz 179549084eaSVladimir Sementsov-Ogievskiy def do_test_inet(self, export_name=None): 180908b3016SMax Reitz while True: 181908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 182b74fc7f7SMax Reitz address = { 'type': 'inet', 183b74fc7f7SMax Reitz 'data': { 184b74fc7f7SMax Reitz 'host': 'localhost', 185908b3016SMax Reitz 'port': str(nbd_port) 186b74fc7f7SMax Reitz } } 187908b3016SMax Reitz if self._try_server_up(address, export_name): 188908b3016SMax Reitz break 189908b3016SMax Reitz 190549084eaSVladimir Sementsov-Ogievskiy export_name = export_name or 'nbd-export' 191908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, export_name), 192549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), export_name) 193549084eaSVladimir Sementsov-Ogievskiy self._server_down() 194549084eaSVladimir Sementsov-Ogievskiy 195549084eaSVladimir Sementsov-Ogievskiy def test_inet_default_export_name(self): 196549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet() 197549084eaSVladimir Sementsov-Ogievskiy 198549084eaSVladimir Sementsov-Ogievskiy def test_inet_same_export_name(self): 199549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet('nbd-export') 200549084eaSVladimir Sementsov-Ogievskiy 201549084eaSVladimir Sementsov-Ogievskiy def test_inet_different_export_name(self): 202549084eaSVladimir Sementsov-Ogievskiy self.do_test_inet('shadow') 203549084eaSVladimir Sementsov-Ogievskiy 204549084eaSVladimir Sementsov-Ogievskiy def test_inet_two_exports(self): 205908b3016SMax Reitz while True: 206908b3016SMax Reitz nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END) 207549084eaSVladimir Sementsov-Ogievskiy address = { 'type': 'inet', 208549084eaSVladimir Sementsov-Ogievskiy 'data': { 209549084eaSVladimir Sementsov-Ogievskiy 'host': 'localhost', 210908b3016SMax Reitz 'port': str(nbd_port) 211549084eaSVladimir Sementsov-Ogievskiy } } 212908b3016SMax Reitz if self._try_server_up(address, 'exp1', 'exp2'): 213908b3016SMax Reitz break 214908b3016SMax Reitz 215908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp1'), 216549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), 'exp1', 'node1', False) 217908b3016SMax Reitz self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp2'), 218549084eaSVladimir Sementsov-Ogievskiy flatten_sock_addr(address), 'exp2', 'node2', False) 219549084eaSVladimir Sementsov-Ogievskiy result = self.vm.qmp('blockdev-del', node_name='node1') 220549084eaSVladimir Sementsov-Ogievskiy self.assert_qmp(result, 'return', {}) 221549084eaSVladimir Sementsov-Ogievskiy result = self.vm.qmp('blockdev-del', node_name='node2') 222549084eaSVladimir Sementsov-Ogievskiy self.assert_qmp(result, 'return', {}) 223b74fc7f7SMax Reitz self._server_down() 224b74fc7f7SMax Reitz 225b74fc7f7SMax Reitz def test_inet6(self): 226cf1cd117SFam Zheng try: 227cf1cd117SFam Zheng socket.getaddrinfo("::0", "0", socket.AF_INET6, 228cf1cd117SFam Zheng socket.SOCK_STREAM, socket.IPPROTO_TCP, 229cf1cd117SFam Zheng socket.AI_ADDRCONFIG | socket.AI_CANONNAME) 230cf1cd117SFam Zheng except socket.gaierror: 231cf1cd117SFam Zheng # IPv6 not available, skip 232cf1cd117SFam Zheng return 233908b3016SMax Reitz 234908b3016SMax Reitz while True: 235908b3016SMax Reitz nbd_port = random.randrange(NBD_IPV6_PORT_START, NBD_IPV6_PORT_END) 236b74fc7f7SMax Reitz address = { 'type': 'inet', 237b74fc7f7SMax Reitz 'data': { 238b74fc7f7SMax Reitz 'host': '::1', 239908b3016SMax Reitz 'port': str(nbd_port), 240b74fc7f7SMax Reitz 'ipv4': False, 241b74fc7f7SMax Reitz 'ipv6': True 242b74fc7f7SMax Reitz } } 243908b3016SMax Reitz if self._try_server_up(address): 244908b3016SMax Reitz break 245908b3016SMax Reitz 246b74fc7f7SMax Reitz filename = { 'driver': 'raw', 247b74fc7f7SMax Reitz 'file': { 248b74fc7f7SMax Reitz 'driver': 'nbd', 249b74fc7f7SMax Reitz 'export': 'nbd-export', 2509445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 251b74fc7f7SMax Reitz } } 2529445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 253b74fc7f7SMax Reitz self._server_down() 254b74fc7f7SMax Reitz 255b74fc7f7SMax Reitz def test_unix(self): 256b74fc7f7SMax Reitz address = { 'type': 'unix', 257b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 258b74fc7f7SMax Reitz self._server_up(address) 259b74fc7f7SMax Reitz self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket, 2609445673eSMarkus Armbruster flatten_sock_addr(address), 'nbd-export') 261b74fc7f7SMax Reitz self._server_down() 262b74fc7f7SMax Reitz 263b74fc7f7SMax Reitz def test_fd(self): 264b74fc7f7SMax Reitz self._server_up({ 'type': 'unix', 265b74fc7f7SMax Reitz 'data': { 'path': unix_socket } }) 266b74fc7f7SMax Reitz 267b74fc7f7SMax Reitz sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 268b74fc7f7SMax Reitz sockfd.connect(unix_socket) 269b74fc7f7SMax Reitz 270bf43b29dSMax Reitz result = self.vm.send_fd_scm(fd=sockfd.fileno()) 271b74fc7f7SMax Reitz self.assertEqual(result, 0, 'Failed to send socket FD') 272b74fc7f7SMax Reitz 273b74fc7f7SMax Reitz result = self.vm.qmp('getfd', fdname='nbd-fifo') 274b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 275b74fc7f7SMax Reitz 276b74fc7f7SMax Reitz address = { 'type': 'fd', 277b74fc7f7SMax Reitz 'data': { 'str': 'nbd-fifo' } } 278b74fc7f7SMax Reitz filename = { 'driver': 'raw', 279b74fc7f7SMax Reitz 'file': { 280b74fc7f7SMax Reitz 'driver': 'nbd', 281b74fc7f7SMax Reitz 'export': 'nbd-export', 2829445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 283b74fc7f7SMax Reitz } } 2849445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 285b74fc7f7SMax Reitz 286b74fc7f7SMax Reitz self._server_down() 287b74fc7f7SMax Reitz 288b74fc7f7SMax Reitz 289b74fc7f7SMax Reitzif __name__ == '__main__': 290*7c932a1dSMax Reitz iotests.main(supported_fmts=['raw'], 291*7c932a1dSMax Reitz supported_protocols=['nbd']) 292