Coverage for manila/tests/network/linux/test_interface.py: 96%
174 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2014 Mirantis Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16from unittest import mock
18from manila.network.linux import interface
19from manila.network.linux import ip_lib
20from manila import test
21from manila.tests import conf_fixture
22from manila.tests import fake_network
23from manila import utils
26class BaseChild(interface.LinuxInterfaceDriver):
27 def plug(self, *args):
28 pass
30 def unplug(self, *args):
31 pass
34FakeSubnet = {
35 'cidr': '192.168.1.1/24',
36}
39FakeAllocation = {
40 'subnet': FakeSubnet,
41 'ip_address': '192.168.1.2',
42 'ip_version': 4,
43}
46FakePort = {
47 'id': 'abcdef01-1234-5678-90ab-ba0987654321',
48 'fixed_ips': [FakeAllocation],
49 'device_id': 'cccccccc-cccc-cccc-cccc-cccccccccccc',
50}
53class TestBase(test.TestCase):
54 def setUp(self):
55 super(TestBase, self).setUp()
56 self.conf = conf_fixture.CONF
57 self.conf.register_opts(interface.OPTS)
58 self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice')
59 self.ip_dev = self.ip_dev_p.start()
60 self.ip_p = mock.patch.object(ip_lib, 'IPWrapper')
61 self.ip = self.ip_p.start()
62 self.device_exists_p = mock.patch.object(ip_lib, 'device_exists')
63 self.device_exists = self.device_exists_p.start()
64 self.addCleanup(self.ip_dev_p.stop)
65 self.addCleanup(self.ip_p.stop)
66 self.addCleanup(self.device_exists_p.stop)
69class TestABCDriver(TestBase):
71 def test_verify_abs_class_has_abs_methods(self):
73 class ICanNotBeInstancetiated(interface.LinuxInterfaceDriver):
74 pass
76 try:
77 # pylint: disable=abstract-class-instantiated
78 ICanNotBeInstancetiated()
79 except TypeError:
80 pass
81 except Exception as e:
82 self.fail("Unexpected exception thrown: '%s'" % e)
83 else:
84 self.fail("ExpectedException 'TypeError' not thrown.")
86 def test_get_device_name(self):
87 bc = BaseChild()
88 device_name = bc.get_device_name(FakePort)
89 self.assertEqual('tapabcdef01-12', device_name)
91 def test_l3_init(self):
92 addresses = [dict(ip_version=4, scope='global',
93 dynamic=False, cidr='172.16.77.240/24')]
94 self.ip_dev().addr.list = mock.Mock(return_value=addresses)
96 bc = BaseChild()
97 self.mock_object(bc, '_remove_outdated_interfaces')
99 ns = '12345678-1234-5678-90ab-ba0987654321'
100 bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns,
101 clear_cidrs=['192.168.0.0/16'])
102 self.ip_dev.assert_has_calls(
103 [mock.call('tap0', namespace=ns),
104 mock.call().route.clear_outdated_routes('192.168.0.0/16'),
105 mock.call().addr.list(scope='global', filters=['permanent']),
106 mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
107 mock.call().addr.delete(4, '172.16.77.240/24'),
108 mock.call().route.pullup_route('tap0')])
109 bc._remove_outdated_interfaces.assert_called_with(self.ip_dev())
111 def test__remove_outdated_interfaces(self):
112 device = fake_network.FakeDevice(
113 'foobarquuz', [dict(ip_version=4, cidr='1.0.0.0/27')])
114 devices = [fake_network.FakeDevice('foobar')]
115 self.ip().get_devices = mock.Mock(return_value=devices)
117 bc = BaseChild()
118 self.mock_object(bc, 'unplug')
120 bc._remove_outdated_interfaces(device)
121 bc.unplug.assert_called_once_with('foobar')
123 def test__get_set_of_device_cidrs(self):
124 device = fake_network.FakeDevice('foo')
125 expected = set(('1.0.0.0/27', '2.0.0.0/27'))
127 bc = BaseChild()
128 result = bc._get_set_of_device_cidrs(device)
130 self.assertEqual(expected, result)
132 def test__get_set_of_device_cidrs_exception(self):
133 device = fake_network.FakeDevice('foo')
134 self.mock_object(device.addr, 'list', mock.Mock(
135 side_effect=Exception('foo does not exist')))
137 bc = BaseChild()
138 result = bc._get_set_of_device_cidrs(device)
140 self.assertEqual(set(), result)
143class TestNoopInterfaceDriver(TestBase):
145 def test_init_l3(self):
146 self.ip.assert_not_called()
147 self.ip_dev.assert_not_called()
149 def test_plug(self):
150 self.ip.assert_not_called()
151 self.ip_dev.assert_not_called()
153 def test_unplug(self):
154 self.ip.assert_not_called()
155 self.ip_dev.assert_not_called()
158class TestOVSInterfaceDriver(TestBase):
160 def test_get_device_name(self):
161 br = interface.OVSInterfaceDriver()
162 device_name = br.get_device_name(FakePort)
163 self.assertEqual('tapabcdef01-12', device_name)
165 def test_plug_no_ns(self):
166 self._test_plug()
168 def test_plug_with_ns(self):
169 self._test_plug(namespace='01234567-1234-1234-99')
171 def test_plug_alt_bridge(self):
172 self._test_plug(bridge='br-foo')
174 def _test_plug(self, additional_expectation=None, bridge=None,
175 namespace=None):
176 if additional_expectation is None: 176 ↛ 178line 176 didn't jump to line 178 because the condition on line 176 was always true
177 additional_expectation = []
178 if not bridge:
179 bridge = 'br-int'
181 def device_exists(dev, namespace=None):
182 return dev == bridge
184 vsctl_cmd = ['ovs-vsctl', '--', '--may-exist', 'add-port',
185 bridge, 'tap0', '--', 'set', 'Interface', 'tap0',
186 'type=internal', '--', 'set', 'Interface', 'tap0',
187 'external-ids:iface-id=port-1234', '--', 'set',
188 'Interface', 'tap0',
189 'external-ids:iface-status=active', '--', 'set',
190 'Interface', 'tap0',
191 'external-ids:attached-mac=aa:bb:cc:dd:ee:ff']
193 with mock.patch.object(utils, 'execute') as execute:
194 ovs = interface.OVSInterfaceDriver()
195 self.device_exists.side_effect = device_exists
196 ovs.plug('tap0',
197 'port-1234',
198 'aa:bb:cc:dd:ee:ff',
199 bridge=bridge,
200 namespace=namespace)
201 execute.assert_called_once_with(*vsctl_cmd, run_as_root=True)
203 expected = [mock.call(),
204 mock.call().device('tap0'),
205 mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')]
206 expected.extend(additional_expectation)
207 if namespace:
208 expected.extend(
209 [mock.call().ensure_namespace(namespace),
210 mock.call().ensure_namespace().add_device_to_namespace(
211 mock.ANY)])
212 expected.extend([mock.call().device().link.set_up()])
214 self.ip.assert_has_calls(expected)
216 def test_plug_reset_mac(self):
217 fake_mac_addr = 'aa:bb:cc:dd:ee:ff'
218 self.device_exists.return_value = True
220 self.ip().device().link.address = mock.Mock(return_value=fake_mac_addr)
221 ovs = interface.OVSInterfaceDriver()
222 ovs.plug('tap0',
223 'port-1234',
224 'ff:ee:dd:cc:bb:aa',
225 bridge='br-int')
226 expected = [mock.call(),
227 mock.call().device('tap0'),
228 mock.call().device().link.set_address('ff:ee:dd:cc:bb:aa'),
229 mock.call().device().link.set_up()]
230 self.ip.assert_has_calls(expected)
232 def test_unplug(self, bridge=None):
233 if not bridge: 233 ↛ 235line 233 didn't jump to line 235 because the condition on line 233 was always true
234 bridge = 'br-int'
235 with mock.patch('manila.network.linux.ovs_lib.OVSBridge') as ovs_br:
236 ovs = interface.OVSInterfaceDriver()
237 ovs.unplug('tap0')
238 ovs_br.assert_has_calls([mock.call(bridge),
239 mock.call().delete_port('tap0')])
242class TestBridgeInterfaceDriver(TestBase):
243 def test_get_device_name(self):
244 br = interface.BridgeInterfaceDriver()
245 device_name = br.get_device_name(FakePort)
246 self.assertEqual('ns-abcdef01-12', device_name)
248 def test_plug_no_ns(self):
249 self._test_plug()
251 def test_plug_with_ns(self):
252 self._test_plug(namespace='01234567-1234-1234-99')
254 def _test_plug(self, namespace=None, mtu=None):
255 def device_exists(device, root_helper=None, namespace=None):
256 return device.startswith('brq')
258 root_veth = mock.Mock()
259 ns_veth = mock.Mock()
261 self.ip().add_veth = mock.Mock(return_value=(root_veth, ns_veth))
263 self.device_exists.side_effect = device_exists
264 br = interface.BridgeInterfaceDriver()
265 mac_address = 'aa:bb:cc:dd:ee:ff'
266 br.plug('ns-0',
267 'port-1234',
268 mac_address,
269 namespace=namespace)
271 ip_calls = [mock.call(),
272 mock.call().add_veth('tap0', 'ns-0', namespace2=namespace)]
273 ns_veth.assert_has_calls([mock.call.link.set_address(mac_address)])
275 self.ip.assert_has_calls(ip_calls)
277 root_veth.assert_has_calls([mock.call.link.set_up()])
278 ns_veth.assert_has_calls([mock.call.link.set_up()])
280 def test_plug_dev_exists(self):
281 self.device_exists.return_value = True
282 with mock.patch('manila.network.linux.interface.LOG.warning') as log:
283 br = interface.BridgeInterfaceDriver()
284 br.plug('port-1234',
285 'tap0',
286 'aa:bb:cc:dd:ee:ff')
287 self.ip_dev.assert_has_calls([])
288 self.assertEqual(1, log.call_count)
290 def test_unplug_no_device(self):
291 self.device_exists.return_value = False
292 self.ip_dev().link.delete.side_effect = RuntimeError
293 with mock.patch('manila.network.linux.interface.LOG') as log:
294 br = interface.BridgeInterfaceDriver()
295 br.unplug('tap0')
296 [mock.call(), mock.call('tap0'), mock.call().link.delete()]
297 self.assertEqual(1, log.error.call_count)
299 def test_unplug(self):
300 self.device_exists.return_value = True
301 with mock.patch('manila.network.linux.interface.LOG.debug') as log:
302 br = interface.BridgeInterfaceDriver()
303 br.unplug('tap0')
304 self.assertTrue(log.called)
305 self.ip_dev.assert_has_calls([mock.call('tap0', None),
306 mock.call().link.delete()])