[AIM] Add support for SNAT

To support 'distributed' SNAT, this change
adds a way to allocate an IP-address per host
that will be used for translating traffic leaving
VMs on that host. This IP-address is conveyed
to the host through GBP RPCs.
Specific subnets in the external network can be
designated for use as a pool of SNAT addresses
using the boolean extension attribute
'host_snat_pool'. Such subnets cannot be used
for allocating floating-IPs to maintain
consistency with current APIC driver, but that
can be relaxed if required.

Change-Id: Ie04095be77a6aefcf8559b743d265fe66551c2bd
Signed-off-by: Amit Bose <amitbose@gmail.com>
This commit is contained in:
Amit Bose
2016-11-02 11:01:11 -07:00
parent eb753f83d5
commit e6eff43101
6 changed files with 448 additions and 47 deletions

View File

@@ -120,6 +120,7 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
data.get(cisco_apic.SNAT_HOST_POOL, False)}
self.set_subnet_extn_db(plugin_context.session, result['id'],
res_dict)
result.update(res_dict)
def process_update_subnet(self, plugin_context, data, result):
if not cisco_apic.SNAT_HOST_POOL in data:
@@ -127,6 +128,7 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
res_dict = {cisco_apic.SNAT_HOST_POOL: data[cisco_apic.SNAT_HOST_POOL]}
self.set_subnet_extn_db(plugin_context.session, result['id'],
res_dict)
result.update(res_dict)
def extend_address_scope_dict(self, session, base_model, result):
try:

View File

