NSXv BGP support

This change implement's a new BGP plugin which allows BGP support in Openstack,
using NSXv service edges (ESG).
When a BGP speaker is associated with an external network, service edges which
accommodates tenant routers that have their GW port on this network would be
configured to enable BGP/Dynamic-routing.
The specific BGP configuration (e.g - localAS, neighbours) for the edge is
retrieved from the BGP speaker object and its peers.

This change also adds an extension to the BGP peer object, this
extension allows the cloud operator to associate a BGP peer with a specific
service edge that will serve as GW edge for the network, multiple GW
edges are supported by enabling ECMP on tenant service edges.

Co-Authored: yuyangbj <yangyu@vmware.com>
Change-Id: Ife69b97f3232bee378a48d91dc53bdc8837de7f5
This commit is contained in:
Roey Chen 2017-04-04 19:58:02 -07:00
parent a2e05bc255
commit 81f9380765
17 changed files with 1454 additions and 2 deletions

View File

@ -107,6 +107,19 @@ Add neutron-fwaas repo as an external repository and configure following flags i
driver = vmware_nsx.services.fwaas.nsx_v.edge_fwaas_driver.EdgeFwaasDriver
Neutron dynamic routing plugin (bgp)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Add neutron-dynamic-routing repo as an external repository and configure following flags in ``local.conf``::
[[local|localrc]]
enable_plugin neutron-dynamic-routing https://git.openstack.org/openstack/neutron-dynamic-routing
DR_MODE=dr_plugin
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
api_extensions_path = $DEST/neutron-dynamic-routing/neutron_dynamic_routing/extensions
NSXv3
-----

View File

@ -9,6 +9,7 @@ ${DIR}/tox_install_project.sh networking-sfc networking_sfc $*
${DIR}/tox_install_project.sh neutron-lbaas neutron_lbaas $*
${DIR}/tox_install_project.sh vmware-nsxlib vmware_nsxlib $*
${DIR}/tox_install_project.sh neutron-fwaas neutron_fwaas $*
${DIR}/tox_install_project.sh neutron-dynamic-routing neutron-dynamic-routing $*
CONSTRAINTS_FILE=$1
shift

View File

@ -1 +1 @@
7c4704ad37df
8699700cd95c

View File

