diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD index 151522ab2..d7a229929 100644 --- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -dc99863d1f2b +fd3e605a7232 diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py new file mode 100644 index 000000000..26186a65a --- /dev/null +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py @@ -0,0 +1,51 @@ +# 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. + +"""HPP Table + +Revision ID: fd3e605a7232 +Revises: dc99863d1f2b +Create Date: 2023-09-19 02:08:54.252877 + +""" + +# revision identifiers, used by Alembic. +revision = 'fd3e605a7232' +down_revision = 'dc99863d1f2b' + +from alembic import op +from alembic import util +import sqlalchemy as sa + + +def upgrade(): + + bind = op.get_bind() + op.create_table( + 'apic_aim_hpp', + sa.Column('hpp_normalized', sa.Boolean, nullable=False), + sa.PrimaryKeyConstraint('hpp_normalized')) + + try: + from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import ( + data_migrations) + + session = sa.orm.Session(bind=bind, autocommit=True) + with session.begin(subtransactions=True): + data_migrations.do_hpp_insertion(session) + except Exception as e: + util.warn("Caught exception while migrating data in %s: %s" % + ('apic_aim_hpp', e)) + + +def downgrade(): + pass 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 4a4f30230..bca7446a8 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py @@ -70,6 +70,11 @@ AddressScopeMapping = sa.Table( sa.Column('vrf_owned', sa.Boolean, nullable=False)) +HPPDB = sa.Table( + 'apic_aim_hpp', sa.MetaData(), + sa.Column('hpp_normalized', sa.Boolean, nullable=False)) + + # The following definition has been taken from commit: # f8b41855acbbb7e59a0bab439445c198fc6aa146 # and is frozen for the data migration script that was included @@ -496,8 +501,7 @@ def do_ha_ip_network_id_insertion(session): haip_port_id = HAIPAddressToPortAssociation.c.port_id with db_api.CONTEXT_WRITER.using(session): - haip_ip = HAIPAddressToPortAssociation.c.ha_ip_address - haip_port_id = HAIPAddressToPortAssociation.c.port_id + port_and_haip_dbs = (session.query(models_v2.Port, HAIPAddressToPortAssociation).join( HAIPAddressToPortAssociation, @@ -512,3 +516,93 @@ def do_ha_ip_network_id_insertion(session): alembic_util.msg( "Finished network id insertion for HA IP table.") + + +def do_hpp_insertion(session): + alembic_util.msg( + "Starting hpp normalized value insertion for HPP table.") + + with session.begin(subtransactions=True): + session.execute(HPPDB.insert().values(hpp_normalized=False)) + + alembic_util.msg( + "Finished hpp normalized value insertion for HPP table.") + + +def normalize_hpp_ips(session): + aim = aim_manager.AimManager() + aim_ctx = aim_context.AimContext(session) + mapper = apic_mapper.APICNameMapper() + + sg_dbs = (session.query(sg_models.SecurityGroup). + options(lazyload('*')).all()) + for sg_db in sg_dbs: + tenant_aname = mapper.project(session, + sg_db['tenant_id']) + + # Create remote group container for each security group + try: + sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=tenant_aname, + security_group_name=sg_db['id'], + name=sg_db['id']) + aim.create(aim_ctx, sg_remote_group) + except Exception as e: + alembic_util.warning("%s" % e) + + 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_db['id']). + options(lazyload('*')).all()) + + for sg_port in sg_ports: + for fixed_ip in sg_port['fixed_ips']: + # Create SG remote ip resource for each fixed ip + sg_remote_group_ip = aim_resource.SecurityGroupRemoteIp( + tenant_name=tenant_aname, + security_group_name=sg_db['id'], + addr=fixed_ip['ip_address']) + aim.create(aim_ctx, sg_remote_group_ip) + + +def normalize_hpps(session): + aim = aim_manager.AimManager() + aim_ctx = aim_context.AimContext(session) + mapper = apic_mapper.APICNameMapper() + + sg_rule_dbs = (session.query(sg_models.SecurityGroupRule). + options(lazyload('*')).all()) + for sg_rule_db in sg_rule_dbs: + sg = (session.query(sg_models.SecurityGroup). + filter(sg_models.SecurityGroup.id == + sg_rule_db['security_group_id']). + options(lazyload('*')).first()) + tenant_id = sg['tenant_id'] + tenant_aname = mapper.project(session, tenant_id) + if sg_rule_db.get('remote_group_id'): + 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']) + sg_rule_aim = aim.get(aim_ctx, sg_rule_aim) + + if sg_rule_aim.remote_ips: + # Get the remote group container's dn + rg = (session.query(sg_models.SecurityGroup). + filter(sg_models.SecurityGroup.id == + sg_rule_db['remote_group_id']). + options(lazyload('*')).first()) + rg_tenant_id = rg['tenant_id'] + rg_tenant_aname = mapper.project(session, rg_tenant_id) + rg_cont = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=rg_tenant_aname, + security_group_name=sg_rule_db['remote_group_id'], + name=sg_rule_db['remote_group_id']) + # Update SG rule tDn with remote group container dn + aim.update(aim_ctx, sg_rule_aim, + remote_ips=[], tDn=rg_cont.dn) diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py index a2a827b87..4f62e18f1 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py @@ -186,6 +186,13 @@ class RouterExtensionContractDb(model_base.BASEV2): provides = sa.Column(sa.Boolean, primary_key=True) +class HPPDb(model_base.BASEV2): + __tablename__ = 'apic_aim_hpp' + + hpp_normalized = sa.Column(sa.Boolean, default=False, + primary_key=True) + + class ExtensionDbMixin(object): def _set_if_not_none(self, res_dict, res_attr, db_attr): @@ -706,3 +713,16 @@ class ExtensionDbMixin(object): 'contract_name', res_dict[cisco_apic_l3.EXTERNAL_CONSUMED_CONTRACTS], router_id=router_id, provides=False) + + def get_hpp_normalized(self, session): + with session.begin(subtransactions=True): + query = BAKERY(lambda s: s.query(HPPDb)) + db_obj = query(session).first() + return db_obj['hpp_normalized'] + + def set_hpp_normalized(self, session, hpp_normalized): + with session.begin(subtransactions=True): + query = BAKERY(lambda s: s.query(HPPDb)) + db_obj = query(session).first() + db_obj['hpp_normalized'] = hpp_normalized + session.add(db_obj) 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 ef77dff47..00d43e5c3 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py @@ -87,6 +87,7 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import ( 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 config # noqa +from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import data_migrations from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import db from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import exceptions from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db @@ -2837,18 +2838,77 @@ class ApicMechanismDriver(api_plus.MechanismDriver, # and try binding hierarchically if the network-type is OpFlex. self._bind_physical_node(context) - def _update_sg_rule_with_remote_group_set(self, context, port): - security_groups = port['security_groups'] - original_port = context.original - if original_port: - removed_sgs = (set(original_port['security_groups']) - - set(security_groups)) - added_sgs = (set(security_groups) - - set(original_port['security_groups'])) - self._really_update_sg_rule_with_remote_group_set( - context, port, removed_sgs, is_delete=True) - self._really_update_sg_rule_with_remote_group_set( - context, port, added_sgs, is_delete=False) + def _really_update_sg_remote_groups( + self, context, port, security_groups, is_delete): + if not security_groups: + return + session = context._plugin_context.session + aim_ctx = aim_context.AimContext(session) + + fixed_ips = [x['ip_address'] for x in port['fixed_ips']] + for sg in security_groups: + tenant_id = self._get_sg_tenant_id(session, sg) + tenant_aname = self.name_mapper.project(session, tenant_id) + for fixed_ip in fixed_ips: + # Create or delete SG remote ip resource for fixed ip + sg_rg_ip_aim = aim_resource.SecurityGroupRemoteIp( + tenant_name=tenant_aname, + security_group_name=sg, + addr=fixed_ip) + if is_delete: + if self.aim.get(aim_ctx, sg_rg_ip_aim): + self.aim.delete(aim_ctx, sg_rg_ip_aim) + else: + if not self.aim.get(aim_ctx, sg_rg_ip_aim): + self.aim.create(aim_ctx, sg_rg_ip_aim) + + query = BAKERY(lambda s: s.query( + models_v2.Port)) + query += lambda q: q.join( + sg_models.SecurityGroupPortBinding, + sg_models.SecurityGroupPortBinding.port_id == + models_v2.Port.id) + query += lambda q: q.filter( + sg_models.SecurityGroupPortBinding.security_group_id == + sa.bindparam('sg_id')) + sg_ports = query(session).params( + sg_id=sg).all() + + all_fixed_ips = [] + for sg_port in sg_ports: + ip_addr = [x['ip_address'] for x in sg_port['fixed_ips']] + all_fixed_ips.extend(ip_addr) + old_fixed_ips = list(set(all_fixed_ips) - set(fixed_ips)) + + if (fixed_ips and not old_fixed_ips): + query = BAKERY(lambda s: s.query( + sg_models.SecurityGroupRule)) + query += lambda q: q.filter( + sg_models.SecurityGroupRule.remote_group_id == + sa.bindparam('security_group')) + sg_rules = query(session).params( + security_group=sg).all() + + sg_rule_to_tenant = {} + for sg_rule in sg_rules: + sg_id = sg_rule['security_group_id'] + sg_rule_tenant_id = sg_rule_to_tenant.setdefault(sg_id, + self._get_sg_rule_tenant_id(session, sg_rule)) + sg_rule_tenant_aname = self.name_mapper.project( + session, sg_rule_tenant_id) + sg_rule_aim = aim_resource.SecurityGroupRule( + tenant_name=sg_rule_tenant_aname, + security_group_name=sg_id, + security_group_subject_name='default', + name=sg_rule['id']) + if is_delete: + self.aim.update(aim_ctx, sg_rule_aim, tDn='') + else: + rg_cont = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=tenant_aname, + security_group_name=sg_rule['remote_group_id'], + name=sg_rule['remote_group_id']) + self.aim.update(aim_ctx, sg_rule_aim, tDn=rg_cont.dn) def _really_update_sg_rule_with_remote_group_set( self, context, port, security_groups, is_delete): @@ -2869,11 +2929,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver, sg_to_tenant = {} for sg_rule in sg_rules: sg_id = sg_rule['security_group_id'] - if sg_id in sg_to_tenant: - tenant_id = sg_to_tenant[sg_id] - else: - tenant_id = self._get_sg_rule_tenant_id(session, sg_rule) - sg_to_tenant[sg_id] = tenant_id + tenant_id = sg_to_tenant.setdefault(sg_id, + self._get_sg_rule_tenant_id(session, sg_rule)) tenant_aname = self.name_mapper.project(session, tenant_id) sg_rule_aim = aim_resource.SecurityGroupRule( tenant_name=tenant_aname, @@ -3140,11 +3197,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver, def create_port_precommit(self, context): port = context.current + session = context._plugin_context.session self._check_active_active_aap(context, port) if port.get(cisco_apic.ERSPAN_CONFIG): self._check_valid_erspan_config(port) - self._really_update_sg_rule_with_remote_group_set( - context, port, port['security_groups'], is_delete=False) + if self.get_hpp_normalized(session): + self._really_update_sg_remote_groups( + context, port, port['security_groups'], is_delete=False) + else: + self._really_update_sg_rule_with_remote_group_set( + context, port, port['security_groups'], is_delete=False) self._insert_provisioning_block(context) # Handle router gateway port creation. @@ -3366,7 +3428,41 @@ class ApicMechanismDriver(api_plus.MechanismDriver, erspan_deletions) self._create_erspan_aim_config(context, cep_dn, port) - self._update_sg_rule_with_remote_group_set(context, port) + security_groups = port['security_groups'] + # Check if any security groups are changed for the port + removed_sgs = (set(orig['security_groups']) - + set(security_groups)) + added_sgs = (set(security_groups) - + set(orig['security_groups'])) + + # Check if any fixed ips are changed for the port + orig_ips = [x['ip_address'] for x in orig['fixed_ips']] + + ips = [x['ip_address'] for x in port['fixed_ips']] + removed_ips = (set(orig_ips) - set(ips)) + added_ips = (set(ips) - set(orig_ips)) + + if self.get_hpp_normalized(session): + self._really_update_sg_remote_groups( + context, port, removed_sgs, is_delete=True) + self._really_update_sg_remote_groups( + context, port, added_sgs, is_delete=False) + if removed_ips or added_ips: + self._really_update_sg_remote_groups( + context, orig, security_groups, is_delete=True) + self._really_update_sg_remote_groups( + context, port, security_groups, is_delete=False) + else: + self._really_update_sg_rule_with_remote_group_set( + context, port, removed_sgs, is_delete=True) + self._really_update_sg_rule_with_remote_group_set( + context, port, added_sgs, is_delete=False) + if removed_ips or added_ips: + self._really_update_sg_rule_with_remote_group_set( + context, orig, security_groups, is_delete=True) + self._really_update_sg_rule_with_remote_group_set( + context, port, security_groups, is_delete=False) + self._check_allowed_address_pairs(context, port) self._insert_provisioning_block(context) registry.publish(aim_cst.GBP_PORT, events.PRECOMMIT_UPDATE, @@ -3423,6 +3519,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, def delete_port_precommit(self, context): port = context.current + session = context._plugin_context.session if self._is_port_bound(port): if self._use_static_path(context.bottom_bound_segment): self._update_static_path(context, remove=True) @@ -3433,8 +3530,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver, context.bottom_bound_segment[api.NETWORK_TYPE])): self.disassociate_domain(context) self._delete_erspan_aim_config(context, port) - self._really_update_sg_rule_with_remote_group_set( - context, port, port['security_groups'], is_delete=True) + if self.get_hpp_normalized(session): + self._really_update_sg_remote_groups( + context, port, port['security_groups'], is_delete=True) + else: + self._really_update_sg_rule_with_remote_group_set( + context, port, port['security_groups'], is_delete=True) # Set status of floating ip DOWN. self._update_floatingip_status( @@ -3546,6 +3647,14 @@ class ApicMechanismDriver(api_plus.MechanismDriver, security_group_name=sg['id'], name='default') self.aim.create(aim_ctx, sg_subject) + if self.get_hpp_normalized(session): + # Create default remote group container + sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=tenant_aname, + security_group_name=sg['id'], + remote_group_id=sg['id']) + self.aim.create(aim_ctx, sg_remote_group) + # Create those implicit rules for sg_rule in sg.get('security_group_rules', []): sg_rule_aim = aim_resource.SecurityGroupRule( @@ -3604,6 +3713,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver, return tenant_id + def _get_sg_tenant_id(self, session, sg_id): + query = BAKERY(lambda s: s.query( + sg_models.SecurityGroup.tenant_id)) + query += lambda q: q.filter( + sg_models.SecurityGroup.id == sa.bindparam('sg_id')) + tenant_id = query(session).params( + sg_id=sg_id).first()[0] + + return tenant_id + def create_security_group_rule_precommit(self, context): session = context._plugin_context.session aim_ctx = aim_context.AimContext(session) @@ -3612,6 +3731,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver, tenant_aname = self.name_mapper.project(session, tenant_id) if sg_rule.get('remote_group_id'): remote_ips = [] + remote_group_id = sg_rule['remote_group_id'] + dn = '' query = BAKERY(lambda s: s.query( models_v2.Port)) @@ -3625,20 +3746,35 @@ class ApicMechanismDriver(api_plus.MechanismDriver, sg_ports = query(session).params( sg_id=sg_rule['remote_group_id']).all() - ip_version = 0 - if sg_rule['ethertype'] == 'IPv4': - ip_version = 4 - elif sg_rule['ethertype'] == 'IPv6': - ip_version = 6 - - 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']) - - remote_group_id = sg_rule['remote_group_id'] + if self.get_hpp_normalized(session): + has_fixed_ips = False + for sg_port in sg_ports: + if sg_port['fixed_ips']: + has_fixed_ips = True + break + if has_fixed_ips: + # Get the remote group container's dn + rg_tenant_id = self._get_sg_tenant_id(session, + sg_rule['remote_group_id']) + rg_tenant_aname = self.name_mapper.project(session, + rg_tenant_id) + rg_cont = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=rg_tenant_aname, + security_group_name=sg_rule['remote_group_id'], + name=sg_rule['remote_group_id']) + dn = rg_cont.dn + else: + ip_version = 0 + if sg_rule['ethertype'] == 'IPv4': + ip_version = 4 + elif sg_rule['ethertype'] == 'IPv6': + ip_version = 6 + 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']) elif sg_rule.get('remote_address_group_id'): remote_ips = [] @@ -3663,11 +3799,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver, remote_ips.append(addr['address']) remote_group_id = '' + dn = '' else: remote_ips = ([sg_rule['remote_ip_prefix']] if sg_rule['remote_ip_prefix'] else '') remote_group_id = '' + dn = '' sg_rule_aim = aim_resource.SecurityGroupRule( tenant_name=tenant_aname, @@ -3692,6 +3830,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, if sg_rule['port_range_min'] else 'unspecified'), to_port=(sg_rule['port_range_max'] if sg_rule['port_range_max'] else 'unspecified'), + tDn=dn, remote_group_id=remote_group_id) self.aim.create(aim_ctx, sg_rule_aim) @@ -6729,6 +6868,25 @@ class ApicMechanismDriver(api_plus.MechanismDriver, metadata={ 'network_id': mapping.network_id})) + def normalize_hpp(self): + session = db_api.get_writer_session() + aim_ctx = aim_context.AimContext(db_session=session) + aim = aim_manager.AimManager() + + remoteip_normalization = aim.get(aim_ctx, + aim_infra.ACISupportedMo(name="remoteipcont")) + if remoteip_normalization.supports: + if not self.get_hpp_normalized(session): + ctx = n_context.get_admin_context() + with db_api.CONTEXT_WRITER.using(ctx): + aim_ctx = aim_context.AimContext(session) + data_migrations.normalize_hpp_ips(session) + data_migrations.normalize_hpps(session) + self.set_hpp_normalized(session, True) + return True + else: + return False + def validate_aim_mapping(self, mgr): mgr.register_aim_resource_class(aim_infra.HostDomainMappingV2) mgr.register_aim_resource_class(aim_resource.ApplicationProfile) @@ -6747,6 +6905,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver, mgr.register_aim_resource_class(aim_resource.L3Outside) mgr.register_aim_resource_class(aim_resource.PhysicalDomain) mgr.register_aim_resource_class(aim_resource.SecurityGroup) + mgr.register_aim_resource_class( + aim_resource.SecurityGroupRemoteIpContainer) + mgr.register_aim_resource_class(aim_resource.SecurityGroupRemoteIp) mgr.register_aim_resource_class(aim_resource.SecurityGroupRule) mgr.register_aim_resource_class(aim_resource.SecurityGroupSubject) mgr.register_aim_resource_class(aim_resource.Subnet) @@ -7506,6 +7667,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, def _validate_security_groups(self, mgr): if not mgr._should_validate_neutron_resource("security_group"): return + session = db_api.get_writer_session() sg_ips = defaultdict(set) query = BAKERY(lambda s: s.query( @@ -7549,17 +7711,45 @@ class ApicMechanismDriver(api_plus.MechanismDriver, tenant_name=tenant_name, security_group_name=sg_db.id, name='default') mgr.expect_aim_resource(sg_subject) + + if self.get_hpp_normalized(mgr.actual_session): + sg_rg = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=tenant_name, + security_group_name=sg_db.id, + name=sg_db.id) + mgr.expect_aim_resource(sg_rg) + remote_ips = [ + ip for ip in sg_ips[sg_db.id]] + for remote_ip in remote_ips: + sg_rg_ip = aim_resource.SecurityGroupRemoteIp( + tenant_name=tenant_name, + security_group_name=sg_db.id, + addr=remote_ip) + mgr.expect_aim_resource(sg_rg_ip) + for rule_db in sg_db.rules: remote_ips = [] remote_group_id = '' + dn = '' if rule_db.remote_group_id: remote_group_id = rule_db.remote_group_id - ip_version = (4 if rule_db.ethertype == 'IPv4' else - 6 if rule_db.ethertype == 'IPv6' else - 0) - remote_ips = [ - ip for ip in sg_ips[rule_db.remote_group_id] - if netaddr.IPAddress(ip).version == ip_version] + if self.get_hpp_normalized(mgr.actual_session): + rg_tenant_id = self._get_sg_tenant_id( + session, rule_db.remote_group_id) + rg_tenant_aname = self.name_mapper.project( + session, rg_tenant_id) + rg = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=rg_tenant_aname, + security_group_name=rule_db.remote_group_id, + name=rule_db.remote_group_id) + dn = rg.dn + else: + ip_version = (4 if rule_db.ethertype == 'IPv4' else + 6 if rule_db.ethertype == 'IPv6' else + 0) + remote_ips = [ + ip for ip in sg_ips[rule_db.remote_group_id] + if netaddr.IPAddress(ip).version == ip_version] elif rule_db.remote_ip_prefix: remote_ips = [rule_db.remote_ip_prefix] sg_rule = aim_resource.SecurityGroupRule( @@ -7577,6 +7767,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, to_port=(rule_db.port_range_max if rule_db.port_range_max else 'unspecified'), + tDn=dn, remote_group_id=remote_group_id) mgr.expect_aim_resource(sg_rule) 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 b77e29ec6..1cb99478b 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py @@ -242,6 +242,7 @@ class ApicAimTestMixin(object): self.aim_cfg_manager = aim_cfg.ConfigManager( aim_context.AimContext(db_session=session), '') self.aim_cfg_manager.replace_all(aim_cfg.CONF) + data_migrations.do_hpp_insertion(session) def set_override(self, item, value, group=None, host=''): # Override DB config as well @@ -544,6 +545,27 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase, self.assertIsNotNone(sg_rule) return sg_rule + def _check_sg_remote_group_container(self, remote_group_name, + sg_name, tenant_name): + session = db_api.get_reader_session() + aim_ctx = aim_context.AimContext(session) + sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer( + tenant_name=tenant_name, security_group_name=sg_name, + name=remote_group_name) + sg_remote_group = self.aim_mgr.get(aim_ctx, sg_remote_group) + self.assertIsNotNone(sg_remote_group) + return sg_remote_group + + def _get_sg_remote_group_ip(self, remote_ip, + sg_name, tenant_name): + session = db_api.get_reader_session() + aim_ctx = aim_context.AimContext(session) + sg_remote_group_ip = aim_resource.SecurityGroupRemoteIp( + tenant_name=tenant_name, security_group_name=sg_name, + addr=remote_ip) + sg_remote_group_ip = self.aim_mgr.get(aim_ctx, sg_remote_group_ip) + return sg_remote_group_ip + def _get_contract(self, contract_name, tenant_name): ctx = n_context.get_admin_context() # TODO(pulkit): replace with AIM reader context once API supports it. @@ -11914,7 +11936,9 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): set(self._doms(epg1.physical_domains, with_type=False))) - def test_update_sg_rule_with_remote_group_set(self): + def _test_sg_update_remote_groups(self): + session = db_api.get_reader_session() + extn = extn_db.ExtensionDbMixin() # Create network. net_resp = self._make_network(self.fmt, 'net1', True) net = net_resp['network'] @@ -11931,16 +11955,29 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): default_sg_id = port['security_groups'][0] default_sg = self._show('security-groups', default_sg_id)['security_group'] + tenant_aname = self.name_mapper.project(None, default_sg['tenant_id']) + for sg_rule in default_sg['security_group_rules']: if sg_rule['remote_group_id'] and sg_rule['ethertype'] == 'IPv4': break - tenant_aname = self.name_mapper.project(None, default_sg['tenant_id']) aim_sg_rule = self._get_sg_rule( sg_rule['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) self.assertEqual( aim_sg_rule.remote_group_id, sg_rule['remote_group_id']) + if extn.get_hpp_normalized(session): + rg_cont = self._check_sg_remote_group_container(default_sg_id, + default_sg_id, tenant_aname) + remote_ips = ['10.0.1.100'] + for remote_ip in remote_ips: + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + remote_ip, default_sg_id, tenant_aname) + self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip) + self.assertEqual(aim_sg_rule.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + # add another rule with remote_group_id set rule1 = self._build_security_group_rule( default_sg_id, 'ingress', n_constants.PROTO_NAME_TCP, '22', '23', @@ -11950,9 +11987,15 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): self.fmt, rules)['security_group_rules'][0] aim_sg_rule = self._get_sg_rule( sg_rule1['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) self.assertEqual( aim_sg_rule.remote_group_id, sg_rule1['remote_group_id']) + if extn.get_hpp_normalized(session): + dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule1[ + 'remote_group_id'] + '/remoteipcont' + self.assertEqual(aim_sg_rule.tDn, dn) + self.assertEqual(aim_sg_rule.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) rule2 = self._build_security_group_rule( default_sg_id, 'ingress', n_constants.PROTO_NAME_ICMP, '33', '2', @@ -11962,9 +12005,17 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): self.fmt, rules)['security_group_rules'][0] aim_sg_rule = self._get_sg_rule( sg_rule2['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + self.assertEqual( + aim_sg_rule.remote_group_id, sg_rule2['remote_group_id']) self.assertEqual(aim_sg_rule.icmp_code, '2') self.assertEqual(aim_sg_rule.icmp_type, '33') + if extn.get_hpp_normalized(session): + dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule2[ + 'remote_group_id'] + '/remoteipcont' + self.assertEqual(aim_sg_rule.tDn, dn) + self.assertEqual(aim_sg_rule.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) rule3 = self._build_security_group_rule( default_sg_id, 'ingress', n_constants.PROTO_NAME_ICMP, None, None, @@ -11974,37 +12025,58 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): self.fmt, rules)['security_group_rules'][0] aim_sg_rule = self._get_sg_rule( sg_rule3['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + self.assertEqual( + aim_sg_rule.remote_group_id, sg_rule3['remote_group_id']) self.assertEqual(aim_sg_rule.icmp_code, 'unspecified') self.assertEqual(aim_sg_rule.icmp_type, 'unspecified') + if extn.get_hpp_normalized(session): + dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule3[ + 'remote_group_id'] + '/remoteipcont' + self.assertEqual(aim_sg_rule.tDn, dn) + self.assertEqual(aim_sg_rule.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) # delete SG from port data = {'port': {'security_groups': []}} port = self._update('ports', port['id'], data)['port'] aim_sg_rule = self._get_sg_rule( sg_rule['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, []) - aim_sg_rule = self._get_sg_rule( + aim_sg_rule1 = self._get_sg_rule( sg_rule1['id'], 'default', default_sg_id, tenant_aname) + if extn.get_hpp_normalized(session): + self._check_sg_remote_group_container( + default_sg_id, default_sg_id, tenant_aname) + remote_ip = '' + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + sg_rule['id'], default_sg_id, tenant_aname) + self.assertIsNone(aim_sg_remote_group_ip) + self.assertEqual(aim_sg_rule.tDn, '') + self.assertEqual(aim_sg_rule1.tDn, '') self.assertEqual(aim_sg_rule.remote_ips, []) + self.assertEqual(aim_sg_rule1.remote_ips, []) # add SG to port data = {'port': {'security_groups': [default_sg_id]}} port = self._update('ports', port['id'], data)['port'] aim_sg_rule = self._get_sg_rule( sg_rule['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) - aim_sg_rule = self._get_sg_rule( + aim_sg_rule1 = self._get_sg_rule( sg_rule1['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) - - self._delete('ports', port['id']) - aim_sg_rule = self._get_sg_rule( - sg_rule['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, []) - aim_sg_rule = self._get_sg_rule( - sg_rule1['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, []) + if extn.get_hpp_normalized(session): + rg_cont = self._check_sg_remote_group_container( + default_sg_id, default_sg_id, tenant_aname) + remote_ips = ['10.0.1.100'] + for remote_ip in remote_ips: + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + remote_ip, default_sg_id, tenant_aname) + self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip) + self.assertEqual(aim_sg_rule.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule1.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100']) def test_sg_rule_with_remote_address_group(self): net_resp = self._make_network(self.fmt, 'net1', True) @@ -12041,7 +12113,9 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): # Delete Address group self._delete('address-groups', ag['address_group']['id']) - def test_create_sg_rule_with_remote_group_set_different_tenant(self): + def _test_create_sg_rule_with_remote_group_set_different_tenant(self): + session = db_api.get_reader_session() + extn = extn_db.ExtensionDbMixin() # Create network. net_resp = self._make_network( self.fmt, 'net1', True, tenant_id='tenant_1') @@ -12065,7 +12139,19 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): tenant_aname = self.name_mapper.project(None, default_sg['tenant_id']) aim_sg_rule = self._get_sg_rule( sg_rule['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + if extn.get_hpp_normalized(session): + rg_cont = self._check_sg_remote_group_container( + sg_rule['remote_group_id'], + sg_rule['remote_group_id'], tenant_aname) + remote_ips = ['10.0.1.100'] + for remote_ip in remote_ips: + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + remote_ip, sg_rule['remote_group_id'], tenant_aname) + self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip) + self.assertEqual(aim_sg_rule.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule.remote_ips, []) + else: + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) self.assertEqual( aim_sg_rule.remote_group_id, sg_rule['remote_group_id']) @@ -12078,7 +12164,19 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): self.fmt, rules, tenant_id='tenant_2')['security_group_rules'][0] aim_sg_rule1 = self._get_sg_rule( sg_rule1['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100']) + if extn.get_hpp_normalized(session): + rg_cont = self._check_sg_remote_group_container( + sg_rule1['remote_group_id'], + sg_rule1['remote_group_id'], tenant_aname) + remote_ips = ['10.0.1.100'] + for remote_ip in remote_ips: + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + remote_ip, sg_rule1['remote_group_id'], tenant_aname) + self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip) + self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule1.remote_ips, []) + else: + self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100']) self.assertEqual( aim_sg_rule1.remote_group_id, sg_rule1['remote_group_id']) @@ -12088,9 +12186,102 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork): tenant_id='tenant_1')['port'] aim_sg_rule1 = self._get_sg_rule( sg_rule1['id'], 'default', default_sg_id, tenant_aname) - self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100', '10.0.1.200']) + if extn.get_hpp_normalized(session): + rg_cont = self._check_sg_remote_group_container( + sg_rule['remote_group_id'], + sg_rule['remote_group_id'], tenant_aname) + remote_ips = ['10.0.1.100', '10.0.1.200'] + for remote_ip in remote_ips: + aim_sg_remote_group_ip = self._get_sg_remote_group_ip( + remote_ip, sg_rule['remote_group_id'], tenant_aname) + self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip) + self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn) + self.assertEqual(aim_sg_rule1.remote_ips, []) + else: + self.assertEqual(aim_sg_rule1.remote_ips, + ['10.0.1.100', '10.0.1.200']) self.assertEqual( - aim_sg_rule1.remote_group_id, sg_rule['remote_group_id']) + aim_sg_rule1.remote_group_id, sg_rule1['remote_group_id']) + + def test_sg_update_remote_groups_normalized(self): + session = db_api.get_reader_session() + extn = extn_db.ExtensionDbMixin() + extn.set_hpp_normalized(session, True) + self._test_sg_update_remote_groups() + self._test_create_sg_rule_with_remote_group_set_different_tenant() + + def test_sg_update_remote_groups_not_normalized(self): + self._test_sg_update_remote_groups() + self._test_create_sg_rule_with_remote_group_set_different_tenant() + + def test_normalize_hpp(self): + session = db_api.get_reader_session() + extn = extn_db.ExtensionDbMixin() + aim_ctx = aim_context.AimContext(session) + # Create network. + net_resp = self._make_network(self.fmt, 'net1', True) + net = net_resp['network'] + + # Create subnet + subnet = self._make_subnet(self.fmt, net_resp, '10.0.1.1', + '10.0.1.0/24')['subnet'] + subnet_id = subnet['id'] + + # Create port on subnet + fixed_ips = [{'subnet_id': subnet_id, 'ip_address': '10.0.1.100'}] + port = self._make_port(self.fmt, net['id'], + fixed_ips=fixed_ips)['port'] + default_sg_id = port['security_groups'][0] + default_sg = self._show('security-groups', + default_sg_id)['security_group'] + for sg_rule in default_sg['security_group_rules']: + if sg_rule['remote_group_id'] and sg_rule['ethertype'] == 'IPv4': + break + tenant_aname = self.name_mapper.project(None, default_sg['tenant_id']) + aim_sg_rule = self._get_sg_rule( + sg_rule['id'], 'default', default_sg_id, tenant_aname) + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + self.assertEqual( + aim_sg_rule.remote_group_id, sg_rule['remote_group_id']) + + # add rule with remote_group_id set + self.assertFalse(extn.get_hpp_normalized(session)) + rule1 = self._build_security_group_rule( + default_sg_id, 'ingress', n_constants.PROTO_NAME_TCP, '22', '23', + remote_group_id=default_sg_id, ethertype=n_constants.IPv4) + rules = {'security_group_rules': [rule1['security_group_rule']]} + sg_rule1 = self._make_security_group_rule( + self.fmt, rules)['security_group_rules'][0] + aim_sg_rule = self._get_sg_rule( + sg_rule1['id'], 'default', default_sg_id, tenant_aname) + self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100']) + self.assertEqual( + aim_sg_rule.remote_group_id, sg_rule1['remote_group_id']) + + # normalize the remote groups + self.remoteip_normalization = aim_infra.ACISupportedMo( + name="remoteipcont", supports=False) + self.aim_mgr.create(aim_ctx, self.remoteip_normalization) + self.mech = md.ApicMechanismDriver() + self.result = self.mech.normalize_hpp() + self.assertFalse(self.result) + + self.aim_mgr.update(aim_ctx, self.remoteip_normalization, + supports=True) + self.result = self.mech.normalize_hpp() + self.assertTrue(self.result) + self.assertTrue(extn.get_hpp_normalized(session)) + rg_cont = self._check_sg_remote_group_container( + default_sg_id, default_sg_id, tenant_aname) + remote_ips = ['10.0.1.100'] + for remote_ip in remote_ips: + aim_sg_remote_ip = self._get_sg_remote_group_ip( + remote_ip, default_sg_id, tenant_aname) + self.assertEqual(aim_sg_remote_ip.addr, remote_ip) + aim_sg_rule = self._get_sg_rule( + sg_rule1['id'], 'default', default_sg_id, tenant_aname) + self.assertEqual(aim_sg_rule.remote_ips, []) + self.assertEqual(aim_sg_rule.tDn, rg_cont.dn) def test_mixed_ports_on_network_with_specific_domains(self): with db_api.CONTEXT_READER.using(self.db_session): diff --git a/gbpservice/tools/hpp_normalize/__init__.py b/gbpservice/tools/hpp_normalize/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gbpservice/tools/hpp_normalize/cli.py b/gbpservice/tools/hpp_normalize/cli.py new file mode 100644 index 000000000..849ed8fd2 --- /dev/null +++ b/gbpservice/tools/hpp_normalize/cli.py @@ -0,0 +1,45 @@ +# Copyright (c) 2018 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. + +import sys + +from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import ( + mechanism_driver as md) +from oslo_config import cfg +from neutron.common import config +from neutron import manager + + +def main(): + config.init(sys.argv[1:]) + + # Enable logging but prevent output to stderr. + cfg.CONF.use_stderr = False + config.setup_logging() + + if not cfg.CONF.config_file: + sys.exit(_("ERROR: Unable to find configuration file via the default" + " search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and" + " the '--config-file' option!")) + + manager.init() + + mech = md.ApicMechanismDriver() + result = mech.normalize_hpp() + + if not result: + sys.exit(_("ERROR: APIC version doesn't support" + " normalization of remote ips")) + return 0 diff --git a/setup.cfg b/setup.cfg index 7c3435912..31ef3b819 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,6 +39,7 @@ scripts = console_scripts= gbp-db-manage = gbpservice.neutron.db.migration.cli:main gbp-validate = gbpservice.tools.validate.cli:main + hpp-normalize = gbpservice.tools.hpp_normalize.cli:main neutron.core_plugins = ml2plus = gbpservice.neutron.plugins.ml2plus.plugin:Ml2PlusPlugin neutron.service_plugins =