Fix dynamic domain association

The current dynamic domain association fails to correctly associate
and disassociate domains during port binding for external networks.

Change-Id: Iecd6989b7eec691374d2258ea2f8273210eac28f
This commit is contained in:
Thomas Bachman 2018-02-09 14:29:13 +00:00
parent a7cf4ee4b5
commit c2b3fbf2bd
2 changed files with 302 additions and 39 deletions

View File

@ -36,6 +36,7 @@ from neutron.db.models import segment as segments_model
from neutron.db import models_v2
from neutron.db import rbac_db_models
from neutron.db import segments_db
from neutron.extensions import external_net
from neutron.plugins.common import constants as pconst
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import models
@ -420,10 +421,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
domain_type=utils.OPENSTACK_VMM_TYPE)
domains = []
if aim_hd_mappings:
domains = self._get_unique_domains(aim_hd_mappings)
if not domains:
domains, _ = self.get_aim_domains(aim_ctx)
if not isinstance(ns, nat_strategy.NoNatStrategy):
if aim_hd_mappings:
domains = self._get_unique_domains(aim_hd_mappings)
if not domains:
domains, _ = self.get_aim_domains(aim_ctx)
ns.create_l3outside(aim_ctx, l3out, vmm_domains=domains)
ns.create_external_network(aim_ctx, ext_net)
@ -2874,10 +2876,24 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
port_context.bottom_bound_segment[api.NETWORK_TYPE])):
self._associate_domain(port_context, is_vmm=True)
def _skip_domain_processing(self, port_context):
ext_net = port_context.network.current
# skip domain processing if it's not managed by us, or
# for external networks with NAT (FIPs or SNAT),
if not ext_net:
return True
if ext_net[external_net.EXTERNAL] is True:
_, _, ns = self._get_aim_nat_strategy(ext_net)
if not isinstance(ns, nat_strategy.NoNatStrategy):
return True
return False
def _associate_domain(self, port_context, is_vmm=True):
port = port_context.current
session = port_context._plugin_context.session
aim_ctx = aim_context.AimContext(session)
if self._skip_domain_processing(port_context):
return
ptg = None
# TODO(kentwu): remove this coupling with policy driver if possible
if self.gbp_driver:
@ -2899,6 +2915,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
domains = []
try:
if is_vmm:
# Get all the openstack VMM domains. We either
# get domains from a lookup of the HostDomainMappingV2
# table, or we get all the applicable VMM domains
# found in AIM. We then apply these to the EPG.
if aim_hd_mappings:
domains = [{'type': mapping.domain_type,
'name': mapping.domain_name}
@ -2917,6 +2937,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
vmms = aim_epg.vmm_domains
self.aim.update(aim_ctx, epg, vmm_domains=vmms)
else:
# Get all the Physical domains. We either get domains
# from a lookup of the HostDomainMappingV2
# table, or we get all the applicable Physical
# domains found in AIM. We then apply these to the EPG.
if aim_hd_mappings:
domains = [{'name': mapping.domain_name}
for mapping in aim_hd_mappings
@ -2947,16 +2971,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
port = port_context.current
if (self._is_opflex_type(btm[api.NETWORK_TYPE]) or
self._is_supported_non_opflex_type(btm[api.NETWORK_TYPE])):
if self._skip_domain_processing(port_context):
return
host_id = (port_context.original_host if use_original
else port_context.host)
session = port_context._plugin_context.session
aim_ctx = aim_context.AimContext(session)
aim_hd_mappings = (self.aim.find(aim_ctx,
aim_infra.HostDomainMappingV2,
host_name=host_id) or
self.aim.find(aim_ctx,
aim_infra.HostDomainMappingV2,
host_name=DEFAULT_HOST_DOMAIN))
aim_hd_mappings = self.aim.find(aim_ctx,
aim_infra.HostDomainMappingV2,
host_name=host_id)
if not aim_hd_mappings:
return
@ -2984,16 +3007,22 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if self.gbp_driver:
ptg, pt = self.gbp_driver._port_id_to_ptg(
port_context._plugin_context, port['id'])
def _bound_port_query(session, port, hosts=None):
ports = (session.query(models.PortBindingLevel).
join(models_v2.Port, models_v2.Port.id ==
models.PortBindingLevel.port_id))
if hosts:
ports = ports.filter(
models.PortBindingLevel.host.in_(hosts))
ports = ports.filter(
models.PortBindingLevel.port_id != port['id'])
return ports
if ptg:
# if there are no other ports under this PTG bound to those
# hosts under this vmm, release the domain
bound_ports = (session
.query(models.PortBindingLevel)
.join(models_v2.Port,
models_v2.Port.id ==
models.PortBindingLevel.port_id)
.filter(models.PortBindingLevel.host.in_(hosts))
.filter(models.PortBindingLevel.port_id != port['id']))
bound_ports = _bound_port_query(session, port, hosts=hosts)
bound_ports = [x['port_id'] for x in bound_ports]
ptg_ports = self.gbp_driver.get_ptg_port_ids(
port_context._plugin_context, ptg)
@ -3004,17 +3033,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
else:
# if there are no other ports under this network bound to those
# hosts under this vmm, release the domain
ports = (session
.query(models.PortBindingLevel)
.join(models_v2.Port,
models_v2.Port.id ==
models.PortBindingLevel.port_id)
.filter(models_v2.Port.network_id ==
port['network_id'])
.filter(models.PortBindingLevel.host.in_(hosts))
.filter(models.PortBindingLevel.port_id != port['id'])
.first())
if ports:
ports = _bound_port_query(session, port, hosts=hosts)
if ports.first():
return
mapping = self._get_network_mapping(
session, port['network_id'])

View File

@ -5150,6 +5150,8 @@ class TestExternalConnectivityBase(object):
self._make_ext_network('net1',
dn=self.dn_t1_l1_n1,
cidrs=['20.10.0.0/16', '4.4.4.0/24'])
if self.nat_type is not 'distributed' and self.nat_type is not 'edge':
vmm_domains = []
self.mock_ns.create_l3outside.assert_called_once_with(
mock.ANY,
aim_resource.L3Outside(tenant_name=self.t1_aname, name='l1'),
@ -6122,10 +6124,12 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# delete the wildcard port
# delete the wildcard port -- the domain should still
# be associated, since we aren't identifying all of the
# host-specific mappings
self._delete('ports', p3['port']['id'])
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
@ -6135,7 +6139,7 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
with self.port(subnet=sub1) as p2:
p2 = self._bind_port_to_host(p2['port']['id'], 'h1')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set(['ph1']),
set(self._doms(epg1.physical_domains,
@ -6143,7 +6147,7 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
# move port to another host
p2 = self._bind_port_to_host(p2['port']['id'], 'h2')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set(['ph2']),
set(self._doms(epg1.physical_domains,
@ -6152,7 +6156,7 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
with self.port(subnet=sub1) as p2a:
p2a = self._bind_port_to_host(p2a['port']['id'], 'h2a')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set(['ph2']),
set(self._doms(epg1.physical_domains,
@ -6161,7 +6165,7 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
# delete 1st port
self._delete('ports', p2['port']['id'])
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set(['ph2']),
set(self._doms(epg1.physical_domains,
@ -6169,7 +6173,7 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
# delete the last port
self._delete('ports', p2a['port']['id'])
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
@ -6179,17 +6183,19 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
with self.port(subnet=sub1) as p3:
p3 = self._bind_port_to_host(p3['port']['id'], 'h3')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set(['ph3']),
set(self._doms(epg1.physical_domains,
with_type=False)))
# delete the wildcard port
# delete the wildcard port -- the domain should still
# be associated, since we aren't identifying all of the
# host-specific mappings
self._delete('ports', p3['port']['id'])
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([]),
self.assertEqual(set([('vm3', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
self.assertEqual(set(['ph3']),
set(self._doms(epg1.physical_domains,
with_type=False)))
@ -6232,6 +6238,243 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
set(self._doms(epg1.physical_domains,
with_type=False)))
def _external_net_test_setup(self, aim_ctx, domains, mappings,
agents, nat_type=None):
for domain in domains:
self.aim_mgr.create(aim_ctx,
aim_resource.VMMDomain(type=domain['type'],
name=domain['name'],
overwrite=True))
for mapping in mappings:
hd_mapping = aim_infra.HostDomainMappingV2(
host_name=mapping['host'],
domain_name=mapping['name'],
domain_type=mapping['type'])
self.aim_mgr.create(aim_ctx, hd_mapping)
for agent in agents:
self._register_agent(agent, AGENT_CONF_OPFLEX)
net1 = self._make_ext_network('net1',
dn=self.dn_t1_l1_n1,
nat_type=nat_type)
epg1 = self._net_2_epg(net1)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
return net1, epg1
def test_no_nat_external_net_default_domains(self):
# no-NAT external networks rely on port-binding
# to dynamically associate domains to the external EPG
aim_ctx = aim_context.AimContext(self.db_session)
domains = [{'type': 'OpenStack', 'name': 'cloud-rtp-1-VMM'},
{'type': 'OpenStack', 'name': 'vm1'}]
mappings = []
agents = ['opflex-1', 'opflex-2']
net1, epg1 = self._external_net_test_setup(aim_ctx, domains,
mappings, agents,
nat_type='')
self.assertEqual(set([]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.subnet(network={'network': net1}) as sub1:
# "normal" port on opflex host
with self.port(subnet=sub1) as p1:
p1 = self._bind_port_to_host(p1['port']['id'], 'opflex-1')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.port(subnet=sub1) as p2:
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind the port - should still have both, since
# we don't know that we can delete the wildcard entry
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
p1 = self._bind_port_to_host(p1['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind port 1 -- domains left in place, as all default
# domain cases should leave domains associated (to keep
# backwards compatibility).
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
def test_no_nat_external_net_specific_domains(self):
# no-NAT external networks rely on port-binding
# to dynamically associate domains to the external EPG
aim_ctx = aim_context.AimContext(self.db_session)
domains = [{'type': 'OpenStack', 'name': 'cloud-rtp-1-VMM'},
{'type': 'OpenStack', 'name': 'vm1'}]
mappings = [{'type': 'OpenStack',
'host': '*',
'name': 'cloud-rtp-1-VMM'},
{'type': 'OpenStack',
'host': 'opflex-1',
'name': 'vm1'}]
agents = ['opflex-1', 'opflex-2']
net1, epg1 = self._external_net_test_setup(aim_ctx, domains,
mappings, agents,
nat_type='')
self.assertEqual(set([]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.subnet(network={'network': net1}) as sub1:
# "normal" port on opflex host
with self.port(subnet=sub1) as p1:
p1 = self._bind_port_to_host(p1['port']['id'], 'opflex-1')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.port(subnet=sub1) as p2:
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind the port - should still have both, since
# we don't know that we can delete the wildcard entry
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
p1 = self._bind_port_to_host(p1['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind port 1 -- should remove all domains, as
# there aren't any ports left in the EPG
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
def test_nat_external_net_specific_domains(self):
# no-NAT external networks rely on port-binding
# to dynamically associate domains to the external EPG
aim_ctx = aim_context.AimContext(self.db_session)
domains = [{'type': 'OpenStack', 'name': 'cloud-rtp-1-VMM'},
{'type': 'OpenStack', 'name': 'vm1'}]
mappings = [{'type': 'OpenStack',
'host': '*',
'name': 'cloud-rtp-1-VMM'},
{'type': 'OpenStack',
'host': 'opflex-1',
'name': 'vm1'}]
agents = ['opflex-1', 'opflex-1']
net1, epg1 = self._external_net_test_setup(aim_ctx, domains,
mappings, agents)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.subnet(network={'network': net1}) as sub1:
# "normal" port on opflex host
with self.port(subnet=sub1) as p1:
p1 = self._bind_port_to_host(p1['port']['id'], 'opflex-1')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
with self.port(subnet=sub1) as p2:
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind the port - should still have both, since
# we don't know that we can delete the wildcard entry
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
p2 = self._bind_port_to_host(p2['port']['id'], 'opflex-2')
p1 = self._bind_port_to_host(p1['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
# unbind port 1 -- should remove all domains, as
# there aren't any ports left in the EPG
p2 = self._bind_port_to_host(p2['port']['id'], None)
epg1 = self.aim_mgr.get(aim_ctx, epg1)
self.assertEqual(set([('cloud-rtp-1-VMM', 'OpenStack'),
('vm1', 'OpenStack')]),
set(self._doms(epg1.vmm_domains)))
self.assertEqual(set([]),
set(self._doms(epg1.physical_domains,
with_type=False)))
class TestPortOnPhysicalNodeSingleDriver(TestPortOnPhysicalNode):
# Tests for binding port on physical node where no other ML2 mechanism