xref: /openbmc/qemu/tests/qemu-iotests/147 (revision 7c932a1d69a6d6ac5c0b615c11d191da3bbe9aa8)
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