Skip to content

Commit

Permalink
Merge "Big Switch: Bind IVS ports in ML2 driver"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Sep 7, 2014
2 parents 4ede706 + 4c4dbd4 commit aa4beec
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
6 changes: 6 additions & 0 deletions neutron/plugins/bigswitch/servermanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
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]
Expand Down Expand Up @@ -578,6 +579,11 @@ def rest_delete_floatingip(self, tenant_id, oldid):
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 "
Expand Down
63 changes: 63 additions & 0 deletions neutron/plugins/ml2/drivers/mech_bigswitch/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# @author: Sumit Naiksatam, [email protected], Big Switch Networks, Inc.
# @author: Kevin Benton, Big Switch Networks, Inc.
import copy
import datetime
import httplib

import eventlet
Expand All @@ -25,14 +26,19 @@
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):
Expand All @@ -59,6 +65,9 @@ def initialize(self):
'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):
Expand Down Expand Up @@ -126,3 +135,57 @@ def _prepare_port_for_controller(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']
54 changes: 54 additions & 0 deletions neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# limitations under the License.

import contextlib
import functools

import mock
import webob.exc

Expand Down Expand Up @@ -87,6 +89,58 @@ def _make_port(self, fmt, net_id, expected_res_status=None, arg_list=None,
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()
Expand Down

0 comments on commit aa4beec

Please sign in to comment.