diff --git a/meta/runtime.yml b/meta/runtime.yml
index 0bdcb52eb..4b462e205 100644
--- a/meta/runtime.yml
+++ b/meta/runtime.yml
@@ -263,3 +263,16 @@ action_groups:
- aci_vrf_leak_internal_subnet
- aci_vrf_multicast
- aci_vzany_to_contract
+
+plugin_routing:
+ modules:
+ aci_l4l7_concrete_interface_attach:
+ redirect: cisco.aci.aci_l4l7_concrete_interface_attachment
+ deprecation:
+ removal_version: 3.0.0
+ warning_text: Use cisco.aci.aci_l4l7_concrete_interface_attachment instead.
+ aci_l4l7_policy_based_redirect_dest:
+ redirect: cisco.aci.aci_l4l7_policy_based_redirect_destination
+ deprecation:
+ removal_version: 3.0.0
+ warning_text: Use cisco.aci.aci_l4l7_policy_based_redirect_destination instead.
diff --git a/plugins/modules/aci_l4l7_concrete_device.py b/plugins/modules/aci_l4l7_concrete_device.py
new file mode 100644
index 000000000..b708899c0
--- /dev/null
+++ b/plugins/modules/aci_l4l7_concrete_device.py
@@ -0,0 +1,296 @@
+#!/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_concrete_device
+short_description: Manage L4-L7 Concrete Devices (vns:CDev)
+description:
+- Manage Layer 4 to Layer 7 (L4-L7) Concrete Devices.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ logical_device:
+ description:
+ - The name of the logical device (vns:lDevVip) the concrete device is attached to.
+ type: str
+ aliases: [ device_name, device, logical_device_name ]
+ name:
+ description:
+ - The name of the concrete device.
+ type: str
+ aliases: [ concrete_device, concrete_device_name ]
+ vcenter_name:
+ description:
+ - The name of the vCenter hosting the L4-L7 device.
+ type: str
+ vm_name:
+ description:
+ - The VM name within the vCenter for the L4-L7 device.
+ 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) and I(logical_device) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_device) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_l4l7_device
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(vns:CDev)
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Shreyas Srish (@shrsr)
+"""
+
+EXAMPLES = r"""
+- name: Add a new concrete device
+ cisco.aci.aci_l4l7_concrete_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ state: present
+ delegate_to: localhost
+
+- name: Query a concrete device
+ cisco.aci.aci_l4l7_concrete_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all concrete devices
+ cisco.aci.aci_l4l7_concrete_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete a concrete device
+ cisco.aci.aci_l4l7_concrete_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ 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"]),
+ logical_device=dict(type="str", aliases=["device_name", "device", "logical_device_name"]),
+ name=dict(type="str", aliases=["concrete_device", "concrete_device_name"]),
+ vcenter_name=dict(type="str"),
+ vm_name=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", "logical_device", "name"]],
+ ["state", "present", ["tenant", "logical_device", "name"]],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+ logical_device = module.params.get("logical_device")
+ name = module.params.get("name")
+ vcenter_name = module.params.get("vcenter_name")
+ vm_name = module.params.get("vm_name")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="vnsLDevVip",
+ aci_rn="lDevVip-{0}".format(logical_device),
+ module_object=logical_device,
+ target_filter={"name": logical_device},
+ ),
+ subclass_2=dict(
+ aci_class="vnsCDev",
+ aci_rn="cDev-{0}".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="vnsCDev",
+ class_config=dict(
+ name=name,
+ vcenterName=vcenter_name,
+ vmName=vm_name,
+ ),
+ )
+ aci.get_diff(aci_class="vnsCDev")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/aci_l4l7_concrete_interface.py b/plugins/modules/aci_l4l7_concrete_interface.py
new file mode 100644
index 000000000..eac42c5ea
--- /dev/null
+++ b/plugins/modules/aci_l4l7_concrete_interface.py
@@ -0,0 +1,358 @@
+#!/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_concrete_interface
+short_description: Manage L4-L7 Concrete Interfaces (vns:CIf)
+description:
+- Manage Layer 4 to Layer 7 (L4-L7) Concrete Interfaces.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ logical_device:
+ description:
+ - The name of an existing logical device.
+ type: str
+ aliases: [ device_name, device, logical_device_name ]
+ concrete_device:
+ description:
+ - The name of an existing concrete device.
+ type: str
+ aliases: [ concrete_device_name ]
+ name:
+ description:
+ - The name of the concrete interface.
+ type: str
+ aliases: [ concrete_interface ]
+ pod_id:
+ description:
+ - The unique identifier for the pod where the concrete interface is located.
+ type: int
+ node_id:
+ description:
+ - The unique identifier for the node where the concrete interface is located.
+ - For Ports and Port-channels, this is represented as a single node ID.
+ - For virtual Port Channels (vPCs), this is represented as a hyphen-separated pair of node IDs, such as "201-202".
+ type: str
+ interface:
+ description:
+ - The path to the physical interface.
+ - For single ports, this is the port name, e.g. "eth1/15".
+ - For Port-channels and vPCs, this is the Interface Policy Group name.
+ type: str
+ aliases: [ path_ep, interface_name, interface_policy_group, interface_policy_group_name ]
+ vnic_name:
+ description:
+ - The concrete interface vNIC name.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+notes:
+- The I(tenant), I(logical_device) and I(concrete_device) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_device) and M(cisco.aci.aci_l4l7_concrete_device) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_l4l7_device
+- module: cisco.aci.aci_l4l7_concrete_device
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(vns:CIf)
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Shreyas Srish (@shrsr)
+"""
+
+EXAMPLES = r"""
+- name: Add a new concrete interface on a single port
+ cisco.aci.aci_l4l7_concrete_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ name: my_concrete_interface
+ pod_id: 1
+ node_id: 201
+ interface: eth1/16
+ state: present
+ delegate_to: localhost
+
+- name: Add a new concrete interface on a vPC
+ cisco.aci.aci_l4l7_concrete_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ name: my_concrete_interface
+ pod_id: 1
+ node_id: 201-202
+ interface: my_vpc_ipg
+ state: present
+ delegate_to: localhost
+
+- name: Query a concrete interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ name: my_concrete_interface
+ pod_id: 1
+ node_id: 201-202
+ interface: my_vpc_ipg
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all concrete interfaces
+ cisco.aci.aci_l4l7_concrete_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete a concrete interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ concrete_device: my_concrete_device
+ name: my_concrete_interface
+ 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"]),
+ logical_device=dict(type="str", aliases=["device_name", "device", "logical_device_name"]),
+ concrete_device=dict(type="str", aliases=["concrete_device_name"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name=dict(type="str", aliases=["concrete_interface"]),
+ pod_id=dict(type="int"),
+ node_id=dict(type="str"),
+ interface=dict(type="str", aliases=["path_ep", "interface_name", "interface_policy_group", "interface_policy_group_name"]),
+ vnic_name=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "logical_device", "concrete_device", "name"]],
+ ["state", "present", ["tenant", "logical_device", "concrete_device", "name", "pod_id", "node_id", "interface"]],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+ logical_device = module.params.get("logical_device")
+ concrete_device = module.params.get("concrete_device")
+ name = module.params.get("name")
+ pod_id = module.params.get("pod_id")
+ node_id = module.params.get("node_id")
+ interface = module.params.get("interface")
+ vnic_name = module.params.get("vnic_name")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="vnsLDevVip",
+ aci_rn="lDevVip-{0}".format(logical_device),
+ module_object=logical_device,
+ target_filter={"name": logical_device},
+ ),
+ subclass_2=dict(
+ aci_class="vnsCDev",
+ aci_rn="cDev-{0}".format(concrete_device),
+ module_object=concrete_device,
+ target_filter={"name": concrete_device},
+ ),
+ subclass_3=dict(
+ aci_class="vnsCIf",
+ aci_rn="cIf-[{0}]".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ child_classes=["vnsRsCIfPathAtt"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ path_dn = "topology/pod-{0}/{1}-{2}/pathep-[{3}]".format(pod_id, "protpaths" if "-" in node_id else "paths", node_id, interface)
+ aci.payload(
+ aci_class="vnsCIf",
+ class_config=dict(
+ name=name,
+ vnicName=vnic_name,
+ ),
+ child_configs=[
+ dict(
+ vnsRsCIfPathAtt=dict(
+ attributes=dict(tDn=path_dn),
+ ),
+ ),
+ ],
+ )
+ aci.get_diff(aci_class="vnsCIf")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/aci_l4l7_concrete_interface_attachment.py b/plugins/modules/aci_l4l7_concrete_interface_attachment.py
new file mode 100644
index 000000000..22b94a48a
--- /dev/null
+++ b/plugins/modules/aci_l4l7_concrete_interface_attachment.py
@@ -0,0 +1,316 @@
+#!/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_concrete_interface_attachment
+short_description: Manage L4-L7 Concrete Interface Attachment (vns:RsCIfAttN)
+description:
+- Manage Layer 4 to Layer 7 (L4-L7) Concrete Interface Attachment to Logical Interfaces.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ logical_device:
+ description:
+ - The name of an existing Logical Device.
+ type: str
+ aliases: [ device_name, device, logical_device_name ]
+ logical_interface:
+ description:
+ - The name of an existing Logical Interface.
+ type: str
+ concrete_device:
+ description:
+ - The name of an existing Concrete Device.
+ type: str
+ aliases: [ concrete_device_name ]
+ concrete_interface:
+ description:
+ - The name of an existing Concrete Interface.
+ 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(logical_device), I(logical_interface) and I(concrete_device) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_device), M(cisco.aci.aci_l4l7_logical_interface)
+ and M(cisco.aci.aci_l4l7_concrete_device) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_l4l7_device
+- module: cisco.aci.aci_l4l7_logical_interface
+- module: cisco.aci.aci_l4l7_concrete_device
+- module: cisco.aci.aci_l4l7_concrete_interface
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(vns:RsCIfAttN)
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Shreyas Srish (@shrsr)
+"""
+
+EXAMPLES = r"""
+- name: Add a new concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attachment:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_log_device
+ logical_interface: my_log_intf
+ concrete_device: my_conc_device
+ concrete_interface: my_conc_intf
+ state: present
+ delegate_to: localhost
+
+- name: Query a concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attachment:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_log_device
+ logical_interface: my_log_intf
+ concrete_device: my_conc_device
+ concrete_interface: my_conc_intf
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all concrete interface attachments
+ cisco.aci.aci_l4l7_concrete_interface_attachment:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete a concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attachment:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_log_device
+ logical_interface: my_log_intf
+ concrete_device: my_conc_device
+ concrete_interface: my_conc_intf
+ 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"]),
+ logical_device=dict(type="str", aliases=["device_name", "device", "logical_device_name"]),
+ logical_interface=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ concrete_device=dict(type="str", aliases=["concrete_device_name"]),
+ concrete_interface=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "logical_device", "logical_interface", "concrete_device", "concrete_interface"]],
+ ["state", "present", ["tenant", "logical_device", "logical_interface", "concrete_device", "concrete_interface"]],
+ ],
+ required_together=[
+ ["tenant", "logical_device", "concrete_device", "concrete_interface"],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+ logical_device = module.params.get("logical_device")
+ logical_interface = module.params.get("logical_interface")
+ concrete_device = module.params.get("concrete_device")
+ concrete_interface = module.params.get("concrete_interface")
+
+ aci = ACIModule(module)
+
+ tdn = (
+ "uni/tn-{0}/lDevVip-{1}/cDev-{2}/cIf-[{3}]".format(tenant, logical_device, concrete_device, concrete_interface)
+ if concrete_interface is not 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="vnsLDevVip",
+ aci_rn="lDevVip-{0}".format(logical_device),
+ module_object=logical_device,
+ target_filter={"name": logical_device},
+ ),
+ subclass_2=dict(
+ aci_class="vnsLIf",
+ aci_rn="lIf-{0}".format(logical_interface),
+ module_object=logical_interface,
+ target_filter={"name": logical_interface},
+ ),
+ subclass_3=dict(
+ aci_class="vnsRsCIfAttN",
+ aci_rn="rscIfAttN-[{0}]".format(tdn),
+ module_object=tdn,
+ target_filter={"tDn": tdn},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="vnsRsCIfAttN",
+ class_config=dict(tDn=tdn),
+ )
+ aci.get_diff(aci_class="vnsRsCIfAttN")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/aci_l4l7_device.py b/plugins/modules/aci_l4l7_device.py
new file mode 100644
index 000000000..c3db71d7e
--- /dev/null
+++ b/plugins/modules/aci_l4l7_device.py
@@ -0,0 +1,366 @@
+#!/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
+short_description: Manage L4-L7 Devices (vns:LDevVip)
+description:
+- Manage Layer 4 to Layer 7 (L4-L7) Devices.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ name:
+ description:
+ - The name of the L4-L7 device.
+ type: str
+ aliases: [ device, logical_device, device_name, logical_device_name ]
+ context_aware:
+ description:
+ - Is device Single or Multi context aware.
+ - The APIC defaults to C(single) when unset during creation.
+ type: str
+ choices: [ multi, single ]
+ device_type:
+ description:
+ - The type of the device.
+ - The APIC defaults to C(physical) when unset during creation.
+ type: str
+ choices: [ physical, virtual ]
+ aliases: [ dev_type ]
+ function_type:
+ description:
+ - The function type of the device.
+ - The APIC defaults to C(go_to) when unset during creation.
+ type: str
+ choices: [ go_to, go_through, l1, l2 ]
+ aliases: [ func_type ]
+ managed:
+ description:
+ - Is the device a managed device.
+ - The APIC defaults to true when unset during creation.
+ type: bool
+ promiscuous_mode:
+ description:
+ - Enable promiscuous mode.
+ - The APIC defaults to false when unset during creation.
+ type: bool
+ aliases: [ prom_mode ]
+ service_type:
+ description:
+ - The service type running on the device.
+ - The APIC defaults to C(others) when unset during creation.
+ type: str
+ choices: [ adc, fw, others ]
+ aliases: [ svc_type ]
+ trunking:
+ description:
+ - Enable trunking.
+ - The APIC defaults to false when unset during creation.
+ type: bool
+ domain:
+ description:
+ - The domain to bind to the device.
+ - The type of domain is controlled by the device_type setting.
+ type: str
+ active_active_mode:
+ description:
+ - The active active mode on the device.
+ - This is only applicable when C(function_type="l1") or C(function_type="l2").
+ - The APIC defaults to 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) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) modules 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:LDevVip)
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Shreyas Srish (@shrsr)
+"""
+
+EXAMPLES = r"""
+- name: Add a new L4-L7 device
+ cisco.aci.aci_l4l7_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ name: my_device
+ state: present
+ domain: phys
+ function_type: go_to
+ context_aware: single
+ managed: false
+ device_type: physical
+ service_type: adc
+ trunking: false
+ promiscuous_mode: true
+ delegate_to: localhost
+
+- name: Query an L4-L7 device
+ cisco.aci.aci_l4l7_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ name: my_device
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all L4-L7 devices
+ cisco.aci.aci_l4l7_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete an existing L4-L7 device
+ cisco.aci.aci_l4l7_device:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ name: my_device
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: ''
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_FUNC_TYPES_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]),
+ name=dict(type="str", aliases=["device", "device_name", "logical_device", "logical_device_name"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ context_aware=dict(type="str", choices=["single", "multi"]),
+ device_type=dict(type="str", aliases=["dev_type"], choices=["physical", "virtual"]),
+ function_type=dict(type="str", aliases=["func_type"], choices=list(L4L7_FUNC_TYPES_MAPPING)),
+ managed=dict(type="bool"),
+ promiscuous_mode=dict(type="bool", aliases=["prom_mode"]),
+ service_type=dict(type="str", aliases=["svc_type"], choices=["adc", "fw", "others"]),
+ trunking=dict(type="bool"),
+ domain=dict(type="str"),
+ active_active_mode=dict(type="bool"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "name"]],
+ ["state", "present", ["tenant", "name", "device_type", "domain"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+ name = module.params.get("name")
+ context_aware = module.params.get("context_aware")
+ device_type = module.params.get("device_type")
+ function_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("function_type"))
+ managed = aci.boolean(module.params.get("managed"))
+ promiscuous_mode = aci.boolean(module.params.get("promiscuous_mode"))
+ service_type = module.params.get("service_type")
+ trunking = aci.boolean(module.params.get("trunking"))
+ domain = module.params.get("domain")
+ active_active_mode = aci.boolean(module.params.get("active_active_mode"))
+
+ 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="vnsLDevVip",
+ aci_rn="lDevVip-{0}".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ child_classes=["vnsRsALDevToPhysDomP", "vnsCDev", "vnsRsALDevToDomP"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ if device_type == "virtual":
+ dom_class = "vnsRsALDevToDomP"
+ tdn = "uni/vmmp-VMware/dom-{0}".format(domain)
+ else:
+ dom_class = "vnsRsALDevToPhysDomP"
+ tdn = "uni/phys-{0}".format(domain)
+ child_configs.append({dom_class: {"attributes": {"tDn": tdn}}})
+
+ aci.payload(
+ aci_class="vnsLDevVip",
+ class_config=dict(
+ name=name,
+ contextAware="{0}-Context".format(context_aware),
+ devtype=device_type.upper(),
+ funcType=function_type,
+ managed=managed,
+ promMode=promiscuous_mode,
+ svcType=service_type.upper(),
+ trunking=trunking,
+ activeActive=active_active_mode,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="vnsLDevVip")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/aci_l4l7_logical_interface.py b/plugins/modules/aci_l4l7_logical_interface.py
new file mode 100644
index 000000000..5a6bd3843
--- /dev/null
+++ b/plugins/modules/aci_l4l7_logical_interface.py
@@ -0,0 +1,295 @@
+#!/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_logical_interface
+short_description: Manage L4-L7 Logical Interface (vns:LIf)
+description:
+- Manage Layer 4 to Layer 7 (L4-L7) Logical Interfaces.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ logical_device:
+ description:
+ - The name of an existing Logical Device.
+ type: str
+ aliases: [ device_name, device, logical_device_name ]
+ logical_interface:
+ description:
+ - The name of an existing Logical Interface.
+ type: str
+ aliases: [ name ]
+ encap:
+ description:
+ - The encapsulation of the Logical Interface.
+ - It requires the VLAN to be prepended, for example, 'vlan-987'.
+ type: str
+ enhanced_lag_policy:
+ description:
+ - Name of the VMM Domain Enhanced Lag Policy.
+ type: str
+ aliases: [ lag_policy ]
+ 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) and I(logical_device) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_device) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_l4l7_device
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(vns:LIf)
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Shreyas Srish (@shrsr)
+"""
+
+EXAMPLES = r"""
+- name: Add a new logical interface
+ cisco.aci.aci_l4l7_logical_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ logical_interface: my_log_intf
+ encap: vlan-987
+ state: present
+ delegate_to: localhost
+
+- name: Query a logical interface
+ cisco.aci.aci_l4l7_logical_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ logical_interface: my_log_intf
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all logical interfaces
+ cisco.aci.aci_l4l7_logical_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete a logical interface
+ cisco.aci.aci_l4l7_logical_interface:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ device: my_device
+ logical_interface: my_log_intf
+ 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"]),
+ logical_device=dict(type="str", aliases=["device_name", "device", "logical_device_name"]),
+ logical_interface=dict(type="str", aliases=["name"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ encap=dict(type="str"),
+ enhanced_lag_policy=dict(type="str", aliases=["lag_policy"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "logical_device", "logical_interface"]],
+ ["state", "present", ["tenant", "logical_device", "logical_interface"]],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+ logical_device = module.params.get("logical_device")
+ logical_interface = module.params.get("logical_interface")
+ encap = module.params.get("encap")
+ enhanced_lag_policy = module.params.get("enhanced_lag_policy")
+
+ 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="vnsLDevVip",
+ aci_rn="lDevVip-{0}".format(logical_device),
+ module_object=logical_device,
+ target_filter={"name": logical_device},
+ ),
+ subclass_2=dict(
+ aci_class="vnsLIf",
+ aci_rn="lIf-{0}".format(logical_interface),
+ module_object=logical_interface,
+ target_filter={"name": logical_interface},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="vnsLIf",
+ class_config=dict(name=logical_interface, encap=encap, lagPolicyName=enhanced_lag_policy),
+ )
+ aci.get_diff(aci_class="vnsLIf")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/integration/targets/aci_l4l7_concrete_device/aliases b/tests/integration/targets/aci_l4l7_concrete_device/aliases
new file mode 100644
index 000000000..209b793f9
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_device/aliases
@@ -0,0 +1,2 @@
+# No ACI simulator yet, so not enabled
+# unsupported
diff --git a/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml
new file mode 100644
index 000000000..1373b5a5d
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml
@@ -0,0 +1,197 @@
+# 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
+
+- 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
+
+# ADD L4-L7 CONCRETE DEVICE
+- name: Create L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device: &l4l7_concrete_device
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ state: present
+ check_mode: true
+ register: add_l4l7_concrete_device_cm
+
+- name: Create L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *l4l7_concrete_device
+ register: add_l4l7_concrete_device
+
+- name: Verify L4-L7 Concrete Device Attributes
+ ansible.builtin.assert:
+ that:
+ - add_l4l7_concrete_device_cm is changed
+ - add_l4l7_concrete_device is changed
+ - add_l4l7_concrete_device_cm.proposed.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device"
+ - add_l4l7_concrete_device_cm.proposed.vnsCDev.attributes.name == "ansible_concrete_device"
+ - add_l4l7_concrete_device.current.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device"
+ - add_l4l7_concrete_device.current.0.vnsCDev.attributes.name == "ansible_concrete_device"
+ - add_l4l7_concrete_device.previous == []
+
+# ADD L4-L7 CONCRETE DEVICE AGAIN TO CHECK IDEMPOTENCE
+- name: Add L4-L7 Concrete Device again
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *l4l7_concrete_device
+ register: add_l4l7_concrete_device_again
+
+- name: Verify L4-L7 Concrete Device Attributes
+ ansible.builtin.assert:
+ that:
+ - add_l4l7_concrete_device_again.previous == add_l4l7_concrete_device_again.current == add_l4l7_concrete_device.current
+ - add_l4l7_concrete_device_again is not changed
+
+- name: Create another L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device2
+ state: present
+ register: add_l4l7_concrete_device_2
+
+# QUERY L4-L7 CONCRETE DEVICE
+- name: Query L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ state: query
+ register: query_l4l7_concrete_device
+
+- name: Verify L4-L7 Concrete Device Attributes
+ ansible.builtin.assert:
+ that:
+ - query_l4l7_concrete_device is not changed
+ - query_l4l7_concrete_device.current.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device"
+ - query_l4l7_concrete_device.current.0.vnsCDev.attributes.name == "ansible_concrete_device"
+
+- name: Query All L4-L7 Concrete Devices
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *aci_info
+ state: query
+ register: query_l4l7_concrete_device_all
+
+- name: Verify L4-L7 Concrete Device Attributes
+ ansible.builtin.assert:
+ that:
+ - query_l4l7_concrete_device_all is not changed
+ - query_l4l7_concrete_device_all.current | length >= 2
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device' in query_l4l7_concrete_device_all.current | map(attribute='vnsCDev.attributes.dn') | list"
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device2' in query_l4l7_concrete_device_all.current | map(attribute='vnsCDev.attributes.dn') | list"
+
+# DELETE L4-L7 CONCRETE DEVICE
+- name: Remove L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device: &remove_l4l7_concrete_device
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ state: absent
+ check_mode: true
+ register: delete_l4l7_concrete_device_cm
+
+- name: Remove L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *remove_l4l7_concrete_device
+ register: delete_l4l7_concrete_device
+
+- name: Verify L4-L7 Concrete Device Deletion
+ ansible.builtin.assert:
+ that:
+ - delete_l4l7_concrete_device_cm is changed
+ - delete_l4l7_concrete_device is changed
+ - delete_l4l7_concrete_device.current == []
+ - delete_l4l7_concrete_device_cm.proposed == {}
+ - delete_l4l7_concrete_device_cm.previous == delete_l4l7_concrete_device.previous == add_l4l7_concrete_device.current
+
+# DELETE L4-L7 CONCRETE DEVICE AGAIN TO TEST IDEMPOTENCE
+- name: Remove L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *remove_l4l7_concrete_device
+ register: delete_l4l7_concrete_device_again
+
+- name: Verify L4-L7 Concrete Device Deletion idempotence
+ ansible.builtin.assert:
+ that:
+ - delete_l4l7_concrete_device_again.previous == delete_l4l7_concrete_device_again.current == []
+ - delete_l4l7_concrete_device_again is not changed
+
+# 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_concrete_interface/aliases b/tests/integration/targets/aci_l4l7_concrete_interface/aliases
new file mode 100644
index 000000000..209b793f9
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_interface/aliases
@@ -0,0 +1,2 @@
+# No ACI simulator yet, so not enabled
+# unsupported
diff --git a/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml
new file mode 100644
index 000000000..cbfb504c0
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml
@@ -0,0 +1,217 @@
+# 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
+
+- 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
+
+# ADD L4-L7 CONCRETE DEVICE
+- name: Create L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ state: present
+
+# ADD L4-L7 CONCRETE INTERFACE
+- name: Create L4-L7 Concrete Interface in check mode
+ cisco.aci.aci_l4l7_concrete_interface: &l4l7_concrete_interface
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ pod_id: 1
+ node_id: 201
+ path_ep: eth1/16
+ state: present
+ check_mode: true
+ register: add_concrete_interface_cm
+
+- name: Create L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *l4l7_concrete_interface
+ register: add_concrete_interface
+
+- name: Create L4-L7 Concrete Interface again
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *l4l7_concrete_interface
+ register: add_concrete_interface_again
+
+- name: Update L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *l4l7_concrete_interface
+ pod_id: 2
+ register: add_concrete_interface_update
+
+- name: Verify L4-L7 Concrete Interface Attributes
+ ansible.builtin.assert:
+ that:
+ - add_concrete_interface_cm is changed
+ - add_concrete_interface is changed
+ - add_concrete_interface_again is not changed
+ - add_concrete_interface_update is changed
+ - add_concrete_interface_cm.proposed.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - add_concrete_interface_cm.proposed.vnsCIf.attributes.name == "ansible_concrete_interface"
+ - add_concrete_interface.current.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - add_concrete_interface.current.0.vnsCIf.attributes.name == "ansible_concrete_interface"
+ - add_concrete_interface_again.previous == add_concrete_interface_again.current == add_concrete_interface.current
+ - add_concrete_interface.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-1/paths-201/pathep-[eth1/16]"
+ - add_concrete_interface_again.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-1/paths-201/pathep-[eth1/16]"
+ - add_concrete_interface_update.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-2/paths-201/pathep-[eth1/16]"
+
+# QUERY L4-L7 CONCRETE INTERFACE
+- name: Create another L4-L7 Concrete Interface for query all
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface2
+ pod_id: 1
+ node_id: 202
+ path_ep: eth1/16
+ state: present
+ register: add_concrete_interface_another
+
+- name: Query L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ pod_id: 1
+ node_id: 201
+ path_ep: eth1/16
+ state: query
+ register: query_concrete_interface
+
+- name: Query all L4-L7 Concrete Interfaces
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *aci_info
+ state: query
+ register: query_all
+
+- name: Verify L4-L7 Concrete Interface Attributes
+ ansible.builtin.assert:
+ that:
+ - query_concrete_interface is not changed
+ - query_concrete_interface.current.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - query_concrete_interface.current.0.vnsCIf.attributes.name == "ansible_concrete_interface"
+ - query_all is not changed
+ - query_all.current | length >= 2
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]' in query_all.current | map(attribute='vnsCIf.attributes.dn') | list"
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface2]' in query_all.current | map(attribute='vnsCIf.attributes.dn') | list"
+ - query_concrete_interface.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-2/paths-201/pathep-[eth1/16]"
+
+# DELETE L4-L7 CONCRETE INTERFACE
+- name: Remove L4-L7 Concrete Interface in check mode
+ cisco.aci.aci_l4l7_concrete_interface: &remove_l4l7_concrete_interface
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ pod_id: 1
+ node_id: 201
+ path_ep: eth1/16
+ state: absent
+ check_mode: true
+ register: delete_concrete_interface_cm
+
+- name: Remove L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *remove_l4l7_concrete_interface
+ register: delete_concrete_interface
+
+- name: Remove L4-L7 Concrete Interface again
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *remove_l4l7_concrete_interface
+ register: delete_concrete_interface_again
+
+- name: Verify L4-L7 Concrete Interface Deletion
+ ansible.builtin.assert:
+ that:
+ - delete_concrete_interface_cm is changed
+ - delete_concrete_interface is changed
+ - delete_concrete_interface_again is not changed
+ - delete_concrete_interface.current == [] == delete_concrete_interface_again.current
+ - delete_concrete_interface_cm.proposed == {}
+ - delete_concrete_interface_cm.previous == delete_concrete_interface.previous == add_concrete_interface_update.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_concrete_interface_attachment/aliases b/tests/integration/targets/aci_l4l7_concrete_interface_attachment/aliases
new file mode 100644
index 000000000..209b793f9
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_interface_attachment/aliases
@@ -0,0 +1,2 @@
+# No ACI simulator yet, so not enabled
+# unsupported
diff --git a/tests/integration/targets/aci_l4l7_concrete_interface_attachment/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_interface_attachment/tasks/main.yml
new file mode 100644
index 000000000..3246dbea8
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_concrete_interface_attachment/tasks/main.yml
@@ -0,0 +1,251 @@
+# 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
+
+- 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 L4-L7 LOGICAL INTERFACE
+- name: Add 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
+
+- name: Add Second Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: second_log_intf
+ encap: vlan-988
+ state: present
+
+# CREATE L4-L7 CONCRETE INTERFACE
+- name: Create L4-L7 Concrete Device
+ cisco.aci.aci_l4l7_concrete_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ state: present
+
+# CREATE L4-L7 CONCRETE INTERFACE
+- name: Create L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ pod_id: 1
+ node_id: 201
+ path_ep: eth1/16
+ state: present
+
+- name: Create Second L4-L7 Concrete Interface
+ cisco.aci.aci_l4l7_concrete_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_second_concrete_interface
+ pod_id: 1
+ node_id: 201
+ path_ep: eth1/17
+ state: present
+
+- name: Add a new concrete interface attachment in check mode
+ cisco.aci.aci_l4l7_concrete_interface_attach: &concrete_interface_attach
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ state: present
+ check_mode: true
+ register: add_concrete_intf_attach_cm
+
+- name: Add a new concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *concrete_interface_attach
+ register: add_concrete_intf_attach
+
+- name: Add a new concrete interface attachment again
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *concrete_interface_attach
+ register: add_concrete_intf_attach_again
+
+- name: Update concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *concrete_interface_attach
+ concrete_interface: ansible_second_concrete_interface
+ register: add_concrete_intf_attach_update
+
+- name: Verify interface attachment
+ ansible.builtin.assert:
+ that:
+ - add_concrete_intf_attach_cm is changed
+ - add_concrete_intf_attach is changed
+ - add_concrete_intf_attach_update is changed
+ - add_concrete_intf_attach.previous == []
+ - add_concrete_intf_attach_cm.proposed.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]"
+ - add_concrete_intf_attach_cm.proposed.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - add_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]"
+ - add_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - add_concrete_intf_attach_again is not changed
+ - add_concrete_intf_attach_again.current == add_concrete_intf_attach.current == add_concrete_intf_attach_again.previous
+ - add_concrete_intf_attach_again.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - add_concrete_intf_attach_update.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_second_concrete_interface]]"
+
+- name: Add a second concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: second_log_intf
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_second_concrete_interface
+ state: present
+
+# QUERY CONCRETE INTERFACE ATTACHMENT
+- name: Query concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ state: query
+ register: query_concrete_intf_attach
+
+- name: Query all concrete interface attachments
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *aci_info
+ state: query
+ register: query_all_attachments
+
+- name: Verify interface attachment
+ ansible.builtin.assert:
+ that:
+ - query_concrete_intf_attach is not changed
+ - query_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]"
+ - query_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]"
+ - query_all_attachments is not changed
+ - query_all_attachments.current | length >= 2
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_second_concrete_interface]]' in query_all_attachments.current | map(attribute='vnsRsCIfAttN.attributes.dn') | list"
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]' in query_all_attachments.current | map(attribute='vnsRsCIfAttN.attributes.dn') | list"
+
+# DELETE CONCRETE INTERFACE ATTACHMENT
+- name: Remove concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach: &remove_concrete_interface_attach
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ concrete_device: ansible_concrete_device
+ concrete_interface: ansible_concrete_interface
+ state: absent
+ check_mode: true
+ register: delete_concrete_intf_attach_cm
+
+- name: Remove concrete interface attachment
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *remove_concrete_interface_attach
+ register: delete_concrete_intf_attach
+
+- name: Remove concrete interface attachment again
+ cisco.aci.aci_l4l7_concrete_interface_attach:
+ <<: *remove_concrete_interface_attach
+ register: delete_concrete_intf_attach_again
+
+- name: Verify interface attachment removal
+ ansible.builtin.assert:
+ that:
+ - delete_concrete_intf_attach_cm is changed
+ - delete_concrete_intf_attach_cm.proposed == {}
+ - delete_concrete_intf_attach is changed
+ - delete_concrete_intf_attach.previous == add_concrete_intf_attach.current
+ - delete_concrete_intf_attach.current == []
+ - delete_concrete_intf_attach_again is not changed
+ - delete_concrete_intf_attach_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_device/aliases b/tests/integration/targets/aci_l4l7_device/aliases
new file mode 100644
index 000000000..209b793f9
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_device/aliases
@@ -0,0 +1,2 @@
+# No ACI simulator yet, so not enabled
+# unsupported
diff --git a/tests/integration/targets/aci_l4l7_device/tasks/main.yml b/tests/integration/targets/aci_l4l7_device/tasks/main.yml
new file mode 100644
index 000000000..8d6f2cb67
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_device/tasks/main.yml
@@ -0,0 +1,327 @@
+# 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
+
+- name: Remove ansible_phys_domain if it already exists
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_phys_dom
+ domain_type: phys
+ state: absent
+
+- name: Remove ansible_vmm_domain if it already exists
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ state: absent
+
+# CREATE TENANT
+- name: Create ansible_tenant
+ cisco.aci.aci_tenant:
+ <<: *aci_info
+ name: ansible_tenant
+ state: present
+
+# CREATE DOMAINS
+- name: Create ansible_phys_domain
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_phys_dom
+ domain_type: phys
+ state: present
+
+- name: Create ansible_vmm_domain
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ state: present
+
+# ADD L4-L7 DEVICE
+- name: Create L4-L7 Physical Device in check mode
+ cisco.aci.aci_l4l7_device: &l4_l7_physical
+ <<: *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
+ check_mode: true
+ register: create_l4l7_device_cm
+
+- name: Create L4-L7 Physical Device
+ cisco.aci.aci_l4l7_device:
+ <<: *l4_l7_physical
+ register: create_l4l7_device
+
+- name: Create L4-L7 Physical Device again
+ cisco.aci.aci_l4l7_device:
+ <<: *l4_l7_physical
+ register: create_l4l7_device_again
+
+- name: Create L4-L7 Virtual Device
+ cisco.aci.aci_l4l7_device: &l4_l7_virtual
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_virt_device
+ domain: ansible_vmm_dom
+ func_type: go_to
+ context_aware: single
+ managed: false
+ dev_type: virtual
+ svc_type: adc
+ trunking: false
+ prom_mode: true
+ state: present
+ register: create_virt_l4l7_device
+
+- name: Create L4-L7 Virtual Device again
+ cisco.aci.aci_l4l7_device:
+ <<: *l4_l7_virtual
+ register: create_virt_l4l7_device_again
+
+- name: Verify L4-L7 device has been created
+ ansible.builtin.assert:
+ that:
+ - create_l4l7_device_cm is changed
+ - create_l4l7_device is changed
+ - create_l4l7_device_again is not changed
+ - create_virt_l4l7_device is changed
+ - create_virt_l4l7_device_again is not changed
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.name == "ansible_device"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.contextAware == "single-Context"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.devtype == "PHYSICAL"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.funcType == "GoTo"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.managed == "no"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.promMode == "yes"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.svcType == "ADC"
+ - create_l4l7_device_cm.proposed.vnsLDevVip.attributes.trunking == "no"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "single-Context"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoTo"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "yes"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "ADC"
+ - create_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "no"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_virt_device"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_virt_device"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "single-Context"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "VIRTUAL"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoTo"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "yes"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "ADC"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "no"
+ - create_l4l7_device.previous == []
+ - create_virt_l4l7_device.previous == []
+ - create_l4l7_device_again.previous == create_l4l7_device_again.current == create_l4l7_device.current
+ - create_virt_l4l7_device_again.previous == create_virt_l4l7_device_again.current == create_virt_l4l7_device.current
+
+- name: Verify domain binding object has been created
+ ansible.builtin.assert:
+ that:
+ - create_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom"
+ - create_virt_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToDomP.attributes.tDn == "uni/vmmp-VMware/dom-ansible_vmm_dom"
+
+- name: Verify domain binding object is still correct
+ ansible.builtin.assert:
+ that:
+ - create_l4l7_device_again.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom"
+ - create_virt_l4l7_device_again.current.0.vnsLDevVip.children.0.vnsRsALDevToDomP.attributes.tDn == "uni/vmmp-VMware/dom-ansible_vmm_dom"
+
+# MODIFY L4-L7 Device
+- name: Update L4-L7 device
+ cisco.aci.aci_l4l7_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ domain: ansible_phys_dom
+ func_type: go_through
+ context_aware: multi
+ managed: false
+ dev_type: physical
+ svc_type: fw
+ trunking: true
+ prom_mode: false
+ state: present
+ register: update_l4l7_device
+
+- name: Verify L4-L7 device has been updated
+ ansible.builtin.assert:
+ that:
+ - update_l4l7_device is changed
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "multi-Context"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoThrough"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "no"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "FW"
+ - update_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "yes"
+
+- name: Verify domain binding object
+ ansible.builtin.assert:
+ that:
+ - update_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom"
+
+- name: Create another L4-L7 device to test active active mode
+ cisco.aci.aci_l4l7_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device2
+ domain: ansible_phys_dom
+ func_type: l1
+ context_aware: multi
+ managed: false
+ dev_type: physical
+ svc_type: others
+ trunking: true
+ prom_mode: false
+ active_active_mode: true
+ state: present
+ register: another_l4l7_device_active
+
+- name: Verify domain binding object
+ ansible.builtin.assert:
+ that:
+ - another_l4l7_device_active is changed
+ - another_l4l7_device_active.current.0.vnsLDevVip.attributes.funcType == "L1"
+ - another_l4l7_device_active.current.0.vnsLDevVip.attributes.activeActive == "yes"
+
+# QUERY DEVICE
+- name: Query L4-L7 Device
+ cisco.aci.aci_l4l7_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ state: query
+ register: query_l4l7_device
+
+- name: Verify L4-L7 Device attributes
+ ansible.builtin.assert:
+ that:
+ - query_l4l7_device is not changed
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "multi-Context"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoThrough"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "no"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "FW"
+ - query_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "yes"
+
+- name: Query all L4-L7 Devices
+ cisco.aci.aci_l4l7_device:
+ <<: *aci_info
+ state: query
+ register: query_l4l7_device_all
+
+- name: Verify L4-L7 Device query idempotence
+ ansible.builtin.assert:
+ that:
+ - query_l4l7_device_all is not changed
+ - query_l4l7_device_all.current | length >= 3
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device' in query_l4l7_device_all.current | map(attribute='vnsLDevVip.attributes.dn') | list"
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_virt_device' in query_l4l7_device_all.current | map(attribute='vnsLDevVip.attributes.dn') | list"
+
+# DELETE DEVICE
+- name: Delete L4-L7 Device
+ cisco.aci.aci_l4l7_device: &remove_l4l7_device
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ state: absent
+ check_mode: true
+ register: remove_l4l7_device_cm
+
+- name: Delete L4-L7 Device
+ cisco.aci.aci_l4l7_device:
+ <<: *remove_l4l7_device
+ register: remove_l4l7_device
+
+- name: Verify L4-L7 Device deletion
+ ansible.builtin.assert:
+ that:
+ - remove_l4l7_device_cm is changed
+ - remove_l4l7_device is changed
+ - remove_l4l7_device_cm.proposed == {}
+ - remove_l4l7_device_cm.previous == remove_l4l7_device.previous == update_l4l7_device.current
+ - remove_l4l7_device.current == []
+
+# DELETE DEVICE AGAIN TO TEST IDEMPOTENCE
+- name: Delete L4-L7 Device again
+ cisco.aci.aci_l4l7_device:
+ <<: *remove_l4l7_device
+ register: remove_l4l7_device_again
+
+- name: Verify L4-L7 Device deletion idempotence
+ ansible.builtin.assert:
+ that:
+ - remove_l4l7_device_again is not changed
+ - remove_l4l7_device_again.current == [] == remove_l4l7_device_again.previous
+
+# 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_vmm_domain
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ state: absent
diff --git a/tests/integration/targets/aci_l4l7_logical_interface/aliases b/tests/integration/targets/aci_l4l7_logical_interface/aliases
new file mode 100644
index 000000000..209b793f9
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_logical_interface/aliases
@@ -0,0 +1,2 @@
+# No ACI simulator yet, so not enabled
+# unsupported
diff --git a/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml b/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml
new file mode 100644
index 000000000..5af74785e
--- /dev/null
+++ b/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml
@@ -0,0 +1,288 @@
+# 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
+
+- name: Remove ansible_phys_domain if it already exists
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_phys_dom
+ domain_type: phys
+ state: absent
+
+- name: Remove ansible_vmm_domain if it already exists
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ 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
+
+- name: Create ansible_vmm_domain
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ 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
+
+- name: Create L4-L7 Virtual Device
+ cisco.aci.aci_l4l7_device:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_virt_device
+ domain: ansible_vmm_dom
+ func_type: go_to
+ context_aware: single
+ managed: false
+ dev_type: virtual
+ svc_type: adc
+ trunking: false
+ prom_mode: true
+ state: present
+
+# CREATE L4-L7 LOGICAL INTERFACE
+- name: Add Logical Interface in check mode
+ cisco.aci.aci_l4l7_logical_interface: &l4l7_logical_interface
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ encap: vlan-987
+ state: present
+ check_mode: true
+ register: add_logical_interface_cm
+
+- name: Add Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *l4l7_logical_interface
+ register: add_logical_interface
+
+- name: Add Logical Interface again
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *l4l7_logical_interface
+ register: add_logical_interface_again
+
+- name: Verify Logical Interface Attributes
+ ansible.builtin.assert:
+ that:
+ - add_logical_interface_cm is changed
+ - add_logical_interface is changed
+ - add_logical_interface.previous == []
+ - add_logical_interface_cm.proposed.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf"
+ - add_logical_interface_cm.proposed.vnsLIf.attributes.name == "ansible_log_intf"
+ - add_logical_interface_cm.proposed.vnsLIf.attributes.encap == "vlan-987"
+ - add_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf"
+ - add_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf"
+ - add_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-987"
+ - add_logical_interface_again is not changed
+ - add_logical_interface_again.current == add_logical_interface_again.previous == add_logical_interface.current
+
+# MODIFY L4-L7 LOGICAL INTERFACE
+- name: Update L4-L7 Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ encap: vlan-988
+ state: present
+ register: update_logical_interface
+
+- name: Verify Logical Interface update
+ ansible.builtin.assert:
+ that:
+ - update_logical_interface is changed
+ - update_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf"
+ - update_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf"
+ - update_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-988"
+
+# Test to verify enhanced_lag_policy
+- name: Create enhanced lag policy
+ cisco.aci.aci_rest:
+ <<: *aci_info
+ path: api/node/mo/uni/vmmp-VMware/dom-ansible_vmm_dom/vswitchpolcont.json
+ method: post
+ content: |
+ {
+ "vmmVSwitchPolicyCont": {
+ "attributes": {
+ "dn": "uni/vmmp-VMware/dom-ansible_vmm_dom/vswitchpolcont",
+ "status": "created,modified"
+ },
+ "children": [{
+ "lacpEnhancedLagPol": {
+ "attributes": {
+ "lbmode": "src-dst-ip",
+ "mode": "active",
+ "name": "enhanced",
+ "numLinks": "2"
+ }
+ }
+ }]
+ }
+ }
+
+- name: Add Logical Interface for virtual device
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_virt_device
+ logical_interface: ansible_virt
+ lag_policy: enhanced
+ state: present
+ register: add_logical_interface_virtual
+
+- name: Verify Logical Interface Virtual
+ ansible.builtin.assert:
+ that:
+ - add_logical_interface_virtual is changed
+ - add_logical_interface_virtual.current.0.vnsLIf.attributes.lagPolicyName == "enhanced"
+
+# QUERY L4-L7 LOGICAL INTERFACE
+- name: Create a second L4-L7 Logical Interface for query all
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_int2
+ encap: vlan-989
+ state: present
+
+- name: Query L4-L7 Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ state: query
+ register: query_logical_interface
+
+- name: Query All L4-L7 Logical Interfaces
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *aci_info
+ state: query
+ register: query_all
+
+- name: Verify Logical Interface Attributes have not changed
+ ansible.builtin.assert:
+ that:
+ - query_logical_interface is not changed
+ - query_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf"
+ - query_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf"
+ - query_all.current | length >= 3
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf' in query_all.current | map(attribute='vnsLIf.attributes.dn') | list"
+ - "'uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_int2' in query_all.current | map(attribute='vnsLIf.attributes.dn') | list"
+ - query_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-988"
+
+# DELETE L4-L7 LOGICAL INTERFACE
+- name: Remove L4-L7 Logical Interface
+ cisco.aci.aci_l4l7_logical_interface: &remove_l4l7_logical_interface
+ <<: *aci_info
+ tenant: ansible_tenant
+ device: ansible_device
+ logical_interface: ansible_log_intf
+ state: absent
+ check_mode: true
+ register: delete_logical_interface_cm
+
+- name: Remove L4-L7 Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *remove_l4l7_logical_interface
+ register: delete_logical_interface
+
+- name: Remove L4-L7 Logical Interface
+ cisco.aci.aci_l4l7_logical_interface:
+ <<: *remove_l4l7_logical_interface
+ register: delete_logical_interface_again
+
+- name: Verify Logical Interface Deletion
+ ansible.builtin.assert:
+ that:
+ - delete_logical_interface_cm is changed
+ - delete_logical_interface_cm.proposed == {}
+ - delete_logical_interface is changed
+ - delete_logical_interface.current == []
+ - delete_logical_interface.previous == update_logical_interface.current
+ - delete_logical_interface.previous.0.vnsLIf.attributes.name == "ansible_log_intf"
+ - delete_logical_interface.previous.0.vnsLIf.attributes.encap == "vlan-988"
+ - delete_logical_interface_again is not changed
+ - delete_logical_interface_again.current == [] == delete_logical_interface_again.previous
+
+# 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_vmm_domain if it already exists
+ cisco.aci.aci_domain:
+ <<: *aci_info
+ domain: ansible_vmm_dom
+ domain_type: vmm
+ vm_provider: vmware
+ state: absent