BGPaaS server side implementation

* Addition of BGP L3 agent extension for handling BGP speaker router
  association and peer association
* Push router,peer association event from BGP plugin to BGP L3 agent extension
* Addition of privilieged module for executing BGP operations
* RPC client calls from L3 agent extension to osken BGP speaker for different
  BGP operations.

Signed-off-by: Manu B <manu.b@est.tech>
Change-Id: Ied48acd5c552515b1a5e16da6fd01c07ba974dd5
This commit is contained in:
Manu B 2021-07-27 09:39:46 +00:00
parent 1e5d8c603f
commit 8ee0e3ca6e
11 changed files with 585 additions and 25 deletions

View File

@ -197,7 +197,7 @@ class BgpDbMixin(object):
def get_bgp_speaker_with_advertised_routes(self, context,
bgp_speaker_id):
bgp_speaker_attrs = ['id', 'local_as', 'tenant_id']
bgp_speaker_attrs = ['id', 'local_as', 'tenant_id', 'ip_version']
bgp_peer_attrs = ['peer_ip', 'remote_as', 'auth_type', 'password']
with db_api.CONTEXT_READER.using(context):
bgp_speaker = self.get_bgp_speaker(context, bgp_speaker_id,
@ -209,6 +209,14 @@ class BgpDbMixin(object):
res['advertised_routes'] = self.get_routes_by_bgp_speaker_id(
context,
bgp_speaker_id)
res['router_associations'] = \
self._get_bgp_speaker_router_association_by_speaker_id(
context,
bgp_speaker_id)
res['peer_associations'] = \
self._get_bgp_speaker_peer_association_by_speaker_id(
context,
bgp_speaker_id)
return res
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
@ -250,6 +258,7 @@ class BgpDbMixin(object):
network_id = self._get_id_for(network_info, 'network_id')
with db_api.CONTEXT_WRITER.using(context):
try:
self._validate_network_binding(context, bgp_speaker_id)
self._save_bgp_speaker_network_binding(context,
bgp_speaker_id,
network_id)
@ -267,6 +276,16 @@ class BgpDbMixin(object):
network_id)
return {'network_id': network_id}
def _validate_network_binding(self, context, speaker_id):
#Do not allow network binding if router association is present.
router_assocs = self._get_bgp_speaker_router_association_by_speaker_id(
context,
speaker_id)
if len(router_assocs) > 0:
raise bgp_asso_ext.DuplicateBgpSpeakerRouterAssociation(
bgp_speaker_id=speaker_id,
router_id=router_assocs[0]['router_id'])
def get_bgp_speaker_router_associations(self, context, bgp_speaker_id,
fields=None, filters=None,
sorts=None, limit=None,
@ -299,14 +318,16 @@ class BgpDbMixin(object):
raise bgp.BgpSpeakerNotFound(id=bgp_speaker_id)
try:
model_query.get_by_id(context, l3_db.Router, router_id)
router_info = model_query.get_by_id(
context, l3_db.Router, router_id)
except sa_exc.NoResultFound:
raise l3_exc.RouterNotFound(router_id=router_id)
res_keys = ['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)
self._validate_router_association(context, bgp_speaker_id,
router_info)
res['id'] = uuidutils.generate_uuid()
speaker_router_assoc_db = BgpSpeakerRouterAssociation(**res)
context.session.add(speaker_router_assoc_db)
@ -325,8 +346,11 @@ class BgpDbMixin(object):
def delete_bgp_speaker_router_association(self, context, id):
with db_api.CONTEXT_WRITER.using(context):
binding = self._get_bgp_speaker_router_association(context, id)
context.session.delete(binding)
speaker_router_assoc_db = \
self._get_bgp_speaker_router_association(context, id)
context.session.delete(speaker_router_assoc_db)
return self._make_bgp_speaker_router_association_dict(
speaker_router_assoc_db)
def get_bgp_speaker_peer_associations(self, context, bgp_speaker_id,
fields=None, filters=None,
@ -375,12 +399,17 @@ class BgpDbMixin(object):
def delete_bgp_speaker_peer_association(self, context, id):
with db_api.CONTEXT_WRITER.using(context):
binding = self._get_bgp_speaker_peer_association(context, id)
context.session.delete(binding)
assoc_db = self._get_bgp_speaker_peer_association(context, id)
context.session.delete(assoc_db)
return self._make_bgp_speaker_peer_association_dict(assoc_db)
def delete_bgp_speaker(self, context, bgp_speaker_id):
with db_api.CONTEXT_WRITER.using(context):
bgp_speaker_db = self._get_bgp_speaker(context, bgp_speaker_id)
if (bgp_speaker_db['router_associations'] or
bgp_speaker_db['peer_associations']):
raise bgp_asso_ext.DependentAssociationExists(
bgp_speaker_id=bgp_speaker_id)
context.session.delete(bgp_speaker_db)
def create_bgp_peer(self, context, bgp_peer):
@ -424,6 +453,11 @@ class BgpDbMixin(object):
def delete_bgp_peer(self, context, bgp_peer_id):
with db_api.CONTEXT_WRITER.using(context):
peer_assocs = self._get_bgp_speaker_peer_association_by_peer_id(
context, bgp_peer_id)
if peer_assocs:
raise bgp_asso_ext.DependentPeerAssociationExists(
bgp_peer_id=bgp_peer_id)
bgp_peer_db = self._get_bgp_peer(context, bgp_peer_id)
context.session.delete(bgp_peer_db)
@ -634,7 +668,7 @@ class BgpDbMixin(object):
return query.filter(
BgpSpeakerRouterAssociation.bgp_speaker_id == bgp_speaker_id).all()
def _validate_router_association(self, context, speaker_id):
def _validate_router_association(self, context, speaker_id, router_info):
# Current implementation allows only one router to be associated with
# BGP speaker.
# Also, do not allow same router to be associated again with speaker.
@ -643,8 +677,8 @@ class BgpDbMixin(object):
speaker_id)
if len(router_assocs) == 1:
raise bgp_asso_ext.DuplicateBgpSpeakerRouterAssociation(
bgp_speaker_id=speaker_id,
router_id=router_assocs[0]['router_id'])
bgp_speaker_id=speaker_id,
router_id=router_assocs[0]['router_id'])
# Do not allow router association if network is already associated to
# BGP speaker.
@ -652,9 +686,13 @@ class BgpDbMixin(object):
context,
speaker_id)
if len(network_bindings) == 1:
raise bgp_asso_ext.InvalidBgpSpeakerRouterAssociation(
bgp_speaker_id=speaker_id,
network_id=network_bindings[0]['network_id'])
raise bgp_asso_ext.InvalidBgpSpeakerAssociation(
bgp_speaker_id=speaker_id,
network_id=network_bindings[0]['network_id'])
if router_info['enable_snat']:
raise bgp_asso_ext.InvalidBgpSpeakerSnatRouterAssociation(
bgp_speaker_id=speaker_id, router_id=router_info['id'])
def _get_bgp_speaker_peer_association_by_speaker_id(self, context,
bgp_speaker_id):
@ -663,6 +701,12 @@ class BgpDbMixin(object):
return query.filter(
BgpSpeakerPeerAssociation.bgp_speaker_id == bgp_speaker_id).all()
def _get_bgp_speaker_peer_association_by_peer_id(self, context, peer_id):
query = model_query.query_with_hooks(context,
BgpSpeakerPeerAssociation)
return query.filter(
BgpSpeakerPeerAssociation.peer_id == peer_id).all()
def _validate_peer_association(self, context, speaker_id, new_peer):
# Do not allow same peer to be associated again with BGP speaker.
peer_assocs = self._get_bgp_speaker_peer_association_by_speaker_id(
@ -675,6 +719,17 @@ class BgpDbMixin(object):
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'])
def _get_bgp_speaker_peer_association(self, context, id):
try:
return model_query.get_by_id(context, BgpSpeakerPeerAssociation,

View File

@ -85,3 +85,18 @@ class BgpSpeakerPeerAssociationNotFound(n_exc.NotFound):
class DuplicateBgpSpeakerPeerAssociation(n_exc.Conflict):
message = _("BGP Speaker %(bgp_speaker_id)s is already associated to "
"peer with id %(peer_id)s.")
class InvalidBgpSpeakerSnatRouterAssociation(n_exc.Conflict):
message = _("BGP Speaker %(bgp_speaker_id)s associated to SNAT "
"enabled router with id %(router_id)s is not supported.")
class DependentAssociationExists(n_exc.Conflict):
message = _("One or more router or peer association is still in use for "
"bgp speaker with id %(bgp_speaker_id)s.")
class DependentPeerAssociationExists(n_exc.Conflict):
message = _("Peer association is still in use for bgp peer with id "
"%(bgp_peer_id)s.")

View File

@ -0,0 +1,53 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# 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.objects import base
from neutron_dynamic_routing.db import bgp_db
from neutron_lib.objects import common_types
from oslo_versionedobjects import fields as obj_fields
@base.NeutronObjectRegistry.register
class BgpSpeakerRouterAssociation(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
db_model = bgp_db.BgpSpeakerRouterAssociation
fields = {
'id': common_types.UUIDField(),
'bgp_speaker_id': common_types.UUIDField(),
'router_id': common_types.UUIDField(),
'advertise_extra_routes': obj_fields.BooleanField(),
'status': obj_fields.StringField(),
}
primary_keys = ['id']
@base.NeutronObjectRegistry.register
class BgpSpeakerPeerAssociation(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
db_model = bgp_db.BgpSpeakerPeerAssociation
fields = {
'id': common_types.UUIDField(),
'bgp_speaker_id': common_types.UUIDField(),
'peer_id': common_types.UUIDField(),
'status': obj_fields.StringField(),
}
primary_keys = ['id']

View File

@ -0,0 +1,22 @@
# 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_privsep import capabilities as caps
from oslo_privsep import priv_context
bgp_speaker_cmd = priv_context.PrivContext(
__name__,
cfg_section='privsep_bgpspeaker',
pypath=__name__ + '.bgp_speaker_cmd',
capabilities=[caps.CAP_NET_BIND_SERVICE,
caps.CAP_SYS_ADMIN]
)

View File

@ -0,0 +1,148 @@
# 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 ctypes
from ctypes import util as ctypes_util
from multiprocessing import Process
import socket
import eventlet
from os_ken.lib import hub
from os_ken.lib import rpc
from os_ken.services.protocols.bgp import bgpspeaker
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
from neutron_dynamic_routing._i18n import _LI
from neutron_dynamic_routing import privileged
from neutron_dynamic_routing.privileged import utils as bgp_utils
from neutron_dynamic_routing.services.bgp.agent.driver import utils
eventlet.monkey_patch()
libc = ctypes.PyDLL(ctypes_util.find_library('c'), use_errno=True)
_setns = libc.setns
CLONE_NEWNET = 0x40000000
LOG = logging.getLogger(__name__)
PROCESS_CACHE = bgp_utils.BgpSpeakerProcessCache()
VERSION_IPV6 = 6
RPC_PORT = 50002
RPC_HOST = '127.0.0.1'
def setns(fd, nstype):
if hasattr(fd, 'fileno'):
fd = fd.fileno()
_setns(fd, nstype)
def get_netns_path(nsname):
return '/var/run/netns/%s' % nsname
@privileged.bgp_speaker_cmd.entrypoint
def add_bgp_speaker(bgp_speaker_id, local_as, bgp_router_id, ns, ip_version):
with open(get_netns_path(ns)) as fd:
setns(fd, CLONE_NEWNET)
bgp_process = BgpProcess(ns, local_as, bgp_router_id, ip_version)
bgp_process.start()
PROCESS_CACHE.put_bgp_speaker_process(bgp_speaker_id, bgp_process)
@privileged.bgp_speaker_cmd.entrypoint
def del_bgp_speaker(bgp_speaker_id, ns):
with open(get_netns_path(ns)) as fd:
setns(fd, CLONE_NEWNET)
endpoint = socket.create_connection((RPC_HOST, RPC_PORT))
client = rpc.Client(endpoint)
client.call('core.stop', [])
process = PROCESS_CACHE.remove_bgp_speaker_process(bgp_speaker_id)
if process:
process.terminate()
@privileged.bgp_speaker_cmd.entrypoint
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:
setns(fd, CLONE_NEWNET)
bgp_neighbor = {
neighbors.IP_ADDRESS: peer_ip,
neighbors.REMOTE_AS: remote_as,
PASSWORD: password,
}
endpoint = socket.create_connection((RPC_HOST, RPC_PORT))
client = rpc.Client(endpoint)
client.call('neighbor.create', [bgp_neighbor])
@privileged.bgp_speaker_cmd.entrypoint
def del_bgp_neighbor(bgp_speaker_id, peer_ip, ns):
LOG.info(_LI('BGPAAS: Router namespace= %(ns)s.'), {'ns': ns})
with open(get_netns_path(ns)) as fd:
setns(fd, CLONE_NEWNET)
bgp_neighbor = {
neighbors.IP_ADDRESS: peer_ip,
}
endpoint = socket.create_connection((RPC_HOST, RPC_PORT))
client = rpc.Client(endpoint)
client.call('neighbor.delete', [bgp_neighbor])
class BgpProcess(Process):
def __init__(self, namespace, local_as, bgp_router_id, ip_version):
Process.__init__(self)
self._namespace = namespace
self._local_as = local_as
self._bgp_router_id = bgp_router_id
self._ip_version = ip_version
def run(self):
utils.validate_as_num('local_as', self._local_as)
server_host = ('0.0.0.0',)
if self._ip_version == VERSION_IPV6:
server_host = ('::',)
bgpspeaker.BGPSpeaker(
as_number=self._local_as,
router_id=self._bgp_router_id,
bgp_server_hosts=server_host,
best_path_change_handler=self.best_path_change_event,
peer_down_handler=self.bgp_peer_down_event,
peer_up_handler=self.bgp_peer_up_event)
hub.spawn(net_ctrl.NET_CONTROLLER.start,
**{net_ctrl.NC_RPC_BIND_IP: '0.0.0.0',
net_ctrl.NC_RPC_BIND_PORT: RPC_PORT}).wait()
def best_path_change_event(self, event):
LOG.info(_LI("Best path change observed. cidr=%(prefix)s, "
"nexthop=%(nexthop)s, remote_as=%(remote_as)d, "
"is_withdraw=%(is_withdraw)s, "
"namespace=%(namespace)s"),
{'prefix': event.prefix, 'nexthop': event.nexthop,
'remote_as': event.remote_as,
'is_withdraw': event.is_withdraw,
'namespace': self._namespace})
# Function for logging BGP peer.
def bgp_peer_down_event(self, remote_ip, remote_as):
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d went '
'DOWN. namespace=%(namespace)s'),
{'peer_ip': remote_ip, 'peer_as': remote_as,
'namespace': self._namespace})
def bgp_peer_up_event(self, remote_ip, remote_as):
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d is UP.'
'namespace=%(namespace)s'),
{'peer_ip': remote_ip, 'peer_as': remote_as,
'namespace': self._namespace})

