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/constants.py b/plugins/module_utils/constants.py index 1bc3af185..73062deb2 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", +} + +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", + "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 new file mode 100644 index 000000000..f25869f08 --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_interface_context.py @@ -0,0 +1,452 @@ +#!/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 + 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: + - 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. + - The APIC defaults to C(false) when unset during creation. + 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 + +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: 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/ +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"), + permit_handoff=dict(type="bool"), + acl=dict(type="bool"), + description=dict(type="str"), + rule_type=dict(type="bool"), + ) + 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") + 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 + + 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.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( + { + "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, + permitHandoff=permit_handoff, + acl=acl, + descr=description, + ruleType=rule_type, + ), + 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..289e3196f --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_policy.py @@ -0,0 +1,339 @@ +#!/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 + description: + description: + - A brief description for the Device Selection Policy. + 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: 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/ +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"), + description=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") + description = module.params.get("description") + + 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) + + 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}, + ), + 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, + descr=description, + ), + 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..78b8dae09 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -0,0 +1,325 @@ +#!/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. + - The APIC defaults to C(unspecified) when unset during creation. + 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. + - 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: + description: + - A description of the Service Graph Template. + 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: 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/ +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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_UI_TEMPLATE_TYPE + + +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", 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"]), + description=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 = 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") + description = (module.params.get("description"),) + + 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, + type=type, + svcRuleType=service_rule_type, + filterBetweenNodes=filter_between_nodes, + descr=description, + ), + ) + 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..d931083a6 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection.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_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 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(unknown) when unset during creation. + type: str + 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. + - 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: 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/ +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", "unknown"]), + connection_type=dict(type="str", choices=["internal", "external"]), + description=dict(type="str"), + ) + + 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") + description = module.params.get("description") + + 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, + descr=description, + ), + ) + 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..498756c05 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py @@ -0,0 +1,301 @@ +#!/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: 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/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +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..39c4841b0 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_functional_connection.py @@ -0,0 +1,324 @@ +#!/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 ] + 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: + - The description for the Service Graph connection. + type: str + 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: + 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. + - 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: 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/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +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"]), + 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( + 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"]], + ], + ) + + 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") + 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( + 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, attNotify=attachment_notify, connType=connection_type, descr=description, deviceLIfName=device_interface_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..5cf87ee4e --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -0,0 +1,384 @@ +#!/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 + description: + description: + - The description of the Service Graph Template Node. + type: str + node: + description: + - The name of the Service Graph Template Node. + type: str + functional_template_type: + description: + - The functional template type for the node. + - The APIC defaults to C(other) when unset during creation. + type: str + 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 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. + 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. + - 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: + - 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: 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/ +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 + functional_template_type: adc_one_arm + function_type: GoTo + device: test-device + managed: false + 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, L4L7_FUNCTIONAL_TEMPLATE_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"]), + 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", choices=["redirect", "unspecified"]), + is_copy=dict(type="bool"), + description=dict(type="str"), + share_encap=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") + 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 = "Redirect" if module.params.get("routing_mode") == "redirect" else module.params.get("routing_mode") + is_copy = aci.boolean(module.params.get("is_copy")) + description = module.params.get("description") + share_encap = aci.boolean(module.params.get("share_encap")) + + 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=functional_template_type, + funcType=function_type, + managed=managed, + routingMode=routing_mode, + isCopy=is_copy, + descr=description, + shareEncap=share_encap, + ), + 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..9c8b21f08 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_term_node.py @@ -0,0 +1,293 @@ +#!/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: 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/ +author: +- Tim Cragg (@timcragg) +- Shreyas Srish (@shrsr) +""" + +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..5e6e0b0fe --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_interface_context/tasks/main.yml @@ -0,0 +1,388 @@ +# 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") }}' + +- 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: + <<: *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: 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: + - 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" + - 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 + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *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" + - 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 + 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..a799b76ce --- /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..827e21022 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml @@ -0,0 +1,189 @@ +# 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") }}' + +- 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: + <<: *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 + ui_template_type: one_node_fw_routed + 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_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 + 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" + +- 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: + <<: *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..f38d9aa64 --- /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..be4d046d3 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_functional_connection/tasks/main.yml @@ -0,0 +1,319 @@ +# 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") }}' + +- 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: + <<: *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" + +- 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: + <<: *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..f93ac77b7 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml @@ -0,0 +1,267 @@ +# 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 + share_encap: true + 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_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" + - 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" + - add_l4l7_node.current.0.vnsAbsNode.attributes.shareEncap == "yes" + +# 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