From 792a6a01039c9cbb1a175ff38c2ddbd535c141da Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Mon, 20 Nov 2017 00:28:37 -0800 Subject: [PATCH] NSX TVD: V, T and simple DVS Coexist in the same plugin Introduce a plugin that can work with all of the VC and NSX offerings under the same umbrella of a single plugin. Co-Authored-By: Adit Sarfaty Change-Id: I0449d64e3cf79b7a3a846dacba95e8854d53bdf8 --- devstack/lib/nsx_common | 2 - devstack/lib/vmware_nsx_tvd | 253 ++++++++ devstack/lib/vmware_nsx_v | 1 + devstack/lib/vmware_nsx_v3 | 1 + devstack/plugin.sh | 22 + setup.cfg | 4 + vmware_nsx/common/availability_zones.py | 19 +- vmware_nsx/common/config.py | 20 + vmware_nsx/common/managers.py | 9 +- vmware_nsx/db/db.py | 62 +- .../alembic_migrations/versions/EXPAND_HEAD | 2 +- .../queens/expand/9799427fc0e1_nsx_tv_map.py | 41 ++ vmware_nsx/db/nsx_models.py | 7 + vmware_nsx/extensions/projectpluginmap.py | 133 ++++ vmware_nsx/osc/v2/project_plugin_map.py | 131 ++++ vmware_nsx/plugin.py | 2 + vmware_nsx/plugins/common/plugin.py | 4 + vmware_nsx/plugins/dvs/plugin.py | 9 + vmware_nsx/plugins/nsx/__init__.py | 0 vmware_nsx/plugins/nsx/plugin.py | 574 ++++++++++++++++++ vmware_nsx/plugins/nsx/utils.py | 24 + .../plugins/nsx_v/availability_zones.py | 5 +- vmware_nsx/plugins/nsx_v/plugin.py | 90 ++- .../plugins/nsx_v3/availability_zones.py | 5 +- vmware_nsx/plugins/nsx_v3/plugin.py | 44 +- 25 files changed, 1403 insertions(+), 61 deletions(-) create mode 100644 devstack/lib/vmware_nsx_tvd create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/queens/expand/9799427fc0e1_nsx_tv_map.py create mode 100644 vmware_nsx/extensions/projectpluginmap.py create mode 100644 vmware_nsx/osc/v2/project_plugin_map.py create mode 100644 vmware_nsx/plugins/nsx/__init__.py create mode 100644 vmware_nsx/plugins/nsx/plugin.py create mode 100644 vmware_nsx/plugins/nsx/utils.py diff --git a/devstack/lib/nsx_common b/devstack/lib/nsx_common index 09d6d9db5d..adfe032432 100644 --- a/devstack/lib/nsx_common +++ b/devstack/lib/nsx_common @@ -33,7 +33,6 @@ function nsxv_configure_service { if [[ "$NSX_L2GW_DRIVER" != "" ]]; then iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER fi - iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv_dns _nsxv_ini_set password "$NSXV_PASSWORD" _nsxv_ini_set user "$NSXV_USER" _nsxv_ini_set vdn_scope_id "$NSXV_VDN_SCOPE_ID" @@ -104,7 +103,6 @@ function nsxv3_configure_service { if [[ "$NSX_L2GW_DRIVER" != "" ]]; then iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER fi - iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv3_dns _nsxv3_ini_set nsx_api_user $NSX_USER _nsxv3_ini_set nsx_api_password $NSX_PASSWORD _nsxv3_ini_set retries $NSX_RETRIES diff --git a/devstack/lib/vmware_nsx_tvd b/devstack/lib/vmware_nsx_tvd new file mode 100644 index 0000000000..ac5bf48004 --- /dev/null +++ b/devstack/lib/vmware_nsx_tvd @@ -0,0 +1,253 @@ +#!/bin/bash + +# Copyright 2015 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. + + +# Neutron VMware NSX plugin +# ------------------------- + +# Settings previously defined in devstack:lib/neutron-legacy +NEUTRON_CONF_DIR=/etc/neutron +export NEUTRON_TEST_CONFIG_FILE=${NEUTRON_TEST_CONFIG_FILE:-"$NEUTRON_CONF_DIR/debug.ini"} +Q_DHCP_CONF_FILE=$NEUTRON_CONF_DIR/dhcp_agent.ini + +# The interface which has connectivity to the NSX Gateway uplink +NSX_GATEWAY_NETWORK_INTERFACE=${NSX_GATEWAY_NETWORK_INTERFACE:-} + +# Override default 'True' in devstack:lib/neutron_plugins/services/l3 +Q_USE_PROVIDERNET_FOR_PUBLIC=False + +# Native support from platform +NATIVE_DHCP_METADATA=${NATIVE_DHCP_METADATA:-True} +NATIVE_METADATA_ROUTE=${NATIVE_METADATA_ROUTE:-169.254.169.254/31} +METADATA_PROXY_SHARED_SECRET=${METADATA_PROXY_SHARED_SECRET:-} + +# Default AZ +NSX_DEFAULT_AZ=${NSX_DEFAULT_AZ:-defaultv3} + +# Save trace setting +NSX_XTRACE=$(set +o | grep xtrace) +set +o xtrace + +# File to store client certificate and PK +CLIENT_CERT_FILE=${DEST}/data/neutron/client.pem + +source $TOP_DIR/lib/neutron_plugins/ovs_base +dir=${GITDIR['vmware-nsx']}/devstack +source $dir/lib/nsx_common + + +function _version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } + +function _ovsdb_connection { + managers=(${NSX_MANAGER//,/ }) + NSX_MGR_IP=${managers[0]} + NSX_VER=$(curl -1 -s -k -u "$NSX_USER:$NSX_PASSWORD" -H 'Accept: application/json' https://$NSX_MGR_IP/api/v1/node | python -c 'import sys, json; print json.load(sys.stdin)["node_version"][:5]') + if [ $(_version $NSX_VER) -ge $(_version 1.1.0) ]; then + echo "unix:/var/run/vmware/nsx-agent/nsxagent_ovsdb.sock" + else + echo "tcp:127.0.0.1:6632" + fi +} + +function setup_integration_bridge { + die_if_not_set $LINENO NSX_MANAGER "NSX_MANAGER has not been set!" + die_if_not_set $LINENO NSX_USER "NSX_USER has not been set!" + die_if_not_set $LINENO NSX_PASSWORD "NSX_PASSWORD has not been set!" + # Ensure that the OVS params are set for the OVS utils + iniset $NEUTRON_CONF DEFAULT ovs_integration_bridge $OVS_BRIDGE + iniset $NEUTRON_CONF OVS ovsdb_connection $(_ovsdb_connection) + iniset $NEUTRON_CONF OVS ovsdb_interface vsctl + _neutron_ovs_base_setup_bridge $OVS_BRIDGE + sudo ovs-vsctl set bridge $OVS_BRIDGE external_ids:bridge-id=nsx-managed + sudo ovs-vsctl set-manager $(_ovsdb_connection) +} + +function is_neutron_ovs_base_plugin { + # This allows the deployer to decide whether devstack should install OVS. + # By default, we install OVS, to change this behavior add "OVS_BASE=1" to your localrc file. + # Note: Any KVM compute must have OVS installed on it. + return ${OVS_BASE:-0} +} + +function neutron_plugin_create_nova_conf { + if [[ "$VIRT_DRIVER" != 'vsphere' ]]; then + # if n-cpu or octavia is enabled, then setup integration bridge + if is_service_enabled n-cpu || is_service_enabled octavia ; then + setup_integration_bridge + if is_service_enabled n-cpu ; then + iniset $NOVA_CONF neutron ovs_bridge $OVS_BRIDGE + fi + fi + fi + # if n-api is enabled, then setup the metadata_proxy_shared_secret + if is_service_enabled n-api; then + iniset $NOVA_CONF neutron service_metadata_proxy True + if [[ "$NATIVE_DHCP_METADATA" == "True" ]]; then + iniset $NOVA_CONF neutron metadata_proxy_shared_secret $METADATA_PROXY_SHARED_SECRET + if [[ "$METADATA_PROXY_USE_HTTPS" == "True" ]]; then + iniset $NOVA_CONF DEFAULT enabled_ssl_apis metadata + if [[ "$METADATA_PROXY_CERT_FILE" != "" ]]; then + iniset $NOVA_CONF wsgi ssl_cert_file $METADATA_PROXY_CERT_FILE + fi + if [[ "$METADATA_PROXY_PRIV_KEY_FILE" != "" ]]; then + iniset $NOVA_CONF wsgi ssl_key_file $METADATA_PROXY_PRIV_KEY_FILE + fi + fi + fi + fi +} + +function neutron_plugin_install_agent_packages { + # VMware NSX Plugin does not run q-agt, but it currently needs dhcp and metadata agents + _neutron_ovs_base_install_agent_packages +} + +function neutron_plugin_configure_common { + Q_PLUGIN_CONF_PATH=etc/neutron/plugins/vmware + Q_PLUGIN_CONF_FILENAME=nsx.ini + Q_PLUGIN_SRC_CONF_PATH=vmware-nsx/etc + VMWARE_NSX_DIR=vmware-nsx + # Uses oslo config generator to generate sample configuration file + (cd $DEST/$VMWARE_NSX_DIR && exec ./tools/generate_config_file_samples.sh) + mkdir -p /$Q_PLUGIN_CONF_PATH + cp $DEST/$Q_PLUGIN_SRC_CONF_PATH/nsx.ini.sample /$Q_PLUGIN_CONF_PATH/$Q_PLUGIN_CONF_FILENAME + sudo install -d -o $STACK_USER $NEUTRON_CONF_DIR/policy.d + cp -vr $DEST/$Q_PLUGIN_SRC_CONF_PATH/policy.d $NEUTRON_CONF_DIR/policy.d + Q_PLUGIN_CLASS="vmware_nsxtvd" +} + +function neutron_plugin_configure_debug_command { + sudo ovs-vsctl --no-wait -- --may-exist add-br $PUBLIC_BRIDGE + iniset $NEUTRON_TEST_CONFIG_FILE DEFAULT external_network_bridge "$PUBLIC_BRIDGE" +} + +function neutron_plugin_configure_dhcp_agent { + setup_integration_bridge + iniset $Q_DHCP_CONF_FILE DEFAULT enable_isolated_metadata True + iniset $Q_DHCP_CONF_FILE DEFAULT enable_metadata_network True + iniset $Q_DHCP_CONF_FILE DEFAULT ovs_use_veth True + iniset $Q_DHCP_CONF_FILE DEFAULT ovs_integration_bridge $OVS_BRIDGE + iniset $Q_DHCP_CONF_FILE OVS ovsdb_connection $(_ovsdb_connection) + iniset $Q_DHCP_CONF_FILE OVS ovsdb_interface vsctl +} + +function neutron_plugin_configure_l3_agent { + # VMware NSX plugin does not run L3 agent + die $LINENO "q-l3 should not be executed with VMware NSX plugin!" +} + +function neutron_plugin_configure_plugin_agent { + # VMware NSX plugin does not run L2 agent + die $LINENO "q-agt must not be executed with VMware NSX plugin!" +} + +function neutron_plugin_configure_service { + nsxv3_configure_service + nsxv_configure_service + iniset /$Q_PLUGIN_CONF_FILE nsx_tvd nsx_v_extension_drivers vmware_nsxv_dns + iniset /$Q_PLUGIN_CONF_FILE nsx_tvd nsx_v3_extension_drivers vmware_nsxv3_dns + iniset /$Q_PLUGIN_CONF_FILE DEFAULT default_availability_zones $NSX_DEFAULT_AZ +} + +function neutron_plugin_setup_interface_driver { + local conf_file=$1 + iniset $conf_file DEFAULT interface_driver neutron.agent.linux.interface.OVSInterfaceDriver +} + +function neutron_plugin_check_adv_test_requirements { + is_service_enabled q-dhcp && return 0 +} + + +function init_vmware_nsx_tvd { + if (is_service_enabled q-svc || is_service_enabled neutron-api) && [[ "$NATIVE_DHCP_METADATA" == "True" ]]; then + if ! is_set DHCP_PROFILE_UUID; then + die $LINENO "DHCP profile needs to be configured!" + fi + if ! is_set METADATA_PROXY_UUID; then + die $LINENO "Metadata proxy needs to be configured!" + fi + if is_service_enabled q-dhcp q-meta; then + die $LINENO "Native support does not require DHCP and Metadata agents!" + fi + fi + # Generate client certificate + if [[ "$NSX_USE_CLIENT_CERT_AUTH" == "True" ]]; then + nsxadmin -o generate -r certificate + fi + if ! is_set NSX_GATEWAY_NETWORK_INTERFACE; then + echo "NSX_GATEWAY_NETWORK_INTERFACE not set not configuring routes" + return + fi + + if ! is_set NSX_GATEWAY_NETWORK_CIDR; then + NSX_GATEWAY_NETWORK_CIDR=$PUBLIC_NETWORK_GATEWAY/${FLOATING_RANGE#*/} + echo "The IP address to set on $PUBLIC_BRIDGE was not specified. " + echo "Defaulting to $NSX_GATEWAY_NETWORK_CIDR" + fi + # Make sure the interface is up, but not configured + sudo ip link set $NSX_GATEWAY_NETWORK_INTERFACE up + # Save and then flush the IP addresses on the interface + addresses=$(ip addr show dev $NSX_GATEWAY_NETWORK_INTERFACE | grep inet | awk {'print $2'}) + sudo ip addr flush $NSX_GATEWAY_NETWORK_INTERFACE + # Use the PUBLIC Bridge to route traffic to the NSX gateway + # NOTE(armando-migliaccio): if running in a nested environment this will work + # only with mac learning enabled, portsecurity and security profiles disabled + # The public bridge might not exist for the NSX plugin if Q_USE_DEBUG_COMMAND is off + # Try to create it anyway + sudo ovs-vsctl --may-exist add-br $PUBLIC_BRIDGE + sudo ovs-vsctl --may-exist add-port $PUBLIC_BRIDGE $NSX_GATEWAY_NETWORK_INTERFACE + # Flush all existing addresses on public bridge + sudo ip addr flush dev $PUBLIC_BRIDGE + nsx_gw_net_if_mac=$(ip link show $NSX_GATEWAY_NETWORK_INTERFACE | awk '/ether/ {print $2}') + sudo ip link set address $nsx_gw_net_if_mac dev $PUBLIC_BRIDGE + for address in $addresses; do + sudo ip addr add dev $PUBLIC_BRIDGE $address + done + sudo ip addr add dev $PUBLIC_BRIDGE $NSX_GATEWAY_NETWORK_CIDR + sudo ip link set $PUBLIC_BRIDGE up +} + +function stop_vmware_nsx_tvd { + # Clean client certificate if exists + nsxadmin -o clean -r certificate + + if ! is_set NSX_GATEWAY_NETWORK_INTERFACE; then + echo "NSX_GATEWAY_NETWORK_INTERFACE was not configured." + return + fi + + if ! is_set NSX_GATEWAY_NETWORK_CIDR; then + NSX_GATEWAY_NETWORK_CIDR=$PUBLIC_NETWORK_GATEWAY/${FLOATING_RANGE#*/} + echo "The IP address expected on $PUBLIC_BRIDGE was not specified. " + echo "Defaulting to "$NSX_GATEWAY_NETWORK_CIDR + fi + sudo ip addr del $NSX_GATEWAY_NETWORK_CIDR dev $PUBLIC_BRIDGE + # Save and then flush remaining addresses on the interface + addresses=$(ip addr show dev $PUBLIC_BRIDGE | grep inet | awk {'print $2'}) + sudo ip addr flush $PUBLIC_BRIDGE + # Try to detach physical interface from PUBLIC_BRIDGE + sudo ovs-vsctl del-port $NSX_GATEWAY_NETWORK_INTERFACE + # Restore addresses on NSX_GATEWAY_NETWORK_INTERFACE + for address in $addresses; do + sudo ip addr add dev $NSX_GATEWAY_NETWORK_INTERFACE $address + done +} + +# Restore xtrace +$NSX_XTRACE diff --git a/devstack/lib/vmware_nsx_v b/devstack/lib/vmware_nsx_v index 98ba24f4a6..fa527d16b9 100644 --- a/devstack/lib/vmware_nsx_v +++ b/devstack/lib/vmware_nsx_v @@ -84,6 +84,7 @@ function neutron_plugin_configure_plugin_agent { function neutron_plugin_configure_service { nsxv_configure_service + iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv_dns } function neutron_plugin_setup_interface_driver { diff --git a/devstack/lib/vmware_nsx_v3 b/devstack/lib/vmware_nsx_v3 index c1097000cc..e78a10ee80 100644 --- a/devstack/lib/vmware_nsx_v3 +++ b/devstack/lib/vmware_nsx_v3 @@ -155,6 +155,7 @@ function neutron_plugin_configure_plugin_agent { function neutron_plugin_configure_service { nsxv3_configure_service + iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv3_dns } function neutron_plugin_setup_interface_driver { diff --git a/devstack/plugin.sh b/devstack/plugin.sh index a5f9290c8f..e2fd9ee950 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -71,6 +71,28 @@ elif [[ $Q_PLUGIN == 'vmware_nsx_v3' ]]; then python $dir/tools/nsxv3_cleanup.py --mgr-ip $NSX_MANAGER --user $NSX_USER --password $NSX_PASSWORD fi fi +elif [[ $Q_PLUGIN == 'vmware_nsx_tvd' ]]; then + source $dir/lib/vmware_nsx_tvd + if [[ "$1" == "stack" && "$2" == "post-config" ]]; then + init_vmware_nsx_tvd + elif [[ "$1" == "unstack" ]]; then + db_connection=$(iniget $NEUTRON_CONF database connection) + stop_vmware_nsx_v3 + # only clean up when q-svc (legacy support) or neutron-api is enabled + if is_service_enabled q-svc || is_service_enabled neutron-api; then + NSX_MANAGER=${NSX_MANAGERS:-$NSX_MANAGER} + IFS=',' + NSX_MANAGER=($NSX_MANAGER) + unset IFS + python $dir/tools/nsxv3_cleanup.py --mgr-ip $NSX_MANAGER --user $NSX_USER --password $NSX_PASSWORD --db-connection $db_connection + python $dir/tools/nsxv_cleanup.py --vsm-ip ${NSXV_MANAGER_URI/https:\/\/} --user $NSXV_USER --password $NSXV_PASSWORD --db-connection $db_connection + fi + elif [[ "$1" == 'clean' ]]; then + if is_service_enabled q-svc || is_service_enabled neutron-api; then + python $dir/tools/nsxv3_cleanup.py --mgr-ip $NSX_MANAGER --user $NSX_USER --password $NSX_PASSWORD + python $dir/tools/nsxv_cleanup.py --vsm-ip ${NSXV_MANAGER_URI/https:\/\/} --user $NSXV_USER --password $NSXV_PASSWORD + fi + fi elif [[ $Q_PLUGIN == 'vmware_dvs' ]]; then source $dir/lib/vmware_dvs fi diff --git a/setup.cfg b/setup.cfg index bb01ad6f5d..32ed8cd3b4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ neutron.core_plugins = vmware_nsxv = vmware_nsx.plugin:NsxVPlugin vmware_nsxv3 = vmware_nsx.plugin:NsxV3Plugin vmware_dvs = vmware_nsx.plugin:NsxDvsPlugin + vmware_nsxtvd = vmware_nsx.plugin:NsxTVDPlugin firewall_drivers = vmware_nsxv_edge = vmware_nsx.services.fwaas.nsx_v.edge_fwaas_driver:EdgeFwaasDriver vmware_nsxv3_edge = vmware_nsx.services.fwaas.nsx_v3.edge_fwaas_driver_v1:EdgeFwaasV3DriverV1 @@ -68,6 +69,9 @@ openstack.nsxclient.v2 = security_group_set = vmware_nsx.osc.v2.security_group:NsxSetSecurityGroup subnet_create = vmware_nsx.osc.v2.subnet:NsxCreateSubnet subnet_set = vmware_nsx.osc.v2.subnet:NsxSetSubnet + project_plugin_create = vmware_nsx.osc.v2.project_plugin_map:CreateProjectPluginMap + project_plugin_show = vmware_nsx.osc.v2.project_plugin_map:ShowProjectPluginMap + project_plugin_list = vmware_nsx.osc.v2.project_plugin_map:ListProjectPluginMap [build_sphinx] source-dir = doc/source diff --git a/vmware_nsx/common/availability_zones.py b/vmware_nsx/common/availability_zones.py index 71c138d8a4..e5c330dba7 100644 --- a/vmware_nsx/common/availability_zones.py +++ b/vmware_nsx/common/availability_zones.py @@ -78,7 +78,7 @@ class ConfiguredAvailabilityZones(object): default_name = DEFAULT_NAME - def __init__(self, az_conf, az_class): + def __init__(self, az_conf, az_class, validate_default=True): self.availability_zones = {} # Add the configured availability zones @@ -100,12 +100,17 @@ class ConfiguredAvailabilityZones(object): reason=_("The NSX plugin supports only 1 default AZ")) default_az_name = cfg.CONF.default_availability_zones[0] if (default_az_name not in self.availability_zones): - raise nsx_exc.NsxInvalidConfiguration( - opt_name="default_availability_zones", - opt_value=cfg.CONF.default_availability_zones, - reason=_("The default AZ is not defined in the NSX " - "plugin")) - self._default_az = self.availability_zones[default_az_name] + if validate_default: + raise nsx_exc.NsxInvalidConfiguration( + opt_name="default_availability_zones", + opt_value=cfg.CONF.default_availability_zones, + reason=_("The default AZ is not defined in the NSX " + "plugin")) + else: + self._default_az = self.availability_zones[ + self.default_name] + else: + self._default_az = self.availability_zones[default_az_name] else: self._default_az = self.availability_zones[self.default_name] diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 06001cb7c6..de8ea70fb1 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -813,12 +813,26 @@ nsxv3_az_opts = [ "on router ports.")), ] +nsx_tvd_opts = [ + cfg.ListOpt('nsx_v_extension_drivers', + default=[], + help=_("An ordered list of NSX-V extension driver " + "entrypoints to be loaded from the " + "vmware_nsx.extension_drivers namespace.")), + cfg.ListOpt('nsx_v3_extension_drivers', + default=[], + help=_("An ordered list of NSX-T extension driver " + "entrypoints to be loaded from the " + "vmware_nsx.extension_drivers namespace.")), +] + # Register the configuration options cfg.CONF.register_opts(connection_opts) cfg.CONF.register_opts(cluster_opts) cfg.CONF.register_opts(nsx_common_opts) cfg.CONF.register_opts(nsx_v3_opts, group="nsx_v3") cfg.CONF.register_opts(nsxv_opts, group="nsxv") +cfg.CONF.register_opts(nsx_tvd_opts, group="nsx_tvd") cfg.CONF.register_opts(base_opts, group="NSX") cfg.CONF.register_opts(sync_opts, group="NSX_SYNC") @@ -891,3 +905,9 @@ def validate_nsxv_config_options(): error = _("dvs host/vcenter credentials must be defined to use " "dvs features") raise nsx_exc.NsxPluginException(err_msg=error) + + +def validate_nsx_config_options(): + if cfg.CONF.nsx_extension_drivers: + error = _("nsx_extension_drivers should not be configured!") + raise nsx_exc.NsxPluginException(err_msg=error) diff --git a/vmware_nsx/common/managers.py b/vmware_nsx/common/managers.py index 9c4a493dac..ea460a0301 100644 --- a/vmware_nsx/common/managers.py +++ b/vmware_nsx/common/managers.py @@ -24,15 +24,18 @@ LOG = log.getLogger(__name__) class ExtensionManager(stevedore.named.NamedExtensionManager): """Manage extension drivers using drivers.""" - def __init__(self): + def __init__(self, extension_drivers=None): # Ordered list of extension drivers, defining # the order in which the drivers are called. self.ordered_ext_drivers = [] + if extension_drivers is None: + extension_drivers = cfg.CONF.nsx_extension_drivers + LOG.info("Configured extension driver names: %s", - cfg.CONF.nsx_extension_drivers) + extension_drivers) super(ExtensionManager, self).__init__('vmware_nsx.extension_drivers', - cfg.CONF.nsx_extension_drivers, + extension_drivers, invoke_on_load=True, name_order=True) LOG.info("Loaded extension driver names: %s", self.names()) diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py index 84c9b95086..bc2f2d540b 100644 --- a/vmware_nsx/db/db.py +++ b/vmware_nsx/db/db.py @@ -20,6 +20,7 @@ from sqlalchemy.orm import exc from oslo_db import exception as db_exc from oslo_log import log as logging from oslo_utils import excutils +from oslo_utils import uuidutils import neutron.db.api as db @@ -289,16 +290,20 @@ def get_neutron_from_nsx_router_id(session, nsx_router_id): LOG.debug("Couldn't find router with nsx id %s", nsx_router_id) -def get_nsx_security_group_id(session, neutron_id): +def get_nsx_security_group_id(session, neutron_id, moref=False): """Return the id of a security group in the NSX backend. Note: security groups are called 'security profiles' in NSX """ try: - mapping = (session.query(nsx_models.NeutronNsxSecurityGroupMapping). - filter_by(neutron_id=neutron_id). - one()) - return mapping['nsx_id'] + mappings = (session.query(nsx_models.NeutronNsxSecurityGroupMapping). + filter_by(neutron_id=neutron_id). + all()) + for mapping in mappings: + if moref and not uuidutils.is_uuid_like(mapping['nsx_id']): + return mapping['nsx_id'] + if not moref and uuidutils.is_uuid_like(mapping['nsx_id']): + return mapping['nsx_id'] except exc.NoResultFound: LOG.debug("NSX identifiers for neutron security group %s not yet " "stored in Neutron DB", neutron_id) @@ -437,14 +442,29 @@ def save_sg_mappings(context, sg_id, nsgroup_id, section_id): nsx_id=nsgroup_id)) -def get_sg_mappings(session, sg_id): - nsgroup_mapping = session.query( +def get_sg_mappings(session, sg_id, moref=False): + nsgroup_mappings = session.query( nsx_models.NeutronNsxSecurityGroupMapping - ).filter_by(neutron_id=sg_id).one() - section_mapping = session.query( + ).filter_by(neutron_id=sg_id).all() + nsgroup_mapping = section_mapping = None + for mapping in nsgroup_mappings: + if moref and not uuidutils.is_uuid_like(mapping['nsx_id']): + nsgroup_mapping = mapping['nsx_id'] + break + if not moref and uuidutils.is_uuid_like(mapping['nsx_id']): + nsgroup_mapping = mapping['nsx_id'] + break + section_mappings = session.query( nsx_models.NeutronNsxFirewallSectionMapping - ).filter_by(neutron_id=sg_id).one() - return nsgroup_mapping.nsx_id, section_mapping.nsx_id + ).filter_by(neutron_id=sg_id).all() + for mapping in section_mappings: + if moref and not uuidutils.is_uuid_like(mapping['nsx_id']): + section_mapping = mapping['nsx_id'] + break + if not moref and uuidutils.is_uuid_like(mapping['nsx_id']): + section_mapping = mapping['nsx_id'] + break + return nsgroup_mapping, section_mapping def get_sg_rule_mapping(session, rule_id): @@ -652,3 +672,23 @@ def get_nsx_lbaas_l7policy_binding(session, l7policy_id): def delete_nsx_lbaas_l7policy_binding(session, l7policy_id): return (session.query(nsx_models.NsxLbaasL7Policy). filter_by(l7policy_id=l7policy_id).delete()) + + +def add_project_plugin_mapping(session, project, plugin): + with session.begin(subtransactions=True): + binding = nsx_models.NsxProjectPluginMapping( + project=project, plugin=plugin) + session.add(binding) + return binding + + +def get_project_plugin_mapping(session, project): + try: + return session.query(nsx_models.NsxProjectPluginMapping).filter_by( + project=project).one() + except exc.NoResultFound: + return + + +def get_project_plugin_mappings(session): + return session.query(nsx_models.NsxProjectPluginMapping).all() diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD index d3fabba9d4..b7146d2bc4 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -ea7a72ab9643 +9799427fc0e1 diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/queens/expand/9799427fc0e1_nsx_tv_map.py b/vmware_nsx/db/migration/alembic_migrations/versions/queens/expand/9799427fc0e1_nsx_tv_map.py new file mode 100644 index 0000000000..ebe337ac67 --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/queens/expand/9799427fc0e1_nsx_tv_map.py @@ -0,0 +1,41 @@ +# 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. + +"""nsx map project to plugin + +Revision ID: 9799427fc0e1 +Revises: ea7a72ab9643 +Create Date: 2017-06-12 16:59:48.021909 + +""" + +# revision identifiers, used by Alembic. +revision = '9799427fc0e1' +down_revision = 'ea7a72ab9643' + +from alembic import op +import sqlalchemy as sa + +plugin_type_enum = sa.Enum('dvs', 'nsx-v', 'nsx-t', + name='nsx_plugin_type') + + +def upgrade(): + op.create_table( + 'nsx_project_plugin_mappings', + sa.Column('project', sa.String(36), nullable=False), + sa.Column('plugin', plugin_type_enum, nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('project')) diff --git a/vmware_nsx/db/nsx_models.py b/vmware_nsx/db/nsx_models.py index 76d7b1357b..b7c2a9acf0 100644 --- a/vmware_nsx/db/nsx_models.py +++ b/vmware_nsx/db/nsx_models.py @@ -481,3 +481,10 @@ class NsxLbaasL7Policy(model_base.BASEV2, models.TimestampMixin): primary_key=True) lb_rule_id = sa.Column(sa.String(36), nullable=False) lb_vs_id = sa.Column(sa.String(36), nullable=False) + + +class NsxProjectPluginMapping(model_base.BASEV2, models.TimestampMixin): + """Stores the mapping between the neutron plugin and the project id""" + __tablename__ = 'nsx_project_plugin_mappings' + project = sa.Column(sa.String(36), primary_key=True) + plugin = sa.Column(sa.Enum('dvs', 'nsx-v', 'nsx-t'), nullable=False) diff --git a/vmware_nsx/extensions/projectpluginmap.py b/vmware_nsx/extensions/projectpluginmap.py new file mode 100644 index 0000000000..9c50616515 --- /dev/null +++ b/vmware_nsx/extensions/projectpluginmap.py @@ -0,0 +1,133 @@ +# Copyright 2017 VMware. 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 abc + +from neutron.api.v2 import resource_helper +from neutron_lib.api import extensions +from neutron_lib.db import constants as db_const +from neutron_lib import exceptions as nexception + +from vmware_nsx._i18n import _ + +PROJECT_PLUGIN_RESOURCE_NAME = "project_plugin_map" +# Use dash for alias and collection name +EXT_ALIAS = PROJECT_PLUGIN_RESOURCE_NAME.replace('_', '-') +PROJECT_PLUGINS = "project_plugin_maps" + + +class NsxPlugins(object): + NSX_V = 'nsx-v' + NSX_T = 'nsx-t' + DVS = 'dvs' + + +VALID_TYPES = [NsxPlugins.NSX_V, + NsxPlugins.NSX_T, + NsxPlugins.DVS] + +RESOURCE_ATTRIBUTE_MAP = { + PROJECT_PLUGINS: { + 'id': { + 'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + # project is the id of the project mapped by this entry + 'project': { + 'allow_post': True, 'allow_put': False, 'is_visible': True}, + 'plugin': { + 'allow_post': True, 'allow_put': False, 'is_visible': True, + 'validate': {'type:values': VALID_TYPES}}, + # tenant id is the id of tenant/project owning this entry + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': { + 'type:string': db_const.PROJECT_ID_FIELD_SIZE}, + 'required_by_policy': True, + 'is_visible': True}, + } +} + + +class Projectpluginmap(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return "Project Plugin Mapping" + + @classmethod + def get_alias(cls): + return EXT_ALIAS + + @classmethod + def get_description(cls): + return "Per Project Core Plugin." + + @classmethod + def get_updated(cls): + return "2017-12-05T00:00:00-00:00" + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + plural_mappings = resource_helper.build_plural_mappings( + {}, RESOURCE_ATTRIBUTE_MAP) + member_actions = {} + return resource_helper.build_resource_info(plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + None, + action_map=member_actions, + register_quota=True, + translate_name=True) + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + +class ProjectPluginReadOnly(nexception.NotAuthorized): + message = _("Project Plugin map entries cannot be modified.") + + +class ProjectPluginAlreadyExists(nexception.Conflict): + message = _("Project Plugin map already exists for project " + "%(project_id)s.") + + +class ProjectPluginMapPluginBase(object): + + @abc.abstractmethod + def create_project_plugin_map(self, context, project_plugin_map): + pass + + @abc.abstractmethod + def update_project_plugin_map(self, context, id, project_plugin_map): + raise ProjectPluginReadOnly() + + @abc.abstractmethod + def get_project_plugin_map(self, context, id, fields=None): + pass + + @abc.abstractmethod + def delete_project_plugin_map(self, context, id): + # TODO(asarfaty): delete when the project is deleted? + raise ProjectPluginReadOnly() + + @abc.abstractmethod + def get_project_plugin_maps(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + pass diff --git a/vmware_nsx/osc/v2/project_plugin_map.py b/vmware_nsx/osc/v2/project_plugin_map.py new file mode 100644 index 0000000000..ecd50ae0fe --- /dev/null +++ b/vmware_nsx/osc/v2/project_plugin_map.py @@ -0,0 +1,131 @@ +# 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. +# + +"""Project Plugin mapping action implementations""" + +from openstack.network import network_service +from openstack import resource2 as resource +from openstackclient.i18n import _ +from osc_lib.command import command +from osc_lib import utils + +project_plugin_maps_path = "/project-plugin-maps" + + +class ProjectPluginMap(resource.Resource): + resource_key = 'project_plugin_map' + resources_key = 'project_plugin_maps' + base_path = '/project-plugin-maps' + service = network_service.NetworkService() + + # capabilities + allow_create = True + allow_get = True + allow_update = False + allow_delete = False + allow_list = True + + _query_mapping = resource.QueryParameters( + 'plugin', 'project', 'tenant_id') + + # Properties + id = resource.Body('id') + project = resource.Body('project') + plugin = resource.Body('plugin') + tenant_id = resource.Body('tenant_id') + + +def _get_columns(item): + columns = ['project', 'plugin'] + return columns, columns + + +def _get_attrs(parsed_args): + attrs = {} + if parsed_args.project is not None: + attrs['project'] = parsed_args.project + + if parsed_args.plugin is not None: + attrs['plugin'] = parsed_args.plugin + return attrs + + +class CreateProjectPluginMap(command.ShowOne): + _description = _("Create project plugin map") + + def get_parser(self, prog_name): + parser = super(CreateProjectPluginMap, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar="", + help=_("project") + ) + parser.add_argument( + '--plugin', + metavar="", + required=True, + help=_('Plugin.)') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(parsed_args) + obj = client._create(ProjectPluginMap, **attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) + + +class ListProjectPluginMap(command.Lister): + _description = _("List project plugin mappings") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'project', + 'plugin' + ) + column_headers = ( + 'Project ID', + 'Plugin', + ) + + client = self.app.client_manager.network + data = client._list(ProjectPluginMap) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowProjectPluginMap(command.ShowOne): + _description = _("Display project plugins mapping") + + def get_parser(self, prog_name): + parser = super(ShowProjectPluginMap, self).get_parser(prog_name) + parser.add_argument( + 'id', + metavar='', + help=_('id') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client._get(ProjectPluginMap, parsed_args.id) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/vmware_nsx/plugin.py b/vmware_nsx/plugin.py index 022e99378f..18e9d8cfc8 100644 --- a/vmware_nsx/plugin.py +++ b/vmware_nsx/plugin.py @@ -21,6 +21,7 @@ from neutron.db.models import securitygroup # noqa from vmware_nsx.plugins.dvs import plugin as dvs +from vmware_nsx.plugins.nsx import plugin as nsx from vmware_nsx.plugins.nsx_mh import plugin as nsx_mh from vmware_nsx.plugins.nsx_v import plugin as nsx_v from vmware_nsx.plugins.nsx_v3 import plugin as nsx_v3 @@ -29,3 +30,4 @@ NsxDvsPlugin = dvs.NsxDvsV2 NsxPlugin = nsx_mh.NsxPluginV2 NsxVPlugin = nsx_v.NsxVPluginV2 NsxV3Plugin = nsx_v3.NsxV3Plugin +NsxTVDPlugin = nsx.NsxTVDPlugin diff --git a/vmware_nsx/plugins/common/plugin.py b/vmware_nsx/plugins/common/plugin.py index a323fc4adf..78192a0f5d 100644 --- a/vmware_nsx/plugins/common/plugin.py +++ b/vmware_nsx/plugins/common/plugin.py @@ -47,6 +47,10 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, address_scope_db.AddressScopeDbMixin): """Common methods for NSX-V and NSX-V3 plugins""" + @property + def plugin_type(self): + return "Unknown" + @staticmethod @resource_extend.extends([net_def.COLLECTION_NAME]) def _ext_extend_network_dict(result, netdb): diff --git a/vmware_nsx/plugins/dvs/plugin.py b/vmware_nsx/plugins/dvs/plugin.py index dee88f0096..d9ad2c88f7 100644 --- a/vmware_nsx/plugins/dvs/plugin.py +++ b/vmware_nsx/plugins/dvs/plugin.py @@ -61,6 +61,7 @@ from vmware_nsx.db import nsxv_db from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes from vmware_nsx.dvs import dvs from vmware_nsx.dvs import dvs_utils +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.plugins.common import plugin as nsx_plugin_common LOG = logging.getLogger(__name__) @@ -115,6 +116,14 @@ class NsxDvsV2(addr_pair_db.AllowedAddressPairsMixin, self._dvs = dvs.SingleDvsManager() self.setup_dhcpmeta_access() + @staticmethod + def plugin_type(): + return projectpluginmap.NsxPlugins.DVS + + @staticmethod + def is_tvd_plugin(): + return False + @staticmethod @resource_extend.extends([port_def.COLLECTION_NAME]) def _extend_port_dict_binding(result, portdb): diff --git a/vmware_nsx/plugins/nsx/__init__.py b/vmware_nsx/plugins/nsx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/plugins/nsx/plugin.py b/vmware_nsx/plugins/nsx/plugin.py new file mode 100644 index 0000000000..d665e97a79 --- /dev/null +++ b/vmware_nsx/plugins/nsx/plugin.py @@ -0,0 +1,574 @@ +# Copyright 2014 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_lib.api.definitions import network as net_def +from neutron_lib.api.definitions import port as port_def +from neutron_lib.api.definitions import subnet as subnet_def +from neutron_lib import context as n_context +from neutron_lib.plugins import directory +from oslo_log import log as logging + +from neutron.db import _resource_extend as resource_extend +from neutron.db import _utils as db_utils +from neutron.db import agents_db +from neutron.db import allowedaddresspairs_db as addr_pair_db +from neutron.db import api as db_api +from neutron.db.availability_zone import router as router_az_db +from neutron.db import external_net_db +from neutron.db import extradhcpopt_db +from neutron.db import extraroute_db +from neutron.db import l3_gwmode_db +from neutron.db.models import l3 as l3_db_models +from neutron.db.models import securitygroup as securitygroup_model # noqa +from neutron.db import models_v2 +from neutron.db import portsecurity_db +from neutron.db import quota_db # noqa +from neutron.db import securitygroups_db +from neutron.quota import resource_registry +from neutron_lib.api import validators +from neutron_lib import exceptions as n_exc + +from vmware_nsx.common import availability_zones as nsx_com_az +from vmware_nsx.common import config # noqa +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.common import managers as nsx_managers +from vmware_nsx.db import ( + routertype as rt_rtr) +from vmware_nsx.db import db as nsx_db +from vmware_nsx.db import nsx_portbindings_db as pbin_db +from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.plugins.common import plugin as nsx_plugin_common +from vmware_nsx.plugins.dvs import plugin as dvs +from vmware_nsx.plugins.nsx_v import plugin as v +from vmware_nsx.plugins.nsx_v3 import plugin as t + +LOG = logging.getLogger(__name__) +TVD_PLUGIN_TYPE = "Nsx-TVD" + + +@resource_extend.has_resource_extenders +class NsxTVDPlugin(addr_pair_db.AllowedAddressPairsMixin, + agents_db.AgentDbMixin, + nsx_plugin_common.NsxPluginBase, + rt_rtr.RouterType_mixin, + external_net_db.External_net_db_mixin, + extraroute_db.ExtraRoute_db_mixin, + extradhcpopt_db.ExtraDhcpOptMixin, + router_az_db.RouterAvailabilityZoneMixin, + l3_gwmode_db.L3_NAT_db_mixin, + pbin_db.NsxPortBindingMixin, + portsecurity_db.PortSecurityDbMixin, + securitygroups_db.SecurityGroupDbMixin, + nsx_com_az.NSXAvailabilityZonesPluginCommon, + projectpluginmap.ProjectPluginMapPluginBase): + + supported_extension_aliases = ['project-plugin-map'] + + __native_bulk_support = False + __native_pagination_support = True + __native_sorting_support = True + + @resource_registry.tracked_resources( + network=models_v2.Network, + port=models_v2.Port, + subnet=models_v2.Subnet, + subnetpool=models_v2.SubnetPool, + security_group=securitygroup_model.SecurityGroup, + security_group_rule=securitygroup_model.SecurityGroupRule, + router=l3_db_models.Router, + floatingip=l3_db_models.FloatingIP) + def __init__(self): + self._extension_manager = nsx_managers.ExtensionManager() + LOG.info("Start NSX TVD Plugin") + LOG.info("This plugin is experimental!") + # Validate configuration + config.validate_nsx_config_options() + super(NsxTVDPlugin, self).__init__() + + # init the different supported plugins + self.init_plugins() + + # init the extensions supported by any of the plugins + self.init_extensions() + + @staticmethod + def plugin_type(): + return TVD_PLUGIN_TYPE + + @staticmethod + def is_tvd_plugin(): + return True + + def init_plugins(self): + # initialize all supported plugins + self.plugins = {} + try: + self.plugins[projectpluginmap.NsxPlugins.NSX_T] = t.NsxV3Plugin() + except Exception as e: + LOG.info("NSX-T plugin will not be supported: %s", e) + + try: + self.plugins[projectpluginmap.NsxPlugins.NSX_V] = v.NsxVPluginV2() + except Exception as e: + LOG.info("NSX-V plugin will not be supported: %s", e) + + try: + self.plugins[projectpluginmap.NsxPlugins.DVS] = dvs.NsxDvsV2() + except Exception as e: + LOG.info("DVS plugin will not be supported: %s", e) + + if not len(self.plugins): + msg = _("No active plugins were found") + raise nsx_exc.NsxPluginException(err_msg=msg) + + # update the default plugin for new projects + # TODO(asarfaty): make the default configurable? + if projectpluginmap.NsxPlugins.NSX_T in self.plugins: + self.default_plugin = projectpluginmap.NsxPlugins.NSX_T + else: + self.default_plugin = self.plugins[0].key + LOG.info("NSX-TVD plugin will use %s as the default plugin", + self.default_plugin) + + def get_plugin_by_type(self, plugin_type): + return self.plugins.get(plugin_type) + + def init_extensions(self): + # Support all the extensions supported by any of the plugins + extensions = [] + for plugin in self.plugins: + extensions.extend(self.plugins[plugin].supported_extension_aliases) + self.supported_extension_aliases = list(set(extensions)) + + # mark extensions which are supported by only one of the plugins + self._unsupported_fields = {} + for plugin in self.plugins: + # TODO(asarfaty): add other resources here + plugin_type = self.plugins[plugin].plugin_type() + self._unsupported_fields[plugin_type] = {'router': []} + + # router size and type are supported only by the V plugin + self._unsupported_fields[t.NsxV3Plugin.plugin_type()]['router'] = [ + 'router_size', 'router_type'] + + def _validate_obj_extensions(self, data, plugin_type, obj_type): + """prevent configuration of unsupported extensions""" + for field in self._unsupported_fields[plugin_type][obj_type]: + if validators.is_attr_set(data.get(field)): + err_msg = (_('Can not support %(field)s extension for ' + '%(obj_type)s %(p)s plugin') % { + 'field': field, + 'obj_type': obj_type, + 'p': plugin_type}) + raise n_exc.InvalidInput(error_message=err_msg) + + def _cleanup_obj_fields(self, data, plugin_type, obj_type): + """Remove data of unsupported extensions""" + for field in self._unsupported_fields[plugin_type][obj_type]: + if field in data: + del data[field] + + def _list_availability_zones(self, context, filters=None): + p = self._get_plugin_from_project(context, context.project_id) + return p._list_availability_zones(context, filters=filters) + + def validate_availability_zones(self, context, resource_type, + availability_zones): + p = self._get_plugin_from_project(context, context.project_id) + return p.validate_availability_zones(context, resource_type, + availability_zones) + + def _get_plugin_from_net_id(self, context, net_id): + # get the network using the super plugin - here we use the + # _get_network (so as not to call the make dict method) + network = super(NsxTVDPlugin, self)._get_network(context, net_id) + return self._get_plugin_from_project(context, network['tenant_id']) + + def get_network_availability_zones(self, net_db): + ctx = n_context.get_admin_context() + p = self._get_plugin_from_project(ctx, net_db['tenant_id']) + return p.get_network_availability_zones(net_db) + + def create_network(self, context, network): + net_data = network['network'] + tenant_id = net_data['tenant_id'] + self._ensure_default_security_group(context, tenant_id) + p = self._get_plugin_from_project(context, tenant_id) + return p.create_network(context, network) + + def delete_network(self, context, id): + p = self._get_plugin_from_net_id(context, id) + p.delete_network(context, id) + + def get_network(self, context, id, fields=None): + p = self._get_plugin_from_net_id(context, id) + return p.get_network(context, id, fields=fields) + + def get_networks(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + filters = filters or {} + with db_api.context_manager.reader.using(context): + networks = ( + super(NsxTVDPlugin, self).get_networks( + context, filters, fields, sorts, + limit, marker, page_reverse)) + for net in networks: + p = self._get_plugin_from_project(context, net['tenant_id']) + p._extend_get_network_dict_provider(context, net) + return (networks if not fields else + [db_utils.resource_fields(network, + fields) for network in networks]) + + def update_network(self, context, id, network): + p = self._get_plugin_from_net_id(context, id) + return p.update_network(context, id, network) + + def create_port(self, context, port): + id = port['port']['network_id'] + p = self._get_plugin_from_net_id(context, id) + return p.create_port(context, port) + + def update_port(self, context, id, port): + db_port = self._get_port(context, id) + p = self._get_plugin_from_net_id(context, db_port['network_id']) + return p.update_port(context, id, port) + + def delete_port(self, context, id, **kwargs): + db_port = self._get_port(context, id) + p = self._get_plugin_from_net_id(context, db_port['network_id']) + p.delete_port(context, id, **kwargs) + + def get_port(self, context, id, fields=None): + db_port = self._get_port(context, id) + p = self._get_plugin_from_net_id(context, db_port['network_id']) + return p.get_port(context, id, fields=fields) + + def get_ports(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + filters = filters or {} + with db_api.context_manager.reader.using(context): + ports = ( + super(NsxTVDPlugin, self).get_ports( + context, filters, fields, sorts, + limit, marker, page_reverse)) + # Add port extensions + for port in ports: + if 'id' in port: + port_model = self._get_port(context, port['id']) + resource_extend.apply_funcs('ports', port, port_model) + p = self._get_plugin_from_net_id(context, port['network_id']) + p._extend_get_port_dict_qos_and_binding(context, port) + p._remove_provider_security_groups_from_list(port) + return (ports if not fields else + [db_utils.resource_fields(port, fields) for port in ports]) + + def get_subnet(self, context, id, fields=None): + db_subnet = self._get_subnet(context, id) + p = self._get_plugin_from_net_id(context, db_subnet['network_id']) + return p.get_subnet(context, id, fields=fields) + + def get_subnets(self, context, filters=None, fields=None, sorts=None, + limit=None, marker=None, page_reverse=False): + # The subnets is tricky as the metadata requests make use of the + # get subnet. So there are two use cases here: + # 1. that the metadata request returns a value + # 2. that this is a general subnet query. + # If none found then we return default plugin subnets + default_plugin_subnets = [] + for plugin in self.plugins.values(): + subnets = plugin.get_subnets(context, filters=filters, + fields=fields, sorts=sorts, + limit=limit, marker=marker, + page_reverse=page_reverse) + if subnets: + return subnets + if self.plugins[self.default_plugin] == plugin: + default_plugin_subnets = subnets + return default_plugin_subnets + + def delete_subnet(self, context, id): + db_subnet = self._get_subnet(context, id) + p = self._get_plugin_from_net_id(context, db_subnet['network_id']) + p.delete_subnet(context, id) + + def create_subnet(self, context, subnet): + id = subnet['subnet']['network_id'] + p = self._get_plugin_from_net_id(context, id) + return p.create_subnet(context, subnet) + + def update_subnet(self, context, id, subnet): + db_subnet = self._get_subnet(context, id) + p = self._get_plugin_from_net_id(context, db_subnet['network_id']) + return p.update_subnet(context, id, subnet) + + def get_router_availability_zones(self, router): + ctx = n_context.get_admin_context() + p = self._get_plugin_from_project(ctx, router['tenant_id']) + return p.get_router_availability_zones(router) + + def _validate_router_gw_plugin(self, context, router_plugin, + gw_info): + if gw_info and gw_info.get('network_id'): + net_plugin = self._get_plugin_from_net_id( + context, gw_info['network_id']) + if net_plugin.plugin_type() != router_plugin.plugin_type(): + err_msg = (_('Router gateway should belong to the %s plugin ' + 'as the router') % router_plugin.plugin_type()) + raise n_exc.InvalidInput(error_message=err_msg) + + def _validate_router_interface_plugin(self, context, router_plugin, + interface_info): + is_port, is_sub = self._validate_interface_info(interface_info) + if is_port: + net_id = self.get_port( + context, interface_info['port_id'])['network_id'] + elif is_sub: + net_id = self.get_subnet( + context, interface_info['subnet_id'])['network_id'] + net_plugin = self._get_plugin_from_net_id(context, net_id) + if net_plugin.plugin_type() != router_plugin.plugin_type(): + err_msg = (_('Router interface should belong to the %s plugin ' + 'as the router') % router_plugin.plugin_type()) + raise n_exc.InvalidInput(error_message=err_msg) + + def _get_plugin_from_router_id(self, context, router_id): + # get the router using the super plugin - here we use the + # _get_router (so as not to call the make dict method) + router = super(NsxTVDPlugin, self)._get_router(context, router_id) + return self._get_plugin_from_project(context, router['tenant_id']) + + def create_router(self, context, router): + tenant_id = router['router']['tenant_id'] + self._ensure_default_security_group(context, tenant_id) + p = self._get_plugin_from_project(context, tenant_id) + self._validate_router_gw_plugin(context, p, router['router'].get( + 'external_gateway_info')) + self._validate_obj_extensions( + router['router'], p.plugin_type(), 'router') + new_router = p.create_router(context, router) + self._cleanup_obj_fields( + router['router'], p.plugin_type(), 'router') + return new_router + + def update_router(self, context, router_id, router): + p = self._get_plugin_from_router_id(context, router_id) + self._validate_router_gw_plugin(context, p, router['router'].get( + 'external_gateway_info')) + self._validate_obj_extensions( + router['router'], p.plugin_type(), 'router') + return p.update_router(context, router_id, router) + + def get_router(self, context, id, fields=None): + p = self._get_plugin_from_router_id(context, id) + router = p.get_router(context, id, fields=fields) + self._cleanup_obj_fields(router, p.plugin_type(), 'router') + return router + + def add_router_interface(self, context, router_id, interface_info): + p = self._get_plugin_from_router_id(context, router_id) + self._validate_router_interface_plugin(context, p, interface_info) + return p.add_router_interface(context, router_id, interface_info) + + def remove_router_interface(self, context, router_id, interface_info): + p = self._get_plugin_from_router_id(context, router_id) + return p.remove_router_interface(context, router_id, interface_info) + + def _validate_fip_router_plugin(self, context, fip_plugin, fip_data): + if 'router_id' in fip_data: + router_plugin = self._get_plugin_from_router_id( + context, fip_data['router_id']) + if router_plugin.plugin_type() != fip_plugin.plugin_type(): + err_msg = (_('Floatingip router should belong to the %s ' + 'plugin as the floatingip') % + fip_plugin.plugin_type()) + raise n_exc.InvalidInput(error_message=err_msg) + + def create_floatingip(self, context, floatingip): + net_id = floatingip['floatingip']['floating_network_id'] + p = self._get_plugin_from_net_id(context, net_id) + self._validate_fip_router_plugin(context, p, floatingip['floatingip']) + return p.create_floatingip(context, floatingip) + + def update_floatingip(self, context, id, floatingip): + fip = self._get_floatingip(context, id) + net_id = fip['floating_network_id'] + p = self._get_plugin_from_net_id(context, net_id) + self._validate_fip_router_plugin(context, p, floatingip['floatingip']) + return p.update_floatingip(context, id, floatingip) + + def delete_floatingip(self, context, id): + fip = self._get_floatingip(context, id) + net_id = fip['floating_network_id'] + p = self._get_plugin_from_net_id(context, net_id) + return p.delete_floatingip(context, id) + + def disassociate_floatingips(self, context, port_id): + db_port = self._get_port(context, port_id) + p = self._get_plugin_from_net_id(context, db_port['network_id']) + return p.disassociate_floatingips(context, port_id) + + def _get_plugin_from_sg_id(self, context, sg_id): + # get the router using the super plugin - here we use the + # _get_router (so as not to call the make dict method) + sg = super(NsxTVDPlugin, self)._get_security_group(context, sg_id) + return self._get_plugin_from_project(context, sg['tenant_id']) + + # TODO(asarfaty): no need to create on both any more? + def create_security_group(self, context, security_group, + default_sg=False): + if not default_sg: + secgroup = security_group['security_group'] + tenant_id = secgroup['tenant_id'] + self._ensure_default_security_group(context, tenant_id) + + p = self._get_plugin_from_project(context, context.project_id) + return p.create_security_group(context, security_group, + default_sg=default_sg) + + def delete_security_group(self, context, id): + p = self._get_plugin_from_sg_id(context, id) + p.delete_security_group(context, id) + # self._v.delete_security_group(context, id, delete_base=False) + # self._t.delete_security_group(context, id) + + def update_security_group(self, context, id, security_group): + p = self._get_plugin_from_sg_id(context, id) + return p.update_security_group(context, id, security_group) + #self._t.update_security_group(context, id, security_group) + #return self._v.update_security_group(context, id, security_group) + + def create_security_group_rule_bulk(self, context, security_group_rules): + p = self._get_plugin_from_project(context, context.project_id) + return p.create_security_group_rule_bulk(context, + security_group_rules) + # sg_rules = security_group_rules['security_group_rules'] + # for r in sg_rules: + # r['security_group_rule']['id'] = ( + # r['security_group_rule'].get('id') or + # uuidutils.generate_uuid()) + # sgs = self._t.create_security_group_rule_bulk(context, + # security_group_rules) + # self._v.create_security_group_rule_bulk(context, + # security_group_rules, + # base_create=False) + # return sgs + + def create_security_group_rule(self, context, security_group_rule): + p = self._get_plugin_from_project(context, context.project_id) + return p.create_security_group_rule(context, security_group_rule) + # security_group_rule['security_group_rule']['id'] = ( + # security_group_rule['security_group_rule'].get('id') or + # uuidutils.generate_uuid()) + # sg = self._t.create_security_group_rule(context, security_group_rule) + # self._v.create_security_group_rule(context, security_group_rule, + # create_base=False) + # return sg + + def delete_security_group_rule(self, context, id): + p = self._get_plugin_from_sg_id(context, id) + p.delete_security_group_rule(context, id) + # self._v.delete_security_group_rule(context, id, delete_base=False) + # self._t.delete_security_group_rule(context, id) + + @staticmethod + @resource_extend.extends([net_def.COLLECTION_NAME]) + def _ext_extend_network_dict(result, netdb): + ctx = n_context.get_admin_context() + # get the core plugin as this is a static method with no 'self' + plugin = directory.get_plugin() + p = plugin._get_plugin_from_project(ctx, netdb['tenant_id']) + with db_api.context_manager.writer.using(ctx): + p._extension_manager.extend_network_dict( + ctx.session, netdb, result) + + @staticmethod + @resource_extend.extends([port_def.COLLECTION_NAME]) + def _ext_extend_port_dict(result, portdb): + ctx = n_context.get_admin_context() + # get the core plugin as this is a static method with no 'self' + plugin = directory.get_plugin() + p = plugin._get_plugin_from_project(ctx, portdb['tenant_id']) + with db_api.context_manager.writer.using(ctx): + p._extension_manager.extend_port_dict( + ctx.session, portdb, result) + + @staticmethod + @resource_extend.extends([subnet_def.COLLECTION_NAME]) + def _ext_extend_subnet_dict(result, subnetdb): + ctx = n_context.get_admin_context() + # get the core plugin as this is a static method with no 'self' + plugin = directory.get_plugin() + p = plugin._get_plugin_from_project(ctx, subnetdb['tenant_id']) + with db_api.context_manager.writer.using(ctx): + p._extension_manager.extend_subnet_dict( + ctx.session, subnetdb, result) + + def _get_project_plugin_dict(self, data): + return {'id': data['project'], + 'project': data['project'], + 'plugin': data['plugin'], + 'tenant_id': data['project']} + + def create_project_plugin_map(self, context, project_plugin_map): + # TODO(asarfaty): Validate project id exists + data = project_plugin_map['project_plugin_map'] + if nsx_db.get_project_plugin_mapping( + context.session, data['project']): + raise projectpluginmap.ProjectPluginAlreadyExists( + project_id=data['project']) + nsx_db.add_project_plugin_mapping(context.session, + data['project'], + data['plugin']) + return self._get_project_plugin_dict(data) + + def get_project_plugin_map(self, context, id, fields=None): + data = nsx_db.get_project_plugin_mapping(context.session, id) + if data: + return self._get_project_plugin_dict(data) + else: + raise n_exc.ObjectNotFound(id=id) + + def get_project_plugin_maps(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + # TODO(asarfaty) filter the results + mappings = nsx_db.get_project_plugin_mappings(context.session) + return [self._get_project_plugin_dict(data) for data in mappings] + + def _get_plugin_from_project(self, context, project_id): + """Get the correct plugin for this project. + + Look for the project in the DB. + If not there - add an entry with the default plugin + """ + plugin_type = self.default_plugin + mapping = nsx_db.get_project_plugin_mapping( + context.session, project_id) + if mapping: + plugin_type = mapping['plugin'] + else: + self.create_project_plugin_map(context, + {'project_plugin_map': {'plugin': plugin_type, + 'project': project_id}}) + if not self.plugins.get(plugin_type): + msg = (_("Cannot use unsupported plugin %(plugin)s for project " + "%(project)s") % {'plugin': plugin_type, + 'project': project_id}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + LOG.debug("Using %s plugin", plugin_type) + return self.plugins[plugin_type] diff --git a/vmware_nsx/plugins/nsx/utils.py b/vmware_nsx/plugins/nsx/utils.py new file mode 100644 index 0000000000..66c8d6da51 --- /dev/null +++ b/vmware_nsx/plugins/nsx/utils.py @@ -0,0 +1,24 @@ +# Copyright 2014 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_config import cfg + + +def is_tvd_core_plugin(): + core_plugin = cfg.CONF.core_plugin + if (core_plugin.endswith('NsxTVDPlugin') or + core_plugin.endswith('vmware_nsxtvd')): + return True + return False diff --git a/vmware_nsx/plugins/nsx_v/availability_zones.py b/vmware_nsx/plugins/nsx_v/availability_zones.py index a427b86388..4679c27f3a 100644 --- a/vmware_nsx/plugins/nsx_v/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v/availability_zones.py @@ -204,10 +204,11 @@ class NsxVAvailabilityZone(common_az.ConfiguredAvailabilityZone): class NsxVAvailabilityZones(common_az.ConfiguredAvailabilityZones): - def __init__(self): + def __init__(self, validate_default=False): super(NsxVAvailabilityZones, self).__init__( cfg.CONF.nsxv.availability_zones, - NsxVAvailabilityZone) + NsxVAvailabilityZone, + validate_default=validate_default) def get_inventory(self): """Return a set of relevant resources in all the availability zones diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index b81a815cf5..41e920e9c0 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -120,12 +120,14 @@ from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain from vmware_nsx.extensions import maclearning as mac_ext from vmware_nsx.extensions import nsxpolicy +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import routersize from vmware_nsx.extensions import secgroup_rule_local_ip_prefix from vmware_nsx.extensions import securitygrouplogging as sg_logging from vmware_nsx.extensions import securitygrouppolicy as sg_policy from vmware_nsx.plugins.common import plugin as nsx_plugin_common +from vmware_nsx.plugins.nsx import utils as tvd_utils from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az from vmware_nsx.plugins.nsx_v import managers from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy @@ -217,8 +219,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, router=l3_db_models.Router, floatingip=l3_db_models.FloatingIP) def __init__(self): - self._extension_manager = nsx_managers.ExtensionManager() + self._is_sub_plugin = tvd_utils.is_tvd_core_plugin() super(NsxVPluginV2, self).__init__() + if self._is_sub_plugin: + extension_drivers = cfg.CONF.nsx_tvd.nsx_v_extension_drivers + else: + extension_drivers = cfg.CONF.nsx_extension_drivers + self._extension_manager = nsx_managers.ExtensionManager( + extension_drivers=extension_drivers) # Bind the dummy L3 notifications self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI() self.init_is_complete = False @@ -311,6 +319,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Bind QoS notifications qos_driver.register(self) + @staticmethod + def plugin_type(): + return projectpluginmap.NsxPlugins.NSX_V + + @staticmethod + def is_tvd_plugin(): + return False + def init_complete(self, resource, event, trigger, payload=None): with locking.LockManager.get_lock('plugin-init-complete'): if self.init_is_complete: @@ -935,7 +951,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if vnic_id is None or added_sgids is None: return for add_sg in added_sgids: - nsx_sg_id = nsx_db.get_nsx_security_group_id(session, add_sg) + nsx_sg_id = nsx_db.get_nsx_security_group_id(session, add_sg, + moref=True) if nsx_sg_id is None: LOG.warning("NSX security group not found for %s", add_sg) else: @@ -958,7 +975,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, return # Remove vnic from delete security groups binding for del_sg in deleted_sgids: - nsx_sg_id = nsx_db.get_nsx_security_group_id(session, del_sg) + nsx_sg_id = nsx_db.get_nsx_security_group_id(session, del_sg, + moref=True) if nsx_sg_id is None: LOG.warning("NSX security group not found for %s", del_sg) else: @@ -994,7 +1012,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, return '%s.%03d' % (device_id, port_index) def init_availability_zones(self): - self._availability_zones_data = nsx_az.NsxVAvailabilityZones() + validate_default = not self._is_sub_plugin + self._availability_zones_data = nsx_az.NsxVAvailabilityZones( + validate_default=validate_default) def _list_availability_zones(self, context, filters=None): #TODO(asarfaty): We may need to use the filters arg, but now it @@ -1010,13 +1030,19 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, obj_data): if az_def.AZ_HINTS in obj_data: self.validate_availability_zones(context, resource_type, - obj_data[az_def.AZ_HINTS]) + obj_data[az_def.AZ_HINTS], + force=True) def validate_availability_zones(self, context, resource_type, - availability_zones): + availability_zones, force=False): """Verify that the availability zones exist, and only 1 hint was set. """ + # This method is called directly from this plugin but also from + # registered callbacks + if self._is_sub_plugin and not force: + # validation should be done together for both plugins + return return self.validate_obj_azs(availability_zones) def _prepare_spoofguard_policy(self, network_type, net_data, net_morefs): @@ -4137,7 +4163,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, def update_security_group(self, context, id, security_group): s = security_group['security_group'] self._validate_security_group(context, s, False, id=id) - nsx_sg_id = nsx_db.get_nsx_security_group_id(context.session, id) + nsx_sg_id = nsx_db.get_nsx_security_group_id(context.session, id, + moref=True) section_uri = self._get_section_uri(context.session, id) section_needs_update = False @@ -4190,7 +4217,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, return sg_data - def delete_security_group(self, context, id): + def delete_security_group(self, context, id, delete_base=True): """Delete a security group.""" self._prevent_non_admin_delete_provider_sg(context, id) self._prevent_non_admin_delete_policy_sg(context, id) @@ -4200,10 +4227,12 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, section_uri = self._get_section_uri(context.session, id) # Find nsx security group - nsx_sg_id = nsx_db.get_nsx_security_group_id(context.session, id) + nsx_sg_id = nsx_db.get_nsx_security_group_id(context.session, id, + moref=True) - # Delete neutron security group - super(NsxVPluginV2, self).delete_security_group(context, id) + if delete_base: + # Delete neutron security group + super(NsxVPluginV2, self).delete_security_group(context, id) # Delete nsx rule sections self._delete_section(section_uri) @@ -4228,7 +4257,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if nsx_sg_id is None: # Find nsx security group for neutron security group nsx_sg_id = nsx_db.get_nsx_security_group_id( - context.session, rule['security_group_id']) + context.session, rule['security_group_id'], + moref=True) # Find the remote nsx security group id, which might be the current # one. In case of the default security-group, the associated @@ -4237,7 +4267,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, remote_nsx_sg_id = nsx_sg_id else: remote_nsx_sg_id = nsx_db.get_nsx_security_group_id( - context.session, rule['remote_group_id']) + context.session, rule['remote_group_id'], moref=True) # Get source and destination containers from rule if rule['direction'] == 'ingress': @@ -4283,12 +4313,15 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, tag='Project_%s' % rule['tenant_id']) return nsx_rule - def create_security_group_rule(self, context, security_group_rule): + def create_security_group_rule(self, context, security_group_rule, + create_base=True): """Create a single security group rule.""" bulk_rule = {'security_group_rules': [security_group_rule]} - return self.create_security_group_rule_bulk(context, bulk_rule)[0] + return self.create_security_group_rule_bulk( + context, bulk_rule, create_base=create_base)[0] - def create_security_group_rule_bulk(self, context, security_group_rules): + def create_security_group_rule_bulk(self, context, security_group_rules, + create_base=True): """Create security group rules. :param security_group_rules: list of rules to create @@ -4321,7 +4354,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, rule = r['security_group_rule'] if not self._check_local_ip_prefix(context, rule): rule[secgroup_rule_local_ip_prefix.LOCAL_IP_PREFIX] = None - rule['id'] = uuidutils.generate_uuid() + rule['id'] = rule.get('id') or uuidutils.generate_uuid() ruleids.add(rule['id']) nsx_rules.append( self._create_nsx_rule(context, rule, @@ -4340,18 +4373,23 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Save new rules in Database, including mappings between Nsx rules # and Neutron security-groups rules with db_api.context_manager.writer.using(context): - new_rule_list = super( - NsxVPluginV2, self).create_security_group_rule_bulk_native( - context, security_group_rules) + if create_base: + new_rule_list = super( + NsxVPluginV2, + self).create_security_group_rule_bulk_native( + context, security_group_rules) + for i, r in enumerate(sg_rules): + self._process_security_group_rule_properties( + context, new_rule_list[i], + r['security_group_rule']) + else: + new_rule_list = sg_rules for pair in rule_pairs: neutron_rule_id = pair['neutron_id'] nsx_rule_id = pair['nsx_id'] if neutron_rule_id in ruleids: nsxv_db.add_neutron_nsx_rule_mapping( context.session, neutron_rule_id, nsx_rule_id) - for i, r in enumerate(sg_rules): - self._process_security_group_rule_properties( - context, new_rule_list[i], r['security_group_rule']) except Exception: with excutils.save_and_reraise_exception(): for nsx_rule_id in [p['nsx_id'] for p in rule_pairs @@ -4363,7 +4401,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, LOG.exception("Failed to create security group rule") return new_rule_list - def delete_security_group_rule(self, context, id): + def delete_security_group_rule(self, context, id, delete_base=True): """Delete a security group rule.""" rule_db = self._get_security_group_rule(context, id) security_group_id = rule_db['security_group_id'] @@ -4383,8 +4421,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, LOG.debug("Security group rule %(id)s deleted, backend " "nsx-rule %(nsx_rule_id)s doesn't exist.", {'id': id, 'nsx_rule_id': nsx_rule_id}) - - securitygroup.SecurityGroupRule.delete_objects(context, id=id) + if delete_base: + securitygroup.SecurityGroupRule.delete_objects(context, id=id) def _remove_vnic_from_spoofguard_policy(self, session, net_id, vnic_id): policy_id = nsxv_db.get_spoofguard_policy_id(session, net_id) diff --git a/vmware_nsx/plugins/nsx_v3/availability_zones.py b/vmware_nsx/plugins/nsx_v3/availability_zones.py index 3b11c88b36..98f8800913 100644 --- a/vmware_nsx/plugins/nsx_v3/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v3/availability_zones.py @@ -204,10 +204,11 @@ class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones): default_name = DEFAULT_NAME - def __init__(self): + def __init__(self, validate_default=False): super(NsxV3AvailabilityZones, self).__init__( cfg.CONF.nsx_v3.availability_zones, - NsxV3AvailabilityZone) + NsxV3AvailabilityZone, + validate_default=validate_default) def dhcp_relay_configured(self): for az in self.availability_zones.values(): diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index ef49620650..7d562d8485 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -99,9 +99,11 @@ from vmware_nsx.db import maclearning as mac_db from vmware_nsx.dhcp_meta import rpc as nsx_rpc from vmware_nsx.extensions import advancedserviceproviders as as_providers from vmware_nsx.extensions import maclearning as mac_ext +from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import securitygrouplogging as sg_logging from vmware_nsx.plugins.common import plugin as nsx_plugin_common +from vmware_nsx.plugins.nsx import utils as tvd_utils from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az from vmware_nsx.plugins.nsx_v3 import utils as v3_utils from vmware_nsx.services.fwaas.common import utils as fwaas_utils @@ -210,9 +212,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, router=l3_db_models.Router, floatingip=l3_db_models.FloatingIP) def __init__(self): + self._is_sub_plugin = tvd_utils.is_tvd_core_plugin() nsxlib_utils.set_is_attr_callback(validators.is_attr_set) self._extend_fault_map() - self._extension_manager = managers.ExtensionManager() + if self._is_sub_plugin: + extension_drivers = cfg.CONF.nsx_tvd.nsx_v3_extension_drivers + else: + extension_drivers = cfg.CONF.nsx_extension_drivers + self._extension_manager = managers.ExtensionManager( + extension_drivers=extension_drivers) super(NsxV3Plugin, self).__init__() # Bind the dummy L3 notifications self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI() @@ -298,6 +306,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Register NSXv3 trunk driver to support trunk extensions self.trunk_driver = trunk_driver.NsxV3TrunkDriver.create(self) + @staticmethod + def plugin_type(): + return projectpluginmap.NsxPlugins.NSX_T + + @staticmethod + def is_tvd_plugin(): + return False + def init_complete(self, resource, event, trigger, payload=None): with locking.LockManager.get_lock('plugin-init-complete'): if self.init_is_complete: @@ -364,7 +380,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "DHCP metadata") LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) - self._availability_zones_data = nsx_az.NsxV3AvailabilityZones() + validate_default = not self._is_sub_plugin + self._availability_zones_data = nsx_az.NsxV3AvailabilityZones( + validate_default=validate_default) def _init_nsx_profiles(self): LOG.debug("Initializing NSX v3 port spoofguard switching profile") @@ -1027,8 +1045,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # validate the availability zone, and get the AZ object if az_def.AZ_HINTS in net_data: - self.validate_availability_zones(context, 'network', - net_data[az_def.AZ_HINTS]) + self._validate_availability_zones_forced( + context, 'network', net_data[az_def.AZ_HINTS]) az = self.get_obj_az_by_hints(net_data) self._ensure_default_security_group(context, tenant_id) @@ -1097,6 +1115,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, {'network': {'vlan_transparent': vlt}}) rollback_network = True + is_ddi_network = self._is_ddi_supported_on_network( context, created_net['id']) if (is_backend_network and @@ -3265,8 +3284,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # validate the availability zone if az_def.AZ_HINTS in r: - self.validate_availability_zones(context, 'router', - r[az_def.AZ_HINTS]) + self._validate_availability_zones_forced(context, 'router', + r[az_def.AZ_HINTS]) gw_info = self._extract_external_gw(context, router, is_extract=True) r['id'] = (r.get('id') or uuidutils.generate_uuid()) @@ -4360,8 +4379,19 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, result[(az, 'network')] = True return result + def _validate_availability_zones_forced(self, context, resource_type, + availability_zones): + return self.validate_availability_zones(context, resource_type, + availability_zones, + force=True) + def validate_availability_zones(self, context, resource_type, - availability_zones): + availability_zones, force=False): + # This method is called directly from this plugin but also from + # registered callbacks + if self._is_sub_plugin and not force: + # validation should be done together for both plugins + return # If no native_dhcp_metadata - use neutron AZs if not cfg.CONF.nsx_v3.native_dhcp_metadata: return super(NsxV3Plugin, self).validate_availability_zones(