diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py new file mode 100644 index 00000000..20bb64b2 --- /dev/null +++ b/plugins/modules/ndo_physical_interface.py @@ -0,0 +1,377 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Anvitha Jain (@anvjain) + +# 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_physical_interface +short_description: Manage Physical Interfaces on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage Physical Interfaces on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.2 (NDO v4.4) and later. +author: +- Anvitha Jain (@anvjain) +options: + template: + description: + - The name of the template. + - The template must be a fabric resource policy template. + type: str + required: true + name: + description: + - The name of the Physical Interface. + type: str + aliases: [ physical_interface ] + uuid: + description: + - The UUID of the Physical Interface. + - This parameter is required when the O(name) needs to be updated. + type: str + aliases: [ physical_interface_uuid ] + description: + description: + - The description of the Physical Interface. + type: str + nodes: + description: + - The node IDs where the Physical Interface policy will be deployed. + type: list + elements: int + interfaces: + description: + - The interface names where the policy will be deployed. + - The old O(interfaces) will be replaced with the new O(interfaces) during an update. + type: list + elements: str + physical_interface_type: + description: + - The type of the interface policy group. + type: str + choices: [ physical, breakout ] + physical_policy_uuid: + description: + - The UUID of the Interface Setting Policy. + - This is only required when creating a new Interface Setting Policy. + - This parameter is required when O(physical_interface_type) is C(physical). + - This parameter can be used instead of O(physical_policy). + type: str + aliases: [ policy_uuid, interface_policy_uuid , interface_policy_group_uuid, interface_setting_uuid] + physical_policy: + description: + - The interface group policy required for physical Interface Setting Policy. + - This parameter is required when O(physical_interface_type) is C(physical). + - This parameter can be used instead of O(physical_policy_uuid). + type: dict + suboptions: + name: + description: + - The name of the Interface Setting Policy. + type: str + template: + description: + - The name of the template in which is referred the Interface Setting Policy. + type: str + aliases: [ policy, interface_policy, interface_policy_group, interface_setting ] + breakout_mode: + description: + - Breakout mode enables breaking down an ethernet port into multiple low-speed ports. + - This parameter is available only when O(physical_interface_type) is C(breakout). + - The default value is C(4x10G). + type: str + choices: [ 4x10G, 4x25G, 4x100G ] + interface_descriptions: + description: + - The interface settings defined in the interface settings policy will be applied to the interfaces on the node IDs configured in C(nodes). + - This parameter when set to an empty list during an update will clear all the existing interface descriptions. + - The API will trigger an error when there are duplicate interface IDs in the list. + type: list + elements: dict + suboptions: + 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 +notes: +- The O(template) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_template) to create the Tenant template. +- The O(physical_policy) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_interface_setting) to create the Interface Setting Policy. +- The O(physical_policy_uuid) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_interface_setting) to create the Interface Setting Policy UUID. +seealso: +- module: cisco.mso.ndo_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create a Physical Interface with physical_interface_type set to physical + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + name: ansible_test_physical_interface_physical + description: "Physical Interface for Ansible Test" + nodes: [101] + interfaces: "1/1" + physical_interface_type: physical + physical_policy: ansible_test_interface_setting_policy_uuid + state: present + register: create_physical_interface_type_physical + +- name: Create a Physical Interface with physical_interface_type set to breakout + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + name: ansible_test_physical_interface_breakout + description: "breakout interface for Ansible Test" + nodes: [101] + interfaces: "1/1" + physical_interface_type: breakout + breakout_mode: 4x25G + interface_descriptions: + - interface_id: "1/1" + description: "Interface description for 1/1" + state: present + register: create_physical_interface_type_breakout + +- name: Query all physical interfaces + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + state: query + register: query_all + +- name: Query a specific Physical Interface with name + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + name: ansible_test_physical_interface_physical + state: query + register: query_one_name + +- name: Query a specific Physical Interface with UUID + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + uuid: "{{ create_physical_interface_type_breakout.current.uuid }}" + state: query + register: query_one_uuid + +- name: Delete a Physical Interface with name + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + name: ansible_test_physical_interface_physical + state: absent + +- name: Delete a Physical Interface with UUID + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + uuid: "{{ query_one_uuid.current.uuid }}" + state: absent +""" + +RETURN = r""" +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import ( + MSOModule, + mso_argument_spec, + format_interface_descriptions, +) +from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate, KVPair +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data +import copy + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dict( + template=dict(type="str", required=True), + name=dict(type="str", aliases=["physical_interface"]), + uuid=dict(type="str", aliases=["physical_interface_uuid"]), + description=dict(type="str"), + nodes=dict(type="list", elements="int"), + interfaces=dict(type="list", elements="str"), + physical_interface_type=dict(type="str", choices=["physical", "breakout"]), + physical_policy_uuid=dict(type="str", aliases=["policy_uuid", "interface_policy_uuid", "interface_policy_group_uuid", "interface_setting_uuid"]), + physical_policy=dict( + type="dict", + options=dict( + name=dict(type="str"), + template=dict(type="str"), + ), + aliases=["policy", "interface_policy", "interface_policy_group", "interface_setting"], + ), + breakout_mode=dict(type="str", choices=["4x10G", "4x25G", "4x100G"]), + interface_descriptions=dict( + type="list", + elements="dict", + options=dict( + 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", "present", ["name", "uuid"], True], + ["state", "absent", ["name", "uuid"], True], + ], + mutually_exclusive=[("physical_policy", "breakout_mode"), ("physical_policy", "physical_policy_uuid")], + ) + + mso = MSOModule(module) + + template = module.params.get("template") + name = module.params.get("name") + uuid = module.params.get("uuid") + description = module.params.get("description") + nodes = module.params.get("nodes") + if nodes: + nodes = [str(node) for node in nodes] + interfaces = module.params.get("interfaces") + if interfaces: + interfaces = ",".join(interfaces) + physical_interface_type = module.params.get("physical_interface_type") + physical_policy_uuid = module.params.get("physical_policy_uuid") + physical_policy = module.params.get("physical_policy") + breakout_mode = module.params.get("breakout_mode") + 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 = "Physical Interface Profile" + path = "/fabricResourceTemplate/template/interfaceProfiles" + + existing_physical_interfaces = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}).get("interfaceProfiles") or [] + + if name or uuid: + match = mso_template.get_object_by_key_value_pairs( + object_description, + existing_physical_interfaces, + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], + ) + if match: + physical_interface_attrs_path = "{0}/{1}".format(path, match.index) + mso.existing = mso.previous = copy.deepcopy(match.details) + else: + mso.existing = mso.previous = existing_physical_interfaces + + if state == "present": + if uuid and not mso.existing: + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) + + if physical_policy and not physical_policy_uuid: + fabric_policy_template = MSOTemplate(mso, "fabric_policy", physical_policy.get("template")) + fabric_policy_template.validate_template("fabricPolicy") + physical_policy_uuid = fabric_policy_template.get_interface_policy_group_uuid(physical_policy.get("name")) + + mso_values = dict( + name=name, + description=description, + nodes=nodes, + interfaces=interfaces, + policyGroupType=physical_interface_type, + ) + + if physical_interface_type == "physical" and physical_policy_uuid: + mso_values["policy"] = physical_policy_uuid + + if physical_interface_type == "breakout" and breakout_mode: + mso_values["breakoutMode"] = breakout_mode + + if interface_descriptions: + mso_values["interfaceDescriptions"] = format_interface_descriptions(mso, interface_descriptions, "") + + if mso.existing: + proposed_payload = copy.deepcopy(mso.existing) + mso_values_remove = list() + + if physical_interface_type and match.details.get("policyGroupType") != physical_interface_type: + mso.fail_json(msg="ERROR: Physical Interface type cannot be changed.") + + if interface_descriptions == [] and proposed_payload.get("interfaceDescriptions"): + mso_values_remove.append("interfaceDescriptions") + + append_update_ops_data(ops, match.details, physical_interface_attrs_path, mso_values, mso_values_remove) + mso.sanitize(match.details, collate=True) + + else: + if not nodes: + mso.fail_json(msg=("ERROR: Missing 'nodes' for creating a Physical Interface.")) + + if not physical_interface_type: + mso.fail_json(msg=("ERROR: Missing Physical Interface type for creating a Physical Interface.")) + + mso.sanitize(mso_values) + ops.append(dict(op="add", path="{0}/-".format(path), value=mso.sent)) + + elif state == "absent": + if match: + ops.append(dict(op="remove", path=physical_interface_attrs_path)) + + if not module.check_mode and ops: + response = mso.request(mso_template.template_path, method="PATCH", data=ops) + physical_interfaces = response.get("fabricResourceTemplate", {}).get("template", {}).get("interfaceProfiles") or [] + match = mso_template.get_object_by_key_value_pairs(object_description, physical_interfaces, [KVPair("uuid", uuid) if uuid else KVPair("name", name)]) + if match: + mso.existing = match.details # When the state is present + else: + mso.existing = {} # When the state is absent + elif module.check_mode and state != "query": # When the state is present/absent with check mode + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ndo_physical_interface/aliases b/tests/integration/targets/ndo_physical_interface/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_physical_interface/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_physical_interface/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml new file mode 100644 index 00000000..34cabea8 --- /dev/null +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -0,0 +1,526 @@ +# Test code for the MSO modules +# Copyright: (c) 2024, Anvitha Jain (@anvjain) + +# 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("debug") }}' + +# 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: Remove fabric resource policy template + cisco.mso.ndo_template: &template_absent + <<: *mso_info + name: ansible_fabric_resource_policy_template + type: fabric_resource + state: absent + + - name: Add fabric resource policy template + cisco.mso.ndo_template: + <<: *template_absent + state: present + + # Create interface policy group in fabric policy template + - name: Remove fabric policy template + cisco.mso.ndo_template: &fabric_policy_template_absent + <<: *mso_info + name: ansible_fabric_policy_template + type: fabric_policy + state: absent + + - name: Create a fabric policy template + cisco.mso.ndo_template: + <<: *fabric_policy_template_absent + state: present + + - name: Create interface policy group (physical policy) + cisco.mso.ndo_interface_setting: &add_interface_policy_group + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_interface_policy_group + interface_type: physical + state: present + register: add_interface_policy_group + + - name: Create another interface policy group + cisco.mso.ndo_interface_setting: + <<: *add_interface_policy_group + name: ansible_interface_policy_group_2 + register: add_interface_policy_group_2 + + # CREATE + + # Create Physical Interface of type physical with physical policy UUID + - name: Create Physical Interface of type 'physical' (check mode) + cisco.mso.ndo_physical_interface: &add_physical_interface + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface + nodes: 103 + interfaces: ['1/3'] + physical_interface_type: physical + physical_policy_uuid: '{{ add_interface_policy_group.current.uuid }}' + state: present + check_mode: true + register: cm_physical_interface + + - name: Create Physical Interface of type 'physical' + cisco.mso.ndo_physical_interface: + <<: *add_physical_interface + register: nm_physical_interface + + - name: Create Physical Interface of type 'physical' again + cisco.mso.ndo_physical_interface: + <<: *add_physical_interface + register: nm_physical_interface_again + + - name: Assert Physical Interface of type 'physical' is created + ansible.builtin.assert: + that: + - cm_physical_interface is changed + - nm_physical_interface is changed + - cm_physical_interface.previous == nm_physical_interface.previous == {} + - cm_physical_interface.current.name == nm_physical_interface.current.name == "ansible_physical_interface" + - nm_physical_interface.current.description == "" + - cm_physical_interface.current.policyGroupType == nm_physical_interface.current.policyGroupType == "physical" + - cm_physical_interface.current.nodes | length == nm_physical_interface.current.nodes | length == 1 + - cm_physical_interface.current.nodes[0] == nm_physical_interface.current.nodes[0] == "103" + - cm_physical_interface.current.interfaces == nm_physical_interface.current.interfaces == "1/3" + - cm_physical_interface.current.policy == nm_physical_interface.current.policy + - nm_physical_interface_again is not changed + - nm_physical_interface_again.previous.name == nm_physical_interface_again.current.name == "ansible_physical_interface" + - nm_physical_interface_again.previous.policyGroupType == nm_physical_interface_again.current.policyGroupType == "physical" + - nm_physical_interface_again.previous.nodes | length == nm_physical_interface_again.current.nodes | length == 1 + - nm_physical_interface_again.previous.nodes[0] == nm_physical_interface_again.current.nodes[0] == "103" + - nm_physical_interface_again.previous.interfaces == nm_physical_interface_again.current.interfaces == "1/3" + - nm_physical_interface_again.previous.policy == nm_physical_interface_again.current.policy + - nm_physical_interface_again.previous.uuid == nm_physical_interface.current.uuid + - nm_physical_interface_again.current.uuid is defined + + - name: Create Physical Interface of type 'physical' with physical policy + cisco.mso.ndo_physical_interface: &add_physical_interface3 + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface_with_policy + nodes: 109 + interfaces: ['1/9'] + physical_interface_type: physical + physical_policy: + template: ansible_fabric_policy_template + name: ansible_interface_policy_group + state: present + register: add_physical_interface_with_policy + + - name: Assert Physical Interface of type 'physical' is created with policy + ansible.builtin.assert: + that: + - add_physical_interface_with_policy is changed + - add_physical_interface_with_policy.previous == {} + - add_physical_interface_with_policy.current.name == "ansible_physical_interface_with_policy" + - add_physical_interface_with_policy.current.policyGroupType == "physical" + - add_physical_interface_with_policy.current.nodes | length == 1 + - add_physical_interface_with_policy.current.nodes[0] == "109" + - add_physical_interface_with_policy.current.interfaces == "1/9" + - add_physical_interface_with_policy.current.policy is defined + - add_physical_interface_with_policy.current.uuid is defined + + - name: Create Physical Interface of type 'breakout' (check mode) + cisco.mso.ndo_physical_interface: &add_physical_interface_breakout + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface_2 + description: "Breakout type Physical Interface" + nodes: [101, 102] + interfaces: + - 1/1 + - 1/2-4 + physical_interface_type: breakout + breakout_mode: 4x10G + state: present + interface_descriptions: + - interface_id: "1/1" + description: "First interface" + check_mode: true + register: cm_physical_interface_2 + + - name: Create Physical Interface of type 'breakout' + cisco.mso.ndo_physical_interface: + <<: *add_physical_interface_breakout + register: nm_physical_interface_2 + + - name: Assert Physical Interface of type 'breakout' is created + ansible.builtin.assert: + that: + - cm_physical_interface_2 is changed + - nm_physical_interface_2 is changed + - cm_physical_interface_2.previous == nm_physical_interface_2.previous == {} + - cm_physical_interface_2.current.name == nm_physical_interface_2.current.name == "ansible_physical_interface_2" + - cm_physical_interface_2.current.description == nm_physical_interface_2.current.description == "Breakout type Physical Interface" + - cm_physical_interface_2.current.policyGroupType == nm_physical_interface_2.current.policyGroupType == "breakout" + - cm_physical_interface_2.current.nodes | length == nm_physical_interface_2.current.nodes | length == 2 + - cm_physical_interface_2.current.nodes[0] == nm_physical_interface_2.current.nodes[0] == "101" + - cm_physical_interface_2.current.nodes[1] == nm_physical_interface_2.current.nodes[1] == "102" + - cm_physical_interface_2.current.interfaces == nm_physical_interface_2.current.interfaces == "1/1,1/2-4" + - cm_physical_interface_2.current.breakoutMode == nm_physical_interface_2.current.breakoutMode == "4x10G" + - cm_physical_interface_2.current.interfaceDescriptions | length == nm_physical_interface_2.current.interfaceDescriptions | length == 1 + - cm_physical_interface_2.current.interfaceDescriptions[0].interfaceID == nm_physical_interface_2.current.interfaceDescriptions[0].interfaceID == "1/1" + - cm_physical_interface_2.current.interfaceDescriptions[0].description == nm_physical_interface_2.current.interfaceDescriptions[0].description == "First interface" + - nm_physical_interface_2.current.uuid is defined + + # UPDATE + - name: Update Physical Interface of type 'physical' (check mode) + cisco.mso.ndo_physical_interface: &update_physical_interface + <<: *add_physical_interface + description: "Updated Physical Interface" + nodes: 105 + interfaces: + - 1/5 + - 2/3-5 + physical_policy_uuid: '{{ add_interface_policy_group_2.current.uuid }}' + interface_descriptions: + - interface_id: "2/5" + description: "Updated interface description" + - interface_id: "2/3" + description: "test description" + state: present + check_mode: true + register: cm_physical_interface_update + + - name: Update Physical Interface of type 'physical' + cisco.mso.ndo_physical_interface: + <<: *update_physical_interface + register: nm_physical_interface_update + + - name: Update Physical Interface of type 'physical' again + cisco.mso.ndo_physical_interface: + <<: *update_physical_interface + register: nm_physical_interface_update_again + + - name: Assert Physical Interface of type 'physical' is updated + ansible.builtin.assert: + that: + - cm_physical_interface_update is changed + - nm_physical_interface_update is changed + - cm_physical_interface_update.previous == nm_physical_interface_update.previous + - cm_physical_interface_update.previous.name == nm_physical_interface_update.previous.name == "ansible_physical_interface" + - cm_physical_interface_update.current.name == nm_physical_interface_update.current.name == "ansible_physical_interface" + - cm_physical_interface_update.previous.description == nm_physical_interface_update.previous.description == "" + - cm_physical_interface_update.current.description == nm_physical_interface_update.current.description == "Updated Physical Interface" + - cm_physical_interface_update.previous.policyGroupType == nm_physical_interface_update.previous.policyGroupType == "physical" + - cm_physical_interface_update.current.policyGroupType == nm_physical_interface_update.current.policyGroupType == "physical" + - cm_physical_interface_update.previous.nodes | length == nm_physical_interface_update.previous.nodes | length == 1 + - cm_physical_interface_update.current.nodes | length == nm_physical_interface_update.current.nodes | length == 1 + - cm_physical_interface_update.current.nodes[0] == nm_physical_interface_update.current.nodes[0] == "105" + - cm_physical_interface_update.previous.interfaces == nm_physical_interface_update.previous.interfaces == "1/3" + - cm_physical_interface_update.current.interfaces == nm_physical_interface_update.current.interfaces == "1/5,2/3-5" + - cm_physical_interface_update.previous.policy == nm_physical_interface_update.previous.policy + - cm_physical_interface_update.current.policy == nm_physical_interface_update.current.policy + - cm_physical_interface_update.current.policy != cm_physical_interface_update.previous.policy + - nm_physical_interface_update.current.policy != nm_physical_interface_update.previous.policy + - cm_physical_interface_update.previous.interfaceDescriptions == nm_physical_interface_update.previous.interfaceDescriptions == None + - cm_physical_interface_update.current.interfaceDescriptions | length == nm_physical_interface_update.current.interfaceDescriptions | length == 2 + - cm_physical_interface_update.current.interfaceDescriptions[0].interfaceID == nm_physical_interface_update.current.interfaceDescriptions[0].interfaceID == "2/5" + - cm_physical_interface_update.current.interfaceDescriptions[0].description == nm_physical_interface_update.current.interfaceDescriptions[0].description == "Updated interface description" + - cm_physical_interface_update.current.interfaceDescriptions[1].interfaceID == nm_physical_interface_update.current.interfaceDescriptions[1].interfaceID == "2/3" + - cm_physical_interface_update.current.interfaceDescriptions[1].description == nm_physical_interface_update.current.interfaceDescriptions[1].description == "test description" + - cm_physical_interface_update.current.uuid is defined + - nm_physical_interface_update_again is not changed + - nm_physical_interface_update_again.previous.name == nm_physical_interface_update_again.current.name == "ansible_physical_interface" + - nm_physical_interface_update_again.previous.description == nm_physical_interface_update_again.current.description == "Updated Physical Interface" + - nm_physical_interface_update_again.previous.policyGroupType == nm_physical_interface_update_again.current.policyGroupType == "physical" + - nm_physical_interface_update_again.previous.nodes | length == nm_physical_interface_update_again.current.nodes | length == 1 + - nm_physical_interface_update_again.previous.interfaces == nm_physical_interface_update_again.current.interfaces == "1/5,2/3-5" + - nm_physical_interface_update_again.previous.interfaceDescriptions | length == nm_physical_interface_update_again.current.interfaceDescriptions | length == 2 + - nm_physical_interface_update_again.previous.policy == nm_physical_interface_update_again.current.policy + - nm_physical_interface_update_again.previous.uuid == nm_physical_interface_update.current.uuid + - nm_physical_interface_update_again.current.uuid is defined + + - name: Update name Physical Interface of type 'breakout' with UUID and change breakout mode + cisco.mso.ndo_physical_interface: &update_physical_interface_uuid + <<: *add_physical_interface_breakout + name: "ansible_physical_interface_changed" + uuid: "{{ nm_physical_interface_2.current.uuid }}" + breakout_mode: 4x25G + register: update_physical_interface_name + + - name: Assert Physical Interface of type 'breakout' is updated breakout_mode and name with UUID + ansible.builtin.assert: + that: + - update_physical_interface_name is changed + - update_physical_interface_name.previous.name == "ansible_physical_interface_2" + - update_physical_interface_name.current.name == "ansible_physical_interface_changed" + - update_physical_interface_name.previous.breakoutMode == "4x10G" + - update_physical_interface_name.current.breakoutMode == "4x25G" + - update_physical_interface_name.previous.description == update_physical_interface_name.current.description == "Breakout type Physical Interface" + - update_physical_interface_name.previous.policyGroupType == update_physical_interface_name.current.policyGroupType == "breakout" + - update_physical_interface_name.previous.nodes | length == update_physical_interface_name.current.nodes | length == 2 + - update_physical_interface_name.previous.interfaces == update_physical_interface_name.current.interfaces == "1/1,1/2-4" + - update_physical_interface_name.previous.uuid is defined + - update_physical_interface_name.current.uuid is defined + + # QUERY + - name: Query one Physical Interface with name + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface + state: query + register: query_physical_interface_with_name + + - name: Query one Physical Interface with UUID + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + uuid: '{{update_physical_interface_name.current.uuid}}' + state: query + register: query_physical_interface_with_uuid + + - name: Query all physical interfaces in ansible_fabric_resource_policy_template + cisco.mso.ndo_physical_interface: &query_all_physical_interface + <<: *mso_info + template: ansible_fabric_resource_policy_template + state: query + register: query_all_physical_interfaces + + - name: Assert Physical Interface of type 'physical' is queried + ansible.builtin.assert: + that: + - query_physical_interface_with_name is not changed + - query_physical_interface_with_uuid is not changed + - query_all_physical_interfaces is not changed + - query_physical_interface_with_name.current.name == "ansible_physical_interface" + - query_physical_interface_with_name.current.description == "Updated Physical Interface" + - query_physical_interface_with_name.current.policyGroupType == "physical" + - query_physical_interface_with_name.current.nodes | length == 1 + - query_physical_interface_with_name.current.nodes[0] == "105" + - query_physical_interface_with_name.current.interfaces == "1/5,2/3-5" + - query_physical_interface_with_name.current.uuid is defined + - query_physical_interface_with_name.current.policy is defined + - query_physical_interface_with_name.current.interfaceDescriptions | length == 2 + - query_physical_interface_with_uuid.current.name == "ansible_physical_interface_changed" + - query_physical_interface_with_uuid.current.breakoutMode == "4x25G" + - query_physical_interface_with_uuid.current.description == "Breakout type Physical Interface" + - query_physical_interface_with_uuid.current.policyGroupType == "breakout" + - query_physical_interface_with_uuid.current.nodes | length == 2 + - query_physical_interface_with_uuid.current.nodes[0] == "101" + - query_physical_interface_with_uuid.current.nodes[1] == "102" + - query_physical_interface_with_uuid.current.interfaces == "1/1,1/2-4" + - query_physical_interface_with_uuid.current.interfaceDescriptions | length == 1 + - query_physical_interface_with_uuid.current.interfaceDescriptions[0].interfaceID == "1/1" + - query_physical_interface_with_uuid.current.interfaceDescriptions[0].description == "First interface" + - query_physical_interface_with_uuid.current.uuid is defined + - query_all_physical_interfaces.current | length == 3 + - query_all_physical_interfaces.current[0].name == "ansible_physical_interface" + - query_all_physical_interfaces.current[1].name == "ansible_physical_interface_with_policy" + - query_all_physical_interfaces.current[2].name == "ansible_physical_interface_changed" + + - name: Update interface descriptions of Physical Interface of type 'breakout' + cisco.mso.ndo_physical_interface: + <<: *update_physical_interface_uuid + interface_descriptions: + - interface_id: "1/1" + description: "Updated interface description" + - interface_id: "1/2" + description: "Updated interface description 2" + register: update_interface_description + + - name: Assert updating with interface descriptions of Physical Interface of type 'breakout' + ansible.builtin.assert: + that: + - update_interface_description is changed + - update_interface_description.previous.name == "ansible_physical_interface_changed" + - update_interface_description.current.name == "ansible_physical_interface_changed" + - update_interface_description.previous.breakoutMode == "4x25G" + - update_interface_description.current.breakoutMode == "4x25G" + - update_interface_description.previous.interfaceDescriptions | length == 1 + - update_interface_description.current.interfaceDescriptions | length == 2 + - update_interface_description.current.interfaceDescriptions[0].interfaceID == "1/1" + - update_interface_description.current.interfaceDescriptions[0].description == "Updated interface description" + - update_interface_description.current.interfaceDescriptions[1].interfaceID == "1/2" + - update_interface_description.current.interfaceDescriptions[1].description == "Updated interface description 2" + + # Remove interface descriptions + - name: Update Physical Interface of type 'breakout' by removing interface interface_descriptions + cisco.mso.ndo_physical_interface: + <<: *update_physical_interface_uuid + interface_descriptions: [] + register: rm_physical_interface_name_interface_descriptions + + - name: Assert Physical Interface of type 'breakout' is updated by removing interface interface_descriptions + ansible.builtin.assert: + that: + - rm_physical_interface_name_interface_descriptions is changed + - rm_physical_interface_name_interface_descriptions.previous.name == "ansible_physical_interface_changed" + - rm_physical_interface_name_interface_descriptions.current.name == "ansible_physical_interface_changed" + - rm_physical_interface_name_interface_descriptions.previous.breakoutMode == "4x25G" + - rm_physical_interface_name_interface_descriptions.current.breakoutMode == "4x25G" + - rm_physical_interface_name_interface_descriptions.previous.interfaceDescriptions | length == 2 + - rm_physical_interface_name_interface_descriptions.previous.uuid is defined + - rm_physical_interface_name_interface_descriptions.current.uuid is defined + - rm_physical_interface_name_interface_descriptions.current.interfaceDescriptions == None + + # Errors + + # Create errors + - name: Create Physical Interface without nodes + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface_invalid + interfaces: ['1/1'] + state: present + ignore_errors: true + register: create_physical_interface_without_nodes + + - name: Assert creating Physical Interface with invalid attributes + ansible.builtin.assert: + that: + - create_physical_interface_without_nodes is failed + - create_physical_interface_without_nodes.msg == "ERROR{{':'}} Missing 'nodes' for creating a Physical Interface." + + - name: Create Physical Interface without_interface_type + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface_invalid + nodes: 103 + interfaces: ['1/1'] + state: present + ignore_errors: true + register: create_physical_interface_without_interface_type + + - name: Assert creating Physical Interface with invalid attributes + ansible.builtin.assert: + that: + - create_physical_interface_without_interface_type is failed + - create_physical_interface_without_interface_type.msg == "ERROR{{':'}} Missing Physical Interface type for creating a Physical Interface." + + # Update errors + - name: Update interfaces without updating the Physical Interface type + cisco.mso.ndo_physical_interface: + <<: *update_physical_interface + physical_interface_type: breakout + ignore_errors: true + register: create_physical_interface_invalid_type + + - name: Assert updating interfaces without updating the Physical Interface type + ansible.builtin.assert: + that: + - create_physical_interface_invalid_type is failed + - create_physical_interface_invalid_type.msg == "ERROR{{':'}} Physical Interface type cannot be changed." + + # DELETE + - name: Delete Physical Interface of type 'physical' (check mode) + cisco.mso.ndo_physical_interface: &delete_physical_interface + <<: *mso_info + template: ansible_fabric_resource_policy_template + name: ansible_physical_interface + state: absent + check_mode: true + register: cm_delete_physical_interface + + - name: Delete Physical Interface of type 'physical' + cisco.mso.ndo_physical_interface: + <<: *delete_physical_interface + register: nm_delete_physical_interface + + - name: Delete Physical Interface of type 'physical' again + cisco.mso.ndo_physical_interface: + <<: *delete_physical_interface + register: nm_delete_physical_interface_again + + - name: Assert Physical Interface of type 'physical' is deleted + ansible.builtin.assert: + that: + - cm_delete_physical_interface is changed + - nm_delete_physical_interface is changed + - cm_delete_physical_interface.current == nm_delete_physical_interface.current == {} + - cm_delete_physical_interface.previous == nm_delete_physical_interface.previous + - cm_delete_physical_interface.previous.name == nm_delete_physical_interface.previous.name == "ansible_physical_interface" + - cm_delete_physical_interface.previous.policyGroupType == nm_delete_physical_interface.previous.policyGroupType == "physical" + - cm_delete_physical_interface.previous.uuid is defined + - nm_delete_physical_interface_again is not changed + - nm_delete_physical_interface_again.previous == nm_delete_physical_interface_again.current == {} + - nm_delete_physical_interface_again.current.uuid is not defined + + - name: Delete Physical Interface of type 'breakout' with UUID + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + uuid: "{{ rm_physical_interface_name_interface_descriptions.current.uuid }}" + state: absent + register: delete_physical_interface_uuid + + - name: Assert Physical Interface of type 'breakout' is deleted with UUID + ansible.builtin.assert: + that: + - delete_physical_interface_uuid is changed + - delete_physical_interface_uuid.previous.name == "ansible_physical_interface_changed" + - delete_physical_interface_uuid.current == {} + - delete_physical_interface_uuid.previous.policyGroupType == "breakout" + - delete_physical_interface_uuid.previous.uuid is defined + + # Errors and no policies found + - name: Delete Physical Interface of type 'physical' when all are deleted + cisco.mso.ndo_physical_interface: + <<: *add_physical_interface3 + state: absent + register: nm_delete_last_physical_interface + + - name: Query all Physical Interface in the template when all are deleted + cisco.mso.ndo_physical_interface: + <<: *query_all_physical_interface + register: query_all_none + + - name: Update Physical Interface with non-existing UUID + cisco.mso.ndo_physical_interface: + <<: *mso_info + template: ansible_fabric_resource_policy_template + uuid: non-existing-uuid + state: present + ignore_errors: true + register: update_non_existing_uuid + + - name: Assert no Physical Interface found + assert: + that: + - nm_delete_last_physical_interface is changed + - nm_delete_last_physical_interface.previous.name == "ansible_physical_interface_with_policy" + - nm_delete_last_physical_interface.previous.policyGroupType == "physical" + - nm_delete_last_physical_interface.current == {} + - query_all_none is not changed + - query_all_none.current == [] + - update_non_existing_uuid is failed + - update_non_existing_uuid.msg == "Physical Interface Profile with the UUID{{":"}} 'non-existing-uuid' not found" + + # CLEANUP TEMPLATE + + - name: Ensure fabric resource policy template do not exist + cisco.mso.ndo_template: + <<: *template_absent + + - name: Ensure fabric policy template does not exist + cisco.mso.ndo_template: + <<: *fabric_policy_template_absent