diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py new file mode 100644 index 000000000..72d7585a5 --- /dev/null +++ b/plugins/module_utils/constants.py @@ -0,0 +1,3 @@ +VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"] + +FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "22": "ssh", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"} diff --git a/plugins/modules/aci_access_span_filter_group.py b/plugins/modules/aci_access_span_filter_group.py new file mode 100644 index 000000000..c715027f6 --- /dev/null +++ b/plugins/modules/aci_access_span_filter_group.py @@ -0,0 +1,248 @@ +#!/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_filter_group +short_description: Manage Access SPAN filter groups (span:FilterGrp) +description: +- Manage Access SPAN filter groups on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + aliases: [ name ] + 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 + +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 filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str", aliases=["name"]), # Not required for querying all objects + 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", ["filter_group"]], + ["state", "present", ["filter_group"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_group") + 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="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + child_classes=["spanFilterEntry"], + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanFilterGrp", class_config=dict(name=filter_group, nameAlias=name_alias)) + + aci.get_diff(aci_class="spanFilterGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_access_span_filter_group_entry.py b/plugins/modules/aci_access_span_filter_group_entry.py new file mode 100644 index 000000000..5d5516751 --- /dev/null +++ b/plugins/modules/aci_access_span_filter_group_entry.py @@ -0,0 +1,346 @@ +#!/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_filter_group_entry +short_description: Manage Access SPAN filter group entries (span:FilterEntry) +description: +- Manage Access SPAN filter group entries on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + source_ip: + description: + - The source IP Prefix. + type: str + destination_ip: + description: + - The destination IP Prefix. + type: str + first_src_port: + description: + - The first source port (from port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + last_src_port: + description: + - The last source port (to port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + first_dest_port: + description: + - The first destination port (from port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + last_dest_port: + description: + - The last destination port (to port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + ip_protocol: + description: + - The IP Protocol. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, 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 + +notes: +- The C(filter_group) used must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_filter_group) module can be used for this. +seealso: +- module: cisco.aci.aci_access_span_filter_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:FilterEntry). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_group_entry: + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING + + +def get_port_value(port): + return FILTER_PORT_MAPPING.get(port, port) if port else "unspecified" + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str"), # Not required for querying all objects + source_ip=dict(type="str"), # Not required for querying all objects + destination_ip=dict(type="str"), # Not required for querying all objects + first_src_port=dict(type="str"), + last_src_port=dict(type="str"), + first_dest_port=dict(type="str"), + last_dest_port=dict(type="str"), + ip_protocol=dict(type="str", choices=VALID_IP_PROTOCOLS), + 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", ["filter_group", "source_ip", "destination_ip"]], + ["state", "present", ["filter_group", "source_ip", "destination_ip"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_group") + source_ip = module.params.get("source_ip") + destination_ip = module.params.get("destination_ip") + first_src_port = module.params.get("first_src_port") + last_src_port = module.params.get("last_src_port") + first_dest_port = module.params.get("first_dest_port") + last_dest_port = module.params.get("last_dest_port") + ip_protocol = module.params.get("ip_protocol") + 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="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + subclass_2=dict( + aci_class="spanFilterEntry", + aci_rn="proto-{0}-src-[{1}]-dst-[{2}]-srcPortFrom-{3}-srcPortTo-{4}-dstPortFrom-{5}-dstPortTo-{6}".format( + ip_protocol if module.params.get("ip_protocol") else "unspecified", + source_ip, + destination_ip, + get_port_value(first_src_port), + get_port_value(last_src_port), + get_port_value(first_dest_port), + get_port_value(last_dest_port), + ), + target_filter={ + "dstAddr": destination_ip, + "dstPortFrom": first_dest_port, + "dstPortTo": last_dest_port, + "ipProto": ip_protocol, + "srcAddr": source_ip, + "srcPortFrom": first_src_port, + "srcPortTo": last_src_port, + }, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="spanFilterEntry", + class_config=dict( + dstAddr=destination_ip, + dstPortFrom=first_dest_port, + dstPortTo=last_dest_port, + ipProto=ip_protocol, + nameAlias=name_alias, + srcAddr=source_ip, + srcPortFrom=first_src_port, + srcPortTo=last_src_port, + ), + ) + + aci.get_diff(aci_class="spanFilterEntry") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_filter_entry.py b/plugins/modules/aci_filter_entry.py index c029442b9..50587e198 100644 --- a/plugins/modules/aci_filter_entry.py +++ b/plugins/modules/aci_filter_entry.py @@ -271,6 +271,8 @@ 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 VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING + VALID_ARP_FLAGS = ["arp_reply", "arp_request", "unspecified"] VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"] @@ -285,11 +287,10 @@ "time_exceeded", "unspecified", ] -VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"] # mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified=None) -FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "20": "ftpData", "53": "dns", "110": "pop3", "554": "rtsp"} + ICMP_MAPPING = { "dst_unreachable": "dst-unreach", "echo": "echo", diff --git a/tests/integration/targets/aci_access_span_filter_group/aliases b/tests/integration/targets/aci_access_span_filter_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml new file mode 100644 index 000000000..c071f747a --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml @@ -0,0 +1,154 @@ +# 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") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: + - query_cloud.current == [] + block: + + # CLEAN TEST ENVIRONMENT + + - name: Query all access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + # TEST CREATE FILTER GROUPS + + - name: Add access span filter group ( check mode ) + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_1 + <<: *aci_info + filter_group: ansible_group_1 + state: present + check_mode: true + register: cm_add_ansible_group_1 + + - name: Add access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + register: nm_add_ansible_group_1 + + - name: Add access span filter group again + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + register: nm_add_ansible_group_1_again + + - name: Verify add access span filter group + 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.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1 is changed + - nm_add_ansible_group_1.previous == [] + - nm_add_ansible_group_1.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again is not changed + - nm_add_ansible_group_1_again.previous.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + + - name: Add two more access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: present + loop: + - ansible_group_2 + - ansible_group_3 + + # TEST QUERY FILTER GROUPS + + - name: Query access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + state: query + register: query_one + + - name: Query all access span filter group ( class query ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_all + + - name: Verify access span filter group queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - query_all is not changed + - query_all.current | length == 3 + - query_all.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - query_all.current.1.spanFilterGrp.attributes.name == "ansible_group_2" + - query_all.current.2.spanFilterGrp.attributes.name == "ansible_group_3" + + # TEST REMOVAL FILTER GROUPS + + - name: Remove access span filter group ( check mode ) + cisco.aci.aci_access_span_filter_group: &remove_ansible_filter_group_1 + <<: *add_ansible_filter_group_1 + state: absent + check_mode: true + register: cm_remove_ansible_filter_group_1 + + - name: Remove access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *remove_ansible_filter_group_1 + register: nm_remove_ansible_filter_group_1 + + - name: Remove access span filter group again + cisco.aci.aci_access_span_filter_group: + <<: *remove_ansible_filter_group_1 + register: nm_remove_ansible_filter_group_1_again + + - name: Verify access span filter group removal + ansible.builtin.assert: + that: + - cm_remove_ansible_filter_group_1 is changed + - cm_remove_ansible_filter_group_1.proposed == {} + - nm_remove_ansible_filter_group_1 is changed + - nm_remove_ansible_filter_group_1.previous.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_remove_ansible_filter_group_1.current == [] + - nm_remove_ansible_filter_group_1_again is not changed + - nm_remove_ansible_filter_group_1_again.previous == [] + - nm_remove_ansible_filter_group_1_again.current == [] + + # CLEAN TEST ENVIRONMENT + + - name: Clean created access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: absent + loop: + - ansible_group_2 + - ansible_group_3 \ No newline at end of file diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/aliases b/tests/integration/targets/aci_access_span_filter_group_entry/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group_entry/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml new file mode 100644 index 000000000..788658f13 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml @@ -0,0 +1,274 @@ +# 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") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: + - query_cloud.current == [] + block: + + # CLEAN TEST ENVIRONMENT + + - name: Query all access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + - name: Add access span filter group 1 + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_1 + <<: *aci_info + filter_group: ansible_group_1 + state: present + + - name: Add access span filter group 2 + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_2 + <<: *aci_info + filter_group: ansible_group_2 + state: present + + # TEST CREATE FILTER GROUP ENTRIES + + - name: Add access span filter group entry 1 to ansible_group_1 ( checkmode ) + cisco.aci.aci_access_span_filter_group_entry: &add_ansible_filter_group_entry_1 + <<: *add_ansible_filter_group_1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + first_src_port: http + last_src_port: https + check_mode: true + register: cm_add_ansible_filter_group_entry_1 + + - name: Add access span filter group entry 1 to ansible_group_1 + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_entry_1 + register: nm_add_ansible_filter_group_entry_1 + + - name: Add access span filter group entry 1 to ansible_group_1 again + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_entry_1 + register: nm_add_ansible_filter_group_entry_1_again + + - name: Add access span filter group entry 2 to ansible_group_1 with ports as digits + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + source_ip: 1.1.1.1 + destination_ip: 3.3.3.3 + first_src_port: http + last_src_port: https + first_dest_port: 80 + last_dest_port: 443 + ip_protocol: tcp + register: nm_add_ansible_filter_group_entry_2 + + - name: Add access span filter group entry 3 to ansible_group_1 + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + source_ip: 2.2.2.2 + destination_ip: 5.5.5.5 + register: nm_add_ansible_filter_group_entry_3 + + - name: Add access span filter group entry 4 to ansible_group_2 + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_2 + source_ip: 2.2.2.2 + destination_ip: 5.5.5.5 + register: nm_add_ansible_filter_group_entry_4 + + - name: Verify add access span filter group entires + ansible.builtin.assert: + that: + - cm_add_ansible_filter_group_entry_1 is changed + - cm_add_ansible_filter_group_entry_1.previous == [] + - cm_add_ansible_filter_group_entry_1.current == [] + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcPortFrom == "http" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1 is changed + - nm_add_ansible_filter_group_entry_1.previous == [] + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_1_again is not changed + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_2 is changed + - nm_add_ansible_filter_group_entry_2.previous == [] + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstAddr == "3.3.3.3" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstPortFrom == "http" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstPortTo == "https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.ipProto == "tcp" + - nm_add_ansible_filter_group_entry_3 is changed + - nm_add_ansible_filter_group_entry_3.previous == [] + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[2.2.2.2]-dst-[5.5.5.5]-srcPortFrom-unspecified-srcPortTo-unspecified-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstAddr == "5.5.5.5" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_4 is changed + - nm_add_ansible_filter_group_entry_4.previous == [] + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_2/proto-unspecified-src-[2.2.2.2]-dst-[5.5.5.5]-srcPortFrom-unspecified-srcPortTo-unspecified-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstAddr == "5.5.5.5" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + + # TEST QUERY FILTER GROUP ENTRIES + + - name: Query one specific access span filter group entry + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + first_src_port: http + last_src_port: https + state: query + register: query_one + + - name: Query all access span filter group entries of one entire group + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + state: query + register: query_one_group + + - name: Query all access span filter group entries ( class query ) + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + state: query + register: query_all + + - name: Query access span filter group entries that match source_ip and first_dest_port + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + first_src_port: http + state: query + register: query_match + + - name: Query access span filter group entries that match source_ip and first_dest_port with ports as digits + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + first_src_port: 80 + last_src_port: 443 + state: query + register: query_match_port_number + + - name: Verify access span filter group queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_one_group is not changed + - query_one_group.current.0.spanFilterGrp.children | length == 3 + - query_all is not changed + - query_all.current | length == 4 + - query_match is not changed + - query_match.current | length == 2 + - query_match.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_match.current.1.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + - query_match_port_number is not changed + - query_match_port_number.current | length == 2 + - query_match_port_number.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_match_port_number.current.1.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + + # TEST REMOVAL FILTER GROUP ENTRIES + + - name: Remove access span filter group ( checkmode ) + cisco.aci.aci_access_span_filter_group_entry: &remove_ansible_filter_group_entry_1 + <<: *add_ansible_filter_group_entry_1 + state: absent + check_mode: true + register: cm_remove_ansible_filter_group_1_entry + + - name: Remove access span filter group + cisco.aci.aci_access_span_filter_group_entry: + <<: *remove_ansible_filter_group_entry_1 + register: nm_remove_ansible_filter_group_1_entry + + - name: Remove access span filter group again + cisco.aci.aci_access_span_filter_group_entry: + <<: *remove_ansible_filter_group_entry_1 + register: nm_remove_ansible_filter_group_1_entry_again + + - name: Verify access span filter group entries removal + ansible.builtin.assert: + that: + - cm_remove_ansible_filter_group_1_entry is changed + - cm_remove_ansible_filter_group_1_entry.proposed == {} + - nm_remove_ansible_filter_group_1_entry is changed + - nm_remove_ansible_filter_group_1_entry.current == [] + - nm_remove_ansible_filter_group_1_entry.previous.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_remove_ansible_filter_group_1_entry_again is not changed + - nm_remove_ansible_filter_group_1_entry_again.current == [] + - nm_remove_ansible_filter_group_1_entry_again.previous == [] + + # CLEAN TEST ENVIRONMENT + + - name: Clean created access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: absent + loop: + - ansible_group_1 + - ansible_group_2