Big Switch: Bind IVS ports in ML2 driver

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
This commit is contained in:
Kevin Benton 2014-08-12 19:02:51 -07:00
parent 7a60f16270
commit cc8a57cb04
3 changed files with 123 additions and 0 deletions

View 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 "

View 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']

View 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()