NSX|P: Port create/update/delete enhancments

Complete the support for port operations
+ enabling hte relevant tempest tests

Depends-on: Ib5d26e8b22a9a167151fb17e087e8b561ea74783
Change-Id: Ia31e2180b880b29608dab42b4c810ae68547c24b
This commit is contained in:
Adit Sarfaty 2018-11-07 11:47:04 +02:00
parent b024fb250d
commit ff6c595b90
5 changed files with 245 additions and 142 deletions

View File

@ -19,7 +19,37 @@
# based on the features that are ready to be tested.
# Begin list of exclusions.
#r="^(?!.*)"
r="^(?!.*"
r="$r(?:tempest\.api\.network\.test_extensions\.ExtensionsTestJSON.*)"
r="$r|(?:tempest\.api\.network\.test_routers\.DvrRoutersTest.*)"
r="$r|(?:tempest\.api\.network\.test_routers_negative\.DvrRoutersNegativeTest.*)"
r="$r(tempest\.api\.network\.test_security_groups|tempest\.api\.network\.test_networks|tempest\.api\.network\.test_networks_negative).*$"
export DEVSTACK_GATE_TEMPEST_REGEX="$r"
r="$r|(?:tempest\.api\.network\.test_allowed_address_pair\.AllowedAddressPairTestJSON\.test_update_port_with_cidr_address_pair.*)"
#Native DHCP has no agents
r="$r|(?:tempest\.api\.network\.admin\.test_agent_management\.AgentManagementTestJSON.*)"
#Can not create more than one DHCP-enabled subnet
r="$r|(?:tempest\.api\.network\.test_ports\.PortsTestJSON\.test_create_update_port_with_second_ip.*)"
r="$r|(?:tempest\.api\.network\.test_ports\.PortsTestJSON\.test_update_port_with_security_group_and_extra_attributes.*)"
r="$r|(?:tempest\.api\.network\.test_ports\.PortsTestJSON\.test_update_port_with_two_security_groups_and_extra_attributes.*)"
r="$r|(?:tempest\.api\.network\.test_extra_dhcp_options\.ExtraDHCPOptionsTestJSON\.test_.*_with_extra_dhcp_options.*)"
r="$r|(?:tempest\.api\.network\.test_floating_ips\.FloatingIPTestJSON\.test_create_update_floatingip_with_port_multiple_ip_address.*)"
r="$r|(?:tempest\.api\.network\.admin\.test_external_network_extension\.ExternalNetworksTestJSON\.test_update_external_network.*)"
# Some ICMP types are not supported by the NSX backend
r="$r|(?:tempest\.api\.network\.test_security_groups\.SecGroupTest\.test_create_security_group_rule_with_icmp_type_code.*)"
# Temporarily exclude packages which are not yet supported by the P plugin
r="$r|(?:tempest\.api\.network\.admin\.test_floating_ips_admin_actions.*)"
r="$r|(?:tempest\.api\.network\.admin\.test_routers.*)"
r="$r|(?:tempest\.api\.network\.admin\.test_routers_negative.*)"
r="$r|(?:tempest\.api\.network\.test_floating_ips.*)"
r="$r|(?:tempest\.api\.network\.test_routers.*)"
# End list of exclusions.
r="$r)"
# only run tempest.api.network tests
r="$r(tempest\.api\.network).*$"
export DEVSTACK_GATE_TEMPEST_REGEX="$r"

View File

