Browse Source

BGP Dynamic Routing: introduce BgpDrAgent

This patch implements a new agent named "BgpDrAgent". The new agent
will host different BGP speaking drivers and makes the required BGP
peering session/s for neutron. The agent takes the needed "peer/s and
route/s" information from the BGP speaker entity and synchronize the
same to the registerd driver.

For realizing HA, two BgpDrAgents should host the same BGP speaker.

Partially-Implements: blueprint bgp-dynamic-routing
Co-Authored-By: Ryan Tidwell <ryan.tidwell@hpe.com>
Co-Authored-By: Jaume Devesa <devvesa@gmail.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Change-Id: I3217795bdd0fa2d9d4b39274f4f95fc013c8d29d
changes/26/309326/1
vikram.choudhary 6 years ago
committed by Ryan Tidwell
parent
commit
8f2b2f35bb
  1. 18
      devstack/lib/bgp
  2. 9
      devstack/plugin.sh
  3. 5
      devstack/settings
  4. 7
      etc/oslo-config-generator/bgp_dragent.ini
  5. 105
      neutron/api/rpc/agentnotifiers/bgp_dr_rpc_agent_api.py
  6. 65
      neutron/api/rpc/handlers/bgp_speaker_rpc.py
  7. 20
      neutron/cmd/eventlet/agents/bgp_dragent.py
  8. 23
      neutron/db/bgp_db.py
  9. 42
      neutron/db/bgp_dragentscheduler_db.py
  10. 12
      neutron/extensions/bgp_dragentscheduler.py
  11. 0
      neutron/services/bgp/agent/__init__.py
  12. 631
      neutron/services/bgp/agent/bgp_dragent.py
  13. 18
      neutron/services/bgp/agent/config.py
  14. 47
      neutron/services/bgp/agent/entry.py
  15. 46
      neutron/services/bgp/bgp_plugin.py
  16. 4
      neutron/services/bgp/common/constants.py
  17. 28
      neutron/services/bgp/common/opts.py
  18. 83
      neutron/tests/unit/api/rpc/agentnotifiers/test_bgp_dr_rpc_agent_api.py
  19. 44
      neutron/tests/unit/api/rpc/handlers/test_bgp_speaker_rpc.py
  20. 0
      neutron/tests/unit/services/bgp/agent/__init__.py
  21. 727
      neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py
  22. 2
      setup.cfg

18
devstack/lib/bgp

