diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index 55d235b4..011e5e7d 100644 --- a/plugins/module_utils/template.py +++ b/plugins/module_utils/template.py @@ -207,3 +207,14 @@ def get_l3out_node_routing_policy_object(self, uuid=None, name=None, fail_module "L3Out Node Routing Policy", existing_l3out_node_routing_policy, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module ) return existing_l3out_node_routing_policy # Query all objects + + def get_interface_policy_group_uuid(self, interface_policy_group): + """ + Get the UUID of an Interface Policy Group by name. + :param interface_policy_group: Name of the Interface Policy Group to search for -> Str + :return: UUID of the Interface Policy Group. -> Str + """ + existing_policy_groups = self.template.get("fabricPolicyTemplate", {}).get("template", {}).get("interfacePolicyGroups", []) + kv_list = [KVPair("name", interface_policy_group)] + match = self.get_object_by_key_value_pairs("Interface Policy Groups", existing_policy_groups, kv_list, fail_module=True) + return match.details.get("uuid") diff --git a/plugins/modules/ndo_virtual_port_channel_interface.py b/plugins/modules/ndo_virtual_port_channel_interface.py new file mode 100644 index 00000000..82c19002 --- /dev/null +++ b/plugins/modules/ndo_virtual_port_channel_interface.py @@ -0,0 +1,383 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Gaspard Micol (@gmicol) + +# 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: ndo_virtual_port_channel_interface +short_description: Manage Virtual Port Channel Interfaces_1 on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage Virtual Port Channel Interfaces_1 on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.2 (NDO v4.4) and later. +author: +- Gaspard Micol (@gmicol) +options: + template: + description: + - The name of the template. + - The template must be a fabric resource template. + type: str + required: true + virtual_port_channel_interface: + description: + - The name of the Virtual Port Channel Interface. + type: str + aliases: [ name, virtual_port_channel, vpc ] + virtual_port_channel_interface_uuid: + description: + - The uuid of the Virtual Port Channel Interface. + - This parameter is required when parameter O(virtual_port_channel_interface) is updated. + type: str + aliases: [ uuid, virtual_port_channel_uuid, vpc_uuid ] + description: + description: + - The description of the Port Channel Interface. + type: str + node_1: + description: + - The first node ID. + type: str + node_2: + description: + - The second node ID. + type: str + interfaces_1: + description: + - The list of used Interface IDs for the first node. + - Ranges of Interface IDs can be used. + type: list + elements: str + aliases: [ members_1 ] + interfaces_2: + description: + - The list of used Interface IDs for the second node. + - Ranges of Interface IDs can be used. + type: list + elements: str + aliases: [ members_2 ] + interface_policy_group_uuid: + description: + - The UUID of the Port Channel Interface Setting Policy. + type: str + aliases: [ policy_uuid, interface_policy_uuid ] + interface_policy_group: + description: + - The name of the Port Channel Interface Policy Group. + - This parameter can be used instead of O(interface_policy_group_uuid). + type: dict + suboptions: + name: + description: + - The name of the Port Channel Interface Setting Policy. + type: str + template: + description: + - The name of the template in which is referred the Port Channel Interface Policy Group. + type: str + aliases: [ policy, interface_policy ] + interface_descriptions: + description: + - The list of descriptions for each interface. + type: list + elements: dict + suboptions: + node: + description: + - The node ID + type: str + interface_id: + description: + - The interface ID. + type: str + description: + description: + - The description of the interface. + type: str + state: + description: + - Use C(absent) for removing. + - Use C(query) for listing an object or multiple objects. + - Use C(present) for creating or updating. + type: str + choices: [ absent, query, present ] + default: query +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create a new Virtual Port Channel Interface + cisco.mso.ndo_virtual_port_channel_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_fabric_resource_template + description: My Ansible Port Channel + virtual_port_channel_interface: ansible_virtual_port_channel_interface + node_1: 101 + node_2: 102 + interfaces_1: + - 1/1 + - 1/10-11 + interfaces_2: + - 1/2 + interface_policy_group: + name: ansible_policy_group + template: ansible_fabric_policy_template + interface_descriptions: + - node: 101 + interface_id: 1/1 + description: My first 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 + - node: 102 + interface_id: 1/2 + description: My first Ansible Interface for second node + state: present + +- name: Query an Virtual Port Channel Interface with template_name + cisco.mso.ndo_virtual_port_channel_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_virtual_port_channel_interface + state: query + register: query_one + +- name: Query all Virtual Port Channel Interfaces in the template + cisco.mso.ndo_virtual_port_channel_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_fabric_resource_template + state: query + register: query_all + +- name: Delete an Virtual Port Channel Interface + cisco.mso.ndo_virtual_port_channel_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_virtual_port_channel_interface + state: absent +""" + +RETURN = r""" +""" + +import copy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import ( + MSOModule, + mso_argument_spec, +) +from ansible_collections.cisco.mso.plugins.module_utils.template import ( + MSOTemplate, + KVPair, +) + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str", required=True), + virtual_port_channel_interface=dict(type="str", aliases=["name", "virtual_port_channel", "vpc"]), + virtual_port_channel_interface_uuid=dict(type="str", aliases=["uuid", "virtual_port_channel_uuid", "vpc_uuid"]), + description=dict(type="str"), + node_1=dict(type="str"), + node_2=dict(type="str"), + interfaces_1=dict(type="list", elements="str", aliases=["members_1"]), + interfaces_2=dict(type="list", elements="str", aliases=["members_2"]), + interface_policy_group=dict( + type="dict", + options=dict( + name=dict(type="str"), + template=dict(type="str"), + ), + aliases=["policy", "interface_policy"], + ), + interface_policy_group_uuid=dict(type="str", aliases=["policy_uuid", "interface_policy_uuid"]), + interface_descriptions=dict( + type="list", + elements="dict", + options=dict( + node=dict(type="str"), + interface_id=dict(type="str"), + description=dict(type="str"), + ), + ), + state=dict(type="str", default="query", choices=["absent", "query", "present"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["template", "virtual_port_channel_interface"]], + ["state", "present", ["template", "virtual_port_channel_interface"]], + ], + ) + + mso = MSOModule(module) + + template = module.params.get("template") + virtual_port_channel_interface = module.params.get("virtual_port_channel_interface") + virtual_port_channel_interface_uuid = module.params.get("virtual_port_channel_interface_uuid") + description = module.params.get("description") + node_1 = module.params.get("node_1") + node_2 = module.params.get("node_2") + interfaces_1 = module.params.get("interfaces_1") + if interfaces_1: + interfaces_1 = ",".join(interfaces_1) + interfaces_2 = module.params.get("interfaces_2") + if interfaces_2: + interfaces_2 = ",".join(interfaces_2) + interface_policy_group = module.params.get("interface_policy_group") + interface_policy_group_uuid = module.params.get("interface_policy_group_uuid") + interface_descriptions = module.params.get("interface_descriptions") + state = module.params.get("state") + + ops = [] + match = None + + mso_template = MSOTemplate(mso, "fabric_resource", template) + mso_template.validate_template("fabricResource") + object_description = "Virtual Port Channel Interface" + + path = "/fabricResourceTemplate/template/virtualPortChannels" + existing_virtual_port_channel_interfaces = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}).get("virtualPortChannels", []) + if virtual_port_channel_interface or virtual_port_channel_interface_uuid: + match = mso_template.get_object_by_key_value_pairs( + object_description, + existing_virtual_port_channel_interfaces, + [KVPair("uuid", virtual_port_channel_interface_uuid) if virtual_port_channel_interface_uuid else KVPair("name", virtual_port_channel_interface)], + ) + if match: + mso.existing = mso.previous = copy.deepcopy(match.details) + else: + mso.existing = mso.previous = existing_virtual_port_channel_interfaces + + if state == "present": + + if interface_policy_group and not interface_policy_group_uuid: + fabric_policy_template = MSOTemplate(mso, "fabric_policy", interface_policy_group.get("template")) + fabric_policy_template.validate_template("fabricPolicy") + interface_policy_group_uuid = fabric_policy_template.get_interface_policy_group_uuid(interface_policy_group.get("name")) + + if match: + if virtual_port_channel_interface and match.details.get("name") != virtual_port_channel_interface: + ops.append(dict(op="replace", path="{0}/{1}/name".format(path, match.index), value=virtual_port_channel_interface)) + match.details["name"] = virtual_port_channel_interface + + if description and match.details.get("description") != description: + ops.append(dict(op="replace", path="{0}/{1}/description".format(path, match.index), value=description)) + match.details["description"] = description + + if node_1 and match.details.get("node1Details", {}).get("node") != node_1: + ops.append(dict(op="replace", path="{0}/{1}/node1Details/node".format(path, match.index), value=node_1)) + match.details["node1Details"]["node"] = node_1 + + if node_2 and match.details.get("node2Details", {}).get("node") != node_2: + ops.append(dict(op="replace", path="{0}/{1}/node2Details/node".format(path, match.index), value=node_2)) + match.details["node2Details"]["node"] = node_2 + + if interface_policy_group_uuid and match.details.get("policy") != interface_policy_group_uuid: + ops.append(dict(op="replace", path="{0}/{1}/policy".format(path, match.index), value=interface_policy_group_uuid)) + match.details["policy"] = interface_policy_group_uuid + + if interfaces_1 and interfaces_1 != match.details.get("node1Details", {}).get("memberInterfaces"): + ops.append(dict(op="replace", path="{0}/{1}/node1Details/memberInterfaces".format(path, match.index), value=interfaces_1)) + match.details["node1Details"]["memberInterfaces"] = interfaces_1 + + if interfaces_2 and interfaces_2 != match.details.get("node2Details", {}).get("memberInterfaces"): + ops.append(dict(op="replace", path="{0}/{1}/node2Details/memberInterfaces".format(path, match.index), value=interfaces_2)) + match.details["node2Details"]["memberInterfaces"] = interfaces_2 + + if interface_descriptions: + interface_descriptions = [ + { + "nodeID": interface.get("node"), + "interfaceID": interface.get("interface_id"), + "description": interface.get("description"), + } + for interface in interface_descriptions + ] + if interface_descriptions != match.details.get("interfaceDescriptions"): + ops.append(dict(op="replace", path="{0}/{1}/interfaceDescriptions".format(path, match.index), value=interface_descriptions)) + match.details["interfaceDescriptions"] = interface_descriptions + elif interface_descriptions == [] and match.details["interfaceDescriptions"]: + ops.append(dict(op="remove", path="{0}/{1}/interfaceDescriptions".format(path, match.index))) + + mso.sanitize(match.details) + + else: + payload = { + "name": virtual_port_channel_interface, + "node1Details": { + "node": node_1, + "memberInterfaces": interfaces_1, + }, + "node2Details": { + "node": node_2, + "memberInterfaces": interfaces_2, + }, + "policy": interface_policy_group_uuid, + } + if description: + payload["description"] = description + if interface_descriptions: + payload["interfaceDescriptions"] = [ + { + "nodeID": interface.get("node"), + "interfaceID": interface.get("interface_id"), + "description": interface.get("description"), + } + for interface in interface_descriptions + ] + + ops.append(dict(op="add", path="{0}/-".format(path), value=payload)) + + mso.sanitize(payload) + + elif state == "absent": + if match: + ops.append(dict(op="remove", path="{0}/{1}".format(path, match.index))) + + if not module.check_mode and ops: + response = mso.request(mso_template.template_path, method="PATCH", data=ops) + virtual_port_channel_interfaces = response.get("fabricResourceTemplate", {}).get("template", {}).get("virtualPortChannels", []) + match = mso_template.get_object_by_key_value_pairs( + object_description, + virtual_port_channel_interfaces, + [KVPair("uuid", virtual_port_channel_interface_uuid) if virtual_port_channel_interface_uuid else KVPair("name", virtual_port_channel_interface)], + ) + if match: + mso.existing = match.details + else: + mso.existing = {} + elif module.check_mode and state != "query": + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ndo_virtual_port_channel_interface/aliases b/tests/integration/targets/ndo_virtual_port_channel_interface/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_virtual_port_channel_interface/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported 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 new file mode 100644 index 00000000..6fa14281 --- /dev/null +++ b/tests/integration/targets/ndo_virtual_port_channel_interface/tasks/main.yml @@ -0,0 +1,401 @@ +# Test code for the MSO modules +# Copyright: (c) 2024, Gaspard Micol (@gmicol) + +# 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 MultiSite host, username and password + ansible.builtin.fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# QUERY VERSION +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + + +- name: Execute tasks only for MSO version > 4.4 + when: version.current.version is version('4.4', '>=') + block: + + - name: Ensure sites exists + cisco.mso.mso_site: + <<: *mso_info + site: '{{ item.site }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ item.apic_site_id }}' + urls: + - https://{{ apic_hostname }} + state: present + loop: + - {site: "ansible_test", apic_site_id: 101} + - {site: "ansible_test_2", apic_site_id: 102} + + - name: Ensure fabric resource template does not exist + cisco.mso.ndo_template: &template_absent + <<: *mso_info + name: ansible_fabric_resource_template + template_type: fabric_resource + state: absent + + - name: Create fabric resource template + cisco.mso.ndo_template: + <<: *template_absent + state: present + + # CREATE + + - name: Create a new virtual port channel interface (check_mode) + cisco.mso.ndo_virtual_port_channel_interface: &create_virtual_port_channel_interface + <<: *mso_info + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_virtual_port_channel_interface + description: Ansible Virtual Port Channel test + node_1: 101 + node_2: 102 + interfaces_1: 1/1 + interfaces_2: 1/1 + interface_policy_group: + name: Gaspard_Interface_setting_test + template: Gaspard_FP_3.2_test + interface_descriptions: + - node: 101 + interface_id: 1/1 + description: first Ansible interface test for first node + - node: 102 + interface_id: 1/1 + description: first Ansible interface test for second node + state: present + check_mode: true + register: cm_create_new_virtual_port_channel_interface + + - name: Create a new virtual port channel interface + cisco.mso.ndo_virtual_port_channel_interface: + <<: *create_virtual_port_channel_interface + register: nm_create_new_virtual_port_channel_interface + + - name: Create a new virtual port channel interface again + cisco.mso.ndo_virtual_port_channel_interface: + <<: *create_virtual_port_channel_interface + register: nm_create_new_virtual_port_channel_interface_again + + - name: Assert virtual port channel interface creation tasks + assert: + that: + - cm_create_new_virtual_port_channel_interface is changed + - cm_create_new_virtual_port_channel_interface.previous == {} + - cm_create_new_virtual_port_channel_interface.current.name == "ansible_virtual_port_channel_interface" + - cm_create_new_virtual_port_channel_interface.current.description == "Ansible Virtual Port Channel test" + - cm_create_new_virtual_port_channel_interface.current.node1Details.node == "101" + - cm_create_new_virtual_port_channel_interface.current.node1Details.memberInterfaces == "1/1" + - cm_create_new_virtual_port_channel_interface.current.node2Details.node == "102" + - cm_create_new_virtual_port_channel_interface.current.node2Details.memberInterfaces == "1/1" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions | length == 2 + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.nodeID == "101" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.interfaceID == "1/1" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.nodeID == "102" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.interfaceID == "1/1" + - cm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + - nm_create_new_virtual_port_channel_interface is changed + - nm_create_new_virtual_port_channel_interface.previous == {} + - nm_create_new_virtual_port_channel_interface.current.name == "ansible_virtual_port_channel_interface" + - nm_create_new_virtual_port_channel_interface.current.description == "Ansible Virtual Port Channel test" + - nm_create_new_virtual_port_channel_interface.current.node1Details.node == "101" + - nm_create_new_virtual_port_channel_interface.current.node1Details.memberInterfaces == "1/1" + - nm_create_new_virtual_port_channel_interface.current.node2Details.node == "102" + - nm_create_new_virtual_port_channel_interface.current.node2Details.memberInterfaces == "1/1" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions | length == 2 + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.nodeID == "101" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.interfaceID == "1/1" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.nodeID == "102" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.interfaceID == "1/1" + - nm_create_new_virtual_port_channel_interface.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + - nm_create_new_virtual_port_channel_interface_again is not changed + - nm_create_new_virtual_port_channel_interface_again.previous == nm_create_new_virtual_port_channel_interface_again.current + - nm_create_new_virtual_port_channel_interface_again.current.name == "ansible_virtual_port_channel_interface" + - nm_create_new_virtual_port_channel_interface_again.current.description == "Ansible Virtual Port Channel test" + - nm_create_new_virtual_port_channel_interface_again.current.node1Details.node == "101" + - nm_create_new_virtual_port_channel_interface_again.current.node1Details.memberInterfaces == "1/1" + - nm_create_new_virtual_port_channel_interface_again.current.node2Details.node == "102" + - nm_create_new_virtual_port_channel_interface_again.current.node2Details.memberInterfaces == "1/1" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions | length == 2 + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.0.nodeID == "101" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.0.interfaceID == "1/1" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.1.nodeID == "102" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.1.interfaceID == "1/1" + - nm_create_new_virtual_port_channel_interface_again.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + + # UPDATE + + - name: Update a virtual port channel interface first node (check_mode) + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface + <<: *create_virtual_port_channel_interface + node_1: 103 + interface_descriptions: + - node: 103 + interface_id: 1/1 + description: first Ansible interface test for first node + - node: 102 + interface_id: 1/1 + description: first Ansible interface test for second node + check_mode: true + register: cm_update_virtual_port_channel_interface_first_node + + - name: Update a virtual port channel interface first node + cisco.mso.ndo_virtual_port_channel_interface: + <<: *update_virtual_port_channel_interface + register: nm_update_virtual_port_channel_interface_first_node + + - name: Update a virtual port channel interface first node again + cisco.mso.ndo_virtual_port_channel_interface: + <<: *update_virtual_port_channel_interface + register: nm_update_virtual_port_channel_interface_first_node_again + + - name: Update a virtual port channel interface second node + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface_second_node + <<: *update_virtual_port_channel_interface + node_2: 104 + interface_descriptions: + - node: 103 + interface_id: 1/1 + description: first Ansible interface test for first node + - node: 104 + interface_id: 1/1 + description: first Ansible interface test for second node + state: present + register: nm_update_virtual_port_channel_interface_second_node + + - name: Update a virtual port channel interface name + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface_name + <<: *update_virtual_port_channel_interface_second_node + virtual_port_channel_interface_uuid: '{{ nm_update_virtual_port_channel_interface_second_node.current.uuid }}' + virtual_port_channel_interface: ansible_virtual_port_channel_interface_changed + register: nm_update_virtual_port_channel_interface_name + + - name: Update a virtual port channel interface description + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface_description + <<: *update_virtual_port_channel_interface_name + description: Ansible Virtual Port Channel test updated + register: nm_update_virtual_port_channel_interface_description + + - name: Update a virtual port channel interface policy + 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 + register: nm_update_virtual_port_channel_interface_policy_group + + - name: Update a virtual port channel interface members for first node + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_first_interface_members + <<: *update_virtual_port_channel_interface_policy_group + interfaces_1: 1/1-3 + register: nm_update_virtual_port_channel_first_interface_members + + - name: Update a virtual port channel interface members for second node + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_second_interface_members + <<: *update_virtual_port_channel_first_interface_members + interfaces_2: 1/1-2 + register: nm_update_virtual_port_channel_second_interface_members + + - name: Update a virtual port channel interface members descriptions + cisco.mso.ndo_virtual_port_channel_interface: &update_virtual_port_channel_interface_descriptions + <<: *update_virtual_port_channel_second_interface_members + 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 + - node: 103 + interface_id: 1/3 + description: new third Ansible interface test for first node + - node: 104 + interface_id: 1/1 + description: new first Ansible interface test for second node + - node: 104 + interface_id: 1/2 + description: new second Ansible interface test for second node + register: nm_update_virtual_port_channel_interface_descriptions + + - name: Delete a virtual port channel interface members descriptions + cisco.mso.ndo_virtual_port_channel_interface: &delete_virtual_port_channel_interface_desciptions + <<: *update_virtual_port_channel_interface_descriptions + interface_descriptions: [] + register: nm_delete_virtual_port_channel_interface_descriptions + + - name: Delete a virtual port channel interface member and add descriptions again for first node + cisco.mso.ndo_virtual_port_channel_interface: &delete_virtual_port_channel_interface_member + <<: *delete_virtual_port_channel_interface_desciptions + 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 + register: nm_delete_virtual_port_channel_interface_member + + - name: Assert virtual port channel interface update tasks + assert: + that: + - cm_update_virtual_port_channel_interface_first_node is changed + - cm_update_virtual_port_channel_interface_first_node.current.node1Details.node == "103" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions | length == 2 + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.nodeID == "103" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.interfaceID == "1/1" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.nodeID == "102" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.interfaceID == "1/1" + - cm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + - nm_update_virtual_port_channel_interface_first_node is changed + - nm_update_virtual_port_channel_interface_first_node.current.node1Details.node == "103" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions | length == 2 + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.nodeID == "103" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.nodeID == "102" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_first_node.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + - nm_update_virtual_port_channel_interface_first_node_again is not changed + - nm_update_virtual_port_channel_interface_first_node_again.current.interfaceDescriptions.0.nodeID == "103" + - nm_update_virtual_port_channel_interface_first_node_again.current == nm_update_virtual_port_channel_interface_first_node_again.previous + - nm_update_virtual_port_channel_interface_second_node is changed + - nm_update_virtual_port_channel_interface_second_node.current.node2Details.node == "104" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions | length == 2 + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.0.nodeID == "103" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.0.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.0.description == "first Ansible interface test for first node" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.1.nodeID == "104" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.1.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_second_node.current.interfaceDescriptions.1.description == "first Ansible interface test for second node" + - nm_update_virtual_port_channel_interface_name is changed + - nm_update_virtual_port_channel_interface_name.current.name == "ansible_virtual_port_channel_interface_changed" + - nm_update_virtual_port_channel_interface_description is changed + - nm_update_virtual_port_channel_interface_description.current.description == "Ansible Virtual Port Channel test updated" + - nm_update_virtual_port_channel_first_interface_members is changed + - nm_update_virtual_port_channel_first_interface_members.current.node1Details.memberInterfaces == "1/1-3" + - nm_update_virtual_port_channel_second_interface_members is changed + - nm_update_virtual_port_channel_second_interface_members.current.node1Details.memberInterfaces == "1/1-2" + - nm_update_virtual_port_channel_interface_descriptions is changed + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions | length == 5 + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.0.nodeID == "103" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.0.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.0.description == "new first Ansible interface test for first node" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.1.nodeID == "103" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.1.interfaceID == "1/2" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.1.description == "new second Ansible interface test for first node" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.2.nodeID == "103" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.2.interfaceID == "1/3" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.2.description == "new third Ansible interface test for first node" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.3.nodeID == "104" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.3.interfaceID == "1/1" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.3.description == "new first Ansible interface test for second node" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.4.nodeID == "104" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.4.interfaceID == "1/2" + - nm_update_virtual_port_channel_interface_descriptions.current.interfaceDescriptions.4.description == "new second Ansible interface test for second node" + - nm_delete_virtual_port_channel_interface_descriptions is changed + - nm_delete_virtual_port_channel_interface_member is changed + - nm_delete_virtual_port_channel_interface_member.current.node1Details.memberInterfaces == "1/1-2" + - 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.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" + + # QUERY + + - name: Create another virtual port channel interface + cisco.mso.ndo_virtual_port_channel_interface: &create_virtual_port_channel_interface_2 + <<: *mso_info + template: ansible_fabric_resource_template + virtual_port_channel_interface: ansible_virtual_port_channel_interface_2 + node_1: 101 + node_2: 102 + interfaces_1: 1/1 + interfaces_2: 1/1 + interface_policy_group: + name: Gaspard_Interface_setting_test + template: Gaspard_FP_3.2_test + state: present + + - name: Query a virtual port channel interface with template_name + cisco.mso.ndo_virtual_port_channel_interface: + <<: *create_virtual_port_channel_interface_2 + state: query + register: query_one + + - name: Query all virtual port channel interfaces in the template + cisco.mso.ndo_virtual_port_channel_interface: + <<: *mso_info + template: ansible_fabric_resource_template + state: query + register: query_all + + - name: Assert virtual port channel interface query tasks + assert: + that: + - query_one is not changed + - query_one.current.name == "ansible_virtual_port_channel_interface_2" + - query_all is not changed + - query_all.current | length == 2 + + # DELETE + + - name: Delete a virtual port channel interface (check_mode) + cisco.mso.ndo_virtual_port_channel_interface: &delete_virtual_port_channel_interface + <<: *delete_virtual_port_channel_interface_member + state: absent + check_mode: true + register: cm_delete_virtual_port_channel_interface + + - name: Delete a virtual port channel interface + cisco.mso.ndo_virtual_port_channel_interface: + <<: *delete_virtual_port_channel_interface + register: nm_delete_virtual_port_channel_interface + + - name: Delete a virtual port channel interface again + cisco.mso.ndo_virtual_port_channel_interface: + <<: *delete_virtual_port_channel_interface + register: nm_delete_virtual_port_channel_interface_again + + - name: Assert virtual port channel interface deletion tasks + assert: + that: + - cm_delete_virtual_port_channel_interface is changed + - cm_delete_virtual_port_channel_interface.previous.name == "ansible_virtual_port_channel_interface_changed" + - cm_delete_virtual_port_channel_interface.current == {} + - nm_delete_virtual_port_channel_interface is changed + - nm_delete_virtual_port_channel_interface.previous.name == "ansible_virtual_port_channel_interface_changed" + - nm_delete_virtual_port_channel_interface.current == {} + - 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 == {} + + # CLEANUP TEMPLATE + + - name: Ensure templates do not exist + cisco.mso.ndo_template: + <<: *template_absent