diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index d88f93d7..a5341f49 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -1709,6 +1709,28 @@ def verify_time_format(self, date_time): except ValueError: return self.fail_json(msg="ERROR: The time must be in 'YYYY-MM-DD HH:MM:SS' format.") + def get_site_interface_details(self, site_id=None, uuid=None, node=None, port=None): + if node and port: + path = "/sitephysifsummary/site/{0}?node={1}".format(site_id, node) + elif uuid: + path = "/sitephysifsummary/site/{0}?uuid={1}".format(site_id, uuid) + + site_data = self.request(path, method="GET") + + if uuid: + if site_data.get("spec", {}).get("monitoringTemplateInterfaces"): + return site_data.get("spec", {}).get("monitoringTemplateInterfaces", [])[0] + else: + self.fail_json(msg="The site port interface not found. Site ID: {0} and UUID: {1}".format(site_id, uuid)) + elif node and port: + for interface in site_data.get("spec", {}).get("interfaces", []): + # To ensure consistency between the API response data and the input data by converting the node to a string + if interface.get("port") == port and str(interface.get("node")) == str(node): + return interface + self.fail_json(msg="The site port interface not found. Site ID: {0}, Node: {1} and Path: {2}".format(site_id, node, port)) + + return {} + def service_node_ref_str_to_dict(serviceNodeRefStr): serviceNodeRefTokens = serviceNodeRefStr.split("/") diff --git a/plugins/modules/ndo_fabric_span_session.py b/plugins/modules/ndo_fabric_span_session.py new file mode 100644 index 00000000..25516c01 --- /dev/null +++ b/plugins/modules/ndo_fabric_span_session.py @@ -0,0 +1,706 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2025, 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_fabric_span_session +short_description: Manage Fabric SPAN Sessions on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage Switched Port Analyzer (SPAN) Sessions on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.1 (NDO v4.4) and later. +author: +- Sabari Jaganathan (@sajagana) +options: + template: + description: + - The name of the template. + - The template must be a Fabric Monitoring Access Policy template. + - This parameter or O(template_id) is required. + type: str + template_id: + description: + - The ID of the template. + - The template must be a Fabric Monitoring Access Policy template. + - This parameter or O(template) is required. + type: str + name: + description: + - The name of the SPAN Session. + type: str + aliases: [ span_session ] + uuid: + description: + - The UUID of the SPAN Session. + - This parameter is required when the O(name) needs to be updated. + type: str + aliases: [ span_session_uuid ] + description: + description: + - The description of the SPAN Session. + type: str + admin_state: + description: + - The administrative state of the SPAN Session. + - Defaults to C(enabled) when unset during creation. + choices: [ enabled, disabled ] + type: str + mtu: + description: + - The MTU truncation size for the SPAN packets. + - The value must be in the range 64 - 9216. + - Defaults to 1518 when unset during creation. + type: int + destination_epg: + description: + - The destination EPG configuration group. + - This parameter or O(destination_port) or O(destination_port_channel) is required when creating the SPAN Session. + type: dict + suboptions: + epg_uuid: + description: + - The UUID of the destination EPG to use for the SPAN Session. + - This parameter or O(destination_epg.epg) is required. + type: str + epg: + description: + - The destination EPG to use for the SPAN Session. + - This parameter or O(destination_epg.epg_uuid) is required. + type: dict + suboptions: + name: + description: + - The name of the destination EPG. + type: str + required: true + template: + description: + - The name of the template that contains the destination EPG. + - This parameter or O(destination_epg.epg.template_id) is required. + type: str + template_id: + description: + - The ID of the template that contains the destination EPG. + - This parameter or O(destination_epg.epg.template) is required. + type: str + schema_id: + description: + - The ID of the schema that contains the destination EPG. + - This parameter or O(destination_epg.epg.schema) is required. + type: str + schema: + description: + - The name of the schema that contains the destination EPG. + - This parameter or O(destination_epg.epg.schema_id) is required. + type: str + anp: + description: + - The name of the ANP that contains the destination EPG. + - This parameter or O(destination_epg.epg.anp_uuid) is required. + type: str + anp_uuid: + description: + - The UUID of the ANP that contains the destination EPG. + - This parameter or O(destination_epg.epg.anp) is required. + type: str + destination_ip: + description: + - The destination IP address to route SPAN Session packets. + type: str + source_ip_prefix: + description: + - The prefix used to assign source IP addresses to ERSPAN packets which can be used to identify which Leaf or Spine is sending the traffic. + - This can be any IP. If the prefix is used, the node ID of the source node is used for the undefined bits of the prefix. + type: str + span_version: + description: + - The version of the SPAN Session. + - Defaults to C(v2) when unset during creation. + choices: [ v1, v2 ] + type: str + enforce_span_version: + description: + - Enforce the SPAN Session version defined in O(destination_epg.span_version). + - Defaults to true when unset during creation. + type: bool + flow_id: + description: + - The flow ID of the SPAN Session packets. + - The value must be in the range 1 - 1023. + - Defaults to 1 when unset during creation. + type: int + ttl: + description: + - The time to live (TTL) of the SPAN Session packets. + - The value must be in the range 1 - 1023. + - Defaults to 1 when unset during creation. + type: int + dscp: + description: + - The DSCP value for sending the monitored SPAN Session packets. + - Defaults to C(unspecified) when unset during creation. + choices: + - af11 + - af12 + - af13 + - af21 + - af22 + - af23 + - af31 + - af32 + - af33 + - af41 + - af42 + - af43 + - cs0 + - cs1 + - cs2 + - cs3 + - cs4 + - cs5 + - cs6 + - cs7 + - expedited_forwarding + - voice_admit + - unspecified + type: str + destination_port: + description: + - The destination port configuration group. + - This parameter or O(destination_epg) or O(destination_port_channel) is required when creating the SPAN Session. + type: dict + suboptions: + port_uuid: + description: + - The UUID of the destination port to use for the SPAN Session. + - This parameter or O(destination_port.port) is required. + type: str + port: + description: + - The destination port to use for the SPAN Session. + - This parameter or O(destination_port.port_uuid) is required. + type: dict + suboptions: + node: + description: + - The Node ID of the Node to use for the SPAN Session. + type: int + required: true + interface: + description: + - The Ethernet interface of the Node to use for the SPAN Session + type: str + required: true + destination_port_channel: + description: + - The destination port channel configuration group. + - This parameter or O(destination_epg) or O(destination_port) is required when creating the SPAN Session. + type: dict + suboptions: + port_channel_uuid: + description: + - The UUID of the destination port channel to use for the SPAN Session. + - This parameter or O(destination_port_channel.port_channel) is required. + type: str + port_channel: + description: + - The destination port channel to use for the SPAN Session. + - This parameter or O(destination_port_channel.port_channel_uuid) is required. + type: dict + suboptions: + name: + description: + - The name of the destination port channel. + type: str + required: true + template: + description: + - The name of the template that contains the destination port channel. + - This parameter or O(destination_port_channel.port_channel.template_id) is required. + type: str + template_id: + description: + - The ID of the template that contains the destination port channel. + - This parameter or O(destination_port_channel.port_channel.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 Fabric Monitoring Access Policy template. +- The O(destination_epg.epg) must exist before using it with this module in your playbook. + Use M(cisco.mso.mso_schema_template_anp_epg) to create the EPG. +- The O(destination_port_channel.port_channel) must exist before using it with this module in your playbook. + Use M(cisco.mso.ndo_port_channel_interface) to create the Fabric resource port channel interface. +seealso: +- module: cisco.mso.ndo_template +- module: cisco.mso.mso_schema_template_anp_epg +- module: cisco.mso.ndo_port_channel_interface +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create Fabric SPAN Session with destination EPG + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_epg + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + register: create_epg_span_session + +- name: Create Fabric SPAN Session with destination Port + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_port + destination_port: + port: + node: 101 + interface: "eth1/1" + state: present + +- name: Create Fabric SPAN Session with destination Port Channel + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_pc + destination_port_channel: + port_channel: + template: ansible_test + name: ansible_test_resource_pc_1 + state: present + +- name: Update Fabric SPAN Session from destination EPG to destination Port + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_epg + destination_port: + port: + node: 101 + interface: "eth1/1" + state: present + +- name: Update Fabric SPAN Session from destination Port to destination Port Channel + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_port + destination_port_channel: + port_channel: + template: ansible_test + name: ansible_test_resource_pc_1 + state: present + +- name: Update Fabric SPAN Session from destination Port Channel to destination EPG + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_pc + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + +- name: Update the Fabric SPAN Session name using UUID + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + uuid: "{{ create_epg_span_session.current.uuid }}" + name: ansible_test_pc_updated + state: present + +- name: Query a specific Fabric SPAN Session using name + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_pc_updated + state: query + register: query_with_name + +- name: Query a specific Fabric SPAN Session using UUID + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + uuid: "{{ create_epg_span_session.current.uuid }}" + state: query + register: query_with_uuid + +- name: Query all Fabric SPAN Sessions + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + state: query + register: query_all_objects + +- name: Delete a specific Fabric SPAN Session using Name + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template: ansible_test + name: ansible_test_pc_updated + state: absent + +- name: Delete a Fabric SPAN Session using UUID + cisco.mso.ndo_fabric_span_session: + host: mso_host + username: admin + password: SomeSecretPassword + template_id: ansible_test + uuid: "{{ create_epg_span_session.current.uuid }}" + state: absent +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, epg_object_reference_spec +from ansible_collections.cisco.mso.plugins.module_utils.schemas import MSOSchemas +from ansible_collections.cisco.mso.plugins.module_utils.templates import MSOTemplates +from ansible_collections.cisco.mso.plugins.module_utils.template import MSOTemplate, KVPair +from ansible_collections.cisco.mso.plugins.module_utils.constants import TARGET_DSCP_MAP, ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP +from ansible_collections.cisco.mso.plugins.module_utils.utils import append_update_ops_data +import copy + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str"), + template_id=dict(type="str"), + name=dict(type="str", aliases=["span_session"]), + uuid=dict(type="str", aliases=["span_session_uuid"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + mtu=dict(type="int"), + destination_epg=dict( + type="dict", + mutually_exclusive=[ + ("epg", "epg_uuid"), + ], + required_one_of=[ + ["epg", "epg_uuid"], + ], + options=dict( + epg_uuid=dict(type="str"), + epg=epg_object_reference_spec(), + destination_ip=dict(type="str"), + source_ip_prefix=dict(type="str"), + span_version=dict(type="str", choices=["v1", "v2"]), + enforce_span_version=dict(type="bool"), + flow_id=dict(type="int"), + ttl=dict(type="int"), + dscp=dict(type="str", choices=list(TARGET_DSCP_MAP)), + ), + ), + destination_port=dict( + type="dict", + mutually_exclusive=[ + ("port", "port_uuid"), + ], + required_one_of=[ + ["port", "port_uuid"], + ], + options=dict( + port_uuid=dict(type="str"), + port=dict( + type="dict", + options=dict( + node=dict(type="int", required=True), + interface=dict(type="str", required=True), + ), + ), + ), + ), + destination_port_channel=dict( + type="dict", + mutually_exclusive=[ + ("port_channel", "port_channel_uuid"), + ], + required_one_of=[ + ["port_channel", "port_channel_uuid"], + ], + options=dict( + port_channel_uuid=dict(type="str"), + port_channel=dict( + type="dict", + 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"), + ], + ), + ), + ), + state=dict(type="str", default="query", choices=["absent", "query", "present"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[("template", "template_id"), ("destination_epg", "destination_port_channel", "destination_port")], + required_if=[ + ["state", "absent", ["name", "uuid"], True], + ["state", "present", ["name", "uuid"], True], + ], + required_one_of=[ + ["template", "template_id"], + ], + ) + + mso = MSOModule(module) + mso_schemas = MSOSchemas(mso) + mso_templates = MSOTemplates(mso) + + template_name = module.params.get("template") + template_id = module.params.get("template_id") + name = module.params.get("name") + uuid = module.params.get("uuid") + description = module.params.get("description") + admin_state = ENABLED_OR_DISABLED_TO_BOOL_STRING_MAP.get(module.params.get("admin_state")) if module.params.get("admin_state") else None + mtu = module.params.get("mtu") + destination_epg = module.params.get("destination_epg") + destination_port = module.params.get("destination_port") + destination_port_channel = module.params.get("destination_port_channel") + state = module.params.get("state") + + mso_template = MSOTemplate(mso, "monitoring_tenant", template_name, template_id) + mso_template.validate_template("monitoring") + object_description = "SPAN Session" + site_id = mso_template.template.get("monitoringTemplate").get("sites")[0].get("siteId") + + match = None + if uuid or name: + match = mso_template.get_object_by_key_value_pairs( + object_description, + mso_template.template.get("monitoringTemplate", {}).get("template", {}).get("spanSessions", []), + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], + ) + else: + match = mso_template.template.get("monitoringTemplate", {}).get("template", {}).get("spanSessions", []) + + if (uuid or name) and match: + set_fabric_span_session_object_details(mso_template, site_id, match.details) + mso.existing = mso.previous = copy.deepcopy(match.details) # Query a specific object + elif match: + mso.existing = [set_fabric_span_session_object_details(mso_template, site_id, obj) for obj in match] # Query all objects + + if state != "query": + span_session_path = "/monitoringTemplate/template/spanSessions/{0}".format(match.index if match else "-") + + ops = [] + + if state == "present": + if uuid and not mso.existing: + mso.fail_json(msg="{0} with the UUID: '{1}' not found".format(object_description, uuid)) + + mso_values = dict() + if destination_epg: + mso_values["destination"] = dict( + remote=dict( + epgRef=mso_schemas.get_epg_uuid(destination_epg.get("epg"), destination_epg.get("epg_uuid")), + spanVersion=destination_epg.get("span_version"), + enforceSpanVersion=destination_epg.get("enforce_span_version"), + destIPAddress=destination_epg.get("destination_ip"), + srcIPPrefix=destination_epg.get("source_ip_prefix"), + flowID=destination_epg.get("flow_id"), + ttl=destination_epg.get("ttl"), + dscp=TARGET_DSCP_MAP.get(destination_epg.get("dscp")), + ), + mtu=mtu, + ) + + if destination_port: + # Destination Port supports UUIDs, but we have no module to get them + destination_port_uuid = destination_port.get("port_uuid") + + if destination_port_uuid is None: + node = destination_port.get("port").get("node") + interface_port = destination_port.get("port").get("interface") + destination_port_uuid = mso.get_site_interface_details(site_id=site_id, uuid=None, node=node, port=interface_port).get("uuid") + mso_values["destination"] = dict(local=dict(accessInterface=destination_port_uuid), mtu=mtu) + + if destination_port_channel: + destination_port_channel_uuid = destination_port_channel.get("port_channel_uuid") + if destination_port_channel_uuid is None: + fabric_resource_template = mso_templates.get_template( + "fabric_resource", + destination_port_channel.get("port_channel").get("template"), + destination_port_channel.get("port_channel").get("template_id"), + ) + + destination_port_channel_uuid = fabric_resource_template.get_template_policy_uuid( + "fabric_resource", destination_port_channel.get("port_channel").get("name"), "portChannels" + ) + + mso_values["destination"] = dict(local=dict(portChannel=destination_port_channel_uuid), mtu=mtu) + + if match: + mso_update_values = {"name": name, "description": description} + mso_update_values[("destination", "mtu")] = mtu + + if admin_state is not None: + mso_update_values[("sourceGroup", "enableAdminState")] = admin_state + + if destination_epg: + if match.details.get("destination", {}).get("local", {}).get("accessInterface") or match.details.get("destination", {}).get("local", {}).get( + "portChannel" + ): + mso_update_values[("destination", "local")] = dict() + + remote_group = mso_values.get("destination", {}).get("remote") + if remote_group: + mso_update_values[("destination", "remote", "epgRef")] = remote_group.get("epgRef") + mso_update_values[("destination", "remote", "spanVersion")] = remote_group.get("spanVersion") + mso_update_values[("destination", "remote", "enforceSpanVersion")] = remote_group.get("enforceSpanVersion") + mso_update_values[("destination", "remote", "destIPAddress")] = remote_group.get("destIPAddress") + mso_update_values[("destination", "remote", "srcIPPrefix")] = remote_group.get("srcIPPrefix") + mso_update_values[("destination", "remote", "flowID")] = remote_group.get("flowID") + mso_update_values[("destination", "remote", "ttl")] = remote_group.get("ttl") + mso_update_values[("destination", "remote", "dscp")] = remote_group.get("dscp") + + if destination_port: + if match.details.get("destination", {}).get("remote", {}).get("epgRef"): + mso_update_values[("destination", "remote")] = dict() + + if match.details.get("destination", {}).get("local", {}).get("portChannel"): + mso_update_values[("destination", "local")] = dict() + + mso_update_values[("destination", "local")] = dict(accessInterface=destination_port_uuid) + + if destination_port_channel: + if match.details.get("destination", {}).get("remote", {}).get("epgRef"): + mso_update_values[("destination", "remote")] = dict() + + if match.details.get("destination", {}).get("local", {}).get("accessInterface"): + mso_update_values[("destination", "local")] = dict() + + mso_update_values[("destination", "local")] = dict(portChannel=destination_port_channel_uuid) + + proposed_payload = copy.deepcopy(match.details) + append_update_ops_data(ops, proposed_payload, span_session_path, mso_update_values) + mso.sanitize(proposed_payload, collate=True) + else: + mso_values["name"] = name + mso_values["description"] = description + + if admin_state is not None: + mso_values["sourceGroup"] = dict(enableAdminState=admin_state) + + mso.sanitize(mso_values) + ops.append(dict(op="add", path=span_session_path, value=mso.sent)) + + elif state == "absent": + if match: + ops.append(dict(op="remove", path=span_session_path)) + + if not module.check_mode and ops: + response = mso.request(mso_template.template_path, method="PATCH", data=ops) + match = mso_template.get_object_by_key_value_pairs( + object_description, + response.get("monitoringTemplate", {}).get("template", {}).get("spanSessions", []), + [KVPair("uuid", uuid) if uuid else KVPair("name", name)], + ) + if match: + set_fabric_span_session_object_details(mso_template, site_id, match.details) + mso.existing = match.details # When the state is present + else: + mso.existing = {} # When the state is absent + elif module.check_mode and state != "query": # When the state is present/absent with check mode + set_fabric_span_session_object_details(mso_template, site_id, mso.proposed) + mso.existing = mso.proposed if state == "present" else {} + mso.exit_json() + + +def set_fabric_span_session_object_details(mso_template, site_id, span_session): + if span_session: + span_session.update({"templateId": mso_template.template_id, "templateName": mso_template.template_name}) + if span_session.get("destination", {}).get("local", {}).get("accessInterface"): + interface = mso_template.mso.get_site_interface_details(site_id, span_session.get("destination").get("local").get("accessInterface")) + interface.pop("uuid", None) + span_session.get("destination").get("local").update(interface) + else: + reference_details = { + "remote": { + "name": "epgName", + "reference": "epgRef", + "type": "epg", + "template": "epgTemplateName", + "templateId": "epgTemplateId", + "schema": "epgSchemaName", + "schemaId": "epgSchemaId", + }, + "local": { + "name": "portChannelName", + "reference": "portChannel", + "type": "portChannel", + "template": "portChannelTemplateName", + "templateId": "portChannelTemplateId", + }, + } + mso_template.update_config_with_template_and_references( + span_session.get("destination"), + reference_details, + False, + ) + return span_session + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ndo_fabric_span_session/aliases b/tests/integration/targets/ndo_fabric_span_session/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_fabric_span_session/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_fabric_span_session/tasks/main.yml b/tests/integration/targets/ndo_fabric_span_session/tasks/main.yml new file mode 100644 index 00000000..08bf8d29 --- /dev/null +++ b/tests/integration/targets/ndo_fabric_span_session/tasks/main.yml @@ -0,0 +1,944 @@ +# Test code for the MSO modules +# Copyright: (c) 2025, 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 + +- 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.4 + when: version.current.version is version('4.4', '>=') + block: + # CLEAN TEST ENVIRONMENT + - name: Ensure Fabric Monitoring Access Policy template do not exist + cisco.mso.ndo_template: &rm_monitoring_access_template + <<: *mso_info + template: ansible_test + template_type: monitoring_access + sites: + - name: '{{ mso_site | default("ansible_test") }}' + state: absent + register: rm_monitoring_access_template + + - name: Ensure Fabric Resource Policy template do not exist + cisco.mso.ndo_template: &rm_fabric_resource_template + <<: *mso_info + template: ansible_test + template_type: fabric_resource + sites: + - name: '{{ mso_site | default("ansible_test") }}' + state: absent + register: rm_fabric_resource_template + + - name: Ensure Fabric Policy template do not exist + cisco.mso.ndo_template: &rm_fabric_policy_template + <<: *mso_info + template: ansible_test + template_type: fabric_policy + state: absent + register: rm_fabric_policy_template + + - name: Ensure Schema do not exist + cisco.mso.mso_schema: + <<: *mso_info + schema: ansible_test + state: absent + + - name: Ensure site exist + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: "{{ apic_username }}" + apic_password: "{{ apic_password }}" + apic_site_id: '{{ apic_site_id | default("101") }}' + urls: + - https://{{ apic_hostname }} + state: present + + - name: Ensure Tenant exist + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - "{{ mso_username }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + + - name: Ensure schema template exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: ansible_test + tenant: ansible_test + template: Template1 + state: present + register: add_schema_template + + - name: Ensure Anp1 exist + cisco.mso.mso_schema_template_anp: + <<: *mso_info + schema: ansible_test + template: Template1 + anp: Anp1 + state: present + register: add_anp1 + + - name: Ensure EPG1 exist + cisco.mso.mso_schema_template_anp_epg: + <<: *mso_info + schema: ansible_test + template: Template1 + anp: Anp1 + epg: EPG1 + state: present + register: add_epg1 + + - name: Ensure EPG2 exist + cisco.mso.mso_schema_template_anp_epg: + <<: *mso_info + schema: ansible_test + template: Template1 + anp: Anp1 + epg: EPG2 + state: present + register: add_epg2 + + - name: Query ansible_test schema # To get the schema, template, anp, epg UUIDs + cisco.mso.mso_schema: + <<: *mso_info + schema: ansible_test + state: query + register: query_schema + + - name: Ensure Fabric Monitoring Access Policy template exist + cisco.mso.ndo_template: + <<: *rm_monitoring_access_template + state: present + register: add_monitoring_access_template + + - name: Ensure Fabric Resource Policy template do not exist + cisco.mso.ndo_template: + <<: *rm_fabric_resource_template + state: present + register: add_fabric_resource_template + + - name: Ensure Fabric Policy template do not exist + cisco.mso.ndo_template: + <<: *rm_fabric_policy_template + state: present + register: add_fabric_policy_template + + - name: Create an Interface Policy group of interface_type 'port_channel' + cisco.mso.ndo_interface_setting: &add_interface_policy_group_pc + <<: *mso_info + template: ansible_test + name: ansible_test_interface_pc_pol + interface_type: port_channel + state: present + register: add_interface_policy_group_pc + + - name: Create ansible_test_resource_pc_1 port channel interface + cisco.mso.ndo_port_channel_interface: &add_ansible_test_1_resource_pc_1 + <<: *mso_info + template: ansible_test + port_channel_interface: ansible_test_resource_pc_1 + node: 101 + interfaces: 1/20 + interface_policy_group: + name: ansible_test_interface_pc_pol + template: ansible_test + state: present + register: add_ansible_test_1_resource_pc_1 + + - name: Create ansible_test_resource_pc_2 port channel interface + cisco.mso.ndo_port_channel_interface: &add_ansible_test_1_resource_pc_2 + <<: *add_ansible_test_1_resource_pc_1 + port_channel_interface: ansible_test_resource_pc_2 + node: 101 + interfaces: 1/21 + state: present + register: add_ansible_test_1_resource_pc_2 + + # CREATE + - name: Create Fabric SPAN Session with destination EPG (check_mode) + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_epg + description: test + admin_state: enabled + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + output_level: debug + check_mode: true + register: cm_add_epg_span_session + + - name: Create Fabric SPAN Session with destination EPG + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_epg + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + register: nm_add_epg_span_session + + - name: Create Fabric SPAN Session with destination EPG again + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_epg + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + register: nm_add_epg_span_session_again + + - name: Create Fabric SPAN Session with destination Port + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_port + destination_port: + port: + node: 101 + interface: "eth1/1" + state: present + register: add_port_span_session + + - name: Create Fabric SPAN Session with destination Port Channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc + destination_port_channel: + port_channel: + template: ansible_test + name: ansible_test_resource_pc_1 + state: present + register: add_port_channel_span_session + + - name: Assertion check for create Fabric SPAN Sessions + ansible.builtin.assert: + that: + - cm_add_epg_span_session is changed + - cm_add_epg_span_session.current.description == "test" + - cm_add_epg_span_session.current.destination.mtu == None + - cm_add_epg_span_session.current.destination.remote.destIPAddress == "1.1.1.1" + - cm_add_epg_span_session.current.destination.remote.dscp == None + - cm_add_epg_span_session.current.destination.remote.enforceSpanVersion == None + - cm_add_epg_span_session.current.destination.remote.epgName == "EPG1" + - cm_add_epg_span_session.current.destination.remote.epgRef != "" + - cm_add_epg_span_session.current.destination.remote.epgSchemaId is defined + - cm_add_epg_span_session.current.destination.remote.epgSchemaName == "ansible_test" + - cm_add_epg_span_session.current.destination.remote.epgTemplateId != "" + - cm_add_epg_span_session.current.destination.remote.epgTemplateName == "Template1" + - cm_add_epg_span_session.current.destination.remote.flowID == None + - cm_add_epg_span_session.current.destination.remote.spanVersion == None + - cm_add_epg_span_session.current.destination.remote.srcIPPrefix == "2.2.2.2" + - cm_add_epg_span_session.current.destination.remote.ttl == None + - cm_add_epg_span_session.current.name == "ansible_test_epg" + - cm_add_epg_span_session.current.sourceGroup.enableAdminState == true + - cm_add_epg_span_session.current.templateId != "" + - cm_add_epg_span_session.current.templateName == "ansible_test" + - cm_add_epg_span_session.previous == {} + - cm_add_epg_span_session.proposed.description == "test" + - cm_add_epg_span_session.proposed.destination.mtu == None + - cm_add_epg_span_session.proposed.destination.remote.destIPAddress == "1.1.1.1" + - cm_add_epg_span_session.proposed.destination.remote.dscp == None + - cm_add_epg_span_session.proposed.destination.remote.enforceSpanVersion == None + - cm_add_epg_span_session.proposed.destination.remote.epgName == "EPG1" + - cm_add_epg_span_session.proposed.destination.remote.epgRef != "" + - cm_add_epg_span_session.proposed.destination.remote.epgSchemaId is defined + - cm_add_epg_span_session.proposed.destination.remote.epgSchemaName == "ansible_test" + - cm_add_epg_span_session.proposed.destination.remote.epgTemplateId != "" + - cm_add_epg_span_session.proposed.destination.remote.epgTemplateName == "Template1" + - cm_add_epg_span_session.proposed.destination.remote.flowID == None + - cm_add_epg_span_session.proposed.destination.remote.spanVersion == None + - cm_add_epg_span_session.proposed.destination.remote.srcIPPrefix == "2.2.2.2" + - cm_add_epg_span_session.proposed.destination.remote.ttl == None + - cm_add_epg_span_session.proposed.name == "ansible_test_epg" + - cm_add_epg_span_session.proposed.sourceGroup.enableAdminState == true + - cm_add_epg_span_session.proposed.templateId != "" + - cm_add_epg_span_session.proposed.templateName == "ansible_test" + - nm_add_epg_span_session is changed + - nm_add_epg_span_session.current.description == "" + - nm_add_epg_span_session.current.destination.local == {} + - nm_add_epg_span_session.current.destination.mtu == 1518 + - nm_add_epg_span_session.current.destination.remote.destIPAddress == "1.1.1.1" + - nm_add_epg_span_session.current.destination.remote.dscp == "unspecified" + - nm_add_epg_span_session.current.destination.remote.epgName == "EPG1" + - nm_add_epg_span_session.current.destination.remote.epgRef != "" + - nm_add_epg_span_session.current.destination.remote.epgSchemaId is defined + - nm_add_epg_span_session.current.destination.remote.epgSchemaName == "ansible_test" + - nm_add_epg_span_session.current.destination.remote.epgTemplateId != "" + - nm_add_epg_span_session.current.destination.remote.epgTemplateName == "Template1" + - nm_add_epg_span_session.current.destination.remote.flowID == 1 + - nm_add_epg_span_session.current.destination.remote.spanVersion == "v1" + - nm_add_epg_span_session.current.destination.remote.srcIPPrefix == "2.2.2.2" + - nm_add_epg_span_session.current.destination.remote.ttl == 64 + - nm_add_epg_span_session.current.name == "ansible_test_epg" + - nm_add_epg_span_session.current.sourceGroup.enableAdminState == false + - nm_add_epg_span_session.current.templateId != "" + - nm_add_epg_span_session.current.templateName == "ansible_test" + - nm_add_epg_span_session.current.uuid is defined + - nm_add_epg_span_session.previous == {} + - nm_add_epg_span_session_again is not changed + - nm_add_epg_span_session_again.current == nm_add_epg_span_session.current + - add_port_span_session is changed + - add_port_span_session.current.description == "" + - add_port_span_session.current.destination.local.accessInterface is defined + - add_port_span_session.current.destination.local.cardNum == "1" + - add_port_span_session.current.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - add_port_span_session.current.destination.local.node == "101" + - add_port_span_session.current.destination.local.pod == "1" + - add_port_span_session.current.destination.local.port == "eth1/1" + - add_port_span_session.current.destination.local.portNum == "1" + - add_port_span_session.current.destination.local.portType == "leaf" + - add_port_span_session.current.destination.mtu == 1518 + - add_port_span_session.current.destination.remote.dscp == "cs0" + - add_port_span_session.current.destination.remote.flowID == 1 + - add_port_span_session.current.destination.remote.spanVersion == "v1" + - add_port_span_session.current.destination.remote.ttl == 64 + - add_port_span_session.current.name == "ansible_test_port" + - add_port_span_session.current.sourceGroup.enableAdminState == false + - add_port_span_session.current.templateId != "" + - add_port_span_session.current.templateName == "ansible_test" + - add_port_span_session.current.uuid is defined + - add_port_span_session.previous == {} + - add_port_channel_span_session is changed + - add_port_channel_span_session.current.description == "" + - add_port_channel_span_session.current.destination.local.portChannel is defined + - add_port_channel_span_session.current.destination.local.portChannelName == "ansible_test_resource_pc_1" + - add_port_channel_span_session.current.destination.local.portChannelTemplateId != "" + - add_port_channel_span_session.current.destination.local.portChannelTemplateName == "ansible_test" + - add_port_channel_span_session.current.destination.mtu == 1518 + - add_port_channel_span_session.current.destination.remote.dscp == "cs0" + - add_port_channel_span_session.current.destination.remote.flowID == 1 + - add_port_channel_span_session.current.destination.remote.spanVersion == "v1" + - add_port_channel_span_session.current.destination.remote.ttl == 64 + - add_port_channel_span_session.current.name == "ansible_test_pc" + - add_port_channel_span_session.current.sourceGroup.enableAdminState == false + - add_port_channel_span_session.current.templateId != "" + - add_port_channel_span_session.current.templateName == "ansible_test" + - add_port_channel_span_session.current.uuid is defined + - add_port_channel_span_session.previous == {} + + # UPDATE + - name: Update Fabric SPAN Session destination EPG using UUID (check_mode) + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template_id: "{{ add_monitoring_access_template.current.templateId }}" + uuid: "{{ nm_add_epg_span_session.current.uuid }}" + destination_epg: + epg: + schema_id: "{{ query_schema.current.id }}" + template_id: "{{ query_schema.current.templates.0.templateID }}" + anp_uuid: "{{ query_schema.current.templates.0.anps.0.uuid }}" + name: EPG1 + destination_ip: "1.1.1.10" + source_ip_prefix: "2.2.2.20" + span_version: v2 + enforce_span_version: true + flow_id: 5 + ttl: 5 + dscp: af11 + state: present + output_level: debug + check_mode: true + register: cm_update_epg_span_session + + - name: Update Fabric SPAN Session destination EPG using UUID + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template_id: "{{ add_monitoring_access_template.current.templateId }}" + uuid: "{{ nm_add_epg_span_session.current.uuid }}" + destination_epg: + epg: + schema_id: "{{ query_schema.current.id }}" + template_id: "{{ query_schema.current.templates.0.templateID }}" + anp_uuid: "{{ query_schema.current.templates.0.anps.0.uuid }}" + name: EPG1 + destination_ip: "1.1.1.10" + source_ip_prefix: "2.2.2.20" + span_version: v2 + enforce_span_version: true + flow_id: 5 + ttl: 5 + dscp: af11 + state: present + register: nm_update_epg_span_session + + - name: Update Fabric SPAN Session destination EPG using UUID again + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template_id: "{{ add_monitoring_access_template.current.templateId }}" + uuid: "{{ nm_add_epg_span_session.current.uuid }}" + destination_epg: + epg: + schema_id: "{{ query_schema.current.id }}" + template_id: "{{ query_schema.current.templates.0.templateID }}" + anp_uuid: "{{ query_schema.current.templates.0.anps.0.uuid }}" + name: EPG1 + destination_ip: "1.1.1.10" + source_ip_prefix: "2.2.2.20" + span_version: v2 + enforce_span_version: true + flow_id: 5 + ttl: 5 + dscp: af11 + state: present + register: nm_update_epg_span_session_again + + - name: Update Fabric SPAN Session destination EPG using EPG2 UUID + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template_id: "{{ add_monitoring_access_template.current.templateId }}" + uuid: "{{ nm_add_epg_span_session.current.uuid }}" + destination_epg: + epg_uuid: "{{ query_schema.current.templates.0.anps.0.epgs.1.uuid }}" + destination_ip: "1.1.1.10" + source_ip_prefix: "2.2.2.20" + span_version: v2 + enforce_span_version: true + flow_id: 5 + ttl: 5 + dscp: af11 + state: present + register: update_with_epg2_uuid + + - name: Update Fabric SPAN Session destination Port value + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_port + destination_port: + port: + node: 101 + interface: "eth1/2" + admin_state: enabled + mtu: 1520 + state: present + register: update_port_span_session + + - name: Update Fabric SPAN Session destination Port Channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc + destination_port_channel: + port_channel_uuid: "{{ add_ansible_test_1_resource_pc_2.current.uuid }}" + state: present + register: update_port_channel_span_session + + - name: Update Fabric SPAN Session from destination EPG to destination Port + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_epg + description: "Changed EPG - Fabric SPAN to Port Fabric SPAN" + destination_port: + port: + node: 101 + interface: "eth1/1" + state: present + register: epg_to_port_span_session + + - name: Update Fabric SPAN Session from destination Port to destination Port Channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_port + description: "Changed Port - Fabric SPAN to Port Channel Fabric SPAN" + destination_port_channel: + port_channel: + template: ansible_test + name: ansible_test_resource_pc_1 + state: present + register: port_to_port_channel_span_session + + - name: Update Fabric SPAN Session from destination Port Channel to destination EPG + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc + description: "Changed Port Channel - Fabric SPAN to EPG Fabric SPAN" + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + register: port_channel_to_epg_span_session + + - name: Update Fabric SPAN Session from destination Port Channel to destination Port + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_port + description: "Changed Port Channel - Fabric SPAN to Port Fabric SPAN" + destination_port: + port: + node: 101 + interface: "eth1/1" + state: present + register: port_channel_to_port_span_session + + - name: Update Fabric SPAN Session from destination EPG to destination Port Channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc + description: "Changed EPG - Fabric SPAN to Port Channel Fabric SPAN" + destination_port_channel: + port_channel: + template: ansible_test + name: ansible_test_resource_pc_1 + state: present + register: epg_to_port_channel_span_session + + - name: Update SPAN Session name using UUID + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + uuid: "{{ epg_to_port_channel_span_session.current.uuid }}" + name: ansible_test_pc_updated + state: present + register: update_span_session_name + + - name: Assertion check for update Fabric SPAN Sessions + ansible.builtin.assert: + that: + - cm_update_epg_span_session is changed + - cm_update_epg_span_session.current.description == "" + - cm_update_epg_span_session.current.destination.local == {} + - cm_update_epg_span_session.current.destination.mtu == 1518 + - cm_update_epg_span_session.current.destination.remote.destIPAddress == "1.1.1.10" + - cm_update_epg_span_session.current.destination.remote.dscp == "af11" + - cm_update_epg_span_session.current.destination.remote.enforceSpanVersion == true + - cm_update_epg_span_session.current.destination.remote.epgName == "EPG1" + - cm_update_epg_span_session.current.destination.remote.epgRef != "" + - cm_update_epg_span_session.current.destination.remote.epgSchemaId is defined + - cm_update_epg_span_session.current.destination.remote.epgSchemaName == "ansible_test" + - cm_update_epg_span_session.current.destination.remote.epgTemplateId != "" + - cm_update_epg_span_session.current.destination.remote.epgTemplateName == "Template1" + - cm_update_epg_span_session.current.destination.remote.flowID == 5 + - cm_update_epg_span_session.current.destination.remote.spanVersion == "v2" + - cm_update_epg_span_session.current.destination.remote.srcIPPrefix == "2.2.2.20" + - cm_update_epg_span_session.current.destination.remote.ttl == 5 + - cm_update_epg_span_session.current.name == "ansible_test_epg" + - cm_update_epg_span_session.current.sourceGroup.enableAdminState == false + - cm_update_epg_span_session.current.templateId != "" + - cm_update_epg_span_session.current.templateName == "ansible_test" + - cm_update_epg_span_session.current.uuid is defined + - cm_update_epg_span_session.previous.description == "" + - cm_update_epg_span_session.previous.destination.local == {} + - cm_update_epg_span_session.previous.destination.mtu == 1518 + - cm_update_epg_span_session.previous.destination.remote.destIPAddress == "1.1.1.1" + - cm_update_epg_span_session.previous.destination.remote.dscp == "unspecified" + - cm_update_epg_span_session.previous.destination.remote.epgName == "EPG1" + - cm_update_epg_span_session.previous.destination.remote.epgRef != "" + - cm_update_epg_span_session.previous.destination.remote.epgSchemaId is defined + - cm_update_epg_span_session.previous.destination.remote.epgSchemaName == "ansible_test" + - cm_update_epg_span_session.previous.destination.remote.epgTemplateId != "" + - cm_update_epg_span_session.previous.destination.remote.epgTemplateName == "Template1" + - cm_update_epg_span_session.previous.destination.remote.flowID == 1 + - cm_update_epg_span_session.previous.destination.remote.spanVersion == "v1" + - cm_update_epg_span_session.previous.destination.remote.srcIPPrefix == "2.2.2.2" + - cm_update_epg_span_session.previous.destination.remote.ttl == 64 + - cm_update_epg_span_session.previous.name == "ansible_test_epg" + - cm_update_epg_span_session.previous.sourceGroup.enableAdminState == false + - cm_update_epg_span_session.previous.templateId != "" + - cm_update_epg_span_session.previous.templateName == "ansible_test" + - cm_update_epg_span_session.previous.uuid is defined + - cm_update_epg_span_session.proposed.description == "" + - cm_update_epg_span_session.proposed.destination.local == {} + - cm_update_epg_span_session.proposed.destination.mtu == 1518 + - cm_update_epg_span_session.proposed.destination.remote.destIPAddress == "1.1.1.10" + - cm_update_epg_span_session.proposed.destination.remote.dscp == "af11" + - cm_update_epg_span_session.proposed.destination.remote.enforceSpanVersion == true + - cm_update_epg_span_session.proposed.destination.remote.epgName == "EPG1" + - cm_update_epg_span_session.proposed.destination.remote.epgRef != "" + - cm_update_epg_span_session.proposed.destination.remote.epgSchemaId is defined + - cm_update_epg_span_session.proposed.destination.remote.epgSchemaName == "ansible_test" + - cm_update_epg_span_session.proposed.destination.remote.epgTemplateId != "" + - cm_update_epg_span_session.proposed.destination.remote.epgTemplateName == "Template1" + - cm_update_epg_span_session.proposed.destination.remote.flowID == 5 + - cm_update_epg_span_session.proposed.destination.remote.spanVersion == "v2" + - cm_update_epg_span_session.proposed.destination.remote.srcIPPrefix == "2.2.2.20" + - cm_update_epg_span_session.proposed.destination.remote.ttl == 5 + - cm_update_epg_span_session.proposed.name == "ansible_test_epg" + - cm_update_epg_span_session.proposed.sourceGroup.enableAdminState == false + - cm_update_epg_span_session.proposed.templateId != "" + - cm_update_epg_span_session.proposed.templateName == "ansible_test" + - cm_update_epg_span_session.proposed.uuid is defined + - nm_update_epg_span_session is changed + - nm_update_epg_span_session.current.description == "" + - nm_update_epg_span_session.current.destination.local == {} + - nm_update_epg_span_session.current.destination.mtu == 1518 + - nm_update_epg_span_session.current.destination.remote.destIPAddress == "1.1.1.10" + - nm_update_epg_span_session.current.destination.remote.dscp == "af11" + - nm_update_epg_span_session.current.destination.remote.enforceSpanVersion == true + - nm_update_epg_span_session.current.destination.remote.epgName == "EPG1" + - nm_update_epg_span_session.current.destination.remote.epgRef != "" + - nm_update_epg_span_session.current.destination.remote.epgSchemaId is defined + - nm_update_epg_span_session.current.destination.remote.epgSchemaName == "ansible_test" + - nm_update_epg_span_session.current.destination.remote.epgTemplateId != "" + - nm_update_epg_span_session.current.destination.remote.epgTemplateName == "Template1" + - nm_update_epg_span_session.current.destination.remote.flowID == 5 + - nm_update_epg_span_session.current.destination.remote.spanVersion == "v2" + - nm_update_epg_span_session.current.destination.remote.srcIPPrefix == "2.2.2.20" + - nm_update_epg_span_session.current.destination.remote.ttl == 5 + - nm_update_epg_span_session.current.name == "ansible_test_epg" + - nm_update_epg_span_session.current.sourceGroup.enableAdminState == false + - nm_update_epg_span_session.current.templateId != "" + - nm_update_epg_span_session.current.templateName == "ansible_test" + - nm_update_epg_span_session.current.uuid is defined + - nm_update_epg_span_session.previous.description == "" + - nm_update_epg_span_session.previous.destination.local == {} + - nm_update_epg_span_session.previous.destination.mtu == 1518 + - nm_update_epg_span_session.previous.destination.remote.destIPAddress == "1.1.1.1" + - nm_update_epg_span_session.previous.destination.remote.dscp == "unspecified" + - nm_update_epg_span_session.previous.destination.remote.epgName == "EPG1" + - nm_update_epg_span_session.previous.destination.remote.epgRef != "" + - nm_update_epg_span_session.previous.destination.remote.epgSchemaId is defined + - nm_update_epg_span_session.previous.destination.remote.epgSchemaName == "ansible_test" + - nm_update_epg_span_session.previous.destination.remote.epgTemplateId != "" + - nm_update_epg_span_session.previous.destination.remote.epgTemplateName == "Template1" + - nm_update_epg_span_session.previous.destination.remote.flowID == 1 + - nm_update_epg_span_session.previous.destination.remote.spanVersion == "v1" + - nm_update_epg_span_session.previous.destination.remote.srcIPPrefix == "2.2.2.2" + - nm_update_epg_span_session.previous.destination.remote.ttl == 64 + - nm_update_epg_span_session.previous.name == "ansible_test_epg" + - nm_update_epg_span_session.previous.sourceGroup.enableAdminState == false + - nm_update_epg_span_session.previous.templateId != "" + - nm_update_epg_span_session.previous.templateName == "ansible_test" + - nm_update_epg_span_session.previous.uuid is defined + - nm_update_epg_span_session_again.current == nm_update_epg_span_session.current + - update_with_epg2_uuid is changed + - update_with_epg2_uuid.current.destination.remote.epgName == "EPG2" + - update_with_epg2_uuid.previous.destination.remote.epgName == "EPG1" + - update_port_span_session is changed + - update_port_span_session.current.description == "" + - update_port_span_session.current.destination.local.accessInterface is defined + - update_port_span_session.current.destination.local.cardNum == "1" + - update_port_span_session.current.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/2]" + - update_port_span_session.current.destination.local.node == "101" + - update_port_span_session.current.destination.local.pod == "1" + - update_port_span_session.current.destination.local.port == "eth1/2" + - update_port_span_session.current.destination.local.portNum == "2" + - update_port_span_session.current.destination.local.portType == "leaf" + - update_port_span_session.previous.destination.local.cardNum == "1" + - update_port_span_session.previous.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - update_port_span_session.previous.destination.local.node == "101" + - update_port_span_session.previous.destination.local.pod == "1" + - update_port_span_session.previous.destination.local.port == "eth1/1" + - update_port_span_session.previous.destination.local.portNum == "1" + - update_port_span_session.previous.destination.local.portType == "leaf" + - update_port_channel_span_session is changed + - update_port_channel_span_session.current.description == "" + - update_port_channel_span_session.current.destination.local.portChannel is defined + - update_port_channel_span_session.current.destination.local.portChannelName == "ansible_test_resource_pc_2" + - update_port_channel_span_session.previous.destination.local.portChannel is defined + - update_port_channel_span_session.previous.destination.local.portChannelName == "ansible_test_resource_pc_1" + - update_port_channel_span_session.previous.uuid is defined + - epg_to_port_span_session is changed + - epg_to_port_span_session.current.description == "Changed EPG - Fabric SPAN to Port Fabric SPAN" + - epg_to_port_span_session.current.destination.local.accessInterface is defined + - epg_to_port_span_session.current.destination.local.cardNum == "1" + - epg_to_port_span_session.current.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - epg_to_port_span_session.current.destination.local.node == "101" + - epg_to_port_span_session.current.destination.local.pod == "1" + - epg_to_port_span_session.current.destination.local.port == "eth1/1" + - epg_to_port_span_session.current.destination.local.portNum == "1" + - epg_to_port_span_session.current.destination.local.portType == "leaf" + - epg_to_port_span_session.previous.destination.remote.destIPAddress == "1.1.1.10" + - epg_to_port_span_session.previous.destination.remote.dscp == "af11" + - epg_to_port_span_session.previous.destination.remote.enforceSpanVersion == true + - epg_to_port_span_session.previous.destination.remote.epgName == "EPG2" + - epg_to_port_span_session.previous.sourceGroup.enableAdminState == false + - epg_to_port_span_session.previous.templateId != "" + - epg_to_port_span_session.previous.templateName == "ansible_test" + - epg_to_port_span_session.previous.uuid is defined + - port_to_port_channel_span_session is changed + - port_to_port_channel_span_session.current.description == "Changed Port - Fabric SPAN to Port Channel Fabric SPAN" + - port_to_port_channel_span_session.current.destination.local.portChannel is defined + - port_to_port_channel_span_session.current.destination.local.portChannelName == "ansible_test_resource_pc_1" + - port_to_port_channel_span_session.previous.destination.local.accessInterface is defined + - port_to_port_channel_span_session.previous.destination.local.cardNum == "1" + - port_to_port_channel_span_session.previous.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/2]" + - port_to_port_channel_span_session.previous.destination.local.node == "101" + - port_to_port_channel_span_session.previous.destination.local.pod == "1" + - port_to_port_channel_span_session.previous.destination.local.port == "eth1/2" + - port_to_port_channel_span_session.previous.destination.local.portNum == "2" + - port_to_port_channel_span_session.previous.destination.local.portType == "leaf" + - port_to_port_channel_span_session.previous.uuid is defined + - port_channel_to_epg_span_session is changed + - port_channel_to_epg_span_session.current.description == "Changed Port Channel - Fabric SPAN to EPG Fabric SPAN" + - port_channel_to_epg_span_session.current.destination.remote.destIPAddress == "1.1.1.1" + - port_channel_to_epg_span_session.current.destination.remote.dscp == "cs0" + - port_channel_to_epg_span_session.current.destination.remote.epgName == "EPG1" + - port_channel_to_epg_span_session.current.destination.remote.epgRef != "" + - port_channel_to_epg_span_session.previous.destination.local.portChannel is defined + - port_channel_to_epg_span_session.previous.destination.local.portChannelName == "ansible_test_resource_pc_2" + - port_channel_to_epg_span_session.previous.destination.local.portChannelTemplateId != "" + - port_channel_to_epg_span_session.previous.destination.local.portChannelTemplateName == "ansible_test" + - port_channel_to_port_span_session is changed + - port_channel_to_port_span_session.current.description == "Changed Port Channel - Fabric SPAN to Port Fabric SPAN" + - port_channel_to_port_span_session.current.destination.local.accessInterface is defined + - port_channel_to_port_span_session.current.destination.local.cardNum == "1" + - port_channel_to_port_span_session.current.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - port_channel_to_port_span_session.current.destination.local.node == "101" + - port_channel_to_port_span_session.current.destination.local.pod == "1" + - port_channel_to_port_span_session.current.destination.local.port == "eth1/1" + - port_channel_to_port_span_session.current.destination.local.portNum == "1" + - port_channel_to_port_span_session.current.destination.local.portType == "leaf" + - port_channel_to_port_span_session.previous.description == "Changed Port - Fabric SPAN to Port Channel Fabric SPAN" + - port_channel_to_port_span_session.previous.destination.local.portChannel is defined + - port_channel_to_port_span_session.previous.destination.local.portChannelName == "ansible_test_resource_pc_1" + - port_channel_to_port_span_session.previous.destination.local.portChannelTemplateId != "" + - port_channel_to_port_span_session.previous.destination.local.portChannelTemplateName == "ansible_test" + - port_channel_to_port_span_session.previous.uuid is defined + - epg_to_port_channel_span_session is changed + - epg_to_port_channel_span_session.current.description == "Changed EPG - Fabric SPAN to Port Channel Fabric SPAN" + - epg_to_port_channel_span_session.current.destination.local.portChannel is defined + - epg_to_port_channel_span_session.current.destination.local.portChannelName == "ansible_test_resource_pc_1" + - epg_to_port_channel_span_session.current.destination.local.portChannelTemplateId != "" + - epg_to_port_channel_span_session.current.destination.local.portChannelTemplateName == "ansible_test" + - epg_to_port_channel_span_session.previous.description == "Changed Port Channel - Fabric SPAN to EPG Fabric SPAN" + - epg_to_port_channel_span_session.previous.destination.remote.destIPAddress == "1.1.1.1" + - epg_to_port_channel_span_session.previous.destination.remote.dscp == "cs0" + - epg_to_port_channel_span_session.previous.destination.remote.epgName == "EPG1" + - epg_to_port_channel_span_session.previous.destination.remote.epgRef != "" + - epg_to_port_channel_span_session.previous.uuid is defined + - update_span_session_name is changed + - update_span_session_name.current.name == "ansible_test_pc_updated" + - update_span_session_name.previous.name == "ansible_test_pc" + + # QUERY + - name: Query a specific SPAN Session + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc_updated + state: query + register: query_one_object + + - name: Query all SPAN Sessions + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + state: query + register: query_all_objects + + - name: Assertion check for query Fabric SPAN Sessions + ansible.builtin.assert: + that: + - query_one_object is not changed + - query_one_object.current.description == "Changed EPG - Fabric SPAN to Port Channel Fabric SPAN" + - query_one_object.current.destination.local.portChannel is defined + - query_one_object.current.destination.local.portChannelName == "ansible_test_resource_pc_1" + - query_one_object.current.destination.local.portChannelTemplateId != "" + - query_one_object.current.destination.local.portChannelTemplateName == "ansible_test" + - query_one_object.current.destination.mtu == 1518 + - query_one_object.current.destination.remote.dscp == "unspecified" + - query_one_object.current.destination.remote.flowID == 1 + - query_one_object.current.destination.remote.spanVersion == "v1" + - query_one_object.current.destination.remote.ttl == 64 + - query_one_object.current.name == "ansible_test_pc_updated" + - query_one_object.current.sourceGroup.enableAdminState == false + - query_one_object.current.templateId != "" + - query_one_object.current.templateName == "ansible_test" + - query_one_object.current.uuid is defined + - query_all_objects is not changed + - query_all_objects.current | length >= 3 + - "'ansible_test_epg' in query_all_objects.current | map(attribute='name') | list" + - "'ansible_test_port' in query_all_objects.current | map(attribute='name') | list" + - "'ansible_test_pc_updated' in query_all_objects.current | map(attribute='name') | list" + + # ERROR + - name: Create Fabric SPAN Session with destination_epg, destination_port and destination_port_channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + destination_epg: {} + destination_port: {} + destination_port_channel: {} + state: present + register: nt_add_with_all_destination + ignore_errors: true + + - name: Create Fabric SPAN Session with invalid destination_port + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: invalid_span_session + destination_port: + port: + node: 1011 + interface: "eth1/1" + state: present + register: nt_add_with_invalid_destination_port + ignore_errors: true + + - name: Create Fabric SPAN Session without destination_epg, destination_port and destination_port_channel + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: nt_span_session + state: present + register: nt_add_without_destination + ignore_errors: true + + - name: Update Fabric SPAN Session with invalid UUID + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + uuid: "invalid_uuid" + destination_epg: + epg: + schema: ansible_test + template: Template1 + anp: Anp1 + name: EPG1 + destination_ip: "1.1.1.1" + source_ip_prefix: "2.2.2.2" + state: present + register: nt_update_port_channel_with_uuid + ignore_errors: true + + - name: Query an invalid Fabric SPAN Session + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: nt_ansible_test_pc + state: query + register: nt_query_one_object + + - name: Assertion check for invalid checks for the Fabric SPAN Session + ansible.builtin.assert: + that: + - nt_add_with_all_destination is not changed + - nt_add_with_all_destination.msg == "parameters are mutually exclusive{{':'}} destination_epg|destination_port_channel|destination_port" + - nt_add_without_destination is not changed + - nt_add_without_destination.msg == "MSO Error 400{{':'}} {\"errors\"{{':'}}[\"SPAN session nt_span_session{{':'}} invalid access span destination - no destination provided\"]}" + - nt_update_port_channel_with_uuid is not changed + - nt_update_port_channel_with_uuid.current == {} + - nt_update_port_channel_with_uuid.msg == "SPAN Session with the UUID{{':'}} 'invalid_uuid' not found" + - nt_update_port_channel_with_uuid.previous == {} + - nt_query_one_object is not changed + - nt_query_one_object.current == {} + - nt_add_with_invalid_destination_port is not changed + - nt_add_with_invalid_destination_port.msg is match("The site port interface not found. Site ID{{':'}} [a-zA-z0-9]*, Node{{':'}} 1011 and Path{{':'}} eth1/1") + + # DELETE + - name: Delete a specific SPAN Session using Name (check_mode) + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc_updated + state: absent + output_level: debug + check_mode: true + register: cm_rm_span_session_with_name + + - name: Delete a specific SPAN Session using Name + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc_updated + state: absent + register: nm_rm_span_session_with_name + + - name: Delete a specific SPAN Session using Name again + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template: ansible_test + name: ansible_test_pc_updated + state: absent + register: nm_rm_span_session_with_name_again + + - name: Delete a Fabric SPAN Session using UUID + cisco.mso.ndo_fabric_span_session: + <<: *mso_info + template_id: "{{ add_monitoring_access_template.current.templateId }}" + uuid: "{{ nm_add_epg_span_session.current.uuid }}" + state: absent + register: rm_span_session_with_uuid + + - name: Assertion check for delete Fabric SPAN Sessions + ansible.builtin.assert: + that: + - cm_rm_span_session_with_name is changed + - cm_rm_span_session_with_name.current == {} + - cm_rm_span_session_with_name.previous.name == "ansible_test_pc_updated" + - cm_rm_span_session_with_name.proposed == {} + - nm_rm_span_session_with_name is changed + - nm_rm_span_session_with_name.current == {} + - nm_rm_span_session_with_name.previous.destination.local.portChannelName == "ansible_test_resource_pc_1" + - nm_rm_span_session_with_name.previous.destination.local.portChannelTemplateId != "" + - nm_rm_span_session_with_name.previous.destination.local.portChannelTemplateName == "ansible_test" + - nm_rm_span_session_with_name.previous.name == "ansible_test_pc_updated" + - nm_rm_span_session_with_name.previous.uuid is defined + - nm_rm_span_session_with_name_again is not changed + - nm_rm_span_session_with_name_again.current == {} + - nm_rm_span_session_with_name_again.previous == {} + - rm_span_session_with_uuid is changed + - rm_span_session_with_uuid.current == {} + - rm_span_session_with_uuid.previous.destination.local.dn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - rm_span_session_with_uuid.previous.name == "ansible_test_epg" + - rm_span_session_with_uuid.previous.uuid is defined + + # CLEAN TEST ENVIRONMENT + - name: Ensure Fabric Monitoring Access Policy template do not exist + cisco.mso.ndo_template: + <<: *rm_monitoring_access_template + + - name: Ensure Fabric Resource Policy template do not exist + cisco.mso.ndo_template: + <<: *rm_fabric_resource_template + + - name: Ensure Fabric Policy template do not exist + cisco.mso.ndo_template: + <<: *rm_fabric_policy_template