diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 33babe01f..eb3b93cdd 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -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 @@ -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": @@ -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 diff --git a/plugins/modules/aci_contract_subject.py b/plugins/modules/aci_contract_subject.py index 0b63bb66c..15c322cd9 100644 --- a/plugins/modules/aci_contract_subject.py +++ b/plugins/modules/aci_contract_subject.py @@ -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. @@ -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. @@ -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", @@ -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( @@ -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: @@ -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), @@ -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() diff --git a/plugins/modules/aci_contract_subject_to_filter.py b/plugins/modules/aci_contract_subject_to_filter.py index 03da33a93..47c677f33 100644 --- a/plugins/modules/aci_contract_subject_to_filter.py +++ b/plugins/modules/aci_contract_subject_to_filter.py @@ -26,13 +26,30 @@ - The name of the Filter to bind to the Subject. type: str aliases: [ filter_name ] - log: + direction: + description: + - The direction of traffic matching for the filter. + type: str + default: both + choices: [ both, consumer_to_provider, provider_to_consumer ] + action: + description: + - The action required when the condition is met. + - The APIC defaults to C(permit) when unset during creation. + type: str + choices: [ deny, permit ] + priority_override: + description: + - Overrides the filter priority for the a single applied filter. + type: str + choices: [ default, level1, level2, level3 ] + directives: description: - Determines if the binding should be set to log. - The APIC defaults to C(none) when unset during creation. type: str - choices: [ log, none ] - aliases: [ directive ] + choices: [ log, no_stats, none ] + aliases: [ log, directive] subject: description: - The name of the Contract Subject. @@ -233,11 +250,16 @@ 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 filter=dict(type="str", aliases=["filter_name"]), # Not required for querying all objects subject=dict(type="str", aliases=["contract_subject", "subject_name"]), # Not required for querying all objects - tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects - log=dict(type="str", choices=["log", "none"], aliases=["directive"]), + # default both because of back-worth compatibility and for determining which config to push + direction=dict(type="str", default="both", choices=["both", "consumer_to_provider", "provider_to_consumer"]), + action=dict(type="str", choices=["deny", "permit"]), + # named directives instead of log/directive for readability of code, aliases and input "none are kept for back-worth compatibility + directives=dict(type="str", choices=["log", "no_stats", "none"], aliases=["log", "directive"]), + priority_override=dict(type="str", choices=["default", "level1", "level2", "level3"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) @@ -252,20 +274,16 @@ def main(): contract = module.params.get("contract") filter_name = module.params.get("filter") - log = module.params.get("log") + # "none" is kept because of back-worth compatibility, could be deleted and keep only None + directives = "" if (module.params.get("directives") is None or module.params.get("directives") == "none") else module.params.get("directives") subject = module.params.get("subject") + direction = module.params.get("direction") + action = module.params.get("action") + priority_override = module.params.get("priority_override") tenant = module.params.get("tenant") state = module.params.get("state") - # Add subject_filter key to modul.params for building the URL - module.params["subject_filter"] = filter_name - - # Convert log to empty string if none, as that is what API expects. An empty string is not a good option to present the user. - if log == "none": - log = "" - - aci = ACIModule(module) - aci.construct_url( + base_subject_dict = dict( root_class=dict( aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), @@ -283,37 +301,72 @@ def main(): aci_rn="subj-{0}".format(subject), module_object=subject, target_filter={"name": subject}, - ), - subclass_3=dict( - aci_class="vzRsSubjFiltAtt", - aci_rn="rssubjFiltAtt-{0}".format(filter_name), - module_object=filter_name, - target_filter={"tnVzFilterName": filter_name}, - ), + ) ) + aci = ACIModule(module) + + # start logic to be consistent with GUI to only allow both direction or a one-way connection + 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"] + if aci.existing: + direction_options = ["consumer_to_provider", "provider_to_consumer"] if "children" in aci.existing[0]["vzSubj"] else ["both"] - if state == "present": - aci.payload( - aci_class="vzRsSubjFiltAtt", - class_config=dict( - tnVzFilterName=filter_name, - directives=log, - ), - ) + 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 a one-way connection + + if direction == "both": + filter_class = "vzRsSubjFiltAtt" + # dict unpacking with **base_subject_dict raises syntax error in python2.7 thus dict lookup + 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"), + subclass_3=dict( + aci_class=filter_class, + aci_rn="rssubjFiltAtt-{0}".format(filter_name), + module_object=filter_name, + target_filter=dict(tnVzFilterName=filter_name))) + else: + term_class, term = ("vzInTerm", "intmnl") if direction == "consumer_to_provider" else ("vzOutTerm", "outtmnl") + filter_class = "vzRsFiltAtt" + # dict unpacking with **base_subject_dict raises syntax error in python2.7 thus dict lookup + 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"), + subclass_3=dict(aci_class=term_class, aci_rn=term), + child_classes=[filter_class]) - aci.get_diff(aci_class="vzRsSubjFiltAtt") + aci.get_existing() + if state == "present": + config = dict(tnVzFilterName=filter_name, directives=directives, action=action, priorityOverride=priority_override) + if direction == "both": + aci.payload(aci_class=filter_class, class_config=config) + aci.get_diff(aci_class=filter_class) + else: + child_config = [dict(vzRsFiltAtt=dict(attributes=config))] + aci.payload(aci_class=term_class, class_config=dict(), child_configs=child_config) + aci.get_diff(aci_class=term_class) aci.post_config() elif state == "absent": aci.delete_config() - # Remove subject_filter used to build URL from module.params - module.params.pop("subject_filter") - - aci.exit_json() + if direction == "both": + aci.exit_json() + else: + # filter the output of current/previous to tnVzFilterName only since existing consist full vzInTerm/vzOutTerm + def filter_result(input_list, name): + return [{key: filter_entry} for entry in input_list if 'children' in entry[term_class] for children in entry[term_class]['children'] + for key, filter_entry in children.items() if filter_entry['attributes']['tnVzFilterName'] == name] + # pass function to + filter_existing = (filter_result, filter_name) + aci.exit_json(filter_existing) if __name__ == "__main__": diff --git a/tests/integration/targets/aci_contract_subject/tasks/main.yml b/tests/integration/targets/aci_contract_subject/tasks/main.yml index 9441f410f..4e4f8bb1b 100644 --- a/tests/integration/targets/aci_contract_subject/tasks/main.yml +++ b/tests/integration/targets/aci_contract_subject/tasks/main.yml @@ -8,240 +8,395 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined -- name: ensure tenant exists for tests to kick off - cisco.aci.aci_tenant: &aci_tenant_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug - state: present - tenant: anstest - register: tenant_present - -- name: ensure contract exists for tests to kick off - cisco.aci.aci_contract: &aci_contract_present - <<: *aci_tenant_present - contract: anstest - register: contract_present - -- name: create subject - check mode works - cisco.aci.aci_contract_subject: &aci_subject_present - <<: *aci_contract_present - subject: anstest - description: Ansible Test - check_mode: yes - register: subject_present_check_mode - -- name: create subject - creation works - cisco.aci.aci_contract_subject: - <<: *aci_subject_present - register: subject_present - -- name: create subject - idempotency works - cisco.aci.aci_contract_subject: - <<: *aci_subject_present - register: subject_present_idempotent - -- name: update subject - update works - cisco.aci.aci_contract_subject: - <<: *aci_subject_present - description: Ansible Test - reverse_filter: "yes" - provider_match: at_most_one - priority: level2 - register: subject_update - -- name: create subject - try additional params - cisco.aci.aci_contract_subject: &aci_subject_present_2 - <<: *aci_contract_present - subject: anstest2 - reverse_filter: "no" - consumer_match: all - priority: level3 - register: subject_present_2 - -- name: missing param - failure message works - cisco.aci.aci_contract_subject: - <<: *aci_tenant_present - ignore_errors: yes - register: present_missing_param - -- name: present assertions - assert: - that: - - subject_present_check_mode is changed - - subject_present_check_mode.sent.vzSubj.attributes.descr == 'Ansible Test' - - subject_present_check_mode.sent.vzSubj.attributes.name == 'anstest' - - subject_present is changed - - subject_present.previous == [] - - subject_present.sent == subject_present_check_mode.sent - - subject_present.current.0.vzSubj.attributes.annotation == 'orchestrator:ansible' - - subject_present_idempotent is not changed - - subject_present_idempotent.previous != [] - - subject_update is changed - - subject_update.sent != subject_update.proposed - - subject_update.sent.vzSubj.attributes.prio == 'level2' - - subject_update.sent.vzSubj.attributes.provMatchT == 'AtmostOne' - - subject_present_2 is changed - - subject_present_2.sent.vzSubj.attributes.consMatchT == 'All' - - subject_present_2.sent.vzSubj.attributes.name == 'anstest2' - - subject_present_2.sent.vzSubj.attributes.prio == 'level3' - - subject_present_2.sent.vzSubj.attributes.revFltPorts == 'no' - - present_missing_param is failed - - 'present_missing_param.msg == "state is present but all of the following are missing: contract, subject"' - -- name: query tenant contract subject - cisco.aci.aci_contract_subject: &aci_query_subject - <<: *aci_subject_present - state: query - register: query_tenant_contract_subject - -- name: query tenant contract - cisco.aci.aci_contract_subject: - <<: *aci_query_subject - subject: "{{ fakevar | default(omit) }}" - register: query_tenant_contract - -- name: query tenant subject - cisco.aci.aci_contract_subject: - <<: *aci_query_subject - contract: "{{ fakevar | default(omit) }}" - register: query_tenant_subject - -- name: query contract subject - cisco.aci.aci_contract_subject: - <<: *aci_query_subject - tenant: "{{ fakevar | default(omit) }}" - register: query_contract_subject - -- name: query tenant - cisco.aci.aci_contract_subject: - <<: *aci_tenant_present - state: query - register: query_tenant - -- name: query contract - cisco.aci.aci_contract_subject: - <<: *aci_contract_present - state: query - tenant: "{{ fakevar | default(omit) }}" - register: query_contract - -- name: query subject - cisco.aci.aci_contract_subject: - <<: *aci_query_subject - tenant: "{{ fakevar | default(omit) }}" - contract: "{{ fakevar | default(omit) }}" - register: query_subject - -- name: query all - cisco.aci.aci_contract_subject: - <<: *aci_tenant_present - state: query - tenant: "{{ fakevar | default(omit) }}" - register: query_all - -- name: query assertions - assert: - that: - - query_tenant_contract_subject is not changed - - query_tenant_contract_subject.current | length == 1 - - query_tenant_contract_subject.current.0.vzSubj.attributes.name == "anstest" - - '"tn-anstest/brc-anstest/subj-anstest.json" in query_tenant_contract_subject.url' - - query_tenant_contract is not changed - - query_tenant_contract.current | length == 1 - - query_tenant_contract.current.0.vzBrCP.attributes.name == "anstest" - - query_tenant_contract.current.0.vzBrCP.children | length == 2 - - '"rsp-subtree-class=vzSubj" in query_tenant_contract.filter_string' - - '"tn-anstest/brc-anstest.json" in query_tenant_contract.url' - - query_tenant_subject is not changed - - query_tenant_subject.current | length == 1 - - query_tenant_subject.current.0.fvTenant.attributes.name == "anstest" - - query_tenant_subject.current.0.fvTenant.children.0.vzBrCP.children | length == 1 - - query_tenant_subject.current.0.fvTenant.children.0.vzBrCP.children.0.vzSubj.attributes.name == "anstest" - - '"rsp-subtree-filter=eq(vzSubj.name,\"anstest\")" in query_tenant_subject.filter_string' - - '"rsp-subtree-class=vzSubj" in query_tenant_subject.filter_string' - - '"tn-anstest.json" in query_tenant_subject.url' - - query_contract_subject is not changed - - query_contract_subject.current.0.vzBrCP.attributes.name == "anstest" - - query_contract_subject.current.0.vzBrCP.children | length == 1 - - query_contract_subject.current.0.vzBrCP.children.0.vzSubj.attributes.name == "anstest" - - '"query-target-filter=eq(vzBrCP.name,\"anstest\")" in query_contract_subject.filter_string' - - '"rsp-subtree-filter=eq(vzSubj.name,\"anstest\")" in query_contract_subject.filter_string' - - '"rsp-subtree-class=vzSubj" in query_contract_subject.filter_string' - - '"class/vzBrCP.json" in query_contract_subject.url' - - query_tenant is not changed - - query_tenant.current | length == 1 - - query_tenant.current.0.fvTenant.attributes.name == "anstest" - - '"rsp-subtree-class=vzBrCP,vzSubj" in query_tenant.filter_string' - - '"tn-anstest.json" in query_tenant.url' - - query_contract is not changed - - query_contract.current.0.vzBrCP.attributes.name == "anstest" - - '"query-target-filter=eq(vzBrCP.name,\"anstest\")" in query_contract.filter_string' - - '"rsp-subtree-class=vzSubj" in query_contract.filter_string' - - '"class/vzBrCP.json" in query_contract.url' - - query_subject is not changed - - query_subject.current.0.vzSubj.attributes.name == "anstest" - - '"query-target-filter=eq(vzSubj.name,\"anstest\")" in query_subject.filter_string' - - '"class/vzSubj.json" in query_subject.url' - - query_all is not changed - - query_all.current|length > 1 - - query_all.current.0.vzSubj is defined - - '"class/vzSubj.json" in query_all.url' - -- name: delete subject - check mode works - cisco.aci.aci_contract_subject: &aci_subject_absent - <<: *aci_subject_present - state: absent - check_mode: yes - register: subject_absent_check_mode - -- name: delete subject - deletion works - cisco.aci.aci_contract_subject: - <<: *aci_subject_absent - register: subject_absent - -- name: delete subject - idempotency works - cisco.aci.aci_contract_subject: - <<: *aci_subject_absent - register: subject_absent_idempotent - -- name: delete subject - cleanup - cisco.aci.aci_contract_subject: - <<: *aci_subject_present_2 - state: absent - -- name: missing params - failure message works - cisco.aci.aci_contract_subject: - <<: *aci_subject_absent - subject: "{{ fakevar | default(omit) }}" - ignore_errors: yes - register: absent_missing_param - -- name: absent assertions - assert: - that: - - subject_absent_check_mode is changed - - subject_absent_check_mode.previous != [] - - subject_absent_check_mode.proposed == {} - - subject_absent is changed - - subject_absent.previous == subject_absent_check_mode.previous - - subject_absent_idempotent is not changed - - subject_absent_idempotent.previous == [] - - absent_missing_param is failed - - 'absent_missing_param.msg == "state is absent but all of the following are missing: subject"' - -- name: cleanup contract - cisco.aci.aci_contract: - <<: *aci_contract_present - state: absent - when: contract_present is changed - -- name: cleanup tenant - cisco.aci.aci_tenant: - <<: *aci_tenant_present - state: absent - when: tenant_present is changed +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Assure tenant removal after tests + block: + - name: ensure tenant is deleted for clean state + cisco.aci.aci_tenant: + <<: *aci_info + tenant: anstest + state: absent + register: tenant_present + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: anstest + state: present + register: tenant_present + + - name: ensure contract exists for tests to kick off + cisco.aci.aci_contract: &aci_contract_present + <<: *aci_tenant_present + contract: anstest + register: contract_present + + - name: create subject - check mode works + cisco.aci.aci_contract_subject: &aci_subject_present + <<: *aci_contract_present + subject: anstest + description: Ansible Test + check_mode: yes + register: subject_present_check_mode + + - name: create subject - creation works + cisco.aci.aci_contract_subject: + <<: *aci_subject_present + register: subject_present + + - name: create subject - idempotency works + cisco.aci.aci_contract_subject: + <<: *aci_subject_present + register: subject_present_idempotent + + - name: update subject - update works + cisco.aci.aci_contract_subject: + <<: *aci_subject_present + description: Ansible Test + reverse_filter: "yes" + provider_match: at_most_one + priority: level2 + register: subject_update + + - name: create subject - try additional params + cisco.aci.aci_contract_subject: &aci_subject_present_2 + <<: *aci_contract_present + subject: anstest2 + reverse_filter: "no" + consumer_match: all + priority: level3 + register: subject_present_2 + + - name: missing param - failure message works + cisco.aci.aci_contract_subject: + <<: *aci_tenant_present + ignore_errors: yes + register: present_missing_param + + - name: present assertions + assert: + that: + - subject_present_check_mode is changed + - subject_present_check_mode.sent.vzSubj.attributes.descr == 'Ansible Test' + - subject_present_check_mode.sent.vzSubj.attributes.name == 'anstest' + - subject_present is changed + - subject_present.previous == [] + - subject_present.sent == subject_present_check_mode.sent + - subject_present.current.0.vzSubj.attributes.annotation == 'orchestrator:ansible' + - subject_present_idempotent is not changed + - subject_present_idempotent.previous != [] + - subject_update is changed + - subject_update.sent != subject_update.proposed + - subject_update.sent.vzSubj.attributes.prio == 'level2' + - subject_update.sent.vzSubj.attributes.provMatchT == 'AtmostOne' + - subject_present_2 is changed + - subject_present_2.sent.vzSubj.attributes.consMatchT == 'All' + - subject_present_2.sent.vzSubj.attributes.name == 'anstest2' + - subject_present_2.sent.vzSubj.attributes.prio == 'level3' + - subject_present_2.sent.vzSubj.attributes.revFltPorts == 'no' + - present_missing_param is failed + - 'present_missing_param.msg == "state is present but all of the following are missing: contract, subject"' + + - name: query tenant contract subject + cisco.aci.aci_contract_subject: &aci_query_subject + <<: *aci_subject_present + state: query + register: query_tenant_contract_subject + + - name: query tenant contract + cisco.aci.aci_contract_subject: + <<: *aci_query_subject + subject: "{{ fakevar | default(omit) }}" + register: query_tenant_contract + + - name: query tenant subject + cisco.aci.aci_contract_subject: + <<: *aci_query_subject + contract: "{{ fakevar | default(omit) }}" + register: query_tenant_subject + + - name: query contract subject + cisco.aci.aci_contract_subject: + <<: *aci_query_subject + tenant: "{{ fakevar | default(omit) }}" + register: query_contract_subject + + - name: query tenant + cisco.aci.aci_contract_subject: + <<: *aci_tenant_present + state: query + register: query_tenant + + - name: query contract + cisco.aci.aci_contract_subject: + <<: *aci_contract_present + state: query + tenant: "{{ fakevar | default(omit) }}" + register: query_contract + + - name: query subject + cisco.aci.aci_contract_subject: + <<: *aci_query_subject + tenant: "{{ fakevar | default(omit) }}" + contract: "{{ fakevar | default(omit) }}" + register: query_subject + + - name: query all + cisco.aci.aci_contract_subject: + <<: *aci_tenant_present + state: query + tenant: "{{ fakevar | default(omit) }}" + register: query_all + + - name: query assertions + assert: + that: + - query_tenant_contract_subject is not changed + - query_tenant_contract_subject.current | length == 1 + - query_tenant_contract_subject.current.0.vzSubj.attributes.name == "anstest" + - '"tn-anstest/brc-anstest/subj-anstest.json" in query_tenant_contract_subject.url' + - query_tenant_contract is not changed + - query_tenant_contract.current | length == 1 + - query_tenant_contract.current.0.vzBrCP.attributes.name == "anstest" + - query_tenant_contract.current.0.vzBrCP.children | length == 2 + - '"?rsp-subtree=full&rsp-subtree-class=vzInTerm,vzOutTerm,vzSubj" in query_tenant_contract.filter_string' + - '"tn-anstest/brc-anstest.json" in query_tenant_contract.url' + - query_tenant_subject is not changed + - query_tenant_subject.current | length == 1 + - query_tenant_subject.current.0.fvTenant.attributes.name == "anstest" + - query_tenant_subject.current.0.fvTenant.children.0.vzBrCP.children | length == 1 + - query_tenant_subject.current.0.fvTenant.children.0.vzBrCP.children.0.vzSubj.attributes.name == "anstest" + - '"rsp-subtree-filter=eq(vzSubj.name,\"anstest\")" in query_tenant_subject.filter_string' + - '"rsp-subtree-class=vzInTerm,vzOutTerm,vzSubj" in query_tenant_subject.filter_string' + - '"tn-anstest.json" in query_tenant_subject.url' + - query_contract_subject is not changed + - query_contract_subject.current.0.vzBrCP.attributes.name == "anstest" + - query_contract_subject.current.0.vzBrCP.children | length == 1 + - query_contract_subject.current.0.vzBrCP.children.0.vzSubj.attributes.name == "anstest" + - '"query-target-filter=eq(vzBrCP.name,\"anstest\")" in query_contract_subject.filter_string' + - '"rsp-subtree-filter=eq(vzSubj.name,\"anstest\")" in query_contract_subject.filter_string' + - '"rsp-subtree-class=vzInTerm,vzOutTerm,vzSubj" in query_contract_subject.filter_string' + - '"class/vzBrCP.json" in query_contract_subject.url' + - query_tenant is not changed + - query_tenant.current | length == 1 + - query_tenant.current.0.fvTenant.attributes.name == "anstest" + - '"rsp-subtree-class=vzBrCP,vzInTerm,vzOutTerm,vzSubj" in query_tenant.filter_string' + - '"tn-anstest.json" in query_tenant.url' + - query_contract is not changed + - query_contract.current.0.vzBrCP.attributes.name == "anstest" + - '"query-target-filter=eq(vzBrCP.name,\"anstest\")" in query_contract.filter_string' + - '"rsp-subtree-class=vzInTerm,vzOutTerm,vzSubj" in query_contract.filter_string' + - '"class/vzBrCP.json" in query_contract.url' + - query_subject is not changed + - query_subject.current.0.vzSubj.attributes.name == "anstest" + - '"query-target-filter=eq(vzSubj.name,\"anstest\")" in query_subject.filter_string' + - '"class/vzSubj.json" in query_subject.url' + - query_all is not changed + - query_all.current|length > 1 + - query_all.current.0.vzSubj is defined + - '"class/vzSubj.json" in query_all.url' + + - name: delete subject - check mode works + cisco.aci.aci_contract_subject: &aci_subject_absent + <<: *aci_subject_present + state: absent + check_mode: yes + register: subject_absent_check_mode + + - name: delete subject - deletion works + cisco.aci.aci_contract_subject: + <<: *aci_subject_absent + register: subject_absent + + - name: delete subject - idempotency works + cisco.aci.aci_contract_subject: + <<: *aci_subject_absent + register: subject_absent_idempotent + + - name: delete subject - cleanup + cisco.aci.aci_contract_subject: + <<: *aci_subject_present_2 + state: absent + + - name: missing params - failure message works + cisco.aci.aci_contract_subject: + <<: *aci_subject_absent + subject: "{{ fakevar | default(omit) }}" + ignore_errors: yes + register: absent_missing_param + + - name: absent assertions + assert: + that: + - subject_absent_check_mode is changed + - subject_absent_check_mode.previous != [] + - subject_absent_check_mode.proposed == {} + - subject_absent is changed + - subject_absent.previous == subject_absent_check_mode.previous + - subject_absent_idempotent is not changed + - subject_absent_idempotent.previous == [] + - absent_missing_param is failed + - 'absent_missing_param.msg == "state is absent but all of the following are missing: subject"' + + - name: create contract 1 + cisco.aci.aci_contract: &aci_contract1 + <<: *aci_tenant_present + contract: contract1 + register: contract_present + + - name: create subject both (check_mode) + cisco.aci.aci_contract_subject: &aci_subject_both + <<: *aci_contract1 + subject: subjectboth + check_mode: yes + register: cm_subject_present_both_default + + - name: create subject both + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + register: nm_subject_present_both_default + + - name: create subject both again with direction both set + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + apply_both_direction: both + register: nm_subject_present_both_again + + - name: create subject one-way (check_mode) + cisco.aci.aci_contract_subject: &aci_subject_one_way + <<: *aci_contract1 + subject: subjectoneway + apply_both_direction: one-way + check_mode: yes + register: cm_subject_present_one_way + + - name: create subject one-way + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + register: nm_subject_present_one_way + + - name: create subject one-way again + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + register: nm_subject_present_one_way_again + + - name: subject assertions + assert: + that: + - cm_subject_present_both_default is changed + - nm_subject_present_both_default is changed + - nm_subject_present_both_default.current.0.vzSubj.attributes.name == "subjectboth" + - nm_subject_present_both_again is not changed + - cm_subject_present_one_way is changed + - nm_subject_present_one_way is changed + - nm_subject_present_one_way.current.0.vzSubj.attributes.name == "subjectoneway" + - nm_subject_present_one_way_again is not changed + + - name: reverse subject both to one-way + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + apply_both_direction: one-way + register: nm_subject_reverse_to_one_way + ignore_errors: yes + + - name: reverse subject one-way to both + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + apply_both_direction: both + register: nm_subject_reverse_to_both + ignore_errors: yes + + - name: subject assertions + assert: + that: + - nm_subject_reverse_to_one_way is not changed + - nm_subject_reverse_to_one_way.msg == "Direction is not allowed, valid option is both." + - nm_subject_reverse_to_both is not changed + - nm_subject_reverse_to_both.msg == "Direction is not allowed, valid option is one-way." + + - name: subject both with qos and dscp + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + priority: level1 + dscp: AF11 + register: nm_subject_both_qos_dscp + + - name: subject both with change to qos + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + priority: unspecified + register: nm_subject_both_qos_change + + - name: subject both with change to dscp + cisco.aci.aci_contract_subject: + <<: *aci_subject_both + dscp: AF41 + register: nm_subject_both_dscp_change + + - name: subject one-way with qos and dscp + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + priority_consumer_to_provider: level2 + dscp_consumer_to_provider: AF12 + priority_provider_to_consumer: level3 + dscp_provider_to_consumer: CS4 + register: nm_subject_one_way_qos_dscp + + - name: subject one-way with qos change + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + priority_consumer_to_provider: unspecified + priority_provider_to_consumer: level1 + register: nm_subject_one_way_qos_change + + - name: subject one-way with dscp change + cisco.aci.aci_contract_subject: + <<: *aci_subject_one_way + dscp_consumer_to_provider: AF13 + dscp_provider_to_consumer: CS1 + register: nm_subject_one_way_dscp_change + + - name: subject assertions + assert: + that: + - nm_subject_both_qos_dscp is changed + - nm_subject_both_qos_dscp.current.0.vzSubj.attributes.prio == "level1" + - nm_subject_both_qos_dscp.current.0.vzSubj.attributes.targetDscp == "AF11" + - nm_subject_both_qos_change is changed + - nm_subject_both_qos_change.current.0.vzSubj.attributes.prio == "unspecified" + - nm_subject_both_qos_change.current.0.vzSubj.attributes.targetDscp == "AF11" + - nm_subject_both_dscp_change is changed + - nm_subject_both_dscp_change.current.0.vzSubj.attributes.prio == "unspecified" + - nm_subject_both_dscp_change.current.0.vzSubj.attributes.targetDscp == "AF41" + - nm_subject_one_way_qos_dscp is changed + - nm_subject_one_way_qos_dscp.current.0.vzSubj.children.0.vzOutTerm.attributes.prio == "level3" + - nm_subject_one_way_qos_dscp.current.0.vzSubj.children.0.vzOutTerm.attributes.targetDscp == "CS4" + - nm_subject_one_way_qos_dscp.current.0.vzSubj.children.1.vzInTerm.attributes.prio == "level2" + - nm_subject_one_way_qos_dscp.current.0.vzSubj.children.1.vzInTerm.attributes.targetDscp == "AF12" + - nm_subject_one_way_qos_change is changed + - nm_subject_one_way_qos_change.current.0.vzSubj.children.0.vzOutTerm.attributes.prio == "level1" + - nm_subject_one_way_qos_change.current.0.vzSubj.children.0.vzOutTerm.attributes.targetDscp == "CS4" + - nm_subject_one_way_qos_change.current.0.vzSubj.children.1.vzInTerm.attributes.prio == "unspecified" + - nm_subject_one_way_qos_change.current.0.vzSubj.children.1.vzInTerm.attributes.targetDscp == "AF12" + - nm_subject_one_way_dscp_change is changed + - nm_subject_one_way_dscp_change.current.0.vzSubj.children.0.vzOutTerm.attributes.prio == "level1" + - nm_subject_one_way_dscp_change.current.0.vzSubj.children.0.vzOutTerm.attributes.targetDscp == "CS1" + - nm_subject_one_way_dscp_change.current.0.vzSubj.children.1.vzInTerm.attributes.prio == "unspecified" + - nm_subject_one_way_dscp_change.current.0.vzSubj.children.1.vzInTerm.attributes.targetDscp == "AF13" + + always: + - name: cleanup tenant + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent + when: tenant_present is changed diff --git a/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml b/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml index 7d176b05f..7b9348313 100644 --- a/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml +++ b/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml @@ -2,194 +2,378 @@ # Copyright: (c) 2017, Jacob McGill (@jmcgill298) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) - - name: Test that we have an ACI APIC host, ACI username and ACI password fail: msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined -- name: ensure tenant exists for tests to kick off - cisco.aci.aci_tenant: &aci_tenant_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' - tenant: anstest - state: present - register: tenant_present - -- name: ensure filter exists for tests to kick off - cisco.aci.aci_filter: &aci_filter_present - <<: *aci_tenant_present - filter: anstest - register: filter_present - -- name: ensure filter exists for tests to kick off - cisco.aci.aci_filter: &aci_filter_present_2 - <<: *aci_tenant_present - filter: anstest2 - register: filter_present_2 - -- name: ensure contract exists for tests to kick off - cisco.aci.aci_contract: &aci_contract_present - <<: *aci_tenant_present - contract: anstest - register: contract_present - -- name: ensure subject exists for tests to kick off - cisco.aci.aci_contract_subject: &aci_subject_present - <<: *aci_contract_present - subject: anstest - register: subject_present - -- name: create subject filter binding - check mode works - cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_present - <<: *aci_subject_present - filter: anstest - log: log - check_mode: yes - register: subject_filter_present_check_mode - -- name: create subject filter binding - creation works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_present - register: subject_filter_present - -- name: create subject filter binding - additional testing - cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_present_2 - <<: *aci_subject_filter_present - filter: anstest2 - register: subject_filter_present_2 - -- name: create subject filter binding - idempotency works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_present - register: subject_filter_present_idempotent - -- name: update subject filter binding - update works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_present - log: none - register: subject_filter_update - -- name: missing param - failure message works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_tenant_present - ignore_errors: yes - register: present_missing_param - -- name: present assertions - assert: - that: - - subject_filter_present_check_mode is changed - - subject_filter_present_check_mode.previous == [] - - subject_filter_present_check_mode.sent.vzRsSubjFiltAtt.attributes.directives == 'log' - - subject_filter_present_check_mode.sent.vzRsSubjFiltAtt.attributes.tnVzFilterName == 'anstest' - - subject_filter_present is changed - - subject_filter_present.previous == [] - - subject_filter_present.sent == subject_filter_present_check_mode.sent - - subject_filter_present.current.0.vzRsSubjFiltAtt.attributes.annotation == 'orchestrator:ansible' - - subject_filter_present_2 is changed - - subject_filter_present_idempotent is not changed - - subject_filter_present_idempotent.previous != [] - - subject_filter_update is changed - - subject_filter_update.sent.vzRsSubjFiltAtt.attributes.directives == '' - - present_missing_param is failed - - 'present_missing_param.msg == "state is present but all of the following are missing: contract, filter, subject"' - -- name: query all - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_tenant_present - state: query - tenant: "{{ fakevar | default(omit) }}" - register: query_all - -- name: query binding - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_present - state: query - register: query_binding - -- name: query assertions - assert: - that: - - query_all is not changed - - query_all.current | length > 1 - - query_all.current.0.vzRsSubjFiltAtt is defined - - query_binding is not changed - - query_binding.current != [] - -- name: delete subject filter binding - check mode works - cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_absent - <<: *aci_subject_filter_present - state: absent - check_mode: yes - register: subject_filter_absent_check_mode - -- name: delete subject filter binding - deletion works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_absent - register: subject_filter_absent - -- name: delete subject filter binding - idempotency works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_absent - register: subject_filter_absent_idempotent - -- name: missing param - failure message works - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_absent - filter: "{{ fakevar | default(omit) }}" - ignore_errors: yes - register: absent_missing_param - -- name: cleanup subject filter binding - cisco.aci.aci_contract_subject_to_filter: - <<: *aci_subject_filter_present_2 - state: absent - -- name: absent assertions - assert: - that: - - subject_filter_absent_check_mode is changed - - subject_filter_absent_check_mode.proposed == {} - - subject_filter_absent_check_mode.previous != [] - - subject_filter_absent is changed - - subject_filter_absent.previous != [] - - subject_filter_absent_idempotent is not changed - - subject_filter_absent_idempotent.previous == [] - - absent_missing_param is failed - - 'absent_missing_param.msg == "state is absent but all of the following are missing: filter"' - -- name: cleanup subject - cisco.aci.aci_contract_subject: - <<: *aci_subject_present - state: absent - when: subject_present is changed - -- name: cleanup contract - cisco.aci.aci_contract: - <<: *aci_contract_present - state: absent - when: contract_present is changed - -- name: cleanup filter - cisco.aci.aci_filter: - <<: *aci_filter_present - state: absent - when: filter_present is changed - -- name: cleanup filter - cisco.aci.aci_filter: - <<: *aci_filter_present_2 - state: absent - when: filter_present_2 is changed - -- name: cleanup tenant - cisco.aci.aci_tenant: - <<: *aci_tenant_present - state: absent - when: tenant_present is changed +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Assure tenant removal after tests + block: + - name: ensure tenant is deleted for clean state + cisco.aci.aci_tenant: + <<: *aci_info + tenant: anstest + state: absent + register: tenant_present + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: anstest + state: present + register: tenant_present + + - name: ensure filter exists for tests to kick off + cisco.aci.aci_filter: &aci_filter_present + <<: *aci_tenant_present + filter: anstest + register: filter_present + + - name: ensure filter exists for tests to kick off + cisco.aci.aci_filter: &aci_filter_present_2 + <<: *aci_tenant_present + filter: anstest2 + register: filter_present_2 + + - name: ensure contract exists for tests to kick off + cisco.aci.aci_contract: &aci_contract_present + <<: *aci_tenant_present + contract: anstest + register: contract_present + + - name: ensure subject exists for tests to kick off + cisco.aci.aci_contract_subject: &aci_subject_present + <<: *aci_contract_present + subject: anstest + register: subject_present + + - name: create subject filter binding - check mode works + cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_present + <<: *aci_subject_present + filter: anstest + log: log + check_mode: yes + register: subject_filter_present_check_mode + + - name: create subject filter binding - creation works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present + register: subject_filter_present + + - name: create subject filter binding - additional testing + cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_present_2 + <<: *aci_subject_filter_present + filter: anstest2 + register: subject_filter_present_2 + + - name: create subject filter binding - idempotency works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present + register: subject_filter_present_idempotent + + - name: update subject filter binding - update works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present + log: none + register: subject_filter_update + + - name: missing param - failure message works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_tenant_present + ignore_errors: yes + register: present_missing_param + + - name: present assertions + assert: + that: + - subject_filter_present_check_mode is changed + - subject_filter_present_check_mode.previous == [] + - subject_filter_present_check_mode.sent.vzRsSubjFiltAtt.attributes.directives == 'log' + - subject_filter_present_check_mode.sent.vzRsSubjFiltAtt.attributes.tnVzFilterName == 'anstest' + - subject_filter_present is changed + - subject_filter_present.previous == [] + - subject_filter_present.sent == subject_filter_present_check_mode.sent + - subject_filter_present.current.0.vzRsSubjFiltAtt.attributes.annotation == 'orchestrator:ansible' + - subject_filter_present_2 is changed + - subject_filter_present_idempotent is not changed + - subject_filter_present_idempotent.previous != [] + - subject_filter_update is changed + - subject_filter_update.sent.vzRsSubjFiltAtt.attributes.directives == '' + - present_missing_param is failed + - 'present_missing_param.msg == "state is present but all of the following are missing: contract, filter, subject"' + + - name: query all + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_tenant_present + state: query + tenant: "{{ fakevar | default(omit) }}" + register: query_all + + - name: query binding + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present + state: query + register: query_binding + + - name: query assertions + assert: + that: + - query_all is not changed + - query_all.current | length > 1 + - query_all.current.0.vzRsSubjFiltAtt is defined + - query_binding is not changed + - query_binding.current != [] + + - name: delete subject filter binding - check mode works + cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_absent + <<: *aci_subject_filter_present + state: absent + check_mode: yes + register: subject_filter_absent_check_mode + + - name: delete subject filter binding - deletion works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_absent + register: subject_filter_absent + + - name: delete subject filter binding - idempotency works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_absent + register: subject_filter_absent_idempotent + + - name: missing param - failure message works + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_absent + filter: "{{ fakevar | default(omit) }}" + ignore_errors: yes + register: absent_missing_param + + - name: cleanup subject filter binding + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present_2 + state: absent + + - name: absent assertions + assert: + that: + - subject_filter_absent_check_mode is changed + - subject_filter_absent_check_mode.proposed == {} + - subject_filter_absent_check_mode.previous != [] + - subject_filter_absent is changed + - subject_filter_absent.previous != [] + - subject_filter_absent_idempotent is not changed + - subject_filter_absent_idempotent.previous == [] + - absent_missing_param is failed + - 'absent_missing_param.msg == "state is absent but all of the following are missing: filter"' + + - name: create filter 1 + cisco.aci.aci_filter: + <<: *aci_tenant_present + filter: filter1 + + - name: create filter 2 + cisco.aci.aci_filter: + <<: *aci_tenant_present + filter: filter2 + + - name: create contract 1 + cisco.aci.aci_contract: &aci_contract1 + <<: *aci_tenant_present + contract: contract1 + + - name: create subject both + cisco.aci.aci_contract_subject: &aci_subject_both + <<: *aci_contract1 + subject: subjectboth + register: subject_both + + - name: create subject one-way + cisco.aci.aci_contract_subject: &aci_subject_one_way + <<: *aci_contract1 + subject: subjectoneway + apply_both_direction: one-way + register: nm_subject_present_one_way_again + + - name: create subject filter with direction both as default (check_mode) + cisco.aci.aci_contract_subject_to_filter: &aci_subject_both_to_filter + <<: *aci_subject_both + filter: filter1 + action: permit + directive: log + priority_override: level1 + state: present + check_mode: yes + register: cm_filter1_present_default + + - name: apply subject filter1 with direction both as default + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + register: nm_filter1_present_default + + - name: apply subject filter1 with direction provider to consumer (error) + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + direction: provider_to_consumer + register: nm_filter1_present_p2c + ignore_errors: yes + + - name: apply subject filter1 with direction consumer to provider (error) + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + direction: consumer_to_provider + register: nm_filter1_present_c2p + ignore_errors: yes + + - name: apply subject filter1 with direction both set + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + filter: filter1 + direction: both + action: permit + directive: no_stats + priority_override: level2 + state: present + register: nm_filter1_present_both + + - name: apply subject filter2 with direction provider to consumer + cisco.aci.aci_contract_subject_to_filter: &aci_subject_one_way_to_filter + <<: *aci_contract1 + subject: subjectoneway + filter: filter2 + direction: provider_to_consumer + action: permit + directive: none + priority_override: level2 + state: present + register: nm_filter2_present_p2c + + - name: apply subject filter2 with direction consumer to provider + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: provider_to_consumer + register: nm_filter2_present_c2p + + - name: apply subject filter2 with direction both (error) + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: both + register: nm_filter2_present_both + ignore_errors: yes + + - name: filter assertions + assert: + that: + - cm_filter1_present_default is changed + - nm_filter1_present_default is changed + - nm_filter1_present_default.current.0.vzRsSubjFiltAtt.attributes.action == "permit" + - nm_filter1_present_default.current.0.vzRsSubjFiltAtt.attributes.tnVzFilterName == "filter1" + - nm_filter1_present_default.current.0.vzRsSubjFiltAtt.attributes.priorityOverride == "level1" + - nm_filter1_present_default.current.0.vzRsSubjFiltAtt.attributes.directives == "log" + - nm_filter1_present_p2c is not changed + - nm_filter1_present_p2c.msg == "Direction is not allowed, valid option is both." + - nm_filter1_present_c2p is not changed + - nm_filter1_present_c2p.msg == "Direction is not allowed, valid option is both." + - nm_filter1_present_both is changed + - nm_filter1_present_both.current.0.vzRsSubjFiltAtt.attributes.action == "permit" + - nm_filter1_present_both.current.0.vzRsSubjFiltAtt.attributes.tnVzFilterName == "filter1" + - nm_filter1_present_both.current.0.vzRsSubjFiltAtt.attributes.priorityOverride == "level2" + - nm_filter1_present_both.current.0.vzRsSubjFiltAtt.attributes.directives == "no_stats" + - nm_filter2_present_p2c is changed + - nm_filter2_present_p2c.current.0.vzRsFiltAtt.attributes.action == "permit" + - nm_filter2_present_p2c.current.0.vzRsFiltAtt.attributes.tnVzFilterName == "filter2" + - nm_filter2_present_p2c.current.0.vzRsFiltAtt.attributes.priorityOverride == "level2" + - nm_filter2_present_p2c.current.0.vzRsFiltAtt.attributes.directives == "" + - nm_filter2_present_c2p is not changed + - nm_filter2_present_c2p.current.0.vzRsFiltAtt.attributes.action == "permit" + - nm_filter2_present_c2p.current.0.vzRsFiltAtt.attributes.tnVzFilterName == "filter2" + - nm_filter2_present_c2p.current.0.vzRsFiltAtt.attributes.priorityOverride == "level2" + - nm_filter2_present_c2p.current.0.vzRsFiltAtt.attributes.directives == "" + - nm_filter2_present_both.msg == "Direction is not allowed, valid option is consumer_to_provider or provider_to_consumer." + + - name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: create subject filter with action deny for non-cloud + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + direction: both + action: deny + register: filter_present_deny_non_cloud_both + + - name: create subject filter with action deny for non-cloud p2c + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: provider_to_consumer + action: deny + register: filter_present_deny_non_cloud_p2c + + - name: create subject filter with action deny for non-cloud p2c + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: consumer_to_provider + action: deny + register: filter_present_deny_non_cloud_c2p + + - name: filter subject with direction assertions + assert: + that: + - filter_present_deny_non_cloud_both is changed + - filter_present_deny_non_cloud_both.current.0.vzRsSubjFiltAtt.attributes.action == "deny" + - filter_present_deny_non_cloud_p2c is changed + - filter_present_deny_non_cloud_p2c.current.0.vzRsFiltAtt.attributes.action == "deny" + - filter_present_deny_non_cloud_c2p is changed + - filter_present_deny_non_cloud_c2p.current.0.vzRsFiltAtt.attributes.action == "deny" + + - name: Execute tasks only for cloud sites + when: query_cloud.current | length > 0 # This condition will execute only cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: create subject filter with action deny casues error for cloud (error) + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_both_to_filter + action: deny + register: filter_present_deny_cloud + ignore_errors: yes + + - name: create subject filter with action deny for cloud p2c + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: provider_to_consumer + action: deny + register: filter_present_deny_cloud_p2c + ignore_errors: yes + + - name: create subject filter with action deny for cloud p2c + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_one_way_to_filter + direction: provider_to_consumer + action: deny + register: filter_present_deny_cloud_c2p + ignore_errors: yes + + - name: cloud assertions + assert: + that: + - filter_present_deny_cloud.msg.startswith("APIC Error 1: Invalid Configuration CLOUD_ONLY_PERMIT_ACTION_SUPPORTED") + - filter_present_deny_cloud_p2c.msg.startswith("APIC Error 1: Invalid Configuration CLOUD_ONLY_PERMIT_ACTION_SUPPORTED") + - filter_present_deny_cloud_c2p.msg.startswith("APIC Error 1: Invalid Configuration CLOUD_ONLY_PERMIT_ACTION_SUPPORTED") + + always: + - name: cleanup tenant + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent + when: tenant_present is changed