diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index 61b2f137..0dbeab41 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -2118,3 +2118,40 @@ def epg_object_reference_spec(aliases=None): if aliases: epg_reference_spec["aliases"] = aliases return epg_reference_spec + + +def ndo_template_object_spec_with_uuid(aliases=None, required_uuid_or_reference=True): + ndo_template_object_spec_with_uuid = dict( + options=dict( + uuid=dict(type="str"), + reference=dict( + type="dict", + aliases=["ref"], + options=dict( + name=dict(type="str", required=True), + template=dict(type="str"), + template_id=dict(type="str"), + ), + required_one_of=[ + ["template", "template_id"], + ], + mutually_exclusive=[ + ("template", "template_id"), + ], + ), + ), + required_one_of=[ + ["reference", "uuid"], + ], + mutually_exclusive=[ + ("reference", "uuid"), + ], + ) + + if not required_uuid_or_reference: + ndo_template_object_spec_with_uuid.pop("required_one_of") + + if aliases: + ndo_template_object_spec_with_uuid["aliases"] = aliases + + return ndo_template_object_spec_with_uuid diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index ecebe8e7..91dbd430 100644 --- a/plugins/module_utils/template.py +++ b/plugins/module_utils/template.py @@ -1433,3 +1433,41 @@ def get_netflow_record(self, uuid=None, name=None, search_object=None, fail_modu if uuid or name: # Query a specific object return self.get_object_by_key_value_pairs("NetFlow Record", match, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module) return match # Query all objects + + def get_netflow_exporter(self, uuid=None, name=None, search_object=None, fail_module=False): + """ + Get the NetFlow Exporter by uuid or name. + :param uuid: UUID of the NetFlow Exporter to search for -> Str + :param name: Name of the NetFlow Exporter to search for -> Str + :param search_object: The object to search in -> Dict + :param fail_module: When match is not found fail the ansible module -> Bool + :return: Dict | None | List[Dict] | List[]: The processed result which could be: + When the UUID | Name is existing in the search list -> Dict + When the UUID | Name is not existing in the search list -> None + When both UUID and Name are None, and the search list is not empty -> List[Dict] + When both UUID and Name are None, and the search list is empty -> List[] + """ + search_object = search_object if search_object else self.template + match = search_object.get("tenantPolicyTemplate", {}).get("template", {}).get("netFlowExporters", []) + if uuid or name: # Query a specific object + return self.get_object_by_key_value_pairs("NetFlow Exporter", match, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module) + return match # Query all objects + + def get_netflow_monitor(self, uuid=None, name=None, search_object=None, fail_module=False): + """ + Get the NetFlow Monitor by uuid or name. + :param uuid: UUID of the NetFlow Monitor to search for -> Str + :param name: Name of the NetFlow Monitor to search for -> Str + :param search_object: The object to search in -> Dict + :param fail_module: When match is not found fail the ansible module -> Bool + :return: Dict | None | List[Dict] | List[]: The processed result which could be: + When the UUID | Name is existing in the search list -> Dict + When the UUID | Name is not existing in the search list -> None + When both UUID and Name are None, and the search list is not empty -> List[Dict] + When both UUID and Name are None, and the search list is empty -> List[] + """ + search_object = search_object if search_object else self.template + match = search_object.get("tenantPolicyTemplate", {}).get("template", {}).get("netFlowMonitors", []) + if uuid or name: # Query a specific object + return self.get_object_by_key_value_pairs("NetFlow Monitor", match, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module) + return match # Query all objects diff --git a/plugins/modules/ndo_tenant_netflow_exporter.py b/plugins/modules/ndo_tenant_netflow_exporter.py new file mode 100644 index 00000000..8bd9883a --- /dev/null +++ b/plugins/modules/ndo_tenant_netflow_exporter.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2026, Sabari Jaganathan (@sajagana) + +# 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_tenant_netflow_exporter +version_added: "2.12.0" +short_description: Manage NetFlow Exporter on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage NetFlow Exporter on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v4.1 and later. +author: +- Sabari Jaganathan (@sajagana) +options: + template: + description: + - The name of the template. + - The template must be a tenant template. + - This parameter or O(template_id) is required. + type: str + template_id: + description: + - The ID of the tenant template. + - This parameter or O(template) is required. + type: str + name: + description: + - The name of the NetFlow Exporter. + type: str + uuid: + description: + - The UUID of the NetFlow Exporter. + - This parameter is required when the O(name) needs to be updated. + 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. +seealso: +- module: cisco.mso.ndo_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a NetFlow Exporter + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_exporter_1 + state: present + register: add_netflow_exporter + +- name: Update a NetFlow Exporter name using UUID + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_exporter_1_updated + uuid: "{{ add_netflow_exporter.current.uuid }}" + state: present + +- name: Query a NetFlow Exporter using name + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_exporter_1_updated + state: query + register: query_one_with_name + +- name: Query a NetFlow Exporter using UUID + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + uuid: "{{ add_netflow_exporter.current.uuid }}" + state: query + register: query_one_with_uuid + +- name: Query all NetFlow Exporters + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + state: query + register: query_all + +- name: Remove a NetFlow Exporter using name + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_exporter_1_updated + state: absent + +- name: Remove a NetFlow Exporter using UUID + cisco.mso.ndo_tenant_netflow_exporter: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + uuid: "{{ add_netflow_exporter.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.templates import MSOTemplates +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str"), + template_id=dict(type="str"), + name=dict(type="str"), + uuid=dict(type="str"), + state=dict(type="str", default="query", choices=["absent", "query", "present"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "uuid"], True], + ["state", "present", ["name", "uuid"], True], + ], + required_one_of=[ + ["template", "template_id"], + ], + ) + + mso = MSOModule(module) + mso_templates = MSOTemplates(mso) + + template_name = mso.params.get("template") + template_id = mso.params.get("template_id") + name = mso.params.get("name") + uuid = mso.params.get("uuid") + state = mso.params.get("state") + + ops = [] + match = None + path = None + + mso_template = mso_templates.get_template("tenant", template_name, template_id) + mso_template.validate_template("tenantPolicy") + + match = mso_template.get_netflow_exporter(uuid, name) + if (uuid or name) and match: # Query a specific object + mso.existing = mso.previous = copy.deepcopy(mso_template.update_config_with_template_and_references(match.details)) + elif match: # Query all objects + mso.existing = [mso_template.update_config_with_template_and_references(obj) for obj in match] + + if state != "query": + path = "/tenantPolicyTemplate/template/netFlowExporters/{0}".format(match.index if match else "-") + + if state == "present": + mso_values = { + "name": name, + } + if match: + append_update_ops_data(ops, match.details, path, mso_values) + mso.sanitize(mso_values, collate=True) + else: + mso.sanitize(mso_values) + ops.append(dict(op="add", path=path, value=mso.sent)) + + elif state == "absent" and match: + ops.append(dict(op="remove", path=path)) + + mso_template.update_config_with_template_and_references(mso.proposed, set_template=mso.proposed != {}) + + if not module.check_mode and ops: + response = mso.request(mso_template.template_path, method="PATCH", data=ops) + match = mso_template.get_netflow_exporter(uuid, name, response) + if match: + mso.existing = mso_template.update_config_with_template_and_references(match.details) # When the state is present + else: + mso.existing = {} # When the state is absent + elif module.check_mode and state != "query": # When the state is present/absent with check mode + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/ndo_tenant_netflow_monitor.py b/plugins/modules/ndo_tenant_netflow_monitor.py new file mode 100644 index 00000000..a3ac9118 --- /dev/null +++ b/plugins/modules/ndo_tenant_netflow_monitor.py @@ -0,0 +1,389 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2026, Sabari Jaganathan (@sajagana) + +# 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_tenant_netflow_monitor +version_added: "2.12.0" +short_description: Manage NetFlow Monitor on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage NetFlow Monitor on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v4.1 and later. +author: +- Sabari Jaganathan (@sajagana) +options: + template: + description: + - The name of the template. + - The template must be a tenant template. + - This parameter or O(template_id) is required. + type: str + template_id: + description: + - The ID of the tenant template. + - This parameter or O(template) is required. + type: str + name: + description: + - The name of the NetFlow Monitor. + type: str + uuid: + description: + - The UUID of the NetFlow Monitor. + - This parameter is required when the O(name) needs to be updated. + type: str + description: + description: + - The description of the NetFlow Monitor. + - Defaults to an empty string when unset during creation. + type: str + netflow_record: + description: + - The NetFlow Record reference details for the NetFlow Monitor. + - Providing an empty dictionary O(netflow_record={}) will remove NetFlow Record from the NetFlow Monitor. + type: dict + suboptions: + uuid: + description: + - The UUID of the NetFlow Record. + - This parameter can be used instead of O(netflow_record.reference). + type: str + reference: + description: + - The reference details of the NetFlow Record. + - This parameter can be used instead of O(netflow_record.uuid). + type: dict + aliases: [ ref ] + suboptions: + name: + description: + - The name of the NetFlow Record. + type: str + required: True + template: + description: + - The template associated with the NetFlow Record. + - This parameter or O(netflow_record.reference.template_id) is required. + type: str + template_id: + description: + - The template ID associated with the NetFlow Record. + - This parameter or O(netflow_record.reference.template) is required. + type: str + netflow_exporters: + description: + - The list of NetFlow Exporter references. + - At least one reference is required when the state is C(present). + - The old O(netflow_exporters) list will be replaced by the new list during an update. + type: list + elements: dict + suboptions: + uuid: + description: + - The UUID of the NetFlow Exporter. + - This parameter or O(netflow_exporters.reference) is required. + type: str + reference: + description: + - The reference of the NetFlow Exporter. + - This parameter or O(netflow_exporters.uuid) is required. + aliases: [ ref ] + type: dict + suboptions: + name: + description: + - The name of the NetFlow Exporter. + type: str + required: True + template: + description: + - The template associated with the NetFlow Exporter. + - This parameter or O(netflow_exporters.reference.template_id) is required. + type: str + template_id: + description: + - The template ID associated with the NetFlow Exporter. + - This parameter or O(netflow_exporters.reference.template) is required. + 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(netflow_record) must exist before using it with this module in your playbook. + Use M(cisco.mso.ndo_tenant_netflow_record) to create the NetFlow Record. +- The O(netflow_exporters) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_tenant_netflow_exporter) to create the NetFlow Exporter. +seealso: +- module: cisco.mso.ndo_template +- module: cisco.mso.ndo_tenant_netflow_record +- module: cisco.mso.ndo_tenant_netflow_exporter +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a NetFlow Monitor + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_monitor_1 + description: NetFlow Monitor for testing + netflow_record: + reference: + name: netflow_record_1 + template: ansible_tenant_template + netflow_exporters: + - reference: + name: netflow_exporter_1 + template: ansible_tenant_template + state: present + register: add_netflow_monitor + +- name: Update a NetFlow Monitor name using UUID + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_monitor_1_updated + uuid: "{{ add_netflow_monitor.current.uuid }}" + netflow_exporters: + - reference: + name: netflow_exporter_1 + template: ansible_tenant_template + - uuid: "{{ add_netflow_exporter_2.current.uuid }}" + state: present + +- name: Query a NetFlow Monitor using name + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_monitor_1_updated + state: query + register: query_one_with_name + +- name: Query a NetFlow Monitor using UUID + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + uuid: "{{ add_netflow_monitor.current.uuid }}" + state: query + register: query_one_with_uuid + +- name: Query all NetFlow Monitors + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + state: query + register: query_all + +- name: Remove a NetFlow Monitor using name + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + name: netflow_monitor_1_updated + state: absent + +- name: Remove a NetFlow Monitor using UUID + cisco.mso.ndo_tenant_netflow_monitor: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_tenant_template + uuid: "{{ add_netflow_monitor.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, ndo_template_object_spec_with_uuid +from ansible_collections.cisco.mso.plugins.module_utils.templates import MSOTemplates +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data, check_if_all_elements_are_none + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str"), + template_id=dict(type="str"), + name=dict(type="str"), + uuid=dict(type="str"), + description=dict(type="str"), + netflow_record=dict(type="dict", **ndo_template_object_spec_with_uuid(required_uuid_or_reference=False)), + netflow_exporters=dict(type="list", elements="dict", **ndo_template_object_spec_with_uuid()), + state=dict(type="str", default="query", choices=["absent", "query", "present"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "uuid"], True], + ["state", "present", ["name", "uuid"], True], + ], + required_one_of=[ + ["template", "template_id"], + ], + ) + + mso = MSOModule(module) + mso_templates = MSOTemplates(mso) + + template_name = mso.params.get("template") + template_id = mso.params.get("template_id") + name = mso.params.get("name") + uuid = mso.params.get("uuid") + description = module.params.get("description") + netflow_record = module.params.get("netflow_record") + netflow_exporters = module.params.get("netflow_exporters") + state = module.params.get("state") + + reference_details = { + "netFlowExporter": { + "name": "exporterName", + "reference": "exporterRef", + "type": "netFlowExporter", + "template": "exporterTemplateName", + "templateId": "exporterTemplateId", + }, + "netFlowRecord": { + "name": "recordName", + "reference": "recordRef", + "type": "netFlowRecord", + "template": "recordTemplateName", + "templateId": "recordTemplateId", + }, + } + + ops = [] + match = None + path = None + + mso_template = mso_templates.get_template("tenant", template_name, template_id) + mso_template.validate_template("tenantPolicy") + + match = mso_template.get_netflow_monitor(uuid, name) + + if (uuid or name) and match: # Query a specific object + netflow_monitor_object = copy.deepcopy(match.details) + netflow_monitor_object["exporterRefs"] = netflow_exporters_list_to_dict(netflow_monitor_object.get("exporterRefs")) + updated_netflow_monitor_object = mso_template.update_config_with_template_and_references(netflow_monitor_object, reference_details) + mso.previous = mso.existing = copy.deepcopy(updated_netflow_monitor_object) + elif match: # Query all objects + for obj in match: + obj["exporterRefs"] = netflow_exporters_list_to_dict(obj.get("exporterRefs")) + + mso.existing = [mso_template.update_config_with_template_and_references(obj, reference_details) for obj in match] + + if state != "query": + path = "/tenantPolicyTemplate/template/netFlowMonitors/{0}".format(match.index if match else "-") + + if state == "present": + netflow_record_uuid = None + if netflow_record: + netflow_record_uuid = netflow_record.get("uuid") + netflow_record_reference = netflow_record.get("reference") + if not netflow_record_uuid and netflow_record_reference and not check_if_all_elements_are_none(netflow_record_reference.values()): + netflow_record_template = mso_templates.get_template( + "tenant", netflow_record_reference.get("template"), netflow_record_reference.get("template_id") + ) + netflow_record_match = netflow_record_template.get_netflow_record( + uuid=None, name=netflow_record_reference.get("name"), search_object=None, fail_module=True + ) + netflow_record_uuid = netflow_record_match.details.get("uuid") + + netflow_exporter_uuids = [] + if netflow_exporters: + for netflow_exporter in netflow_exporters: + if netflow_exporter and netflow_exporter.get("uuid"): + netflow_exporter_uuids.append(netflow_exporter.get("uuid")) + elif netflow_exporter and not check_if_all_elements_are_none(netflow_exporter.get("reference", {}).values()): + ref = netflow_exporter.get("reference") + netflow_exporter_template = mso_templates.get_template("tenant", ref.get("template"), ref.get("template_id")) + netflow_exporter_match = netflow_exporter_template.get_netflow_exporter( + uuid=None, name=ref.get("name"), search_object=None, fail_module=True + ) + netflow_exporter_uuids.append(netflow_exporter_match.details.get("uuid")) + + mso_values = { + "name": name, + "description": description, + } + + if netflow_exporter_uuids: + mso_values["exporterRefs"] = netflow_exporter_uuids + + if netflow_record_uuid is not None: + mso_values["recordRef"] = netflow_record_uuid + + if match: + mso_remove_values = [] + if match.details.get("recordRef") and netflow_record is not None and check_if_all_elements_are_none(netflow_record.values()): + mso_remove_values.append("recordRef") + + append_update_ops_data(ops, match.details, path, mso_values, mso_remove_values) + mso.sanitize(mso_values, collate=True) + else: + mso.sanitize(mso_values) + ops.append(dict(op="add", path=path, value=mso.sent)) + + elif state == "absent" and match: + ops.append(dict(op="remove", path=path)) + + if mso.proposed.get("exporterRefs") and isinstance(mso.proposed.get("exporterRefs"), list) and not isinstance(mso.proposed.get("exporterRefs")[0], dict): + mso.proposed["exporterRefs"] = netflow_exporters_list_to_dict(mso.proposed.get("exporterRefs", [])) + + if mso.proposed.get("recordRef") == "": + reference_details.pop("recordRef", None) + mso_template.update_config_with_template_and_references(mso.proposed, reference_details, set_template=mso.proposed != {}) + + if not module.check_mode and ops: + response = mso.request(mso_template.template_path, method="PATCH", data=ops) + match = mso_template.get_netflow_monitor(uuid, name, response) + if match: + match.details["exporterRefs"] = netflow_exporters_list_to_dict(match.details.get("exporterRefs")) + mso.existing = mso_template.update_config_with_template_and_references(match.details, reference_details) # When the state is present + else: + mso.existing = {} # When the state is absent + + elif module.check_mode and state != "query": # When the state is present/absent with check mode + mso.existing = mso.proposed if state == "present" else {} + + mso.exit_json() + + +def netflow_exporters_list_to_dict(netflow_exporters): + return [{"exporterRef": netflow_exporter} for netflow_exporter in netflow_exporters] + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/ndo_tenant_netflow_record.py b/plugins/modules/ndo_tenant_netflow_record.py index 490ece47..c4d4c3cb 100644 --- a/plugins/modules/ndo_tenant_netflow_record.py +++ b/plugins/modules/ndo_tenant_netflow_record.py @@ -233,9 +233,7 @@ def main(): elif state == "absent" and match: ops.append(dict(op="remove", path=path)) - if mso.proposed: - mso.proposed = copy.deepcopy(mso.proposed) - mso_template.update_config_with_template_and_references(mso.proposed) + mso_template.update_config_with_template_and_references(mso.proposed, set_template=mso.proposed != {}) 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_tenant_netflow_exporter/aliases b/tests/integration/targets/ndo_tenant_netflow_exporter/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_tenant_netflow_exporter/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_tenant_netflow_exporter/tasks/main.yml b/tests/integration/targets/ndo_tenant_netflow_exporter/tasks/main.yml new file mode 100644 index 00000000..6faba016 --- /dev/null +++ b/tests/integration/targets/ndo_tenant_netflow_exporter/tasks/main.yml @@ -0,0 +1,321 @@ +# Test code for the MSO modules +# Copyright: (c) 2026, Sabari Jaganathan (@sajagana) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + ansible.builtin.fail: + msg: "Please define the following variables: mso_hostname, mso_username and mso_password." + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: "{{ mso_hostname }}" + username: "{{ mso_username }}" + password: "{{ mso_password }}" + validate_certs: "{{ mso_validate_certs | default(false) }}" + use_ssl: "{{ mso_use_ssl | default(true) }}" + use_proxy: "{{ mso_use_proxy | default(true) }}" + output_level: '{{ mso_output_level | default("info") }}' + +# QUERY VERSION +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for NDO version >= 4.1 + when: version.current.version is version('4.1', '>=') + block: + # CLEAN ENVIRONMENT + - name: Ensure ansible_test exist + cisco.mso.mso_tenant: + <<: *mso_info + name: ansible_test + state: present + + - name: Ensure templates do not exist + cisco.mso.ndo_template: &template_absent + <<: *mso_info + name: ansible_tenant_template + template_type: tenant + tenant: ansible_test + state: absent + + - name: Ensure templates exist + cisco.mso.ndo_template: + <<: *template_absent + state: present + register: add_ansible_tenant_template + + # CREATE + - name: Add NetFlow Exporter 1 (check_mode) + cisco.mso.ndo_tenant_netflow_exporter: &cm_add_netflow_exporter + <<: *mso_info + template: ansible_tenant_template + name: netflow_exporter_1 + state: present + output_level: debug + check_mode: true + register: cm_add_netflow_exporter + + - name: Add NetFlow Exporter 1 + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_add_netflow_exporter + register: nm_add_netflow_exporter + + - name: Add NetFlow Exporter 1 again + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_add_netflow_exporter + register: nm_add_netflow_exporter_again + + - name: Add NetFlow Exporter 2 + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template_id: "{{ add_ansible_tenant_template.current.templateId }}" + name: netflow_exporter_2 + state: present + register: add_netflow_exporter_2 + + - name: Assertion check for add NetFlow Exporter + ansible.builtin.assert: + that: + - cm_add_netflow_exporter is changed + - cm_add_netflow_exporter.current.name == "netflow_exporter_1" + - cm_add_netflow_exporter.current.templateId is defined + - cm_add_netflow_exporter.current.templateName == "ansible_tenant_template" + - cm_add_netflow_exporter.previous == {} + - cm_add_netflow_exporter.proposed.name == "netflow_exporter_1" + - cm_add_netflow_exporter.proposed.templateId is defined + - cm_add_netflow_exporter.proposed.templateName == "ansible_tenant_template" + - nm_add_netflow_exporter is changed + - nm_add_netflow_exporter.current.name == "netflow_exporter_1" + - nm_add_netflow_exporter.current.templateId is defined + - nm_add_netflow_exporter.current.templateName == "ansible_tenant_template" + - nm_add_netflow_exporter.current.uuid is defined + - nm_add_netflow_exporter.previous == {} + - nm_add_netflow_exporter.proposed.name == "netflow_exporter_1" + - nm_add_netflow_exporter.proposed.templateId is defined + - nm_add_netflow_exporter.proposed.templateName == "ansible_tenant_template" + - nm_add_netflow_exporter_again is not changed + - nm_add_netflow_exporter_again.current.name == "netflow_exporter_1" + - nm_add_netflow_exporter_again.current.templateId is defined + - nm_add_netflow_exporter_again.current.templateName == "ansible_tenant_template" + - nm_add_netflow_exporter_again.current.uuid is defined + - nm_add_netflow_exporter_again.previous.name == "netflow_exporter_1" + - nm_add_netflow_exporter_again.previous.templateId is defined + - nm_add_netflow_exporter_again.previous.templateName == "ansible_tenant_template" + - nm_add_netflow_exporter_again.previous.uuid is defined + - add_netflow_exporter_2 is changed + - add_netflow_exporter_2.current.name == "netflow_exporter_2" + - add_netflow_exporter_2.current.templateId is defined + - add_netflow_exporter_2.current.templateName == "ansible_tenant_template" + - add_netflow_exporter_2.current.uuid is defined + - add_netflow_exporter_2.previous == {} + + # UPDATE + - name: Update NetFlow Exporter 1 (check_mode) + cisco.mso.ndo_tenant_netflow_exporter: &cm_update_netflow_exporter + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_exporter.current.uuid }}" + name: netflow_exporter_1_updated + state: present + output_level: debug + check_mode: true + register: cm_update_netflow_exporter + + - name: Update NetFlow Exporter 1 + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_update_netflow_exporter + register: nm_update_netflow_exporter + + - name: Update NetFlow Exporter 1 again + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_update_netflow_exporter + register: nm_update_netflow_exporter_again + + - name: Assertion check for update NetFlow Exporter + ansible.builtin.assert: + that: + - cm_update_netflow_exporter is changed + - cm_update_netflow_exporter.current.name == "netflow_exporter_1_updated" + - cm_update_netflow_exporter.current.templateId is defined + - cm_update_netflow_exporter.current.templateName == "ansible_tenant_template" + - cm_update_netflow_exporter.current.uuid is defined + - cm_update_netflow_exporter.previous.name == "netflow_exporter_1" + - cm_update_netflow_exporter.previous.templateId is defined + - cm_update_netflow_exporter.previous.templateName == "ansible_tenant_template" + - cm_update_netflow_exporter.previous.uuid is defined + - cm_update_netflow_exporter.proposed.name == "netflow_exporter_1_updated" + - cm_update_netflow_exporter.proposed.templateId is defined + - cm_update_netflow_exporter.proposed.templateName == "ansible_tenant_template" + - cm_update_netflow_exporter.proposed.uuid is defined + - nm_update_netflow_exporter is changed + - nm_update_netflow_exporter.current.name == "netflow_exporter_1_updated" + - nm_update_netflow_exporter.current.templateId is defined + - nm_update_netflow_exporter.current.templateName == "ansible_tenant_template" + - nm_update_netflow_exporter.current.uuid is defined + - nm_update_netflow_exporter.previous.name == "netflow_exporter_1" + - nm_update_netflow_exporter.previous.templateId is defined + - nm_update_netflow_exporter.previous.templateName == "ansible_tenant_template" + - nm_update_netflow_exporter.previous.uuid is defined + - nm_update_netflow_exporter_again is not changed + - nm_update_netflow_exporter_again.current.name == "netflow_exporter_1_updated" + - nm_update_netflow_exporter_again.current.templateId is defined + - nm_update_netflow_exporter_again.current.templateName == "ansible_tenant_template" + - nm_update_netflow_exporter_again.current.uuid is defined + - nm_update_netflow_exporter_again.previous.name == "netflow_exporter_1_updated" + - nm_update_netflow_exporter_again.previous.templateId is defined + - nm_update_netflow_exporter_again.previous.templateName == "ansible_tenant_template" + - nm_update_netflow_exporter_again.previous.uuid is defined + + # QUERY + - name: Query NetFlow Exporter 1 using UUID + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_exporter.current.uuid }}" + state: query + register: query_netflow_exporter_1_uuid + + - name: Query NetFlow Exporter 1 using name + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + name: netflow_exporter_1_updated + state: query + register: query_netflow_exporter_1_name + + - name: Query all NetFlow Exporter + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + state: query + register: query_all + + - name: Assertion check for query all NetFlow Exporter + ansible.builtin.assert: + that: + - query_netflow_exporter_1_uuid is not changed + - query_netflow_exporter_1_uuid.current.name == "netflow_exporter_1_updated" + - query_netflow_exporter_1_uuid.current.templateId is defined + - query_netflow_exporter_1_uuid.current.templateName == "ansible_tenant_template" + - query_netflow_exporter_1_uuid.current.uuid is defined + - query_netflow_exporter_1_name is not changed + - query_netflow_exporter_1_uuid.current == query_netflow_exporter_1_name.current + - query_all is not changed + - query_all.current.0.name == "netflow_exporter_1_updated" + - query_all.current.0.templateId is defined + - query_all.current.0.templateName == "ansible_tenant_template" + - query_all.current.0.uuid is defined + - query_all.current.1.name == "netflow_exporter_2" + - query_all.current.1.templateId is defined + - query_all.current.1.templateName == "ansible_tenant_template" + - query_all.current.1.uuid is defined + + # DELETE + - name: Delete NetFlow Exporter 1 using UUID (check_mode) + cisco.mso.ndo_tenant_netflow_exporter: &cm_rm_netflow_exporter_1_uuid + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_exporter.current.uuid }}" + state: absent + output_level: debug + check_mode: true + register: cm_rm_netflow_exporter_1_uuid + + - name: Delete NetFlow Exporter 1 using UUID + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_rm_netflow_exporter_1_uuid + register: nm_rm_netflow_exporter_1_uuid + + - name: Delete NetFlow Exporter 1 using UUID again + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_rm_netflow_exporter_1_uuid + register: nm_rm_netflow_exporter_1_uuid_again + + - name: Delete NetFlow Exporter 2 using name (check_mode) + cisco.mso.ndo_tenant_netflow_exporter: &cm_rm_netflow_exporter_2_name + <<: *mso_info + template: ansible_tenant_template + name: netflow_exporter_2 + state: absent + output_level: debug + check_mode: true + register: cm_rm_netflow_exporter_2_name + + - name: Delete NetFlow Exporter 2 using name + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_rm_netflow_exporter_2_name + state: absent + register: nm_rm_netflow_exporter_2_name + + - name: Delete NetFlow Exporter 2 using name again + cisco.mso.ndo_tenant_netflow_exporter: + <<: *cm_rm_netflow_exporter_2_name + state: absent + register: nm_rm_netflow_exporter_2_name_again + + - name: Assertion check for delete NetFlow Exporter + ansible.builtin.assert: + that: + - cm_rm_netflow_exporter_1_uuid is changed + - cm_rm_netflow_exporter_1_uuid.current == {} + - cm_rm_netflow_exporter_1_uuid.proposed == {} + - nm_rm_netflow_exporter_1_uuid is changed + - nm_rm_netflow_exporter_1_uuid.current == {} + - nm_rm_netflow_exporter_1_uuid.previous == cm_rm_netflow_exporter_1_uuid.previous + - nm_rm_netflow_exporter_1_uuid.previous.name == "netflow_exporter_1_updated" + - nm_rm_netflow_exporter_1_uuid.previous.templateId is defined + - nm_rm_netflow_exporter_1_uuid.previous.templateName == "ansible_tenant_template" + - nm_rm_netflow_exporter_1_uuid.previous.uuid is defined + - nm_rm_netflow_exporter_1_uuid_again is not changed + - nm_rm_netflow_exporter_1_uuid_again.current == {} + - nm_rm_netflow_exporter_1_uuid_again.previous == {} + - cm_rm_netflow_exporter_2_name is changed + - cm_rm_netflow_exporter_2_name.current == {} + - cm_rm_netflow_exporter_2_name.proposed == {} + - nm_rm_netflow_exporter_2_name is changed + - nm_rm_netflow_exporter_2_name.current == {} + - cm_rm_netflow_exporter_2_name.previous == nm_rm_netflow_exporter_2_name.previous + - nm_rm_netflow_exporter_2_name.previous.name == "netflow_exporter_2" + - nm_rm_netflow_exporter_2_name.previous.templateId is defined + - nm_rm_netflow_exporter_2_name.previous.templateName == "ansible_tenant_template" + - nm_rm_netflow_exporter_2_name.previous.uuid is defined + - nm_rm_netflow_exporter_2_name_again is not changed + - nm_rm_netflow_exporter_2_name_again.current == {} + - nm_rm_netflow_exporter_2_name_again.previous == {} + + # ERROR + - name: Negative test add NetFlow Exporter without template + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + name: netflow_exporter_1 + state: present + register: nt_add_netflow_exporter_without_template + ignore_errors: true + + - name: Negative test add NetFlow Exporter without name and uuid + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + state: present + register: nt_add_netflow_exporter_without_name_uuid + ignore_errors: true + + - name: Assertion check for negative test add NetFlow Exporter + ansible.builtin.assert: + that: + - nt_add_netflow_exporter_without_template is not changed + - 'nt_add_netflow_exporter_without_template.msg == "one of the following is required: template, template_id"' + - nt_add_netflow_exporter_without_name_uuid is not changed + - 'nt_add_netflow_exporter_without_name_uuid.msg == "state is present but any of the following are missing: name, uuid"' + + # CLEAN ENVIRONMENT + - name: Ensure templates do not exist + cisco.mso.ndo_template: + <<: *template_absent \ No newline at end of file diff --git a/tests/integration/targets/ndo_tenant_netflow_monitor/aliases b/tests/integration/targets/ndo_tenant_netflow_monitor/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_tenant_netflow_monitor/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_tenant_netflow_monitor/tasks/main.yml b/tests/integration/targets/ndo_tenant_netflow_monitor/tasks/main.yml new file mode 100644 index 00000000..27997abd --- /dev/null +++ b/tests/integration/targets/ndo_tenant_netflow_monitor/tasks/main.yml @@ -0,0 +1,554 @@ +# Test code for the MSO modules +# Copyright: (c) 2026, Sabari Jaganathan (@sajagana) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + ansible.builtin.fail: + msg: "Please define the following variables: mso_hostname, mso_username and mso_password." + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: "{{ mso_hostname }}" + username: "{{ mso_username }}" + password: "{{ mso_password }}" + validate_certs: "{{ mso_validate_certs | default(false) }}" + use_ssl: "{{ mso_use_ssl | default(true) }}" + use_proxy: "{{ mso_use_proxy | default(true) }}" + output_level: '{{ mso_output_level | default("info") }}' + +# QUERY VERSION +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for NDO version >= 4.1 + when: version.current.version is version('4.1', '>=') + block: + # CLEAN ENVIRONMENT + - name: Ensure ansible_test exist + cisco.mso.mso_tenant: + <<: *mso_info + name: ansible_test + state: present + + - name: Ensure templates do not exist + cisco.mso.ndo_template: &template_absent + <<: *mso_info + name: ansible_tenant_template + template_type: tenant + tenant: ansible_test + state: absent + + - name: Ensure templates exist + cisco.mso.ndo_template: + <<: *template_absent + state: present + register: add_ansible_tenant_template + + - name: Ensure NetFlow Record 1 exist + cisco.mso.ndo_tenant_netflow_record: + <<: *mso_info + template: ansible_tenant_template + name: netflow_record_1 + state: present + register: add_netflow_record_1 + + - name: Ensure NetFlow Record 2 exist + cisco.mso.ndo_tenant_netflow_record: + <<: *mso_info + template: ansible_tenant_template + name: netflow_record_2 + state: present + register: add_netflow_record_2 + + - name: Ensure NetFlow Exporter 1 exist + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + name: netflow_exporter_1 + state: present + register: add_netflow_exporter_1 + + - name: Ensure NetFlow Exporter 2 exist + cisco.mso.ndo_tenant_netflow_exporter: + <<: *mso_info + template: ansible_tenant_template + name: netflow_exporter_2 + state: present + register: add_netflow_exporter_2 + + # CREATE + - name: Add NetFlow Monitor (check_mode) + cisco.mso.ndo_tenant_netflow_monitor: &cm_add_netflow_monitor + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_1 + netflow_exporters: + - {"reference": {"name": "netflow_exporter_1", "template": "ansible_tenant_template"}} + - {"reference": {"name": "netflow_exporter_2", "template": "ansible_tenant_template"}} + state: present + output_level: debug + check_mode: true + register: cm_add_netflow_monitor + + - name: Add NetFlow Monitor + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_add_netflow_monitor + register: nm_add_netflow_monitor + + - name: Add NetFlow Monitor again + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_add_netflow_monitor + register: nm_add_netflow_monitor_again + + - name: Add NetFlow Monitor 2 + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_2 + description: test + netflow_record: {"reference": {"name": "netflow_record_2", "template": "ansible_tenant_template"}} + netflow_exporters: + - uuid: "{{ add_netflow_exporter_1.current.uuid }}" + - uuid: "{{ add_netflow_exporter_2.current.uuid }}" + state: present + register: add_netflow_monitor_2 + + - name: Assertion check for add NetFlow Monitor + ansible.builtin.assert: + that: + - cm_add_netflow_monitor is changed + - cm_add_netflow_monitor.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - cm_add_netflow_monitor.current.exporterRefs.0.exporterRef != "" + - cm_add_netflow_monitor.current.exporterRefs.0.exporterTemplateId != "" + - cm_add_netflow_monitor.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_add_netflow_monitor.current.exporterRefs.1.exporterName == "netflow_exporter_2" + - cm_add_netflow_monitor.current.exporterRefs.1.exporterRef != "" + - cm_add_netflow_monitor.current.exporterRefs.1.exporterTemplateId != "" + - cm_add_netflow_monitor.current.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - cm_add_netflow_monitor.current.name == "netflow_monitor_1" + - cm_add_netflow_monitor.current.recordRef == "" + - cm_add_netflow_monitor.previous == {} + - cm_add_netflow_monitor.proposed.exporterRefs.0.exporterName == "netflow_exporter_1" + - cm_add_netflow_monitor.proposed.exporterRefs.0.exporterRef != "" + - cm_add_netflow_monitor.proposed.exporterRefs.0.exporterTemplateId != "" + - cm_add_netflow_monitor.proposed.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_add_netflow_monitor.proposed.exporterRefs.1.exporterName == "netflow_exporter_2" + - cm_add_netflow_monitor.proposed.exporterRefs.1.exporterRef != "" + - cm_add_netflow_monitor.proposed.exporterRefs.1.exporterTemplateId != "" + - cm_add_netflow_monitor.proposed.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - cm_add_netflow_monitor.proposed.name == "netflow_monitor_1" + - cm_add_netflow_monitor.proposed.recordRef == "" + - nm_add_netflow_monitor is changed + - nm_add_netflow_monitor.current.description == "" + - nm_add_netflow_monitor.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - nm_add_netflow_monitor.current.exporterRefs.0.exporterRef != "" + - nm_add_netflow_monitor.current.exporterRefs.0.exporterTemplateId != "" + - nm_add_netflow_monitor.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - nm_add_netflow_monitor.current.exporterRefs.1.exporterName == "netflow_exporter_2" + - nm_add_netflow_monitor.current.exporterRefs.1.exporterRef != "" + - nm_add_netflow_monitor.current.exporterRefs.1.exporterTemplateId != "" + - nm_add_netflow_monitor.current.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - nm_add_netflow_monitor.current.name == "netflow_monitor_1" + - nm_add_netflow_monitor.current.recordRef == "" + - nm_add_netflow_monitor.current.uuid is defined + - nm_add_netflow_monitor.previous == {} + - nm_add_netflow_monitor_again is not changed + - nm_add_netflow_monitor_again.current == nm_add_netflow_monitor_again.previous == nm_add_netflow_monitor.current + - add_netflow_monitor_2 is changed + - add_netflow_monitor_2.current.description == "test" + - add_netflow_monitor_2.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - add_netflow_monitor_2.current.exporterRefs.0.exporterRef != "" + - add_netflow_monitor_2.current.exporterRefs.0.exporterTemplateId != "" + - add_netflow_monitor_2.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - add_netflow_monitor_2.current.exporterRefs.1.exporterName == "netflow_exporter_2" + - add_netflow_monitor_2.current.exporterRefs.1.exporterRef != "" + - add_netflow_monitor_2.current.exporterRefs.1.exporterTemplateId != "" + - add_netflow_monitor_2.current.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - add_netflow_monitor_2.current.name == "netflow_monitor_2" + - add_netflow_monitor_2.current.recordName == "netflow_record_2" + - add_netflow_monitor_2.current.recordRef != "" + - add_netflow_monitor_2.current.recordTemplateId != "" + - add_netflow_monitor_2.current.recordTemplateName == "ansible_tenant_template" + - add_netflow_monitor_2.current.uuid is defined + - add_netflow_monitor_2.previous == {} + + # UPDATE + - name: Update NetFlow Monitor (check_mode) + cisco.mso.ndo_tenant_netflow_monitor: &cm_update_netflow_monitor + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_monitor.current.uuid }}" + name: netflow_monitor_1_updated + description: updated + netflow_record: + uuid: "{{ add_netflow_record_2.current.uuid }}" + netflow_exporters: + - uuid: "{{ add_netflow_exporter_2.current.uuid }}" + output_level: debug + state: present + check_mode: true + register: cm_update_netflow_monitor + + - name: Update NetFlow Monitor + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_update_netflow_monitor + register: nm_update_netflow_monitor + + - name: Update NetFlow Monitor again + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_update_netflow_monitor + register: nm_update_netflow_monitor_again + + - name: Remove NetFlow Record and description from NetFlow Monitor 2 + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_2 + description: "" + netflow_record: {} + netflow_exporters: + - uuid: "{{ add_netflow_exporter_1.current.uuid }}" + state: present + register: update_netflow_monitor_2 + + - name: Update description from NetFlow Monitor 2 without specifying NetFlow Exporter + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_2 + description: "Updated" + state: present + register: update_netflow_monitor_2_without_changing_exporter + + - name: Update NetFlow Monitor without specifying description, NetFlow Record and NetFlow Exporter + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_monitor.current.uuid }}" + name: netflow_monitor_1_updated + state: present + register: update_netflow_monitor_without_description_record_exporter + + - name: Assertion check for update NetFlow Monitor + ansible.builtin.assert: + that: + - cm_update_netflow_monitor is changed + - cm_update_netflow_monitor.current.templateName == "ansible_tenant_template" + - cm_update_netflow_monitor.current.templateId is defined + - cm_update_netflow_monitor.current.description == "updated" + - cm_update_netflow_monitor.current.exporterRefs.0.exporterName == "netflow_exporter_2" + - cm_update_netflow_monitor.current.exporterRefs.0.exporterRef != "" + - cm_update_netflow_monitor.current.exporterRefs.0.exporterTemplateId != "" + - cm_update_netflow_monitor.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.current.name == "netflow_monitor_1_updated" + - cm_update_netflow_monitor.current.recordName == "netflow_record_2" + - cm_update_netflow_monitor.current.recordRef != "" + - cm_update_netflow_monitor.current.recordTemplateId != "" + - cm_update_netflow_monitor.current.recordTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.current.uuid is defined + - cm_update_netflow_monitor.previous.description == "" + - cm_update_netflow_monitor.previous.exporterRefs.0.exporterName == "netflow_exporter_1" + - cm_update_netflow_monitor.previous.exporterRefs.0.exporterRef != "" + - cm_update_netflow_monitor.previous.exporterRefs.0.exporterTemplateId != "" + - cm_update_netflow_monitor.previous.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.previous.exporterRefs.1.exporterName == "netflow_exporter_2" + - cm_update_netflow_monitor.previous.exporterRefs.1.exporterRef != "" + - cm_update_netflow_monitor.previous.exporterRefs.1.exporterTemplateId != "" + - cm_update_netflow_monitor.previous.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.previous.name == "netflow_monitor_1" + - cm_update_netflow_monitor.previous.recordRef == "" + - cm_update_netflow_monitor.previous.uuid is defined + - cm_update_netflow_monitor.proposed.description == "updated" + - cm_update_netflow_monitor.proposed.exporterRefs.0.exporterName == "netflow_exporter_2" + - cm_update_netflow_monitor.proposed.exporterRefs.0.exporterRef != "" + - cm_update_netflow_monitor.proposed.exporterRefs.0.exporterTemplateId != "" + - cm_update_netflow_monitor.proposed.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.proposed.name == "netflow_monitor_1_updated" + - cm_update_netflow_monitor.proposed.recordName == "netflow_record_2" + - cm_update_netflow_monitor.proposed.recordRef != "" + - cm_update_netflow_monitor.proposed.recordTemplateId != "" + - cm_update_netflow_monitor.proposed.recordTemplateName == "ansible_tenant_template" + - cm_update_netflow_monitor.proposed.uuid is defined + - nm_update_netflow_monitor is changed + - nm_update_netflow_monitor.current.templateName == "ansible_tenant_template" + - nm_update_netflow_monitor.current.templateId is defined + - nm_update_netflow_monitor.current.description == "updated" + - nm_update_netflow_monitor.current.exporterRefs.0.exporterName == "netflow_exporter_2" + - nm_update_netflow_monitor.current.exporterRefs.0.exporterRef != "" + - nm_update_netflow_monitor.current.exporterRefs.0.exporterTemplateId != "" + - nm_update_netflow_monitor.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - nm_update_netflow_monitor.current.name == "netflow_monitor_1_updated" + - nm_update_netflow_monitor.current.recordName == "netflow_record_2" + - nm_update_netflow_monitor.current.recordRef != "" + - nm_update_netflow_monitor.current.recordTemplateId != "" + - nm_update_netflow_monitor.current.recordTemplateName == "ansible_tenant_template" + - nm_update_netflow_monitor.current.uuid is defined + - nm_update_netflow_monitor.previous.description == "" + - nm_update_netflow_monitor.previous.exporterRefs.0.exporterName == "netflow_exporter_1" + - nm_update_netflow_monitor.previous.exporterRefs.0.exporterRef != "" + - nm_update_netflow_monitor.previous.exporterRefs.0.exporterTemplateId != "" + - nm_update_netflow_monitor.previous.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - nm_update_netflow_monitor.previous.exporterRefs.1.exporterName == "netflow_exporter_2" + - nm_update_netflow_monitor.previous.exporterRefs.1.exporterRef != "" + - nm_update_netflow_monitor.previous.exporterRefs.1.exporterTemplateId != "" + - nm_update_netflow_monitor.previous.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - nm_update_netflow_monitor.previous.name == "netflow_monitor_1" + - nm_update_netflow_monitor.previous.recordRef == "" + - nm_update_netflow_monitor.previous.uuid is defined + - nm_update_netflow_monitor_again is not changed + - nm_update_netflow_monitor_again.current == nm_update_netflow_monitor_again.previous + - update_netflow_monitor_2 is changed + - update_netflow_monitor_2.current.templateName == "ansible_tenant_template" + - update_netflow_monitor_2.current.templateId is defined + - update_netflow_monitor_2.current.description == "" + - update_netflow_monitor_2.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - update_netflow_monitor_2.current.exporterRefs.0.exporterRef != "" + - update_netflow_monitor_2.current.exporterRefs.0.exporterTemplateId != "" + - update_netflow_monitor_2.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - update_netflow_monitor_2.current.name == "netflow_monitor_2" + - update_netflow_monitor_2.current.recordRef == "" + - update_netflow_monitor_2.current.uuid is defined + - update_netflow_monitor_2.previous.templateName == "ansible_tenant_template" + - update_netflow_monitor_2.previous.templateId is defined + - update_netflow_monitor_2.previous.description == "test" + - update_netflow_monitor_2.previous.exporterRefs.0.exporterName == "netflow_exporter_1" + - update_netflow_monitor_2.previous.exporterRefs.0.exporterRef != "" + - update_netflow_monitor_2.previous.exporterRefs.0.exporterTemplateId != "" + - update_netflow_monitor_2.previous.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - update_netflow_monitor_2.previous.exporterRefs.1.exporterName == "netflow_exporter_2" + - update_netflow_monitor_2.previous.exporterRefs.1.exporterRef != "" + - update_netflow_monitor_2.previous.exporterRefs.1.exporterTemplateId != "" + - update_netflow_monitor_2.previous.exporterRefs.1.exporterTemplateName == "ansible_tenant_template" + - update_netflow_monitor_2.previous.name == "netflow_monitor_2" + - update_netflow_monitor_2.previous.recordName == "netflow_record_2" + - update_netflow_monitor_2.previous.recordRef != "" + - update_netflow_monitor_2.previous.recordTemplateId != "" + - update_netflow_monitor_2.previous.recordTemplateName == "ansible_tenant_template" + - update_netflow_monitor_2.previous.uuid is defined + - update_netflow_monitor_2_without_changing_exporter is changed + - update_netflow_monitor_2_without_changing_exporter.current.description == "Updated" + - update_netflow_monitor_2_without_changing_exporter.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - update_netflow_monitor_2_without_changing_exporter.current.exporterRefs.0.exporterRef != "" + - update_netflow_monitor_2_without_changing_exporter.current.exporterRefs.0.exporterTemplateId != "" + - update_netflow_monitor_2_without_changing_exporter.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - update_netflow_monitor_2_without_changing_exporter.current.name == "netflow_monitor_2" + - update_netflow_monitor_2_without_changing_exporter.current.recordRef == "" + - update_netflow_monitor_2_without_changing_exporter.previous.description == "" + - update_netflow_monitor_without_description_record_exporter is not changed + - update_netflow_monitor_without_description_record_exporter.current == nm_update_netflow_monitor_again.current + - update_netflow_monitor_without_description_record_exporter.previous == nm_update_netflow_monitor_again.previous + + # QUERY + - name: Query NetFlow Monitor using UUID + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_monitor.current.uuid }}" + state: query + register: query_netflow_monitor_1_uuid + + - name: Query NetFlow Monitor using name + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_2 + state: query + register: query_netflow_monitor_2_name + + - name: Query all NetFlow Monitor + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + state: query + register: query_all_netflow_monitors + + - name: Assertion check for query NetFlow Monitor + ansible.builtin.assert: + that: + - query_netflow_monitor_1_uuid is not changed + - query_netflow_monitor_1_uuid.current.templateName == "ansible_tenant_template" + - query_netflow_monitor_1_uuid.current.templateId is defined + - query_netflow_monitor_1_uuid.current.description == "updated" + - query_netflow_monitor_1_uuid.current.exporterRefs.0.exporterName == "netflow_exporter_2" + - query_netflow_monitor_1_uuid.current.exporterRefs.0.exporterRef != "" + - query_netflow_monitor_1_uuid.current.exporterRefs.0.exporterTemplateId != "" + - query_netflow_monitor_1_uuid.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - query_netflow_monitor_1_uuid.current.name == "netflow_monitor_1_updated" + - query_netflow_monitor_1_uuid.current.recordName == "netflow_record_2" + - query_netflow_monitor_1_uuid.current.recordRef != "" + - query_netflow_monitor_1_uuid.current.recordTemplateId != "" + - query_netflow_monitor_1_uuid.current.recordTemplateName == "ansible_tenant_template" + - query_netflow_monitor_1_uuid.current.uuid is defined + - query_netflow_monitor_2_name is not changed + - query_netflow_monitor_2_name.current.description == "Updated" + - query_netflow_monitor_2_name.current.exporterRefs.0.exporterName == "netflow_exporter_1" + - query_netflow_monitor_2_name.current.exporterRefs.0.exporterRef != "" + - query_netflow_monitor_2_name.current.exporterRefs.0.exporterTemplateId != "" + - query_netflow_monitor_2_name.current.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - query_netflow_monitor_2_name.current.name == "netflow_monitor_2" + - query_netflow_monitor_2_name.current.recordRef == "" + - query_netflow_monitor_2_name.current.uuid is defined + - query_all_netflow_monitors is not changed + - query_all_netflow_monitors.current.0 == query_netflow_monitor_1_uuid.current + - query_all_netflow_monitors.current.1 == query_netflow_monitor_2_name.current + + # ERROR + - name: Negative test add NetFlow Monitor without template + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + name: netflow_monitor_3 + state: present + register: nt_add_netflow_monitor_without_template + ignore_errors: true + + - name: Negative test add NetFlow Monitor without name and uuid + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + state: present + register: nt_add_netflow_monitor_without_name_uuid + ignore_errors: true + + - name: Negative test add NetFlow Monitor with incomplete NetFlow Record + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_3 + netflow_record: + reference: + name: netflow_record_1 + state: present + register: nt_add_netflow_monitor_with_incomplete_record + ignore_errors: true + + - name: Negative test add NetFlow Monitor with incomplete NetFlow Exporter + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_3 + netflow_exporters: + - reference: + name: netflow_exporter_1 + state: present + register: nt_add_netflow_monitor_with_incomplete_exporter + ignore_errors: true + + - name: Negative test create NetFlow Monitor 3 without netflow_exporters + cisco.mso.ndo_tenant_netflow_monitor: + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_3 + description: test + netflow_record: {"reference": {"name": "netflow_record_2", "template": "ansible_tenant_template"}} + state: present + register: nt_add_netflow_monitor_3_without_exporter + ignore_errors: true + + - name: Assertion check for NetFlow Monitor negative tests + ansible.builtin.assert: + that: + - nt_add_netflow_monitor_without_template is not changed + - 'nt_add_netflow_monitor_without_template.msg == "one of the following is required: template, template_id"' + - nt_add_netflow_monitor_without_name_uuid is not changed + - 'nt_add_netflow_monitor_without_name_uuid.msg == "state is present but any of the following are missing: name, uuid"' + - nt_add_netflow_monitor_with_incomplete_record is not changed + - 'nt_add_netflow_monitor_with_incomplete_record.msg == "one of the following is required: template, template_id found in netflow_record -> reference"' + - nt_add_netflow_monitor_with_incomplete_exporter is not changed + - 'nt_add_netflow_monitor_with_incomplete_exporter.msg == "one of the following is required: template, template_id found in netflow_exporters -> reference"' + - nt_add_netflow_monitor_3_without_exporter is not changed + - 'nt_add_netflow_monitor_3_without_exporter.msg == "MSO Error 400: At least one NetFlow Exporter needs to be provided to NetFlow Monitor netflow_monitor_3"' + + # DELETE + - name: Delete NetFlow Monitor 1 using UUID (check_mode) + cisco.mso.ndo_tenant_netflow_monitor: &cm_delete_netflow_monitor_1_uuid + <<: *mso_info + template: ansible_tenant_template + uuid: "{{ nm_add_netflow_monitor.current.uuid }}" + state: absent + output_level: debug + check_mode: true + register: cm_delete_netflow_monitor_1_uuid + + - name: Delete NetFlow Monitor 1 using UUID + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_delete_netflow_monitor_1_uuid + register: nm_delete_netflow_monitor_1_uuid + + - name: Delete NetFlow Monitor 1 using UUID again + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_delete_netflow_monitor_1_uuid + register: nm_delete_netflow_monitor_1_uuid_again + + - name: Delete NetFlow Monitor 2 using name (check_mode) + cisco.mso.ndo_tenant_netflow_monitor: &cm_delete_netflow_monitor_2_name + <<: *mso_info + template: ansible_tenant_template + name: netflow_monitor_2 + state: absent + output_level: debug + check_mode: true + register: cm_delete_netflow_monitor_2_name + + - name: Delete NetFlow Monitor 2 using name + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_delete_netflow_monitor_2_name + state: absent + register: nm_delete_netflow_monitor_2_name + + - name: Delete NetFlow Monitor 2 using name again + cisco.mso.ndo_tenant_netflow_monitor: + <<: *cm_delete_netflow_monitor_2_name + state: absent + register: nm_delete_netflow_monitor_2_name_again + + - name: Assertion check for delete NetFlow Monitor + ansible.builtin.assert: + that: + - cm_delete_netflow_monitor_1_uuid is changed + - cm_delete_netflow_monitor_1_uuid.current == {} + - cm_delete_netflow_monitor_1_uuid.previous.description == "updated" + - cm_delete_netflow_monitor_1_uuid.previous.exporterRefs.0.exporterName == "netflow_exporter_2" + - cm_delete_netflow_monitor_1_uuid.previous.exporterRefs.0.exporterRef != "" + - cm_delete_netflow_monitor_1_uuid.previous.exporterRefs.0.exporterTemplateId != "" + - cm_delete_netflow_monitor_1_uuid.previous.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_delete_netflow_monitor_1_uuid.previous.name == "netflow_monitor_1_updated" + - cm_delete_netflow_monitor_1_uuid.previous.recordName == "netflow_record_2" + - cm_delete_netflow_monitor_1_uuid.previous.recordRef != "" + - cm_delete_netflow_monitor_1_uuid.previous.recordTemplateId != "" + - cm_delete_netflow_monitor_1_uuid.previous.recordTemplateName == "ansible_tenant_template" + - cm_delete_netflow_monitor_1_uuid.previous.uuid is defined + - nm_delete_netflow_monitor_1_uuid is changed + - nm_delete_netflow_monitor_1_uuid.current == {} + - nm_delete_netflow_monitor_1_uuid.previous == cm_delete_netflow_monitor_1_uuid.previous + - nm_delete_netflow_monitor_1_uuid_again is not changed + - nm_delete_netflow_monitor_1_uuid_again.current == {} + - nm_delete_netflow_monitor_1_uuid_again.previous == {} + - cm_delete_netflow_monitor_2_name is changed + - cm_delete_netflow_monitor_2_name.current == {} + - cm_delete_netflow_monitor_2_name.previous.description == "Updated" + - cm_delete_netflow_monitor_2_name.previous.exporterRefs.0.exporterName == "netflow_exporter_1" + - cm_delete_netflow_monitor_2_name.previous.exporterRefs.0.exporterRef != "" + - cm_delete_netflow_monitor_2_name.previous.exporterRefs.0.exporterTemplateId != "" + - cm_delete_netflow_monitor_2_name.previous.exporterRefs.0.exporterTemplateName == "ansible_tenant_template" + - cm_delete_netflow_monitor_2_name.previous.name == "netflow_monitor_2" + - cm_delete_netflow_monitor_2_name.previous.recordRef == "" + - cm_delete_netflow_monitor_2_name.previous.uuid is defined + - nm_delete_netflow_monitor_2_name is changed + - nm_delete_netflow_monitor_2_name.current == {} + - nm_delete_netflow_monitor_2_name.previous == cm_delete_netflow_monitor_2_name.previous + - nm_delete_netflow_monitor_2_name_again is not changed + - nm_delete_netflow_monitor_2_name_again.current == {} + - nm_delete_netflow_monitor_2_name_again.previous == {} + + # CLEANUP + - name: Ensure templates do not exist + cisco.mso.ndo_template: + <<: *template_absent + state: absent