From e253d7bd76cc86f6bce27b018504c6ba4f606fa8 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Mon, 14 Oct 2024 10:15:03 -0700 Subject: [PATCH] [minor_changes] Adding new module for physical interface (object: interfaceProfiles) --- plugins/module_utils/template.py | 11 + plugins/modules/ndo_physical_interface.py | 422 ++++++++++++++++ .../targets/ndo_physical_interface/aliases | 2 + .../ndo_physical_interface/tasks/main.yml | 450 ++++++++++++++++++ 4 files changed, 885 insertions(+) create mode 100644 plugins/modules/ndo_physical_interface.py create mode 100644 tests/integration/targets/ndo_physical_interface/aliases create mode 100644 tests/integration/targets/ndo_physical_interface/tasks/main.yml diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index 55d235b4..4392ce92 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_policies = 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_policies, kv_list, fail_module=True) + return match.details.get("uuid") diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py new file mode 100644 index 00000000..b2ee2bfb --- /dev/null +++ b/plugins/modules/ndo_physical_interface.py @@ -0,0 +1,422 @@ +#!/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 interface on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage physical interface on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.1 (NDO v4.3) 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 + physical_interface: + description: + - The name of the physical interface. + type: str + aliases: [ name ] + physical_interface_uuid: + description: + - The UUID of the physical interface. + - This parameter is required when the O(physical_interface) needs to be updated. + type: str + aliases: [ 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 Port Channel Interface. + - 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 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_policy_group, interface_setting ] + breakout_mode: + description: + - The breakout mode enabled splitting of the ethernet 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 nodes you provided. + 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 an physical interface physical_interface_type physical + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + physical_interface: 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 + +- name: Create an physical interface physical_interface_type breakout + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + physical_interface: 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 + +- 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 + physical_interface: 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 + physical_interface_uuid: ansible_test_physical_interface_uuid + state: query + register: query_one_uuid + +- name: Delete an physical interface with name + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + physical_interface: ansible_test_physical_interface_physical + state: absent + +- name: Delete an physical interface with UUID + cisco.mso.ndo_physical_interface: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + physical_interface_uuid: ansible_test_physical_interface_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, +) +from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate, KVPair +import copy + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dict( + template=dict(type="str", required=True), + physical_interface=dict(type="str", aliases=["name"]), + physical_interface_uuid=dict(type="str", aliases=["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", ["physical_interface", "physical_interface_uuid"], True], + ["state", "absent", ["physical_interface", "physical_interface_uuid"], True], + ], + mutually_exclusive=[("physical_policy", "breakout_mode"), ("physical_policy", "physical_policy_uuid")], + ) + + mso = MSOModule(module) + + template = module.params.get("template") + physical_interface = module.params.get("physical_interface") + physical_interface_uuid = module.params.get("physical_interface_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") + + path = "/fabricResourceTemplate/template/interfaceProfiles" + object_description = "Physical Interface Profile" + + existing_interface_policies = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}) + if existing_interface_policies.get("interfaceProfiles") is not None: + existing_interface_policies = existing_interface_policies.get("interfaceProfiles") + else: + existing_interface_policies = [] + + if physical_interface or physical_interface_uuid: + match = mso_template.get_object_by_key_value_pairs( + object_description, + existing_interface_policies, + [KVPair("uuid", physical_interface_uuid) if physical_interface_uuid else KVPair("name", physical_interface)], + ) + if match: + mso.existing = mso.previous = copy.deepcopy(match.details) + else: + mso.existing = mso.previous = existing_interface_policies + + if state == "present": + + if physical_policy and not physical_policy_uuid: # check this part and see if this is required or use mutually_exclusive + 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")) + + if match: + + if physical_interface_type and match.details.get("policyGroupType") != physical_interface_type: + mso.fail_json(msg="ERROR: Physical Interface type cannot be changed.") + + if physical_interface and match.details.get("name") != physical_interface: + ops.append(dict(op="replace", path="{0}/{1}/name".format(path, match.index), value=physical_interface)) + match.details["name"] = physical_interface + + if description is not None 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 nodes and match.details.get("nodes") != nodes: + ops.append(dict(op="replace", path="{0}/{1}/nodes".format(path, match.index), value=nodes)) + match.details["nodes"] = nodes + + if physical_policy_uuid and match.details.get("policy") != physical_policy_uuid: + ops.append(dict(op="replace", path="{0}/{1}/policy".format(path, match.index), value=physical_policy_uuid)) + match.details["policy"] = physical_policy_uuid + + if breakout_mode and match.details.get("breakoutMode") != breakout_mode: + ops.append(dict(op="replace", path="{0}/{1}/breakoutMode".format(path, match.index), value=breakout_mode)) + match.details["breakoutMode"] = breakout_mode + + if interfaces and interfaces != match.details.get("interfaces"): + ops.append(dict(op="replace", path="{0}/{1}/interfaces".format(path, match.index), value=interfaces)) + match.details["interfaces"] = interfaces + + # Node changes are not reflected on UI + if interface_descriptions and match.details.get("interfaceDescriptions") != interface_descriptions: + updated_interface_descriptions = validate_interface_description(interface_descriptions) + ops.append(dict(op="replace", path="{0}/{1}/interfaceDescriptions".format(path, match.index), value=updated_interface_descriptions)) + match.details["interfaceDescriptions"] = updated_interface_descriptions + elif interface_descriptions == [] and match.details.get("interfaceDescriptions"): + ops.append(dict(op="remove", path="{0}/{1}/interfaceDescriptions".format(path, match.index))) + + mso.sanitize(match.details) + + 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.")) + + payload = { + "name": physical_interface, + "templateId": mso_template.template.get("templateId"), + "schemaId": mso_template.template.get("schemaId"), + "nodes": nodes, + "interfaces": interfaces, + "policyGroupType": physical_interface_type, + } + + if description: + payload["description"] = description + + if physical_interface_type == "physical" and physical_policy_uuid: + payload["policy"] = physical_policy_uuid + + if physical_interface_type == "breakout" and breakout_mode: + payload["breakoutMode"] = breakout_mode + + if interface_descriptions: + payload["interfaceDescriptions"] = validate_interface_description(interface_descriptions) + + ops.append(dict(op="add", path="{0}/-".format(path), value=copy.deepcopy(payload))) + + mso.sanitize(payload) + + mso.existing = mso.proposed + + 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) + interface_policies = response.get("fabricResourceTemplate", {}).get("template", {}).get("interfaceProfiles", []) + match = mso_template.get_object_by_key_value_pairs( + object_description, + interface_policies, + [KVPair("uuid", physical_interface_uuid) if physical_interface_uuid else KVPair("name", physical_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() + + +def validate_interface_description(interface_descriptions): + interface_descriptions = [ + { + "interfaceID": interface_description.get("interface_id"), + "description": interface_description.get("description"), + } + for interface_description in interface_descriptions + ] + + return interface_descriptions + + +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..49dfe1db --- /dev/null +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -0,0 +1,450 @@ +# 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.3 + when: version.current.version is version('4.3', '>=') + 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: + <<: *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 + interface_policy_group: 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 + interface_policy_group: 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 + physical_interface: ansible_physical_interface + nodes: 103 + interfaces: ['1/3'] + physical_interface_type: physical + # physical_policy_uuid: "164e7885-f4b8-419d-9a8f-aa77a0368c44" + 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: + <<: *mso_info + template: ansible_fabric_resource_policy_template + physical_interface: 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 + physical_interface: 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: "7513267f-f239-40cd-8c6f-3954734972df" + 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 + physical_interface: "ansible_physical_interface_changed" + physical_interface_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 + physical_interface: 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 + physical_interface_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: + <<: *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 + + # Remove interface descriptions + - name: Update name 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 == 1 + - 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 + physical_interface: 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 + physical_interface: 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 + physical_interface: 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 + + # 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