Gary Kotton 427cf1ff94 NSXV3: ensure all OS ports are added to default section
Enable different application to use the NSX and not have the
default section discard all traffic on ports not owned by OpenStack.

This ensures that all openstack ports are added to the default group.

A admin utility has been added to enabled the migration:
nsxadmin -r ports -o nsx-tag-default

Change-Id: I2572241ec1906ee396b61f520e8e860799367f5b
2017-04-26 20:13:22 +03:00

373 lines
15 KiB
Python

# Copyright 2016 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
from oslo_log import log as logging
from sqlalchemy.orm import exc
from vmware_nsx.common import utils as nsx_utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import nsx_models
from vmware_nsx.dvs import dvs
from vmware_nsx.plugins.nsx_v3 import plugin
from vmware_nsx.services.qos.common import utils as qos_utils
from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import formatters
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils as v3_utils
from vmware_nsx.shell import resources as shell
from vmware_nsxlib.v3 import exceptions as nsx_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
from vmware_nsxlib.v3 import resources
from vmware_nsxlib.v3 import security
from neutron.db import allowedaddresspairs_db as addr_pair_db
from neutron.db import db_base_plugin_v2
from neutron.db import l3_db
from neutron.db import portsecurity_db
from neutron.extensions import allowedaddresspairs
from neutron_lib.callbacks import registry
from neutron_lib import constants as const
from neutron_lib import context as neutron_context
from neutron_lib.plugins import directory
LOG = logging.getLogger(__name__)
class PortsPlugin(db_base_plugin_v2.NeutronDbPluginV2,
portsecurity_db.PortSecurityDbMixin,
addr_pair_db.AllowedAddressPairsMixin):
def __enter__(self):
directory.add_plugin(const.CORE, self)
return self
def __exit__(self, exc_type, exc_value, traceback):
directory.add_plugin(const.CORE, None)
def get_port_nsx_id(session, neutron_id):
# get the nsx port id from the DB mapping
try:
mapping = (session.query(nsx_models.NeutronNsxPortMapping).
filter_by(neutron_id=neutron_id).
one())
return mapping['nsx_port_id']
except exc.NoResultFound:
pass
def get_network_nsx_id(session, neutron_id):
# get the nsx switch id from the DB mapping
mappings = nsx_db.get_nsx_switch_ids(session, neutron_id)
if not mappings or len(mappings) == 0:
LOG.debug("Unable to find NSX mappings for neutron "
"network %s.", neutron_id)
# fallback in case we didn't find the id in the db mapping
# This should not happen, but added here in case the network was
# created before this code was added.
return neutron_id
else:
return mappings[0]
def get_port_and_profile_clients():
_nsx_client = v3_utils.get_nsxv3_client()
return (resources.LogicalPort(_nsx_client),
resources.SwitchingProfile(_nsx_client))
def get_dhcp_profile_id(profile_client):
profiles = profile_client.find_by_display_name(
plugin.NSX_V3_DHCP_PROFILE_NAME)
if profiles and len(profiles) == 1:
return profiles[0]['id']
LOG.warning("Could not find DHCP profile on backend")
def get_spoofguard_profile_id(profile_client):
profiles = profile_client.find_by_display_name(
plugin.NSX_V3_PSEC_PROFILE_NAME)
if profiles and len(profiles) == 1:
return profiles[0]['id']
LOG.warning("Could not find Spoof Guard profile on backend")
def add_profile_mismatch(problems, neutron_id, nsx_id, prf_id, title):
msg = ('Wrong %(title)s profile %(prf_id)s') % {'title': title,
'prf_id': prf_id}
problems.append({'neutron_id': neutron_id,
'nsx_id': nsx_id,
'error': msg})
@admin_utils.output_header
def list_missing_ports(resource, event, trigger, **kwargs):
"""List neutron ports that are missing the NSX backend port
And ports with wrong switch profiles
"""
admin_cxt = neutron_context.get_admin_context()
with PortsPlugin() as plugin:
neutron_ports = plugin.get_ports(admin_cxt)
port_client, profile_client = get_port_and_profile_clients()
# get pre-defined profile ids
dhcp_profile_id = get_dhcp_profile_id(profile_client)
dhcp_profile_key = resources.SwitchingProfileTypes.SWITCH_SECURITY
spoofguard_profile_id = get_spoofguard_profile_id(profile_client)
spoofguard_profile_key = resources.SwitchingProfileTypes.SPOOF_GUARD
qos_profile_key = resources.SwitchingProfileTypes.QOS
problems = []
for port in neutron_ports:
neutron_id = port['id']
# get the network nsx id from the mapping table
nsx_id = get_port_nsx_id(admin_cxt.session, neutron_id)
if not nsx_id:
# skip external ports
pass
else:
try:
nsx_port = port_client.get(nsx_id)
except nsx_exc.ResourceNotFound:
problems.append({'neutron_id': neutron_id,
'nsx_id': nsx_id,
'error': 'Missing from backend'})
continue
# Port found on backend!
# Check that it has all the expected switch profiles.
# create a dictionary of the current profiles:
profiles_dict = {}
for prf in nsx_port['switching_profile_ids']:
profiles_dict[prf['key']] = prf['value']
# DHCP port: neutron dhcp profile should be attached
# to logical ports created for neutron DHCP but not
# for native DHCP.
if (port.get('device_owner') == const.DEVICE_OWNER_DHCP and
not cfg.CONF.nsx_v3.native_dhcp_metadata):
prf_id = profiles_dict[dhcp_profile_key]
if prf_id != dhcp_profile_id:
add_profile_mismatch(problems, neutron_id, nsx_id,
prf_id, "DHCP security")
# Port with QoS policy: a matching profile should be attached
qos_policy_id = qos_utils.get_port_policy_id(admin_cxt,
neutron_id)
if qos_policy_id:
qos_profile_id = nsx_db.get_switch_profile_by_qos_policy(
admin_cxt.session, qos_policy_id)
prf_id = profiles_dict[qos_profile_key]
if prf_id != qos_profile_id:
add_profile_mismatch(problems, neutron_id, nsx_id,
prf_id, "QoS")
# Port with security & fixed ips/address pairs:
# neutron spoofguard profile should be attached
port_sec, has_ip = plugin._determine_port_security_and_has_ip(
admin_cxt, port)
addr_pair = port.get(allowedaddresspairs.ADDRESS_PAIRS)
if port_sec and (has_ip or addr_pair):
prf_id = profiles_dict[spoofguard_profile_key]
if prf_id != spoofguard_profile_id:
add_profile_mismatch(problems, neutron_id, nsx_id,
prf_id, "Spoof Guard")
if len(problems) > 0:
title = ("Found internal ports misconfiguration on the "
"NSX manager:")
LOG.info(formatters.output_formatter(
title, problems,
['neutron_id', 'nsx_id', 'error']))
else:
LOG.info("All internal ports verified on the NSX manager")
def get_vm_network_device(vm_mng, vm_moref, mac_address):
"""Return the network device with MAC 'mac_address'.
This code was inspired by Nova vif.get_network_device
"""
hardware_devices = vm_mng.get_vm_interfaces_info(vm_moref)
if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice":
hardware_devices = hardware_devices.VirtualDevice
for device in hardware_devices:
if hasattr(device, 'macAddress'):
if device.macAddress == mac_address:
return device
def migrate_compute_ports_vms(resource, event, trigger, **kwargs):
"""Update the VMs ports on the backend after migrating nsx-v -> nsx-v3
After using api_replay to migrate the neutron data from NSX-V to NSX-T
we need to update the VM ports to use OpaqueNetwork instead of
DistributedVirtualPortgroup
"""
# Connect to the DVS manager, using the configuration parameters
try:
vm_mng = dvs.VMManager()
except Exception as e:
LOG.error("Cannot connect to the DVS: Please update the [dvs] "
"section in the nsx.ini file: %s", e)
return
# Go over all the compute ports from the plugin
admin_cxt = neutron_context.get_admin_context()
port_filters = {'device_owner': ['compute:None']}
with PortsPlugin() as plugin:
neutron_ports = plugin.get_ports(admin_cxt, filters=port_filters)
for port in neutron_ports:
device_id = port.get('device_id')
# get the vm moref & spec from the DVS
vm_moref = vm_mng.get_vm_moref_obj(device_id)
vm_spec = vm_mng.get_vm_spec(vm_moref)
if not vm_spec:
LOG.error("Failed to get the spec of vm %s", device_id)
continue
# Go over the VM interfaces and check if it should be updated
update_spec = False
for prop in vm_spec.propSet:
if (prop.name == 'network' and
hasattr(prop.val, 'ManagedObjectReference')):
for net in prop.val.ManagedObjectReference:
if net._type == 'DistributedVirtualPortgroup':
update_spec = True
if not update_spec:
LOG.info("No need to update the spec of vm %s", device_id)
continue
# find the old interface by it's mac and delete it
device = get_vm_network_device(vm_mng, vm_moref, port['mac_address'])
if device is None:
LOG.warning("No device with MAC address %s exists on the VM",
port['mac_address'])
continue
device_type = device.__class__.__name__
LOG.info("Detaching old interface from VM %s", device_id)
vm_mng.detach_vm_interface(vm_moref, device)
# add the new interface as OpaqueNetwork
LOG.info("Attaching new interface to VM %s", device_id)
nsx_net_id = get_network_nsx_id(admin_cxt.session, port['network_id'])
vm_mng.attach_vm_interface(vm_moref, port['id'], port['mac_address'],
nsx_net_id, device_type)
def migrate_exclude_ports(resource, event, trigger, **kwargs):
_nsx_client = v3_utils.get_nsxv3_client()
nsxlib = v3_utils.get_connected_nsxlib()
version = nsxlib.get_version()
if not nsx_utils.is_nsx_version_2_0_0(version):
LOG.info("Migration only supported from 2.0 onwards")
LOG.info("Version is %s", version)
return
admin_cxt = neutron_context.get_admin_context()
plugin = PortsPlugin()
_port_client = resources.LogicalPort(_nsx_client)
exclude_list = nsxlib.firewall_section.get_excludelist()
for member in exclude_list['members']:
if member['target_type'] == 'LogicalPort':
port_id = member['target_id']
# Get port
try:
nsx_port = _port_client.get(port_id)
except nsx_exc.ResourceNotFound:
LOG.info("Port %s not found", port_id)
continue
# Validate its a neutron port
is_neutron_port = False
for tag in nsx_port['tags']:
if tag['scope'] == 'os-neutron-port-id':
is_neutron_port = True
neutron_port_id = tag['tag']
break
if not is_neutron_port:
LOG.info("Port %s is not a neutron port", port_id)
continue
# Check if this port exists in the DB
try:
plugin.get_port(admin_cxt, neutron_port_id)
except Exception:
LOG.info("Port %s is not defined in DB", neutron_port_id)
continue
# Update tag for the port
tags_update = [{'scope': security.PORT_SG_SCOPE,
'tag': nsxlib_consts.EXCLUDE_PORT}]
_port_client.update(port_id, None,
tags_update=tags_update)
# Remove port from the exclude list
nsxlib.firewall_section.remove_member_from_fw_exclude_list(
port_id, nsxlib_consts.TARGET_TYPE_LOGICAL_PORT)
LOG.info("Port %s successfully updated", port_id)
def tag_default_ports(resource, event, trigger, **kwargs):
nsxlib = v3_utils.get_connected_nsxlib()
admin_cxt = neutron_context.get_admin_context()
# the plugin creation below will create the NS group and update the default
# OS section to have the correct applied to group
with v3_utils.NsxV3PluginWrapper() as _plugin:
neutron_ports = _plugin.get_ports(admin_cxt)
for port in neutron_ports:
neutron_id = port['id']
# get the network nsx id from the mapping table
nsx_id = get_port_nsx_id(admin_cxt.session, neutron_id)
if not nsx_id:
continue
device_owner = port['device_owner']
if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or
device_owner == const.DEVICE_OWNER_DHCP):
continue
ps = _plugin._get_port_security_binding(admin_cxt,
neutron_id)
if not ps:
continue
try:
nsx_port = nsxlib.logical_port.get(nsx_id)
except nsx_exc.ResourceNotFound:
continue
tags_update = nsx_port['tags']
tags_update += [{'scope': security.PORT_SG_SCOPE,
'tag': plugin.NSX_V3_DEFAULT_SECTION}]
nsxlib.logical_port.update(nsx_id, None,
tags_update=tags_update)
registry.subscribe(list_missing_ports,
constants.PORTS,
shell.Operations.LIST_MISMATCHES.value)
registry.subscribe(migrate_compute_ports_vms,
constants.PORTS,
shell.Operations.NSX_MIGRATE_V_V3.value)
registry.subscribe(migrate_exclude_ports,
constants.PORTS,
shell.Operations.NSX_MIGRATE_EXCLUDE_PORTS.value)
registry.subscribe(tag_default_ports,
constants.PORTS,
shell.Operations.NSX_TAG_DEFAULT.value)