389 lines
17 KiB
Python
389 lines
17 KiB
Python
# Copyright 2014 DreamHost, LLC
|
|
#
|
|
# Author: DreamHost, LLC
|
|
#
|
|
# 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 functools
|
|
|
|
from sqlalchemy import exc as sql_exc
|
|
|
|
from neutron.api.rpc.handlers import dhcp_rpc, l3_rpc
|
|
from neutron.common import constants
|
|
from neutron.common import exceptions as n_exc
|
|
from neutron.common import rpc as n_rpc
|
|
from neutron.common import topics
|
|
from neutron.db import agents_db
|
|
from neutron.db import l3_db
|
|
from oslo_log import log as logging
|
|
from oslo.db import exception as db_exc
|
|
from neutron.i18n import _
|
|
from neutron.plugins.vmware.api_client import exception as api_exc
|
|
from neutron.plugins.vmware.common import nsx_utils
|
|
from neutron.plugins.vmware.common import sync as nsx_sync
|
|
from neutron.plugins.vmware.dbexts import db as nsx_db
|
|
from neutron.plugins.vmware.nsxlib import switch as switchlib
|
|
from neutron.plugins.vmware.plugins import base
|
|
from neutron.plugins.vmware.plugins.base import cfg as n_cfg
|
|
|
|
from astara_neutron.plugins import decorators as astara
|
|
from astara_neutron.plugins import floatingip
|
|
|
|
LOG = logging.getLogger("NeutronPlugin")
|
|
|
|
|
|
def astara_nvp_ipv6_port_security_wrapper(f):
|
|
@functools.wraps(f)
|
|
def wrapper(lport_obj, mac_address, fixed_ips, port_security_enabled,
|
|
security_profiles, queue_id, mac_learning_enabled,
|
|
allowed_address_pairs):
|
|
|
|
f(lport_obj, mac_address, fixed_ips, port_security_enabled,
|
|
security_profiles, queue_id, mac_learning_enabled,
|
|
allowed_address_pairs)
|
|
|
|
# evaulate the state so that we only override the value when enabled
|
|
# otherwise we are preserving the underlying behavior of the NVP plugin
|
|
if port_security_enabled:
|
|
# hotfix to enable egress mulitcast
|
|
lport_obj['allow_egress_multicast'] = True
|
|
|
|
# TODO(mark): investigate moving away from this an wrapping
|
|
# (create|update)_port
|
|
# add link-local and subnet cidr for IPv6 temp addresses
|
|
special_ipv6_addrs = astara.get_special_ipv6_addrs(
|
|
(p['ip_address'] for p in lport_obj['allowed_address_pairs']),
|
|
mac_address
|
|
)
|
|
|
|
lport_obj['allowed_address_pairs'].extend(
|
|
{'mac_address': mac_address, 'ip_address': addr}
|
|
for addr in special_ipv6_addrs
|
|
)
|
|
|
|
return wrapper
|
|
|
|
|
|
base.switchlib._configure_extensions = astara_nvp_ipv6_port_security_wrapper(
|
|
base.switchlib._configure_extensions
|
|
)
|
|
|
|
|
|
class AstaraNsxSynchronizer(nsx_sync.NsxSynchronizer):
|
|
"""
|
|
The NsxSynchronizer class in Neutron runs a synchronization thread to
|
|
sync nvp objects with neutron objects. Since we don't use nvp's routers
|
|
the sync was failing making neutron showing all the routers like if the
|
|
were in Error state. To fix this behaviour we override the two methods
|
|
responsible for the routers synchronization in the NsxSynchronizer class
|
|
to be a noop
|
|
|
|
"""
|
|
|
|
def _synchronize_state(self, *args, **kwargs):
|
|
"""
|
|
Given the complexicity of the NSX synchronization process, there are
|
|
about a million ways for it to go wrong. (MySQL connection issues,
|
|
transactional race conditions, etc...) In the event that an exception
|
|
is thrown, behavior of the upstream implementation is to immediately
|
|
report the exception and kill the synchronizer thread.
|
|
|
|
This makes it very difficult to detect failure (because the thread just
|
|
ends) and the problem can only be fixed by completely restarting
|
|
neutron.
|
|
|
|
This implementation changes the behavior to repeatedly fail (and retry)
|
|
and log verbosely during failure so that the failure is more obvious
|
|
(and so that auto-recovery is a possibility if e.g., the database
|
|
comes back to life or a network-related issue becomes resolved).
|
|
"""
|
|
try:
|
|
return nsx_sync.NsxSynchronizer._synchronize_state(
|
|
self, *args, **kwargs
|
|
)
|
|
except:
|
|
LOG.exception("An error occurred while communicating with "
|
|
"NSX backend. Will retry synchronization "
|
|
"in %d seconds" % self._sync_backoff)
|
|
self._sync_backoff = min(self._sync_backoff * 2, 64)
|
|
return self._sync_backoff
|
|
else:
|
|
self._sync_backoff = 1
|
|
|
|
def _synchronize_lrouters(self, *args, **kwargs):
|
|
pass
|
|
|
|
def synchronize_router(self, *args, **kwargs):
|
|
pass
|
|
|
|
|
|
class NsxPluginV2(floatingip.ExplicitFloatingIPAllocationMixin,
|
|
base.NsxPluginV2):
|
|
"""
|
|
NsxPluginV2 is a Neutron plugin that provides L2 Virtual Network
|
|
functionality using NSX.
|
|
"""
|
|
supported_extension_aliases = (
|
|
base.NsxPluginV2.supported_extension_aliases +
|
|
astara.SUPPORTED_EXTENSIONS
|
|
)
|
|
|
|
def __init__(self):
|
|
# In order to force this driver to not sync neutron routers with
|
|
# with NSX routers, we need to use our subclass of the
|
|
# NsxSynchronizer object. Sadly, the call to the __init__ method
|
|
# of the superclass instantiates a non-customizable NsxSynchronizer
|
|
# object wich spawns a sync thread that sets the state of all the
|
|
# neutron routers to ERROR when neutron starts. To avoid spawning
|
|
# that thread, we need to temporarily override the cfg object and
|
|
# disable NSX synchronization in the superclass constructor.
|
|
|
|
actual = {
|
|
'state_sync_interval': n_cfg.CONF.NSX_SYNC.state_sync_interval,
|
|
'max_random_sync_delay': n_cfg.CONF.NSX_SYNC.max_random_sync_delay,
|
|
'min_sync_req_delay': n_cfg.CONF.NSX_SYNC.min_sync_req_delay
|
|
}
|
|
for key in actual:
|
|
n_cfg.CONF.set_override(key, 0, 'NSX_SYNC')
|
|
super(NsxPluginV2, self).__init__()
|
|
for key, value in actual.items():
|
|
n_cfg.CONF.set_override(key, value, 'NSX_SYNC')
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Original code:
|
|
# self._port_drivers = {
|
|
# 'create': {l3_db.DEVICE_OWNER_ROUTER_GW:
|
|
# self._nsx_create_ext_gw_port,
|
|
# l3_db.DEVICE_OWNER_FLOATINGIP:
|
|
# self._nsx_create_fip_port,
|
|
# l3_db.DEVICE_OWNER_ROUTER_INTF:
|
|
# self._nsx_create_router_port,
|
|
# networkgw_db.DEVICE_OWNER_NET_GW_INTF:
|
|
# self._nsx_create_l2_gw_port,
|
|
# 'default': self._nsx_create_port},
|
|
# 'delete': {l3_db.DEVICE_OWNER_ROUTER_GW:
|
|
# self._nsx_delete_ext_gw_port,
|
|
# l3_db.DEVICE_OWNER_ROUTER_INTF:
|
|
# self._nsx_delete_router_port,
|
|
# l3_db.DEVICE_OWNER_FLOATINGIP:
|
|
# self._nsx_delete_fip_port,
|
|
# networkgw_db.DEVICE_OWNER_NET_GW_INTF:
|
|
# self._nsx_delete_port,
|
|
# 'default': self._nsx_delete_port}
|
|
# }
|
|
|
|
self._port_drivers = {
|
|
'create': {
|
|
l3_db.DEVICE_OWNER_FLOATINGIP: self._nsx_create_fip_port,
|
|
'default': self._nsx_create_port
|
|
},
|
|
'delete': {
|
|
l3_db.DEVICE_OWNER_FLOATINGIP: self._nsx_delete_fip_port,
|
|
'default': self._nsx_delete_port
|
|
}
|
|
}
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Create a synchronizer instance for backend sync
|
|
# ---------------------------------------------------------------------
|
|
# Note(rods):
|
|
# We added this code with the only purpose to make the nsx driver use
|
|
# our subclass of the NsxSynchronizer object.
|
|
#
|
|
# DHC-2385
|
|
#
|
|
# Original code:
|
|
# self._synchronizer = sync.NsxSynchronizer(
|
|
# self, self.cluster,
|
|
# self.nsx_sync_opts.state_sync_interval,
|
|
# self.nsx_sync_opts.min_sync_req_delay,
|
|
# self.nsx_sync_opts.min_chunk_size,
|
|
# self.nsx_sync_opts.max_random_sync_delay)
|
|
|
|
self._synchronizer = AstaraNsxSynchronizer(
|
|
self, self.cluster,
|
|
self.nsx_sync_opts.state_sync_interval,
|
|
self.nsx_sync_opts.min_sync_req_delay,
|
|
self.nsx_sync_opts.min_chunk_size,
|
|
self.nsx_sync_opts.max_random_sync_delay)
|
|
# ---------------------------------------------------------------------
|
|
|
|
def setup_dhcpmeta_access(self):
|
|
# Ok, so we're going to add L3 here too with the DHCP
|
|
self.conn = n_rpc.create_connection(new=True)
|
|
self.conn.create_consumer(
|
|
topics.PLUGIN,
|
|
[dhcp_rpc.DhcpRpcCallback(), agents_db.AgentExtRpcCallback()],
|
|
fanout=False
|
|
)
|
|
|
|
self.conn.create_consumer(
|
|
topics.L3PLUGIN,
|
|
[l3_rpc.L3RpcCallback()],
|
|
fanout=False
|
|
)
|
|
|
|
# Consume from all consumers in a thread
|
|
self.conn.consume_in_threads()
|
|
|
|
self.handle_network_dhcp_access_delegate = noop
|
|
self.handle_port_dhcp_access_delegate = noop
|
|
self.handle_port_metadata_access_delegate = noop
|
|
self.handle_metadata_access_delegate = noop
|
|
|
|
@astara.auto_add_ipv6_subnet
|
|
def create_network(self, context, network):
|
|
return super(NsxPluginV2, self).create_network(context, network)
|
|
|
|
@astara.auto_add_subnet_to_router
|
|
def create_subnet(self, context, subnet):
|
|
return super(NsxPluginV2, self).create_subnet(context, subnet)
|
|
|
|
# we need to use original versions l3_db.L3_NAT_db_mixin mixin and not
|
|
# NSX versions that manage NSX's logical router
|
|
|
|
create_router = l3_db.L3_NAT_db_mixin.create_router
|
|
update_router = l3_db.L3_NAT_db_mixin.update_router
|
|
delete_router = l3_db.L3_NAT_db_mixin.delete_router
|
|
get_router = l3_db.L3_NAT_db_mixin.get_router
|
|
get_routers = l3_db.L3_NAT_db_mixin.get_routers
|
|
add_router_interface = l3_db.L3_NAT_db_mixin.add_router_interface
|
|
remove_router_interface = l3_db.L3_NAT_db_mixin.remove_router_interface
|
|
update_floatingip = l3_db.L3_NAT_db_mixin.update_floatingip
|
|
delete_floatingip = l3_db.L3_NAT_db_mixin.delete_floatingip
|
|
get_floatingip = l3_db.L3_NAT_db_mixin.get_floatingip
|
|
get_floatings = l3_db.L3_NAT_db_mixin.get_floatingips
|
|
_update_fip_assoc = l3_db.L3_NAT_db_mixin._update_fip_assoc
|
|
_update_router_gw_info = l3_db.L3_NAT_db_mixin._update_router_gw_info
|
|
disassociate_floatingips = l3_db.L3_NAT_db_mixin.disassociate_floatingips
|
|
get_sync_data = l3_db.L3_NAT_db_mixin.get_sync_data
|
|
|
|
def _ensure_metadata_host_route(self, *args, **kwargs):
|
|
""" Astara metadata services are provided by router so make no-op/"""
|
|
pass
|
|
|
|
def _nsx_create_port(self, context, port_data):
|
|
"""Driver for creating a logical switch port on NSX platform."""
|
|
# FIXME(salvatore-orlando): On the NSX platform we do not really have
|
|
# external networks. So if as user tries and create a "regular" VIF
|
|
# port on an external network we are unable to actually create.
|
|
# However, in order to not break unit tests, we need to still create
|
|
# the DB object and return success
|
|
|
|
# NOTE(rods): Reporting mark's comment on havana version of this patch.
|
|
# Astara does want ports for external networks so this method is
|
|
# basically same with external check removed and the auto plugging of
|
|
# router ports
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Note(rods): Remove the check on the external network
|
|
#
|
|
# Original code:
|
|
# if self._network_is_external(context, port_data['network_id']):
|
|
# LOG.info(_("NSX plugin does not support regular VIF ports on "
|
|
# "external networks. Port %s will be down."),
|
|
# port_data['network_id'])
|
|
# # No need to actually update the DB state - the default is down
|
|
# return port_data
|
|
# ---------------------------------------------------------------------
|
|
lport = None
|
|
selected_lswitch = None
|
|
try:
|
|
selected_lswitch = self._nsx_find_lswitch_for_port(context,
|
|
port_data)
|
|
lport = self._nsx_create_port_helper(context.session,
|
|
selected_lswitch['uuid'],
|
|
port_data,
|
|
True)
|
|
nsx_db.add_neutron_nsx_port_mapping(
|
|
context.session, port_data['id'],
|
|
selected_lswitch['uuid'], lport['uuid'])
|
|
# -----------------------------------------------------------------
|
|
# Note(rods): Auto plug router ports
|
|
#
|
|
# Original code:
|
|
# if port_data['device_owner'] not in self.port_special_owners:
|
|
# switchlib.plug_vif_interface(
|
|
# self.cluster, selected_lswitch['uuid'],
|
|
# lport['uuid'], "VifAttachment", port_data['id'])
|
|
|
|
switchlib.plug_vif_interface(
|
|
self.cluster, selected_lswitch['uuid'],
|
|
lport['uuid'], "VifAttachment", port_data['id'])
|
|
# -----------------------------------------------------------------
|
|
|
|
LOG.debug(_("_nsx_create_port completed for port %(name)s "
|
|
"on network %(network_id)s. The new port id is "
|
|
"%(id)s."), port_data)
|
|
except (api_exc.NsxApiException, n_exc.NeutronException):
|
|
self._handle_create_port_exception(
|
|
context, port_data['id'],
|
|
selected_lswitch and selected_lswitch['uuid'],
|
|
lport and lport['uuid'])
|
|
except db_exc.DBError as e:
|
|
if (port_data['device_owner'] == constants.DEVICE_OWNER_DHCP and
|
|
isinstance(e.inner_exception, sql_exc.IntegrityError)):
|
|
msg = (_("Concurrent network deletion detected; Back-end Port "
|
|
"%(nsx_id)s creation to be rolled back for Neutron "
|
|
"port: %(neutron_id)s")
|
|
% {'nsx_id': lport['uuid'],
|
|
'neutron_id': port_data['id']})
|
|
LOG.warning(msg)
|
|
if selected_lswitch and lport:
|
|
try:
|
|
switchlib.delete_port(self.cluster,
|
|
selected_lswitch['uuid'],
|
|
lport['uuid'])
|
|
except n_exc.NotFound:
|
|
LOG.debug(_("NSX Port %s already gone"), lport['uuid'])
|
|
|
|
def _nsx_delete_port(self, context, port_data):
|
|
# FIXME(salvatore-orlando): On the NSX platform we do not really have
|
|
# external networks. So deleting regular ports from external networks
|
|
# does not make sense. However we cannot raise as this would break
|
|
# unit tests.
|
|
|
|
# NOTE(rods): reporting mark's comment on havana version of this patch.
|
|
# Astara does want ports for external networks so this method is
|
|
# basically same with external check removed
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Original code:
|
|
# if self._network_is_external(context, port_data['network_id']):
|
|
# LOG.info(_("NSX plugin does not support regular VIF ports on "
|
|
# "external networks. Port %s will be down."),
|
|
# port_data['network_id'])
|
|
# return
|
|
# ---------------------------------------------------------------------
|
|
|
|
nsx_switch_id, nsx_port_id = nsx_utils.get_nsx_switch_and_port_id(
|
|
context.session, self.cluster, port_data['id'])
|
|
if not nsx_port_id:
|
|
LOG.debug(_("Port '%s' was already deleted on NSX platform"), id)
|
|
return
|
|
# TODO(bgh): if this is a bridged network and the lswitch we just got
|
|
# back will have zero ports after the delete we should garbage collect
|
|
# the lswitch.
|
|
try:
|
|
switchlib.delete_port(self.cluster, nsx_switch_id, nsx_port_id)
|
|
LOG.debug(_("_nsx_delete_port completed for port %(port_id)s "
|
|
"on network %(net_id)s"),
|
|
{'port_id': port_data['id'],
|
|
'net_id': port_data['network_id']})
|
|
except n_exc.NotFound:
|
|
LOG.warning(_("Port %s not found in NSX"), port_data['id'])
|
|
|
|
|
|
def noop(*args, **kwargs):
|
|
pass
|