Add a new interface driver OVSVethInterfaceDriver.

Fixes bug 1048681

This commit fixes a bug that quantum agent using a namespace does not
work with NEC plugin. NEC plugin uses an Open vSwitch as an OpenFlow switch,
but an OVS port created by ovs-vsctl add-port becomes down when the port is
moved to some network namespace. Usual OpenFlow controllers respect the
OpenFlow port status. Since DHCP server (dnsmasq) is connected to the down
port, DHCP server disappears from the virtual network.

This behavior can be avoided if we use a similar approach used in
BridgeInterfaceDriver, i.e., we first create a veth pair, add one veth device
to OVS and move the other veth device to a network namespace. To accomplish it
this patch introduces a new interface driver OVSVethInterfaceDriver.

In addition, this patch adds a 'prefix' paramter to unplug(). When deleting
a veth pair, a veth device added to OVS bridge is not removed automatically.
Thus we need a mean to know the veth device name from a device_name to be
removed. It is the reason to add 'prefix' parameter to unplug().

Change-Id: I3f4eae371a27a3171a6a8c9e38ddc9354ed1b6b0
This commit is contained in:
Akihiro MOTOKI 2012-09-10 22:21:35 +09:00
parent b8ecd0aa22
commit aa5a7aaf47
3 changed files with 169 additions and 23 deletions

View File

@ -151,11 +151,13 @@ class L3NATAgent(object):
for d in ns_ip.get_devices(exclude_loopback=True):
if d.name.startswith(INTERNAL_DEV_PREFIX):
# device is on default bridge
self.driver.unplug(d.name, namespace=namespace)
self.driver.unplug(d.name, namespace=namespace,
prefix=INTERNAL_DEV_PREFIX)
elif d.name.startswith(EXTERNAL_DEV_PREFIX):
self.driver.unplug(d.name,
bridge=self.conf.external_network_bridge,
namespace=namespace)
namespace=namespace,
prefix=EXTERNAL_DEV_PREFIX)
#(TODO) Address the failure for the deletion of the namespace
def _create_router_namespace(self, ri):
@ -386,7 +388,8 @@ class L3NATAgent(object):
namespace=ri.ns_name()):
self.driver.unplug(interface_name,
bridge=self.conf.external_network_bridge,
namespace=ri.ns_name())
namespace=ri.ns_name(),
prefix=EXTERNAL_DEV_PREFIX)
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
for c, r in self.external_gateway_filter_rules():
@ -444,7 +447,8 @@ class L3NATAgent(object):
if ip_lib.device_exists(interface_name,
root_helper=self.conf.root_helper,
namespace=ri.ns_name()):
self.driver.unplug(interface_name, namespace=ri.ns_name())
self.driver.unplug(interface_name, namespace=ri.ns_name(),
prefix=INTERNAL_DEV_PREFIX)
if ex_gw_port:
ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']

View File

