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:
parent
1e5d8c603f
commit
dcb382e1e8
|
@ -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,
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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']
|
|
@ -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]
|
||||
)
|
|
@ -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})
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue