Merge "SVI network support for APIC."

This commit is contained in:
Zuul 2018-02-16 18:28:36 +00:00 committed by Gerrit Code Review
commit 70731da6f2
12 changed files with 1304 additions and 437 deletions

View File

@ -0,0 +1,44 @@
# 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.
#
"""svi-apic
Revision ID: 5fe2285071fd
Revises: 886c376885a3
Create Date: 2018-01-05 00:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = '5fe2285071fd'
down_revision = '886c376885a3'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('apic_aim_network_extensions',
sa.Column('svi', sa.Boolean))
op.add_column('apic_aim_network_mappings',
sa.Column('l3out_name', sa.String(64), nullable=True))
op.add_column('apic_aim_network_mappings',
sa.Column('l3out_ext_net_name', sa.String(64),
nullable=True))
op.add_column('apic_aim_network_mappings',
sa.Column('l3out_tenant_name', sa.String(64),
nullable=True))
def downgrade():
pass

View File

@ -1 +1 @@
886c376885a3
5fe2285071fd

View File

@ -26,6 +26,7 @@ SYNC_STATE = 'apic:synchronization_state'
NAT_TYPE = 'apic:nat_type'
SNAT_HOST_POOL = 'apic:snat_host_pool'
EXTERNAL_CIDRS = 'apic:external_cidrs'
SVI = 'apic:svi'
BD = 'BridgeDomain'
EPG = 'EndpointGroup'
@ -44,6 +45,14 @@ APIC_ATTRIBUTES = {
SYNC_STATE: {'allow_post': False, 'allow_put': False, 'is_visible': True}
}
NET_ATTRIBUTES = {
SVI: {
'allow_post': True, 'allow_put': False,
'is_visible': True, 'default': False,
'convert_to': conv.convert_to_boolean,
},
}
EXT_NET_ATTRIBUTES = {
DIST_NAMES: {
# DN of corresponding APIC L3Out external network; can be
@ -105,7 +114,8 @@ ADDRESS_SCOPE_ATTRIBUTES = {
EXTENDED_ATTRIBUTES_2_0 = {
net_def.COLLECTION_NAME: dict(
APIC_ATTRIBUTES.items() + EXT_NET_ATTRIBUTES.items()),
APIC_ATTRIBUTES.items() + EXT_NET_ATTRIBUTES.items() +
NET_ATTRIBUTES.items()),
subnet_def.COLLECTION_NAME: dict(
APIC_ATTRIBUTES.items() + EXT_SUBNET_ATTRIBUTES.items()),
as_def.COLLECTION_NAME: dict(

View File

@ -40,6 +40,11 @@ apic_opts = [
default=False,
help=("This will enable the iptables firewall implementation "
"on those compute nodes.")),
# TODO(kentwu): Need to define the external routed domain
# AIM object instead.
cfg.StrOpt('l3_domain_dn', default='',
help=("The DN of the APIC external routed domain used by the "
"auto l3out created for the SVI networks.")),
]

View File

@ -172,7 +172,7 @@ def do_ap_name_change(session, conf=None):
net_dbs = session.query(models_v2.Network).options(lazyload('*')).all()
for net_db in net_dbs:
ext_db = ext_mixin.get_network_extn_db(session, net_db.id)
if ext_db and ext_db[ext.EXTERNAL_NETWORK]:
if ext_db and ext_db.get(ext.EXTERNAL_NETWORK):
alembic_util.msg("Migrating external network: %s" % net_db)
# Its a managed external network.
ext_net = aim_resource.ExternalNetwork.from_dn(

View File

@ -64,6 +64,10 @@ class NetworkMapping(model_base.BASEV2):
epg_app_profile_name = sa.Column(sa.String(64))
epg_tenant_name = sa.Column(sa.String(64))
l3out_name = sa.Column(sa.String(64))
l3out_ext_net_name = sa.Column(sa.String(64))
l3out_tenant_name = sa.Column(sa.String(64))
vrf_name = sa.Column(sa.String(64))
vrf_tenant_name = sa.Column(sa.String(64))
@ -95,16 +99,26 @@ class DbMixin(object):
tenant_name=mapping.vrf_tenant_name,
name=mapping.vrf_name)
def _add_network_mapping(self, session, network_id, bd, epg, vrf):
mapping = NetworkMapping(
network_id=network_id,
bd_name=bd.name,
bd_tenant_name=bd.tenant_name,
epg_name=epg.name,
epg_app_profile_name=epg.app_profile_name,
epg_tenant_name=epg.tenant_name,
vrf_name=vrf.name,
vrf_tenant_name=vrf.tenant_name)
def _add_network_mapping(self, session, network_id, bd, epg, vrf,
ext_net=None):
if not ext_net:
mapping = NetworkMapping(
network_id=network_id,
bd_name=bd.name,
bd_tenant_name=bd.tenant_name,
epg_name=epg.name,
epg_app_profile_name=epg.app_profile_name,
epg_tenant_name=epg.tenant_name,
vrf_name=vrf.name,
vrf_tenant_name=vrf.tenant_name)
else:
mapping = NetworkMapping(
network_id=network_id,
l3out_name=ext_net.l3out_name,
l3out_ext_net_name=ext_net.name,
l3out_tenant_name=ext_net.tenant_name,
vrf_name=vrf.name,
vrf_tenant_name=vrf.tenant_name)
session.add(mapping)
return mapping
@ -142,6 +156,18 @@ class DbMixin(object):
app_profile_name=mapping.epg_app_profile_name,
name=mapping.epg_name)
def _get_network_l3out(self, mapping):
if not mapping:
return None
return aim_resource.L3Outside(
tenant_name=mapping.l3out_tenant_name,
name=mapping.l3out_name)
def _get_network_l3out_ext_net(self, mapping):
return aim_resource.ExternalNetwork(
tenant_name=mapping.l3out_tenant_name,
l3out_name=mapping.l3out_name, name=mapping.l3out_ext_net_name)
def _get_network_vrf(self, mapping):
return aim_resource.VRF(
tenant_name=mapping.vrf_tenant_name,
@ -156,6 +182,10 @@ class DbMixin(object):
mapping.epg_app_profile_name = epg.app_profile_name
mapping.epg_name = epg.name
def _set_network_l3out(self, mapping, l3out):
mapping.l3out_tenant_name = l3out.tenant_name
mapping.l3out_name = l3out.name
def _set_network_vrf(self, mapping, vrf):
mapping.vrf_tenant_name = vrf.tenant_name
mapping.vrf_name = vrf.name

View File

@ -53,3 +53,12 @@ class SnatPortsInUse(exceptions.SubnetInUse):
class SnatPoolCannotBeUsedForFloatingIp(exceptions.InvalidInput):
message = _("Floating IP cannot be allocated in SNAT host pool subnet.")
class PreExistingSVICannotBeConnectedToRouter(exceptions.BadRequest):
message = _("A SVI network with pre-existing l3out is not allowed to "
"be connected to a router.")
class OnlyOneSubnetInSVINetwork(exceptions.BadRequest):
message = _("Only one subnet is allowed in SVI network.")

View File

@ -13,8 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.db import models_v2
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm
from gbpservice.neutron.extensions import cisco_apic
from gbpservice.neutron.extensions import cisco_apic_l3
@ -29,6 +31,12 @@ class NetworkExtensionDb(model_base.BASEV2):
primary_key=True)
external_network_dn = sa.Column(sa.String(1024))
nat_type = sa.Column(sa.Enum('distributed', 'edge', ''))
svi = sa.Column(sa.Boolean)
network = orm.relationship(models_v2.Network,
backref=orm.backref(
'aim_extension_mapping', lazy='joined',
uselist=False, cascade='delete'))
class NetworkExtensionCidrDb(model_base.BASEV2):
@ -79,8 +87,10 @@ class ExtensionDbMixin(object):
db_obj['external_network_dn'])
self._set_if_not_none(result, cisco_apic.NAT_TYPE,
db_obj['nat_type'])
self._set_if_not_none(result, cisco_apic.SVI, db_obj['svi'])
if result.get(cisco_apic.EXTERNAL_NETWORK):
result[cisco_apic.EXTERNAL_CIDRS] = [c['cidr'] for c in db_cidrs]
return result
def set_network_extn_db(self, session, network_id, res_dict):
@ -93,7 +103,10 @@ class ExtensionDbMixin(object):
res_dict[cisco_apic.EXTERNAL_NETWORK])
if cisco_apic.NAT_TYPE in res_dict:
db_obj['nat_type'] = res_dict[cisco_apic.NAT_TYPE]
if cisco_apic.SVI in res_dict:
db_obj['svi'] = res_dict[cisco_apic.SVI]
session.add(db_obj)
if cisco_apic.EXTERNAL_CIDRS in res_dict:
self._update_list_attr(session, NetworkExtensionCidrDb, 'cidr',
res_dict[cisco_apic.EXTERNAL_CIDRS],

View File

@ -77,6 +77,12 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
LOG.exception("APIC AIM extend_network_dict failed")
def process_create_network(self, plugin_context, data, result):
is_svi = data.get(cisco_apic.SVI, False)
res_dict = {cisco_apic.SVI: is_svi}
self.set_network_extn_db(plugin_context.session, result['id'],
res_dict)
result.update(res_dict)
if (data.get(cisco_apic.DIST_NAMES) and
data[cisco_apic.DIST_NAMES].get(cisco_apic.EXTERNAL_NETWORK)):
dn = data[cisco_apic.DIST_NAMES][cisco_apic.EXTERNAL_NETWORK]
@ -85,11 +91,15 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
except aim_exc.InvalidDNForAciResource:
raise n_exc.InvalidInput(
error_message=('%s is not valid ExternalNetwork DN' % dn))
res_dict = {cisco_apic.EXTERNAL_NETWORK: dn,
cisco_apic.NAT_TYPE:
data.get(cisco_apic.NAT_TYPE, 'distributed'),
cisco_apic.EXTERNAL_CIDRS:
data.get(cisco_apic.EXTERNAL_CIDRS, ['0.0.0.0/0'])}
if is_svi:
res_dict = {cisco_apic.EXTERNAL_NETWORK: dn}
else:
res_dict = {cisco_apic.EXTERNAL_NETWORK: dn,
cisco_apic.NAT_TYPE:
data.get(cisco_apic.NAT_TYPE, 'distributed'),
cisco_apic.EXTERNAL_CIDRS:
data.get(
cisco_apic.EXTERNAL_CIDRS, ['0.0.0.0/0'])}
self.set_network_extn_db(plugin_context.session, result['id'],
res_dict)
result.setdefault(cisco_apic.DIST_NAMES, {})[

View File

@ -15,6 +15,7 @@
import copy
import netaddr
import re
import sqlalchemy as sa
from aim.aim_lib import nat_strategy
@ -69,6 +70,7 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import trunk_driver
LOG = log.getLogger(__name__)
DEVICE_OWNER_SNAT_PORT = 'apic:snat-pool'
DEVICE_OWNER_SVI_PORT = 'apic:svi'
ANY_FILTER_NAME = 'AnyFilter'
ANY_FILTER_ENTRY_NAME = 'AnyFilterEntry'
@ -77,6 +79,9 @@ UNROUTED_VRF_NAME = 'UnroutedVRF'
COMMON_TENANT_NAME = 'common'
ROUTER_SUBJECT_NAME = 'route'
DEFAULT_SG_NAME = 'DefaultSecurityGroup'
L3OUT_NODE_PROFILE_NAME = 'NodeProfile'
L3OUT_IF_PROFILE_NAME = 'IfProfile'
L3OUT_EXT_EPG = 'ExtEpg'
AGENT_TYPE_DVS = 'DVS agent'
VIF_TYPE_DVS = 'dvs'
@ -90,6 +95,16 @@ NO_ADDR_SCOPE = object()
DVS_AGENT_KLASS = 'networking_vsphere.common.dvs_agent_rpc_api.DVSClientAPI'
DEFAULT_HOST_DOMAIN = '*'
# TODO(kentwu): Move this to AIM utils maybe to avoid adding too much
# APIC logic to the mechanism driver
ACI_CHASSIS_DESCR_STRING = 'topology/pod-%s/node-%s'
ACI_PORT_DESCR_FORMATS = ('topology/pod-(\d+)/paths-(\d+)/pathep-'
'\[eth(\d+)/(\d+(\/\d+)*)\]')
ACI_VPCPORT_DESCR_FORMAT = ('topology/pod-(\d+)/protpaths-(\d+)-(\d+)/pathep-'
'\[(.*)\]')
# TODO(kentwu): A pool of router IDs has to be put in the config file instead
APIC_ROUTER_IDS = ['199.199.199.198', '199.199.199.199']
class KeystoneNotificationEndpoint(object):
filter_rule = oslo_messaging.NotificationFilter(
@ -200,9 +215,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
enable_keystone_notification_purge)
self.enable_iptables_firewall = (cfg.CONF.ml2_apic_aim.
enable_iptables_firewall)
self.l3_domain_dn = cfg.CONF.ml2_apic_aim.l3_domain_dn
local_api.QUEUE_OUT_OF_PROCESS_NOTIFICATIONS = True
self._ensure_static_resources()
trunk_driver.register()
self.port_desc_re = re.compile(ACI_PORT_DESCR_FORMATS)
self.vpcport_desc_re = re.compile(ACI_VPCPORT_DESCR_FORMAT)
def _ensure_static_resources(self):
session = db_api.get_writer_session()
@ -439,6 +457,42 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
epg = resource
elif isinstance(resource, aim_resource.VRF):
vrf = resource
elif self._is_svi(current):
_, ext_net, _ = self._get_aim_external_stuff(current)
# This means no DN is being provided. Then we should try to create
# the l3out automatically
if not ext_net:
tenant_aname = self.name_mapper.project(session,
current['tenant_id'])
vrf = self._map_unrouted_vrf()
aname = self.name_mapper.network(session, current['id'])
dname = aim_utils.sanitize_display_name(current['name'])
aim_l3out = aim_resource.L3Outside(
tenant_name=tenant_aname,
name=aname, display_name=dname, vrf_name=vrf.name,
l3_domain_dn=self.l3_domain_dn)
self.aim.create(aim_ctx, aim_l3out)
aim_ext_net = aim_resource.ExternalNetwork(
tenant_name=tenant_aname,
l3out_name=aname, name=L3OUT_EXT_EPG)
self.aim.create(aim_ctx, aim_ext_net)
aim_ext_subnet_ipv4 = aim_resource.ExternalSubnet(
tenant_name=tenant_aname,
l3out_name=aname,
external_network_name=L3OUT_EXT_EPG, cidr='0.0.0.0/0')
self.aim.create(aim_ctx, aim_ext_subnet_ipv4)
aim_ext_subnet_ipv6 = aim_resource.ExternalSubnet(
tenant_name=tenant_aname,
l3out_name=aname,
external_network_name=L3OUT_EXT_EPG, cidr='::/0')
self.aim.create(aim_ctx, aim_ext_subnet_ipv6)
self._add_network_mapping(session, current['id'], None, None,
vrf, aim_ext_net)
return
else:
bd, epg = self._map_network(session, current)
@ -479,10 +533,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if (not is_ext and
current['name'] != original['name']):
dname = aim_utils.sanitize_display_name(current['name'])
bd = self._get_network_bd(mapping)
self.aim.update(aim_ctx, bd, display_name=dname)
epg = self._get_network_epg(mapping)
self.aim.update(aim_ctx, epg, display_name=dname)
if not self._is_svi(current):
bd = self._get_network_bd(mapping)
self.aim.update(aim_ctx, bd, display_name=dname)
epg = self._get_network_epg(mapping)
self.aim.update(aim_ctx, epg, display_name=dname)
else:
l3out = self._get_network_l3out(mapping)
if l3out:
self.aim.update(aim_ctx, l3out, display_name=dname)
if is_ext:
_, ext_net, ns = self._get_aim_nat_strategy(current)
@ -508,6 +567,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# TODO(amitbose) delete L3out only if no other Neutron
# network is using the L3out
ns.delete_l3outside(aim_ctx, l3out)
elif self._is_svi(current):
l3out, ext_net, _ = self._get_aim_external_stuff(current)
aim_l3out = self.aim.get(aim_ctx, l3out)
if not aim_l3out:
return
# this means its pre-existing l3out
if aim_l3out.monitored:
# just delete everything under NodeProfile
aim_l3out_np = aim_resource.L3OutNodeProfile(
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
name=L3OUT_NODE_PROFILE_NAME)
self.aim.delete(aim_ctx, aim_l3out_np, cascade=True)
else:
self.aim.delete(aim_ctx, l3out, cascade=True)
else:
mapping = self._get_network_mapping(session, current['id'])
bd = self._get_network_bd(mapping)
@ -527,18 +600,33 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# mapping = network_db.aim_mapping
mapping = self._get_network_mapping(session, network_db['id'])
if mapping:
bd = self._get_network_bd(mapping)
dist_names[cisco_apic.BD] = bd.dn
sync_state = self._merge_status(aim_ctx, sync_state, bd)
if mapping.epg_name:
bd = self._get_network_bd(mapping)
dist_names[cisco_apic.BD] = bd.dn
sync_state = self._merge_status(aim_ctx, sync_state, bd)
epg = self._get_network_epg(mapping)
dist_names[cisco_apic.EPG] = epg.dn
sync_state = self._merge_status(aim_ctx, sync_state, epg)
epg = self._get_network_epg(mapping)
dist_names[cisco_apic.EPG] = epg.dn
sync_state = self._merge_status(aim_ctx, sync_state, epg)
# SVI network with auto l3out.
elif mapping.l3out_name:
l3out_ext_net = self._get_network_l3out_ext_net(mapping)
dist_names[cisco_apic.EXTERNAL_NETWORK] = l3out_ext_net.dn
sync_state = self._merge_status(aim_ctx, sync_state,
l3out_ext_net)
vrf = self._get_network_vrf(mapping)
dist_names[cisco_apic.VRF] = vrf.dn
sync_state = self._merge_status(aim_ctx, sync_state, vrf)
# SVI network with pre-existing l3out.
if (network_db.aim_extension_mapping.svi and
network_db.aim_extension_mapping.external_network_dn):
_, ext_net, _ = self._get_aim_external_stuff_db(session,
network_db)
if ext_net:
sync_state = self._merge_status(aim_ctx, sync_state, ext_net)
# REVISIT: Should the external network be persisted in the
# mapping along with the other resources?
if network_db.external is not None:
@ -567,6 +655,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ns.create_subnet(aim_ctx, l3out,
self._subnet_to_gw_ip_mask(current))
# Limit 1 subnet per SVI network as each SVI interface
# in ACI can only have 1 primary addr
if network_db.aim_extension_mapping.svi:
subnets_size = (session.query(models_v2.Subnet)
.filter(models_v2.Subnet.network_id == network_id)
.count())
if subnets_size > 1:
raise exceptions.OnlyOneSubnetInSVINetwork()
# Neutron subnets in non-external networks are mapped to AIM
# Subnets as they are added to routers as interfaces.
@ -593,6 +690,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if (not is_ext and
current['name'] != original['name']):
# Nothing to be done for SVI network.
if self._is_svi(context.network.current):
return
bd = self._get_network_bd(network_db.aim_mapping)
@ -663,7 +763,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if sub:
dist_names[cisco_apic.SUBNET] = sub.dn
sync_state = self._merge_status(aim_ctx, sync_state, sub)
elif network_db.aim_mapping:
elif network_db.aim_mapping and network_db.aim_mapping.bd_name:
bd = self._get_network_bd(network_db.aim_mapping)
for gw_ip, router_id in self._subnet_router_ips(session,
@ -853,13 +953,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
filter_by(id=subnet_db.network_id).
one())
dname = aim_utils.sanitize_display_name(
name + "-" + (subnet_db.name or subnet_db.cidr))
bd = self._get_network_bd(network_db.aim_mapping)
sn = self._map_subnet(subnet_db, intf.ip_address, bd)
self.aim.update(aim_ctx, sn, display_name=dname)
if network_db.aim_mapping and network_db.aim_mapping.bd_name:
dname = aim_utils.sanitize_display_name(
name + "-" + (subnet_db.name or subnet_db.cidr))
bd = self._get_network_bd(network_db.aim_mapping)
sn = self._map_subnet(subnet_db, intf.ip_address, bd)
self.aim.update(aim_ctx, sn, display_name=dname)
def is_diff(old, new, attr):
return sorted(old[attr]) != sorted(new[attr])
@ -962,10 +1061,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
n_constants.DEVICE_OWNER_ROUTER_INTF)):
ip_address, subnet_db, network_db = intf
bd = self._get_network_bd(network_db.aim_mapping)
sn = self._map_subnet(subnet_db, intf.ip_address, bd)
dist_names[intf.ip_address] = sn.dn
sync_state = self._merge_status(aim_ctx, sync_state, sn)
if network_db.aim_mapping.bd_name:
bd = self._get_network_bd(network_db.aim_mapping)
sn = self._map_subnet(subnet_db, intf.ip_address, bd)
dist_names[intf.ip_address] = sn.dn
sync_state = self._merge_status(aim_ctx, sync_state, sn)
scope_id = (subnet_db.subnetpool and
subnet_db.subnetpool.address_scope_id)
@ -1007,6 +1107,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
network_id = port['network_id']
network_db = self.plugin._get_network(context, network_id)
# SVI network with pre-existing l3out is not allowed to be
# connected to a router at this moment
if (network_db.aim_extension_mapping.svi and
network_db.aim_extension_mapping.external_network_dn):
raise exceptions.PreExistingSVICannotBeConnectedToRouter()
# Find the address_scope(s) for the new interface.
#
# REVISIT: If dual-stack interfaces allowed, process each
@ -1147,12 +1253,17 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
else:
vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
epg = None
# Associate or map network, depending on whether it has other
# interfaces.
if not net_intfs:
# First interface for network.
bd, epg = self._associate_network_with_vrf(
aim_ctx, network_db, vrf, nets_to_notify)
if network_db.aim_mapping.epg_name:
bd, epg = self._associate_network_with_vrf(
aim_ctx, network_db, vrf, nets_to_notify)
elif network_db.aim_mapping.l3out_name:
l3out, epg = self._associate_network_with_vrf(
aim_ctx, network_db, vrf, nets_to_notify)
else:
# Network is already routed.
#
@ -1160,40 +1271,46 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# to move the BD and EPG from already-routed v6 VRF to
# newly-routed v4 VRF, and setup identity NAT for the v6
# traffic.
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
if network_db.aim_mapping.epg_name:
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
elif network_db.aim_mapping.l3out_name:
epg = self._get_network_l3out_ext_net(
network_db.aim_mapping)
# Create AIM Subnet(s) for each added Neutron subnet.
for subnet in subnets:
gw_ip = self._ip_for_subnet(subnet, port['fixed_ips'])
if network_db.aim_mapping.epg_name:
# Create AIM Subnet(s) for each added Neutron subnet.
for subnet in subnets:
gw_ip = self._ip_for_subnet(subnet, port['fixed_ips'])
dname = aim_utils.sanitize_display_name(
router['name'] + "-" +
(subnet['name'] or subnet['cidr']))
dname = aim_utils.sanitize_display_name(
router['name'] + "-" +
(subnet['name'] or subnet['cidr']))
sn = self._map_subnet(subnet, gw_ip, bd)
sn.display_name = dname
sn = self.aim.create(aim_ctx, sn)
sn = self._map_subnet(subnet, gw_ip, bd)
sn.display_name = dname
sn = self.aim.create(aim_ctx, sn)
# Ensure network's EPG provides/consumes router's Contract.
contract = self._map_router(session, router, True)
# this could be internal or external EPG
epg = self.aim.get(aim_ctx, epg)
contracts = epg.consumed_contract_names
if contract.name not in contracts:
contracts.append(contract.name)
epg = self.aim.update(aim_ctx, epg,
consumed_contract_names=contracts)
contracts = epg.provided_contract_names
if contract.name not in contracts:
contracts.append(contract.name)
epg = self.aim.update(aim_ctx, epg,
provided_contract_names=contracts)
if epg:
contracts = epg.consumed_contract_names
if contract.name not in contracts:
contracts.append(contract.name)
epg = self.aim.update(aim_ctx, epg,
consumed_contract_names=contracts)
contracts = epg.provided_contract_names
if contract.name not in contracts:
contracts.append(contract.name)
epg = self.aim.update(aim_ctx, epg,
provided_contract_names=contracts)
# If external-gateway is set, handle external-connectivity changes.
if router.gw_port_id:
# External network is not supported for SVI network for now.
if router.gw_port_id and not network_db.aim_extension_mapping.svi:
net = self.plugin.get_network(context,
router.gw_port.network_id)
# If this is first interface-port, then that will determine
@ -1244,20 +1361,24 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# stack's scope separately, or at least raise an exception.
scope_id = self._get_address_scope_id_for_subnets(context, subnets)
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
old_vrf = self._get_network_vrf(network_db.aim_mapping)
router_db = (session.query(l3_db.Router).
filter_by(id=router_id).
one())
contract = self._map_router(session, router_db, True)
# Remove AIM Subnet(s) for each removed Neutron subnet.
for subnet in subnets:
gw_ip = self._ip_for_subnet(subnet, port['fixed_ips'])
sn = self._map_subnet(subnet, gw_ip, bd)
self.aim.delete(aim_ctx, sn)
epg = None
old_vrf = self._get_network_vrf(network_db.aim_mapping)
if network_db.aim_mapping.epg_name:
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
# Remove AIM Subnet(s) for each removed Neutron subnet.
for subnet in subnets:
gw_ip = self._ip_for_subnet(subnet, port['fixed_ips'])
sn = self._map_subnet(subnet, gw_ip, bd)
self.aim.delete(aim_ctx, sn)
# SVI network with auto l3out.
elif network_db.aim_mapping.l3out_name:
epg = self._get_network_l3out_ext_net(network_db.aim_mapping)
# Find remaining routers with interfaces to this network.
router_ids = [r[0] for r in
@ -1271,7 +1392,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# If network is no longer connected to this router, stop
# network's EPG from providing/consuming this router's
# Contract.
if router_id not in router_ids:
if router_id not in router_ids and epg:
epg = self.aim.get(aim_ctx, epg)
contracts = [name for name in epg.consumed_contract_names
@ -1331,7 +1452,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self._cleanup_default_vrf(aim_ctx, old_vrf)
# If external-gateway is set, handle external-connectivity changes.
if router_db.gw_port_id:
# External network is not supproted for SVI network for now.
if router_db.gw_port_id and not network_db.aim_extension_mapping.svi:
net = self.plugin.get_network(context,
router_db.gw_port.network_id)
# If this was the last interface for this VRF for this
@ -2015,38 +2137,61 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# NOTE: Must only be called for networks that are not yet
# attached to any router.
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
if not network_db.aim_extension_mapping.svi:
bd = self._get_network_bd(network_db.aim_mapping)
epg = self._get_network_epg(network_db.aim_mapping)
tenant_name = bd.tenant_name
else:
l3out = self._get_network_l3out(network_db.aim_mapping)
tenant_name = l3out.tenant_name
if (new_vrf.tenant_name != COMMON_TENANT_NAME and
bd.tenant_name != new_vrf.tenant_name):
tenant_name != new_vrf.tenant_name):
# Move BD and EPG to new VRF's Tenant, set VRF, and make
# sure routing is enabled.
LOG.debug("Moving network from tenant %(old)s to tenant %(new)s",
{'old': bd.tenant_name, 'new': new_vrf.tenant_name})
{'old': tenant_name, 'new': new_vrf.tenant_name})
if not network_db.aim_extension_mapping.svi:
bd = self.aim.get(aim_ctx, bd)
self.aim.delete(aim_ctx, bd)
bd.tenant_name = new_vrf.tenant_name
bd.enable_routing = True
bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, bd)
self._set_network_bd(network_db.aim_mapping, bd)
bd = self.aim.get(aim_ctx, bd)
self.aim.delete(aim_ctx, bd)
bd.tenant_name = new_vrf.tenant_name
bd.enable_routing = True
bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, bd)
self._set_network_bd(network_db.aim_mapping, bd)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
# ensure app profile exists in destination tenant
ap = aim_resource.ApplicationProfile(
tenant_name=new_vrf.tenant_name, name=self.ap_name)
if not self.aim.get(aim_ctx, ap):
self.aim.create(aim_ctx, ap)
epg.tenant_name = new_vrf.tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
# ensure app profile exists in destination tenant
ap = aim_resource.ApplicationProfile(
tenant_name=new_vrf.tenant_name, name=self.ap_name)
if not self.aim.get(aim_ctx, ap):
self.aim.create(aim_ctx, ap)
epg.tenant_name = new_vrf.tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
else:
old_l3out = self.aim.get(aim_ctx, l3out)
l3out = copy.copy(old_l3out)
l3out.tenant_name = new_vrf.tenant_name
l3out.vrf_name = new_vrf.name
l3out = self.aim.create(aim_ctx, l3out)
self._set_network_l3out(network_db.aim_mapping,
l3out)
for old_child in self.aim.get_subtree(aim_ctx, old_l3out):
new_child = copy.copy(old_child)
new_child.tenant_name = new_vrf.tenant_name
new_child = self.aim.create(aim_ctx, new_child)
self.aim.delete(aim_ctx, old_child)
self.aim.delete(aim_ctx, old_l3out)
else:
# Just set VRF and enable routing.
bd = self.aim.update(aim_ctx, bd, enable_routing=True,
vrf_name=new_vrf.name)
if not network_db.aim_extension_mapping.svi:
bd = self.aim.update(aim_ctx, bd, enable_routing=True,
vrf_name=new_vrf.name)
else:
l3out = self.aim.update(aim_ctx, l3out,
vrf_name=new_vrf.name)
self._set_network_vrf(network_db.aim_mapping, new_vrf)
@ -2055,7 +2200,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# Tenants have changed.
nets_to_notify.add(network_db.id)
return bd, epg
if not network_db.aim_extension_mapping.svi:
return bd, epg
else:
ext_net = self._get_network_l3out_ext_net(network_db.aim_mapping)
return l3out, ext_net
def _dissassociate_network_from_vrf(self, aim_ctx, network_db, old_vrf,
nets_to_notify):
@ -2078,26 +2227,47 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
LOG.debug("Moving network from tenant %(old)s to tenant %(new)s",
{'old': old_vrf.tenant_name, 'new': new_tenant_name})
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.get(aim_ctx, bd)
self.aim.delete(aim_ctx, bd)
bd.tenant_name = new_tenant_name
bd.enable_routing = False
bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, bd)
self._set_network_bd(network_db.aim_mapping, bd)
if not network_db.aim_extension_mapping.svi:
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.get(aim_ctx, bd)
self.aim.delete(aim_ctx, bd)
bd.tenant_name = new_tenant_name
bd.enable_routing = False
bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, bd)
self._set_network_bd(network_db.aim_mapping, bd)
epg = self._get_network_epg(network_db.aim_mapping)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
epg.tenant_name = new_tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
epg = self._get_network_epg(network_db.aim_mapping)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
epg.tenant_name = new_tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
else:
l3out = self._get_network_l3out(network_db.aim_mapping)
old_l3out = self.aim.get(aim_ctx, l3out)
l3out = copy.copy(old_l3out)
l3out.tenant_name = new_tenant_name
l3out.vrf_name = new_vrf.name
l3out = self.aim.create(aim_ctx, l3out)
self._set_network_l3out(network_db.aim_mapping,
l3out)
for old_child in self.aim.get_subtree(aim_ctx, old_l3out):
new_child = copy.copy(old_child)
new_child.tenant_name = new_tenant_name
new_child = self.aim.create(aim_ctx, new_child)
self.aim.delete(aim_ctx, old_child)
self.aim.delete(aim_ctx, old_l3out)
else:
# Just set unrouted VRF and disable routing.
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.update(aim_ctx, bd, enable_routing=False,
vrf_name=new_vrf.name)
if not network_db.aim_extension_mapping.svi:
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.update(aim_ctx, bd, enable_routing=False,
vrf_name=new_vrf.name)
else:
l3out = self._get_network_l3out(network_db.aim_mapping)
l3out = self.aim.update(aim_ctx, l3out,
vrf_name=new_vrf.name)
self._set_network_vrf(network_db.aim_mapping, new_vrf)
@ -2126,32 +2296,55 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
{'net': network_db.id,
'old': old_vrf.tenant_name,
'new': new_vrf.tenant_name})
if network_db.aim_mapping.epg_name:
bd = self._get_network_bd(network_db.aim_mapping)
old_bd = self.aim.get(aim_ctx, bd)
new_bd = copy.copy(old_bd)
new_bd.tenant_name = new_vrf.tenant_name
new_bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, new_bd)
self._set_network_bd(network_db.aim_mapping, bd)
for subnet in self.aim.find(
aim_ctx, aim_resource.Subnet,
tenant_name=old_bd.tenant_name,
bd_name=old_bd.name):
self.aim.delete(aim_ctx, subnet)
subnet.tenant_name = bd.tenant_name
subnet = self.aim.create(aim_ctx, subnet)
self.aim.delete(aim_ctx, old_bd)
bd = self._get_network_bd(network_db.aim_mapping)
old_bd = self.aim.get(aim_ctx, bd)
new_bd = copy.copy(old_bd)
new_bd.tenant_name = new_vrf.tenant_name
new_bd.vrf_name = new_vrf.name
bd = self.aim.create(aim_ctx, new_bd)
self._set_network_bd(network_db.aim_mapping, bd)
for subnet in self.aim.find(
aim_ctx, aim_resource.Subnet,
tenant_name=old_bd.tenant_name, bd_name=old_bd.name):
self.aim.delete(aim_ctx, subnet)
subnet.tenant_name = bd.tenant_name
subnet = self.aim.create(aim_ctx, subnet)
self.aim.delete(aim_ctx, old_bd)
epg = self._get_network_epg(network_db.aim_mapping)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
epg.tenant_name = new_vrf.tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
epg = self._get_network_epg(network_db.aim_mapping)
epg = self.aim.get(aim_ctx, epg)
self.aim.delete(aim_ctx, epg)
epg.tenant_name = new_vrf.tenant_name
epg = self.aim.create(aim_ctx, epg)
self._set_network_epg(network_db.aim_mapping, epg)
# SVI network with auto l3out.
elif network_db.aim_mapping.l3out_name:
l3out = self._get_network_l3out(network_db.aim_mapping)
old_l3out = self.aim.get(aim_ctx, l3out)
l3out = copy.copy(old_l3out)
l3out.tenant_name = new_vrf.tenant_name
l3out.vrf_name = new_vrf.name
l3out = self.aim.create(aim_ctx, l3out)
self._set_network_l3out(network_db.aim_mapping,
l3out)
for old_child in self.aim.get_subtree(aim_ctx, old_l3out):
new_child = copy.copy(old_child)
new_child.tenant_name = new_vrf.tenant_name
new_child = self.aim.create(aim_ctx, new_child)
self.aim.delete(aim_ctx, old_child)
self.aim.delete(aim_ctx, old_l3out)
else:
# New VRF is in same Tenant, so just set BD's VRF.
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.update(aim_ctx, bd, vrf_name=new_vrf.name)
if network_db.aim_mapping.epg_name:
# New VRF is in same Tenant, so just set BD's VRF.
bd = self._get_network_bd(network_db.aim_mapping)
bd = self.aim.update(aim_ctx, bd, vrf_name=new_vrf.name)
elif network_db.aim_mapping.l3out_name:
# New VRF is in same Tenant, so just set l3out's VRF.
l3out = self._get_network_l3out(network_db.aim_mapping)
l3out = self.aim.update(aim_ctx, l3out,
vrf_name=new_vrf.name)
self._set_network_vrf(network_db.aim_mapping, new_vrf)
@ -2416,6 +2609,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
def _is_external(self, network):
return network.get('router:external')
def _is_svi(self, network):
return network.get(cisco_apic.SVI)
def _nat_type_to_strategy(self, nat_type):
ns_cls = nat_strategy.DistributedNatStrategy
if nat_type == '':
@ -2427,9 +2623,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ns.common_scope = self.apic_system_id
return ns
def _get_aim_nat_strategy(self, network):
if not self._is_external(network):
return None, None, None
def _get_aim_external_stuff(self, network):
ext_net_dn = (network.get(cisco_apic.DIST_NAMES, {})
.get(cisco_apic.EXTERNAL_NETWORK))
if not ext_net_dn:
@ -2440,19 +2634,28 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
tenant_name=aim_ext_net.tenant_name, name=aim_ext_net.l3out_name)
return aim_l3out, aim_ext_net, self._nat_type_to_strategy(nat_type)
def _get_aim_nat_strategy(self, network):
if not self._is_external(network):
return None, None, None
return self._get_aim_external_stuff(network)
def _get_aim_external_stuff_db(self, session, network_db):
extn_db = extension_db.ExtensionDbMixin()
extn_info = extn_db.get_network_extn_db(session, network_db.id)
if extn_info and cisco_apic.EXTERNAL_NETWORK in extn_info:
dn = extn_info[cisco_apic.EXTERNAL_NETWORK]
a_ext_net = aim_resource.ExternalNetwork.from_dn(dn)
a_l3out = aim_resource.L3Outside(
tenant_name=a_ext_net.tenant_name,
name=a_ext_net.l3out_name)
ns = self._nat_type_to_strategy(
extn_info.get(cisco_apic.NAT_TYPE))
return a_l3out, a_ext_net, ns
return None, None, None
def _get_aim_nat_strategy_db(self, session, network_db):
if network_db.external is not None:
extn_db = extension_db.ExtensionDbMixin()
extn_info = extn_db.get_network_extn_db(session, network_db.id)
if extn_info and cisco_apic.EXTERNAL_NETWORK in extn_info:
dn = extn_info[cisco_apic.EXTERNAL_NETWORK]
a_ext_net = aim_resource.ExternalNetwork.from_dn(dn)
a_l3out = aim_resource.L3Outside(
tenant_name=a_ext_net.tenant_name,
name=a_ext_net.l3out_name)
ns = self._nat_type_to_strategy(
extn_info[cisco_apic.NAT_TYPE])
return a_l3out, a_ext_net, ns
return self._get_aim_external_stuff_db(session, network_db)
return None, None, None
def _subnet_to_gw_ip_mask(self, subnet):
@ -2764,6 +2967,131 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self._is_supported_non_opflex_type(
bound_segment[api.NETWORK_TYPE]))
def _update_static_path_for_svi(self, session, port_context, network,
segment, old_path=None, new_path=None):
if new_path and not segment:
return
if segment:
if segment.get(api.NETWORK_TYPE) in [pconst.TYPE_VLAN]:
seg = 'vlan-%s' % segment[api.SEGMENTATION_ID]
else:
LOG.debug('Unsupported segmentation type for static path '
'binding: %s',
segment.get(api.NETWORK_TYPE))
return
path = new_path
if not path:
path = old_path
nodes = []
node_paths = []
is_vpc = False
match = self.port_desc_re.match(path)
if match:
pod_id, switch, module, port = match.group(1, 2, 3, 4)
nodes.append(switch)
node_paths.append(ACI_CHASSIS_DESCR_STRING % (pod_id, switch))
else:
match = self.vpcport_desc_re.match(path)
if match:
pod_id, switch1, switch2, bundle = match.group(1, 2, 3, 4)
nodes.append(switch1)
nodes.append(switch2)
node_paths.append(ACI_CHASSIS_DESCR_STRING % (pod_id,
switch1))
node_paths.append(ACI_CHASSIS_DESCR_STRING % (pod_id,
switch2))
is_vpc = True
else:
LOG.error('Unsupported static path format: %s', path)
return
aim_ctx = aim_context.AimContext(db_session=session)
l3out, ext_net, _ = self._get_aim_external_stuff(network)
if new_path:
aim_l3out_np = aim_resource.L3OutNodeProfile(
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
name=L3OUT_NODE_PROFILE_NAME)
aim_l3out_np_db = self.aim.get(aim_ctx, aim_l3out_np)
if not aim_l3out_np_db:
self.aim.create(aim_ctx, aim_l3out_np)
for idx, node_path in enumerate(node_paths):
aim_l3out_node = aim_resource.L3OutNode(
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
node_profile_name=L3OUT_NODE_PROFILE_NAME,
node_path=node_path, router_id=APIC_ROUTER_IDS[idx],
router_id_loopback=False)
aim_l3out_node_db = self.aim.get(aim_ctx, aim_l3out_node)
if not aim_l3out_node_db:
self.aim.create(aim_ctx, aim_l3out_node)
aim_l3out_ip = aim_resource.L3OutInterfaceProfile(
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
node_profile_name=L3OUT_NODE_PROFILE_NAME,
name=L3OUT_IF_PROFILE_NAME)
aim_l3out_ip_db = self.aim.get(aim_ctx, aim_l3out_ip)
if not aim_l3out_ip_db:
self.aim.create(aim_ctx, aim_l3out_ip)
subnet = (session.query(models_v2.Subnet)
.filter(models_v2.Subnet.id ==
network['subnets'][0]).one())
mask = subnet['cidr'].split('/')[1]
plugin_context = port_context._plugin_context
primary_ips = []
for node in nodes:
filters = {'network_id': [network['id']],
'name': ['apic-svi-port:node-%s' % node]}
svi_ports = self.plugin.get_ports(plugin_context, filters)
if svi_ports and svi_ports[0]['fixed_ips']:
ip = svi_ports[0]['fixed_ips'][0]['ip_address']
primary_ips.append(ip + '/' + mask)
else:
attrs = {'device_id': '',
'device_owner': DEVICE_OWNER_SVI_PORT,
'tenant_id': network['tenant_id'],
'name': 'apic-svi-port:node-%s' % node,
'network_id': network['id'],
'mac_address': n_constants.ATTR_NOT_SPECIFIED,
'fixed_ips': [{'subnet_id':
network['subnets'][0]}],
'admin_state_up': False}
port = self.plugin.create_port(plugin_context,
{'port': attrs})
if port and port['fixed_ips']:
ip = port['fixed_ips'][0]['ip_address']
primary_ips.append(ip + '/' + mask)
else:
LOG.error('cannot allocate a port for the SVI primary'
' addr')
return
secondary_ip = subnet['gateway_ip'] + '/' + mask
aim_l3out_if = aim_resource.L3OutInterface(
tenant_name=l3out.tenant_name,
l3out_name=l3out.name,
node_profile_name=L3OUT_NODE_PROFILE_NAME,
interface_profile_name=L3OUT_IF_PROFILE_NAME,
interface_path=path, encap=seg,
primary_addr_a=primary_ips[0],
secondary_addr_a_list=[{'addr': secondary_ip}],
primary_addr_b=primary_ips[1] if is_vpc else '',
secondary_addr_b_list=[{'addr':
secondary_ip}] if is_vpc else [])
aim_l3out_if_db = self.aim.get(aim_ctx, aim_l3out_if)
if not aim_l3out_if_db:
self.aim.create(aim_ctx, aim_l3out_if)
else:
aim_l3out_if = aim_resource.L3OutInterface(
tenant_name=l3out.tenant_name,
l3out_name=l3out.name,
node_profile_name=L3OUT_NODE_PROFILE_NAME,
interface_profile_name=L3OUT_IF_PROFILE_NAME,
interface_path=path)
self.aim.delete(aim_ctx, aim_l3out_if)
def _update_static_path_for_network(self, session, network, segment,
old_path=None, new_path=None):
if new_path and not segment:
@ -2833,9 +3161,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
{'host': host, 'interface': interface})
continue
host_link = host_link[0].path
self._update_static_path_for_network(
session, port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
if self._is_svi(port_context.network.current):
self._update_static_path_for_svi(
session, port_context,
port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
else:
self._update_static_path_for_network(
session, port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
static_path_updated = True
# acting as a fallback also
@ -2847,9 +3181,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
host)
return
host_link = host_link[0].path
self._update_static_path_for_network(
session, port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
if self._is_svi(port_context.network.current):
self._update_static_path_for_svi(
session, port_context,
port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
else:
self._update_static_path_for_network(
session, port_context.network.current, segment,
**{'old_path' if remove else 'new_path': host_link})
def _release_dynamic_segment(self, port_context, use_original=False):
top = (port_context.original_top_bound_segment if use_original
@ -2894,6 +3234,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return False
def _associate_domain(self, port_context, is_vmm=True):
if self._is_svi(port_context.network.current):
return
port = port_context.current
session = port_context._plugin_context.session
aim_ctx = aim_context.AimContext(session)
@ -2969,6 +3311,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# public interface for aim_mapping GBP policy driver also
def disassociate_domain(self, port_context, use_original=False):
if self._is_svi(port_context.network.current):
return
btm = (port_context.original_bottom_bound_segment if use_original
else port_context.bottom_bound_segment)
if not btm:

View File

@ -240,3 +240,10 @@ def patched_get_locked_port_and_binding(context, port_id):
ml2_db.get_locked_port_and_binding = patched_get_locked_port_and_binding
from neutron.db import db_base_plugin_v2
DEVICE_OWNER_SVI_PORT = 'apic:svi'
db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS.append(DEVICE_OWNER_SVI_PORT)