]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Big Switch: Bind IVS ports in ML2 driver
authorKevin Benton <blak111@gmail.com>
Wed, 13 Aug 2014 02:02:51 +0000 (19:02 -0700)
committerKevin Benton <blak111@gmail.com>
Mon, 1 Sep 2014 18:34:39 +0000 (11:34 -0700)
Add support to bind IVS ports in the Big Switch ML2
mechanism driver. The backend controller will be checked
to determine if a host is connected using the Indigo vswitch.
If so, the mechanism driver will mark it as bound since
it will be provisioned by the backend controller.

Implements: blueprint bsn-ml2-bind-ivs
Change-Id: Ic481fc31c8c123899fddf8185c32f127dff53b7a

neutron/plugins/bigswitch/servermanager.py
neutron/plugins/ml2/drivers/mech_bigswitch/driver.py
neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py

index bc070c2e75a451cb7fc48ef000f9de38e9e6226c..ebb2f543fa266a61ab7e55af2feb313d13934b2c 100644 (file)
@@ -64,6 +64,7 @@ ROUTERS_PATH = "/tenants/%s/routers/%s"
 ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s"
 TOPOLOGY_PATH = "/topology"
 HEALTH_PATH = "/health"
+SWITCHES_PATH = "/switches/%s"
 SUCCESS_CODES = range(200, 207)
 FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
                  504, 505]
@@ -568,6 +569,11 @@ class ServerPool(object):
         errstr = _("Unable to delete floating IP: %s")
         self.rest_action('DELETE', resource, errstr=errstr)
 
+    def rest_get_switch(self, switch_id):
+        resource = SWITCHES_PATH % switch_id
+        errstr = _("Unable to retrieve switch: %s")
+        return self.rest_action('GET', resource, errstr=errstr)
+
     def _consistency_watchdog(self, polling_interval=60):
         if 'consistency' not in self.get_capabilities():
             LOG.warning(_("Backend server(s) do not support automated "
index 259b14673c846e83490dc3503a179fff15c9a1bd..75f25cea924bad0253d61f68f6fb70b05edfadd8 100644 (file)
@@ -16,6 +16,7 @@
 # @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
 # @author: Kevin Benton, Big Switch Networks, Inc.
 import copy
+import datetime
 import httplib
 
 import eventlet
@@ -25,14 +26,19 @@ from neutron import context as ctx
 from neutron.extensions import portbindings
 from neutron.openstack.common import excutils
 from neutron.openstack.common import log
+from neutron.openstack.common import timeutils
 from neutron.plugins.bigswitch import config as pl_config
 from neutron.plugins.bigswitch import plugin
 from neutron.plugins.bigswitch import servermanager
+from neutron.plugins.common import constants as pconst
 from neutron.plugins.ml2 import driver_api as api
 
 
 LOG = log.getLogger(__name__)
 
+# time in seconds to maintain existence of vswitch response
+CACHE_VSWITCH_TIME = 60
+
 
 class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
                                api.MechanismDriver):
@@ -59,6 +65,9 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
                                                'get_floating_ips': False,
                                                'get_routers': False}
         self.segmentation_types = ', '.join(cfg.CONF.ml2.type_drivers)
+        # Track hosts running IVS to avoid excessive calls to the backend
+        self.ivs_host_cache = {}
+
         LOG.debug(_("Initialization done"))
 
     def create_network_postcommit(self, context):
@@ -126,3 +135,57 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
             # the host_id set
             return False
         return prepped_port
