eedbcdd03b
This patch switches the code over to the payload style of callbacks [1] for PORT ROUTER_GATEWAY events for those that are not using them yet. The unit tests are also updated where needed to account for the payload style callbacks and publish() method. Finally the patch normalizes the passing of gateway IPs which are currently referred to as 'gw_ips' and 'gateway_ips' depending on the event; now all events use 'gateway_ips'. [1] https://docs.openstack.org/neutron-lib/latest/contributor/callbacks.html Change-Id: Ibc255de79443e908cc3615a8e1cb108757f80011
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, payload=None):
|
|
context = payload.context or n_context.get_admin_context()
|
|
context = context.elevated()
|
|
router_id = payload.resource_id
|
|
network_id = payload.metadata['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 = payload.metadata['gateway_ips']
|
|
driver.disable_bgp_on_router(context,
|
|
speaker,
|
|
router_id,
|
|
gw_ips[0])
|
|
if event == events.AFTER_UPDATE:
|
|
updated_port = payload.metadata['updated_port']
|
|
router = payload.latest_state
|
|
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
|