Merge "BGP Dynamic Routing: introduce BgpDriver"
This commit is contained in:
commit
a8270d82a9
|
@ -1,3 +1,5 @@
|
||||||
|
RYU_BGP_SPEAKER_DRIVER="neutron.services.bgp.driver.ryu.driver.RyuBgpDriver"
|
||||||
|
|
||||||
function configure_bgp_service_plugin {
|
function configure_bgp_service_plugin {
|
||||||
_neutron_service_plugin_class_add "bgp"
|
_neutron_service_plugin_class_add "bgp"
|
||||||
}
|
}
|
||||||
|
@ -14,6 +16,10 @@ function configure_bgp_dragent {
|
||||||
if [ -n "$BGP_ROUTER_ID" ]; then
|
if [ -n "$BGP_ROUTER_ID" ]; then
|
||||||
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_router_id $BGP_ROUTER_ID
|
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_router_id $BGP_ROUTER_ID
|
||||||
fi
|
fi
|
||||||
|
if [ -z "$BGP_SPEAKER_DRIVER" ]; then
|
||||||
|
BGP_SPEAKER_DRIVER=$RYU_BGP_SPEAKER_DRIVER
|
||||||
|
fi
|
||||||
|
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_speaker_driver $BGP_SPEAKER_DRIVER
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_bgp_dragent {
|
function start_bgp_dragent {
|
||||||
|
@ -22,4 +28,4 @@ function start_bgp_dragent {
|
||||||
|
|
||||||
function stop_bgp_dragent {
|
function stop_bgp_dragent {
|
||||||
stop_process q-bgp-agt
|
stop_process q-bgp-agt
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,11 @@ class BgpDbMixin(common_db.CommonDbMixin):
|
||||||
|
|
||||||
def create_bgp_peer(self, context, bgp_peer):
|
def create_bgp_peer(self, context, bgp_peer):
|
||||||
ri = bgp_peer[bgp_ext.BGP_PEER_BODY_KEY_NAME]
|
ri = bgp_peer[bgp_ext.BGP_PEER_BODY_KEY_NAME]
|
||||||
|
auth_type = ri.get('auth_type')
|
||||||
|
password = ri.get('password')
|
||||||
|
if auth_type == 'md5' and not password:
|
||||||
|
raise bgp_ext.InvalidBgpPeerMd5Authentication()
|
||||||
|
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
res_keys = ['tenant_id', 'name', 'remote_as', 'peer_ip',
|
res_keys = ['tenant_id', 'name', 'remote_as', 'peer_ip',
|
||||||
'auth_type', 'password']
|
'auth_type', 'password']
|
||||||
|
|
|
@ -19,13 +19,13 @@ from neutron.api import extensions
|
||||||
from neutron.api.v2 import attributes as attr
|
from neutron.api.v2 import attributes as attr
|
||||||
from neutron.api.v2 import resource_helper as rh
|
from neutron.api.v2 import resource_helper as rh
|
||||||
from neutron.common import exceptions
|
from neutron.common import exceptions
|
||||||
|
from neutron.services.bgp.common import constants as bgp_consts
|
||||||
|
|
||||||
BGP_EXT_ALIAS = 'bgp'
|
BGP_EXT_ALIAS = 'bgp'
|
||||||
BGP_SPEAKER_RESOURCE_NAME = 'bgp-speaker'
|
BGP_SPEAKER_RESOURCE_NAME = 'bgp-speaker'
|
||||||
BGP_SPEAKER_BODY_KEY_NAME = 'bgp_speaker'
|
BGP_SPEAKER_BODY_KEY_NAME = 'bgp_speaker'
|
||||||
BGP_PEER_BODY_KEY_NAME = 'bgp_peer'
|
BGP_PEER_BODY_KEY_NAME = 'bgp_peer'
|
||||||
|
|
||||||
bgp_supported_auth_types = ['none', 'md5']
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
RESOURCE_ATTRIBUTE_MAP = {
|
||||||
BGP_SPEAKER_RESOURCE_NAME + 's': {
|
BGP_SPEAKER_RESOURCE_NAME + 's': {
|
||||||
|
@ -36,7 +36,8 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||||
'validate': {'type:string': attr.NAME_MAX_LEN},
|
'validate': {'type:string': attr.NAME_MAX_LEN},
|
||||||
'is_visible': True, 'default': ''},
|
'is_visible': True, 'default': ''},
|
||||||
'local_as': {'allow_post': True, 'allow_put': False,
|
'local_as': {'allow_post': True, 'allow_put': False,
|
||||||
'validate': {'type:range': (1, 65535)},
|
'validate': {'type:range': (bgp_consts.MIN_ASNUM,
|
||||||
|
bgp_consts.MAX_ASNUM)},
|
||||||
'is_visible': True, 'default': None,
|
'is_visible': True, 'default': None,
|
||||||
'required_by_policy': False,
|
'required_by_policy': False,
|
||||||
'enforce_policy': False},
|
'enforce_policy': False},
|
||||||
|
@ -88,13 +89,15 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||||
'validate': {'type:ip_address': None},
|
'validate': {'type:ip_address': None},
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
'remote_as': {'allow_post': True, 'allow_put': False,
|
'remote_as': {'allow_post': True, 'allow_put': False,
|
||||||
'validate': {'type:range': (1, 65535)},
|
'validate': {'type:range': (bgp_consts.MIN_ASNUM,
|
||||||
|
bgp_consts.MAX_ASNUM)},
|
||||||
'is_visible': True, 'default': None,
|
'is_visible': True, 'default': None,
|
||||||
'required_by_policy': False,
|
'required_by_policy': False,
|
||||||
'enforce_policy': False},
|
'enforce_policy': False},
|
||||||
'auth_type': {'allow_post': True, 'allow_put': False,
|
'auth_type': {'allow_post': True, 'allow_put': False,
|
||||||
'required_by_policy': True,
|
'required_by_policy': True,
|
||||||
'validate': {'type:values': bgp_supported_auth_types},
|
'validate': {'type:values':
|
||||||
|
bgp_consts.SUPPORTED_AUTH_TYPES},
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
'password': {'allow_post': True, 'allow_put': True,
|
'password': {'allow_post': True, 'allow_put': True,
|
||||||
'required_by_policy': True,
|
'required_by_policy': True,
|
||||||
|
@ -147,6 +150,10 @@ class DuplicateBgpPeerIpException(exceptions.Conflict):
|
||||||
"BGP Peer %(bgp_peer_id)s.")
|
"BGP Peer %(bgp_peer_id)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidBgpPeerMd5Authentication(exceptions.BadRequest):
|
||||||
|
message = _("A password must be supplied when using auth_type md5.")
|
||||||
|
|
||||||
|
|
||||||
class Bgp(extensions.ExtensionDescriptor):
|
class Bgp(extensions.ExtensionDescriptor):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -20,6 +20,7 @@ from oslo_log import log as logging
|
||||||
import oslo_messaging
|
import oslo_messaging
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
from oslo_service import periodic_task
|
from oslo_service import periodic_task
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from neutron.agent import rpc as agent_rpc
|
from neutron.agent import rpc as agent_rpc
|
||||||
from neutron.common import constants
|
from neutron.common import constants
|
||||||
|
@ -31,6 +32,7 @@ from neutron.extensions import bgp as bgp_ext
|
||||||
from neutron._i18n import _, _LE, _LI, _LW
|
from neutron._i18n import _, _LE, _LI, _LW
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
from neutron.services.bgp.common import constants as bgp_consts
|
||||||
|
from neutron.services.bgp.driver import exceptions as driver_exc
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ class BgpDrAgent(manager.Manager):
|
||||||
|
|
||||||
def __init__(self, host, conf=None):
|
def __init__(self, host, conf=None):
|
||||||
super(BgpDrAgent, self).__init__()
|
super(BgpDrAgent, self).__init__()
|
||||||
self.conf = conf
|
self.initialize_driver(conf)
|
||||||
self.needs_resync_reasons = collections.defaultdict(list)
|
self.needs_resync_reasons = collections.defaultdict(list)
|
||||||
self.needs_full_sync_reason = None
|
self.needs_full_sync_reason = None
|
||||||
|
|
||||||
|
@ -61,6 +63,27 @@ class BgpDrAgent(manager.Manager):
|
||||||
self.plugin_rpc = BgpDrPluginApi(bgp_consts.BGP_PLUGIN,
|
self.plugin_rpc = BgpDrPluginApi(bgp_consts.BGP_PLUGIN,
|
||||||
self.context, host)
|
self.context, host)
|
||||||
|
|
||||||
|
def initialize_driver(self, conf):
|
||||||
|
self.conf = conf or cfg.CONF.BGP
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls = (
|
||||||
|
importutils.import_object(self.conf.bgp_speaker_driver,
|
||||||
|
self.conf))
|
||||||
|
except ImportError:
|
||||||
|
LOG.exception(_LE("Error while importing BGP speaker driver %s"),
|
||||||
|
self.conf.bgp_speaker_driver)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
def _handle_driver_failure(self, bgp_speaker_id, method, driver_exec):
|
||||||
|
self.schedule_resync(reason=driver_exec,
|
||||||
|
speaker_id=bgp_speaker_id)
|
||||||
|
LOG.error(_LE('Call to driver for BGP Speaker %(bgp_speaker)s '
|
||||||
|
'%(method)s has failed with exception '
|
||||||
|
'%(driver_exec)s.'),
|
||||||
|
{'bgp_speaker': bgp_speaker_id,
|
||||||
|
'method': method,
|
||||||
|
'driver_exec': driver_exec})
|
||||||
|
|
||||||
def after_start(self):
|
def after_start(self):
|
||||||
self.run()
|
self.run()
|
||||||
LOG.info(_LI("BGP Dynamic Routing agent started"))
|
LOG.info(_LI("BGP Dynamic Routing agent started"))
|
||||||
|
@ -225,9 +248,9 @@ class BgpDrAgent(manager.Manager):
|
||||||
|
|
||||||
def add_bgp_peer_helper(self, bgp_speaker_id, bgp_peer_id):
|
def add_bgp_peer_helper(self, bgp_speaker_id, bgp_peer_id):
|
||||||
"""Add BGP peer."""
|
"""Add BGP peer."""
|
||||||
# Check if the BGP Speaker is already added or not
|
# Ideally BGP Speaker must be added by now, If not then let's
|
||||||
|
# re-sync.
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
||||||
# Something went wrong. Let's re-sync
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Speaker Out-of-sync")
|
reason="BGP Speaker Out-of-sync")
|
||||||
return
|
return
|
||||||
|
@ -243,9 +266,9 @@ class BgpDrAgent(manager.Manager):
|
||||||
|
|
||||||
def add_routes_helper(self, bgp_speaker_id, routes):
|
def add_routes_helper(self, bgp_speaker_id, routes):
|
||||||
"""Advertise routes to BGP speaker."""
|
"""Advertise routes to BGP speaker."""
|
||||||
# Check if the BGP Speaker is already added or not
|
# Ideally BGP Speaker must be added by now, If not then let's
|
||||||
|
# re-sync.
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
||||||
# Something went wrong. Let's re-sync
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Speaker Out-of-sync")
|
reason="BGP Speaker Out-of-sync")
|
||||||
return
|
return
|
||||||
|
@ -260,8 +283,9 @@ class BgpDrAgent(manager.Manager):
|
||||||
|
|
||||||
def withdraw_routes_helper(self, bgp_speaker_id, routes):
|
def withdraw_routes_helper(self, bgp_speaker_id, routes):
|
||||||
"""Withdraw routes advertised by BGP speaker."""
|
"""Withdraw routes advertised by BGP speaker."""
|
||||||
|
# Ideally BGP Speaker must be added by now, If not then let's
|
||||||
|
# re-sync.
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
||||||
# Something went wrong. Let's re-sync
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Speaker Out-of-sync")
|
reason="BGP Speaker Out-of-sync")
|
||||||
return
|
return
|
||||||
|
@ -321,6 +345,13 @@ class BgpDrAgent(manager.Manager):
|
||||||
' speaking for local_as %(local_as)s',
|
' speaking for local_as %(local_as)s',
|
||||||
{'speaker_id': bgp_speaker['id'],
|
{'speaker_id': bgp_speaker['id'],
|
||||||
'local_as': bgp_speaker['local_as']})
|
'local_as': bgp_speaker['local_as']})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.add_bgp_speaker(bgp_speaker['local_as'])
|
||||||
|
except driver_exc.BgpSpeakerAlreadyScheduled:
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker['id'],
|
||||||
|
'add_bgp_speaker', e)
|
||||||
|
|
||||||
# Add peer and route information to the driver.
|
# Add peer and route information to the driver.
|
||||||
self.add_bgp_peers_to_bgp_speaker(bgp_speaker)
|
self.add_bgp_peers_to_bgp_speaker(bgp_speaker)
|
||||||
|
@ -336,9 +367,17 @@ class BgpDrAgent(manager.Manager):
|
||||||
|
|
||||||
LOG.debug('Calling driver for removing BGP speaker %(speaker_as)s',
|
LOG.debug('Calling driver for removing BGP speaker %(speaker_as)s',
|
||||||
{'speaker_as': bgp_speaker_as})
|
{'speaker_as': bgp_speaker_as})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.delete_bgp_speaker(bgp_speaker_as)
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker_id,
|
||||||
|
'remove_bgp_speaker', e)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Something went wrong. Let's re-sync
|
# Ideally, only the added speakers can be removed by the neutron
|
||||||
|
# server. Looks like there might be some synchronization
|
||||||
|
# issue between the server and the agent. Let's initiate a re-sync
|
||||||
|
# to resolve the issue.
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Speaker Out-of-sync")
|
reason="BGP Speaker Out-of-sync")
|
||||||
|
|
||||||
|
@ -363,10 +402,20 @@ class BgpDrAgent(manager.Manager):
|
||||||
{'peer_ip': bgp_peer['peer_ip'],
|
{'peer_ip': bgp_peer['peer_ip'],
|
||||||
'remote_as': bgp_peer['remote_as'],
|
'remote_as': bgp_peer['remote_as'],
|
||||||
'local_as': bgp_speaker_as})
|
'local_as': bgp_speaker_as})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.add_bgp_peer(bgp_speaker_as,
|
||||||
|
bgp_peer['peer_ip'],
|
||||||
|
bgp_peer['remote_as'],
|
||||||
|
bgp_peer['auth_type'],
|
||||||
|
bgp_peer['password'])
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker_id,
|
||||||
|
'add_bgp_peer', e)
|
||||||
|
|
||||||
def remove_bgp_peer_from_bgp_speaker(self, bgp_speaker_id, bgp_peer_ip):
|
def remove_bgp_peer_from_bgp_speaker(self, bgp_speaker_id, bgp_peer_ip):
|
||||||
|
# Ideally BGP Speaker must be added by now, If not then let's
|
||||||
|
# re-sync.
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
||||||
# Something went wrong. Let's re-sync
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Speaker Out-of-sync")
|
reason="BGP Speaker Out-of-sync")
|
||||||
return
|
return
|
||||||
|
@ -381,9 +430,18 @@ class BgpDrAgent(manager.Manager):
|
||||||
'%(peer_ip)s from BGP Speaker running for '
|
'%(peer_ip)s from BGP Speaker running for '
|
||||||
'local_as=%(local_as)d',
|
'local_as=%(local_as)d',
|
||||||
{'peer_ip': bgp_peer_ip, 'local_as': bgp_speaker_as})
|
{'peer_ip': bgp_peer_ip, 'local_as': bgp_speaker_as})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.delete_bgp_peer(bgp_speaker_as,
|
||||||
|
bgp_peer_ip)
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker_id,
|
||||||
|
'remove_bgp_peer', e)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Peer should have been found, Some problem, Let's re-sync
|
# Ideally, only the added peers can be removed by the neutron
|
||||||
|
# server. Looks like there might be some synchronization
|
||||||
|
# issue between the server and the agent. Let's initiate a re-sync
|
||||||
|
# to resolve the issue.
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="BGP Peer Out-of-sync")
|
reason="BGP Peer Out-of-sync")
|
||||||
|
|
||||||
|
@ -406,6 +464,13 @@ class BgpDrAgent(manager.Manager):
|
||||||
'next_hop: %(nexthop)s',
|
'next_hop: %(nexthop)s',
|
||||||
{'cidr': route['destination'],
|
{'cidr': route['destination'],
|
||||||
'nexthop': route['next_hop']})
|
'nexthop': route['next_hop']})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.advertise_route(bgp_speaker_as,
|
||||||
|
route['destination'],
|
||||||
|
route['next_hop'])
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker_id,
|
||||||
|
'advertise_route', e)
|
||||||
|
|
||||||
def withdraw_route_via_bgp_speaker(self, bgp_speaker_id,
|
def withdraw_route_via_bgp_speaker(self, bgp_speaker_id,
|
||||||
bgp_speaker_as, route):
|
bgp_speaker_as, route):
|
||||||
|
@ -415,8 +480,19 @@ class BgpDrAgent(manager.Manager):
|
||||||
'next_hop: %(nexthop)s',
|
'next_hop: %(nexthop)s',
|
||||||
{'cidr': route['destination'],
|
{'cidr': route['destination'],
|
||||||
'nexthop': route['next_hop']})
|
'nexthop': route['next_hop']})
|
||||||
|
try:
|
||||||
|
self.dr_driver_cls.withdraw_route(bgp_speaker_as,
|
||||||
|
route['destination'],
|
||||||
|
route['next_hop'])
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_driver_failure(bgp_speaker_id,
|
||||||
|
'withdraw_route', e)
|
||||||
return
|
return
|
||||||
# Something went wrong. Let's re-sync
|
|
||||||
|
# Ideally, only the advertised routes can be withdrawn by the
|
||||||
|
# neutron server. Looks like there might be some synchronization
|
||||||
|
# issue between the server and the agent. Let's initiate a re-sync
|
||||||
|
# to resolve the issue.
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
self.schedule_resync(speaker_id=bgp_speaker_id,
|
||||||
reason="Advertised routes Out-of-sync")
|
reason="Advertised routes Out-of-sync")
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,18 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
BGP_DRIVER_OPTS = []
|
from oslo_config import cfg
|
||||||
|
|
||||||
BGP_PROTO_CONFIG_OPTS = []
|
from neutron._i18n import _
|
||||||
|
|
||||||
|
BGP_DRIVER_OPTS = [
|
||||||
|
cfg.StrOpt('bgp_speaker_driver',
|
||||||
|
default=None,
|
||||||
|
help=_("BGP speaker driver class to be instantiated."))
|
||||||
|
]
|
||||||
|
|
||||||
|
BGP_PROTO_CONFIG_OPTS = [
|
||||||
|
cfg.StrOpt('bgp_router_id',
|
||||||
|
help=_("32-bit BGP identifier, typically an IPv4 address "
|
||||||
|
"owned by the system running the BGP DrAgent."))
|
||||||
|
]
|
||||||
|
|
|
@ -18,3 +18,10 @@ AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'
|
||||||
BGP_DRAGENT = 'bgp_dragent'
|
BGP_DRAGENT = 'bgp_dragent'
|
||||||
|
|
||||||
BGP_PLUGIN = 'q-bgp-plugin'
|
BGP_PLUGIN = 'q-bgp-plugin'
|
||||||
|
|
||||||
|
# List of supported authentication types.
|
||||||
|
SUPPORTED_AUTH_TYPES = ['none', 'md5']
|
||||||
|
|
||||||
|
# Supported AS number range
|
||||||
|
MIN_ASNUM = 1
|
||||||
|
MAX_ASNUM = 65535
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BgpDriverBase(object):
|
||||||
|
"""Base class for BGP Speaking drivers.
|
||||||
|
|
||||||
|
Any class which provides BGP functionality should extend this
|
||||||
|
defined base class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_bgp_speaker(self, speaker_as):
|
||||||
|
"""Add a BGP speaker.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:raises: BgpSpeakerAlreadyScheduled, BgpSpeakerMaxScheduled,
|
||||||
|
InvalidParamType, InvalidParamRange
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_bgp_speaker(self, speaker_as):
|
||||||
|
"""Deletes BGP speaker.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:raises: BgpSpeakerNotAdded
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
|
||||||
|
auth_type='none', password=None):
|
||||||
|
"""Add a new BGP peer.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:param peer_ip: Specifies the IP address of the peer.
|
||||||
|
:type peer_ip: string
|
||||||
|
:param peer_as: Specifies Autonomous Number of the peer.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type peer_as: integer
|
||||||
|
:param auth_type: Specifies authentication type.
|
||||||
|
By default, authentication will be disabled.
|
||||||
|
:type auth_type: value in SUPPORTED_AUTH_TYPES
|
||||||
|
:param password: Authentication password.By default, authentication
|
||||||
|
will be disabled.
|
||||||
|
:type password: string
|
||||||
|
:raises: BgpSpeakerNotAdded, InvalidParamType, InvalidParamRange,
|
||||||
|
InvaildAuthType, PasswordNotSpecified
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_bgp_peer(self, speaker_as, peer_ip):
|
||||||
|
"""Delete a BGP peer associated with the given peer IP
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:param peer_ip: Specifies the IP address of the peer. Must be the
|
||||||
|
string representation of an IP address.
|
||||||
|
:type peer_ip: string
|
||||||
|
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def advertise_route(self, speaker_as, cidr, nexthop):
|
||||||
|
"""Add a new prefix to advertise.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:param cidr: CIDR of the network to advertise. Must be the string
|
||||||
|
representation of an IP network (e.g., 10.1.1.0/24)
|
||||||
|
:type cidr: string
|
||||||
|
:param nexthop: Specifies the next hop address for the above
|
||||||
|
prefix.
|
||||||
|
:type nexthop: string
|
||||||
|
:raises: BgpSpeakerNotAdded, InvalidParamType
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def withdraw_route(self, speaker_as, cidr, nexthop=None):
|
||||||
|
"""Withdraw an advertised prefix.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:param cidr: CIDR of the network to withdraw. Must be the string
|
||||||
|
representation of an IP network (e.g., 10.1.1.0/24)
|
||||||
|
:type cidr: string
|
||||||
|
:param nexthop: Specifies the next hop address for the above
|
||||||
|
prefix.
|
||||||
|
:type nexthop: string
|
||||||
|
:raises: BgpSpeakerNotAdded, RouteNotAdvertised, InvalidParamType
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_bgp_speaker_statistics(self, speaker_as):
|
||||||
|
"""Collect BGP Speaker statistics.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:raises: BgpSpeakerNotAdded
|
||||||
|
:returns: bgp_speaker_stats: string
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_bgp_peer_statistics(self, speaker_as, peer_ip, peer_as):
|
||||||
|
"""Collect BGP Peer statistics.
|
||||||
|
|
||||||
|
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
||||||
|
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type speaker_as: integer
|
||||||
|
:param peer_ip: Specifies the IP address of the peer.
|
||||||
|
:type peer_ip: string
|
||||||
|
:param peer_as: Specifies the AS number of the peer. Must be an
|
||||||
|
integer between MIN_ASNUM and MAX_ASNUM.
|
||||||
|
:type peer_as: integer .
|
||||||
|
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
|
||||||
|
:returns: bgp_peer_stats: string
|
||||||
|
"""
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
|
|
||||||
|
|
||||||
|
# BGP Driver Exceptions
|
||||||
|
class BgpSpeakerNotAdded(n_exc.BadRequest):
|
||||||
|
message = _("BGP Speaker for local_as=%(local_as)s with "
|
||||||
|
"router_id=%(rtid)s not added yet.")
|
||||||
|
|
||||||
|
|
||||||
|
class BgpSpeakerMaxScheduled(n_exc.BadRequest):
|
||||||
|
message = _("Already hosting maximum number of BGP Speakers. "
|
||||||
|
"Allowed scheduled count=%(count)d")
|
||||||
|
|
||||||
|
|
||||||
|
class BgpSpeakerAlreadyScheduled(n_exc.Conflict):
|
||||||
|
message = _("Already hosting BGP Speaker for local_as=%(current_as)d with "
|
||||||
|
"router_id=%(rtid)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class BgpPeerNotAdded(n_exc.BadRequest):
|
||||||
|
message = _("BGP Peer %(peer_ip)s for remote_as=%(remote_as)s, running "
|
||||||
|
"for BGP Speaker %(speaker_as)d not added yet.")
|
||||||
|
|
||||||
|
|
||||||
|
class RouteNotAdvertised(n_exc.BadRequest):
|
||||||
|
message = _("Route %(cidr)s not advertised for BGP Speaker "
|
||||||
|
"%(speaker_as)d.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidParamType(n_exc.NeutronException):
|
||||||
|
message = _("Parameter %(param)s must be of %(param_type)s type.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidParamRange(n_exc.NeutronException):
|
||||||
|
message = _("%(param)s must be in %(range)s range.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvaildAuthType(n_exc.BadRequest):
|
||||||
|
message = _("Authentication type not supported. Requested "
|
||||||
|
"type=%(auth_type)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordNotSpecified(n_exc.BadRequest):
|
||||||
|
message = _("Password not specified for authentication "
|
||||||
|
"type=%(auth_type)s.")
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from ryu.services.protocols.bgp import bgpspeaker
|
||||||
|
from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE_ACTIVE
|
||||||
|
|
||||||
|
from neutron.services.bgp.driver import base
|
||||||
|
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
||||||
|
from neutron.services.bgp.driver import utils
|
||||||
|
from neutron._i18n import _LE, _LI
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Function for logging BGP peer and path changes.
|
||||||
|
def bgp_peer_down_cb(remote_ip, remote_as):
|
||||||
|
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d went DOWN.'),
|
||||||
|
{'peer_ip': remote_ip, 'peer_as': remote_as})
|
||||||
|
|
||||||
|
|
||||||
|
def bgp_peer_up_cb(remote_ip, remote_as):
|
||||||
|
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d is UP.'),
|
||||||
|
{'peer_ip': remote_ip, 'peer_as': remote_as})
|
||||||
|
|
||||||
|
|
||||||
|
def best_path_change_cb(event):
|
||||||
|
LOG.info(_LI("Best path change observed. cidr=%(prefix)s, "
|
||||||
|
"nexthop=%(nexthop)s, remote_as=%(remote_as)d, "
|
||||||
|
"is_withdraw=%(is_withdraw)s"),
|
||||||
|
{'prefix': event.prefix, 'nexthop': event.nexthop,
|
||||||
|
'remote_as': event.remote_as,
|
||||||
|
'is_withdraw': event.is_withdraw})
|
||||||
|
|
||||||
|
|
||||||
|
class RyuBgpDriver(base.BgpDriverBase):
|
||||||
|
"""BGP speaker implementation via Ryu."""
|
||||||
|
|
||||||
|
def __init__(self, cfg):
|
||||||
|
LOG.info(_LI('Initializing Ryu driver for BGP Speaker functionality.'))
|
||||||
|
self._read_config(cfg)
|
||||||
|
|
||||||
|
# Note: Even though Ryu can only support one BGP speaker as of now,
|
||||||
|
# we have tried making the framework generic for the future purposes.
|
||||||
|
self.cache = utils.BgpMultiSpeakerCache()
|
||||||
|
|
||||||
|
def _read_config(self, cfg):
|
||||||
|
if cfg is None or cfg.bgp_router_id is None:
|
||||||
|
# If either cfg or router_id is not specified, raise voice
|
||||||
|
LOG.error(_LE('BGP router-id MUST be specified for the correct '
|
||||||
|
'functional working.'))
|
||||||
|
else:
|
||||||
|
self.routerid = cfg.bgp_router_id
|
||||||
|
LOG.info(_LI('Initialized Ryu BGP Speaker driver interface with '
|
||||||
|
'bgp_router_id=%s'), self.routerid)
|
||||||
|
|
||||||
|
def add_bgp_speaker(self, speaker_as):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if curr_speaker is not None:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerAlreadyScheduled(
|
||||||
|
current_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
|
||||||
|
# Ryu can only support One speaker
|
||||||
|
if self.cache.get_hosted_bgp_speakers_count() == 1:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerMaxScheduled(count=1)
|
||||||
|
|
||||||
|
# Validate input parameters.
|
||||||
|
# speaker_as must be an integer in the allowed range.
|
||||||
|
utils.validate_as_num('local_as', speaker_as)
|
||||||
|
|
||||||
|
# Notify Ryu about BGP Speaker addition.
|
||||||
|
# Please note: Since, only the route-advertisement support is
|
||||||
|
# implemented we are explicitly setting the bgp_server_port
|
||||||
|
# attribute to 0 which disables listening on port 179.
|
||||||
|
curr_speaker = bgpspeaker.BGPSpeaker(as_number=speaker_as,
|
||||||
|
router_id=self.routerid, bgp_server_port=0,
|
||||||
|
best_path_change_handler=best_path_change_cb,
|
||||||
|
peer_down_handler=bgp_peer_down_cb,
|
||||||
|
peer_up_handler=bgp_peer_up_cb)
|
||||||
|
LOG.info(_LI('Added BGP Speaker for local_as=%(as)d with '
|
||||||
|
'router_id= %(rtid)s.'),
|
||||||
|
{'as': speaker_as, 'rtid': self.routerid})
|
||||||
|
|
||||||
|
self.cache.put_bgp_speaker(speaker_as, curr_speaker)
|
||||||
|
|
||||||
|
def delete_bgp_speaker(self, speaker_as):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
# Notify Ryu about BGP Speaker deletion
|
||||||
|
curr_speaker.shutdown()
|
||||||
|
LOG.info(_LI('Removed BGP Speaker for local_as=%(as)d with '
|
||||||
|
'router_id=%(rtid)s.'),
|
||||||
|
{'as': speaker_as, 'rtid': self.routerid})
|
||||||
|
self.cache.remove_bgp_speaker(speaker_as)
|
||||||
|
|
||||||
|
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
|
||||||
|
auth_type='none', password=None):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
|
||||||
|
# Validate peer_ip and peer_as.
|
||||||
|
utils.validate_as_num('remote_as', peer_as)
|
||||||
|
utils.validate_string(peer_ip)
|
||||||
|
utils.validate_auth(auth_type, password)
|
||||||
|
|
||||||
|
# Notify Ryu about BGP Peer addition
|
||||||
|
curr_speaker.neighbor_add(address=peer_ip,
|
||||||
|
remote_as=peer_as,
|
||||||
|
password=password,
|
||||||
|
connect_mode=CONNECT_MODE_ACTIVE)
|
||||||
|
LOG.info(_LI('Added BGP Peer %(peer)s for remote_as=%(as)d to '
|
||||||
|
'BGP Speaker running for local_as=%(local_as)d.'),
|
||||||
|
{'peer': peer_ip, 'as': peer_as, 'local_as': speaker_as})
|
||||||
|
|
||||||
|
def delete_bgp_peer(self, speaker_as, peer_ip):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
# Validate peer_ip. It must be a string.
|
||||||
|
utils.validate_string(peer_ip)
|
||||||
|
|
||||||
|
# Notify Ryu about BGP Peer removal
|
||||||
|
curr_speaker.neighbor_del(address=peer_ip)
|
||||||
|
LOG.info(_LI('Removed BGP Peer %(peer)s from BGP Speaker '
|
||||||
|
'running for local_as=%(local_as)d.'),
|
||||||
|
{'peer': peer_ip, 'local_as': speaker_as})
|
||||||
|
|
||||||
|
def advertise_route(self, speaker_as, cidr, nexthop):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
|
||||||
|
# Validate cidr and nexthop. Both must be strings.
|
||||||
|
utils.validate_string(cidr)
|
||||||
|
utils.validate_string(nexthop)
|
||||||
|
|
||||||
|
# Notify Ryu about route advertisement
|
||||||
|
curr_speaker.prefix_add(prefix=cidr, next_hop=nexthop)
|
||||||
|
LOG.info(_LI('Route cidr=%(prefix)s, nexthop=%(nexthop)s is '
|
||||||
|
'advertised for BGP Speaker running for '
|
||||||
|
'local_as=%(local_as)d.'),
|
||||||
|
{'prefix': cidr, 'nexthop': nexthop, 'local_as': speaker_as})
|
||||||
|
|
||||||
|
def withdraw_route(self, speaker_as, cidr, nexthop=None):
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
# Validate cidr. It must be a string.
|
||||||
|
utils.validate_string(cidr)
|
||||||
|
|
||||||
|
# Notify Ryu about route withdrawal
|
||||||
|
curr_speaker.prefix_del(prefix=cidr)
|
||||||
|
LOG.info(_LI('Route cidr=%(prefix)s is withdrawn from BGP Speaker '
|
||||||
|
'running for local_as=%(local_as)d.'),
|
||||||
|
{'prefix': cidr, 'local_as': speaker_as})
|
||||||
|
|
||||||
|
def get_bgp_speaker_statistics(self, speaker_as):
|
||||||
|
LOG.info(_LI('Collecting BGP Speaker statistics for local_as=%d.'),
|
||||||
|
speaker_as)
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
|
||||||
|
# TODO(vikram): Filter and return the necessary information.
|
||||||
|
# Will be done as part of new RFE requirement
|
||||||
|
# https://bugs.launchpad.net/neutron/+bug/1527993
|
||||||
|
return curr_speaker.neighbor_state_get()
|
||||||
|
|
||||||
|
def get_bgp_peer_statistics(self, speaker_as, peer_ip):
|
||||||
|
LOG.info(_LI('Collecting BGP Peer statistics for peer_ip=%(peer)s, '
|
||||||
|
'running in speaker_as=%(speaker_as)d '),
|
||||||
|
{'peer': peer_ip, 'speaker_as': speaker_as})
|
||||||
|
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
||||||
|
if not curr_speaker:
|
||||||
|
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
||||||
|
rtid=self.routerid)
|
||||||
|
|
||||||
|
# TODO(vikram): Filter and return the necessary information.
|
||||||
|
# Will be done as part of new RFE requirement
|
||||||
|
# https://bugs.launchpad.net/neutron/+bug/1527993
|
||||||
|
return curr_speaker.neighbor_state_get(address=peer_ip)
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from neutron.services.bgp.common import constants as bgp_consts
|
||||||
|
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
||||||
|
|
||||||
|
|
||||||
|
# Parameter validation functions provided are provided by the base.
|
||||||
|
def validate_as_num(param, as_num):
|
||||||
|
if not isinstance(as_num, six.integer_types):
|
||||||
|
raise bgp_driver_exc.InvalidParamType(param=param,
|
||||||
|
param_type='integer')
|
||||||
|
|
||||||
|
if not (bgp_consts.MIN_ASNUM <= as_num <= bgp_consts.MAX_ASNUM):
|
||||||
|
# Must be in [AS_NUM_MIN, AS_NUM_MAX] range.
|
||||||
|
allowed_range = ('[' +
|
||||||
|
str(bgp_consts.MIN_ASNUM) + '-' +
|
||||||
|
str(bgp_consts.MAX_ASNUM) +
|
||||||
|
']')
|
||||||
|
raise bgp_driver_exc.InvalidParamRange(param=param,
|
||||||
|
range=allowed_range)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_auth(auth_type, password):
|
||||||
|
validate_string(password)
|
||||||
|
if auth_type in bgp_consts.SUPPORTED_AUTH_TYPES:
|
||||||
|
if auth_type != 'none' and password is None:
|
||||||
|
raise bgp_driver_exc.PasswordNotSpecified(auth_type=auth_type)
|
||||||
|
if auth_type == 'none' and password is not None:
|
||||||
|
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
|
||||||
|
else:
|
||||||
|
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_string(param):
|
||||||
|
if param is not None:
|
||||||
|
if not isinstance(param, six.string_types):
|
||||||
|
raise bgp_driver_exc.InvalidParamType(param=param,
|
||||||
|
param_type='string')
|
||||||
|
|
||||||
|
|
||||||
|
class BgpMultiSpeakerCache(object):
|
||||||
|
"""Class for saving multiple BGP speakers information.
|
||||||
|
|
||||||
|
Version history:
|
||||||
|
1.0 - Initial version for caching multiple BGP speaker information.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def get_hosted_bgp_speakers_count(self):
|
||||||
|
return len(self.cache)
|
||||||
|
|
||||||
|
def put_bgp_speaker(self, local_as, speaker):
|
||||||
|
self.cache[local_as] = speaker
|
||||||
|
|
||||||
|
def get_bgp_speaker(self, local_as):
|
||||||
|
return self.cache.get(local_as)
|
||||||
|
|
||||||
|
def remove_bgp_speaker(self, local_as):
|
||||||
|
self.cache.pop(local_as, None)
|
|
@ -329,3 +329,9 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase,
|
||||||
self.assertEqual(1, len(speaker['networks']))
|
self.assertEqual(1, len(speaker['networks']))
|
||||||
self.assertEqual(network_id,
|
self.assertEqual(network_id,
|
||||||
speaker['networks'][0])
|
speaker['networks'][0])
|
||||||
|
|
||||||
|
def test_create_bgp_peer_md5_auth_no_password(self):
|
||||||
|
bgp_peer = {'bgp_peer': {'auth_type': 'md5', 'password': None}}
|
||||||
|
self.assertRaises(bgp.InvalidBgpPeerMd5Authentication,
|
||||||
|
self.bgp_plugin.create_bgp_peer,
|
||||||
|
self.context, bgp_peer)
|
||||||
|
|
|
@ -40,12 +40,14 @@ FAKE_BGP_SPEAKER = {'id': FAKE_BGPSPEAKER_UUID,
|
||||||
'local_as': 12345,
|
'local_as': 12345,
|
||||||
'peers': [{'remote_as': '2345',
|
'peers': [{'remote_as': '2345',
|
||||||
'peer_ip': '1.1.1.1',
|
'peer_ip': '1.1.1.1',
|
||||||
|
'auth_type': 'none',
|
||||||
'password': ''}],
|
'password': ''}],
|
||||||
'advertised_routes': []}
|
'advertised_routes': []}
|
||||||
|
|
||||||
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
|
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
|
||||||
'remote_as': '2345',
|
'remote_as': '2345',
|
||||||
'peer_ip': '1.1.1.1',
|
'peer_ip': '1.1.1.1',
|
||||||
|
'auth_type': 'none',
|
||||||
'password': ''}
|
'password': ''}
|
||||||
|
|
||||||
FAKE_ROUTE = {'id': FAKE_BGPSPEAKER_UUID,
|
FAKE_ROUTE = {'id': FAKE_BGPSPEAKER_UUID,
|
||||||
|
@ -65,6 +67,9 @@ class TestBgpDrAgent(base.BaseTestCase):
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
||||||
mock_log_p = mock.patch.object(bgp_dragent, 'LOG')
|
mock_log_p = mock.patch.object(bgp_dragent, 'LOG')
|
||||||
self.mock_log = mock_log_p.start()
|
self.mock_log = mock_log_p.start()
|
||||||
|
self.driver_cls_p = mock.patch(
|
||||||
|
'neutron.services.bgp.agent.bgp_dragent.importutils.import_class')
|
||||||
|
self.driver_cls = self.driver_cls_p.start()
|
||||||
self.context = context.get_admin_context()
|
self.context = context.get_admin_context()
|
||||||
|
|
||||||
def test_bgp_dragent_manager(self):
|
def test_bgp_dragent_manager(self):
|
||||||
|
@ -446,7 +451,7 @@ class TestBgpDrAgent(base.BaseTestCase):
|
||||||
|
|
||||||
def test_add_bgp_peer_not_cached(self):
|
def test_add_bgp_peer_not_cached(self):
|
||||||
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
||||||
'password': 'abc'}
|
'auth_type': 'md5', 'password': 'abc'}
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
||||||
'peers': {},
|
'peers': {},
|
||||||
'advertised_routes': []}}
|
'advertised_routes': []}}
|
||||||
|
@ -455,7 +460,7 @@ class TestBgpDrAgent(base.BaseTestCase):
|
||||||
|
|
||||||
def test_add_bgp_peer_already_cached(self):
|
def test_add_bgp_peer_already_cached(self):
|
||||||
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
||||||
'password': 'abc'}
|
'auth_type': 'md5', 'password': 'abc'}
|
||||||
cached_peers = {'1.1.1.1': {'peer_ip': '1.1.1.1', 'remote_as': 34567}}
|
cached_peers = {'1.1.1.1': {'peer_ip': '1.1.1.1', 'remote_as': 34567}}
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
||||||
'peers': cached_peers,
|
'peers': cached_peers,
|
||||||
|
@ -521,6 +526,10 @@ class TestBgpDrAgentEventHandler(base.BaseTestCase):
|
||||||
self.cache = mock.Mock()
|
self.cache = mock.Mock()
|
||||||
cache_cls.return_value = self.cache
|
cache_cls.return_value = self.cache
|
||||||
|
|
||||||
|
self.driver_cls_p = mock.patch(
|
||||||
|
'neutron.services.bgp.agent.bgp_dragent.importutils.import_class')
|
||||||
|
self.driver_cls = self.driver_cls_p.start()
|
||||||
|
|
||||||
self.bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
self.bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
||||||
self.schedule_full_resync_p = mock.patch.object(
|
self.schedule_full_resync_p = mock.patch.object(
|
||||||
self.bgp_dr, 'schedule_full_resync')
|
self.bgp_dr, 'schedule_full_resync')
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
from ryu.services.protocols.bgp import bgpspeaker
|
||||||
|
from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE_ACTIVE
|
||||||
|
|
||||||
|
from neutron.services.bgp.agent import config as bgp_config
|
||||||
|
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
||||||
|
from neutron.services.bgp.driver.ryu import driver as ryu_driver
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
# Test variables for BGP Speaker
|
||||||
|
FAKE_LOCAL_AS1 = 12345
|
||||||
|
FAKE_LOCAL_AS2 = 23456
|
||||||
|
FAKE_ROUTER_ID = '1.1.1.1'
|
||||||
|
|
||||||
|
# Test variables for BGP Peer
|
||||||
|
FAKE_PEER_AS = 45678
|
||||||
|
FAKE_PEER_IP = '2.2.2.5'
|
||||||
|
FAKE_AUTH_TYPE = 'md5'
|
||||||
|
FAKE_PEER_PASSWORD = 'awesome'
|
||||||
|
|
||||||
|
# Test variables for Route
|
||||||
|
FAKE_ROUTE = '2.2.2.0/24'
|
||||||
|
FAKE_NEXTHOP = '5.5.5.5'
|
||||||
|
|
||||||
|
|
||||||
|
class TestRyuBgpDriver(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRyuBgpDriver, self).setUp()
|
||||||
|
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
||||||
|
cfg.CONF.set_override('bgp_router_id', FAKE_ROUTER_ID, 'BGP')
|
||||||
|
self.ryu_bgp_driver = ryu_driver.RyuBgpDriver(cfg.CONF.BGP)
|
||||||
|
mock_ryu_speaker_p = mock.patch.object(bgpspeaker, 'BGPSpeaker')
|
||||||
|
self.mock_ryu_speaker = mock_ryu_speaker_p.start()
|
||||||
|
|
||||||
|
def test_add_new_bgp_speaker(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.mock_ryu_speaker.assert_called_once_with(
|
||||||
|
as_number=FAKE_LOCAL_AS1, router_id=FAKE_ROUTER_ID,
|
||||||
|
bgp_server_port=0,
|
||||||
|
best_path_change_handler=ryu_driver.best_path_change_cb,
|
||||||
|
peer_down_handler=ryu_driver.bgp_peer_down_cb,
|
||||||
|
peer_up_handler=ryu_driver.bgp_peer_up_cb)
|
||||||
|
|
||||||
|
def test_remove_bgp_speaker(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(0,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.assertEqual(1, speaker.shutdown.call_count)
|
||||||
|
|
||||||
|
def test_add_bgp_peer_without_password(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
|
||||||
|
FAKE_PEER_IP,
|
||||||
|
FAKE_PEER_AS)
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
speaker.neighbor_add.assert_called_once_with(
|
||||||
|
address=FAKE_PEER_IP,
|
||||||
|
remote_as=FAKE_PEER_AS,
|
||||||
|
password=None,
|
||||||
|
connect_mode=CONNECT_MODE_ACTIVE)
|
||||||
|
|
||||||
|
def test_add_bgp_peer_with_password(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
|
||||||
|
FAKE_PEER_IP,
|
||||||
|
FAKE_PEER_AS,
|
||||||
|
FAKE_AUTH_TYPE,
|
||||||
|
FAKE_PEER_PASSWORD)
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
speaker.neighbor_add.assert_called_once_with(
|
||||||
|
address=FAKE_PEER_IP,
|
||||||
|
remote_as=FAKE_PEER_AS,
|
||||||
|
password=FAKE_PEER_PASSWORD,
|
||||||
|
connect_mode=CONNECT_MODE_ACTIVE)
|
||||||
|
|
||||||
|
def test_remove_bgp_peer(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.delete_bgp_peer(FAKE_LOCAL_AS1, FAKE_PEER_IP)
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
speaker.neighbor_del.assert_called_once_with(address=FAKE_PEER_IP)
|
||||||
|
|
||||||
|
def test_advertise_route(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.advertise_route(FAKE_LOCAL_AS1,
|
||||||
|
FAKE_ROUTE,
|
||||||
|
FAKE_NEXTHOP)
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
speaker.prefix_add.assert_called_once_with(prefix=FAKE_ROUTE,
|
||||||
|
next_hop=FAKE_NEXTHOP)
|
||||||
|
|
||||||
|
def test_withdraw_route(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.withdraw_route(FAKE_LOCAL_AS1, FAKE_ROUTE)
|
||||||
|
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
speaker.prefix_del.assert_called_once_with(prefix=FAKE_ROUTE)
|
||||||
|
|
||||||
|
def test_add_same_bgp_speakers_twice(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerAlreadyScheduled,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker, FAKE_LOCAL_AS1)
|
||||||
|
|
||||||
|
def test_add_different_bgp_speakers_when_one_already_added(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker,
|
||||||
|
FAKE_LOCAL_AS2)
|
||||||
|
|
||||||
|
def test_add_bgp_speaker_with_invalid_asnum_paramtype(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker, '12345')
|
||||||
|
|
||||||
|
def test_add_bgp_speaker_with_invalid_asnum_range(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker, -1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker, 65536)
|
||||||
|
|
||||||
|
def test_add_bgp_peer_with_invalid_paramtype(self):
|
||||||
|
# Test with an invalid asnum data-type
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, '12345')
|
||||||
|
# Test with an invalid auth-type and an invalid password
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
||||||
|
'sha-1', 1234)
|
||||||
|
# Test with an invalid auth-type and a valid password
|
||||||
|
self.assertRaises(bgp_driver_exc.InvaildAuthType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
||||||
|
'hmac-md5', FAKE_PEER_PASSWORD)
|
||||||
|
# Test with none auth-type and a valid password
|
||||||
|
self.assertRaises(bgp_driver_exc.InvaildAuthType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
||||||
|
'none', FAKE_PEER_PASSWORD)
|
||||||
|
# Test with none auth-type and an invalid password
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
||||||
|
'none', 1234)
|
||||||
|
# Test with a valid auth-type and no password
|
||||||
|
self.assertRaises(bgp_driver_exc.PasswordNotSpecified,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
||||||
|
FAKE_AUTH_TYPE, None)
|
||||||
|
|
||||||
|
def test_add_bgp_peer_with_invalid_asnum_range(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, -1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, 65536)
|
||||||
|
|
||||||
|
def test_add_bgp_peer_without_adding_speaker(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
||||||
|
self.ryu_bgp_driver.add_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS)
|
||||||
|
|
||||||
|
def test_remove_bgp_peer_with_invalid_paramtype(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.delete_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, 12345)
|
||||||
|
|
||||||
|
def test_remove_bgp_peer_without_adding_speaker(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
||||||
|
self.ryu_bgp_driver.delete_bgp_peer,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_PEER_IP)
|
||||||
|
|
||||||
|
def test_advertise_route_with_invalid_paramtype(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.advertise_route,
|
||||||
|
FAKE_LOCAL_AS1, 12345, FAKE_NEXTHOP)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.advertise_route,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_ROUTE, 12345)
|
||||||
|
|
||||||
|
def test_advertise_route_without_adding_speaker(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
||||||
|
self.ryu_bgp_driver.advertise_route,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_ROUTE, FAKE_NEXTHOP)
|
||||||
|
|
||||||
|
def test_withdraw_route_with_invalid_paramtype(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.withdraw_route,
|
||||||
|
FAKE_LOCAL_AS1, 12345)
|
||||||
|
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
||||||
|
self.ryu_bgp_driver.withdraw_route,
|
||||||
|
FAKE_LOCAL_AS1, 12345)
|
||||||
|
|
||||||
|
def test_withdraw_route_without_adding_speaker(self):
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
||||||
|
self.ryu_bgp_driver.withdraw_route,
|
||||||
|
FAKE_LOCAL_AS1, FAKE_ROUTE)
|
||||||
|
|
||||||
|
def test_add_multiple_bgp_speakers(self):
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
|
||||||
|
self.ryu_bgp_driver.add_bgp_speaker,
|
||||||
|
FAKE_LOCAL_AS2)
|
||||||
|
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
||||||
|
self.ryu_bgp_driver.delete_bgp_speaker,
|
||||||
|
FAKE_LOCAL_AS2)
|
||||||
|
self.assertEqual(1,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
||||||
|
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
|
||||||
|
self.assertEqual(0,
|
||||||
|
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from neutron.services.bgp.driver import utils
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
FAKE_LOCAL_AS = 12345
|
||||||
|
FAKE_RYU_SPEAKER = {}
|
||||||
|
|
||||||
|
|
||||||
|
class TestBgpMultiSpeakerCache(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBgpMultiSpeakerCache, self).setUp()
|
||||||
|
self.expected_cache = {FAKE_LOCAL_AS: FAKE_RYU_SPEAKER}
|
||||||
|
self.bs_cache = utils.BgpMultiSpeakerCache()
|
||||||
|
|
||||||
|
def test_put_bgp_speaker(self):
|
||||||
|
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
||||||
|
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
||||||
|
|
||||||
|
def test_remove_bgp_speaker(self):
|
||||||
|
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
||||||
|
self.assertEqual(1, len(self.bs_cache.cache))
|
||||||
|
self.bs_cache.remove_bgp_speaker(FAKE_LOCAL_AS)
|
||||||
|
self.assertEqual(0, len(self.bs_cache.cache))
|
||||||
|
|
||||||
|
def test_get_bgp_speaker(self):
|
||||||
|
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
||||||
|
self.assertEqual(
|
||||||
|
FAKE_RYU_SPEAKER,
|
||||||
|
self.bs_cache.get_bgp_speaker(FAKE_LOCAL_AS))
|
||||||
|
|
||||||
|
def test_get_hosted_bgp_speakers_count(self):
|
||||||
|
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
||||||
|
self.assertEqual(1, self.bs_cache.get_hosted_bgp_speakers_count())
|
Loading…
Reference in New Issue