Merge "[AIM] Validate network segments and port bindings"

This commit is contained in:
Zuul 2018-07-19 19:46:54 +00:00 committed by Gerrit Code Review
commit 558cb3edec
3 changed files with 139 additions and 3 deletions

View File

@ -4067,6 +4067,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self._validate_ports(mgr)
self._validate_subnetpools(mgr)
self._validate_floatingips(mgr)
self._validate_port_bindings(mgr)
def _validate_static_resources(self, mgr):
self._ensure_common_tenant(mgr.expected_aim_ctx)
@ -4128,7 +4129,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
def _validate_networks(self, mgr, router_dbs, ext_net_routers):
net_dbs = {net_db.id: net_db for net_db in
mgr.actual_session.query(models_v2.Network)}
mgr.actual_session.query(models_v2.Network).options(
orm.joinedload('segments'))}
router_ext_prov, router_ext_cons = self._get_router_ext_contracts(mgr)
routed_nets = self._get_router_interface_info(mgr)
@ -4140,6 +4142,22 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
for subnet_db in net_db.subnets:
self._expect_project(mgr, subnet_db.project_id)
for segment_db in net_db.segments:
# REVISIT: Consider validating that physical_network
# and segmentation_id values make sense for the
# network_type, and possibly validate that there are
# no conflicting segment allocations.
if (segment_db.network_type not in
self.plugin.type_manager.drivers):
# REVISIT: For migration from non-APIC backends,
# change type to 'opflex'?
mgr.validation_failed(
"network %(net_id)s segment %(segment_id)s type "
"%(type)s is invalid" % {
'net_id': segment_db.network_id,
'segment_id': segment_db.id,
'type': segment_db.network_type})
bd = None
epg = None
vrf = None
@ -4555,6 +4573,45 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
distinct()):
self._expect_project(mgr, project_id)
def _validate_port_bindings(self, mgr):
# REVISIT: Deal with distributed port bindings? Also, consider
# moving this to the ML2Plus plugin or to a base validation
# manager, as it is not specific to this mechanism driver.
for port in (mgr.actual_session.query(models_v2.Port).
options(orm.joinedload('binding_levels'))):
binding = port.port_binding
levels = port.binding_levels
unbind = False
# REVISIT: Validate that vif_type and vif_details are
# correct when host is empty?
for level in levels:
if (level.driver not in
self.plugin.mechanism_manager.mech_drivers):
if mgr.should_repair(
"port %(id)s bound with invalid driver "
"%(driver)s" %
{'id': port.id, 'driver': level.driver},
"Unbinding"):
unbind = True
elif (level.host != binding.host):
if mgr.should_repair(
"port %(id)s bound with invalid host "
"%(host)s" %
{'id': port.id, 'host': level.host},
"Unbinding"):
unbind = True
elif (not level.segment_id):
if mgr.should_repair(
"port %s bound without valid segment" % port.id,
"Unbinding"):
unbind = True
if unbind:
binding.vif_type = portbindings.VIF_TYPE_UNBOUND
binding.vif_details = ''
for level in port.binding_levels:
mgr.actual_session.delete(level)
def _expect_project(self, mgr, project_id):
# REVISIT: Currently called for all Neutron and GBP resources
# for which plugin create methods call _ensure_tenant. Remove
@ -4577,3 +4634,21 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ap.display_name = aim_utils.sanitize_display_name(self.ap_name)
ap.monitored = False
mgr.expect_aim_resource(ap)
def bind_unbound_ports(self, mgr):
# REVISIT: Deal with distributed port bindings? Also, consider
# moving this to the ML2Plus plugin or to a base validation
# manager, as it is not specific to this mechanism driver.
plugin_context = nctx.get_admin_context()
for port_id, in (mgr.actual_session.query(models.PortBinding.port_id).
filter(models.PortBinding.host != '',
models.PortBinding.vif_type ==
portbindings.VIF_TYPE_UNBOUND)):
# REVISIT: Use the more efficient get_bound_port_contexts,
# which is not available in stable/newton?
pc = self.plugin.get_bound_port_context(plugin_context, port_id)
if (pc.vif_type == portbindings.VIF_TYPE_BINDING_FAILED or
pc.vif_type == portbindings.VIF_TYPE_UNBOUND):
mgr.validation_failed(
"unable to bind port %(port)s on host %(host)s" %
{'port': port_id, 'host': pc.host})