@ -5,3 +5,21 @@ function configure_bgp_service_plugin {
function configure_bgp {
configure_bgp_service_plugin
}
function configure_bgp_dragent {
cp $NEUTRON_DIR/etc/bgp_dragent.ini.sample $Q_BGP_DRAGENT_CONF_FILE
iniset $Q_BGP_DRAGENT_CONF_FILE DEFAULT verbose True
iniset $Q_BGP_DRAGENT_CONF_FILE DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
if [ -n "$BGP_ROUTER_ID" ]; then
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_router_id $BGP_ROUTER_ID
fi
}
function start_bgp_dragent {
run_process q-bgp-agt "$AGENT_BGP_BINARY --config-file $NEUTRON_CONF --config-file /$Q_BGP_DRAGENT_CONF_FILE"
}
function stop_bgp_dragent {
stop_process q-bgp-agt
}

9
devstack/plugin.sh

@ -24,6 +24,9 @@ if [[ "$1" == "stack" ]]; then
if is_service_enabled q-agt; then
configure_l2_agent
fi
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
configure_bgp_dragent
fi
#Note: sriov agent should run with OVS or linux bridge agent
#because they are the mechanisms that bind the DHCP and router ports.
#Currently devstack lacks the option to run two agents on the same node.
@ -39,10 +42,16 @@ if [[ "$1" == "stack" ]]; then
if is_service_enabled q-sriov-agt; then
start_l2_agent_sriov
fi
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
start_bgp_dragent
fi
;;
esac
elif [[ "$1" == "unstack" ]]; then
if is_service_enabled q-sriov-agt; then
stop_l2_agent_sriov
fi
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
stop_bgp_dragent
fi
fi

5
devstack/settings

@ -1 +1,6 @@
L2_AGENT_EXTENSIONS=${L2_AGENT_EXTENSIONS:-}
#BGP binary and config information
AGENT_BGP_BINARY=${AGENT_BGP_BINARY:-"$NEUTRON_BIN_DIR/neutron-bgp-dragent"}
Q_BGP_DRAGENT_CONF_FILE=${Q_BGP_DRAGENT_CONF_FILE:-"$NEUTRON_CONF_DIR/bgp_dragent.ini"}
BGP_ROUTER_ID=${BGP_ROUTER_ID:-}

7
etc/oslo-config-generator/bgp_dragent.ini

@ -0,0 +1,7 @@
[DEFAULT]
output_file = etc/bgp_dragent.ini.sample
wrap_width = 79
namespace = neutron.base.agent
namespace = neutron.bgp.agent
namespace = oslo.log

105
neutron/api/rpc/agentnotifiers/bgp_dr_rpc_agent_api.py

@ -0,0 +1,105 @@
# 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.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.services.bgp_speaker.agent.bgp_dragent.BgpDrAgent. For more
information about rpc interfaces, please see doc/source/devref/rpc_api.rst.
"""
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)

65
neutron/api/rpc/handlers/bgp_speaker_rpc.py

@ -0,0 +1,65 @@
# 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.extensions import bgp as bgp_ext
from neutron import manager
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.services.bgp_speaker.agent.bgp_dragent.BgpDrPluginApi.
For more information about changing RPC interfaces,
see doc/source/devref/rpc_api.rst.
"""
# 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)

20
neutron/cmd/eventlet/agents/bgp_dragent.py

@ -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.services.bgp.agent import entry as bgp_dragent
def main():
bgp_dragent.main()

23
neutron/db/bgp_db.py

@ -129,6 +129,20 @@ class BgpDbMixin(common_db.CommonDbMixin):
bgp_speaker = self._get_bgp_speaker(context, bgp_speaker_id)
return self._make_bgp_speaker_dict(bgp_speaker, fields)
def get_bgp_speaker_with_advertised_routes(self, context,
bgp_speaker_id):
bgp_speaker_attrs = ['id', 'local_as', 'tenant_id']
bgp_peer_attrs = ['peer_ip', 'remote_as', 'password']
with context.session.begin(subtransactions=True):
bgp_speaker = self.get_bgp_speaker(context, bgp_speaker_id,
fields=bgp_speaker_attrs)
res = dict((k, bgp_speaker[k]) for k in bgp_speaker_attrs)
res['peers'] = self.get_bgp_peers_by_bgp_speaker(context,
bgp_speaker['id'],
fields=bgp_peer_attrs)
res['advertised_routes'] = []
return res
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
bp = bgp_speaker[bgp_ext.BGP_SPEAKER_BODY_KEY_NAME]
with context.session.begin(subtransactions=True):
@ -211,6 +225,15 @@ class BgpDbMixin(common_db.CommonDbMixin):
sorts=sorts, limit=limit,
page_reverse=page_reverse)
def get_bgp_peers_by_bgp_speaker(self, context,
bgp_speaker_id, fields=None):
filters = [BgpSpeakerPeerBinding.bgp_speaker_id == bgp_speaker_id,
BgpSpeakerPeerBinding.bgp_peer_id == BgpPeer.id]
with context.session.begin(subtransactions=True):
query = context.session.query(BgpPeer)
query = query.filter(*filters)
return [self._make_bgp_peer_dict(x) for x in query.all()]
def get_bgp_peer(self, context, bgp_peer_id, fields=None):
bgp_peer_db = self._get_bgp_peer(context, bgp_peer_id)
return self._make_bgp_peer_dict(bgp_peer_db, fields=fields)

42
neutron/db/bgp_dragentscheduler_db.py

