From f8b41855acbbb7e59a0bab439445c198fc6aa146 Mon Sep 17 00:00:00 2001 From: Ivar Lazzaro Date: Tue, 24 Oct 2017 18:32:18 -0700 Subject: [PATCH] Scope common App Profile with the system ID For compatibility on multi OS instances per APIC Change-Id: Iaf93f24466889a7f2a1e84605ca59b6e9ad5ec56 --- .../alembic_migrations/versions/HEAD | 2 +- .../facc1ababba1_change_common_ap_name.py | 46 +++++++++ gbpservice/neutron/db/migration/cli.py | 7 ++ .../drivers/apic_aim/data_migrations.py | 81 ++++++++++++++++ .../drivers/apic_aim/mechanism_driver.py | 11 +-- .../drivers/cisco/apic/aim_mapping.py | 3 + .../unit/plugins/ml2plus/test_apic_aim.py | 96 +++++++++++++++++++ .../grouppolicy/test_aim_mapping_driver.py | 7 +- 8 files changed, 240 insertions(+), 13 deletions(-) create mode 100644 gbpservice/neutron/db/migration/alembic_migrations/versions/facc1ababba1_change_common_ap_name.py diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD index cefabfaec..220e8fb2d 100644 --- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -d978a7a73785 +facc1ababba1 \ No newline at end of file diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/facc1ababba1_change_common_ap_name.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/facc1ababba1_change_common_ap_name.py new file mode 100644 index 000000000..85c8bfd96 --- /dev/null +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/facc1ababba1_change_common_ap_name.py @@ -0,0 +1,46 @@ +# 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. +# + +"""change common ap name + +Revision ID: facc1ababba1 +Revises: d978a7a73785 +Create Date: 2017-05-15 00:00:00.000000 + +""" + +# revision identifiers, used by Alembic. +revision = 'facc1ababba1' +down_revision = 'd978a7a73785' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # See if AIM is being used, and if so, migrate data. + bind = op.get_bind() + insp = sa.engine.reflection.Inspector.from_engine(bind) + if 'aim_tenants' in insp.get_table_names(): + # Note - this cannot be imported unless we know the + # apic_aim mechanism driver is deployed, since the AIM + # library may not be installed. + from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import ( + data_migrations) + + session = sa.orm.Session(bind=bind, autocommit=True) + data_migrations.do_ap_name_change(session) + + +def downgrade(): + pass diff --git a/gbpservice/neutron/db/migration/cli.py b/gbpservice/neutron/db/migration/cli.py index 36900f462..12a5b7310 100644 --- a/gbpservice/neutron/db/migration/cli.py +++ b/gbpservice/neutron/db/migration/cli.py @@ -13,6 +13,13 @@ from neutron.db.migration.cli import * # noqa +global_opts = [ + cfg.StrOpt('apic_system_id', + help="Prefix for APIC domain/names/profiles created"), +] +CONF.register_cli_opts(global_opts) + + def main(): config = alembic_config.Config( os.path.join(os.path.dirname(__file__), 'alembic.ini')) diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py index ce179b1b9..2ffb5b2df 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py @@ -19,10 +19,13 @@ from aim.api import resource as aim_resource from aim import context as aim_context from alembic import util as alembic_util from neutron.db.models import address_scope as as_db +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 gbpservice.neutron.extensions import cisco_apic as ext from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import db from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db @@ -149,3 +152,81 @@ def do_apic_aim_persist_migration(session): alembic_util.msg( "Finished data migration for apic_aim mechanism driver persistence.") + + +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) + ext_mixin = extension_db.ExtensionDbMixin() + db_mixin = db.DbMixin() + with session.begin(subtransactions=True): + net_dbs = session.query(models_v2.Network).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]: + 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.query(db.NetworkMapping).filter( + db.NetworkMapping.network_id == net_db.id).delete() + 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 + db_mixin._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) diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py index 70d5e2aa8..53159b5e8 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py @@ -2248,16 +2248,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver, mapping = self._get_network_mapping(session, network['id']) return mapping and self._get_network_vrf(mapping) - # DB Configuration callbacks - def _set_enable_metadata_opt(self, new_conf): - self.enable_metadata_opt = new_conf['value'] - - def _set_enable_dhcp_opt(self, new_conf): - self.enable_dhcp_opt = new_conf['value'] - - def _set_ap_name(self, new_conf): - self.ap_name = new_conf['value'] - def get_aim_domains(self, aim_ctx): vmms = [x.name for x in self.aim.find(aim_ctx, aim_resource.VMMDomain) if x.type == utils.OPENSTACK_VMM_TYPE] @@ -2276,6 +2266,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, ns_cls = nat_strategy.EdgeNatStrategy ns = ns_cls(self.aim) ns.app_profile_name = self.ap_name + ns.common_scope = self.apic_system_id return ns def _get_aim_nat_strategy(self, network): diff --git a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py index 7e087005e..f50c1a94c 100644 --- a/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py +++ b/gbpservice/neutron/services/grouppolicy/drivers/cisco/apic/aim_mapping.py @@ -1985,6 +1985,8 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin): ext_segment_name = dn.replace('/', ':') ipms.append({'external_segment_name': ext_segment_name, 'nat_epg_name': ext_net_epg.name, + 'nat_epg_app_profile': ( + ext_net_epg.app_profile_name), 'nat_epg_tenant': ext_net_epg.tenant_name}) # TODO(amitbose) Set next_hop_ep_tenant for per-tenant NAT EPG if host: @@ -1996,6 +1998,7 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin): else: for f in fips_in_ext_net: f['nat_epg_name'] = ext_net_epg.name + f['nat_epg_app_profile'] = ext_net_epg.app_profile_name f['nat_epg_tenant'] = ext_net_epg.tenant_name return fips, ipms, host_snat_ips diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py index caa77bfce..26e073f3d 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py @@ -3282,6 +3282,102 @@ class TestMigrations(ApicAimTestCase, db.DbMixin): epg = self._find_by_dn(net1_epg, aim_resource.EndpointGroup) self.assertIsNone(epg) + def test_ap_name_change(self): + self.tenant_1 = 't1' + net = self._make_ext_network( + 'net2', dn='uni/tn-common/out-l1/instP-n1') + self._make_subnet(self.fmt, {'network': net}, '10.0.1.1', + '10.0.1.0/24') + + addr_scope = self._make_address_scope( + self.fmt, 4, name='as1', tenant_id=self.tenant_1)['address_scope'] + subnetpool = self._make_subnetpool( + self.fmt, ['10.0.0.0/8'], name='spool1', tenant_id=self.tenant_1, + address_scope_id=addr_scope['id'])['subnetpool'] + net_int = self._make_network(self.fmt, 'pvt-net', True, + tenant_id=self.tenant_1)['network'] + sp_id = subnetpool['id'] + sub = self._make_subnet( + self.fmt, {'network': net_int}, '10.10.1.1', '10.10.1.0/24', + subnetpool_id=sp_id)['subnet'] + router = self._make_router( + self.fmt, self.tenant_1, 'router', + arg_list=self.extension_attributes, + external_gateway_info={'network_id': net['id']})['router'] + self._router_interface_action('add', router['id'], sub['id'], None) + + aim = self.aim_mgr + aim_ctx = aim_context.AimContext(self.db_session) + ns = self.driver._nat_type_to_strategy(None) + ext_net = aim_resource.ExternalNetwork.from_dn( + net[DN]['ExternalNetwork']) + l3out = aim_resource.L3Outside(tenant_name=ext_net.tenant_name, + name=ext_net.l3out_name) + right_res = ns.get_l3outside_resources(aim_ctx, l3out) + for res in copy.deepcopy(right_res): + if isinstance(res, aim_resource.ApplicationProfile): + aim.delete(aim_ctx, res) + res.name = self.driver.ap_name + wrong_ap = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.EndpointGroup): + aim.delete(aim_ctx, res) + res.app_profile_name = self.driver.ap_name + res.bd_name = 'EXT-%s' % l3out.name + wrong_epg = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.BridgeDomain): + aim.delete(aim_ctx, res) + res.name = 'EXT-%s' % l3out.name + wrong_bd = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.Contract): + aim.delete(aim_ctx, res) + res.name = 'EXT-%s' % l3out.name + wrong_ctr = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.Filter): + aim.delete(aim_ctx, res) + res.name = 'EXT-%s' % l3out.name + wrong_flt = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.FilterEntry): + aim.delete(aim_ctx, res) + res.filter_name = 'EXT-%s' % l3out.name + wrong_entry = aim.create(aim_ctx, res) + if isinstance(res, aim_resource.ContractSubject): + aim.delete(aim_ctx, res) + res.contract_name = 'EXT-%s' % l3out.name + wrong_sbj = aim.create(aim_ctx, res) + + ns.common_scope = None + wrong_res = ns.get_l3outside_resources(aim_ctx, l3out) + self.assertEqual(len(right_res), len(wrong_res)) + self.assertNotEqual(sorted(right_res), sorted(wrong_res)) + + data_migrations.do_ap_name_change(self.db_session, cfg.CONF) + # Verify new model + ns = self.driver._nat_type_to_strategy(None) + final_res = ns.get_l3outside_resources(aim_ctx, l3out) + self.assertEqual(sorted(right_res), sorted(final_res)) + self.assertIsNone(aim.get(aim_ctx, wrong_ap)) + self.assertIsNone(aim.get(aim_ctx, wrong_epg)) + self.assertIsNone(aim.get(aim_ctx, wrong_bd)) + self.assertIsNone(aim.get(aim_ctx, wrong_ctr)) + self.assertIsNone(aim.get(aim_ctx, wrong_flt)) + self.assertIsNone(aim.get(aim_ctx, wrong_entry)) + self.assertIsNone(aim.get(aim_ctx, wrong_sbj)) + self.assertIsNotNone(ns.get_subnet(aim_ctx, l3out, '10.0.1.1/24')) + # Top level objects are scoped + for r in final_res: + if any(isinstance(r, x) for x in [aim_resource.ApplicationProfile, + aim_resource.BridgeDomain, + aim_resource.Contract, + aim_resource.Filter]): + self.assertTrue(r.name.startswith(self.driver.apic_system_id), + '%s name: %s' % (type(r), r.name)) + self._update('routers', router['id'], + {'router': {'external_gateway_info': {}}}) + self._delete('networks', net['id']) + for r in final_res: + self.assertIsNone(aim.get(aim_ctx, r), + 'Resource: %s still in AIM' % r) + class TestPortBinding(ApicAimTestCase): def test_bind_opflex_agent(self): diff --git a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py index 545b63ae0..90e072481 100644 --- a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py +++ b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py @@ -2886,17 +2886,20 @@ class TestPolicyTarget(AIMBaseTestCase): return ext_seg, subnet def _verify_fip_details(self, mapping, fip, ext_epg_tenant, - ext_epg_name): + ext_epg_name, ext_epg_app_profile='OpenStack'): self.assertEqual(1, len(mapping['floating_ip'])) fip = copy.deepcopy(fip) fip['nat_epg_name'] = ext_epg_name fip['nat_epg_tenant'] = ext_epg_tenant + fip['nat_epg_app_profile'] = ext_epg_app_profile self.assertEqual(fip, mapping['floating_ip'][0]) def _verify_ip_mapping_details(self, mapping, ext_segment_name, - ext_epg_tenant, ext_epg_name): + ext_epg_tenant, ext_epg_name, + ext_epg_app_profile='OpenStack'): self.assertTrue({'external_segment_name': ext_segment_name, 'nat_epg_name': ext_epg_name, + 'nat_epg_app_profile': ext_epg_app_profile, 'nat_epg_tenant': ext_epg_tenant} in mapping['ip_mapping'])