Updating status, name field and handling reboot operation

* Status update
* New unit test cases
* Addition of field 'name' for association to DB and other operations.
* Handle reboot cases

Signed-off-by: Manu B <manu.b@est.tech>
Change-Id: I81fa87752c9c6eb48990f099577f20aa48af3103
This commit is contained in:
Manu B 2021-09-28 05:48:09 +00:00
parent 58042bb4ee
commit b62135aa3d
12 changed files with 603 additions and 47 deletions

View File

@ -0,0 +1,119 @@
# 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.api.rpc.callbacks import events as rpc_events
from neutron.api.rpc.callbacks import resources as resources_registry
from neutron.api.rpc.handlers import resources_rpc
from neutron_dynamic_routing.objects import bgp_associations as assoc_objects
class BgpL3AgentNotifyApi(object):
"""API for plugin to notify BGP L3Agent.
This class implements the client side of an rpc interface. The server side
is neutron_dynamic_routing.services.bgp.agent.l3.bgp_extension. For
more information about rpc interfaces, please see
https://docs.openstack.org/neutron/latest/contributor/internals/rpc_api.html.
"""
def __init__(self):
self._register_resources()
self.push_api = resources_rpc.ResourcesPushRpcApi()
def _register_resources(self):
resources_registry.register_resource_class(
assoc_objects.BgpSpeakerRouterAssociation)
resources_registry.register_resource_class(
assoc_objects.BgpSpeakerPeerAssociation)
def bgp_peer_association_created(self, context, bgp_speaker_id,
assoc_info):
"""Tell L3 agent about a BGP Peer Association.
This effectively tells the BgpL3Agent to start a peering session.
"""
peer_assoc_obj = assoc_objects.BgpSpeakerPeerAssociation(
context,
**assoc_info)
self.push_api.push(context, [peer_assoc_obj], rpc_events.CREATED)
def bgp_peer_association_deleted(self, context, bgp_speaker_id,
assoc_info):
"""Tell L3 agent about a BGP Peer disassociation.
This effectively tells the BgpL3Agent to stop a peering session.
"""
peer_assoc_obj = assoc_objects.BgpSpeakerPeerAssociation(context,
**assoc_info)
self.push_api.push(context, [peer_assoc_obj], rpc_events.DELETED)
def bgp_router_association_created(self, context, bgp_speaker_id,
assoc_info):
"""Tell L3 agent about a BGP Router Association.
This effectively tells the BgpL3Agent to start a speaker inside
router namespace.
"""
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.CREATED)
def bgp_router_association_updated(self, context, bgp_speaker_id,
assoc_info):
"""Tell L3 agent about a BGP Router Association updation."""
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.UPDATED)
def bgp_router_association_deleted(self, context, bgp_speaker_id,
assoc_info):
"""Tell L3 agent about a BGP Router disassociation.
This effectively tells the BgpL3Agent to stop the speaker inside
router namespace.
"""
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.DELETED)
def agent_updated(self, context, admin_state_up, host):
pass
def bgp_routes_advertisement(self, context, bgp_speaker_id,
routes, host):
"""Currently this is handled by L3 agent itself on router and peer
association.
This can be used in future to support FIP and other scenarios for
router attached speakers.
"""
def bgp_routes_withdrawal(self, context, bgp_speaker_id,
routes, host):
pass
def bgp_speaker_created(self, context, bgp_speaker_id, host):
"""BGP speaker is currently created whenever router is associated
to BGP speaker.
"""
def bgp_speaker_removed(self, context, bgp_speaker_id, host):
pass
def bgp_peer_disassociated(self, context, bgp_speaker_id,
bgp_peer_ip, host=None):
"""Currently router attached speakers only support peer association
objects. But this can be enhanced in future
"""
def bgp_peer_associated(self, context, bgp_speaker_id,
bgp_peer_id, host=None):
pass

View File

@ -83,3 +83,15 @@ class BgpSpeakerRpcCallback(object):
"""Returns the list of BGP speakers to which the router is associated
"""
return self.plugin.get_speaker_associated_to_router(context, router_id)
def update_speaker_router_association_status(self, context, assoc_id,
status):
"""Updates the status of router association"""
self.plugin.update_speaker_router_association_status(context,
assoc_id, status)
def update_speaker_peer_association_status(self, context, assoc_id,
status):
"""Updates the status of peer association"""
self.plugin.update_speaker_peer_association_status(context, assoc_id,
status)

View File