@ -18,6 +18,7 @@ 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._i18n import _
from neutron._i18n import _LW
@ -36,7 +37,7 @@ BGP_DRAGENT_SCHEDULER_OPTS = [
'bgp_drscheduler_driver',
default='neutron.services.bgp.scheduler'
'.bgp_dragent_scheduler.ChanceScheduler',
help=_('Driver used for scheduling BGP speakers to BGP DrAgent')),
help=_('Driver used for scheduling BGP speakers to BGP DrAgent'))
]
cfg.CONF.register_opts(BGP_DRAGENT_SCHEDULER_OPTS)
@ -73,7 +74,12 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
def schedule_bgp_speaker(self, context, created_bgp_speaker):
if self.bgp_drscheduler:
self.bgp_drscheduler.schedule(context, created_bgp_speaker)
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."))
@ -107,6 +113,8 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
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)
@ -129,6 +137,8 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
{'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)
@ -175,3 +185,31 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
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 {}

12
neutron/extensions/bgp_dragentscheduler.py

@ -169,3 +169,15 @@ class BgpDrSchedulerPluginBase(object):
@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

0
neutron/services/bgp/agent/__init__.py

631
neutron/services/bgp/agent/bgp_dragent.py

@ -0,0 +1,631 @@
# 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 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 neutron.agent import rpc as agent_rpc
from neutron.common import constants
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.common import utils
from neutron import context
from neutron.extensions import bgp as bgp_ext
from neutron._i18n import _, _LE, _LI, _LW
from neutron import manager
from neutron.services.bgp.common import constants as bgp_consts
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
doc/source/devref/rpc_api.rst.
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.conf = 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 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."""
# Check if the BGP Speaker is already added or not
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
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."""
# Check if the BGP Speaker is already added or not
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
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."""
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
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']})
# 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})
return
# Something went wrong. Let's re-sync
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})
def remove_bgp_peer_from_bgp_speaker(self, bgp_speaker_id, bgp_peer_ip):
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
# Something went wrong. Let's re-sync
self.schedule_resync(speaker_id=bgp_speaker_id,
reason="BGP Speaker Out-of-sync")
return
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})
return
# Peer should have been found, Some problem, Let's re-sync
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']})
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']})
return
# Something went wrong. Let's re-sync
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.PLUGIN)
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 == constants.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"))

18
neutron/services/bgp/agent/config.py

@ -0,0 +1,18 @@
# 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.
BGP_DRIVER_OPTS = []
BGP_PROTO_CONFIG_OPTS = []

47
neutron/services/bgp/agent/entry.py

@ -0,0 +1,47 @@
# 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.services.bgp.agent import config as bgp_dragent_config
from neutron.services.bgp.common import constants as bgp_consts
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.services.bgp.agent.bgp_dragent.'
'BgpDrAgentWithStateReport')
service.launch(cfg.CONF, server).wait()

46
neutron/services/bgp/bgp_plugin.py

@ -13,15 +13,21 @@
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from neutron.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api
from neutron.api.rpc.handlers import bgp_speaker_rpc as bs_rpc
from neutron.common import rpc as n_rpc
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db
from neutron.extensions import bgp as bgp_ext
from neutron.extensions import bgp_dragentscheduler as dras_ext
from neutron.services.bgp.common import constants as bgp_consts
from neutron.services import service_base
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
LOG = logging.getLogger(__name__)
class BgpPlugin(service_base.ServicePluginBase,
@ -35,6 +41,7 @@ class BgpPlugin(service_base.ServicePluginBase,
super(BgpPlugin, self).__init__()
self.bgp_drscheduler = importutils.import_object(
cfg.CONF.bgp_drscheduler_driver)
self._setup_rpc()
def get_plugin_name(self):
return PLUGIN_NAME
@ -46,3 +53,42 @@ class BgpPlugin(service_base.ServicePluginBase,
"""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 add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
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):
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)

4
neutron/services/bgp/common/constants.py

@ -14,3 +14,7 @@
# under the License.
AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'
BGP_DRAGENT = 'bgp_dragent'
BGP_PLUGIN = 'q-bgp-plugin'

28
neutron/services/bgp/common/opts.py

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

83
neutron/tests/unit/api/rpc/agentnotifiers/test_bgp_dr_rpc_agent_api.py

@ -0,0 +1,83 @@
# 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.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api
from neutron import context
from neutron.tests import base
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)

44
neutron/tests/unit/api/rpc/handlers/test_bgp_speaker_rpc.py

@ -0,0 +1,44 @@
# 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.api.rpc.handlers import bgp_speaker_rpc
from neutron.tests import base
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))

0
neutron/tests/unit/services/bgp/agent/__init__.py

727
neutron/tests/unit/services/bgp/agent/test_bgp_dragent.py

@ -0,0 +1,727 @@
# 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.common import config as common_config
from neutron import context
from neutron.services.bgp.agent import bgp_dragent
from neutron.services.bgp.agent import config as bgp_config
from neutron.services.bgp.agent import entry
from neutron.tests import base
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',
'password': ''}],
'advertised_routes': []}
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
'remote_as': '2345',
'peer_ip': '1.1.1.1',
'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.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')]
common_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()
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