Move BGP service plugin, agent, and tests out of Neutron repo

This patch moves the BGP service plugin, agent, driver, and
tests out of the neutron repository and into the
neutron-dynamic-routing repository.

Partially-Implements: blueprint bgp-spinout
Partial-Bug: #1560003
Co-Authored-By: vikram.choudhary <vikram.choudhary@huawei.com>

Change-Id: I80ea28a51d7b18e67d6ed4cd2da22520f950300f
This commit is contained in:
Ryan Tidwell 2016-04-26 09:56:44 -07:00 committed by vikram.choudhary
parent 70da5ae1a1
commit 270b37f22b
63 changed files with 7068 additions and 17 deletions

View File

View File

@ -0,0 +1,107 @@
# 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 oslo_messaging
from neutron.common import rpc as n_rpc
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
class BgpDrAgentNotifyApi(object):
"""API for plugin to notify BGP DrAgent.
This class implements the client side of an rpc interface. The server side
is neutron_dynamic_routing.services.bgp.agent.bgp_dragent.BgpDrAgent. For
more information about rpc interfaces, please see
http://docs.openstack.org/developer/neutron/devref/rpc_api.html.
"""
def __init__(self, topic=bgp_consts.BGP_DRAGENT):
target = oslo_messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
self.topic = topic
def bgp_routes_advertisement(self, context, bgp_speaker_id,
routes, host):
"""Tell BgpDrAgent to begin advertising the given route.
Invoked on FIP association, adding router port to a tenant network,
and new DVR port-host bindings, and subnet creation(?).
"""
self._notification_host_cast(context, 'bgp_routes_advertisement_end',
{'advertise_routes': {'speaker_id': bgp_speaker_id,
'routes': routes}}, host)
def bgp_routes_withdrawal(self, context, bgp_speaker_id,
routes, host):
"""Tell BgpDrAgent to stop advertising the given route.
Invoked on FIP disassociation, removal of a router port on a
network, and removal of DVR port-host binding, and subnet delete(?).
"""
self._notification_host_cast(context, 'bgp_routes_withdrawal_end',
{'withdraw_routes': {'speaker_id': bgp_speaker_id,
'routes': routes}}, host)
def bgp_peer_disassociated(self, context, bgp_speaker_id,
bgp_peer_ip, host):
"""Tell BgpDrAgent about a new BGP Peer association.
This effectively tells the BgpDrAgent to stop a peering session.
"""
self._notification_host_cast(context, 'bgp_peer_disassociation_end',
{'bgp_peer': {'speaker_id': bgp_speaker_id,
'peer_ip': bgp_peer_ip}}, host)
def bgp_peer_associated(self, context, bgp_speaker_id,
bgp_peer_id, host):
"""Tell BgpDrAgent about a BGP Peer disassociation.
This effectively tells the bgp_dragent to open a peering session.
"""
self._notification_host_cast(context, 'bgp_peer_association_end',
{'bgp_peer': {'speaker_id': bgp_speaker_id,
'peer_id': bgp_peer_id}}, host)
def bgp_speaker_created(self, context, bgp_speaker_id, host):
"""Tell BgpDrAgent about the creation of a BGP Speaker.
Because a BGP Speaker can be created with BgpPeer binding in place,
we need to inform the BgpDrAgent of a new BGP Speaker in case a
peering session needs to opened immediately.
"""
self._notification_host_cast(context, 'bgp_speaker_create_end',
{'bgp_speaker': {'id': bgp_speaker_id}}, host)
def bgp_speaker_removed(self, context, bgp_speaker_id, host):
"""Tell BgpDrAgent about the removal of a BGP Speaker.
Because a BGP Speaker can be removed with BGP Peer binding in
place, we need to inform the BgpDrAgent of the removal of a
BGP Speaker in case peering sessions need to be stopped.
"""
self._notification_host_cast(context, 'bgp_speaker_remove_end',
{'bgp_speaker': {'id': bgp_speaker_id}}, host)
def _notification_host_cast(self, context, method, payload, host):
"""Send payload to BgpDrAgent in the cast mode"""
cctxt = self.client.prepare(topic=self.topic, server=host)
cctxt.cast(context, method, payload=payload)
def _notification_host_call(self, context, method, payload, host):
"""Send payload to BgpDrAgent in the call mode"""
cctxt = self.client.prepare(topic=self.topic, server=host)
cctxt.call(context, method, payload=payload)

View File

@ -0,0 +1,66 @@
# 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 oslo_messaging
from neutron import manager
from neutron_dynamic_routing.extensions import bgp as bgp_ext
class BgpSpeakerRpcCallback(object):
"""BgpDrAgent RPC callback in plugin implementations.
This class implements the server side of an RPC interface.
The client side of this interface can be found in
neutron_dynamic_routing.services.bgp.agent.bgp_dragent.BgpDrPluginApi.
For more information about changing RPC interfaces,
see http://docs.openstack.org/developer/neutron/devref/rpc_api.html.
"""
# API version history:
# 1.0 BGPDRPluginApi BASE_RPC_API_VERSION
target = oslo_messaging.Target(version='1.0')
@property
def plugin(self):
if not hasattr(self, '_plugin'):
self._plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
return self._plugin
def get_bgp_speaker_info(self, context, bgp_speaker_id):
"""Return BGP Speaker details such as peer list and local_as.
Invoked by the BgpDrAgent to lookup the details of a BGP Speaker.
"""
return self.plugin.get_bgp_speaker_with_advertised_routes(
context, bgp_speaker_id)
def get_bgp_peer_info(self, context, bgp_peer_id):
"""Return BgpPeer details such as IP, remote_as, and credentials.
Invoked by the BgpDrAgent to lookup the details of a BGP peer.
"""
return self.plugin.get_bgp_peer(context, bgp_peer_id,
['peer_ip', 'remote_as',
'auth_type', 'password'])
def get_bgp_speakers(self, context, host=None, **kwargs):
"""Returns the list of all BgpSpeakers.
Typically invoked by the BgpDrAgent as part of its bootstrap process.
"""
return self.plugin.get_bgp_speakers_for_agent_host(context, host)

View File

View File

@ -1,6 +1,3 @@
# Copyright (c) 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
@ -13,18 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
""" Tests for `neutron_dynamic_routing` module"""
from neutron.common import eventlet_utils
from oslotest import base
class TestNeutron_dynamic_routing(base.BaseTestCase):
"""TestNeutron_dynamic_routing base class"""
def setUp(self):
"""setUp function"""
super(TestNeutron_dynamic_routing, self).setUp()
def test_dummy(self):
"""Added dummy test just for test"""
pass
eventlet_utils.monkey_patch()

View File

@ -0,0 +1,20 @@
# 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_dynamic_routing.services.bgp.agent import entry as bgp_dragent
def main():
bgp_dragent.main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,216 @@
# 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_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc
from neutron.db import agents_db
from neutron.db import agentschedulers_db as as_db
from neutron.db import model_base
from neutron_dynamic_routing._i18n import _
from neutron_dynamic_routing._i18n import _LW
from neutron_dynamic_routing.extensions import bgp_dragentscheduler as bgp_dras_ext # noqa
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
LOG = logging.getLogger(__name__)
BGP_DRAGENT_SCHEDULER_OPTS = [
cfg.StrOpt(
'bgp_drscheduler_driver',
default='neutron_dynamic_routing.services.bgp.scheduler'
'.bgp_dragent_scheduler.ChanceScheduler',
help=_('Driver used for scheduling BGP speakers to BGP DrAgent'))
]
cfg.CONF.register_opts(BGP_DRAGENT_SCHEDULER_OPTS)
class BgpSpeakerDrAgentBinding(model_base.BASEV2):
"""Represents a mapping between BGP speaker and BGP DRAgent"""
__tablename__ = 'bgp_speaker_dragent_bindings'
bgp_speaker_id = sa.Column(sa.String(length=36),
sa.ForeignKey("bgp_speakers.id",
ondelete='CASCADE'),
nullable=False)
dragent = orm.relation(agents_db.Agent)
agent_id = sa.Column(sa.String(length=36),
sa.ForeignKey("agents.id",
ondelete='CASCADE'),
primary_key=True)
class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
as_db.AgentSchedulerDbMixin):
bgp_drscheduler = None
def schedule_unscheduled_bgp_speakers(self, context, host):
if self.bgp_drscheduler:
return self.bgp_drscheduler.schedule_unscheduled_bgp_speakers(
context, host)
else:
LOG.warning(_LW("Cannot schedule BgpSpeaker to DrAgent. "
"Reason: No scheduler registered."))
def schedule_bgp_speaker(self, context, created_bgp_speaker):
if self.bgp_drscheduler:
agents = self.bgp_drscheduler.schedule(context,
created_bgp_speaker)
for agent in agents:
self._bgp_rpc.bgp_speaker_created(context,
created_bgp_speaker['id'],
agent.host)
else:
LOG.warning(_LW("Cannot schedule BgpSpeaker to DrAgent. "
"Reason: No scheduler registered."))
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
"""Associate a BgpDrAgent with a BgpSpeaker."""
try:
self._save_bgp_speaker_dragent_binding(context,
agent_id,
speaker_id)
except db_exc.DBDuplicateEntry:
raise bgp_dras_ext.DrAgentAssociationError(
agent_id=agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s added to '
'BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': speaker_id, 'agent_id': agent_id})
def _save_bgp_speaker_dragent_binding(self, context,
agent_id, speaker_id):
with context.session.begin(subtransactions=True):
agent_db = self._get_agent(context, agent_id)
agent_up = agent_db['admin_state_up']
is_agent_bgp = (agent_db['agent_type'] ==
bgp_consts.AGENT_TYPE_BGP_ROUTING)
if not is_agent_bgp or not agent_up:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
binding = BgpSpeakerDrAgentBinding()
binding.bgp_speaker_id = speaker_id
binding.agent_id = agent_id
context.session.add(binding)
self._bgp_rpc.bgp_speaker_created(context, speaker_id, agent_db.host)
def remove_bgp_speaker_from_dragent(self, context, agent_id, speaker_id):
with context.session.begin(subtransactions=True):
agent_db = self._get_agent(context, agent_id)
is_agent_bgp = (agent_db['agent_type'] ==
bgp_consts.AGENT_TYPE_BGP_ROUTING)
if not is_agent_bgp:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.filter_by(bgp_speaker_id=speaker_id,
agent_id=agent_id)
num_deleted = query.delete()
if not num_deleted:
raise bgp_dras_ext.DrAgentNotHostingBgpSpeaker(
bgp_speaker_id=speaker_id,
agent_id=agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s removed from '
'BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': speaker_id,
'agent_id': agent_id})
self._bgp_rpc.bgp_speaker_removed(context, speaker_id, agent_db.host)
def get_dragents_hosting_bgp_speakers(self, context, bgp_speaker_ids,
active=None, admin_state_up=None):
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.options(orm.contains_eager(
BgpSpeakerDrAgentBinding.dragent))
query = query.join(BgpSpeakerDrAgentBinding.dragent)
if len(bgp_speaker_ids) == 1:
query = query.filter(
BgpSpeakerDrAgentBinding.bgp_speaker_id == (
bgp_speaker_ids[0]))
elif bgp_speaker_ids:
query = query.filter(
BgpSpeakerDrAgentBinding.bgp_speaker_id in bgp_speaker_ids)
if admin_state_up is not None:
query = query.filter(agents_db.Agent.admin_state_up ==
admin_state_up)
return [binding.dragent
for binding in query
if as_db.AgentSchedulerDbMixin.is_eligible_agent(
active, binding.dragent)]
def get_dragent_bgp_speaker_bindings(self, context):
return context.session.query(BgpSpeakerDrAgentBinding).all()
def list_dragent_hosting_bgp_speaker(self, context, speaker_id):
dragents = self.get_dragents_hosting_bgp_speakers(context,
[speaker_id])
agent_ids = [dragent.id for dragent in dragents]
if not agent_ids:
return {'agents': []}
return {'agents': self.get_agents(context, filters={'id': agent_ids})}
def list_bgp_speaker_on_dragent(self, context, agent_id):
query = context.session.query(BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = query.filter_by(agent_id=agent_id)
bgp_speaker_ids = [item[0] for item in query]
if not bgp_speaker_ids:
# Exception will be thrown if the requested agent does not exist.
self._get_agent(context, agent_id)
return {'bgp_speakers': []}
return {'bgp_speakers':
self.get_bgp_speakers(context,
filters={'id': bgp_speaker_ids})}
def get_bgp_speakers_for_agent_host(self, context, host):
agent = self._get_agent_by_type_and_host(
context, bgp_consts.AGENT_TYPE_BGP_ROUTING, host)
if not agent.admin_state_up:
return {}
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.filter(BgpSpeakerDrAgentBinding.agent_id == agent.id)
try:
binding = query.one()
except exc.NoResultFound:
return []
bgp_speaker = self.get_bgp_speaker_with_advertised_routes(
context, binding['bgp_speaker_id'])
return [bgp_speaker]
def get_bgp_speaker_by_speaker_id(self, context, bgp_speaker_id):
try:
return self.get_bgp_speaker(context, bgp_speaker_id)
except exc.NoResultFound:
return {}
def get_bgp_peer_by_peer_id(self, context, bgp_peer_id):
try:
return self.get_bgp_peer(context, bgp_peer_id)
except exc.NoResultFound:
return {}

View File

@ -14,6 +14,9 @@
from neutron.db import model_base
from neutron_dynamic_routing.db import bgp_db # noqa
from neutron_dynamic_routing.db import bgp_dragentscheduler_db # noqa
def get_metadata():
return model_base.BASEV2.metadata

View File

@ -0,0 +1,209 @@
# Copyright 2016 Hewlett Packard Development Coompany LP
#
# 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_lib import exceptions as n_exc
from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import resource_helper as rh
from neutron_dynamic_routing._i18n import _
from neutron_dynamic_routing.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'
RESOURCE_ATTRIBUTE_MAP = {
BGP_SPEAKER_RESOURCE_NAME + 's': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True, 'primary_key': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.NAME_MAX_LEN},
'is_visible': True, 'default': ''},
'local_as': {'allow_post': True, 'allow_put': False,
'validate': {'type:range': (bgp_consts.MIN_ASNUM,
bgp_consts.MAX_ASNUM)},
'is_visible': True, 'default': None,
'required_by_policy': False,
'enforce_policy': False},
'ip_version': {'allow_post': True, 'allow_put': False,
'validate': {'type:values': [4, 6]},
'is_visible': True, 'default': None,
'required_by_policy': False,
'enforce_policy': False},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': False,
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
'is_visible': True},
'peers': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid_list': None},
'is_visible': True, 'default': [],
'required_by_policy': False,
'enforce_policy': True},
'networks': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid_list': None},
'is_visible': True, 'default': [],
'required_by_policy': False,
'enforce_policy': True},
'advertise_floating_ip_host_routes': {
'allow_post': True,
'allow_put': True,
'convert_to': attr.convert_to_boolean,
'validate': {'type:boolean': None},
'is_visible': True, 'default': True,
'required_by_policy': False,
'enforce_policy': True},
'advertise_tenant_networks': {
'allow_post': True,
'allow_put': True,
'convert_to': attr.convert_to_boolean,
'validate': {'type:boolean': None},
'is_visible': True, 'default': True,
'required_by_policy': False,
'enforce_policy': True},
},
'bgp-peers': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True, 'primary_key': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.NAME_MAX_LEN},
'is_visible': True, 'default': ''},
'peer_ip': {'allow_post': True, 'allow_put': False,
'required_by_policy': True,
'validate': {'type:ip_address': None},
'is_visible': True},
'remote_as': {'allow_post': True, 'allow_put': False,
'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_consts.SUPPORTED_AUTH_TYPES},
'is_visible': True},
'password': {'allow_post': True, 'allow_put': True,
'required_by_policy': True,
'validate': {'type:string_or_none': None},
'is_visible': False,
'default': None},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': False,
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
'is_visible': True}
}
}
# Dynamic Routing Exceptions
class BgpSpeakerNotFound(n_exc.NotFound):
message = _("BGP speaker %(id)s could not be found.")
class BgpPeerNotFound(n_exc.NotFound):
message = _("BGP peer %(id)s could not be found.")
class BgpPeerNotAuthenticated(n_exc.NotFound):
message = _("BGP peer %(bgp_peer_id)s not authenticated.")
class BgpSpeakerPeerNotAssociated(n_exc.NotFound):
message = _("BGP peer %(bgp_peer_id)s is not associated with "
"BGP speaker %(bgp_speaker_id)s.")
class BgpSpeakerNetworkNotAssociated(n_exc.NotFound):
message = _("Network %(network_id)s is not associated with "
"BGP speaker %(bgp_speaker_id)s.")
class BgpSpeakerNetworkBindingError(n_exc.Conflict):
message = _("Network %(network_id)s is already bound to BgpSpeaker "
"%(bgp_speaker_id)s.")
class NetworkNotBound(n_exc.NotFound):
message = _("Network %(network_id)s is not bound to a BgpSpeaker.")
class DuplicateBgpPeerIpException(n_exc.Conflict):
_message = _("BGP Speaker %(bgp_speaker_id)s is already configured to "
"peer with a BGP Peer at %(peer_ip)s, it cannot peer with "
"BGP Peer %(bgp_peer_id)s.")
class InvalidBgpPeerMd5Authentication(n_exc.BadRequest):
message = _("A password must be supplied when using auth_type md5.")
class NetworkNotBoundForIpVersion(NetworkNotBound):
message = _("Network %(network_id)s is not bound to a IPv%(ip_version)s "
"BgpSpeaker.")
class Bgp(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Neutron BGP Dynamic Routing Extension"
@classmethod
def get_alias(cls):
return BGP_EXT_ALIAS
@classmethod
def get_description(cls):
return("Discover and advertise routes for Neutron prefixes "
"dynamically via BGP")
@classmethod
def get_updated(cls):
return "2016-05-10T15:37:00-00:00"
@classmethod
def get_resources(cls):
plural_mappings = rh.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
action_map = {BGP_SPEAKER_RESOURCE_NAME:
{'add_bgp_peer': 'PUT',
'remove_bgp_peer': 'PUT',
'add_gateway_network': 'PUT',
'remove_gateway_network': 'PUT',
'get_advertised_routes': 'GET'}}
exts = rh.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
BGP_EXT_ALIAS,
action_map=action_map)
return exts
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}
def update_attributes_map(self, attributes):
super(Bgp, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)

