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 22b74fc7f7SMax Reitzimport socket 23b74fc7f7SMax Reitzimport stat 24b74fc7f7SMax Reitzimport time 25b74fc7f7SMax Reitzimport iotests 26b74fc7f7SMax Reitzfrom iotests import cachemode, imgfmt, qemu_img, qemu_nbd 27b74fc7f7SMax Reitz 28b74fc7f7SMax ReitzNBD_PORT = 10811 29b74fc7f7SMax Reitz 30b74fc7f7SMax Reitztest_img = os.path.join(iotests.test_dir, 'test.img') 31b74fc7f7SMax Reitzunix_socket = os.path.join(iotests.test_dir, 'nbd.socket') 32b74fc7f7SMax Reitz 339445673eSMarkus Armbruster 349445673eSMarkus Armbrusterdef flatten_sock_addr(crumpled_address): 359445673eSMarkus Armbruster result = { 'type': crumpled_address['type'] } 369445673eSMarkus Armbruster result.update(crumpled_address['data']) 379445673eSMarkus Armbruster return result 389445673eSMarkus Armbruster 399445673eSMarkus Armbruster 40b74fc7f7SMax Reitzclass NBDBlockdevAddBase(iotests.QMPTestCase): 41b74fc7f7SMax Reitz def blockdev_add_options(self, address, export=None): 42b74fc7f7SMax Reitz options = { 'node-name': 'nbd-blockdev', 43b74fc7f7SMax Reitz 'driver': 'raw', 44b74fc7f7SMax Reitz 'file': { 45b74fc7f7SMax Reitz 'driver': 'nbd', 46*1104d83cSEric Blake 'read-only': True, 47b74fc7f7SMax Reitz 'server': address 48b74fc7f7SMax Reitz } } 49b74fc7f7SMax Reitz if export is not None: 50b74fc7f7SMax Reitz options['file']['export'] = export 51b74fc7f7SMax Reitz return options 52b74fc7f7SMax Reitz 53b74fc7f7SMax Reitz def client_test(self, filename, address, export=None): 54b74fc7f7SMax Reitz bao = self.blockdev_add_options(address, export) 55b74fc7f7SMax Reitz result = self.vm.qmp('blockdev-add', **bao) 56b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 57b74fc7f7SMax Reitz 58b74fc7f7SMax Reitz result = self.vm.qmp('query-named-block-nodes') 59b74fc7f7SMax Reitz for node in result['return']: 60b74fc7f7SMax Reitz if node['node-name'] == 'nbd-blockdev': 61b74fc7f7SMax Reitz if isinstance(filename, str): 62b74fc7f7SMax Reitz self.assert_qmp(node, 'image/filename', filename) 63b74fc7f7SMax Reitz else: 64b74fc7f7SMax Reitz self.assert_json_filename_equal(node['image']['filename'], 65b74fc7f7SMax Reitz filename) 66b74fc7f7SMax Reitz break 67b74fc7f7SMax Reitz 6879b7a77eSMarkus Armbruster result = self.vm.qmp('blockdev-del', node_name='nbd-blockdev') 69b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 70b74fc7f7SMax Reitz 71b74fc7f7SMax Reitz 72b74fc7f7SMax Reitzclass QemuNBD(NBDBlockdevAddBase): 73b74fc7f7SMax Reitz def setUp(self): 74b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 75b74fc7f7SMax Reitz self.vm = iotests.VM() 76b74fc7f7SMax Reitz self.vm.launch() 77b74fc7f7SMax Reitz 78b74fc7f7SMax Reitz def tearDown(self): 79b74fc7f7SMax Reitz self.vm.shutdown() 80b74fc7f7SMax Reitz os.remove(test_img) 81b74fc7f7SMax Reitz try: 82b74fc7f7SMax Reitz os.remove(unix_socket) 83b74fc7f7SMax Reitz except OSError: 84b74fc7f7SMax Reitz pass 85b74fc7f7SMax Reitz 86b74fc7f7SMax Reitz def _server_up(self, *args): 87b74fc7f7SMax Reitz self.assertEqual(qemu_nbd('-f', imgfmt, test_img, *args), 0) 88b74fc7f7SMax Reitz 89b74fc7f7SMax Reitz def test_inet(self): 90b74fc7f7SMax Reitz self._server_up('-p', str(NBD_PORT)) 91b74fc7f7SMax Reitz address = { 'type': 'inet', 92b74fc7f7SMax Reitz 'data': { 93b74fc7f7SMax Reitz 'host': 'localhost', 94b74fc7f7SMax Reitz 'port': str(NBD_PORT) 95b74fc7f7SMax Reitz } } 969445673eSMarkus Armbruster self.client_test('nbd://localhost:%i' % NBD_PORT, 979445673eSMarkus Armbruster flatten_sock_addr(address)) 98b74fc7f7SMax Reitz 99b74fc7f7SMax Reitz def test_unix(self): 100b74fc7f7SMax Reitz self._server_up('-k', unix_socket) 101b74fc7f7SMax Reitz address = { 'type': 'unix', 102b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 1039445673eSMarkus Armbruster self.client_test('nbd+unix://?socket=' + unix_socket, 1049445673eSMarkus Armbruster flatten_sock_addr(address)) 105b74fc7f7SMax Reitz 106b74fc7f7SMax Reitz 107b74fc7f7SMax Reitzclass BuiltinNBD(NBDBlockdevAddBase): 108b74fc7f7SMax Reitz def setUp(self): 109b74fc7f7SMax Reitz qemu_img('create', '-f', iotests.imgfmt, test_img, '64k') 110b74fc7f7SMax Reitz self.vm = iotests.VM() 111b74fc7f7SMax Reitz self.vm.launch() 112b74fc7f7SMax Reitz self.server = iotests.VM('.server') 113b74fc7f7SMax Reitz self.server.add_drive_raw('if=none,id=nbd-export,' + 114b74fc7f7SMax Reitz 'file=%s,' % test_img + 115b74fc7f7SMax Reitz 'format=%s,' % imgfmt + 116b74fc7f7SMax Reitz 'cache=%s' % cachemode) 117b74fc7f7SMax Reitz self.server.launch() 118b74fc7f7SMax Reitz 119b74fc7f7SMax Reitz def tearDown(self): 120b74fc7f7SMax Reitz self.vm.shutdown() 121b74fc7f7SMax Reitz self.server.shutdown() 122b74fc7f7SMax Reitz os.remove(test_img) 123b74fc7f7SMax Reitz try: 124b74fc7f7SMax Reitz os.remove(unix_socket) 125b74fc7f7SMax Reitz except OSError: 126b74fc7f7SMax Reitz pass 127b74fc7f7SMax Reitz 128b74fc7f7SMax Reitz def _server_up(self, address): 129b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-start', addr=address) 130b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 131b74fc7f7SMax Reitz 132b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-add', device='nbd-export') 133b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 134b74fc7f7SMax Reitz 135b74fc7f7SMax Reitz def _server_down(self): 136b74fc7f7SMax Reitz result = self.server.qmp('nbd-server-stop') 137b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 138b74fc7f7SMax Reitz 139b74fc7f7SMax Reitz def test_inet(self): 140b74fc7f7SMax Reitz address = { 'type': 'inet', 141b74fc7f7SMax Reitz 'data': { 142b74fc7f7SMax Reitz 'host': 'localhost', 143b74fc7f7SMax Reitz 'port': str(NBD_PORT) 144b74fc7f7SMax Reitz } } 145b74fc7f7SMax Reitz self._server_up(address) 146b74fc7f7SMax Reitz self.client_test('nbd://localhost:%i/nbd-export' % NBD_PORT, 1479445673eSMarkus Armbruster flatten_sock_addr(address), 'nbd-export') 148b74fc7f7SMax Reitz self._server_down() 149b74fc7f7SMax Reitz 150b74fc7f7SMax Reitz def test_inet6(self): 151cf1cd117SFam Zheng try: 152cf1cd117SFam Zheng socket.getaddrinfo("::0", "0", socket.AF_INET6, 153cf1cd117SFam Zheng socket.SOCK_STREAM, socket.IPPROTO_TCP, 154cf1cd117SFam Zheng socket.AI_ADDRCONFIG | socket.AI_CANONNAME) 155cf1cd117SFam Zheng except socket.gaierror: 156cf1cd117SFam Zheng # IPv6 not available, skip 157cf1cd117SFam Zheng return 158b74fc7f7SMax Reitz address = { 'type': 'inet', 159b74fc7f7SMax Reitz 'data': { 160b74fc7f7SMax Reitz 'host': '::1', 161b74fc7f7SMax Reitz 'port': str(NBD_PORT), 162b74fc7f7SMax Reitz 'ipv4': False, 163b74fc7f7SMax Reitz 'ipv6': True 164b74fc7f7SMax Reitz } } 165b74fc7f7SMax Reitz filename = { 'driver': 'raw', 166b74fc7f7SMax Reitz 'file': { 167b74fc7f7SMax Reitz 'driver': 'nbd', 168b74fc7f7SMax Reitz 'export': 'nbd-export', 1699445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 170b74fc7f7SMax Reitz } } 171b74fc7f7SMax Reitz self._server_up(address) 1729445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 173b74fc7f7SMax Reitz self._server_down() 174b74fc7f7SMax Reitz 175b74fc7f7SMax Reitz def test_unix(self): 176b74fc7f7SMax Reitz address = { 'type': 'unix', 177b74fc7f7SMax Reitz 'data': { 'path': unix_socket } } 178b74fc7f7SMax Reitz self._server_up(address) 179b74fc7f7SMax Reitz self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket, 1809445673eSMarkus Armbruster flatten_sock_addr(address), 'nbd-export') 181b74fc7f7SMax Reitz self._server_down() 182b74fc7f7SMax Reitz 183b74fc7f7SMax Reitz def test_fd(self): 184b74fc7f7SMax Reitz self._server_up({ 'type': 'unix', 185b74fc7f7SMax Reitz 'data': { 'path': unix_socket } }) 186b74fc7f7SMax Reitz 187b74fc7f7SMax Reitz sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 188b74fc7f7SMax Reitz sockfd.connect(unix_socket) 189b74fc7f7SMax Reitz 190b74fc7f7SMax Reitz result = self.vm.send_fd_scm(str(sockfd.fileno())) 191b74fc7f7SMax Reitz self.assertEqual(result, 0, 'Failed to send socket FD') 192b74fc7f7SMax Reitz 193b74fc7f7SMax Reitz result = self.vm.qmp('getfd', fdname='nbd-fifo') 194b74fc7f7SMax Reitz self.assert_qmp(result, 'return', {}) 195b74fc7f7SMax Reitz 196b74fc7f7SMax Reitz address = { 'type': 'fd', 197b74fc7f7SMax Reitz 'data': { 'str': 'nbd-fifo' } } 198b74fc7f7SMax Reitz filename = { 'driver': 'raw', 199b74fc7f7SMax Reitz 'file': { 200b74fc7f7SMax Reitz 'driver': 'nbd', 201b74fc7f7SMax Reitz 'export': 'nbd-export', 2029445673eSMarkus Armbruster 'server': flatten_sock_addr(address) 203b74fc7f7SMax Reitz } } 2049445673eSMarkus Armbruster self.client_test(filename, flatten_sock_addr(address), 'nbd-export') 205b74fc7f7SMax Reitz 206b74fc7f7SMax Reitz self._server_down() 207b74fc7f7SMax Reitz 208b74fc7f7SMax Reitz 209b74fc7f7SMax Reitzif __name__ == '__main__': 210b74fc7f7SMax Reitz # Need to support image creation 211b74fc7f7SMax Reitz iotests.main(supported_fmts=['vpc', 'parallels', 'qcow', 'vdi', 'qcow2', 212b74fc7f7SMax Reitz 'vmdk', 'raw', 'vhdx', 'qed']) 213