@ -0,0 +1,51 @@
# Copyright 2017 VMware, 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.
"""nsxv_bgp_speaker_mapping
Revision ID: 8699700cd95c
Revises: 7c4704ad37df
Create Date: 2017-02-16 03:13:39.775670
"""
# revision identifiers, used by Alembic.
revision = '8699700cd95c'
down_revision = '7c4704ad37df'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'nsxv_bgp_speaker_bindings',
sa.Column('edge_id', sa.String(36), nullable=False),
sa.Column('bgp_speaker_id', sa.String(36), nullable=False),
sa.Column('bgp_identifier', sa.String(64), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['bgp_speaker_id'], ['bgp_speakers.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('edge_id'))
op.create_table(
'nsxv_bgp_peer_edge_bindings',
sa.Column('peer_id', sa.String(36), nullable=False),
sa.Column('edge_id', sa.String(36), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['peer_id'], ['bgp_peers.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('peer_id'))

View File

@ -894,3 +894,56 @@ def update_nsxv_port_ext_attributes(session, port_id,
except exc.NoResultFound:
return add_nsxv_port_ext_attributes(
session, port_id, vnic_type=vnic_type)
def add_nsxv_bgp_speaker_binding(session, edge_id, speaker_id,
bgp_identifier):
with session.begin(subtransactions=True):
binding = nsxv_models.NsxvBgpSpeakerBinding(
edge_id=edge_id,
bgp_speaker_id=speaker_id,
bgp_identifier=bgp_identifier)
session.add(binding)
return binding
def get_nsxv_bgp_speaker_binding(session, edge_id):
try:
binding = (session.query(nsxv_models.NsxvBgpSpeakerBinding).
filter_by(edge_id=edge_id).
one())
return binding
except exc.NoResultFound:
LOG.debug("No dynamic routing enabled on edge %s.", edge_id)
def get_nsxv_bgp_speaker_bindings(session, speaker_id):
try:
return (session.query(nsxv_models.NsxvBgpSpeakerBinding).
filter_by(bgp_speaker_id=speaker_id).all())
except exc.NoResultFound:
return []
def delete_nsxv_bgp_speaker_binding(session, edge_id):
binding = session.query(
nsxv_models.NsxvBgpSpeakerBinding).filter_by(edge_id=edge_id)
if binding:
binding.delete()
def add_nsxv_bgp_peer_edge_binding(session, peer_id, edge_id):
with session.begin(subtransactions=True):
binding = nsxv_models.NsxvBgpPeerEdgeBinding(edge_id=edge_id,
peer_id=peer_id)
session.add(binding)
return binding
def get_nsxv_bgp_peer_edge_binding(session, peer_id):
try:
binding = (session.query(nsxv_models.NsxvBgpPeerEdgeBinding).
filter_by(peer_id=peer_id).one())
return binding
except exc.NoResultFound:
pass

View File

@ -381,3 +381,28 @@ class NsxvPortExtAttributes(model_base.BASEV2, models.TimestampMixin):
models_v2.Port,
backref=orm.backref("nsx_port_attributes", lazy='joined',
uselist=False, cascade='delete'))
class NsxvBgpSpeakerBinding(model_base.BASEV2, models.TimestampMixin):
# Maps bgp_speaker_id to NSXv edge id
__tablename__ = 'nsxv_bgp_speaker_bindings'
edge_id = sa.Column(sa.String(36), primary_key=True)
bgp_speaker_id = sa.Column(sa.String(36),
sa.ForeignKey('bgp_speakers.id',
ondelete='CASCADE'),
nullable=False)
# A given BGP speaker sets the value of its BGP Identifier to an IP address
# that is assigned to that BGP speaker.
bgp_identifier = sa.Column(sa.String(64), nullable=False)
class NsxvBgpPeerEdgeBinding(model_base.BASEV2, models.TimestampMixin):
# Maps between bgp-peer and edges service gateway.
__tablename__ = 'nsxv_bgp_peer_edge_bindings'
peer_id = sa.Column(sa.String(36),
sa.ForeignKey('bgp_peers.id',
ondelete='CASCADE'),
primary_key=True,
nullable=False)
edge_id = sa.Column(sa.String(36), nullable=False)

View File

@ -0,0 +1,98 @@
# Copyright 2017 VMware, Inc. 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.
import re
from neutron_lib.api import extensions
from neutron_lib.api import validators
from neutron_lib import exceptions as nexception
from vmware_nsx._i18n import _
EDGE_SERVICE_GW = 'esg_id'
ESG_BGP_PEER_EXT_ALIAS = 'edge-service-gateway-bgp-peer'
def _validate_edge_service_gw_id(esg_id, valid_values=None):
if re.match(r'^edge-[1-9]+[0-9]*$', esg_id) is None:
msg = _("'%s' is not a valid edge service gateway id.") % esg_id
return msg
validators.add_validator('validate_edge_service_gw_id',
_validate_edge_service_gw_id)
RESOURCE_ATTRIBUTE_MAP = {
'bgp-peers': {
EDGE_SERVICE_GW: {
'allow_post': True,
'allow_put': False,
'default': None,
'validate': {'type:validate_edge_service_gw_id': None},
'enforce_policy': True,
'is_visible': True,
'required_by_policy': False
}
}
}
class BgpDisabledOnEsgPeer(nexception.InvalidInput):
message = _("To add this peer to BGP speaker you must first enable BGP on "
"the associated ESG - '%(esg_id)s'.")
class EsgRemoteASDoNotMatch(nexception.InvalidInput):
message = _("Specified remote AS is '%(remote_as)s', but ESG '%(esg_id)s' "
"is configured on AS %(esg_as)s.")
class ExternalSubnetHasGW(nexception.InvalidInput):
message = _("Subnet '%(subnet_id)s' on external network '%(network_id)s' "
"is configured with gateway IP, set to None before enabling "
"BGP on the network.")
class Edge_service_gateway_bgp_peer(extensions.ExtensionDescriptor):
"""Extension class to allow identifying of-peer with specificN SXv edge
service gateway.
"""
@classmethod
def get_name(cls):
return "Edge service gateway bgp peer"
@classmethod
def get_alias(cls):
return ESG_BGP_PEER_EXT_ALIAS
@classmethod
def get_description(cls):
return ("Adding a new (optional) attribute 'esg_id' to bgp-peer "
"resource, where esg_id is a valid NSXv Edge service gateway "
"id.")
@classmethod
def get_updated(cls):
return "2017-04-01T10:00:00-00:00"
def get_required_extensions(self):
return ["bgp"]
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -0,0 +1,200 @@
# Copyright 2017 VMware, 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 oslo_log import log as logging
from vmware_nsx.common import locking
LOG = logging.getLogger(__name__)
class EdgeDynamicRoutingDriver(object):
"""Edge driver API to implement the dynamic routing"""
def __init__(self):
# it will be initialized at subclass
self.vcns = None
def _get_routing_global_config(self, edge_id):
h, config = self.vcns.get_dynamic_routing_service(edge_id)
global_config = config if config else {}
global_config.setdefault('ipPrefixes', {'ipPrefixes': []})
curr_prefixes = [{'ipPrefix': prx}
for prx in global_config['ipPrefixes']['ipPrefixes']]
global_config['ipPrefixes'] = curr_prefixes
return {'routingGlobalConfig': global_config}
def _update_global_routing_config(self, edge_id, **kwargs):
global_config = self._get_routing_global_config(edge_id)
current_prefixes = global_config['routingGlobalConfig']['ipPrefixes']
global_config['routingGlobalConfig']['ecmp'] = True
if 'router_id' in kwargs:
global_config['routingGlobalConfig']['routerId'] = (
kwargs['router_id'])
current_prefixes[:] = [p for p in current_prefixes
if p['ipPrefix']['name'] not in
kwargs.get('prefixes_to_remove', [])]
# Avoid adding duplicate rules when shared router relocation
current_prefixes.extend([p for p in kwargs.get('prefixes_to_add', [])
if p not in current_prefixes])
self.vcns.update_dynamic_routing_service(edge_id, global_config)
def _reset_routing_global_config(self, edge_id):
global_config = self._get_routing_global_config(edge_id)
global_config['routingGlobalConfig']['ecmp'] = False
global_config['routingGlobalConfig'].pop('routerId', None)
global_config['routingGlobalConfig'].pop('ipPrefixes', None)
self.vcns.update_dynamic_routing_service(edge_id, global_config)
def get_routing_bgp_config(self, edge_id):
h, config = self.vcns.get_bgp_routing_config(edge_id)
bgp_config = config if config else {}
bgp_config.setdefault('enabled', False)
bgp_config.setdefault('bgpNeighbours', {'bgpNeighbours': []})
bgp_config.setdefault('redistribution', {'rules': {'rules': []}})
curr_neighbours = [{'bgpNeighbour': nbr} for nbr in
bgp_config['bgpNeighbours']['bgpNeighbours']]
bgp_config['bgpNeighbours'] = curr_neighbours
for nbr in curr_neighbours:
bgp_filters = [{'bgpFilter': bf} for bf
in nbr['bgpNeighbour']['bgpFilters']['bgpFilters']]
nbr['bgpNeighbour']['bgpFilters'] = bgp_filters
redistribution_rules = [{'rule': rule} for rule in
bgp_config['redistribution']['rules']['rules']]
bgp_config['redistribution']['rules'] = redistribution_rules
return {'bgp': bgp_config}
def _update_bgp_routing_config(self, edge_id, **kwargs):
bgp_config = self.get_routing_bgp_config(edge_id)
curr_neighbours = bgp_config['bgp']['bgpNeighbours']
curr_rules = bgp_config['bgp']['redistribution']['rules']
bgp_config['bgp']['enabled'] = True
if 'local_as' in kwargs:
bgp_config['bgp']['localAS'] = kwargs['local_as']
if 'enabled' in kwargs:
bgp_config['bgp']['redistribution']['enabled'] = kwargs['enabled']
curr_rules[:] = [rule for rule in curr_rules
if rule['rule'].get('prefixName') not in
kwargs.get('rules_to_remove', [])]
# Avoid adding duplicate rules when shared router relocation
curr_rules_prefixes = [r['rule'].get('prefixName') for r in curr_rules]
curr_rules.extend([r for r in kwargs.get('rules_to_add', [])
if r['rule'].get('prefixName') not in
curr_rules_prefixes])
neighbours_to_remove = [nbr['bgpNeighbour']['ipAddress'] for nbr in
kwargs.get('neighbours_to_remove', [])]
curr_neighbours[:] = [nbr for nbr in curr_neighbours
if nbr['bgpNeighbour']['ipAddress']
not in neighbours_to_remove]
curr_neighbours.extend(kwargs.get('neighbours_to_add', []))
self.vcns.update_bgp_dynamic_routing(edge_id, bgp_config)
def _get_edge_static_routes(self, edge_id):
h, routes = self.vcns.get_routes(edge_id)
static_routes = routes if routes else {}
static_routes.setdefault('staticRoutes', {'staticRoutes': []})
return static_routes
def _remove_default_static_routes(self, edge_id, droutes=None):
routes = self._get_edge_static_routes(edge_id)
# if droutes is empty or None, then remove all default static routes
droutes = droutes or routes['staticRoutes']['staticRoutes']
droutes = [r['nextHop'] for r in droutes]
routes['staticRoutes']['staticRoutes'] = [
r for r in routes['staticRoutes']['staticRoutes']
if r['network'] != '0.0.0.0/0' or r['nextHop'] not in droutes]
self.vcns.update_routes(edge_id, routes)
def _add_default_static_routes(self, edge_id, default_routes):
routes = self._get_edge_static_routes(edge_id)
routes['staticRoutes']['staticRoutes'].extend(default_routes)
self.vcns.update_routes(edge_id, routes)
def add_bgp_speaker_config(self, edge_id, prot_router_id, local_as,
enabled, default_routes, bgp_neighbours,
prefixes, redistribution_rules):
with locking.LockManager.get_lock(str(edge_id)):
self._update_global_routing_config(edge_id,
router_id=prot_router_id,
prefixes_to_add=prefixes)
self._update_bgp_routing_config(edge_id, enabled=enabled,
local_as=local_as,
neighbours_to_add=bgp_neighbours,
prefixes_to_add=prefixes,
rules_to_add=redistribution_rules)
self._add_default_static_routes(edge_id, default_routes)
def delete_bgp_speaker_config(self, edge_id):
with locking.LockManager.get_lock(str(edge_id)):
self.vcns.delete_bgp_routing_config(edge_id)
self._remove_default_static_routes(edge_id)
self._reset_routing_global_config(edge_id)
def add_bgp_neighbours(self, edge_id, bgp_neighbours,
default_routes=None):
# Query the bgp config first and update the bgpNeighbour
with locking.LockManager.get_lock(str(edge_id)):
self._update_bgp_routing_config(edge_id,
neighbours_to_add=bgp_neighbours)
if default_routes:
self._add_default_static_routes(edge_id, default_routes)
def remove_bgp_neighbours(self, edge_id, bgp_neighbours,
default_routes=None):
with locking.LockManager.get_lock(str(edge_id)):
self._update_bgp_routing_config(
edge_id, neighbours_to_remove=bgp_neighbours)
if default_routes:
self._remove_default_static_routes(edge_id, default_routes)
def update_bgp_neighbour(self, edge_id, bgp_neighbour):
with locking.LockManager.get_lock(str(edge_id)):
self._update_bgp_routing_config(
edge_id,
neighbours_to_remove=[bgp_neighbour],
neighbours_to_add=[bgp_neighbour])
def update_routing_redistribution(self, edge_id, enabled):
with locking.LockManager.get_lock(str(edge_id)):
self._update_bgp_routing_config(edge_id, enabled=enabled)
def add_bgp_redistribution_rules(self, edge_id, prefixes, rules):
with locking.LockManager.get_lock(str(edge_id)):
self._update_global_routing_config(edge_id,
prefixes_to_add=prefixes)
self._update_bgp_routing_config(edge_id, rules_to_add=rules)
LOG.debug("Added redistribution rules %s on edge %s", rules, edge_id)
def remove_bgp_redistribution_rules(self, edge_id, prefixes):
with locking.LockManager.get_lock(str(edge_id)):
self._update_bgp_routing_config(edge_id, rules_to_remove=prefixes)
self._update_global_routing_config(edge_id,
prefixes_to_remove=prefixes)
LOG.debug("Removed redistribution rules for prefixes %s on edge %s",
prefixes, edge_id)
def update_router_id(self, edge_id, router_id):
with locking.LockManager.get_lock(str(edge_id)):
self._update_global_routing_config(edge_id, router_id=router_id)

View File

@ -83,6 +83,10 @@ CERTIFICATE = "certificate"
NETWORK_TYPES = ['Network', 'VirtualWire', 'DistributedVirtualPortgroup']
# Dynamic routing constants
GLOBAL_ROUTING_CONFIG = "routing/config/global"
BGP_ROUTING_CONFIG = "routing/config/bgp"
def retry_upon_exception_exclude_error_codes(
exc, excluded_errors, delay=0.5, max_delay=4, max_attempts=0):
@ -1065,3 +1069,27 @@ class Vcns(object):
uri = '%s/scope/globalroot-0' % APPLICATION_PREFIX
h, apps = self.do_request(HTTP_GET, uri, decode=True)
return apps
def update_dynamic_routing_service(self, edge_id, request_config):
uri = self._build_uri_path(edge_id, GLOBAL_ROUTING_CONFIG)
return self.do_request(HTTP_PUT, uri,
VcnsApiClient.xmldumps(request_config),
format='xml')
def get_dynamic_routing_service(self, edge_id):
uri = self._build_uri_path(edge_id, GLOBAL_ROUTING_CONFIG)
return self.do_request(HTTP_GET, uri)
def update_bgp_dynamic_routing(self, edge_id, bgp_request):
uri = self._build_uri_path(edge_id, BGP_ROUTING_CONFIG)
return self.do_request(HTTP_PUT, uri,
VcnsApiClient.xmldumps(bgp_request),
format='xml')
def get_bgp_routing_config(self, edge_id):
uri = self._build_uri_path(edge_id, BGP_ROUTING_CONFIG)
return self.do_request(HTTP_GET, uri)
def delete_bgp_routing_config(self, edge_id):
uri = self._build_uri_path(edge_id, BGP_ROUTING_CONFIG)
return self.do_request(HTTP_DELETE, uri)

View File

@ -18,6 +18,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from vmware_nsx.plugins.nsx_v.vshield import edge_appliance_driver
from vmware_nsx.plugins.nsx_v.vshield import edge_dynamic_routing_driver
from vmware_nsx.plugins.nsx_v.vshield import edge_firewall_driver
from vmware_nsx.plugins.nsx_v.vshield.tasks import tasks
from vmware_nsx.plugins.nsx_v.vshield import vcns
@ -29,7 +30,8 @@ LOG = logging.getLogger(__name__)
class VcnsDriver(edge_appliance_driver.EdgeApplianceDriver,
lbaas_v2.EdgeLoadbalancerDriverV2,
edge_firewall_driver.EdgeFirewallDriver):
edge_firewall_driver.EdgeFirewallDriver,
edge_dynamic_routing_driver.EdgeDynamicRoutingDriver):
def __init__(self, callbacks):
super(VcnsDriver, self).__init__()

View File

@ -0,0 +1,256 @@
# Copyright 2017 VMware, Inc.
#
# 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 oslo_log import log as logging
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron_dynamic_routing.db import bgp_db
from neutron_dynamic_routing.extensions import bgp as bgp_ext
from neutron_lib import context as n_context
from neutron_lib.services import base as service_base
from vmware_nsx.common import locking
from vmware_nsx.common import nsxv_constants
from vmware_nsx.db import nsxv_db
from vmware_nsx.extensions import edge_service_gateway_bgp_peer as ext_esg
from vmware_nsx.services.dynamic_routing.nsx_v import driver as nsxv_driver
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_nsx_svc_plugin'
LOG = logging.getLogger(__name__)
class NSXvBgpPlugin(service_base.ServicePluginBase, bgp_db.BgpDbMixin):
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS,
ext_esg.ESG_BGP_PEER_EXT_ALIAS]
def __init__(self):
super(NSXvBgpPlugin, self).__init__()
self.nsxv_driver = nsxv_driver.NSXvBgpDriver(self)
self._register_callbacks()
def get_plugin_name(self):
return PLUGIN_NAME
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 project networks, floating IP's, and DVR host routes.")
def _register_callbacks(self):
registry.subscribe(self.router_interface_callback,
resources.ROUTER_INTERFACE,
events.AFTER_CREATE)
registry.subscribe(self.router_interface_callback,
resources.ROUTER_INTERFACE,
events.AFTER_DELETE)
registry.subscribe(self.router_gateway_callback,
resources.ROUTER_GATEWAY,
events.AFTER_CREATE)
registry.subscribe(self.router_gateway_callback,
resources.ROUTER_GATEWAY,
events.AFTER_DELETE)
registry.subscribe(self._after_service_edge_create_callback,
nsxv_constants.SERVICE_EDGE,
events.AFTER_CREATE)
registry.subscribe(self._before_service_edge_delete_callback,
nsxv_constants.SERVICE_EDGE,
events.BEFORE_DELETE)
def create_bgp_speaker(self, context, bgp_speaker):
self.nsxv_driver.create_bgp_speaker(context, bgp_speaker)
return super(NSXvBgpPlugin, self).create_bgp_speaker(context,
bgp_speaker)
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
self.nsxv_driver.update_bgp_speaker(context, bgp_speaker_id,
bgp_speaker)
# TBD(roeyc): rolling back changes on edges base class call failed.
return super(NSXvBgpPlugin, self).update_bgp_speaker(
context, bgp_speaker_id, bgp_speaker)
def delete_bgp_speaker(self, context, bgp_speaker_id):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
self.nsxv_driver.delete_bgp_speaker(context, bgp_speaker_id)
super(NSXvBgpPlugin, self).delete_bgp_speaker(context,
bgp_speaker_id)
def _get_esg_peer_info(self, context, bgp_peer_id):
binding = nsxv_db.get_nsxv_bgp_peer_edge_binding(context.session,
bgp_peer_id)
if binding:
return binding['edge_id']
def get_bgp_peer(self, context, bgp_peer_id, fields=None):
peer = super(NSXvBgpPlugin, self).get_bgp_peer(context,
bgp_peer_id, fields)
if fields is None or 'esg_id' in fields:
peer['esg_id'] = self._get_esg_peer_info(context, bgp_peer_id)
return peer
def get_bgp_peers_by_bgp_speaker(self, context,
bgp_speaker_id, fields=None):
ret = super(NSXvBgpPlugin, self).get_bgp_peers_by_bgp_speaker(
context, bgp_speaker_id, fields=fields)
if fields is None or 'esg_id' in fields:
for peer in ret:
peer['esg_id'] = self._get_esg_peer_info(context, peer['id'])
return ret
def create_bgp_peer(self, context, bgp_peer):
self.nsxv_driver.create_bgp_peer(context, bgp_peer)
peer = super(NSXvBgpPlugin, self).create_bgp_peer(context, bgp_peer)
esg_id = bgp_peer['bgp_peer'].get('esg_id')
if esg_id:
nsxv_db.add_nsxv_bgp_peer_edge_binding(context.session, peer['id'],
esg_id)
peer['esg_id'] = esg_id
return peer
def update_bgp_peer(self, context, bgp_peer_id, bgp_peer):
super(NSXvBgpPlugin, self).update_bgp_peer(context,
bgp_peer_id, bgp_peer)
self.nsxv_driver.update_bgp_peer(context, bgp_peer_id, bgp_peer)
return self.get_bgp_peer(context, bgp_peer_id)
def delete_bgp_peer(self, context, bgp_peer_id):
bgp_peer_info = {'bgp_peer_id': bgp_peer_id}
bgp_speaker_ids = self.nsxv_driver._get_bgp_speakers_by_bgp_peer(
context, bgp_peer_id)
for speaker_id in bgp_speaker_ids:
self.remove_bgp_peer(context, speaker_id, bgp_peer_info)
super(NSXvBgpPlugin, self).delete_bgp_peer(context, bgp_peer_id)
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
self.nsxv_driver.add_bgp_peer(context,
bgp_speaker_id, bgp_peer_info)
return super(NSXvBgpPlugin, self).add_bgp_peer(context,
bgp_speaker_id,
bgp_peer_info)
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
speaker = self._get_bgp_speaker(context, bgp_speaker_id)
if bgp_peer_info['bgp_peer_id'] not in speaker['peers']:
return
self.nsxv_driver.remove_bgp_peer(context,
bgp_speaker_id, bgp_peer_info)
return super(NSXvBgpPlugin, self).remove_bgp_peer(
context, bgp_speaker_id, bgp_peer_info)
def add_gateway_network(self, context, bgp_speaker_id, network_info):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
self.nsxv_driver.add_gateway_network(context,
bgp_speaker_id,
network_info)
return super(NSXvBgpPlugin, self).add_gateway_network(
context, bgp_speaker_id, network_info)
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
with locking.LockManager.get_lock(str(bgp_speaker_id)):
super(NSXvBgpPlugin, self).remove_gateway_network(
context, bgp_speaker_id, network_info)
self.nsxv_driver.remove_gateway_network(context,
bgp_speaker_id,
network_info)
def get_advertised_routes(self, context, bgp_speaker_id):
return super(NSXvBgpPlugin, self).get_advertised_routes(
context, bgp_speaker_id)
def router_interface_callback(self, resource, event, trigger, **kwargs):
if not kwargs['network_id']:
# No GW network, hence no BGP speaker associated
return
context = kwargs['context']
router_id = kwargs['router_id']
subnets = kwargs.get('subnets')
network_id = kwargs['network_id']
port = kwargs['port']
speakers = self._bgp_speakers_for_gateway_network(context,
network_id)
for speaker in speakers:
speaker_id = speaker.id
with locking.LockManager.get_lock(str(speaker_id)):
speaker = self.get_bgp_speaker(context, speaker_id)
if network_id not in speaker['networks']:
continue
if event == events.AFTER_CREATE:
self.nsxv_driver.advertise_subnet(context, speaker_id,
router_id, subnets[0])
if event == events.AFTER_DELETE:
subnet_id = port['fixed_ips'][0]['subnet_id']
self.nsxv_driver.withdraw_subnet(context, speaker_id,
router_id, subnet_id)
def router_gateway_callback(self, resource, event, trigger, **kwargs):
context = kwargs.get('context') or n_context.get_admin_context()
router_id = kwargs['router_id']
network_id = kwargs['network_id']
speakers = self._bgp_speakers_for_gateway_network(context, network_id)
for speaker in speakers:
speaker_id = speaker.id
with locking.LockManager.get_lock(str(speaker_id)):
speaker = self.get_bgp_speaker(context, speaker_id)
if network_id not in speaker['networks']:
continue
if event == events.AFTER_DELETE:
gw_ips = kwargs['gateway_ips']
self.nsxv_driver.disable_bgp_on_router(context,
speaker,
router_id,
gw_ips[0])
def _before_service_edge_delete_callback(self, resource, event,
trigger, **kwargs):
context = kwargs['context']
router = kwargs['router']
ext_net_id = router.gw_port and router.gw_port['network_id']
gw_ip = router.gw_port and router.gw_port['fixed_ips'][0]['ip_address']
edge_id = kwargs.get('edge_id')
speakers = self._bgp_speakers_for_gateway_network(context, ext_net_id)
for speaker in speakers:
with locking.LockManager.get_lock(speaker.id):
speaker = self.get_bgp_speaker(context, speaker.id)
if ext_net_id not in speaker['networks']:
continue
self.nsxv_driver.disable_bgp_on_router(context, speaker,
router['id'],
gw_ip, edge_id)
def _after_service_edge_create_callback(self, resource, event,
trigger, **kwargs):
context = kwargs['context']
router = kwargs['router']
ext_net_id = router.gw_port and router.gw_port['network_id']
speakers = self._bgp_speakers_for_gateway_network(context, ext_net_id)
for speaker in speakers:
with locking.LockManager.get_lock(speaker.id):
speaker = self.get_bgp_speaker(context, speaker.id)
if ext_net_id not in speaker['networks']:
continue
self.nsxv_driver.enable_bgp_on_router(context, speaker,
router['id'])

View File

@ -0,0 +1,541 @@
# Copyright 2017 VMware, Inc.
#
# 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.
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from neutron_lib import constants as n_const
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory
from vmware_nsx._i18n import _
from vmware_nsx.common import locking
from vmware_nsx.common import nsxv_constants
from vmware_nsx.db import nsxv_db
from vmware_nsx.extensions import edge_service_gateway_bgp_peer as ext_esg_peer
from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc
LOG = logging.getLogger(__name__)
def ip_prefix(name, ip_address):
return {'ipPrefix': {'name': name, 'ipAddress': ip_address}}
def redistribution_rule(advertise_static_routes, prefix_name, action='permit'):
rule = {
'prefixName': prefix_name,
'action': action,
'from': {
'ospf': False,
'bgp': False,
'connected': not advertise_static_routes,
'static': advertise_static_routes
}
}
return {'rule': rule}
def bgp_neighbour(bgp_peer):
bgp_filter = {'bgpFilter': [{'direction': 'out', 'action': 'permit'}]}
nbr = {
'ipAddress': bgp_peer['peer_ip'],
'remoteAS': bgp_peer['remote_as'],
'bgpFilters': bgp_filter,
'password': bgp_peer['password']
}
return {'bgpNeighbour': nbr}
def gw_bgp_neighbour(ip_address, remote_as, password):
bgp_filter = {'bgpFilter': [{'direction': 'in', 'action': 'permit'}]}
nbr = {
'ipAddress': ip_address,
'remoteAS': remote_as,
'bgpFilters': bgp_filter,
'password': password
}
return {'bgpNeighbour': nbr}
def default_route(nexthop):
return {'network': '0.0.0.0/0',
'nextHop': nexthop}
class NSXvBgpDriver(object):
"""Class driver to address the neutron_dynamic_routing API"""
def __init__(self, plugin):
super(NSXvBgpDriver, self).__init__()
self._edge_password = cfg.CONF.nsxv.edge_appliance_password
self._plugin = plugin
self._core_plugin = directory.get_plugin()
self._nsxv = self._core_plugin.nsx_v
self._edge_manager = self._core_plugin.edge_manager
def prefix_name(self, subnet_id):
return 'subnet-%s' % subnet_id
def _get_router_edge_info(self, context, router_id):
edge_binding = nsxv_db.get_nsxv_router_binding(context.session,
router_id)
if not edge_binding:
return None, None
# Idicates which routes should be advertised - connected or static.
advertise_static_routes = False
if edge_binding['edge_type'] != nsxv_constants.SERVICE_EDGE:
# Distributed router
plr_id = self._edge_manager.get_plr_by_tlr_id(context, router_id)
edge_binding = nsxv_db.get_nsxv_router_binding(context.session,
plr_id)
if not edge_binding:
# Distributed router isn't bound to plr
return None, None
# PLR for distributed router, advertise static routes.
advertise_static_routes = True
return edge_binding['edge_id'], advertise_static_routes
def _get_dynamic_routing_edge_list(self, context, gateway_network_id):
# Filter the routers attached this network as gateway interface
filters = {'network_id': [gateway_network_id],
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}
fields = ['device_id', 'fixed_ips']
gateway_ports = self._core_plugin.get_ports(context, filters=filters,
fields=fields)
edge_router_dict = {}
for port in gateway_ports:
router_id = port['device_id']
router = self._core_plugin._get_router(context, router_id)
bgp_identifier = port['fixed_ips'][0]['ip_address']
edge_id, advertise_static_routes = (
self._get_router_edge_info(context, router_id))
if not edge_id:
# Shared router is not attached on any edge
continue
if edge_id not in edge_router_dict:
edge_router_dict[edge_id] = {'no_snat_routers': [],
'bgp_identifier':
bgp_identifier,
'advertise_static_routes':
advertise_static_routes}
if not router.enable_snat:
edge_router_dict[edge_id]['no_snat_routers'].append(router_id)
return edge_router_dict
def _query_tenant_subnets(self, context, router_ids):
# Query subnets attached to all of routers attached to same edge
subnets = []
for router_id in router_ids:
filters = {'device_id': [router_id],
'device_owner': [n_const.DEVICE_OWNER_ROUTER_INTF]}
int_ports = self._core_plugin.get_ports(context,
filters=filters,
fields=['fixed_ips'])
for p in int_ports:
subnet_id = p['fixed_ips'][0]['subnet_id']
subnet = self._core_plugin.get_subnet(context, subnet_id)
subnets.append({'id': subnet_id,
'cidr': subnet['cidr']})
LOG.debug("Got related subnets %s", subnets)
return subnets
def _get_bgp_speakers_by_bgp_peer(self, context, bgp_peer_id):
fields = ['id', 'peers']
bgp_speakers = self._plugin.get_bgp_speakers(context, fields=fields)
bgp_speaker_ids = [bgp_speaker['id'] for bgp_speaker in bgp_speakers
if bgp_peer_id in bgp_speaker['peers']]
return bgp_speaker_ids
def _get_prefixes_and_redistribution_rules(self, subnets,
advertise_static_routes):
prefixes = []
redis_rules = []
for subnet in subnets:
prefix_name = self.prefix_name(subnet['id'])
prefix = ip_prefix(prefix_name, subnet['cidr'])
prefixes.append(prefix)
rule = redistribution_rule(advertise_static_routes, prefix_name)
redis_rules.append(rule)
return prefixes, redis_rules
def create_bgp_speaker(self, context, bgp_speaker):
bgp_speaker_data = bgp_speaker['bgp_speaker']
ip_version = bgp_speaker_data.get('ip_version')
if ip_version and ip_version == 6:
err_msg = _("NSXv BGP does not support for IPv6")
raise n_exc.InvalidInput(error_message=err_msg)
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
bgp_obj = bgp_speaker['bgp_speaker']
old_speaker_info = self._plugin.get_bgp_speaker(context,
bgp_speaker_id)
enabled_state = old_speaker_info['advertise_tenant_networks']
new_enabled_state = bgp_obj.get('advertise_tenant_networks',
enabled_state)
if new_enabled_state == enabled_state:
return
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
context.session, bgp_speaker_id)
edge_ids = [bgp_binding['edge_id'] for bgp_binding in bgp_bindings]
action = 'Enabling' if new_enabled_state else 'Disabling'
LOG.info("%s BGP route redistribution on edges: %s.", action, edge_ids)
for edge_id in edge_ids:
try:
self._nsxv.update_routing_redistribution(edge_id,
new_enabled_state)
except vcns_exc.VcnsApiException:
LOG.warning("Failed to update BGP on edge %s.", edge_id)
def delete_bgp_speaker(self, context, bgp_speaker_id):
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
context.session, bgp_speaker_id)
self._stop_bgp_on_edges(context, bgp_bindings, bgp_speaker_id)
def _validate_bgp_configuration_on_peer_esg(self, bgp_peer):
if not bgp_peer.get('esg_id'):
return
# TBD(roeyc): Validate peer_ip is on subnet
bgp_config = self._nsxv.get_routing_bgp_config(bgp_peer['esg_id'])
remote_as = bgp_peer['remote_as']
esg_id = bgp_peer['esg_id']
esg_as = bgp_config['bgp'].get('localAS')
if not bgp_config['bgp']['enabled']:
raise ext_esg_peer.BgpDisabledOnEsgPeer(esg_id=esg_id)
if esg_as != int(remote_as):
raise ext_esg_peer.EsgRemoteASDoNotMatch(remote_as=remote_as,
esg_id=esg_id,
esg_as=esg_as)
def create_bgp_peer(self, context, bgp_peer):
bgp_peer = bgp_peer['bgp_peer']
remote_ip = bgp_peer['peer_ip']
if not netaddr.valid_ipv4(remote_ip):
err_msg = _("NSXv BGP does not support for IPv6")
raise n_exc.InvalidInput(error_message=err_msg)
self._validate_bgp_configuration_on_peer_esg(bgp_peer)
def update_bgp_peer(self, context, bgp_peer_id, bgp_peer):
password = bgp_peer['bgp_peer'].get('password')
old_bgp_peer = self._plugin.get_bgp_peer(context, bgp_peer_id)
# Only password update is relevant for backend.
if old_bgp_peer['password'] == password:
return
bgp_speaker_ids = self._get_bgp_speakers_by_bgp_peer(context,
bgp_peer_id)
# Update the password for the old bgp peer and update NSX
old_bgp_peer['password'] = password
neighbour = bgp_neighbour(old_bgp_peer)
for bgp_speaker_id in bgp_speaker_ids:
with locking.LockManager.get_lock(bgp_speaker_id):
speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id)
if bgp_peer_id not in speaker['peers']:
continue
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
context.session, bgp_speaker_id)
for binding in bgp_bindings:
try:
self._nsxv.update_bgp_neighbour(binding['edge_id'],
neighbour)
except vcns_exc.VcnsApiException:
LOG.error("Failed to update BGP neighbor '%s' on "
"edge '%s'", old_bgp_peer['peer_ip'],
binding['edge_id'])
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
bgp_peer_obj = self._plugin.get_bgp_peer(context,
bgp_peer_info['bgp_peer_id'])
nbr = bgp_neighbour(bgp_peer_obj)
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(context.session,
bgp_speaker_id)
speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id)
# list of tenant edge routers to be removed as bgp-neighbours to this
# peer if it's associated with specific ESG.
neighbours = []
droute = default_route(nbr['bgpNeighbour']['ipAddress'])
for binding in bgp_bindings:
try:
self._nsxv.add_bgp_neighbours(binding['edge_id'], [nbr],
default_routes=[droute])
except vcns_exc.VcnsApiException:
LOG.error("Failed to add BGP neighbour on '%s'",
binding['edge_id'])
else:
nbr = gw_bgp_neighbour(binding['bgp_identifier'],
speaker['local_as'],
self._edge_password)
neighbours.append(nbr)
LOG.debug("Succesfully added BGP neighbor '%s' on '%s'",
bgp_peer_obj['peer_ip'], binding['edge_id'])
if bgp_peer_obj.get('esg_id'):
edge_gw = bgp_peer_obj['esg_id']
try:
self._nsxv.add_bgp_neighbours(edge_gw, neighbours)
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.error("Failed to add BGP neighbour on GW Edge '%s'",
edge_gw)
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
bgp_peer_id = bgp_peer_info['bgp_peer_id']
bgp_peer_obj = self._plugin.get_bgp_peer(context, bgp_peer_id)
nbr = bgp_neighbour(bgp_peer_obj)
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
context.session, bgp_speaker_id)
speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id)
# list of tenant edge routers to be removed as bgp-neighbours to this
# peer if it's associated with specific ESG.
neighbours = []
droute = default_route(nbr['bgpNeighbour']['ipAddress'])
for binding in bgp_bindings:
try:
self._nsxv.remove_bgp_neighbours(binding['edge_id'], [nbr],
default_routes=[droute])
except vcns_exc.VcnsApiException:
LOG.error("Failed to remove BGP neighbour on '%s'",
binding['edge_id'])
else:
nbr = gw_bgp_neighbour(binding['bgp_identifier'],
speaker['local_as'],
self._edge_password)
neighbours.append(nbr)
LOG.debug("Succesfully removed BGP neighbor '%s' on '%s'",
bgp_peer_obj['peer_ip'], binding['edge_id'])
if bgp_peer_obj.get('esg_id'):
edge_gw = bgp_peer_obj['esg_id']
try:
self._nsxv.remove_bgp_neighbours(edge_gw, neighbours)
except vcns_exc.VcnsApiException:
LOG.error("Failed to remove BGP neighbour on GW Edge '%s'",
edge_gw)
def add_gateway_network(self, context, bgp_speaker_id, network_info):
gateway_network_id = network_info['network_id']
ext_net = self._core_plugin.get_network(context, gateway_network_id)
if not ext_net['subnets']:
return
subnet_id = ext_net['subnets'][0]
ext_subnet = self._core_plugin.get_subnet(context, subnet_id)
if ext_subnet.get('gateway_ip'):
raise ext_esg_peer.ExternalSubnetHasGW(
network_id=gateway_network_id, subnet_id=subnet_id)
edge_router_dict = self._get_dynamic_routing_edge_list(
context, gateway_network_id)
speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id)
bgp_peers = self._plugin.get_bgp_peers_by_bgp_speaker(
context, bgp_speaker_id)
neighbours = []
for edge_id, edge_router_config in edge_router_dict.items():
router_ids = edge_router_config['no_snat_routers']
advertise_static_routes = (
edge_router_config['advertise_static_routes'])
subnets = self._query_tenant_subnets(context, router_ids)
# router_id here is in IP address format and is required for
# the BGP configuration.
bgp_identifier = edge_router_config['bgp_identifier']
try:
self._start_bgp_on_edge(context, edge_id, speaker,
bgp_peers, bgp_identifier, subnets,
advertise_static_routes)
except vcns_exc.VcnsApiException:
LOG.error("Failed to configure BGP speaker %s on edge '%s'.",
bgp_speaker_id)
else:
nbr = gw_bgp_neighbour(bgp_identifier, speaker['local_as'],
self._edge_password)
neighbours.append(nbr)
for edge_gw in [peer['esg_id'] for peer in bgp_peers
if peer.get('esg_id')]:
try:
self._nsxv.add_bgp_neighbours(edge_gw, neighbours)
except vcns_exc.VcnsApiException:
LOG.error("Failed to add BGP neighbour on GW Edge '%s'",
edge_gw)
def _start_bgp_on_edge(self, context, edge_id, speaker, bgp_peers,
bgp_identifier, subnets, advertise_static_routes):
enabled_state = speaker['advertise_tenant_networks']
local_as = speaker['local_as']
prefixes, redis_rules = self._get_prefixes_and_redistribution_rules(
subnets, advertise_static_routes)
bgp_neighbours = [bgp_neighbour(bgp_peer) for bgp_peer in bgp_peers]
default_routes = [default_route(peer['peer_ip']) for peer in bgp_peers]
try:
self._nsxv.add_bgp_speaker_config(edge_id, bgp_identifier,
local_as, enabled_state,
default_routes,
bgp_neighbours, prefixes,
redis_rules)
except vcns_exc.VcnsApiException:
with excutils.save_and_reraise_exception():
LOG.error("Failed to configure BGP speaker %s on edge '%s'.",
speaker['id'], edge_id)
else:
nsxv_db.add_nsxv_bgp_speaker_binding(context.session, edge_id,
speaker['id'], bgp_identifier)
def _stop_bgp_on_edges(self, context, bgp_bindings, speaker_id):
neighbours_to_remove = []
speaker = self._plugin.get_bgp_speaker(context, speaker_id)
for bgp_binding in bgp_bindings:
edge_id = bgp_binding['edge_id']
try:
self._nsxv.delete_bgp_speaker_config(edge_id)
except vcns_exc.VcnsApiException:
LOG.error("Failed to delete BGP speaker %s config on edge "
"%s.", speaker_id, edge_id)
else:
nsxv_db.delete_nsxv_bgp_speaker_binding(context.session,
edge_id)
nbr = gw_bgp_neighbour(bgp_binding['bgp_identifier'],
speaker['local_as'],
self._edge_password)
neighbours_to_remove.append(nbr)
# We should also remove all bgp neighbours on gw-edges which
# corresponds with tenant routers that are associated with this bgp
# speaker.
bgp_peers = self._plugin.get_bgp_peers_by_bgp_speaker(context,
speaker_id)
gw_edges = [peer['esg_id'] for peer in bgp_peers if peer.get('esg_id')]
for gw_edge in gw_edges:
try:
self._nsxv.remove_bgp_neighbours(gw_edge, neighbours_to_remove)
except vcns_exc.VcnsApiException:
LOG.error("Failed to remove BGP neighbour on GW edge '%s'.",
gw_edge)
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
context.session, bgp_speaker_id)
self._stop_bgp_on_edges(context, bgp_bindings, bgp_speaker_id)
def enable_bgp_on_router(self, context, speaker, router_id):
edge_id, advertise_static_routes = (
self._get_router_edge_info(context, router_id))
if not edge_id:
# shared router is not attached on any edge
return
router = self._core_plugin._get_router(context, router_id)
if router.enable_snat:
subnets = []
else:
subnets = self._query_tenant_subnets(context, [router_id])
bgp_peers = self._plugin.get_bgp_peers_by_bgp_speaker(
context, speaker['id'])
bgp_binding = nsxv_db.get_nsxv_bgp_speaker_binding(
context.session, edge_id)
if bgp_binding and subnets:
# Edge already configured with BGP (e.g - shared router edge),
# Add the router attached subnets.
prefixes, redis_rules = (
self._get_prefixes_and_redistribution_rules(
subnets, advertise_static_routes))
self._nsxv.add_bgp_redistribution_rules(edge_id, prefixes,
redis_rules)
elif not bgp_binding:
gw_port = router.gw_port['fixed_ips'][0]
bgp_identifier = gw_port['ip_address']
self._start_bgp_on_edge(context, edge_id, speaker, bgp_peers,
bgp_identifier, subnets,
advertise_static_routes)
nbr = gw_bgp_neighbour(bgp_identifier, speaker['local_as'],
self._edge_password)
for gw_edge_id in [peer['esg_id'] for peer in bgp_peers
if peer['esg_id']]:
self._nsxv.add_bgp_neighbours(gw_edge_id, [nbr])
def disable_bgp_on_router(self, context, speaker, router_id, gw_ip,
edge_id=None):
speaker = self._plugin.get_bgp_speaker(context, speaker['id'])
current_edge_id, advertise_static_routes = (
self._get_router_edge_info(context, router_id))
edge_id = edge_id or current_edge_id
if not edge_id:
return
routers_ids = (
self._core_plugin.edge_manager.get_routers_on_same_edge(
context, router_id))
bgp_binding = nsxv_db.get_nsxv_bgp_speaker_binding(context.session,
edge_id)
if not bgp_binding:
return
if len(routers_ids) > 1:
routers_ids.remove(router_id)
# Shared router, only remove prefixes and redistribution
# rules.
subnets = self._query_tenant_subnets(context, [router_id])
prefixes = [self.prefix_name(subnet['id'])
for subnet in subnets]
self._nsxv.remove_bgp_redistribution_rules(edge_id, prefixes)
if bgp_binding['bgp_identifier'] == gw_ip:
router = self._core_plugin._get_router(context, routers_ids[0])
new_bgp_identifier = (
router.gw_port['fixed_ips'][0]['ip_address'])
with context.session.begin(subtransactions=True):
bgp_binding['bgp_identifier'] = new_bgp_identifier
self._nsxv.update_router_id(edge_id, new_bgp_identifier)
else:
self._stop_bgp_on_edges(context, [bgp_binding], speaker['id'])
def advertise_subnet(self, context, speaker_id, router_id, subnet):
router = self._core_plugin._get_router(context, router_id)
if router.enable_snat:
# Do nothing, by default, only when advertisement is needed we add
# a new redistribution rule
return
edge_id, advertise_static_routes = (
self._get_router_edge_info(context, router_id))
if not edge_id:
# shared router is not attached on any edge
return
prefixes, redis_rules = self._get_prefixes_and_redistribution_rules(
[subnet], advertise_static_routes)
self._nsxv.add_bgp_redistribution_rules(edge_id, prefixes, redis_rules)
def withdraw_subnet(self, context, speaker_id, router_id, subnet_id):
router = self._core_plugin._get_router(context, router_id)
if router.enable_snat:
# Do nothing, by default, only when advertisement is needed we add
# a new redistribution rule
return
edge_id, advertise_static_routes = (
self._get_router_edge_info(context, router_id))
prefix_name = self.prefix_name(subnet_id)
self._nsxv.remove_bgp_redistribution_rules(edge_id, [prefix_name])

