Files
group-based-policy/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py
Sumit Naiksatam 6dbbafdeb4 Freezing DB table definitions for data migrations
A few data migrations were added in the following module:
gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py

which reference tables defined in the corresponding DB models. However,
this opens up the possibility that those definitions might change via
migration scripts that get added after the data migration scripts in which
they are referenced. This leads to the situation that when the data
migration is run, sqlalchemy loads the newer table definition, but the DB
itself does not have the changes (defined in the schema migration that has not
yet run).

To get around this, instead of referencing the tables from the DB models,
the table defintion must be added to the data migration script itself and
referenced. This freezes the table definition that is expected by the data
migration script and ensures that the data migration script will work.

This patch updates the aforementioned data migration script to add the relevant
table definitions (based on the commit at the time those migrations were added).

It also eliminates the use of DB mixin methods to retrieve resources (which
can change over time and might include new attributes not available to this
migration).

A sanity UT is added for the Security Groups migration which did not have
UT coverage. The other data migrations are already being tested in UTs, but
to ensure that the older models are loaded (and the newer model defintions are not
referenced) some more UTs can be added as follow up.

Going forward, each data migration script should be contained in the alembic
migration script along with the table definitions that it uses. For reference
see how its done here:
625de54de3/neutron/db/migration/alembic_migrations/versions/newton/contract/3b935b28e7a0_migrate_to_pluggable_ipam.py

Change-Id: If7b74160a36b9946a7ab8de8c4c19fed2a87d850
2018-02-23 17:22:44 +00:00

389 lines
17 KiB
Python

