Add bgp service plugin
Change-Id: I8095ab4636bdb608572b6ae9523c708cc58d3e51 Partially-implements: blueprint bgp-dynamic-routing Depends-On: Ief5df0845816c39ed761baf3c202eebc3b90a8a3
This commit is contained in:
parent
df420471c7
commit
82fa657f14
|
@ -155,6 +155,16 @@ function configure_qos {
|
|||
iniset /$Q_PLUGIN_CONF_FILE ml2 extension_drivers "$Q_ML2_PLUGIN_EXT_DRIVERS"
|
||||
}
|
||||
|
||||
function configure_bgp {
|
||||
setup_develop $DEST/neutron-dynamic-routing
|
||||
sudo install -d -o $STACK_USER $NEUTRON_CONF_DIR/policy.d
|
||||
cp -v $DEST/neutron-dynamic-routing/etc/neutron/policy.d/dynamic_routing.conf $NEUTRON_CONF_DIR/policy.d
|
||||
_neutron_service_plugin_class_add df-bgp
|
||||
# Since we are using a plugin outside neutron-dynamic-routing, we need to
|
||||
# specify api_extensions_path explicitly.
|
||||
iniset $NEUTRON_CONF DEFAULT api_extensions_path "$DEST/neutron-dynamic-routing/neutron_dynamic_routing/extensions"
|
||||
}
|
||||
|
||||
function init_neutron_sample_config {
|
||||
# NOTE: We must make sure that neutron config file exists before
|
||||
# going further with ovs setup
|
||||
|
@ -182,6 +192,10 @@ function configure_df_plugin {
|
|||
configure_qos
|
||||
fi
|
||||
|
||||
if [[ "$DR_MODE" == "df-bgp" ]]; then
|
||||
configure_bgp
|
||||
fi
|
||||
|
||||
# NOTE(gsagie) needed for tempest
|
||||
export NETWORK_API_EXTENSIONS=$(python -c \
|
||||
'from dragonflow.common import extensions ;\
|
||||
|
|
|
@ -19,6 +19,8 @@ METADATA_PROXY_SHARED_SECRET=${METADATA_PROXY_SHARED_SECRET:-"secret"}
|
|||
|
||||
# df-bgp
|
||||
DF_BGP_SERVICE=${DF_BGP_SERVICE:-"$NEUTRON_BIN_DIR/df-bgp-service"}
|
||||
# This can be overridden in the localrc file
|
||||
DR_MODE=${DR_MODE:-df-bgp}
|
||||
|
||||
DF_L2_RESPONDER=${DF_L2_RESPONDER:-'True'}
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ RESOURCE_FIP_UPDATE_OR_DELETE = 7
|
|||
RESOURCE_ROUTER_UPDATE_OR_DELETE = 8
|
||||
RESOURCE_QOS = 9
|
||||
RESOURCE_NEUTRON_LISTENER = 10
|
||||
RESOURCE_BGP_SPEAKER = 11
|
||||
RESOURCE_BGP_PEER = 12
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
@ -116,6 +118,10 @@ def _get_lock_id_by_resource_type(resource_type, *args, **kwargs):
|
|||
# The db model of lock is uuid of 36 chars, but the neutron listener
|
||||
# uses hostname as lock-id, so we need to truncate it.
|
||||
lock_id = args[0][1][:35]
|
||||
elif RESOURCE_BGP_SPEAKER == resource_type:
|
||||
lock_id = args[0][2]
|
||||
elif RESOURCE_BGP_PEER == resource_type:
|
||||
lock_id = args[0][2]
|
||||
else:
|
||||
raise df_exc.UnknownResourceException(resource_type=resource_type)
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron_dynamic_routing.db import bgp_db
|
||||
from neutron_dynamic_routing.extensions import bgp as bgp_ext
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.services import base as service_base
|
||||
from oslo_log import log as logging
|
||||
|
||||
from dragonflow.db.models import bgp
|
||||
from dragonflow.db.neutron import lockedobjects_db as lock_db
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def bgp_peer_from_neutron_bgp_peer(peer):
|
||||
return bgp.BGPPeer(id=peer.get('id'),
|
||||
topic=peer.get('tenant_id'),
|
||||
name=peer.get('name'),
|
||||
peer_ip=peer.get('peer_ip'),
|
||||
remote_as=int(peer.get('remote_as')),
|
||||
auth_type=peer.get('auth_type'),
|
||||
password=peer.get('password'))
|
||||
|
||||
|
||||
def bgp_speaker_from_neutron_bgp_speaker(speaker):
|
||||
return bgp.BGPSpeaker(id=speaker.get('id'),
|
||||
topic=speaker.get('tenant_id'),
|
||||
name=speaker.get('name'),
|
||||
local_as=int(speaker.get('local_as')),
|
||||
peers=speaker.get('peers', []),
|
||||
ip_version=speaker.get('ip_version'))
|
||||
|
||||
|
||||
class DFBgpPlugin(service_base.ServicePluginBase,
|
||||
bgp_db.BgpDbMixin):
|
||||
|
||||
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS]
|
||||
|
||||
def __init__(self):
|
||||
super(DFBgpPlugin, self).__init__()
|
||||
self._nb_api = None
|
||||
self._register_callbacks()
|
||||
|
||||
@property
|
||||
def nb_api(self):
|
||||
if self._nb_api is None:
|
||||
plugin = directory.get_plugin()
|
||||
mech_driver = plugin.mechanism_manager.mech_drivers['df'].obj
|
||||
self._nb_api = mech_driver.nb_api
|
||||
|
||||
return self._nb_api
|
||||
|
||||
def get_plugin_name(self):
|
||||
return bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
|
||||
|
||||
def get_plugin_type(self):
|
||||
return bgp_ext.BGP_EXT_ALIAS
|
||||
|
||||
def get_plugin_description(self):
|
||||
"""returns string description of the plugin."""
|
||||
return ("BGP dynamic routing service for announcement of next-hops "
|
||||
"for private networks and floating IP's host routes.")
|
||||
|
||||
def _register_callbacks(self):
|
||||
# TODO(xiaohhui): Add subscribers to router and floatingip changes.
|
||||
pass
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN)
|
||||
def create_bgp_speaker(self, context, bgp_speaker):
|
||||
bgp_speaker = super(DFBgpPlugin, self).create_bgp_speaker(context,
|
||||
bgp_speaker)
|
||||
self.nb_api.create(bgp_speaker_from_neutron_bgp_speaker(bgp_speaker),
|
||||
skip_send_event=True)
|
||||
return bgp_speaker
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
|
||||
bgp_speaker = super(DFBgpPlugin, self).update_bgp_speaker(
|
||||
context, bgp_speaker_id, bgp_speaker)
|
||||
self.nb_api.update(bgp_speaker_from_neutron_bgp_speaker(bgp_speaker),
|
||||
skip_send_event=True)
|
||||
return bgp_speaker
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def delete_bgp_speaker(self, context, bgp_speaker_id):
|
||||
super(DFBgpPlugin, self).delete_bgp_speaker(context, bgp_speaker_id)
|
||||
self.nb_api.delete(bgp.BGPSpeaker(id=bgp_speaker_id),
|
||||
skip_send_event=True)
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN)
|
||||
def create_bgp_peer(self, context, bgp_peer):
|
||||
bgp_peer = super(DFBgpPlugin, self).create_bgp_peer(context, bgp_peer)
|
||||
self.nb_api.create(bgp_peer_from_neutron_bgp_peer(bgp_peer),
|
||||
skip_send_event=True)
|
||||
return bgp_peer
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_PEER)
|
||||
def update_bgp_peer(self, context, bgp_peer_id, bgp_peer):
|
||||
bgp_peer = super(DFBgpPlugin, self).update_bgp_peer(context,
|
||||
bgp_peer_id,
|
||||
bgp_peer)
|
||||
self.nb_api.update(bgp_peer_from_neutron_bgp_peer(bgp_peer),
|
||||
skip_send_event=True)
|
||||
return bgp_peer
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_PEER)
|
||||
def delete_bgp_peer(self, context, bgp_peer_id):
|
||||
super(DFBgpPlugin, self).delete_bgp_peer(context, bgp_peer_id)
|
||||
self.nb_api.delete(bgp.BGPPeer(id=bgp_peer_id),
|
||||
skip_send_event=True)
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
||||
ret_value = super(DFBgpPlugin, self).add_bgp_peer(context,
|
||||
bgp_speaker_id,
|
||||
bgp_peer_info)
|
||||
tenant_id = context.tenant_id
|
||||
bgp_speaker = self.nb_api.get(bgp.BGPSpeaker(id=bgp_speaker_id,
|
||||
topic=tenant_id))
|
||||
bgp_speaker.peers.append(ret_value['bgp_peer_id'])
|
||||
self.nb_api.update(bgp_speaker, skip_send_event=True)
|
||||
return ret_value
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
||||
ret_value = super(DFBgpPlugin, self).remove_bgp_peer(context,
|
||||
bgp_speaker_id,
|
||||
bgp_peer_info)
|
||||
tenant_id = context.tenant_id
|
||||
bgp_speaker = self.nb_api.get(bgp.BGPSpeaker(id=bgp_speaker_id,
|
||||
topic=tenant_id))
|
||||
bgp_speaker.remove_peer(ret_value['bgp_peer_id'])
|
||||
self.nb_api.update(bgp_speaker, skip_send_event=True)
|
||||
return ret_value
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def add_gateway_network(self, context, bgp_speaker_id, network_info):
|
||||
ret_value = super(DFBgpPlugin, self).add_gateway_network(
|
||||
context, bgp_speaker_id, network_info)
|
||||
# TODO(xiaohhui): Calculate routes for bgp_speaker_id with network.
|
||||
return ret_value
|
||||
|
||||
@lock_db.wrap_db_lock(lock_db.RESOURCE_BGP_SPEAKER)
|
||||
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
|
||||
ret_value = super(DFBgpPlugin, self).remove_gateway_network(
|
||||
context, bgp_speaker_id, network_info)
|
||||
|
||||
# TODO(xiaohhui): Calculate routes for bgp_speaker_id without network.
|
||||
return ret_value
|
||||
|
||||
def get_advertised_routes(self, context, bgp_speaker_id):
|
||||
tenant_id = context.tenant_id
|
||||
bgp_speaker = self.nb_api.get(bgp.BGPSpeaker(id=bgp_speaker_id,
|
||||
topic=tenant_id))
|
||||
# Translate to the format that neutron will acccept.
|
||||
return {'advertised_routes': [{'destination': r.destination,
|
||||
'next_hop': r.nexthop}
|
||||
for r in bgp_speaker.routes]}
|
|
@ -598,3 +598,53 @@ class TestNeutronAPIandDB(test_base.DFTestBase):
|
|||
self.assertIsNotNone(lport)
|
||||
real_pairs = lport.get_allowed_address_pairs()
|
||||
self.assertItemsEqual(expected_pairs, real_pairs)
|
||||
|
||||
def test_create_delete_bgp_peer(self):
|
||||
bgp_peer = self.store(
|
||||
objects.BGPPeerTestObj(self.neutron, self.nb_api))
|
||||
bgp_peer.create()
|
||||
self.assertTrue(bgp_peer.exists())
|
||||
bgp_peer.close()
|
||||
self.assertFalse(bgp_peer.exists())
|
||||
|
||||
def test_create_delete_bgp_speaker(self):
|
||||
bgp_speaker = self.store(
|
||||
objects.BGPSpeakerTestObj(self.neutron, self.nb_api))
|
||||
bgp_speaker.create()
|
||||
self.assertTrue(bgp_speaker.exists())
|
||||
bgp_speaker.close()
|
||||
self.assertFalse(bgp_speaker.exists())
|
||||
|
||||
def test_add_remove_bgp_peer(self):
|
||||
bgp_peer = self.store(
|
||||
objects.BGPPeerTestObj(self.neutron, self.nb_api))
|
||||
bgp_speaker = self.store(
|
||||
objects.BGPSpeakerTestObj(self.neutron, self.nb_api))
|
||||
bgp_peer.create()
|
||||
bgp_speaker.create()
|
||||
bgp_speaker.add_peer(bgp_peer.peer_id)
|
||||
nb_bgp_speaker = bgp_speaker.get_nb_bgp_speaker()
|
||||
peers = [peer.id for peer in nb_bgp_speaker.peers]
|
||||
self.assertIn(bgp_peer.peer_id, peers)
|
||||
|
||||
bgp_speaker.remove_peer(bgp_peer.peer_id)
|
||||
nb_bgp_speaker = bgp_speaker.get_nb_bgp_speaker()
|
||||
peers = [peer.id for peer in nb_bgp_speaker.peers]
|
||||
self.assertNotIn(bgp_peer.peer_id, nb_bgp_speaker.peers)
|
||||
|
||||
@lockutils.synchronized('need-external-net')
|
||||
def test_add_remove_bgp_network(self):
|
||||
bgp_peer = self.store(
|
||||
objects.BGPPeerTestObj(self.neutron, self.nb_api))
|
||||
bgp_speaker = self.store(
|
||||
objects.BGPSpeakerTestObj(self.neutron, self.nb_api))
|
||||
bgp_peer.create()
|
||||
bgp_speaker.create()
|
||||
with self._prepare_ext_net() as external_network_id:
|
||||
bgp_speaker.add_network(external_network_id)
|
||||
# TODO(xiaohhui): Verify the routes has been added to
|
||||
# bgp speaker nb db data
|
||||
|
||||
bgp_speaker.remove_network(external_network_id)
|
||||
nb_bgp_speaker = bgp_speaker.get_nb_bgp_speaker()
|
||||
self.assertFalse(nb_bgp_speaker.routes)
|
||||
|
|
|
@ -17,6 +17,7 @@ from neutron.agent.common import utils as agent_utils
|
|||
from neutronclient.common import exceptions
|
||||
from oslo_log import log
|
||||
|
||||
from dragonflow.db.models import bgp
|
||||
from dragonflow.db.models import l2
|
||||
from dragonflow.db.models import l3
|
||||
from dragonflow.db.models import qos
|
||||
|
@ -527,3 +528,74 @@ class QosPolicyTestObj(object):
|
|||
if qospolicy:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class BGPPeerTestObj(object):
|
||||
def __init__(self, neutron, nb_api):
|
||||
self.peer_id = None
|
||||
self.neutron = neutron
|
||||
self.nb_api = nb_api
|
||||
self.closed = False
|
||||
|
||||
def create(self, bgp_peer={'name': "peer1",
|
||||
'peer_ip': "172.24.4.100",
|
||||
'remote_as': 4321,
|
||||
'auth_type': 'none'}):
|
||||
bgp_peer = self.neutron.create_bgp_peer({'bgp_peer': bgp_peer})
|
||||
self.peer_id = bgp_peer['bgp_peer']['id']
|
||||
return self.peer_id
|
||||
|
||||
def close(self):
|
||||
if self.closed or self.peer_id is None:
|
||||
return
|
||||
self.neutron.delete_bgp_peer(self.peer_id)
|
||||
self.closed = True
|
||||
|
||||
def exists(self):
|
||||
bgp_peer = self.nb_api.get(bgp.BGPPeer(id=self.peer_id))
|
||||
return bool(bgp_peer)
|
||||
|
||||
|
||||
class BGPSpeakerTestObj(object):
|
||||
def __init__(self, neutron, nb_api):
|
||||
self.speaker_id = None
|
||||
self.neutron = neutron
|
||||
self.nb_api = nb_api
|
||||
self.closed = False
|
||||
|
||||
def create(self, bgp_speaker={'name': 'speaker1',
|
||||
'local_as': 1234,
|
||||
'ip_version': 4}):
|
||||
bgp_speaker = self.neutron.create_bgp_speaker(
|
||||
{'bgp_speaker': bgp_speaker})
|
||||
self.speaker_id = bgp_speaker['bgp_speaker']['id']
|
||||
return self.speaker_id
|
||||
|
||||
def add_peer(self, peer_id):
|
||||
self.neutron.add_peer_to_bgp_speaker(self.speaker_id,
|
||||
{'bgp_peer_id': peer_id})
|
||||
|
||||
def remove_peer(self, peer_id):
|
||||
self.neutron.remove_peer_from_bgp_speaker(self.speaker_id,
|
||||
{'bgp_peer_id': peer_id})
|
||||
|
||||
def add_network(self, network_id):
|
||||
self.neutron.add_network_to_bgp_speaker(self.speaker_id,
|
||||
{'network_id': network_id})
|
||||
|
||||
def remove_network(self, network_id):
|
||||
self.neutron.remove_network_from_bgp_speaker(
|
||||
self.speaker_id, {'network_id': network_id})
|
||||
|
||||
def close(self):
|
||||
if self.closed or self.speaker_id is None:
|
||||
return
|
||||
self.neutron.delete_bgp_speaker(self.speaker_id)
|
||||
self.closed = True
|
||||
|
||||
def get_nb_bgp_speaker(self):
|
||||
return self.nb_api.get(bgp.BGPSpeaker(id=self.speaker_id))
|
||||
|
||||
def exists(self):
|
||||
bgp_speaker = self.nb_api.get(bgp.BGPSpeaker(id=self.speaker_id))
|
||||
return bool(bgp_speaker)
|
||||
|
|
|
@ -72,5 +72,6 @@ dragonflow.port_status_driver =
|
|||
redis_port_status_notifier_driver = dragonflow.db.pubsub_drivers.redis_port_status_notifier:RedisPortStatusNotifier
|
||||
neutron.service_plugins =
|
||||
df-l3 = dragonflow.neutron.services.l3_router_plugin:DFL3RouterPlugin
|
||||
df-bgp = dragonflow.neutron.services.bgp.bgp_plugin:DFBgpPlugin
|
||||
oslo.config.opts =
|
||||
dragonflow.conf = dragonflow.conf.opts:list_opts
|
||||
|
|
Loading…
Reference in New Issue