[AIM] Validate network segments and port bindings

Validate that network segment types match registered type driver
names. Validate that port binding levels have valid segment IDs and
match registered mechanism driver names. After commiting repair
transaction, attempt to bind any unbound ports.

Change-Id: Ia8b665b95c1d6a50434621c61b8c2708ed5eac48
This commit is contained in:
Robert Kukura 2018-06-18 11:32:16 -04:00
parent 583584cdf7
commit 2bf9bd7751
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):