Merge "BGP Dynamic Routing: introduce BgpDriver"

This commit is contained in:
Jenkins 2016-03-01 04:45:27 +00:00 committed by Gerrit Code Review
commit 85abeded9e
18 changed files with 925 additions and 19 deletions

View File

@ -1,3 +1,5 @@
RYU_BGP_SPEAKER_DRIVER="neutron.services.bgp.driver.ryu.driver.RyuBgpDriver"
function configure_bgp_service_plugin {
_neutron_service_plugin_class_add "bgp"
}
@ -14,6 +16,10 @@ function configure_bgp_dragent {
if [ -n "$BGP_ROUTER_ID" ]; then
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_router_id $BGP_ROUTER_ID
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 {
@ -22,4 +28,4 @@ function start_bgp_dragent {
function stop_bgp_dragent {
stop_process q-bgp-agt
}
}

View File

@ -206,6 +206,11 @@ class BgpDbMixin(common_db.CommonDbMixin):
def create_bgp_peer(self, context, bgp_peer):
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):
res_keys = ['tenant_id', 'name', 'remote_as', 'peer_ip',
'auth_type', 'password']

View File

@ -19,13 +19,13 @@ from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import resource_helper as rh
from neutron.common import exceptions
from neutron.services.bgp.common import constants as bgp_consts
BGP_EXT_ALIAS = 'bgp'
BGP_SPEAKER_RESOURCE_NAME = 'bgp-speaker'
BGP_SPEAKER_BODY_KEY_NAME = 'bgp_speaker'
BGP_PEER_BODY_KEY_NAME = 'bgp_peer'
bgp_supported_auth_types = ['none', 'md5']
RESOURCE_ATTRIBUTE_MAP = {
BGP_SPEAKER_RESOURCE_NAME + 's': {
@ -36,7 +36,8 @@ RESOURCE_ATTRIBUTE_MAP = {
'validate': {'type:string': attr.NAME_MAX_LEN},
'is_visible': True, 'default': ''},
'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,
'required_by_policy': False,
'enforce_policy': False},
@ -88,13 +89,15 @@ RESOURCE_ATTRIBUTE_MAP = {
'validate': {'type:ip_address': None},
'is_visible': True},
'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,
'required_by_policy': False,
'enforce_policy': False},
'auth_type': {'allow_post': True, 'allow_put': False,
'required_by_policy': True,
'validate': {'type:values': bgp_supported_auth_types},
'validate': {'type:values':
bgp_consts.SUPPORTED_AUTH_TYPES},
'is_visible': True},
'password': {'allow_post': True, 'allow_put': True,
'required_by_policy': True,
@ -147,6 +150,10 @@ class DuplicateBgpPeerIpException(exceptions.Conflict):
"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):
@classmethod

View File

@ -20,6 +20,7 @@ from oslo_log import log as logging
import oslo_messaging
from oslo_service import loopingcall
from oslo_service import periodic_task
from oslo_utils import importutils
from neutron.agent import rpc as agent_rpc
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 import manager
from neutron.services.bgp.common import constants as bgp_consts
from neutron.services.bgp.driver import exceptions as driver_exc
LOG = logging.getLogger(__name__)
@ -52,7 +54,7 @@ class BgpDrAgent(manager.Manager):
def __init__(self, host, conf=None):
super(BgpDrAgent, self).__init__()
self.conf = conf
self.initialize_driver(conf)
self.needs_resync_reasons = collections.defaultdict(list)
self.needs_full_sync_reason = None
@ -61,6 +63,27 @@ class BgpDrAgent(manager.Manager):
self.plugin_rpc = BgpDrPluginApi(bgp_consts.BGP_PLUGIN,
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):
self.run()
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):
"""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):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
@ -243,9 +266,9 @@ class BgpDrAgent(manager.Manager):
def add_routes_helper(self, bgp_speaker_id, routes):
"""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):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
@ -260,8 +283,9 @@ class BgpDrAgent(manager.Manager):
def withdraw_routes_helper(self, bgp_speaker_id, routes):
"""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):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
@ -321,6 +345,13 @@ class BgpDrAgent(manager.Manager):
' speaking for local_as %(local_as)s',
{'speaker_id': bgp_speaker['id'],
'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.
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',
{'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
# 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,
reason="BGP Speaker Out-of-sync")
@ -363,10 +402,20 @@ class BgpDrAgent(manager.Manager):
{'peer_ip': bgp_peer['peer_ip'],
'remote_as': bgp_peer['remote_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):
# 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):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
@ -381,9 +430,18 @@ class BgpDrAgent(manager.Manager):
'%(peer_ip)s from BGP Speaker running for '
'local_as=%(local_as)d',
{'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
# 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,
reason="BGP Peer Out-of-sync")
@ -406,6 +464,13 @@ class BgpDrAgent(manager.Manager):
'next_hop: %(nexthop)s',
{'cidr': route['destination'],
'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,
bgp_speaker_as, route):
@ -415,8 +480,19 @@ class BgpDrAgent(manager.Manager):
'next_hop: %(nexthop)s',
{'cidr': route['destination'],
'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
# 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,
reason="Advertised routes Out-of-sync")

View File

@ -13,6 +13,18 @@
# See the License for the specific language governing permissions and
# 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."))
]

View File

@ -18,3 +18,10 @@ AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'
BGP_DRAGENT = 'bgp_dragent'
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

View File

View File

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

View File

@ -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.")

View File

@ -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)

View File

@ -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)

View File

@ -329,3 +329,9 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase,
self.assertEqual(1, len(speaker['networks']))
self.assertEqual(network_id,
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)

View File

@ -40,12 +40,14 @@ FAKE_BGP_SPEAKER = {'id': FAKE_BGPSPEAKER_UUID,
'local_as': 12345,
'peers': [{'remote_as': '2345',
'peer_ip': '1.1.1.1',
'auth_type': 'none',
'password': ''}],
'advertised_routes': []}
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
'remote_as': '2345',
'peer_ip': '1.1.1.1',
'auth_type': 'none',
'password': ''}
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')
mock_log_p = mock.patch.object(bgp_dragent, 'LOG')
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()
def test_bgp_dragent_manager(self):
@ -446,7 +451,7 @@ class TestBgpDrAgent(base.BaseTestCase):
def test_add_bgp_peer_not_cached(self):
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},
'peers': {},
'advertised_routes': []}}
@ -455,7 +460,7 @@ class TestBgpDrAgent(base.BaseTestCase):
def test_add_bgp_peer_already_cached(self):
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_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': cached_peers,
@ -521,6 +526,10 @@ class TestBgpDrAgentEventHandler(base.BaseTestCase):
self.cache = mock.Mock()
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.schedule_full_resync_p = mock.patch.object(
self.bgp_dr, 'schedule_full_resync')

View File

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

View File

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