Skip to content

Commit

Permalink
Remove Unmanaged Policy (#202)
Browse files Browse the repository at this point in the history
* start of remove policy

* more work on policy remove

* fix local lint errors

* fix lint errors

* revert update hostname plugin

* refactor remove policy

* fix path

* remove path

* clean up task vernacular & add remove policy tag in global tags

* add comments to action plugin

* fix lint error

* fix lint error
  • Loading branch information
mtarking authored Nov 8, 2024
1 parent f656c31 commit 01e1575
Show file tree
Hide file tree
Showing 22 changed files with 483 additions and 92 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
198 changes: 198 additions & 0 deletions plugins/action/dtc/unmanaged_policy.py
Original file line number Diff line number Diff line change
@@ -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": <mgmt_ip_address>,
# }
# ]
# }
# ]
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": '<ip_address>',
# "policies": [
# {
# "name": <Policy ID>,
# "description": "nac_<description>"
# },
# {
# "name": <Policy ID>,
# "description": "nac_<description>"
# },
# ]
# },
# ]
# }
# ]

# 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
8 changes: 4 additions & 4 deletions plugins/action/dtc/update_switch_hostname_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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))
Expand Down
63 changes: 0 additions & 63 deletions plugins/action/helper_functions.py

This file was deleted.

Empty file added plugins/plugin_utils/.gitkeep
Empty file.
Loading

0 comments on commit 01e1575

Please sign in to comment.