View File

@ -0,0 +1,32 @@
# 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.
class BgpSpeakerProcessCache():
"""Class for saving multiple BGP speaker process information. This class
is currently used only for router-associated BGP speakers. In this case,
each BGP speaker associated with a router will be spawned in a seperate
process. This class holds bgp speaker id as the key and the process object
as the value.
"""
def __init__(self):
self.cache = {}
def put_bgp_speaker_process(self, speaker_id, process):
self.cache[speaker_id] = process
def get_bgp_speaker_process(self, speaker_id):
return self.cache.get(speaker_id)
def remove_bgp_speaker_process(self, speaker_id):
return self.cache.pop(speaker_id, None)

View File

@ -0,0 +1,146 @@
# Copyright 2021 OpenStack Foundation
# 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 neutron.api.rpc.callbacks.consumer import registry
from neutron.api.rpc.callbacks import events as rpc_events
from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc
from neutron_lib.agent import l3_extension
from neutron_lib import rpc as n_rpc
from oslo_log import log as logging
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_rpc_api
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
LOG = logging.getLogger(__name__)
BGP_ROUTER_ASSOC = assoc_objects.BgpSpeakerRouterAssociation.obj_name()
BGP_PEER_ASSOC = assoc_objects.BgpSpeakerPeerAssociation.obj_name()
class BgpAgentExtension(l3_extension.L3AgentExtension):
SUPPORTED_RESOURCE_TYPES = [BGP_ROUTER_ASSOC, BGP_PEER_ASSOC]
def initialize(self, connection, driver_type):
LOG.debug("Initializing BgpL3AgentExtension")
resources.register_resource_class(
assoc_objects.BgpSpeakerRouterAssociation)
resources.register_resource_class(
assoc_objects.BgpSpeakerPeerAssociation)
self._register_rpc_consumers()
self.rpc_plugin = bgp_rpc_api.BgpL3ExtPluginApi(
bgp_consts.BGP_PLUGIN)
LOG.debug("Initialized BgpaasAgentExtension")
def _register_rpc_consumers(self):
LOG.debug("Reg RPC consumers BgpaasAgentExtension")
registry.register(self._handle_router_notification, BGP_ROUTER_ASSOC)
registry.register(self._handle_peer_notification, BGP_PEER_ASSOC)
self._register_router_consumers()
self._register_peer_consumers()
LOG.debug("Registered RPC consumers BgpaasAgentExtension")
def _register_router_consumers(self):
self._connection = n_rpc.Connection()
endpoints = [resources_rpc.ResourcesPushRpcCallback()]
topic = resources_rpc.resource_type_versioned_topic(BGP_ROUTER_ASSOC)
self._connection.create_consumer(topic, endpoints, fanout=True)
self._connection.consume_in_threads()
def _register_peer_consumers(self):
self._connection_peer = n_rpc.Connection()
endpoints = [resources_rpc.ResourcesPushRpcCallback()]
topic_peer = resources_rpc.resource_type_versioned_topic(
BGP_PEER_ASSOC)
self._connection_peer.create_consumer(topic_peer, endpoints,
fanout=True)
self._connection_peer.consume_in_threads()
def consume_api(self, agent_api):
self.agent_api = agent_api
def _handle_router_notification(self, context, resource_type,
router_assocs, event_type):
LOG.debug('Received router associated push event %s for resource %s,'
'with router associations %s', event_type, resource_type,
router_assocs)
for assoc in router_assocs:
router_info = self.agent_api.get_router_info(assoc.router_id)
sp = self.rpc_plugin.get_bgp_speaker_info(context,
assoc.bgp_speaker_id)
if event_type == rpc_events.CREATED:
priv_driver.add_bgp_speaker(assoc.bgp_speaker_id,
sp['local_as'], '127.0.0.1',
router_info.ns_name,
sp['ip_version'])
elif event_type == rpc_events.DELETED:
priv_driver.del_bgp_speaker(assoc.bgp_speaker_id,
router_info.ns_name)
def _handle_peer_notification(self, context, resource_type,
peer_assocs, event_type):
LOG.debug('Received peer associated push event %s for resource %s,'
'with peer associations %s', event_type, resource_type,
peer_assocs)
for peer_assoc in peer_assocs:
sp = self.rpc_plugin.get_bgp_speaker_info(
context,
peer_assoc.bgp_speaker_id)
peer = self.rpc_plugin.get_bgp_peer_info(context,
peer_assoc.peer_id)
for router_assoc in sp['router_associations']:
router_id = router_assoc['router_id']
router_info = self.agent_api.get_router_info(router_id)
if event_type == rpc_events.CREATED:
priv_driver.add_bgp_neighbor(peer_assoc.bgp_speaker_id,
peer['peer_ip'],
peer['remote_as'],
router_info.ns_name)
elif event_type == rpc_events.DELETED:
priv_driver.del_bgp_neighbor(peer_assoc.bgp_speaker_id,
peer['peer_ip'],
router_info.ns_name)
def add_router(self, context, data):
"""Handle a router add event.
Called on router create.
:param context: RPC context.
:param data: Router data.
"""
pass
def update_router(self, context, data):
"""Handle a router update event.
Called on router update.
:param context: RPC context.
:param data: Router data.
"""
pass
def delete_router(self, context, data):
"""Handle a router delete event.
:param context: RPC context.
:param data: Router data.
"""
pass
def ha_state_change(self, context, data):
pass

