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"
|
ROUTER_INTF_PATH = "/tenants/%s/routers/%s/interfaces/%s"
|
||||||
TOPOLOGY_PATH = "/topology"
|
TOPOLOGY_PATH = "/topology"
|
||||||
HEALTH_PATH = "/health"
|
HEALTH_PATH = "/health"
|
||||||
|
SWITCHES_PATH = "/switches/%s"
|
||||||
SUCCESS_CODES = range(200, 207)
|
SUCCESS_CODES = range(200, 207)
|
||||||
FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
|
FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503,
|
||||||
504, 505]
|
504, 505]
|
||||||
@ -568,6 +569,11 @@ class ServerPool(object):
|
|||||||
errstr = _("Unable to delete floating IP: %s")
|
errstr = _("Unable to delete floating IP: %s")
|
||||||
self.rest_action('DELETE', resource, errstr=errstr)
|
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):
|
def _consistency_watchdog(self, polling_interval=60):
|
||||||
if 'consistency' not in self.get_capabilities():
|
if 'consistency' not in self.get_capabilities():
|
||||||
LOG.warning(_("Backend server(s) do not support automated "
|
LOG.warning(_("Backend server(s) do not support automated "
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||||
# @author: Kevin Benton, Big Switch Networks, Inc.
|
# @author: Kevin Benton, Big Switch Networks, Inc.
|
||||||
import copy
|
import copy
|
||||||
|
import datetime
|
||||||
import httplib
|
import httplib
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
@ -25,14 +26,19 @@ from neutron import context as ctx
|
|||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.openstack.common import excutils
|
from neutron.openstack.common import excutils
|
||||||
from neutron.openstack.common import log
|
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 config as pl_config
|
||||||
from neutron.plugins.bigswitch import plugin
|
from neutron.plugins.bigswitch import plugin
|
||||||
from neutron.plugins.bigswitch import servermanager
|
from neutron.plugins.bigswitch import servermanager
|
||||||
|
from neutron.plugins.common import constants as pconst
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
# time in seconds to maintain existence of vswitch response
|
||||||
|
CACHE_VSWITCH_TIME = 60
|
||||||
|
|
||||||
|
|
||||||
class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
|
class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
|
||||||
api.MechanismDriver):
|
api.MechanismDriver):
|
||||||
@ -59,6 +65,9 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
|
|||||||
'get_floating_ips': False,
|
'get_floating_ips': False,
|
||||||
'get_routers': False}
|
'get_routers': False}
|
||||||
self.segmentation_types = ', '.join(cfg.CONF.ml2.type_drivers)
|
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"))
|
LOG.debug(_("Initialization done"))
|
||||||
|
|
||||||
def create_network_postcommit(self, context):
|
def create_network_postcommit(self, context):
|
||||||
@ -126,3 +135,57 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
|
|||||||
# the host_id set
|
# the host_id set
|
||||||
return False
|
return False
|
||||||
return prepped_port
|
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.
|
# limitations under the License.
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import functools
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
@ -87,6 +89,58 @@ class TestBigSwitchMechDriverPortsV2(test_db_plugin.TestPortsV2,
|
|||||||
raise webob.exc.HTTPClientError(code=res.status_int)
|
raise webob.exc.HTTPClientError(code=res.status_int)
|
||||||
return self.deserialize(fmt, res)
|
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):
|
def test_create404_triggers_background_sync(self):
|
||||||
# allow the async background thread to run for this test
|
# allow the async background thread to run for this test
|
||||||
self.spawn_p.stop()
|
self.spawn_p.stop()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user