-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Device Config backup workflow module #382
Changes from 2 commits
a4deace
c27b21c
4c0cad4
772b61b
1470053
3351518
fb2c7bc
0b3daa2
a68ccd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
__author__ = ("Abinash Mishra, Rugvedi Kapse, Madhan Sankaranarayanan") | ||
__author__ = ("Abinash Mishra, Rugvedi Kapse, Madhan Sankaranarayanan, Sonali Deepthi Kesali") | ||
|
||
DOCUMENTATION = r""" | ||
--- | ||
|
@@ -21,6 +21,7 @@ | |
author: Abinash Mishra (@abimishr) | ||
Rugvedi Kapse (@rukapse) | ||
Madhan Sankaranarayanan (@madhansansel) | ||
Sonali Deepthi Kesali (@skesali) | ||
options: | ||
config_verify: | ||
description: Set to True to verify the Cisco Catalyst Center config after applying the playbook config. | ||
|
@@ -113,26 +114,29 @@ | |
- one special characters from -=\\\\\\\\;,./~!@$%^&*()_+{}[]|:?" | ||
type: str | ||
requirements: | ||
- dnacentersdk == 2.6.10 | ||
- dnacentersdk == 2.9.2 | ||
- python >= 3.5 | ||
|
||
notes: | ||
- SDK Methods used are | ||
sites.Sites.get_site | ||
Site_design.Site_design.get_sites | ||
sites.Sites.get_membership | ||
site_design.Site_design.get_site_assigned_network_devices | ||
devices.Devices.get_device_list | ||
devices.Devices.get_device_by_id | ||
configuration_archive.ConfigurationsArchive.export_device_configurations | ||
file.Files.download_a_file_by_fileid | ||
task.Task.get_task_by_id | ||
|
||
- Paths used are | ||
get /dna/intent/api/v1/site | ||
get /dna/intent/api/v1/membership/${siteId} | ||
get /dna/intent/api/v1/network-device | ||
post /dna/intent/api/v1/network-device-archive/cleartext | ||
get /dna/intent/api/v1/file/${fileId} | ||
get /dna/intent/api/v1/task/${taskId} | ||
|
||
get /dna/intent/api/v1/networkDevices/assignedToSite | ||
get /dna/intent/api/v1/sites | ||
get /dna/intent/api/v1/network-device/${id} | ||
""" | ||
|
||
EXAMPLES = r""" | ||
|
@@ -394,6 +398,11 @@ class Device_configs_backup(DnacBase): | |
def __init__(self, module): | ||
super().__init__(module) | ||
self.skipped_devices_list = [] | ||
self.payload = module.params | ||
self.keymap = {} | ||
self.dnac_version = int(self.payload.get( | ||
"dnac_version").replace(".", "")) | ||
self.version_2_3_5_3, self.version_2_3_7_6 = 2353, 2376 | ||
|
||
def validate_input(self): | ||
""" | ||
|
@@ -514,109 +523,101 @@ def validate_site_exists(self, site_name): | |
""" | ||
site_exists = False | ||
site_id = None | ||
response = None | ||
|
||
# Attempt to retrieve site information from Catalyst Center | ||
try: | ||
response = self.dnac._exec( | ||
family="sites", | ||
function="get_site", | ||
op_modifies=True, | ||
params={"name": site_name}, | ||
) | ||
self.log("Response received post 'get_site' API call: {0}".format(str(response)), "DEBUG") | ||
response = response.get("response") | ||
|
||
# Process the response if available | ||
if response: | ||
site_id = response[0].get("id") | ||
site_exists = True | ||
else: | ||
self.log("No response received from the 'get_site' API call.", "WARNING") | ||
response = self.get_site(site_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need this API at all. Check get_site_id API and use it.. if you need site_exists also, then change the get_site_id API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed that API and directly using get_site_id There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can directly use get_site_id rite? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed and using get_site_id directly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still don't need this API. What is the purpose of the API? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
if response is None: | ||
raise ValueError | ||
site = response.get("response") | ||
site_id = site[0].get("id") | ||
site_exists = True | ||
|
||
except Exception as e: | ||
# Log an error message and fail if an exception occurs | ||
self.log("An error occurred while retrieving site details for Site '{0}' using 'get_site' API call: {1}".format(site_name, str(e)), "ERROR") | ||
|
||
# Update result if the site does not exist | ||
if not site_exists: | ||
self.msg = "An error occurred while retrieving site details for Site '{0}'. Please verify that the site exists.".format(site_name) | ||
self.update_result("failed", False, self.msg, "ERROR") | ||
self.status = "failed" | ||
self.msg = ("An exception occurred: Site '{0}' does not exist in the Cisco Catalyst Center.".format(site_name)) | ||
self.result['response'] = self.msg | ||
self.log(self.msg, "ERROR") | ||
self.check_return_status() | ||
|
||
return (site_exists, site_id) | ||
|
||
def get_device_ids_from_site(self, site_name, site_id): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this API to dnac.py and make it common.... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved, I added two functions in dnac.py: one for getting device IDs from a site and another for retrieving the IP address from a device id. |
||
""" | ||
Retrieves device IDs from a specified site in Cisco Catalyst Center. | ||
Retrieves the management IP addresses and their corresponding instance UUIDs of devices associated with a specific site in Cisco Catalyst Center. | ||
|
||
Parameters: | ||
site_name (str): The name of the site. | ||
site_id (str): The ID of the site. | ||
site_name (str): The name of the site whose devices' information is to be retrieved. | ||
site_id (str): The unique identifier of the site. | ||
|
||
Returns: | ||
dict: A dictionary mapping management IP addresses to instance UUIDs of reachable devices that are not Unified APs. | ||
dict: A dictionary mapping management IP addresses to their instance UUIDs. | ||
|
||
Description: | ||
This method queries Cisco Catalyst Center to retrieve device information associated with the provided site ID. | ||
It filters out unreachable devices and Unified APs, returning a dictionary of management IP addresses mapped to their instance UUIDs. | ||
Logs detailed information about the number of devices processed and skipped. | ||
This method queries Cisco Catalyst Center to fetch the list of devices associated with the provided site. | ||
It then extracts the management IP addresses and their instance UUIDs from the response. | ||
Devices that are not reachable are logged as critical errors, and the function fails. | ||
If no reachable devices are found for the specified site, it logs an error message and fails. | ||
""" | ||
mgmt_ip_to_instance_id_map = {} | ||
processed_device_count = 0 | ||
skipped_device_count = 0 | ||
|
||
# Parameters for the site membership API call | ||
site_params = { | ||
"site_id": site_id, | ||
} | ||
|
||
# Attempt to retrieve device information associated with the site | ||
try: | ||
response = self.dnac._exec( | ||
family="sites", | ||
function="get_membership", | ||
op_modifies=True, | ||
params=site_params, | ||
) | ||
self.log("Response received post 'get_membership' API Call: {0} ".format(str(response)), "DEBUG") | ||
if self.dnac_version <= self.version_2_3_5_3: | ||
response = self.dnac._exec( | ||
family="sites", | ||
function="get_membership", | ||
op_modifies=True, | ||
params=site_params, | ||
) | ||
self.log("Response received post 'get_membership' API Call: {0}".format(str(response)), "DEBUG") | ||
|
||
devices = response.get("device", []) | ||
for item in devices: | ||
for item_dict in item.get("response", []): | ||
if item_dict["reachabilityStatus"] == "Reachable" and item_dict["family"] != "Unified AP": | ||
mgmt_ip_to_instance_id_map[item_dict["managementIpAddress"]] = item_dict["instanceUuid"] | ||
else: | ||
msg = "Skipping device {0} in site {1} as its status is {2}".format( | ||
item_dict["managementIpAddress"], site_name, item_dict.get("reachabilityStatus", "Unknown") | ||
) | ||
self.log(msg, "INFO" if item_dict["family"] == "Unified AP" else "WARNING") | ||
|
||
# Process the response if available | ||
if response: | ||
response = response["device"] | ||
# Iterate over the devices in the site membership | ||
for item in response: | ||
if item["response"]: | ||
for item_dict in item["response"]: | ||
processed_device_count += 1 | ||
# Check if the device is reachable | ||
if item_dict["reachabilityStatus"] == "Reachable" and item_dict["collectionStatus"] == "Managed": | ||
if item_dict["family"] != "Unified AP": | ||
mgmt_ip_to_instance_id_map[item_dict["managementIpAddress"]] = item_dict["instanceUuid"] | ||
else: | ||
skipped_device_count += 1 | ||
self.skipped_devices_list.append(item_dict["managementIpAddress"]) | ||
msg = "Skipping device {0} in site {1} as its family is {2}".format( | ||
item_dict["managementIpAddress"], site_name, item_dict["family"]) | ||
self.log(msg, "INFO") | ||
else: | ||
skipped_device_count += 1 | ||
self.skipped_devices_list.append(item_dict["managementIpAddress"]) | ||
msg = "Skipping device {0} in site {1} as its status is {2} or collectionStatus is {3} ".format( | ||
item_dict["managementIpAddress"], site_name, item_dict["reachabilityStatus"], item_dict["collectionStatus"]) | ||
self.log(msg, "WARNING") | ||
else: | ||
# If unable to retrieve device information, log an error message | ||
self.log("No response received from API call 'get_membership' to get membership information for site. {0}".format(site_name), "ERROR") | ||
response = self.dnac._exec( | ||
family="site_design", | ||
function="get_site_assigned_network_devices", | ||
op_modifies=True, | ||
params=site_params, | ||
) | ||
|
||
# Log the total number of devices processed and skipped | ||
self.log("Total number of devices processed: {0}".format(processed_device_count), "INFO") | ||
self.log("Number of devices skipped due to being unreachable or APs: {0}".format(skipped_device_count), "INFO") | ||
self.log("Filtered devices available for configuration backup: {0}".format(mgmt_ip_to_instance_id_map), "INFO") | ||
self.log("Received API response from 'get_site_assigned_network_devices': {0}".format(str(response)), "DEBUG") | ||
for device in response.get("response", []): | ||
device_id = device.get("deviceId") | ||
if device_id: | ||
device_response = self.dnac._exec( | ||
family="devices", | ||
function="get_device_by_id", | ||
op_modifies=True, | ||
params={"id": device_id} | ||
) | ||
|
||
management_ip = device_response.get("response", {}).get("managementIpAddress") | ||
if management_ip: | ||
mgmt_ip_to_instance_id_map[management_ip] = device_id | ||
else: | ||
self.log(f"Management IP not found for device ID: {device_id}", "WARNING") | ||
|
||
except Exception as e: | ||
# Log an error message if any exception occurs during the process | ||
self.log("Unable to fetch the device(s) associated to the site '{0}' due to {1}".format(site_name, str(e)), "ERROR") | ||
self.log("Unable to fetch the device(s) associated with the site '{0}' due to {1}".format(site_name, str(e)), "ERROR") | ||
|
||
# Log a warning if no reachable devices are found | ||
if not mgmt_ip_to_instance_id_map: | ||
self.log("Reachable devices not found at Site: {0}".format(site_name), "WARNING") | ||
self.msg = "No reachable devices found at Site: {0}".format(site_name) | ||
self.update_result("ok", False, self.msg, "INFO") | ||
self.module.exit_json(**self.result) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to exit from the module if devices not found at site? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the device ID is not available, then the IP address cannot be retrieved in this task |
||
|
||
return mgmt_ip_to_instance_id_map | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting dnac version is already done in dnac.py.. Can you check?
https://github.com/madhansansel/dnacenter-ansible/blob/main/plugins/module_utils/dnac.py
self.payload = module.params
self.dnac_version = int(self.payload.get("dnac_version").replace(".", ""))
# Dictionary to store multiple versions for easy maintenance and scalability
# To add a new version, simply update the 'dnac_versions' dictionary with the new version string as the key
# and the corresponding version number as the value.
self.dnac_versions = {
"2.3.5.3": 2353,
"2.3.7.6": 2376,
"2.2.3.3": 2233
# Add new versions here, e.g., "2.4.0.0": 2400
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed