From 15af3183a35ee67edbf0d04ecefb11087d385cc5 Mon Sep 17 00:00:00 2001 From: Faiz Mohammad Date: Tue, 21 May 2024 17:16:31 +0530 Subject: [PATCH] enhancement for node_mgmt_epg_to_contract --- .../modules/aci_node_mgmt_epg_to_contract.py | 366 +++++++++++++ .../aci_node_mgmt_epg_to_contract/aliases | 2 + .../tasks/main.yml | 487 ++++++++++++++++++ 3 files changed, 855 insertions(+) create mode 100644 plugins/modules/aci_node_mgmt_epg_to_contract.py create mode 100644 tests/integration/targets/aci_node_mgmt_epg_to_contract/aliases create mode 100644 tests/integration/targets/aci_node_mgmt_epg_to_contract/tasks/main.yml 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..bcc9b4cd2 --- /dev/null +++ b/plugins/modules/aci_node_mgmt_epg_to_contract.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2023, Akini Ross (@akinross) +# 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_epg_to_contract +short_description: Bind EPGs to Contracts (fv:RsCons, fv:RsProv, fv:RsProtBy, fv:RsConsIf, and fv:RsIntraEpg) +description: +- Bind EPGs to Contracts on Cisco ACI fabrics. +notes: +- The C(tenant), C(app_profile), C(EPG), and C(Contract) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap), M(cisco.aci.aci_epg), and M(cisco.aci.aci_contract) modules can be used for this. +options: + ap: + description: + - Name of an existing application network profile, that will contain the EPGs. + type: str + aliases: [ app_profile, app_profile_name ] + 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, intra_epg ] + epg: + description: + - The name of the end point group. + type: str + aliases: [ epg_name ] + priority: + description: + - 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 ] + contract_label: + description: + - Contract label to match + type: str + subject_label: + description: + - Subject label to match + type: str + 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 + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_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(fv:RsIntraEpg). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a new contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + contract_label: contractlabel + subject_label: subjlabel + state: present + delegate_to: localhost + +- name: Remove an existing contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + state: absent + delegate_to: localhost + +- name: Query a specific contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Query all provider contract to EPG bindings + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + contract_type: provider + state: query + delegate_to: localhost + register: query_result +""" + +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( + type=dict(type="str", choices=["in_band", "out_of_band"], required=True) #>>>> really required, both are under the same class + contract_type=dict(type="str", required=True, choices=["consumer", "provider", "taboo", "interface"]), + # ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + 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"]), + # tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects >>>>>> HARDCODE mgmt TENANT ??? + # contract_label=dict(type="str"), + # subject_label=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract", "epg"]], + ["state", "present", ["contract", "epg"]], + ], + ) + + # ap = module.params.get("ap") + 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") + # tenant = module.params.get("tenant") + # contract_label = module.params.get("contract_label") + # subject_label = module.params.get("subject_label") + + aci_class = ACI_CLASS_MAPPING[contract_type]["class"] + aci_rn = ACI_CLASS_MAPPING[contract_type]["rn"] + aci_name = ACI_CLASS_MAPPING[contract_type]["name"] + child_classes = [] + + if contract_type != "provider" and provider_match is not None: + module.fail_json(msg="the 'provider_match' is only configurable for Provided Contracts") + + # if contract_type in ["taboo", "interface"] and (contract_label is not None or subject_label is not None): + # module.fail_json(msg="the 'contract_label' and 'subject_label' are not configurable for {0} contracts".format(contract_type)) + + if contract_type not in ["taboo", "interface"]: + contract_label_class = CONTRACT_LABEL_MAPPING.get(contract_type) + subject_label_class = SUBJ_LABEL_MAPPING.get(contract_type) + child_classes = [subject_label_class, contract_label_class] + + 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="fvAEPg", + aci_rn="epg-{0}".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}, + ), + child_classes=child_classes, #>>>>> are child classes present under the DN???? Remove if not. + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + # if contract_label is not None: + # child_configs.append({contract_label_class: {"attributes": {"name": contract_label}}}) + # if subject_label is not None: + # child_configs.append({subject_label_class: {"attributes": {"name": subject_label}}}) + aci.payload( + aci_class=aci_class, + class_config={"matchT": provider_match, "prio": priority, aci_name: contract}, + child_configs=child_configs, + ) + + 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..918be3f34 --- /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 \ No newline at end of file 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..4227134ac --- /dev/null +++ b/tests/integration/targets/aci_node_mgmt_epg_to_contract/tasks/main.yml @@ -0,0 +1,487 @@ +# Test code for the ACI modules +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# 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 + +- 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: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + ansible.builtin.include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Ensure contract binding does not exist prior to testing + cisco.aci.aci_node_mgmt_epg_to_contract: + 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") }}' + epg: "{{ item }}" + contract_type: provider + contract: "anstest_http" + state: absent + +- 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: Remove node mgmt in_band epg + cisco.aci.aci_node_mgmt_epg: + <<: *aci_info + type: in_band + epg: "{{ item }}" + encap: vlan-1 + bd: bd1 + state: absent + loop: + - ansible-inband + - ansible-inband-2 + + - name: Remove node mgmt out_of_band epg + cisco.aci.aci_node_mgmt_epg: + <<: *aci_info + type: out_of_band + epg: "{{ item }}" + state: absent + loop: + - ansible-outofband + - ansible-outofband-2 + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_tenant_absent + state: present + register: tenant_present + + - name: ensure contracts exist for tests to kick off + cisco.aci.aci_contract: + <<: *aci_tenant_present + contract: "{{ item }}" + with_items: ["anstest_http", "anstest_https", "anstest_db", "anstest_no_previous", "anstest_no_lb_no_v"] + + - name: ensure ap exists + cisco.aci.aci_ap: &aci_ap_present + <<: *aci_tenant_present + ap: anstest + register: ap_present + + - name: ensure epg exists + cisco.aci.aci_epg: &aci_epg_present + <<: *aci_ap_present + epg: anstest + register: epg_present + + - name: bind contract to epg - check mode works + cisco.aci.aci_epg_to_contract: &aci_epg_provide_present + <<: *aci_epg_present + contract_type: provider + contract: anstest_http + contract_label: anstest_contract_label + subject_label: anstest_subject_label + check_mode: true + register: provide_present_check_mode + + - name: bind contract to epg - provide works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_provide_present + register: provide_present + + - name: bind contract to epg - consume works + cisco.aci.aci_epg_to_contract: &aci_epg_consume_present + <<: *aci_epg_provide_present + contract_type: consumer + contract: anstest_db + register: consume_present + + - name: bind contract to epg - add additional contract + cisco.aci.aci_epg_to_contract: &aci_epg_provide_present2 + <<: *aci_epg_provide_present + contract: anstest_https + provider_match: at_most_one + contract_label: "{{ fakevar | default(omit) }}" + subject_label: "{{ fakevar | default(omit) }}" + register: provide_present2 + + - name: bind contract to epg - idempotency works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_provide_present + register: idempotent_present + + - name: missing param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_tenant_present + contract_type: provider + ignore_errors: true + register: missing_param_present + + - name: missing required param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_tenant_present + ignore_errors: true + register: missing_required_present + + - name: incompatible param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_present + provider_match: all + ignore_errors: true + register: incompatible_present + + - name: present assertions + ansible.builtin.assert: + that: + - provide_present_check_mode is changed + - provide_present_check_mode.sent.fvRsProv.attributes.tnVzBrCPName == 'anstest_http' + - provide_present is changed + - provide_present.sent == provide_present_check_mode.sent + - provide_present.current.0.fvRsProv.attributes.annotation == 'orchestrator:ansible' + - provide_present.previous == [] + - consume_present is changed + - consume_present.previous == [] + - consume_present.sent.fvRsCons.attributes.tnVzBrCPName == 'anstest_db' + - provide_present2 is changed + - provide_present2.previous == [] + - missing_param_present is failed + - 'missing_param_present.msg == "state is present but all of the following are missing: ap, contract, epg"' + - missing_required_present is failed + - 'missing_required_present.msg == "missing required arguments: contract_type"' + - incompatible_present is failed + - incompatible_present.msg == "the 'provider_match' is only configurable for Provided Contracts" + + - name: bind taboo contract to epg + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_present + contract: anstest_https + contract_type: taboo + register: taboo_present + + - name: bind interface contract to epg + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_present + contract: anstest_https + contract_type: interface + register: interface_present + + - name: bind intra epg contract to epg + cisco.aci.aci_epg_to_contract: &aci_epg_intra_present + <<: *aci_epg_present + contract: anstest_https + contract_type: intra_epg + register: intra_epg_present + + - name: bind intra epg contract to epg (error contract label) + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_intra_present + contract_label: anstest_contract_label + ignore_errors: true + register: err_contract_label + + - name: bind intra epg contract to epg (error subject label) + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_intra_present + subject_label: anstest_subject_label + ignore_errors: true + register: err_subject_label + + - name: bind intra epg contract to epg (error subject and contract label) + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_intra_present + contract_label: anstest_contract_label + subject_label: anstest_subject_label + ignore_errors: true + register: err_subject_and_contract_label + + - name: present assertions for taboo, interface and intra_epg contract types + assert: + that: + - taboo_present is changed + - taboo_present.previous == [] + - taboo_present.current.0.fvRsProtBy.attributes.tnVzTabooName == 'anstest_https' + - interface_present is changed + - interface_present.previous == [] + - interface_present.current.0.fvRsConsIf.attributes.tnVzCPIfName == 'anstest_https' + - intra_epg_present is changed + - intra_epg_present.previous == [] + - intra_epg_present.current.0.fvRsIntraEpg.attributes.tnVzBrCPName == 'anstest_https' + - err_contract_label is failed + - err_contract_label.msg == "the 'contract_label' and 'subject_label' are not configurable for intra_epg contracts" + - err_subject_label is failed + - err_subject_label.msg == "the 'contract_label' and 'subject_label' are not configurable for intra_epg contracts" + - err_subject_and_contract_label is failed + - err_subject_and_contract_label.msg == "the 'contract_label' and 'subject_label' are not configurable for intra_epg contracts" + + # TEST NO PREVIOUS + - name: create epg contract to epg with no previous (check mode) + cisco.aci.aci_epg_to_contract: &aci_epg_to_contract_no_previous + <<: *aci_epg_consume_present + contract: anstest_no_previous + no_previous: true + check_mode: true + register: epg_to_contract_present_no_previous_cm + + - name: create epg contract to epg with no previous + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_previous + register: epg_to_contract_present_no_previous + + - name: create epg contract to epg with no previous again + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_previous + register: epg_to_contract_present_no_previous_again + + - name: update epg contract to epg with no previous + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_previous + priority: level1 + register: update_epg_to_contract_present_no_previous + + - name: delete epg contract to epg with no previous + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_previous + state: absent + register: delete_epg_to_contract_present_no_previous + + - name: delete epg contract to epg with no previous again + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_previous + state: absent + register: delete_epg_to_contract_present_no_previous_again + + - name: no previous asserts + ansible.builtin.assert: + that: + - epg_to_contract_present_no_previous_cm is changed + - epg_to_contract_present_no_previous_cm.current == [] + - epg_to_contract_present_no_previous_cm.previous == [] + - epg_to_contract_present_no_previous_cm.proposed.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_previous" + - epg_to_contract_present_no_previous_cm.proposed.fvRsCons.attributes.annotation == "orchestrator:ansible" + - epg_to_contract_present_no_previous is changed + - epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_previous" + - epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.annotation == "orchestrator:ansible" + - epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.prio == "unspecified" + - epg_to_contract_present_no_previous.proposed.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_previous" + - epg_to_contract_present_no_previous.proposed.fvRsCons.attributes.annotation == "orchestrator:ansible" + - epg_to_contract_present_no_previous.previous == [] + - epg_to_contract_present_no_previous_again is changed + - epg_to_contract_present_no_previous_again.current == epg_to_contract_present_no_previous.current + - epg_to_contract_present_no_previous_again.proposed == epg_to_contract_present_no_previous.proposed + - epg_to_contract_present_no_previous_again.previous == [] + - update_epg_to_contract_present_no_previous is changed + - update_epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_previous" + - update_epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.annotation == "orchestrator:ansible" + - update_epg_to_contract_present_no_previous.current.0.fvRsCons.attributes.prio == "level1" + - delete_epg_to_contract_present_no_previous is changed + - delete_epg_to_contract_present_no_previous.current == [] + - delete_epg_to_contract_present_no_previous.previous == [] + - delete_epg_to_contract_present_no_previous.proposed == {} + - delete_epg_to_contract_present_no_previous_again is changed + - delete_epg_to_contract_present_no_previous_again.current == [] + - delete_epg_to_contract_present_no_previous_again.previous == [] + - delete_epg_to_contract_present_no_previous_again.proposed == {} + + # TEST NO PREVIOUS & NO VERIFICATION + - name: create epg contract to epg with no previous & no verify (check mode) + cisco.aci.aci_epg_to_contract: &aci_epg_to_contract_no_lb_no_v + <<: *aci_epg_consume_present + contract: anstest_no_lb_no_v + no_previous: true + no_verify: true + check_mode: true + register: epg_to_contract_present_no_lb_no_v_cm + + - name: create epg contract to epg with no look bac & no verify + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_lb_no_v + register: epg_to_contract_present_no_lb_no_v + + - name: create epg contract to epg with no previous again & no verify + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_lb_no_v + register: epg_to_contract_present_no_lb_no_v_again + + - name: update epg contract to epg with no previous & no verify + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_lb_no_v + priority: level1 + register: update_epg_to_contract_present_no_lb_no_v + + - name: delete epg contract to epg with no previous & no verify + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_lb_no_v + state: absent + register: delete_epg_to_contract_present_no_lb_no_v + + - name: delete epg contract to epg with no previous again & no verify + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_to_contract_no_lb_no_v + state: absent + register: delete_epg_to_contract_present_no_lb_no_v_again + + - name: no previous & no verify asserts + ansible.builtin.assert: + that: + - epg_to_contract_present_no_lb_no_v_cm is changed + - epg_to_contract_present_no_lb_no_v_cm.current.0 == epg_to_contract_present_no_lb_no_v_cm.proposed + - epg_to_contract_present_no_lb_no_v_cm.previous == [] + - epg_to_contract_present_no_lb_no_v_cm.proposed.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_lb_no_v" + - epg_to_contract_present_no_lb_no_v_cm.proposed.fvRsCons.attributes.annotation == "orchestrator:ansible" + - epg_to_contract_present_no_lb_no_v is changed + - epg_to_contract_present_no_lb_no_v.current.0 == epg_to_contract_present_no_lb_no_v.proposed + - epg_to_contract_present_no_lb_no_v.previous == [] + - epg_to_contract_present_no_lb_no_v_again is changed + - epg_to_contract_present_no_lb_no_v_again.current == epg_to_contract_present_no_lb_no_v.current + - epg_to_contract_present_no_lb_no_v_again.proposed == epg_to_contract_present_no_lb_no_v.proposed + - epg_to_contract_present_no_lb_no_v_again.previous == [] + - update_epg_to_contract_present_no_lb_no_v is changed + - update_epg_to_contract_present_no_lb_no_v.current.0.fvRsCons.attributes.dn == "uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_no_lb_no_v" + - update_epg_to_contract_present_no_lb_no_v.current.0.fvRsCons.attributes.annotation == "orchestrator:ansible" + - update_epg_to_contract_present_no_lb_no_v.current.0.fvRsCons.attributes.prio == "level1" + - delete_epg_to_contract_present_no_lb_no_v is changed + - delete_epg_to_contract_present_no_lb_no_v.current == [] + - delete_epg_to_contract_present_no_lb_no_v.previous == [] + - delete_epg_to_contract_present_no_lb_no_v.proposed == {} + - delete_epg_to_contract_present_no_lb_no_v_again is changed + - delete_epg_to_contract_present_no_lb_no_v_again.current == [] + - delete_epg_to_contract_present_no_lb_no_v_again.previous == [] + - delete_epg_to_contract_present_no_lb_no_v_again.proposed == {} + + - name: get binding + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_provide_present2 + state: query + register: query_provide_contract + + - name: get binding + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_present + state: query + register: query_consume_contract + + - name: get all bindings + cisco.aci.aci_epg_to_contract: + <<: *aci_tenant_present + state: query + tenant: "{{ fakevar | default(omit) }}" + contract_type: provider + register: query_all + + - name: missing required param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_tenant_present + state: query + ignore_errors: true + register: missing_required_query + + - name: query assertions + ansible.builtin.assert: + that: + - query_provide_contract is not changed + - query_provide_contract.current != [] + - '"uni/tn-ansible_test/ap-anstest/epg-anstest/rsprov-anstest_https.json" in query_provide_contract.url' + - query_consume_contract is not changed + - query_consume_contract.current != [] + - '"uni/tn-ansible_test/ap-anstest/epg-anstest/rscons-anstest_db.json" in query_consume_contract.url' + - query_all is not changed + - '"class/fvRsProv.json" in query_all.url' + - missing_required_query is failed + - 'missing_required_query.msg == "missing required arguments: contract_type"' + + - name: delete consume binding - check mode works + cisco.aci.aci_epg_to_contract: &aci_epg_consume_absent + <<: *aci_epg_consume_present + state: absent + check_mode: true + register: consume_absent_check_mode + + - name: delete consume binding - deletion works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_absent + register: consume_absent + + - name: delete provide binding - deletion works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_provide_present + state: absent + register: provide_absent + + - name: delete provide binding - deletion works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_provide_present2 + state: absent + register: provide_absent2 + + - name: delete consume binding - idempotency works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_absent + register: consume_absent_idempotent + + - name: missing param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_absent + contract: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_param_absent + + - name: missing required param - failure message works + cisco.aci.aci_epg_to_contract: + <<: *aci_epg_consume_absent + contract_type: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: missing_required_absent + + - name: absent assertions + 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 + - provide_absent is changed + - provide_absent.previous.0.fvRsProv is defined + - provide_absent2 is changed + - consume_absent_idempotent is not changed + - consume_absent_idempotent.previous == [] + - 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: contract_type"' + + - name: cleanup contracts + cisco.aci.aci_contract: + <<: *aci_tenant_present + state: absent + contract: "{{ item }}" + with_items: ["anstest_http", "anstest_https", "anstest_db", "anstest_no_previous", "anstest_no_lb_no_v"] + + - name: cleanup epg + cisco.aci.aci_epg: + <<: *aci_epg_present + state: absent + when: epg_present is changed + + - name: cleanup ap + cisco.aci.aci_ap: + <<: *aci_ap_present + state: absent + when: ap_present is changed + + - name: cleanup tenant + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent + when: tenant_present is changed \ No newline at end of file