Merge "BGP Dynamic Routing: introduce BgpDriver"
This commit is contained in:
commit
85abeded9e
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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."))
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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