xref: /openbmc/qemu/tests/qemu-iotests/147 (revision b0c4cf21b0f26533e4aa6ca1930b2c9f52371bfa)
1903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3
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
278dff69b9SAarushi Mehtafrom iotests import cachemode, aiomode, 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')
35610dffaaSMax Reitzunix_socket = os.path.join(iotests.sock_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 +
137*b0c4cf21SMax Reitz                                  'cache=%s,' % cachemode +
1388dff69b9SAarushi Mehta                                  'aio=%s' % aiomode)
139b74fc7f7SMax Reitz        self.server.launch()
140b74fc7f7SMax Reitz
141b74fc7f7SMax Reitz    def tearDown(self):
142b74fc7f7SMax Reitz        self.vm.shutdown()
143b74fc7f7SMax Reitz        self.server.shutdown()
144b74fc7f7SMax Reitz        os.remove(test_img)
145b74fc7f7SMax Reitz        try:
146b74fc7f7SMax Reitz            os.remove(unix_socket)
147b74fc7f7SMax Reitz        except OSError:
148b74fc7f7SMax Reitz            pass
149b74fc7f7SMax Reitz
150908b3016SMax Reitz    # Returns False on EADDRINUSE; fails an assertion on other errors.
151908b3016SMax Reitz    # Returns True on success.
152908b3016SMax Reitz    def _try_server_up(self, address, export_name=None, export_name2=None):
153b74fc7f7SMax Reitz        result = self.server.qmp('nbd-server-start', addr=address)
154908b3016SMax Reitz        if 'error' in result and \
155908b3016SMax Reitz           'Address already in use' in result['error']['desc']:
156908b3016SMax Reitz            return False
157b74fc7f7SMax Reitz        self.assert_qmp(result, 'return', {})
158b74fc7f7SMax Reitz
159549084eaSVladimir Sementsov-Ogievskiy        if export_name is None:
160b74fc7f7SMax Reitz            result = self.server.qmp('nbd-server-add', device='nbd-export')
161549084eaSVladimir Sementsov-Ogievskiy        else:
162549084eaSVladimir Sementsov-Ogievskiy            result = self.server.qmp('nbd-server-add', device='nbd-export',
163549084eaSVladimir Sementsov-Ogievskiy                                     name=export_name)
164b74fc7f7SMax Reitz        self.assert_qmp(result, 'return', {})
165b74fc7f7SMax Reitz
166549084eaSVladimir Sementsov-Ogievskiy        if export_name2 is not None:
167549084eaSVladimir Sementsov-Ogievskiy            result = self.server.qmp('nbd-server-add', device='nbd-export',
168549084eaSVladimir Sementsov-Ogievskiy                                     name=export_name2)
169549084eaSVladimir Sementsov-Ogievskiy            self.assert_qmp(result, 'return', {})
170549084eaSVladimir Sementsov-Ogievskiy
171908b3016SMax Reitz        return True
172908b3016SMax Reitz
173908b3016SMax Reitz    def _server_up(self, address, export_name=None, export_name2=None):
174908b3016SMax Reitz        self.assertTrue(self._try_server_up(address, export_name, export_name2))
175549084eaSVladimir Sementsov-Ogievskiy
176b74fc7f7SMax Reitz    def _server_down(self):
177b74fc7f7SMax Reitz        result = self.server.qmp('nbd-server-stop')
178b74fc7f7SMax Reitz        self.assert_qmp(result, 'return', {})
179b74fc7f7SMax Reitz
180549084eaSVladimir Sementsov-Ogievskiy    def do_test_inet(self, export_name=None):
181908b3016SMax Reitz        while True:
182908b3016SMax Reitz            nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END)
183b74fc7f7SMax Reitz            address = { 'type': 'inet',
184b74fc7f7SMax Reitz                        'data': {
185b74fc7f7SMax Reitz                            'host': 'localhost',
186908b3016SMax Reitz                            'port': str(nbd_port)
187b74fc7f7SMax Reitz                        } }
188908b3016SMax Reitz            if self._try_server_up(address, export_name):
189908b3016SMax Reitz                break
190908b3016SMax Reitz
191549084eaSVladimir Sementsov-Ogievskiy        export_name = export_name or 'nbd-export'
192908b3016SMax Reitz        self.client_test('nbd://localhost:%i/%s' % (nbd_port, export_name),
193549084eaSVladimir Sementsov-Ogievskiy                         flatten_sock_addr(address), export_name)
194549084eaSVladimir Sementsov-Ogievskiy        self._server_down()
195549084eaSVladimir Sementsov-Ogievskiy
196549084eaSVladimir Sementsov-Ogievskiy    def test_inet_default_export_name(self):
197549084eaSVladimir Sementsov-Ogievskiy        self.do_test_inet()
198549084eaSVladimir Sementsov-Ogievskiy
199549084eaSVladimir Sementsov-Ogievskiy    def test_inet_same_export_name(self):
200549084eaSVladimir Sementsov-Ogievskiy        self.do_test_inet('nbd-export')
201549084eaSVladimir Sementsov-Ogievskiy
202549084eaSVladimir Sementsov-Ogievskiy    def test_inet_different_export_name(self):
203549084eaSVladimir Sementsov-Ogievskiy        self.do_test_inet('shadow')
204549084eaSVladimir Sementsov-Ogievskiy
205549084eaSVladimir Sementsov-Ogievskiy    def test_inet_two_exports(self):
206908b3016SMax Reitz        while True:
207908b3016SMax Reitz            nbd_port = random.randrange(NBD_PORT_START, NBD_PORT_END)
208549084eaSVladimir Sementsov-Ogievskiy            address = { 'type': 'inet',
209549084eaSVladimir Sementsov-Ogievskiy                        'data': {
210549084eaSVladimir Sementsov-Ogievskiy                            'host': 'localhost',
211908b3016SMax Reitz                            'port': str(nbd_port)
212549084eaSVladimir Sementsov-Ogievskiy                        } }
213908b3016SMax Reitz            if self._try_server_up(address, 'exp1', 'exp2'):
214908b3016SMax Reitz                break
215908b3016SMax Reitz
216908b3016SMax Reitz        self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp1'),
217549084eaSVladimir Sementsov-Ogievskiy                         flatten_sock_addr(address), 'exp1', 'node1', False)
218908b3016SMax Reitz        self.client_test('nbd://localhost:%i/%s' % (nbd_port, 'exp2'),
219549084eaSVladimir Sementsov-Ogievskiy                         flatten_sock_addr(address), 'exp2', 'node2', False)
220549084eaSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('blockdev-del', node_name='node1')
221549084eaSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', {})
222549084eaSVladimir Sementsov-Ogievskiy        result = self.vm.qmp('blockdev-del', node_name='node2')
223549084eaSVladimir Sementsov-Ogievskiy        self.assert_qmp(result, 'return', {})
224b74fc7f7SMax Reitz        self._server_down()
225b74fc7f7SMax Reitz
226b74fc7f7SMax Reitz    def test_inet6(self):
227cf1cd117SFam Zheng        try:
228cf1cd117SFam Zheng            socket.getaddrinfo("::0", "0", socket.AF_INET6,
229cf1cd117SFam Zheng                               socket.SOCK_STREAM, socket.IPPROTO_TCP,
230cf1cd117SFam Zheng                               socket.AI_ADDRCONFIG | socket.AI_CANONNAME)
231cf1cd117SFam Zheng        except socket.gaierror:
232cf1cd117SFam Zheng            # IPv6 not available, skip
233cf1cd117SFam Zheng            return
234908b3016SMax Reitz
235908b3016SMax Reitz        while True:
236908b3016SMax Reitz            nbd_port = random.randrange(NBD_IPV6_PORT_START, NBD_IPV6_PORT_END)
237b74fc7f7SMax Reitz            address = { 'type': 'inet',
238b74fc7f7SMax Reitz                        'data': {
239b74fc7f7SMax Reitz                            'host': '::1',
240908b3016SMax Reitz                            'port': str(nbd_port),
241b74fc7f7SMax Reitz                            'ipv4': False,
242b74fc7f7SMax Reitz                            'ipv6': True
243b74fc7f7SMax Reitz                        } }
244908b3016SMax Reitz            if self._try_server_up(address):
245908b3016SMax Reitz                break
246908b3016SMax Reitz
247b74fc7f7SMax Reitz        filename = { 'driver': 'raw',
248b74fc7f7SMax Reitz                     'file': {
249b74fc7f7SMax Reitz                         'driver': 'nbd',
250b74fc7f7SMax Reitz                         'export': 'nbd-export',
2519445673eSMarkus Armbruster                         'server': flatten_sock_addr(address)
252b74fc7f7SMax Reitz                     } }
2539445673eSMarkus Armbruster        self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
254b74fc7f7SMax Reitz        self._server_down()
255b74fc7f7SMax Reitz
256b74fc7f7SMax Reitz    def test_unix(self):
257b74fc7f7SMax Reitz        address = { 'type': 'unix',
258b74fc7f7SMax Reitz                    'data': { 'path': unix_socket } }
259b74fc7f7SMax Reitz        self._server_up(address)
260b74fc7f7SMax Reitz        self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket,
2619445673eSMarkus Armbruster                         flatten_sock_addr(address), 'nbd-export')
262b74fc7f7SMax Reitz        self._server_down()
263b74fc7f7SMax Reitz
264b74fc7f7SMax Reitz    def test_fd(self):
265b74fc7f7SMax Reitz        self._server_up({ 'type': 'unix',
266b74fc7f7SMax Reitz                          'data': { 'path': unix_socket } })
267b74fc7f7SMax Reitz
268b74fc7f7SMax Reitz        sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
269b74fc7f7SMax Reitz        sockfd.connect(unix_socket)
270b74fc7f7SMax Reitz
271bf43b29dSMax Reitz        result = self.vm.send_fd_scm(fd=sockfd.fileno())
272b74fc7f7SMax Reitz        self.assertEqual(result, 0, 'Failed to send socket FD')
273b74fc7f7SMax Reitz
274b74fc7f7SMax Reitz        result = self.vm.qmp('getfd', fdname='nbd-fifo')
275b74fc7f7SMax Reitz        self.assert_qmp(result, 'return', {})
276b74fc7f7SMax Reitz
277b74fc7f7SMax Reitz        address = { 'type': 'fd',
278b74fc7f7SMax Reitz                    'data': { 'str': 'nbd-fifo' } }
279b74fc7f7SMax Reitz        filename = { 'driver': 'raw',
280b74fc7f7SMax Reitz                     'file': {
281b74fc7f7SMax Reitz                         'driver': 'nbd',
282b74fc7f7SMax Reitz                         'export': 'nbd-export',
2839445673eSMarkus Armbruster                         'server': flatten_sock_addr(address)
284b74fc7f7SMax Reitz                     } }
2859445673eSMarkus Armbruster        self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
286b74fc7f7SMax Reitz
287b74fc7f7SMax Reitz        self._server_down()
288b74fc7f7SMax Reitz
289b74fc7f7SMax Reitz
290b74fc7f7SMax Reitzif __name__ == '__main__':
2917c932a1dSMax Reitz    iotests.main(supported_fmts=['raw'],
2927c932a1dSMax Reitz                 supported_protocols=['nbd'])
293