@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import sqlalchemy as sa
from aim.aim_lib import nat_strategy
from aim import aim_manager
from aim.api import resource as aim_resource
@@ -22,6 +24,7 @@ from aim import context as aim_context
from aim import utils as aim_utils
from neutron._i18n import _LI
from neutron._i18n import _LW
from neutron.api.v2 import attributes
from neutron.common import constants as n_constants
from neutron.common import exceptions
from neutron.common import topics as n_topics
@@ -44,9 +47,13 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
from oslo_serialization.jsonutils import netaddr
LOG = log.getLogger(__name__)
DEVICE_OWNER_SNAT_PORT = 'apic:snat-pool'
# REVISIT(rkukura): Consider making these APIC name constants
# configurable, although changing them would break an existing
# deployment.
@@ -69,6 +76,16 @@ class UnsupportedRoutingTopology(exceptions.BadRequest):
"same router or the same subnet.")
class SnatPortsInUse(exceptions.SubnetInUse):
def __init__(self, **kwargs):
kwargs['reason'] = _('Subnet has SNAT IP addresses allocated')
super(SnatPortsInUse, self).__init__(**kwargs)
class SnatPoolCannotBeUsedForFloatingIp(exceptions.InvalidInput):
message = _("Floating IP cannot be allocated in SNAT host pool subnet.")
NO_ADDR_SCOPE = object()
@@ -337,6 +354,14 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
network_db = self.plugin._get_network(context._plugin_context,
network_id)
is_ext = network_db.external is not None
session = context._plugin_context.session
# If subnet is no longer a SNAT pool, check if SNAT IP ports
# are allocated
if (is_ext and original[cisco_apic.SNAT_HOST_POOL] and
not current[cisco_apic.SNAT_HOST_POOL] and
self._has_snat_ip_ports(context._plugin_context, current['id'])):
raise SnatPortsInUse(subnet_id=current['id'])
if (not is_ext and
current['name'] != original['name']):
@@ -560,6 +585,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
{}).get('network_id')
new_net = (current.get('external_gateway_info') or
{}).get('network_id')
if old_net and not new_net:
self._delete_snat_ip_ports_if_reqd(context, old_net,
current['id'])
if ((old_net != new_net or
is_diff(original, current, a_l3.EXTERNAL_PROVIDED_CONTRACTS) or
is_diff(original, current, a_l3.EXTERNAL_CONSUMED_CONTRACTS)) and
@@ -843,6 +871,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self._manage_external_connectivity(
context, router_db, net, None, scope_id=scope_id)
self._delete_snat_ip_ports_if_reqd(context, net['id'],
router_id)
def bind_port(self, context):
current = context.current
LOG.debug("Attempting to bind port %(port)s on network %(net)s",
@@ -1312,3 +1343,133 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if self._is_port_bound(port):
LOG.debug("APIC notify port %s", port['id'])
self.notifier.port_update(plugin_context, port)
def get_or_allocate_snat_ip(self, plugin_context, host_or_vrf,
ext_network):
"""Fetch or allocate SNAT IP on the external network.
IP allocation is done by creating a port on the external network,
and associating an owner with it. The owner could be the ID of
a host (or VRF) if SNAT IP allocation per host (or per VRF) is
desired.
If IP was found or successfully allocated, returns a dict like:
{'host_snat_ip': <ip_addr>,
'gateway_ip': <gateway_ip of subnet>,
'prefixlen': <prefix_length_of_subnet>}
"""
session = plugin_context.session
snat_port = (session.query(models_v2.Port)
.filter(models_v2.Port.network_id == ext_network['id'],
models_v2.Port.device_id == host_or_vrf,
models_v2.Port.device_owner ==
DEVICE_OWNER_SNAT_PORT)
.first())
snat_ip = None
if not snat_port or snat_port.fixed_ips is None:
# allocate SNAT port
extn_db_sn = extension_db.SubnetExtensionDb
snat_subnets = (session.query(models_v2.Subnet)
.join(extn_db_sn)
.filter(models_v2.Subnet.network_id ==
ext_network['id'])
.filter(extn_db_sn.snat_host_pool.is_(True))
.all())
if not snat_subnets:
LOG.info(_LI('No subnet in external network %s is marked as '
'SNAT-pool'),
ext_network['id'])
return
for snat_subnet in snat_subnets:
try:
attrs = {'device_id': host_or_vrf,
'device_owner': DEVICE_OWNER_SNAT_PORT,
'tenant_id': ext_network['tenant_id'],
'name': 'snat-pool-port:%s' % host_or_vrf,
'network_id': ext_network['id'],
'mac_address': attributes.ATTR_NOT_SPECIFIED,
'fixed_ips': [{'subnet_id': snat_subnet.id}],
'admin_state_up': False}
port = self.plugin.create_port(plugin_context,
{'port': attrs})
if port and port['fixed_ips']:
snat_ip = port['fixed_ips'][0]['ip_address']
break
except exceptions.IpAddressGenerationFailure:
LOG.info(_LI('No more addresses available in subnet %s '
'for SNAT IP allocation'),
snat_subnet['id'])
else:
snat_ip = snat_port.fixed_ips[0].ip_address
snat_subnet = (session.query(models_v2.Subnet)
.filter(models_v2.Subnet.id ==
snat_port.fixed_ips[0].subnet_id)
.one())
if snat_ip:
return {'host_snat_ip': snat_ip,
'gateway_ip': snat_subnet['gateway_ip'],
'prefixlen': int(snat_subnet['cidr'].split('/')[1])}
def _has_snat_ip_ports(self, plugin_context, subnet_id):
session = plugin_context.session
return (session.query(models_v2.Port)
.join(models_v2.IPAllocation)
.filter(models_v2.IPAllocation.subnet_id == subnet_id)
.filter(models_v2.Port.device_owner == DEVICE_OWNER_SNAT_PORT)
.first())
def _delete_snat_ip_ports_if_reqd(self, plugin_context,
ext_network_id, exclude_router_id):
session = plugin_context.session
# if there are no routers uplinked to the external network,
# then delete any ports allocated for SNAT IP
gw_qry = (session.query(models_v2.Port)
.filter(models_v2.Port.network_id == ext_network_id,
models_v2.Port.device_owner ==
n_constants.DEVICE_OWNER_ROUTER_GW,
models_v2.Port.device_id != exclude_router_id))
if not gw_qry.first():
snat_ports = (session.query(models_v2.Port.id)
.filter(models_v2.Port.network_id == ext_network_id,
models_v2.Port.device_owner ==
DEVICE_OWNER_SNAT_PORT)
.all())
for p in snat_ports:
try:
self.plugin.delete_port(plugin_context, p[0])
except exceptions.NeutronException as ne:
LOG.warning(_LW('Failed to delete SNAT port %(port)s: '
'%(ex)s'),
{'port': p, 'ex': ne})
def check_floatingip_external_address(self, context, floatingip):
session = context.session
if floatingip.get('subnet_id'):
sn_ext = (extension_db.ExtensionDbMixin()
.get_subnet_extn_db(session,
floatingip['subnet_id']))
if sn_ext.get(cisco_apic.SNAT_HOST_POOL, False):
raise SnatPoolCannotBeUsedForFloatingIp()
elif floatingip.get('floating_ip_address'):
extn_db_sn = extension_db.SubnetExtensionDb
cidrs = (session.query(models_v2.Subnet.cidr)
.join(extn_db_sn)
.filter(models_v2.Subnet.network_id ==
floatingip['floating_network_id'])
.filter(extn_db_sn.snat_host_pool.is_(True))
.all())
cidrs = netaddr.IPSet([c[0] for c in cidrs])
if floatingip['floating_ip_address'] in cidrs:
raise SnatPoolCannotBeUsedForFloatingIp()
def get_subnets_for_fip(self, context, floatingip):
session = context.session
extn_db_sn = extension_db.SubnetExtensionDb
other_sn = (session.query(models_v2.Subnet.id)
.outerjoin(extn_db_sn)
.filter(models_v2.Subnet.network_id ==
floatingip['floating_network_id'])
.filter(sa.or_(extn_db_sn.snat_host_pool.is_(False),
extn_db_sn.snat_host_pool.is_(None)))
.all())
return [s[0] for s in other_sn]

View File

@@ -22,6 +22,7 @@ from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.extensions import l3
from neutron.plugins.common import constants
from neutron_lib import exceptions
from oslo_log import log as logging
from oslo_utils import excutils
from sqlalchemy import inspect
@@ -209,14 +210,33 @@ class ApicL3Plugin(common_db_mixin.CommonDbMixin,
return port_db, subnets
def create_floatingip(self, context, floatingip):
# if not floatingip['floatingip'].get('subnet_id'):
# # TODO(amitbose) Verify that subnet is not a SNAT host-pool
# else:
# # TODO(amitbose) Iterate over non SNAT host-pool subnets
# # and try to allocate a FIP
fip = floatingip['floatingip']
# Verify that subnet is not a SNAT host-pool
self._md.check_floatingip_external_address(context, fip)
with context.session.begin(subtransactions=True):
result = super(ApicL3Plugin, self).create_floatingip(
context, floatingip)
if fip.get('subnet_id') or fip.get('floating_ip_address'):
result = super(ApicL3Plugin, self).create_floatingip(
context, floatingip)
else:
# Iterate over non SNAT host-pool subnets and try to allocate
# an address
other_subs = self._md.get_subnets_for_fip(context, fip)
result = None
for ext_sn in other_subs:
fip['subnet_id'] = ext_sn
try:
with context.session.begin(nested=True):
result = (super(ApicL3Plugin, self)
.create_floatingip(context, floatingip))
break
except exceptions.IpAddressGenerationFailure:
LOG.info(_LI('No more floating IP addresses available '
'in subnet %s'),
ext_sn)
if not result:
raise exceptions.IpAddressGenerationFailure(
net_id=fip['floating_network_id'])
self._md.create_floatingip(context, result)
self.update_floatingip_status(context, result['id'],
result['status'])

View File

@@ -1578,19 +1578,17 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
fips_in_ext_net = filter(
lambda x: x['floating_network_id'] == ext_net['id'], fips)
if not fips_in_ext_net:
ipms.append({'external_segment_name': ext_net['name'],
ext_segment_name = dn.replace('/', ':')
ipms.append({'external_segment_name': ext_segment_name,
'nat_epg_name': ext_net_epg.name,
'nat_epg_tenant': ext_net_epg.tenant_name})
# TODO(amitbose) Set next_hop_ep_tenant for per-tenant NAT EPG
if host:
# TODO(amitbose) Allocate host-specific SNAT IP
# and append it to host_snat_ips in the format:
# [ {'external_segment_name': <ext_segment_name1>,
# 'host_snat_ip': <ip_addr>,
# 'gateway_ip': <gateway_ip>,
# 'prefixlen': <prefix_length_of_subnet>},
# {..}, ... ]
pass
snat_ip = self.aim_mech_driver.get_or_allocate_snat_ip(
plugin_context, host, ext_net)
if snat_ip:
snat_ip['external_segment_name'] = ext_segment_name
host_snat_ips.append(snat_ip)
else:
for f in fips_in_ext_net:
f['nat_epg_name'] = ext_net_epg.name

View File

@@ -14,6 +14,7 @@
# under the License.
import mock
import netaddr
from aim.aim_lib import nat_strategy
from aim import aim_manager
@@ -54,6 +55,7 @@ DN = 'apic:distinguished_names'
CIDR = 'apic:external_cidrs'
PROV = 'apic:external_provided_contracts'
CONS = 'apic:external_consumed_contracts'
SNAT_POOL = 'apic:snat_host_pool'
aim_resource.ResourceBase.__repr__ = lambda x: x.__dict__.__repr__()
@@ -158,7 +160,7 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
self._tenant_name = self._map_name({'id': 'test-tenant',
'name': 'TestTenantName'})
self.extension_attributes = ('router:external', DN,
'apic:nat_type', 'apic:snat_host_pool',
'apic:nat_type', SNAT_POOL,
CIDR, PROV, CONS)
def tearDown(self):
@@ -1478,24 +1480,21 @@ class TestExtensionAttributes(ApicAimTestCase):
subnet = self._make_subnet(
self.fmt, {'network': net1}, '10.0.0.1', '10.0.0.0/24')['subnet']
subnet = self._show('subnets', subnet['id'])['subnet']
self.assertFalse(subnet['apic:snat_host_pool'])
self.assertFalse(subnet[SNAT_POOL])
# Update something other than snat_host_pool
self._update('subnets', subnet['id'],
{'subnet': {'name': 'foo'}})
subnet = self._show('subnets', subnet['id'])['subnet']
self.assertFalse(subnet['apic:snat_host_pool'])
subnet = self._update('subnets', subnet['id'],
{'subnet': {'name': 'foo'}})['subnet']
self.assertFalse(subnet[SNAT_POOL])
# Update snat_host_pool
self._update('subnets', subnet['id'],
{'subnet': {'apic:snat_host_pool': True}})
subnet = self._show('subnets', subnet['id'])['subnet']
self.assertTrue(subnet['apic:snat_host_pool'])
subnet = self._update('subnets', subnet['id'],
{'subnet': {SNAT_POOL: True}})['subnet']
self.assertTrue(subnet[SNAT_POOL])
self._update('subnets', subnet['id'],
{'subnet': {'apic:snat_host_pool': False}})
subnet = self._show('subnets', subnet['id'])['subnet']
self.assertFalse(subnet['apic:snat_host_pool'])
subnet = self._update('subnets', subnet['id'],
{'subnet': {SNAT_POOL: False}})['subnet']
self.assertFalse(subnet[SNAT_POOL])
# delete subnet
self._delete('subnets', subnet['id'])
@@ -1506,19 +1505,18 @@ class TestExtensionAttributes(ApicAimTestCase):
subnet2 = self._make_subnet(
self.fmt, {'network': net1}, '20.0.0.1', '20.0.0.0/24')['subnet']
self._update('subnets', subnet2['id'],
{'subnet': {'apic:snat_host_pool': True}})
{'subnet': {SNAT_POOL: True}})
with session.begin(subtransactions=True):
db_obj = session.query(extn_db.SubnetExtensionDb).filter(
extn_db.SubnetExtensionDb.subnet_id ==
subnet2['id']).one()
session.delete(db_obj)
subnet2 = self._show('subnets', subnet2['id'])['subnet']
self.assertFalse(subnet2['apic:snat_host_pool'])
self.assertFalse(subnet2[SNAT_POOL])
self._update('subnets', subnet2['id'],
{'subnet': {'apic:snat_host_pool': True}})
subnet2 = self._show('subnets', subnet2['id'])['subnet']
self.assertTrue(subnet2['apic:snat_host_pool'])
subnet2 = self._update('subnets', subnet2['id'],
{'subnet': {SNAT_POOL: True}})['subnet']
self.assertTrue(subnet2[SNAT_POOL])
def test_router_lifecycle(self):
session = db_api.get_session()
@@ -2139,3 +2137,195 @@ class TestExternalEdgeNat(TestExternalConnectivityBase,
class TestExternalNoNat(TestExternalConnectivityBase,
ApicAimTestCase):
nat_type = ''
class TestSnatIpAllocation(ApicAimTestCase):
def test_get_alloc_ip(self):
admin_ctx = context.get_admin_context()
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
sub1 = self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/29')['subnet']
sub2 = self._make_subnet(
self.fmt, {'network': ext_net}, '200.100.100.1',
'200.100.100.0/28')['subnet']
# No SNAT pools -> no allocation possible
alloc = self.driver.get_or_allocate_snat_ip(admin_ctx, 'h0', ext_net)
self.assertIsNone(alloc)
# Add one SNAT pool
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: True}})
# Allocate IP and then verify that same IP is returned on get
for x in range(0, 5):
alloc = self.driver.get_or_allocate_snat_ip(admin_ctx,
'h%d' % x, ext_net)
self.assertEqual({'host_snat_ip': '100.100.100.%d' % (x + 2),
'gateway_ip': '100.100.100.1',
'prefixlen': 29}, alloc)
alloc = self.driver.get_or_allocate_snat_ip(admin_ctx,
'h%d' % x, ext_net)
self.assertEqual({'host_snat_ip': '100.100.100.%d' % (x + 2),
'gateway_ip': '100.100.100.1',
'prefixlen': 29}, alloc)
# First pool exhausted, no further allocations possible
alloc = self.driver.get_or_allocate_snat_ip(admin_ctx, 'h5', ext_net)
self.assertIsNone(alloc)
# Add a second pool and try to re-allocate
self._update('subnets', sub2['id'],
{'subnet': {SNAT_POOL: True}})
alloc = self.driver.get_or_allocate_snat_ip(admin_ctx, 'h5', ext_net)
self.assertEqual({'host_snat_ip': '200.100.100.2',
'gateway_ip': '200.100.100.1',
'prefixlen': 28}, alloc)
def test_snat_pool_flag_update_no_ip(self):
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
sub1 = self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/29')['subnet']
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: True}})
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: False}})
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: True}})
self._delete('subnets', sub1['id'])
def test_snat_pool_flag_update_with_ip(self):
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
sub1 = self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/29')['subnet']
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: True}})
alloc = self.driver.get_or_allocate_snat_ip(
context.get_admin_context(), 'h0', ext_net)
self.assertIsNotNone(alloc)
self._update('subnets', sub1['id'],
{'subnet': {SNAT_POOL: False}}, expected_code=500)
self._delete('subnets', sub1['id'], expected_code=409)
def _setup_router_with_ext_net(self):
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/24')
net = self._make_network(self.fmt, 'pvt-net1', True)['network']
pvt_sub = self._make_subnet(
self.fmt, {'network': net}, '10.10.1.1',
'10.10.1.0/24')['subnet']
rtr = self._make_router(
self.fmt, net['tenant_id'], 'router1',
external_gateway_info={'network_id': ext_net['id']})['router']
self._router_interface_action('add', rtr['id'], pvt_sub['id'], None)
sub2 = self._make_subnet(
self.fmt, {'network': ext_net}, '200.100.100.1',
'200.100.100.0/29')['subnet']
self._update('subnets', sub2['id'],
{'subnet': {SNAT_POOL: True}})
alloc = self.driver.get_or_allocate_snat_ip(
context.get_admin_context(), 'h0', ext_net)
self.assertIsNotNone(alloc)
return sub2, rtr, pvt_sub
def _get_snat_ports(self, snat_subnet):
snat_ports = self._list('ports',
query_params=('network_id=%s' % snat_subnet['network_id'])
)['ports']
return [p for p in snat_ports
if p['fixed_ips'][0]['subnet_id'] == snat_subnet['id']]
def test_snat_port_delete_on_router_gw_clear(self):
snat_sub, rtr, _ = self._setup_router_with_ext_net()
self.assertTrue(self._get_snat_ports(snat_sub))
self._update('routers', rtr['id'],
{'router': {'external_gateway_info': None}})
self.assertFalse(self._get_snat_ports(snat_sub))
self._update('subnets', snat_sub['id'],
{'subnet': {SNAT_POOL: False}})
def test_snat_port_delete_on_router_intf_remove(self):
snat_sub, rtr, pvt_sub = self._setup_router_with_ext_net()
self.assertTrue(self._get_snat_ports(snat_sub))
self._router_interface_action('remove', rtr['id'], pvt_sub['id'],
None)
self.assertFalse(self._get_snat_ports(snat_sub))
self._update('subnets', snat_sub['id'],
{'subnet': {SNAT_POOL: False}})
def test_floatingip_alloc_in_snat_pool(self):
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
snat_sub = self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/24')['subnet']
self._update('subnets', snat_sub['id'],
{'subnet': {SNAT_POOL: True}})
# allocate FIP by subnet
res = self._create_floatingip(self.fmt, ext_net['id'],
subnet_id=snat_sub['id'])
self.assertEqual(400, res.status_int)
res = self.deserialize(self.fmt, res)
self.assertEqual('SnatPoolCannotBeUsedForFloatingIp',
res['NeutronError']['type'])
# allocate FIP by external address
res = self._make_floatingip(self.fmt, ext_net['id'],
floating_ip='100.100.100.10',
http_status=400)
self.assertEqual('SnatPoolCannotBeUsedForFloatingIp',
res['NeutronError']['type'])
def test_floatingip_alloc_in_non_snat_pool(self):
ext_net = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
snat_sub = self._make_subnet(
self.fmt, {'network': ext_net}, '100.100.100.1',
'100.100.100.0/24')['subnet']
self._update('subnets', snat_sub['id'],
{'subnet': {SNAT_POOL: True}})
fip_sub1 = self._make_subnet(
self.fmt, {'network': ext_net}, '200.100.100.1',
'200.100.100.0/29')['subnet']
self._make_subnet(
self.fmt, {'network': ext_net}, '250.100.100.1',
'250.100.100.0/29')
# FIP with subnet
fip1 = self._create_floatingip(self.fmt, ext_net['id'],
subnet_id=fip_sub1['id'])
self.assertEqual(201, fip1.status_int)
fip1 = self.deserialize(self.fmt, fip1)['floatingip']
self.assertEqual('200.100.100.2', fip1['floating_ip_address'])
# FIP with external-address
fip2 = self._make_floatingip(self.fmt, ext_net['id'],
floating_ip='250.100.100.3')['floatingip']
self.assertEqual('250.100.100.3', fip2['floating_ip_address'])
# FIP with no IP specifications - exhaust all available IPs
ips = netaddr.IPSet(['200.100.100.0/29', '250.100.100.0/29'])
for x in range(0, 8):
fip = self._make_floatingip(self.fmt, ext_net['id'])['floatingip']
self.assertTrue(fip['floating_ip_address'] in ips)