View File

@ -0,0 +1,45 @@
# Copyright 2021 OpenStack Foundation
# 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 neutron_lib import rpc as n_rpc
import oslo_messaging
class BgpL3ExtPluginApi(object):
"""Agent side of the bgp l3 agent ext RPC API.
This class implements the client side of an rpc interface.
The server side of this interface can be found in
api.rpc.handlers.bgp_speaker_rpc.BgpSpeakerRpcCallback.
API version history:
1.0 - Initial version.
"""
def __init__(self, topic):
target = oslo_messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
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)

View File

@ -14,6 +14,9 @@
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
from neutron_lib.api.definitions import bgp_4byte_asn
from neutron_lib.api.definitions import bgp_associations
@ -35,6 +38,7 @@ 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.objects import bgp_associations as assoc_objects
from neutron_dynamic_routing.services.bgp.common import constants as bgp_consts
PLUGIN_NAME = bgp_ext.ALIAS + '_svc_plugin'
@ -57,6 +61,8 @@ class BgpPlugin(service_base.ServicePluginBase,
self._setup_rpc()
self._register_callbacks()
self.add_periodic_dragent_status_check()
self._register_resources()
self.push_api = resources_rpc.ResourcesPushRpcApi()
def get_plugin_type(self):
return bgp_ext.ALIAS
@ -104,6 +110,12 @@ class BgpPlugin(service_base.ServicePluginBase,
resources.PORT,
events.AFTER_UPDATE)
def _register_resources(self):
resources_registry.register_resource_class(
assoc_objects.BgpSpeakerRouterAssociation)
resources_registry.register_resource_class(
assoc_objects.BgpSpeakerPeerAssociation)
def get_bgp_speakers(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
@ -245,9 +257,16 @@ class BgpPlugin(service_base.ServicePluginBase,
def create_bgp_speaker_router_association(self, context, bgp_speaker_id,
router_association):
return super(BgpPlugin, self).create_bgp_speaker_router_association(
context, bgp_speaker_id,
router_association)
assoc_info = super(BgpPlugin, self)\
.create_bgp_speaker_router_association(context,
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)
return assoc_info
def update_bgp_speaker_router_association(self, context, assoc_id,
bgp_speaker_id, router_assoc):
@ -257,8 +276,13 @@ class BgpPlugin(service_base.ServicePluginBase,
def delete_bgp_speaker_router_association(self, context, id,
bgp_speaker_id):
super(BgpPlugin, self).delete_bgp_speaker_router_association(context,
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)
def get_bgp_speaker_peer_associations(
self, context, bgp_speaker_id,
@ -280,14 +304,24 @@ class BgpPlugin(service_base.ServicePluginBase,
def create_bgp_speaker_peer_association(self, context, bgp_speaker_id,
peer_association):
return super(BgpPlugin, self).create_bgp_speaker_peer_association(
context, bgp_speaker_id,
peer_association)
assoc_info = super(BgpPlugin, self)\
.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)
return assoc_info
def delete_bgp_speaker_peer_association(self, context, id,
bgp_speaker_id):
super(BgpPlugin, self).delete_bgp_speaker_peer_association(
context, 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)
def get_routes(self, context, bgp_speaker_id):
return super(BgpPlugin, self).get_routes(context,

View File

@ -133,6 +133,8 @@ class BgpEntityCreationMixin(object):
for arg in kwargs:
request['router'][arg] = kwargs[arg]
router = self.l3plugin.create_router(self.context, request)
if 'enable_snat' in kwargs and not kwargs['enable_snat']:
self.l3plugin.update_router(self.context, router['id'], request)
yield router
@contextlib.contextmanager
@ -145,7 +147,8 @@ class BgpEntityCreationMixin(object):
distributed=False,
ha=False,
ext_net_use_addr_scope=True,
tenant_net_use_addr_scope=True):
tenant_net_use_addr_scope=True,
enable_snat=True):
gw_ip_net = netaddr.IPNetwork(gw_prefix)
tenant_ip_net = netaddr.IPNetwork(tenant_prefix)
ext_pool_args = {'tenant_id': tenant_id,
@ -173,9 +176,11 @@ class BgpEntityCreationMixin(object):
cidr=tenant_prefix,
subnetpool_id=int_subnetpool_id,
ip_version=tenant_ip_net.version) as int_subnet:
ext_gw_info = {'network_id': gw_net_id}
ext_gw_info = {'network_id': gw_net_id,
'enable_snat': enable_snat}
with self.router(external_gateway_info=ext_gw_info,
distributed=distributed,
enable_snat=enable_snat,
ha=ha) as router:
router_id = router['id']
router_interface_info = {'subnet_id':
@ -512,6 +517,7 @@ class BgpTests(BgpEntityCreationMixin):
{'address_scope': scope_data})
with self.router_with_external_and_tenant_networks(
tenant_id=tenant_id,
enable_snat=False,
address_scope=scope) as res,\
self.bgp_speaker(scope_data['ip_version'], 1234) as speaker:
router, *_ = res
@ -543,6 +549,7 @@ class BgpTests(BgpEntityCreationMixin):
{'address_scope': scope_data})
with self.router_with_external_and_tenant_networks(
tenant_id=tenant_id,
enable_snat=False,
address_scope=scope) as res:
router, *_ = res
router_id = router['id']
@ -570,6 +577,7 @@ class BgpTests(BgpEntityCreationMixin):
{'address_scope': scope_data})
with self.router_with_external_and_tenant_networks(
tenant_id=tenant_id,
enable_snat=False,
address_scope=scope) as res:
router, *_ = res
router_id = router['id']

View File

@ -37,3 +37,5 @@ neutron.policies =
neutron-dynamic-routing = neutron_dynamic_routing.policies:list_rules
neutron.service_plugins =
bgp = neutron_dynamic_routing.services.bgp.bgp_plugin:BgpPlugin
neutron.agent.l3.extensions =
bgp-ext = neutron_dynamic_routing.services.bgp.agent.l3.bgp_extension:BgpAgentExtension