View File

@ -0,0 +1,184 @@
# 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.
import abc
import six
import webob
from neutron_lib import exceptions as n_exc
from oslo_log import log as logging
from neutron.api import extensions
from neutron.api.v2 import base
from neutron.api.v2 import resource
from neutron.extensions import agent
from neutron import manager
from neutron import wsgi
from neutron_dynamic_routing._i18n import _, _LE
from neutron_dynamic_routing.extensions import bgp as bgp_ext
LOG = logging.getLogger(__name__)
BGP_DRAGENT_SCHEDULER_EXT_ALIAS = 'bgp_dragent_scheduler'
BGP_DRINSTANCE = 'bgp-drinstance'
BGP_DRINSTANCES = BGP_DRINSTANCE + 's'
BGP_DRAGENT = 'bgp-dragent'
BGP_DRAGENTS = BGP_DRAGENT + 's'
class DrAgentInvalid(agent.AgentNotFound):
message = _("BgpDrAgent %(id)s is invalid or has been disabled.")
class DrAgentNotHostingBgpSpeaker(n_exc.NotFound):
message = _("BGP speaker %(bgp_speaker_id)s is not hosted "
"by the BgpDrAgent %(agent_id)s.")
class DrAgentAssociationError(n_exc.Conflict):
message = _("BgpDrAgent %(agent_id)s is already associated "
"to a BGP speaker.")
class BgpDrSchedulerController(wsgi.Controller):
"""Schedule BgpSpeaker for a BgpDrAgent"""
def get_plugin(self):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
if not plugin:
LOG.error(_LE('No plugin for BGP routing registered'))
msg = _('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg)
return plugin
def index(self, request, **kwargs):
plugin = self.get_plugin()
return plugin.list_bgp_speaker_on_dragent(
request.context, kwargs['agent_id'])
def create(self, request, body, **kwargs):
plugin = self.get_plugin()
return plugin.add_bgp_speaker_to_dragent(
request.context,
kwargs['agent_id'],
body['bgp_speaker_id'])
def delete(self, request, id, **kwargs):
plugin = self.get_plugin()
return plugin.remove_bgp_speaker_from_dragent(
request.context, kwargs['agent_id'], id)
class BgpDrAgentController(wsgi.Controller):
def get_plugin(self):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
if not plugin:
LOG.error(_LE('No plugin for BGP routing registered'))
msg = _LE('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg)
return plugin
def index(self, request, **kwargs):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
return plugin.list_dragent_hosting_bgp_speaker(
request.context, kwargs['bgp_speaker_id'])
class Bgp_dragentscheduler(extensions.ExtensionDescriptor):
"""Extension class supporting Dynamic Routing scheduler.
"""
@classmethod
def get_name(cls):
return "BGP Dynamic Routing Agent Scheduler"
@classmethod
def get_alias(cls):
return BGP_DRAGENT_SCHEDULER_EXT_ALIAS
@classmethod
def get_description(cls):
return "Schedules BgpSpeakers on BgpDrAgent"
@classmethod
def get_updated(cls):
return "2015-07-30T10:00:00-00:00"
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
exts = []
parent = dict(member_name="agent",
collection_name="agents")
controller = resource.Resource(BgpDrSchedulerController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(BGP_DRINSTANCES,
controller, parent))
parent = dict(member_name="bgp_speaker",
collection_name="bgp-speakers")
controller = resource.Resource(BgpDrAgentController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(BGP_DRAGENTS,
controller, parent))
return exts
def get_extended_resources(self, version):
return {}
@six.add_metaclass(abc.ABCMeta)
class BgpDrSchedulerPluginBase(object):
"""REST API to operate BGP dynamic routing agent scheduler.
All the methods must be executed in admin context.
"""
def get_plugin_description(self):
return "Neutron BGP dynamic routing scheduler Plugin"
def get_plugin_type(self):
return bgp_ext.BGP_EXT_ALIAS
@abc.abstractmethod
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
pass
@abc.abstractmethod
def remove_bgp_speaker_from_dragent(self, context, agent_id, speaker_id):
pass
@abc.abstractmethod
def list_dragent_hosting_bgp_speaker(self, context, speaker_id):
pass
@abc.abstractmethod
def list_bgp_speaker_on_dragent(self, context, agent_id):
pass
@abc.abstractmethod
def get_bgp_speakers_for_agent_host(self, context, host):
pass
@abc.abstractmethod
def get_bgp_speaker_by_speaker_id(self, context, speaker_id):
pass
@abc.abstractmethod
def get_bgp_peer_by_peer_id(self, context, bgp_peer_id):
pass

View File

@ -0,0 +1,708 @@
# 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 collections
from neutron_lib import constants as n_const
from oslo_config import cfg
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 rpc as n_rpc
from neutron.common import topics
from neutron.common import utils
from neutron import context
from neutron import manager
from neutron_dynamic_routing.extensions import bgp as bgp_ext
from neutron_dynamic_routing._i18n import _, _LE, _LI, _LW
from neutron_dynamic_routing.services.bgp.agent.driver import exceptions as driver_exc # noqa
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts # noqa
LOG = logging.getLogger(__name__)
class BgpDrAgent(manager.Manager):
"""BGP Dynamic Routing agent service manager.
Note that the public methods of this class are exposed as the server side
of an rpc interface. The neutron server uses
neutron.api.rpc.agentnotifiers.bgp_dr_rpc_agent_api.
BgpDrAgentNotifyApi as the client side to execute the methods
here. For more information about changing rpc interfaces, see
http://docs.openstack.org/developer/neutron/devref/rpc_api.html.
API version history:
1.0 initial Version
"""
target = oslo_messaging.Target(version='1.0')
def __init__(self, host, conf=None):
super(BgpDrAgent, self).__init__()
self.initialize_driver(conf)
self.needs_resync_reasons = collections.defaultdict(list)
self.needs_full_sync_reason = None
self.cache = BgpSpeakerCache()
self.context = context.get_admin_context_without_session()
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"))
def run(self):
"""Activate BGP Dynamic Routing agent."""
self.sync_state(self.context)
self.periodic_resync(self.context)
@utils.synchronized('bgp-dragent')
def sync_state(self, context, full_sync=None, bgp_speakers=None):
try:
hosted_bgp_speakers = self.plugin_rpc.get_bgp_speakers(context)
hosted_bgp_speaker_ids = [bgp_speaker['id']
for bgp_speaker in hosted_bgp_speakers]
cached_bgp_speakers = self.cache.get_bgp_speaker_ids()
for bgp_speaker_id in cached_bgp_speakers:
if bgp_speaker_id not in hosted_bgp_speaker_ids:
self.remove_bgp_speaker_from_dragent(bgp_speaker_id)
resync_all = not bgp_speakers or full_sync
only_bs = set() if resync_all else set(bgp_speakers)
for hosted_bgp_speaker in hosted_bgp_speakers:
hosted_bs_id = hosted_bgp_speaker['id']
if resync_all or hosted_bs_id in only_bs:
if not self.cache.is_bgp_speaker_added(hosted_bs_id):
self.safe_configure_dragent_for_bgp_speaker(
hosted_bgp_speaker)
continue
self.sync_bgp_speaker(hosted_bgp_speaker)
resync_reason = "Periodic route cache refresh"
self.schedule_resync(speaker_id=hosted_bs_id,
reason=resync_reason)
except Exception as e:
self.schedule_full_resync(reason=e)
LOG.error(_LE('Unable to sync BGP speaker state.'))
def sync_bgp_speaker(self, bgp_speaker):
# sync BGP Speakers
bgp_peer_ips = set(
[bgp_peer['peer_ip'] for bgp_peer in bgp_speaker['peers']])
cached_bgp_peer_ips = set(
self.cache.get_bgp_peer_ips(bgp_speaker['id']))
removed_bgp_peer_ips = cached_bgp_peer_ips - bgp_peer_ips
for bgp_peer_ip in removed_bgp_peer_ips:
self.remove_bgp_peer_from_bgp_speaker(bgp_speaker['id'],
bgp_peer_ip)
if bgp_peer_ips:
self.add_bgp_peers_to_bgp_speaker(bgp_speaker)
# sync advertise routes
cached_adv_routes = self.cache.get_adv_routes(bgp_speaker['id'])
adv_routes = bgp_speaker['advertised_routes']
if cached_adv_routes == adv_routes:
return
for cached_route in cached_adv_routes:
if cached_route not in adv_routes:
self.withdraw_route_via_bgp_speaker(bgp_speaker['id'],
bgp_speaker['local_as'],
cached_route)
self.advertise_routes_via_bgp_speaker(bgp_speaker)
@utils.exception_logger()
def _periodic_resync_helper(self, context):
"""Resync the BgpDrAgent state at the configured interval."""
if self.needs_resync_reasons or self.needs_full_sync_reason:
full_sync = self.needs_full_sync_reason
reasons = self.needs_resync_reasons
# Reset old reasons
self.needs_full_sync_reason = None
self.needs_resync_reasons = collections.defaultdict(list)
if full_sync:
LOG.debug("resync all: %(reason)s", {"reason": full_sync})
for bgp_speaker, reason in reasons.items():
LOG.debug("resync (%(bgp_speaker)s): %(reason)s",
{"reason": reason, "bgp_speaker": bgp_speaker})
self.sync_state(
context, full_sync=full_sync, bgp_speakers=reasons.keys())
# NOTE: spacing is set 1 sec. The actual interval is controlled
# by neutron/service.py which defaults to CONF.periodic_interval
@periodic_task.periodic_task(spacing=1)
def periodic_resync(self, context):
LOG.debug("Started periodic resync.")
self._periodic_resync_helper(context)
@utils.synchronized('bgp-dr-agent')
def bgp_speaker_create_end(self, context, payload):
"""Handle bgp_speaker_create_end notification event."""
bgp_speaker_id = payload['bgp_speaker']['id']
LOG.debug('Received BGP speaker create notification for '
'speaker_id=%(speaker_id)s from the neutron server.',
{'speaker_id': bgp_speaker_id})
self.add_bgp_speaker_helper(bgp_speaker_id)
@utils.synchronized('bgp-dr-agent')
def bgp_speaker_remove_end(self, context, payload):
"""Handle bgp_speaker_create_end notification event."""
bgp_speaker_id = payload['bgp_speaker']['id']
LOG.debug('Received BGP speaker remove notification for '
'speaker_id=%(speaker_id)s from the neutron server.',
{'speaker_id': bgp_speaker_id})
self.remove_bgp_speaker_from_dragent(bgp_speaker_id)
@utils.synchronized('bgp-dr-agent')
def bgp_peer_association_end(self, context, payload):
"""Handle bgp_peer_association_end notification event."""
bgp_peer_id = payload['bgp_peer']['peer_id']
bgp_speaker_id = payload['bgp_peer']['speaker_id']
LOG.debug('Received BGP peer associate notification for '
'speaker_id=%(speaker_id)s peer_id=%(peer_id)s '
'from the neutron server.',
{'speaker_id': bgp_speaker_id,
'peer_id': bgp_peer_id})
self.add_bgp_peer_helper(bgp_speaker_id, bgp_peer_id)
@utils.synchronized('bgp-dr-agent')
def bgp_peer_disassociation_end(self, context, payload):
"""Handle bgp_peer_disassociation_end notification event."""
bgp_peer_ip = payload['bgp_peer']['peer_ip']
bgp_speaker_id = payload['bgp_peer']['speaker_id']
LOG.debug('Received BGP peer disassociate notification for '
'speaker_id=%(speaker_id)s peer_ip=%(peer_ip)s '
'from the neutron server.',
{'speaker_id': bgp_speaker_id,
'peer_ip': bgp_peer_ip})
self.remove_bgp_peer_from_bgp_speaker(bgp_speaker_id, bgp_peer_ip)
@utils.synchronized('bgp-dr-agent')
def bgp_routes_advertisement_end(self, context, payload):
"""Handle bgp_routes_advertisement_end notification event."""
bgp_speaker_id = payload['advertise_routes']['speaker_id']
LOG.debug('Received routes advertisement end notification '
'for speaker_id=%(speaker_id)s from the neutron server.',
{'speaker_id': bgp_speaker_id})
routes = payload['advertise_routes']['routes']
self.add_routes_helper(bgp_speaker_id, routes)
@utils.synchronized('bgp-dr-agent')
def bgp_routes_withdrawal_end(self, context, payload):
"""Handle bgp_routes_withdrawal_end notification event."""
bgp_speaker_id = payload['withdraw_routes']['speaker_id']
LOG.debug('Received route withdrawal notification for '
'speaker_id=%(speaker_id)s from the neutron server.',
{'speaker_id': bgp_speaker_id})
routes = payload['withdraw_routes']['routes']
self.withdraw_routes_helper(bgp_speaker_id, routes)
def add_bgp_speaker_helper(self, bgp_speaker_id):
"""Add BGP speaker."""
bgp_speaker = self.safe_get_bgp_speaker_info(bgp_speaker_id)
if bgp_speaker:
self.add_bgp_speaker_on_dragent(bgp_speaker)
def add_bgp_peer_helper(self, bgp_speaker_id, bgp_peer_id):
"""Add BGP peer."""
# 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):
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
bgp_peer = self.safe_get_bgp_peer_info(bgp_speaker_id,
bgp_peer_id)
if bgp_peer:
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
bgp_speaker_id)
self.add_bgp_peer_to_bgp_speaker(bgp_speaker_id,
bgp_speaker_as,
bgp_peer)
def add_routes_helper(self, bgp_speaker_id, routes):
"""Advertise routes to 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):
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(bgp_speaker_id)
for route in routes:
self.advertise_route_via_bgp_speaker(bgp_speaker_id,
bgp_speaker_as,
route)
if self.is_resync_scheduled(bgp_speaker_id):
break
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):
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(bgp_speaker_id)
for route in routes:
self.withdraw_route_via_bgp_speaker(bgp_speaker_id,
bgp_speaker_as,
route)
if self.is_resync_scheduled(bgp_speaker_id):
break
def safe_get_bgp_speaker_info(self, bgp_speaker_id):
try:
bgp_speaker = self.plugin_rpc.get_bgp_speaker_info(self.context,
bgp_speaker_id)
if not bgp_speaker:
LOG.warning(_LW('BGP Speaker %s has been deleted.'),
bgp_speaker_id)
return bgp_speaker
except Exception as e:
self.schedule_resync(speaker_id=bgp_speaker_id,
reason=e)
LOG.error(_LE('BGP Speaker %(bgp_speaker)s info call '
'failed with reason=%(e)s.'),
{'bgp_speaker': bgp_speaker_id, 'e': e})
def safe_get_bgp_peer_info(self, bgp_speaker_id, bgp_peer_id):
try:
bgp_peer = self.plugin_rpc.get_bgp_peer_info(self.context,
bgp_peer_id)
if not bgp_peer:
LOG.warning(_LW('BGP Peer %s has been deleted.'), bgp_peer)
return bgp_peer
except Exception as e:
self.schedule_resync(speaker_id=bgp_speaker_id,
reason=e)
LOG.error(_LE('BGP peer %(bgp_peer)s info call '
'failed with reason=%(e)s.'),
{'bgp_peer': bgp_peer_id, 'e': e})
@utils.exception_logger()
def safe_configure_dragent_for_bgp_speaker(self, bgp_speaker):
try:
self.add_bgp_speaker_on_dragent(bgp_speaker)
except (bgp_ext.BgpSpeakerNotFound, RuntimeError):
LOG.warning(_LW('BGP speaker %s may have been deleted and its '
'resources may have already been disposed.'),
bgp_speaker['id'])
def add_bgp_speaker_on_dragent(self, bgp_speaker):
# Caching BGP speaker details in BGPSpeakerCache. Will be used
# during smooth.
self.cache.put_bgp_speaker(bgp_speaker)
LOG.debug('Calling driver for adding BGP speaker %(speaker_id)s,'
' 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)
self.advertise_routes_via_bgp_speaker(bgp_speaker)
self.schedule_resync(speaker_id=bgp_speaker['id'],
reason="Periodic route cache refresh")
def remove_bgp_speaker_from_dragent(self, bgp_speaker_id):
if self.cache.is_bgp_speaker_added(bgp_speaker_id):
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
bgp_speaker_id)
self.cache.remove_bgp_speaker_by_id(bgp_speaker_id)
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
# 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")
def add_bgp_peers_to_bgp_speaker(self, bgp_speaker):
for bgp_peer in bgp_speaker['peers']:
self.add_bgp_peer_to_bgp_speaker(bgp_speaker['id'],
bgp_speaker['local_as'],
bgp_peer)
if self.is_resync_scheduled(bgp_speaker['id']):
break
def add_bgp_peer_to_bgp_speaker(self, bgp_speaker_id,
bgp_speaker_as, bgp_peer):
if self.cache.get_bgp_peer_by_ip(bgp_speaker_id, bgp_peer['peer_ip']):
return
self.cache.put_bgp_peer(bgp_speaker_id, bgp_peer)
LOG.debug('Calling driver interface for adding BGP peer %(peer_ip)s '
'remote_as=%(remote_as)s to BGP Speaker running for '
'local_as=%(local_as)d',
{'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):
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
if self.cache.is_bgp_peer_added(bgp_speaker_id, bgp_peer_ip):
self.cache.remove_bgp_peer_by_ip(bgp_speaker_id, bgp_peer_ip)
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
bgp_speaker_id)
LOG.debug('Calling driver interface to remove BGP peer '
'%(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
# 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")
def advertise_routes_via_bgp_speaker(self, bgp_speaker):
for route in bgp_speaker['advertised_routes']:
self.advertise_route_via_bgp_speaker(bgp_speaker['id'],
bgp_speaker['local_as'],
route)
if self.is_resync_scheduled(bgp_speaker['id']):
break
def advertise_route_via_bgp_speaker(self, bgp_speaker_id,
bgp_speaker_as, route):
if self.cache.is_route_advertised(bgp_speaker_id, route):
# Requested route already advertised. Hence, Nothing to be done.
return
self.cache.put_adv_route(bgp_speaker_id, route)
LOG.debug('Calling driver for advertising prefix: %(cidr)s, '
'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):
if self.cache.is_route_advertised(bgp_speaker_id, route):
self.cache.remove_adv_route(bgp_speaker_id, route)
LOG.debug('Calling driver for withdrawing prefix: %(cidr)s, '
'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
# 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")
def schedule_full_resync(self, reason):
LOG.debug('Recording full resync request for all BGP Speakers '
'with reason=%s', reason)
self.needs_full_sync_reason = reason
def schedule_resync(self, reason, speaker_id):
"""Schedule a full resync for a given BGP Speaker.
If no BGP Speaker is specified, resync all BGP Speakers.
"""
LOG.debug('Recording resync request for BGP Speaker %s '
'with reason=%s', speaker_id, reason)
self.needs_resync_reasons[speaker_id].append(reason)
def is_resync_scheduled(self, bgp_speaker_id):
if bgp_speaker_id not in self.needs_resync_reasons:
return False
reason = self.needs_resync_reasons[bgp_speaker_id]
# Re-sync scheduled for the queried BGP speaker. No point
# continuing further. Let's stop processing and wait for
# re-sync to happen.
LOG.debug('Re-sync already scheduled for BGP Speaker %s '
'with reason=%s', bgp_speaker_id, reason)
return True
class BgpDrPluginApi(object):
"""Agent side of BgpDrAgent RPC API.
This class implements the client side of an rpc interface.
The server side of this interface can be found in
neutron.api.rpc.handlers.bgp_speaker_rpc.BgpSpeakerRpcCallback.
For more information about changing rpc interfaces, see
doc/source/devref/rpc_api.rst.
API version history:
1.0 - Initial version.
"""
def __init__(self, topic, context, host):
self.context = context
self.host = host
target = oslo_messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
def get_bgp_speakers(self, context):
"""Make a remote process call to retrieve all BGP speakers info."""
cctxt = self.client.prepare()
return cctxt.call(context, 'get_bgp_speakers', host=self.host)
def get_bgp_speaker_info(self, context, bgp_speaker_id):
"""Make a remote process call to retrieve a BGP speaker info."""
cctxt = self.client.prepare()
return cctxt.call(context, 'get_bgp_speaker_info',
bgp_speaker_id=bgp_speaker_id)
def get_bgp_peer_info(self, context, bgp_peer_id):
"""Make a remote process call to retrieve a BGP peer info."""
cctxt = self.client.prepare()
return cctxt.call(context, 'get_bgp_peer_info',
bgp_peer_id=bgp_peer_id)
class BgpSpeakerCache(object):
"""Agent cache of the current BGP speaker state.
This class is designed to support the advertisement for
multiple BGP speaker via a single driver interface.
Version history:
1.0 - Initial version for caching the state of BGP speaker.
"""
def __init__(self):
self.cache = {}
def get_bgp_speaker_ids(self):
return self.cache.keys()
def put_bgp_speaker(self, bgp_speaker):
if bgp_speaker['id'] in self.cache:
self.remove_bgp_speaker_by_id(self.cache[bgp_speaker['id']])
self.cache[bgp_speaker['id']] = {'bgp_speaker': bgp_speaker,
'peers': {},
'advertised_routes': []}
def get_bgp_speaker_by_id(self, bgp_speaker_id):
if bgp_speaker_id in self.cache:
return self.cache[bgp_speaker_id]['bgp_speaker']
def get_bgp_speaker_local_as(self, bgp_speaker_id):
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
if bgp_speaker:
return bgp_speaker['local_as']
def is_bgp_speaker_added(self, bgp_speaker_id):
return self.get_bgp_speaker_by_id(bgp_speaker_id)
def remove_bgp_speaker_by_id(self, bgp_speaker_id):
if bgp_speaker_id in self.cache:
del self.cache[bgp_speaker_id]
def put_bgp_peer(self, bgp_speaker_id, bgp_peer):
if bgp_peer['peer_ip'] in self.get_bgp_peer_ips(bgp_speaker_id):
del self.cache[bgp_speaker_id]['peers'][bgp_peer['peer_ip']]
self.cache[bgp_speaker_id]['peers'][bgp_peer['peer_ip']] = bgp_peer
def is_bgp_peer_added(self, bgp_speaker_id, bgp_peer_ip):
return self.get_bgp_peer_by_ip(bgp_speaker_id, bgp_peer_ip)
def get_bgp_peer_ips(self, bgp_speaker_id):
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
if bgp_speaker:
return self.cache[bgp_speaker_id]['peers'].keys()
def get_bgp_peer_by_ip(self, bgp_speaker_id, bgp_peer_ip):
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
if bgp_speaker:
return self.cache[bgp_speaker_id]['peers'].get(bgp_peer_ip)
def remove_bgp_peer_by_ip(self, bgp_speaker_id, bgp_peer_ip):
if bgp_peer_ip in self.get_bgp_peer_ips(bgp_speaker_id):
del self.cache[bgp_speaker_id]['peers'][bgp_peer_ip]
def put_adv_route(self, bgp_speaker_id, route):
self.cache[bgp_speaker_id]['advertised_routes'].append(route)
def is_route_advertised(self, bgp_speaker_id, route):
routes = self.cache[bgp_speaker_id]['advertised_routes']
for r in routes:
if r['destination'] == route['destination'] and (
r['next_hop'] == route['next_hop']):
return True
return False
def remove_adv_route(self, bgp_speaker_id, route):
routes = self.cache[bgp_speaker_id]['advertised_routes']
updated_routes = [r for r in routes if (
r['destination'] != route['destination'])]
self.cache[bgp_speaker_id]['advertised_routes'] = updated_routes
def get_adv_routes(self, bgp_speaker_id):
return self.cache[bgp_speaker_id]['advertised_routes']
def get_state(self):
bgp_speaker_ids = self.get_bgp_speaker_ids()
num_bgp_speakers = len(bgp_speaker_ids)
num_bgp_peers = 0
num_advertised_routes = 0
for bgp_speaker_id in bgp_speaker_ids:
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
num_bgp_peers += len(bgp_speaker['peers'])
num_advertised_routes += len(bgp_speaker['advertised_routes'])
return {'bgp_speakers': num_bgp_speakers,
'bgp_peers': num_bgp_peers,
'advertise_routes': num_advertised_routes}
class BgpDrAgentWithStateReport(BgpDrAgent):
def __init__(self, host, conf=None):
super(BgpDrAgentWithStateReport,
self).__init__(host, conf)
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.REPORTS)
self.agent_state = {
'agent_type': bgp_consts.AGENT_TYPE_BGP_ROUTING,
'binary': 'neutron-bgp-dragent',
'configurations': {},
'host': host,
'topic': bgp_consts.BGP_DRAGENT,
'start_flag': True}
report_interval = cfg.CONF.AGENT.report_interval
if report_interval:
self.heartbeat = loopingcall.FixedIntervalLoopingCall(
self._report_state)
self.heartbeat.start(interval=report_interval)
def _report_state(self):
LOG.debug("Report state task started")
try:
self.agent_state.get('configurations').update(
self.cache.get_state())
ctx = context.get_admin_context_without_session()
agent_status = self.state_rpc.report_state(ctx, self.agent_state,
True)
if agent_status == n_const.AGENT_REVIVED:
LOG.info(_LI("Agent has just been revived. "
"Scheduling full sync"))
self.schedule_full_resync(
reason=_("Agent has just been revived"))
except AttributeError:
# This means the server does not support report_state
LOG.warning(_LW("Neutron server does not support state report. "
"State report for this agent will be disabled."))
self.heartbeat.stop()
self.run()
return
except Exception:
LOG.exception(_LE("Failed reporting state!"))
return
if self.agent_state.pop('start_flag', None):
self.run()
def agent_updated(self, context, payload):
"""Handle the agent_updated notification event."""
self.schedule_full_resync(
reason=_("BgpDrAgent updated: %s") % payload)
LOG.info(_LI("agent_updated by server side %s!"), payload)
def after_start(self):
LOG.info(_LI("BGP dynamic routing agent started"))

View File

@ -0,0 +1,29 @@
# 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 oslo_config import cfg
from neutron_dynamic_routing._i18n import _
BGP_DRIVER_OPTS = [
cfg.StrOpt('bgp_speaker_driver',
help=_("BGP speaker driver class to be instantiated."))
]
BGP_PROTO_CONFIG_OPTS = [
cfg.StrOpt('bgp_router_id',
help=_("32-bit BGP identifier, typically an IPv4 address "
"owned by the system running the BGP DrAgent."))
]

View File

@ -0,0 +1,142 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class BgpDriverBase(object):
"""Base class for BGP Speaking drivers.
Any class which provides BGP functionality should extend this
defined base class.
"""
@abc.abstractmethod
def add_bgp_speaker(self, speaker_as):
"""Add a BGP speaker.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:raises: BgpSpeakerAlreadyScheduled, BgpSpeakerMaxScheduled,
InvalidParamType, InvalidParamRange
"""
@abc.abstractmethod
def delete_bgp_speaker(self, speaker_as):
"""Deletes BGP speaker.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:raises: BgpSpeakerNotAdded
"""
@abc.abstractmethod
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
auth_type='none', password=None):
"""Add a new BGP peer.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:param peer_ip: Specifies the IP address of the peer.
:type peer_ip: string
:param peer_as: Specifies Autonomous Number of the peer.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type peer_as: integer
:param auth_type: Specifies authentication type.
By default, authentication will be disabled.
:type auth_type: value in SUPPORTED_AUTH_TYPES
:param password: Authentication password.By default, authentication
will be disabled.
:type password: string
:raises: BgpSpeakerNotAdded, InvalidParamType, InvalidParamRange,
InvaildAuthType, PasswordNotSpecified
"""
@abc.abstractmethod
def delete_bgp_peer(self, speaker_as, peer_ip):
"""Delete a BGP peer associated with the given peer IP
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:param peer_ip: Specifies the IP address of the peer. Must be the
string representation of an IP address.
:type peer_ip: string
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
"""
@abc.abstractmethod
def advertise_route(self, speaker_as, cidr, nexthop):
"""Add a new prefix to advertise.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:param cidr: CIDR of the network to advertise. Must be the string
representation of an IP network (e.g., 10.1.1.0/24)
:type cidr: string
:param nexthop: Specifies the next hop address for the above
prefix.
:type nexthop: string
:raises: BgpSpeakerNotAdded, InvalidParamType
"""
@abc.abstractmethod
def withdraw_route(self, speaker_as, cidr, nexthop=None):
"""Withdraw an advertised prefix.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:param cidr: CIDR of the network to withdraw. Must be the string
representation of an IP network (e.g., 10.1.1.0/24)
:type cidr: string
:param nexthop: Specifies the next hop address for the above
prefix.
:type nexthop: string
:raises: BgpSpeakerNotAdded, RouteNotAdvertised, InvalidParamType
"""
@abc.abstractmethod
def get_bgp_speaker_statistics(self, speaker_as):
"""Collect BGP Speaker statistics.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:raises: BgpSpeakerNotAdded
:returns: bgp_speaker_stats: string
"""
@abc.abstractmethod
def get_bgp_peer_statistics(self, speaker_as, peer_ip, peer_as):
"""Collect BGP Peer statistics.
:param speaker_as: Specifies BGP Speaker autonomous system number.
Must be an integer between MIN_ASNUM and MAX_ASNUM.
:type speaker_as: integer
:param peer_ip: Specifies the IP address of the peer.
:type peer_ip: string
:param peer_as: Specifies the AS number of the peer. Must be an
integer between MIN_ASNUM and MAX_ASNUM.
:type peer_as: integer .
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
:returns: bgp_peer_stats: string
"""

View File

@ -0,0 +1,62 @@
# 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_lib import exceptions as n_exc
from neutron_dynamic_routing._i18n import _
# BGP Driver Exceptions
class BgpSpeakerNotAdded(n_exc.BadRequest):
message = _("BGP Speaker for local_as=%(local_as)s with "
"router_id=%(rtid)s not added yet.")
class BgpSpeakerMaxScheduled(n_exc.BadRequest):
message = _("Already hosting maximum number of BGP Speakers. "
"Allowed scheduled count=%(count)d")
class BgpSpeakerAlreadyScheduled(n_exc.Conflict):
message = _("Already hosting BGP Speaker for local_as=%(current_as)d with "
"router_id=%(rtid)s.")
class BgpPeerNotAdded(n_exc.BadRequest):
message = _("BGP Peer %(peer_ip)s for remote_as=%(remote_as)s, running "
"for BGP Speaker %(speaker_as)d not added yet.")
class RouteNotAdvertised(n_exc.BadRequest):
message = _("Route %(cidr)s not advertised for BGP Speaker "
"%(speaker_as)d.")
class InvalidParamType(n_exc.NeutronException):
message = _("Parameter %(param)s must be of %(param_type)s type.")
class InvalidParamRange(n_exc.NeutronException):
message = _("%(param)s must be in %(range)s range.")
class InvaildAuthType(n_exc.BadRequest):
message = _("Authentication type not supported. Requested "
"type=%(auth_type)s.")
class PasswordNotSpecified(n_exc.BadRequest):
message = _("Password not specified for authentication "
"type=%(auth_type)s.")

View File

@ -0,0 +1,202 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from ryu.services.protocols.bgp import bgpspeaker
from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE_ACTIVE
from neutron_dynamic_routing._i18n import _LE, _LI
from neutron_dynamic_routing.services.bgp.agent.driver import base
from neutron_dynamic_routing.services.bgp.agent.driver import exceptions as bgp_driver_exc # noqa
from neutron_dynamic_routing.services.bgp.agent.driver import utils
LOG = logging.getLogger(__name__)
# Function for logging BGP peer and path changes.
def bgp_peer_down_cb(remote_ip, remote_as):
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d went DOWN.'),
{'peer_ip': remote_ip, 'peer_as': remote_as})
def bgp_peer_up_cb(remote_ip, remote_as):
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d is UP.'),
{'peer_ip': remote_ip, 'peer_as': remote_as})
def best_path_change_cb(event):
LOG.info(_LI("Best path change observed. cidr=%(prefix)s, "
"nexthop=%(nexthop)s, remote_as=%(remote_as)d, "
"is_withdraw=%(is_withdraw)s"),
{'prefix': event.prefix, 'nexthop': event.nexthop,
'remote_as': event.remote_as,
'is_withdraw': event.is_withdraw})
class RyuBgpDriver(base.BgpDriverBase):
"""BGP speaker implementation via Ryu."""
def __init__(self, cfg):
LOG.info(_LI('Initializing Ryu driver for BGP Speaker functionality.'))
self._read_config(cfg)
# Note: Even though Ryu can only support one BGP speaker as of now,
# we have tried making the framework generic for the future purposes.
self.cache = utils.BgpMultiSpeakerCache()
def _read_config(self, cfg):
if cfg is None or cfg.bgp_router_id is None:
# If either cfg or router_id is not specified, raise voice
LOG.error(_LE('BGP router-id MUST be specified for the correct '
'functional working.'))
else:
self.routerid = cfg.bgp_router_id
LOG.info(_LI('Initialized Ryu BGP Speaker driver interface with '
'bgp_router_id=%s'), self.routerid)
def add_bgp_speaker(self, speaker_as):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if curr_speaker is not None:
raise bgp_driver_exc.BgpSpeakerAlreadyScheduled(
current_as=speaker_as,
rtid=self.routerid)
# Ryu can only support One speaker
if self.cache.get_hosted_bgp_speakers_count() == 1:
raise bgp_driver_exc.BgpSpeakerMaxScheduled(count=1)
# Validate input parameters.
# speaker_as must be an integer in the allowed range.
utils.validate_as_num('local_as', speaker_as)
# Notify Ryu about BGP Speaker addition.
# Please note: Since, only the route-advertisement support is
# implemented we are explicitly setting the bgp_server_port
# attribute to 0 which disables listening on port 179.
curr_speaker = bgpspeaker.BGPSpeaker(as_number=speaker_as,
router_id=self.routerid, bgp_server_port=0,
best_path_change_handler=best_path_change_cb,
peer_down_handler=bgp_peer_down_cb,
peer_up_handler=bgp_peer_up_cb)
LOG.info(_LI('Added BGP Speaker for local_as=%(as)d with '
'router_id= %(rtid)s.'),
{'as': speaker_as, 'rtid': self.routerid})
self.cache.put_bgp_speaker(speaker_as, curr_speaker)
def delete_bgp_speaker(self, speaker_as):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# Notify Ryu about BGP Speaker deletion
curr_speaker.shutdown()
LOG.info(_LI('Removed BGP Speaker for local_as=%(as)d with '
'router_id=%(rtid)s.'),
{'as': speaker_as, 'rtid': self.routerid})
self.cache.remove_bgp_speaker(speaker_as)
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
auth_type='none', password=None):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# Validate peer_ip and peer_as.
utils.validate_as_num('remote_as', peer_as)
utils.validate_string(peer_ip)
utils.validate_auth(auth_type, password)
# Notify Ryu about BGP Peer addition
curr_speaker.neighbor_add(address=peer_ip,
remote_as=peer_as,
password=password,
connect_mode=CONNECT_MODE_ACTIVE)
LOG.info(_LI('Added BGP Peer %(peer)s for remote_as=%(as)d to '
'BGP Speaker running for local_as=%(local_as)d.'),
{'peer': peer_ip, 'as': peer_as, 'local_as': speaker_as})
def delete_bgp_peer(self, speaker_as, peer_ip):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# Validate peer_ip. It must be a string.
utils.validate_string(peer_ip)
# Notify Ryu about BGP Peer removal
curr_speaker.neighbor_del(address=peer_ip)
LOG.info(_LI('Removed BGP Peer %(peer)s from BGP Speaker '
'running for local_as=%(local_as)d.'),
{'peer': peer_ip, 'local_as': speaker_as})
def advertise_route(self, speaker_as, cidr, nexthop):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# Validate cidr and nexthop. Both must be strings.
utils.validate_string(cidr)
utils.validate_string(nexthop)
# Notify Ryu about route advertisement
curr_speaker.prefix_add(prefix=cidr, next_hop=nexthop)
LOG.info(_LI('Route cidr=%(prefix)s, nexthop=%(nexthop)s is '
'advertised for BGP Speaker running for '
'local_as=%(local_as)d.'),
{'prefix': cidr, 'nexthop': nexthop, 'local_as': speaker_as})
def withdraw_route(self, speaker_as, cidr, nexthop=None):
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# Validate cidr. It must be a string.
utils.validate_string(cidr)
# Notify Ryu about route withdrawal
curr_speaker.prefix_del(prefix=cidr)
LOG.info(_LI('Route cidr=%(prefix)s is withdrawn from BGP Speaker '
'running for local_as=%(local_as)d.'),
{'prefix': cidr, 'local_as': speaker_as})
def get_bgp_speaker_statistics(self, speaker_as):
LOG.info(_LI('Collecting BGP Speaker statistics for local_as=%d.'),
speaker_as)
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# TODO(vikram): Filter and return the necessary information.
# Will be done as part of new RFE requirement
# https://bugs.launchpad.net/neutron/+bug/1527993
return curr_speaker.neighbor_state_get()
def get_bgp_peer_statistics(self, speaker_as, peer_ip):
LOG.info(_LI('Collecting BGP Peer statistics for peer_ip=%(peer)s, '
'running in speaker_as=%(speaker_as)d '),
{'peer': peer_ip, 'speaker_as': speaker_as})
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
if not curr_speaker:
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
rtid=self.routerid)
# TODO(vikram): Filter and return the necessary information.
# Will be done as part of new RFE requirement
# https://bugs.launchpad.net/neutron/+bug/1527993
return curr_speaker.neighbor_state_get(address=peer_ip)

View File

@ -0,0 +1,75 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import six
from neutron_dynamic_routing.services.bgp.agent.driver import exceptions as bgp_driver_exc # noqa
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts # noqa
# Parameter validation functions provided are provided by the base.
def validate_as_num(param, as_num):
if not isinstance(as_num, six.integer_types):
raise bgp_driver_exc.InvalidParamType(param=param,
param_type='integer')
if not (bgp_consts.MIN_ASNUM <= as_num <= bgp_consts.MAX_ASNUM):
# Must be in [AS_NUM_MIN, AS_NUM_MAX] range.
allowed_range = ('[' +
str(bgp_consts.MIN_ASNUM) + '-' +
str(bgp_consts.MAX_ASNUM) +
']')
raise bgp_driver_exc.InvalidParamRange(param=param,
range=allowed_range)
def validate_auth(auth_type, password):
validate_string(password)
if auth_type in bgp_consts.SUPPORTED_AUTH_TYPES:
if auth_type != 'none' and password is None:
raise bgp_driver_exc.PasswordNotSpecified(auth_type=auth_type)
if auth_type == 'none' and password is not None:
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
else:
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
def validate_string(param):
if param is not None:
if not isinstance(param, six.string_types):
raise bgp_driver_exc.InvalidParamType(param=param,
param_type='string')
class BgpMultiSpeakerCache(object):
"""Class for saving multiple BGP speakers information.
Version history:
1.0 - Initial version for caching multiple BGP speaker information.
"""
def __init__(self):
self.cache = {}
def get_hosted_bgp_speakers_count(self):
return len(self.cache)
def put_bgp_speaker(self, local_as, speaker):
self.cache[local_as] = speaker
def get_bgp_speaker(self, local_as):
return self.cache.get(local_as)
def remove_bgp_speaker(self, local_as):
self.cache.pop(local_as, None)

View File

@ -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.
import sys
from oslo_config import cfg
from oslo_service import service
from neutron.agent.common import config
from neutron.agent.linux import external_process
from neutron.common import config as common_config
from neutron import service as neutron_service
from neutron_dynamic_routing.services.bgp.agent import config as bgp_dragent_config # noqa
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts # noqa
def register_options():
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF)
cfg.CONF.register_opts(bgp_dragent_config.BGP_DRIVER_OPTS, 'BGP')
cfg.CONF.register_opts(bgp_dragent_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
cfg.CONF.register_opts(external_process.OPTS)
def main():
register_options()
common_config.init(sys.argv[1:])
config.setup_logging()
server = neutron_service.Service.create(
binary='neutron-bgp-dragent',
topic=bgp_consts.BGP_DRAGENT,
report_interval=cfg.CONF.AGENT.report_interval,
manager='neutron_dynamic_routing.services.bgp.agent.bgp_dragent.'
'BgpDrAgentWithStateReport')
service.launch(cfg.CONF, server).wait()

View File

@ -0,0 +1,390 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 netaddr import IPAddress
from neutron_lib import constants as n_const
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import rpc as n_rpc
from neutron import context
from neutron import policy
from neutron.services import service_base
from neutron_dynamic_routing.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api # noqa
from neutron_dynamic_routing.api.rpc.handlers import bgp_speaker_rpc as bs_rpc
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db
from neutron_dynamic_routing.extensions import bgp as bgp_ext
from neutron_dynamic_routing.extensions import bgp_dragentscheduler as dras_ext
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
LOG = logging.getLogger(__name__)
class BgpPlugin(service_base.ServicePluginBase,
bgp_db.BgpDbMixin,
bgp_dragentscheduler_db.BgpDrAgentSchedulerDbMixin):
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS,
dras_ext.BGP_DRAGENT_SCHEDULER_EXT_ALIAS]
def __init__(self):
super(BgpPlugin, self).__init__()
self.bgp_drscheduler = importutils.import_object(
cfg.CONF.bgp_drscheduler_driver)
self._setup_rpc()
self._register_callbacks()
def get_plugin_name(self):
return PLUGIN_NAME
def get_plugin_type(self):
return bgp_ext.BGP_EXT_ALIAS
def get_plugin_description(self):
"""returns string description of the plugin."""
return ("BGP dynamic routing service for announcement of next-hops "
"for tenant networks, floating IP's, and DVR host routes.")
def _setup_rpc(self):
self.topic = bgp_consts.BGP_PLUGIN
self.conn = n_rpc.create_connection()
self.agent_notifiers[bgp_consts.AGENT_TYPE_BGP_ROUTING] = (
bgp_dr_rpc_agent_api.BgpDrAgentNotifyApi()
)
self._bgp_rpc = self.agent_notifiers[bgp_consts.AGENT_TYPE_BGP_ROUTING]
self.endpoints = [bs_rpc.BgpSpeakerRpcCallback()]
self.conn.create_consumer(self.topic, self.endpoints,
fanout=False)
self.conn.consume_in_threads()
def _register_callbacks(self):
registry.subscribe(self.floatingip_update_callback,
resources.FLOATING_IP,
events.AFTER_UPDATE)
registry.subscribe(self.router_interface_callback,
resources.ROUTER_INTERFACE,
events.AFTER_CREATE)
registry.subscribe(self.router_interface_callback,
resources.ROUTER_INTERFACE,
events.BEFORE_CREATE)
registry.subscribe(self.router_interface_callback,
resources.ROUTER_INTERFACE,
events.AFTER_DELETE)
registry.subscribe(self.router_gateway_callback,
resources.ROUTER_GATEWAY,
events.AFTER_CREATE)
registry.subscribe(self.router_gateway_callback,
resources.ROUTER_GATEWAY,
events.AFTER_DELETE)
def get_bgp_speakers(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
policy.check_is_admin(context)
return super(BgpPlugin, self).get_bgp_speakers(
context,
filters=filters,
fields=fields,
sorts=sorts,
limit=limit,
marker=marker,
page_reverse=page_reverse)
def get_bgp_speaker(self, context, bgp_speaker_id, fields=None):
policy.check_is_admin(context)
return super(BgpPlugin, self).get_bgp_speaker(context,
bgp_speaker_id,
fields=fields)
def create_bgp_speaker(self, context, bgp_speaker):
policy.check_is_admin(context)
bgp_speaker = super(BgpPlugin, self).create_bgp_speaker(context,
bgp_speaker)
return bgp_speaker
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
policy.check_is_admin(context)
return super(BgpPlugin, self).update_bgp_speaker(context,
bgp_speaker_id,
bgp_speaker)
def delete_bgp_speaker(self, context, bgp_speaker_id):
policy.check_is_admin(context)
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
context,
[bgp_speaker_id])
super(BgpPlugin, self).delete_bgp_speaker(context, bgp_speaker_id)
for agent in hosted_bgp_dragents:
self._bgp_rpc.bgp_speaker_removed(context,
bgp_speaker_id,
agent.host)
def get_bgp_peers(self, context, fields=None, filters=None, sorts=None,
limit=None, marker=None, page_reverse=False):
policy.check_is_admin(context)
return super(BgpPlugin, self).get_bgp_peers(
context, fields=fields,
filters=filters, sorts=sorts,
limit=limit, marker=marker,
page_reverse=page_reverse)
def get_bgp_peer(self, context, bgp_peer_id, fields=None):
policy.check_is_admin(context)
return super(BgpPlugin, self).get_bgp_peer(context,
bgp_peer_id,
fields=fields)
def create_bgp_peer(self, context, bgp_peer):
policy.check_is_admin(context)
return super(BgpPlugin, self).create_bgp_peer(context, bgp_peer)
def update_bgp_peer(self, context, bgp_peer_id, bgp_peer):
policy.check_is_admin(context)
return super(BgpPlugin, self).update_bgp_peer(context,
bgp_peer_id,
bgp_peer)
def delete_bgp_peer(self, context, bgp_peer_id):
policy.check_is_admin(context)
super(BgpPlugin, self).delete_bgp_peer(context, bgp_peer_id)
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
policy.check_is_admin(context)
ret_value = super(BgpPlugin, self).add_bgp_peer(context,
bgp_speaker_id,
bgp_peer_info)
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
context,
[bgp_speaker_id])
for agent in hosted_bgp_dragents:
self._bgp_rpc.bgp_peer_associated(context, bgp_speaker_id,
ret_value['bgp_peer_id'],
agent.host)
return ret_value
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
policy.check_is_admin(context)
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
context, [bgp_speaker_id])
ret_value = super(BgpPlugin, self).remove_bgp_peer(context,
bgp_speaker_id,
bgp_peer_info)
for agent in hosted_bgp_dragents:
self._bgp_rpc.bgp_peer_disassociated(context,
bgp_speaker_id,
ret_value['bgp_peer_id'],
agent.host)
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
policy.check_is_admin(context)
super(BgpPlugin, self).add_bgp_speaker_to_dragent(context,
agent_id,
speaker_id)
def remove_bgp_speaker_from_dragent(self, context, agent_id, speaker_id):
policy.check_is_admin(context)
super(BgpPlugin, self).remove_bgp_speaker_from_dragent(context,
agent_id,
speaker_id)
def list_bgp_speaker_on_dragent(self, context, agent_id):
policy.check_is_admin(context)
return super(BgpPlugin, self).list_bgp_speaker_on_dragent(context,
agent_id)
def list_dragent_hosting_bgp_speaker(self, context, speaker_id):
policy.check_is_admin(context)
return super(BgpPlugin, self).list_dragent_hosting_bgp_speaker(
context,
speaker_id)
def add_gateway_network(self, context, bgp_speaker_id, network_info):
policy.check_is_admin(context)
return super(BgpPlugin, self).add_gateway_network(context,
bgp_speaker_id,
network_info)
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
policy.check_is_admin(context)
return super(BgpPlugin, self).remove_gateway_network(context,
bgp_speaker_id,
network_info)
def get_advertised_routes(self, context, bgp_speaker_id):
policy.check_is_admin(context)
return super(BgpPlugin, self).get_advertised_routes(context,
bgp_speaker_id)
def floatingip_update_callback(self, resource, event, trigger, **kwargs):
if event != events.AFTER_UPDATE:
return
ctx = context.get_admin_context()
new_router_id = kwargs['router_id']
last_router_id = kwargs['last_known_router_id']
next_hop = kwargs['next_hop']
dest = kwargs['floating_ip_address'] + '/32'
bgp_speakers = self._bgp_speakers_for_gw_network_by_family(
ctx,
kwargs['floating_network_id'],
n_const.IP_VERSION_4)
if last_router_id and new_router_id != last_router_id:
for bgp_speaker in bgp_speakers:
self.stop_route_advertisements(ctx, self._bgp_rpc,
bgp_speaker.id, [dest])
if next_hop and new_router_id != last_router_id:
new_host_route = {'destination': dest, 'next_hop': next_hop}
for bgp_speaker in bgp_speakers:
self.start_route_advertisements(ctx, self._bgp_rpc,
bgp_speaker.id,
[new_host_route])
def router_interface_callback(self, resource, event, trigger, **kwargs):
if event == events.AFTER_CREATE:
self._handle_router_interface_after_create(**kwargs)
if event == events.AFTER_DELETE:
gw_network = kwargs['network_id']
next_hops = self._next_hops_from_gateway_ips(
kwargs['gateway_ips'])
ctx = context.get_admin_context()
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
for speaker in speakers:
routes = self._route_list_from_prefixes_and_next_hop(
kwargs['cidrs'],
next_hops[speaker.ip_version])
self._handle_router_interface_after_delete(gw_network, routes)
def _handle_router_interface_after_create(self, **kwargs):
gw_network = kwargs['network_id']
if not gw_network:
return
ctx = context.get_admin_context()
with ctx.session.begin(subtransactions=True):
speakers = self._bgp_speakers_for_gateway_network(ctx,
gw_network)
next_hops = self._next_hops_from_gateway_ips(
kwargs['gateway_ips'])
for speaker in speakers:
prefixes = self._tenant_prefixes_by_router(
ctx,
kwargs['router_id'],
speaker.id)
next_hop = next_hops.get(speaker.ip_version)
if next_hop:
rl = self._route_list_from_prefixes_and_next_hop(prefixes,
next_hop)
self.start_route_advertisements(ctx,
self._bgp_rpc,
speaker.id,
rl)
def router_gateway_callback(self, resource, event, trigger, **kwargs):
if event == events.AFTER_CREATE:
self._handle_router_gateway_after_create(**kwargs)
if event == events.AFTER_DELETE:
gw_network = kwargs['network_id']
router_id = kwargs['router_id']
next_hops = self._next_hops_from_gateway_ips(
kwargs['gateway_ips'])
ctx = context.get_admin_context()
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
for speaker in speakers:
if speaker.ip_version in next_hops:
next_hop = next_hops[speaker.ip_version]
prefixes = self._tenant_prefixes_by_router(ctx,
router_id,
speaker.id)
routes = self._route_list_from_prefixes_and_next_hop(
prefixes,
next_hop)
self._handle_router_interface_after_delete(gw_network, routes)
def _handle_router_gateway_after_create(self, **kwargs):
ctx = context.get_admin_context()
gw_network = kwargs['network_id']
router_id = kwargs['router_id']
with ctx.session.begin(subtransactions=True):
speakers = self._bgp_speakers_for_gateway_network(ctx,
gw_network)
next_hops = self._next_hops_from_gateway_ips(kwargs['gw_ips'])
for speaker in speakers:
if speaker.ip_version in next_hops:
next_hop = next_hops[speaker.ip_version]
prefixes = self._tenant_prefixes_by_router(ctx,
router_id,
speaker.id)
routes = self._route_list_from_prefixes_and_next_hop(
prefixes,
next_hop)
self.start_route_advertisements(ctx, self._bgp_rpc,
speaker.id, routes)
def _handle_router_interface_after_delete(self, gw_network, routes):
if gw_network and routes:
ctx = context.get_admin_context()
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
for speaker in speakers:
self.stop_route_advertisements(ctx, self._bgp_rpc,
speaker.id, routes)
def _next_hops_from_gateway_ips(self, gw_ips):
if gw_ips:
return {IPAddress(ip).version: ip for ip in gw_ips}
return {}
def start_route_advertisements(self, ctx, bgp_rpc,
bgp_speaker_id, routes):
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
for agent in agents['agents']:
bgp_rpc.bgp_routes_advertisement(ctx,
bgp_speaker_id,
routes,
agent['host'])
msg = "Starting route advertisements for %s on BgpSpeaker %s"
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
def stop_route_advertisements(self, ctx, bgp_rpc,
bgp_speaker_id, routes):
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
for agent in agents['agents']:
bgp_rpc.bgp_routes_withdrawal(ctx,
bgp_speaker_id,
routes,
agent['host'])
msg = "Stopping route advertisements for %s on BgpSpeaker %s"
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
def _debug_log_for_routes(self, msg, routes, bgp_speaker_id):
# Could have a large number of routes passed, check log level first
if LOG.isEnabledFor(logging.DEBUG):
for route in routes:
LOG.debug(msg, route, bgp_speaker_id)

View File

@ -0,0 +1,27 @@
# 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.
AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'
BGP_DRAGENT = 'bgp_dragent'
BGP_PLUGIN = 'q-bgp-plugin'
# List of supported authentication types.
SUPPORTED_AUTH_TYPES = ['none', 'md5']
# Supported AS number range
MIN_ASNUM = 1
MAX_ASNUM = 65535

View File

@ -0,0 +1,30 @@
# 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.
import itertools
import neutron_dynamic_routing.services.bgp.agent.config
def list_bgp_agent_opts():
return [
('BGP',
itertools.chain(
neutron_dynamic_routing.services.bgp.agent.
config.BGP_DRIVER_OPTS,
neutron_dynamic_routing.services.bgp.agent.
config.BGP_PROTO_CONFIG_OPTS)
)
]

View File

@ -0,0 +1,192 @@
# 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_db import exception as db_exc
from oslo_log import log as logging
from sqlalchemy.orm import exc
from sqlalchemy import sql
from neutron.db import agents_db
from neutron.scheduler import base_resource_filter
from neutron.scheduler import base_scheduler
from neutron_dynamic_routing._i18n import _LI, _LW
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
LOG = logging.getLogger(__name__)
BGP_SPEAKER_PER_DRAGENT = 1
class BgpDrAgentFilter(base_resource_filter.BaseResourceFilter):
def bind(self, context, agents, bgp_speaker_id):
"""Bind the BgpSpeaker to a BgpDrAgent."""
bound_agents = agents[:]
for agent in agents:
# saving agent_id to use it after rollback to avoid
# DetachedInstanceError
agent_id = agent.id
binding = bgp_dras_db.BgpSpeakerDrAgentBinding()
binding.agent_id = agent_id
binding.bgp_speaker_id = bgp_speaker_id
try:
with context.session.begin(subtransactions=True):
context.session.add(binding)
except db_exc.DBDuplicateEntry:
# it's totally ok, someone just did our job!
bound_agents.remove(agent)
LOG.info(_LI('BgpDrAgent %s already present'), agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s is scheduled to be '
'hosted by BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': bgp_speaker_id,
'agent_id': agent_id})
super(BgpDrAgentFilter, self).bind(context, bound_agents,
bgp_speaker_id)
def filter_agents(self, plugin, context, bgp_speaker):
"""Return the agents that can host the BgpSpeaker."""
agents_dict = self._get_bgp_speaker_hostable_dragents(
plugin, context, bgp_speaker)
if not agents_dict['hostable_agents'] or agents_dict['n_agents'] <= 0:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
return agents_dict
def _get_active_dragents(self, plugin, context):
"""Return a list of active BgpDrAgents."""
with context.session.begin(subtransactions=True):
active_dragents = plugin.get_agents_db(
context, filters={
'agent_type': [bgp_consts.AGENT_TYPE_BGP_ROUTING],
'admin_state_up': [True]})
if not active_dragents:
return []
return active_dragents
def _get_num_dragents_hosting_bgp_speaker(self, bgp_speaker_id,
dragent_bindings):
return sum(1 if dragent_binding.bgp_speaker_id == bgp_speaker_id else 0
for dragent_binding in dragent_bindings)
def _get_bgp_speaker_hostable_dragents(self, plugin, context, bgp_speaker):
"""Return number of additional BgpDrAgents which will actually host
the given BgpSpeaker and a list of BgpDrAgents which can host the
given BgpSpeaker
"""
# only one BgpSpeaker can be hosted by a BgpDrAgent for now.
dragents_per_bgp_speaker = BGP_SPEAKER_PER_DRAGENT
dragent_bindings = plugin.get_dragent_bgp_speaker_bindings(context)
agents_hosting = [dragent_binding.agent_id
for dragent_binding in dragent_bindings]
num_dragents_hosting_bgp_speaker = (
self._get_num_dragents_hosting_bgp_speaker(bgp_speaker['id'],
dragent_bindings))
n_agents = dragents_per_bgp_speaker - num_dragents_hosting_bgp_speaker
if n_agents <= 0:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
active_dragents = self._get_active_dragents(plugin, context)
hostable_dragents = [
agent for agent in set(active_dragents)
if agent.id not in agents_hosting and plugin.is_eligible_agent(
active=True, agent=agent)
]
if not hostable_dragents:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
n_agents = min(len(hostable_dragents), n_agents)
return {'n_agents': n_agents,
'hostable_agents': hostable_dragents,
'hosted_agents': num_dragents_hosting_bgp_speaker}
class BgpDrAgentSchedulerBase(BgpDrAgentFilter):
def schedule_unscheduled_bgp_speakers(self, context, host):
"""Schedule unscheduled BgpSpeaker to a BgpDrAgent.
"""
LOG.debug('Started auto-scheduling on host %s', host)
with context.session.begin(subtransactions=True):
query = context.session.query(agents_db.Agent)
query = query.filter_by(
agent_type=bgp_consts.AGENT_TYPE_BGP_ROUTING,
host=host,
admin_state_up=sql.true())
try:
bgp_dragent = query.one()
except (exc.NoResultFound):
LOG.debug('No enabled BgpDrAgent on host %s', host)
return False
if agents_db.AgentDbMixin.is_agent_down(
bgp_dragent.heartbeat_timestamp):
LOG.warning(_LW('BgpDrAgent %s is down'), bgp_dragent.id)
return False
if self._is_bgp_speaker_hosted(context, bgp_dragent['id']):
# One BgpDrAgent can only host one BGP speaker
LOG.debug('BgpDrAgent already hosting a speaker on host %s. '
'Cannot schedule an another one', host)
return False
unscheduled_speakers = self._get_unscheduled_bgp_speakers(context)
if not unscheduled_speakers:
LOG.debug('Nothing to auto-schedule on host %s', host)
return False
self.bind(context, [bgp_dragent], unscheduled_speakers[0])
return True
def _is_bgp_speaker_hosted(self, context, agent_id):
speaker_binding_model = bgp_dras_db.BgpSpeakerDrAgentBinding
query = context.session.query(speaker_binding_model)
query = query.filter(speaker_binding_model.agent_id == agent_id)
return query.count() > 0
def _get_unscheduled_bgp_speakers(self, context):
"""BGP speakers that needs to be scheduled.
"""
no_agent_binding = ~sql.exists().where(
bgp_db.BgpSpeaker.id ==
bgp_dras_db.BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = context.session.query(bgp_db.BgpSpeaker.id).filter(
no_agent_binding)
return [bgp_speaker_id_[0] for bgp_speaker_id_ in query]
class ChanceScheduler(base_scheduler.BaseChanceScheduler,
BgpDrAgentSchedulerBase):
def __init__(self):
super(ChanceScheduler, self).__init__(self)
class WeightScheduler(base_scheduler.BaseWeightScheduler,
BgpDrAgentSchedulerBase):
def __init__(self):
super(WeightScheduler, self).__init__(self)

View File

@ -0,0 +1,288 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 netaddr
from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
import testtools
from neutron.tests.api import base
from neutron.tests.tempest.common import tempest_fixtures as fixtures
CONF = config.CONF
class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest):
default_bgp_speaker_args = {'local_as': '1234',
'ip_version': 4,
'name': 'my-bgp-speaker',
'advertise_floating_ip_host_routes': True,
'advertise_tenant_networks': True}
default_bgp_peer_args = {'remote_as': '4321',
'name': 'my-bgp-peer',
'peer_ip': '192.168.1.1',
'auth_type': 'md5', 'password': 'my-secret'}
@classmethod
def resource_setup(cls):
super(BgpSpeakerTestJSONBase, cls).resource_setup()
if not test.is_extension_enabled('bgp_speaker', 'network'):
msg = "BGP Speaker extension is not enabled."
raise cls.skipException(msg)
cls.admin_routerports = []
cls.admin_floatingips = []
cls.admin_routers = []
cls.ext_net_id = CONF.network.public_network_id
@classmethod
def resource_cleanup(cls):
for floatingip in cls.admin_floatingips:
cls._try_delete_resource(cls.admin_client.delete_floatingip,
floatingip['id'])
for routerport in cls.admin_routerports:
cls._try_delete_resource(
cls.admin_client.remove_router_interface_with_subnet_id,
routerport['router_id'], routerport['subnet_id'])
for router in cls.admin_routers:
cls._try_delete_resource(cls.admin_client.delete_router,
router['id'])
super(BgpSpeakerTestJSONBase, cls).resource_cleanup()
def create_bgp_speaker(self, auto_delete=True, **args):
data = {'bgp_speaker': args}
bgp_speaker = self.admin_client.create_bgp_speaker(data)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
if auto_delete:
self.addCleanup(self.delete_bgp_speaker, bgp_speaker_id)
return bgp_speaker
def create_bgp_peer(self, **args):
bgp_peer = self.admin_client.create_bgp_peer({'bgp_peer': args})
bgp_peer_id = bgp_peer['bgp-peer']['id']
self.addCleanup(self.delete_bgp_peer, bgp_peer_id)
return bgp_peer
def update_bgp_speaker(self, id, **args):
data = {'bgp_speaker': args}
return self.admin_client.update_bgp_speaker(id, data)
def delete_bgp_speaker(self, id):
return self.admin_client.delete_bgp_speaker(id)
def get_bgp_speaker(self, id):
return self.admin_client.get_bgp_speaker(id)
def create_bgp_speaker_and_peer(self):
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
return (bgp_speaker, bgp_peer)
def delete_bgp_peer(self, id):
return self.admin_client.delete_bgp_peer(id)
def add_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
return self.admin_client.add_bgp_peer_with_id(bgp_speaker_id,
bgp_peer_id)
def remove_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
return self.admin_client.remove_bgp_peer_with_id(bgp_speaker_id,
bgp_peer_id)
def delete_address_scope(self, id):
return self.admin_client.delete_address_scope(id)
class BgpSpeakerTestJSON(BgpSpeakerTestJSONBase):
"""
Tests the following operations in the Neutron API using the REST client for
Neutron:
Create bgp-speaker
Delete bgp-speaker
Create bgp-peer
Update bgp-peer
Delete bgp-peer
"""
@test.idempotent_id('df259771-7104-4ffa-b77f-bd183600d7f9')
def test_delete_bgp_speaker(self):
bgp_speaker = self.create_bgp_speaker(auto_delete=False,
**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.delete_bgp_speaker(bgp_speaker_id)
self.assertRaises(lib_exc.NotFound,
self.get_bgp_speaker,
bgp_speaker_id)
@test.idempotent_id('81d9dc45-19f8-4c6e-88b8-401d965cd1b0')
def test_create_bgp_peer(self):
self.create_bgp_peer(**self.default_bgp_peer_args)
@test.idempotent_id('6ade0319-1ee2-493c-ac4b-5eb230ff3a77')
def test_add_bgp_peer(self):
bgp_speaker, bgp_peer = self.create_bgp_speaker_and_peer()
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
bgp_peer_id = bgp_peer['bgp-peer']['id']
self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
self.assertEqual(1, len(bgp_peers_list))
self.assertTrue(bgp_peer_id in bgp_peers_list)
@test.idempotent_id('f9737708-1d79-440b-8350-779f97d882ee')
def test_remove_bgp_peer(self):
bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
bgp_peer_id = bgp_peer['bgp-peer']['id']
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
self.assertTrue(bgp_peer_id in bgp_peers_list)
bgp_speaker = self.remove_bgp_peer(bgp_speaker_id, bgp_peer_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
self.assertTrue(not bgp_peers_list)
@testtools.skip('bug/1553374')
@test.idempotent_id('23c8eb37-d10d-4f43-b2e7-6542cb6a4405')
def test_add_gateway_network(self):
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
self.ext_net_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
network_list = bgp_speaker['bgp-speaker']['networks']
self.assertEqual(1, len(network_list))
self.assertTrue(self.ext_net_id in network_list)
@testtools.skip('bug/1553374')
@test.idempotent_id('6cfc7137-0d99-4a3d-826c-9d1a3a1767b0')
def test_remove_gateway_network(self):
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
self.ext_net_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
networks = bgp_speaker['bgp-speaker']['networks']
self.assertTrue(self.ext_net_id in networks)
self.admin_client.remove_bgp_gateway_network(bgp_speaker_id,
self.ext_net_id)
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
network_list = bgp_speaker['bgp-speaker']['networks']
self.assertTrue(not network_list)
@testtools.skip('bug/1553374')
@test.idempotent_id('5bef22ad-5e70-4f7b-937a-dc1944642996')
def test_get_advertised_routes_null_address_scope(self):
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
self.ext_net_id)
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
self.assertEqual(0, len(routes['advertised_routes']))
@testtools.skip('bug/1553374')
@test.idempotent_id('cae9cdb1-ad65-423c-9604-d4cd0073616e')
def test_get_advertised_routes_floating_ips(self):
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
self.ext_net_id)
tenant_net = self.create_network()
tenant_subnet = self.create_subnet(tenant_net)
ext_gw_info = {'network_id': self.ext_net_id}
router = self.admin_client.create_router(
'my-router',
external_gateway_info=ext_gw_info,
admin_state_up=True,
distributed=False)
self.admin_routers.append(router['router'])
self.admin_client.add_router_interface_with_subnet_id(
router['router']['id'],
tenant_subnet['id'])
self.admin_routerports.append({'router_id': router['router']['id'],
'subnet_id': tenant_subnet['id']})
tenant_port = self.create_port(tenant_net)
floatingip = self.create_floatingip(self.ext_net_id)
self.admin_floatingips.append(floatingip)
self.client.update_floatingip(floatingip['id'],
port_id=tenant_port['id'])
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
self.assertEqual(1, len(routes['advertised_routes']))
self.assertEqual(floatingip['floating_ip_address'] + '/32',
routes['advertised_routes'][0]['destination'])
@testtools.skip('bug/1553374')
@test.idempotent_id('c9ad566e-fe8f-4559-8303-bbad9062a30c')
def test_get_advertised_routes_tenant_networks(self):
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
addr_scope = self.create_address_scope('my-scope', ip_version=4)
ext_net = self.create_shared_network(**{'router:external': True})
tenant_net = self.create_network()
ext_subnetpool = self.create_subnetpool(
'test-pool-ext',
is_admin=True,
default_prefixlen=24,
address_scope_id=addr_scope['id'],
prefixes=['8.0.0.0/8'])
tenant_subnetpool = self.create_subnetpool(
'tenant-test-pool',
default_prefixlen=25,
address_scope_id=addr_scope['id'],
prefixes=['10.10.0.0/16'])
self.create_subnet({'id': ext_net['id']},
cidr=netaddr.IPNetwork('8.0.0.0/24'),
ip_version=4,
client=self.admin_client,
subnetpool_id=ext_subnetpool['id'])
tenant_subnet = self.create_subnet(
{'id': tenant_net['id']},
cidr=netaddr.IPNetwork('10.10.0.0/24'),
ip_version=4,
subnetpool_id=tenant_subnetpool['id'])
ext_gw_info = {'network_id': ext_net['id']}
router = self.admin_client.create_router(
'my-router',
external_gateway_info=ext_gw_info,
distributed=False)['router']
self.admin_routers.append(router)
self.admin_client.add_router_interface_with_subnet_id(
router['id'],
tenant_subnet['id'])
self.admin_routerports.append({'router_id': router['id'],
'subnet_id': tenant_subnet['id']})
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
ext_net['id'])
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
self.assertEqual(1, len(routes['advertised_routes']))
self.assertEqual(tenant_subnet['cidr'],
routes['advertised_routes'][0]['destination'])
fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
self.assertEqual(fixed_ip['ip_address'],
routes['advertised_routes'][0]['next_hop'])

View File

@ -0,0 +1,121 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 netaddr
from tempest.lib import exceptions as lib_exc
from tempest import test
from neutron_dynamic_routing.tests.api import test_bgp_speaker_extensions as test_base # noqa
class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase):
"""Negative test cases asserting proper behavior of BGP API extension"""
@test.attr(type=['negative', 'smoke'])
@test.idempotent_id('75e9ee2f-6efd-4320-bff7-ae24741c8b06')
def test_create_bgp_speaker_illegal_local_asn(self):
self.assertRaises(lib_exc.BadRequest,
self.create_bgp_speaker,
local_as='65537')
@test.attr(type=['negative', 'smoke'])
@test.idempotent_id('6742ec2e-382a-4453-8791-13a19b47cd13')
def test_create_bgp_speaker_non_admin(self):
self.assertRaises(lib_exc.Forbidden,
self.client.create_bgp_speaker,
{'bgp_speaker': self.default_bgp_speaker_args})
@test.attr(type=['negative', 'smoke'])
@test.idempotent_id('33f7aaf0-9786-478b-b2d1-a51086a50eb4')
def test_create_bgp_peer_non_admin(self):
self.assertRaises(lib_exc.Forbidden,
self.client.create_bgp_peer,
{'bgp_peer': self.default_bgp_peer_args})
@test.attr(type=['negative', 'smoke'])
@test.idempotent_id('39435932-0266-4358-899b-0e9b1e53c3e9')
def test_update_bgp_speaker_local_asn(self):
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.assertRaises(lib_exc.BadRequest, self.update_bgp_speaker,
bgp_speaker_id, local_as='4321')
@test.idempotent_id('9cc33701-51e5-421f-a5d5-fd7b330e550f')
def test_get_advertised_routes_tenant_networks(self):
addr_scope1 = self.create_address_scope('my-scope1', ip_version=4)
addr_scope2 = self.create_address_scope('my-scope2', ip_version=4)
ext_net = self.create_shared_network(**{'router:external': True})
tenant_net1 = self.create_network()
tenant_net2 = self.create_network()
ext_subnetpool = self.create_subnetpool(
'test-pool-ext',
is_admin=True,
default_prefixlen=24,
address_scope_id=addr_scope1['id'],
prefixes=['8.0.0.0/8'])
tenant_subnetpool1 = self.create_subnetpool(
'tenant-test-pool',
default_prefixlen=25,
address_scope_id=addr_scope1['id'],
prefixes=['10.10.0.0/16'])
tenant_subnetpool2 = self.create_subnetpool(
'tenant-test-pool',
default_prefixlen=25,
address_scope_id=addr_scope2['id'],
prefixes=['11.10.0.0/16'])
self.create_subnet({'id': ext_net['id']},
cidr=netaddr.IPNetwork('8.0.0.0/24'),
ip_version=4,
client=self.admin_client,
subnetpool_id=ext_subnetpool['id'])
tenant_subnet1 = self.create_subnet(
{'id': tenant_net1['id']},
cidr=netaddr.IPNetwork('10.10.0.0/24'),
ip_version=4,
subnetpool_id=tenant_subnetpool1['id'])
tenant_subnet2 = self.create_subnet(
{'id': tenant_net2['id']},
cidr=netaddr.IPNetwork('11.10.0.0/24'),
ip_version=4,
subnetpool_id=tenant_subnetpool2['id'])
ext_gw_info = {'network_id': ext_net['id']}
router = self.admin_client.create_router(
'my-router',
distributed=False,
external_gateway_info=ext_gw_info)['router']
self.admin_routers.append(router)
self.admin_client.add_router_interface_with_subnet_id(
router['id'],
tenant_subnet1['id'])
self.admin_routerports.append({'router_id': router['id'],
'subnet_id': tenant_subnet1['id']})
self.admin_client.add_router_interface_with_subnet_id(
router['id'],
tenant_subnet2['id'])
self.admin_routerports.append({'router_id': router['id'],
'subnet_id': tenant_subnet2['id']})
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
ext_net['id'])
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
self.assertEqual(1, len(routes['advertised_routes']))
self.assertEqual(tenant_subnet1['cidr'],
routes['advertised_routes'][0]['destination'])
fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
self.assertEqual(fixed_ip['ip_address'],
routes['advertised_routes'][0]['next_hop'])

View File

@ -0,0 +1,42 @@
# Copyright 2016 Hewlett Packard Development Co
#
# 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 import context
from neutron.tests.common import helpers
from neutron_dynamic_routing.services.bgp.common import constants as bgp_const
def _get_bgp_dragent_dict(host):
agent = {
'binary': 'neutron-bgp-dragent',
'host': host,
'topic': 'q-bgp_dragent',
'agent_type': bgp_const.AGENT_TYPE_BGP_ROUTING,
'configurations': {'bgp_speakers': 1}}
return agent
def register_bgp_dragent(host=helpers.HOST, admin_state_up=True,
alive=True):
agent = helpers._register_agent(
_get_bgp_dragent_dict(host))
if not admin_state_up:
helpers.set_agent_admin_state(agent['id'])
if not alive:
helpers.kill_agent(agent['id'])
return helpers.FakePlugin()._get_agent_by_type_and_host(
context.get_admin_context(), agent['agent_type'], agent['host'])

View File

@ -0,0 +1,209 @@
# 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.
import testscenarios
from neutron import context
from neutron.db import agents_db
from neutron.db import common_db_mixin
from neutron.tests.unit import testlib_api
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron_dynamic_routing.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras # noqa
from neutron_dynamic_routing.tests.common import helpers
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestAutoSchedule(testlib_api.SqlTestCase,
bgp_dras_db.BgpDrAgentSchedulerDbMixin,
agents_db.AgentDbMixin,
common_db_mixin.CommonDbMixin):
"""Test various scenarios for schedule_unscheduled_bgp_speakers.
Below is the brief description of the scenario variables
--------------------------------------------------------
host_count
number of hosts.
agent_count
number of BGP dynamic routing agents.
down_agent_count
number of DRAgents which are inactive.
bgp_speaker_count
Number of bgp_speakers.
hosted_bgp_speakers
A mapping of agent id to the ids of the bgp_speakers that they
should be initially hosting.
expected_schedule_return_value
Expected return value of 'schedule_unscheduled_bgp_speakers'.
expected_hosted_bgp_speakers
This stores the expected bgp_speakers that should have been
scheduled (or that could have already been scheduled) for each
agent after the 'schedule_unscheduled_bgp_speakers' function is
called.
"""
scenarios = [
('No BgpDrAgent scheduled, if no DRAgent is present',
dict(host_count=1,
agent_count=0,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=False)),
('No BgpDrAgent scheduled, if no BGP speaker are present',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=0,
hosted_bgp_speakers={},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': []})),
('No BgpDrAgent scheduled, if BGP speaker already hosted',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']})),
('BgpDrAgent scheduled to the speaker, if the speaker is not hosted',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=True,
expected_hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']})),
('No BgpDrAgent scheduled, if all the agents are down',
dict(host_count=2,
agent_count=2,
down_agent_count=2,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': [],
'agent-1': [], })),
]
def _strip_host_index(self, name):
"""Strips the host index.
Eg. if name = '2-agent-3', then 'agent-3' is returned.
"""
return name[name.find('-') + 1:]
def _extract_index(self, name):
"""Extracts the index number and returns.
Eg. if name = '2-agent-3', then 3 is returned
"""
return int(name.split('-')[-1])
def _get_hosted_bgp_speakers_on_dragent(self, agent_id):
query = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = query.filter(
bgp_dras_db.BgpSpeakerDrAgentBinding.agent_id ==
agent_id)
return [item[0] for item in query]
def _create_and_set_agents_down(self, hosts, agent_count=0,
down_agent_count=0, admin_state_up=True):
agents = []
if agent_count:
for i, host in enumerate(hosts):
is_alive = i >= down_agent_count
agents.append(helpers.register_bgp_dragent(
host,
admin_state_up=admin_state_up,
alive=is_alive))
return agents
def _save_bgp_speakers(self, bgp_speakers):
cls = bgp_db.BgpDbMixin()
bgp_speaker_body = {
'bgp_speaker': {'name': 'fake_bgp_speaker',
'ip_version': '4',
'local_as': '123',
'advertise_floating_ip_host_routes': '0',
'advertise_tenant_networks': '0',
'peers': [],
'networks': []}}
i = 1
for bgp_speaker_id in bgp_speakers:
bgp_speaker_body['bgp_speaker']['local_as'] = i
cls._save_bgp_speaker(self.ctx, bgp_speaker_body,
uuid=bgp_speaker_id)
i = i + 1
def _test_auto_schedule(self, host_index):
scheduler = bgp_dras.ChanceScheduler()
self.ctx = context.get_admin_context()
msg = 'host_index = %s' % host_index
# create hosts
hosts = ['%s-agent-%s' % (host_index, i)
for i in range(self.host_count)]
bgp_dragents = self._create_and_set_agents_down(hosts,
self.agent_count,
self.down_agent_count)
# create bgp_speakers
self._bgp_speakers = ['%s-bgp-speaker-%s' % (host_index, i)
for i in range(self.bgp_speaker_count)]
self._save_bgp_speakers(self._bgp_speakers)
# pre schedule the bgp_speakers to the agents defined in
# self.hosted_bgp_speakers before calling auto_schedule_bgp_speaker
for agent, bgp_speakers in self.hosted_bgp_speakers.items():
agent_index = self._extract_index(agent)
for bgp_speaker in bgp_speakers:
bs_index = self._extract_index(bgp_speaker)
scheduler.bind(self.ctx, [bgp_dragents[agent_index]],
self._bgp_speakers[bs_index])
retval = scheduler.schedule_unscheduled_bgp_speakers(self.ctx,
hosts[host_index])
self.assertEqual(self.expected_schedule_return_value, retval,
message=msg)
if self.agent_count:
agent_id = bgp_dragents[host_index].id
hosted_bgp_speakers = self._get_hosted_bgp_speakers_on_dragent(
agent_id)
hosted_bs_ids = [self._strip_host_index(net)
for net in hosted_bgp_speakers]
expected_hosted_bgp_speakers = self.expected_hosted_bgp_speakers[
'agent-%s' % host_index]
self.assertItemsEqual(hosted_bs_ids, expected_hosted_bgp_speakers,
msg)
def test_auto_schedule(self):
for i in range(self.host_count):
self._test_auto_schedule(i)

View File

@ -0,0 +1,84 @@
# 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 neutron import context
from neutron.tests import base
from neutron_dynamic_routing.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api
class TestBgpDrAgentNotifyApi(base.BaseTestCase):
def setUp(self):
super(TestBgpDrAgentNotifyApi, self).setUp()
self.notifier = (
bgp_dr_rpc_agent_api.BgpDrAgentNotifyApi())
mock_cast_p = mock.patch.object(self.notifier,
'_notification_host_cast')
self.mock_cast = mock_cast_p.start()
mock_call_p = mock.patch.object(self.notifier,
'_notification_host_call')
self.mock_call = mock_call_p.start()
self.context = context.get_admin_context()
self.host = 'host-1'
def test_notify_dragent_bgp_routes_advertisement(self):
bgp_speaker_id = 'bgp-speaker-1'
routes = [{'destination': '1.1.1.1', 'next_hop': '2.2.2.2'}]
self.notifier.bgp_routes_advertisement(self.context, bgp_speaker_id,
routes, self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)
def test_notify_dragent_bgp_routes_withdrawal(self):
bgp_speaker_id = 'bgp-speaker-1'
routes = [{'destination': '1.1.1.1'}]
self.notifier.bgp_routes_withdrawal(self.context, bgp_speaker_id,
routes, self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)
def test_notify_bgp_peer_disassociated(self):
bgp_speaker_id = 'bgp-speaker-1'
bgp_peer_ip = '1.1.1.1'
self.notifier.bgp_peer_disassociated(self.context, bgp_speaker_id,
bgp_peer_ip, self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)
def test_notify_bgp_peer_associated(self):
bgp_speaker_id = 'bgp-speaker-1'
bgp_peer_id = 'bgp-peer-1'
self.notifier.bgp_peer_associated(self.context, bgp_speaker_id,
bgp_peer_id, self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)
def test_notify_bgp_speaker_created(self):
bgp_speaker_id = 'bgp-speaker-1'
self.notifier.bgp_speaker_created(self.context, bgp_speaker_id,
self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)
def test_notify_bgp_speaker_removed(self):
bgp_speaker_id = 'bgp-speaker-1'
self.notifier.bgp_speaker_removed(self.context, bgp_speaker_id,
self.host)
self.assertEqual(1, self.mock_cast.call_count)
self.assertEqual(0, self.mock_call.call_count)

View File

@ -0,0 +1,45 @@
# 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 neutron.tests import base
from neutron_dynamic_routing.api.rpc.handlers import bgp_speaker_rpc
class TestBgpSpeakerRpcCallback(base.BaseTestCase):
def setUp(self):
self.plugin_p = mock.patch('neutron.manager.NeutronManager.'
'get_service_plugins')
self.plugin = self.plugin_p.start()
self.callback = bgp_speaker_rpc.BgpSpeakerRpcCallback()
super(TestBgpSpeakerRpcCallback, self).setUp()
def test_get_bgp_speaker_info(self):
self.callback.get_bgp_speaker_info(mock.Mock(),
bgp_speaker_id='id1')
self.assertIsNotNone(len(self.plugin.mock_calls))
def test_get_bgp_peer_info(self):
self.callback.get_bgp_peer_info(mock.Mock(),
bgp_peer_id='id1')
self.assertIsNotNone(len(self.plugin.mock_calls))
def test_get_bgp_speakers(self):
self.callback.get_bgp_speakers(mock.Mock(),
host='host')
self.assertIsNotNone(len(self.plugin.mock_calls))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P.
#
# 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_config import cfg
from oslo_utils import importutils
from neutron.api.v2 import attributes
from neutron import context
from neutron.extensions import agent
from neutron import manager
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_base_plugin
from neutron.tests.unit.extensions import test_agent
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron_dynamic_routing.extensions import bgp
from neutron_dynamic_routing.extensions import bgp_dragentscheduler as bgp_dras_ext # noqa
from neutron_dynamic_routing.tests.common import helpers
from neutron_dynamic_routing.tests.unit.db import test_bgp_db
from webob import exc
class BgpDrSchedulerTestExtensionManager(object):
def get_resources(self):
attributes.RESOURCE_ATTRIBUTE_MAP.update(
agent.RESOURCE_ATTRIBUTE_MAP)
resources = agent.Agent.get_resources()
resources.extend(bgp_dras_ext.Bgp_dragentscheduler.get_resources())
return resources
def get_actions(self):
return []
def get_request_extensions(self):
return []
class TestBgpDrSchedulerPlugin(bgp_db.BgpDbMixin,
bgp_dras_db.BgpDrAgentSchedulerDbMixin):
bgp_drscheduler = importutils.import_object(
cfg.CONF.bgp_drscheduler_driver)
supported_extension_aliases = ["bgp_dragent_scheduler"]
def get_plugin_description(self):
return ("BGP dynamic routing service Plugin test class that test "
"BGP speaker functionality, with scheduler.")
class BgpDrSchedulingTestCase(test_agent.AgentDBTestMixIn,
test_bgp_db.BgpEntityCreationMixin):
def test_schedule_bgp_speaker(self):
"""Test happy path over full scheduling cycle."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
helpers.register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
agent_id = agent['id']
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request('agents', data, self.fmt,
agent_id, 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
req_show = self.new_show_request('agents', agent_id, self.fmt,
'bgp-drinstances')
res = req_show.get_response(self.ext_api)
self.assertEqual(exc.HTTPOk.code, res.status_int)
res = self.deserialize(self.fmt, res)
self.assertIn('bgp_speakers', res)
self.assertTrue(bgp_speaker_id,
res['bgp_speakers'][0]['id'])
req = self.new_delete_request('agents',
agent_id,
self.fmt,
'bgp-drinstances',
bgp_speaker_id)
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPNoContent.code, res.status_int)
res = req_show.get_response(self.ext_api)
self.assertEqual(exc.HTTPOk.code, res.status_int)
res = self.deserialize(self.fmt, res)
self.assertIn('bgp_speakers', res)
self.assertEqual([], res['bgp_speakers'])
def test_schedule_bgp_speaker_on_invalid_agent(self):
"""Test error while scheduling BGP speaker on an invalid agent."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
self._register_l3_agent(host='host1') # Register wrong agent
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
# Raises an AgentNotFound exception if the agent is invalid
self.assertEqual(exc.HTTPNotFound.code, res.status_int)
def test_schedule_bgp_speaker_twice_on_same_agent(self):
"""Test error if a BGP speaker is scheduled twice on same agent"""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
helpers.register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
# Try second time, should raise conflict
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPConflict.code, res.status_int)
def test_schedule_bgp_speaker_on_two_different_agents(self):
"""Test that a BGP speaker can be associated to two agents."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
helpers.register_bgp_dragent(host='host1')
helpers.register_bgp_dragent(host='host2')
data = {'bgp_speaker_id': bgp_speaker_id}
agent1 = self._list('agents')['agents'][0]
req = self.new_create_request(
'agents', data, self.fmt,
agent1['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
agent2 = self._list('agents')['agents'][1]
req = self.new_create_request(
'agents', data, self.fmt,
agent2['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
def test_schedule_multi_bgp_speaker_on_one_dragent(self):
"""Test only one BGP speaker can be associated to one dragent."""
with self.bgp_speaker(4, 1) as ri1, self.bgp_speaker(4, 2) as ri2:
helpers.register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': ri1['id']}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
data = {'bgp_speaker_id': ri2['id']}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPConflict.code, res.status_int)
def test_non_scheduled_bgp_speaker_binding_removal(self):
"""Test exception while removing an invalid binding."""
with self.bgp_speaker(4, 1234) as ri1:
helpers.register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
agent_id = agent['id']
self.assertRaises(bgp_dras_ext.DrAgentNotHostingBgpSpeaker,
self.bgp_plugin.remove_bgp_speaker_from_dragent,
self.context, agent_id, ri1['id'])
class BgpDrPluginSchedulerTests(test_db_base_plugin.NeutronDbPluginV2TestCase,
BgpDrSchedulingTestCase):
def setUp(self, plugin=None, ext_mgr=None, service_plugins=None):
if not plugin:
plugin = ('neutron_dynamic_routing.tests.unit.db.'
'test_bgp_dragentscheduler_db.TestBgpDrSchedulerPlugin')
if not service_plugins:
service_plugins = {bgp.BGP_EXT_ALIAS:
'neutron_dynamic_routing.services.bgp.'
'bgp_plugin.BgpPlugin'}
ext_mgr = ext_mgr or BgpDrSchedulerTestExtensionManager()
super(BgpDrPluginSchedulerTests, self).setUp(
plugin=plugin, ext_mgr=ext_mgr, service_plugins=service_plugins)
self.bgp_plugin = manager.NeutronManager.get_service_plugins().get(
bgp.BGP_EXT_ALIAS)
self.context = context.get_admin_context()

View File

@ -0,0 +1,748 @@
# 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 copy
import sys
import uuid
import eventlet
import mock
from oslo_config import cfg
import testtools
from neutron.agent.common import config
from neutron.common import config as n_config
from neutron import context
from neutron.tests import base
from neutron_dynamic_routing.services.bgp.agent import bgp_dragent
from neutron_dynamic_routing.services.bgp.agent import config as bgp_config
from neutron_dynamic_routing.services.bgp.agent import entry
HOSTNAME = 'hostname'
rpc_api = bgp_dragent.BgpDrPluginApi
BGP_PLUGIN = '%s.%s' % (rpc_api.__module__, rpc_api.__name__)
FAKE_BGPSPEAKER_UUID = str(uuid.uuid4())
FAKE_BGPPEER_UUID = str(uuid.uuid4())
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,
'destination': '2.2.2.2/32',
'next_hop': '3.3.3.3'}
FAKE_ROUTES = {'routes': {'id': FAKE_BGPSPEAKER_UUID,
'destination': '2.2.2.2/32',
'next_hop': '3.3.3.3'}
}
class TestBgpDrAgent(base.BaseTestCase):
def setUp(self):
super(TestBgpDrAgent, self).setUp()
cfg.CONF.register_opts(bgp_config.BGP_DRIVER_OPTS, 'BGP')
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_dynamic_routing.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):
state_rpc_str = 'neutron.agent.rpc.PluginReportStateAPI'
# sync_state is needed for this test
with mock.patch.object(bgp_dragent.BgpDrAgentWithStateReport,
'sync_state',
autospec=True) as mock_sync_state:
with mock.patch(state_rpc_str) as state_rpc:
with mock.patch.object(sys, 'argv') as sys_argv:
sys_argv.return_value = [
'bgp_dragent', '--config-file',
base.etcdir('neutron.conf')]
config.register_agent_state_opts_helper(cfg.CONF)
n_config.init(sys.argv[1:])
agent_mgr = bgp_dragent.BgpDrAgentWithStateReport(
'testhost')
eventlet.greenthread.sleep(1)
agent_mgr.after_start()
self.assertIsNotNone(len(mock_sync_state.mock_calls))
state_rpc.assert_has_calls(
[mock.call(mock.ANY),
mock.call().report_state(mock.ANY, mock.ANY,
mock.ANY)])
def test_bgp_dragent_main_agent_manager(self):
logging_str = 'neutron.agent.common.config.setup_logging'
launcher_str = 'oslo_service.service.ServiceLauncher'
with mock.patch(logging_str):
with mock.patch.object(sys, 'argv') as sys_argv:
with mock.patch(launcher_str) as launcher:
sys_argv.return_value = ['bgp_dragent', '--config-file',
base.etcdir('neutron.conf')]
entry.main()
if launcher.mock_calls[0][2]:
launcher.assert_has_calls(
[mock.call(cfg.CONF, restart_method='reload'),
mock.call().launch_service(mock.ANY, workers=1),
mock.call().wait()])
else:
launcher.assert_has_calls(
[mock.call(cfg.CONF),
mock.call().launch_service(mock.ANY),
mock.call().wait()])
def test_run_completes_single_pass(self):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
bgp_dr.run()
self.assertIsNotNone(len(sync_state.mock_calls))
def test_after_start(self):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
bgp_dr.after_start()
self.assertIsNotNone(len(sync_state.mock_calls))
def _test_sync_state_helper(self, bgp_speaker_list=None,
cached_info=None,
safe_configure_call_count=0,
sync_bgp_speaker_call_count=0,
remove_bgp_speaker_call_count=0,
remove_bgp_speaker_ids=None,
added_bgp_speakers=None,
synced_bgp_speakers=None):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
attrs_to_mock = dict(
[(a, mock.MagicMock())
for a in ['plugin_rpc', 'sync_bgp_speaker',
'safe_configure_dragent_for_bgp_speaker',
'remove_bgp_speaker_from_dragent']])
with mock.patch.multiple(bgp_dr, **attrs_to_mock):
if not cached_info:
cached_info = {}
if not added_bgp_speakers:
added_bgp_speakers = []
if not remove_bgp_speaker_ids:
remove_bgp_speaker_ids = []
if not synced_bgp_speakers:
synced_bgp_speakers = []
bgp_dr.plugin_rpc.get_bgp_speakers.return_value = bgp_speaker_list
bgp_dr.cache.cache = cached_info
bgp_dr.cache.clear_cache = mock.Mock()
bgp_dr.sync_state(mock.ANY)
self.assertEqual(
remove_bgp_speaker_call_count,
bgp_dr.remove_bgp_speaker_from_dragent.call_count)
if remove_bgp_speaker_call_count:
expected_calls = [mock.call(bgp_speaker_id)
for bgp_speaker_id in remove_bgp_speaker_ids]
bgp_dr.remove_bgp_speaker_from_dragent.assert_has_calls(
expected_calls)
self.assertEqual(
safe_configure_call_count,
bgp_dr.safe_configure_dragent_for_bgp_speaker.call_count)
if safe_configure_call_count:
expected_calls = [mock.call(bgp_speaker)
for bgp_speaker in added_bgp_speakers]
bgp_dr.safe_configure_dragent_for_bgp_speaker.assert_has_calls(
expected_calls)
self.assertEqual(sync_bgp_speaker_call_count,
bgp_dr.sync_bgp_speaker.call_count)
if sync_bgp_speaker_call_count:
expected_calls = [mock.call(bgp_speaker)
for bgp_speaker in synced_bgp_speakers]
bgp_dr.sync_bgp_speaker.assert_has_calls(expected_calls)
def test_sync_state_bgp_speaker_added(self):
bgp_speaker_list = [{'id': 'foo-id',
'local_as': 12345,
'peers': [],
'advertised_routes': []}]
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
safe_configure_call_count=1,
added_bgp_speakers=bgp_speaker_list)
def test_sync_state_bgp_speaker_deleted(self):
bgp_speaker_list = []
cached_bgp_speaker = {'id': 'foo-id',
'local_as': 12345,
'peers': ['peer-1'],
'advertised_routes': []}
cached_info = {'foo-id': cached_bgp_speaker}
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
cached_info=cached_info,
remove_bgp_speaker_call_count=1,
remove_bgp_speaker_ids=['foo-id'])
def test_sync_state_added_and_deleted(self):
bgp_speaker_list = [{'id': 'foo-id',
'local_as': 12345,
'peers': [],
'advertised_routes': []}]
cached_bgp_speaker = {'bgp_speaker': {'local_as': 12345},
'peers': ['peer-1'],
'advertised_routes': []}
cached_info = {'bar-id': cached_bgp_speaker}
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
cached_info=cached_info,
remove_bgp_speaker_call_count=1,
remove_bgp_speaker_ids=['bar-id'],
safe_configure_call_count=1,
added_bgp_speakers=bgp_speaker_list)
def test_sync_state_added_and_synced(self):
bgp_speaker_list = [{'id': 'foo-id',
'local_as': 12345,
'peers': [],
'advertised_routes': []},
{'id': 'bar-id', 'peers': ['peer-2'],
'advertised_routes': []},
{'id': 'temp-id', 'peers': ['temp-1'],
'advertised_routes': []}]
cached_bgp_speaker = {'id': 'bar-id', 'bgp_speaker': {'id': 'bar-id'},
'peers': ['peer-1'],
'advertised_routes': []}
cached_bgp_speaker_2 = {'id': 'temp-id',
'bgp_speaker': {'id': 'temp-id'},
'peers': ['temp-1'],
'advertised_routes': []}
cached_info = {'bar-id': cached_bgp_speaker,
'temp-id': cached_bgp_speaker_2}
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
cached_info=cached_info,
safe_configure_call_count=1,
added_bgp_speakers=[bgp_speaker_list[0]],
sync_bgp_speaker_call_count=2,
synced_bgp_speakers=[bgp_speaker_list[1],
bgp_speaker_list[2]]
)
def test_sync_state_added_synced_and_removed(self):
bgp_speaker_list = [{'id': 'foo-id',
'local_as': 12345,
'peers': [],
'advertised_routes': []},
{'id': 'bar-id', 'peers': ['peer-2'],
'advertised_routes': []}]
cached_bgp_speaker = {'id': 'bar-id',
'bgp_speaker': {'id': 'bar-id'},
'peers': ['peer-1'],
'advertised_routes': []}
cached_bgp_speaker_2 = {'id': 'temp-id',
'bgp_speaker': {'id': 'temp-id'},
'peers': ['temp-1'],
'advertised_routes': []}
cached_info = {'bar-id': cached_bgp_speaker,
'temp-id': cached_bgp_speaker_2}
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
cached_info=cached_info,
remove_bgp_speaker_call_count=1,
remove_bgp_speaker_ids=['temp-id'],
safe_configure_call_count=1,
added_bgp_speakers=[bgp_speaker_list[0]],
sync_bgp_speaker_call_count=1,
synced_bgp_speakers=[bgp_speaker_list[1]])
def _test_sync_bgp_speaker_helper(self, bgp_speaker, cached_info=None,
remove_bgp_peer_call_count=0,
removed_bgp_peer_ip_list=None,
withdraw_route_call_count=0,
withdraw_routes_list=None,
add_bgp_peers_called=False,
advertise_routes_called=False):
if not cached_info:
cached_info = {}
if not removed_bgp_peer_ip_list:
removed_bgp_peer_ip_list = []
if not withdraw_routes_list:
withdraw_routes_list = []
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
attrs_to_mock = dict(
[(a, mock.MagicMock())
for a in ['remove_bgp_peer_from_bgp_speaker',
'add_bgp_peers_to_bgp_speaker',
'advertise_routes_via_bgp_speaker',
'withdraw_route_via_bgp_speaker']])
with mock.patch.multiple(bgp_dr, **attrs_to_mock):
bgp_dr.cache.cache = cached_info
bgp_dr.sync_bgp_speaker(bgp_speaker)
self.assertEqual(
remove_bgp_peer_call_count,
bgp_dr.remove_bgp_peer_from_bgp_speaker.call_count)
if remove_bgp_peer_call_count:
expected_calls = [mock.call(bgp_speaker['id'], peer_ip)
for peer_ip in removed_bgp_peer_ip_list]
bgp_dr.remove_bgp_peer_from_bgp_speaker.assert_has_calls(
expected_calls)
self.assertEqual(add_bgp_peers_called,
bgp_dr.add_bgp_peers_to_bgp_speaker.called)
if add_bgp_peers_called:
bgp_dr.add_bgp_peers_to_bgp_speaker.assert_called_with(
bgp_speaker)
self.assertEqual(
withdraw_route_call_count,
bgp_dr.withdraw_route_via_bgp_speaker.call_count)
if withdraw_route_call_count:
expected_calls = [mock.call(bgp_speaker['id'], 12345, route)
for route in withdraw_routes_list]
bgp_dr.withdraw_route_via_bgp_speaker.assert_has_calls(
expected_calls)
self.assertEqual(advertise_routes_called,
bgp_dr.advertise_routes_via_bgp_speaker.called)
if advertise_routes_called:
bgp_dr.advertise_routes_via_bgp_speaker.assert_called_with(
bgp_speaker)
def test_sync_bgp_speaker_bgp_peers_updated(self):
peers = [{'id': 'peer-1', 'peer_ip': '1.1.1.1'},
{'id': 'peer-2', 'peer_ip': '2.2.2.2'}]
bgp_speaker = {'id': 'foo-id',
'local_as': 12345,
'peers': peers,
'advertised_routes': []}
cached_peers = {'1.1.1.1': {'id': 'peer-2', 'peer_ip': '1.1.1.1'},
'3.3.3.3': {'id': 'peer-3', 'peer_ip': '3.3.3.3'}}
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': cached_peers,
'advertised_routes': []}}
self._test_sync_bgp_speaker_helper(
bgp_speaker, cached_info=cached_bgp_speaker,
remove_bgp_peer_call_count=1,
removed_bgp_peer_ip_list=['3.3.3.3'],
add_bgp_peers_called=True,
advertise_routes_called=False)
def test_sync_bgp_speaker_routes_updated(self):
adv_routes = [{'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'},
{'destination': '20.0.0.0/24', 'next_hop': '2.2.2.2'}]
bgp_speaker = {'id': 'foo-id',
'local_as': 12345,
'peers': {},
'advertised_routes': adv_routes}
cached_adv_routes = [{'destination': '20.0.0.0/24',
'next_hop': '2.2.2.2'},
{'destination': '30.0.0.0/24',
'next_hop': '3.3.3.3'}]
cached_bgp_speaker = {
'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': {},
'advertised_routes': cached_adv_routes}}
self._test_sync_bgp_speaker_helper(
bgp_speaker, cached_info=cached_bgp_speaker,
withdraw_route_call_count=1,
withdraw_routes_list=[cached_adv_routes[1]],
add_bgp_peers_called=False,
advertise_routes_called=True)
def test_sync_bgp_speaker_peers_routes_added(self):
peers = [{'id': 'peer-1', 'peer_ip': '1.1.1.1'},
{'id': 'peer-2', 'peer_ip': '2.2.2.2'}]
adv_routes = [{'destination': '10.0.0.0/24',
'next_hop': '1.1.1.1'},
{'destination': '20.0.0.0/24',
'next_hop': '2.2.2.2'}]
bgp_speaker = {'id': 'foo-id',
'local_as': 12345,
'peers': peers,
'advertised_routes': adv_routes}
cached_bgp_speaker = {
'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': {},
'advertised_routes': []}}
self._test_sync_bgp_speaker_helper(
bgp_speaker, cached_info=cached_bgp_speaker,
add_bgp_peers_called=True,
advertise_routes_called=True)
def test_sync_state_plugin_error(self):
with mock.patch(BGP_PLUGIN) as plug:
mock_plugin = mock.Mock()
mock_plugin.get_bgp_speakers.side_effect = Exception
plug.return_value = mock_plugin
with mock.patch.object(bgp_dragent.LOG, 'error') as log:
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
with mock.patch.object(bgp_dr,
'schedule_full_resync') as schedule_full_resync:
bgp_dr.sync_state(mock.ANY)
self.assertTrue(log.called)
self.assertTrue(schedule_full_resync.called)
def test_periodic_resync(self):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
with mock.patch.object(bgp_dr,
'_periodic_resync_helper') as resync_helper:
bgp_dr.periodic_resync(self.context)
self.assertTrue(resync_helper.called)
def test_periodic_resync_helper(self):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
bgp_dr.schedule_resync('foo reason', 'foo-id')
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
sync_state.side_effect = RuntimeError
with testtools.ExpectedException(RuntimeError):
bgp_dr._periodic_resync_helper(self.context)
self.assertTrue(sync_state.called)
self.assertEqual(len(bgp_dr.needs_resync_reasons), 0)
def _test_add_bgp_peer_helper(self, bgp_speaker_id,
bgp_peer, cached_bgp_speaker,
put_bgp_peer_called=True):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
bgp_dr.cache.cache = cached_bgp_speaker
with mock.patch.object(
bgp_dr.cache, 'put_bgp_peer') as mock_put_bgp_peer:
bgp_dr.add_bgp_peer_to_bgp_speaker('foo-id', 12345, bgp_peer)
if put_bgp_peer_called:
mock_put_bgp_peer.assert_called_once_with(
bgp_speaker_id, bgp_peer)
else:
self.assertFalse(mock_put_bgp_peer.called)
def test_add_bgp_peer_not_cached(self):
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
'auth_type': 'md5', 'password': 'abc'}
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': {},
'advertised_routes': []}}
self._test_add_bgp_peer_helper('foo-id', bgp_peer, cached_bgp_speaker)
def test_add_bgp_peer_already_cached(self):
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
'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,
'advertised_routes': []}}
self._test_add_bgp_peer_helper('foo-id', bgp_peer, cached_bgp_speaker,
put_bgp_peer_called=False)
def _test_advertise_route_helper(self, bgp_speaker_id,
route, cached_bgp_speaker,
put_adv_route_called=True):
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
bgp_dr.cache.cache = cached_bgp_speaker
with mock.patch.object(
bgp_dr.cache, 'put_adv_route') as mock_put_adv_route:
bgp_dr.advertise_route_via_bgp_speaker(bgp_speaker_id, 12345,
route)
if put_adv_route_called:
mock_put_adv_route.assert_called_once_with(
bgp_speaker_id, route)
else:
self.assertFalse(mock_put_adv_route.called)
def test_advertise_route_helper_not_cached(self):
route = {'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'}
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': {},
'advertised_routes': []}}
self._test_advertise_route_helper('foo-id', route, cached_bgp_speaker,
put_adv_route_called=True)
def test_advertise_route_helper_already_cached(self):
route = {'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'}
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
'peers': {},
'advertised_routes': [route]}}
self._test_advertise_route_helper('foo-id', route, cached_bgp_speaker,
put_adv_route_called=False)
class TestBgpDrAgentEventHandler(base.BaseTestCase):
cache_cls = 'neutron_dynamic_routing.services.bgp.'\
'agent.bgp_dragent.BgpSpeakerCache'
def setUp(self):
super(TestBgpDrAgentEventHandler, self).setUp()
cfg.CONF.register_opts(bgp_config.BGP_DRIVER_OPTS, 'BGP')
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.plugin_p = mock.patch(BGP_PLUGIN)
plugin_cls = self.plugin_p.start()
self.plugin = mock.Mock()
plugin_cls.return_value = self.plugin
self.cache_p = mock.patch(self.cache_cls)
cache_cls = self.cache_p.start()
self.cache = mock.Mock()
cache_cls.return_value = self.cache
self.driver_cls_p = mock.patch(
'neutron_dynamic_routing.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')
self.schedule_full_resync = self.schedule_full_resync_p.start()
self.context = mock.Mock()
def test_bgp_speaker_create_end(self):
payload = {'bgp_speaker': {'id': FAKE_BGPSPEAKER_UUID}}
with mock.patch.object(self.bgp_dr,
'add_bgp_speaker_helper') as enable:
self.bgp_dr.bgp_speaker_create_end(None, payload)
enable.assert_called_once_with(FAKE_BGP_SPEAKER['id'])
def test_bgp_peer_association_end(self):
payload = {'bgp_peer': {'speaker_id': FAKE_BGPSPEAKER_UUID,
'peer_id': FAKE_BGPPEER_UUID}}
with mock.patch.object(self.bgp_dr,
'add_bgp_peer_helper') as enable:
self.bgp_dr.bgp_peer_association_end(None, payload)
enable.assert_called_once_with(FAKE_BGP_SPEAKER['id'],
FAKE_BGP_PEER['id'])
def test_route_advertisement_end(self):
routes = [{'destination': '2.2.2.2/32', 'next_hop': '3.3.3.3'},
{'destination': '4.4.4.4/32', 'next_hop': '5.5.5.5'}]
payload = {'advertise_routes': {'speaker_id': FAKE_BGPSPEAKER_UUID,
'routes': routes}}
expected_calls = [mock.call(FAKE_BGP_SPEAKER['id'], routes)]
with mock.patch.object(self.bgp_dr,
'add_routes_helper') as enable:
self.bgp_dr.bgp_routes_advertisement_end(None, payload)
enable.assert_has_calls(expected_calls)
def test_add_bgp_speaker_helper(self):
self.plugin.get_bgp_speaker_info.return_value = FAKE_BGP_SPEAKER
add_bs_p = mock.patch.object(self.bgp_dr,
'add_bgp_speaker_on_dragent')
add_bs = add_bs_p.start()
self.bgp_dr.add_bgp_speaker_helper(FAKE_BGP_SPEAKER['id'])
self.plugin.assert_has_calls([
mock.call.get_bgp_speaker_info(mock.ANY,
FAKE_BGP_SPEAKER['id'])])
add_bs.assert_called_once_with(FAKE_BGP_SPEAKER)
def test_add_bgp_peer_helper(self):
self.plugin.get_bgp_peer_info.return_value = FAKE_BGP_PEER
add_bp_p = mock.patch.object(self.bgp_dr,
'add_bgp_peer_to_bgp_speaker')
add_bp = add_bp_p.start()
self.bgp_dr.add_bgp_peer_helper(FAKE_BGP_SPEAKER['id'],
FAKE_BGP_PEER['id'])
self.plugin.assert_has_calls([
mock.call.get_bgp_peer_info(mock.ANY,
FAKE_BGP_PEER['id'])])
self.assertEqual(1, add_bp.call_count)
def test_add_routes_helper(self):
add_rt_p = mock.patch.object(self.bgp_dr,
'advertise_route_via_bgp_speaker')
add_bp = add_rt_p.start()
self.bgp_dr.add_routes_helper(FAKE_BGP_SPEAKER['id'], FAKE_ROUTES)
self.assertEqual(1, add_bp.call_count)
def test_bgp_speaker_remove_end(self):
payload = {'bgp_speaker': {'id': FAKE_BGPSPEAKER_UUID}}
with mock.patch.object(self.bgp_dr,
'remove_bgp_speaker_from_dragent') as disable:
self.bgp_dr.bgp_speaker_remove_end(None, payload)
disable.assert_called_once_with(FAKE_BGP_SPEAKER['id'])
def test_bgp_peer_disassociation_end(self):
payload = {'bgp_peer': {'speaker_id': FAKE_BGPSPEAKER_UUID,
'peer_ip': '1.1.1.1'}}
with mock.patch.object(self.bgp_dr,
'remove_bgp_peer_from_bgp_speaker') as disable:
self.bgp_dr.bgp_peer_disassociation_end(None, payload)
disable.assert_called_once_with(FAKE_BGPSPEAKER_UUID,
FAKE_BGP_PEER['peer_ip'])
def test_bgp_routes_withdrawal_end(self):
withdraw_routes = [{'destination': '2.2.2.2/32'},
{'destination': '3.3.3.3/32'}]
payload = {'withdraw_routes': {'speaker_id': FAKE_BGPSPEAKER_UUID,
'routes': withdraw_routes}}
expected_calls = [mock.call(FAKE_BGP_SPEAKER['id'], withdraw_routes)]
with mock.patch.object(self.bgp_dr,
'withdraw_routes_helper') as disable:
self.bgp_dr.bgp_routes_withdrawal_end(None, payload)
disable.assert_has_calls(expected_calls)
class TestBGPSpeakerCache(base.BaseTestCase):
def setUp(self):
super(TestBGPSpeakerCache, self).setUp()
self.expected_cache = {FAKE_BGP_SPEAKER['id']:
{'bgp_speaker': FAKE_BGP_SPEAKER,
'peers': {},
'advertised_routes': []}}
self.bs_cache = bgp_dragent.BgpSpeakerCache()
def test_put_bgp_speaker(self):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.assertEqual(self.expected_cache, self.bs_cache.cache)
def test_put_bgp_speaker_existing(self):
prev_bs_info = {'id': 'foo-id'}
with mock.patch.object(self.bs_cache,
'remove_bgp_speaker_by_id') as remove:
self.bs_cache.cache[FAKE_BGP_SPEAKER['id']] = prev_bs_info
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
remove.assert_called_once_with(prev_bs_info)
self.assertEqual(self.expected_cache, self.bs_cache.cache)
def remove_bgp_speaker_by_id(self):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.assertEqual(1, len(self.bs_cache.cache))
self.bs_cache.remove_bgp_speaker_by_id(FAKE_BGP_SPEAKER['id'])
self.assertEqual(0, len(self.bs_cache.cache))
def test_get_bgp_speaker_by_id(self):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.assertEqual(
FAKE_BGP_SPEAKER,
self.bs_cache.get_bgp_speaker_by_id(FAKE_BGP_SPEAKER['id']))
def test_get_bgp_speaker_ids(self):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.assertEqual([FAKE_BGP_SPEAKER['id']],
list(self.bs_cache.get_bgp_speaker_ids()))
def _test_bgp_peer_helper(self, remove=False):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.bs_cache.put_bgp_peer(FAKE_BGP_SPEAKER['id'], FAKE_BGP_PEER)
expected_cache = copy.deepcopy(self.expected_cache)
expected_cache[FAKE_BGP_SPEAKER['id']]['peers'] = {
FAKE_BGP_PEER['peer_ip']: FAKE_BGP_PEER}
self.assertEqual(expected_cache, self.bs_cache.cache)
if remove:
self.bs_cache.remove_bgp_peer_by_ip(FAKE_BGP_SPEAKER['id'],
'foo-ip')
self.assertEqual(expected_cache, self.bs_cache.cache)
self.bs_cache.remove_bgp_peer_by_ip(FAKE_BGP_SPEAKER['id'],
FAKE_BGP_PEER['peer_ip'])
self.assertEqual(self.expected_cache, self.bs_cache.cache)
def test_put_bgp_peer(self):
self._test_bgp_peer_helper()
def test_remove_bgp_peer(self):
self._test_bgp_peer_helper(remove=True)
def _test_bgp_speaker_adv_route_helper(self, remove=False):
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
self.bs_cache.put_adv_route(FAKE_BGP_SPEAKER['id'], FAKE_ROUTE)
expected_cache = copy.deepcopy(self.expected_cache)
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'].append(
FAKE_ROUTE)
self.assertEqual(expected_cache, self.bs_cache.cache)
fake_route_2 = copy.deepcopy(FAKE_ROUTE)
fake_route_2['destination'] = '4.4.4.4/32'
self.bs_cache.put_adv_route(FAKE_BGP_SPEAKER['id'], fake_route_2)
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'].append(
fake_route_2)
self.assertEqual(expected_cache, self.bs_cache.cache)
if remove:
self.bs_cache.remove_adv_route(FAKE_BGP_SPEAKER['id'],
fake_route_2)
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'] = (
[FAKE_ROUTE])
self.assertEqual(expected_cache, self.bs_cache.cache)
self.bs_cache.remove_adv_route(FAKE_BGP_SPEAKER['id'],
FAKE_ROUTE)
self.assertEqual(self.expected_cache, self.bs_cache.cache)
def test_put_bgp_speaker_adv_route(self):
self._test_bgp_speaker_adv_route_helper()
def test_remove_bgp_speaker_adv_route(self):
self._test_bgp_speaker_adv_route_helper(remove=True)
def test_is_bgp_speaker_adv_route_present(self):
self._test_bgp_speaker_adv_route_helper()
self.assertTrue(self.bs_cache.is_route_advertised(
FAKE_BGP_SPEAKER['id'], FAKE_ROUTE))
self.assertFalse(self.bs_cache.is_route_advertised(
FAKE_BGP_SPEAKER['id'], {'destination': 'foo-destination',
'next_hop': 'foo-next-hop'}))

