2c21c61709
The BGP plugin and driver used for NSX-v should be used for TVD as well. This plugin suports only NSX-V under the TVD plugin. Change-Id: I95be40fe0f58f0ff2b06590400d708ee95294b82
701 lines
32 KiB
Python
701 lines
32 KiB
Python
# 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 neutron_dynamic_routing.extensions import bgp as bgp_ext
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
|
|
from neutron_lib.api.definitions import address_scope
|
|
from neutron_lib.api.definitions import external_net as extnet_apidef
|
|
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 exceptions as nsx_exc
|
|
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.extensions import projectpluginmap
|
|
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 _get_bgp_neighbour(ip_address, remote_as, password, direction):
|
|
bgp_filter = {'bgpFilter': [{'direction': direction, 'action': 'permit'}]}
|
|
nbr = {
|
|
'ipAddress': ip_address,
|
|
'remoteAS': remote_as,
|
|
'bgpFilters': bgp_filter,
|
|
'holdDownTimer': cfg.CONF.nsxv.bgp_neighbour_hold_down_timer,
|
|
'keepAliveTimer': cfg.CONF.nsxv.bgp_neighbour_keep_alive_timer
|
|
}
|
|
if password:
|
|
nbr['password'] = password
|
|
return {'bgpNeighbour': nbr}
|
|
|
|
|
|
def bgp_neighbour_from_peer(bgp_peer):
|
|
return _get_bgp_neighbour(bgp_peer['peer_ip'],
|
|
bgp_peer['remote_as'],
|
|
bgp_peer['password'],
|
|
direction='out')
|
|
|
|
|
|
def gw_bgp_neighbour(ip_address, remote_as, password):
|
|
return _get_bgp_neighbour(ip_address, remote_as, password,
|
|
direction='in')
|
|
|
|
|
|
class NSXvBgpDriver(object):
|
|
"""Class driver to address the neutron_dynamic_routing API"""
|
|
|
|
def __init__(self, plugin):
|
|
super(NSXvBgpDriver, self).__init__()
|
|
self._plugin = plugin
|
|
self._core_plugin = directory.get_plugin()
|
|
if self._core_plugin.is_tvd_plugin():
|
|
self._core_plugin = self._core_plugin.get_plugin_by_type(
|
|
projectpluginmap.NsxPlugins.NSX_V)
|
|
if not self._core_plugin:
|
|
err_msg = _("NSXv BGP cannot work without the NSX-V core plugin")
|
|
raise n_exc.InvalidInput(error_message=err_msg)
|
|
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
|
|
|
|
# Indicates 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_advertised_routes(self, context, bgp_speaker_id):
|
|
routes = []
|
|
bgp_speaker = self._plugin.get_bgp_speaker(context, bgp_speaker_id)
|
|
edge_router_dict = (
|
|
self._get_dynamic_routing_edge_list(context,
|
|
bgp_speaker['networks'][0],
|
|
bgp_speaker_id))
|
|
for edge_id, edge_router_config in edge_router_dict.items():
|
|
bgp_identifier = edge_router_config['bgp_identifier']
|
|
subnets = self._query_tenant_subnets(
|
|
context, edge_router_config['no_snat_routers'])
|
|
routes.extend([(subnet['cidr'], bgp_identifier)
|
|
for subnet in subnets])
|
|
routes = self._plugin._make_advertised_routes_list(routes)
|
|
return self._plugin._make_advertised_routes_dict(routes)
|
|
|
|
def _get_dynamic_routing_edge_list(self, context,
|
|
gateway_network_id, bgp_speaker_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)
|
|
|
|
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
|
|
context.session, bgp_speaker_id)
|
|
binding_info = {bgp_binding['edge_id']: bgp_binding['bgp_identifier']
|
|
for bgp_binding in bgp_bindings}
|
|
|
|
edge_router_dict = {}
|
|
for port in gateway_ports:
|
|
router_id = port['device_id']
|
|
router = self._core_plugin._get_router(context, 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
|
|
continue
|
|
|
|
if edge_id not in edge_router_dict:
|
|
bgp_identifier = binding_info.get(
|
|
edge_id, port['fixed_ips'][0]['ip_address'])
|
|
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 _get_md_proxy_for_router(self, context, router_id):
|
|
binding = nsxv_db.get_nsxv_router_binding(context.session,
|
|
router_id)
|
|
md_proxy = None
|
|
if binding:
|
|
az_name = binding['availability_zone']
|
|
md_proxy = self._core_plugin.get_metadata_proxy_handler(
|
|
az_name)
|
|
return md_proxy
|
|
|
|
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'])
|
|
# We need to skip metadata subnets
|
|
md_proxy = self._get_md_proxy_for_router(context, router_id)
|
|
for p in int_ports:
|
|
subnet_id = p['fixed_ips'][0]['subnet_id']
|
|
if md_proxy and md_proxy.is_md_subnet(subnet_id):
|
|
continue
|
|
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)
|
|
h, resp = self._nsxv.vcns.get_interfaces(esg_id)
|
|
for iface in resp['vnics']:
|
|
address_groups = iface['addressGroups']['addressGroups']
|
|
matching_iface = [ag for ag in address_groups
|
|
if ag['primaryAddress'] == bgp_peer['peer_ip']]
|
|
if matching_iface:
|
|
break
|
|
else:
|
|
raise ext_esg_peer.EsgInternalIfaceDoesNotMatch(esg_id=esg_id)
|
|
|
|
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_from_peer(old_bgp_peer)
|
|
for bgp_speaker_id in bgp_speaker_ids:
|
|
with locking.LockManager.get_lock(bgp_speaker_id):
|
|
peers = self._plugin.get_bgp_peers_by_bgp_speaker(
|
|
context, bgp_speaker_id)
|
|
if bgp_peer_id not in [p['id'] for p in peers]:
|
|
continue
|
|
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(
|
|
context.session, bgp_speaker_id)
|
|
for binding in bgp_bindings:
|
|
try:
|
|
# Neighbours are identified by their ip address
|
|
self._nsxv.update_bgp_neighbours(binding['edge_id'],
|
|
[neighbour],
|
|
[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 _validate_bgp_peer(self, context, bgp_speaker_id, new_peer_id):
|
|
new_peer = self._plugin._get_bgp_peer(context, new_peer_id)
|
|
peers = self._plugin._get_bgp_peers_by_bgp_speaker_binding(
|
|
context, bgp_speaker_id)
|
|
self._plugin._validate_peer_ips(bgp_speaker_id, peers, new_peer)
|
|
|
|
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
|
bgp_peer_id = self._plugin._get_id_for(bgp_peer_info, 'bgp_peer_id')
|
|
bgp_peer_obj = self._plugin.get_bgp_peer(context,
|
|
bgp_peer_id)
|
|
|
|
nbr = bgp_neighbour_from_peer(bgp_peer_obj)
|
|
bgp_bindings = nsxv_db.get_nsxv_bgp_speaker_bindings(context.session,
|
|
bgp_speaker_id)
|
|
self._validate_bgp_peer(context, bgp_speaker_id, bgp_peer_obj['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 = []
|
|
for binding in bgp_bindings:
|
|
try:
|
|
self._nsxv.add_bgp_neighbours(binding['edge_id'], [nbr])
|
|
except vcns_exc.VcnsApiException:
|
|
LOG.error("Failed to add BGP neighbour on '%s'",
|
|
binding['edge_id'])
|
|
else:
|
|
gw_nbr = gw_bgp_neighbour(binding['bgp_identifier'],
|
|
speaker['local_as'],
|
|
bgp_peer_obj['password'])
|
|
neighbours.append(gw_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_from_peer(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 = []
|
|
for binding in bgp_bindings:
|
|
try:
|
|
self._nsxv.remove_bgp_neighbours(binding['edge_id'], [nbr])
|
|
except vcns_exc.VcnsApiException:
|
|
LOG.error("Failed to remove BGP neighbour on '%s'",
|
|
binding['edge_id'])
|
|
else:
|
|
gw_nbr = gw_bgp_neighbour(binding['bgp_identifier'],
|
|
speaker['local_as'],
|
|
bgp_peer_obj['password'])
|
|
neighbours.append(gw_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 _validate_gateway_network(self, context, speaker_id, network_id):
|
|
ext_net = self._core_plugin.get_network(context, network_id)
|
|
|
|
if not ext_net.get(extnet_apidef.EXTERNAL):
|
|
raise nsx_exc.NsxBgpNetworkNotExternal(net_id=network_id)
|
|
if not ext_net['subnets']:
|
|
raise nsx_exc.NsxBgpGatewayNetworkHasNoSubnets(net_id=network_id)
|
|
|
|
# REVISIT(roeyc): Currently not allowing more than one bgp speaker per
|
|
# gateway network.
|
|
speakers_on_network = self._plugin._bgp_speakers_for_gateway_network(
|
|
context, network_id)
|
|
if speakers_on_network:
|
|
raise bgp_ext.BgpSpeakerNetworkBindingError(
|
|
network_id=network_id,
|
|
bgp_speaker_id=speakers_on_network[0]['id'])
|
|
|
|
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=network_id, subnet_id=subnet_id)
|
|
|
|
if not ext_net[address_scope.IPV4_ADDRESS_SCOPE]:
|
|
raise nsx_exc.NsxBgpSpeakerUnableToAddGatewayNetwork(
|
|
network_id=network_id, bgp_speaker_id=speaker_id)
|
|
return True
|
|
|
|
def add_gateway_network(self, context, bgp_speaker_id, network_info):
|
|
gateway_network_id = network_info['network_id']
|
|
|
|
if not self._validate_gateway_network(context, bgp_speaker_id,
|
|
gateway_network_id):
|
|
return
|
|
|
|
edge_router_dict = self._get_dynamic_routing_edge_list(
|
|
context, gateway_network_id, bgp_speaker_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)
|
|
local_as = speaker['local_as']
|
|
peers = []
|
|
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, edge_id)
|
|
else:
|
|
peers.append(bgp_identifier)
|
|
|
|
for edge_gw, password in [(peer['esg_id'], peer['password'])
|
|
for peer in bgp_peers if peer.get('esg_id')]:
|
|
neighbours = [gw_bgp_neighbour(bgp_id, local_as, password)
|
|
for bgp_id in peers]
|
|
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_from_peer(bgp_peer)
|
|
for bgp_peer in bgp_peers]
|
|
try:
|
|
self._nsxv.add_bgp_speaker_config(edge_id, bgp_identifier,
|
|
local_as, enabled_state,
|
|
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):
|
|
peers_to_remove = []
|
|
speaker = self._plugin.get_bgp_speaker(context, speaker_id)
|
|
local_as = speaker['local_as']
|
|
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)
|
|
peers_to_remove.append(bgp_binding['bgp_identifier'])
|
|
|
|
# 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'], peer['password'])
|
|
for peer in bgp_peers if peer.get('esg_id')]
|
|
for gw_edge, password in gw_edges:
|
|
neighbours_to_remove = [gw_bgp_neighbour(bgp_identifier,
|
|
local_as,
|
|
password)
|
|
for bgp_identifier in peers_to_remove]
|
|
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 _update_edge_bgp_identifier(self, context, bgp_binding, speaker,
|
|
new_bgp_identifier):
|
|
local_as = speaker['local_as']
|
|
bgp_peers = self._plugin.get_bgp_peers_by_bgp_speaker(context,
|
|
speaker['id'])
|
|
self._nsxv.update_router_id(bgp_binding['edge_id'], new_bgp_identifier)
|
|
for gw_edge_id, password in [(peer['esg_id'], peer['password'])
|
|
for peer in bgp_peers
|
|
if peer.get('esg_id')]:
|
|
nbr_to_remove = gw_bgp_neighbour(bgp_binding['bgp_identifier'],
|
|
local_as, password)
|
|
nbr_to_add = gw_bgp_neighbour(new_bgp_identifier, local_as,
|
|
password)
|
|
self._nsxv.update_bgp_neighbours(gw_edge_id,
|
|
[nbr_to_add],
|
|
[nbr_to_remove])
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
bgp_binding['bgp_identifier'] = new_bgp_identifier
|
|
|
|
def process_router_gw_port_update(self, context, speaker,
|
|
router, updated_port):
|
|
router_id = router['id']
|
|
gw_fixed_ip = router.gw_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
|
|
return
|
|
|
|
bgp_binding = nsxv_db.get_nsxv_bgp_speaker_binding(
|
|
context.session, edge_id)
|
|
|
|
if bgp_binding:
|
|
new_fixed_ip = updated_port['fixed_ips'][0]['ip_address']
|
|
fixed_ip_updated = gw_fixed_ip != new_fixed_ip
|
|
subnets = self._query_tenant_subnets(context, [router_id])
|
|
prefixes, redis_rules = (
|
|
self._get_prefixes_and_redistribution_rules(
|
|
subnets, advertise_static_routes))
|
|
# Handle possible snat/no-nat update
|
|
if router.enable_snat:
|
|
self._nsxv.remove_bgp_redistribution_rules(edge_id, prefixes)
|
|
else:
|
|
self._nsxv.add_bgp_redistribution_rules(edge_id, prefixes,
|
|
redis_rules)
|
|
if bgp_binding['bgp_identifier'] == gw_fixed_ip:
|
|
if fixed_ip_updated:
|
|
self._update_edge_bgp_identifier(context,
|
|
bgp_binding,
|
|
speaker,
|
|
new_fixed_ip)
|
|
|
|
def enable_bgp_on_router(self, context, speaker, router_id):
|
|
local_as = speaker['local_as']
|
|
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)
|
|
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.
|
|
if router.enable_snat:
|
|
prefixes = [self.prefix_name(subnet['id'])
|
|
for subnet in subnets]
|
|
self._nsxv.remove_bgp_redistribution_rules(edge_id, prefixes)
|
|
else:
|
|
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:
|
|
if router.enable_snat:
|
|
subnets = []
|
|
bgp_identifier = router.gw_port['fixed_ips'][0]['ip_address']
|
|
self._start_bgp_on_edge(context, edge_id, speaker, bgp_peers,
|
|
bgp_identifier, subnets,
|
|
advertise_static_routes)
|
|
for gw_edge_id, password in [(peer['esg_id'], peer['password'])
|
|
for peer in bgp_peers
|
|
if peer.get('esg_id')]:
|
|
nbr = gw_bgp_neighbour(bgp_identifier, local_as, password)
|
|
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
|
|
|
|
bgp_binding = nsxv_db.get_nsxv_bgp_speaker_binding(context.session,
|
|
edge_id)
|
|
if not bgp_binding:
|
|
return
|
|
|
|
# Need to ensure that we do not use the metadata IP's
|
|
md_proxy = self._get_md_proxy_for_router(context, router_id)
|
|
|
|
routers_ids = (
|
|
self._core_plugin.edge_manager.get_routers_on_same_edge(
|
|
context, router_id))
|
|
routers_ids.remove(router_id)
|
|
|
|
# We need to find out what other routers are hosted on the edges and
|
|
# whether they have a gw addresses that could replace the current
|
|
# bgp-identifier (if required).
|
|
filters = {'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW],
|
|
'device_id': routers_ids}
|
|
edge_gw_ports = self._core_plugin.get_ports(context, filters=filters)
|
|
alt_bgp_identifiers = [
|
|
p['fixed_ips'][0]['ip_address'] for p in edge_gw_ports
|
|
if (not md_proxy or
|
|
not md_proxy.is_md_subnet(
|
|
p['fixed_ips'][0]['subnet_id']))]
|
|
if alt_bgp_identifiers:
|
|
# 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:
|
|
self._update_edge_bgp_identifier(context, bgp_binding, speaker,
|
|
alt_bgp_identifiers[0])
|
|
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])
|