@ -157,6 +157,96 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
self._get_security_groups_on_port(context, port))
return port_security, has_ip, sgids, psgids
def _should_validate_port_sec_on_update_port(self, port_data):
# Need to determine if we skip validations for port security.
# This is the edge case when the subnet is deleted.
# This should be called prior to deleting the fixed ip from the
# port data
for fixed_ip in port_data.get('fixed_ips', []):
if 'delete_subnet' in fixed_ip:
return False
return True
def _update_port_preprocess_security(
self, context, port, id, updated_port, is_ens_tz_port,
validate_port_sec=True, direct_vnic_type=False):
delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
port)
has_addr_pairs = self._check_update_has_allowed_address_pairs(port)
has_security_groups = self._check_update_has_security_groups(port)
delete_security_groups = self._check_update_deletes_security_groups(
port)
# populate port_security setting
port_data = port['port']
if psec.PORTSECURITY not in port_data:
updated_port[psec.PORTSECURITY] = \
self._get_port_security_binding(context, id)
has_ip = self._ip_on_port(updated_port)
# validate port security and allowed address pairs
if not updated_port[psec.PORTSECURITY]:
# has address pairs in request
if has_addr_pairs:
raise addr_exc.AddressPairAndPortSecurityRequired()
elif not delete_addr_pairs:
# check if address pairs are in db
updated_port[addr_apidef.ADDRESS_PAIRS] = (
self.get_allowed_address_pairs(context, id))
if updated_port[addr_apidef.ADDRESS_PAIRS]:
raise addr_exc.AddressPairAndPortSecurityRequired()
if delete_addr_pairs or has_addr_pairs:
self._validate_ipv4_address_pairs(
updated_port[addr_apidef.ADDRESS_PAIRS])
# delete address pairs and read them in
self._delete_allowed_address_pairs(context, id)
self._process_create_allowed_address_pairs(
context, updated_port,
updated_port[addr_apidef.ADDRESS_PAIRS])
if updated_port[psec.PORTSECURITY] and psec.PORTSECURITY in port_data:
# No port security is allowed if the port belongs to an ENS TZ
if is_ens_tz_port and not self._ens_psec_supported():
raise nsx_exc.NsxENSPortSecurity()
# No port security is allowed if the port has a direct vnic type
if direct_vnic_type:
err_msg = _("Security features are not supported for "
"ports with direct/direct-physical VNIC type")
raise n_exc.InvalidInput(error_message=err_msg)
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
provider_sgs_specified = self._provider_sgs_specified(updated_port)
if (validate_port_sec and
not (has_ip and updated_port[psec.PORTSECURITY])):
if has_security_groups or provider_sgs_specified:
LOG.error("Port has conflicting port security status and "
"security groups")
raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
# Update did not have security groups passed in. Check
# that port does not have any security groups already on it.
filters = {'port_id': [id]}
security_groups = (
super(NsxPluginV3Base, self)._get_port_security_group_bindings(
context, filters)
)
if security_groups and not delete_security_groups:
raise psec_exc.PortSecurityPortHasSecurityGroup()
if delete_security_groups or has_security_groups:
# delete the port binding and read it with the new rules.
self._delete_port_security_group_bindings(context, id)
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, updated_port,
sgids)
if psec.PORTSECURITY in port['port']:
self._process_port_port_security_update(
context, port['port'], updated_port)
return updated_port
def _validate_create_network(self, context, net_data):
"""Validate the parameters of the new network to be created
@ -678,3 +768,24 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
if net.get(pnet.NETWORK_TYPE) in net_types:
return True
return False
def _revert_neutron_port_update(self, context, port_id,
original_port, updated_port,
port_security, sec_grp_updated):
# revert the neutron port update
super(NsxPluginV3Base, self).update_port(context, port_id,
{'port': original_port})
# revert allowed address pairs
if port_security:
orig_pair = original_port.get(addr_apidef.ADDRESS_PAIRS)
updated_pair = updated_port.get(addr_apidef.ADDRESS_PAIRS)
if orig_pair != updated_pair:
self._delete_allowed_address_pairs(context, port_id)
if orig_pair:
self._process_create_allowed_address_pairs(
context, original_port, orig_pair)
# revert the security groups modifications
if sec_grp_updated:
self.update_security_group_on_port(
context, port_id, {'port': original_port},
updated_port, original_port)

View File

@ -188,7 +188,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return None
try:
resource_api.get(name_or_id)
resource_api.get(name_or_id, silent=True)
return name_or_id
except nsx_lib_exc.ResourceNotFound:
try:
@ -516,6 +516,19 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return address_bindings
def _get_network_nsx_id(self, context, network_id):
"""Return the id of this logical switch in the nsx manager
(Not the segment in the policy manager)
The nova api will use this to attach to the instance
"""
#TODO(asarfaty): This is a backend call that will be called for
# each get_port/s. We should consider caching the results or adding
# to DB
if not self._network_is_external(context, network_id):
segment_id = self._get_network_nsx_segment_id(context, network_id)
return self.nsxpolicy.segment.get_realized_id(segment_id)
def _get_network_nsx_segment_id(self, context, network_id):
"""Return the NSX segment ID matching the neutron network id
Usually the NSX ID is the same as the neutron ID. The exception is
@ -549,9 +562,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
address_bindings = self._build_port_address_bindings(
context, port_data)
device_owner = port_data.get('device_owner')
vif_id = None
attachment_type = vif_id = None
if device_owner and device_owner != l3_db.DEVICE_OWNER_ROUTER_INTF:
vif_id = port_data['id']
attachment_type = nsxlib_consts.ATTACHMENT_VIF
tags = self.nsxpolicy.build_v3_api_version_project_tag(
context.tenant_name)
@ -559,7 +573,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
tags.extend(self.nsxpolicy.build_v3_api_version_project_tag(
context.tenant_name))
segment_id = self._get_network_nsx_id(
segment_id = self._get_network_nsx_segment_id(
context, port_data['network_id'])
self.nsxpolicy.segment_port.create_or_overwrite(
name, segment_id,
@ -567,6 +581,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
description=port_data.get('description'),
address_bindings=address_bindings,
vif_id=vif_id,
attachment_type=attachment_type,
tags=tags)
def base_create_port(self, context, port):
@ -606,6 +621,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._process_port_create_security_group(context, port_data, sgids)
self._process_port_create_provider_security_group(
context, port_data, psgids)
#TODO(asarfaty): Handle mac learning
if not is_external_net:
try:
@ -633,21 +649,20 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
l3_port_check=True, l2gw_port_check=True,
force_delete_dhcp=False,
force_delete_vpn=False):
# first update neutron (this will perform all types of validations)
port_data = self.get_port(context, port_id)
segment_id = self._get_network_nsx_id(
context, port_data['network_id'])
net_id = port_data['network_id']
self.disassociate_floatingips(context, port_id)
super(NsxPolicyPlugin, self).delete_port(context, port_id)
if not self._network_is_external(context, port_data['network_id']):
if not self._network_is_external(context, net_id):
try:
segment_id = self._get_network_nsx_segment_id(context, net_id)
self.nsxpolicy.segment_port.delete(segment_id, port_data['id'])
except Exception as ex:
LOG.error("Failed to delete port %(id)s on NSX backend "
"due to %(e)s",
{'id': port_data['id'], 'e': ex})
self.disassociate_floatingips(context, port_id)
super(NsxPolicyPlugin, self).delete_port(context, port_id)
"due to %(e)s", {'id': port_id, 'e': ex})
# Do not fail the neutron action
def _update_port_on_backend(self, context, lport_id,
original_port, updated_port):
@ -655,12 +670,15 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# Update might evolve with more features
return self._create_port_on_backend(context, updated_port)
def update_port(self, context, id, port):
def update_port(self, context, port_id, port):
with db_api.CONTEXT_WRITER.using(context):
# get the original port, and keep it honest as it is later used
# for notifications
original_port = super(NsxPolicyPlugin, self).get_port(context, id)
original_port = super(NsxPolicyPlugin, self).get_port(
context, port_id)
port_data = port['port']
validate_port_sec = self._should_validate_port_sec_on_update_port(
port_data)
is_external_net = self._network_is_external(
context, original_port['network_id'])
if is_external_net:
@ -671,8 +689,12 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._validate_max_ips_per_port(
port_data.get('fixed_ips', []), device_owner)
updated_port = super(NsxPolicyPlugin, self).update_port(context,
id, port)
direct_vnic_type = self._validate_port_vnic_type(
context, port_data, original_port['network_id'])
updated_port = super(NsxPolicyPlugin, self).update_port(
context, port_id, port)
self._extension_manager.process_update_port(context, port_data,
updated_port)
# copy values over - except fixed_ips as
@ -680,15 +702,47 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
port_data.pop('fixed_ips', None)
updated_port.update(port_data)
updated_port = self._update_port_preprocess_security(
context, port, port_id, updated_port, False,
validate_port_sec=validate_port_sec,
direct_vnic_type=direct_vnic_type)
sec_grp_updated = self.update_security_group_on_port(
context, port_id, port, original_port, updated_port)
self._process_port_update_provider_security_group(
context, port, original_port, updated_port)
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, updated_port)
self._remove_provider_security_groups_from_list(updated_port)
self._process_portbindings_create_and_update(
context, port_data, updated_port,
vif_type=self._vif_type_by_vnic_type(direct_vnic_type))
self._extend_nsx_port_dict_binding(context, updated_port)
#TODO(asarfaty): Handle mac learning
# update the port in the backend, only if it exists in the DB
# (i.e not external net)
if not is_external_net:
self._update_port_on_backend(context, id,
original_port, updated_port)
try:
self._update_port_on_backend(context, port_id,
original_port, updated_port)
except Exception as e:
LOG.error('Failed to update port %(id)s on NSX '
'backend. Exception: %(e)s',
{'id': port_id, 'e': e})
# Rollback the change
with excutils.save_and_reraise_exception():
with db_api.CONTEXT_WRITER.using(context):
self._revert_neutron_port_update(
context, port_id, original_port, updated_port,
port_security, sec_grp_updated)
# Make sure the port revision is updated
if 'revision_number' in updated_port:
port_model = self._get_port(context, id)
port_model = self._get_port(context, port_id)
updated_port['revision_number'] = port_model.revision_number
# Notifications must be sent after the above transaction is complete
@ -702,8 +756,9 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
registry.notify(resources.PORT, events.AFTER_UPDATE, self, **kwargs)
return updated_port
def get_port(self, context, id, fields=None):
port = super(NsxPolicyPlugin, self).get_port(context, id, fields=None)
def get_port(self, context, port_id, fields=None):
port = super(NsxPolicyPlugin, self).get_port(
context, port_id, fields=None)
if 'id' in port:
port_model = self._get_port(context, port['id'])
resource_extend.apply_funcs('ports', port, port_model)
@ -821,7 +876,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._validate_interface_address_scope(context, router_db, info)
subnet = self.get_subnet(context, info['subnet_ids'][0])
segment_id = self._get_network_nsx_id(context, network_id)
segment_id = self._get_network_nsx_segment_id(context, network_id)
# TODO(annak): Validate TZ
try:
# This is always an overwrite call

