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 <asarfaty@vmware.com> Change-Id: I0449d64e3cf79b7a3a846dacba95e8854d53bdf8
This commit is contained in:
parent
315e0befe0
commit
792a6a0103
@ -33,7 +33,6 @@ function nsxv_configure_service {
|
|||||||
if [[ "$NSX_L2GW_DRIVER" != "" ]]; then
|
if [[ "$NSX_L2GW_DRIVER" != "" ]]; then
|
||||||
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER
|
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER
|
||||||
fi
|
fi
|
||||||
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv_dns
|
|
||||||
_nsxv_ini_set password "$NSXV_PASSWORD"
|
_nsxv_ini_set password "$NSXV_PASSWORD"
|
||||||
_nsxv_ini_set user "$NSXV_USER"
|
_nsxv_ini_set user "$NSXV_USER"
|
||||||
_nsxv_ini_set vdn_scope_id "$NSXV_VDN_SCOPE_ID"
|
_nsxv_ini_set vdn_scope_id "$NSXV_VDN_SCOPE_ID"
|
||||||
@ -104,7 +103,6 @@ function nsxv3_configure_service {
|
|||||||
if [[ "$NSX_L2GW_DRIVER" != "" ]]; then
|
if [[ "$NSX_L2GW_DRIVER" != "" ]]; then
|
||||||
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER
|
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_l2gw_driver $NSX_L2GW_DRIVER
|
||||||
fi
|
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_user $NSX_USER
|
||||||
_nsxv3_ini_set nsx_api_password $NSX_PASSWORD
|
_nsxv3_ini_set nsx_api_password $NSX_PASSWORD
|
||||||
_nsxv3_ini_set retries $NSX_RETRIES
|
_nsxv3_ini_set retries $NSX_RETRIES
|
||||||
|
253
devstack/lib/vmware_nsx_tvd
Normal file
253
devstack/lib/vmware_nsx_tvd
Normal file
@ -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
|
@ -84,6 +84,7 @@ function neutron_plugin_configure_plugin_agent {
|
|||||||
|
|
||||||
function neutron_plugin_configure_service {
|
function neutron_plugin_configure_service {
|
||||||
nsxv_configure_service
|
nsxv_configure_service
|
||||||
|
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv_dns
|
||||||
}
|
}
|
||||||
|
|
||||||
function neutron_plugin_setup_interface_driver {
|
function neutron_plugin_setup_interface_driver {
|
||||||
|
@ -155,6 +155,7 @@ function neutron_plugin_configure_plugin_agent {
|
|||||||
|
|
||||||
function neutron_plugin_configure_service {
|
function neutron_plugin_configure_service {
|
||||||
nsxv3_configure_service
|
nsxv3_configure_service
|
||||||
|
iniset /$Q_PLUGIN_CONF_FILE DEFAULT nsx_extension_drivers vmware_nsxv3_dns
|
||||||
}
|
}
|
||||||
|
|
||||||
function neutron_plugin_setup_interface_driver {
|
function neutron_plugin_setup_interface_driver {
|
||||||
|
@ -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
|
python $dir/tools/nsxv3_cleanup.py --mgr-ip $NSX_MANAGER --user $NSX_USER --password $NSX_PASSWORD
|
||||||
fi
|
fi
|
||||||
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
|
elif [[ $Q_PLUGIN == 'vmware_dvs' ]]; then
|
||||||
source $dir/lib/vmware_dvs
|
source $dir/lib/vmware_dvs
|
||||||
fi
|
fi
|
||||||
|
@ -33,6 +33,7 @@ neutron.core_plugins =
|
|||||||
vmware_nsxv = vmware_nsx.plugin:NsxVPlugin
|
vmware_nsxv = vmware_nsx.plugin:NsxVPlugin
|
||||||
vmware_nsxv3 = vmware_nsx.plugin:NsxV3Plugin
|
vmware_nsxv3 = vmware_nsx.plugin:NsxV3Plugin
|
||||||
vmware_dvs = vmware_nsx.plugin:NsxDvsPlugin
|
vmware_dvs = vmware_nsx.plugin:NsxDvsPlugin
|
||||||
|
vmware_nsxtvd = vmware_nsx.plugin:NsxTVDPlugin
|
||||||
firewall_drivers =
|
firewall_drivers =
|
||||||
vmware_nsxv_edge = vmware_nsx.services.fwaas.nsx_v.edge_fwaas_driver:EdgeFwaasDriver
|
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
|
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
|
security_group_set = vmware_nsx.osc.v2.security_group:NsxSetSecurityGroup
|
||||||
subnet_create = vmware_nsx.osc.v2.subnet:NsxCreateSubnet
|
subnet_create = vmware_nsx.osc.v2.subnet:NsxCreateSubnet
|
||||||
subnet_set = vmware_nsx.osc.v2.subnet:NsxSetSubnet
|
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]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
@ -78,7 +78,7 @@ class ConfiguredAvailabilityZones(object):
|
|||||||
|
|
||||||
default_name = DEFAULT_NAME
|
default_name = DEFAULT_NAME
|
||||||
|
|
||||||
def __init__(self, az_conf, az_class):
|
def __init__(self, az_conf, az_class, validate_default=True):
|
||||||
self.availability_zones = {}
|
self.availability_zones = {}
|
||||||
|
|
||||||
# Add the configured availability zones
|
# Add the configured availability zones
|
||||||
@ -100,12 +100,17 @@ class ConfiguredAvailabilityZones(object):
|
|||||||
reason=_("The NSX plugin supports only 1 default AZ"))
|
reason=_("The NSX plugin supports only 1 default AZ"))
|
||||||
default_az_name = cfg.CONF.default_availability_zones[0]
|
default_az_name = cfg.CONF.default_availability_zones[0]
|
||||||
if (default_az_name not in self.availability_zones):
|
if (default_az_name not in self.availability_zones):
|
||||||
raise nsx_exc.NsxInvalidConfiguration(
|
if validate_default:
|
||||||
opt_name="default_availability_zones",
|
raise nsx_exc.NsxInvalidConfiguration(
|
||||||
opt_value=cfg.CONF.default_availability_zones,
|
opt_name="default_availability_zones",
|
||||||
reason=_("The default AZ is not defined in the NSX "
|
opt_value=cfg.CONF.default_availability_zones,
|
||||||
"plugin"))
|
reason=_("The default AZ is not defined in the NSX "
|
||||||
self._default_az = self.availability_zones[default_az_name]
|
"plugin"))
|
||||||
|
else:
|
||||||
|
self._default_az = self.availability_zones[
|
||||||
|
self.default_name]
|
||||||
|
else:
|
||||||
|
self._default_az = self.availability_zones[default_az_name]
|
||||||
else:
|
else:
|
||||||
self._default_az = self.availability_zones[self.default_name]
|
self._default_az = self.availability_zones[self.default_name]
|
||||||
|
|
||||||
|
@ -813,12 +813,26 @@ nsxv3_az_opts = [
|
|||||||
"on router ports.")),
|
"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
|
# Register the configuration options
|
||||||
cfg.CONF.register_opts(connection_opts)
|
cfg.CONF.register_opts(connection_opts)
|
||||||
cfg.CONF.register_opts(cluster_opts)
|
cfg.CONF.register_opts(cluster_opts)
|
||||||
cfg.CONF.register_opts(nsx_common_opts)
|
cfg.CONF.register_opts(nsx_common_opts)
|
||||||
cfg.CONF.register_opts(nsx_v3_opts, group="nsx_v3")
|
cfg.CONF.register_opts(nsx_v3_opts, group="nsx_v3")
|
||||||
cfg.CONF.register_opts(nsxv_opts, group="nsxv")
|
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(base_opts, group="NSX")
|
||||||
cfg.CONF.register_opts(sync_opts, group="NSX_SYNC")
|
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 "
|
error = _("dvs host/vcenter credentials must be defined to use "
|
||||||
"dvs features")
|
"dvs features")
|
||||||
raise nsx_exc.NsxPluginException(err_msg=error)
|
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)
|
||||||
|
@ -24,15 +24,18 @@ LOG = log.getLogger(__name__)
|
|||||||
class ExtensionManager(stevedore.named.NamedExtensionManager):
|
class ExtensionManager(stevedore.named.NamedExtensionManager):
|
||||||
"""Manage extension drivers using drivers."""
|
"""Manage extension drivers using drivers."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, extension_drivers=None):
|
||||||
# Ordered list of extension drivers, defining
|
# Ordered list of extension drivers, defining
|
||||||
# the order in which the drivers are called.
|
# the order in which the drivers are called.
|
||||||
self.ordered_ext_drivers = []
|
self.ordered_ext_drivers = []
|
||||||
|
|
||||||
|
if extension_drivers is None:
|
||||||
|
extension_drivers = cfg.CONF.nsx_extension_drivers
|
||||||
|
|
||||||
LOG.info("Configured extension driver names: %s",
|
LOG.info("Configured extension driver names: %s",
|
||||||
cfg.CONF.nsx_extension_drivers)
|
extension_drivers)
|
||||||
super(ExtensionManager, self).__init__('vmware_nsx.extension_drivers',
|
super(ExtensionManager, self).__init__('vmware_nsx.extension_drivers',
|
||||||
cfg.CONF.nsx_extension_drivers,
|
extension_drivers,
|
||||||
invoke_on_load=True,
|
invoke_on_load=True,
|
||||||
name_order=True)
|
name_order=True)
|
||||||
LOG.info("Loaded extension driver names: %s", self.names())
|
LOG.info("Loaded extension driver names: %s", self.names())
|
||||||
|
@ -20,6 +20,7 @@ from sqlalchemy.orm import exc
|
|||||||
from oslo_db import exception as db_exc
|
from oslo_db import exception as db_exc
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
import neutron.db.api as db
|
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)
|
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.
|
"""Return the id of a security group in the NSX backend.
|
||||||
|
|
||||||
Note: security groups are called 'security profiles' in NSX
|
Note: security groups are called 'security profiles' in NSX
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
mapping = (session.query(nsx_models.NeutronNsxSecurityGroupMapping).
|
mappings = (session.query(nsx_models.NeutronNsxSecurityGroupMapping).
|
||||||
filter_by(neutron_id=neutron_id).
|
filter_by(neutron_id=neutron_id).
|
||||||
one())
|
all())
|
||||||
return mapping['nsx_id']
|
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:
|
except exc.NoResultFound:
|
||||||
LOG.debug("NSX identifiers for neutron security group %s not yet "
|
LOG.debug("NSX identifiers for neutron security group %s not yet "
|
||||||
"stored in Neutron DB", neutron_id)
|
"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))
|
nsx_id=nsgroup_id))
|
||||||
|
|
||||||
|
|
||||||
def get_sg_mappings(session, sg_id):
|
def get_sg_mappings(session, sg_id, moref=False):
|
||||||
nsgroup_mapping = session.query(
|
nsgroup_mappings = session.query(
|
||||||
nsx_models.NeutronNsxSecurityGroupMapping
|
nsx_models.NeutronNsxSecurityGroupMapping
|
||||||
).filter_by(neutron_id=sg_id).one()
|
).filter_by(neutron_id=sg_id).all()
|
||||||
section_mapping = session.query(
|
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
|
nsx_models.NeutronNsxFirewallSectionMapping
|
||||||
).filter_by(neutron_id=sg_id).one()
|
).filter_by(neutron_id=sg_id).all()
|
||||||
return nsgroup_mapping.nsx_id, section_mapping.nsx_id
|
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):
|
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):
|
def delete_nsx_lbaas_l7policy_binding(session, l7policy_id):
|
||||||
return (session.query(nsx_models.NsxLbaasL7Policy).
|
return (session.query(nsx_models.NsxLbaasL7Policy).
|
||||||
filter_by(l7policy_id=l7policy_id).delete())
|
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()
|
||||||
|
@ -1 +1 @@
|
|||||||
ea7a72ab9643
|
9799427fc0e1
|
||||||
|
@ -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'))
|
@ -481,3 +481,10 @@ class NsxLbaasL7Policy(model_base.BASEV2, models.TimestampMixin):
|
|||||||
primary_key=True)
|
primary_key=True)
|
||||||
lb_rule_id = sa.Column(sa.String(36), nullable=False)
|
lb_rule_id = sa.Column(sa.String(36), nullable=False)
|
||||||
lb_vs_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)
|
||||||
|
133
vmware_nsx/extensions/projectpluginmap.py
Normal file
133
vmware_nsx/extensions/projectpluginmap.py
Normal file
@ -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
|
131
vmware_nsx/osc/v2/project_plugin_map.py
Normal file
131
vmware_nsx/osc/v2/project_plugin_map.py
Normal file
@ -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="<project>",
|
||||||
|
help=_("project")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--plugin',
|
||||||
|
metavar="<plugin>",
|
||||||
|
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='<id>',
|
||||||
|
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
|
@ -21,6 +21,7 @@
|
|||||||
from neutron.db.models import securitygroup # noqa
|
from neutron.db.models import securitygroup # noqa
|
||||||
|
|
||||||
from vmware_nsx.plugins.dvs import plugin as dvs
|
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_mh import plugin as nsx_mh
|
||||||
from vmware_nsx.plugins.nsx_v import plugin as nsx_v
|
from vmware_nsx.plugins.nsx_v import plugin as nsx_v
|
||||||
from vmware_nsx.plugins.nsx_v3 import plugin as nsx_v3
|
from vmware_nsx.plugins.nsx_v3 import plugin as nsx_v3
|
||||||
@ -29,3 +30,4 @@ NsxDvsPlugin = dvs.NsxDvsV2
|
|||||||
NsxPlugin = nsx_mh.NsxPluginV2
|
NsxPlugin = nsx_mh.NsxPluginV2
|
||||||
NsxVPlugin = nsx_v.NsxVPluginV2
|
NsxVPlugin = nsx_v.NsxVPluginV2
|
||||||
NsxV3Plugin = nsx_v3.NsxV3Plugin
|
NsxV3Plugin = nsx_v3.NsxV3Plugin
|
||||||
|
NsxTVDPlugin = nsx.NsxTVDPlugin
|
||||||
|
@ -47,6 +47,10 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
address_scope_db.AddressScopeDbMixin):
|
address_scope_db.AddressScopeDbMixin):
|
||||||
"""Common methods for NSX-V and NSX-V3 plugins"""
|
"""Common methods for NSX-V and NSX-V3 plugins"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin_type(self):
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@resource_extend.extends([net_def.COLLECTION_NAME])
|
@resource_extend.extends([net_def.COLLECTION_NAME])
|
||||||
def _ext_extend_network_dict(result, netdb):
|
def _ext_extend_network_dict(result, netdb):
|
||||||
|
@ -61,6 +61,7 @@ from vmware_nsx.db import nsxv_db
|
|||||||
from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes
|
from vmware_nsx.dhcp_meta import modes as dhcpmeta_modes
|
||||||
from vmware_nsx.dvs import dvs
|
from vmware_nsx.dvs import dvs
|
||||||
from vmware_nsx.dvs import dvs_utils
|
from vmware_nsx.dvs import dvs_utils
|
||||||
|
from vmware_nsx.extensions import projectpluginmap
|
||||||
from vmware_nsx.plugins.common import plugin as nsx_plugin_common
|
from vmware_nsx.plugins.common import plugin as nsx_plugin_common
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -115,6 +116,14 @@ class NsxDvsV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
self._dvs = dvs.SingleDvsManager()
|
self._dvs = dvs.SingleDvsManager()
|
||||||
self.setup_dhcpmeta_access()
|
self.setup_dhcpmeta_access()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def plugin_type():
|
||||||
|
return projectpluginmap.NsxPlugins.DVS
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_tvd_plugin():
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@resource_extend.extends([port_def.COLLECTION_NAME])
|
@resource_extend.extends([port_def.COLLECTION_NAME])
|
||||||
def _extend_port_dict_binding(result, portdb):
|
def _extend_port_dict_binding(result, portdb):
|
||||||
|
0
vmware_nsx/plugins/nsx/__init__.py
Normal file
0
vmware_nsx/plugins/nsx/__init__.py
Normal file
574
vmware_nsx/plugins/nsx/plugin.py
Normal file
574
vmware_nsx/plugins/nsx/plugin.py
Normal file
@ -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]
|
24
vmware_nsx/plugins/nsx/utils.py
Normal file
24
vmware_nsx/plugins/nsx/utils.py
Normal file
@ -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
|
@ -204,10 +204,11 @@ class NsxVAvailabilityZone(common_az.ConfiguredAvailabilityZone):
|
|||||||
|
|
||||||
class NsxVAvailabilityZones(common_az.ConfiguredAvailabilityZones):
|
class NsxVAvailabilityZones(common_az.ConfiguredAvailabilityZones):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, validate_default=False):
|
||||||
super(NsxVAvailabilityZones, self).__init__(
|
super(NsxVAvailabilityZones, self).__init__(
|
||||||
cfg.CONF.nsxv.availability_zones,
|
cfg.CONF.nsxv.availability_zones,
|
||||||
NsxVAvailabilityZone)
|
NsxVAvailabilityZone,
|
||||||
|
validate_default=validate_default)
|
||||||
|
|
||||||
def get_inventory(self):
|
def get_inventory(self):
|
||||||
"""Return a set of relevant resources in all the availability zones
|
"""Return a set of relevant resources in all the availability zones
|
||||||
|
@ -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 dns_search_domain as ext_dns_search_domain
|
||||||
from vmware_nsx.extensions import maclearning as mac_ext
|
from vmware_nsx.extensions import maclearning as mac_ext
|
||||||
from vmware_nsx.extensions import nsxpolicy
|
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 providersecuritygroup as provider_sg
|
||||||
from vmware_nsx.extensions import routersize
|
from vmware_nsx.extensions import routersize
|
||||||
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix
|
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix
|
||||||
from vmware_nsx.extensions import securitygrouplogging as sg_logging
|
from vmware_nsx.extensions import securitygrouplogging as sg_logging
|
||||||
from vmware_nsx.extensions import securitygrouppolicy as sg_policy
|
from vmware_nsx.extensions import securitygrouppolicy as sg_policy
|
||||||
from vmware_nsx.plugins.common import plugin as nsx_plugin_common
|
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 availability_zones as nsx_az
|
||||||
from vmware_nsx.plugins.nsx_v import managers
|
from vmware_nsx.plugins.nsx_v import managers
|
||||||
from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy
|
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,
|
router=l3_db_models.Router,
|
||||||
floatingip=l3_db_models.FloatingIP)
|
floatingip=l3_db_models.FloatingIP)
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._extension_manager = nsx_managers.ExtensionManager()
|
self._is_sub_plugin = tvd_utils.is_tvd_core_plugin()
|
||||||
super(NsxVPluginV2, self).__init__()
|
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
|
# Bind the dummy L3 notifications
|
||||||
self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI()
|
self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI()
|
||||||
self.init_is_complete = False
|
self.init_is_complete = False
|
||||||
@ -311,6 +319,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
# Bind QoS notifications
|
# Bind QoS notifications
|
||||||
qos_driver.register(self)
|
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):
|
def init_complete(self, resource, event, trigger, payload=None):
|
||||||
with locking.LockManager.get_lock('plugin-init-complete'):
|
with locking.LockManager.get_lock('plugin-init-complete'):
|
||||||
if self.init_is_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:
|
if vnic_id is None or added_sgids is None:
|
||||||
return
|
return
|
||||||
for add_sg in added_sgids:
|
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:
|
if nsx_sg_id is None:
|
||||||
LOG.warning("NSX security group not found for %s", add_sg)
|
LOG.warning("NSX security group not found for %s", add_sg)
|
||||||
else:
|
else:
|
||||||
@ -958,7 +975,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
return
|
return
|
||||||
# Remove vnic from delete security groups binding
|
# Remove vnic from delete security groups binding
|
||||||
for del_sg in deleted_sgids:
|
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:
|
if nsx_sg_id is None:
|
||||||
LOG.warning("NSX security group not found for %s", del_sg)
|
LOG.warning("NSX security group not found for %s", del_sg)
|
||||||
else:
|
else:
|
||||||
@ -994,7 +1012,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
return '%s.%03d' % (device_id, port_index)
|
return '%s.%03d' % (device_id, port_index)
|
||||||
|
|
||||||
def init_availability_zones(self):
|
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):
|
def _list_availability_zones(self, context, filters=None):
|
||||||
#TODO(asarfaty): We may need to use the filters arg, but now it
|
#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):
|
obj_data):
|
||||||
if az_def.AZ_HINTS in obj_data:
|
if az_def.AZ_HINTS in obj_data:
|
||||||
self.validate_availability_zones(context, resource_type,
|
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,
|
def validate_availability_zones(self, context, resource_type,
|
||||||
availability_zones):
|
availability_zones, force=False):
|
||||||
"""Verify that the availability zones exist, and only 1 hint
|
"""Verify that the availability zones exist, and only 1 hint
|
||||||
was set.
|
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)
|
return self.validate_obj_azs(availability_zones)
|
||||||
|
|
||||||
def _prepare_spoofguard_policy(self, network_type, net_data, net_morefs):
|
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):
|
def update_security_group(self, context, id, security_group):
|
||||||
s = security_group['security_group']
|
s = security_group['security_group']
|
||||||
self._validate_security_group(context, s, False, id=id)
|
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_uri = self._get_section_uri(context.session, id)
|
||||||
section_needs_update = False
|
section_needs_update = False
|
||||||
|
|
||||||
@ -4190,7 +4217,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
|
|
||||||
return sg_data
|
return sg_data
|
||||||
|
|
||||||
def delete_security_group(self, context, id):
|
def delete_security_group(self, context, id, delete_base=True):
|
||||||
"""Delete a security group."""
|
"""Delete a security group."""
|
||||||
self._prevent_non_admin_delete_provider_sg(context, id)
|
self._prevent_non_admin_delete_provider_sg(context, id)
|
||||||
self._prevent_non_admin_delete_policy_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)
|
section_uri = self._get_section_uri(context.session, id)
|
||||||
|
|
||||||
# Find nsx security group
|
# 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
|
if delete_base:
|
||||||
super(NsxVPluginV2, self).delete_security_group(context, id)
|
# Delete neutron security group
|
||||||
|
super(NsxVPluginV2, self).delete_security_group(context, id)
|
||||||
|
|
||||||
# Delete nsx rule sections
|
# Delete nsx rule sections
|
||||||
self._delete_section(section_uri)
|
self._delete_section(section_uri)
|
||||||
@ -4228,7 +4257,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
if nsx_sg_id is None:
|
if nsx_sg_id is None:
|
||||||
# Find nsx security group for neutron security group
|
# Find nsx security group for neutron security group
|
||||||
nsx_sg_id = nsx_db.get_nsx_security_group_id(
|
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
|
# Find the remote nsx security group id, which might be the current
|
||||||
# one. In case of the default security-group, the associated
|
# 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
|
remote_nsx_sg_id = nsx_sg_id
|
||||||
else:
|
else:
|
||||||
remote_nsx_sg_id = nsx_db.get_nsx_security_group_id(
|
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
|
# Get source and destination containers from rule
|
||||||
if rule['direction'] == 'ingress':
|
if rule['direction'] == 'ingress':
|
||||||
@ -4283,12 +4313,15 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
tag='Project_%s' % rule['tenant_id'])
|
tag='Project_%s' % rule['tenant_id'])
|
||||||
return nsx_rule
|
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."""
|
"""Create a single security group rule."""
|
||||||
bulk_rule = {'security_group_rules': [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.
|
"""Create security group rules.
|
||||||
|
|
||||||
:param security_group_rules: list of rules to create
|
:param security_group_rules: list of rules to create
|
||||||
@ -4321,7 +4354,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
rule = r['security_group_rule']
|
rule = r['security_group_rule']
|
||||||
if not self._check_local_ip_prefix(context, rule):
|
if not self._check_local_ip_prefix(context, rule):
|
||||||
rule[secgroup_rule_local_ip_prefix.LOCAL_IP_PREFIX] = None
|
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'])
|
ruleids.add(rule['id'])
|
||||||
nsx_rules.append(
|
nsx_rules.append(
|
||||||
self._create_nsx_rule(context, rule,
|
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
|
# Save new rules in Database, including mappings between Nsx rules
|
||||||
# and Neutron security-groups rules
|
# and Neutron security-groups rules
|
||||||
with db_api.context_manager.writer.using(context):
|
with db_api.context_manager.writer.using(context):
|
||||||
new_rule_list = super(
|
if create_base:
|
||||||
NsxVPluginV2, self).create_security_group_rule_bulk_native(
|
new_rule_list = super(
|
||||||
context, security_group_rules)
|
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:
|
for pair in rule_pairs:
|
||||||
neutron_rule_id = pair['neutron_id']
|
neutron_rule_id = pair['neutron_id']
|
||||||
nsx_rule_id = pair['nsx_id']
|
nsx_rule_id = pair['nsx_id']
|
||||||
if neutron_rule_id in ruleids:
|
if neutron_rule_id in ruleids:
|
||||||
nsxv_db.add_neutron_nsx_rule_mapping(
|
nsxv_db.add_neutron_nsx_rule_mapping(
|
||||||
context.session, neutron_rule_id, nsx_rule_id)
|
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:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
for nsx_rule_id in [p['nsx_id'] for p in rule_pairs
|
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")
|
LOG.exception("Failed to create security group rule")
|
||||||
return new_rule_list
|
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."""
|
"""Delete a security group rule."""
|
||||||
rule_db = self._get_security_group_rule(context, id)
|
rule_db = self._get_security_group_rule(context, id)
|
||||||
security_group_id = rule_db['security_group_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 "
|
LOG.debug("Security group rule %(id)s deleted, backend "
|
||||||
"nsx-rule %(nsx_rule_id)s doesn't exist.",
|
"nsx-rule %(nsx_rule_id)s doesn't exist.",
|
||||||
{'id': id, 'nsx_rule_id': nsx_rule_id})
|
{'id': id, 'nsx_rule_id': nsx_rule_id})
|
||||||
|
if delete_base:
|
||||||
securitygroup.SecurityGroupRule.delete_objects(context, id=id)
|
securitygroup.SecurityGroupRule.delete_objects(context, id=id)
|
||||||
|
|
||||||
def _remove_vnic_from_spoofguard_policy(self, session, net_id, vnic_id):
|
def _remove_vnic_from_spoofguard_policy(self, session, net_id, vnic_id):
|
||||||
policy_id = nsxv_db.get_spoofguard_policy_id(session, net_id)
|
policy_id = nsxv_db.get_spoofguard_policy_id(session, net_id)
|
||||||
|
@ -204,10 +204,11 @@ class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones):
|
|||||||
|
|
||||||
default_name = DEFAULT_NAME
|
default_name = DEFAULT_NAME
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, validate_default=False):
|
||||||
super(NsxV3AvailabilityZones, self).__init__(
|
super(NsxV3AvailabilityZones, self).__init__(
|
||||||
cfg.CONF.nsx_v3.availability_zones,
|
cfg.CONF.nsx_v3.availability_zones,
|
||||||
NsxV3AvailabilityZone)
|
NsxV3AvailabilityZone,
|
||||||
|
validate_default=validate_default)
|
||||||
|
|
||||||
def dhcp_relay_configured(self):
|
def dhcp_relay_configured(self):
|
||||||
for az in self.availability_zones.values():
|
for az in self.availability_zones.values():
|
||||||
|
@ -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.dhcp_meta import rpc as nsx_rpc
|
||||||
from vmware_nsx.extensions import advancedserviceproviders as as_providers
|
from vmware_nsx.extensions import advancedserviceproviders as as_providers
|
||||||
from vmware_nsx.extensions import maclearning as mac_ext
|
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 providersecuritygroup as provider_sg
|
||||||
from vmware_nsx.extensions import securitygrouplogging as sg_logging
|
from vmware_nsx.extensions import securitygrouplogging as sg_logging
|
||||||
from vmware_nsx.plugins.common import plugin as nsx_plugin_common
|
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 availability_zones as nsx_az
|
||||||
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
|
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
|
||||||
from vmware_nsx.services.fwaas.common import utils as fwaas_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,
|
router=l3_db_models.Router,
|
||||||
floatingip=l3_db_models.FloatingIP)
|
floatingip=l3_db_models.FloatingIP)
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self._is_sub_plugin = tvd_utils.is_tvd_core_plugin()
|
||||||
nsxlib_utils.set_is_attr_callback(validators.is_attr_set)
|
nsxlib_utils.set_is_attr_callback(validators.is_attr_set)
|
||||||
self._extend_fault_map()
|
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__()
|
super(NsxV3Plugin, self).__init__()
|
||||||
# Bind the dummy L3 notifications
|
# Bind the dummy L3 notifications
|
||||||
self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI()
|
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
|
# Register NSXv3 trunk driver to support trunk extensions
|
||||||
self.trunk_driver = trunk_driver.NsxV3TrunkDriver.create(self)
|
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):
|
def init_complete(self, resource, event, trigger, payload=None):
|
||||||
with locking.LockManager.get_lock('plugin-init-complete'):
|
with locking.LockManager.get_lock('plugin-init-complete'):
|
||||||
if self.init_is_complete:
|
if self.init_is_complete:
|
||||||
@ -364,7 +380,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
|||||||
"DHCP metadata")
|
"DHCP metadata")
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise n_exc.InvalidInput(error_message=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):
|
def _init_nsx_profiles(self):
|
||||||
LOG.debug("Initializing NSX v3 port spoofguard switching profile")
|
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
|
# validate the availability zone, and get the AZ object
|
||||||
if az_def.AZ_HINTS in net_data:
|
if az_def.AZ_HINTS in net_data:
|
||||||
self.validate_availability_zones(context, 'network',
|
self._validate_availability_zones_forced(
|
||||||
net_data[az_def.AZ_HINTS])
|
context, 'network', net_data[az_def.AZ_HINTS])
|
||||||
az = self.get_obj_az_by_hints(net_data)
|
az = self.get_obj_az_by_hints(net_data)
|
||||||
|
|
||||||
self._ensure_default_security_group(context, tenant_id)
|
self._ensure_default_security_group(context, tenant_id)
|
||||||
@ -1097,6 +1115,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
|||||||
{'network': {'vlan_transparent': vlt}})
|
{'network': {'vlan_transparent': vlt}})
|
||||||
|
|
||||||
rollback_network = True
|
rollback_network = True
|
||||||
|
|
||||||
is_ddi_network = self._is_ddi_supported_on_network(
|
is_ddi_network = self._is_ddi_supported_on_network(
|
||||||
context, created_net['id'])
|
context, created_net['id'])
|
||||||
if (is_backend_network and
|
if (is_backend_network and
|
||||||
@ -3265,8 +3284,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
|||||||
|
|
||||||
# validate the availability zone
|
# validate the availability zone
|
||||||
if az_def.AZ_HINTS in r:
|
if az_def.AZ_HINTS in r:
|
||||||
self.validate_availability_zones(context, 'router',
|
self._validate_availability_zones_forced(context, 'router',
|
||||||
r[az_def.AZ_HINTS])
|
r[az_def.AZ_HINTS])
|
||||||
|
|
||||||
gw_info = self._extract_external_gw(context, router, is_extract=True)
|
gw_info = self._extract_external_gw(context, router, is_extract=True)
|
||||||
r['id'] = (r.get('id') or uuidutils.generate_uuid())
|
r['id'] = (r.get('id') or uuidutils.generate_uuid())
|
||||||
@ -4360,8 +4379,19 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
|||||||
result[(az, 'network')] = True
|
result[(az, 'network')] = True
|
||||||
return result
|
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,
|
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 no native_dhcp_metadata - use neutron AZs
|
||||||
if not cfg.CONF.nsx_v3.native_dhcp_metadata:
|
if not cfg.CONF.nsx_v3.native_dhcp_metadata:
|
||||||
return super(NsxV3Plugin, self).validate_availability_zones(
|
return super(NsxV3Plugin, self).validate_availability_zones(
|
||||||
|
Loading…
Reference in New Issue
Block a user