BGP Dynamic Routing: introduce BgpDriver
This patch introduces BgpDriver interface which will be used by the BgpDrAgent for passing BGP speaker, peer and route information to the registered BGP driver. In addition, this patch also implements Ryu as a reference BGP driver for the proof of concept. Partially-Implements: blueprint bgp-dynamic-routing Co-Authored-By: Ryan Tidwell <ryan.tidwell@hp.com> Co-Authored-By: Jaume Devesa <devvesa@gmail.com> Co-Authored-By: vikram.choudhary <vikram.choudhary@huawei.com> Co-Authored-By: Numan Siddique <nusiddiq@redhat.com> Change-Id: If9a7e2c4c45c395b8e93bd1293667bf67f53dcfa
This commit is contained in:
parent
3f153b485a
commit
b33c4dd2ce
@ -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
neutron/services/bgp/driver/__init__.py
Normal file
0
neutron/services/bgp/driver/__init__.py
Normal file
142
neutron/services/bgp/driver/base.py
Normal file
142
neutron/services/bgp/driver/base.py
Normal 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
|
||||
"""
|
61
neutron/services/bgp/driver/exceptions.py
Normal file
61
neutron/services/bgp/driver/exceptions.py
Normal 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.")
|
0
neutron/services/bgp/driver/ryu/__init__.py
Normal file
0
neutron/services/bgp/driver/ryu/__init__.py
Normal file
202
neutron/services/bgp/driver/ryu/driver.py
Normal file
202
neutron/services/bgp/driver/ryu/driver.py
Normal 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)
|
75
neutron/services/bgp/driver/utils.py
Normal file
75
neutron/services/bgp/driver/utils.py
Normal 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)
|
@ -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
neutron/tests/unit/services/bgp/driver/__init__.py
Normal file
0
neutron/tests/unit/services/bgp/driver/__init__.py
Normal file
250
neutron/tests/unit/services/bgp/driver/ryu/test_driver.py
Normal file
250
neutron/tests/unit/services/bgp/driver/ryu/test_driver.py
Normal 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())
|
48
neutron/tests/unit/services/bgp/driver/test_utils.py
Normal file
48
neutron/tests/unit/services/bgp/driver/test_utils.py
Normal 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())
|
Loading…
Reference in New Issue
Block a user