View File

@ -116,6 +116,11 @@ class ValidationManager(object):
print("Rolling back attempted repairs")
self.actual_session.rollback()
# Bind unbound ports outside transaction.
if self.repair and self.result is not api.VALIDATION_FAILED:
print("Binding unbound ports")
self.md.bind_unbound_ports(self)
print("Validation result: %s" % self.result)
return self.result

View File

@ -19,6 +19,8 @@ from aim.aim_lib.db import model as aim_lib_model
from aim.api import infra as aim_infra
from aim.api import resource as aim_resource
from aim import context as aim_context
from neutron.db.models import segment
from neutron.plugins.ml2 import models as ml2_models
from neutron.tests.unit.extensions import test_securitygroup
from neutron_lib import context as n_context
@ -492,7 +494,6 @@ class TestNeutronMapping(AimValidationTestCase):
subnetpool_id=pool_id, tenant_id='tenant_2')
# Create external network.
#
kwargs = {'router:external': True,
'apic:distinguished_names':
{'ExternalNetwork': 'uni/tn-common/out-l1/instP-n1'}}
@ -501,7 +502,6 @@ class TestNeutronMapping(AimValidationTestCase):
**kwargs)['network']
# Create extra external network to test CloneL3Out record below.
#
kwargs = {'router:external': True,
'apic:distinguished_names':
{'ExternalNetwork': 'uni/tn-common/out-l2/instP-n2'}}
@ -675,6 +675,62 @@ class TestNeutronMapping(AimValidationTestCase):
tenant_name=tenant_name)
self._test_aim_resource(aim_rule)
def test_network_segment(self):
# REVISIT: Test repair when migration from other types to
# 'opflex' is supported.
# Create network.
net = self._make_network(self.fmt, 'net1', True)['network']
# Change network's segment to an unknown type.
self.db_session.query(segment.NetworkSegment).filter_by(
network_id=net['id']).update({'network_type': 'xxx'})
# Test that validation fails.
self._validate_unrepairable()
def test_port_binding(self):
# Create network, subnet, and bound port.
net_resp = self._make_network(self.fmt, 'net1', True)
net = net_resp['network']
subnet = self._make_subnet(
self.fmt, net_resp, None, '10.0.0.0/24')['subnet']
fixed_ips = [{'subnet_id': subnet['id'], 'ip_address': '10.0.0.100'}]
port = self._make_port(
self.fmt, net['id'], fixed_ips=fixed_ips)['port']
port = self._bind_port_to_host(port['id'], 'host1')['port']
self._validate()
# Change port binding level to unknown mechanism driver and
# test.
self.db_session.query(ml2_models.PortBindingLevel).filter_by(
port_id=port['id'], level=0).update({'driver': 'xxx'})
self._validate_repair_validate()
# Change port binding level to incorrect host and test.
self.db_session.query(ml2_models.PortBindingLevel).filter_by(
port_id=port['id'], level=0).update({'host': 'yyy'})
self._validate_repair_validate()
# Change port binding level to null segment ID and test.
self.db_session.query(ml2_models.PortBindingLevel).filter_by(
port_id=port['id'], level=0).update({'segment_id': None})
self._validate_repair_validate()
# Change port binding level to unknown mechanism driver, set
# bad host, and test that repair fails.
#
# REVISIT: The apic_aim MD currently tries to allocate a
# dynamic segment whenever there is no agent on the port's
# host, which is probably wrong, but it does fail to bind, so
# this test succeeds.
self.db_session.query(ml2_models.PortBindingLevel).filter_by(
port_id=port['id'], level=0).update({'driver': 'xxx',
'host': 'yyy'})
self.db_session.query(ml2_models.PortBinding).filter_by(
port_id=port['id']).update({'host': 'yyy'})
self._validate_unrepairable()
class TestGbpMapping(AimValidationTestCase):