From 228cb4f673e5dc939665fbc2d29c9a523f304d50 Mon Sep 17 00:00:00 2001 From: Gaspard Micol Date: Tue, 10 Dec 2024 09:47:19 -0500 Subject: [PATCH] [ignore] Modify Documentation, required attributes in ndo_virtual_port_channel_interface module. enhance format_interface_descriptions function to format range of interface IDs. --- plugins/module_utils/mso.py | 40 +++-- .../ndo_virtual_port_channel_interface.py | 46 +++--- .../tasks/main.yml | 153 ++++++++++++++++-- 3 files changed, 196 insertions(+), 43 deletions(-) diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index f12bad240..27aa66915 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -318,20 +318,38 @@ def write_file(module, url, dest, content, resp, tmpsrc=None): os.remove(tmpsrc) -def format_interface_descriptions(interface_descriptions, node=None): +def format_interface_descriptions(mso, interface_descriptions, node=None): if interface_descriptions: - formated_interface_descriptions = [ - { - "nodeID": node if node is not None else interface_description.get("node"), - "interfaceID": interface_description.get("interface_id", interface_description.get("interfaceID")), - "description": interface_description.get("description"), - } + + def format_range_interfaces(format_dict): + ids = format_dict.get("interfaceID") + if re.fullmatch(r"((\d+/)+\d+$)", ids): + yield format_dict + elif re.fullmatch(r"((\d+/)+\d+-\d+$)", ids): + slots = ids.rsplit("/", 1)[0] + range_start, range_stop = ids.rsplit("/", 1)[1].split("-") + if int(range_stop) > int(range_start): + for x in range(int(range_start), int(range_stop) + 1): + copy_format_dict = deepcopy(format_dict) + copy_format_dict.update(interfaceID="{0}/{1}".format(slots, x)) + yield copy_format_dict + else: + mso.fail_json(msg="Range start is greater than or equal to range stop for range of IDs '{0}'".format(ids)) + else: + mso.fail_json(msg="Incorrect interface ID or range of IDs. Got '{0}'".format(ids)) + + return [ + item for interface_description in interface_descriptions + for item in format_range_interfaces( + { + "nodeID": node if node is not None else interface_description.get("node"), + "interfaceID": interface_description.get("interface_id", interface_description.get("interfaceID")), + "description": interface_description.get("description"), + } + ) ] - else: - formated_interface_descriptions = [] - - return formated_interface_descriptions + return [] class MSOModule(object): diff --git a/plugins/modules/ndo_virtual_port_channel_interface.py b/plugins/modules/ndo_virtual_port_channel_interface.py index 30fa58446..6f6a330c2 100644 --- a/plugins/modules/ndo_virtual_port_channel_interface.py +++ b/plugins/modules/ndo_virtual_port_channel_interface.py @@ -89,10 +89,12 @@ description: - The name of the Interface Policy Group. type: str + required: true template: description: - The name of the template in which the Interface Policy Group has been created. type: str + required: true aliases: [ policy, interface_policy, interface_setting ] interface_descriptions: description: @@ -104,10 +106,13 @@ description: - The node ID. type: str + required: true interface_id: description: - - The interface ID. + - The interface ID or a range of IDs. + - Using a range of interface IDs will apply the same O(description) for every ID in range. type: str + required: true description: description: - The description of the interface. @@ -121,6 +126,14 @@ choices: [ absent, query, present ] default: query extends_documentation_fragment: cisco.mso.modules +notes: +- The O(template) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_template) to create the Fabric Resource template. +- The O(interface_policy_group) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_interface_setting) to create the Interface Policy Group of type Port Channel. +seealso: +- module: cisco.mso.ndo_template +- module: cisco.mso.ndo_interface_setting """ EXAMPLES = r""" @@ -145,16 +158,13 @@ interface_descriptions: - node: 101 interface_id: 1/1 - description: My first Ansible Interface for first node + description: My single Ansible Interface for first node - node: 101 - interface_id: 1/10 - description: My second Ansible Interface for first node - - node: 101 - interface_id: 1/11 - description: My third Ansible Interface for first node + interface_id: 1/10-11 + description: My group of Ansible Interface for first node - node: 102 interface_id: 1/2 - description: My first Ansible Interface for second node + description: My single Ansible Interface for second node state: present register: virtual_port_channel_interface_1 @@ -176,7 +186,7 @@ template: ansible_fabric_resource_template name: ansible_virtual_port_channel_interface_changed state: query - register: query_one + register: query_name - name: Query a Virtual Port Channel Interface with UUID cisco.mso.ndo_virtual_port_channel_interface: @@ -186,7 +196,7 @@ template: ansible_fabric_resource_template uuid: "{{ virtual_port_channel_interface_1.current.uuid }}" state: query - register: query_one_uuid + register: query_uuid - name: Query all Virtual Port Channel Interfaces in a Fabric Resource Template cisco.mso.ndo_virtual_port_channel_interface: @@ -246,8 +256,8 @@ def main(): interface_policy_group=dict( type="dict", options=dict( - name=dict(type="str"), - template=dict(type="str"), + name=dict(type="str", required=True), + template=dict(type="str", required=True), ), aliases=["policy", "interface_policy", "interface_setting"], ), @@ -256,8 +266,8 @@ def main(): type="list", elements="dict", options=dict( - node=dict(type="str"), - interface_id=dict(type="str"), + node=dict(type="str", required=True), + interface_id=dict(type="str", required=True), description=dict(type="str"), ), ), @@ -282,10 +292,10 @@ def main(): node_1 = module.params.get("node_1") node_2 = module.params.get("node_2") interfaces_node_1 = module.params.get("interfaces_node_1") - if interfaces_node_1: + if isinstance(interfaces_node_1, list): interfaces_node_1 = ",".join(interfaces_node_1) interfaces_node_2 = module.params.get("interfaces_node_2") - if interfaces_node_2: + if isinstance(interfaces_node_2, list): interfaces_node_2 = ",".join(interfaces_node_2) interface_policy_group = module.params.get("interface_policy_group") interface_policy_group_uuid = module.params.get("interface_policy_group_uuid") @@ -358,7 +368,7 @@ def main(): proposed_payload["node2Details"]["memberInterfaces"] = interfaces_node_2 if interface_descriptions: - interface_descriptions = format_interface_descriptions(interface_descriptions) + interface_descriptions = format_interface_descriptions(mso, interface_descriptions) if interface_descriptions != mso.existing.get("interfaceDescriptions"): ops.append(dict(op="replace", path="{0}/{1}/interfaceDescriptions".format(path, match.index), value=interface_descriptions)) proposed_payload["interfaceDescriptions"] = interface_descriptions @@ -380,7 +390,7 @@ def main(): }, "policy": interface_policy_group_uuid, "description": description, - "interfaceDescriptions": format_interface_descriptions(interface_descriptions), + "interfaceDescriptions": format_interface_descriptions(mso, interface_descriptions), } mso.sanitize(payload) diff --git a/tests/integration/targets/ndo_virtual_port_channel_interface/tasks/main.yml b/tests/integration/targets/ndo_virtual_port_channel_interface/tasks/main.yml index 73314fc67..6b5b2b1fc 100644 --- a/tests/integration/targets/ndo_virtual_port_channel_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_virtual_port_channel_interface/tasks/main.yml @@ -58,6 +58,29 @@ <<: *template_absent state: present + - name: Ensure fabric policy template does not exist + cisco.mso.ndo_template: &template_policy_absent + <<: *mso_info + name: ansible_fabric_policy_template + template_type: fabric_policy + state: absent + + - name: Create fabric resource template + cisco.mso.ndo_template: + <<: *template_policy_absent + state: present + + - name: Create two Interface policy groups of type port channel + cisco.mso.ndo_interface_setting: + <<: *mso_info + template: ansible_fabric_policy_template + name: "{{ item }}" + interface_type: port_channel + state: present + loop: + - ansible_test_interface_policy_group_port_channel + - ansible_test_interface_policy_group_port_channel_2 + # CREATE - name: Create a new virtual port channel interface (check_mode) @@ -71,8 +94,8 @@ interfaces_1: 1/1 interfaces_2: 1/1 interface_policy_group: - name: Gaspard_Interface_setting_test - template: Gaspard_FP_3.2_test + name: ansible_test_interface_policy_group_port_channel + template: ansible_fabric_policy_template interface_descriptions: - node: 101 interface_id: 1/1 @@ -200,8 +223,8 @@ cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface_policy_group <<: *update_virtual_port_channel_interface_description interface_policy_group: - name: Gaspard_Interface_setting_test_2 - template: Gaspard_FP_3.2_test + name: ansible_test_interface_policy_group_port_channel_2 + template: ansible_fabric_policy_template register: nm_update_virtual_port_channel_interface_policy_group - name: Update a virtual port channel interface members for first node @@ -249,11 +272,8 @@ interfaces_1: 1/1-2 interface_descriptions: - node: 103 - interface_id: 1/1 - description: new first Ansible interface test for first node - - node: 103 - interface_id: 1/2 - description: new second Ansible interface test for first node + interface_id: 1/1-2 + description: New group of Ansible interfaces test for first node register: nm_delete_virtual_port_channel_interface_member - name: Assert virtual port channel interface update tasks @@ -320,10 +340,10 @@ - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions | length == 2 - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.0.nodeID == "103" - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.0.interfaceID == "1/1" - - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.0.description == "new first Ansible interface test for first node" + - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.0.description == "New group of Ansible interfaces test for first node" - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.1.nodeID == "103" - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.1.interfaceID == "1/2" - - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.1.description == "new second Ansible interface test for first node" + - nm_delete_virtual_port_channel_interface_member.current.interfaceDescriptions.1.description == "New group of Ansible interfaces test for first node" # QUERY @@ -337,8 +357,8 @@ interfaces_1: 1/1 interfaces_2: 1/1 interface_policy_group: - name: Gaspard_Interface_setting_test - template: Gaspard_FP_3.2_test + name: ansible_test_interface_policy_group_port_channel_2 + template: ansible_fabric_policy_template state: present - name: Query a virtual port channel interface with template_name @@ -364,6 +384,80 @@ - query_all.current.0.name == "ansible_virtual_port_channel_interface_changed" - query_all.current.1.name == "ansible_virtual_port_channel_interface_2" + # ERRORS + + - name: Create a new virtual port channel interface without valid range IDs in interface descriptions + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_port_channel_interface_error + node_1: 101 + node_2: 102 + interfaces_1: 1/1-2 + interfaces_2: 1/1 + interface_policy_group: + name: ansible_test_interface_policy_group_port_channel + template: ansible_fabric_policy_template + interface_descriptions: + - node: 101 + interface_id: 1/2-1 + description: Incorrect Range starting and ending ID values + state: present + ignore_errors: true + register: nm_create_invalid_range + + - name: Create a new virtual port channel interface without valid IDs values in interface descriptions + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_port_channel_interface_error + node_1: 101 + node_2: 102 + interfaces_1: 1/1-2 + interfaces_2: 1/1 + interface_policy_group: + name: ansible_test_interface_policy_group_port_channel + template: ansible_fabric_policy_template + interface_descriptions: + - node: 101 + interface_id: invalid_id + description: Invalid ID value + state: present + ignore_errors: true + register: nm_create_invalid_id + + - name: delete first interface policy group of type port channel + cisco.mso.ndo_interface_setting: + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_test_interface_policy_group_port_channel + interface_type: port_channel + state: absent + + - name: Create a new virtual port channel interface without an existing interface policy group + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_port_channel_interface_error + node_1: 101 + node_2: 102 + interfaces_1: 1/1-2 + interfaces_2: 1/1 + interface_policy_group: + name: ansible_test_interface_policy_group_port_channel + template: ansible_fabric_policy_template + state: present + ignore_errors: true + register: nm_create_without_existing_policy + + - name: Assert virtual port channel interface errors tasks + assert: + that: + - nm_create_missing_node.msg == "Missing parameter 'node' for creating a Port Channel Interface" + - nm_create_invalid_range.msg == "Range start is greater than or equal to range stop for range of IDs '1/2-1'" + - nm_create_invalid_range.msg == "Incorrect interface ID or range of IDs. Got 'invalid_id'" + - nm_create_without_existing_policy.msg == "Provided Interface Policy Groups with '[KVPair(key='name', value='ansible_test_interface_policy_group_port_channel')]' not matching existing object(s): ansible_test_interface_policy_group_port_channel_2" + # DELETE - name: Delete a virtual port channel interface (check_mode) @@ -395,9 +489,40 @@ - nm_delete_virtual_port_channel_interface_again is not changed - nm_delete_virtual_port_channel_interface_again.previous == {} - nm_delete_virtual_port_channel_interface_again.current == {} + + + # ERRORS AND NO PORT CHANNEL INTERFACES FOUND + + - name: Query all virtual port channel interfaces in the template when all are deleted + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + state: query + register: query_all_none + + - name: Update with non-existing UUID + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + uuid: non-existing-uuid + state: present + ignore_errors: true + register: update_non_existing_uuid + + - name: Assert no Virtual Port Channel Interface found + assert: + that: + - query_all_none is not changed + - query_all_none.current == {} + - update_non_existing_uuid is failed + - update_non_existing_uuid.msg == "Virtual Port Channel Interface with the UUID{{":"}} 'non-existing-uuid' not found" # CLEANUP TEMPLATE - - name: Ensure templates do not exist + - name: Ensure fabric resource template does not exist cisco.mso.ndo_template: <<: *template_absent + + - name: Ensure fabric policy template does not exist + cisco.mso.ndo_template: + <<: *template_policy_absent