Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions plugins/module_utils/aci.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,42 @@ def expression_spec():
)


def aci_contract_qos_spec():
return dict(type="str", choices=["level1", "level2", "level3", "unspecified"])


def aci_contract_dscp_spec(direction=None):
return dict(
type="str",
aliases=["target" if not direction else "target_{0}".format(direction)],
choices=[
"AF11",
"AF12",
"AF13",
"AF21",
"AF22",
"AF23",
"AF31",
"AF32",
"AF33",
"AF41",
"AF42",
"AF43",
"CS0",
"CS1",
"CS2",
"CS3",
"CS4",
"CS5",
"CS6",
"CS7",
"EF",
"VA",
"unspecified"
],
)


class ACIModule(object):
def __init__(self, module):
self.module = module
Expand Down Expand Up @@ -1369,12 +1405,18 @@ def post_config(self):
self.result["changed"] = True
self.method = "POST"

def exit_json(self, **kwargs):
def exit_json(self, filter_existing=None, **kwargs):
"""
:param filter_existing: tuple consisting of the function at (index 0) and the args at (index 1)
CAUTION: the function should always take in self.existing in its first parameter
:param kwargs: kwargs to be passed to ansible module exit_json()
filter_existing is not passed via kwargs since it cant handle function type and should not be exposed to user
"""

if "state" in self.params:
if self.params.get("state") in ("absent", "present"):
if self.params.get("output_level") in ("debug", "info"):
self.result["previous"] = self.existing
self.result["previous"] = self.existing if not filter_existing else filter_existing[0](self.existing, filter_existing[1])

# Return the gory details when we need it
if self.params.get("output_level") == "debug":
Expand All @@ -1398,7 +1440,7 @@ def exit_json(self, **kwargs):
# before=json.dumps(self.original, sort_keys=True, indent=4),
# after=json.dumps(self.existing, sort_keys=True, indent=4),
# )
self.result["current"] = self.existing
self.result["current"] = self.existing if not filter_existing else filter_existing[0](self.existing, filter_existing[1])

if self.params.get("output_level") in ("debug", "info"):
self.result["sent"] = self.config
Expand Down
145 changes: 96 additions & 49 deletions plugins/modules/aci_contract_subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
- The contract subject name.
type: str
aliases: [ contract_subject, name, subject_name ]
apply_both_direction:
description:
- The direction of traffic matching for the filter.
type: str
default: both
choices: [ both, one-way ]
contract:
description:
- The name of the Contract.
Expand All @@ -51,6 +57,34 @@
choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43,
CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ]
aliases: [ target ]
priority_consumer_to_provider:
description:
- The QoS class of Filter Chain For Consumer to Provider.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
choices: [ level1, level2, level3, unspecified ]
dscp_consumer_to_provider:
description:
- The target DSCP of Filter Chain For Consumer to Provider.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43,
CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ]
aliases: [ target_consumer_to_provider ]
priority_provider_to_consumer:
description:
- The QoS class of Filter Chain For Provider to Consumer.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
choices: [ level1, level2, level3, unspecified ]
dscp_provider_to_consumer:
description:
- The target DSCP of Filter Chain For Provider to Consumer.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43,
CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ]
aliases: [ target_provider_to_consumer ]
description:
description:
- Description for the contract subject.
Expand Down Expand Up @@ -251,7 +285,8 @@
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, \
aci_contract_dscp_spec, aci_contract_qos_spec

MATCH_MAPPING = dict(
all="All",
Expand All @@ -265,45 +300,23 @@ def main():
argument_spec = aci_argument_spec()
argument_spec.update(aci_annotation_spec())
argument_spec.update(
tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
contract=dict(type="str", aliases=["contract_name"]), # Not required for querying all objects
subject=dict(type="str", aliases=["contract_subject", "name", "subject_name"]), # Not required for querying all objects
tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
priority=dict(type="str", choices=["unspecified", "level1", "level2", "level3"]),
reverse_filter=dict(type="bool"),
dscp=dict(
type="str",
aliases=["target"],
choices=[
"AF11",
"AF12",
"AF13",
"AF21",
"AF22",
"AF23",
"AF31",
"AF32",
"AF33",
"AF41",
"AF42",
"AF43",
"CS0",
"CS1",
"CS2",
"CS3",
"CS4",
"CS5",
"CS6",
"CS7",
"EF",
"VA",
"unspecified",
],
),
description=dict(type="str", aliases=["descr"]),
consumer_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]),
provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
# default both because of back-worth compatibility and for determining which config to push
apply_both_direction=dict(type="str", default="both", choices=["both", "one-way"]),
priority=aci_contract_qos_spec(),
dscp=aci_contract_dscp_spec(),
priority_consumer_to_provider=aci_contract_qos_spec(),
dscp_consumer_to_provider=aci_contract_dscp_spec("consumer_to_provider"),
priority_provider_to_consumer=aci_contract_qos_spec(),
dscp_provider_to_consumer=aci_contract_dscp_spec("provider_to_consumer"),
)

