a055ef3dc1
blueprint non-polling-dhcp-impl This requires a change to devstack/stack.sh. See https://review.openstack.org/#/c/11278/ for the required changes. Change-Id: I1ea22c8e1b80e630bcb83f27a31aaeef482aff6c
389 lines
14 KiB
Python
389 lines
14 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 OpenStack LLC
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import unittest
|
|
|
|
import mock
|
|
|
|
from quantum.agent.common import config
|
|
from quantum.agent.linux import interface
|
|
from quantum.agent.linux import ip_lib
|
|
from quantum.agent.linux import utils
|
|
from quantum.openstack.common import cfg
|
|
from quantum.agent.dhcp_agent import DeviceManager
|
|
|
|
|
|
class BaseChild(interface.LinuxInterfaceDriver):
|
|
def plug(*args):
|
|
pass
|
|
|
|
def unplug(*args):
|
|
pass
|
|
|
|
|
|
class FakeNetwork:
|
|
id = '12345678-1234-5678-90ab-ba0987654321'
|
|
|
|
|
|
class FakeSubnet:
|
|
cidr = '192.168.1.1/24'
|
|
|
|
|
|
class FakeAllocation:
|
|
subnet = FakeSubnet()
|
|
ip_address = '192.168.1.2'
|
|
ip_version = 4
|
|
|
|
|
|
class FakePort:
|
|
id = 'abcdef01-1234-5678-90ab-ba0987654321'
|
|
fixed_ips = [FakeAllocation]
|
|
device_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
|
|
network = FakeNetwork()
|
|
network_id = network.id
|
|
|
|
|
|
class TestBase(unittest.TestCase):
|
|
def setUp(self):
|
|
root_helper_opt = [
|
|
cfg.StrOpt('root_helper', default='sudo'),
|
|
]
|
|
self.conf = config.setup_conf()
|
|
self.conf.register_opts(interface.OPTS)
|
|
self.conf.register_opts(root_helper_opt)
|
|
self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice')
|
|
self.ip_dev = self.ip_dev_p.start()
|
|
self.ip_p = mock.patch.object(ip_lib, 'IPWrapper')
|
|
self.ip = self.ip_p.start()
|
|
self.device_exists_p = mock.patch.object(ip_lib, 'device_exists')
|
|
self.device_exists = self.device_exists_p.start()
|
|
|
|
def tearDown(self):
|
|
# sometimes a test may turn this off
|
|
try:
|
|
self.device_exists_p.stop()
|
|
except RuntimeError, e:
|
|
pass
|
|
self.ip_dev_p.stop()
|
|
self.ip_p.stop()
|
|
|
|
|
|
class TestABCDriver(TestBase):
|
|
def test_get_device_name(self):
|
|
bc = BaseChild(self.conf)
|
|
device_name = bc.get_device_name(FakePort())
|
|
self.assertEqual('tapabcdef01-12', device_name)
|
|
|
|
def test_l3_init(self):
|
|
addresses = [dict(ip_version=4, scope='global',
|
|
dynamic=False, cidr='172.16.77.240/24')]
|
|
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
|
|
|
bc = BaseChild(self.conf)
|
|
ns = '12345678-1234-5678-90ab-ba0987654321'
|
|
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns)
|
|
self.ip_dev.assert_has_calls(
|
|
[mock.call('tap0', 'sudo', namespace=ns),
|
|
mock.call().addr.list(scope='global', filters=['permanent']),
|
|
mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
|
|
mock.call().addr.delete(4, '172.16.77.240/24')])
|
|
|
|
|
|
class TestOVSInterfaceDriver(TestBase):
|
|
|
|
def test_plug_no_ns(self):
|
|
self._test_plug()
|
|
|
|
def test_plug_with_ns(self):
|
|
self._test_plug(namespace='01234567-1234-1234-99')
|
|
|
|
def test_plug_alt_bridge(self):
|
|
self._test_plug(bridge='br-foo')
|
|
|
|
def _test_plug(self, additional_expectation=[], bridge=None,
|
|
namespace=None):
|
|
|
|
if not bridge:
|
|
bridge = 'br-int'
|
|
|
|
def device_exists(dev, root_helper=None, namespace=None):
|
|
return dev == bridge
|
|
|
|
vsctl_cmd = ['ovs-vsctl', '--', '--may-exist', 'add-port',
|
|
bridge, 'tap0', '--', 'set', 'Interface', 'tap0',
|
|
'type=internal', '--', 'set', 'Interface', 'tap0',
|
|
'external-ids:iface-id=port-1234', '--', 'set',
|
|
'Interface', 'tap0',
|
|
'external-ids:iface-status=active', '--', 'set',
|
|
'Interface', 'tap0',
|
|
'external-ids:attached-mac=aa:bb:cc:dd:ee:ff']
|
|
|
|
with mock.patch.object(utils, 'execute') as execute:
|
|
ovs = interface.OVSInterfaceDriver(self.conf)
|
|
self.device_exists.side_effect = device_exists
|
|
ovs.plug('01234567-1234-1234-99',
|
|
'port-1234',
|
|
'tap0',
|
|
'aa:bb:cc:dd:ee:ff',
|
|
bridge=bridge,
|
|
namespace=namespace)
|
|
execute.assert_called_once_with(vsctl_cmd, 'sudo')
|
|
|
|
expected = [mock.call('sudo'),
|
|
mock.call().device('tap0'),
|
|
mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')]
|
|
expected.extend(additional_expectation)
|
|
if namespace:
|
|
expected.extend(
|
|
[mock.call().ensure_namespace(namespace),
|
|
mock.call().ensure_namespace().add_device_to_namespace(
|
|
mock.ANY)])
|
|
expected.extend([mock.call().device().link.set_up()])
|
|
|
|
self.ip.assert_has_calls(expected)
|
|
|
|
def test_plug_mtu(self):
|
|
self.conf.set_override('network_device_mtu', 9000)
|
|
self._test_plug([mock.call().device().link.set_mtu(9000)])
|
|
|
|
def test_unplug(self, bridge=None):
|
|
if not bridge:
|
|
bridge = 'br-int'
|
|
with mock.patch('quantum.agent.linux.ovs_lib.OVSBridge') as ovs_br:
|
|
ovs = interface.OVSInterfaceDriver(self.conf)
|
|
ovs.unplug('tap0')
|
|
ovs_br.assert_has_calls([mock.call(bridge, 'sudo'),
|
|
mock.call().delete_port('tap0')])
|
|
|
|
|
|
class TestBridgeInterfaceDriver(TestBase):
|
|
def test_get_device_name(self):
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
device_name = br.get_device_name(FakePort())
|
|
self.assertEqual('dhcabcdef01-12', device_name)
|
|
|
|
def test_plug_no_ns(self):
|
|
self._test_plug()
|
|
|
|
def test_plug_with_ns(self):
|
|
self._test_plug(namespace='01234567-1234-1234-99')
|
|
|
|
def _test_plug(self, namespace=None):
|
|
def device_exists(device, root_helper=None, namespace=None):
|
|
return device.startswith('brq')
|
|
|
|
root_veth = mock.Mock()
|
|
ns_veth = mock.Mock()
|
|
|
|
self.ip().add_veth = mock.Mock(return_value=(root_veth, ns_veth))
|
|
|
|
expected = [mock.call(c, 'sudo') for c in [
|
|
['ip', 'tuntap', 'add', 'tap0', 'mode', 'tap'],
|
|
['ip', 'link', 'set', 'tap0', 'address', 'aa:bb:cc:dd:ee:ff'],
|
|
['ip', 'link', 'set', 'tap0', 'up']]
|
|
]
|
|
|
|
self.device_exists.side_effect = device_exists
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
br.plug('01234567-1234-1234-99',
|
|
'port-1234',
|
|
'dhc0',
|
|
'aa:bb:cc:dd:ee:ff',
|
|
namespace=namespace)
|
|
|
|
ip_calls = [mock.call('sudo'), mock.call().add_veth('tap0', 'dhc0')]
|
|
if namespace:
|
|
ip_calls.extend([
|
|
mock.call().ensure_namespace('01234567-1234-1234-99'),
|
|
mock.call().ensure_namespace().add_device_to_namespace(
|
|
ns_veth)])
|
|
|
|
self.ip.assert_has_calls(ip_calls)
|
|
|
|
root_veth.assert_has_calls([mock.call.link.set_up()])
|
|
ns_veth.assert_has_calls([mock.call.link.set_up()])
|
|
|
|
def test_plug_dev_exists(self):
|
|
self.device_exists.return_value = True
|
|
with mock.patch('quantum.agent.linux.interface.LOG.warn') as log:
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
br.plug('01234567-1234-1234-99',
|
|
'port-1234',
|
|
'tap0',
|
|
'aa:bb:cc:dd:ee:ff')
|
|
self.ip_dev.assert_has_calls([])
|
|
self.assertEquals(log.call_count, 1)
|
|
|
|
def test_unplug(self):
|
|
self.device_exists.return_value = True
|
|
with mock.patch('quantum.agent.linux.interface.LOG.debug') as log:
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
br.unplug('tap0')
|
|
log.assert_called_once()
|
|
self.execute.assert_has_calls(
|
|
[mock.call(['ip', 'link', 'delete', 'tap0'], 'sudo')])
|
|
|
|
def test_unplug_no_device(self):
|
|
self.device_exists.return_value = False
|
|
self.ip_dev().link.delete.side_effect = RuntimeError
|
|
with mock.patch('quantum.agent.linux.interface.LOG') as log:
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
br.unplug('tap0')
|
|
[mock.call(), mock.call('tap0', 'sudo'), mock.call().link.delete()]
|
|
self.assertEqual(log.error.call_count, 1)
|
|
|
|
def test_unplug(self):
|
|
self.device_exists.return_value = True
|
|
with mock.patch('quantum.agent.linux.interface.LOG.debug') as log:
|
|
br = interface.BridgeInterfaceDriver(self.conf)
|
|
br.unplug('tap0')
|
|
log.assert_called_once()
|
|
|
|
self.ip_dev.assert_has_calls([mock.call('tap0', 'sudo', None),
|
|
mock.call().link.delete()])
|
|
|
|
|
|
class TestRyuInterfaceDriver(TestBase):
|
|
def setUp(self):
|
|
super(TestRyuInterfaceDriver, self).setUp()
|
|
self.ryu_mod = mock.Mock()
|
|
self.ryu_app_mod = self.ryu_mod.app
|
|
self.ryu_app_client = self.ryu_app_mod.client
|
|
self.ryu_mod_p = mock.patch.dict('sys.modules',
|
|
{'ryu': self.ryu_mod,
|
|
'ryu.app': self.ryu_app_mod,
|
|
'ryu.app.client':
|
|
self.ryu_app_client})
|
|
self.ryu_mod_p.start()
|
|
|
|
def tearDown(self):
|
|
self.ryu_mod_p.stop()
|
|
super(TestRyuInterfaceDriver, self).tearDown()
|
|
|
|
def test_plug_no_ns(self):
|
|
self._test_plug()
|
|
|
|
def test_plug_with_ns(self):
|
|
self._test_plug(namespace='01234567-1234-1234-99')
|
|
|
|
def test_plug_alt_bridge(self):
|
|
self._test_plug(bridge='br-foo')
|
|
|
|
def _test_plug(self, namespace=None, bridge=None):
|
|
if not bridge:
|
|
bridge = 'br-int'
|
|
|
|
def _device_exists(dev, root_helper=None, namespace=None):
|
|
return dev == bridge
|
|
|
|
vsctl_cmd_plug = ['ovs-vsctl', '--', '--may-exist', 'add-port',
|
|
bridge, 'tap0', '--', 'set', 'Interface', 'tap0',
|
|
'type=internal', '--', 'set', 'Interface', 'tap0',
|
|
'external-ids:iface-id=port-1234', '--', 'set',
|
|
'Interface', 'tap0',
|
|
'external-ids:iface-status=active', '--', 'set',
|
|
'Interface', 'tap0',
|
|
'external-ids:attached-mac=aa:bb:cc:dd:ee:ff']
|
|
vsctl_cmd_ofport = ['ovs-vsctl', '--timeout=2',
|
|
'get', 'Interface', 'tap0', 'ofport']
|
|
vsctl_cmd_dp = ['ovs-vsctl', '--timeout=2',
|
|
'get', 'Bridge', bridge, 'datapath_id']
|
|
|
|
with mock.patch.object(utils, 'execute') as execute:
|
|
self.device_exists.side_effect = _device_exists
|
|
ryu = interface.RyuInterfaceDriver(self.conf)
|
|
|
|
ryu.plug('01234567-1234-1234-99',
|
|
'port-1234',
|
|
'tap0',
|
|
'aa:bb:cc:dd:ee:ff',
|
|
bridge=bridge,
|
|
namespace=namespace)
|
|
|
|
execute.assert_has_calls([mock.call(vsctl_cmd_dp,
|
|
root_helper='sudo')])
|
|
execute.assert_has_calls([mock.call(vsctl_cmd_plug, 'sudo')])
|
|
execute.assert_has_calls([mock.call(vsctl_cmd_ofport,
|
|
root_helper='sudo')])
|
|
|
|
self.ryu_app_client.OFPClient.assert_called_once_with('127.0.0.1:8080')
|
|
|
|
expected = [
|
|
mock.call('sudo'),
|
|
mock.call().device('tap0'),
|
|
mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')]
|
|
if namespace:
|
|
expected.extend([
|
|
mock.call().ensure_namespace(namespace),
|
|
mock.call().ensure_namespace().add_device_to_namespace(
|
|
mock.ANY)])
|
|
expected.extend([mock.call().device().link.set_up()])
|
|
|
|
self.ip.assert_has_calls(expected)
|
|
|
|
|
|
class TestMetaInterfaceDriver(TestBase):
|
|
def setUp(self):
|
|
super(TestMetaInterfaceDriver, self).setUp()
|
|
self.conf.register_opts(DeviceManager.OPTS)
|
|
self.client_cls_p = mock.patch('quantumclient.v2_0.client.Client')
|
|
client_cls = self.client_cls_p.start()
|
|
self.client_inst = mock.Mock()
|
|
client_cls.return_value = self.client_inst
|
|
|
|
fake_network = {'network': {'flavor:id': 'fake1'}}
|
|
fake_port = {'ports':
|
|
[{'mac_address':
|
|
'aa:bb:cc:dd:ee:ffa', 'network_id': 'test'}]}
|
|
|
|
self.client_inst.list_ports.return_value = fake_port
|
|
self.client_inst.show_network.return_value = fake_network
|
|
|
|
self.conf.set_override('auth_url', 'http://localhost:35357/v2.0')
|
|
self.conf.set_override('auth_region', 'RegionOne')
|
|
self.conf.set_override('admin_user', 'quantum')
|
|
self.conf.set_override('admin_password', 'password')
|
|
self.conf.set_override('admin_tenant_name', 'service')
|
|
self.conf.set_override(
|
|
'meta_flavor_driver_mappings',
|
|
'fake1:quantum.agent.linux.interface.OVSInterfaceDriver,'
|
|
'fake2:quantum.agent.linux.interface.BridgeInterfaceDriver')
|
|
|
|
def tearDown(self):
|
|
self.client_cls_p.stop()
|
|
super(TestMetaInterfaceDriver, self).tearDown()
|
|
|
|
def test_get_driver_by_network_id(self):
|
|
meta_interface = interface.MetaInterfaceDriver(self.conf)
|
|
driver = meta_interface._get_driver_by_network_id('test')
|
|
self.assertTrue(isinstance(
|
|
driver,
|
|
interface.OVSInterfaceDriver))
|
|
|
|
def test_get_driver_by_device_name(self):
|
|
device_address_p = mock.patch(
|
|
'quantum.agent.linux.ip_lib.IpLinkCommand.address')
|
|
device_address = device_address_p.start()
|
|
device_address.return_value = 'aa:bb:cc:dd:ee:ffa'
|
|
|
|
meta_interface = interface.MetaInterfaceDriver(self.conf)
|
|
driver = meta_interface._get_driver_by_device_name('test')
|
|
self.assertTrue(isinstance(
|
|
driver,
|
|
interface.OVSInterfaceDriver))
|
|
device_address_p.stop()
|