Skip to content

Commit

Permalink
Merge branch 'main' into provision_workflow_feature
Browse files Browse the repository at this point in the history
  • Loading branch information
syed-khadeerahmed authored Oct 24, 2024
2 parents f0dd351 + 6651dc7 commit 065184f
Show file tree
Hide file tree
Showing 15 changed files with 1,744 additions and 1,119 deletions.
655 changes: 411 additions & 244 deletions plugins/module_utils/dnac.py

Large diffs are not rendered by default.

152 changes: 54 additions & 98 deletions plugins/modules/device_configs_backup_workflow_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,14 +615,10 @@ def get_device_id_list(self, config):

# Retrieve device IDs for each site in the unique_sites set
for site_name in unique_sites:
(site_exists, site_id) = self.get_site_id(site_name)
if site_exists:
site_mgmt_ip_to_instance_id_map = self.get_device_ip_from_device_id(site_id)
self.log("Retrieved following Device Id(s) of device(s): {0} from the provided site: {1}".format(
site_mgmt_ip_to_instance_id_map, site_name), "DEBUG")
mgmt_ip_to_instance_id_map.update(site_mgmt_ip_to_instance_id_map)
else:
self.log("Site '{0}' does not exist.".format(site_name), "WARNING")
site_mgmt_ip_to_instance_id_map, skipped_devices_list = self.get_reachable_devices_from_site(site_name)
self.log("Retrieved following Device Id(s) of device(s): {0} from the provided site: {1}".format(
site_mgmt_ip_to_instance_id_map, site_name), "DEBUG")
mgmt_ip_to_instance_id_map.update(site_mgmt_ip_to_instance_id_map)

# Get additional device list parameters excluding site_list
get_device_list_params = self.get_device_list_params(config)
Expand Down Expand Up @@ -802,34 +798,6 @@ def export_device_configurations(self, export_device_configurations_params):
self.set_operation_result("failed", False, self.msg, "ERROR")
self.check_return_status()

def exit_while_loop(self, start_time, task_id, task_name, response):
"""
Check if the elapsed time exceeds the specified timeout period and exit the while loop if it does.
Parameters:
- start_time (float): The time when the while loop started.
- task_id (str): ID of the task being monitored.
- task_name (str): Name of the task being monitored.
- response (dict): Response received from the task status check.
Returns:
bool: True if the elapsed time exceeds the timeout period, False otherwise.
"""
# If the elapsed time exceeds the timeout period
if time.time() - start_time > self.params.get("dnac_api_task_timeout"):
if response.get("data"):
# If there is data in the response, include it in the error message
self.msg = "Task {0} with task id {1} has not completed within the timeout period. Task Status: {2} ".format(
task_name, task_id, response.get("data"))
else:
# If there is no data in the response, generate a generic error message
self.msg = "Task {0} with task id {1} has not completed within the timeout period.".format(
task_name, task_id)

# Update the result with failure status and log the error message
self.update_result("failed", False, self.msg, "ERROR")
return True

return False

def download_file(self, additional_status_url=None):
"""
Downloads a file from Cisco Catalyst Center and stores it locally.
Expand Down Expand Up @@ -916,71 +884,59 @@ def get_export_device_config_task_status(self, task_id):
file if the task completes successfully.
"""
task_name = "Backup Device Configuration"
start_time = time.time()

while True:
# Retrieve the task status using the task ID
response = self.get_task_details(task_id)
success_msg = "{0} Task with task ID {1} completed successfully. Exiting the loop.".format(task_name, task_id)
if self.dnac_version <= self.version_2_3_5_3:
progress_validation = "Device configuration Successfully exported as password protected ZIP"
failure_msg = (
"An error occurred while performing {0} task with task ID {1} for export_device_configurations_params: {2}"
.format(task_name, task_id, self.want.get("export_device_configurations_params"))
)
self.get_task_status_from_task_by_id(task_id, task_name, failure_msg, success_msg, progress_validation=progress_validation)
else:
self.get_task_status_from_tasks_by_id(task_id, task_name, success_msg)

