Add bgp service plugin

Change-Id: I8095ab4636bdb608572b6ae9523c708cc58d3e51
Partially-implements: blueprint bgp-dynamic-routing
Depends-On: Ief5df0845816c39ed761baf3c202eebc3b90a8a3
This commit is contained in:
Hong Hui Xiao 2017-04-01 11:53:39 +08:00
parent df420471c7
commit 82fa657f14
8 changed files with 314 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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