diff --git a/plugins/modules/aci_igmp_interface_policy.py b/plugins/modules/aci_igmp_interface_policy.py new file mode 100644 index 000000000..7f537871a --- /dev/null +++ b/plugins/modules/aci_igmp_interface_policy.py @@ -0,0 +1,383 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# 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_igmp_interface_policy +short_description: Manage IGMP Interface Policies (igmp:IfPol) +description: +- Manage IGMP Interface Policies on Cisco ACI fabrics. +options: + name: + description: + - The name of the IGMP Interface Policy. + type: str + tenant: + description: + - The tenant to build the IGMP Interface Policy under. + type: str + description: + description: + - The description of the IGMP Interface Policy. + type: str + group_timeout: + description: + - The IGMP group timeout in seconds. + - The APIC defaults to 260 when unset during creation. + type: int + query_interval: + description: + - The IGMP query interval in seconds. + - The APIC defaults to 125 when unset during creation. + type: int + query_response_interval: + description: + - The IGMP query response interval in seconds. + - The APIC defaults to 10 when unset during creation. + type: int + last_member_count: + description: + - The last member query count. + - The APIC defaults to 2 when unset during creation. + type: int + last_member_response: + description: + - The last member response time in seconds. + - The APIC defaults to 1 when unset during creation. + type: int + startup_query_count: + description: + - The Startup Query Count. + - The APIC defaults to 2 when unset during creation. + type: int + startup_query_interval: + description: + - The startup query interval in seconds. + - The APIC defaults to 31 when unset during creation. + type: int + querier_timeout: + description: + - The querier timeout in seconds. + - The APIC defaults to 255 when unset during creation. + type: int + robustness_variable: + description: + - The robustness factor. + - The APIC defaults to 2 when unset during creation. + type: int + igmp_version: + description: + - The IGMP version to run. + - The APIC defaults to v2 when unset during creation. + type: str + choices: [ v2, v3 ] + allow_v3_asm: + description: + - Enable the Allow v3 ASM option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, fast_leave and report_link_local_groups must also be set. + type: bool + fast_leave: + description: + - Enable the Fast Leave option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, allow_v3_asm and report_link_local_groups must also be set. + type: bool + report_link_local_groups: + description: + - Enable the Report Link Local Groups option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, allow_v3_asm and fast_leave must also be set. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(igmp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + query_interval: 200 + state: present + delegate_to: localhost + +- name: Query an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + state: query + delegate_to: localhost + +- name: Query all IGMP Interface Policies + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + name=dict(type="str"), + tenant=dict(type="str"), + description=dict(type="str"), + group_timeout=dict(type="int"), + query_interval=dict(type="int"), + query_response_interval=dict(type="int"), + last_member_count=dict(type="int"), + last_member_response=dict(type="int"), + startup_query_count=dict(type="int"), + startup_query_interval=dict(type="int"), + querier_timeout=dict(type="int"), + robustness_variable=dict(type="int"), + igmp_version=dict(type="str", choices=["v2", "v3"]), + allow_v3_asm=dict(type="bool"), + fast_leave=dict(type="bool"), + report_link_local_groups=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["name"]], + ["state", "absent", ["name"]], + ], + required_together=[ + ["allow_v3_asm", "fast_leave", "report_link_local_groups"], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + tenant = module.params.get("tenant") + description = module.params.get("description") + group_timeout = module.params.get("group_timeout") + query_interval = module.params.get("query_interval") + query_response_interval = module.params.get("query_response_interval") + last_member_count = module.params.get("last_member_count") + last_member_response = module.params.get("last_member_response") + startup_query_count = module.params.get("startup_query_count") + startup_query_interval = module.params.get("startup_query_interval") + querier_timeout = module.params.get("querier_timeout") + robustness_variable = module.params.get("robustness_variable") + igmp_version = module.params.get("igmp_version") + allow_v3_asm = module.params.get("allow_v3_asm") + fast_leave = module.params.get("fast_leave") + report_link_local_groups = module.params.get("report_link_local_groups") + state = module.params.get("state") + + 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="igmpIfPol", + aci_rn="igmpIfPol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + if allow_v3_asm is not None: + if_ctrl_list = [] + if allow_v3_asm: + if_ctrl_list.append("allow-v3-asm") + if fast_leave: + if_ctrl_list.append("fast-leave") + if report_link_local_groups: + if_ctrl_list.append("rep-ll") + if_ctrl = ",".join(if_ctrl_list) + else: + if_ctrl = None + + aci.payload( + aci_class="igmpIfPol", + class_config=dict( + name=name, + descr=description, + grpTimeout=group_timeout, + ifCtrl=if_ctrl, + lastMbrCnt=last_member_count, + lastMbrRespTime=last_member_response, + querierTimeout=querier_timeout, + queryIntvl=query_interval, + robustFac=robustness_variable, + rspIntvl=query_response_interval, + startQueryCnt=startup_query_count, + startQueryIntvl=startup_query_interval, + ver=igmp_version, + ), + ) + + aci.get_diff(aci_class="igmpIfPol") + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_igmp_interface_policy/aliases b/tests/integration/targets/aci_igmp_interface_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_igmp_interface_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_igmp_interface_policy/tasks/main.yml b/tests/integration/targets/aci_igmp_interface_policy/tasks/main.yml new file mode 100644 index 000000000..eac9daf6b --- /dev/null +++ b/tests/integration/targets/aci_igmp_interface_policy/tasks/main.yml @@ -0,0 +1,239 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + port: "{{ aci_port | default(omit) }}" + 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 the ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE IGMP INTERFACE POLICY +- name: Create the ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +- name: Create an IGMP interface policy (check mode) + cisco.aci.aci_igmp_interface_policy: &aci_igmp + <<: *aci_info + name: ansible_igmp_intf_policy + tenant: ansible_tenant + description: Test IGMP Interface Policy + group_timeout: 300 + query_interval: 100 + query_response_interval: 8 + last_member_count: 3 + last_member_response: 5 + startup_query_count: 3 + startup_query_interval: 5 + querier_timeout: 200 + robustness_variable: 3 + igmp_version: v3 + allow_v3_asm: true + fast_leave: true + report_link_local_groups: false + check_mode: yes + register: cm_create_igmp_intf_policy + +- name: Create an IGMP interface policy + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + register: nm_create_igmp_intf_policy + +- name: Create IGMP interface policy again to test idempotence + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + register: nm_create_igmp_intf_policy_again + +- name: Create IGMP interface policy with default parameters + cisco.aci.aci_igmp_interface_policy: + <<: *aci_info + name: ansible_igmp_dflt_intf_policy + tenant: ansible_tenant + register: default_igmp_intf_policy + +- name: Verify creation of IGMP interface policy + ansible.builtin.assert: + that: + - cm_create_igmp_intf_policy is changed + - nm_create_igmp_intf_policy is changed + - nm_create_igmp_intf_policy_again is not changed + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.dn == "uni/tn-ansible_tenant/igmpIfPol-ansible_igmp_intf_policy" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.descr == "Test IGMP Interface Policy" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.name == "ansible_igmp_intf_policy" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.grpTimeout == "300" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.queryIntvl == "100" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.rspIntvl == "8" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrCnt == "3" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrRespTime == "5" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryCnt == "3" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryIntvl == "5" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.querierTimeout == "200" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.robustFac == "3" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.ver == "v3" + - nm_create_igmp_intf_policy.current.0.igmpIfPol.attributes.ifCtrl == "allow-v3-asm,fast-leave" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.dn == "uni/tn-ansible_tenant/igmpIfPol-ansible_igmp_intf_policy" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.descr == "Test IGMP Interface Policy" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.name == "ansible_igmp_intf_policy" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.grpTimeout == "300" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.queryIntvl == "100" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.rspIntvl == "8" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.lastMbrCnt == "3" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.lastMbrRespTime == "5" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.startQueryCnt == "3" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.startQueryIntvl == "5" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.querierTimeout == "200" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.robustFac == "3" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.ver == "v3" + - nm_create_igmp_intf_policy_again.current.0.igmpIfPol.attributes.ifCtrl == "allow-v3-asm,fast-leave" + +- name: Verify default values + ansible.builtin.assert: + that: + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.grpTimeout == "260" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.queryIntvl == "125" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.rspIntvl == "10" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrCnt == "2" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrRespTime == "1" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryCnt == "2" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryIntvl == "31" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.querierTimeout == "255" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.robustFac == "2" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.ver == "v2" + - default_igmp_intf_policy.current.0.igmpIfPol.attributes.ifCtrl == "" + +# UPDATE IGMP INTERFACE POLICY +- name: Update IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + description: Updated IGMP Interface Policy + group_timeout: 250 + query_interval: 150 + query_response_interval: 7 + last_member_count: 4 + last_member_response: 6 + startup_query_count: 4 + startup_query_interval: 6 + querier_timeout: 180 + robustness_variable: 5 + igmp_version: v2 + allow_v3_asm: false + fast_leave: false + report_link_local_groups: true + register: update_igmp_intf_policy + +- name: Update IGMP Interface Policy without ifCtrl options + cisco.aci.aci_igmp_interface_policy: + <<: *aci_info + name: ansible_igmp_intf_policy + tenant: ansible_tenant + igmp_version: v3 + register: update_igmp_no_ifctrl + +- name: Verify update of IGMP Interface Policy + ansible.builtin.assert: + that: + - update_igmp_intf_policy is changed + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.dn == "uni/tn-ansible_tenant/igmpIfPol-ansible_igmp_intf_policy" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.descr == "Updated IGMP Interface Policy" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.name == "ansible_igmp_intf_policy" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.grpTimeout == "250" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.queryIntvl == "150" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.rspIntvl == "7" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrCnt == "4" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.lastMbrRespTime == "6" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryCnt == "4" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.startQueryIntvl == "6" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.querierTimeout == "180" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.robustFac == "5" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.ver == "v2" + - update_igmp_intf_policy.current.0.igmpIfPol.attributes.ifCtrl == "rep-ll" + +- name: Verify an update without ifCtrl options present leaves existing ifCtrl in place + ansible.builtin.assert: + that: + - update_igmp_no_ifctrl is changed + - update_igmp_no_ifctrl.current.0.igmpIfPol.attributes.ver == "v3" + - update_igmp_no_ifctrl.current.0.igmpIfPol.attributes.ifCtrl == "rep-ll" + +# QUERY IGMP INTERFACE POLICY +- name: Query an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + state: query + register: query_one + +- name: Query all IGMP Interface Policies + cisco.aci.aci_igmp_interface_policy: + <<: *aci_info + state: query + register: query_all + +- name: Verify IGMP Interface Policy queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current.0.igmpIfPol.attributes.dn == "uni/tn-ansible_tenant/igmpIfPol-ansible_igmp_intf_policy" + - query_one.current.0.igmpIfPol.attributes.descr == "Updated IGMP Interface Policy" + - query_one.current.0.igmpIfPol.attributes.name == "ansible_igmp_intf_policy" + - query_one.current.0.igmpIfPol.attributes.grpTimeout == "250" + - query_one.current.0.igmpIfPol.attributes.queryIntvl == "150" + - query_one.current.0.igmpIfPol.attributes.rspIntvl == "7" + - query_one.current.0.igmpIfPol.attributes.lastMbrCnt == "4" + - query_one.current.0.igmpIfPol.attributes.lastMbrRespTime == "6" + - query_one.current.0.igmpIfPol.attributes.startQueryCnt == "4" + - query_one.current.0.igmpIfPol.attributes.startQueryIntvl == "6" + - query_one.current.0.igmpIfPol.attributes.querierTimeout == "180" + - query_one.current.0.igmpIfPol.attributes.robustFac == "5" + - query_one.current.0.igmpIfPol.attributes.ver == "v3" + - query_one.current.0.igmpIfPol.attributes.ifCtrl == "rep-ll" + - query_all is not changed + - query_all.current | length > 1 + +# REMOVE IGMP INTERFACE POLICY +- name: Delete an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + state: absent + register: delete + +- name: Delete IGMP Interface Policy again + cisco.aci.aci_igmp_interface_policy: + <<: *aci_igmp + state: absent + register: delete_again + +- name: Verify deletion of IGMP Interface Policy + ansible.builtin.assert: + that: + - delete is changed + - delete_again is not changed + - delete.current == [] + +# CLEAN UP +- name: Remove the ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent \ No newline at end of file