Router gw port subnet extension
Change-Id: I22c7dee488861fc98be55a3e4b0d2012e7df756c
This commit is contained in:
parent
5e605dae20
commit
f1b0dcc5f5
|
@ -1 +1 @@
|
|||
fd3e605a7232
|
||||
e70e4d426ce0
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""adds router gw ip pool subnet extension support
|
||||
|
||||
Revision ID: e70e4d426ce0
|
||||
Revises: fd3e605a7232
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e70e4d426ce0'
|
||||
down_revision = 'fd3e605a7232'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import sql
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('apic_aim_subnet_extensions',
|
||||
sa.Column('router_gw_ip_pool', sa.Boolean,
|
||||
nullable=False, server_default=sql.false()))
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
|
@ -57,6 +57,7 @@ NO_NAT_CIDRS = 'apic:no_nat_cidrs'
|
|||
MULTI_EXT_NETS = 'apic:multi_ext_nets'
|
||||
ADVERTISED_EXTERNALLY = 'apic:advertised_externally'
|
||||
SHARED_BETWEEN_VRFS = 'apic:shared_between_vrfs'
|
||||
ROUTER_GW_IP_POOL = 'apic:router_gw_ip_pool'
|
||||
|
||||
BD = 'BridgeDomain'
|
||||
EPG = 'EndpointGroup'
|
||||
|
@ -424,6 +425,12 @@ EXT_SUBNET_ATTRIBUTES = {
|
|||
'allow_post': True, 'allow_put': True,
|
||||
'is_visible': True, 'default': False,
|
||||
'convert_to': conv.convert_to_boolean,
|
||||
},
|
||||
ROUTER_GW_IP_POOL: {
|
||||
# Whether this subnet is used for snat ip allocation
|
||||
'allow_post': True, 'allow_put': True,
|
||||
'is_visible': True, 'default': False,
|
||||
'convert_to': conv.convert_to_boolean,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ class SnatPoolCannotBeUsedForFloatingIp(exceptions.InvalidInput):
|
|||
message = _("Floating IP cannot be allocated in SNAT host pool subnet.")
|
||||
|
||||
|
||||
class RouterGWIPPoolCannotBeUsedForFloatingIp(exceptions.InvalidInput):
|
||||
message = _("Floating IP cannot be allocated in router gw ip pool subnet.")
|
||||
|
||||
|
||||
class PreExistingSVICannotBeConnectedToRouter(exceptions.BadRequest):
|
||||
message = _("A SVI network with pre-existing l3out is not allowed to "
|
||||
"be connected to a router.")
|
||||
|
|
|
@ -169,6 +169,7 @@ class SubnetExtensionDb(model_base.BASEV2):
|
|||
epg_subnet = sa.Column(sa.Boolean)
|
||||
advertised_externally = sa.Column(sa.Boolean)
|
||||
shared_between_vrfs = sa.Column(sa.Boolean)
|
||||
router_gw_ip_pool = sa.Column(sa.Boolean)
|
||||
subnet = orm.relationship(models_v2.Subnet,
|
||||
backref=orm.backref(
|
||||
'aim_extension_mapping', lazy='joined',
|
||||
|
@ -580,6 +581,8 @@ class ExtensionDbMixin(object):
|
|||
db_obj['advertised_externally'])
|
||||
self._set_if_not_none(result, cisco_apic.SHARED_BETWEEN_VRFS,
|
||||
db_obj['shared_between_vrfs'])
|
||||
self._set_if_not_none(result, cisco_apic.ROUTER_GW_IP_POOL,
|
||||
db_obj['router_gw_ip_pool'])
|
||||
return result
|
||||
|
||||
def set_subnet_extn_db(self, session, subnet_id, res_dict):
|
||||
|
@ -607,6 +610,9 @@ class ExtensionDbMixin(object):
|
|||
if cisco_apic.SHARED_BETWEEN_VRFS in res_dict:
|
||||
db_obj['shared_between_vrfs'] = res_dict[
|
||||
cisco_apic.SHARED_BETWEEN_VRFS]
|
||||
if cisco_apic.ROUTER_GW_IP_POOL in res_dict:
|
||||
db_obj['router_gw_ip_pool'] = res_dict[
|
||||
cisco_apic.ROUTER_GW_IP_POOL]
|
||||
session.add(db_obj)
|
||||
|
||||
def get_router_extn_db(self, session, router_id):
|
||||
|
|
|
@ -282,6 +282,8 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
res_dict.get(cisco_apic.ADVERTISED_EXTERNALLY, True))
|
||||
result[cisco_apic.SHARED_BETWEEN_VRFS] = (
|
||||
res_dict.get(cisco_apic.SHARED_BETWEEN_VRFS, False))
|
||||
result[cisco_apic.ROUTER_GW_IP_POOL] = (
|
||||
res_dict.get(cisco_apic.ROUTER_GW_IP_POOL, False))
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if db_api.is_retriable(e):
|
||||
|
@ -307,6 +309,8 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
res_dict.get(cisco_apic.ADVERTISED_EXTERNALLY, True))
|
||||
result[cisco_apic.SHARED_BETWEEN_VRFS] = (
|
||||
res_dict.get(cisco_apic.SHARED_BETWEEN_VRFS, False))
|
||||
result[cisco_apic.ROUTER_GW_IP_POOL] = (
|
||||
res_dict.get(cisco_apic.ROUTER_GW_IP_POOL, False))
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if db_api.is_retriable(e):
|
||||
|
@ -327,7 +331,9 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
cisco_apic.ADVERTISED_EXTERNALLY:
|
||||
data.get(cisco_apic.ADVERTISED_EXTERNALLY, True),
|
||||
cisco_apic.SHARED_BETWEEN_VRFS:
|
||||
data.get(cisco_apic.SHARED_BETWEEN_VRFS, False)}
|
||||
data.get(cisco_apic.SHARED_BETWEEN_VRFS, False),
|
||||
cisco_apic.ROUTER_GW_IP_POOL:
|
||||
data.get(cisco_apic.ROUTER_GW_IP_POOL, False)}
|
||||
self.set_subnet_extn_db(plugin_context.session, result['id'],
|
||||
res_dict)
|
||||
result.update(res_dict)
|
||||
|
@ -336,7 +342,8 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
if (cisco_apic.SNAT_HOST_POOL not in data and
|
||||
cisco_apic.SNAT_SUBNET_ONLY not in data and
|
||||
cisco_apic.ADVERTISED_EXTERNALLY not in data and
|
||||
cisco_apic.SHARED_BETWEEN_VRFS not in data):
|
||||
cisco_apic.SHARED_BETWEEN_VRFS not in data and
|
||||
cisco_apic.ROUTER_GW_IP_POOL not in data):
|
||||
return
|
||||
|
||||
res_dict = {}
|
||||
|
@ -356,6 +363,10 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
res_dict.update({cisco_apic.SHARED_BETWEEN_VRFS:
|
||||
data[cisco_apic.SHARED_BETWEEN_VRFS]})
|
||||
|
||||
if cisco_apic.ROUTER_GW_IP_POOL in data:
|
||||
res_dict.update({cisco_apic.ROUTER_GW_IP_POOL:
|
||||
data[cisco_apic.ROUTER_GW_IP_POOL]})
|
||||
|
||||
self.set_subnet_extn_db(plugin_context.session, result['id'],
|
||||
res_dict)
|
||||
result.update(res_dict)
|
||||
|
|
|
@ -5537,6 +5537,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||
sn_ext = self.get_subnet_extn_db(session, floatingip['subnet_id'])
|
||||
if sn_ext.get(cisco_apic.SNAT_HOST_POOL, False):
|
||||
raise exceptions.SnatPoolCannotBeUsedForFloatingIp()
|
||||
if sn_ext.get(cisco_apic.ROUTER_GW_IP_POOL, False):
|
||||
raise exceptions.RouterGWIPPoolCannotBeUsedForFloatingIp()
|
||||
elif floatingip.get('floating_ip_address'):
|
||||
extn_db_sn = extension_db.SubnetExtensionDb
|
||||
|
||||
|
@ -5547,7 +5549,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
|
|||
extn_db_sn.subnet_id == models_v2.Subnet.id)
|
||||
query += lambda q: q.filter(
|
||||
models_v2.Subnet.network_id == sa.bindparam('network_id'))
|
||||
query += lambda q: q.filter(extn_db_sn.snat_host_pool.is_(True))
|
||||
query += lambda q: q.filter(sa.or_(
|
||||
extn_db_sn.snat_host_pool.is_(True),
|
||||
extn_db_sn.router_gw_ip_pool.is_(True)))
|
||||
|
||||
cidrs = query(session).params(
|
||||
network_id=floatingip['floating_network_id']).all()
|
||||
|
||||
|
|
|
@ -201,12 +201,20 @@ class ApicL3Plugin(extraroute_db.ExtraRoute_db_mixin,
|
|||
# No subnet set, so look for at most one subnet in
|
||||
# each address family to include in the request,
|
||||
# skipping any subnets with the SNAT only extension
|
||||
# set to True.
|
||||
# set to True while prioritizing those with the
|
||||
# router gw ip pool extension set to True.
|
||||
subnets_in_nw = self._core_plugin.get_subnets_by_network(context,
|
||||
gw_info['network_id'])
|
||||
subs = {}
|
||||
for ext_sn in subnets_in_nw:
|
||||
if (ext_sn['apic:snat_subnet_only'] is False and
|
||||
ext_sn['apic:snat_host_pool'] is False and
|
||||
ext_sn['apic:router_gw_ip_pool'] is True and
|
||||
not subs.get(ext_sn['ip_version'])):
|
||||
subs[ext_sn['ip_version']] = {'subnet_id': ext_sn['id']}
|
||||
for ext_sn in subnets_in_nw:
|
||||
if (ext_sn['apic:snat_subnet_only'] is False and
|
||||
ext_sn['apic:snat_host_pool'] is False and
|
||||
not subs.get(ext_sn['ip_version'])):
|
||||
subs[ext_sn['ip_version']] = {'subnet_id': ext_sn['id']}
|
||||
gw_info.update({'external_fixed_ips': list(subs.values())})
|
||||
|
|
|
@ -134,6 +134,7 @@ SNAT_SUBNET_ONLY = 'apic:snat_subnet_only'
|
|||
EPG_SUBNET = 'apic:epg_subnet'
|
||||
ADVERTISED_EXTERNALLY = 'apic:advertised_externally'
|
||||
SHARED_BETWEEN_VRFS = 'apic:shared_between_vrfs'
|
||||
ROUTER_GW_IP_POOL = 'apic:router_gw_ip_pool'
|
||||
|
||||
|
||||
def sort_if_list(attr):
|
||||
|
@ -368,7 +369,8 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
|
|||
'provider:network_type',
|
||||
'apic:multi_ext_nets',
|
||||
ADVERTISED_EXTERNALLY,
|
||||
SHARED_BETWEEN_VRFS
|
||||
SHARED_BETWEEN_VRFS,
|
||||
ROUTER_GW_IP_POOL
|
||||
)
|
||||
self.name_mapper = apic_mapper.APICNameMapper()
|
||||
self.t1_aname = self.name_mapper.project(None, 't1')
|
||||
|
@ -2443,6 +2445,70 @@ class TestAimMapping(ApicAimTestCase):
|
|||
def test_subnetpool_lifecycle_with_scopes(self):
|
||||
self._test_subnetpool_lifecycle(use_scopes=True)
|
||||
|
||||
def test_router_gw_port(self):
|
||||
ext_net = self._make_ext_network(
|
||||
'ext-net', dn=self.dn_t1_l1_n1)
|
||||
aim_resource.L3Outside(tenant_name=self.t1_aname, name='l1')
|
||||
|
||||
subnet = self._create_subnet_with_extension(
|
||||
self.fmt, ext_net, '10.0.0.1', '10.0.0.0/24',
|
||||
**{'gateway_ip': '10.0.0.1',
|
||||
ROUTER_GW_IP_POOL: False})['subnet']
|
||||
|
||||
subnet2 = self._create_subnet_with_extension(
|
||||
self.fmt, ext_net, '20.0.0.1', '20.0.0.0/24',
|
||||
**{'gateway_ip': '20.0.0.1',
|
||||
ROUTER_GW_IP_POOL: False})['subnet']
|
||||
|
||||
subnet3 = self._create_subnet_with_extension(
|
||||
self.fmt, ext_net, '30.0.0.1', '30.0.0.0/24',
|
||||
**{'gateway_ip': '30.0.0.1',
|
||||
ROUTER_GW_IP_POOL: True})['subnet']
|
||||
|
||||
router = self._make_router(
|
||||
self.fmt, self._tenant_id, 'router1',
|
||||
external_gateway_info={'network_id': ext_net['id']})['router']
|
||||
routerb = self._make_router(
|
||||
self.fmt, self._tenant_id, 'router2',
|
||||
external_gateway_info={'network_id': ext_net['id']})['router']
|
||||
self._check_router(router)
|
||||
self._check_router(routerb)
|
||||
self.assertEqual(subnet3['id'],
|
||||
router['external_gateway_info']
|
||||
['external_fixed_ips'][0]['subnet_id'])
|
||||
self.assertEqual(subnet3['id'],
|
||||
routerb['external_gateway_info']
|
||||
['external_fixed_ips'][0]['subnet_id'])
|
||||
|
||||
# Test updating extension
|
||||
self._update('subnets', subnet2['id'],
|
||||
{'subnet': {ROUTER_GW_IP_POOL: True}})
|
||||
|
||||
router = self._make_router(
|
||||
self.fmt, self._tenant_id, 'router3',
|
||||
external_gateway_info={'network_id': ext_net['id']})['router']
|
||||
self.assertEqual(subnet2['id'],
|
||||
router['external_gateway_info']
|
||||
['external_fixed_ips'][0]['subnet_id'])
|
||||
|
||||
self._update('subnets', subnet2['id'],
|
||||
{'subnet': {ROUTER_GW_IP_POOL: False}})
|
||||
router = self._make_router(
|
||||
self.fmt, self._tenant_id, 'router4',
|
||||
external_gateway_info={'network_id': ext_net['id']})['router']
|
||||
self.assertEqual(subnet3['id'],
|
||||
router['external_gateway_info']
|
||||
['external_fixed_ips'][0]['subnet_id'])
|
||||
|
||||
self._update('subnets', subnet['id'],
|
||||
{'subnet': {ROUTER_GW_IP_POOL: True}})
|
||||
router = self._make_router(
|
||||
self.fmt, self._tenant_id, 'router4',
|
||||
external_gateway_info={'network_id': ext_net['id']})['router']
|
||||
self.assertEqual(subnet['id'],
|
||||
router['external_gateway_info']
|
||||
['external_fixed_ips'][0]['subnet_id'])
|
||||
|
||||
def test_router_lifecycle(self):
|
||||
# Test create.
|
||||
router = self._make_router(
|
||||
|
|
Loading…
Reference in New Issue