View File

@ -1474,3 +1474,128 @@ class FakeVcns(object):
'objectID': 'application-1001'}]
return applications
def update_dynamic_routing_service(self, edge_id, request_config):
header = {'status': 201}
response = {
'routerId': '172.24.4.12',
'ipPrefixes': {
'ipPrefixes': [
{'ipAddress': '10.0.0.0/24',
'name': 'prefix-name'}
]
}
}
return self.return_helper(header, response)
def get_dynamic_routing_service(self, edge_id):
header = {'status': 200}
response = {
'routerId': '172.24.4.12',
'ipPrefixes': {
'ipPrefixes': [
{'ipAddress': '10.0.0.0/24',
'name': 'prefix-name'}
]
},
'logging': {
'logLevel': 'info',
'enable': False
},
'ecmp': False
}
return self.return_helper(header, response)
def update_bgp_dynamic_routing(self, edge_id, bgp_request):
header = {"status": 201}
response = {
"localAS": 65000,
"enabled": True,
"bgpNeighbours": {
"bgpNeighbours": [
{
"bgpFilters": {
"bgpFilters": [
{
"action": "deny",
"direction": "in"
}
]
},
"password": None,
"ipAddress": "172.24.4.253",
"remoteAS": 65000
}
]
},
"redistribution": {
"rules": {
"rules": [
{
"action": "deny",
"from": {
"bgp": False,
"connected": False,
"static": False,
"ospf": False
},
"id": 0
},
{
"action": "permit",
"from": {
"bgp": False,
"connected": True,
"static": True,
"ospf": False
},
"id": 1,
"prefixName": "eee4eb79-359e-4416"
}
]
},
"enabled": True
}
}
return self.return_helper(header, response)
def get_bgp_routing_config(self, edge_id):
header = {'status': 200}
response = {
"localAS": 65000,
"enabled": True,
"redistribution": {
"rules": {
"rules": [
{
"action": "deny",
"from": {
"bgp": False,
"connected": False,
"static": False,
"ospf": False
},
"id": 0
},
{
"action": "permit",
"from": {
"bgp": False,
"connected": True,
"static": True,
"ospf": False
},
"id": 1,
"prefixName": "eee4eb79-359e-4416"
}
]
},
"enabled": True
}
}
return self.return_helper(header, response)
def delete_bgp_routing_config(self, edge_id):
header = {'status': 200}
response = ''
return header, response

