vmware-nsx/quantum/tests/unit/test_linux_interface.py
Nachi Ueno 7811e2a7c3 Initial implemention of MetaPlugin
This plugin supports multiple plugins at same time. This plugin is for L3 connectivility
between networks which are realized by different plugins. This plugin add new attribute 'flavor:id'.
flavor:id correspond to specific plugin. flavor-plugin mapping could be configureable by plugin_list config.
This plugin also support extensions. We can map extension to plugin by using extension_map config.

Implements blueprint metaplugin

Change-Id: Ia94d2349fb3ce9f121bbd2505324ae6f0c34247a
2012-08-13 06:19:31 +00:00

388 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()
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'),
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()