@ -21,6 +21,8 @@ from neutron.db.models import address_scope as address_scope_db
from neutron.db.models import l3 as l3_db
from neutron.db.models import l3_attrs as l3_attrs_db
from neutron.db import models_v2
from neutron.objects import agent as ag_obj
from neutron.objects import l3agent as rb_obj
from neutron.objects import ports
from neutron.objects import subnet as subnet_obj
from neutron.objects import subnetpool as subnetpool_obj
@ -81,6 +83,7 @@ class BgpSpeakerRouterAssociation(model_base.BASEV2,
__tablename__ = 'bgp_speaker_router_associations'
name = sa.Column(sa.String(255), nullable=False)
bgp_speaker_id = sa.Column(sa.String(length=36),
sa.ForeignKey('bgp_speakers.id'),
nullable=False)
@ -99,6 +102,7 @@ class BgpSpeakerPeerAssociation(model_base.BASEV2,
__tablename__ = 'bgp_speaker_peer_associations'
name = sa.Column(sa.String(255), nullable=False)
bgp_speaker_id = sa.Column(sa.String(length=36),
sa.ForeignKey('bgp_speakers.id'),
nullable=False)
@ -323,7 +327,7 @@ class BgpDbMixin(object):
except sa_exc.NoResultFound:
raise l3_exc.RouterNotFound(router_id=router_id)
res_keys = ['bgp_speaker_id', 'tenant_id', 'router_id',
res_keys = ['name', 'bgp_speaker_id', 'tenant_id', 'router_id',
'advertise_extra_routes', 'status']
res = dict((k, assoc_info[k]) for k in res_keys)
self._validate_router_association(context, bgp_speaker_id,
@ -344,6 +348,13 @@ class BgpDbMixin(object):
return self._make_bgp_speaker_router_association_dict(
speaker_router_assoc_db)
def update_speaker_router_association_status(self, context, id, status):
assoc_info = {}
assoc_info['status'] = status
with db_api.CONTEXT_WRITER.using(context):
assoc_db = self._get_bgp_speaker_router_association(context, id)
assoc_db.update(assoc_info)
def delete_bgp_speaker_router_association(self, context, id):
with db_api.CONTEXT_WRITER.using(context):
speaker_router_assoc_db = \
@ -388,7 +399,8 @@ class BgpDbMixin(object):
except sa_exc.NoResultFound:
raise bgp_ext.BgpPeerNotFound(id=peer_id)
res_keys = ['bgp_speaker_id', 'tenant_id', 'peer_id', 'status']
res_keys = ['name', 'bgp_speaker_id', 'tenant_id', 'peer_id',
'status']
res = dict((k, assoc_info[k]) for k in res_keys)
self._validate_peer_association(context, bgp_speaker_id, peer)
res['id'] = uuidutils.generate_uuid()
@ -397,6 +409,13 @@ class BgpDbMixin(object):
return self._make_bgp_speaker_peer_association_dict(
speaker_peer_assoc_db)
def update_speaker_peer_association_status(self, context, id, status):
assoc_info = {}
assoc_info['status'] = status
with db_api.CONTEXT_WRITER.using(context):
assoc_db = self._get_bgp_speaker_peer_association(context, id)
assoc_db.update(assoc_info)
def delete_bgp_speaker_peer_association(self, context, id):
with db_api.CONTEXT_WRITER.using(context):
assoc_db = self._get_bgp_speaker_peer_association(context, id)
@ -506,8 +525,13 @@ class BgpDbMixin(object):
return self._make_advertised_routes_dict(routes)
def get_routes(self, context, bgp_speaker_id):
"""TODO: Add implementation to list advertised and learnt routes"""
pass
assocs = self._get_bgp_speaker_router_association_by_speaker_id(
context, bgp_speaker_id)
routes = []
for assoc in assocs:
routes.extend(self._get_routes_by_router_association(
context, assoc['router_id'], bgp_speaker_id))
return {'routes': self._prefix_list_from_route_tuples(routes)}
def get_speaker_associated_to_router(self, context, router_id):
return self._get_bgp_speaker_router_association_by_router_id(context,
@ -551,6 +575,7 @@ class BgpDbMixin(object):
models_v2.SubnetPool.address_scope_id == address_scope.id,
BgpSpeaker.id == bgp_speaker_id,
BgpSpeaker.ip_version == address_scope.ip_version,
BgpSpeaker.advertise_tenant_networks == sa.sql.true(),
models_v2.Subnet.ip_version == address_scope.ip_version)
return tenant_router_query.all()
@ -785,6 +810,16 @@ class BgpDbMixin(object):
raise bgp_asso_ext.DuplicateBgpSpeakerPeerAssociation(
bgp_speaker_id=speaker_id,
peer_id=new_peer.id)
# Do not allow peer association if network is already associated to
# BGP speaker. Currently association objects are only supported with
# router attached BGP speakers.
network_bindings = self._get_bgp_speaker_network_binding_by_speaker_id(
context,
speaker_id)
if len(network_bindings) == 1:
raise bgp_asso_ext.InvalidBgpSpeakerAssociation(
bgp_speaker_id=speaker_id,
network_id=network_bindings[0]['network_id'])
# Do not allow peer association if network is already associated to
# BGP speaker. Currently association objects are only supported with
@ -806,14 +841,15 @@ class BgpDbMixin(object):
def _make_bgp_speaker_router_association_dict(self, router_association,
fields=None):
attrs = ['id', 'tenant_id', 'router_id', 'bgp_speaker_id',
attrs = ['id', 'name', 'tenant_id', 'router_id', 'bgp_speaker_id',
'advertise_extra_routes', 'status']
res = dict((k, router_association[k]) for k in attrs)
return db_utils.resource_fields(res, fields)
def _make_bgp_speaker_peer_association_dict(self, peer_association,
fields=None):
attrs = ['id', 'tenant_id', 'peer_id', 'bgp_speaker_id', 'status']
attrs = ['id', 'name', 'tenant_id', 'peer_id', 'bgp_speaker_id',
'status']
res = dict((k, peer_association[k]) for k in attrs)
return db_utils.resource_fields(res, fields)
@ -1570,3 +1606,13 @@ class BgpDbMixin(object):
ext_nets.append(net)
return ext_nets
def get_l3_agent_hosting_router(self, context, router_id):
with db_api.CONTEXT_READER.using(context):
record_objs = rb_obj.RouterL3AgentBinding.get_objects(
context, router_id=[router_id])
l3_agents = [
ag_obj.Agent.get_object(context, id=obj.l3_agent_id)
for obj in record_objs
]
return l3_agents[0]

View File

@ -274,3 +274,69 @@ class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
return self.get_bgp_peer(context, bgp_peer_id)
except exc.NoResultFound:
return {}
def _create_or_update_bgp_speaker_l3agent_binding(self, context, agent_id,
speaker_id,
dragent_id=None):
with db_api.CONTEXT_WRITER.using(context):
agent_db = self._get_agent(context, agent_id)
is_agent_l3 = (agent_db['agent_type'] ==
bgp_consts.L3_AGENT_BGP_ROUTING)
if not is_agent_l3:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
binding = BgpSpeakerDrAgentBinding()
binding.bgp_speaker_id = speaker_id
binding.agent_id = agent_id
query = context.session.query(BgpSpeakerDrAgentBinding)
if dragent_id:
try:
existing_binding = query.filter_by(
bgp_speaker_id=speaker_id, agent_id=dragent_id).one()
existing_binding.update(binding)
except exc.NoResultFound:
#Create a new binding with l3agent and speaker mapping
context.session.add(binding)
else:
context.session.add(binding)
def reschedule_bgp_speaker_on_l3_agent(self, context, bgp_speaker_id,
l3_agent_id):
dragents = self.get_dragents_hosting_bgp_speakers(
context, [bgp_speaker_id])
if len(dragents) > 1:
for dragent in dragents[1:]:
self.remove_bgp_speaker_from_dragent(context, dragent.id,
bgp_speaker_id)
#Replacing dragent with l3agent so that it is not autoscheduled
if dragents:
dragent_id = dragents[0].id
self._create_or_update_bgp_speaker_l3agent_binding(
context, l3_agent_id, bgp_speaker_id, dragent_id)
agent_db = self._get_agent(context, dragent_id)
self._bgp_rpc.bgp_speaker_removed(context, bgp_speaker_id,
agent_db.host)
else:
self._create_or_update_bgp_speaker_l3agent_binding(
context, l3_agent_id, bgp_speaker_id)
def remove_bgp_speaker_from_l3agent(self, context, bgp_speaker_id):
l3agent = self.get_dragents_hosting_bgp_speakers(
context, [bgp_speaker_id])[0]
agent_id = l3agent.id
with db_api.CONTEXT_WRITER.using(context):
agent_db = self._get_agent(context, agent_id)
is_agent_l3 = (agent_db['agent_type'] ==
bgp_consts.L3_AGENT_BGP_ROUTING)
if not is_agent_l3:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.filter_by(bgp_speaker_id=bgp_speaker_id,
agent_id=agent_id)
query.delete()
LOG.debug('BgpSpeaker %(bgp_speaker_id)s removed from '
'L3Agent %(agent_id)s',
{'bgp_speaker_id': bgp_speaker_id,
'agent_id': agent_id})

View File

@ -33,6 +33,7 @@ def upgrade():
'bgp_speaker_router_associations',
sa.Column('id', sa.String(length=constants.UUID_FIELD_SIZE),
nullable=False),
sa.Column('name', sa.String(255), nullable=False),
sa.Column('project_id', sa.String(length=255)),
sa.Column('bgp_speaker_id', sa.String(length=36),
sa.ForeignKey('bgp_speakers.id'), nullable=False),
@ -45,6 +46,7 @@ def upgrade():
'bgp_speaker_peer_associations',
sa.Column('id', sa.String(length=constants.UUID_FIELD_SIZE),
nullable=False),
sa.Column('name', sa.String(255), nullable=False),
sa.Column('project_id', sa.String(length=255)),
sa.Column('bgp_speaker_id', sa.String(length=36),
sa.ForeignKey('bgp_speakers.id'),

View File

@ -73,7 +73,7 @@ class DuplicateBgpSpeakerRouterAssociation(n_exc.Conflict):
"router with id %(router_id)s.")
class InvalidBgpSpeakerRouterAssociation(n_exc.Conflict):
class InvalidBgpSpeakerAssociation(n_exc.Conflict):
message = _("BGP Speaker %(bgp_speaker_id)s is already associated to "
"network with id %(network_id)s.")

View File

@ -25,6 +25,7 @@ from os_ken.services.protocols.bgp import net_ctrl
from os_ken.services.protocols.bgp.rtconf import neighbors
from os_ken.services.protocols.bgp.rtconf.neighbors import PASSWORD
from oslo_log import log as logging
import tenacity
from neutron.privileged.agent.linux import ip_lib
from neutron_dynamic_routing._i18n import _LI
@ -75,6 +76,9 @@ def del_bgp_speaker(bgp_speaker_id, ns):
@privileged.bgp_speaker_cmd.entrypoint
@tenacity.retry(retry=tenacity.retry_if_exception_type(
(EOFError, ConnectionRefusedError)),
wait=tenacity.wait_random(min=1, max=2))
def add_bgp_neighbor(bgp_speaker_id, peer_ip, remote_as, ns,
password=None, auth_type='none'):
with open(get_netns_path(ns)) as fd:

View File

@ -129,6 +129,8 @@ class BgpAgentExtension(l3_extension.L3AgentExtension):
context, assoc.bgp_speaker_id, assoc.router_id)
self.routes_cache.create_speaker_routes(assoc.bgp_speaker_id,
routes)
self.rpc_plugin.update_speaker_router_association_status(
context, assoc.id, bgp_consts.STATUS_ACTIVE)
elif event_type == rpc_events.UPDATED:
if assoc.advertise_extra_routes:
routes = self.rpc_plugin.get_routes_to_advertise(
@ -168,6 +170,8 @@ class BgpAgentExtension(l3_extension.L3AgentExtension):
priv_driver.advertise_routes(
tuple(self.routes_cache.get_speaker_routes(
peer_assoc.bgp_speaker_id)))
self.rpc_plugin.update_speaker_peer_association_status(
context, peer_assoc.id, bgp_consts.STATUS_ACTIVE)
elif event_type == rpc_events.DELETED:
priv_driver.del_bgp_neighbor(peer_assoc.bgp_speaker_id,
peer['peer_ip'],
@ -181,7 +185,33 @@ class BgpAgentExtension(l3_extension.L3AgentExtension):
:param context: RPC context.
:param data: Router data.
"""
pass
LOG.debug("ADD ROUTER %s", data)
router_info = self.agent_api.get_router_info(data['id'])
if router_info:
router_assocs = self.rpc_plugin.get_speaker_associated_to_router(
context, data['id'])
for router_assoc in router_assocs:
bgp_speaker_id = router_assoc['bgp_speaker_id']
sp = self.rpc_plugin.get_bgp_speaker_info(context,
bgp_speaker_id)
if priv_driver.PROCESS_CACHE.get_bgp_speaker_process(
bgp_speaker_id):
LOG.debug("BGP process already running for speaker %s",
bgp_speaker_id)
continue
priv_driver.add_bgp_speaker(bgp_speaker_id,
sp['local_as'], '1.1.1.1',
router_info.ns_name,
sp['ip_version'])
LOG.debug("ADD ROUTER PEER ASSOC %s", sp['peer_associations'])
for peer_assoc in sp['peer_associations']:
peer = self.rpc_plugin.get_bgp_peer_info(
context, peer_assoc['peer_id'])
LOG.debug("ADD ROUTER PEER %s", peer)
priv_driver.add_bgp_neighbor(peer_assoc['bgp_speaker_id'],
peer['peer_ip'],
peer['remote_as'],
router_info.ns_name)
def update_router(self, context, data):
"""Handle a router update event.

View File

@ -65,3 +65,17 @@ class BgpL3ExtPluginApi(object):
cctxt = self.client.prepare()
return cctxt.call(context, 'get_speaker_associated_to_router',
router_id=router_id)
def update_speaker_router_association_status(self, context, assoc_id,
status):
"""Make a remote process call to update router association status"""
cctxt = self.client.prepare()
return cctxt.call(context, 'update_speaker_router_association_status',
assoc_id=assoc_id, status=status)
def update_speaker_peer_association_status(self, context, assoc_id,
status):
"""Make a remote process call to update peer association status"""
cctxt = self.client.prepare()
return cctxt.call(context, 'update_speaker_peer_association_status',
assoc_id=assoc_id, status=status)

View File

@ -14,7 +14,6 @@
from netaddr import IPAddress
from neutron.api.rpc.callbacks import events as rpc_events
from neutron.api.rpc.callbacks import resources as resources_registry
from neutron.api.rpc.handlers import resources_rpc
from neutron_lib.api.definitions import bgp as bgp_ext
@ -34,10 +33,12 @@ from oslo_log import log as logging
from oslo_utils import importutils
from neutron_dynamic_routing.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api # noqa
from neutron_dynamic_routing.api.rpc.agentnotifiers import l3_bgp_rpc_agent_api
from neutron_dynamic_routing.api.rpc.callbacks import resources as dr_resources
from neutron_dynamic_routing.api.rpc.handlers import bgp_speaker_rpc as bs_rpc
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.db import bgp_dragentscheduler_db
from neutron_dynamic_routing.extensions import bgp_dragentscheduler as bgp_dras_ext # noqa
from neutron_dynamic_routing.objects import bgp_associations as assoc_objects
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
@ -153,10 +154,11 @@ class BgpPlugin(service_base.ServicePluginBase,
context,
[bgp_speaker_id])
super(BgpPlugin, self).delete_bgp_speaker(context, bgp_speaker_id)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
for agent in hosted_bgp_dragents:
self._bgp_rpc.bgp_speaker_removed(context,
bgp_speaker_id,
agent.host)
agent_rpc.bgp_speaker_removed(context,
bgp_speaker_id,
agent.host)
def get_bgp_peers(self, context, fields=None, filters=None, sorts=None,
limit=None, marker=None, page_reverse=False):
@ -189,10 +191,11 @@ class BgpPlugin(service_base.ServicePluginBase,
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
context,
[bgp_speaker_id])
agent_rpc = self.get_rpc(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)
agent_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):
@ -202,12 +205,12 @@ class BgpPlugin(service_base.ServicePluginBase,
ret_value = super(BgpPlugin, self).remove_bgp_peer(context,
bgp_speaker_id,
bgp_peer_info)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
for agent in hosted_bgp_dragents:
self._bgp_rpc.bgp_peer_disassociated(context,
bgp_speaker_id,
ret_value['bgp_peer_id'],
agent.host)
agent_rpc.bgp_peer_disassociated(context,
bgp_speaker_id,
ret_value['bgp_peer_id'],
agent.host)
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
super(BgpPlugin, self).add_bgp_speaker_to_dragent(context,
@ -262,10 +265,16 @@ class BgpPlugin(service_base.ServicePluginBase,
bgp_speaker_id,
router_association)
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.CREATED)
#By default, BGP speaker is bound to dr agent. On router association
#it has to be rescheduled to l3 agent
router_id = assoc_info['router_id']
l3_agent = super(BgpPlugin, self).get_l3_agent_hosting_router(
context, router_id)
super(BgpPlugin, self).reschedule_bgp_speaker_on_l3_agent(
context, bgp_speaker_id, l3_agent.id)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
agent_rpc.bgp_router_association_created(context, bgp_speaker_id,
assoc_info)
return assoc_info
def update_bgp_speaker_router_association(self, context, assoc_id,
@ -274,21 +283,31 @@ class BgpPlugin(service_base.ServicePluginBase,
.update_bgp_speaker_router_association(context, assoc_id,
router_assoc)
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.UPDATED)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
agent_rpc.bgp_router_association_updated(context, bgp_speaker_id,
assoc_info)
return assoc_info
def update_speaker_router_association_status(self, context, assoc_id,
status):
super(BgpPlugin, self).update_speaker_router_association_status(
context, assoc_id, status)
def delete_bgp_speaker_router_association(self, context, id,
bgp_speaker_id):
assoc_info = super(BgpPlugin, self)\
.delete_bgp_speaker_router_association(context, id)
router_assoc_obj = assoc_objects.BgpSpeakerRouterAssociation(
context,
**assoc_info)
self.push_api.push(context, [router_assoc_obj], rpc_events.DELETED)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
agent_rpc.bgp_router_association_deleted(context, bgp_speaker_id,
assoc_info)
#Remove l3 agent to speaker mapping and reschedule bgp speaker
#to BGP DR agent again
super(BgpPlugin, self).remove_bgp_speaker_from_l3agent(context,
bgp_speaker_id)
bgp_speaker = self.get_bgp_speaker(context, bgp_speaker_id)
super(BgpPlugin, self).schedule_bgp_speaker(context, bgp_speaker)
def get_bgp_speaker_peer_associations(
self, context, bgp_speaker_id,
@ -314,20 +333,24 @@ class BgpPlugin(service_base.ServicePluginBase,
.create_bgp_speaker_peer_association(context, bgp_speaker_id,
peer_association)
peer_assoc_obj = assoc_objects.BgpSpeakerPeerAssociation(
context,
**assoc_info)
self.push_api.push(context, [peer_assoc_obj], rpc_events.CREATED)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
agent_rpc.bgp_peer_association_created(context, bgp_speaker_id,
assoc_info)
return assoc_info
def update_speaker_peer_association_status(self, context, assoc_id,
status):
super(BgpPlugin, self).update_speaker_peer_association_status(
context, assoc_id, status)
def delete_bgp_speaker_peer_association(self, context, id,
bgp_speaker_id):
assoc_info = super(BgpPlugin, self)\
.delete_bgp_speaker_peer_association(context, id)
peer_assoc_obj = assoc_objects.BgpSpeakerPeerAssociation(context,
**assoc_info)
self.push_api.push(context, [peer_assoc_obj], rpc_events.DELETED)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
agent_rpc.bgp_peer_association_deleted(context, bgp_speaker_id,
assoc_info)
def get_speaker_associated_to_router(self, context, router_id):
return super(BgpPlugin, self).get_speaker_associated_to_router(
@ -515,11 +538,12 @@ class BgpPlugin(service_base.ServicePluginBase,
def start_route_advertisements(self, ctx, bgp_rpc,
bgp_speaker_id, routes):
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
for agent in agents['agents']:
bgp_rpc.bgp_routes_advertisement(ctx,
bgp_speaker_id,
routes,
agent['host'])
agent_rpc.bgp_routes_advertisement(ctx,
bgp_speaker_id,
routes,
agent['host'])
msg = "Starting route advertisements for %s on BgpSpeaker %s"
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
@ -527,11 +551,12 @@ class BgpPlugin(service_base.ServicePluginBase,
def stop_route_advertisements(self, ctx, bgp_rpc,
bgp_speaker_id, routes):
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
agent_rpc = self.get_rpc(context, bgp_speaker_id)
for agent in agents['agents']:
bgp_rpc.bgp_routes_withdrawal(ctx,
bgp_speaker_id,
routes,
agent['host'])
agent_rpc.bgp_routes_withdrawal(ctx,
bgp_speaker_id,
routes,
agent['host'])
msg = "Stopping route advertisements for %s on BgpSpeaker %s"
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
@ -542,3 +567,19 @@ class BgpPlugin(service_base.ServicePluginBase,
if LOG.isEnabledFor(logging.DEBUG):
for route in routes:
LOG.debug(msg, route, bgp_speaker_id)
def get_rpc(self, context, bgp_speaker_id):
hosted_agents = self.get_dragents_hosting_bgp_speakers(
context, [bgp_speaker_id])
rpc = self._bgp_rpc
if len(hosted_agents) >= 1:
#Checking agent type of a single binding
agent_id = hosted_agents[0].id
agent_db = self._get_agent(context, agent_id)
if agent_db['agent_type'] == bgp_consts.AGENT_TYPE_BGP_ROUTING:
return rpc
elif agent_db['agent_type'] == bgp_consts.L3_AGENT_BGP_ROUTING:
rpc = l3_bgp_rpc_agent_api.BgpL3AgentNotifyApi()
else:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
return rpc

View File

@ -14,6 +14,7 @@
# under the License.
AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'
L3_AGENT_BGP_ROUTING = 'L3 agent'
BGP_DRAGENT = 'bgp_dragent'

View File

@ -0,0 +1,221 @@
# 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 unittest import mock
from neutron.agent.l3 import agent as l3_agent
from neutron.agent.l3 import l3_agent_extension_api as l3_ext_api
from neutron.agent.l3 import router_info as l3router
from neutron.api.rpc.callbacks.consumer import registry
from neutron.api.rpc.callbacks import events
from neutron.api.rpc.handlers import resources_rpc
from neutron.tests.unit.agent.l3 import test_agent
from neutron_lib import constants
from neutron_lib import context
from oslo_utils import uuidutils
from neutron_dynamic_routing.objects import bgp_associations as assoc_objects
from neutron_dynamic_routing.privileged import driver as priv_driver
from neutron_dynamic_routing.services.bgp.agent.l3 import bgp_extension
BGP_ROUTER_ASSOC = assoc_objects.BgpSpeakerRouterAssociation.obj_name()
BGP_PEER_ASSOC = assoc_objects.BgpSpeakerPeerAssociation.obj_name()
HOSTNAME = 'testhost'
FAKE_BGPSPEAKER_UUID = uuidutils.generate_uuid()
FAKE_ROUTER_ASSOC_UUID = uuidutils.generate_uuid()
FAKE_PEER_ASSOC_UUID = uuidutils.generate_uuid()
FAKE_ROUTER_UUID = uuidutils.generate_uuid()
FAKE_BGPPEER_UUID = uuidutils.generate_uuid()
FAKE_BGP_SPEAKER = {'id': FAKE_BGPSPEAKER_UUID,
'ip_version': 4,
'local_as': 12345,
'peers': [{'remote_as': '2345',
'peer_ip': '1.1.1.1',
'auth_type': 'none',
'password': ''}],
'router_associations': [{
'bgp_speaker_id': FAKE_BGPSPEAKER_UUID,
'router_id': FAKE_ROUTER_UUID,
'advertise_extra_routes': True}],
'peer_associations': [{
'bgp_speaker_id': FAKE_BGPSPEAKER_UUID,
'peer_id': FAKE_BGPPEER_UUID}],
'advertised_routes': []}
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
'remote_as': '2345',
'peer_ip': '1.1.1.1',
'auth_type': 'none',
'password': ''}
FAKE_ROUTES = ['192.168.145.0/24', '145.0.0.0/24']
FAKE_EXTRA_ROUTE = ['145.0.0.0/24']
class BgpL3AgentExtensionBaseTestCase(
test_agent.BasicRouterOperationsFramework):
def setUp(self):
super(BgpL3AgentExtensionBaseTestCase, self).setUp()
self.bgp_ext = bgp_extension.BgpAgentExtension()
self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None)
self.bgp_ext.consume_api(self.agent_api)
self.connection = mock.Mock()
self.context = context.get_admin_context()
self.agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
self.router_id = FAKE_ROUTER_UUID
self.router = {'id': self.router_id,
'ha': False,
'distributed': False}
self.router_info = l3router.RouterInfo(self.agent, self.router_id,
self.router, **self.ri_kwargs)
self.agent.router_info[self.router['id']] = self.router_info
self.get_router_info = mock.patch(
'neutron.agent.l3.l3_agent_extension_api.'
'L3AgentExtensionAPI.get_router_info').start()
self.get_router_info.return_value = self.router_info
class BgpL3AgentExtensionInitializeTestCase(BgpL3AgentExtensionBaseTestCase):
@mock.patch.object(registry, 'register')
@mock.patch.object(resources_rpc, 'ResourcesPushRpcCallback')
def test_initialize_subscribed_to_rpc(self, rpc_mock, subscribe_mock):
call_to_patch = 'neutron_lib.rpc.Connection'
with mock.patch(call_to_patch,
return_value=self.connection) as create_connection:
self.bgp_ext.initialize(
self.connection, constants.L3_AGENT_MODE)
create_connection.assert_has_calls([mock.call()])
self.connection.create_consumer.assert_has_calls(
[mock.call(
resources_rpc.resource_type_versioned_topic(
BGP_ROUTER_ASSOC),
[rpc_mock()],
fanout=True),
mock.call(
resources_rpc.resource_type_versioned_topic(
BGP_PEER_ASSOC),
[rpc_mock()],
fanout=True)]
)
subscribe_mock.assert_any_call(
mock.ANY, BGP_ROUTER_ASSOC)
subscribe_mock.assert_called_with(
mock.ANY, BGP_PEER_ASSOC)
class BGPL3AgentExtensionRouterAssocTestCase(BgpL3AgentExtensionBaseTestCase):
def setUp(self):
super(BGPL3AgentExtensionRouterAssocTestCase, self).setUp()
self.bgp_ext.initialize(
self.connection, constants.L3_AGENT_MODE)
router_assoc_info = {'id': FAKE_ROUTER_ASSOC_UUID,
'router_id': self.router_id,
'bgp_speaker_id': FAKE_BGPSPEAKER_UUID,
'advertise_extra_routes': True}
self.router_assoc = assoc_objects.BgpSpeakerRouterAssociation(
self.context, **router_assoc_info)
peer_assoc_info = {'id': FAKE_PEER_ASSOC_UUID,
'peer_id': FAKE_BGPPEER_UUID,
'bgp_speaker_id': FAKE_BGPSPEAKER_UUID}
self.peer_assoc = assoc_objects.BgpSpeakerPeerAssociation(
self.context, **peer_assoc_info)
self.bgp_ext.rpc_plugin = mock.Mock()
self.plugin_rpc = self.bgp_ext.rpc_plugin
self.plugin_rpc.get_bgp_speaker_info.return_value = FAKE_BGP_SPEAKER
self.plugin_rpc.get_bgp_peer_info.return_value = FAKE_BGP_PEER
self.plugin_rpc.get_routes_to_advertise.return_value = FAKE_ROUTES
self.plugin_rpc.get_routes_to_withdraw.return_value = FAKE_EXTRA_ROUTE
self.plugin_rpc.get_speaker_associated_to_router.return_value = [
router_assoc_info]
@mock.patch.object(priv_driver, 'add_bgp_speaker')
def test_handle_create_router_assoc(self, driver_mock):
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[self.router_assoc],
events.CREATED)
driver_mock.assert_called_once_with(FAKE_BGPSPEAKER_UUID,
FAKE_BGP_SPEAKER['local_as'],
mock.ANY,
mock.ANY, 4)
@mock.patch.object(priv_driver, 'advertise_routes')
def test_handle_update_router_assoc_with_advertise_extra_routes(
self, driver_mock):
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[self.router_assoc],
events.UPDATED)
driver_mock.assert_called_once_with(tuple(FAKE_ROUTES))
@mock.patch.object(priv_driver, 'withdraw_routes')
def test_handle_update_router_assoc_without_advertise_extra_routes(
self, driver_mock):
assoc_info = {'id': FAKE_ROUTER_ASSOC_UUID,
'router_id': self.router_id,
'bgp_speaker_id': FAKE_BGPSPEAKER_UUID,
'advertise_extra_routes': False}
router_assoc = assoc_objects.BgpSpeakerPeerAssociation(
self.context, **assoc_info)
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[router_assoc],
events.UPDATED)
driver_mock.assert_called_once_with(tuple(FAKE_EXTRA_ROUTE))
@mock.patch.object(priv_driver, 'del_bgp_speaker')
def test_handle_delete_router_assoc(self, driver_mock):
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[self.router_assoc],
events.DELETED)
driver_mock.assert_called_once_with(FAKE_BGPSPEAKER_UUID, mock.ANY)
@mock.patch.object(priv_driver, 'advertise_routes')
@mock.patch.object(priv_driver, 'add_bgp_neighbor')
def test_handle_create_peer_assoc(self, driver_mock, advertise_mock):
priv_driver.add_bgp_speaker = mock.Mock()
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[self.router_assoc],
events.CREATED)
self.bgp_ext._handle_peer_notification(self.context, mock.Mock(),
[self.peer_assoc],
events.CREATED)
driver_mock.assert_called_once_with(FAKE_BGPSPEAKER_UUID,
FAKE_BGP_PEER['peer_ip'],
FAKE_BGP_PEER['remote_as'],
mock.ANY)
advertise_mock.assert_called_once_with(tuple(FAKE_ROUTES))
@mock.patch.object(priv_driver, 'del_bgp_neighbor')
def test_handle_delete_peer_assoc(self, driver_mock):
self.bgp_ext._handle_peer_notification(self.context, mock.Mock(),
[self.peer_assoc],
events.DELETED)
driver_mock.assert_called_once_with(FAKE_BGPSPEAKER_UUID,
FAKE_BGP_PEER['peer_ip'],
mock.ANY)
@mock.patch.object(priv_driver, 'advertise_routes')
@mock.patch.object(priv_driver, 'withdraw_routes')
def test_update_router(self, mock_withdraw, mock_advertise):
priv_driver.add_bgp_speaker = mock.Mock()
priv_driver.add_bgp_neighbor = mock.Mock()
self.bgp_ext._handle_router_notification(self.context, mock.Mock(),
[self.router_assoc],
events.CREATED)
self.bgp_ext._handle_peer_notification(self.context, mock.Mock(),
[self.peer_assoc],
events.CREATED)
new_route = ['155.0.0.0/24']
self.plugin_rpc.get_routes_to_advertise.return_value = new_route
self.bgp_ext.update_router(self.context, self.router)
mock_advertise.assert_called_with(tuple(new_route))
mock_withdraw.assert_called_with(tuple(FAKE_ROUTES))