From: Kevin Benton Date: Tue, 4 Jun 2013 23:18:58 +0000 (-0700) Subject: Adds support for the Indigo Virtual Switch (IVS) X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=e02c746a94c3024678dd6b3458573667b1cb9d73;p=openstack-build%2Fneutron-build.git Adds support for the Indigo Virtual Switch (IVS) Implements: blueprint ivs-interface-driver Adds the IVS VIF type to portbindings.py. Adds the VIF interface class to allow agents to bind to VIF switches. Adds support to the BigSwitch plugin to request the IVS VIF type on nova compute nodes. Adds unit tests for new interface class and changes to BigSwitch plugin. Change-Id: I2fe104000523c60097c22946b0a80dc404dfe273 --- diff --git a/etc/quantum/plugins/bigswitch/restproxy.ini b/etc/quantum/plugins/bigswitch/restproxy.ini index 74a8e619d..9e1a52119 100644 --- a/etc/quantum/plugins/bigswitch/restproxy.ini +++ b/etc/quantum/plugins/bigswitch/restproxy.ini @@ -40,3 +40,9 @@ servers=localhost:8080 #server_ssl=True #sync_data=True #server_timeout=10 + +[NOVA] +# Specify the VIF_TYPE that will be controlled on the Nova compute instances +# options: ivs or ovs +# default: ovs +# vif_type = ovs diff --git a/etc/quantum/rootwrap.d/dhcp.filters b/etc/quantum/rootwrap.d/dhcp.filters index 0a4ac41b3..f8d2a45eb 100644 --- a/etc/quantum/rootwrap.d/dhcp.filters +++ b/etc/quantum/rootwrap.d/dhcp.filters @@ -21,6 +21,7 @@ kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP # dhcp-agent uses cat cat: RegExpFilter, cat, root, cat, /proc/\d+/cmdline ovs-vsctl: CommandFilter, ovs-vsctl, root +ivs-ctl: CommandFilter, ivs-ctl, root # metadata proxy metadata_proxy: CommandFilter, quantum-ns-metadata-proxy, root diff --git a/quantum/agent/linux/interface.py b/quantum/agent/linux/interface.py index 056a2bcce..3f8c901c2 100644 --- a/quantum/agent/linux/interface.py +++ b/quantum/agent/linux/interface.py @@ -219,6 +219,69 @@ class OVSInterfaceDriver(LinuxInterfaceDriver): device_name) +class IVSInterfaceDriver(LinuxInterfaceDriver): + """Driver for creating an internal interface on an IVS bridge.""" + + DEV_NAME_PREFIX = 'tap' + + def __init__(self, conf): + super(IVSInterfaceDriver, self).__init__(conf) + self.DEV_NAME_PREFIX = 'ns-' + + def _get_tap_name(self, dev_name, prefix=None): + dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX, 'tap') + return dev_name + + def _ivs_add_port(self, device_name, port_id, mac_address): + cmd = ['ivs-ctl', 'add-port', device_name] + utils.execute(cmd, self.root_helper) + + def plug(self, network_id, port_id, device_name, mac_address, + bridge=None, namespace=None, prefix=None): + """Plug in the interface.""" + if not ip_lib.device_exists(device_name, + self.root_helper, + namespace=namespace): + + ip = ip_lib.IPWrapper(self.root_helper) + tap_name = self._get_tap_name(device_name, prefix) + + root_dev, ns_dev = ip.add_veth(tap_name, device_name) + + self._ivs_add_port(tap_name, port_id, mac_address) + + ns_dev = ip.device(device_name) + ns_dev.link.set_address(mac_address) + + if self.conf.network_device_mtu: + ns_dev.link.set_mtu(self.conf.network_device_mtu) + root_dev.link.set_mtu(self.conf.network_device_mtu) + + if namespace: + namespace_obj = ip.ensure_namespace(namespace) + namespace_obj.add_device_to_namespace(ns_dev) + + ns_dev.link.set_up() + root_dev.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.""" + tap_name = self._get_tap_name(device_name, prefix) + try: + cmd = ['ivs-ctl', 'del-port', tap_name] + utils.execute(cmd, self.root_helper) + device = ip_lib.IPDevice(device_name, + self.root_helper, + namespace) + device.link.delete() + LOG.debug(_("Unplugged interface '%s'"), device_name) + except RuntimeError: + LOG.error(_("Failed unplugging interface '%s'"), + device_name) + + class BridgeInterfaceDriver(LinuxInterfaceDriver): """Driver for creating bridge interfaces.""" diff --git a/quantum/extensions/portbindings.py b/quantum/extensions/portbindings.py index 2bf4f24d2..f99c29dfe 100644 --- a/quantum/extensions/portbindings.py +++ b/quantum/extensions/portbindings.py @@ -38,6 +38,7 @@ CAP_PORT_FILTER = 'port_filter' VIF_TYPE_UNBOUND = 'unbound' VIF_TYPE_BINDING_FAILED = 'binding_failed' VIF_TYPE_OVS = 'ovs' +VIF_TYPE_IVS = 'ivs' VIF_TYPE_BRIDGE = 'bridge' VIF_TYPE_802_QBG = '802.1qbg' VIF_TYPE_802_QBH = '802.1qbh' diff --git a/quantum/plugins/bigswitch/plugin.py b/quantum/plugins/bigswitch/plugin.py index 78bb92e39..c5d9e46af 100644 --- a/quantum/plugins/bigswitch/plugin.py +++ b/quantum/plugins/bigswitch/plugin.py @@ -104,6 +104,16 @@ restproxy_opts = [ cfg.CONF.register_opts(restproxy_opts, "RESTPROXY") +nova_opts = [ + cfg.StrOpt('vif_type', default='ovs', + help=_("Virtual interface type to configure on " + "Nova compute nodes")), +] + + +cfg.CONF.register_opts(nova_opts, "NOVA") + + # The following are used to invoke the API on the external controller NET_RESOURCE_PATH = "/tenants/%s/networks" PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports" @@ -1267,7 +1277,16 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2, return data def _extend_port_dict_binding(self, context, port): - port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS + cfg_vif_type = cfg.CONF.NOVA.vif_type.lower() + if not cfg_vif_type in (portbindings.VIF_TYPE_OVS, + portbindings.VIF_TYPE_IVS): + LOG.warning(_("Unrecognized vif_type in configuration " + "[%s]. Defaulting to ovs. "), + cfg_vif_type) + cfg_vif_type = portbindings.VIF_TYPE_OVS + + port[portbindings.VIF_TYPE] = cfg_vif_type + port[portbindings.CAPABILITIES] = { portbindings.CAP_PORT_FILTER: 'security-group' in self.supported_extension_aliases} diff --git a/quantum/tests/unit/bigswitch/etc/restproxy.ini.test b/quantum/tests/unit/bigswitch/etc/restproxy.ini.test index ebcc7fe09..e2b99416f 100644 --- a/quantum/tests/unit/bigswitch/etc/restproxy.ini.test +++ b/quantum/tests/unit/bigswitch/etc/restproxy.ini.test @@ -24,3 +24,10 @@ reconnect_interval = 2 servers=localhost:8899 serverssl=False #serverauth=username:password + +[NOVA] +# Specify the VIF_TYPE that will be controlled on the Nova compute instances +# options: ivs or ovs +# default: ovs +vif_type = ovs + diff --git a/quantum/tests/unit/bigswitch/test_restproxy_plugin.py b/quantum/tests/unit/bigswitch/test_restproxy_plugin.py index 006a98378..b6174fb8c 100644 --- a/quantum/tests/unit/bigswitch/test_restproxy_plugin.py +++ b/quantum/tests/unit/bigswitch/test_restproxy_plugin.py @@ -18,6 +18,7 @@ import os from mock import patch +from oslo.config import cfg import quantum.common.test_lib as test_lib from quantum.extensions import portbindings @@ -93,6 +94,18 @@ class TestBigSwitchProxyPortsV2(test_plugin.TestPortsV2, HAS_PORT_FILTER = False +class TestBigSwitchProxyPortsV2IVS(test_plugin.TestPortsV2, + BigSwitchProxyPluginV2TestCase, + test_bindings.PortBindingsTestCase): + VIF_TYPE = portbindings.VIF_TYPE_IVS + HAS_PORT_FILTER = False + + def setUp(self): + super(TestBigSwitchProxyPortsV2IVS, + self).setUp() + cfg.CONF.set_override('vif_type', 'ivs', 'NOVA') + + class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2, BigSwitchProxyPluginV2TestCase): diff --git a/quantum/tests/unit/test_linux_interface.py b/quantum/tests/unit/test_linux_interface.py index df8b561b9..053225fab 100644 --- a/quantum/tests/unit/test_linux_interface.py +++ b/quantum/tests/unit/test_linux_interface.py @@ -394,3 +394,79 @@ class TestMetaInterfaceDriver(TestBase): namespace=namespace) self.ip_dev.assert_has_calls(expected) self.assertEquals('fake1', plugin_tag1) + + +class TestIVSInterfaceDriver(TestBase): + + def setUp(self): + super(TestIVSInterfaceDriver, self).setUp() + + def test_get_device_name(self): + br = interface.IVSInterfaceDriver(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, namespace=None, + prefix=None, mtu=None): + + if not devname: + devname = 'ns-0' + + def device_exists(dev, root_helper=None, namespace=None): + return dev == 'indigo' + + ivs = interface.IVSInterfaceDriver(self.conf) + self.device_exists.side_effect = device_exists + + root_dev = mock.Mock() + _ns_dev = mock.Mock() + ns_dev = mock.Mock() + self.ip().add_veth = mock.Mock(return_value=(root_dev, _ns_dev)) + self.ip().device = mock.Mock(return_value=(ns_dev)) + expected = [mock.call('sudo'), mock.call().add_veth('tap0', devname), + mock.call().device(devname)] + + ivsctl_cmd = ['ivs-ctl', 'add-port', 'tap0'] + + with mock.patch.object(utils, 'execute') as execute: + ivs.plug('01234567-1234-1234-99', + 'port-1234', + devname, + 'aa:bb:cc:dd:ee:ff', + namespace=namespace, + prefix=prefix) + execute.assert_called_once_with(ivsctl_cmd, 'sudo') + + ns_dev.assert_has_calls( + [mock.call.link.set_address('aa:bb:cc:dd:ee:ff')]) + if mtu: + ns_dev.assert_has_calls([mock.call.link.set_mtu(mtu)]) + root_dev.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_dev.assert_has_calls([mock.call.link.set_up()]) + ns_dev.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_plug_namespace(self): + self._test_plug(namespace='mynamespace') + + def test_unplug(self): + ivs = interface.IVSInterfaceDriver(self.conf) + ivsctl_cmd = ['ivs-ctl', 'del-port', 'tap0'] + with mock.patch.object(utils, 'execute') as execute: + ivs.unplug('ns-0') + execute.assert_called_once_with(ivsctl_cmd, 'sudo') + self.ip_dev.assert_has_calls([mock.call('ns-0', 'sudo', None), + mock.call().link.delete()])