[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:
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user