View File

@ -0,0 +1,251 @@
# 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.tests import base
from neutron_dynamic_routing.services.bgp.agent import config as bgp_config
from neutron_dynamic_routing.services.bgp.agent.driver import exceptions as bgp_driver_exc # noqa
from neutron_dynamic_routing.services.bgp.agent.driver.ryu import driver as ryu_driver # noqa
# Test variables for BGP Speaker
FAKE_LOCAL_AS1 = 12345
FAKE_LOCAL_AS2 = 23456
FAKE_ROUTER_ID = '1.1.1.1'
# Test variables for BGP Peer
FAKE_PEER_AS = 45678
FAKE_PEER_IP = '2.2.2.5'
FAKE_AUTH_TYPE = 'md5'
FAKE_PEER_PASSWORD = 'awesome'
# Test variables for Route
FAKE_ROUTE = '2.2.2.0/24'
FAKE_NEXTHOP = '5.5.5.5'
class TestRyuBgpDriver(base.BaseTestCase):
def setUp(self):
super(TestRyuBgpDriver, self).setUp()
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
cfg.CONF.set_override('bgp_router_id', FAKE_ROUTER_ID, 'BGP')
self.ryu_bgp_driver = ryu_driver.RyuBgpDriver(cfg.CONF.BGP)
mock_ryu_speaker_p = mock.patch.object(bgpspeaker, 'BGPSpeaker')
self.mock_ryu_speaker = mock_ryu_speaker_p.start()
def test_add_new_bgp_speaker(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.mock_ryu_speaker.assert_called_once_with(
as_number=FAKE_LOCAL_AS1, router_id=FAKE_ROUTER_ID,
bgp_server_port=0,
best_path_change_handler=ryu_driver.best_path_change_cb,
peer_down_handler=ryu_driver.bgp_peer_down_cb,
peer_up_handler=ryu_driver.bgp_peer_up_cb)
def test_remove_bgp_speaker(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(0,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.assertEqual(1, speaker.shutdown.call_count)
def test_add_bgp_peer_without_password(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
FAKE_PEER_IP,
FAKE_PEER_AS)
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
speaker.neighbor_add.assert_called_once_with(
address=FAKE_PEER_IP,
remote_as=FAKE_PEER_AS,
password=None,
connect_mode=CONNECT_MODE_ACTIVE)
def test_add_bgp_peer_with_password(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
FAKE_PEER_IP,
FAKE_PEER_AS,
FAKE_AUTH_TYPE,
FAKE_PEER_PASSWORD)
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
speaker.neighbor_add.assert_called_once_with(
address=FAKE_PEER_IP,
remote_as=FAKE_PEER_AS,
password=FAKE_PEER_PASSWORD,
connect_mode=CONNECT_MODE_ACTIVE)
def test_remove_bgp_peer(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.delete_bgp_peer(FAKE_LOCAL_AS1, FAKE_PEER_IP)
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
speaker.neighbor_del.assert_called_once_with(address=FAKE_PEER_IP)
def test_advertise_route(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.advertise_route(FAKE_LOCAL_AS1,
FAKE_ROUTE,
FAKE_NEXTHOP)
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
speaker.prefix_add.assert_called_once_with(prefix=FAKE_ROUTE,
next_hop=FAKE_NEXTHOP)
def test_withdraw_route(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.withdraw_route(FAKE_LOCAL_AS1, FAKE_ROUTE)
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
speaker.prefix_del.assert_called_once_with(prefix=FAKE_ROUTE)
def test_add_same_bgp_speakers_twice(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.BgpSpeakerAlreadyScheduled,
self.ryu_bgp_driver.add_bgp_speaker, FAKE_LOCAL_AS1)
def test_add_different_bgp_speakers_when_one_already_added(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
self.ryu_bgp_driver.add_bgp_speaker,
FAKE_LOCAL_AS2)
def test_add_bgp_speaker_with_invalid_asnum_paramtype(self):
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.add_bgp_speaker, '12345')
def test_add_bgp_speaker_with_invalid_asnum_range(self):
self.assertRaises(bgp_driver_exc.InvalidParamRange,
self.ryu_bgp_driver.add_bgp_speaker, -1)
self.assertRaises(bgp_driver_exc.InvalidParamRange,
self.ryu_bgp_driver.add_bgp_speaker, 65536)
def test_add_bgp_peer_with_invalid_paramtype(self):
# Test with an invalid asnum data-type
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, '12345')
# Test with an invalid auth-type and an invalid password
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
'sha-1', 1234)
# Test with an invalid auth-type and a valid password
self.assertRaises(bgp_driver_exc.InvaildAuthType,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
'hmac-md5', FAKE_PEER_PASSWORD)
# Test with none auth-type and a valid password
self.assertRaises(bgp_driver_exc.InvaildAuthType,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
'none', FAKE_PEER_PASSWORD)
# Test with none auth-type and an invalid password
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
'none', 1234)
# Test with a valid auth-type and no password
self.assertRaises(bgp_driver_exc.PasswordNotSpecified,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
FAKE_AUTH_TYPE, None)
def test_add_bgp_peer_with_invalid_asnum_range(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.InvalidParamRange,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, -1)
self.assertRaises(bgp_driver_exc.InvalidParamRange,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, 65536)
def test_add_bgp_peer_without_adding_speaker(self):
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
self.ryu_bgp_driver.add_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS)
def test_remove_bgp_peer_with_invalid_paramtype(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.delete_bgp_peer,
FAKE_LOCAL_AS1, 12345)
def test_remove_bgp_peer_without_adding_speaker(self):
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
self.ryu_bgp_driver.delete_bgp_peer,
FAKE_LOCAL_AS1, FAKE_PEER_IP)
def test_advertise_route_with_invalid_paramtype(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.advertise_route,
FAKE_LOCAL_AS1, 12345, FAKE_NEXTHOP)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.advertise_route,
FAKE_LOCAL_AS1, FAKE_ROUTE, 12345)
def test_advertise_route_without_adding_speaker(self):
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
self.ryu_bgp_driver.advertise_route,
FAKE_LOCAL_AS1, FAKE_ROUTE, FAKE_NEXTHOP)
def test_withdraw_route_with_invalid_paramtype(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.withdraw_route,
FAKE_LOCAL_AS1, 12345)
self.assertRaises(bgp_driver_exc.InvalidParamType,
self.ryu_bgp_driver.withdraw_route,
FAKE_LOCAL_AS1, 12345)
def test_withdraw_route_without_adding_speaker(self):
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
self.ryu_bgp_driver.withdraw_route,
FAKE_LOCAL_AS1, FAKE_ROUTE)
def test_add_multiple_bgp_speakers(self):
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
self.ryu_bgp_driver.add_bgp_speaker,
FAKE_LOCAL_AS2)
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
self.ryu_bgp_driver.delete_bgp_speaker,
FAKE_LOCAL_AS2)
self.assertEqual(1,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
self.assertEqual(0,
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())

View File

@ -0,0 +1,49 @@
# 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.tests import base
from neutron_dynamic_routing.services.bgp.agent.driver import utils as bgp_driver_utils # noqa
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 = bgp_driver_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())

