diff --git a/aim/agent/aid/universes/aci/converter.py b/aim/agent/aid/universes/aci/converter.py index 8b08540f..b1cbda89 100644 --- a/aim/agent/aid/universes/aci/converter.py +++ b/aim/agent/aid/universes/aci/converter.py @@ -35,7 +35,6 @@ MODIFIED_STATUS = "modified" CREATED_STATUS = "created" - # TODO(amitbose) Instead of aliasing, replace local references with the # ones from utils default_identity_converter = utils.default_identity_converter @@ -553,6 +552,82 @@ def bgp_extp_converter(object_dict, otype, helper, return result +def rsFilt_converter(direction, aci_mo=None): + def func(object_dict, otype, helper, source_identity_attributes, + destination_identity_attributes, to_aim=True): + result = [] + id_conv = (helper.get('identity_converter') or + default_identity_converter) + if to_aim: + res_dict = {} + aci_type = aci_mo or otype + try: + id = id_conv(object_dict, aci_type, helper, to_aim=True) + except apic_client.DNManager.InvalidNameFormat: + return [] + for index, attr in enumerate(destination_identity_attributes): + res_dict[attr] = id[index] + if object_dict.get('action') and direction: + res_dict['direction'] = direction + res_dict['action'] = object_dict['action'] + result.append(default_to_resource(res_dict, helper, to_aim=True)) + else: + aci_type = aci_mo or helper['resource'] + dn = id_conv(object_dict, otype, helper, + aci_mo_type=aci_type, to_aim=False)[0] + action = 'permit' + if object_dict.get('action'): + action = object_dict['action'] + convert = False + if ((object_dict['direction'] == 'bi' and + aci_type == 'vzRsSubjFiltAtt') or + (object_dict['direction'] == 'in' and + aci_type == 'vzRsFiltAtt__In') or + (object_dict['direction'] == 'out' and + aci_type == 'vzRsFiltAtt__Out')): + convert = True + + if convert: + result.append({aci_type: + {'attributes': + {'dn': dn, + 'action': action, + 'tnVzFilterName': + object_dict['filter_name']}}}) + return result + return func + + +def vzterm_converter(object_dict, otype, helper, source_identity_attributes, + destination_identity_attributes, to_aim=True): + result = [] + id_conv = (helper.get('identity_converter') or + default_identity_converter) + if to_aim: + pass + else: + aci_type = helper['resource'] + dn = id_conv(object_dict, otype, helper, + aci_mo_type=aci_type, to_aim=False)[0] + direction = '' + inGraph = '' + outGraph = '' + if object_dict.get('direction'): + direction = object_dict['direction'] + if object_dict.get('in_service_graph_name'): + inGraph = object_dict['in_service_graph_name'] + if object_dict.get('out_service_graph_name'): + outGraph = object_dict['out_service_graph_name'] + in_ = ('vzInTerm' in aci_type and + (direction == 'in' or inGraph)) + out = ('vzOutTerm' in aci_type and + (direction == 'out' or outGraph)) + if in_ or out: + result.append({aci_type: + {'attributes': + {'dn': dn}}}) + return result + # Resource map maps APIC objects into AIM ones. the key of this map is the # object APIC type, while the values contain the followings: # - Resource: AIM resource when direct mapping is applicable @@ -581,11 +656,11 @@ def bgp_extp_converter(object_dict, otype, helper, 'tnSpanVSrcGrpName') infraRsSpanVDestGrp_converter = child_list('span_vdest_group_names', 'tnSpanVDestGrpName') -vzRsSubjFiltAtt_converter = child_list('bi_filters', 'tnVzFilterName') -vzInTerm_vzRsFiltAtt_converter = child_list('in_filters', 'tnVzFilterName', - aci_mo='vzRsFiltAtt__In') -vzOutTerm_vzRsFiltAtt_converter = child_list('out_filters', 'tnVzFilterName', - aci_mo='vzRsFiltAtt__Out') +vzRsSubjFiltAtt_converter = rsFilt_converter('bi') +vzInTerm_vzRsFiltAtt_converter = rsFilt_converter('in', + aci_mo='vzRsFiltAtt__In') +vzOutTerm_vzRsFiltAtt_converter = rsFilt_converter('out', + aci_mo='vzRsFiltAtt__Out') fvRsProv_Ext_converter = child_list('provided_contract_names', 'tnVzBrCPName', aci_mo='fvRsProv__Ext') fvRsCons_Ext_converter = child_list('consumed_contract_names', 'tnVzBrCPName', @@ -788,7 +863,7 @@ def bgp_as_id_converter(object_dict, otype, helper, to_aim=True): 'out_service_graph_name'], }], 'vzRsSubjFiltAtt': [{ - 'resource': resource.ContractSubject, + 'resource': resource.ContractSubjectRsFilter, 'converter': vzRsSubjFiltAtt_converter }], 'vzRsSubjGraphAtt': [{ @@ -797,20 +872,18 @@ def bgp_as_id_converter(object_dict, otype, helper, to_aim=True): 'skip_if_empty': True}}, 'to_resource': default_to_resource_strict, }], - 'vzRsFiltAtt': [{'resource': resource.ContractSubject, + 'vzRsFiltAtt': [{'resource': resource.ContractSubjectRsFilter, 'converter': vzInTerm_vzRsFiltAtt_converter}, - {'resource': resource.ContractSubject, + {'resource': resource.ContractSubjectRsFilter, 'converter': vzOutTerm_vzRsFiltAtt_converter}], - 'vzInTerm': [{ - 'resource': resource.ContractSubject, - 'to_resource': to_resource_filter_container, - 'skip': ['display_name'] - }], - 'vzOutTerm': [{ - 'resource': resource.ContractSubject, - 'to_resource': to_resource_filter_container, - 'skip': ['display_name'] - }], + 'vzInTerm': [{'resource': resource.ContractSubject, + 'converter': vzterm_converter}, + {'resource': resource.ContractSubjectRsFilter, + 'converter': vzterm_converter}], + 'vzOutTerm': [{'resource': resource.ContractSubject, + 'converter': vzterm_converter}, + {'resource': resource.ContractSubjectRsFilter, + 'converter': vzterm_converter}], 'vzRsInTermGraphAtt': [{ 'resource': resource.ContractSubject, 'exceptions': {'tnVnsAbsGraphName': diff --git a/aim/aim_lib/nat_strategy.py b/aim/aim_lib/nat_strategy.py index f94a1a2b..37f4d2d5 100644 --- a/aim/aim_lib/nat_strategy.py +++ b/aim/aim_lib/nat_strategy.py @@ -479,8 +479,13 @@ def _get_nat_objects(self, ctx, l3out): subject = resource.ContractSubject( tenant_name=contract.tenant_name, contract_name=contract.name, - name='Allow', display_name='Allow', - bi_filters=[fltr.name]) + name='Allow', display_name='Allow') + subject_filter = resource.ContractSubjectRsFilter( + tenant_name=contract.tenant_name, + contract_name=contract.name, + contract_subject_name='Allow', + filter_name=fltr.name, + direction='bi') bd = self._get_nat_bd(ctx, l3out) bd.vrf_name = l3out.vrf_name ap, epg = self._get_nat_ap_epg(ctx, l3out) @@ -497,7 +502,7 @@ def _get_nat_objects(self, ctx, l3out): epg.consumed_contract_names = [contract.name] epg.vmm_domains = vm_doms epg.physical_domains = phy_doms - return [fltr, entry, contract, subject, bd, ap, epg] + return [fltr, entry, contract, subject, subject_filter, bd, ap, epg] def _select_domains(self, objs, vmm_domains=None, phys_domains=None): for obj in objs: diff --git a/aim/aim_manager.py b/aim/aim_manager.py index 478ff7c5..bfdfa022 100644 --- a/aim/aim_manager.py +++ b/aim/aim_manager.py @@ -64,6 +64,7 @@ class AimManager(object): api_res.FilterEntry, api_res.Contract, api_res.ContractSubject, + api_res.ContractSubjectRsFilter, api_status.AciStatus, api_status.AciFault, api_res.Endpoint, diff --git a/aim/aim_store.py b/aim/aim_store.py index 00e6ec93..07947e68 100644 --- a/aim/aim_store.py +++ b/aim/aim_store.py @@ -209,6 +209,8 @@ class SqlAlchemyStore(AimStore): api_res.FilterEntry: models.FilterEntry, api_res.Contract: models.Contract, api_res.ContractSubject: models.ContractSubject, + api_res.ContractSubjectRsFilter: + models.ContractSubjectRsFilter, api_status.AciStatus: status_model.Status, api_status.AciFault: status_model.Fault, api_res.Endpoint: models.Endpoint, diff --git a/aim/api/resource.py b/aim/api/resource.py index 6c3ab058..b9812887 100644 --- a/aim/api/resource.py +++ b/aim/api/resource.py @@ -671,6 +671,34 @@ def __init__(self, **kwargs): 'monitored': False}, **kwargs) +class ContractSubjectRsFilter(AciResourceBase): + """Resource representing a subject within a contract in ACI. + + Identity attributes: name of ACI tenant, name of contract and + name of subject. + """ + + identity_attributes = t.identity( + ('tenant_name', t.name), + ('contract_name', t.name), + ('contract_subject_name', t.name), + ('filter_name', t.name)) + other_attributes = t.other( + ('direction', t.enum('bi', 'in', 'out')), + ('display_name', t.name), + ('action', t.enum('permit', 'deny')), + ('monitored', t.bool)) + + _aci_mo_name = 'vzRsSubjFiltAtt' + _tree_parent = ContractSubject + + def __init__(self, **kwargs): + super(ContractSubjectRsFilter, self).__init__({'action': 'permit', + 'direction': 'bi', + 'monitored': False}, + **kwargs) + + class Endpoint(ResourceBase): """Resource representing an endpoint. diff --git a/aim/db/migration/alembic_migrations/versions/185efc4806b2_contract_subject_filter.py b/aim/db/migration/alembic_migrations/versions/185efc4806b2_contract_subject_filter.py new file mode 100644 index 00000000..20b925de --- /dev/null +++ b/aim/db/migration/alembic_migrations/versions/185efc4806b2_contract_subject_filter.py @@ -0,0 +1,64 @@ +# Copyright (c) 2017 Cisco Systems +# 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. + + +""" + +Revision ID: 185efc4806b2 +Revises: 2f05b6baf008 +Create Date: 2021-02-23 12:04:35.098964 + +""" + +# revision identifiers, used by Alembic. +revision = '185efc4806b2' +down_revision = '2f05b6baf008' +branch_labels = None +depends_on = None + +from aim.common import utils +from aim.db.migration.data_migration import contract_subject_filter +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table( + 'aim_contract_subject_filter_relation', + sa.Column('aim_id', sa.String(255), default=utils.generate_uuid), + sa.Column('filter_name', sa.String(64), nullable=False), + sa.Column('contract_name', sa.String(64), nullable=False), + sa.Column('contract_subject_name', sa.String(64), nullable=False), + sa.Column('tenant_name', sa.String(64), nullable=False), + sa.Column('display_name', sa.String(256), nullable=False, default=''), + sa.Column('monitored', sa.Boolean, nullable=False, default=False), + sa.Column('epoch', sa.BigInteger(), nullable=False, + server_default='0'), + sa.Column('direction', sa.Enum('bi', 'in', 'out')), + sa.Column('action', sa.Enum('permit', 'deny'), default='permit'), + sa.PrimaryKeyConstraint('aim_id'), + sa.UniqueConstraint('tenant_name', 'contract_name', + 'contract_subject_name', + 'filter_name', 'direction', + name='uniq_aim_contract_subjects_filter_identity'), + sa.Index('idx_aim_contract_subjects_filter_identity', + 'tenant_name', 'contract_name', 'contract_subject_name', + 'filter_name', 'direction')) + session = sa.orm.Session(bind=op.get_bind(), autocommit=True) + contract_subject_filter.migrate(session) + + +def downgrade(): + pass diff --git a/aim/db/migration/data_migration/contract_subject_filter.py b/aim/db/migration/data_migration/contract_subject_filter.py new file mode 100644 index 00000000..c137997e --- /dev/null +++ b/aim/db/migration/data_migration/contract_subject_filter.py @@ -0,0 +1,75 @@ +# Copyright (c) 2018 Cisco Systems +# 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. + +from aim.common import utils +import sqlalchemy as sa +from sqlalchemy.ext import declarative +from sqlalchemy import orm + +Base = declarative.declarative_base() + +ContractSubjectRsFilter = sa.Table( + 'aim_contract_subject_filter_relation', sa.MetaData(), + sa.Column('aim_id', sa.String(255), + default=utils.generate_uuid, primary_key=True), + sa.Column('tenant_name', sa.String(64), primary_key=True), + sa.Column('contract_name', sa.String(64), primary_key=True), + sa.Column('contract_subject_name', sa.String(64), primary_key=True), + sa.Column('filter_name', sa.String(64), primary_key=True), + sa.Column('direction', sa.Enum('bi', 'in', 'out', primary_key=True)), + sa.Column('display_name', sa.String(64)), + sa.Column('monitored', sa.Boolean), + sa.Column('action', sa.Enum('permit', 'deny')) +) + + +class ContractSubjectFilter(Base): + __tablename__ = 'aim_contract_subject_filters' + subject_aim_id = sa.Column(sa.Integer, + sa.ForeignKey('aim_contract_subjects.aim_id'), + primary_key=True) + name = sa.Column(sa.String(64), primary_key=True) + direction = sa.Column(sa.Enum('bi', 'in', 'out'), primary_key=True) + + +class ContractSubject(Base): + __tablename__ = 'aim_contract_subjects' + aim_id = sa.Column(sa.Integer, primary_key=True) + tenant_name = sa.Column(sa.String(64), primary_key=True) + contract_name = sa.Column(sa.String(64), primary_key=True) + name = sa.Column(sa.String(64), primary_key=True) + filters = orm.relationship(ContractSubjectFilter, + backref='contract', + cascade='all, delete-orphan', + lazy='joined') + + +def migrate(session): + with session.begin(subtransactions=True): + migrations = [] + contract_subjects = session.query(ContractSubject).all() + for subj in contract_subjects: + for flt in subj.filters: + migrations.append({'tenant_name': subj.tenant_name, + 'contract_name': subj.contract_name, + 'contract_subject_name': subj.name, + 'filter_name': flt.name, + 'direction': flt.direction, + 'display_name': '', + 'monitored': False, + 'action': 'permit'}) + if migrations: + session.execute(ContractSubjectRsFilter.insert().values( + migrations)) diff --git a/aim/db/models.py b/aim/db/models.py index 4d35a871..896e22d3 100644 --- a/aim/db/models.py +++ b/aim/db/models.py @@ -574,6 +574,26 @@ def to_attr(self, session): return res_attr +class ContractSubjectRsFilter(model_base.Base, model_base.HasAimId, + model_base.HasTenantName, + model_base.HasDisplayName, + model_base.AttributeMixin, + model_base.IsMonitored): + """DB model for filters used by Contract Subject.""" + __tablename__ = 'aim_contract_subject_filter_relation' + __table_args__ = ( + model_base.uniq_column(__tablename__, 'tenant_name', 'contract_name', + 'contract_subject_name', + 'filter_name', 'direction') + + model_base.to_tuple(model_base.Base.__table_args__)) + + contract_name = model_base.name_column(nullable=False) + contract_subject_name = model_base.name_column(nullable=False) + filter_name = sa.Column(sa.String(64), nullable=False) + direction = sa.Column(sa.Enum('bi', 'in', 'out'), nullable=False) + action = sa.Column(sa.Enum('permit', 'deny')) + + class Endpoint(model_base.Base, model_base.HasDisplayName, model_base.AttributeMixin): """DB model for Endpoint.""" diff --git a/aim/tests/unit/agent/aid_universes/test_aci_universe.py b/aim/tests/unit/agent/aid_universes/test_aci_universe.py index b499386f..3617fad0 100644 --- a/aim/tests/unit/agent/aid_universes/test_aci_universe.py +++ b/aim/tests/unit/agent/aid_universes/test_aci_universe.py @@ -253,13 +253,16 @@ def test_get_resources(self): 'dn': 'uni/tn-t1/brc-c/subj-s/outtmnl'}}}, {'vzRsSubjFiltAtt': {'attributes': { 'dn': 'uni/tn-t1/brc-c/subj-s/rssubjFiltAtt-f', - 'tnVzFilterName': 'f'}}}, + 'tnVzFilterName': 'f', + 'action': 'permit'}}}, {'vzRsFiltAtt': {'attributes': { 'dn': 'uni/tn-t1/brc-c/subj-s/intmnl/rsfiltAtt-g', - 'tnVzFilterName': 'g'}}}, + 'tnVzFilterName': 'g', + 'action': 'permit'}}}, {'vzRsFiltAtt': {'attributes': { 'dn': 'uni/tn-t1/brc-c/subj-s/outtmnl/rsfiltAtt-h', - 'tnVzFilterName': 'h'}}}] + 'tnVzFilterName': 'h', + 'action': 'permit'}}}] self._add_data_to_tree(objs, self.backend_state) keys = [('fvTenant|t1', 'fvAp|a1', 'fvAEPg|test', 'faultInst|951'), ('fvTenant|test-tenant', 'fvBD|test'), diff --git a/aim/tests/unit/agent/aid_universes/test_aim_db_universe.py b/aim/tests/unit/agent/aid_universes/test_aim_db_universe.py index ecacd792..cba93de9 100644 --- a/aim/tests/unit/agent/aid_universes/test_aim_db_universe.py +++ b/aim/tests/unit/agent/aid_universes/test_aim_db_universe.py @@ -323,8 +323,8 @@ def test_push_resources(self): subj = resource.ContractSubject(tenant_name='t1', contract_name='c', name='s2') status = aim_mgr.get_status(self.ctx, subj) - self.assertEqual(3, len(status.faults)) - self.assertEqual(['F1111', 'F1112', 'F1113'], + self.assertEqual(2, len(status.faults)) + self.assertEqual(['F1111', 'F1112'], [f.fault_code for f in status.faults]) # delete filter faults diff --git a/aim/tests/unit/agent/aid_universes/test_converter.py b/aim/tests/unit/agent/aid_universes/test_converter.py index 8d841b81..381385bb 100644 --- a/aim/tests/unit/agent/aid_universes/test_converter.py +++ b/aim/tests/unit/agent/aid_universes/test_converter.py @@ -638,27 +638,16 @@ class TestAciToAimConverterContractSubject(TestAciToAimConverterBase, 'skip': ['inFilters', 'outFilters', 'biFilters', 'serviceGraphName', 'inServiceGraphName', 'outServiceGraphName']}, - {'resource': 'vzRsSubjFiltAtt', - 'exceptions': {}, - 'converter': converter.vzRsSubjFiltAtt_converter}, {'resource': 'vzRsSubjGraphAtt', 'exceptions': {'service_graph_name': {'other': 'tnVnsAbsGraphName', 'skip_if_empty': True}}, 'to_resource': converter.default_to_resource_strict}, - {'resource': 'vzRsFiltAtt', - 'exceptions': {}, - 'converter': converter.vzInTerm_vzRsFiltAtt_converter}, - {'resource': 'vzRsFiltAtt', - 'exceptions': {}, - 'converter': converter.vzOutTerm_vzRsFiltAtt_converter}, {'resource': 'vzInTerm', 'exceptions': {}, - 'skip': ['displayName'], - 'to_resource': converter.to_resource_filter_container}, + 'converter': converter.vzterm_converter}, {'resource': 'vzOutTerm', 'exceptions': {}, - 'skip': ['displayName'], - 'to_resource': converter.to_resource_filter_container}, + 'converter': converter.vzterm_converter}, {'resource': 'vzRsInTermGraphAtt', 'exceptions': {'in_service_graph_name': {'other': 'tnVnsAbsGraphName', @@ -670,12 +659,6 @@ class TestAciToAimConverterContractSubject(TestAciToAimConverterBase, 'skip_if_empty': True}}, 'to_resource': converter.default_to_resource_strict}] sample_input = [[get_example_aci_subject(nameAlias='alias'), - _aci_obj('vzRsSubjFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/rssubjFiltAtt-f1', - tnVzFilterName='f1'), - _aci_obj('vzRsSubjFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/rssubjFiltAtt-f2', - tnVzFilterName='f2'), _aci_obj('vzRsSubjGraphAtt', dn='uni/tn-t1/brc-c/subj-s/rsSubjGraphAtt', tnVnsAbsGraphName='g1'), @@ -683,18 +666,6 @@ class TestAciToAimConverterContractSubject(TestAciToAimConverterBase, dn='uni/tn-t1/brc-c/subj-s/intmnl'), _aci_obj('vzOutTerm', dn='uni/tn-t1/brc-c/subj-s/outtmnl'), - _aci_obj('vzRsFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/intmnl/rsfiltAtt-i1', - tnVzFilterName='i1'), - _aci_obj('vzRsFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/intmnl/rsfiltAtt-i2', - tnVzFilterName='i2'), - _aci_obj('vzRsFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/outtmnl/rsfiltAtt-o1', - tnVzFilterName='o1'), - _aci_obj('vzRsFiltAtt', - dn='uni/tn-t1/brc-c/subj-s/outtmnl/rsfiltAtt-o2', - tnVzFilterName='o2'), _aci_obj('vzRsInTermGraphAtt', dn='uni/tn-t1/brc-c/subj-s/intmnl/' 'rsInTermGraphAtt', @@ -702,60 +673,75 @@ class TestAciToAimConverterContractSubject(TestAciToAimConverterBase, _aci_obj('vzRsOutTermGraphAtt', dn='uni/tn-t1/brc-c/subj-s/outtmnl/' 'rsOutTermGraphAtt', - tnVnsAbsGraphName='g3'), ], - [{'vzSubj': { + tnVnsAbsGraphName='g3')], + [{'vzSubj': {'attributes': { + 'dn': 'uni/tn-common/brc-prs1/subj-prs1', + 'revFltPorts': 'yes', 'nameAlias': 'prs1', + 'name': 'prs1', 'prio': 'unspecified', + 'targetDscp': 'unspecified', 'descr': '', + 'consMatchT': 'AtleastOne', + 'provMatchT': 'AtleastOne'}}}]] + sample_output = [ + resource.ContractSubject(tenant_name='t1', contract_name='c', name='s', + in_filters=[], + out_filters=[], + bi_filters=[], + service_graph_name='g1', + in_service_graph_name='g2', + out_service_graph_name='g3', + display_name='alias'), + resource.ContractSubject(tenant_name='common', contract_name='prs1', + name='prs1', + display_name='prs1')] + + +class TestAciToAimConverterContractSubjectRsFilter(TestAciToAimConverterBase, + base.TestAimDBBase): + resource_type = resource.ContractSubjectRsFilter + reverse_map_output = [ + {'resource': 'vzRsSubjFiltAtt', + 'exceptions': {}, + 'converter': converter.vzRsSubjFiltAtt_converter}, + {'resource': 'vzRsFiltAtt', + 'exceptions': {}, + 'converter': converter.vzInTerm_vzRsFiltAtt_converter}, + {'resource': 'vzRsFiltAtt', + 'exceptions': {}, + 'converter': converter.vzOutTerm_vzRsFiltAtt_converter}, + {'resource': 'vzInTerm', + 'exceptions': {}, + 'converter': converter.vzterm_converter}, + {'resource': 'vzOutTerm', + 'exceptions': {}, + 'converter': converter.vzterm_converter}] + sample_input = [[{"vzRsSubjFiltAtt": { + "attributes": {"action": "permit", + "dn": "uni/tn-common/brc-prs1/" + "subj-prs1/rssubjFiltAtt-f1", + "tnVzFilterName": "f1"}}}], + [{'vzRsFiltAtt': { 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1', - 'revFltPorts': 'yes', 'nameAlias': 'prs1', - 'name': 'prs1', 'prio': 'unspecified', - 'targetDscp': 'unspecified', 'descr': '', - 'consMatchT': 'AtleastOne', - 'provMatchT': 'AtleastOne'}}}, - {'vzRsFiltAtt': { - 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1/intmnl' - '/rsfiltAtt-reverse-pr1', 'directives': '', - 'tnVzFilterName': 'reverse-pr1'}}}, - {'vzRsFiltAtt': { - 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1/intmnl' - '/rsfiltAtt-pr1', - 'directives': '', 'tnVzFilterName': 'pr1'}}}, - {'vzRsFiltAtt': { - 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1/outtmnl' - '/rsfiltAtt-pr1', 'directives': '', - 'tnVzFilterName': 'pr1'}}}, - {'vzRsFiltAtt': { - 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1/outtmnl' - '/rsfiltAtt-reverse-pr1', 'directives': '', - 'tnVzFilterName': 'reverse-pr1'}}}, + 'dn': 'uni/tn-common/brc-prs1/subj-prs1/intmnl/' + 'rsfiltAtt-reverse-pr1', + 'tnVzFilterName': 'reverse-pr1', + 'action': 'deny'}}}, {'vzInTerm': { 'attributes': { 'dn': 'uni/tn-common/brc-prs1/subj-prs1/intmnl', 'nameAlias': '', 'name': '', 'descr': '', 'targetDscp': 'unspecified', - 'prio': 'unspecified'}}}, - {'vzOutTerm': { - 'attributes': { - 'dn': 'uni/tn-common/brc-prs1/subj-prs1/outtmnl', - 'nameAlias': '', 'name': '', 'descr': '', - 'targetDscp': 'unspecified', 'prio': 'unspecified'}}}]] sample_output = [ - resource.ContractSubject(tenant_name='t1', contract_name='c', name='s', - in_filters=['i1', 'i2'], - out_filters=['o1', 'o2'], - bi_filters=['f1', 'f2'], - service_graph_name='g1', - in_service_graph_name='g2', - out_service_graph_name='g3', - display_name='alias'), - resource.ContractSubject(tenant_name='common', contract_name='prs1', - name='prs1', display_name='prs1', - in_filters=['pr1', 'reverse-pr1'], - out_filters=['pr1', 'reverse-pr1'])] + resource.ContractSubjectRsFilter(tenant_name='common', + contract_name='prs1', + contract_subject_name='prs1', + filter_name='f1', + action='permit', direction='bi'), + resource.ContractSubjectRsFilter(tenant_name='common', + contract_name='prs1', + contract_subject_name='prs1', + filter_name='reverse-pr1', + action='deny', direction='in')] class TestAciToAimConverterFault(TestAciToAimConverterBase, @@ -2643,10 +2629,7 @@ def get_example_aim_contract_subject(**kwargs): class TestAimToAciConverterContractSubject(TestAimToAciConverterBase, base.TestAimDBBase): sample_input = [ - get_example_aim_contract_subject(in_filters=['i1', 'i2'], - out_filters=['o1', 'o2'], - bi_filters=['f1', 'f2'], - service_graph_name='g1', + get_example_aim_contract_subject(service_graph_name='g1', in_service_graph_name='g2', display_name='alias'), get_example_aim_contract_subject(name='s2', @@ -2654,31 +2637,11 @@ class TestAimToAciConverterContractSubject(TestAimToAciConverterBase, sample_output = [ [_aci_obj('vzSubj', dn='uni/tn-test-tenant/brc-c1/subj-s1', nameAlias='alias'), - _aci_obj('vzRsSubjFiltAtt', - dn='uni/tn-test-tenant/brc-c1/subj-s1/rssubjFiltAtt-f1', - tnVzFilterName='f1'), - _aci_obj('vzRsSubjFiltAtt', - dn='uni/tn-test-tenant/brc-c1/subj-s1/rssubjFiltAtt-f2', - tnVzFilterName='f2'), _aci_obj('vzRsSubjGraphAtt', dn='uni/tn-test-tenant/brc-c1/subj-s1/rsSubjGraphAtt', tnVnsAbsGraphName='g1'), - _aci_obj('vzRsFiltAtt__In', - dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl/rsfiltAtt-i1', - tnVzFilterName='i1'), - _aci_obj('vzRsFiltAtt__In', - dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl/rsfiltAtt-i2', - tnVzFilterName='i2'), - _aci_obj('vzRsFiltAtt__Out', - dn='uni/tn-test-tenant/brc-c1/subj-s1/outtmnl/rsfiltAtt-o1', - tnVzFilterName='o1'), - _aci_obj('vzRsFiltAtt__Out', - dn='uni/tn-test-tenant/brc-c1/subj-s1/outtmnl/rsfiltAtt-o2', - tnVzFilterName='o2'), _aci_obj('vzInTerm', dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl'), - _aci_obj('vzOutTerm', - dn='uni/tn-test-tenant/brc-c1/subj-s1/outtmnl'), _aci_obj('vzRsInTermGraphAtt', dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl/' 'rsInTermGraphAtt', @@ -2693,6 +2656,53 @@ class TestAimToAciConverterContractSubject(TestAimToAciConverterBase, tnVnsAbsGraphName='g3')]] +def get_example_aim_contract_subject_filter(**kwargs): + example = resource.ContractSubjectRsFilter(tenant_name='test-tenant', + contract_name='c1', + contract_subject_name='s1', + filter_name='f1') + example.__dict__.update(kwargs) + return example + + +class TestAimToAciConverterContractSubjectRsFilter( + TestAimToAciConverterBase, + base.TestAimDBBase): + sample_input = [ + get_example_aim_contract_subject_filter(direction='bi', + action='permit'), + get_example_aim_contract_subject_filter(filter_name='f2', + direction='bi', + action='deny'), + get_example_aim_contract_subject_filter(filter_name='i1', + direction='in', + action='permit'), + get_example_aim_contract_subject_filter(filter_name='o1', + direction='out', + action='deny')] + sample_output = [ + [_aci_obj('vzRsSubjFiltAtt', + dn='uni/tn-test-tenant/brc-c1/subj-s1/rssubjFiltAtt-f1', + tnVzFilterName='f1', + action='permit')], + [_aci_obj('vzRsSubjFiltAtt', + dn='uni/tn-test-tenant/brc-c1/subj-s1/rssubjFiltAtt-f2', + tnVzFilterName='f2', + action='deny')], + [_aci_obj('vzInTerm', + dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl'), + _aci_obj('vzRsFiltAtt__In', + dn='uni/tn-test-tenant/brc-c1/subj-s1/intmnl/rsfiltAtt-i1', + tnVzFilterName='i1', + action='permit')], + [_aci_obj('vzRsFiltAtt__Out', + dn='uni/tn-test-tenant/brc-c1/subj-s1/outtmnl/rsfiltAtt-o1', + tnVzFilterName='o1', + action='deny'), + _aci_obj('vzOutTerm', + dn='uni/tn-test-tenant/brc-c1/subj-s1/outtmnl')]] + + def get_example_aim_l3outside(**kwargs): example = resource.L3Outside(tenant_name='t1', name='inet1') diff --git a/aim/tests/unit/agent/test_agent.py b/aim/tests/unit/agent/test_agent.py index 8353f458..c14d4178 100644 --- a/aim/tests/unit/agent/test_agent.py +++ b/aim/tests/unit/agent/test_agent.py @@ -1364,11 +1364,18 @@ def test_create_delete(self): name='rtr_fb8f33cf-fe9c-48a9-a7b2-aa35ac63f189') sub = resource.ContractSubject( tenant_name=tenant_name, contract_name=ctr.name, - name='route', bi_filters=['noirolab_AnyFilter']) + name='route') + subflt = resource.ContractSubjectRsFilter( + tenant_name=tenant_name, contract_name=ctr.name, + contract_subject_name=sub.name, + filter_name='noirolab_AnyFilter', + direction='bi') with self.ctx.store.begin(subtransactions=True): self.aim_manager.create(self.ctx, ctr) self.aim_manager.create(self.ctx, sub) + self.aim_manager.create(self.ctx, subflt) with self.ctx.store.begin(subtransactions=True): + self.aim_manager.delete(self.ctx, subflt) self.aim_manager.delete(self.ctx, sub) self.aim_manager.delete(self.ctx, ctr) desired_config.observe(self.ctx) diff --git a/aim/tests/unit/aim_lib/test_nat_strategy.py b/aim/tests/unit/aim_lib/test_nat_strategy.py index 086bba6f..1c5e38c9 100644 --- a/aim/tests/unit/aim_lib/test_nat_strategy.py +++ b/aim/tests/unit/aim_lib/test_nat_strategy.py @@ -93,8 +93,10 @@ def _get_l3out_objects(self, l3out_name=None, l3out_display_name=None, a_res.Contract(tenant_name='t1', name=name, display_name=d_name), a_res.ContractSubject(tenant_name='t1', contract_name=name, - name='Allow', display_name='Allow', - bi_filters=[name]), + name='Allow', display_name='Allow'), + a_res.ContractSubjectRsFilter(tenant_name='t1', contract_name=name, + contract_subject_name='Allow', + filter_name=name, direction='bi'), a_res.BridgeDomain(tenant_name='t1', name=name, display_name=d_name, vrf_name=nat_vrf_name or name, diff --git a/aim/tests/unit/test_aim_manager.py b/aim/tests/unit/test_aim_manager.py index cc015814..d75fa533 100644 --- a/aim/tests/unit/test_aim_manager.py +++ b/aim/tests/unit/test_aim_manager.py @@ -1014,16 +1014,11 @@ class TestContractSubjectMixin(object): test_required_attributes = {'tenant_name': 'tenant1', 'contract_name': 'contract1', 'name': 'subject1', - 'in_filters': ['f1', 'f2'], - 'out_filters': ['f2', 'f3'], - 'bi_filters': ['f1', 'f3', 'f4'], 'service_graph_name': 'g1', 'in_service_graph_name': 'g2', 'out_service_graph_name': 'g3'} test_search_attributes = {'name': 'subject1'} - test_update_attributes = {'in_filters': ['f1', 'f2', 'f3'], - 'out_filters': [], - 'service_graph_name': 'g11', + test_update_attributes = {'service_graph_name': 'g11', 'in_service_graph_name': 'g21', 'out_service_graph_name': 'g31'} test_default_values = {'in_filters': [], @@ -1036,6 +1031,30 @@ class TestContractSubjectMixin(object): res_command = 'contract-subject' +class TestContractSubjectRsFilterMixin(object): + resource_class = resource.ContractSubjectRsFilter + resource_root_type = resource.Tenant._aci_mo_name + prereq_objects = [ + resource.Tenant(name='tenant1'), + resource.Contract(tenant_name='tenant1', name='contract1')] + test_identity_attributes = {'tenant_name': 'tenant1', + 'contract_name': 'contract1', + 'contract_subject_name': 'subject1', + 'filter_name': 'filter1'} + test_required_attributes = {'tenant_name': 'tenant1', + 'contract_name': 'contract1', + 'contract_subject_name': 'subject1', + 'filter_name': 'filter1', + 'action': 'permit', + 'direction': 'bi'} + test_search_attributes = {'filter_name': 'filter1'} + test_update_attributes = {'action': 'deny'} + test_default_values = {'action': 'permit'} + test_dn = \ + 'uni/tn-tenant1/brc-contract1/subj-subject1/rssubjFiltAtt-filter1' + res_command = 'contract-subject-rs-filter' + + class TestEndpointMixin(object): resource_class = resource.Endpoint prereq_objects = [ @@ -2529,6 +2548,11 @@ class TestContractSubject(TestContractSubjectMixin, TestAciResourceOpsBase, pass +class TestContractSubjectRsFilter(TestContractSubjectRsFilterMixin, + TestAciResourceOpsBase, base.TestAimDBBase): + pass + + class TestEndpoint(TestEndpointMixin, TestResourceOpsBase, base.TestAimDBBase): pass diff --git a/aim/tests/unit/test_hashtree_db_listener.py b/aim/tests/unit/test_hashtree_db_listener.py index a0f2c4c6..7c9104dc 100644 --- a/aim/tests/unit/test_hashtree_db_listener.py +++ b/aim/tests/unit/test_hashtree_db_listener.py @@ -345,11 +345,27 @@ def test_subject_related_objects(self): self.ctx, aim_res.Contract(tenant_name='common', name='c-name')) subj = aim_res.ContractSubject( **{'contract_name': 'c-name', - 'out_filters': ['pr_1', 'reverse-pr_1', 'pr_2', 'reverse-pr_2'], 'name': 's-name', - 'tenant_name': 'common', 'monitored': False, 'bi_filters': [], - 'in_filters': ['pr_1', 'reverse-pr_1', 'pr_2', 'reverse-pr_2']}) + 'tenant_name': 'common', 'monitored': False}) subj = self.mgr.create(self.ctx, subj) + + subj_flt = aim_res.ContractSubjectRsFilter( + **{'contract_name': 'c-name', + 'contract_subject_name': 's-name', + 'tenant_name': 'common', + 'monitored': False, + 'filter_name': 'pr_1', + 'direction': 'out'}) + subj_flt = self.mgr.create(self.ctx, subj_flt) + + subj_flt1 = aim_res.ContractSubjectRsFilter( + **{'contract_name': 'c-name', + 'contract_subject_name': 's-name', + 'tenant_name': 'common', + 'monitored': False, + 'filter_name': 'pr_1', + 'direction': 'in'}) + subj_flt1 = self.mgr.create(self.ctx, subj_flt1) cfg_tree = self.tt_mgr.get(self.ctx, 'tn-common', tree=tree_manager.CONFIG_TREE) # verify pr_1 and its reverse are in the tree @@ -358,22 +374,17 @@ def test_subject_related_objects(self): "vzOutTerm|outtmnl", "vzRsFiltAtt|pr_1")) rev_pr_1 = cfg_tree.find( ("fvTenant|common", "vzBrCP|c-name", "vzSubj|s-name", - "vzOutTerm|outtmnl", "vzRsFiltAtt|reverse-pr_1")) + "vzInTerm|intmnl", "vzRsFiltAtt|pr_1")) self.assertIsNotNone(pr_1) self.assertIsNotNone(rev_pr_1) - self.mgr.update(self.ctx, subj, out_filters=['pr_2', 'reverse-pr_2'], - in_filters=['pr_2', 'reverse-pr_2']) + self.mgr.update(self.ctx, subj_flt1, action='deny') cfg_tree = self.tt_mgr.get(self.ctx, 'tn-common', tree=tree_manager.CONFIG_TREE) - pr_1 = cfg_tree.find( - ("fvTenant|common", "vzBrCP|c-name", "vzSubj|s-name", - "vzOutTerm|outtmnl", "vzRsFiltAtt|pr_1")) rev_pr_1 = cfg_tree.find( ("fvTenant|common", "vzBrCP|c-name", "vzSubj|s-name", - "vzOutTerm|outtmnl", "vzRsFiltAtt|reverse-pr_1")) - self.assertIsNone(pr_1) - self.assertIsNone(rev_pr_1) + "vzInTerm|intmnl", "vzRsFiltAtt|pr_1")) + self.assertIsNotNone(rev_pr_1) def test_delete_all_trees(self): self.mgr.create(self.ctx, aim_res.Tenant(name='common')) diff --git a/aim/tests/unit/test_structured_hash_tree.py b/aim/tests/unit/test_structured_hash_tree.py index 8e3dc950..22d7a684 100644 --- a/aim/tests/unit/test_structured_hash_tree.py +++ b/aim/tests/unit/test_structured_hash_tree.py @@ -925,23 +925,36 @@ def test_update_1(self): exp_tree = tree.StructuredHashTree() subj = resource.ContractSubject(tenant_name='t1', contract_name='c1', - name='s1', in_filters=['i1'], - out_filters=['o1'], bi_filters=['f1']) - self.maker.update(htree, [subj]) + name='s1') + subj_flt = resource.ContractSubjectRsFilter( + tenant_name='t1', contract_name='c1', + contract_subject_name='s1', filter_name='f1', + direction='bi') + subj_flt_in = resource.ContractSubjectRsFilter( + tenant_name='t1', contract_name='c1', + contract_subject_name='s1', filter_name='i1', + direction='in') + subj_flt_out = resource.ContractSubjectRsFilter( + tenant_name='t1', contract_name='c1', contract_subject_name='s1', + filter_name='o1', direction='out') + self.maker.update(htree, [subj, subj_flt, subj_flt_in, subj_flt_out]) exp_tree = exp_tree.add(('fvTenant|t1', 'vzBrCP|c1', 'vzSubj|s1'), nameAlias='') exp_tree = exp_tree.add( ('fvTenant|t1', 'vzBrCP|c1', 'vzSubj|s1', 'vzInTerm|intmnl', 'vzRsFiltAtt|i1'), - tnVzFilterName='i1') + **{'tnVzFilterName': 'i1', + 'action': 'permit'}) exp_tree = exp_tree.add( ('fvTenant|t1', 'vzBrCP|c1', 'vzSubj|s1', 'vzOutTerm|outtmnl', 'vzRsFiltAtt|o1'), - tnVzFilterName='o1') + **{'tnVzFilterName': 'o1', + 'action': 'permit'}) exp_tree = exp_tree.add( ('fvTenant|t1', 'vzBrCP|c1', 'vzSubj|s1', 'vzRsSubjFiltAtt|f1'), - tnVzFilterName='f1') + **{'tnVzFilterName': 'f1', + 'action': 'permit'}) exp_tree = exp_tree.add( ('fvTenant|t1', 'vzBrCP|c1', 'vzSubj|s1', 'vzInTerm|intmnl')) exp_tree = exp_tree.add( diff --git a/aim/tests/unit/tools/cli/test_manager.py b/aim/tests/unit/tools/cli/test_manager.py index 356d8fd5..5cd9a125 100644 --- a/aim/tests/unit/tools/cli/test_manager.py +++ b/aim/tests/unit/tools/cli/test_manager.py @@ -692,6 +692,13 @@ class TestContractSubject(test_aim_manager.TestContractSubjectMixin, pass +class TestContractSubjectiRsFilter( + test_aim_manager.TestContractSubjectRsFilterMixin, + TestManagerResourceOpsBase, + base.TestShell): + pass + + class TestEndpoint(test_aim_manager.TestEndpointMixin, TestManagerResourceOpsBase, base.TestShell):