diff --git a/plugins/modules/aci_access_span_dst_group.py b/plugins/modules/aci_access_span_dst_group.py new file mode 100644 index 000000000..2da778c75 --- /dev/null +++ b/plugins/modules/aci_access_span_dst_group.py @@ -0,0 +1,485 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_dst_group +short_description: Manage Access SPAN destination groups (span:DestGrp) +description: +- Manage Access SPAN destination groups on Cisco ACI fabrics. +options: + destination_group: + description: + - The name of the Access SPAN destination group. + type: str + aliases: [ name, dst_group ] + description: + description: + - The description of the Access SPAN destination group. + type: str + aliases: [ descr ] + access_interface: + description: + - The destination access interface. + - The I(access_interface) and I(destination_epg) cannot be configured simultaneously. + type: dict + suboptions: + pod: + description: + - The pod id part of the destination path. + type: int + required: true + aliases: [ pod_id, pod_number ] + node: + description: + - The node id part of the destination path. + type: int + required: true + aliases: [ node_id ] + path: + description: + - The interface part of the destination path. + - When path is of type port a interface like C(eth1/7) must be provided. + - When path is of type direct_port_channel the name of a policy group like C(test_PolGrp) must be provided. + type: str + required: true + mtu: + description: + - The MTU truncation size for the packets. + - The APIC defaults to C(1518) when unset during creation. + type: int + destination_epg: + description: + - The destination end point group. + - The I(access_interface) and I(destination_epg) cannot be configured simultaneously. + type: dict + suboptions: + tenant: + description: + - The name of the tenant. + type: str + required: true + aliases: [ tenant_name ] + ap: + description: + - The name of application profile. + type: str + required: true + epg: + description: + - The name of the end point group. + type: str + required: true + span_version: + description: + - The SPAN version. + - The APIC defaults to C(version_2) when unset during creation. + type: str + choices: [ version_1, version_2 ] + version_enforced: + description: + - Enforce SPAN version. + type: bool + source_ip: + description: + - The source IP address or prefix. + type: str + required: true + destination_ip: + description: + - The destination IP address. + type: str + required: true + flow_id: + description: + - The flow ID of the SPAN packet. + - The APIC defaults to C(1) when unset during creation. + type: int + ttl: + description: + - The time to live of the span session packets. + - The APIC defaults to C(64) when unset during creation. + type: int + mtu: + description: + - The MTU truncation size for the packets. + - The APIC defaults to C(1518) when unset during creation. + type: int + dscp: + description: + - The DSCP value for sending the monitored packets using ERSPAN. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, unspecified ] + 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 + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:DestGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Access SPAN destination group of type EPG + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + span_version: version_1 + version_enforced: false + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: CS1 + state: present + delegate_to: localhost + +- name: Add a Access SPAN destination group of type access interface port + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + access_interface: + pod: 1 + node: 101 + path: 1/1 + mtu: 1500 + state: present + delegate_to: localhost + +- name: Add a Access SPAN destination group of type access interface direct_port_channel + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + access_interface: + pod: 1 + node: 101 + path: Switch101_1-ports-1-2_PolGrp + mtu: 1500 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN destination group + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN destination group + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: query + delegate_to: localhost + +- name: Query all Access SPAN destination groups + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + 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 destination_epg_spec(): + return dict( + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + ap=dict(type="str", required=True), + epg=dict(type="str", required=True), + source_ip=dict(type="str", required=True), + destination_ip=dict(type="str", required=True), + span_version=dict(type="str", choices=["version_1", "version_2"]), + version_enforced=dict(type="bool"), + flow_id=dict(type="int"), + ttl=dict(type="int"), + mtu=dict(type="int"), + dscp=dict( + type="str", + choices=[ + "CS0", + "CS1", + "CS2", + "CS3", + "CS4", + "CS5", + "CS6", + "CS7", + "EF", + "VA", + "AF11", + "AF12", + "AF13", + "AF21", + "AF22", + "AF23", + "AF31", + "AF32", + "AF33", + "AF41", + "AF42", + "AF43", + "unspecified", + ], + ), + ) + + +def access_interface_spec(): + return dict( + pod=dict(type="int", required=True, aliases=["pod_id", "pod_number"]), + node=dict(type="int", required=True, aliases=["node_id"]), + path=dict(type="str", required=True), + mtu=dict(type="int"), + ) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + destination_group=dict(type="str", aliases=["name", "dst_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + access_interface=dict(type="dict", options=access_interface_spec()), + destination_epg=dict(type="dict", options=destination_epg_spec()), + name_alias=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", ["destination_group"]], + ["state", "present", ["destination_group"]], + ["state", "present", ["access_interface", "destination_epg"], True], + ], + mutually_exclusive=[ + ("access_interface", "destination_epg"), + ], + ) + + aci = ACIModule(module) + + destination_group = module.params.get("destination_group") + description = module.params.get("description") + access_interface = module.params.get("access_interface") + destination_epg = module.params.get("destination_epg") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanDestGrp", + aci_rn="destgrp-{0}".format(destination_group), + module_object=destination_group, + target_filter={"name": destination_group}, + ), + child_classes=["spanDest", "spanRsDestEpg", "spanRsDestPathEp"], + ) + + aci.get_existing() + + if state == "present": + if destination_epg: + attributes = dict( + tDn="uni/tn-{0}/ap-{1}/epg-{2}".format(destination_epg.get("tenant"), destination_epg.get("ap"), destination_epg.get("epg")), + ip=destination_epg.get("destination_ip"), + srcIpPrefix=destination_epg.get("source_ip"), + ) + if destination_epg.get("span_version") is not None: + attributes["ver"] = "ver1" if destination_epg.get("span_version") == "version_1" else "ver2" + if destination_epg.get("version_enforced") is not None: + attributes["verEnforced"] = "yes" if destination_epg.get("version_enforced") else "no" + if destination_epg.get("ttl") is not None: + attributes["ttl"] = str(destination_epg.get("ttl")) + if destination_epg.get("mtu") is not None: + attributes["mtu"] = str(destination_epg.get("mtu")) + if destination_epg.get("flow_id") is not None: + attributes["flowId"] = str(destination_epg.get("flow_id")) + if destination_epg.get("dscp") is not None: + attributes["dscp"] = destination_epg.get("dscp") + span_rs_dest = dict(spanRsDestEpg=dict(attributes=attributes)) + + else: + attributes = dict( + tDn="topology/pod-{0}/paths-{1}/pathep-[{2}]".format(access_interface.get("pod"), access_interface.get("node"), access_interface.get("path")) + ) + if access_interface.get("mtu") is not None: + attributes["mtu"] = str(access_interface.get("mtu")) + span_rs_dest = dict(spanRsDestPathEp=dict(attributes=attributes)) + + aci.payload( + aci_class="spanDestGrp", + class_config=dict(name=destination_group, descr=description, nameAlias=name_alias), + child_configs=[dict(spanDest=dict(attributes=dict(name=destination_group), children=[span_rs_dest]))], + ) + + aci.get_diff(aci_class="spanDestGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/inventory.networking b/tests/integration/inventory.networking index cd1634d1c..16e3d1ee1 100644 --- a/tests/integration/inventory.networking +++ b/tests/integration/inventory.networking @@ -4,7 +4,7 @@ cn-dmz-apic-m1-03-v52 ansible_host=173.36.219.69 aci_hostname=173.36.219.69 cn-dmz-apic-m1-04-v60 ansible_host=173.36.219.70 aci_hostname=173.36.219.70 cn-dmz-apic-m1-07-v32 ansible_host=173.36.219.73 aci_hostname=173.36.219.73 aws_cloud ansible_host=52.52.20.121 aci_hostname=52.52.20.121 aci_password="sJ94G92#8dq2hx*K4qh" cloud_type=aws region=us-east-1 region_2=us-west-1 availability_zone=us-west-1a -azure_cloud ansible_host=104.42.26.226 aci_hostname=104.42.26.226 aci_password="sJ94G92#8dq2hx*K4qh" cloud_type=azure region=westus region_2=westus2 vnet_gateway=true +azure_cloud ansible_host=20.245.236.136 aci_hostname=20.245.236.136 aci_password="sJ94G92#8dq2hx*K4qh" cloud_type=azure region=westus region_2=westus2 vnet_gateway=true [aci:vars] aci_username=ansible_github_ci diff --git a/tests/integration/targets/aci_access_span_dst_group/aliases b/tests/integration/targets/aci_access_span_dst_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_access_span_dst_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_access_span_dst_group/tasks/main.yml b/tests/integration/targets/aci_access_span_dst_group/tasks/main.yml new file mode 100644 index 000000000..03c2e92c2 --- /dev/null +++ b/tests/integration/targets/aci_access_span_dst_group/tasks/main.yml @@ -0,0 +1,347 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# 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 + +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT + +- name: Query all access span destination groups + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +# TEST CREATE AND UPDATE DESTINATION GROUPS + +- name: Add access span destination group type epg ( check mode ) + cisco.aci.aci_access_span_dst_group: &add_ansible_group_1 + <<: *aci_info + destination_group: ansible_group_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + check_mode: true + register: cm_add_ansible_group_1 + +- name: Add access span destination group type epg + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_1 + register: nm_add_ansible_group_1 + +- name: Add access span destination group type epg again + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_1 + register: nm_add_ansible_group_1_again + +- name: Verify add access span destination group type epg + ansible.builtin.assert: + that: + - cm_add_ansible_group_1 is changed + - cm_add_ansible_group_1.current == [] + - cm_add_ansible_group_1.previous == [] + - cm_add_ansible_group_1.proposed.spanDestGrp.attributes.name == "ansible_group_1" + - cm_add_ansible_group_1.proposed.spanDestGrp.attributes.descr == "test span epg" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1 is changed + - nm_add_ansible_group_1.previous == [] + - nm_add_ansible_group_1.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1.current.0.spanDestGrp.attributes.descr == "test span epg" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - nm_add_ansible_group_1_again is not changed + - nm_add_ansible_group_1_again.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.attributes.descr == "test span epg" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + +- name: Change access span destination group type epg + cisco.aci.aci_access_span_dst_group: &change_ansible_group_1 + <<: *aci_info + destination_group: ansible_group_1 + description: changed test span epg + destination_epg: + destination_ip: 10.0.0.2 + source_ip: 10.0.2.2 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + version_enforced: true + span_version: version_1 + ttl: 4 + mtu: 1500 + flow_id: 2 + dscp: "VA" + register: change_ansible_group_1 + +- name: Apply same changes to access span destination group type epg + cisco.aci.aci_access_span_dst_group: + <<: *change_ansible_group_1 + register: change_ansible_group_1_again + +- name: Verify change access span destination group type epg + ansible.builtin.assert: + that: + - change_ansible_group_1 is changed + - change_ansible_group_1.previous.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1.previous.0.spanDestGrp.attributes.descr == "test span epg" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - change_ansible_group_1.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1.current.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + - change_ansible_group_1_again is not changed + - change_ansible_group_1_again.previous.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.previous.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + - change_ansible_group_1_again.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.current.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + +- name: Change access span destination group type epg path information (error) + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_1 + destination_epg: + destination_ip: 10.0.0.2 + source_ip: 10.0.2.2 + tenant: ansible_test_2 + ap: ansible_test_2 + epg: ansible_test_2 + register: error_on_path_change_ansible_group_1 + ignore_errors: true + +- name: Verify change access span destination group type epg path information (error) + ansible.builtin.assert: + that: + - error_on_path_change_ansible_group_1.msg == "APIC Error 105{{':'}} Span source or destination relation target configuration cannot be dynamically modified" + +- name: Add access span destination group type access interface port + cisco.aci.aci_access_span_dst_group: &add_ansible_group_2 + <<: *aci_info + destination_group: ansible_group_2 + description: test span access interface port + access_interface: + pod: 1 + node: 101 + path: eth1/1 + mtu: 1500 + state: present + register: add_ansible_group_2 + +- name: Add access span destination group type access interface direct port channel + cisco.aci.aci_access_span_dst_group: &add_ansible_group_3 + <<: *aci_info + destination_group: ansible_group_3 + description: test span access interface port channel + access_interface: + pod: 1 + node: 101 + path: test-PolGrp + state: present + register: add_ansible_group_3 + +- name: Verify add access span destination groups type access interface port and direct port channel + ansible.builtin.assert: + that: + - add_ansible_group_2 is changed + - add_ansible_group_2.current.0.spanDestGrp.attributes.name == "ansible_group_2" + - add_ansible_group_2.current.0.spanDestGrp.attributes.descr == "test span access interface port" + - add_ansible_group_2.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_2" + - add_ansible_group_2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestPathEp.attributes.mtu == "1500" + - add_ansible_group_2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - add_ansible_group_3 is changed + - add_ansible_group_3.current.0.spanDestGrp.attributes.name == "ansible_group_3" + - add_ansible_group_3.current.0.spanDestGrp.attributes.descr == "test span access interface port channel" + - add_ansible_group_3.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_3" + - add_ansible_group_3.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestPathEp.attributes.mtu == "1518" + - add_ansible_group_3.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[test-PolGrp]" + +# TEST QUERY DESTINATION GROUPS + +- name: Query one access span destination group + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_1 + state: query + register: query_one + +- name: Query all access span destination group + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying access span destination groups + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - query_all is not changed + - query_all.current | length >= 3 # greater or equal because there could be configuration present that is not access span but is returned in ( class based ) query all + +# TEST REMOVAL DESTINATION GROUPS + +- name: Remove access span destination group type epg ( check mode ) + cisco.aci.aci_access_span_dst_group: &remove_ansible_group_1 + <<: *add_ansible_group_1 + state: absent + check_mode: true + register: cm_remove_ansible_group_1 + +- name: Remove access span destination group type epg + cisco.aci.aci_access_span_dst_group: + <<: *remove_ansible_group_1 + register: nm_remove_ansible_group_1 + +- name: Remove access span destination group type epg again + cisco.aci.aci_access_span_dst_group: + <<: *remove_ansible_group_1 + register: nm_remove_ansible_group_1_again + +- name: Remove access span destination group type access interface port + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_2 + state: absent + register: nm_remove_ansible_group_2 + +- name: Remove access span destination group type access interface direct port channel + cisco.aci.aci_access_span_dst_group: + <<: *add_ansible_group_3 + state: absent + register: nm_remove_ansible_group_3 + +- name: Verify remove access span destination groups + ansible.builtin.assert: + that: + - cm_remove_ansible_group_1 is changed + - cm_remove_ansible_group_1.current | length == 1 + - cm_remove_ansible_group_1.previous | length == 1 + - cm_remove_ansible_group_1.proposed == {} + - nm_remove_ansible_group_1 is changed + - nm_remove_ansible_group_1.current == [] + - nm_remove_ansible_group_1.previous | length == 1 + - nm_remove_ansible_group_1_again is not changed + - nm_remove_ansible_group_1_again.current == [] + - nm_remove_ansible_group_1_again.previous == [] + - nm_remove_ansible_group_2 is changed + - nm_remove_ansible_group_2.current == [] + - nm_remove_ansible_group_2.previous | length == 1 + - nm_remove_ansible_group_3 is changed + - nm_remove_ansible_group_3.current == [] + - nm_remove_ansible_group_3.previous | length == 1 + +# TEST INCORRECT MODULE INPUT + +- name: Mutually exclusive parameters provided together (error) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: ansible_group_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + access_interface: + pod: 1 + node: 101 + path: test-PolGrp + state: present + register: incorrect_input_both_types + ignore_errors: true + +- name: Missing input parameters for state is present (error) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: ansible_group_1 + description: test span epg + register: incorrect_input_no_types + ignore_errors: true + +- name: Verify incorrect module input + ansible.builtin.assert: + that: + - incorrect_input_both_types.msg == "parameters are mutually exclusive{{':'}} access_interface|destination_epg" + - incorrect_input_no_types.msg == "state is present but any of the following are missing{{':'}} access_interface, destination_epg" \ No newline at end of file