11c6b535a1
This patch: - Addresses a few missed by https://review.openstack.org/#/c/637962/ - Updates all the vmware_nsx.extensions.* to use ALIAS for their API extension alias variable rather than use a random or no constant name for the alias. Change-Id: Ice88f1d6a451fb0d82587a9fb6b1a6a7f285c8c6
368 lines
17 KiB
Python
368 lines
17 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.
|
|
|
|
from neutron_dynamic_routing.db import bgp_db
|
|
from neutron_dynamic_routing.extensions import bgp as bgp_ext
|
|
from neutron_lib.callbacks import events
|
|
from neutron_lib.callbacks import registry
|
|
from neutron_lib.callbacks import resources
|
|
from neutron_lib import context as n_context
|
|
from neutron_lib import exceptions as n_exc
|
|
from neutron_lib.plugins import directory
|
|
from neutron_lib.services import base as service_base
|
|
from oslo_log import log as logging
|
|
|
|
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.extensions import projectpluginmap
|
|
from vmware_nsx.services.dynamic_routing.nsx_v import driver as nsxv_driver
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_nsx_svc_plugin'
|
|
|
|
|
|
class NSXBgpPlugin(service_base.ServicePluginBase, bgp_db.BgpDbMixin):
|
|
"""BGP service plugin for NSX-V as well as TVD plugins.
|
|
|
|
Currently only the nsx-v is supported. other plugins will be refused.
|
|
"""
|
|
|
|
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS,
|
|
ext_esg.ALIAS]
|
|
|
|
def __init__(self):
|
|
super(NSXBgpPlugin, self).__init__()
|
|
self._core_plugin = directory.get_plugin()
|
|
|
|
# initialize the supported drivers (currently only NSX-v)
|
|
self.drivers = {}
|
|
try:
|
|
self.drivers[projectpluginmap.NsxPlugins.NSX_V] = (
|
|
nsxv_driver.NSXvBgpDriver(self))
|
|
except Exception:
|
|
# No driver found
|
|
LOG.warning("NSXBgpPlugin failed to initialize the NSX-V driver")
|
|
self.drivers[projectpluginmap.NsxPlugins.NSX_V] = None
|
|
|
|
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_UPDATE)
|
|
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 _get_driver_by_project(self, context, project):
|
|
# Check if the current project id has a matching driver
|
|
# Currently only NSX-V is supported
|
|
if self._core_plugin.is_tvd_plugin():
|
|
plugin_type = self._core_plugin.get_plugin_type_from_project(
|
|
context, project)
|
|
else:
|
|
plugin_type = self._core_plugin.plugin_type()
|
|
|
|
if not self.drivers.get(plugin_type):
|
|
msg = (_("Project %(project)s with plugin %(plugin)s has no "
|
|
"support for dynamic routing") % {
|
|
'project': project,
|
|
'plugin': plugin_type})
|
|
raise n_exc.InvalidInput(error_message=msg)
|
|
|
|
return self.drivers[plugin_type]
|
|
|
|
def _get_driver_by_speaker(self, context, bgp_speaker_id):
|
|
try:
|
|
speaker = self.get_bgp_speaker(context, bgp_speaker_id)
|
|
except Exception:
|
|
msg = _("BGP speaker %s could not be found") % bgp_speaker_id
|
|
raise n_exc.BadRequest(resource=bgp_ext.BGP_SPEAKER_RESOURCE_NAME,
|
|
msg=msg)
|
|
return self._get_driver_by_project(context, speaker['tenant_id'])
|
|
|
|
def create_bgp_speaker(self, context, bgp_speaker):
|
|
driver = self._get_driver_by_project(
|
|
context, bgp_speaker['bgp_speaker']['tenant_id'])
|
|
driver.create_bgp_speaker(context, bgp_speaker)
|
|
return super(NSXBgpPlugin, self).create_bgp_speaker(context,
|
|
bgp_speaker)
|
|
|
|
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
driver.update_bgp_speaker(context, bgp_speaker_id, bgp_speaker)
|
|
# TBD(roeyc): rolling back changes on edges base class call failed.
|
|
return super(NSXBgpPlugin, self).update_bgp_speaker(
|
|
context, bgp_speaker_id, bgp_speaker)
|
|
|
|
def delete_bgp_speaker(self, context, bgp_speaker_id):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
driver.delete_bgp_speaker(context, bgp_speaker_id)
|
|
super(NSXBgpPlugin, self).delete_bgp_speaker(context,
|
|
bgp_speaker_id)
|
|
|
|
def _add_esg_peer_info(self, context, peer):
|
|
# TODO(asarfaty): only if nsxv driver, or do it in the driver itself
|
|
binding = nsxv_db.get_nsxv_bgp_peer_edge_binding(context.session,
|
|
peer['id'])
|
|
if binding:
|
|
peer['esg_id'] = binding['edge_id']
|
|
|
|
def get_bgp_peer(self, context, bgp_peer_id, fields=None):
|
|
peer = super(NSXBgpPlugin, self).get_bgp_peer(context,
|
|
bgp_peer_id, fields)
|
|
if not fields or 'esg_id' in fields:
|
|
self._add_esg_peer_info(context, peer)
|
|
return peer
|
|
|
|
def get_bgp_peers_by_bgp_speaker(self, context,
|
|
bgp_speaker_id, fields=None):
|
|
ret = super(NSXBgpPlugin, 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:
|
|
self._add_esg_peer_info(context, peer)
|
|
return ret
|
|
|
|
def _get_driver_by_peer(self, context, bgp_peer_id):
|
|
try:
|
|
peer = self.get_bgp_peer(context, bgp_peer_id)
|
|
except Exception:
|
|
raise bgp_ext.BgpPeerNotFound(id=bgp_peer_id)
|
|
return self._get_driver_by_project(context, peer['tenant_id'])
|
|
|
|
def create_bgp_peer(self, context, bgp_peer):
|
|
driver = self._get_driver_by_project(
|
|
context, bgp_peer['bgp_peer']['tenant_id'])
|
|
driver.create_bgp_peer(context, bgp_peer)
|
|
peer = super(NSXBgpPlugin, self).create_bgp_peer(context, bgp_peer)
|
|
# TODO(asarfaty): only if nsxv driver, or do it in the driver itself
|
|
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):
|
|
driver = self._get_driver_by_peer(context, bgp_peer_id)
|
|
super(NSXBgpPlugin, self).update_bgp_peer(context,
|
|
bgp_peer_id, bgp_peer)
|
|
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):
|
|
driver = self._get_driver_by_peer(context, bgp_peer_id)
|
|
bgp_peer_info = {'bgp_peer_id': bgp_peer_id}
|
|
bgp_speaker_ids = driver._get_bgp_speakers_by_bgp_peer(
|
|
context, bgp_peer_id)
|
|
for speaker_id in bgp_speaker_ids:
|
|
try:
|
|
self.remove_bgp_peer(context, speaker_id, bgp_peer_info)
|
|
except bgp_ext.BgpSpeakerPeerNotAssociated:
|
|
LOG.debug("Couldn't find bgp speaker %s peer binding while "
|
|
"deleting bgp peer %s", speaker_id, bgp_peer_id)
|
|
super(NSXBgpPlugin, self).delete_bgp_peer(context, bgp_peer_id)
|
|
|
|
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
|
# speaker & peer must belong to the same driver
|
|
if not bgp_peer_info.get('bgp_peer_id'):
|
|
msg = _("bgp_peer_id must be specified")
|
|
raise n_exc.BadRequest(resource='bgp-peer', msg=msg)
|
|
peer_driver = self._get_driver_by_peer(
|
|
context, bgp_peer_info['bgp_peer_id'])
|
|
speaker_driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
if peer_driver != speaker_driver:
|
|
msg = _("Peer and Speaker must belong to the same plugin")
|
|
raise n_exc.InvalidInput(error_message=msg)
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
speaker_driver.add_bgp_peer(context,
|
|
bgp_speaker_id, bgp_peer_info)
|
|
return super(NSXBgpPlugin, self).add_bgp_peer(context,
|
|
bgp_speaker_id,
|
|
bgp_peer_info)
|
|
|
|
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
ret = super(NSXBgpPlugin, self).remove_bgp_peer(
|
|
context, bgp_speaker_id, bgp_peer_info)
|
|
driver.remove_bgp_peer(context, bgp_speaker_id, bgp_peer_info)
|
|
return ret
|
|
|
|
def _validate_network_plugin(
|
|
self, context, network_info,
|
|
plugin_type=projectpluginmap.NsxPlugins.NSX_V):
|
|
"""Make sure the network belongs to the NSX0-V plugin"""
|
|
if not network_info.get('network_id'):
|
|
msg = _("network_id must be specified")
|
|
raise n_exc.BadRequest(resource=bgp_ext.BGP_SPEAKER_RESOURCE_NAME,
|
|
msg=msg)
|
|
net_id = network_info['network_id']
|
|
p = self._core_plugin._get_plugin_from_net_id(context, net_id)
|
|
if p.plugin_type() != plugin_type:
|
|
msg = (_('Network should belong to the %s plugin as the bgp '
|
|
'speaker') % plugin_type)
|
|
raise n_exc.InvalidInput(error_message=msg)
|
|
|
|
def add_gateway_network(self, context, bgp_speaker_id, network_info):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
if self._core_plugin.is_tvd_plugin():
|
|
# The plugin of the network and speaker must be the same
|
|
self._validate_network_plugin(context, network_info)
|
|
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
driver.add_gateway_network(context,
|
|
bgp_speaker_id,
|
|
network_info)
|
|
return super(NSXBgpPlugin, self).add_gateway_network(
|
|
context, bgp_speaker_id, network_info)
|
|
|
|
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
with locking.LockManager.get_lock(str(bgp_speaker_id)):
|
|
super(NSXBgpPlugin, self).remove_gateway_network(
|
|
context, bgp_speaker_id, network_info)
|
|
driver.remove_gateway_network(context,
|
|
bgp_speaker_id,
|
|
network_info)
|
|
|
|
def get_advertised_routes(self, context, bgp_speaker_id):
|
|
driver = self._get_driver_by_speaker(context, bgp_speaker_id)
|
|
return driver.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'].elevated()
|
|
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)
|
|
driver = self._get_driver_by_project(
|
|
context, speaker['tenant_id'])
|
|
if network_id not in speaker['networks']:
|
|
continue
|
|
if event == events.AFTER_CREATE:
|
|
driver.advertise_subnet(context, speaker_id,
|
|
router_id, subnets[0])
|
|
if event == events.AFTER_DELETE:
|
|
subnet_id = port['fixed_ips'][0]['subnet_id']
|
|
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()
|
|
context = context.elevated()
|
|
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
|
|
driver = self._get_driver_by_project(
|
|
context, speaker['tenant_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']
|
|
driver.disable_bgp_on_router(context,
|
|
speaker,
|
|
router_id,
|
|
gw_ips[0])
|
|
if event == events.AFTER_UPDATE:
|
|
updated_port = kwargs['updated_port']
|
|
router = kwargs['router']
|
|
driver.process_router_gw_port_update(
|
|
context, speaker, router, updated_port)
|
|
|
|
def _before_service_edge_delete_callback(self, resource, event,
|
|
trigger, payload=None):
|
|
context = payload.context.elevated()
|
|
router = payload.latest_state
|
|
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 = payload.resource_id
|
|
speakers = self._bgp_speakers_for_gateway_network(context, ext_net_id)
|
|
for speaker in speakers:
|
|
driver = self._get_driver_by_project(
|
|
context, speaker['tenant_id'])
|
|
with locking.LockManager.get_lock(speaker.id):
|
|
speaker = self.get_bgp_speaker(context, speaker.id)
|
|
if ext_net_id not in speaker['networks']:
|
|
continue
|
|
driver.disable_bgp_on_router(context, speaker,
|
|
router['id'],
|
|
gw_ip, edge_id)
|
|
|
|
def _after_service_edge_create_callback(self, resource, event,
|
|
trigger, payload=None):
|
|
context = payload.context.elevated()
|
|
router = payload.latest_state
|
|
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:
|
|
driver = self._get_driver_by_project(
|
|
context, speaker['tenant_id'])
|
|
with locking.LockManager.get_lock(speaker.id):
|
|
speaker = self.get_bgp_speaker(context, speaker.id)
|
|
if ext_net_id not in speaker['networks']:
|
|
continue
|
|
driver.enable_bgp_on_router(context, speaker, router['id'])
|
|
|
|
|
|
class NSXvBgpPlugin(NSXBgpPlugin):
|
|
"""Defined for backwards compatibility only"""
|
|
pass
|