View File

@ -0,0 +1,59 @@
# Copyright 2017 VMware, Inc.
# 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.
import mock
from neutron.tests import base
from neutron_dynamic_routing.db import bgp_db # noqa
from neutron_lib import context
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory
from vmware_nsx.services.dynamic_routing.nsx_v import driver as nsxv_driver
class TestNSXvBgpDriver(base.BaseTestCase):
def setUp(self):
super(TestNSXvBgpDriver, self).setUp()
self.context = context.get_admin_context()
with mock.patch.object(directory, 'get_plugin',
new=mock.Mock()):
self.provider = nsxv_driver.NSXvBgpDriver(mock.MagicMock())
def test_create_v6_bgp_speaker(self):
fake_bgp_speaker = {
"bgp_speaker": {
"ip_version": 6,
"local_as": "1000",
"name": "bgp-speaker"
}
}
self.assertRaises(n_exc.InvalidInput,
self.provider.create_bgp_speaker,
self.context, fake_bgp_speaker)
def test_create_v6_bgp_peer(self):
fake_bgp_peer = {
"bgp_peer": {
"auth_type": "none",
"remote_as": "1000",
"name": "bgp-peer",
"peer_ip": "fc00::/7"
}
}
self.assertRaises(n_exc.InvalidInput,
self.provider.create_bgp_peer,
self.context, fake_bgp_peer)