# Check if response returned
if not response:
self.msg = "Error retrieving Task status for the task_name {0} task_id {1}".format(task_name, task_id)
self.set_operation_result("failed", False, self.msg, "ERROR")
return self

# Check if the elapsed time exceeds the timeout
if self.exit_while_loop(start_time, task_id, task_name, response):
return self

# Handle error if task execution encounters an error
if response.get("isError") or re.search("failed", response.get("progress"), flags=re.IGNORECASE):
failure_reason = response.get("failureReason", "No detailed reason provided")
if self.status == "success":
self.log("Task '{0}' completed successfully for task ID {1}.".format(task_name, task_id), "INFO")
if self.dnac_version <= self.version_2_3_5_3:
response = self.get_task_details(task_id)
additional_status_url = response.get("additionalStatusURL")
else:
response = self.get_tasks_by_id(task_id)
additional_status_url = response.get("resultLocation")

if not additional_status_url:
self.msg = "Error retrieving the Device Config Backup file ID for task ID {0}".format(task_id)
self.fail_and_exit(self.msg)
self.log("Additional status URL retrieved: {0}".format(additional_status_url), "DEBUG")

# Perform additional tasks after breaking the loop
mgmt_ip_to_instance_id_map = self.want.get("mgmt_ip_to_instance_id_map")

# Download the file using the additional status URL
self.log("Downloading the Device Config Backup file from {0}.".format(additional_status_url), "DEBUG")
file_id, downloaded_file = self.download_file(additional_status_url=additional_status_url)
self.log("Retrived file data for file ID: {0}.".format(file_id), "DEBUG")
if not downloaded_file:
self.msg = "Error downloading Device Config Backup file(s) with file ID: {0}. ".format(file_id)
self.fail_and_exit(self.msg)

# Unzip the downloaded file
self.log("Unzipping the downloaded Device Config Backup file(s) for file ID: {0}.".format(file_id), "DEBUG")
download_status = self.unzip_data(file_id, downloaded_file)
if download_status:
self.log("{0} task has been successfully performed on {1} device(s): {2}.".format(
task_name, len(mgmt_ip_to_instance_id_map), list(mgmt_ip_to_instance_id_map.keys())), "INFO")
self.log("{0} task has been skipped for {1} device(s): {2}".format(
task_name, len(self.skipped_devices_list), self.skipped_devices_list), "INFO")
self.msg = (
"An error occurred while performing {0} task for export_device_configurations_params: {1}. "
"The operation failed due to the following reason: {2}".format(
task_name, self.want.get("export_device_configurations_params"), failure_reason
)
"{0} task has been successfully performed on {1} device(s) and skipped on {2} device(s). "
"The backup configuration files can be found at: {3}".format(
task_name, len(mgmt_ip_to_instance_id_map), len(self.skipped_devices_list), pathlib.Path(self.want.get("file_path")).resolve())
)
self.set_operation_result("failed", False, self.msg, "ERROR")
return self

# Check if task completed successfully and exit the loop
if not response.get("isError") and response.get("progress") == "Device configuration Successfully exported as password protected ZIP.":
self.log("{0} Task completed successfully. Exiting the loop.".format(task_name), "INFO")
break

self.log("The progress status is {0}, continue to check the status after 3 seconds. Putting into sleep for 3 seconds.".format(
response.get("progress")), "INFO")
time.sleep(3)

# Perform additional tasks after breaking the loop
mgmt_ip_to_instance_id_map = self.want.get("mgmt_ip_to_instance_id_map")
additional_status_url = response.get("additionalStatusURL")

# Download the file using the additional status URL
file_id, downloaded_file = self.download_file(additional_status_url=additional_status_url)
self.log("Retrived file data for file ID: {0}.".format(file_id), "DEBUG")
if not downloaded_file:
self.msg = "Error downloading Device Config Backup file(s) with file ID: {0}. ".format(file_id)
self.set_operation_result("Failed", True, self.msg, "CRITICAL")
return self