+
+    def bind_port(self, context):
+        if not self.does_vswitch_exist(context.host):
+            # this is not an IVS host
+            return
+
+        # currently only vlan segments are supported
+        for segment in context.network.network_segments:
+            if segment[api.NETWORK_TYPE] == pconst.TYPE_VLAN:
+                context.set_binding(segment[api.ID], portbindings.VIF_TYPE_IVS,
+                                    {portbindings.CAP_PORT_FILTER: True,
+                                     portbindings.OVS_HYBRID_PLUG: False})
+
+    def does_vswitch_exist(self, host):
+        """Check if Indigo vswitch exists with the given hostname.
+
+        Returns True if switch exists on backend.
+        Returns False if switch does not exist.
+        Returns None if backend could not be reached.
+        Caches response from backend.
+        """
+        try:
+            return self._get_cached_vswitch_existence(host)
+        except ValueError:
+            # cache was empty for that switch or expired
+            pass
+
+        try:
+            self.servers.rest_get_switch(host)
+            exists = True
+        except servermanager.RemoteRestError as e:
+            if e.status == 404:
+                exists = False
+            else:
+                # Another error, return without caching to try again on
+                # next binding
+                return
+        self.ivs_host_cache[host] = {
+            'timestamp': datetime.datetime.now(),
+            'exists': exists
+        }
+        return exists
+
+    def _get_cached_vswitch_existence(self, host):
+        """Returns cached existence. Old and non-cached raise ValueError."""
+        entry = self.ivs_host_cache.get(host)
+        if not entry:
+            raise ValueError(_('No cache entry for host %s') % host)
+        diff = timeutils.delta_seconds(entry['timestamp'],
+                                       datetime.datetime.now())
+        if diff > CACHE_VSWITCH_TIME:
+            self.ivs_host_cache.pop(host)
+            raise ValueError(_('Expired cache entry for host %s') % host)
+        return entry['exists']
index b3536c856bb5787a4e3d84209b044d6680739352..2dbe2a6d6d165505b147df1cd80485e30368945d 100644 (file)
@@ -15,6 +15,8 @@
 # limitations under the License.
 
 import contextlib
+import functools
+
 import mock
 import webob.exc
 
@@ -87,6 +89,58 @@ class TestBigSwitchMechDriverPortsV2(test_db_plugin.TestPortsV2,
             raise webob.exc.HTTPClientError(code=res.status_int)
         return self.deserialize(fmt, res)
 
+    def test_bind_ivs_port(self):
+        host_arg = {portbindings.HOST_ID: 'hostname'}
+        with contextlib.nested(
+            mock.patch(SERVER_POOL + '.rest_get_switch', return_value=True),
+            self.port(arg_list=(portbindings.HOST_ID,), **host_arg)
+        ) as (rmock, port):
+            rmock.assert_called_once_with('hostname')
+            p = port['port']
+            self.assertEqual('ACTIVE', p['status'])
+            self.assertEqual('hostname', p[portbindings.HOST_ID])
+            self.assertEqual(portbindings.VIF_TYPE_IVS,
+                             p[portbindings.VIF_TYPE])
+
+    def test_dont_bind_non_ivs_port(self):
+        host_arg = {portbindings.HOST_ID: 'hostname'}
+        with contextlib.nested(
+            mock.patch(SERVER_POOL + '.rest_get_switch',
+                       side_effect=servermanager.RemoteRestError(
+                           reason='No such switch', status=404)),
+            self.port(arg_list=(portbindings.HOST_ID,), **host_arg)
+        ) as (rmock, port):
+            rmock.assert_called_once_with('hostname')
+            p = port['port']
+            self.assertNotEqual(portbindings.VIF_TYPE_IVS,
+                                p[portbindings.VIF_TYPE])
+
+    def test_bind_port_cache(self):
+        with contextlib.nested(
+            self.subnet(),
+            mock.patch(SERVER_POOL + '.rest_get_switch', return_value=True)
+        ) as (sub, rmock):
+            makeport = functools.partial(self.port, **{
+                'subnet': sub, 'arg_list': (portbindings.HOST_ID,),
+                portbindings.HOST_ID: 'hostname'})
+
+            with contextlib.nested(makeport(), makeport(),
+                                   makeport()) as ports:
+                # response from first should be cached
+                rmock.assert_called_once_with('hostname')
+                for port in ports:
+                    self.assertEqual(portbindings.VIF_TYPE_IVS,
+                                     port['port'][portbindings.VIF_TYPE])
+            rmock.reset_mock()
+            # expired cache should result in new calls
+            mock.patch(DRIVER_MOD + '.CACHE_VSWITCH_TIME', new=0).start()
+            with contextlib.nested(makeport(), makeport(),
+                                   makeport()) as ports:
+                self.assertEqual(3, rmock.call_count)
+                for port in ports:
+                    self.assertEqual(portbindings.VIF_TYPE_IVS,
+                                     port['port'][portbindings.VIF_TYPE])
+
     def test_create404_triggers_background_sync(self):
         # allow the async background thread to run for this test
         self.spawn_p.stop()