View File

@ -0,0 +1,225 @@
# 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.
import testscenarios
from oslo_utils import importutils
from neutron import context
from neutron.tests.unit import testlib_api
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron_dynamic_routing.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras # noqa
from neutron_dynamic_routing.tests.common import helpers
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestBgpDrAgentSchedulerBaseTestCase(testlib_api.SqlTestCase):
def setUp(self):
super(TestBgpDrAgentSchedulerBaseTestCase, self).setUp()
self.ctx = context.get_admin_context()
self.bgp_speaker = {'id': 'foo_bgp_speaker_id'}
self.bgp_speaker_id = 'foo_bgp_speaker_id'
self._save_bgp_speaker(self.bgp_speaker_id)
def _create_and_set_agents_down(self, hosts, down_agent_count=0,
admin_state_up=True):
agents = []
for i, host in enumerate(hosts):
is_alive = i >= down_agent_count
agents.append(helpers.register_bgp_dragent(
host,
admin_state_up=admin_state_up,
alive=is_alive))
return agents
def _save_bgp_speaker(self, bgp_speaker_id):
cls = bgp_db.BgpDbMixin()
bgp_speaker_body = {'bgp_speaker': {
'name': 'fake_bgp_speaker',
'ip_version': '4',
'local_as': '123',
'advertise_floating_ip_host_routes': '0',
'advertise_tenant_networks': '0',
'peers': [],
'networks': []}}
cls._save_bgp_speaker(self.ctx, bgp_speaker_body, uuid=bgp_speaker_id)
def _test_schedule_bind_bgp_speaker(self, agents, bgp_speaker_id):
scheduler = bgp_dras.ChanceScheduler()
scheduler.resource_filter.bind(self.ctx, agents, bgp_speaker_id)
results = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).filter_by(
bgp_speaker_id=bgp_speaker_id).all()
for result in results:
self.assertEqual(bgp_speaker_id, result.bgp_speaker_id)
class TestBgpDrAgentScheduler(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin):
def test_schedule_bind_bgp_speaker_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
def test_schedule_bind_bgp_speaker_multi_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
class TestBgpAgentFilter(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin,
bgp_dras_db.BgpDrAgentSchedulerDbMixin):
def setUp(self):
super(TestBgpAgentFilter, self).setUp()
self.bgp_drscheduler = importutils.import_object(
'neutron_dynamic_routing.services.bgp.scheduler.'
'bgp_dragent_scheduler.ChanceScheduler'
)
self.plugin = self
def _test_filter_agents_helper(self, bgp_speaker,
expected_filtered_dragent_ids=None,
expected_num_agents=1):
filtered_agents = (
self.plugin.bgp_drscheduler.resource_filter.filter_agents(
self.plugin, self.ctx, bgp_speaker))
self.assertEqual(expected_num_agents,
filtered_agents['n_agents'])
actual_filtered_dragent_ids = [
agent.id for agent in filtered_agents['hostable_agents']]
if expected_filtered_dragent_ids is None:
expected_filtered_dragent_ids = []
self.assertEqual(len(expected_filtered_dragent_ids),
len(actual_filtered_dragent_ids))
for filtered_agent_id in actual_filtered_dragent_ids:
self.assertIn(filtered_agent_id, expected_filtered_dragent_ids)
def test_filter_agents_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
expected_filtered_dragent_ids = [agents[0].id]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_no_agents(self):
expected_filtered_dragent_ids = []
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids,
expected_num_agents=0)
def test_filter_agents_two_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
expected_filtered_dragent_ids = [agent.id for agent in agents]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_agent_already_scheduled(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
self._test_filter_agents_helper(self.bgp_speaker,
expected_num_agents=0)
def test_filter_agents_multiple_agents_bgp_speakers(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
bgp_speaker = {'id': 'bar-speaker-id'}
self._save_bgp_speaker(bgp_speaker['id'])
expected_filtered_dragent_ids = [agents[1].id]
self._test_filter_agents_helper(
bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
class TestAutoScheduleBgpSpeakers(TestBgpDrAgentSchedulerBaseTestCase):
"""Unit test scenarios for schedule_unscheduled_bgp_speakers.
bgp_speaker_present
BGP speaker is present or not
scheduled_already
BGP speaker is already scheduled to the agent or not
agent_down
BGP DRAgent is down or alive
valid_host
If true, then an valid host is passed to schedule BGP speaker,
else an invalid host is passed.
"""
scenarios = [
('BGP speaker present',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=True)),
('No BGP speaker',
dict(bgp_speaker_present=False,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP speaker already scheduled',
dict(bgp_speaker_present=True,
scheduled_already=True,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP DR agent down',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=True,
valid_host=False,
expected_result=False)),
('Invalid host',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=False,
expected_result=False)),
]
def test_auto_schedule_bgp_speaker(self):
scheduler = bgp_dras.ChanceScheduler()
if self.bgp_speaker_present:
down_agent_count = 1 if self.agent_down else 0
agents = self._create_and_set_agents_down(
['host-a'], down_agent_count=down_agent_count)
if self.scheduled_already:
self._test_schedule_bind_bgp_speaker(agents,
self.bgp_speaker_id)
expected_hosted_agents = (1 if self.bgp_speaker_present and
self.valid_host else 0)
host = "host-a" if self.valid_host else "host-b"
observed_ret_value = scheduler.schedule_unscheduled_bgp_speakers(
self.ctx, host)
self.assertEqual(self.expected_result, observed_ret_value)
hosted_agents = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).all()
self.assertEqual(expected_hosted_agents, len(hosted_agents))

View File

@ -27,6 +27,8 @@ setup-hooks =
[entry_points]
neutron.db.alembic_migrations =
neutron-dynamic-routing = neutron_dynamic_routing.db.migration:alembic_migrations
oslo.config.opts =
bgp.agent = neutron_dynamic_routing.services.bgp.common.opts:list_bgp_agent_opts
[build_sphinx]
all_files = 1

28
tox.ini
View File

@ -18,6 +18,34 @@ commands =
# there is also secret magic in pretty_tox.sh which lets you run in a fail only
# mode. To do this define the TRACE_FAILONLY environmental variable.
[testenv:functional]
setenv = OS_TEST_PATH=./neutron_dynamic_routing/tests/functional
commands =
python setup.py testr --slowest --testr-args='{posargs}'
[testenv:api]
sitepackages=True
setenv =
OS_TEST_PATH=./neutron_dynamic_routing/tests/api/
OS_TESTR_CONCURRENCY=1
TEMPEST_CONFIG_DIR={env:TEMPEST_CONFIG_DIR:/opt/stack/tempest/etc}
commands =
python setup.py testr --slowest --testr-args='{posargs}'
[testenv:dsvm-functional]
setenv =
OS_TEST_PATH=./neutron_dynamic_routing/tests/functional
OS_SUDO_TESTING=1
OS_ROOTWRAP_CMD=sudo {envdir}/bin/neutron-rootwrap {envdir}/etc/neutron/rootwrap.conf
OS_ROOTWRAP_DAEMON_CMD=sudo {envdir}/bin/neutron-rootwrap-daemon {envdir}/etc/neutron/rootwrap.conf
OS_FAIL_ON_MISSING_DEPS=1
whitelist_externals =
sh
cp
sudo
commands =
python setup.py testr --slowest --testr-args='{posargs}'
[testenv:releasenotes]
# TODO(ihrachys): remove once infra supports constraints for this target
install_command = {toxinidir}/tools/tox_install.sh unconstrained {opts} {packages}