diff --git a/README.md b/README.md index 18955b1e..0e7197d0 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ The following control variables are available in this collection. | `inventory_delete_mode` | Remove inventory state as part of the remove role | `false` | | `link_vpc_delete_mode` | Remove vpc link state as part of the remove role | `false` | | `vpc_delete_mode` | Remove vpc pair state as part of the remove role | `false` | +| `policy_delete_mode` | Remove policy state as part of the remove role | `false` | These variables are described in more detail in different sections of this document. diff --git a/plugins/action/common/prepare_plugins/prep_001_list_defaults.py b/plugins/action/common/prepare_plugins/prep_001_list_defaults.py index c3fb522d..75a5f776 100644 --- a/plugins/action/common/prepare_plugins/prep_001_list_defaults.py +++ b/plugins/action/common/prepare_plugins/prep_001_list_defaults.py @@ -20,7 +20,7 @@ # SPDX-License-Identifier: MIT -from ...helper_functions import data_model_key_check +from ....plugin_utils.helper_functions import data_model_key_check def update_nested_dict(nested_dict, keys, new_value): diff --git a/plugins/action/dtc/unmanaged_policy.py b/plugins/action/dtc/unmanaged_policy.py new file mode 100644 index 00000000..3e972c26 --- /dev/null +++ b/plugins/action/dtc/unmanaged_policy.py @@ -0,0 +1,198 @@ +# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible.plugins.action import ActionBase +from ...plugin_utils.helper_functions import ndfc_get_nac_switch_policy_using_desc + + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + results = super(ActionModule, self).run(tmp, task_vars) + results['changed'] = False + + # List of switch serial numbes obtained directly from NDFC + ndfc_sw_serial_numbers = self._task.args["switch_serial_numbers"] + # Data from data model + model_data = self._task.args["model_data"] + + # Switches list from data model + dm_topology_switches = model_data["vxlan"]["topology"]["switches"] + + # Policy, Poilcy Groups, and Switches Policy Group lists from data model + dm_policy_policies = model_data["vxlan"]["policy"]["policies"] + dm_policy_groups = model_data["vxlan"]["policy"]["groups"] + dm_policy_switches = model_data["vxlan"]["policy"]["switches"] + + # Build list of VRF Lites from data model if entries exist + # Used to exclude matching on VRF Lites as part of the unmanaged policies + vrf_lites = [] + if model_data["vxlan"].get("overlay_extensions", None): + if model_data["vxlan"]["overlay_extensions"].get("vrf_lites", None): + dm_vrf_lites = model_data["vxlan"]["overlay_extensions"]["vrf_lites"] + for dm_vrf_lite in dm_vrf_lites: + for dm_vrf_lite_switch in dm_vrf_lite["switches"]: + unique_name = f"nac_{dm_vrf_lite['name']}_{dm_vrf_lite_switch['name']}" + vrf_lites.append(unique_name) + + # Set defaults for management IP addresses, current switch policies, and unmanaged policies + dm_management_ipv4_address = "" + dm_management_ipv6_address = "" + # For each switch current_sw_policies will be used to store a list of policies currently associated to the switch + current_sw_policies = [] + # For each switch that has unmanaged policies, the switch IP address and the list of unmanaged policies will be stored + # This default dict is the start of what is required for the NDFC policy module + unmanaged_policies = [ + { + "switch": [] + } + ] + + # Loop over each serial number obtained from NDFC + for ndfc_sw_serial_number in ndfc_sw_serial_numbers: + # Check if the serial number from NDFC matches any serial number for a switch in the data model + # If found, grab the specific switch entry from the data model + # Also if a match, set the IP mgmt information for the current switch found + if any(switch["serial_number"] == ndfc_sw_serial_number for switch in dm_topology_switches): + dm_switch_found = next( + (dm_topology_switch for dm_topology_switch in dm_topology_switches if dm_topology_switch["serial_number"] == ndfc_sw_serial_number) + ) + dm_management_ipv4_address = dm_switch_found["management"].get("management_ipv4_address", None) + dm_management_ipv6_address = dm_switch_found["management"].get("management_ipv6_address", None) + + # Check if the name matching either the IPv4 or IPv6 mgmt address is found in the policy switches data model + # If found, grab the specific entry from the policy switches data model and store + # This stores the current switches policy group list + if any( + (switch["name"] == dm_management_ipv4_address for switch in dm_policy_switches) or + (switch["name"] == dm_management_ipv6_address for switch in dm_policy_switches) + ): + dm_policy_switch = next( + ( + dm_policy_switch for dm_policy_switch in dm_policy_switches + if dm_policy_switch["name"] == dm_management_ipv4_address or dm_policy_switch["name"] == dm_management_ipv6_address + ) + ) + + # Loop over each policy group associated to the current switch in the data model + for dm_sw_policy_group in dm_policy_switch["groups"]: + # Check if the policy group name associated to the switch is found in the policy groups data model + # If found, store a list of current policies that are part of that policy group in the data model + # In the process of storing, reformat the policy description name to prepend "nac_" and replace white spaces with underscores + if any(dm_policy_group["name"] == dm_sw_policy_group for dm_policy_group in dm_policy_groups): + current_sw_policies = next( + ( + ["nac_" + policy["name"].replace(" ", "_") for policy in dm_policy_group["policies"]] + for dm_policy_group in dm_policy_groups if dm_policy_group["name"] == dm_sw_policy_group + ) + ) + + # Query NDFC for the current switch's serial number to get back any policy that exists for that switch + # with the description prepended with "nac_" + ndfc_policies_with_nac_desc = ndfc_get_nac_switch_policy_using_desc(self, task_vars, tmp, ndfc_sw_serial_number) + + # Currently, check two things to determine an unmanaged policy: + # Check no matching policy in the data model against the policy returned from NDFC for the current switch + # This check uses the prepended "nac_" + # Additionally, as of now, check no matching policy is from the VRF Lite policy of the data model + if any( + ((ndfc_policy_with_desc["description"] not in current_sw_policies) and (ndfc_policy_with_desc["description"] not in vrf_lites)) + for ndfc_policy_with_desc in ndfc_policies_with_nac_desc + ): + # If found, do the following: + # Update Ansible result status + # Add the switch to unmanaged_policies payload + # Get the last index of where the switch was added + # Build specific unmanaged policy entry + # Add unmanaged policy entry to last switch added to list + + # Update Ansible for a configuration change + results['changed'] = True + + # Update unmanaged_policies with the IP address of the switch that now has unmanaged policy + # The NDFC policy module can take a list of various dictionaries with the switch key previously being pre-stored + # Given this, each update the switch element with a new switch entry is the zeroth reference location always in unmanaged_policies + # Example: + # [ + # { + # "switch": [ + # { + # "ip": , + # } + # ] + # } + # ] + unmanaged_policies[0]["switch"].append( + { + "ip": dm_management_ipv4_address if dm_management_ipv4_address else dm_management_ipv6_address + } + ) + + # Grab the last index of a switch added + last_idx = len(unmanaged_policies[0]["switch"]) - 1 + + # Since initially found there is indeed an unmananged policy, build a list of unmanaged policy + _unmanaged_policies = [ + { + "name": ndfc_policy_with_desc["policyId"], + "description": ndfc_policy_with_desc["description"] + } + for ndfc_policy_with_desc in ndfc_policies_with_nac_desc + if ((ndfc_policy_with_desc["description"] not in current_sw_policies) and (ndfc_policy_with_desc["description"] not in vrf_lites)) + ] + + # Update the dictionary entry for the last switch with the expected policies key the NDFC policy module expects + unmanaged_policies[0]["switch"][last_idx].update( + { + "policies": _unmanaged_policies + } + ) + + # Example of unmanaged policy payload: + # [ + # { + # "switch": [ + # { + # "ip": '', + # "policies": [ + # { + # "name": , + # "description": "nac_" + # }, + # { + # "name": , + # "description": "nac_" + # }, + # ] + # }, + # ] + # } + # ] + + # Store the unmanaged policy payload for return and usage in the NDFC policy module to delete from NDFC + results['unmanaged_policies'] = unmanaged_policies + + return results diff --git a/plugins/action/dtc/update_switch_hostname_policy.py b/plugins/action/dtc/update_switch_hostname_policy.py index 450c2b23..f52891b2 100644 --- a/plugins/action/dtc/update_switch_hostname_policy.py +++ b/plugins/action/dtc/update_switch_hostname_policy.py @@ -25,7 +25,7 @@ __metaclass__ = type from ansible.plugins.action import ActionBase -from ..helper_functions import ndfc_get_switch_policy +from ...plugin_utils.helper_functions import ndfc_get_switch_policy_using_template class ActionModule(ActionBase): @@ -41,12 +41,12 @@ def run(self, tmp=None, task_vars=None): policy_update = {} for switch_serial_number in switch_serial_numbers: - policy_match = ndfc_get_switch_policy( + policy_match = ndfc_get_switch_policy_using_template( self=self, task_vars=task_vars, tmp=tmp, - template_name=template_name, - switch_serial_number=switch_serial_number + switch_serial_number=switch_serial_number, + template_name=template_name ) switch_match = next((item for item in model_data["vxlan"]["topology"]["switches"] if item["serial_number"] == switch_serial_number)) diff --git a/plugins/action/helper_functions.py b/plugins/action/helper_functions.py deleted file mode 100644 index 104b9fc7..00000000 --- a/plugins/action/helper_functions.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -# the Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# SPDX-License-Identifier: MIT - -# This is an example file for help functions that can be called by -# our various action plugins for common routines. -# -# For example in prepare_serice_model.py we can do the following: -# from ..helper_functions import do_something - -def data_model_key_check(tested_object, keys): - dm_key_dict = {'keys_found': [], 'keys_not_found': [], 'keys_data': [], 'keys_no_data': []} - for key in keys: - if tested_object and key in tested_object: - dm_key_dict['keys_found'].append(key) - tested_object = tested_object[key] - if tested_object: - dm_key_dict['keys_data'].append(key) - else: - dm_key_dict['keys_no_data'].append(key) - else: - dm_key_dict['keys_not_found'].append(key) - return dm_key_dict - - -def ndfc_get_switch_policy(self, task_vars, tmp, template_name, switch_serial_number): - policy_data = self._execute_module( - module_name="cisco.dcnm.dcnm_rest", - module_args={ - "method": "GET", - "path": f"/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/policies/switches/{switch_serial_number}/SWITCH/SWITCH" - }, - task_vars=task_vars, - tmp=tmp - ) - - try: - policy_match = next( - (item for item in policy_data["response"]["DATA"] if item["templateName"] == template_name and item['serialNumber'] == switch_serial_number) - ) - except StopIteration: - err_msg = f"Policy for template {template_name} and switch {switch_serial_number} not found!" - err_msg += f" Please ensure switch with serial number {switch_serial_number} is part of the fabric." - raise Exception(err_msg) - - return policy_match diff --git a/plugins/plugin_utils/.gitkeep b/plugins/plugin_utils/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/plugins/plugin_utils/helper_functions.py b/plugins/plugin_utils/helper_functions.py new file mode 100644 index 00000000..8c8a3df3 --- /dev/null +++ b/plugins/plugin_utils/helper_functions.py @@ -0,0 +1,141 @@ +# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + +# This is an example file for help functions that can be called by +# our various action plugins for common routines. +# +# For example in prepare_serice_model.py we can do the following: +# from ..helper_functions import do_something + + +def data_model_key_check(tested_object, keys): + """ + Check if key(s) are found and exist in the data model. + + :Parameters: + :tested_object (dict): Data model to check for keys. + :keys (list): List of keys to check in the data model. + + :Returns: + :dm_key_dict (dict): Dictionary of lists for keys found, not found, and corresponding data or empty data. + + :Raises: + N/A + """ + dm_key_dict = {'keys_found': [], 'keys_not_found': [], 'keys_data': [], 'keys_no_data': []} + for key in keys: + if tested_object and key in tested_object: + dm_key_dict['keys_found'].append(key) + tested_object = tested_object[key] + if tested_object: + dm_key_dict['keys_data'].append(key) + else: + dm_key_dict['keys_no_data'].append(key) + else: + dm_key_dict['keys_not_found'].append(key) + + return dm_key_dict + + +def ndfc_get_switch_policy(self, task_vars, tmp, switch_serial_number): + """ + Get NDFC policy for a given managed switch by the switch's serial number. + + :Parameters: + :self: Ansible action plugin instance object. + :task_vars (dict): Ansible task vars. + :tmp (None, optional): Ansible tmp object. Defaults to None via Action Plugin. + :switch_serial_number (str): The serial number of the managed switch for which the NDFC policy is to be retrieved. + + :Returns: + :policy_data: The NDFC policy data for the given switch. + + :Raises: + N/A + """ + policy_data = self._execute_module( + module_name="cisco.dcnm.dcnm_rest", + module_args={ + "method": "GET", + "path": f"/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/policies/switches/{switch_serial_number}/SWITCH/SWITCH" + }, + task_vars=task_vars, + tmp=tmp + ) + + return policy_data + + +def ndfc_get_switch_policy_using_template(self, task_vars, tmp, switch_serial_number, template_name): + """ + Get NDFC policy for a given managed switch by the switch's serial number and a specified NDFC template name. + + :Parameters: + :self: Ansible action plugin instance object. + :task_vars (dict): Ansible task vars. + :tmp (None, optional): Ansible tmp object. Defaults to None via Action Plugin. + :switch_serial_number (str): The serial number of the managed switch for which the NDFC policy is to be retrieved. + :template_name (str): The name of the NDFC template for which the policy is to be retrieved. + + :Returns: + :policy_match: The NDFC policy data for the given switch and matching template. + + :Raises: + :Exception: If the policy for the given switch and template is not found. + """ + policy_data = ndfc_get_switch_policy(self, task_vars, tmp, switch_serial_number) + + try: + policy_match = next( + (item for item in policy_data["response"]["DATA"] if item["templateName"] == template_name and item['serialNumber'] == switch_serial_number) + ) + except StopIteration: + err_msg = f"Policy for template {template_name} and switch {switch_serial_number} not found!" + err_msg += f" Please ensure switch with serial number {switch_serial_number} is part of the fabric." + raise Exception(err_msg) + + return policy_match + + +def ndfc_get_nac_switch_policy_using_desc(self, task_vars, tmp, switch_serial_number): + """ + Get NDFC policy for a given managed switch by the switch's serial number and the prepanded string nac. + + :Parameters: + :self: Ansible action plugin instance object. + :task_vars (dict): Ansible task vars. + :tmp (None, optional): Ansible tmp object. Defaults to None via Action Plugin. + :switch_serial_number (str): The serial number of the managed switch for which the NDFC policy is to be retrieved. + + :Returns: + :policy_match: The NDFC policy data for the given switch and matching template. + + :Raises: + N/A + """ + policy_data = ndfc_get_switch_policy(self, task_vars, tmp, switch_serial_number) + + policy_match = [ + item for item in policy_data["response"]["DATA"] + if item.get("description", None) and "nac_" in item.get("description", None) and item["source"] == "" + ] + + return policy_match diff --git a/roles/common_global/vars/main.yml b/roles/common_global/vars/main.yml index 7821ee4d..3bdc7daf 100644 --- a/roles/common_global/vars/main.yml +++ b/roles/common_global/vars/main.yml @@ -39,6 +39,7 @@ nac_tags: - rr_manage_vpc_peers - rr_manage_links - rr_manage_switches + - rr_manage_policy # ------------------------- - role_validate - role_create @@ -62,6 +63,7 @@ nac_tags: - rr_manage_vpc_peers - rr_manage_links - rr_manage_switches + - rr_manage_policy # We need the ability to pass tags to the common role but we don't need the following # - validate, cc_verify common_role: @@ -80,6 +82,7 @@ nac_tags: - rr_manage_vpc_peers - rr_manage_links - rr_manage_switches + - rr_manage_policy # We need the ability to pass tags to the validate role but we don't need the following # - cc_verify validate_role: @@ -99,6 +102,7 @@ nac_tags: - rr_manage_vpc_peers - rr_manage_links - rr_manage_switches + - rr_manage_policy # All Create Tags create: - cr_manage_fabric @@ -127,6 +131,7 @@ nac_tags: - rr_manage_vpc_peers - rr_manage_links - rr_manage_switches + - rr_manage_policy remove_interfaces: - rr_manage_interfaces remove_networks: @@ -139,7 +144,7 @@ nac_tags: - rr_manage_links remove_switches: - rr_manage_switches + remove_policy: + - rr_manage_policy deploy: - role_deploy - - \ No newline at end of file diff --git a/roles/dtc/common/templates/ndfc_policy.j2 b/roles/dtc/common/templates/ndfc_policy.j2 index 4f539725..6701cc09 100644 --- a/roles/dtc/common/templates/ndfc_policy.j2 +++ b/roles/dtc/common/templates/ndfc_policy.j2 @@ -2,6 +2,7 @@ # This NDFC policy and switch attachments config data structure is auto-generated # DO NOT EDIT MANUALLY # +{% if MD_Extended.vxlan.policy.policies | default([]) | length > 0 %} - switch: {% for switch in MD_Extended.vxlan.policy.switches %} - ip: {{ switch.name }} @@ -12,8 +13,9 @@ {% for policy in policy_group_match.policies %} {% set query = "[?(@.name==`" ~ policy.name ~ "`)]" %} {% set policy_match = MD_Extended.vxlan.policy.policies | community.general.json_query(query) | first %} +{% set policy_name = policy_match.name | ansible.builtin.regex_replace('\\s+', '_') %} - create_additional_policy: False - description: {{ policy_match.name }} + description: {{ 'nac_' + policy_name }} {% if (policy_match.template_name is defined and policy_match.template_name) or (policy_match.filename is defined and policy_match.filename and (".yaml" in policy_match.filename or ".yml" in policy_match.filename)) %} name: {{ policy_match.template_name | default(defaults.vxlan.policy.template_name) }} {% elif policy_match.filename is defined and policy_match.filename and ".cfg" in policy_match.filename %} @@ -42,3 +44,4 @@ {% endfor %} {% endfor %} {% endfor %} +{% endif %} diff --git a/roles/dtc/connectivity_check/tasks/verify_ndfc_authorization.yml b/roles/dtc/connectivity_check/tasks/verify_ndfc_authorization.yml index 6455bf6f..91c8868b 100644 --- a/roles/dtc/connectivity_check/tasks/verify_ndfc_authorization.yml +++ b/roles/dtc/connectivity_check/tasks/verify_ndfc_authorization.yml @@ -1,3 +1,24 @@ +# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + --- - name: Verify Authorization to NDFC @@ -18,7 +39,7 @@ validate_certs: false timeout: 30 register: response - # no_log: true + no_log: true delegate_to: localhost rescue: diff --git a/roles/dtc/connectivity_check/tasks/verify_ndfc_connectivity.yml b/roles/dtc/connectivity_check/tasks/verify_ndfc_connectivity.yml index 647a448b..9fc51d92 100644 --- a/roles/dtc/connectivity_check/tasks/verify_ndfc_connectivity.yml +++ b/roles/dtc/connectivity_check/tasks/verify_ndfc_connectivity.yml @@ -1,3 +1,24 @@ +# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + --- - name: Verify Connection to NDFC {{ ansible_host }} on Port {{ ansible_httpapi_port | default(443) }} diff --git a/roles/dtc/remove/tasks/interfaces.yml b/roles/dtc/remove/tasks/interfaces.yml index 96e995bf..ab08d570 100644 --- a/roles/dtc/remove/tasks/interfaces.yml +++ b/roles/dtc/remove/tasks/interfaces.yml @@ -20,13 +20,13 @@ # SPDX-License-Identifier: MIT --- -- ansible.builtin.debug: msg="Removing all Unmanaged Fabric Interfaces. This could take several minutes..." +- ansible.builtin.debug: msg="Removing Unmanaged Fabric Interfaces. This could take several minutes..." when: - switch_list.response.DATA | length > 0 - (interface_delete_mode is defined) and (interface_delete_mode is true|bool) - -- cisco.dcnm.dcnm_interface: +- name: Remove Unmanaged Fabric Interfaces + cisco.dcnm.dcnm_interface: fabric: "{{ MD.vxlan.global.name }}" state: overridden config: "{{ interface_all }}" @@ -65,4 +65,4 @@ - "-------------------------------------------------------------------------------------------------------------------" - "+ SKIPPING Remove Unmanaged Fabric Interfaces task because interface_delete_mode flag is set to False +" - "-------------------------------------------------------------------------------------------------------------------" - when: not ((interface_delete_mode is defined) and (interface_delete_mode is true|bool)) \ No newline at end of file + when: not ((interface_delete_mode is defined) and (interface_delete_mode is true|bool)) diff --git a/roles/dtc/remove/tasks/links.yml b/roles/dtc/remove/tasks/links.yml index 39677bac..d547771a 100644 --- a/roles/dtc/remove/tasks/links.yml +++ b/roles/dtc/remove/tasks/links.yml @@ -20,12 +20,12 @@ # SPDX-License-Identifier: MIT --- -- ansible.builtin.debug: msg="Removing all Unmanaged links. This could take several minutes..." +- ansible.builtin.debug: msg="Removing Unmanaged Links. This could take several minutes..." when: - switch_list.response.DATA | length > 0 - (link_vpc_delete_mode is defined) and (link_vpc_delete_mode is true|bool) -- name: Remove Intra Fabric Links for vpc peering +- name: Remove Intra Fabric Links for vPC Peering cisco.dcnm.dcnm_links: state: replaced src_fabric: "{{ MD_Extended.vxlan.global.name }}" diff --git a/roles/dtc/remove/tasks/networks.yml b/roles/dtc/remove/tasks/networks.yml index 3ae54cdd..82487a74 100644 --- a/roles/dtc/remove/tasks/networks.yml +++ b/roles/dtc/remove/tasks/networks.yml @@ -25,7 +25,8 @@ - switch_list.response.DATA | length > 0 - (network_delete_mode is defined) and (network_delete_mode is true|bool) -- cisco.dcnm.dcnm_network: +- name: Remove Unmanaged Fabric Networks + cisco.dcnm.dcnm_network: fabric: "{{ MD.vxlan.global.name }}" state: overridden config: "{{ net_config }}" diff --git a/roles/dtc/remove/tasks/policy.yml b/roles/dtc/remove/tasks/policy.yml new file mode 100644 index 00000000..d203a5ad --- /dev/null +++ b/roles/dtc/remove/tasks/policy.yml @@ -0,0 +1,58 @@ +# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT +--- + +- block: + - ansible.builtin.debug: msg="Removing Unmanaged Fabric Policy From Switches. This could take several minutes..." + + - name: Create List of Switch Serial Numbers from NDFC Switch List + ansible.builtin.set_fact: + switch_serial_numbers: "{{ switch_list.response.DATA | map(attribute='serialNumber') | list }}" + delegate_to: localhost + + - name: Build Unmanaged Fabric Policy Payload + cisco.nac_dc_vxlan.dtc.unmanaged_policy: + switch_serial_numbers: "{{ switch_serial_numbers }}" + model_data: "{{ MD_Extended }}" + register: unmanaged_policy_config + # do not delegate_to: localhost as this action plugin uses Python to execute cisco.dcnm.dcnm_rest + + - name: Remove Unmanaged NDFC Fabric Policy + cisco.dcnm.dcnm_policy: + fabric: "{{ MD.vxlan.global.name }}" + use_desc_as_key: true + config: "{{ unmanaged_policy_config.unmanaged_policies }}" + deploy: true + state: deleted + when: unmanaged_policy_config.unmanaged_policies | length > 0 + vars: + ansible_command_timeout: 3000 + ansible_connect_timeout: 3000 + when: + - switch_list.response.DATA | length > 0 + - (policy_delete_mode is defined) and (policy_delete_mode is true|bool) + +- ansible.builtin.debug: + msg: + - "--------------------------------------------------------------------------------------------------------" + - "+ SKIPPING Remove Unmanaged Policy from Switches task because policy_delete_mode flag is set to False +" + - "--------------------------------------------------------------------------------------------------------" + when: not ((policy_delete_mode is defined) and (policy_delete_mode is true|bool)) diff --git a/roles/dtc/remove/tasks/sub_main.yml b/roles/dtc/remove/tasks/sub_main.yml index 9a8f0e3c..d4f6daf8 100644 --- a/roles/dtc/remove/tasks/sub_main.yml +++ b/roles/dtc/remove/tasks/sub_main.yml @@ -32,15 +32,19 @@ - ansible.builtin.debug: msg="Configuring NXOS Devices using NDFC (Direct to Controller)" tags: "{{ nac_tags.remove }}" -- ansible.builtin.debug: msg="Query NDFC for List of Fabric Switches" - tags: "{{ nac_tags.remove }}" - -- cisco.dcnm.dcnm_rest: +- name: Get List of Fabric Switches from NDFC + cisco.dcnm.dcnm_rest: method: GET path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ MD.vxlan.global.name }}/inventory/switchesByFabric" register: switch_list tags: "{{ nac_tags.remove }}" +- name: Remove Fabric Policy + ansible.builtin.import_tasks: policy.yml + tags: "{{ nac_tags.remove_policy }}" + when: + - changes_detected_policy + - name: Remove Fabric Interfaces ansible.builtin.import_tasks: interfaces.yml tags: "{{ nac_tags.remove_interfaces }}" @@ -75,4 +79,4 @@ ansible.builtin.import_tasks: switches.yml tags: "{{ nac_tags.remove_switches }}" when: - - changes_detected_inventory \ No newline at end of file + - changes_detected_inventory diff --git a/roles/dtc/remove/tasks/switches.yml b/roles/dtc/remove/tasks/switches.yml index d7e70044..a3ab2de8 100644 --- a/roles/dtc/remove/tasks/switches.yml +++ b/roles/dtc/remove/tasks/switches.yml @@ -24,7 +24,7 @@ when: - (inventory_delete_mode is defined) and (inventory_delete_mode is true|bool) -- name: Remove NDFC Fabric Devices {{ MD.vxlan.global.name }} +- name: Remove Unmanaged NDFC Fabric Devices cisco.dcnm.dcnm_inventory: fabric: "{{ MD.vxlan.global.name }}" config: "{{ updated_inv_config['updated_inv_list'] }}" @@ -42,4 +42,4 @@ - "----------------------------------------------------------------------------------------------------------" - "+ SKIPPING Remove NDFC Fabric Devices task because inventory_delete_mode flag is set to False +" - "----------------------------------------------------------------------------------------------------------" - when: not ((inventory_delete_mode is defined) and (inventory_delete_mode is true|bool)) \ No newline at end of file + when: not ((inventory_delete_mode is defined) and (inventory_delete_mode is true|bool)) diff --git a/roles/dtc/remove/tasks/vpc_peers.yml b/roles/dtc/remove/tasks/vpc_peers.yml index 1a426282..9c400a64 100644 --- a/roles/dtc/remove/tasks/vpc_peers.yml +++ b/roles/dtc/remove/tasks/vpc_peers.yml @@ -20,12 +20,12 @@ # SPDX-License-Identifier: MIT --- -- ansible.builtin.debug: msg="Removing all Unmanaged vPC Peering. This could take several minutes..." +- ansible.builtin.debug: msg="Removing Unmanaged vPC Peering. This could take several minutes..." when: - switch_list.response.DATA | length > 0 - (vpc_delete_mode is defined) and (vpc_delete_mode is true|bool) -- name: Remove vPC Peering +- name: Remove Unmanaged vPC Peering cisco.dcnm.dcnm_vpc_pair: src_fabric: "{{ MD.vxlan.global.name }}" deploy: true diff --git a/roles/dtc/remove/tasks/vrfs.yml b/roles/dtc/remove/tasks/vrfs.yml index 83aa51b3..87e39d7e 100644 --- a/roles/dtc/remove/tasks/vrfs.yml +++ b/roles/dtc/remove/tasks/vrfs.yml @@ -25,7 +25,8 @@ - switch_list.response.DATA | length > 0 - (vrf_delete_mode is defined) and (vrf_delete_mode is true|bool) -- cisco.dcnm.dcnm_vrf: +- name: Remove Unmanaged Fabric VRFs + cisco.dcnm.dcnm_vrf: fabric: "{{ MD.vxlan.global.name }}" state: overridden config: "{{ vrf_config }}" diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 18784758..956af7b1 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -17,10 +17,10 @@ plugins/action/dtc/vpc_pair_check.py action-plugin-docs # action plugin has no m plugins/action/dtc/verify_tags.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/diff_model_changes.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/update_switch_hostname_policy.py action-plugin-docs # action plugin has no matching module to provide documentation +plugins/action/dtc/unmanaged_policy.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/get_poap_data.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtd/prepare_service_model.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/test/inventory.py action-plugin-docs # action plugin has no matching module to provide documentation -plugins/action/helper_functions.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/common/nac_dc_validate.py import-3.10!skip plugins/action/test/inventory.py import-3.10!skip plugins/action/common/run_map.py import-3.10!skip diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 18784758..956af7b1 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -17,10 +17,10 @@ plugins/action/dtc/vpc_pair_check.py action-plugin-docs # action plugin has no m plugins/action/dtc/verify_tags.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/diff_model_changes.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/update_switch_hostname_policy.py action-plugin-docs # action plugin has no matching module to provide documentation +plugins/action/dtc/unmanaged_policy.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/get_poap_data.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtd/prepare_service_model.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/test/inventory.py action-plugin-docs # action plugin has no matching module to provide documentation -plugins/action/helper_functions.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/common/nac_dc_validate.py import-3.10!skip plugins/action/test/inventory.py import-3.10!skip plugins/action/common/run_map.py import-3.10!skip diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 18784758..956af7b1 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -17,10 +17,10 @@ plugins/action/dtc/vpc_pair_check.py action-plugin-docs # action plugin has no m plugins/action/dtc/verify_tags.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/diff_model_changes.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/update_switch_hostname_policy.py action-plugin-docs # action plugin has no matching module to provide documentation +plugins/action/dtc/unmanaged_policy.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtc/get_poap_data.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/dtd/prepare_service_model.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/test/inventory.py action-plugin-docs # action plugin has no matching module to provide documentation -plugins/action/helper_functions.py action-plugin-docs # action plugin has no matching module to provide documentation plugins/action/common/nac_dc_validate.py import-3.10!skip plugins/action/test/inventory.py import-3.10!skip plugins/action/common/run_map.py import-3.10!skip