Browse Source

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
changes/24/312324/17
Ryan Tidwell 5 years ago
committed by vikram.choudhary
parent
commit
270b37f22b
  1. 0
      neutron_dynamic_routing/api/__init__.py
  2. 0
      neutron_dynamic_routing/api/rpc/__init__.py
  3. 0
      neutron_dynamic_routing/api/rpc/agentnotifiers/__init__.py
  4. 107
      neutron_dynamic_routing/api/rpc/agentnotifiers/bgp_dr_rpc_agent_api.py
  5. 0
      neutron_dynamic_routing/api/rpc/handlers/__init__.py
  6. 66
      neutron_dynamic_routing/api/rpc/handlers/bgp_speaker_rpc.py
  7. 0
      neutron_dynamic_routing/cmd/__init__.py
  8. 19
      neutron_dynamic_routing/cmd/eventlet/__init__.py
  9. 0
      neutron_dynamic_routing/cmd/eventlet/agents/__init__.py
  10. 20
      neutron_dynamic_routing/cmd/eventlet/agents/bgp_dragent.py
  11. 1011
      neutron_dynamic_routing/db/bgp_db.py
  12. 216
      neutron_dynamic_routing/db/bgp_dragentscheduler_db.py
  13. 3
      neutron_dynamic_routing/db/migration/models/head.py
  14. 0
      neutron_dynamic_routing/extensions/__init__.py
  15. 209
      neutron_dynamic_routing/extensions/bgp.py
  16. 184
      neutron_dynamic_routing/extensions/bgp_dragentscheduler.py
  17. 0
      neutron_dynamic_routing/services/__init__.py
  18. 0
      neutron_dynamic_routing/services/bgp/__init__.py
  19. 0
      neutron_dynamic_routing/services/bgp/agent/__init__.py
  20. 708
      neutron_dynamic_routing/services/bgp/agent/bgp_dragent.py
  21. 29
      neutron_dynamic_routing/services/bgp/agent/config.py
  22. 0
      neutron_dynamic_routing/services/bgp/agent/driver/__init__.py
  23. 142
      neutron_dynamic_routing/services/bgp/agent/driver/base.py
  24. 62
      neutron_dynamic_routing/services/bgp/agent/driver/exceptions.py
  25. 0
      neutron_dynamic_routing/services/bgp/agent/driver/ryu/__init__.py
  26. 202
      neutron_dynamic_routing/services/bgp/agent/driver/ryu/driver.py
  27. 75
      neutron_dynamic_routing/services/bgp/agent/driver/utils.py
  28. 48
      neutron_dynamic_routing/services/bgp/agent/entry.py
  29. 390
      neutron_dynamic_routing/services/bgp/bgp_plugin.py
  30. 0
      neutron_dynamic_routing/services/bgp/common/__init__.py
  31. 27
      neutron_dynamic_routing/services/bgp/common/constants.py
  32. 30
      neutron_dynamic_routing/services/bgp/common/opts.py
  33. 0
      neutron_dynamic_routing/services/bgp/scheduler/__init__.py
  34. 192
      neutron_dynamic_routing/services/bgp/scheduler/bgp_dragent_scheduler.py
  35. 288
      neutron_dynamic_routing/tests/api/test_bgp_speaker_extensions.py
  36. 121
      neutron_dynamic_routing/tests/api/test_bgp_speaker_extensions_negative.py
  37. 0
      neutron_dynamic_routing/tests/common/__init__.py
  38. 42
      neutron_dynamic_routing/tests/common/helpers.py
  39. 0
      neutron_dynamic_routing/tests/functional/services/__init__.py
  40. 0
      neutron_dynamic_routing/tests/functional/services/bgp/__init__.py
  41. 0
      neutron_dynamic_routing/tests/functional/services/bgp/scheduler/__init__.py
  42. 209
      neutron_dynamic_routing/tests/functional/services/bgp/scheduler/test_bgp_dragent_scheduler.py
  43. 0
      neutron_dynamic_routing/tests/unit/api/__init__.py
  44. 0
      neutron_dynamic_routing/tests/unit/api/rpc/__init__.py
  45. 0
      neutron_dynamic_routing/tests/unit/api/rpc/agentnotifiers/__init__.py
  46. 84
      neutron_dynamic_routing/tests/unit/api/rpc/agentnotifiers/test_bgp_dr_rpc_agent_api.py
  47. 0
      neutron_dynamic_routing/tests/unit/api/rpc/handlers/__init__.py
  48. 45
      neutron_dynamic_routing/tests/unit/api/rpc/handlers/test_bgp_speaker_rpc.py
  49. 0
      neutron_dynamic_routing/tests/unit/db/__init__.py
  50. 1047
      neutron_dynamic_routing/tests/unit/db/test_bgp_db.py
  51. 206
      neutron_dynamic_routing/tests/unit/db/test_bgp_dragentscheduler_db.py
  52. 0
      neutron_dynamic_routing/tests/unit/services/__init__.py
  53. 0
      neutron_dynamic_routing/tests/unit/services/bgp/__init__.py
  54. 0
      neutron_dynamic_routing/tests/unit/services/bgp/agent/__init__.py
  55. 748
      neutron_dynamic_routing/tests/unit/services/bgp/agent/test_bgp_dragent.py
  56. 0
      neutron_dynamic_routing/tests/unit/services/bgp/driver/__init__.py
  57. 0
      neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/__init__.py
  58. 251
      neutron_dynamic_routing/tests/unit/services/bgp/driver/ryu/test_driver.py
  59. 49
      neutron_dynamic_routing/tests/unit/services/bgp/driver/test_utils.py
  60. 0
      neutron_dynamic_routing/tests/unit/services/bgp/scheduler/__init__.py
  61. 225
      neutron_dynamic_routing/tests/unit/services/bgp/scheduler/test_bgp_dragent_scheduler.py
  62. 2
      setup.cfg
  63. 28
      tox.ini

0
neutron_dynamic_routing/api/__init__.py

0
neutron_dynamic_routing/api/rpc/__init__.py

0
neutron_dynamic_routing/api/rpc/agentnotifiers/__init__.py

107
neutron_dynamic_routing/api/rpc/agentnotifiers/bgp_dr_rpc_agent_api.py

@ -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)

0
neutron_dynamic_routing/api/rpc/handlers/__init__.py

66
neutron_dynamic_routing/api/rpc/handlers/bgp_speaker_rpc.py

@ -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)

0
neutron_dynamic_routing/cmd/__init__.py

19
neutron_dynamic_routing/tests/unit/test_neutron_dynamic_routing.py → neutron_dynamic_routing/cmd/eventlet/__init__.py

@ -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 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()
from neutron.common import eventlet_utils
def test_dummy(self):
"""Added dummy test just for test"""
pass
eventlet_utils.monkey_patch()

0
neutron_dynamic_routing/cmd/eventlet/agents/__init__.py

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

1011
neutron_dynamic_routing/db/bgp_db.py
File diff suppressed because it is too large
View File

216
neutron_dynamic_routing/db/bgp_dragentscheduler_db.py

@ -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 {}

3
neutron_dynamic_routing/db/migration/models/head.py

@ -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

0
neutron_dynamic_routing/extensions/__init__.py

209
neutron_dynamic_routing/extensions/bgp.py

@ -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)

184
neutron_dynamic_routing/extensions/bgp_dragentscheduler.py

@ -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

0
neutron_dynamic_routing/services/__init__.py

0
neutron_dynamic_routing/services/bgp/__init__.py

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

708
neutron_dynamic_routing/services/bgp/agent/bgp_dragent.py

@ -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][