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]
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 "
# @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
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):
'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):
# 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']
# limitations under the License.
import contextlib
+import functools
+
import mock
import webob.exc
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()