diff --git a/plugins/modules/aci_node_mgmt_epg_to_contract.py b/plugins/modules/aci_node_mgmt_epg_to_contract.py new file mode 100644 index 000000000..2cd500d1b --- /dev/null +++ b/plugins/modules/aci_node_mgmt_epg_to_contract.py @@ -0,0 +1,370 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Faiz Mohammad (@Ziaf007) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_node_mgmt_epg_to_contract +short_description: Bind Node Mgmt EPGs to Contracts (fv:RsCons, fv:RsProv, fv:RsProtBy, fv:RsConsIf, and mgmt:RsOoBProv) +description: +- Bind Node mgmt EPGs to Contracts on Cisco ACI fabrics. +notes: +- The C(Node Mgmt EPG), and C(Contract) used must exist before using this module in your playbook. + The M(cisco.aci.aci_node_mgmt_epg), M(cisco.aci.aci_oob_contract) and M(cisco.aci.aci_contract) modules can be used for this. +options: + contract: + description: + - The name of the contract or contract interface. + type: str + aliases: [ contract_name, contract_interface ] + contract_type: + description: + - Determines the type of the Contract. + type: str + required: true + choices: [ consumer, provider, taboo, interface ] + epg: + description: + - The name of the Node Mgmt end point group. + type: str + aliases: [ epg_name ] + priority: + description: + - Quality of Service (QoS) class. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + provider_match: + description: + - The matching algorithm for Provided Contracts. + - The APIC defaults to C(at_least_one) when unset during creation. + type: str + choices: [ all, at_least_one, at_most_one, none ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_node_mgmt_epg +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_oob_contract +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fv:RsCons), B(fv:RsProv), B(fv:RsProtBy), B(fv:RsConsIf), and B(mgmt:RsOoBProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Faiz Mohammad (@Ziaf007) +""" + +EXAMPLES = r""" +- name: Add a new contract to Inband EPG binding + cisco.aci.aci_node_mgmt_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + epg: anstest + epg_type: in_band + contract: anstest_http + contract_type: consumer + priority: level2 + state: present + delegate_to: localhost + +- name: Add a new contract to Out-of-Band EPG binding + cisco.aci.aci_node_mgmt_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + epg: anstest + epg_type: out_of_band + contract: anstest_http + contract_type: provider + priority: level3 + provider_match: at_least_one + state: present + delegate_to: localhost + +- name: Query a specific contract to EPG binding + cisco.aci.aci_inbepg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + epg: anstest + epg_type: in_band + contract: anstest_http + contract_type: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all provider contract to EPG bindings + cisco.aci.aci_inbepg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + contract_type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Remove an existing contract to Inband EPG binding + cisco.aci.aci_inbepg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + epg: anstest + epg_type: in_band + contract: anstest_http + contract_type: consumer + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +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.constants import ACI_CLASS_MAPPING, CONTRACT_LABEL_MAPPING, PROVIDER_MATCH_MAPPING, SUBJ_LABEL_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + contract_type=dict(type="str", choices=["consumer", "provider", "taboo", "interface"], required=True), + epg_type=dict( + type="str", aliases=["type"], choices=["in_band", "out_of_band"], required=True + ), # required for querying as provider class for INB and OOB are different + epg=dict(type="str", aliases=["epg_name"]), # Not required for querying all objects + contract=dict(type="str", aliases=["contract_name", "contract_interface"]), # Not required for querying all objects + priority=dict(type="str", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"]), + provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["epg", "contract"]], + ["state", "present", ["epg", "contract"]], + ], + ) + + epg_type = module.params.get("epg_type") + contract = module.params.get("contract") + contract_type = module.params.get("contract_type") + epg = module.params.get("epg") + priority = module.params.get("priority") + provider_match = module.params.get("provider_match") + if provider_match is not None: + provider_match = PROVIDER_MATCH_MAPPING[provider_match] + state = module.params.get("state") + + if epg_type == "in_band": + + if contract_type != "provider" and provider_match is not None: + module.fail_json(msg="the provider_match is only configurable for Provider Contracts") + + aci_class = ACI_CLASS_MAPPING[contract_type]["class"] + aci_rn = ACI_CLASS_MAPPING[contract_type]["rn"] + aci_name = ACI_CLASS_MAPPING[contract_type]["name"] + class_config = {"matchT": provider_match, "prio": priority, aci_name: contract} + + elif epg_type == "out_of_band": + + if contract_type != "provider": + module.fail_json(msg="only provider contract_type is supported for out_of_band epg_type.") + + if provider_match is not None: + module.fail_json(msg="The provider_match argument is not supported for out_of_band Provider contracts") + + aci_class = "mgmtRsOoBProv" + aci_rn = "rsooBProv-" + aci_name = "tnVzOOBBrCPName" + class_config = {"prio": priority, aci_name: contract} + + class_Map = {"in_band": [dict(epg_class="mgmtInB", epg_rn="inb-{0}")], "out_of_band": [dict(epg_class="mgmtOoB", epg_rn="oob-{0}")]} + + aci = ACIModule(module) + aci.construct_url( + # root_class=dict( + # aci_class="fvTenant", + # aci_rn="tn-mgmt", + # module_object="mgmt", + # target_filter={"name": "mgmt"} + # ), + # subclass_1=dict( + # aci_class="mgmtMgmtP", + # aci_rn="mgmtp-default", + # module_object="default", + # target_filter={"name": "default"} + # ), + # subclass_2=dict( + # aci_class=class_Map[epg_type][0]["epg_class"], + # aci_rn=class_Map[epg_type][0]["epg_rn"].format(epg), + # module_object=epg, + # target_filter={"name": epg} + # ), + # subclass_3=dict( + # aci_class=aci_class, + # aci_rn="{0}{1}".format(aci_rn, contract), + # module_object=contract, + # target_filter={aci_name: contract} + # ) + root_class=dict( + # aci_class="fvTenant", + aci_rn="tn-mgmt/mgmtp-default", + module_object=None + ), + subclass_1=dict( + aci_class=class_Map[epg_type][0]["epg_class"], + aci_rn=class_Map[epg_type][0]["epg_rn"].format(epg), + module_object=epg, + target_filter={"name": epg} + ), + subclass_2=dict( + aci_class=aci_class, + aci_rn="{0}{1}".format(aci_rn, contract), + module_object=contract, + target_filter={aci_name: contract} + ) + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=class_config, + ) + + aci.get_diff(aci_class=aci_class) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_node_mgmt_epg_to_contract/aliases b/tests/integration/targets/aci_node_mgmt_epg_to_contract/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_node_mgmt_epg_to_contract/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_node_mgmt_epg_to_contract/tasks/main.yml b/tests/integration/targets/aci_node_mgmt_epg_to_contract/tasks/main.yml new file mode 100644 index 000000000..7908e1c83 --- /dev/null +++ b/tests/integration/targets/aci_node_mgmt_epg_to_contract/tasks/main.yml @@ -0,0 +1,481 @@ +# Test code for the ACI modules +# Copyright: (c) 2025, Faiz Mohammad (@faizmoh) + +# 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 + ansible.builtin.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 + +# SET VARS +- name: Set vars + ansible.builtin.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("debug") }}' + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +- name: Verify Cloud and Non-Cloud Sites in use. + ansible.builtin.include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for ACI v5+ and non-cloud sites + when: + - version.current.0.topSystem.attributes.version is version('5', '>=') + - query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # CLEAN ENVIRONMENT + - name: ensuring OOB EPG doesn't exist before + cisco.aci.aci_node_mgmt_epg: &aci_oob_epg_absent + <<: *aci_info + epg: anstest_oob + type: out_of_band + state: absent + + - name: ensuring INB EPG doesn't exist before + cisco.aci.aci_node_mgmt_epg: &aci_inb_epg_absent + <<: *aci_info + epg: anstest_inb + type: in_band + bd: inb + encap: vlan-1604 + state: absent + + - name: creating new OOB EPG for testing + cisco.aci.aci_node_mgmt_epg: &aci_oob_epg_present + <<: *aci_oob_epg_absent + state: present + + - name: creating new INB EPG for testing + cisco.aci.aci_node_mgmt_epg: &aci_inb_epg_present + <<: *aci_inb_epg_absent + state: present + + # CREATE - INB + - name: bind inb provider contract to inband epg - Check Mode works + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_inb_epg_provide_present + <<: *aci_info + contract_type: provider + contract: aci_inb_http + epg: anstest_inb + epg_type: in_band + state: present + check_mode: true + register: inb_provide_present_check_mode + + - name: bind inb provider contract to inband epg - Normal Mode works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + register: inb_provide_present + + - name: bind additional inb provider contract to inband epg - add additional contract + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_inb_epg_provide_present2 + <<: *aci_inb_epg_provide_present + contract: aci_inb_https + provider_match: at_most_one + register: inb_provide_present2 + + - name: bind inb provider contract to epg again - idempotency works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + register: inb_idempotent_present + + - name: Assert Inband Provider Contract was created + ansible.builtin.assert: + that: + - inb_provide_present_check_mode is changed + - inb_provide_present_check_mode.previous == [] + - inb_provide_present_check_mode.current == [] + - inb_provide_present_check_mode.sent.fvRsProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprov-aci_inb_http" + - inb_provide_present_check_mode.sent.fvRsProv.attributes.tnVzBrCPName == 'aci_inb_http' + - inb_provide_present is changed + - inb_provide_present.previous == [] + - inb_provide_present.sent == inb_provide_present_check_mode.sent + - inb_provide_present.current.0.fvRsProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprov-aci_inb_http" + - inb_provide_present.current.0.fvRsProv.attributes.tnVzBrCPName == "aci_inb_http" + - inb_provide_present.current.0.fvRsProv.attributes.annotation == 'orchestrator:ansible' + - inb_provide_present.current.0.fvRsProv.attributes.prio == "unspecified" + - inb_provide_present2 is changed + - inb_provide_present2.previous == [] + - inb_provide_present2.current.0.fvRsProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprov-aci_inb_https" + - inb_provide_present2.current.0.fvRsProv.attributes.tnVzBrCPName == "aci_inb_https" + - inb_provide_present2.current.0.fvRsProv.attributes.annotation == 'orchestrator:ansible' + - inb_provide_present2.current.0.fvRsProv.attributes.prio == "unspecified" + - inb_provide_present2.current.0.fvRsProv.attributes.matchT == "AtmostOne" + - inb_idempotent_present is not changed + - inb_idempotent_present.previous.0.fvRsProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprov-aci_inb_http" + - inb_idempotent_present.previous.0.fvRsProv.attributes.tnVzBrCPName == "aci_inb_http" + - inb_idempotent_present.previous.0.fvRsProv.attributes.annotation == 'orchestrator:ansible' + - inb_idempotent_present.previous.0.fvRsProv.attributes.prio == "unspecified" + - inb_idempotent_present.current.0.fvRsProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprov-aci_inb_http" + - inb_idempotent_present.current.0.fvRsProv.attributes.tnVzBrCPName == "aci_inb_http" + - inb_idempotent_present.current.0.fvRsProv.attributes.annotation == 'orchestrator:ansible' + - inb_idempotent_present.current.0.fvRsProv.attributes.prio == "unspecified" + + - name: bind inb consumer contract to epg + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_inb_epg_consume_present + <<: *aci_inb_epg_provide_present + contract_type: consumer + contract: anstest_inb_db + register: inb_consume_present + + - name: bind taboo contract to inband epg + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + contract: aci_inb_https + contract_type: taboo + register: taboo_present + + - name: bind interface contract to inband epg + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + contract: aci_inb_https + contract_type: interface + register: interface_present + + - name: Assert all type Inband Contracts are created + ansible.builtin.assert: + that: + - inb_consume_present is changed + - inb_consume_present.previous == [] + - inb_consume_present.current.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - inb_consume_present.current.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - inb_consume_present.current.0.fvRsCons.attributes.annotation == 'orchestrator:ansible' + - inb_consume_present.current.0.fvRsCons.attributes.prio == "unspecified" + - taboo_present is changed + - taboo_present.previous == [] + - taboo_present.current.0.fvRsProtBy.attributes.tnVzTabooName == 'aci_inb_https' + - taboo_present.current.0.fvRsProtBy.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsprotBy-aci_inb_https" + - interface_present is changed + - interface_present.previous == [] + - interface_present.current.0.fvRsConsIf.attributes.tnVzCPIfName == 'aci_inb_https' + - interface_present.current.0.fvRsConsIf.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rsconsIf-aci_inb_https" + + + # CREATE - OOB + - name: bind oob provider contract to out-of-band epg - Check Mode works + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_oob_epg_provide_present + <<: *aci_info + contract_type: provider + contract: aci_oob_http + epg: anstest_oob + type: out_of_band + state: present + check_mode: true + register: oob_provide_present_check_mode + + - name: bind oob provider contract to out-of-band epg - Normal Mode works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + register: oob_provide_present + + - name: bind oob Provider contract to out-of-band epg - add additional contract + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_oob_epg_provide_present2 + <<: *aci_oob_epg_provide_present + contract: aci_oob_https + register: oob_provide_present2 + + - name: bind oob provider contract to epg again - idempotency works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + register: oob_idempotent_present + + - name: Assert OOB Contract was created + ansible.builtin.assert: + that: + - oob_provide_present_check_mode is changed + - oob_provide_present_check_mode.previous == [] + - oob_provide_present_check_mode.current == [] + - oob_provide_present_check_mode.sent.mgmtRsOoBProv.attributes.tnVzOOBBrCPName == 'aci_oob_http' + - oob_provide_present_check_mode.sent.mgmtRsOoBProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/oob-anstest_oob/rsooBProv-aci_oob_http" + - oob_provide_present is changed + - oob_provide_present.previous == [] + - oob_provide_present.sent == oob_provide_present_check_mode.sent + - oob_provide_present.sent.mgmtRsOoBProv.attributes.tnVzOOBBrCPName == 'aci_oob_http' + - oob_provide_present.sent.mgmtRsOoBProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/oob-anstest_oob/rsooBProv-aci_oob_http" + - oob_provide_present.current.0.mgmtRsOoBProv.attributes.prio == "unspecified" + - oob_provide_present.current.0.mgmtRsOoBProv.attributes.annotation == 'orchestrator:ansible' + - oob_provide_present2 is changed + - oob_provide_present2.previous == [] + - oob_provide_present2.current.0.mgmtRsOoBProv.attributes.annotation == 'orchestrator:ansible' + - oob_provide_present2.current.0.mgmtRsOoBProv.attributes.tnVzOOBBrCPName == 'aci_oob_https' + - oob_provide_present2.current.0.mgmtRsOoBProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/oob-anstest_oob/rsooBProv-aci_oob_https" + - oob_provide_present2.current.0.mgmtRsOoBProv.attributes.prio == "unspecified" + + # UPDATE + - name: Update inb consumer contract - check mode + cisco.aci.aci_node_mgmt_epg_to_contract: &update_inb_consumer_contract + <<: *aci_inb_epg_consume_present + priority: "level6" + check_mode: true + register: upd_inb_consumer_check_mode + + - name: Update inb consumer contract - normal mode + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_present + priority: "level6" + register: upd_inb_consumer + + - name: Update inb consumer contract - again + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *update_inb_consumer_contract + register: upd_inb_consumer_again + + - name: Update oob provider contract + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + priority: "level4" + register: upd_oob_provider + + - name: Assert Contract Update operations + ansible.builtin.assert: + that: + - upd_inb_consumer_check_mode is changed + - upd_inb_consumer_check_mode.previous.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer_check_mode.previous.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer_check_mode.previous.0.fvRsCons.attributes.prio == "unspecified" + - upd_inb_consumer_check_mode.current.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer_check_mode.current.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer_check_mode.current.0.fvRsCons.attributes.prio == "unspecified" + - upd_inb_consumer_check_mode.proposed.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer_check_mode.proposed.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer_check_mode.proposed.fvRsCons.attributes.prio == "level6" + - upd_inb_consumer is changed + - upd_inb_consumer.previous.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer.previous.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer.previous.0.fvRsCons.attributes.prio == "unspecified" + - upd_inb_consumer.current.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer.current.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer.current.0.fvRsCons.attributes.prio == "level6" + - upd_inb_consumer_again is not changed + - upd_inb_consumer_again.previous.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer_again.previous.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer_again.previous.0.fvRsCons.attributes.prio == "level6" + - upd_inb_consumer_again.current.0.fvRsCons.attributes.dn == "uni/tn-mgmt/mgmtp-default/inb-anstest_inb/rscons-anstest_inb_db" + - upd_inb_consumer_again.current.0.fvRsCons.attributes.tnVzBrCPName == "anstest_inb_db" + - upd_inb_consumer_again.current.0.fvRsCons.attributes.prio == "level6" + - upd_oob_provider is changed + - upd_oob_provider.previous.0.mgmtRsOoBProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/oob-anstest_oob/rsooBProv-aci_oob_http" + - upd_oob_provider.previous.0.mgmtRsOoBProv.attributes.tnVzOOBBrCPName == "aci_oob_http" + - upd_oob_provider.previous.0.mgmtRsOoBProv.attributes.prio == "unspecified" + - upd_oob_provider.current.0.mgmtRsOoBProv.attributes.dn == "uni/tn-mgmt/mgmtp-default/oob-anstest_oob/rsooBProv-aci_oob_http" + - upd_oob_provider.current.0.mgmtRsOoBProv.attributes.tnVzOOBBrCPName == "aci_oob_http" + - upd_oob_provider.current.0.mgmtRsOoBProv.attributes.prio == "level4" + + + #MISSING PARAMS + - name: missing param - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + epg: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_param_present + + - name: missing required param - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + contract_type: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_required_present + + - name: incompatible param OOB - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + contract_type: consumer + ignore_errors: true + register: oob_incompatible_present + + - name: incompatible param OOB provider_match - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + provider_match: "at_most_one" + ignore_errors: true + register: oob_incompatible_present2 + + - name: incompatible param INB - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + contract_type: consumer + provider_match: at_least_one + ignore_errors: true + register: inb_incompatible_present + + - name: Assert Missing parameter failure messages + ansible.builtin.assert: + that: + - missing_param_present is failed + - 'missing_param_present.msg == "state is present but all of the following are missing: epg"' + - missing_required_present is failed + - 'missing_required_present.msg == "missing required arguments: contract_type"' + - oob_incompatible_present is failed + - 'oob_incompatible_present.msg == "only provider contract_type is supported for out_of_band epg_type."' + - oob_incompatible_present2 is failed + - 'oob_incompatible_present2.msg == "The provider_match argument is not supported for out_of_band Provider contracts"' + - inb_incompatible_present is failed + - 'inb_incompatible_present.msg == "the provider_match is only configurable for Provider Contracts"' + + # QUERY + - name: get inb binding for provider contract + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present2 + state: query + register: query_inb_provide_contract + + - name: get inb binding for consumer contract + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_present + state: query + register: query_inb_consume_contract + + - name: get oob binding for provider contract + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present2 + state: query + register: query_oob_provide_contract + + - name: Get all inband provider bindings + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_info + epg_type: in_band + tenant: "{{ fakevar | default(omit) }}" + state: query + contract_type: provider + register: query_inb_all + ignore_errors: yes + + - name: get all out_of_band provider bindings + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_info + epg_type: out_of_band + tenant: "{{ fakevar | default(omit) }}" + state: query + contract_type: provider + register: query_oob_all + ignore_errors: yes + + - name: missing required param - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_info + state: query + contract_type: provider + epg_type: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_required_query + + - name: Assert INB & OOB contracts were queried + ansible.builtin.assert: + that: + - query_inb_provide_contract is not changed + - query_inb_provide_contract.current != [] + - '"/api/class/mgmtInB.json" in query_inb_provide_contract.url' + - query_inb_provide_contract.filter_string is search('query-target-filter=eq\(mgmtInB.name,"anstest_inb"\)') + - query_inb_provide_contract.filter_string is search('rsp-subtree-filter=eq\(fvRsProv.tnVzBrCPName,"aci_inb_https"\)') + - query_inb_consume_contract is not changed + - query_inb_consume_contract.current != [] + - query_inb_consume_contract.filter_string is search('query-target-filter=eq\\(mgmtInB.name,"anstest_inb"\\)') + - query_inb_consume_contract.filter_string is search('rsp-subtree-filter=eq\\(fvRsCons.tnVzBrCPName,"anstest_inb_db"\\)') + - query_oob_provide_contract is not changed + - query_oob_provide_contract.current != [] + - '"/api/class/mgmtOoB.json" in query_oob_provide_contract.url' + - query_inb_all is not changed + - query_inb_all.filter_string == "" + - query_oob_all is not changed + - query_oob_all.filter_string == "" + - missing_required_query is failed + - 'missing_required_query.msg == "missing required arguments: epg_type"' + + # DELETE + - name: delete inb consumer binding - check mode works + cisco.aci.aci_node_mgmt_epg_to_contract: &aci_inb_epg_consume_absent + <<: *aci_inb_epg_consume_present + state: absent + check_mode: true + register: consume_absent_check_mode + + - name: delete inb consumer binding - deletion works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_absent + register: consume_absent + + - name: delete inb provider binding - deletion works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present + state: absent + register: inb_provide_absent + + - name: delete inb provider binding 2 - deletion works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_provide_present2 + state: absent + register: inb_provide_absent2 + + - name: delete inb consumer binding - idempotency works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_absent + register: consume_absent_idempotent + + - name: delete oob provider binding - deletion works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present + state: absent + register: oob_provide_absent + + - name: delete oob provider binding 2 - deletion works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_oob_epg_provide_present2 + state: absent + register: oob_provide_absent2 + + - name: missing param - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_absent + contract: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_param_absent + + - name: missing required param - failure message works + cisco.aci.aci_node_mgmt_epg_to_contract: + <<: *aci_inb_epg_consume_absent + epg_type: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_required_absent + + - name: Assert that INB & OOB Contract were deleted + ansible.builtin.assert: + that: + - consume_absent_check_mode is changed + - consume_absent_check_mode.previous.0.fvRsCons is defined + - consume_absent is changed + - consume_absent.previous == consume_absent_check_mode.previous + - inb_provide_absent is changed + - inb_provide_absent.previous.0.fvRsProv is defined + - inb_provide_absent2 is changed + - consume_absent_idempotent is not changed + - consume_absent_idempotent.previous == [] + - oob_provide_absent is changed + - oob_provide_absent.previous.0.mgmtRsOoBProv is defined + - oob_provide_absent2 is changed + - missing_param_absent is failed + - 'missing_param_absent.msg == "state is absent but all of the following are missing: contract"' + - missing_required_absent is failed + - 'missing_required_absent.msg == "missing required arguments: epg_type"' + + #CLEANUP + - name: cleanup OOB epg + cisco.aci.aci_node_mgmt_epg: + <<: *aci_oob_epg_absent + + - name: cleanup INB epg + cisco.aci.aci_node_mgmt_epg: + <<: *aci_inb_epg_absent