View File

@ -28,9 +28,7 @@ from neutron_lib.api import faults
from neutron_lib.api.validators import availability_zone as az_validator
from neutron_lib.db import api as db_api
from neutron_lib.db import utils as db_utils
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
from neutron_lib.exceptions import l3 as l3_exc
from neutron_lib.exceptions import port_security as psec_exc
from neutron_lib.plugins import constants as plugin_const
from neutron_lib.plugins import directory
from neutron_lib import rpc as n_rpc
@ -2786,86 +2784,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
is_delete=True)
super(NsxV3Plugin, self).delete_port(context, port_id)
def _update_port_preprocess_security(
self, context, port, id, updated_port, is_ens_tz_port,
validate_port_sec=True, direct_vnic_type=False):
delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
port)
has_addr_pairs = self._check_update_has_allowed_address_pairs(port)
has_security_groups = self._check_update_has_security_groups(port)
delete_security_groups = self._check_update_deletes_security_groups(
port)
# populate port_security setting
port_data = port['port']
if psec.PORTSECURITY not in port_data:
updated_port[psec.PORTSECURITY] = \
self._get_port_security_binding(context, id)
has_ip = self._ip_on_port(updated_port)
# validate port security and allowed address pairs
if not updated_port[psec.PORTSECURITY]:
# has address pairs in request
if has_addr_pairs:
raise addr_exc.AddressPairAndPortSecurityRequired()
elif not delete_addr_pairs:
# check if address pairs are in db
updated_port[addr_apidef.ADDRESS_PAIRS] = (
self.get_allowed_address_pairs(context, id))
if updated_port[addr_apidef.ADDRESS_PAIRS]:
raise addr_exc.AddressPairAndPortSecurityRequired()
if delete_addr_pairs or has_addr_pairs:
self._validate_ipv4_address_pairs(
updated_port[addr_apidef.ADDRESS_PAIRS])
# delete address pairs and read them in
self._delete_allowed_address_pairs(context, id)
self._process_create_allowed_address_pairs(
context, updated_port,
updated_port[addr_apidef.ADDRESS_PAIRS])
if updated_port[psec.PORTSECURITY] and psec.PORTSECURITY in port_data:
# No port security is allowed if the port belongs to an ENS TZ
if is_ens_tz_port and not self._ens_psec_supported():
raise nsx_exc.NsxENSPortSecurity()
# No port security is allowed if the port has a direct vnic type
if direct_vnic_type:
err_msg = _("Security features are not supported for "
"ports with direct/direct-physical VNIC type")
raise n_exc.InvalidInput(error_message=err_msg)
# checks if security groups were updated adding/modifying
# security groups, port security is set and port has ip
provider_sgs_specified = self._provider_sgs_specified(updated_port)
if (validate_port_sec and
not (has_ip and updated_port[psec.PORTSECURITY])):
if has_security_groups or provider_sgs_specified:
LOG.error("Port has conflicting port security status and "
"security groups")
raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups()
# Update did not have security groups passed in. Check
# that port does not have any security groups already on it.
filters = {'port_id': [id]}
security_groups = (
super(NsxV3Plugin, self)._get_port_security_group_bindings(
context, filters)
)
if security_groups and not delete_security_groups:
raise psec_exc.PortSecurityPortHasSecurityGroup()
if delete_security_groups or has_security_groups:
# delete the port binding and read it with the new rules.
self._delete_port_security_group_bindings(context, id)
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, updated_port,
sgids)
if psec.PORTSECURITY in port['port']:
self._process_port_port_security_update(
context, port['port'], updated_port)
return updated_port
def _get_resource_type_for_device_id(self, device_owner, device_id):
if device_owner in const.ROUTER_INTERFACE_OWNERS:
return 'os-router-uuid'
@ -3052,17 +2970,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return policy_id, profile_id
def update_port(self, context, id, port):
switch_profile_ids = None
# Need to determine if we skip validations for port security.
# This is the edge case when the subnet is deleted.
validate_port_sec = True
fixed_ips = port['port'].get('fixed_ips', [])
for fixed_ip in fixed_ips:
if 'delete_subnet' in fixed_ip:
validate_port_sec = False
break
with db_api.CONTEXT_WRITER.using(context):
# get the original port, and keep it honest as it is later used
# for notifications
@ -3070,7 +2977,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._extend_get_port_dict_qos_and_binding(context, original_port)
self._remove_provider_security_groups_from_list(original_port)
port_data = port['port']
validate_port_sec = self._should_validate_port_sec_on_update_port(
port_data)
nsx_lswitch_id, nsx_lport_id = nsx_db.get_nsx_switch_and_port_id(
context.session, id)
@ -3159,25 +3067,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
"changes on neutron")
with excutils.save_and_reraise_exception(reraise=False):
with db_api.CONTEXT_WRITER.using(context):
super(NsxV3Plugin, self).update_port(
context, id, {'port': original_port})
# revert allowed address pairs
if port_security:
orig_pair = original_port.get(
addr_apidef.ADDRESS_PAIRS)
updated_pair = updated_port.get(
addr_apidef.ADDRESS_PAIRS)
if orig_pair != updated_pair:
self._delete_allowed_address_pairs(context, id)
if orig_pair:
self._process_create_allowed_address_pairs(
context, original_port, orig_pair)
if sec_grp_updated:
self.update_security_group_on_port(
context, id, {'port': original_port},
updated_port, original_port)
self._revert_neutron_port_update(
context, id, original_port, updated_port,
port_security, sec_grp_updated)
# NOTE(arosen): this is to translate between nsxlib
# exceptions and the plugin exceptions. This should be
# later refactored.

