diff --git a/plugins/module_utils/template.py b/plugins/module_utils/template.py index 55d235b4..8a93e480 100644 --- a/plugins/module_utils/template.py +++ b/plugins/module_utils/template.py @@ -207,3 +207,37 @@ def get_l3out_node_routing_policy_object(self, uuid=None, name=None, fail_module "L3Out Node Routing Policy", existing_l3out_node_routing_policy, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module ) return existing_l3out_node_routing_policy # Query all objects + + def get_l3out_object(self, uuid=None, name=None, fail_module=False): + """ + Get the L3Out by uuid or name. + :param uuid: UUID of the L3Out to search for -> Str + :param name: Name of the L3Out to search for -> Str + :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[] + """ + existing_l3outs = self.template.get("l3outTemplate", {}).get("l3outs", []) + if uuid or name: # Query a specific object + return self.get_object_by_key_value_pairs("L3Out", existing_l3outs, [KVPair("uuid", uuid) if uuid else KVPair("name", name)], fail_module) + return existing_l3outs # Query all objects + + def get_l3out_node_group(self, name, l3out_object, fail_module=False): + """ + Get the L3Out Node/Interface Group Policy by name. + :param name: Name of the L3Out Node/Interface Group Policy to search for -> Str + :param l3out_object: L3Out object to search Node/Interface Group Policy -> 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[] + """ + existing_l3out_node_groups = l3out_object.get("nodeGroups", []) + if name: # Query a specific object + return self.get_object_by_key_value_pairs("L3Out Node/Interface Group Policy", existing_l3out_node_groups, [KVPair("name", name)], fail_module) + return existing_l3out_node_groups # Query all objects diff --git a/plugins/modules/ndo_l3out_node_group_policy.py b/plugins/modules/ndo_l3out_node_group_policy.py new file mode 100644 index 00000000..bf4d878e --- /dev/null +++ b/plugins/modules/ndo_l3out_node_group_policy.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, 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_l3out_node_group_policy +short_description: Manage L3Out Node/Interface Group Policy on Cisco Nexus Dashboard Orchestrator (NDO). +description: +- Manage L3Out Node/Interface Group Policy on Cisco Nexus Dashboard Orchestrator (NDO). +- This module is only supported on ND v3.1 (NDO v4.3) and later. +author: +- Sabari Jaganathan (@sajagana) +options: + template: + description: + - The name of the template. + - The template must be a L3Out template. + type: str + required: true + l3out: + description: + - The name of the L3Out. + type: str + required: true + name: + description: + - The name of the L3Out Node/Interface Group Policy. + type: str + description: + description: + - The description of the L3Out Node/Interface Group Policy. + type: str + node_routing_policy: + description: + - The name of the L3Out Node Routing Policy. + type: str + bfd_multi_hop_authentication: + description: + - The bidirectional forwarding detection (BFD) multi-hop authentication of the L3Out Node/Interface Group Policy. + - To enable the O(bfd_multi_hop_authentication) BGP routing protocol must be configured on the L3Out. + type: str + choices: [ enabled, disabled ] + bfd_multi_hop_key_id: + description: + - The BFD multi-hop key ID of the L3Out Node/Interface Group Policy. + type: int + bfd_multi_hop_key: + description: + - The BFD multi-hop key of the L3Out Node/Interface Group Policy. + type: str + target_dscp: + description: + - The DSCP Level of the L3Out Node/Interface Group Policy. + type: str + choices: + - af11 + - af12 + - af13 + - af21 + - af22 + - af23 + - af31 + - af32 + - af33 + - af41 + - af42 + - af43 + - cs0 + - cs1 + - cs2 + - cs3 + - cs4 + - cs5 + - cs6 + - cs7 + - expedited_forwarding + - unspecified + - voice_admit + 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 L3Out template. +- The O(l3out) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_l3out_template) to create the L3Out object under the L3Out template. +- The O(node_routing_policy) must exist before using this module in your playbook. + Use M(cisco.mso.ndo_l3out_node_routing_policy) to create the L3Out Node Routing Policy. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create a new L3Out node group policy + cisco.mso.ndo_l3out_node_group_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: l3out_template + l3out: l3out + name: "node_group_policy_1" + state: present + +- name: Update an existing L3Out node group policy + cisco.mso.ndo_l3out_node_group_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: l3out_template + l3out: l3out + name: "node_group_policy_1" + description: "Updated description" + node_routing_policy: ans_node_policy_group_1 + bfd_multi_hop_authentication: enabled + bfd_multi_hop_key_id: 1 + bfd_multi_hop_key: TestKey + target_dscp: af11 + state: present + +- name: Query a L3Out node group policy + cisco.mso.ndo_l3out_node_group_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: l3out_template + l3out: l3out + name: "node_group_policy_1" + state: query + register: query_with_name + +- name: Delete an existing L3Out node group policy with name + cisco.mso.ndo_l3out_node_group_policy: + host: mso_host + username: admin + password: SomeSecretPassword + template: l3out_template + l3out: l3out + name: "node_group_policy_1" + 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.template import MSOTemplate, KVPair +from ansible_collections.cisco.mso.plugins.module_utils.constants import TARGET_DSCP_MAP +from ansible_collections.cisco.mso.plugins.module_utils.utils import generate_api_endpoint + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + template=dict(type="str", required=True), # L3Out template name + l3out=dict(type="str", required=True), # L3Out name + name=dict(type="str"), # L3Out Node/Interface Group Policy name + description=dict(type="str"), + node_routing_policy=dict(type="str"), + bfd_multi_hop_authentication=dict(type="str", choices=["enabled", "disabled"]), + bfd_multi_hop_key_id=dict(type="int"), + bfd_multi_hop_key=dict(type="str", no_log=False), + target_dscp=dict(type="str", choices=list(TARGET_DSCP_MAP)), + 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"]], + ["state", "present", ["name"]], + ["bfd_multi_hop_authentication", "enabled", ["bfd_multi_hop_key_id", "bfd_multi_hop_key"]], + ], + ) + + mso = MSOModule(module) + + template = module.params.get("template") + l3out = module.params.get("l3out") + name = module.params.get("name") + description = module.params.get("description") + node_routing_policy = module.params.get("node_routing_policy") + bfd_multi_hop_authentication = module.params.get("bfd_multi_hop_authentication") + bfd_multi_hop_key_id = module.params.get("bfd_multi_hop_key_id") + bfd_multi_hop_key = module.params.get("bfd_multi_hop_key") + target_dscp = TARGET_DSCP_MAP.get(module.params.get("target_dscp")) + state = module.params.get("state") + + mso_template = MSOTemplate(mso, "l3out", template) + mso_template.validate_template("l3out") + + l3out_object = mso_template.get_l3out_object(name=l3out, fail_module=True) + l3out_node_group = mso_template.get_l3out_node_group(name, l3out_object.details) + + if name: + if l3out_node_group: + mso.existing = mso.previous = copy.deepcopy(l3out_node_group.details) # Query a specific object + elif l3out_node_group: + mso.existing = l3out_node_group # Query all objects + + if state != "query": + node_group_policy_path = "/l3outTemplate/l3outs/{0}/nodeGroups/{1}".format(l3out_object.index, l3out_node_group.index if l3out_node_group else "-") + + ops = [] + + if state == "present": + l3out_node_routing_policy_object = None + if node_routing_policy: + l3out_node_routing_policy_objects = mso.query_objs( + generate_api_endpoint( + "templates/objects", **{"type": "l3OutNodePolGroup", "tenant-id": mso_template.template_summary.get("tenantId"), "include-common": "true"} + ) + ) + l3out_node_routing_policy_object = mso_template.get_object_by_key_value_pairs( + "L3Out Node Routing Policy", l3out_node_routing_policy_objects, [KVPair("name", node_routing_policy)], True + ) + + if mso.existing: + proposed_payload = copy.deepcopy(mso.existing) + + if description is not None and mso.existing.get("description") != description: + ops.append(dict(op="replace", path=node_group_policy_path + "/description", value=description)) + proposed_payload["description"] = description + + if ( + node_routing_policy is not None + and l3out_node_routing_policy_object + and mso.existing.get("nodeRoutingPolicyRef") != l3out_node_routing_policy_object.details.get("uuid") + ): + ops.append( + dict(op="replace", path=node_group_policy_path + "/nodeRoutingPolicyRef", value=l3out_node_routing_policy_object.details.get("uuid")) + ) + proposed_payload["nodeRoutingPolicyRef"] = l3out_node_routing_policy_object.details.get("uuid") + elif node_routing_policy == "" and mso.existing.get("nodeRoutingPolicyRef"): + ops.append(dict(op="remove", path=node_group_policy_path + "/nodeRoutingPolicyRef")) + proposed_payload.pop("nodeRoutingPolicyRef", None) + + if bfd_multi_hop_authentication == "enabled": + if not mso.existing.get("bfdMultiHop"): + proposed_payload["bfdMultiHop"] = dict() + ops.append(dict(op="replace", path=node_group_policy_path + "/bfdMultiHop", value=dict())) + + if mso.existing.get("bfdMultiHop", {}).get("keyID") != bfd_multi_hop_key_id: + ops.append(dict(op="replace", path=node_group_policy_path + "/bfdMultiHop/keyID", value=bfd_multi_hop_key_id)) + proposed_payload["bfdMultiHop"]["keyID"] = bfd_multi_hop_key_id + + ops.append(dict(op="replace", path=node_group_policy_path + "/bfdMultiHop/value", value=bfd_multi_hop_key)) + proposed_payload["bfdMultiHop"]["value"] = bfd_multi_hop_key + + elif bfd_multi_hop_authentication == "disabled" and mso.existing.get("bfdMultiHop"): + proposed_payload.pop("bfdMultiHop", None) + ops.append(dict(op="remove", path=node_group_policy_path + "/bfdMultiHop")) + + if target_dscp is not None and mso.existing.get("targetDscp") != target_dscp: + ops.append(dict(op="replace", path=node_group_policy_path + "/targetDscp", value=target_dscp)) + proposed_payload["targetDscp"] = target_dscp + + mso.sanitize(proposed_payload, collate=True) + + else: + payload = dict(name=name) + + if description: + payload["description"] = description + + if node_routing_policy and l3out_node_routing_policy_object: + payload["nodeRoutingPolicyRef"] = l3out_node_routing_policy_object.details.get("uuid") + + if bfd_multi_hop_authentication == "enabled": + payload["bfdMultiHop"] = dict(authEnabled=True, keyID=bfd_multi_hop_key_id, key=dict(value=bfd_multi_hop_key)) + + if target_dscp: + payload["targetDscp"] = target_dscp + + mso.sanitize(payload) + ops.append(dict(op="add", path=node_group_policy_path, value=payload)) + + mso.existing = mso.proposed + + elif state == "absent": + if mso.existing: + ops.append(dict(op="remove", path=node_group_policy_path)) + + if not module.check_mode and ops: + mso_template.template = mso.request(mso_template.template_path, method="PATCH", data=ops) + l3out_object = mso_template.get_l3out_object(name=l3out, fail_module=True) + l3out_node_group = mso_template.get_l3out_node_group(name, l3out_object.details) + if l3out_node_group: + mso.existing = l3out_node_group.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/tests/integration/targets/ndo_l3out_node_group_policy/aliases b/tests/integration/targets/ndo_l3out_node_group_policy/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/ndo_l3out_node_group_policy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/ndo_l3out_node_group_policy/tasks/main.yml b/tests/integration/targets/ndo_l3out_node_group_policy/tasks/main.yml new file mode 100644 index 00000000..6b7ac110 --- /dev/null +++ b/tests/integration/targets/ndo_l3out_node_group_policy/tasks/main.yml @@ -0,0 +1,591 @@ +# Test code for the MSO modules +# Copyright: (c) 2024, 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.2 + when: version.current.version is version('4.2', '>') + block: + # Setup Part + - name: Ensure l3out template not exist + cisco.mso.ndo_template: &ndo_l3out_template_absent + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + template_type: l3out + tenant: '{{ ansible_tenant | default("ansible_test") }}' + sites: + - name: '{{ mso_site | default("ansible_test") }}' + state: absent + + - name: Ensure ansible_test_policy tenant policy template not exists + cisco.mso.ndo_template: &tenant_pol_template_absent + <<: *mso_info + name: ansible_test_policy + template_type: tenant + tenant: '{{ ansible_tenant | default("ansible_test") }}' + state: absent + + - name: Ensure common tenant policy template not exists + cisco.mso.ndo_template: &common_ansible_test_policy + <<: *tenant_pol_template_absent + name: common_ansible_test_policy + tenant: common + state: absent + + - name: Ensure ansible_test schema template not exist + cisco.mso.mso_schema_template: &mso_schema_template_absent + <<: *mso_info + schema: '{{ ansible_schema | default("ansible_test") }}' + tenant: '{{ ansible_tenant | default("ansible_test") }}' + template: "Template1" + state: absent + + - name: Ensure ansible_test site exist + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: ansible_test_site + + - name: Ensure ansible_test tenant exist + cisco.mso.mso_tenant: + <<: *mso_info + tenant: '{{ ansible_tenant | default("ansible_test") }}' + users: + - "{{ mso_username }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + register: ansible_test_tenant + when: ansible_test_site.current.common.name == 'ansible_test' + + - name: Ensure common tenant exist + cisco.mso.mso_tenant: + <<: *mso_info + tenant: common + users: + - "{{ mso_username }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + register: ansible_test_tenant + when: ansible_test_site.current.common.name == 'ansible_test' + + # Schema Template Setup for the VRF + - name: Add an ansible_test schema template + cisco.mso.mso_schema_template: + <<: *mso_schema_template_absent + state: present + + - name: Add a new VRF1 + cisco.mso.mso_schema_template_vrf: + <<: *mso_info + schema: '{{ ansible_schema | default("ansible_test") }}' + template: "Template1" + vrf: VRF1 + layer3_multicast: true + state: present + + # Tenant Policy Template Setup for the L3Out Node Routing Policy + - name: Ensure ansible_test_policy tenant policy template exists + cisco.mso.ndo_template: + <<: *tenant_pol_template_absent + state: present + register: ansible_test_policy + + - name: Ensure common tenant policy template exists + cisco.mso.ndo_template: + <<: *common_ansible_test_policy + state: present + register: common_ansible_test_policy + + - name: Add a new L3Out Node Routing Policy + cisco.mso.ndo_l3out_node_routing_policy: + <<: *mso_info + template: ansible_test_policy + name: "{{ item }}" + bfd_multi_hop_settings: + state: enabled + bgp_node_settings: + state: enabled + as_path_multipath_relax: enabled + state: present + loop: + - ans_node_policy_group_1 + - ans_node_policy_group_2 + + - name: Add a new L3Out Node Routing Policy to the common template + cisco.mso.ndo_l3out_node_routing_policy: + <<: *mso_info + template: common_ansible_test_policy + name: ans_node_policy_group_common + bfd_multi_hop_settings: + state: enabled + bgp_node_settings: + state: enabled + as_path_multipath_relax: enabled + state: present + + # L3Out Template Setup + - name: Create a new l3out template + cisco.mso.ndo_template: + <<: *ndo_l3out_template_absent + state: present + register: create_new_l3out_policy_template + + - name: Create l3out_1 object without routing protocols + cisco.mso.ndo_l3out_template: &l3out_1_present + <<: *mso_info + l3out_template: '{{ ansible_l3out_template | default("ansible_test") }}' + name: "l3out_1" + vrf: + name: "VRF1" + schema: '{{ ansible_schema | default("ansible_test") }}' + template: "Template1" + state: "present" + + - name: Create l3out_2 object with bgp enabled + cisco.mso.ndo_l3out_template: + <<: *l3out_1_present + name: "l3out_2" + bgp: + state: enabled + + # Test Part + - name: Query all L3Out node group policy objects when the L3Out is empty + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + state: query + register: query_all_node_groups_1 + + - name: Assertion check for query all L3Out node group policy objects when the L3Out is empty + ansible.builtin.assert: + that: + - query_all_node_groups_1 is not changed + - query_all_node_groups_1.current == {} + + - name: Query node_group_policy_1 L3Out node group policy object when the L3Out is empty + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_1" + state: query + register: query_node_group_policy_1 + + - name: Assertion check for query node_group_policy_1 L3Out node group policy object when the L3Out is empty + ansible.builtin.assert: + that: + - query_node_group_policy_1 is not changed + - query_node_group_policy_1.current == {} + + - name: Create L3Out node group policy object with default values - check mode + cisco.mso.ndo_l3out_node_group_policy: &cm_node_group_policy_1_present + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_1" + state: present + check_mode: true + register: cm_node_group_policy_1_present + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object with default values - check mode + ansible.builtin.assert: + that: + - cm_node_group_policy_1_present is changed + - cm_node_group_policy_1_present.current.name == "node_group_policy_1" + - cm_node_group_policy_1_present.previous == {} + + - name: Create L3Out node group policy object with default values - normal mode + cisco.mso.ndo_l3out_node_group_policy: &nm_node_group_policy_1_present + <<: *cm_node_group_policy_1_present + register: nm_node_group_policy_1_present + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object with default values - normal mode + ansible.builtin.assert: + that: + - nm_node_group_policy_1_present is changed + - nm_node_group_policy_1_present.current.name == "node_group_policy_1" + - nm_node_group_policy_1_present.current.nodeRoutingPolicyRef == "" + - nm_node_group_policy_1_present.current.targetDscp == "unspecified" + - nm_node_group_policy_1_present.previous == {} + + - name: Create L3Out node group policy object with default values - normal mode again + cisco.mso.ndo_l3out_node_group_policy: + <<: *nm_node_group_policy_1_present + register: nm_node_group_policy_1_present_again + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object with default values - normal mode again + ansible.builtin.assert: + that: + - nm_node_group_policy_1_present_again is not changed + - nm_node_group_policy_1_present_again.current.name == "node_group_policy_1" + - nm_node_group_policy_1_present_again.current.nodeRoutingPolicyRef == "" + - nm_node_group_policy_1_present_again.current.targetDscp == "unspecified" + - nm_node_group_policy_1_present_again.previous.name == "node_group_policy_1" + - nm_node_group_policy_1_present_again.previous.nodeRoutingPolicyRef == "" + - nm_node_group_policy_1_present_again.previous.targetDscp == "unspecified" + + - name: Update L3Out node group policy object with check mode + cisco.mso.ndo_l3out_node_group_policy: &cm_update_node_group_policy_1 + <<: *nm_node_group_policy_1_present + description: "Test description" + node_routing_policy: ans_node_policy_group_1 + bfd_multi_hop_authentication: enabled + bfd_multi_hop_key_id: 1 + bfd_multi_hop_key: TestKey + target_dscp: af11 + state: present + check_mode: true + register: cm_update_node_group_policy_1 + ignore_errors: true + + - name: Assertion check for update L3Out node group policy object with check mode + ansible.builtin.assert: + that: + - cm_update_node_group_policy_1 is changed + - cm_update_node_group_policy_1.current.bfdMultiHop.keyID == 1 + - cm_update_node_group_policy_1.current.bfdMultiHop.value == "TestKey" + - cm_update_node_group_policy_1.current.description == "Test description" + - cm_update_node_group_policy_1.current.name == "node_group_policy_1" + - cm_update_node_group_policy_1.current.nodeRoutingPolicyRef != "" + - cm_update_node_group_policy_1.current.targetDscp == "af11" + - cm_update_node_group_policy_1.previous.name == "node_group_policy_1" + - cm_update_node_group_policy_1.previous.nodeRoutingPolicyRef == "" + - cm_update_node_group_policy_1.previous.targetDscp == "unspecified" + + - name: Update L3Out node group policy object with normal mode + cisco.mso.ndo_l3out_node_group_policy: &nm_update_node_group_policy_1 + <<: *cm_update_node_group_policy_1 + register: nm_update_node_group_policy_1 + ignore_errors: true + + - name: Assertion check for update L3Out node group policy object with normal mode + ansible.builtin.assert: + that: + - nm_update_node_group_policy_1 is changed + - nm_update_node_group_policy_1.current.bfdMultiHop.authEnabled == false + - nm_update_node_group_policy_1.current.bfdMultiHop.keyID == 1 + - nm_update_node_group_policy_1.current.description == "Test description" + - nm_update_node_group_policy_1.current.name == "node_group_policy_1" + - nm_update_node_group_policy_1.current.nodeRoutingPolicyRef != "" + - nm_update_node_group_policy_1.current.targetDscp == "af11" + - nm_update_node_group_policy_1.previous.name == "node_group_policy_1" + - nm_update_node_group_policy_1.previous.nodeRoutingPolicyRef == "" + - nm_update_node_group_policy_1.previous.targetDscp == "unspecified" + + - name: Update L3Out node group policy object with normal mode again + cisco.mso.ndo_l3out_node_group_policy: + <<: *nm_update_node_group_policy_1 + register: nm_update_node_group_policy_1_again + ignore_errors: true + + - name: Assertion check for update L3Out node group policy object with normal mode again + ansible.builtin.assert: + that: + - nm_update_node_group_policy_1_again is not changed + - nm_update_node_group_policy_1_again.current.bfdMultiHop.authEnabled == false + - nm_update_node_group_policy_1_again.current.bfdMultiHop.keyID == 1 + - nm_update_node_group_policy_1_again.current.description == "Test description" + - nm_update_node_group_policy_1_again.current.name == "node_group_policy_1" + - nm_update_node_group_policy_1_again.current.nodeRoutingPolicyRef != "" + - nm_update_node_group_policy_1_again.current.targetDscp == "af11" + - nm_update_node_group_policy_1_again.previous.bfdMultiHop.authEnabled == false + - nm_update_node_group_policy_1_again.previous.bfdMultiHop.keyID == 1 + - nm_update_node_group_policy_1_again.previous.description == "Test description" + - nm_update_node_group_policy_1_again.previous.name == "node_group_policy_1" + - nm_update_node_group_policy_1_again.previous.nodeRoutingPolicyRef != "" + - nm_update_node_group_policy_1_again.previous.targetDscp == "af11" + + - name: Update node_group_policy_1 - bfd_multi_hop_key id, value, target_dscp and node_routing_policy values + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_1" + description: "Test description updated" + node_routing_policy: ans_node_policy_group_2 + bfd_multi_hop_authentication: enabled + bfd_multi_hop_key_id: 2 + bfd_multi_hop_key: TestKeyUpdated + target_dscp: af12 + state: present + register: update_node_group_policy_attrs + ignore_errors: true + + - name: Assertion check for update node_group_policy_1 - bfd_multi_hop_key id, value, target_dscp and node_routing_policy values + ansible.builtin.assert: + that: + - update_node_group_policy_attrs is changed + - update_node_group_policy_attrs.current.bfdMultiHop.authEnabled == false + - update_node_group_policy_attrs.current.bfdMultiHop.keyID == 2 + - update_node_group_policy_attrs.current.description == "Test description updated" + - update_node_group_policy_attrs.current.name == "node_group_policy_1" + - update_node_group_policy_attrs.current.nodeRoutingPolicyRef != "" + - update_node_group_policy_attrs.current.targetDscp == "af12" + - update_node_group_policy_attrs.previous.bfdMultiHop.authEnabled == false + - update_node_group_policy_attrs.previous.bfdMultiHop.keyID == 1 + - update_node_group_policy_attrs.previous.description == "Test description" + - update_node_group_policy_attrs.previous.name == "node_group_policy_1" + - update_node_group_policy_attrs.previous.nodeRoutingPolicyRef != "" + - update_node_group_policy_attrs.previous.targetDscp == "af11" + + - name: Clear node_group_policy_1 - bfd_multi_hop_key id, value, target_dscp and node_routing_policy values + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_1" + description: "" + node_routing_policy: "" + bfd_multi_hop_authentication: disabled + target_dscp: "unspecified" + state: present + register: clear_node_group_policy_attrs + ignore_errors: true + + - name: Assertion check for clear node_group_policy_1 - bfd_multi_hop_key id, value, target_dscp and node_routing_policy values + ansible.builtin.assert: + that: + - clear_node_group_policy_attrs is changed + - clear_node_group_policy_attrs.current.name == "node_group_policy_1" + - clear_node_group_policy_attrs.current.nodeRoutingPolicyRef == "" + - clear_node_group_policy_attrs.current.targetDscp == "unspecified" + - clear_node_group_policy_attrs.previous.bfdMultiHop.authEnabled == false + - clear_node_group_policy_attrs.previous.bfdMultiHop.keyID == 2 + - clear_node_group_policy_attrs.previous.description == "Test description updated" + - clear_node_group_policy_attrs.previous.name == "node_group_policy_1" + - clear_node_group_policy_attrs.previous.nodeRoutingPolicyRef != "" + - clear_node_group_policy_attrs.previous.targetDscp == "af12" + + - name: Query node_group_policy_1 + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_1" + state: query + register: query_node_group_policy_1 + ignore_errors: true + + - name: Assertion check for query node_group_policy_1 + ansible.builtin.assert: + that: + - query_node_group_policy_1 is not changed + - query_node_group_policy_1.current.name == "node_group_policy_1" + - query_node_group_policy_1.current.nodeRoutingPolicyRef == "" + - query_node_group_policy_1.current.targetDscp == "unspecified" + + - name: Add node_group_policy_2 with common tenant node_routing_policy object + cisco.mso.ndo_l3out_node_group_policy: &add_node_group_policy_2 + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_2" + description: "Create test description" + target_dscp: cs7 + node_routing_policy: "ans_node_policy_group_common" + state: present + register: add_node_group_policy_2 + ignore_errors: true + + - name: Assertion check for add node_group_policy_2 with common tenant node_routing_policy object + ansible.builtin.assert: + that: + - add_node_group_policy_2 is changed + - add_node_group_policy_2.current.description == "Create test description" + - add_node_group_policy_2.current.name == "node_group_policy_2" + - add_node_group_policy_2.current.nodeRoutingPolicyRef != "" + - add_node_group_policy_2.current.targetDscp == "cs7" + - add_node_group_policy_2.previous == {} + + - name: Query node_group_policy_2 + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + name: "node_group_policy_2" + state: query + register: query_node_group_policy_2 + ignore_errors: true + + - name: Assertion check for query node_group_policy_2 + ansible.builtin.assert: + that: + - query_node_group_policy_2 is not changed + - query_node_group_policy_2.current.description == "Create test description" + - query_node_group_policy_2.current.name == "node_group_policy_2" + - query_node_group_policy_2.current.nodeRoutingPolicyRef != "" + - query_node_group_policy_2.current.targetDscp == "cs7" + + - name: Query all node_group_policies + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_2" + state: query + register: query_all_node_group_policies + ignore_errors: true + + - name: Assertion check for query all node_group_policies + ansible.builtin.assert: + that: + - query_all_node_group_policies is not changed + - query_all_node_group_policies.current | length == 2 + + - name: Remove node_group_policy_2 with check mode + cisco.mso.ndo_l3out_node_group_policy: &cm_rm_node_group_policy_2 + <<: *add_node_group_policy_2 + state: absent + check_mode: true + register: cm_rm_node_group_policy_2 + + - name: Assertion check for remove node_group_policy_2 with check mode + ansible.builtin.assert: + that: + - cm_rm_node_group_policy_2 is changed + - cm_rm_node_group_policy_2.current == {} + - cm_rm_node_group_policy_2.previous.description == "Create test description" + - cm_rm_node_group_policy_2.previous.name == "node_group_policy_2" + - cm_rm_node_group_policy_2.previous.nodeRoutingPolicyRef != "" + - cm_rm_node_group_policy_2.previous.targetDscp == "cs7" + + - name: Remove node_group_policy_2 with normal mode + cisco.mso.ndo_l3out_node_group_policy: + <<: *cm_rm_node_group_policy_2 + register: nm_rm_node_group_policy_2 + + - name: Assertion check for remove node_group_policy_2 with normal mode + ansible.builtin.assert: + that: + - nm_rm_node_group_policy_2 is changed + - nm_rm_node_group_policy_2.current == {} + - nm_rm_node_group_policy_2.previous.description == "Create test description" + - nm_rm_node_group_policy_2.previous.name == "node_group_policy_2" + - nm_rm_node_group_policy_2.previous.nodeRoutingPolicyRef != "" + - nm_rm_node_group_policy_2.previous.targetDscp == "cs7" + + - name: Remove node_group_policy_2 with normal mode + cisco.mso.ndo_l3out_node_group_policy: + <<: *cm_rm_node_group_policy_2 + register: nm_rm_node_group_policy_2_again + + - name: Assertion check for remove node_group_policy_2 with normal mode + ansible.builtin.assert: + that: + - nm_rm_node_group_policy_2_again is not changed + - nm_rm_node_group_policy_2_again.current == {} + - nm_rm_node_group_policy_2_again.previous == {} + + # Negative test part begins + - name: Create L3Out node group policy object with invalid l3out name + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_nt" + name: "node_group_policy_nt" + state: present + register: node_group_policy_nt + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object with invalid l3out name + ansible.builtin.assert: + that: + - node_group_policy_nt is not changed + - node_group_policy_nt.current == {} + - node_group_policy_nt.msg == "Provided L3Out with '[KVPair(key='name', value='l3out_nt')]' not matching existing object(s){{':'}} l3out_1, l3out_2" + - node_group_policy_nt.previous == {} + + - name: Create L3Out node group policy object with l3out_1 template + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_1" + name: "node_group_policy_nt" + node_routing_policy: ans_node_policy_group_1 + bfd_multi_hop_authentication: enabled + bfd_multi_hop_key_id: 12 + bfd_multi_hop_key: "test" + state: present + register: node_group_policy_nt_with_l3out_1 + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object with l3out_1 template + ansible.builtin.assert: + that: + - node_group_policy_nt_with_l3out_1 is changed + - node_group_policy_nt_with_l3out_1.current.bfdMultiHop.authEnabled == true + - node_group_policy_nt_with_l3out_1.current.bfdMultiHop.key.value == "test" + - node_group_policy_nt_with_l3out_1.current.bfdMultiHop.keyID == 12 + - node_group_policy_nt_with_l3out_1.current.name == "node_group_policy_nt" + - node_group_policy_nt_with_l3out_1.current.nodeRoutingPolicyRef != "" + - node_group_policy_nt_with_l3out_1.msg == "MSO Error 400{{':'}} Invalid configuration in L3Out 'l3out_1'{{':'}} node group 'node_group_policy_nt'{{':'}} BFD Multihop is not supported with non-BGP routing protocols. Current protocol{{':'}} none" + - node_group_policy_nt_with_l3out_1.previous == {} + + - name: Create L3Out node group policy object without key and id when bfd_multi_hop_authentication is enabled + cisco.mso.ndo_l3out_node_group_policy: + <<: *mso_info + template: '{{ ansible_l3out_template | default("ansible_test") }}' + l3out: "l3out_1" + name: "node_group_policy_nt" + bfd_multi_hop_authentication: enabled + state: present + register: node_group_policy_nt_without_key + ignore_errors: true + + - name: Assertion check for create L3Out node group policy object without key and id when bfd_multi_hop_authentication is enabled + ansible.builtin.assert: + that: + - node_group_policy_nt_without_key is not changed + - node_group_policy_nt_without_key.msg == "bfd_multi_hop_authentication is enabled but all of the following are missing{{':'}} bfd_multi_hop_key_id, bfd_multi_hop_key" + # Negative test part ends + + # Cleanup Part + - name: Remove l3out tenant template + cisco.mso.ndo_template: + <<: *ndo_l3out_template_absent + + - name: Remove tenant policy template + cisco.mso.ndo_template: + <<: *tenant_pol_template_absent + + - name: Remove ansible_test schema template not exist + cisco.mso.mso_schema_template: + <<: *mso_schema_template_absent + + - name: Ensure ansible_test_policy tenant policy template not exists - cleanup + cisco.mso.ndo_template: + <<: *tenant_pol_template_absent + + - name: Ensure common tenant policy template not exists - cleanup + cisco.mso.ndo_template: + <<: *common_ansible_test_policy