# Copyright (c) 2017 Cisco Systems Inc.
# All Rights Reserved.
#
# 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.
# Note: This module should be treated as legacy and should not be extended to
# add any new data migrations. New data migrations should be added
# directly to the alembic migration script along with the table definitions
# that are being referenced.
# For reference see how its done here:
# https://github.com/openstack/neutron/blob/
# 625de54de3936b0da8760c3da76d2d315d05f94e/neutron/db/migration/
# alembic_migrations/versions/newton/contract/
# 3b935b28e7a0_migrate_to_pluggable_ipam.py
import netaddr
from aim.aim_lib import nat_strategy
from aim import aim_manager
from aim.api import resource as aim_resource
from aim import context as aim_context
from aim import utils as aim_utils
from alembic import util as alembic_util
from neutron.db.models import address_scope as as_db
from neutron.db.models import securitygroup as sg_models
from neutron.db.migration.cli import * # noqa
from neutron.db import models_v2
from neutron.db import segments_db # noqa
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy.orm import lazyload
from gbpservice.neutron.extensions import cisco_apic as ext
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
# The following definitions have been taken from commit:
# 9b4b7276ad8a0f181c9be12ba5a0192432aa5027
# and is frozen for the data migration script that was included
# in this module. It should not be changed in this module.
NetworkExtensionDb = sa.Table(
'apic_aim_network_extensions', sa.MetaData(),
sa.Column('network_id', sa.String(36), nullable=False),
sa.Column('external_network_dn', sa.String(1024)),
sa.Column('nat_type', sa.Enum('distributed', 'edge', '')))
NetworkExtensionCidrDb = sa.Table(
'apic_aim_network_external_cidrs', sa.MetaData(),
sa.Column('network_id', sa.String(36), nullable=False),
sa.Column('cidr', sa.String(64), nullable=False))
AddressScopeMapping = sa.Table(
'apic_aim_address_scope_mappings', sa.MetaData(),
sa.Column('scope_id', sa.String(36)),
sa.Column('vrf_name', sa.String(64)),
sa.Column('vrf_tenant_name', sa.String(64)),
sa.Column('vrf_owned', sa.Boolean, nullable=False))
# The following definition has been taken from commit:
# f8b41855acbbb7e59a0bab439445c198fc6aa146
# and is frozen for the data migration script that was included
# in this module. It should not be changed in this module.
NetworkMapping = sa.Table(
'apic_aim_network_mappings', sa.MetaData(),
sa.Column('network_id', sa.String(36), nullable=False),
sa.Column('bd_name', sa.String(64), nullable=True),
sa.Column('bd_tenant_name', sa.String(64), nullable=True),
sa.Column('epg_name', sa.String(64), nullable=True),
sa.Column('epg_tenant_name', sa.String(64), nullable=True),
sa.Column('epg_app_profile_name', sa.String(64), nullable=True),
sa.Column('vrf_name', sa.String(64), nullable=True),
sa.Column('vrf_tenant_name', sa.String(64), nullable=True))
class DefunctAddressScopeExtensionDb(model_base.BASEV2):
# REVISIT: This DB model class is used only for the
# apic_aim_persist data migration, after which this table is
# dropped.
__tablename__ = 'apic_aim_addr_scope_extensions'
address_scope_id = sa.Column(
sa.String(36), sa.ForeignKey('address_scopes.id', ondelete="CASCADE"),
primary_key=True)
vrf_dn = sa.Column(sa.String(1024))
def _add_address_scope_mapping(session, scope_id, vrf, vrf_owned=True):
session.execute(AddressScopeMapping.insert().values(
scope_id=scope_id, vrf_name=vrf.name, vrf_tenant_name=vrf.tenant_name,
vrf_owned=vrf_owned))
def _add_network_mapping(session, network_id, bd, epg, vrf, ext_net=None):
if not ext_net:
session.execute(NetworkMapping.insert().values(
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 do_apic_aim_persist_migration(session):
alembic_util.msg(
"Starting data migration for apic_aim mechanism driver persistence.")
aim = aim_manager.AimManager()
aim_ctx = aim_context.AimContext(session)
mapper = apic_mapper.APICNameMapper()
with session.begin(subtransactions=True):
# Migrate address scopes.
scope_dbs = (session.query(as_db.AddressScope)
.options(lazyload('*')).all())
for scope_db in scope_dbs:
alembic_util.msg("Migrating address scope: %s" % scope_db)
vrf = None
ext_db = (session.query(DefunctAddressScopeExtensionDb).
filter_by(address_scope_id=scope_db.id).
one_or_none())
if ext_db:
# It has a pre-existing VRF.
vrf = aim_resource.VRF.from_dn(ext_db.vrf_dn)
# REVISIT: Get VRF to verify it exists?
vrf_owned = False
if not vrf:
# It does not have a pre-existing VRF.
aname = mapper.address_scope(session, scope_db.id)
vrfs = aim.find(
aim_ctx, aim_resource.VRF,
name=aname)
if vrfs:
vrf = vrfs[0]
vrf_owned = True
if vrf:
_add_address_scope_mapping(
session, scope_db.id, vrf, vrf_owned)
else:
alembic_util.warn(
"No AIM VRF found for address scope: %s" % scope_db)
# Migrate networks.
net_dbs = (session.query(models_v2.Network)
.options(lazyload('*')).all())
for net_db in net_dbs:
alembic_util.msg("Migrating network: %s" % net_db)
bd = None
epg = None
vrf = None
ext_db = (session.query(NetworkExtensionDb).
filter_by(network_id=net_db.id).
one_or_none())
if ext_db and ext_db.external_network_dn:
# Its a managed external network.
ext_net = aim_resource.ExternalNetwork.from_dn(
ext_db.external_network_dn)
# REVISIT: Get ExternalNetwork to verify it exists?
l3out = aim_resource.L3Outside(
tenant_name=ext_net.tenant_name,
name=ext_net.l3out_name)
if ext_db.nat_type == '':
ns_cls = nat_strategy.NoNatStrategy
elif ext_db.nat_type == 'edge':
ns_cls = nat_strategy.EdgeNatStrategy
else:
ns_cls = nat_strategy.DistributedNatStrategy
ns = ns_cls(aim)
ns.app_profile_name = 'OpenStack'
for resource in ns.get_l3outside_resources(aim_ctx, l3out):
if isinstance(resource, aim_resource.BridgeDomain):
bd = resource
elif isinstance(resource, aim_resource.EndpointGroup):
epg = resource
elif isinstance(resource, aim_resource.VRF):
vrf = resource
if not bd:
# It must be a normal network.
aname = mapper.network(session, net_db.id)
bds = aim.find(
aim_ctx, aim_resource.BridgeDomain,
name=aname)
if bds:
bd = bds[0]
epgs = aim.find(
aim_ctx, aim_resource.EndpointGroup,
name=aname)
if epgs:
epg = epgs[0]
if bd:
vrfs = (
aim.find(
aim_ctx, aim_resource.VRF,
tenant_name=bd.tenant_name,
name=bd.vrf_name) or
aim.find(
aim_ctx, aim_resource.VRF,
tenant_name='common',
name=bd.vrf_name))
if vrfs:
vrf = vrfs[0]
if bd and epg and vrf:
_add_network_mapping(session, net_db.id, bd, epg, vrf)
elif not net_db.external:
alembic_util.warn(
"AIM BD, EPG or VRF not found for network: %s" % net_db)
alembic_util.msg(
"Finished data migration for apic_aim mechanism driver persistence.")
def _get_network_extn_db(session, network_id):
netres = (session.query(NetworkExtensionDb).filter_by(
network_id=network_id).first())
if netres:
_, ext_net_dn, nat_type = netres
db_cidrs = (session.query(NetworkExtensionCidrDb).filter_by(
network_id=network_id).all())
result = {}
if ext_net_dn is not None:
result[ext.EXTERNAL_NETWORK] = ext_net_dn
if nat_type is not None:
result[ext.NAT_TYPE] = nat_type
if result.get(ext.EXTERNAL_NETWORK):
result[ext.EXTERNAL_CIDRS] = [c for _, c in db_cidrs]
return result
def do_ap_name_change(session, conf=None):
alembic_util.msg("Starting data migration for apic_aim ap name change.")
cfg = conf or CONF
aim = aim_manager.AimManager()
aim_ctx = aim_context.AimContext(session)
system_id = cfg.apic_system_id
alembic_util.msg("APIC System ID: %s" % system_id)
with session.begin(subtransactions=True):
net_dbs = session.query(models_v2.Network).options(lazyload('*')).all()
for net_db in net_dbs:
ext_db = _get_network_extn_db(session, net_db.id)
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(
ext_db[ext.EXTERNAL_NETWORK])
ext_net = aim.get(aim_ctx, ext_net)
l3out = aim_resource.L3Outside(tenant_name=ext_net.tenant_name,
name=ext_net.l3out_name)
if ext_db[ext.NAT_TYPE] == '':
ns_cls = nat_strategy.NoNatStrategy
elif ext_db[ext.NAT_TYPE] == 'edge':
ns_cls = nat_strategy.EdgeNatStrategy
else:
ns_cls = nat_strategy.DistributedNatStrategy
clone_ext_nets = {}
ns = ns_cls(aim)
ns.app_profile_name = 'OpenStack'
ns.common_scope = None
# Start Cleanup
if not isinstance(ns, nat_strategy.NoNatStrategy):
l3out_clones = ns.db.get_clones(aim_ctx, l3out)
# Retrieve External Networks
for l3out_clone in l3out_clones:
for extc in aim.find(
aim_ctx, aim_resource.ExternalNetwork,
tenant_name=l3out_clone[0],
l3out_name=l3out_clone[1]):
clone_ext_nets[(l3out.tenant_name,
l3out.name,
extc.name)] = extc
vrfs = ns.read_vrfs(aim_ctx, ext_net)
session.execute(NetworkMapping.delete().where(
NetworkMapping.c.network_id == net_db.id))
for vrf in vrfs:
ns.disconnect_vrf(aim_ctx, ext_net, vrf)
ns.delete_external_network(aim_ctx, ext_net)
ns.delete_l3outside(aim_ctx, l3out)
# Recreate
ns.common_scope = system_id
ns.create_l3outside(aim_ctx, l3out)
ns.create_external_network(aim_ctx, ext_net)
ns.update_external_cidrs(aim_ctx, ext_net,
ext_db[ext.EXTERNAL_CIDRS])
for subnet in net_db.subnets:
aim_subnet = aim_resource.Subnet.to_gw_ip_mask(
subnet.gateway_ip, int(subnet.cidr.split('/')[1]))
ns.create_subnet(aim_ctx, l3out, aim_subnet)
for resource in ns.get_l3outside_resources(aim_ctx, l3out):
if isinstance(resource, aim_resource.BridgeDomain):
bd = resource
elif isinstance(resource, aim_resource.EndpointGroup):
epg = resource
elif isinstance(resource, aim_resource.VRF):
vrf = resource
_add_network_mapping(session, net_db.id, bd, epg, vrf)
eid = (ext_net.tenant_name, ext_net.l3out_name, ext_net.name)
for vrf in vrfs:
if eid in clone_ext_nets:
ext_net.provided_contract_names = clone_ext_nets[
eid].provided_contract_names
ext_net.consumed_contract_names = clone_ext_nets[
eid].consumed_contract_names
ns.connect_vrf(aim_ctx, ext_net, vrf)
def do_apic_aim_security_group_migration(session):
alembic_util.msg(
"Starting data migration for SGs and its rules.")
aim = aim_manager.AimManager()
aim_ctx = aim_context.AimContext(session)
mapper = apic_mapper.APICNameMapper()
with session.begin(subtransactions=True):
# Migrate SG.
sg_dbs = (session.query(sg_models.SecurityGroup).
options(lazyload('*')).all())
for sg_db in sg_dbs:
alembic_util.msg("Migrating SG: %s" % sg_db)
tenant_aname = mapper.project(session, sg_db['tenant_id'])
sg_aim = aim_resource.SecurityGroup(
tenant_name=tenant_aname, name=sg_db['id'],
display_name=aim_utils.sanitize_display_name(sg_db['name']))
aim.create(aim_ctx, sg_aim, overwrite=True)
# Always create this default subject
sg_subject = aim_resource.SecurityGroupSubject(
tenant_name=tenant_aname,
security_group_name=sg_db['id'], name='default')
aim.create(aim_ctx, sg_subject, overwrite=True)
# Migrate SG rules.
sg_rule_dbs = (session.query(sg_models.SecurityGroupRule).
options(lazyload('*')).all())
for sg_rule_db in sg_rule_dbs:
tenant_aname = mapper.project(session, sg_rule_db['tenant_id'])
if sg_rule_db.get('remote_group_id'):
ip_version = 0
if sg_rule_db['ethertype'] == 'IPv4':
ip_version = 4
elif sg_rule_db['ethertype'] == 'IPv6':
ip_version = 6
remote_ips = []
sg_ports = (session.query(models_v2.Port).
join(sg_models.SecurityGroupPortBinding,
sg_models.SecurityGroupPortBinding.port_id ==
models_v2.Port.id).
filter(sg_models.SecurityGroupPortBinding.
security_group_id ==
sg_rule_db['remote_group_id']).
options(lazyload('*')).all())
for sg_port in sg_ports:
for fixed_ip in sg_port['fixed_ips']:
if ip_version == netaddr.IPAddress(
fixed_ip['ip_address']).version:
remote_ips.append(fixed_ip['ip_address'])
else:
remote_ips = ([sg_rule_db['remote_ip_prefix']]
if sg_rule_db['remote_ip_prefix'] else '')
sg_rule_aim = aim_resource.SecurityGroupRule(
tenant_name=tenant_aname,
security_group_name=sg_rule_db['security_group_id'],
security_group_subject_name='default',
name=sg_rule_db['id'],
direction=sg_rule_db['direction'],
ethertype=sg_rule_db['ethertype'].lower(),
ip_protocol=(sg_rule_db['protocol'] if sg_rule_db['protocol']
else 'unspecified'),
remote_ips=remote_ips,
from_port=(sg_rule_db['port_range_min']
if sg_rule_db['port_range_min'] else 'unspecified'),
to_port=(sg_rule_db['port_range_max']
if sg_rule_db['port_range_max'] else 'unspecified'))
aim.create(aim_ctx, sg_rule_aim, overwrite=True)
alembic_util.msg(
"Finished data migration for SGs and its rules.")