View File

@ -19,6 +19,7 @@ from oslo_config import cfg
from oslo_utils import uuidutils
from webob import exc
from neutron.extensions import securitygroup as secgrp
from neutron.tests.unit.db import test_db_base_plugin_v2
from neutron.tests.unit.extensions import test_securitygroup
@ -368,6 +369,24 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
def test_update_port_add_additional_ip(self):
self.skipTest('Multiple fixed ips on a port are not supported')
def test_update_port_delete_ip(self):
# This test case overrides the default because the nsx plugin
# implements port_security/security groups and it is not allowed
# to remove an ip address from a port unless the security group
# is first removed.
with self.subnet() as subnet:
with self.port(subnet=subnet) as port:
data = {'port': {'admin_state_up': False,
'fixed_ips': [],
secgrp.SECURITYGROUPS: []}}
req = self.new_update_request('ports',
data, port['port']['id'])
res = self.deserialize('json', req.get_response(self.api))
self.assertEqual(res['port']['admin_state_up'],
data['port']['admin_state_up'])
self.assertEqual(res['port']['fixed_ips'],
data['port']['fixed_ips'])
class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin,
test_securitygroup.TestSecurityGroups,
@ -525,7 +544,3 @@ class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin,
psec.PORTSECURITY),
**kwargs)
self.assertEqual(res.status_int, exc.HTTPBadRequest.code)
# Temporarily skip all port related tests until the plugin supports it
def test_update_port_with_security_group(self):
self.skipTest('Temporarily not supported')