module = AnsibleModule(
Expand All @@ -319,9 +332,13 @@ def main():

subject = module.params.get("subject")
priority = module.params.get("priority")
dscp = module.params.get("dscp")
priority_consumer_to_provider = module.params.get("priority_consumer_to_provider")
dscp_consumer_to_provider = module.params.get("dscp_consumer_to_provider")
priority_provider_to_consumer = module.params.get("priority_provider_to_consumer")
dscp_provider_to_consumer = module.params.get("dscp_provider_to_consumer")
reverse_filter = aci.boolean(module.params.get("reverse_filter"))
contract = module.params.get("contract")
dscp = module.params.get("dscp")
description = module.params.get("description")
consumer_match = module.params.get("consumer_match")
if consumer_match is not None:
Expand All @@ -332,8 +349,10 @@ def main():
state = module.params.get("state")
tenant = module.params.get("tenant")
name_alias = module.params.get("name_alias")
direction = module.params.get("apply_both_direction")

aci.construct_url(
subject_class = "vzSubj"
base_subject_dict = dict(
root_class=dict(
aci_class="fvTenant",
aci_rn="tn-{0}".format(tenant),
Expand All @@ -347,31 +366,59 @@ def main():
target_filter={"name": contract},
),
subclass_2=dict(
aci_class="vzSubj",
aci_class=subject_class,
aci_rn="subj-{0}".format(subject),
module_object=subject,
target_filter={"name": subject},
),
)
)

# start logic to be consistent with GUI to only allow both direction or one-way
aci.construct_url(root_class=base_subject_dict.get("root_class"),
subclass_1=base_subject_dict.get("subclass_1"),
subclass_2=base_subject_dict.get("subclass_2"),
child_classes=["vzInTerm", "vzOutTerm"])
aci.get_existing()
direction_options = ["both", "one-way"]
if aci.existing and subject_class in aci.existing[0]:
direction_options = ["one-way"] if "children" in aci.existing[0][subject_class] else ["both"]
if direction not in direction_options:
module.fail_json(msg="Direction is not allowed, valid option is {0}.".format(" or ".join(direction_options)))
# end logic to be consistent with GUI to only allow both direction or one-way

if state == "present":
aci.payload(
aci_class="vzSubj",
class_config=dict(
name=subject,
prio=priority,
revFltPorts=reverse_filter,
targetDscp=dscp,
consMatchT=consumer_match,
provMatchT=provider_match,
descr=description,
nameAlias=name_alias,
),

config = dict(
name=subject,
prio=priority,
revFltPorts=reverse_filter,
targetDscp=dscp,
consMatchT=consumer_match,
provMatchT=provider_match,
descr=description,
nameAlias=name_alias,
)

aci.get_diff(aci_class="vzSubj")
child_configs = []
if direction == "one-way" and (
len(direction_options) == 2 or
dscp_consumer_to_provider is not None or
priority_consumer_to_provider is not None or
dscp_provider_to_consumer is not None or
priority_provider_to_consumer is not None):
subj_dn = "uni/tn-{0}/brc-{1}/subj-{2}".format(tenant, contract, subject)
child_configs = [
dict(vzInTerm=dict(attributes=dict(dn="{0}/intmnl".format(subj_dn),
targetDscp=dscp_consumer_to_provider,
prio=priority_consumer_to_provider))),
dict(vzOutTerm=dict(attributes=dict(dn="{0}/outtmnl".format(subj_dn),
targetDscp=dscp_provider_to_consumer,
prio=priority_provider_to_consumer))),
]

aci.payload(aci_class=subject_class, class_config=config, child_configs=child_configs)

aci.get_diff(aci_class=subject_class)

aci.post_config()

Expand Down
Loading