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:
parent
7a60f16270
commit
cc8a57cb04
@ -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 "
|
||||
|
@ -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']
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user