# Unzip the downloaded file
download_status = self.unzip_data(file_id, downloaded_file)
if download_status:
self.log("{0} task has been successfully performed on {1} device(s): {2}.".format(
task_name, len(mgmt_ip_to_instance_id_map), list(mgmt_ip_to_instance_id_map.keys())), "INFO")
self.log("{0} task has been skipped for {1} device(s): {2}".format(
task_name, len(self.skipped_devices_list), self.skipped_devices_list), "INFO")
self.msg = (
"{0} task has been successfully performed on {1} device(s) and skipped on {2} device(s). "
"The backup configuration files can be found at: {3}".format(
task_name, len(mgmt_ip_to_instance_id_map), len(self.skipped_devices_list), pathlib.Path(self.want.get("file_path")).resolve())
)
self.set_operation_result("success", True, self.msg, "INFO")
else:
self.msg = "Error unzipping Device Config Backup file(s) with file ID: {0}. ".format(file_id)
self.set_operation_result("failed", False, self.msg, "ERROR")
self.set_operation_result("success", True, self.msg, "INFO")
else:
self.msg = "Error unzipping Device Config Backup file(s) with file ID: {0}. ".format(file_id)
self.fail_and_exit(self.msg)

return self

Expand Down
2 changes: 1 addition & 1 deletion plugins/modules/inventory_workflow_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,7 @@ def provision_wired_device_v2(self, device_ip, site_name):

assign_params = {'deviceIds': [device_id], 'siteId': site_id}
provision_params = [{"siteId": site_id, "networkDeviceId": device_id}]
is_device_provisioned = self.is_device_provisioned(device_id)
is_device_provisioned = self.is_device_provisioned(device_id, device_ip)
is_device_assigned_to_site = self.is_device_assigned_to_site(device_id)