@ -92,7 +92,7 @@ class LinuxInterfaceDriver(object):
"""Plug in the interface."""
@abc.abstractmethod
def unplug(self, device_name, bridge=None, namespace=None):
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
"""Unplug the interface."""
@ -101,13 +101,27 @@ class NullDriver(LinuxInterfaceDriver):
bridge=None, namespace=None, prefix=None):
pass
def unplug(self, device_name, bridge=None, namespace=None):
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
pass
class OVSInterfaceDriver(LinuxInterfaceDriver):
"""Driver for creating an internal interface on an OVS bridge."""
def _ovs_add_port(self, bridge, device_name, port_id, mac_address,
internal=True):
cmd = ['ovs-vsctl', '--', '--may-exist',
'add-port', bridge, device_name]
if internal:
cmd += ['--', 'set', 'Interface', device_name, 'type=internal']
cmd += ['--', 'set', 'Interface', device_name,
'external-ids:iface-id=%s' % port_id,
'--', 'set', 'Interface', device_name,
'external-ids:iface-status=active',
'--', 'set', 'Interface', device_name,
'external-ids:attached-mac=%s' % mac_address]
utils.execute(cmd, self.conf.root_helper)
def plug(self, network_id, port_id, device_name, mac_address,
bridge=None, namespace=None, prefix=None):
"""Plug in the interface."""
@ -120,19 +134,7 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
self.conf.root_helper,
namespace=namespace):
utils.execute(['ovs-vsctl',
'--', '--may-exist', 'add-port', bridge,
device_name,
'--', 'set', 'Interface', device_name,
'type=internal',
'--', 'set', 'Interface', device_name,
'external-ids:iface-id=%s' % port_id,
'--', 'set', 'Interface', device_name,
'external-ids:iface-status=active',
'--', 'set', 'Interface', device_name,
'external-ids:attached-mac=%s' %
mac_address],
self.conf.root_helper)
self._ovs_add_port(bridge, device_name, port_id, mac_address)
ip = ip_lib.IPWrapper(self.conf.root_helper)
device = ip.device(device_name)
@ -145,7 +147,7 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
namespace_obj.add_device_to_namespace(device)
device.link.set_up()
def unplug(self, device_name, bridge=None, namespace=None):
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
"""Unplug the interface."""
if not bridge:
bridge = self.conf.ovs_integration_bridge
@ -186,7 +188,7 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
else:
LOG.warn(_("Device %s already exists") % device_name)
def unplug(self, device_name, bridge=None, namespace=None):
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
"""Unplug the interface."""
device = ip_lib.IPDevice(device_name, self.conf.root_helper, namespace)
try:
@ -224,6 +226,69 @@ class RyuInterfaceDriver(OVSInterfaceDriver):
self.ryu_client.create_port(network_id, datapath_id, port_no)
class OVSVethInterfaceDriver(OVSInterfaceDriver):
"""Driver for creating an OVS interface using veth."""
DEV_NAME_PREFIX = 'ns-'
def _get_tap_name(self, device_name, prefix=None):
if not prefix:
prefix = self.DEV_NAME_PREFIX
return device_name.replace(prefix, 'tap')
def plug(self, network_id, port_id, device_name, mac_address,
bridge=None, namespace=None, prefix=None):
"""Plugin the interface."""
if not bridge:
bridge = self.conf.ovs_integration_bridge
self.check_bridge_exists(bridge)
if not ip_lib.device_exists(device_name,
self.conf.root_helper,
namespace=namespace):
ip = ip_lib.IPWrapper(self.conf.root_helper)
tap_name = self._get_tap_name(device_name, prefix)
root_veth, ns_veth = ip.add_veth(tap_name, device_name)
self._ovs_add_port(bridge, tap_name, port_id, mac_address,
internal=False)
ns_veth.link.set_address(mac_address)
if self.conf.network_device_mtu:
ns_veth.link.set_mtu(self.conf.network_device_mtu)
root_veth.link.set_mtu(self.conf.network_device_mtu)
if namespace:
namespace_obj = ip.ensure_namespace(namespace)
namespace_obj.add_device_to_namespace(ns_veth)
root_veth.link.set_up()
ns_veth.link.set_up()
else:
LOG.warn(_("Device %s already exists") % device_name)
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
"""Unplug the interface."""
if not bridge:
bridge = self.conf.ovs_integration_bridge
tap_name = self._get_tap_name(device_name, prefix)
self.check_bridge_exists(bridge)
ovs = ovs_lib.OVSBridge(bridge, self.conf.root_helper)
try:
ovs.delete_port(tap_name)
device = ip_lib.IPDevice(device_name, self.conf.root_helper,
namespace)
device.link.delete()
LOG.debug(_("Unplugged interface '%s'") % device_name)
except RuntimeError:
LOG.error(_("Failed unplugging interface '%s'") %
device_name)
class MetaInterfaceDriver(LinuxInterfaceDriver):
def __init__(self, conf):
super(MetaInterfaceDriver, self).__init__(conf)
@ -266,9 +331,9 @@ class MetaInterfaceDriver(LinuxInterfaceDriver):
return driver.plug(network_id, port_id, device_name, mac_address,
bridge=bridge, namespace=namespace, prefix=prefix)
def unplug(self, device_name, bridge=None, namespace=None):
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
driver = self._get_driver_by_device_name(device_name, namespace=None)
return driver.unplug(device_name, bridge, namespace)
return driver.unplug(device_name, bridge, namespace, prefix)
def _load_driver(self, driver_provider):
LOG.debug("Driver location:%s", driver_provider)

View File

@ -171,6 +171,83 @@ class TestOVSInterfaceDriver(TestBase):
mock.call().delete_port('tap0')])
class TestOVSVethInterfaceDriver(TestOVSInterfaceDriver):
def test_get_device_name(self):
br = interface.OVSVethInterfaceDriver(self.conf)
device_name = br.get_device_name(FakePort())
self.assertEqual('ns-abcdef01-12', device_name)
def test_plug_with_prefix(self):
self._test_plug(devname='qr-0', prefix='qr-')
def _test_plug(self, devname=None, bridge=None, namespace=None,
prefix=None, mtu=None):
if not devname:
devname = 'ns-0'
if not bridge:
bridge = 'br-int'
def device_exists(dev, root_helper=None, namespace=None):
return dev == bridge
ovs = interface.OVSVethInterfaceDriver(self.conf)
self.device_exists.side_effect = device_exists
root_veth = mock.Mock()
ns_veth = mock.Mock()
self.ip().add_veth = mock.Mock(return_value=(root_veth, ns_veth))
expected = [mock.call('sudo'), mock.call().add_veth('tap0', devname)]
vsctl_cmd = ['ovs-vsctl', '--', '--may-exist', 'add-port',
bridge, 'tap0', '--', '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.plug('01234567-1234-1234-99',
'port-1234',
devname,
'aa:bb:cc:dd:ee:ff',
bridge=bridge,
namespace=namespace,
prefix=prefix)
execute.assert_called_once_with(vsctl_cmd, 'sudo')
ns_veth.assert_has_calls(
[mock.call.link.set_address('aa:bb:cc:dd:ee:ff')])
if mtu:
ns_veth.assert_has_calls([mock.call.link.set_mtu(mtu)])
root_veth.assert_has_calls([mock.call.link.set_mtu(mtu)])
if namespace:
expected.extend(
[mock.call().ensure_namespace(namespace),
mock.call().ensure_namespace().add_device_to_namespace(
mock.ANY)])
self.ip.assert_has_calls(expected)
root_veth.assert_has_calls([mock.call.link.set_up()])
ns_veth.assert_has_calls([mock.call.link.set_up()])
def test_plug_mtu(self):
self.conf.set_override('network_device_mtu', 9000)
self._test_plug(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.OVSVethInterfaceDriver(self.conf)
ovs.unplug('ns-0', bridge=bridge)
ovs_br.assert_has_calls([mock.call(bridge, 'sudo'),
mock.call().delete_port('tap0')])
self.ip_dev.assert_has_calls([mock.call('ns-0', 'sudo', None),
mock.call().link.delete()])
class TestBridgeInterfaceDriver(TestBase):
def test_get_device_name(self):
br = interface.BridgeInterfaceDriver(self.conf)