View File

@@ -1553,18 +1553,34 @@ class TestPolicyTarget(AIMBaseTestCase):
fip['nat_epg_tenant'] = ext_epg_tenant
self.assertEqual(fip, mapping['floating_ip'][0])
def _verify_ip_mapping_details(self, mapping, ext_net, ext_epg_tenant,
ext_epg_name):
self.assertTrue({'external_segment_name': ext_net,
def _verify_ip_mapping_details(self, mapping, ext_segment_name,
ext_epg_tenant, ext_epg_name):
self.assertTrue({'external_segment_name': ext_segment_name,
'nat_epg_name': ext_epg_name,
'nat_epg_tenant': ext_epg_tenant}
in mapping['ip_mapping'])
def _verify_host_snat_ip_details(self, mapping, ext_segment_name,
snat_ip, subnet_cidr):
gw, prefix = subnet_cidr.split('/')
self.assertEqual({'external_segment_name': ext_segment_name,
'host_snat_ip': snat_ip,
'gateway_ip': gw,
'prefixlen': int(prefix)},
mapping['host_snat_ips'][0])
def _do_test_get_gbp_details(self):
es1, es1_sub = self._setup_external_segment(
'es1', dn='uni/tn-t1/out-l1/instP-n1')
es2, _ = self._setup_external_segment(
es2, es2_sub1 = self._setup_external_segment(
'es2', dn='uni/tn-t1/out-l2/instP-n2')
es2_sub2 = self._make_subnet(
self.fmt, {'network': {'id': es2_sub1['network_id'],
'tenant_id': es2_sub1['tenant_id']}},
'200.200.0.1', '200.200.0.0/16')['subnet']
self._update('subnets', es2_sub2['id'],
{'subnet': {'apic:snat_host_pool': True}})
l3p = self.create_l3_policy(name='myl3',
external_segments={es1['id']: [], es2['id']: []})['l3_policy']
l2p = self.create_l2_policy(name='myl2',
@@ -1598,8 +1614,10 @@ class TestPolicyTarget(AIMBaseTestCase):
self._verify_gbp_details_assertions(
mapping, req_mapping, pt1['port_id'], epg_name, epg_tenant, subnet)
self._verify_fip_details(mapping, fip, 't1', 'EXT-l1')
self._verify_ip_mapping_details(mapping, 'es2',
't1', 'EXT-l2')
self._verify_ip_mapping_details(mapping,
'uni:tn-t1:out-l2:instP-n2', 't1', 'EXT-l2')
self._verify_host_snat_ip_details(mapping,
'uni:tn-t1:out-l2:instP-n2', '200.200.0.2', '200.200.0.1/16')
# Create event on a second host to verify that the SNAT
# port gets created for this second host
@@ -1611,8 +1629,12 @@ class TestPolicyTarget(AIMBaseTestCase):
self._neutron_admin_context, device='tap%s' % pt2['port_id'],
host='h2')
self.assertEqual(pt2['port_id'], mapping['port_id'])
self._verify_ip_mapping_details(mapping, 'es1', 't1', 'EXT-l1')
self._verify_ip_mapping_details(mapping, 'es2', 't1', 'EXT-l2')
self._verify_ip_mapping_details(mapping,
'uni:tn-t1:out-l1:instP-n1', 't1', 'EXT-l1')
self._verify_ip_mapping_details(mapping,
'uni:tn-t1:out-l2:instP-n2', 't1', 'EXT-l2')
self._verify_host_snat_ip_details(mapping,
'uni:tn-t1:out-l2:instP-n2', '200.200.0.3', '200.200.0.1/16')
def _do_test_gbp_details_no_pt(self):
# Create port and bind it
@@ -1629,8 +1651,13 @@ class TestPolicyTarget(AIMBaseTestCase):
ext_net1, router1 = self._setup_external_network(
'l1', dn='uni/tn-t1/out-l1/instP-n1')
_, router2 = self._setup_external_network(
ext_net2, router2 = self._setup_external_network(
'l2', dn='uni/tn-t1/out-l2/instP-n2')
ext_net2_sub2 = self._make_subnet(
self.fmt, {'network': ext_net2}, '200.200.0.1',
'200.200.0.0/16')['subnet']
self._update('subnets', ext_net2_sub2['id'],
{'subnet': {'apic:snat_host_pool': True}})
with self.network() as network:
with self.subnet(network=network, cidr='1.1.2.0/24',
@@ -1685,8 +1712,11 @@ class TestPolicyTarget(AIMBaseTestCase):
['10.10.0.0/26', '1.1.0.0/16', '2.1.0.0/16'],
epg_tenant)
self._verify_fip_details(mapping, fip, 't1', 'EXT-l1')
self._verify_ip_mapping_details(mapping, 'l2',
't1', 'EXT-l2')
self._verify_ip_mapping_details(mapping,
'uni:tn-t1:out-l2:instP-n2', 't1', 'EXT-l2')
self._verify_host_snat_ip_details(mapping,
'uni:tn-t1:out-l2:instP-n2', '200.200.0.2',
'200.200.0.1/16')
def test_get_gbp_details(self):
self._do_test_get_gbp_details()