From b33c4dd2ce394a0bca53b96ed9aeeeffbefd2456 Mon Sep 17 00:00:00 2001 From: "vikram.choudhary" Date: Sun, 14 Feb 2016 14:11:37 +0530 Subject: [PATCH] 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 Co-Authored-By: Jaume Devesa Co-Authored-By: vikram.choudhary Co-Authored-By: Numan Siddique Change-Id: If9a7e2c4c45c395b8e93bd1293667bf67f53dcfa --- devstack/lib/bgp | 8 +- neutron/db/bgp_db.py | 5 + neutron/extensions/bgp.py | 15 +- neutron/services/bgp/agent/bgp_dragent.py | 96 ++++++- neutron/services/bgp/agent/config.py | 16 +- neutron/services/bgp/common/constants.py | 7 + neutron/services/bgp/driver/__init__.py | 0 neutron/services/bgp/driver/base.py | 142 ++++++++++ neutron/services/bgp/driver/exceptions.py | 61 +++++ neutron/services/bgp/driver/ryu/__init__.py | 0 neutron/services/bgp/driver/ryu/driver.py | 202 ++++++++++++++ neutron/services/bgp/driver/utils.py | 75 ++++++ neutron/tests/unit/db/test_bgp_db.py | 6 + .../services/bgp/agent/test_bgp_dragent.py | 13 +- .../unit/services/bgp/driver/__init__.py | 0 .../unit/services/bgp/driver/ryu/__init__.py | 0 .../services/bgp/driver/ryu/test_driver.py | 250 ++++++++++++++++++ .../unit/services/bgp/driver/test_utils.py | 48 ++++ 18 files changed, 925 insertions(+), 19 deletions(-) create mode 100644 neutron/services/bgp/driver/__init__.py create mode 100644 neutron/services/bgp/driver/base.py create mode 100644 neutron/services/bgp/driver/exceptions.py create mode 100644 neutron/services/bgp/driver/ryu/__init__.py create mode 100644 neutron/services/bgp/driver/ryu/driver.py create mode 100644 neutron/services/bgp/driver/utils.py create mode 100644 neutron/tests/unit/services/bgp/driver/__init__.py create mode 100644 neutron/tests/unit/services/bgp/driver/ryu/__init__.py create mode 100644 neutron/tests/unit/services/bgp/driver/ryu/test_driver.py create mode 100644 neutron/tests/unit/services/bgp/driver/test_utils.py diff --git a/devstack/lib/bgp b/devstack/lib/bgp index 61a2fc26b73..364e75be8a7 100644 --- a/devstack/lib/bgp +++ b/devstack/lib/bgp @@ -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 -} \ No newline at end of file +} diff --git a/neutron/db/bgp_db.py b/neutron/db/bgp_db.py index a33cf25cde0..d1ddfe6e73e 100644 --- a/neutron/db/bgp_db.py +++ b/neutron/db/bgp_db.py @@ -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'] diff --git a/neutron/extensions/bgp.py b/neutron/extensions/bgp.py index 019b9841480..1d8ec5b9cc3 100644 --- a/neutron/extensions/bgp.py +++ b/neutron/extensions/bgp.py @@ -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 diff --git a/neutron/services/bgp/agent/bgp_dragent.py b/neutron/services/bgp/agent/bgp_dragent.py index 389984acc14..720aa2a630e 100644 --- a/neutron/services/bgp/agent/bgp_dragent.py +++ b/neutron/services/bgp/agent/bgp_dragent.py @@ -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") diff --git a/neutron/services/bgp/agent/config.py b/neutron/services/bgp/agent/config.py index bbdfeeaefa7..18f2d2be264 100644 --- a/neutron/services/bgp/agent/config.py +++ b/neutron/services/bgp/agent/config.py @@ -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.")) +] diff --git a/neutron/services/bgp/common/constants.py b/neutron/services/bgp/common/constants.py index 0c2b18184d6..42c47bd0ef5 100644 --- a/neutron/services/bgp/common/constants.py +++ b/neutron/services/bgp/common/constants.py @@ -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 diff --git a/neutron/services/bgp/driver/__init__.py b/neutron/services/bgp/driver/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/bgp/driver/base.py b/neutron/services/bgp/driver/base.py new file mode 100644 index 00000000000..128651b7dcc --- /dev/null +++ b/neutron/services/bgp/driver/base.py @@ -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 + """ diff --git a/neutron/services/bgp/driver/exceptions.py b/neutron/services/bgp/driver/exceptions.py new file mode 100644 index 00000000000..36c49a04080 --- /dev/null +++ b/neutron/services/bgp/driver/exceptions.py @@ -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.") diff --git a/neutron/services/bgp/driver/ryu/__init__.py b/neutron/services/bgp/driver/ryu/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/bgp/driver/ryu/driver.py b/neutron/services/bgp/driver/ryu/driver.py new file mode 100644 index 00000000000..3825cc2f9c4 --- /dev/null +++ b/neutron/services/bgp/driver/ryu/driver.py @@ -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) diff --git a/neutron/services/bgp/driver/utils.py b/neutron/services/bgp/driver/utils.py new file mode 100644 index 00000000000..c09e6daf79d --- /dev/null +++ b/neutron/services/bgp/driver/utils.py @@ -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) diff --git a/neutron/tests/unit/db/test_bgp_db.py b/neutron/tests/unit/db/test_bgp_db.py index 52921556f42..2d417d8f97a 100644 --- a/neutron/tests/unit/db/test_bgp_db.py +++ b/neutron/tests/unit/db/test_bgp_db.py @@ -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) diff --git a/neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py b/neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py index 8f738c5f2b6..09c1f853dde 100644 --- a/neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py +++ b/neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py @@ -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') diff --git a/neutron/tests/unit/services/bgp/driver/__init__.py b/neutron/tests/unit/services/bgp/driver/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/bgp/driver/ryu/__init__.py b/neutron/tests/unit/services/bgp/driver/ryu/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/bgp/driver/ryu/test_driver.py b/neutron/tests/unit/services/bgp/driver/ryu/test_driver.py new file mode 100644 index 00000000000..ed8c6ba4cd6 --- /dev/null +++ b/neutron/tests/unit/services/bgp/driver/ryu/test_driver.py @@ -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()) diff --git a/neutron/tests/unit/services/bgp/driver/test_utils.py b/neutron/tests/unit/services/bgp/driver/test_utils.py new file mode 100644 index 00000000000..34bd854dbc1 --- /dev/null +++ b/neutron/tests/unit/services/bgp/driver/test_utils.py @@ -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())