diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 21740e9bb..929b04a62 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -162,9 +162,9 @@ def aci_owner_spec(): ) -def enhanced_lag_spec(): +def enhanced_lag_spec(name_is_required=True): return dict( - name=dict(type="str", required=True), + name=dict(type="str", required=name_is_required), lacp_mode=dict(type="str", choices=["active", "passive"]), load_balancing_mode=dict( type="str", diff --git a/plugins/modules/aci_vmm_enhanced_lag_policy.py b/plugins/modules/aci_vmm_enhanced_lag_policy.py new file mode 100644 index 000000000..7ea30c97a --- /dev/null +++ b/plugins/modules/aci_vmm_enhanced_lag_policy.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, Dev Sinha (@DevSinha13) +# 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_vmm_enhanced_lag_policy +short_description: Manage Enhanced LACP Policy for Virtual Machine Manager (VMM) in Cisco ACI (lacp:EnhancedLagPol) +description: +- Manage Enhanced LACP Policy for VMM domains on Cisco ACI fabrics. +- The Enhanced LACP Policy allows you to configure advanced Link Aggregation Control Protocol settings for virtual switches in VMM domains. + +options: + name: + description: + - The name of the Enhanced LACP Policy. + type: str + domain: + description: + - The name of the virtual domain profile where the Enhanced LACP Policy is applied. + type: str + aliases: [ domain_name, domain_profile ] + vm_provider: + description: + - The virtualization platform provider for the VMM domain. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] + lacp_mode: + description: + - The LACP mode for the policy. + - Determines whether the policy initiates or responds to LACP negotiations. + - The APIC defaults to C(active) when unset during creation. + type: str + choices: [ active, passive ] + load_balancing_mode: + description: + - The load balancing algorithm for distributing traffic across links in the port channel. + - See the APIC Management Information Model reference for more details. + - The APIC defaults to C(src-dst-ip) when unset during creation + type: str + choices: + - dst-ip + - dst-ip-l4port + - dst-ip-vlan + - dst-ip-l4port-vlan + - dst-mac + - dst-l4port + - src-ip + - src-ip-l4port + - src-ip-vlan + - src-ip-l4port-vlan + - src-mac + - src-l4port + - src-dst-ip + - src-dst-ip-l4port + - src-dst-ip-vlan + - src-dst-ip-l4port-vlan + - src-dst-mac + - src-dst-l4port + - src-port-id + - vlan + number_uplinks: + description: + - The minimum number of uplinks required for the port channel. + - Must be a value between 2 and 8. + - The APIC defaults to 2 when unset during creation + type: int + state: + description: + - The desired state of the Enhanced LACP Policy. + - Use C(present) to create or update the policy. + - Use C(absent) to delete the policy. + - Use C(query) to retrieve information about the policy. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(vmm_domain) and I(vSwitch_policy) must exist before using this module in a playbook. +- The modules M(cisco.aci.aci_domain) and M(cisco.aci.aci_vmm_vswitch_policy) can be used for this. +seealso: +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vmm_vswitch_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(lacp:EnhancedLagPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dev Sinha (@DevSinha13) +""" + +EXAMPLES = r""" +- name: Create an Enhanced LACP Policy + cisco.aci.aci_vmm_enhanced_lag_policy: + host: apic.example.com + username: admin + password: SomeSecretPassword + name: my_enhanced_lag_policy + domain: my_vmm_domain + vm_provider: vmware + lacp_mode: active + load_balancing_mode: src-dst-ip + number_uplinks: 4 + state: present + +- name: Update an existing Enhanced LACP Policy + cisco.aci.aci_vmm_enhanced_lag_policy: + host: apic.example.com + username: admin + password: SomeSecretPassword + name: my_enhanced_lag_policy + domain: my_vmm_domain + vm_provider: vmware + lacp_mode: passive + load_balancing_mode: src-dst-ip-l4port + number_uplinks: 6 + state: present + +- name: Query a specific Enhanced LACP Policy + cisco.aci.aci_vmm_enhanced_lag_policy: + host: apic.example.com + username: admin + password: SomeSecretPassword + name: my_enhanced_lag_policy + domain: my_vmm_domain + vm_provider: vmware + state: query + register: query_result + +- name: Query all Enhanced LACP Policies in a VMM domain + cisco.aci.aci_vmm_enhanced_lag_policy: + host: apic.example.com + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + vm_provider: vmware + state: query + register: query_all_result + +- name: Delete an Enhanced LACP Policy + cisco.aci.aci_vmm_enhanced_lag_policy: + host: apic.example.com + username: admin + password: SomeSecretPassword + name: my_enhanced_lag_policy + domain: my_vmm_domain + vm_provider: vmware + state: absent +""" +RETURN = r""" +current: + description: The existing configuration of the Enhanced LACP Policy from the APIC after the module has finished. + returned: success + type: list + sample: + [ + { + "lacpEnhancedLagPol": { + "attributes": { + "name": "test_enhanced_lag_policy", + "mode": "active", + "lbmode": "src-dst-ip", + "numLinks": "4", + "dn": "uni/vmmp-VMware/dom-test_vmm_dom/vswitchpolcont/enlacplagp-test_enhanced_lag_policy" + } + } + } + ] +error: + description: The error information as returned from the APIC. + returned: failure + type: dict + sample: + { + "code": "801", + "text": "property name of enlacplagp-test_enhanced_lag_policy failed validation" + } +proposed: + description: The configuration sent to the APIC. + returned: info + type: dict + sample: + { + "lacpEnhancedLagPol": { + "attributes": { + "name": "test_enhanced_lag_policy", + "mode": "active", + "lbmode": "src-dst-ip", + "numLinks": "4" + } + } + } +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + ACIModule, + aci_argument_spec, + enhanced_lag_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + aci_annotation_spec, + aci_owner_spec, +) + +from ansible_collections.cisco.aci.plugins.module_utils.constants import ( + VM_PROVIDER_MAPPING, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update(enhanced_lag_spec(name_is_required=False)) + argument_spec.update( + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING)), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "domain", "vm_provider"]], + ["state", "present", ["name", "domain", "vm_provider"]], + ], + ) + + name = module.params.get("name") + lacp_mode = module.params.get("lacp_mode") + load_balancing_mode = module.params.get("load_balancing_mode") + number_uplinks = module.params.get("number_uplinks") + domain = module.params.get("domain") + state = module.params.get("state") + vm_provider = module.params.get("vm_provider") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="vmmProvP", + aci_rn="vmmp-{0}".format(VM_PROVIDER_MAPPING.get(vm_provider)), + module_object=vm_provider, + target_filter={"vendor": vm_provider}, + ), + subclass_1=dict( + aci_class="vmmDomP", + aci_rn="dom-{0}".format(domain), + module_object=domain, + target_filter={"name": domain}, + ), + subclass_2=dict( + aci_class="vmmVSwitchPolicyCont", + aci_rn="vswitchpolcont", + module_object="vswitchpolcont", + ), + subclass_3=dict( + aci_class="lacpEnhancedLagPol", + aci_rn="enlacplagp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="lacpEnhancedLagPol", + class_config=dict( + name=name, + mode=lacp_mode, + lbmode=load_balancing_mode, + numLinks=number_uplinks, + ), + ) + + aci.get_diff(aci_class="lacpEnhancedLagPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_vmm_enhanced_lag_policy/aliases b/tests/integration/targets/aci_vmm_enhanced_lag_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_vmm_enhanced_lag_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_vmm_enhanced_lag_policy/tasks/main.yml b/tests/integration/targets/aci_vmm_enhanced_lag_policy/tasks/main.yml new file mode 100644 index 000000000..25fb5a858 --- /dev/null +++ b/tests/integration/targets/aci_vmm_enhanced_lag_policy/tasks/main.yml @@ -0,0 +1,238 @@ +# Test code for the ACI modules +# Copyright: (c) 2025, Dev Sinha (@DevSinha13) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +# CLEAN ENVIRONMENT +- 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 + +# Remove VMM domain +- name: Remove VMM domain (normal mode) + cisco.aci.aci_domain: + <<: *aci_info + domain: test_vmm_dom + domain_type: vmm + vm_provider: vmware + state: absent + +- 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 non-cloud sites + when: query_cloud.current == [] # This condition will skip execution for cloud sites + block: + # ADD DOMAIN + - name: Add VMM domain + cisco.aci.aci_domain: + <<: *aci_info + domain: test_vmm_dom + domain_type: vmm + vm_provider: vmware + state: present + + - name: Add a vSwitch policy to vmware domain + cisco.aci.aci_vmm_vswitch_policy: &add_vmware_policies + <<: *aci_info + domain: test_vmm_dom + vm_provider: vmware + lldp_policy: LLDP_policy + cdp_policy: CDP_policy + port_channel_policy: PORT_Channel_policy + state: present + + - name: Create Enhanced LAG policy in check mode + cisco.aci.aci_vmm_enhanced_lag_policy: &enhanced_lag_policy_info + <<: *aci_info + name: test_lag + domain: test_vmm_dom + vm_provider: vmware + lacp_mode: active + load_balancing_mode: src-dst-ip + number_uplinks: 2 + state: present + check_mode: true + register: create_enhanced_lag_policy_cm + + - name: Verify Enhanced LAG policy creation in check mode + ansible.builtin.assert: + that: + - create_enhanced_lag_policy_cm is changed + - create_enhanced_lag_policy_cm.previous == [] + - create_enhanced_lag_policy_cm.proposed.lacpEnhancedLagPol.attributes.name == "test_lag" + - create_enhanced_lag_policy_cm.proposed.lacpEnhancedLagPol.attributes.mode == "active" + - create_enhanced_lag_policy_cm.proposed.lacpEnhancedLagPol.attributes.lbmode == "src-dst-ip" + - create_enhanced_lag_policy_cm.proposed.lacpEnhancedLagPol.attributes.numLinks == "2" + - create_enhanced_lag_policy_cm.proposed.lacpEnhancedLagPol.attributes.dn == "uni/vmmp-VMware/dom-test_vmm_dom/vswitchpolcont/enlacplagp-test_lag" + + - name: Create enhanced lag policy without check mode + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *enhanced_lag_policy_info + register: create_enhanced_lag_policy + + - name: Verify Enhanced LAG policy creation + ansible.builtin.assert: + that: + - create_enhanced_lag_policy is changed + - create_enhanced_lag_policy.previous == [] + - create_enhanced_lag_policy.current.0.lacpEnhancedLagPol.attributes.name == 'test_lag' + - create_enhanced_lag_policy.current.0.lacpEnhancedLagPol.attributes.mode == 'active' + - create_enhanced_lag_policy.current.0.lacpEnhancedLagPol.attributes.lbmode == 'src-dst-ip' + - create_enhanced_lag_policy.current.0.lacpEnhancedLagPol.attributes.numLinks == '2' + - create_enhanced_lag_policy.current.0.lacpEnhancedLagPol.attributes.dn == 'uni/vmmp-VMware/dom-test_vmm_dom/vswitchpolcont/enlacplagp-test_lag' + + - name: Create Enhanced LAG policy again (idempotency) + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *enhanced_lag_policy_info + register: create_enhanced_lag_policy_again + + - name: Verify Enhanced LAG policy idempotency + ansible.builtin.assert: + that: + - create_enhanced_lag_policy_again is not changed + - create_enhanced_lag_policy_again.current == create_enhanced_lag_policy.current == create_enhanced_lag_policy_again.previous + + - name: Update Enhanced LAG policy with different options + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *aci_info + name: test_lag + domain: test_vmm_dom + vm_provider: vmware + lacp_mode: passive + load_balancing_mode: src-dst-ip-l4port + number_uplinks: 4 + state: present + register: update_enhanced_lag + + - name: Assert Enhanced LAG policy update + ansible.builtin.assert: + that: + - update_enhanced_lag is changed + - update_enhanced_lag.previous.0.lacpEnhancedLagPol.attributes.name == 'test_lag' + - update_enhanced_lag.previous.0.lacpEnhancedLagPol.attributes.mode == 'active' + - update_enhanced_lag.previous.0.lacpEnhancedLagPol.attributes.lbmode == 'src-dst-ip' + - update_enhanced_lag.previous.0.lacpEnhancedLagPol.attributes.numLinks == '2' + - update_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.name == 'test_lag' + - update_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.mode == 'passive' + - update_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.lbmode == 'src-dst-ip-l4port' + - update_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.numLinks == '4' + + - name: Create second Enhanced LAG policy + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *aci_info + name: test_lag2 + domain: test_vmm_dom + vm_provider: vmware + lacp_mode: active + load_balancing_mode: src-ip + number_uplinks: 3 + state: present + register: create_second_enhanced_lag + + - name: Assert second Enhanced LAG policy creation + ansible.builtin.assert: + that: + - create_second_enhanced_lag is changed + - create_second_enhanced_lag.previous == [] + - create_second_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.name == 'test_lag2' + - create_second_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.mode == 'active' + - create_second_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.lbmode == 'src-ip' + - create_second_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.numLinks == '3' + + - name: Query first Enhanced LAG policy + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *aci_info + name: test_lag + domain: test_vmm_dom + vm_provider: vmware + state: query + register: query_first_enhanced_lag + + - name: Verify first Enhanced LAG policy query + ansible.builtin.assert: + that: + - query_first_enhanced_lag is not changed + - query_first_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.name == 'test_lag' + - query_first_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.mode == 'passive' + - query_first_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.lbmode == 'src-dst-ip-l4port' + - query_first_enhanced_lag.current.0.lacpEnhancedLagPol.attributes.numLinks == '4' + + - name: Query all Enhanced LAG policies + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *aci_info + state: query + register: query_all_enhanced_lag + + - name: Verify Query all Lag policy + ansible.builtin.assert: + that: + - query_all_enhanced_lag.current | length >= 2 + - query_all_enhanced_lag is not changed + - "'uni/vmmp-VMware/dom-test_vmm_dom/vswitchpolcont/enlacplagp-test_lag' in query_all_enhanced_lag.current | map(attribute='lacpEnhancedLagPol.attributes.dn') | list" + - "'uni/vmmp-VMware/dom-test_vmm_dom/vswitchpolcont/enlacplagp-test_lag2' in query_all_enhanced_lag.current | map(attribute='lacpEnhancedLagPol.attributes.dn') | list" + + - name: Remove first Enhanced LAG policy in check mode + cisco.aci.aci_vmm_enhanced_lag_policy: &delete_enhanced_lag_policy + <<: *aci_info + name: test_lag + domain: test_vmm_dom + vm_provider: vmware + state: absent + check_mode: true + register: remove_first_enhanced_lag_check + + - name: Verify first Enhanced LAG policy removal in check mode + ansible.builtin.assert: + that: + - remove_first_enhanced_lag_check is changed + - remove_first_enhanced_lag_check.previous.0.lacpEnhancedLagPol.attributes.name == 'test_lag' + - remove_first_enhanced_lag_check.previous.0.lacpEnhancedLagPol.attributes.mode == 'passive' + - remove_first_enhanced_lag_check.previous.0.lacpEnhancedLagPol.attributes.lbmode == 'src-dst-ip-l4port' + - remove_first_enhanced_lag_check.previous.0.lacpEnhancedLagPol.attributes.numLinks == '4' + - remove_first_enhanced_lag_check.proposed == {} + + - name: Remove first Enhanced LAG policy + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *delete_enhanced_lag_policy + register: remove_first_enhanced_lag + + - name: Verify first Enhanced LAG policy removal + ansible.builtin.assert: + that: + - remove_first_enhanced_lag is changed + - remove_first_enhanced_lag.previous == remove_first_enhanced_lag_check.previous + - remove_first_enhanced_lag.current == [] + + - name: Remove first Enhanced LAG policy again (idempotency) + cisco.aci.aci_vmm_enhanced_lag_policy: + <<: *delete_enhanced_lag_policy + register: remove_first_enhanced_lag_again + + - name: Verify first Enhanced LAG policy removal idempotency + ansible.builtin.assert: + that: + - remove_first_enhanced_lag_again is not changed + - remove_first_enhanced_lag_again.current == remove_first_enhanced_lag_again.previous == [] + + - name: Remove VMM domain again (clean slate) + cisco.aci.aci_domain: + <<: *aci_info + domain: test_vmm_dom + domain_type: vmm + vm_provider: vmware + state: absent