Skip to content

Commit

Permalink
Deny action support for contracts
Browse files Browse the repository at this point in the history
A new AIM resource ContractSubjectRsFilter is introduced as a child of
ContractSubject, which replaces bifilter, infilter and outfilter fields

Direction field in the resource will control the in,out and bi types, and
action to store permit/deny
  • Loading branch information
mpaidipa-aci committed Feb 25, 2021
1 parent dfff338 commit 4dab736
Show file tree
Hide file tree
Showing 17 changed files with 500 additions and 155 deletions.
111 changes: 92 additions & 19 deletions aim/agent/aid/universes/aci/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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': [{
Expand All @@ -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':
Expand Down
11 changes: 8 additions & 3 deletions aim/aim_lib/nat_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions aim/aim_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions aim/aim_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 28 additions & 0 deletions aim/api/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions aim/db/migration/data_migration/contract_subject_filter.py
Original file line number Diff line number Diff line change
@@ -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))
20 changes: 20 additions & 0 deletions aim/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading

0 comments on commit 4dab736

Please sign in to comment.