diff --git a/plugins/modules/aci_interface_policy_port_channel_member.py b/plugins/modules/aci_interface_policy_port_channel_member.py new file mode 100644 index 000000000..44209b342 --- /dev/null +++ b/plugins/modules/aci_interface_policy_port_channel_member.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Akini Ross (@akinross) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_port_channel_member +short_description: Manage Port Channel Member interface policies (lacp:IfPol) +description: +- Manage Port Channel Member interface policy configuration on Cisco ACI fabrics. +options: + name: + description: + - The name of the Port Channel Member interface policy. + type: str + aliases: [ port_channel_member_interface_policy ] + description: + description: + - The description of the Port Channel Member interface policy. + type: str + priority: + description: + - The priority of the Port Channel Member interface policy. + - The APIC defaults to C(32768) when not provided. + - Accepted values range between C(1) and C(65535). + type: int + transmit_rate: + description: + - The transmit rate of the Port Channel Member interface policy. + - The APIC defaults to C(normal) when not provided. + type: str + choices: [ normal, fast ] + name_alias: + description: + - The alias for the current object. + - This relates to the nameAlias field in ACI. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(lacp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a new Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_port_channel_member_policy + description: Ansible Port Channel Member interface policy + priority: 32700 + transmit_rate: fast + state: present + delegate_to: localhost + +- name: Query a Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_port_channel_member_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all Port Channel Member interface policies + cisco.aci.aci_interface_policy_port_channel_member: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + host: apic + username: admin + password: SomeSecretPassword + name: my_ntp_policy + 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", aliases=["port_channel_member_interface_policy"]), + description=dict(type="str"), + priority=dict(type="int"), + transmit_rate=dict(type="str", choices=["normal", "fast"]), + 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", ["name"]], + ["state", "present", ["name"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + priority = module.params.get("priority") + transmit_rate = module.params.get("transmit_rate") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="lacpIfPol", + aci_rn="infra/lacpifp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="lacpIfPol", + class_config=dict( + name=name, + descr=description, + prio=priority, + txRate=transmit_rate, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="lacpIfPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_interface_policy_port_channel_member/aliases b/tests/integration/targets/aci_interface_policy_port_channel_member/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_interface_policy_port_channel_member/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_interface_policy_port_channel_member/tasks/main.yml b/tests/integration/targets/aci_interface_policy_port_channel_member/tasks/main.yml new file mode 100644 index 000000000..5036d1836 --- /dev/null +++ b/tests/integration/targets/aci_interface_policy_port_channel_member/tasks/main.yml @@ -0,0 +1,225 @@ +# Test code for the ACI modules +# Copyright: (c) 2025, Akini Ross (@akinross) + +# 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 + ansible.builtin.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 + +# SET VARS +- 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("debug") }}' + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +- name: Verify Cloud and Non-Cloud Sites in use. + ansible.builtin.include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for ACI v5+ and non-cloud sites + when: + - version.current.0.topSystem.attributes.version is version('5', '>=') + - query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + + # CLEAN ENVIRONMENT + + - name: Clean existing Port Channel Member interface policies + cisco.aci.aci_interface_policy_port_channel_member: + <<: *aci_info + name: "{{ item }}" + state: absent + loop: + - ansible_port_channel_member_interface_policy_1 + - ansible_port_channel_member_interface_policy_2 + + # CREATE + + - name: Create Port Channel Member interface policy (check_mode) + cisco.aci.aci_interface_policy_port_channel_member: &create_port_channel_member_interface_policy + <<: *aci_info + name: ansible_port_channel_member_interface_policy_1 + check_mode: true + register: cm_create_port_channel_member_interface_policy + + - name: Create Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + <<: *create_port_channel_member_interface_policy + register: nm_create_port_channel_member_interface_policy + + - name: Create Port Channel Member interface policy again + cisco.aci.aci_interface_policy_port_channel_member: + <<: *create_port_channel_member_interface_policy + register: nm_create_port_channel_member_interface_policy_again + + - name: Assert Port Channel Member interface policy creation + ansible.builtin.assert: + that: + - cm_create_port_channel_member_interface_policy is changed + - cm_create_port_channel_member_interface_policy.previous == [] + - cm_create_port_channel_member_interface_policy.current == [] + - cm_create_port_channel_member_interface_policy.proposed.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_create_port_channel_member_interface_policy is changed + - nm_create_port_channel_member_interface_policy.previous == [] + - nm_create_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_create_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.descr == '' + - nm_create_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.prio == '32768' + - nm_create_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.txRate == 'normal' + - nm_create_port_channel_member_interface_policy_again is not changed + - nm_create_port_channel_member_interface_policy_again.previous == nm_create_port_channel_member_interface_policy_again.current + - nm_create_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_create_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.descr == '' + - nm_create_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.prio == '32768' + - nm_create_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.txRate == 'normal' + + # UPDATE + + - name: Update Port Channel Member interface policy (check_mode) + cisco.aci.aci_interface_policy_port_channel_member: &update_port_channel_member_interface_policy + <<: *aci_info + name: ansible_port_channel_member_interface_policy_1 + description: description_changed + priority: 32700 + transmit_rate: fast + check_mode: true + register: cm_update_port_channel_member_interface_policy + + - name: Update Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + <<: *update_port_channel_member_interface_policy + register: nm_update_port_channel_member_interface_policy + + - name: Update Port Channel Member interface policy again + cisco.aci.aci_interface_policy_port_channel_member: + <<: *update_port_channel_member_interface_policy + register: nm_update_port_channel_member_interface_policy_again + + - name: Assert Port Channel Member interface policy update + ansible.builtin.assert: + that: + - cm_update_port_channel_member_interface_policy is changed + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.descr == '' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.prio == '32768' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.txRate == 'normal' + - cm_update_port_channel_member_interface_policy.proposed.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_update_port_channel_member_interface_policy.proposed.lacpIfPol.attributes.descr == 'description_changed' + - cm_update_port_channel_member_interface_policy.proposed.lacpIfPol.attributes.prio == '32700' + - cm_update_port_channel_member_interface_policy.proposed.lacpIfPol.attributes.txRate == 'fast' + - cm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.descr == '' + - cm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.prio == '32768' + - cm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.txRate == 'normal' + - nm_update_port_channel_member_interface_policy is changed + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.descr == '' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.prio == '32768' + - cm_update_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.txRate == 'normal' + - nm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.descr == 'description_changed' + - nm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.prio == '32700' + - nm_update_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.txRate == 'fast' + - nm_update_port_channel_member_interface_policy_again is not changed + - nm_update_port_channel_member_interface_policy_again.previous == nm_update_port_channel_member_interface_policy_again.current + - nm_update_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_update_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.descr == 'description_changed' + - nm_update_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.prio == '32700' + - nm_update_port_channel_member_interface_policy_again.current.0.lacpIfPol.attributes.txRate == 'fast' + + # QUERY + + - name: Create another Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + <<: *create_port_channel_member_interface_policy + name: ansible_port_channel_member_interface_policy_2 + + - name: Query Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + <<: *create_port_channel_member_interface_policy + state: query + register: query_one + + - name: Query all Port Channel Member interface policies + cisco.aci.aci_interface_policy_port_channel_member: + <<: *aci_info + state: query + register: query_all + + - name: Assert Port Channel Member interface policy query + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - query_one.current.0.lacpIfPol.attributes.descr == 'description_changed' + - query_one.current.0.lacpIfPol.attributes.prio == '32700' + - query_one.current.0.lacpIfPol.attributes.txRate == 'fast' + - query_all is not changed + - query_all.current | length >= 2 + - "'ansible_port_channel_member_interface_policy_1' in query_all.current | map(attribute='lacpIfPol.attributes.name') | list" + - "'ansible_port_channel_member_interface_policy_2' in query_all.current | map(attribute='lacpIfPol.attributes.name') | list" + + # DELETE + + - name: Delete Port Channel Member interface policy (check_mode) + cisco.aci.aci_interface_policy_port_channel_member: &delete_port_channel_member_interface_policy + <<: *update_port_channel_member_interface_policy + state: absent + register: cm_delete_port_channel_member_interface_policy + check_mode: true + + - name: Delete Port Channel Member interface policy + cisco.aci.aci_interface_policy_port_channel_member: + <<: *delete_port_channel_member_interface_policy + register: nm_delete_port_channel_member_interface_policy + + - name: Delete Port Channel Member interface policy again + cisco.aci.aci_interface_policy_port_channel_member: + <<: *delete_port_channel_member_interface_policy + register: nm_delete_port_channel_member_interface_policy_again + + - name: Assert Port Channel Member interface policy deletion + ansible.builtin.assert: + that: + - cm_delete_port_channel_member_interface_policy is changed + - cm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.descr == 'description_changed' + - cm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.prio == '32700' + - cm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.txRate == 'fast' + - cm_delete_port_channel_member_interface_policy.proposed == {} + - cm_delete_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - cm_delete_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.descr == 'description_changed' + - cm_delete_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.prio == '32700' + - cm_delete_port_channel_member_interface_policy.current.0.lacpIfPol.attributes.txRate == 'fast' + - nm_delete_port_channel_member_interface_policy is changed + - nm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.name == 'ansible_port_channel_member_interface_policy_1' + - nm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.descr == 'description_changed' + - nm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.prio == '32700' + - nm_delete_port_channel_member_interface_policy.previous.0.lacpIfPol.attributes.txRate == 'fast' + - nm_delete_port_channel_member_interface_policy.current == [] + - nm_delete_port_channel_member_interface_policy_again is not changed + - nm_delete_port_channel_member_interface_policy_again.previous == [] + - nm_delete_port_channel_member_interface_policy_again.current == [] + + # CLEAN ENVIRONMENT + + - name: Clean remaining Port Channel Member interface policies + cisco.aci.aci_interface_policy_port_channel_member: + <<: *aci_info + name: ansible_port_channel_member_interface_policy_2 + state: absent +