if not is_device_assigned_to_site:
Expand Down
85 changes: 74 additions & 11 deletions plugins/modules/ise_radius_integration_workflow_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ def get_obj_params(self, get_object):
obj_params = [
("protocol", "protocol"),
("retries", "retries"),
("timeoutSeconds", "timeoutSeconds")
("timeoutSeconds", "timeoutSeconds"),
("pxgridEnabled", "pxgridEnabled"),
("useDnacCertForPxgrid", "useDnacCertForPxgrid"),
]
else:
raise ValueError("Received an unexpected value for 'get_object': {0}"
Expand Down Expand Up @@ -1368,6 +1370,51 @@ def check_auth_server_response_status(self, response, validation_string_set, api

return self

def check_ise_server_updation_status(self, have_auth_details, want_auth_details):
"""
Check if the Cisco ISE server requires an update by comparing the user name,
FQDN, and the state of the Cisco ISE server.
Parameters:
have_auth_details (dict) - Current Cisco Catalyst Center authentication server information.
want_auth_details (dict) - Desired authentication server configuration from the playbook.
Returns:
True or False (bool): True if the Cisco ISE server requires an update; otherwise, False.
Description:
Compares the user name and FQDN between the existing configuration and the new configuration.
If there is a discrepancy, or if the server state is not 'ACTIVE', an update is required.
"""

ip_address = have_auth_details.get("ipAddress")
have_cisco_ise_dtos = have_auth_details.get("ciscoIseDtos")[0]
want_cisco_ise_dtos = want_auth_details.get("ciscoIseDtos")[0]
self.log(
"Checking if the Cisco ISE server '{ip_address}' requires an update."
.format(ip_address=ip_address), "DEBUG"
)
check_list = ["userName", "fqdn"]
for item in check_list:
if have_cisco_ise_dtos[item] != want_cisco_ise_dtos[item]:
self.log(
"Cisco ISE server '{ip_address}' requires an update: {item} has changed."
.format(item=item, ip_address=ip_address), "INFO"
)
return True

state = have_auth_details.get("state")
if state != "ACTIVE":
self.log(
"Cisco ISE server '{ip_address}' is not in 'ACTIVE' state (current state: '{state}'). "
"Update required.".format(ip_address=ip_address, state=state), "DEBUG"
)
return True

self.log(
"No updates are required for the Cisco ISE server '{ip_address}' based on username, fqdn, and state."
.format(ip_address=ip_address), "DEBUG"
)
return False

def format_payload_for_update(self, have_auth_server, want_auth_server):
"""
Format the parameter of the payload for updating the authentication and policy server
Expand Down Expand Up @@ -1500,17 +1547,33 @@ def update_auth_policy_server(self, authentication_policy_server):

# Authentication and Policy Server exists, check update is required
# Edit API not working, remove this
self.format_payload_for_update(self.have.get("authenticationPolicyServer")[auth_server_index].get("details"),
self.want.get("authenticationPolicyServer")[auth_server_index]).check_return_status()
is_ise_server_enabled = self.have.get("authenticationPolicyServer")[auth_server_index].get("details").get("isIseEnabled")
if not (is_ise_server_enabled or self.requires_update(self.have.get("authenticationPolicyServer")[auth_server_index].get("details"),
self.want.get("authenticationPolicyServer")[auth_server_index],
self.authentication_policy_server_obj_params)):
have_auth_server_details = self.have.get("authenticationPolicyServer")[auth_server_index].get("details")
want_auth_server_details = self.want.get("authenticationPolicyServer")[auth_server_index]

self.log(
"Formatting payload for update between current and desired authentication server details.", "DEBUG"
)
self.format_payload_for_update(have_auth_server_details, want_auth_server_details).check_return_status()

is_ise_server_enabled = have_auth_server_details.get("isIseEnabled")
ise_server_requires_update = False
if is_ise_server_enabled:
self.log("Cisco ISE server is enabled; checking if an update is required.")
ise_server_requires_update = self.check_ise_server_updation_status(have_auth_server_details,
want_auth_server_details)
if ise_server_requires_update:
self.log("Cisco ISE server requires an update based on configuration changes.", "DEBUG")
else:
self.log("Cisco ISE server does not require any updates.", "DEBUG")

if not (ise_server_requires_update or self.requires_update(have_auth_server_details,
want_auth_server_details,
self.authentication_policy_server_obj_params)):
self.log("Authentication and Policy Server '{0}' doesn't require an update"
.format(ip_address), "INFO")
result_auth_server.get("response").get(ip_address).update({
"Cisco Catalyst Center params":
self.have.get("authenticationPolicyServer")[auth_server_index].get("details")
have_auth_server_details
})
result_auth_server.get("response").get(ip_address).update({
"Id": self.have.get("authenticationPolicyServer")[auth_server_index].get("id")
Expand All @@ -1523,12 +1586,12 @@ def update_auth_policy_server(self, authentication_policy_server):
self.log("Authentication and Policy Server requires update", "DEBUG")

# Authenticaiton and Policy Server Exists
auth_server_params = copy.deepcopy(self.want.get("authenticationPolicyServer")[auth_server_index])
auth_server_params = copy.deepcopy(want_auth_server_details)
auth_server_params.update({"id": self.have.get("authenticationPolicyServer")[auth_server_index].get("id")})
self.log("Desired State for Authentication and Policy Server (want): {0}"
.format(auth_server_params), "DEBUG")
self.log("Current State for Authentication and Policy Server (have): {0}"
.format(self.have.get("authenticationPolicyServer")[auth_server_index].get("details")), "DEBUG")
.format(have_auth_server_details), "DEBUG")
function_name = "edit_authentication_and_policy_server_access_configuration"
response = self.dnac._exec(
family="system_settings",
Expand All @@ -1548,7 +1611,7 @@ def update_auth_policy_server(self, authentication_policy_server):
trusted_server_msg = ""
if is_ise_server_enabled:
trusted_server = self.want.get("trusted_server")
state = self.have.get("authenticationPolicyServer")[auth_server_index].get("details").get("state")
state = have_auth_server_details.get("state")
if state != "ACTIVE":
self.check_ise_server_integration_status(ip_address)
self.accept_cisco_ise_server_certificate(ip_address, trusted_server)
Expand Down
Loading

0 comments on commit 065184f

Please sign in to comment.