-
Notifications
You must be signed in to change notification settings - Fork 1
Swim bugs completed #141
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
Swim bugs completed #141
Changes from 5 commits
f06ed05
a2703a8
999dabe
1afa78a
6d272d3
c006930
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 |
|---|---|---|
|
|
@@ -411,6 +411,36 @@ | |
| description: Specify the name of the device | ||
| family such as Switches and Hubs, etc. | ||
| type: str | ||
| image_distribution_timeout: | ||
| description: | | ||
| Timeout duration in seconds for image distribution API operations. | ||
| Controls how long the system waits for image distribution tasks to complete, | ||
| including image transfer to target devices, network propagation, and distribution validation. | ||
|
|
||
| Operation phases covered by this timeout: | ||
| - Image preparation and validation on Catalyst Center | ||
| - Network transfer to target devices | ||
| - Installation verification on target devices | ||
| - Distribution status confirmation | ||
|
|
||
| Default of 1800 seconds (30 minutes) accounts for: | ||
| - Large image files (up to several GB) | ||
| - Multiple target devices in site-based operations | ||
| - Network latency and bandwidth constraints | ||
| - Device processing and storage capabilities | ||
|
|
||
| Recommended timeout values: | ||
| - Small networks (1-10 devices): 900-1800 seconds | ||
| - Medium networks (10-50 devices): 1800-3600 seconds | ||
| - Large networks (50+ devices): 3600-7200 seconds | ||
|
|
||
| Note: This timeout is independent of the global 'dnac_api_task_timeout' parameter | ||
| and specifically applies to distribution operations only. | ||
|
|
||
| default: 1800 | ||
| type: int | ||
| version_added: 3.1.3.0 | ||
|
|
||
| site_name: | ||
| description: Used to get device details | ||
| associated to this site. | ||
|
|
@@ -467,6 +497,39 @@ | |
| ACCESS, BORDER ROUTER, DISTRIBUTION, and | ||
| CORE. | ||
| type: str | ||
| image_activation_timeout: | ||
| description: | | ||
| Timeout duration in seconds for image activation API operations. | ||
| Controls how long the system waits for image activation processes to complete | ||
| before timing out, including device reboot and startup verification. | ||
|
|
||
| Operation phases covered by this timeout: | ||
| - Image validation and preparation for activation | ||
| - Device upgrade mode processing (install/bundle/currentlyExists) | ||
| - Device reboot and startup sequence (if required) | ||
| - Post-activation connectivity and status verification | ||
| - Golden image validation (if applicable) | ||
|
|
||
| Default of 1800 seconds (30 minutes) accommodates: | ||
| - Device boot time variations (switches: 5-15 min, routers: 10-20 min) | ||
| - Image installation and verification processes | ||
| - Network convergence and connectivity restoration | ||
| - Multiple devices in concurrent activation scenarios | ||
|
|
||
| Recommended timeout values by device type: | ||
| - Access switches: 1200-1800 seconds (20-30 minutes) | ||
| - Distribution/Core switches: 1800-2700 seconds (30-45 minutes) | ||
| - Routers and complex devices: 2700-3600 seconds (45-60 minutes) | ||
|
|
||
| Warning: Setting timeout too low may cause premature failure reporting | ||
| for successful but slow activation processes. | ||
|
|
||
| Note: This timeout is independent of the global 'dnac_api_task_timeout' parameter | ||
|
|
||
| type: int | ||
| default: 1800 | ||
| version_added: 3.1.3.0 | ||
|
|
||
| device_family_name: | ||
| description: Specify the name of the device | ||
| family such as Switches and Hubs, etc. | ||
|
|
@@ -831,6 +894,7 @@ | |
| device_family_name: Switches and Hubs | ||
| device_series_name: Cisco Catalyst 9300 Series | ||
| Switches | ||
|
|
||
| - name: Activate the given image on devices associated | ||
| to that site with specified role. | ||
| cisco.dnac.swim_workflow_manager: | ||
|
|
@@ -850,7 +914,52 @@ | |
| device_role: ALL | ||
| device_family_name: Switches and Hubs | ||
| device_series_name: Cisco Catalyst 9300 Series Switches | ||
| scehdule_validate: false | ||
| schedule_validate: false | ||
| activate_lower_image_version: true | ||
| distribute_if_needed: true | ||
|
|
||
| - name: Activate the golden image on devices associated | ||
| to that site with specified role. | ||
| cisco.dnac.swim_workflow_manager: | ||
| dnac_host: "{{dnac_host}}" | ||
| dnac_username: "{{dnac_username}}" | ||
| dnac_password: "{{dnac_password}}" | ||
| dnac_verify: "{{dnac_verify}}" | ||
| dnac_port: "{{dnac_port}}" | ||
| dnac_version: "{{dnac_version}}" | ||
| dnac_debug: "{{dnac_debug}}" | ||
| dnac_log_level: "{{dnac_log_level}}" | ||
| dnac_log: true | ||
| config: | ||
| - image_activation_details: | ||
| site_name: Global/USA/San Francisco/BGL_18 | ||
| device_role: ALL | ||
| device_family_name: Switches and Hubs | ||
| device_series_name: Cisco Catalyst 9300 Series Switches | ||
| schedule_validate: false | ||
| activate_lower_image_version: true | ||
| distribute_if_needed: true | ||
|
|
||
| - name: distribute the golden image on devices associated | ||
| to that site with specified role with custom api timeout. | ||
| cisco.dnac.swim_workflow_manager: | ||
| dnac_host: "{{dnac_host}}" | ||
| dnac_username: "{{dnac_username}}" | ||
| dnac_password: "{{dnac_password}}" | ||
| dnac_verify: "{{dnac_verify}}" | ||
| dnac_port: "{{dnac_port}}" | ||
| dnac_version: "{{dnac_version}}" | ||
| dnac_debug: "{{dnac_debug}}" | ||
| dnac_log_level: "{{dnac_log_level}}" | ||
| dnac_log: true | ||
| config: | ||
| - image_activation_details: | ||
| site_name: Global/USA/San Francisco/BGL_18 | ||
| image_activation_timeout: 2500 | ||
| device_role: ALL | ||
| device_family_name: Switches and Hubs | ||
| device_series_name: Cisco Catalyst 9300 Series Switches | ||
| schedule_validate: false | ||
| activate_lower_image_version: true | ||
| distribute_if_needed: true | ||
|
|
||
|
|
@@ -1365,11 +1474,21 @@ def get_device_uuids( | |
| device_uuid_list = [] | ||
| device_id_list, site_response_list = [], [] | ||
| if not site_name: | ||
| site_names = "Global/.*" | ||
| self.log( | ||
| "Site name not specified; defaulting to 'Global' to fetch all devices under this category", | ||
| "INFO", | ||
| ) | ||
| current_version = self.get_ccc_version() | ||
| if self.compare_dnac_versions(current_version, "2.3.5.3") <= 0: | ||
| site_name = "Global/.*" | ||
| self.log( | ||
| "Catalyst Center version {0} (≤2.3.5.3) detected - using wildcard pattern 'Global/.*' " | ||
| "to fetch devices from Global site and all child sites via legacy API".format(current_version), | ||
| "INFO", | ||
| ) | ||
| else: | ||
| site_name = "Global" | ||
| self.log( | ||
| "Catalyst Center version {0} (>2.3.5.3) detected - using 'Global' site name " | ||
| "to fetch devices via enhanced site hierarchy API".format(current_version), | ||
| "INFO", | ||
| ) | ||
|
|
||
| (site_exists, site_id) = self.site_exists(site_name) | ||
| if not site_exists: | ||
|
|
@@ -1429,6 +1548,9 @@ def get_device_uuids( | |
| site_response_list.append(item_dict) | ||
| else: | ||
| if site_name: | ||
| self.log( | ||
| "Fetching devices for site '{0}'".format(site_name), "DEBUG" | ||
| ) | ||
| site_type = self.get_sites_type(site_name) | ||
| self.log("Determined site type: {0}".format(site_type), "DEBUG") | ||
| site_info = {} | ||
|
|
@@ -1493,7 +1615,7 @@ def get_device_uuids( | |
| self.log("No child site data found under: {0}".format(wildcard_site_name), "DEBUG") | ||
| site_names = site_name | ||
|
|
||
| elif site_type == "area": | ||
| elif site_type in ["area", "global"]: | ||
| self.log( | ||
| "Processing site as an area: {site_name}".format(site_name=site_name), | ||
| "DEBUG", | ||
|
|
@@ -1528,7 +1650,7 @@ def get_device_uuids( | |
| "ERROR", | ||
| ) | ||
|
|
||
| if site_type in ["area", "floor"]: | ||
| if site_type in ["area", "floor", "global"]: | ||
| self.log("Fetching site names for pattern: {0}".format(site_names), "DEBUG") | ||
| get_site_names = self.get_site(site_names) | ||
| self.log("Fetched site names: {0}".format(str(get_site_names)), "DEBUG") | ||
|
|
@@ -2999,7 +3121,7 @@ def get_diff_distribution(self): | |
| device_family = distribution_details.get("device_family_name") | ||
| device_role = distribution_details.get("device_role", "ALL") | ||
| device_series_name = distribution_details.get("device_series_name") | ||
|
|
||
| self.max_timeout = distribution_details.get("image_distribution_timeout", 1800) | ||
| self.log( | ||
| "Fetching device UUIDs for site '{0}', family '{1}', role '{2}', and series '{3}'.".format( | ||
| site_name, device_family, device_role, device_series_name | ||
|
|
@@ -3087,6 +3209,7 @@ def get_diff_distribution(self): | |
| ) | ||
| self.set_operation_result("success", False, self.msg, "INFO") | ||
| return self | ||
|
|
||
| if ( | ||
| self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") | ||
| <= 0 | ||
|
|
@@ -3213,7 +3336,7 @@ def get_diff_distribution(self): | |
| "DEBUG", | ||
| ) | ||
|
|
||
| self.check_tasks_response_status( | ||
| self.check_swim_tasks_response_status( | ||
| response, "distribute_images_on_the_network_device" | ||
| ) | ||
|
|
||
|
|
@@ -3401,7 +3524,7 @@ def get_diff_distribution(self): | |
|
|
||
| self.log("API response from 'bulk_distribute_images_on_network_devices': {0}".format(str(response)), "DEBUG") | ||
|
|
||
| self.check_tasks_response_status( | ||
| self.check_swim_tasks_response_status( | ||
| response, "bulk_distribute_images_on_network_devices" | ||
| ) | ||
|
|
||
|
|
@@ -3559,6 +3682,7 @@ def get_diff_activation(self): | |
| device_family = activation_details.get("device_family_name") | ||
| device_role = activation_details.get("device_role", "ALL") | ||
| device_series_name = activation_details.get("device_series_name") | ||
| self.max_timeout = activation_details.get("image_activation_timeout", 1800) | ||
|
|
||
| self.log( | ||
| "Fetching device UUIDs for site '{0}', family '{1}', role '{2}', and series '{3}'.".format( | ||
|
|
@@ -3653,7 +3777,7 @@ def get_diff_activation(self): | |
| activation_payload_list = [] | ||
|
|
||
| # OLD FLOW (for DNAC < 2.3.7.9) | ||
| if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") < 0: | ||
| if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") <= 0: | ||
| for image_name, image_id in image_ids.items(): | ||
| payload = [ | ||
| { | ||
|
|
@@ -3715,7 +3839,7 @@ def get_diff_activation(self): | |
| self.log(failed_msg, "ERROR") | ||
| break | ||
|
|
||
| # NEW FLOW (for Catalyst Center >= 2.3.7.9) | ||
| # NEW FLOW (for Catalyst Center > 2.3.7.9) | ||
| else: | ||
| self.log("Using new SWIM API for image activation (after 2.3.7.9)", "INFO") | ||
|
|
||
|
|
@@ -3751,7 +3875,7 @@ def get_diff_activation(self): | |
| ) | ||
|
|
||
| self.log("API response from 'update_images_on_the_network_device': {0}".format(str(response)), "DEBUG") | ||
| self.check_tasks_response_status(response, "update_images_on_the_network_device") | ||
| self.check_swim_tasks_response_status(response, "update_images_on_the_network_device") | ||
|
|
||
| device_ip = self.get_device_ip_from_id(activation_device_id) | ||
| if response and self.status not in ["failed", "exited"]: | ||
|
|
@@ -3816,8 +3940,8 @@ def get_diff_activation(self): | |
| elg_device_list = [] | ||
| device_ip_for_not_elg_list = [] | ||
|
|
||
| # OLD FLOW (for DNAC < 2.3.7.9) | ||
| if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") < 0: | ||
| # OLD FLOW (for DNAC <= 2.3.7.9) | ||
| if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") <= 0: | ||
| for device_uuid in device_uuid_list: | ||
| device_ip = self.get_device_ip_from_id(device_uuid) | ||
| activated = False | ||
|
|
@@ -3903,7 +4027,7 @@ def get_diff_activation(self): | |
| "{} to {}".format(img, ", ".join(devices)) for img, devices in failed_image_map.items() | ||
| ] | ||
|
|
||
| # NEW FLOW (for DNAC >= 2.3.7.9) | ||
| # NEW FLOW (for DNAC > 2.3.7.9) | ||
| else: | ||
| image_id_base = self.have.get("activation_image_id") | ||
| # Resolve sub-package ids (if any) | ||
|
|
@@ -3914,7 +4038,6 @@ def get_diff_activation(self): | |
|
|
||
| for device_uuid in device_uuid_list: | ||
| device_ip = self.get_device_ip_from_id(device_uuid) | ||
| device_ips.append(device_ip) | ||
| self.log("Processing device: {0}".format(device_ip), "DEBUG") | ||
|
|
||
| # Aggregate all image ids for this device | ||
|
|
@@ -3934,6 +4057,8 @@ def get_diff_activation(self): | |
| device_ip_for_not_elg_list.append(device_ip) | ||
| continue | ||
|
|
||
| device_ips.append(elg_device_ip) | ||
|
|
||
| activation_payload = {} | ||
| if device_id: | ||
| activation_payload["id"] = device_id | ||
|
|
@@ -3968,7 +4093,7 @@ def get_diff_activation(self): | |
| params={"payload": activation_payload_list}, | ||
| ) | ||
| self.log("API response from 'bulk_update_images_on_network_devices': {0}".format(str(response)), "DEBUG") | ||
| self.check_tasks_response_status(response, "bulk_update_images_on_network_devices") | ||
| self.check_swim_tasks_response_status(response, "bulk_update_images_on_network_devices") | ||
|
|
||
| if response and self.status not in ["failed", "exited"]: | ||
| self.msg = "All eligible images activated successfully on the devices {0}.".format(", ".join(device_ips)) | ||
|
|
@@ -4014,6 +4139,73 @@ def get_diff_activation(self): | |
| self.complete_successful_activation = True | ||
| return self | ||
|
|
||
| def check_swim_tasks_response_status(self, response, api_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. Shall we add operation_details as well?
|
||
| """ | ||
| Get the task response status from taskId | ||
| Args: | ||
| self: The current object details. | ||
|
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. |
||
| response (dict): API response. | ||
| api_name (str): API name. | ||
| Returns: | ||
| self (object): The current object with updated desired Fabric Transits information. | ||
| Description: | ||
| Poll the function 'get_tasks_by_id' until it returns either 'SUCCESS' or 'FAILURE' | ||
| state or till it reaches the maximum timeout. | ||
| Log the task details and return self. | ||
| """ | ||
| self.log("Starting SWIM task status monitoring for API operation: {0}".format(api_name), "DEBUG") | ||
| self.log("Input response: {0}".format(response), "DEBUG") | ||
| self.log("Max timeout for task monitoring is set to {0} seconds.".format(self.max_timeout), "DEBUG") | ||
| if not response: | ||
| self.msg = "response is empty" | ||
|
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. please add a detailed message..
|
||
| self.status = "exited" | ||
| return self | ||
|
|
||
| if not isinstance(response, dict): | ||
| self.msg = "response is not a dictionary" | ||
|
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. |
||
| self.status = "exited" | ||
| return self | ||
|
|
||
| task_info = response.get("response") | ||
|
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. first check the task_info, if not then return.. |
||
| if task_info.get("errorcode") is not None: | ||
| self.msg = response.get("response").get("detail") | ||
| self.status = "failed" | ||
| return self | ||
|
|
||
| task_id = task_info.get("taskId") | ||
|
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 task_id is none, then return?? |
||
| start_time = time.time() | ||
|
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. it is good to determine the timeout for this operation.. |
||
| while True: | ||
|
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. Add a sample code to check the task_status.. But you can refine and update if required.. 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. |
||
| elapsed_time = time.time() - start_time | ||
| if elapsed_time >= self.max_timeout: | ||
| self.msg = "Max timeout of {0} sec has reached for the task id '{1}'. " \ | ||
| .format(self.max_timeout, task_id) + \ | ||
| "Exiting the loop due to unexpected API '{0}' status.".format(api_name) | ||
| self.log(self.msg, "WARNING") | ||
| self.status = "failed" | ||
| break | ||
|
|
||
| task_details = self.get_tasks_by_id(task_id) | ||
| self.log('Getting tasks details from task ID {0}: {1}' | ||
| .format(task_id, task_details), "DEBUG") | ||
|
|
||
| task_status = task_details.get("status") | ||
| if task_status == "FAILURE": | ||
| details = self.get_task_details_by_id(task_id) | ||
| self.msg = details.get("failureReason") | ||
| self.status = "failed" | ||
| break | ||
|
|
||
| elif task_status == "SUCCESS": | ||
| self.result["changed"] = True | ||
| self.log("The task with task ID '{0}' is executed successfully." | ||
| .format(task_id), "INFO") | ||
| break | ||
|
|
||
| self.log("Progress is {0} for task ID: {1}" | ||
| .format(task_status, task_id), "DEBUG") | ||
|
|
||
| return self | ||
|
|
||
| def get_diff_merged(self, config): | ||
| """ | ||
| Get tagging details and then trigger distribution followed by activation if specified in the playbook. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "docs": "https://docs.ansible.com/ansible-core/2.15/dev_guide/testing/sanity/pep8.html", | ||
| "results": [ | ||
| { | ||
| "message": "The test `ansible-test sanity --test pep8` [[explain](https://docs.ansible.com/ansible-core/2.15/dev_guide/testing/sanity/pep8.html)] failed with 2 errors:", | ||
| "output": "plugins/modules/swim_workflow_manager.py:4340:19: W291: trailing whitespace\nplugins/modules/swim_workflow_manager.py:4354:5: E301: expected 1 blank line, found 0" | ||
| } | ||
| ], | ||
| "verified": false | ||
| } |
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.
The name "api_task_timeout" may get confused with the existing global dnac_api_task_timeout parameter. Please note that the parameter name should reflect its specific operation scope.
Can we say
OR
OR
image_distribution_timeout. .. image_activation_timeout ...Check with @DNACENSolutions and update...
type: int
default: 1800