From f14b04f3672241c81790b0cf23044aab88c8ddfe Mon Sep 17 00:00:00 2001 From: samitab Date: Wed, 27 Nov 2024 00:55:04 +1000 Subject: [PATCH 1/5] [minor_change] Added ndo_mcp_global_policy for fabric policy global MCP configuration. --- plugins/modules/ndo_mcp_global_policy.py | 341 ++++++++++++++++++ .../targets/ndo_mcp_global_policy/aliases | 2 + .../ndo_mcp_global_policy/tasks/main.yml | 280 ++++++++++++++ 3 files changed, 623 insertions(+) create mode 100644 plugins/modules/ndo_mcp_global_policy.py create mode 100644 tests/integration/targets/ndo_mcp_global_policy/aliases create mode 100644 tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml diff --git a/plugins/modules/ndo_mcp_global_policy.py b/plugins/modules/ndo_mcp_global_policy.py new file mode 100644 index 00000000..546b2004 --- /dev/null +++ b/plugins/modules/ndo_mcp_global_policy.py @@ -0,0 +1,341 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) + +# 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_mcp_global_policy +short_description: Manage the MCP Global Policy in a Fabric Policy Template on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage the MisCabling Protocol (MCP) Global Policy in a Fabric Policy Template on Cisco Nexus Dashboard Orchestrator (NDO). +- There can only be a single MCP Global Policy in a Fabric Policy Template. +- This module is only supported on ND v3.1 (NDO v4.3) and later. +author: +- Samita Bhattacharjee (@samiib) +options: + template: + description: + - The name of the template. + - The template must be a Fabric Policy template. + type: str + aliases: [ fabric_template ] + required: true + name: + description: + - The name of the MCP Global Policy. + type: str + aliases: [ mcp_global_policy ] + uuid: + description: + - The UUID of the MCP Global Policy. + type: str + aliases: [ mcp_global_policy_uuid ] + description: + description: + - The description of the MCP Global Policy. + - Providing an empty string will remove the O(description="") from the MCP Global Policy. + type: str + admin_state: + description: + - The administrative state of the MCP Global Policy. + - Defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + key: + description: + - The key of the MCP Global Policy. + type: str + per_vlan: + description: + - Enable or disable MCP packets being sent to each End Point Group (EPG). + - Defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + aliases: [ per_epg, mcp_pdu_per_vlan ] + loop_detection_factor: + description: + - The amount of MCP packets that will be received before port disable loop protection action takes place. + - Defaults to 3 when unset during creation. + - The value must be between 0 and 255. + type: int + aliases: [ loop_factor, loop_detection_mult_factor ] + port_disable: + description: + - Enable or disable port disabling when MCP packets are recived. + - Defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + aliases: [ port_disable_protection ] + initial_delay_time: + description: + - The MCP initial delay time in seconds. + - Defaults to 180 when unset during creation. + - The value must be between 0 and 1800. + type: int + aliases: [ initial_delay ] + transmission_frequency_sec: + description: + - The MCP transmission frequency in seconds. + - Defaults to 2 when unset during creation. + - The value must be between 0 and 300. + type: int + aliases: [ tx_freq ] + transmission_frequency_msec: + description: + - The MCP transmission frequency in milliseconds. + - Defaults to 0 when unset during creation. + - The value must be between 0 and 999. + type: int + aliases: [ tx_freq_ms ] + 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 Fabric Policy template. +- Attempts to create any additional MCP Global Policies will only update the existing + object in the Fabric Policy template. +seealso: +- module: cisco.mso.ndo_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create the MCP Global Policy object + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + name: mcp_global_policy_1 + key: cisco + state: present + register: mcp_global_policy_1 + +- name: Create the MCP Global Policy object with all attributes + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + name: mcp_global_policy_1 + description: A Global MCP Policy + key: cisco + admin_state: enabled + per_vlan: enabled + loop_detection_factor: 3 + port_disable: enabled + initial_delay_time: 180 + transmission_frequency_sec: 2 + transmission_frequency_msec: 10 + state: present + register: mcp_global_policy_1 + +- name: Update the MCP Global Policy object with UUID + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + name: mcp_global_policy_1 + uuid: "{{ mcp_global_policy_1.current.uuid }}" + state: present + +- name: Query the MCP Global Policy object with name + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + name: mcp_global_policy_1 + state: query + register: query_name + +- name: Query the MCP Global Policy object with UUID + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + uuid: "{{ mcp_global_policy_1.current.uuid }}" + state: query + register: query_uuid + +- name: Query the MCP Global Policy object in a Fabric Template + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + state: query + register: query_all + +- name: Delete the MCP Global Policy object with name + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + name: mcp_global_policy_1 + state: absent + +- name: Delete the MCP Global Policy object with UUID + cisco.mso.ndo_mcp_global_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: fabric_template + uuid: "{{ mcp_global_policy_1.current.uuid }}" + state: absent +""" + +RETURN = r""" +""" + +import copy +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP +from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate + + +def check_existing_identifier(mso, object_description, uuid): + if uuid and mso.existing.get("uuid") != uuid: + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str", required=True, aliases=["fabric_template"]), + name=dict(type="str", aliases=["mcp_global_policy"]), + uuid=dict(type="str", aliases=["mcp_global_policy_uuid"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + key=dict(type="str", no_log=False), + per_vlan=dict( + type="str", + aliases=["per_epg", "mcp_pdu_per_vlan"], + choices=["enabled", "disabled"], + ), + loop_detection_factor=dict(type="int", aliases=["loop_factor", "loop_detection_mult_factor"]), + port_disable=dict( + type="str", + aliases=["port_disable_protection"], + choices=["enabled", "disabled"], + ), + initial_delay_time=dict(type="int", aliases=["initial_delay"]), + transmission_frequency_sec=dict(type="int", aliases=["tx_freq"]), + transmission_frequency_msec=dict(type="int", aliases=["tx_freq_ms"]), + state=dict(type="str", default="query", choices=["absent", "query", "present"]), + ) + + # Enforcing that a user must specify a name or uuid when + # adding, updating or removing even though there is only one policy per template. + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "uuid"], True], + ["state", "present", ["name", "uuid"], True], + ], + ) + + mso = MSOModule(module) + + name = module.params.get("name") + uuid = module.params.get("uuid") + state = module.params.get("state") + per_vlan = module.params.get("per_vlan") + port_disable = module.params.get("port_disable") + + mso_values = { + "name": name, + "description": module.params.get("description"), + "adminState": module.params.get("admin_state"), + "key": module.params.get("key"), + "enablePduPerVlan": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP[per_vlan] if per_vlan else None, + "loopDetectMultFactor": module.params.get("loop_detection_factor"), + "protectPortDisable": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP[port_disable] if port_disable else None, + "initialDelayTime": module.params.get("initial_delay_time"), + "txFreq": module.params.get("transmission_frequency_sec"), + "txFreqMsec": module.params.get("transmission_frequency_msec"), + } + + ops = [] + + mso_template = MSOTemplate(mso, "fabric_policy", module.params.get("template")) + mso_template.validate_template("fabricPolicy") + + object_description = "MCP Global Policy" + object_base_path = "/fabricPolicyTemplate/template/mcpGlobalPolicy" + + existing_mcp_global_policy = mso_template.template.get("fabricPolicyTemplate", {}).get("template", {}).get("mcpGlobalPolicy", {}) + + if state in ["query", "absent"] and existing_mcp_global_policy == {}: + mso.exit_json() + + elif state == "query" and not (name or uuid): + mso.existing = [existing_mcp_global_policy] + + elif existing_mcp_global_policy and (name or uuid): + mso.existing = mso.previous = copy.deepcopy(existing_mcp_global_policy) + + if state == "present": + if mso.existing: + check_existing_identifier(mso, object_description, uuid) + proposed_payload = copy.deepcopy(mso.existing) + for mso_name, mso_value in mso_values.items(): + if mso_value is not None and mso.existing.get(mso_name) != mso_value: + ops.append( + dict( + op="replace", + path="{}/{}".format(object_base_path, mso_name), + value=mso_value, + ) + ) + proposed_payload[mso_name] = mso_value + + mso.sanitize(proposed_payload, collate=True) + else: + if not name: + mso.fail_json(msg="{0} name cannot be empty".format(object_description)) + payload = dict() + for mso_name, mso_value in mso_values.items(): + if mso_value: + payload[mso_name] = mso_value + mso.sanitize(payload) + ops.append(dict(op="add", path=object_base_path, value=mso.sent)) + + mso.existing = mso.proposed + + elif state == "absent": + if mso.existing: + check_existing_identifier(mso, object_description, uuid) + ops.append(dict(op="remove", path=object_base_path)) + + if not module.check_mode and ops: + response_object = mso.request(mso_template.template_path, method="PATCH", data=ops) + mso.existing = response_object.get("fabricPolicyTemplate", {}).get("template", {}).get("mcpGlobalPolicy", {}) + elif module.check_mode and state != "query": # When the state is present/absent with check mode + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ndo_mcp_global_policy/aliases b/tests/integration/targets/ndo_mcp_global_policy/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_mcp_global_policy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml new file mode 100644 index 00000000..d4bf81b6 --- /dev/null +++ b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml @@ -0,0 +1,280 @@ +# Test code for the MSO modules +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) + +# 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 template + cisco.mso.ndo_template: &template_absent + <<: *mso_info + name: ansible_fabric_policy_template + type: fabric_policy + state: absent + + - name: Create a fabric template + cisco.mso.ndo_template: + <<: *template_absent + state: present + + # CREATE + - name: Create the MCP Global Policy (check mode) + cisco.mso.ndo_mcp_global_policy: &add_mcp_global_policy + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_mcp_global_policy + state: present + check_mode: true + register: cm_add_mcp_global_policy + + - name: Create the MCP Global Policy + cisco.mso.ndo_mcp_global_policy: + <<: *add_mcp_global_policy + register: nm_add_mcp_global_policy + + - name: Create the MCP Global Policy again + cisco.mso.ndo_mcp_global_policy: + <<: *add_mcp_global_policy + register: nm_add_mcp_global_policy_again + + - name: Assert the MCP Global Policy was created + ansible.builtin.assert: + that: + - cm_add_mcp_global_policy is changed + - nm_add_mcp_global_policy is changed + - cm_add_mcp_global_policy.previous == nm_add_mcp_global_policy.previous == {} + - cm_add_mcp_global_policy.current == cm_add_mcp_global_policy.proposed + - cm_add_mcp_global_policy.current.name == nm_add_mcp_global_policy.current.name == "ansible_mcp_global_policy" + - nm_add_mcp_global_policy.current.description == "" + - nm_add_mcp_global_policy.current.adminState == "disabled" + - nm_add_mcp_global_policy.current.enablePduPerVlan == false + - nm_add_mcp_global_policy.current.loopDetectMultFactor == 3 + - nm_add_mcp_global_policy.current.protectPortDisable == true + - nm_add_mcp_global_policy.current.initialDelayTime == 180 + - nm_add_mcp_global_policy.current.txFreq == 2 + - nm_add_mcp_global_policy.current.txFreqMsec == 0 + - nm_add_mcp_global_policy.current.uuid is defined + - nm_add_mcp_global_policy.current.key is undefined + - nm_add_mcp_global_policy_again is not changed + - nm_add_mcp_global_policy_again.previous.name == nm_add_mcp_global_policy_again.current.name == "ansible_mcp_global_policy" + - nm_add_mcp_global_policy_again.previous.description == nm_add_mcp_global_policy.current.description == "" + - nm_add_mcp_global_policy_again.previous.adminState == nm_add_mcp_global_policy.current.adminState == "disabled" + - nm_add_mcp_global_policy_again.previous.enablePduPerVlan == nm_add_mcp_global_policy.current.enablePduPerVlan == false + - nm_add_mcp_global_policy_again.previous.loopDetectMultFactor == nm_add_mcp_global_policy.current.loopDetectMultFactor == 3 + - nm_add_mcp_global_policy_again.previous.protectPortDisable == nm_add_mcp_global_policy.current.protectPortDisable == true + - nm_add_mcp_global_policy_again.previous.initialDelayTime == nm_add_mcp_global_policy.current.initialDelayTime == 180 + - nm_add_mcp_global_policy_again.previous.txFreq == nm_add_mcp_global_policy.current.txFreq == 2 + - nm_add_mcp_global_policy_again.previous.txFreqMsec == nm_add_mcp_global_policy.current.txFreqMsec == 0 + - nm_add_mcp_global_policy_again.previous.uuid is defined + - nm_add_mcp_global_policy_again.current.uuid is defined + + # Update + - name: Update the MCP Global Policy (check mode) + cisco.mso.ndo_mcp_global_policy: &update_mcp_global_policy + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_mcp_global_policy_new + description: A Global MCP Policy + key: cisco + admin_state: enabled + per_vlan: enabled + loop_detection_factor: 4 + port_disable: disabled + initial_delay_time: 200 + transmission_frequency_sec: 3 + transmission_frequency_msec: 10 + state: present + check_mode: true + register: cm_update_mcp_global_policy + + - name: Update the MCP Global Policy + cisco.mso.ndo_mcp_global_policy: + <<: *update_mcp_global_policy + register: nm_update_mcp_global_policy + + - name: Update the MCP Global Policy again + cisco.mso.ndo_mcp_global_policy: + <<: *update_mcp_global_policy + register: update_mcp_global_policy_again + + - name: Assert the MCP Global Policy updated + ansible.builtin.assert: + that: + - cm_update_mcp_global_policy is changed + - nm_update_mcp_global_policy is changed + - cm_update_mcp_global_policy.previous == nm_update_mcp_global_policy.previous + - cm_update_mcp_global_policy.previous.name == nm_update_mcp_global_policy.previous.name == "ansible_mcp_global_policy" + - cm_update_mcp_global_policy.previous.adminState == nm_update_mcp_global_policy.previous.adminState == "disabled" + - cm_update_mcp_global_policy.previous.description == nm_update_mcp_global_policy.previous.description == "" + - cm_update_mcp_global_policy.previous.enablePduPerVlan == nm_update_mcp_global_policy.previous.enablePduPerVlan == false + - cm_update_mcp_global_policy.previous.key is undefined + - nm_update_mcp_global_policy.previous.key is undefined + - cm_update_mcp_global_policy.previous.loopDetectMultFactor == nm_update_mcp_global_policy.previous.loopDetectMultFactor == 3 + - cm_update_mcp_global_policy.previous.protectPortDisable == nm_update_mcp_global_policy.previous.protectPortDisable == true + - cm_update_mcp_global_policy.previous.txFreq == nm_update_mcp_global_policy.previous.txFreq == 2 + - cm_update_mcp_global_policy.previous.txFreqMsec == nm_update_mcp_global_policy.previous.txFreqMsec == 0 + - cm_update_mcp_global_policy.current == cm_update_mcp_global_policy.proposed + - cm_update_mcp_global_policy.current.name == nm_update_mcp_global_policy.current.name == "ansible_mcp_global_policy_new" + - cm_update_mcp_global_policy.current.adminState == nm_update_mcp_global_policy.current.adminState == "enabled" + - cm_update_mcp_global_policy.current.description == nm_update_mcp_global_policy.current.description == "A Global MCP Policy" + - cm_update_mcp_global_policy.current.enablePduPerVlan == nm_update_mcp_global_policy.current.enablePduPerVlan == true + - cm_update_mcp_global_policy.current.key == nm_update_mcp_global_policy.current.key == "cisco" + - cm_update_mcp_global_policy.current.loopDetectMultFactor == nm_update_mcp_global_policy.current.loopDetectMultFactor == 4 + - cm_update_mcp_global_policy.current.protectPortDisable == nm_update_mcp_global_policy.current.protectPortDisable == false + - cm_update_mcp_global_policy.current.txFreq == nm_update_mcp_global_policy.current.txFreq == 3 + - cm_update_mcp_global_policy.current.txFreqMsec == nm_update_mcp_global_policy.current.txFreqMsec == 10 + - update_mcp_global_policy_again is not changed + - update_mcp_global_policy_again.previous == update_mcp_global_policy_again.current + + # QUERY + - name: Query the MCP Global Policy with name + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_mcp_global_policy_new + state: query + register: query_mcp_policy_with_name + + - name: Query the MCP Global Policy with UUID + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: '{{update_mcp_global_policy_again.current.uuid}}' + state: query + register: query_mcp_policy_with_uuid + + - name: Query the MCP Global Policy without name or UUID + cisco.mso.ndo_mcp_global_policy: &query_mcp_global_policy + <<: *mso_info + template: ansible_fabric_policy_template + state: query + register: query_all_mcp_policy + + - name: Assert MCP Global Policy is queried + ansible.builtin.assert: + that: + - query_mcp_policy_with_name is not changed + - query_mcp_policy_with_uuid is not changed + - query_all_mcp_policy is not changed + - query_mcp_policy_with_uuid.current == query_mcp_policy_with_name.current + - query_mcp_policy_with_name.current.name == "ansible_mcp_global_policy_new" + - query_mcp_policy_with_name.current.key == "cisco" + - query_mcp_policy_with_name.current.description == "A Global MCP Policy" + - query_mcp_policy_with_name.current.adminState == "enabled" + - query_mcp_policy_with_name.current.enablePduPerVlan == true + - query_mcp_policy_with_name.current.protectPortDisable == false + - query_mcp_policy_with_name.current.loopDetectMultFactor == 4 + - query_mcp_policy_with_name.current.txFreq == 3 + - query_mcp_policy_with_name.current.txFreqMsec == 10 + - query_mcp_policy_with_name.current.uuid is defined + - query_all_mcp_policy.current | length == 1 # Can only be one + + # DELETE + - name: Delete a MCP Global Policy with name (check mode) + cisco.mso.ndo_mcp_global_policy: &delete_mcp_global_policy + <<: *mso_info + template: ansible_fabric_policy_template + name: ansible_mcp_global_policy_changed + state: absent + check_mode: true + register: cm_delete_mcp_global_policy + + - name: Delete a MCP Global Policy with name + cisco.mso.ndo_mcp_global_policy: + <<: *delete_mcp_global_policy + register: nm_delete_mcp_global_policy + + - name: Delete MCP Global Policy with name again + cisco.mso.ndo_mcp_global_policy: + <<: *delete_mcp_global_policy + register: nm_delete_mcp_global_policy_again + + - name: Assert that the MCP Global Policy was deleted with name + ansible.builtin.assert: + that: + - cm_delete_mcp_global_policy is changed + - nm_delete_mcp_global_policy is changed + - cm_delete_mcp_global_policy.current == nm_delete_mcp_global_policy.current == {} + - cm_delete_mcp_global_policy.previous.name == nm_delete_mcp_global_policy.previous.name == "ansible_mcp_global_policy_new" + - cm_delete_mcp_global_policy.previous.key == nm_delete_mcp_global_policy.previous.key == "cisco" + - cm_delete_mcp_global_policy.previous.description == nm_delete_mcp_global_policy.previous.description == "A Global MCP Policy" + - nm_delete_mcp_global_policy.previous.uuid is defined + - nm_delete_mcp_global_policy_again is not changed + - nm_delete_mcp_global_policy_again.previous == nm_delete_mcp_global_policy_again.current == {} + + - name: Create the MCP Global Policy + cisco.mso.ndo_mcp_global_policy: + <<: *add_mcp_global_policy + name: ansible_mcp_global_policy_again + description: An MCP Global policy to delete with UUID + register: new_mcp_global_policy + + - name: Delete a MCP Global Policy with UUID + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: '{{ new_mcp_global_policy.current.uuid }}' + state: absent + register: nm_delete_mcp_global_policy_uuid + + - name: Assert that the MCP Global Policy was deleted with UUID + ansible.builtin.assert: + that: + - nm_delete_mcp_global_policy_uuid is changed + - nm_delete_mcp_global_policy_uuid.previous.uuid is defined + - nm_delete_mcp_global_policy_uuid.previous.name == "ansible_mcp_global_policy_again" + - nm_delete_mcp_global_policy_uuid.previous.description == "An MCP Global policy to delete with UUID" + - nm_delete_mcp_global_policy_uuid.current == {} + + # Errors and no policy found + - name: Query the MCP Global Policy in the template when its deleted + cisco.mso.ndo_mcp_global_policy: + <<: *query_mcp_global_policy + register: query_none + + - name: Create an MCP Global Policy without a name + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: non-existing-uuid + state: present + ignore_errors: true + register: update_non_existing_uuid + + - name: Assert no MCP Global Policy found + ansible.builtin.assert: + that: + - query_none is not changed + - query_none.current == {} + - update_non_existing_uuid is failed + - update_non_existing_uuid.msg == "MCP Global Policy name cannot be empty" + + # CLEANUP TEMPLATE + - name: Ensure fabric resource policy template do not exist + cisco.mso.ndo_template: + <<: *template_absent From 26ad82116637bc3c5f127f0df9f17c7481689d2e Mon Sep 17 00:00:00 2001 From: samitab Date: Mon, 2 Dec 2024 21:30:02 +1000 Subject: [PATCH 2/5] [ignore] Added extra test cases when UUID does not match --- .../ndo_mcp_global_policy/tasks/main.yml | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml index d4bf81b6..4db9a5f6 100644 --- a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml +++ b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml @@ -122,6 +122,13 @@ <<: *update_mcp_global_policy register: update_mcp_global_policy_again + - name: Update the MCP Global Policy with incorrect UUID + cisco.mso.ndo_mcp_global_policy: + <<: *update_mcp_global_policy + uuid: bad-uuid + ignore_errors: true + register: nm_update_mcp_global_policy_no_uuid + - name: Assert the MCP Global Policy updated ansible.builtin.assert: that: @@ -150,6 +157,8 @@ - cm_update_mcp_global_policy.current.txFreqMsec == nm_update_mcp_global_policy.current.txFreqMsec == 10 - update_mcp_global_policy_again is not changed - update_mcp_global_policy_again.previous == update_mcp_global_policy_again.current + - nm_update_mcp_global_policy_no_uuid is failed + - nm_update_mcp_global_policy_no_uuid.msg == "MCP Global Policy with the UUID{{":"}} 'bad-uuid' not found" # QUERY - name: Query the MCP Global Policy with name @@ -234,7 +243,16 @@ description: An MCP Global policy to delete with UUID register: new_mcp_global_policy - - name: Delete a MCP Global Policy with UUID + - name: Delete the MCP Global Policy with incorrect UUID + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: does-not-exist + state: absent + ignore_errors: true + register: no_uuid_delete_mcp_global_policy + + - name: Delete the MCP Global Policy with UUID cisco.mso.ndo_mcp_global_policy: <<: *mso_info template: ansible_fabric_policy_template @@ -266,11 +284,13 @@ ignore_errors: true register: update_non_existing_uuid - - name: Assert no MCP Global Policy found + - name: Assert errors and no MCP Global Policy found ansible.builtin.assert: that: - query_none is not changed - query_none.current == {} + - no_uuid_delete_mcp_global_policy is failed + - no_uuid_delete_mcp_global_policy.msg == "MCP Global Policy with the UUID{{":"}} 'does-not-exist' not found" - update_non_existing_uuid is failed - update_non_existing_uuid.msg == "MCP Global Policy name cannot be empty" From 11fe3aec7e6558a8cc9043cc6d0d95901f34deae Mon Sep 17 00:00:00 2001 From: samitab Date: Tue, 3 Dec 2024 12:11:28 +1000 Subject: [PATCH 3/5] [ignore] Fix type and move check UUID function --- plugins/modules/ndo_mcp_global_policy.py | 13 ++++--------- .../targets/ndo_mcp_global_policy/tasks/main.yml | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/plugins/modules/ndo_mcp_global_policy.py b/plugins/modules/ndo_mcp_global_policy.py index 546b2004..2095af8f 100644 --- a/plugins/modules/ndo_mcp_global_policy.py +++ b/plugins/modules/ndo_mcp_global_policy.py @@ -70,7 +70,7 @@ aliases: [ loop_factor, loop_detection_mult_factor ] port_disable: description: - - Enable or disable port disabling when MCP packets are recived. + - Enable or disable port disabling when MCP packets are received. - Defaults to C(enabled) when unset during creation. type: str choices: [ enabled, disabled ] @@ -151,7 +151,7 @@ username: admin password: SomeSecretPassword template: fabric_template - name: mcp_global_policy_1 + name: mcp_global_policy_update uuid: "{{ mcp_global_policy_1.current.uuid }}" state: present @@ -213,11 +213,6 @@ from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate -def check_existing_identifier(mso, object_description, uuid): - if uuid and mso.existing.get("uuid") != uuid: - mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) - - def main(): argument_spec = mso_argument_spec() argument_spec.update( @@ -294,10 +289,11 @@ def main(): elif existing_mcp_global_policy and (name or uuid): mso.existing = mso.previous = copy.deepcopy(existing_mcp_global_policy) + if uuid and mso.existing.get("uuid") != uuid: + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) if state == "present": if mso.existing: - check_existing_identifier(mso, object_description, uuid) proposed_payload = copy.deepcopy(mso.existing) for mso_name, mso_value in mso_values.items(): if mso_value is not None and mso.existing.get(mso_name) != mso_value: @@ -325,7 +321,6 @@ def main(): elif state == "absent": if mso.existing: - check_existing_identifier(mso, object_description, uuid) ops.append(dict(op="remove", path=object_base_path)) if not module.check_mode and ops: diff --git a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml index 4db9a5f6..5a9f1efc 100644 --- a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml +++ b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml @@ -184,7 +184,7 @@ state: query register: query_all_mcp_policy - - name: Assert MCP Global Policy is queried + - name: Assert MCP Global Policy is queried ansible.builtin.assert: that: - query_mcp_policy_with_name is not changed From d3188cde1b9c7ab6d71be9da306cf476140e1c32 Mon Sep 17 00:00:00 2001 From: samitab Date: Tue, 3 Dec 2024 23:11:41 +1000 Subject: [PATCH 4/5] [ignore] Fixed based on PR comments --- plugins/modules/ndo_mcp_global_policy.py | 32 ++++++------ .../ndo_mcp_global_policy/tasks/main.yml | 50 +++++++++++++------ 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/plugins/modules/ndo_mcp_global_policy.py b/plugins/modules/ndo_mcp_global_policy.py index 2095af8f..1529e0b9 100644 --- a/plugins/modules/ndo_mcp_global_policy.py +++ b/plugins/modules/ndo_mcp_global_policy.py @@ -56,7 +56,8 @@ type: str per_vlan: description: - - Enable or disable MCP packets being sent to each End Point Group (EPG). + - When enabled MCP will send packets on a per End Point Group (EPG) basis. + - If disabled, the packets will only be sent on untagged EPGs which allows detecting loops in the native VLAN only. - Defaults to C(disabled) when unset during creation. type: str choices: [ enabled, disabled ] @@ -65,7 +66,7 @@ description: - The amount of MCP packets that will be received before port disable loop protection action takes place. - Defaults to 3 when unset during creation. - - The value must be between 0 and 255. + - The value must be between 1 and 255. type: int aliases: [ loop_factor, loop_detection_mult_factor ] port_disable: @@ -121,10 +122,9 @@ username: admin password: SomeSecretPassword template: fabric_template - name: mcp_global_policy_1 - key: cisco + name: example_mcp_global_policy state: present - register: mcp_global_policy_1 + register: mcp_global_policy_new - name: Create the MCP Global Policy object with all attributes cisco.mso.ndo_mcp_global_policy: @@ -132,7 +132,7 @@ username: admin password: SomeSecretPassword template: fabric_template - name: mcp_global_policy_1 + name: example_mcp_global_policy description: A Global MCP Policy key: cisco admin_state: enabled @@ -143,7 +143,7 @@ transmission_frequency_sec: 2 transmission_frequency_msec: 10 state: present - register: mcp_global_policy_1 + register: mcp_global_policy_all - name: Update the MCP Global Policy object with UUID cisco.mso.ndo_mcp_global_policy: @@ -152,7 +152,7 @@ password: SomeSecretPassword template: fabric_template name: mcp_global_policy_update - uuid: "{{ mcp_global_policy_1.current.uuid }}" + uuid: "{{ mcp_global_policy_all.current.uuid }}" state: present - name: Query the MCP Global Policy object with name @@ -161,7 +161,7 @@ username: admin password: SomeSecretPassword template: fabric_template - name: mcp_global_policy_1 + name: example_mcp_global_policy state: query register: query_name @@ -171,7 +171,7 @@ username: admin password: SomeSecretPassword template: fabric_template - uuid: "{{ mcp_global_policy_1.current.uuid }}" + uuid: "{{ mcp_global_policy_all.current.uuid }}" state: query register: query_uuid @@ -190,7 +190,7 @@ username: admin password: SomeSecretPassword template: fabric_template - name: mcp_global_policy_1 + name: example_mcp_global_policy state: absent - name: Delete the MCP Global Policy object with UUID @@ -199,7 +199,7 @@ username: admin password: SomeSecretPassword template: fabric_template - uuid: "{{ mcp_global_policy_1.current.uuid }}" + uuid: "{{ mcp_global_policy_all.current.uuid }}" state: absent """ @@ -263,9 +263,9 @@ def main(): "description": module.params.get("description"), "adminState": module.params.get("admin_state"), "key": module.params.get("key"), - "enablePduPerVlan": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP[per_vlan] if per_vlan else None, + "enablePduPerVlan": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP.get(per_vlan), "loopDetectMultFactor": module.params.get("loop_detection_factor"), - "protectPortDisable": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP[port_disable] if port_disable else None, + "protectPortDisable": ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP.get(port_disable), "initialDelayTime": module.params.get("initial_delay_time"), "txFreq": module.params.get("transmission_frequency_sec"), "txFreqMsec": module.params.get("transmission_frequency_msec"), @@ -308,8 +308,8 @@ def main(): mso.sanitize(proposed_payload, collate=True) else: - if not name: - mso.fail_json(msg="{0} name cannot be empty".format(object_description)) + if uuid: + mso.fail_json(msg="{0} cannot be created with a UUID".format(object_description)) payload = dict() for mso_name, mso_value in mso_values.items(): if mso_value: diff --git a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml index 5a9f1efc..5449813f 100644 --- a/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml +++ b/tests/integration/targets/ndo_mcp_global_policy/tasks/main.yml @@ -204,7 +204,7 @@ - query_all_mcp_policy.current | length == 1 # Can only be one # DELETE - - name: Delete a MCP Global Policy with name (check mode) + - name: Delete MCP Global Policy with name (check mode) cisco.mso.ndo_mcp_global_policy: &delete_mcp_global_policy <<: *mso_info template: ansible_fabric_policy_template @@ -213,7 +213,7 @@ check_mode: true register: cm_delete_mcp_global_policy - - name: Delete a MCP Global Policy with name + - name: Delete MCP Global Policy with name cisco.mso.ndo_mcp_global_policy: <<: *delete_mcp_global_policy register: nm_delete_mcp_global_policy @@ -243,15 +243,6 @@ description: An MCP Global policy to delete with UUID register: new_mcp_global_policy - - name: Delete the MCP Global Policy with incorrect UUID - cisco.mso.ndo_mcp_global_policy: - <<: *mso_info - template: ansible_fabric_policy_template - uuid: does-not-exist - state: absent - ignore_errors: true - register: no_uuid_delete_mcp_global_policy - - name: Delete the MCP Global Policy with UUID cisco.mso.ndo_mcp_global_policy: <<: *mso_info @@ -275,7 +266,20 @@ <<: *query_mcp_global_policy register: query_none - - name: Create an MCP Global Policy without a name + - name: Create the MCP Global Policy with a UUID + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: not-possible + state: present + ignore_errors: true + register: create_with_uuid + + - name: Create the MCP Global Policy + cisco.mso.ndo_mcp_global_policy: + <<: *add_mcp_global_policy + + - name: Update the MCP Global Policy with incorrect UUID cisco.mso.ndo_mcp_global_policy: <<: *mso_info template: ansible_fabric_policy_template @@ -284,15 +288,31 @@ ignore_errors: true register: update_non_existing_uuid + - name: Delete the MCP Global Policy with incorrect UUID + cisco.mso.ndo_mcp_global_policy: + <<: *mso_info + template: ansible_fabric_policy_template + uuid: does-not-exist + state: absent + ignore_errors: true + register: delete_non_existing_uuid + + - name: Delete the MCP Global Policy + cisco.mso.ndo_mcp_global_policy: + <<: *add_mcp_global_policy + state: absent + - name: Assert errors and no MCP Global Policy found ansible.builtin.assert: that: - query_none is not changed - query_none.current == {} - - no_uuid_delete_mcp_global_policy is failed - - no_uuid_delete_mcp_global_policy.msg == "MCP Global Policy with the UUID{{":"}} 'does-not-exist' not found" - update_non_existing_uuid is failed - - update_non_existing_uuid.msg == "MCP Global Policy name cannot be empty" + - update_non_existing_uuid.msg == "MCP Global Policy with the UUID{{":"}} 'non-existing-uuid' not found" + - delete_non_existing_uuid is failed + - delete_non_existing_uuid.msg == "MCP Global Policy with the UUID{{":"}} 'does-not-exist' not found" + - create_with_uuid is failed + - create_with_uuid.msg == "MCP Global Policy cannot be created with a UUID" # CLEANUP TEMPLATE - name: Ensure fabric resource policy template do not exist From 7d7b19971632ba617913c1853ec6a807097daac4 Mon Sep 17 00:00:00 2001 From: samitab Date: Thu, 5 Dec 2024 12:05:01 +1000 Subject: [PATCH 5/5] [ignore] Use append_update_ops_data --- plugins/modules/ndo_mcp_global_policy.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/plugins/modules/ndo_mcp_global_policy.py b/plugins/modules/ndo_mcp_global_policy.py index 1529e0b9..078ef63e 100644 --- a/plugins/modules/ndo_mcp_global_policy.py +++ b/plugins/modules/ndo_mcp_global_policy.py @@ -211,6 +211,7 @@ from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec from ansible_collections.cisco.mso.plugins.module_utils.constants import ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data def main(): @@ -295,26 +296,12 @@ def main(): if state == "present": if mso.existing: proposed_payload = copy.deepcopy(mso.existing) - for mso_name, mso_value in mso_values.items(): - if mso_value is not None and mso.existing.get(mso_name) != mso_value: - ops.append( - dict( - op="replace", - path="{}/{}".format(object_base_path, mso_name), - value=mso_value, - ) - ) - proposed_payload[mso_name] = mso_value - + append_update_ops_data(ops, proposed_payload, object_base_path, mso_values) mso.sanitize(proposed_payload, collate=True) else: if uuid: mso.fail_json(msg="{0} cannot be created with a UUID".format(object_description)) - payload = dict() - for mso_name, mso_value in mso_values.items(): - if mso_value: - payload[mso_name] = mso_value - mso.sanitize(payload) + mso.sanitize(mso_values) ops.append(dict(op="add", path=object_base_path, value=mso.sent)) mso.existing = mso.proposed