]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Adds support for the Indigo Virtual Switch (IVS)
authorKevin Benton <kevin.benton@bigswitch.com>
Tue, 4 Jun 2013 23:18:58 +0000 (16:18 -0700)
committerKevin Benton <kevin.benton@bigswitch.com>
Thu, 6 Jun 2013 22:33:12 +0000 (15:33 -0700)
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

etc/quantum/plugins/bigswitch/restproxy.ini
etc/quantum/rootwrap.d/dhcp.filters
quantum/agent/linux/interface.py
quantum/extensions/portbindings.py
quantum/plugins/bigswitch/plugin.py
quantum/tests/unit/bigswitch/etc/restproxy.ini.test
quantum/tests/unit/bigswitch/test_restproxy_plugin.py
quantum/tests/unit/test_linux_interface.py

index 74a8e619d4ebecc684e6c4519180c63f50eda2e4..9e1a52119df4bc22bb9f8676e09ce4565fe875cc 100644 (file)
@@ -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
index 0a4ac41b33cc0fbcbfccfae6e1f779bd4f0995c0..f8d2a45ebb632fcde9fdeeacfe874e0c0b5e3eeb 100644 (file)
@@ -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
index 056a2bcce1f846733daac0b4985b65677cd51901..3f8c901c2fc4f04727d5aa7060f254c1f6899bdb 100644 (file)
@@ -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."""
 
index 2bf4f24d2c875dddc5811884f1d6f73f725e86ab..f99c29dfe5b27ba3b0acd38351f0b9f8f1e4447b 100644 (file)
@@ -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'
index 78bb92e3949ebeda57a44b2777414f1d2aff9bd7..c5d9e46af065a765a636da7ef751be9d69d73f52 100644 (file)
@@ -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}
index ebcc7fe092747a850280013871789ad7389047db..e2b99416f5766bd4a4b70c7933b5c1ef1c7833c3 100644 (file)
@@ -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
+
index 006a98378ae5ca60e2e890776faf4de2c11f4d84..b6174fb8c606c886884f179422545075236ebbee 100644 (file)
@@ -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):
 
index df8b561b903c06925d9e0718374f7eb56ef9926e..053225fabbbba6abbe524a273c6538f9d1bb8a69 100644 (file)
@@ -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()])