From c3b147a3575d09f75e09dab8463b1ffb47db8e51 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Tue, 6 May 2025 15:08:29 -0400 Subject: [PATCH 1/8] [minor_change] Addition of service graph modules and their test files --- meta/runtime.yml | 15 + plugins/module_utils/aci.py | 13 + ...l4l7_device_selection_interface_context.py | 412 ++++++++++ .../aci_l4l7_device_selection_policy.py | 332 ++++++++ .../aci_l4l7_service_graph_template.py | 271 +++++++ ...7_service_graph_template_abs_connection.py | 323 ++++++++ ...ice_graph_template_abs_connection_conns.py | 300 +++++++ ...ce_graph_template_functional_connection.py | 294 +++++++ .../aci_l4l7_service_graph_template_node.py | 359 +++++++++ ...i_l4l7_service_graph_template_term_node.py | 292 +++++++ .../tasks/main.yml | 342 ++++++++ .../tasks/main.yml | 244 ++++++ .../tasks/main.yml | 158 ++++ .../tasks/main.yml | 218 +++++ .../tasks/main.yml | 747 ++++++++++++++++++ .../tasks/main.yml | 291 +++++++ .../tasks/main.yml | 264 +++++++ .../tasks/main.yml | 233 ++++++ 18 files changed, 5108 insertions(+) create mode 100644 plugins/modules/aci_l4l7_device_selection_interface_context.py create mode 100644 plugins/modules/aci_l4l7_device_selection_policy.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template_abs_connection.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template_functional_connection.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template_node.py create mode 100644 plugins/modules/aci_l4l7_service_graph_template_term_node.py create mode 100644 tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template_abs_connection/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml create mode 100644 tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 4b462e205..345a53f66 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -276,3 +276,18 @@ plugin_routing: deprecation: removal_version: 3.0.0 warning_text: Use cisco.aci.aci_l4l7_policy_based_redirect_destination instead. + aci_l4l7_service_graph_template_func_conn: + redirect: cisco.aci.aci_l4l7_service_graph_template_functional_connection + deprecation: + removal_version: 3.0.0 + warning_text: Use aci_l4l7_service_graph_template_functional_connection instead. + aci_l4l7_service_graph_template_abs_conn: + redirect: cisco.aci.aci_l4l7_service_graph_template_abs_connection + deprecation: + removal_version: 3.0.0 + warning_text: Use cisco.aci.aci_l4l7_service_graph_template_abs_connection instead. + aci_l4l7_device_selection_if_context: + redirect: cisco.aci.aci_l4l7_device_selection_interface_context + deprecation: + removal_version: 3.0.0 + warning_text: Use cisco.aci.aci_l4l7_device_selection_interface_context instead. diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 21740e9bb..54cdbb337 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -1772,3 +1772,16 @@ def api_call(self, method, url, data=None, return_response=False): except KeyError: # Connection error self.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) + + def delete_child(self, path): + if self.params.get("port") is not None: + url = "{protocol}://{host}:{port}/{path}".format( + path=path, + **self.module.params + ) + else: + url = "{protocol}://{host}/{path}".format( + path=path, + **self.module.params + ) + self.api_call("DELETE", url) diff --git a/plugins/modules/aci_l4l7_device_selection_interface_context.py b/plugins/modules/aci_l4l7_device_selection_interface_context.py new file mode 100644 index 000000000..d20e2aec9 --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_device_selection_interface_context +short_description: Manage L4-L7 Device Selection Policy Logical Interface Contexts (vns:LIfCtx) +description: +- Manage L4-L7 Device Selection Policy Logical Interface Contexts +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + contract: + description: + - The name of an existing contract. + type: str + aliases: [ contract_name ] + graph: + description: + - The name of an existing Service Graph Template. + type: str + aliases: [ service_graph, service_graph_name ] + node: + description: + - The name of an existing Service Graph Node. + type: str + aliases: [ node_name ] + context: + description: + - The name of the logical interface context. + type: str + l3_destination: + description: + - Whether the context is a Layer3 destination. + - The APIC defaults to C(true) when unset during creation. + type: bool + aliases: [ l3_dest ] + permit_log: + description: + - Whether to log permitted traffic. + - The APIC defaults to C(false) when unset during creation. + type: bool + bridge_domain: + description: + - The Bridge Domain to bind to the Context. + type: str + aliases: [ bd, bd_name ] + bridge_domain_tenant: + description: + - The tenant the Bridge Domain resides in. + - Omit this variable if both context and Bridge Domain are in the same tenant. + - Intended use case is for when the Bridge Domain is in the common tenant, but the context is not. + type: str + aliases: [ bd_tenant ] + logical_device: + description: + - The Logical Device to bind the context to. + type: str + logical_interface: + description: + - The Logical Interface to bind the context to. + type: str + redirect_policy: + description: + - The Redirect Policy to bind the context to. + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(tenant), I(graph), I(contract) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template), M(cisco.aci.aci_contract) + and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- module: aci_contract +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:LIfCtx) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new interface context + cisco.aci.aci_l4l7_device_selection_interface_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: provider + state: present + delegate_to: localhost + +- name: Query an interface context + cisco.aci.aci_l4l7_device_selection_interface_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all interface contexts + cisco.aci.aci_l4l7_device_selection_interface_context: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete an interface context + cisco.aci.aci_l4l7_device_selection_interface_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: provider + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + contract=dict(type="str", aliases=["contract_name"]), + graph=dict(type="str", aliases=["service_graph", "service_graph_name"]), + node=dict(type="str", aliases=["node_name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + context=dict(type="str"), + l3_destination=dict(type="bool", aliases=["l3_dest"]), + permit_log=dict(type="bool"), + bridge_domain=dict(type="str", aliases=["bd", "bd_name"]), + bridge_domain_tenant=dict(type="str", aliases=["bd_tenant"]), + logical_device=dict(type="str"), + logical_interface=dict(type="str"), + redirect_policy=dict(type="str"), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "contract", "graph", "node", "context"]], + ["state", "present", ["tenant", "contract", "graph", "node", "context"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + state = module.params.get("state") + contract = module.params.get("contract") + graph = module.params.get("graph") + node = module.params.get("node") + context = module.params.get("context") + l3_destination = aci.boolean(module.params.get("l3_destination")) + permit_log = aci.boolean(module.params.get("permit_log")) + bridge_domain = module.params.get("bridge_domain") + bridge_domain_tenant = module.params.get("bridge_domain_tenant") + logical_device = module.params.get("logical_device") + logical_interface = module.params.get("logical_interface") + redirect_policy = module.params.get("redirect_policy") + + ldev_ctx_rn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsLDevCtx", + aci_rn=ldev_ctx_rn, + module_object=ldev_ctx_rn, + target_filter={"dn": ldev_ctx_rn}, + ), + subclass_2=dict( + aci_class="vnsLIfCtx", + aci_rn="lIfCtx-c-{0}".format(context), + module_object=context, + target_filter={"connNameOrLbl": context}, + ), + child_classes=["vnsRsLIfCtxToBD", "vnsRsLIfCtxToLIf", "vnsRsLIfCtxToSvcRedirectPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if bridge_domain: + if bridge_domain_tenant is None: + bridge_domain_tenant = tenant + bd_tdn = "uni/tn-{0}/BD-{1}".format(bridge_domain_tenant, bridge_domain) + child_configs.append({"vnsRsLIfCtxToBD": {"attributes": {"tDn": bd_tdn}}}) + else: + bd_tdn = None + if logical_interface: + log_intf_tdn = "uni/tn-{0}/lDevVip-{1}/lIf-{2}".format(tenant, logical_device, logical_interface) + child_configs.append({"vnsRsLIfCtxToLIf": {"attributes": {"tDn": log_intf_tdn}}}) + else: + log_intf_tdn = None + if redirect_policy: + redir_pol_tdn = "uni/tn-{0}/svcCont/svcRedirectPol-{1}".format(tenant, redirect_policy) + child_configs.append({"vnsRsLIfCtxToSvcRedirectPol": {"attributes": {"tDn": redir_pol_tdn}}}) + else: + redir_pol_tdn = None + # Validate if existing and remove child objects when do not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("vnsLIfCtx", {}).get("children", {}): + if child.get("vnsRsLIfCtxToBD") and child.get("vnsRsLIfCtxToBD").get("attributes").get("tDn") != bd_tdn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class vnsRsLIfCtxToBD is already attached. + # A seperate delete request to dn of the vnsRsLIfCtxToBD is needed to remove the object prior to adding to child_configs. + # child_configs.append( + # { + # "vnsRsLIfCtxToBD": { + # "attributes": { + # "dn": child.get("vnsRsLIfCtxToBD").get("attributes").get("dn"), + # "status": "deleted", + # } + # } + # } + # ) + aci.delete_child("/api/mo/uni/tn-{0}/ldevCtx-c-{1}-g-{2}-n-{3}/lIfCtx-c-{4}/rsLIfCtxToBD.json".format(tenant, contract, graph, node, context)) + elif child.get("vnsRsLIfCtxToLIf") and child.get("vnsRsLIfCtxToLIf").get("attributes").get("tDn") != log_intf_tdn: + child_configs.append( + { + "vnsRsLIfCtxToLIf": { + "attributes": { + "dn": child.get("vnsRsLIfCtxToLIf").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + elif child.get("vnsRsLIfCtxToSvcRedirectPol") and child.get("vnsRsLIfCtxToSvcRedirectPol").get("attributes").get("tDn") != redir_pol_tdn: + child_configs.append( + { + "vnsRsLIfCtxToSvcRedirectPol": { + "attributes": { + "dn": child.get("vnsRsLIfCtxToSvcRedirectPol").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class="vnsLIfCtx", + class_config=dict(connNameOrLbl=context, l3Dest=l3_destination, permitLog=permit_log), + child_configs=child_configs, + ) + aci.get_diff(aci_class="vnsLIfCtx") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_device_selection_policy.py b/plugins/modules/aci_l4l7_device_selection_policy.py new file mode 100644 index 000000000..5c936a90d --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_policy.py @@ -0,0 +1,332 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_device_selection_policy +short_description: Manage L4-L7 Device Selection Policies (vns:LDevCtx) +description: +- Manage L4-L7 Device Selection Policies +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + contract: + description: + - The name of an existing contract. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ contract_name ] + graph: + description: + - The name of an existing service graph. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ service_graph, service_graph_name ] + node: + description: + - The name of an existing L4-L7 node. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ node_name ] + device: + description: + - The name of the L4-L7 Device to bind to the policy. + type: str + context: + description: + - The context name. + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +notes: +- The I(tenant), I(contract), I(graph), I(device) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_contract), M(cisco.aci.aci_l4l7_service_graph), M(cisco.aci.aci_l4l7_device) and + M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_contract +- module: aci_l4l7_service_graph +- module: aci_l4l7_device +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:LDevCtx) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + state: present + delegate_to: localhost + +- name: Query a device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + state: query + delegate_to: localhost + register: query_result + +- name: Query all device selection policies + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + contract=dict(type="str", aliases=["contract_name"]), + graph=dict(type="str", aliases=["service_graph", "service_graph_name"]), + node=dict(type="str", aliases=["node_name"]), + device=dict(type="str"), + context=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "contract", "graph", "node"]], + ["state", "present", ["tenant", "contract", "graph", "node"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + contract = module.params.get("contract") + graph = module.params.get("graph") + node = module.params.get("node") + device = module.params.get("device") + context = module.params.get("context") + + policy_dn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsLDevCtx", + aci_rn=policy_dn, + module_object=policy_dn, + target_filter={"dn": policy_dn}, + ), + child_classes=["vnsRsLDevCtxToLDev"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if device: + device_tdn = "uni/tn-{0}/lDevVip-{1}".format(tenant, device) + child_configs.append({"vnsRsLDevCtxToLDev": {"attributes": {"tDn": device_tdn}}}) + else: + device_tdn = None + # Validate if existing and remove child objects when do not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("vnsLDevCtx", {}).get("children", {}): + if child.get("vnsRsLDevCtxToLDev") and child.get("vnsRsLDevCtxToLDev").get("attributes").get("tDn") != device_tdn: + child_configs.append( + { + "vnsRsLDevCtxToLDev": { + "attributes": { + "dn": child.get("vnsRsLDevCtxToLDev").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class="vnsLDevCtx", + class_config=dict( + ctrctNameOrLbl=contract, + graphNameOrLbl=graph, + nodeNameOrLbl=node, + context=context, + ), + child_configs=child_configs, + ) + aci.get_diff(aci_class="vnsLDevCtx") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py new file mode 100644 index 000000000..e3f80a932 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template +short_description: Manage L4-L7 Service Graph Templates (vns:AbsGraph) +description: +- Manage Layer 4 to Layer 7 (L4-L7) Service Graph Templates on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of Service Graph Template. + type: str + ui_template_type: + description: + - The UI Template Type. + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner +notes: +- The I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:AbsGraph) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + state: present + delegate_to: localhost + +- name: Query a Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + state: query + register: query_result + delegate_to: localhost + +- name: Query all Service Graph Templates + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + 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, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ui_template_type=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph"]], + ["state", "present", ["tenant", "service_graph"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + ui_template_type = module.params.get("ui_template_type") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + child_classes=["vnsAbsTermNodeProv", "vnsAbsTermNodeCon"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsGraph", + class_config=dict(name=service_graph, uiTemplateType=ui_template_type), + ) + aci.get_diff(aci_class="vnsAbsGraph") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py new file mode 100644 index 000000000..d8a93c5d0 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template_abs_connection +short_description: Manage L4-L7 Service Graph Template Abs Connections (vns:AbsConnection) +description: +- Manage Layer 4 to Layer 7 (L4-L7) Service Graph Template Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + connection_name: + description: + - The name of the connection. + type: str + direct_connect: + description: + - Whether to enable direct connections. + - The APIC defaults to C(false) when unset during creation. + type: bool + unicast_route: + description: + - Whether to enable unicast routing. + - The APIC defaults to C(true) when unset during creation. + type: bool + adjacency_type: + description: + - Whether the adjacecncy is Layer2 or Layer3. + - The APIC defaults to C(l2) when unset during creation. + type: str + choices: [ l2, l3 ] + connector_direction: + description: + - The connector direction. + type: str + choices: [ provider, consumer ] + connection_type: + description: + - The connection type. + type: str + choices: [ internal, external ] + 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 +- cisco.aci.owner +notes: +- The I(tenant) and I(service_graph) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:AbsConnection) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + state: present + delegate_to: localhost + +- name: Query a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all connections + cisco.aci.aci_l4l7_service_graph_template_abs_connection: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + 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, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str"), + direct_connect=dict(type="bool"), + unicast_route=dict(type="bool"), + adjacency_type=dict(type="str", choices=["l2", "l3"]), + connector_direction=dict(type="str", choices=["provider", "consumer"]), + connection_type=dict(type="str", choices=["internal", "external"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "connection_name"]], + ["state", "present", ["tenant", "service_graph", "connection_name"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + unicast_route = aci.boolean(module.params.get("unicast_route")) + adjacency_type = module.params.get("adjacency_type").upper() if module.params.get("adjacency_type") is not None else None + direct_connect = aci.boolean(module.params.get("direct_connect")) + connector_direction = module.params.get("connector_direction") + connection_type = module.params.get("connection_type") + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsConnection", + aci_rn="AbsConnection-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsConnection", + class_config=dict( + name=connection_name, + adjType=adjacency_type, + directConnect=direct_connect, + unicastRoute=unicast_route, + connType=connection_type, + connDir=connector_direction, + ), + ) + aci.get_diff(aci_class="vnsAbsConnection") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py new file mode 100644 index 000000000..7f6069edd --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template_abs_connection_conns +short_description: Manage L4-L7 Service Graph Template Connections (vns:RsAbsConnectionConns) +description: +- Manage Manage L4-L7 Service Graph Template Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + connection_name: + description: + - The name of an existing vns:AbsConnection object. + type: str + direction: + description: + - The direction of the connection. + - If this links to a terminal node, both vns:RsAbsConnectionConns will use the same direction. + - Otherwise one vns:RsAbsConnectionConns will be consumer, and the other will be provider. + type: str + choices: [ consumer, provider ] + connected_node: + description: + - The name of an existing node. + - Omit this variable for connections to terminal nodes. + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +notes: +- The I(tenant), I(service_graph) and I(connection_name) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) + and M(cisco.aci.aci_l4l7_service_graph_template_abs_conn) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_service_graph_template_abs_connection +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:RsAbsConnectionConns) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + state: present + delegate_to: localhost + +- name: Query a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + state: query + delegate_to: localhost + register: query_result + +- name: Delete a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str"), + direction=dict(type="str", choices=["consumer", "provider"]), + connected_node=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "connection_name", "direction"]], + ["state", "present", ["tenant", "service_graph", "connection_name", "direction"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + direction = module.params.get("direction") + connected_node = module.params.get("connected_node") + + aci = ACIModule(module) + if connected_node: + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsNode-{2}/AbsFConn-{3}".format(tenant, service_graph, connected_node, direction) + elif direction == "consumer": + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsTermNodeCon-T1/AbsTConn".format(tenant, service_graph) + elif direction == "provider": + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsTermNodeProv-T2/AbsTConn".format(tenant, service_graph) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsConnection", + aci_rn="AbsConnection-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + subclass_3=dict( + aci_class="vnsRsAbsConnectionConns", + aci_rn="rsabsConnectionConns-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsRsAbsConnectionConns", + class_config=dict(tDn=tdn), + ) + aci.get_diff(aci_class="vnsRsAbsConnectionConns") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py new file mode 100644 index 000000000..ecbc11c9e --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py @@ -0,0 +1,294 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template_functional_connection +short_description: Manage L4-L7 Service Graph Templates Functional Connections (vns:AbsFuncConn) +description: +- Manage Manage L4-L7 Service Graph Templates Functional Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + node: + description: + - The name an existing Service Graph Node. + type: str + connection_name: + description: + - Whether this Functional Connection is the consumer or provider. + type: str + choices: [ consumer, provider ] + 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 +- cisco.aci.owner +notes: +- The I(tenant), I(service_graph) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC classes, B(vns:AbsFuncConn) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Consumer Functional Connection + cisco.aci.aci_l4l7_service_graph_template_functional_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: consumer + state: present + delegate_to: localhost + +- name: Query a Functional Connection + cisco.aci.aci_l4l7_service_graph_template_functional_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all Functional Connections + cisco.aci.aci_l4l7_service_graph_template_functional_connection: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Functional Connection + cisco.aci.aci_l4l7_service_graph_template_functional_connection: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: 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, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + node=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str", choices=["consumer", "provider"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node", "connection_name"]], + ["state", "present", ["tenant", "service_graph", "node", "connection_name"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + node = module.params.get("node") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsNode", + aci_rn="AbsNode-{0}".format(node), + module_object=node, + target_filter={"name": node}, + ), + subclass_3=dict( + aci_class="vnsAbsFuncConn", + aci_rn="AbsFConn-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsFuncConn", + class_config=dict(name=connection_name), + ) + aci.get_diff(aci_class="vnsAbsFuncConn") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py new file mode 100644 index 000000000..bbd8fe9da --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -0,0 +1,359 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template_node +short_description: Manage L4-L7 Service Graph Templates Nodes (vns:AbsNode) +description: +- Manage Manage L4-L7 Service Graph Templates Nodes. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + node: + description: + - The name of the Service Graph Template Node. + type: str + func_template_type: + description: + - The functional template type for the node. + - The APIC defaults to C(other) when unset during creation. + type: str + choices: [ fw_trans, fw_routed, adc_one_arm, adc_two_arm, other ] + func_type: + description: + - The type of connection. + - The APIC defaults to C(go_to) when unset during creation. + type: str + choices: [ go_to, go_through, l1, l2 ] + device: + description: + - The name of an existing logical device. + type: str + device_tenant: + description: + - The tenant the logical device exists under. + - This variable is only used if logical device and node exist within different tenants. + - Intended use case is when the device is in the C(common) tenant but the node is not. + type: str + managed: + description: + - Whether this device managed by the apic. + - The APIC defaults to C(true) when unset during creation. + type: bool + routing_mode: + description: + - The routing mode for the node. + type: str + is_copy: + description: + - Whether the device is a copy device. + type: bool + 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 +- cisco.aci.owner +notes: +- The I(tenant), I(service_graph) and I(device) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_device) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_device +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vnsAbsNode) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + func_template_type: adc_one_arm + func_type: GoTo + device: test-device + managed: no + routing_mode: Redirect + state: present + delegate_to: localhost + +- name: Query a Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + state: query + delegate_to: localhost + register: query_result + +- name: Query all Service Graph Template Nodes + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + 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, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_FUNC_TYPES_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + node=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + func_template_type=dict(type="str", choices=["fw_trans", "fw_routed", "adc_one_arm", "adc_two_arm", "other"]), + func_type=dict(type="str", choices=["go_to", "go_through", "l1", "l2"]), + device=dict(type="str"), + device_tenant=dict(type="str"), + managed=dict(type="bool"), + routing_mode=dict(type="str"), + is_copy=dict(type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node"]], + ["state", "present", ["tenant", "service_graph", "node", "device"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + node = module.params.get("node") + state = module.params.get("state") + func_template_type = module.params.get("func_template_type") + func_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("func_type")) + device = module.params.get("device") + device_tenant = module.params.get("device_tenant") + managed = aci.boolean(module.params.get("managed")) + routing_mode = module.params.get("routing_mode") + is_copy = aci.boolean(module.params.get("is_copy")) + + if func_template_type: + func_template_upper = func_template_type.upper() + else: + func_template_upper = None + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsNode", + aci_rn="AbsNode-{0}".format(node), + module_object=node, + target_filter={"name": node}, + ), + child_classes=["vnsRsNodeToLDev"], + ) + + aci.get_existing() + if not device_tenant: + device_tenant = tenant + dev_tdn = "uni/tn-{0}/lDevVip-{1}".format(device_tenant, device) + + if state == "present": + aci.payload( + aci_class="vnsAbsNode", + class_config=dict( + name=node, + funcTemplateType=func_template_upper, + funcType=func_type, + managed=managed, + routingMode=routing_mode, + isCopy=is_copy, + ), + child_configs=[ + dict( + vnsRsNodeToLDev=dict( + attributes=dict(tDn=dev_tdn), + ), + ), + ], + ) + aci.get_diff(aci_class="vnsAbsNode") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_term_node.py b/plugins/modules/aci_l4l7_service_graph_template_term_node.py new file mode 100644 index 000000000..8daae00e2 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_term_node.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) +# 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": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l4l7_service_graph_template_term_node +short_description: Manage L4-L7 SGT Term Nodes (vns:AbsTermNodeCon, vns:AbsTermNodeProv and vns:AbsTermConn) +description: +- Manage L4-L7 Service Graph Template Term Nodes on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service graph. + type: str + node_name: + description: + - The name of the Term Node. + type: str + choices: [ T1, T2 ] + 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 +- cisco.aci.owner +notes: +- The I(tenant) and I(service_graph) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template) modules can be used for this. +seealso: +- module: aci_tenant +- module: aci_l4l7_service_graph_template +- name: APIC Management Information Model reference + description: More information about the internal APIC classes, B(vns:AbsTermNodeCon), B(vns:AbsTermNodeProv), B(vns:AbsTermConn) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + state: present + delegate_to: localhost + +- name: Query a Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + 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, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + node_name=dict(type="str", choices=["T1", "T2"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node_name"]], + ["state", "present", ["tenant", "service_graph", "node_name"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + node_name = module.params.get("node_name") + + aci = ACIModule(module) + + if node_name == "T1": + term_class = "vnsAbsTermNodeCon" + term_rn = "AbsTermNodeCon-T1" + term_module_object = "T1" + term_target_filter = {"name": "T1"} + name = "T1" + elif node_name == "T2": + term_class = "vnsAbsTermNodeProv" + term_rn = "AbsTermNodeProv-T2" + term_module_object = "T2" + term_target_filter = {"name": "T2"} + name = "T2" + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class=term_class, + aci_rn=term_rn, + module_object=term_module_object, + target_filter=term_target_filter, + ), + child_classes=["vnsAbsTermConn"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=term_class, + class_config=dict(name=name), + child_configs=[ + dict( + vnsAbsTermConn=dict( + attributes=dict(name="1"), + ), + ), + ], + ) + aci.get_diff(aci_class=term_class) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml new file mode 100644 index 000000000..632b2a203 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml @@ -0,0 +1,342 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + + # GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE CONTRACT +- name: Create contract + cisco.aci.aci_contract: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + scope: application-profile + state: present + +# CREATE L4-L7 DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE L4-L7 SERVICE GRAPH +- name: Create L4-L7 Service Graph + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# CREATE L4-L7 SERVICE GRAPH NODE +- name: Add a new Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# CREATE L4-L7 DEVICE SELECTION POLICY +- name: Add a new device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: present + +# CREATE BRIDGE DOMAINS +- name: Add a Bridge Domain in ansible_tenant + cisco.aci.aci_bd: + <<: *aci_info + tenant: ansible_tenant + bd: ansible_bd + state: present + +- name: Add a Bridge Domain in the common tenant + cisco.aci.aci_bd: + <<: *aci_info + tenant: common + bd: ansible_if_context_bd + state: present + +# CREATE LOGICAL INTERFACE +- name: Add a new logical interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-987 + state: present + +# CREATE L4-L7 REDIRECT POLICY +- name: Add a new Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l3 + hash_algorithm: destination_ip + resilient_hash: yes + state: present + +# CREATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT +- name: Create L4-L7 Device Selection Logical Interface Context in check mode + cisco.aci.aci_l4l7_device_selection_if_context: &add_context + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_bd + logical_device: ansible_device + logical_interface: ansible_log_intf + redirect_policy: ansible_pbr_policy + state: present + check_mode: true + register: add_context_cm + +- name: Create L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *add_context + register: add_context + +- name: Verify context creation + ansible.builtin.assert: + that: + - add_context_cm is changed + - add_context is changed + - add_context_cm.proposed.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - add_context_cm.proposed.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - add_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - add_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - add_context.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - add_context.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + +# CREATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT AGAIN TO TEST IDEMPOTENCE +- name: Create L4-L7 Device Selection Logical Interface Context again + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *add_context + register: add_context_again + +- name: Verify context creation idempotence + ansible.builtin.assert: + that: + - add_context_again is not changed + - add_context_again.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - add_context_again.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - add_context_again.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - add_context_again.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_context_again.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + +# UPDATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT +- name: Modify L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_if_context_bd + bridge_domain_tenant: common + state: present + register: update_context + +- name: Verify context update + ansible.builtin.assert: + that: + - update_context is changed + - update_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - update_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - update_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-common/BD-ansible_if_context_bd" + - update_context.current.0.vnsLIfCtx.children | length == 1 + +- name: Remove L4-L7 Device Selection Logical Interface Context BD + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: present + register: update_context + +- name: Verify BD binding removal + ansible.builtin.assert: + that: + - update_context is changed + - update_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - update_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - update_context.current.0.vnsLIfCtx.children is not defined + +- name: Re-add L4-L7 Device Selection Logical Interface Context BD binding + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_if_context_bd + bridge_domain_tenant: common + state: present + +# QUERY L4-L7 DEVICE SELECTION INTERFACE CONTEXT +- name: Query L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: query + register: query_context + +- name: Verify context query + ansible.builtin.assert: + that: + - query_context is not changed + - query_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - query_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - query_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-common/BD-ansible_if_context_bd" + +# QUERY ALL L4-L7 DEVICE SELECTION INTERFACE CONTEXTS +- name: Query all L4-L7 Device Selection Logical Interface Contexts + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + state: query + register: query_all_contexts + +- name: Verify Query All + ansible.builtin.assert: + that: + - query_all_contexts is not changed + +# DELETE L4-L7 DEVICE SELECTION INTERFACE CONTEXT +- name: Delete L4-L7 Device Selection Logical Interface Context in check mode + cisco.aci.aci_l4l7_device_selection_if_context: &delete_context + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: absent + check_mode: true + register: delete_context_cm + +- name: Delete L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *delete_context + register: delete_context + +- name: Verify context deletion + ansible.builtin.assert: + that: + - delete_context_cm is changed + - delete_context_cm.proposed == {} + - delete_context_cm.previous == delete_context.previous + - delete_context is changed + - delete_context.current == [] + - delete_context.previous.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - delete_context.previous.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + +# DELETE L4-L7 DEVICE SELECTION INTERFACE CONTEXT AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Device Selection Logical Interface Context again + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *delete_context + register: delete_context_again + +- name: Verify context deletion idempotence + ansible.builtin.assert: + that: + - delete_context_again is not changed + - delete_context_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +- name: Remove ansible_if_context_bd + cisco.aci.aci_bd: + <<: *aci_info + tenant: common + bd: ansible_if_context_bd + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml new file mode 100644 index 000000000..1ac63b9f5 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml @@ -0,0 +1,244 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE CONTRACT +- name: Create contract + cisco.aci.aci_contract: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + scope: application-profile + state: present + +# CREATE L4-L7 SERVICE GRAPH +- name: Create L4-L7 Service Graph + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: present + +# CREATE L4-L7 SERVICE GRAPH NODE +- name: Create Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# CREATE L4-L7 DEVICE SELECTION POLICY +- name: Create L4-L7 Device Selection Policy in check mode + cisco.aci.aci_l4l7_device_selection_policy: &add_l4l7_device_selection_policy + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + device: ansible_device + state: present + check_mode: true + register: add_l4l7_device_selection_policy_cm + +- name: Create L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *add_l4l7_device_selection_policy + register: add_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy attributes + ansible.builtin.assert: + that: + - add_l4l7_device_selection_policy_cm is changed + - add_l4l7_device_selection_policy is changed + - add_l4l7_device_selection_policy_cm.proposed.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - add_l4l7_device_selection_policy_cm.proposed.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - add_l4l7_device_selection_policy_cm.proposed.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - add_l4l7_device_selection_policy_cm.proposed.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - add_l4l7_device_selection_policy_cm.proposed.vnsLDevCtx.children.0.vnsRsLDevCtxToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.children.0.vnsRsLDevCtxToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# ADD L4-L7 DEVICE SELECTION POLICY AGAIN TO CHECK IDEMPOTENCE +- name: Add L4-L7 Device Selection Policy again + cisco.aci.aci_l4l7_device_selection_policy: + <<: *add_l4l7_device_selection_policy + register: add_l4l7_device_selection_policy_again + +- name: Verify L4-L7 Device Selection Policy attributes are unchanged + ansible.builtin.assert: + that: + - add_l4l7_device_selection_policy_again is not changed + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.children.0.vnsRsLDevCtxToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# REMOVE LOGICAL DEVICE BINDING +- name: Remove L4-L7 Device Selection Policy Device Binding + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: present + register: remove_l4l7_device_selection_policy_device_binding + +- name: Verify L4-L7 Device Selection Policy Device Binding removal + ansible.builtin.assert: + that: + - remove_l4l7_device_selection_policy_device_binding is changed + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.children is not defined + +# QUERY L4-L7 DEVICE SELECTION POLICY +- name: Query L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: query + register: query_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy attributes + ansible.builtin.assert: + that: + - query_l4l7_device_selection_policy is not changed + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + +# DELETE L4-L7 DEVICE SELECTION POLICY +- name: Delete L4-L7 Device Selection Policy in check mode + cisco.aci.aci_l4l7_device_selection_policy: &delete_l4l7_device_selection_policy + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: absent + check_mode: true + register: delete_l4l7_device_selection_policy_cm + +- name: Delete L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *delete_l4l7_device_selection_policy + register: delete_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy removal + ansible.builtin.assert: + that: + - delete_l4l7_device_selection_policy_cm is changed + - delete_l4l7_device_selection_policy_cm.proposed == {} + - delete_l4l7_device_selection_policy_cm.previous == delete_l4l7_device_selection_policy.previous + - delete_l4l7_device_selection_policy is changed + - delete_l4l7_device_selection_policy.current == [] + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + +# DELETE L4-L7 DEVICE SELECTION POLICY AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *delete_l4l7_device_selection_policy + register: delete_l4l7_device_selection_policy_again + +- name: Verify L4-L7 Device Selection Policy removal + ansible.builtin.assert: + that: + - delete_l4l7_device_selection_policy_again is not changed + - delete_l4l7_device_selection_policy_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml new file mode 100644 index 000000000..72855c132 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml @@ -0,0 +1,158 @@ +# Test code for the ACI modules +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD service graph template +- name: Create L4-L7 Service Graph Template in check mode + cisco.aci.aci_l4l7_service_graph_template: &sgt_add + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: present + check_mode: true + register: create_sgt_cm + +- name: Create L4-L7 Service Graph Template again + cisco.aci.aci_l4l7_service_graph_template: + <<: *sgt_add + register: create_sgt + +- name: Verify L4-L7 Service Graph Template has been created + ansible.builtin.assert: + that: + - create_sgt_cm is changed + - create_sgt is changed + - create_sgt_cm.proposed.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - create_sgt_cm.proposed.vnsAbsGraph.attributes.name == "ansible_service_graph" + - create_sgt.previous == [] + - create_sgt.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - create_sgt.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# ADD service graph template again to check idempotence +- name: Create L4-L7 Service Graph Template again + cisco.aci.aci_l4l7_service_graph_template: + <<: *sgt_add + register: create_sgt_again + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - create_sgt_again is not changed + - create_sgt.current == create_sgt_again.previous + - create_sgt_again.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - create_sgt_again.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# QUERY service graph template +- name: Create another L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph_another + state: present + +- name: Query L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: query + register: query_sgt + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - query_sgt is not changed + - query_sgt.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - query_sgt.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +- name: Query all L4-L7 Service Graph Templates + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + state: query + register: query_sgt_all + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - query_sgt_all is not changed + - query_sgt_all.current | length >= 2 + - "'uni/tn-ansible_tenant/AbsGraph-ansible_service_graph' in query_sgt_all.current | map(attribute='vnsAbsGraph.attributes.dn') | list" + - "'uni/tn-ansible_tenant/AbsGraph-ansible_service_graph_another' in query_sgt_all.current | map(attribute='vnsAbsGraph.attributes.dn') | list" + +# DELETE service graph template +- name: Remove L4-L7 Service Graph Template in check mode + cisco.aci.aci_l4l7_service_graph_template: &remove_sgt + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: absent + check_mode: true + register: remove_sgt_cm + +- name: Remove L4-L7 Service Graph Template in check mode + cisco.aci.aci_l4l7_service_graph_template: + <<: *remove_sgt + register: remove_sgt + +- name: Verify L4-L7 Service Graph Template deletion + ansible.builtin.assert: + that: + - remove_sgt_cm is changed + - remove_sgt_cm.proposed == {} + - remove_sgt_cm.previous == remove_sgt.previous + - remove_sgt is changed + - remove_sgt.current == [] + - remove_sgt.previous.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - remove_sgt.previous.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# DELETE SERVICE GRAPH TEMPLATE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *remove_sgt + register: remove_sgt_again + +- name: Verify L4-L7 Service Graph Template deletion idempotence + ansible.builtin.assert: + that: + - remove_sgt_again is not changed + - remove_sgt_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection/tasks/main.yml new file mode 100644 index 000000000..3d0c88b82 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection/tasks/main.yml @@ -0,0 +1,218 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH ABS CONNECTION +- name: Create L4-L7 Service Graph Abs Connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_conn: &add_l4l7_abs_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + connector_direction: provider + connection_type: external + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + check_mode: true + register: add_l4l7_abs_conn_cm + +- name: Create L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *add_l4l7_abs_conn + register: add_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - add_l4l7_abs_conn_cm is changed + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.name == "C1" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.adjType == "L3" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.connDir == "provider" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.connType == "external" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.directConnect == "yes" + - add_l4l7_abs_conn_cm.proposed.vnsAbsConnection.attributes.unicastRoute == "yes" + - add_l4l7_abs_conn is changed + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L3" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "yes" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "yes" + +# ADD SERVICE GRAPH ABS CONNECTION AGAIN TO TEST IDEMPOTENCE +- name: Create L4-L7 Service Graph Abs Connection again + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *add_l4l7_abs_conn + register: add_l4l7_abs_conn_again + +# VERIFY SERVICE GRAPH ABS CONNECTION ARE UNCHANGED +- name: Verify Connection Attributes are unchanged + ansible.builtin.assert: + that: + - add_l4l7_abs_conn_again is not changed + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.name == "C1" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.adjType == "L3" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.connDir == "provider" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.connType == "external" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.directConnect == "yes" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.unicastRoute == "yes" + +# MODIFY SERVICE GRAPH ABS CONNECTION +- name: Modify L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: false + unicast_route: false + adjacency_type: l2 + state: present + register: update_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - update_l4l7_abs_conn is changed + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L2" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "no" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# QUERY SERVICE GRAPH ABS CONNECTION +- name: Query L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + state: query + register: query_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - query_l4l7_abs_conn is not changed + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L2" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "no" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# QUERY ALL SERVICE GRAPH ABS CONNECTIONS +- name: Query all L4-L7 Service Graph Abs Connections + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + state: query + register: query_l4l7_abs_conn_all + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - query_l4l7_abs_conn_all is not changed + +# DELETE SERVICE GRAPH ABS CONNECTION +- name: Delete L4-L7 Service Graph Abs Connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_conn: &delete_l4l7_abs_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + state: absent + check_mode: true + register: delete_l4l7_abs_conn_cm + +- name: Delete L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *delete_l4l7_abs_conn + register: delete_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION DELETION +- name: Verify Connection Deletion + ansible.builtin.assert: + that: + - delete_l4l7_abs_conn_cm is changed + - delete_l4l7_abs_conn_cm.proposed == {} + - delete_l4l7_abs_conn_cm.previous == delete_l4l7_abs_conn.previous + - delete_l4l7_abs_conn is changed + - delete_l4l7_abs_conn.current == [] + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.name == "C1" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.adjType == "L2" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.connDir == "provider" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.connType == "external" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.directConnect == "no" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# DELETE ABS CONN AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Service Graph Abs Connection again + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *delete_l4l7_abs_conn + register: delete_l4l7_abs_conn_again + +- name: Verify Connection Deletion idempotence + ansible.builtin.assert: + that: + - delete_l4l7_abs_conn_again is not changed + - delete_l4l7_abs_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml new file mode 100644 index 000000000..21372146e --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml @@ -0,0 +1,747 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD L4-L7 LOGICAL DEVICES +- name: Create PBR Device 1 + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device1 + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: false + state: present + +- name: Create PBR Device 2 + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device2 + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: false + state: present + +# ADD SERVICE GRAPH NODES +- name: Add Service Graph Template Node 1 + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node1 + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device1 + managed: false + routing_mode: Redirect + state: present + +- name: Add Service Graph Template Node 2 + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node2 + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device2 + managed: false + routing_mode: Redirect + state: present + +# ADD SERVICE GRAPH ABS CONNECTIONS +- name: Create L4-L7 Service Graph Abs Connection C1 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +- name: Create L4-L7 Service Graph Abs Connection C2 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C2 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +- name: Create L4-L7 Service Graph Abs Connection C3 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C3 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +# CREATE CONNECTION CONNS +- name: Add C1 node connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c1_node_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: present + check_mode: true + register: add_c1_node_conn_cm + +- name: Add C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c1_node_conn + register: add_c1_node_conn + +- name: Add C1 term connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c1_term_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: present + check_mode: true + register: add_c1_term_conn_cm + +- name: Add C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c1_term_conn + register: add_c1_term_conn + +- name: Add C2 provider connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c2_prov_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: present + check_mode: true + register: add_c2_prov_conn_cm + +- name: Add C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c2_prov_conn + register: add_c2_prov_conn + +- name: Add C2 consumer connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c2_cons_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: present + check_mode: true + register: add_c2_cons_conn_cm + +- name: Add C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c2_cons_conn + register: add_c2_cons_conn + +- name: Add C3 node connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c3_node_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: present + check_mode: true + register: add_c3_node_conn_cm + +- name: Add C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c3_node_conn + register: add_c3_node_conn + +- name: Add C3 term connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &add_c3_term_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: present + check_mode: true + register: add_c3_term_conn_cm + +- name: Add C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c3_term_conn + register: add_c3_term_conn + +# VERIFY CONNECTION ATTRIBUTES +- name: Verify C1 Node Connection + ansible.builtin.assert: + that: + - add_c1_node_conn_cm is changed + - add_c1_node_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - add_c1_node_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + - add_c1_node_conn is changed + - add_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - add_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection + ansible.builtin.assert: + that: + - add_c1_term_conn_cm is changed + - add_c1_term_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - add_c1_term_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + - add_c1_term_conn is changed + - add_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - add_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection + ansible.builtin.assert: + that: + - add_c2_prov_conn_cm is changed + - add_c2_prov_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - add_c2_prov_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + - add_c2_prov_conn is changed + - add_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - add_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection + ansible.builtin.assert: + that: + - add_c2_cons_conn_cm is changed + - add_c2_cons_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - add_c2_cons_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + - add_c2_cons_conn is changed + - add_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - add_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection + ansible.builtin.assert: + that: + - add_c3_node_conn_cm is changed + - add_c3_node_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - add_c3_node_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + - add_c3_node_conn is changed + - add_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - add_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection + ansible.builtin.assert: + that: + - add_c3_term_conn_cm is changed + - add_c3_term_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - add_c3_term_conn_cm.proposed.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + - add_c3_term_conn is changed + - add_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - add_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# ADD CONNECTIONS AGAIN TO TEST IDEMPOTENCE +- name: Add C1 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c1_node_conn + register: add_c1_node_conn_again + +- name: Add C1 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c1_term_conn + register: add_c1_term_conn_again + +- name: Add C2 provider connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c2_prov_conn + register: add_c2_prov_conn_again + +- name: Add C2 consumer connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c2_cons_conn + register: add_c2_cons_conn_again + +- name: Add C3 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c3_node_conn + register: add_c3_node_conn_again + +- name: Add C3 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *add_c3_term_conn + register: add_c3_term_conn_again + +# VERIFY CONNECTION ATTRIBUTES +- name: Verify C1 Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c1_node_conn_again is not changed + - add_c1_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - add_c1_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c1_term_conn_again is not changed + - add_c1_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - add_c1_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection is unchanged + ansible.builtin.assert: + that: + - add_c2_prov_conn_again is not changed + - add_c2_prov_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - add_c2_prov_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection is unchanged + ansible.builtin.assert: + that: + - add_c2_cons_conn_again is not changed + - add_c2_cons_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - add_c2_cons_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c3_node_conn_again is not changed + - add_c3_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - add_c3_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c3_term_conn_again is not changed + - add_c3_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - add_c3_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# QUERY CONNECTION ATTRIBUTES +- name: Query C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: query + register: query_c1_node_conn + +- name: Query C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: query + register: query_c1_term_conn + +- name: Query C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: query + register: query_c2_prov_conn + +- name: Query C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: query + register: query_c2_cons_conn + +- name: Query C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: query + register: query_c3_node_conn + +- name: Query C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: query + register: query_c3_term_conn + +- name: Verify C1 Node Connection + ansible.builtin.assert: + that: + - query_c1_node_conn is not changed + - query_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - query_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection + ansible.builtin.assert: + that: + - query_c1_term_conn is not changed + - query_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - query_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection + ansible.builtin.assert: + that: + - query_c2_prov_conn is not changed + - query_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - query_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection + ansible.builtin.assert: + that: + - query_c2_cons_conn is not changed + - query_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - query_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection + ansible.builtin.assert: + that: + - query_c3_node_conn is not changed + - query_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - query_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection + ansible.builtin.assert: + that: + - query_c3_term_conn is not changed + - query_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - query_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# DELETE CONNECTIONS +# Delete C1 Node Connection +- name: Delete C1 node connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c1_node_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: absent + check_mode: true + register: delete_c1_node_conn_cm + +- name: Delete C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c1_node_conn + register: delete_c1_node_conn + +# Delete C1 Term Connection +- name: Delete C1 term connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c1_term_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: absent + check_mode: true + register: delete_c1_term_conn_cm + +- name: Delete C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c1_term_conn + register: delete_c1_term_conn + +# Delete C2 Provider Connection +- name: Delete C2 provider connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c2_prov_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: absent + check_mode: true + register: delete_c2_prov_conn_cm + +- name: Delete C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c2_prov_conn + register: delete_c2_prov_conn + +# Delete C2 Consumer Connection +- name: Delete C2 consumer connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c2_cons_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: absent + check_mode: true + register: delete_c2_cons_conn_cm + +- name: Delete C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c2_cons_conn + register: delete_c2_cons_conn + +# Delete C3 Node Connection +- name: Delete C3 node connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c3_node_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: absent + check_mode: true + register: delete_c3_node_conn_cm + +- name: Delete C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c3_node_conn + register: delete_c3_node_conn + +# Delete C3 Term Connection +- name: Delete C3 term connection in check mode + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: &delete_c3_term_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: absent + check_mode: true + register: delete_c3_term_conn_cm + +- name: Delete C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c3_term_conn + register: delete_c3_term_conn + +- name: Verify C1 Node Connection Removal + ansible.builtin.assert: + that: + - delete_c1_node_conn_cm is changed + - delete_c1_node_conn_cm.proposed == {} + - delete_c1_node_conn_cm.previous == delete_c1_node_conn.previous + - delete_c1_node_conn is changed + - delete_c1_node_conn.current == [] + - delete_c1_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - delete_c1_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection Removal + ansible.builtin.assert: + that: + - delete_c1_term_conn_cm is changed + - delete_c1_term_conn_cm.proposed == {} + - delete_c1_term_conn_cm.previous == delete_c1_term_conn.previous + - delete_c1_term_conn is changed + - delete_c1_term_conn.current == [] + - delete_c1_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - delete_c1_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection Removal + ansible.builtin.assert: + that: + - delete_c2_prov_conn_cm is changed + - delete_c2_prov_conn_cm.proposed == {} + - delete_c2_prov_conn_cm.previous == delete_c2_prov_conn.previous + - delete_c2_prov_conn is changed + - delete_c2_prov_conn.current == [] + - delete_c2_prov_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - delete_c2_prov_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection Removal + ansible.builtin.assert: + that: + - delete_c2_cons_conn_cm is changed + - delete_c2_cons_conn_cm.proposed == {} + - delete_c2_cons_conn_cm.previous == delete_c2_cons_conn.previous + - delete_c2_cons_conn is changed + - delete_c2_cons_conn.current == [] + - delete_c2_cons_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - delete_c2_cons_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection Removal + ansible.builtin.assert: + that: + - delete_c3_node_conn_cm is changed + - delete_c3_node_conn_cm.proposed == {} + - delete_c3_node_conn_cm.previous == delete_c3_node_conn.previous + - delete_c3_node_conn is changed + - delete_c3_node_conn.current == [] + - delete_c3_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - delete_c3_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection Removal + ansible.builtin.assert: + that: + - delete_c3_term_conn_cm is changed + - delete_c3_term_conn_cm.proposed == {} + - delete_c3_term_conn_cm.previous == delete_c3_term_conn.previous + - delete_c3_term_conn is changed + - delete_c3_term_conn.current == [] + - delete_c3_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - delete_c3_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# REMOVE CONNECTIONS AGAIN TO TEST IDEMPOTENCE +- name: Delete C1 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c1_node_conn + register: delete_c1_node_conn_again + +- name: Delete C1 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c1_term_conn + register: delete_c1_term_conn_again + +- name: Delete C2 provider connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c2_prov_conn + register: delete_c2_prov_conn_again + +- name: Delete C2 consumer connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c2_cons_conn + register: delete_c2_cons_conn_again + +- name: Delete C3 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c3_node_conn + register: delete_c3_node_conn_again + +- name: Delete C3 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *delete_c3_term_conn + register: delete_c3_term_conn_again + +- name: Verify C1 Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c1_node_conn_again is not changed + - delete_c1_node_conn_again.current == [] + +- name: Verify C1 Term Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c1_term_conn_again is not changed + - delete_c1_term_conn_again.current == [] + +- name: Verify C2 Provider Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c2_prov_conn_again is not changed + - delete_c2_prov_conn_again.current == [] + +- name: Verify C2 Consumer Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c2_cons_conn_again is not changed + - delete_c2_cons_conn_again.current == [] + +- name: Verify C3 Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c3_node_conn_again is not changed + - delete_c3_node_conn_again.current == [] + +- name: Verify C3 Term Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c3_term_conn_again is not changed + - delete_c3_term_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml new file mode 100644 index 000000000..d6a81e9e3 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml @@ -0,0 +1,291 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH NODE +- name: Create L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# ADD FUNCTIONAL CONNECTIONS +- name: Create Consumer Func Conn in check mode + cisco.aci.aci_l4l7_service_graph_template_func_conn: &add_cons_func_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: present + check_mode: true + register: add_cons_func_conn_cm + +- name: Create Provider Func Conn in check mode + cisco.aci.aci_l4l7_service_graph_template_func_conn: &add_prov_func_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: present + check_mode: true + register: add_prov_func_conn_cm + +- name: Create Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *add_cons_func_conn + register: add_cons_func_conn + +- name: Create Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *add_prov_func_conn + register: add_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Creation + ansible.builtin.assert: + that: + - add_cons_func_conn_cm is changed + - add_cons_func_conn is changed + - add_cons_func_conn_cm.proposed.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - add_cons_func_conn_cm.proposed.vnsAbsFuncConn.attributes.name == "consumer" + - add_cons_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - add_cons_func_conn.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Creation + ansible.builtin.assert: + that: + - add_prov_func_conn_cm is changed + - add_prov_func_conn_cm.proposed.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - add_prov_func_conn_cm.proposed.vnsAbsFuncConn.attributes.name == "provider" + - add_prov_func_conn is changed + - add_prov_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - add_prov_func_conn.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# ADD FUNC CONNS AGAIN TO TEST IDEMPOTENCE +- name: Add Consumer Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *add_cons_func_conn + register: add_cons_func_conn_again + +- name: Add Provider Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *add_prov_func_conn + register: add_prov_func_conn_again + +- name: Verify Consumer Func Conn is not changed + ansible.builtin.assert: + that: + - add_cons_func_conn_again is not changed + - add_cons_func_conn_again.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - add_cons_func_conn_again.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn is not changed + ansible.builtin.assert: + that: + - add_prov_func_conn_again is not changed + - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# QUERY FUNC CONNS +- name: Query Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: query + register: query_cons_func_conn + +- name: Query Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: query + register: query_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Attributes + ansible.builtin.assert: + that: + - query_cons_func_conn is not changed + - query_cons_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - query_cons_func_conn.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Attributes + ansible.builtin.assert: + that: + - query_prov_func_conn is not changed + - query_prov_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - query_prov_func_conn.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# QUERY ALL FUNC CONNS +- name: Query All Func Conns + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + state: query + register: query_func_conn_all + +- name: Verify query all + ansible.builtin.assert: + that: + - query_func_conn_all is not changed + - query_func_conn_all | length >=2 + +# DELETE FUNC CONNS +- name: Delete Consumer Func Conn in check mode + cisco.aci.aci_l4l7_service_graph_template_func_conn: &delete_cons_func_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: absent + check_mode: true + register: delete_cons_func_conn_cm + +- name: Delete Provider Func Conn in check mode + cisco.aci.aci_l4l7_service_graph_template_func_conn: &delete_prov_func_conn + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: absent + check_mode: true + register: delete_prov_func_conn_cm + +- name: Delete Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *delete_cons_func_conn + register: delete_cons_func_conn + +- name: Delete Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *delete_prov_func_conn + register: delete_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Removal + ansible.builtin.assert: + that: + - delete_cons_func_conn_cm is changed + - delete_cons_func_conn_cm.proposed == {} + - delete_cons_func_conn_cm.previous == delete_cons_func_conn.previous + - delete_cons_func_conn is changed + - delete_cons_func_conn.current == [] + - delete_cons_func_conn.previous.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - delete_cons_func_conn.previous.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Removal + ansible.builtin.assert: + that: + - delete_prov_func_conn_cm is changed + - delete_prov_func_conn_cm.proposed == {} + - delete_prov_func_conn_cm.previous == delete_prov_func_conn.previous + - delete_prov_func_conn is changed + - delete_prov_func_conn.current == [] + - delete_prov_func_conn.previous.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - delete_prov_func_conn.previous.0.vnsAbsFuncConn.attributes.name == "provider" + +# DELETE FUNC CONN AGAIN TO TEST IDEMPOTENCE +- name: Delete Consumer Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *delete_cons_func_conn + register: delete_cons_func_conn_again + +- name: Verify Consumer Func Conn Removal idempotence + ansible.builtin.assert: + that: + - delete_cons_func_conn_again is not changed + - delete_cons_func_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_dom + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml new file mode 100644 index 000000000..75f056322 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml @@ -0,0 +1,264 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, Tim Cragg (@timcragg) + +# 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 + + # GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH NODE +- name: Create L4-L7 Service Graph Node in check mode + cisco.aci.aci_l4l7_service_graph_template_node: &l4l7node + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + check_mode: true + register: add_l4l7_node_cm + +- name: Create L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *l4l7node + register: add_l4l7_node + +# VERIFY NODE CREATION +- name: Verify Node has been created correctly + ansible.builtin.assert: + that: + - add_l4l7_node_cm is changed + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.name == "ansible_node" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.funcType == "GoTo" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.managed == "no" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.routingMode == "Redirect" + - add_l4l7_node is changed + - add_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - add_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - add_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoTo" + - add_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" + - add_l4l7_node.current.0.vnsAbsNode.attributes.managed == "no" + - add_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - add_l4l7_node.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# ADD NODE AGAIN TO TEST IDEMPOTENCE +- name: Create L4-L7 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *l4l7node + register: add_l4l7_node_again + +# VERIFY NODE IS NOT MODIFIED +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - add_l4l7_node_again is not changed + - add_l4l7_node_again.previous == add_l4l7_node_again.current + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.name == "ansible_node" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.funcType == "GoTo" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.managed == "no" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device is not changed + ansible.builtin.assert: + that: + - add_l4l7_node_again.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# MODIFY L4-L7 NODE +- name: Update L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_two_arm + func_type: go_through + device: ansible_device + managed: true + routing_mode: Redirect + state: present + register: update_l4l7_node + +# VERIFY NODE ATTRIBUTES +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - update_l4l7_node is changed + - update_l4l7_node.previous == add_l4l7_node_again.current + - update_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - update_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - update_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoThrough" + - update_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - update_l4l7_node.current.0.vnsAbsNode.attributes.managed == "yes" + - update_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - update_l4l7_node.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# QUERY L4-L7 NODE +- name: Query L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: query + register: query_l4l7_node + +# VERIFY NODE ATTRIBUTES +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - query_l4l7_node is not changed + - query_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - query_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - query_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoThrough" + - query_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - query_l4l7_node.current.0.vnsAbsNode.attributes.managed == "yes" + - query_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - query_l4l7_node is not changed + - query_l4l7_node.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# DELETE L4-L7 NODE +- name: Remove L4-L7 Service Graph Node in check mode + cisco.aci.aci_l4l7_service_graph_template_node: &removel4l7 + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: absent + check_mode: true + register: delete_l4l7_node_cm + +- name: Remove L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *removel4l7 + register: delete_l4l7_node + +# VERIFY NODE REMOVAL +- name: Verify Node removal + ansible.builtin.assert: + that: + - delete_l4l7_node_cm is changed + - delete_l4l7_node_cm.proposed == {} + - delete_l4l7_node_cm.previous == delete_l4l7_node.previous + - delete_l4l7_node is changed + - delete_l4l7_node.current == [] + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.name == "ansible_node" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.funcType == "GoThrough" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.managed == "yes" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# DELETE L4-L7 NODE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: absent + register: delete_l4l7_node_again + +# VERIFY NODE REMOVAL IDEMPOTENCE +- name: Verify Node removal idempotence + ansible.builtin.assert: + that: + - delete_l4l7_node_again is not changed + - delete_l4l7_node_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_dom + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml new file mode 100644 index 000000000..460a48209 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml @@ -0,0 +1,233 @@ +# Test code for the ACI modules +# Copyright: (c) 2025, Tim Cragg (@timcragg) +# Copyright: (c) 2025, Shreyas Srish (@shrsr) + +# 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 + +# GET Credentials from the inventory +- 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") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH TERM NODE +- name: Create L4-L7 T1 Service Graph Node in check mode + cisco.aci.aci_l4l7_service_graph_template_term_node: &add_l4l7_term_node + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: present + check_mode: true + register: add_l4l7_term_node_cm + +- name: Create L4-L7 T1 Service Graph Node in check mode + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *add_l4l7_term_node + register: add_l4l7_term_node + +# VERIFY TERM NODE CREATION +- name: Verify Term Node Creation + ansible.builtin.assert: + that: + - add_l4l7_term_node_cm is changed + - add_l4l7_term_node is changed + - add_l4l7_term_node_cm.proposed.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - add_l4l7_term_node_cm.proposed.vnsAbsTermNodeCon.attributes.name == "T1" + - add_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - add_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# ADD SERVICE GRAPH TERM NODE AGAIN TO CHECK IDEMPOTENCE +- name: Create L4-L7 T1 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *add_l4l7_term_node + register: add_l4l7_term_node_again + +# VERIFY TERM NODE UNCHANGED +- name: Verify Term Node is not changed + ansible.builtin.assert: + that: + - add_l4l7_term_node_again is not changed + - add_l4l7_term_node_again.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - add_l4l7_term_node_again.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# QUERY TERM NODE +- name: Query L4-L7 T1 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: query + register: query_l4l7_term_node + +# VERIFY TERM NODE QUERY +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - query_l4l7_term_node is not changed + - query_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - query_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# DELETE TERM NODE T1 +- name: Remove L4-L7 T1 Service Graph Node in check mode + cisco.aci.aci_l4l7_service_graph_template_term_node: &remove_l4l7_term_node + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: absent + check_mode: true + register: delete_l4l7_term_node_cm + +- name: Remove L4-L7 T1 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *remove_l4l7_term_node + register: delete_l4l7_term_node + +- name: Remove L4-L7 T1 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *remove_l4l7_term_node + register: delete_l4l7_term_node_again + +# VERIFY DELETION +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - delete_l4l7_term_node_cm is changed + - delete_l4l7_term_node is changed + - delete_l4l7_term_node.current == [] + - delete_l4l7_term_node_cm.previous == delete_l4l7_term_node.previous + - delete_l4l7_term_node.previous.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - delete_l4l7_term_node.previous.0.vnsAbsTermNodeCon.attributes.name == "T1" + - delete_l4l7_term_node_again is not changed + - delete_l4l7_term_node_again.current == [] + +# ADD SERVICE GRAPH TERM NODE +- name: Create L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: present + register: add_l4l7_term_node2 + +# VERIFY TERM NODE CREATION +- name: Verify Term Node Creation + ansible.builtin.assert: + that: + - add_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - add_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# ADD SERVICE GRAPH TERM NODE AGAIN TO CHECK IDEMPOTENCE +- name: Create L4-L7 T2 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: present + register: add_l4l7_term_node2_again + +# VERIFY TERM NODE UNCHANGED +- name: Verify Term Node is not changed + ansible.builtin.assert: + that: + - add_l4l7_term_node2_again is not changed + - add_l4l7_term_node2_again.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - add_l4l7_term_node2_again.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# QUERY TERM NODE +- name: Query L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: query + register: query_l4l7_term_node2 + +# VERIFY TERM NODE QUERY +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - query_l4l7_term_node2 is not changed + - query_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - query_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# DELETE TERM NODE T2 +- name: Remove L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: absent + register: delete_l4l7_term_node2 + +# VERIFY DELETION +- name: Verify Term Node T2 deletion + ansible.builtin.assert: + that: + - delete_l4l7_term_node2 is changed + - delete_l4l7_term_node2.current == [] + - delete_l4l7_term_node2.previous.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - delete_l4l7_term_node2.previous.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# DELETE TERM NODE T2 AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: absent + register: delete_l4l7_term_node2_again + +# VERIFY DELETION IDEMPOTENCE +- name: Verify Term Node T2 deletion idempotence + ansible.builtin.assert: + that: + - delete_l4l7_term_node2_again is not changed + - delete_l4l7_term_node2_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent From c8b7e31ba672adbce18f7c047dee12f876d24b91 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Tue, 6 May 2025 15:14:16 -0400 Subject: [PATCH 2/8] [ignore] Modified files to adhere to black formatting in service_graph_template_node module --- plugins/module_utils/aci.py | 10 ++-------- .../modules/aci_l4l7_service_graph_template_node.py | 2 +- .../aci_l4l7_service_graph_template_term_node.py | 1 + 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 54cdbb337..492fb7bc1 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -1775,13 +1775,7 @@ def api_call(self, method, url, data=None, return_response=False): def delete_child(self, path): if self.params.get("port") is not None: - url = "{protocol}://{host}:{port}/{path}".format( - path=path, - **self.module.params - ) + url = "{protocol}://{host}:{port}/{path}".format(path=path, **self.module.params) else: - url = "{protocol}://{host}/{path}".format( - path=path, - **self.module.params - ) + url = "{protocol}://{host}/{path}".format(path=path, **self.module.params) self.api_call("DELETE", url) diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py index bbd8fe9da..ec1c9848a 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -104,7 +104,7 @@ func_template_type: adc_one_arm func_type: GoTo device: test-device - managed: no + managed: false routing_mode: Redirect state: present delegate_to: localhost diff --git a/plugins/modules/aci_l4l7_service_graph_template_term_node.py b/plugins/modules/aci_l4l7_service_graph_template_term_node.py index 8daae00e2..b7d64637c 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_term_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_term_node.py @@ -54,6 +54,7 @@ link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) """ EXAMPLES = r""" From 358484f796cfd715003326878f6fc6f29c3bb944 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Tue, 6 May 2025 15:17:03 -0400 Subject: [PATCH 3/8] [ignore] Modified files to adhere to black formatting in device_selection_interface_context module --- .../modules/aci_l4l7_device_selection_interface_context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/modules/aci_l4l7_device_selection_interface_context.py b/plugins/modules/aci_l4l7_device_selection_interface_context.py index d20e2aec9..3cbbca9ec 100644 --- a/plugins/modules/aci_l4l7_device_selection_interface_context.py +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -370,7 +370,9 @@ def main(): # } # } # ) - aci.delete_child("/api/mo/uni/tn-{0}/ldevCtx-c-{1}-g-{2}-n-{3}/lIfCtx-c-{4}/rsLIfCtxToBD.json".format(tenant, contract, graph, node, context)) + aci.delete_child( + "/api/mo/uni/tn-{0}/ldevCtx-c-{1}-g-{2}-n-{3}/lIfCtx-c-{4}/rsLIfCtxToBD.json".format(tenant, contract, graph, node, context) + ) elif child.get("vnsRsLIfCtxToLIf") and child.get("vnsRsLIfCtxToLIf").get("attributes").get("tDn") != log_intf_tdn: child_configs.append( { From bd30ac9459fd24c82af156c42ae5beda25e7e03c Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 8 May 2025 16:30:57 -0400 Subject: [PATCH 4/8] [ignore] Added new attributes to service_graph_template modules --- plugins/module_utils/aci.py | 7 -- plugins/module_utils/constants.py | 28 ++++++++ ...l4l7_device_selection_interface_context.py | 41 +++++++++++- .../aci_l4l7_device_selection_policy.py | 15 +++-- .../aci_l4l7_service_graph_template.py | 60 +++++++++++++++-- ...7_service_graph_template_abs_connection.py | 15 ++++- ...ce_graph_template_functional_connection.py | 33 +++++++++- .../aci_l4l7_service_graph_template_node.py | 64 +++++++++++++------ .../tasks/main.yml | 60 +++++++++++++++-- .../tasks/main.yml | 2 +- .../tasks/main.yml | 31 +++++++++ .../tasks/main.yml | 4 +- .../tasks/main.yml | 30 ++++++++- .../tasks/main.yml | 7 +- 14 files changed, 338 insertions(+), 59 deletions(-) diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 492fb7bc1..21740e9bb 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -1772,10 +1772,3 @@ def api_call(self, method, url, data=None, return_response=False): except KeyError: # Connection error self.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) - - def delete_child(self, path): - if self.params.get("port") is not None: - url = "{protocol}://{host}:{port}/{path}".format(path=path, **self.module.params) - else: - url = "{protocol}://{host}/{path}".format(path=path, **self.module.params) - self.api_call("DELETE", url) diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py index 1bc3af185..04d4d479b 100644 --- a/plugins/module_utils/constants.py +++ b/plugins/module_utils/constants.py @@ -451,3 +451,31 @@ L4L7_FUNC_TYPES_MAPPING = {"go_to": "GoTo", "go_through": "GoThrough", "l1": "L1", "l2": "L2"} L4L7_HASH_ALGORITHMS_MAPPING = {"source_ip": "sip", "destination_ip": "dip", "ip_and_protocol": "sip-dip-prototype"} + +L4L7_FUNCTIONAL_TEMPLATE_TYPES_MAPPING = { + "adc_one_arm": "ADC_ONE_ARM", + "adc_two_arm": "ADC_TWO_ARM", + "cloud_native_fw": "CLOUD_NATIVE_FW", + "cloud_native_lb": "CLOUD_NATIVE_LB", + "cloud_vendor_fw": "CLOUD_VENDOR_FW", + "cloud_vendor_lb": "CLOUD_VENDOR_LB", + "fw_routed": "FW_ROUTED", + "fw_trans": "FW_TRANS", + "other": "OTHER", +} + +UI_TEMPLATE_TYPE = { + "ndo_implicit_template": "NDO_IMPLICIT_TEMPLATE", + "one_node_adc_one_arm": "ONE_NODE_ADC_ONE_ARM", + "one_node_adc_one_arm_l3ext": "ONE_NODE_ADC_ONE_ARM_L3EXT", + "one_node_adc_two_arm": "ONE_NODE_ADC_TWO_ARM", + "one_node_fw_routed": "ONE_NODE_FW_ROUTED", + "one_node_fw_trans": "ONE_NODE_FW_TRANS", + "two_node_fw_routed_adc_one_arm": "TWO_NODE_FW_ROUTED_ADC_ONE_ARM", + "two_node_fw_routed_adc_one_arm_l3ext": "TWO_NODE_FW_ROUTED_ADC_ONE_ARM_L3EXT", + "two_node_fw_routed_adc_two_arm": "TWO_NODE_FW_ROUTED_ADC_TWO_ARM", + "two_node_fw_trans_adc_one_arm": "TWO_NODE_FW_TRANS_ADC_ONE_ARM", + "two_node_fw_trans_adc_one_arm_l3ext": "TWO_NODE_FW_TRANS_ADC_ONE_ARM_L3EXT", + "two_node_fw_trans_adc_two_arm": "TWO_NODE_FW_TRANS_ADC_TWO_ARM", + "unspecified": "UNSPECIFIED", +} diff --git a/plugins/modules/aci_l4l7_device_selection_interface_context.py b/plugins/modules/aci_l4l7_device_selection_interface_context.py index 3cbbca9ec..80cf9da04 100644 --- a/plugins/modules/aci_l4l7_device_selection_interface_context.py +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -77,6 +77,22 @@ description: - The Redirect Policy to bind the context to. type: str + permit_handoff: + description: + - Indicates whether to allow handoff of traffic to the associated logical interface. + type: bool + acl: + description: + - Specifies whether an Access Control List (ACL) is applied to the logical interface. + type: bool + description: + description: + - A brief description for the Logical Interface Context. + type: str + rule_type: + description: + - Indicates whether the context uses a specific rule type for traffic handling. + type: bool state: description: - Use C(present) or C(absent) for adding or removing. @@ -283,6 +299,10 @@ def main(): logical_device=dict(type="str"), logical_interface=dict(type="str"), redirect_policy=dict(type="str"), + permit_handoff=dict(type="bool"), + acl=dict(type="bool"), + description=dict(type="str"), + rule_type=dict(type="bool"), ) module = AnsibleModule( argument_spec=argument_spec, @@ -308,6 +328,10 @@ def main(): logical_device = module.params.get("logical_device") logical_interface = module.params.get("logical_interface") redirect_policy = module.params.get("redirect_policy") + permit_handoff = aci.boolean(module.params.get("permit_handoff")) + acl = aci.boolean(module.params.get("acl")) + description = module.params.get("description") + rule_type = aci.boolean(module.params.get("rule_type")) ldev_ctx_rn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None @@ -370,8 +394,11 @@ def main(): # } # } # ) - aci.delete_child( - "/api/mo/uni/tn-{0}/ldevCtx-c-{1}-g-{2}-n-{3}/lIfCtx-c-{4}/rsLIfCtxToBD.json".format(tenant, contract, graph, node, context) + aci.api_call( + "DELETE", + "{0}/api/mo/uni/tn-{1}/ldevCtx-c-{2}-g-{3}-n-{4}/lIfCtx-c-{5}/rsLIfCtxToBD.json".format( + aci.base_url, tenant, contract, graph, node, context + ), ) elif child.get("vnsRsLIfCtxToLIf") and child.get("vnsRsLIfCtxToLIf").get("attributes").get("tDn") != log_intf_tdn: child_configs.append( @@ -397,7 +424,15 @@ def main(): ) aci.payload( aci_class="vnsLIfCtx", - class_config=dict(connNameOrLbl=context, l3Dest=l3_destination, permitLog=permit_log), + class_config=dict( + connNameOrLbl=context, + l3Dest=l3_destination, + permitLog=permit_log, + permitHandoff=permit_handoff, + acl=acl, + descr=description, + ruleType=rule_type, + ), child_configs=child_configs, ) aci.get_diff(aci_class="vnsLIfCtx") diff --git a/plugins/modules/aci_l4l7_device_selection_policy.py b/plugins/modules/aci_l4l7_device_selection_policy.py index 5c936a90d..67d464490 100644 --- a/plugins/modules/aci_l4l7_device_selection_policy.py +++ b/plugins/modules/aci_l4l7_device_selection_policy.py @@ -49,6 +49,10 @@ description: - The context name. type: str + description: + description: + - A brief description for the Device Selection Policy. + type: str state: description: - Use C(present) or C(absent) for adding or removing. @@ -245,6 +249,7 @@ def main(): node=dict(type="str", aliases=["node_name"]), device=dict(type="str"), context=dict(type="str"), + description=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) @@ -264,8 +269,9 @@ def main(): node = module.params.get("node") device = module.params.get("device") context = module.params.get("context") + description = module.params.get("description") - policy_dn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None + ldev_ctx_rn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None aci = ACIModule(module) @@ -278,9 +284,9 @@ def main(): ), subclass_1=dict( aci_class="vnsLDevCtx", - aci_rn=policy_dn, - module_object=policy_dn, - target_filter={"dn": policy_dn}, + aci_rn=ldev_ctx_rn, + module_object=ldev_ctx_rn, + target_filter={"dn": ldev_ctx_rn}, ), child_classes=["vnsRsLDevCtxToLDev"], ) @@ -315,6 +321,7 @@ def main(): graphNameOrLbl=graph, nodeNameOrLbl=node, context=context, + descr=description, ), child_configs=child_configs, ) diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py index e3f80a932..01659e76a 100644 --- a/plugins/modules/aci_l4l7_service_graph_template.py +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -25,11 +25,45 @@ aliases: [ tenant_name ] service_graph: description: - - The name of Service Graph Template. + - The name of Service Graph Template. type: str ui_template_type: description: - - The UI Template Type. + - The UI Template Type. + type: str + choices: [ + ndo_implicit_template, + one_node_adc_one_arm, + one_node_adc_one_arm_l3ext, + one_node_adc_two_arm, + one_node_fw_routed, + one_node_fw_trans, + two_node_fw_routed_adc_one_arm, + two_node_fw_routed_adc_one_arm_l3ext, + two_node_fw_routed_adc_two_arm, + two_node_fw_trans_adc_one_arm, + two_node_fw_trans_adc_one_arm_l3ext, + two_node_fw_trans_adc_two_arm, + unspecified + ] + type: + description: + - Specifies the type of Service Graph Template. + type: str + choices: [ cloud, legacy ] + service_rule_type: + description: + - Defines the type of service rule applied within the Service Graph Template. + type: str + choices: [ epg, subnet, vrf ] + filter_between_nodes: + description: + - Determines how traffic is filtered between nodes in the Service Graph Template. + type: str + choices: [ allow-all, filters-from-contract ] + description: + description: + - A description of the Service Graph Template. type: str state: description: @@ -205,6 +239,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import UI_TEMPLATE_TYPE def main(): @@ -215,7 +250,11 @@ def main(): tenant=dict(type="str", aliases=["tenant_name"]), service_graph=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), - ui_template_type=dict(type="str"), + ui_template_type=dict(type="str", choices=list(UI_TEMPLATE_TYPE)), + type=dict(type="str", choices=["cloud", "legacy"]), + service_rule_type=dict(type="str", choices=["epg", "subnet", "vrf"]), + filter_between_nodes=dict(type="str", choices=["allow-all", "filters-from-contract"]), + description=dict(type="str"), ) module = AnsibleModule( @@ -230,7 +269,11 @@ def main(): tenant = module.params.get("tenant") service_graph = module.params.get("service_graph") state = module.params.get("state") - ui_template_type = module.params.get("ui_template_type") + ui_template_type = UI_TEMPLATE_TYPE.get(module.params.get("ui_template_type")) + type = module.params.get("type") + service_rule_type = module.params.get("service_rule_type") + filter_between_nodes = module.params.get("filter_between_nodes") + description = (module.params.get("description"),) aci = ACIModule(module) @@ -255,7 +298,14 @@ def main(): if state == "present": aci.payload( aci_class="vnsAbsGraph", - class_config=dict(name=service_graph, uiTemplateType=ui_template_type), + class_config=dict( + name=service_graph, + uiTemplateType=ui_template_type, + type=type, + svcRuleType=service_rule_type, + filterBetweenNodes=filter_between_nodes, + descr=description, + ), ) aci.get_diff(aci_class="vnsAbsGraph") diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py index d8a93c5d0..3bc8f1a89 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py @@ -16,7 +16,7 @@ module: aci_l4l7_service_graph_template_abs_connection short_description: Manage L4-L7 Service Graph Template Abs Connections (vns:AbsConnection) description: -- Manage Layer 4 to Layer 7 (L4-L7) Service Graph Template Connections. +- Manage Layer 4 to Layer 7 (L4-L7) Service Graph Template Connections. options: tenant: description: @@ -50,13 +50,19 @@ connector_direction: description: - The connector direction. + - The APIC defaults to C(uknown) when unset during creation. type: str - choices: [ provider, consumer ] + choices: [ provider, consumer, unknown ] connection_type: description: - The connection type. + - The APIC defaults to C(external) when unset during creation. type: str choices: [ internal, external ] + description: + description: + - The description for the Service Graph Template connection. + type: str state: description: - Use C(present) or C(absent) for adding or removing. @@ -249,8 +255,9 @@ def main(): direct_connect=dict(type="bool"), unicast_route=dict(type="bool"), adjacency_type=dict(type="str", choices=["l2", "l3"]), - connector_direction=dict(type="str", choices=["provider", "consumer"]), + connector_direction=dict(type="str", choices=["provider", "consumer", "unknown"]), connection_type=dict(type="str", choices=["internal", "external"]), + description=dict(type="str"), ) module = AnsibleModule( @@ -273,6 +280,7 @@ def main(): direct_connect = aci.boolean(module.params.get("direct_connect")) connector_direction = module.params.get("connector_direction") connection_type = module.params.get("connection_type") + description = module.params.get("description") aci.construct_url( root_class=dict( @@ -307,6 +315,7 @@ def main(): unicastRoute=unicast_route, connType=connection_type, connDir=connector_direction, + descr=description, ), ) aci.get_diff(aci_class="vnsAbsConnection") diff --git a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py index ecbc11c9e..aee195a8c 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py @@ -34,6 +34,23 @@ - Whether this Functional Connection is the consumer or provider. type: str choices: [ consumer, provider ] + attachment_notify: + description: + - Indicates whether attachment notifications are enabled for this connection. + type: bool + description: + description: + - The description for the Service Graph connection. + type: str + connection_type: + description: + - Specifies the type of connection for the node. + type: str + choices: [ dnat, none, redir, snat, snat_dnat ] + device_interface_name: + description: + - The name of the device interface associated with this connection. + type: str state: description: - Use C(present) or C(absent) for adding or removing. @@ -227,6 +244,10 @@ def main(): node=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), connection_name=dict(type="str", choices=["consumer", "provider"]), + attachment_notify=dict(type="bool"), + connection_type=dict(type="str", choices=["dnat", "none", "redir", "snat", "snat_dnat"]), + description=dict(type="str"), + device_interface_name=dict(type="str"), ) module = AnsibleModule( @@ -238,13 +259,17 @@ def main(): ], ) + aci = ACIModule(module) + tenant = module.params.get("tenant") service_graph = module.params.get("service_graph") node = module.params.get("node") state = module.params.get("state") connection_name = module.params.get("connection_name") - - aci = ACIModule(module) + attachment_notify = aci.boolean(module.params.get("attachment_notification")) + description = module.params.get("description") + connection_type = module.params.get("connection_type") + device_interface_name = module.params.get("device_interface_name") aci.construct_url( root_class=dict( @@ -278,7 +303,9 @@ def main(): if state == "present": aci.payload( aci_class="vnsAbsFuncConn", - class_config=dict(name=connection_name), + class_config=dict( + name=connection_name, attNotify=attachment_notify, connType=connection_type, descr=description, deviceLIfName=device_interface_name + ), ) aci.get_diff(aci_class="vnsAbsFuncConn") diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py index ec1c9848a..4bb5a49af 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -27,22 +27,38 @@ description: - The name of an existing Service Graph. type: str + description: + description: + - The description of the Service Graph Template Node. + type: str node: description: - The name of the Service Graph Template Node. type: str - func_template_type: + functional_template_type: description: - The functional template type for the node. - The APIC defaults to C(other) when unset during creation. type: str - choices: [ fw_trans, fw_routed, adc_one_arm, adc_two_arm, other ] - func_type: + choices: [ + adc_one_arm, + adc_two_arm, + cloud_native_fw, + cloud_native_lb, + cloud_vendor_fw, + cloud_vendor_lb, + fw_routed, + fw_trans, + other + ] + aliases: [ func_template_type ] + function_type: description: - - The type of connection. + - The type of function. - The APIC defaults to C(go_to) when unset during creation. type: str choices: [ go_to, go_through, l1, l2 ] + aliases: [ func_type ] device: description: - The name of an existing logical device. @@ -62,10 +78,15 @@ description: - The routing mode for the node. type: str + choices: [ redirect, unspecified ] is_copy: description: - Whether the device is a copy device. type: bool + share_encap: + description: + - Whether to share encapsulation across the service graph. + type: bool state: description: - Use C(present) or C(absent) for adding or removing. @@ -101,11 +122,11 @@ tenant: my_tenant service_graph: test-graph node: test-node - func_template_type: adc_one_arm - func_type: GoTo + functional_template_type: adc_one_arm + function_type: GoTo device: test-device managed: false - routing_mode: Redirect + routing_mode: redirect state: present delegate_to: localhost @@ -250,7 +271,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec -from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_FUNC_TYPES_MAPPING +from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_FUNC_TYPES_MAPPING, L4L7_FUNCTIONAL_TEMPLATE_TYPES_MAPPING def main(): @@ -262,13 +283,15 @@ def main(): service_graph=dict(type="str"), node=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), - func_template_type=dict(type="str", choices=["fw_trans", "fw_routed", "adc_one_arm", "adc_two_arm", "other"]), - func_type=dict(type="str", choices=["go_to", "go_through", "l1", "l2"]), + functional_template_type=dict(type="str", aliases=["func_template_type"], choices=list(L4L7_FUNCTIONAL_TEMPLATE_TYPES_MAPPING)), + function_type=dict(type="str", aliases=["func_type"], choices=list(L4L7_FUNC_TYPES_MAPPING)), device=dict(type="str"), device_tenant=dict(type="str"), managed=dict(type="bool"), - routing_mode=dict(type="str"), + routing_mode=dict(type="str", choices=["redirect", "unspecified"]), is_copy=dict(type="bool"), + description=dict(type="str"), + share_encap=dict(type="bool"), ) module = AnsibleModule( @@ -286,18 +309,15 @@ def main(): service_graph = module.params.get("service_graph") node = module.params.get("node") state = module.params.get("state") - func_template_type = module.params.get("func_template_type") - func_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("func_type")) + functional_template_type = L4L7_FUNCTIONAL_TEMPLATE_TYPES_MAPPING.get(module.params.get("functional_template_type")) + function_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("function_type")) device = module.params.get("device") device_tenant = module.params.get("device_tenant") managed = aci.boolean(module.params.get("managed")) - routing_mode = module.params.get("routing_mode") + routing_mode = "Redirect" if module.params.get("routing_mode") == "redirect" else module.params.get("routing_mode") is_copy = aci.boolean(module.params.get("is_copy")) - - if func_template_type: - func_template_upper = func_template_type.upper() - else: - func_template_upper = None + description = module.params.get("description") + share_encap = aci.boolean(module.params.get("share_encap")) aci.construct_url( root_class=dict( @@ -331,11 +351,13 @@ def main(): aci_class="vnsAbsNode", class_config=dict( name=node, - funcTemplateType=func_template_upper, - funcType=func_type, + funcTemplateType=functional_template_type, + funcType=function_type, managed=managed, routingMode=routing_mode, isCopy=is_copy, + descr=description, + shareEncap=share_encap, ), child_configs=[ dict( diff --git a/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml index 632b2a203..5e6e0b0fe 100644 --- a/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml @@ -20,6 +20,13 @@ use_proxy: '{{ aci_use_proxy | default(true) }}' output_level: '{{ aci_output_level | default("info") }}' +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + # CLEAN ENVIRONMENT - name: Remove ansible_tenant if it already exists cisco.aci.aci_tenant: @@ -93,7 +100,7 @@ func_type: go_to device: ansible_device managed: false - routing_mode: Redirect + routing_mode: redirect state: present # CREATE L4-L7 DEVICE SELECTION POLICY @@ -164,6 +171,10 @@ <<: *add_context register: add_context +- name: Sort children by tDn attribute + set_fact: + sorted_children: "{{ add_context.current[0].vnsLIfCtx.children | map('dict2items') | sort(attribute='0.key') | map('items2dict') | list }}" + - name: Verify context creation ansible.builtin.assert: that: @@ -173,9 +184,10 @@ - add_context_cm.proposed.vnsLIfCtx.attributes.connNameOrLbl == "provider" - add_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" - add_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" - - add_context.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" - - add_context.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" - - add_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - sorted_children.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - sorted_children.1.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - sorted_children.2.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + # CREATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT AGAIN TO TEST IDEMPOTENCE - name: Create L4-L7 Device Selection Logical Interface Context again @@ -183,15 +195,49 @@ <<: *add_context register: add_context_again +- name: Sort children by tDn attribute again + set_fact: + sorted_children_again: "{{ add_context_again.current[0].vnsLIfCtx.children | map('dict2items') | sort(attribute='0.key') | map('items2dict') | list }}" + - name: Verify context creation idempotence ansible.builtin.assert: that: - add_context_again is not changed - add_context_again.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" - add_context_again.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" - - add_context_again.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" - - add_context_again.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" - - add_context_again.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - sorted_children_again.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - sorted_children_again.1.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - sorted_children_again.2.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + +- name: Execute tasks only for ACI v6+ + when: + - version.current.0.topSystem.attributes.version is version('6', '>=') + block: + - name: Create L4-L7 Device Selection Logical Interface Context with new attributes + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_bd + logical_device: ansible_device + logical_interface: ansible_log_intf + redirect_policy: ansible_pbr_policy + acl: true + permit_handoff: true + rule_type: true + state: present + register: add_context_new + + - name: Verify L4-L7 Device Selection Logical Interface Context with new attributes + ansible.builtin.assert: + that: + - add_context_new is changed + - add_context_new.current.0.vnsLIfCtx.attributes.acl == "yes" + - add_context_new.current.0.vnsLIfCtx.attributes.ruleType == "yes" + - add_context_new.current.0.vnsLIfCtx.attributes.permitHandoff == "yes" # UPDATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT - name: Modify L4-L7 Device Selection Logical Interface Context diff --git a/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml index 1ac63b9f5..a799b76ce 100644 --- a/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml @@ -93,7 +93,7 @@ func_type: go_to device: ansible_device managed: false - routing_mode: Redirect + routing_mode: redirect state: present # CREATE L4-L7 DEVICE SELECTION POLICY diff --git a/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml index 72855c132..827e21022 100644 --- a/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml @@ -21,6 +21,13 @@ use_proxy: '{{ aci_use_proxy | default(true) }}' output_level: '{{ aci_output_level | default("info") }}' +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + # CLEAN ENVIRONMENT - name: Remove ansible_tenant if it already exists cisco.aci.aci_tenant: @@ -41,6 +48,7 @@ <<: *aci_info tenant: ansible_tenant service_graph: ansible_service_graph + ui_template_type: one_node_fw_routed state: present check_mode: true register: create_sgt_cm @@ -57,9 +65,11 @@ - create_sgt is changed - create_sgt_cm.proposed.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" - create_sgt_cm.proposed.vnsAbsGraph.attributes.name == "ansible_service_graph" + - create_sgt_cm.proposed.vnsAbsGraph.attributes.uiTemplateType == "ONE_NODE_FW_ROUTED" - create_sgt.previous == [] - create_sgt.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" - create_sgt.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + - create_sgt.current.0.vnsAbsGraph.attributes.uiTemplateType == "ONE_NODE_FW_ROUTED" # ADD service graph template again to check idempotence - name: Create L4-L7 Service Graph Template again @@ -75,6 +85,27 @@ - create_sgt_again.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" - create_sgt_again.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" +- name: Execute tasks only for ACI v5+ + when: + - version.current.0.topSystem.attributes.version is version('5', '>=') + block: + - name: Create L4-L7 Service Graph Template with new attributes + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph_new + service_rule_type: epg + state: present + register: create_sgt_new + + - name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - create_sgt_new is changed + - create_sgt_new.current.0.vnsAbsGraph.attributes.svcRuleType == "epg" + - create_sgt_new.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph_new" + - create_sgt_new.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph_new" + # QUERY service graph template - name: Create another L4-L7 Service Graph Template cisco.aci.aci_l4l7_service_graph_template: diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml index 21372146e..f38d9aa64 100644 --- a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml @@ -99,7 +99,7 @@ func_type: go_to device: ansible_device1 managed: false - routing_mode: Redirect + routing_mode: redirect state: present - name: Add Service Graph Template Node 2 @@ -112,7 +112,7 @@ func_type: go_to device: ansible_device2 managed: false - routing_mode: Redirect + routing_mode: redirect state: present # ADD SERVICE GRAPH ABS CONNECTIONS diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml index d6a81e9e3..be4d046d3 100644 --- a/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml @@ -20,6 +20,13 @@ use_proxy: '{{ aci_use_proxy | default(true) }}' output_level: '{{ aci_output_level | default("info") }}' +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + # CLEAN ENVIRONMENT - name: Remove ansible_tenant if it already exists cisco.aci.aci_tenant: @@ -77,7 +84,7 @@ func_type: go_to device: ansible_device managed: false - routing_mode: Redirect + routing_mode: redirect state: present # ADD FUNCTIONAL CONNECTIONS @@ -159,6 +166,27 @@ - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.name == "provider" +- name: Execute tasks only for ACI v5+ + when: + - version.current.0.topSystem.attributes.version is version('5', '>=') + block: + - name: Modify Provider Func Conn with new attributes + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + connection_type: redir + state: present + register: add_prov_func_conn_new + + - name: Verify L4-L7 Device Selection Logical Interface Context with new attributes + ansible.builtin.assert: + that: + - add_prov_func_conn_new is changed + - add_prov_func_conn_new.current.0.vnsAbsFuncConn.attributes.connType == "redir" + # QUERY FUNC CONNS - name: Query Consumer Func Conn cisco.aci.aci_l4l7_service_graph_template_func_conn: diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml index 75f056322..f93ac77b7 100644 --- a/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml +++ b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml @@ -77,7 +77,8 @@ func_type: go_to device: ansible_device managed: false - routing_mode: Redirect + routing_mode: redirect + share_encap: true state: present check_mode: true register: add_l4l7_node_cm @@ -98,6 +99,7 @@ - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.managed == "no" - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.routingMode == "Redirect" + - add_l4l7_node_cm.proposed.vnsAbsNode.attributes.shareEncap == "yes" - add_l4l7_node is changed - add_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" - add_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" @@ -105,6 +107,7 @@ - add_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" - add_l4l7_node.current.0.vnsAbsNode.attributes.managed == "no" - add_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + - add_l4l7_node.current.0.vnsAbsNode.attributes.shareEncap == "yes" # VERIFY NODE BINDING TO LOGICAL DEVICE - name: Verify Node Binding to Logical Device @@ -148,7 +151,7 @@ func_type: go_through device: ansible_device managed: true - routing_mode: Redirect + routing_mode: redirect state: present register: update_l4l7_node From eb76e2e1a48b5649b5fac3bc3cbd37a2ebb77779 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 8 May 2025 19:45:43 -0400 Subject: [PATCH 5/8] [ignore] Added namespace and collection to see also note in docs of service_graph_template modules --- .../aci_l4l7_device_selection_interface_context.py | 8 ++++---- plugins/modules/aci_l4l7_device_selection_policy.py | 10 +++++----- plugins/modules/aci_l4l7_service_graph_template.py | 2 +- .../aci_l4l7_service_graph_template_abs_connection.py | 4 ++-- ...l4l7_service_graph_template_abs_connection_conns.py | 8 ++++---- ...4l7_service_graph_template_functional_connection.py | 6 +++--- .../modules/aci_l4l7_service_graph_template_node.py | 6 +++--- .../aci_l4l7_service_graph_template_term_node.py | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/modules/aci_l4l7_device_selection_interface_context.py b/plugins/modules/aci_l4l7_device_selection_interface_context.py index 80cf9da04..680ecc74b 100644 --- a/plugins/modules/aci_l4l7_device_selection_interface_context.py +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -109,10 +109,10 @@ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template), M(cisco.aci.aci_contract) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template -- module: aci_contract -- module: aci_l4l7_service_graph_template_node +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_l4l7_service_graph_template_node - name: APIC Management Information Model reference description: More information about the internal APIC class, B(vns:LIfCtx) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_device_selection_policy.py b/plugins/modules/aci_l4l7_device_selection_policy.py index 67d464490..289e3196f 100644 --- a/plugins/modules/aci_l4l7_device_selection_policy.py +++ b/plugins/modules/aci_l4l7_device_selection_policy.py @@ -68,11 +68,11 @@ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_contract), M(cisco.aci.aci_l4l7_service_graph), M(cisco.aci.aci_l4l7_device) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_contract -- module: aci_l4l7_service_graph -- module: aci_l4l7_device -- module: aci_l4l7_service_graph_template_node +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_l4l7_service_graph +- module: cisco.aci.aci_l4l7_device +- module: cisco.aci.aci_l4l7_service_graph_template_node - name: APIC Management Information Model reference description: More information about the internal APIC class B(vns:LDevCtx) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py index 01659e76a..1a97ac2cf 100644 --- a/plugins/modules/aci_l4l7_service_graph_template.py +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -80,7 +80,7 @@ - The I(tenant) must exist before using this module in your playbook. The M(cisco.aci.aci_tenant) module can be used for this. seealso: -- module: aci_tenant +- module: cisco.aci.aci_tenant - name: APIC Management Information Model reference description: More information about the internal APIC class B(vns:AbsGraph) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py index 3bc8f1a89..88a987f42 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py @@ -78,8 +78,8 @@ - The I(tenant) and I(service_graph) must exist before using this module in your playbook. The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template - name: APIC Management Information Model reference description: More information about the internal APIC class, B(vns:AbsConnection) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py index 7f6069edd..eae4aa781 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py @@ -56,10 +56,10 @@ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_service_graph_template_abs_conn) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template -- module: aci_l4l7_service_graph_template_abs_connection -- module: aci_l4l7_service_graph_template_node +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template +- module: cisco.aci.aci_l4l7_service_graph_template_abs_connection +- module: cisco.aci.aci_l4l7_service_graph_template_node - name: APIC Management Information Model reference description: More information about the internal APIC class, B(vns:RsAbsConnectionConns) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py index aee195a8c..c72706193 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py @@ -66,9 +66,9 @@ - The I(tenant), I(service_graph) and I(node) must exist before using this module in your playbook. The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template -- module: aci_l4l7_service_graph_template_node +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template +- module: cisco.aci.aci_l4l7_service_graph_template_node - name: APIC Management Information Model reference description: More information about the internal APIC classes, B(vns:AbsFuncConn) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py index 4bb5a49af..f290ae0ae 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -102,9 +102,9 @@ - The I(tenant), I(service_graph) and I(device) must exist before using this module in your playbook. The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_device) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template -- module: aci_l4l7_device +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template +- module: cisco.aci.aci_l4l7_device - name: APIC Management Information Model reference description: More information about the internal APIC classes B(vnsAbsNode) link: https://developer.cisco.com/docs/apic-mim-ref/ diff --git a/plugins/modules/aci_l4l7_service_graph_template_term_node.py b/plugins/modules/aci_l4l7_service_graph_template_term_node.py index b7d64637c..9c8b21f08 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_term_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_term_node.py @@ -47,8 +47,8 @@ - The I(tenant) and I(service_graph) must exist before using this module in your playbook. The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template) modules can be used for this. seealso: -- module: aci_tenant -- module: aci_l4l7_service_graph_template +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l4l7_service_graph_template - name: APIC Management Information Model reference description: More information about the internal APIC classes, B(vns:AbsTermNodeCon), B(vns:AbsTermNodeProv), B(vns:AbsTermConn) link: https://developer.cisco.com/docs/apic-mim-ref/ From bb2348639cd63662a717bc9289e3c79a3505368d Mon Sep 17 00:00:00 2001 From: Shreyas Date: Sun, 11 May 2025 19:44:10 -0400 Subject: [PATCH 6/8] [ignore] Added default values in the description for service_graph_template modules --- .../modules/aci_l4l7_device_selection_interface_context.py | 3 +++ plugins/modules/aci_l4l7_service_graph_template.py | 4 ++++ .../aci_l4l7_service_graph_template_abs_connection_conns.py | 1 + .../aci_l4l7_service_graph_template_functional_connection.py | 3 +++ plugins/modules/aci_l4l7_service_graph_template_node.py | 3 +++ 5 files changed, 14 insertions(+) diff --git a/plugins/modules/aci_l4l7_device_selection_interface_context.py b/plugins/modules/aci_l4l7_device_selection_interface_context.py index 680ecc74b..f25869f08 100644 --- a/plugins/modules/aci_l4l7_device_selection_interface_context.py +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -80,10 +80,12 @@ permit_handoff: description: - Indicates whether to allow handoff of traffic to the associated logical interface. + - The APIC defaults to C(false) when unset during creation. type: bool acl: description: - Specifies whether an Access Control List (ACL) is applied to the logical interface. + - The APIC defaults to C(false) when unset during creation. type: bool description: description: @@ -92,6 +94,7 @@ rule_type: description: - Indicates whether the context uses a specific rule type for traffic handling. + - The APIC defaults to C(false) when unset during creation. type: bool state: description: diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py index 1a97ac2cf..35ff749b0 100644 --- a/plugins/modules/aci_l4l7_service_graph_template.py +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -30,6 +30,7 @@ ui_template_type: description: - The UI Template Type. + - The APIC defaults to C(unspecified) when unset during creation. type: str choices: [ ndo_implicit_template, @@ -49,16 +50,19 @@ type: description: - Specifies the type of Service Graph Template. + - The APIC defaults to C(legacy) when unset during creation. type: str choices: [ cloud, legacy ] service_rule_type: description: - Defines the type of service rule applied within the Service Graph Template. + - The APIC defaults to C(vrf) when unset during creation. type: str choices: [ epg, subnet, vrf ] filter_between_nodes: description: - Determines how traffic is filtered between nodes in the Service Graph Template. + - The APIC defaults to C(allow-all) when unset during creation. type: str choices: [ allow-all, filters-from-contract ] description: diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py index eae4aa781..498756c05 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py @@ -65,6 +65,7 @@ link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) """ EXAMPLES = r""" diff --git a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py index c72706193..39c4841b0 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py @@ -37,6 +37,7 @@ attachment_notify: description: - Indicates whether attachment notifications are enabled for this connection. + - The APIC defaults to C(false) when unset during creation. type: bool description: description: @@ -45,6 +46,7 @@ connection_type: description: - Specifies the type of connection for the node. + - The APIC defaults to C(none) when unset during creation. type: str choices: [ dnat, none, redir, snat, snat_dnat ] device_interface_name: @@ -74,6 +76,7 @@ link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) """ EXAMPLES = r""" diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py index f290ae0ae..5cf87ee4e 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_node.py +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -77,15 +77,18 @@ routing_mode: description: - The routing mode for the node. + - The APIC defaults to C(unspecified) when unset during creation. type: str choices: [ redirect, unspecified ] is_copy: description: - Whether the device is a copy device. + - The APIC defaults to C(false) when unset during creation. type: bool share_encap: description: - Whether to share encapsulation across the service graph. + - The APIC defaults to C(false) when unset during creation. type: bool state: description: From 1f791c9d70108236a99af2da2143e3b5da339768 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Tue, 13 May 2025 15:49:18 -0400 Subject: [PATCH 7/8] [ignore] Changes description of docs in aci_l4l7_service_graph_template_abs_connection --- .../modules/aci_l4l7_service_graph_template_abs_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py index 88a987f42..d931083a6 100644 --- a/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.py @@ -43,14 +43,14 @@ type: bool adjacency_type: description: - - Whether the adjacecncy is Layer2 or Layer3. + - Whether the adjacency is Layer2 or Layer3. - The APIC defaults to C(l2) when unset during creation. type: str choices: [ l2, l3 ] connector_direction: description: - The connector direction. - - The APIC defaults to C(uknown) when unset during creation. + - The APIC defaults to C(unknown) when unset during creation. type: str choices: [ provider, consumer, unknown ] connection_type: From 8239643de999b9091febeaa18f0fe2f6ce8c88b2 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Mon, 26 May 2025 10:26:12 -0400 Subject: [PATCH 8/8] [ignore] Prepended name of a constant with L4L7 in constants.py --- plugins/module_utils/constants.py | 2 +- plugins/modules/aci_l4l7_service_graph_template.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py index 04d4d479b..73062deb2 100644 --- a/plugins/module_utils/constants.py +++ b/plugins/module_utils/constants.py @@ -464,7 +464,7 @@ "other": "OTHER", } -UI_TEMPLATE_TYPE = { +L4L7_UI_TEMPLATE_TYPE = { "ndo_implicit_template": "NDO_IMPLICIT_TEMPLATE", "one_node_adc_one_arm": "ONE_NODE_ADC_ONE_ARM", "one_node_adc_one_arm_l3ext": "ONE_NODE_ADC_ONE_ARM_L3EXT", diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py index 35ff749b0..78b8dae09 100644 --- a/plugins/modules/aci_l4l7_service_graph_template.py +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -243,7 +243,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec -from ansible_collections.cisco.aci.plugins.module_utils.constants import UI_TEMPLATE_TYPE +from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_UI_TEMPLATE_TYPE def main(): @@ -254,7 +254,7 @@ def main(): tenant=dict(type="str", aliases=["tenant_name"]), service_graph=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), - ui_template_type=dict(type="str", choices=list(UI_TEMPLATE_TYPE)), + ui_template_type=dict(type="str", choices=list(L4L7_UI_TEMPLATE_TYPE)), type=dict(type="str", choices=["cloud", "legacy"]), service_rule_type=dict(type="str", choices=["epg", "subnet", "vrf"]), filter_between_nodes=dict(type="str", choices=["allow-all", "filters-from-contract"]), @@ -273,7 +273,7 @@ def main(): tenant = module.params.get("tenant") service_graph = module.params.get("service_graph") state = module.params.get("state") - ui_template_type = UI_TEMPLATE_TYPE.get(module.params.get("ui_template_type")) + ui_template_type = L4L7_UI_TEMPLATE_TYPE.get(module.params.get("ui_template_type")) type = module.params.get("type") service_rule_type = module.params.get("service_rule_type") filter_between_nodes = module.params.get("filter_between_nodes")