From df9dbb6751d823bcab09fbdb711784183e0a19c7 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Mon, 14 Oct 2024 10:15:03 -0700 Subject: [PATCH 1/6] [minor_changes] Adding new module for physical interface (object: interfaceProfiles) --- plugins/modules/ndo_physical_interface.py | 422 ++++++++++++++++ .../targets/ndo_physical_interface/aliases | 2 + .../ndo_physical_interface/tasks/main.yml | 450 ++++++++++++++++++ 3 files changed, 874 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/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 From 7b0f10bc7224ddef166739639b22e9c5dc46b907 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Wed, 6 Nov 2024 11:23:32 -0800 Subject: [PATCH 2/6] [ignore_changes] Fixing documentation. --- plugins/module_utils/template.py | 2 + plugins/modules/ndo_physical_interface.py | 273 ++++++++---------- .../ndo_physical_interface/tasks/main.yml | 240 +++++++++------ 3 files changed, 274 insertions(+), 241 deletions(-) diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index aaaab21d..4a849719 100644 --- a/plugins/module_utils/template.py +++ b/plugins/module_utils/template.py @@ -102,6 +102,8 @@ def get_object_by_key_value_pairs(self, object_description, search_list, kv_list :param fail_module: When match is not found fail the ansible module. -> Bool :return: The object. -> Dict | None """ + if search_list is None or kv_list is None: + return None match, existing = self.get_object_from_list(search_list, kv_list) if not match and fail_module: msg = "Provided {0} with '{1}' not matching existing object(s): {2}".format(object_description, kv_list, ", ".join(existing)) diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py index b2ee2bfb..99b7c74e 100644 --- a/plugins/modules/ndo_physical_interface.py +++ b/plugins/modules/ndo_physical_interface.py @@ -14,10 +14,10 @@ DOCUMENTATION = r""" --- module: ndo_physical_interface -short_description: Manage physical interface on Cisco Nexus Dashboard Orchestrator (NDO). +short_description: Manage Physical Interfaces 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. +- 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: @@ -27,24 +27,24 @@ - The template must be a fabric resource policy template. type: str required: true - physical_interface: + name: description: - - The name of the physical interface. + - The name of the Physical Interface. type: str - aliases: [ name ] - physical_interface_uuid: + aliases: [ physical_interface ] + uuid: description: - - The UUID of the physical interface. - - This parameter is required when the O(physical_interface) needs to be updated. + - The UUID of the Physical Interface. + - This parameter is required when the O(name) needs to be updated. type: str - aliases: [ uuid ] + aliases: [ physical_interface_uuid ] description: description: - - The description of the physical interface. + - The description of the Physical Interface. type: str nodes: description: - - The node IDs where the physical interface policy will be deployed. + - The node IDs where the Physical Interface policy will be deployed. type: list elements: int interfaces: @@ -61,7 +61,7 @@ physical_policy_uuid: description: - The UUID of the Interface Setting Policy. - - This is only required when creating a new Port Channel Interface. + - 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 @@ -75,23 +75,23 @@ suboptions: name: description: - - The name of the Port Channel Interface Setting Policy. + - The name of the Interface Setting Policy. type: str template: description: - - The name of the template in which is referred the Port Channel Interface Policy Group. + - 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: - - The breakout mode enabled splitting of the ethernet ports. + - 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 nodes you provided. + - The interface settings defined in the interface settings policy will be applied to the interfaces on the node IDs configured in C(nodes). type: list elements: dict suboptions: @@ -124,83 +124,83 @@ """ EXAMPLES = r""" -- name: Create an physical interface physical_interface_type physical +- 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 - 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 + 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 + +- 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 - 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 + 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 - name: Query all physical interfaces cisco.mso.ndo_physical_interface: - host: mso_host - username: admin - password: SomeSecretPassword - template: ansible_test_template - state: query + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + state: query register: query_all -- name: Query a specific physical interface with name +- 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 + 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 +- 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 + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + uuid: "{{ query_one_name.current.uuid }}" + state: query register: query_one_uuid -- name: Delete an physical interface with name +- name: Delete a 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 + 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 - physical_interface_uuid: ansible_test_physical_interface_uuid - state: absent + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test_template + uuid: "{{ query_one_uuid.current.uuid }}" + state: absent """ RETURN = r""" @@ -211,6 +211,7 @@ 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 import copy @@ -221,8 +222,8 @@ def main(): 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"]), + 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"), @@ -253,8 +254,8 @@ def main(): 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], + ["state", "present", ["name", "uuid"], True], + ["state", "absent", ["name", "uuid"], True], ], mutually_exclusive=[("physical_policy", "breakout_mode"), ("physical_policy", "physical_policy_uuid")], ) @@ -262,8 +263,8 @@ def main(): mso = MSOModule(module) template = module.params.get("template") - physical_interface = module.params.get("physical_interface") - physical_interface_uuid = module.params.get("physical_interface_uuid") + name = module.params.get("name") + uuid = module.params.get("uuid") description = module.params.get("description") nodes = module.params.get("nodes") if nodes: @@ -284,83 +285,80 @@ def main(): 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 = [] + existing_physical_interfaces = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}).get("interfaceProfiles", []) - if physical_interface or physical_interface_uuid: + if state in ["query", "absent"] and not existing_physical_interfaces: + mso.exit_json() + elif state == "query" and not (name or uuid): + mso.existing = existing_physical_interfaces + elif existing_physical_interfaces and (name or 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)], + object_description, existing_physical_interfaces, [KVPair("uuid", uuid) if uuid else KVPair("name", name)] ) if match: + physical_policy_path = "/fabricResourceTemplate/template/interfaceProfiles/{0}".format(match.index) mso.existing = mso.previous = copy.deepcopy(match.details) - else: - mso.existing = mso.previous = existing_interface_policies 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: # 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 mso.existing: + proposed_payload = copy.deepcopy(match.details) 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 name and match.details.get("name") != name: + ops.append(dict(op="replace", path=physical_policy_path + "/name", value=name)) + proposed_payload["name"] = name 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 + ops.append(dict(op="replace", path=physical_policy_path + "/description", value=description)) + proposed_payload["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 + ops.append(dict(op="replace", path=physical_policy_path + "/nodes", value=nodes)) + proposed_payload["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 + ops.append(dict(op="replace", path=physical_policy_path + "/policy", value=physical_policy_uuid)) + proposed_payload["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 + ops.append(dict(op="replace", path=physical_policy_path + "/breakoutMode", value=breakout_mode)) + proposed_payload["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 + ops.append(dict(op="replace", path=physical_policy_path + "/interfaces", value=interfaces)) + proposed_payload["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 + updated_interface_descriptions = format_interface_descriptions(interface_descriptions, "") + ops.append(dict(op="replace", path=physical_policy_path + "/interfaceDescriptions", value=updated_interface_descriptions)) + proposed_payload["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))) + ops.append(dict(op="remove", path=physical_policy_path + "/interfaceDescriptions")) - mso.sanitize(match.details) + mso.sanitize(proposed_payload, 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.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"), + "name": name, "nodes": nodes, "interfaces": interfaces, "policyGroupType": physical_interface_type, @@ -376,47 +374,28 @@ def main(): 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))) + payload["interfaceDescriptions"] = format_interface_descriptions(interface_descriptions, "") mso.sanitize(payload) - - mso.existing = mso.proposed + ops.append(dict(op="add", path="/fabricResourceTemplate/template/interfaceProfiles/-", value=mso.sent)) elif state == "absent": if match: - ops.append(dict(op="remove", path="{0}/{1}".format(path, match.index))) + ops.append(dict(op="remove", path=physical_policy_path)) 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)], - ) + physical_interfaces = response.get("fabricResourceTemplate", {}).get("template", {}).get("interfaceProfiles", []) + 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 + mso.existing = match.details # When the state is present else: - mso.existing = {} - elif module.check_mode and state != "query": + 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() -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/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml index 49dfe1db..b99bf5d3 100644 --- a/tests/integration/targets/ndo_physical_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -55,49 +55,48 @@ <<: *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 + # - 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) + # 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 + name: 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 }} + physical_policy_uuid: "8a5b5701-5cfc-4b27-9c73-4c42504b1999" # '{{ add_interface_policy_group.current.uuid }}' state: present check_mode: true register: cm_physical_interface - - name: Create physical interface of type 'physical' + - 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 + - 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 + - name: Assert Physical Interface of type 'physical' is created ansible.builtin.assert: that: - cm_physical_interface is changed @@ -120,39 +119,39 @@ - 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) + # - 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 - physical_interface: ansible_physical_interface_2 - description: "Breakout type physical interface" + name: ansible_physical_interface_2 + description: "Breakout type Physical Interface" nodes: [101, 102] interfaces: - 1/1 @@ -166,19 +165,19 @@ check_mode: true register: cm_physical_interface_2 - - name: Create physical interface of type 'breakout' + - 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 + - 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.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" @@ -191,16 +190,15 @@ - nm_physical_interface_2.current.uuid is defined # UPDATE - - name: Update physical interface of type 'physical' (check mode) + - name: Update Physical Interface of type 'physical' (check mode) cisco.mso.ndo_physical_interface: &update_physical_interface <<: *add_physical_interface - description: "Updated 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 }} + physical_policy_uuid: "7513267f-f239-40cd-8c6f-3954734972df" # '{{ add_interface_policy_group_2.current.uuid }}' interface_descriptions: - interface_id: "2/5" description: "Updated interface description" @@ -210,17 +208,17 @@ check_mode: true register: cm_physical_interface_update - - name: Update physical interface of type 'physical' + - 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 + - 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 + - name: Assert Physical Interface of type 'physical' is updated ansible.builtin.assert: that: - cm_physical_interface_update is changed @@ -229,7 +227,7 @@ - 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.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 @@ -250,7 +248,7 @@ - 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.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" @@ -259,15 +257,15 @@ - 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 + - 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 }}" + 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 + - 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 @@ -275,7 +273,7 @@ - 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.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" @@ -283,37 +281,37 @@ - update_physical_interface_name.current.uuid is defined # QUERY - - name: Query one physical interface with name + - 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 + name: ansible_physical_interface state: query register: query_physical_interface_with_name - - name: Query one physical interface with UUID + - 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}}' + 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: + 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 + - 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.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" @@ -323,7 +321,7 @@ - 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.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" @@ -333,16 +331,16 @@ - 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 | length == 3 # Remove interface descriptions - - name: Update name physical interface of type 'breakout' by removing interface 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 + - 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 @@ -358,74 +356,74 @@ # Errors # Create errors - - name: Create physical interface without nodes + - 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 + 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 + - 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 + - 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 + 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 + - 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." + - 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 + - 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 + - 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) + - 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 + name: ansible_physical_interface state: absent check_mode: true register: cm_delete_physical_interface - - name: Delete physical interface of type 'physical' + - 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 + - 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 + - name: Assert Physical Interface of type 'physical' is deleted ansible.builtin.assert: that: - cm_delete_physical_interface is changed @@ -439,6 +437,60 @@ - nm_delete_physical_interface_again.previous == nm_delete_physical_interface_again.current == {} - nm_delete_physical_interface_again.current.uuid is not defined + - name: Query all tests + cisco.mso.ndo_physical_interface: + <<: *query_all_physical_interface + + - 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 From 6a125293de2a2bae51d59cc5e47e0648afb19930 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Tue, 10 Dec 2024 00:44:17 -0800 Subject: [PATCH 3/6] [ignore_changes] Additional test case usage uncommented after rebasing. --- plugins/modules/ndo_physical_interface.py | 2 +- .../ndo_physical_interface/tasks/main.yml | 104 +++++++++--------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py index 99b7c74e..94f65b84 100644 --- a/plugins/modules/ndo_physical_interface.py +++ b/plugins/modules/ndo_physical_interface.py @@ -305,7 +305,7 @@ def main(): 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: # check this part and see if this is required or use mutually_exclusive + 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")) diff --git a/tests/integration/targets/ndo_physical_interface/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml index b99bf5d3..75c33aea 100644 --- a/tests/integration/targets/ndo_physical_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -52,23 +52,23 @@ - name: Create a fabric policy template cisco.mso.ndo_template: - <<: *template_absent + <<: *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 + - 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 @@ -81,7 +81,7 @@ nodes: 103 interfaces: ['1/3'] physical_interface_type: physical - physical_policy_uuid: "8a5b5701-5cfc-4b27-9c73-4c42504b1999" # '{{ add_interface_policy_group.current.uuid }}' + physical_policy_uuid: '{{ add_interface_policy_group.current.uuid }}' state: present check_mode: true register: cm_physical_interface @@ -119,32 +119,32 @@ - 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 '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 @@ -198,7 +198,7 @@ interfaces: - 1/5 - 2/3-5 - physical_policy_uuid: "7513267f-f239-40cd-8c6f-3954734972df" # '{{ add_interface_policy_group_2.current.uuid }}' + physical_policy_uuid: '{{ add_interface_policy_group_2.current.uuid }}' interface_descriptions: - interface_id: "2/5" description: "Updated interface description" @@ -459,11 +459,11 @@ - 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: 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: @@ -482,10 +482,10 @@ - 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 == {} + - 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 From 3eb66cbc25cbdf17cfbc28e6fe48be052187c3d1 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Fri, 13 Dec 2024 06:38:11 -0800 Subject: [PATCH 4/6] [ignore_changes] Made changes aligned with the new fuctional changes in utils file. --- plugins/modules/ndo_physical_interface.py | 95 +++++++------------ .../ndo_physical_interface/tasks/main.yml | 4 +- 2 files changed, 37 insertions(+), 62 deletions(-) diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py index 94f65b84..da3aba06 100644 --- a/plugins/modules/ndo_physical_interface.py +++ b/plugins/modules/ndo_physical_interface.py @@ -214,6 +214,7 @@ 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 @@ -284,22 +285,21 @@ def main(): 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", []) - if state in ["query", "absent"] and not existing_physical_interfaces: - mso.exit_json() - elif state == "query" and not (name or uuid): - mso.existing = existing_physical_interfaces - elif existing_physical_interfaces and (name or uuid): + 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)] + object_description, + existing_physical_interfaces, + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], ) if match: - physical_policy_path = "/fabricResourceTemplate/template/interfaceProfiles/{0}".format(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: @@ -311,44 +311,38 @@ def main(): physical_policy_uuid = fabric_policy_template.get_interface_policy_group_uuid(physical_policy.get("name")) if mso.existing: - proposed_payload = copy.deepcopy(match.details) - - if physical_interface_type and match.details.get("policyGroupType") != physical_interface_type: - mso.fail_json(msg="ERROR: Physical Interface type cannot be changed.") + proposed_payload = copy.deepcopy(mso.existing) + mso_values_remove = list() + + mso_values = dict( + name=name, + description=description, + nodes=nodes, + interfaces=interfaces, + policyGroupType=physical_interface_type, + ) - if name and match.details.get("name") != name: - ops.append(dict(op="replace", path=physical_policy_path + "/name", value=name)) - proposed_payload["name"] = name + if physical_interface_type == "physical" and physical_policy_uuid: + mso_values["policy"] = physical_policy_uuid - if description is not None and match.details.get("description") != description: - ops.append(dict(op="replace", path=physical_policy_path + "/description", value=description)) - proposed_payload["description"] = description + if physical_interface_type == "breakout" and breakout_mode: + mso_values["breakoutMode"] = breakout_mode - if nodes and match.details.get("nodes") != nodes: - ops.append(dict(op="replace", path=physical_policy_path + "/nodes", value=nodes)) - proposed_payload["nodes"] = nodes + if interface_descriptions: + mso_values["interfaceDescriptions"] = format_interface_descriptions(interface_descriptions, "") - if physical_policy_uuid and match.details.get("policy") != physical_policy_uuid: - ops.append(dict(op="replace", path=physical_policy_path + "/policy", value=physical_policy_uuid)) - proposed_payload["policy"] = physical_policy_uuid + if match: - if breakout_mode and match.details.get("breakoutMode") != breakout_mode: - ops.append(dict(op="replace", path=physical_policy_path + "/breakoutMode", value=breakout_mode)) - proposed_payload["breakoutMode"] = breakout_mode + if physical_interface_type and match.details.get("policyGroupType") != physical_interface_type: + mso.fail_json(msg="ERROR: Physical Interface type cannot be changed.") - if interfaces and interfaces != match.details.get("interfaces"): - ops.append(dict(op="replace", path=physical_policy_path + "/interfaces", value=interfaces)) - proposed_payload["interfaces"] = interfaces + update_path = "{0}/{1}".format(path, match.index) - # Node changes are not reflected on UI - if interface_descriptions and match.details.get("interfaceDescriptions") != interface_descriptions: - updated_interface_descriptions = format_interface_descriptions(interface_descriptions, "") - ops.append(dict(op="replace", path=physical_policy_path + "/interfaceDescriptions", value=updated_interface_descriptions)) - proposed_payload["interfaceDescriptions"] = updated_interface_descriptions - elif interface_descriptions == [] and match.details.get("interfaceDescriptions"): - ops.append(dict(op="remove", path=physical_policy_path + "/interfaceDescriptions")) + if interface_descriptions == [] and proposed_payload.get("interfaceDescriptions"): + mso_values_remove.append("interfaceDescriptions") - mso.sanitize(proposed_payload, collate=True) + append_update_ops_data(ops, match.details, update_path, mso_values, mso_values_remove) + mso.sanitize(match.details, collate=True) else: if not nodes: @@ -357,31 +351,12 @@ def main(): if not physical_interface_type: mso.fail_json(msg=("ERROR: Missing Physical Interface type for creating a Physical Interface.")) - payload = { - "name": name, - "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"] = format_interface_descriptions(interface_descriptions, "") - - mso.sanitize(payload) - ops.append(dict(op="add", path="/fabricResourceTemplate/template/interfaceProfiles/-", value=mso.sent)) + 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_policy_path)) + 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) diff --git a/tests/integration/targets/ndo_physical_interface/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml index 75c33aea..f5c1bf3e 100644 --- a/tests/integration/targets/ndo_physical_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -334,7 +334,7 @@ # - query_all_physical_interfaces.current | length == 3 # Remove interface descriptions - - name: Update name Physical Interface of type 'breakout' by removing interface 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: [] @@ -487,7 +487,7 @@ - nm_delete_last_physical_interface.previous.policyGroupType == "physical" - nm_delete_last_physical_interface.current == {} - query_all_none is not changed - - query_all_none.current == {} + - query_all_none.current == None - update_non_existing_uuid is failed - update_non_existing_uuid.msg == "Physical Interface Profile with the UUID{{":"}} 'non-existing-uuid' not found" From 6bb72e87aea56d43ff434cd13723509232ba0ae7 Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Wed, 8 Jan 2025 16:36:06 -0800 Subject: [PATCH 5/6] [ignore_changes] Removed extra tasks from tests and updated documentation. --- plugins/modules/ndo_physical_interface.py | 19 +++++---- .../ndo_physical_interface/tasks/main.yml | 40 +++++++++++++++---- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py index da3aba06..18005e4b 100644 --- a/plugins/modules/ndo_physical_interface.py +++ b/plugins/modules/ndo_physical_interface.py @@ -92,6 +92,8 @@ 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: @@ -297,6 +299,7 @@ def main(): [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 @@ -310,10 +313,6 @@ def main(): fabric_policy_template.validate_template("fabricPolicy") physical_policy_uuid = fabric_policy_template.get_interface_policy_group_uuid(physical_policy.get("name")) - if mso.existing: - proposed_payload = copy.deepcopy(mso.existing) - mso_values_remove = list() - mso_values = dict( name=name, description=description, @@ -329,19 +328,19 @@ def main(): mso_values["breakoutMode"] = breakout_mode if interface_descriptions: - mso_values["interfaceDescriptions"] = format_interface_descriptions(interface_descriptions, "") + mso_values["interfaceDescriptions"] = format_interface_descriptions(mso, interface_descriptions, "") - if match: + 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.") - update_path = "{0}/{1}".format(path, match.index) - if interface_descriptions == [] and proposed_payload.get("interfaceDescriptions"): mso_values_remove.append("interfaceDescriptions") - append_update_ops_data(ops, match.details, update_path, mso_values, mso_values_remove) + append_update_ops_data(ops, match.details, physical_interface_attrs_path, mso_values, mso_values_remove) mso.sanitize(match.details, collate=True) else: @@ -356,7 +355,7 @@ def main(): elif state == "absent": if match: - ops.append(dict(op="remove", path="{0}/{1}".format(path, match.index))) + 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) diff --git a/tests/integration/targets/ndo_physical_interface/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml index f5c1bf3e..ddfc35d4 100644 --- a/tests/integration/targets/ndo_physical_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -27,8 +27,8 @@ state: query register: version -- name: Execute tasks only for MSO version > 4.3 - when: version.current.version is version('4.3', '>=') +- 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 @@ -331,7 +331,35 @@ - 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 | 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 @@ -348,7 +376,7 @@ - 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.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 @@ -437,10 +465,6 @@ - nm_delete_physical_interface_again.previous == nm_delete_physical_interface_again.current == {} - nm_delete_physical_interface_again.current.uuid is not defined - - name: Query all tests - cisco.mso.ndo_physical_interface: - <<: *query_all_physical_interface - - name: Delete Physical Interface of type 'breakout' with UUID cisco.mso.ndo_physical_interface: <<: *mso_info From fc5485ac05ab338a00d5085af2cd17b26ec51def Mon Sep 17 00:00:00 2001 From: anvitha-jain Date: Mon, 13 Jan 2025 11:21:36 -0800 Subject: [PATCH 6/6] [ignore_changes] get_object_by_key_value_pairs function changes removed from template.py and localized in module. --- plugins/module_utils/template.py | 2 -- plugins/modules/ndo_physical_interface.py | 8 +++++--- .../targets/ndo_physical_interface/tasks/main.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index 4a849719..aaaab21d 100644 --- a/plugins/module_utils/template.py +++ b/plugins/module_utils/template.py @@ -102,8 +102,6 @@ def get_object_by_key_value_pairs(self, object_description, search_list, kv_list :param fail_module: When match is not found fail the ansible module. -> Bool :return: The object. -> Dict | None """ - if search_list is None or kv_list is None: - return None match, existing = self.get_object_from_list(search_list, kv_list) if not match and fail_module: msg = "Provided {0} with '{1}' not matching existing object(s): {2}".format(object_description, kv_list, ", ".join(existing)) diff --git a/plugins/modules/ndo_physical_interface.py b/plugins/modules/ndo_physical_interface.py index 18005e4b..20bb64b2 100644 --- a/plugins/modules/ndo_physical_interface.py +++ b/plugins/modules/ndo_physical_interface.py @@ -139,6 +139,7 @@ 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: @@ -156,6 +157,7 @@ - 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: @@ -182,7 +184,7 @@ username: admin password: SomeSecretPassword template: ansible_test_template - uuid: "{{ query_one_name.current.uuid }}" + uuid: "{{ create_physical_interface_type_breakout.current.uuid }}" state: query register: query_one_uuid @@ -290,7 +292,7 @@ def main(): object_description = "Physical Interface Profile" path = "/fabricResourceTemplate/template/interfaceProfiles" - existing_physical_interfaces = mso_template.template.get("fabricResourceTemplate", {}).get("template", {}).get("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( @@ -359,7 +361,7 @@ def main(): 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", []) + 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 diff --git a/tests/integration/targets/ndo_physical_interface/tasks/main.yml b/tests/integration/targets/ndo_physical_interface/tasks/main.yml index ddfc35d4..34cabea8 100644 --- a/tests/integration/targets/ndo_physical_interface/tasks/main.yml +++ b/tests/integration/targets/ndo_physical_interface/tasks/main.yml @@ -511,7 +511,7 @@ - nm_delete_last_physical_interface.previous.policyGroupType == "physical" - nm_delete_last_physical_interface.current == {} - query_all_none is not changed - - query_all_none.current == None + - 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"