From 224c316ad01e9a98828464fd9d3ac07c71e310d1 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Fri, 20 Sep 2024 16:39:10 +0530 Subject: [PATCH 01/28] provision and inventory code 60 % compleated --- plugins/module_utils/dnac.py | 32 ++ plugins/modules/inventory_workflow_manager.py | 383 ++++++++++---- plugins/modules/provision_workflow_manager.py | 479 +++++++++++------- 3 files changed, 614 insertions(+), 280 deletions(-) diff --git a/plugins/module_utils/dnac.py b/plugins/module_utils/dnac.py index 3820ecffd0..4e6c4dcd2e 100644 --- a/plugins/module_utils/dnac.py +++ b/plugins/module_utils/dnac.py @@ -510,6 +510,38 @@ def check_string_dictionary(self, task_details_data): pass return None + def get_sites(self, site_name): + """ + Retrieve site details from Cisco Catalyst Center based on the provided site name. + Args: + - site_name (str): The name or hierarchy of the site to be retrieved. + Returns: + - response (dict or None): The response from the API call, typically a dictionary containing site details. + Returns None if an error occurs or if the response is empty. + Criteria: + - This function uses the Cisco Catalyst Center SDK to execute the 'get_sites' function from the 'site_design' family. + - If the response is empty, a warning is logged. + - Any exceptions during the API call are caught, logged as errors, and the function returns None. + """ + try: + response = self.dnac._exec( + family="site_design", + function='get_sites', + op_modifies=True, + params={"name_hierarchy": site_name}, + ) + + if not response: + self.log("The response from 'get_sites' is empty.", "WARNING") + return None + + self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") + return response + + except Exception as e: + self.log("An error occurred in 'get_sites':{0}".format(e), "ERROR") + return None + def generate_key(self): """ Generate a new encryption key using Fernet. diff --git a/plugins/modules/inventory_workflow_manager.py b/plugins/modules/inventory_workflow_manager.py index 61fbef9f0f..6a9045a145 100644 --- a/plugins/modules/inventory_workflow_manager.py +++ b/plugins/modules/inventory_workflow_manager.py @@ -545,10 +545,48 @@ site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" resync_retry_count: 200 resync_retry_interval: 2 + provisioning: True - device_ip: "2.2.2.2" site_name: "Global/USA/San Francisco/BGL_18/floor_test" resync_retry_count: 200 resync_retry_interval: 2 + provisioning: True + +- name: Associate Wired Devices to a site + cisco.dnac.inventory_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: False + state: merged + config: + - provision_wired_device: + - device_ip: "1.1.1.1" + site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: False + - device_ip: "2.2.2.2" + site_name: "Global/USA/San Francisco/BGL_18/floor_test" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: False + - provision_wireless_device: + - device_ip: "1.1.1.1" + site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: False + - device_ip: "2.2.2.2" + site_name: "Global/USA/San Francisco/BGL_18/floor_test" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: False - name: Update Device Role with IP Address cisco.dnac.inventory_workflow_manager: @@ -730,8 +768,6 @@ DnacBase, validate_list_of_dicts, ) -# Defer this feature as API issue is there once it's fixed we will addresses it in upcoming release iac2.0 -support_for_provisioning_wireless = False class Inventory(DnacBase): @@ -740,6 +776,9 @@ class Inventory(DnacBase): def __init__(self, module): super().__init__(module) self.supported_states = ["merged", "deleted"] + self.payload = module.params + self.dnac_version = int(self.payload.get("dnac_version").replace(".", "")) + self.version_2_3_5_3, self.version_2_3_7_6, self.version_2_2_3_3 = 2353, 2376, 2233 def validate_input(self): """ @@ -827,6 +866,17 @@ def validate_input(self): 'site_name': {'type': 'str'}, 'resync_retry_count': {'default': 200, 'type': 'int'}, 'resync_retry_interval': {'default': 2, 'type': 'int'}, + "provisioning": {'type':'bool'} + }, + 'provision_wireless_device': { + 'type': 'list', + 'elements': 'dict', + 'device_ip': {'type': 'str'}, + 'site_name': {'type': 'str'}, + 'resync_retry_count': {'default': 200, 'type': 'int'}, + 'resync_retry_interval': {'default': 2, 'type': 'int'}, + "managed_ap_locations": {'type': 'list', 'element': 'str'}, + 'dynamic_interfaces': {'type': 'list', 'element': 'dict'} } } @@ -1674,107 +1724,239 @@ def provisioned_wired_device(self): total_devices_to_provisioned = len(provision_wired_list) device_ip_list = [] provision_count, already_provision_count = 0, 0 + if self.dnac_version <= self.version_2_3_5_3: + for prov_dict in provision_wired_list: + managed_flag = False + device_ip = prov_dict['device_ip'] + device_ip_list.append(device_ip) + site_name = prov_dict['site_name'] + device_type = "Wired" + resync_retry_count = prov_dict.get("resync_retry_count", 200) + # This resync retry interval will be in seconds which will check device status at given interval + resync_retry_interval = prov_dict.get("resync_retry_interval", 2) - for prov_dict in provision_wired_list: - managed_flag = False - device_ip = prov_dict['device_ip'] - device_ip_list.append(device_ip) - site_name = prov_dict['site_name'] - device_type = "Wired" - resync_retry_count = prov_dict.get("resync_retry_count", 200) - # This resync retry interval will be in seconds which will check device status at given interval - resync_retry_interval = prov_dict.get("resync_retry_interval", 2) + if not site_name or not device_ip: + self.status = "failed" + self.msg = "Site and Device IP are required for Provisioning of Wired Devices." + self.log(self.msg, "ERROR") + self.result['response'] = self.msg + return self - if not site_name or not device_ip: - self.status = "failed" - self.msg = "Site and Device IP are required for Provisioning of Wired Devices." - self.log(self.msg, "ERROR") - self.result['response'] = self.msg - return self + provision_wired_params = { + 'deviceManagementIpAddress': device_ip, + 'siteNameHierarchy': site_name + } - provision_wired_params = { - 'deviceManagementIpAddress': device_ip, - 'siteNameHierarchy': site_name - } + # Check the provisioning status of device + device_prov_status = self.get_provision_wired_device(device_ip) + if device_prov_status == 2: + self.status = "success" + already_provision_count += 1 + self.result['changed'] = False + self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) + self.log(self.msg, "INFO") + continue + if device_prov_status == 3: + self.status = "failed" + error_msg = "Cannot do Provisioning for device {0}.".format(device_ip) + self.log(error_msg, "ERROR") + continue - # Check the provisioning status of device - device_prov_status = self.get_provision_wired_device(device_ip) - if device_prov_status == 2: - self.status = "success" - already_provision_count += 1 - self.result['changed'] = False - self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) - self.log(self.msg, "INFO") - continue - if device_prov_status == 3: - self.status = "failed" - error_msg = "Cannot do Provisioning for device {0}.".format(device_ip) - self.log(error_msg, "ERROR") - continue + # Check till device comes into managed state + while resync_retry_count: + response = self.get_device_response(device_ip) + self.log("Device is in {0} state waiting for Managed State.".format(response['managementState']), "DEBUG") - # Check till device comes into managed state - while resync_retry_count: - response = self.get_device_response(device_ip) - self.log("Device is in {0} state waiting for Managed State.".format(response['managementState']), "DEBUG") - - if ( - response.get('managementState') == "Managed" - and response.get('collectionStatus') == "Managed" - and response.get("hostname") - ): - msg = """Device '{0}' comes to managed state and ready for provisioning with the resync_retry_count - '{1}' left having resync interval of {2} seconds""".format(device_ip, resync_retry_count, resync_retry_interval) - self.log(msg, "INFO") - managed_flag = True - break - if response.get('collectionStatus') == "Partial Collection Failure" or response.get('collectionStatus') == "Could Not Synchronize": - device_status = response.get('collectionStatus') - msg = """Device '{0}' comes to '{1}' state and never goes for provisioning with the resync_retry_count - '{2}' left having resync interval of {3} seconds""".format(device_ip, device_status, resync_retry_count, resync_retry_interval) - self.log(msg, "INFO") - managed_flag = False - break + if ( + response.get('managementState') == "Managed" + and response.get('collectionStatus') == "Managed" + and response.get("hostname") + ): + msg = """Device '{0}' comes to managed state and ready for provisioning with the resync_retry_count + '{1}' left having resync interval of {2} seconds""".format(device_ip, resync_retry_count, resync_retry_interval) + self.log(msg, "INFO") + managed_flag = True + break + if response.get('collectionStatus') == "Partial Collection Failure" or response.get('collectionStatus') == "Could Not Synchronize": + device_status = response.get('collectionStatus') + msg = """Device '{0}' comes to '{1}' state and never goes for provisioning with the resync_retry_count + '{2}' left having resync interval of {3} seconds""".format(device_ip, device_status, resync_retry_count, resync_retry_interval) + self.log(msg, "INFO") + managed_flag = False + break - time.sleep(resync_retry_interval) - resync_retry_count = resync_retry_count - 1 + time.sleep(resync_retry_interval) + resync_retry_count = resync_retry_count - 1 - if not managed_flag: - self.log("""Device {0} is not transitioning to the managed state, so provisioning operation cannot - be performed.""".format(device_ip), "WARNING") - continue + if not managed_flag: + self.log("""Device {0} is not transitioning to the managed state, so provisioning operation cannot + be performed.""".format(device_ip), "WARNING") + continue - try: + try: + response = self.dnac._exec( + family="sda", + function='provision_wired_device', + op_modifies=True, + params=provision_wired_params, + ) + + if response.get("status") == "failed": + description = response.get("description") + error_msg = "Cannot do Provisioning for device {0} beacuse of {1}".format(device_ip, description) + self.log(error_msg, "ERROR") + continue + + task_id = response.get("taskId") + + while True: + execution_details = self.get_task_details(task_id) + progress = execution_details.get("progress") + + if 'TASK_PROVISION' in progress: + self.handle_successful_provisioning(device_ip, execution_details, device_type) + provision_count += 1 + break + elif execution_details.get("isError"): + self.handle_failed_provisioning(device_ip, execution_details, device_type) + break + + except Exception as e: + # Not returning from here as there might be possiblity that for some devices it comes into exception + # but for others it gets provision successfully or If some devices are already provsioned + self.handle_provisioning_exception(device_ip, e, device_type) + else: + for prov_dict in provision_wired_list: + device_ip = prov_dict['device_ip'] + device_ip_list.append(device_ip) + site_name = prov_dict['site_name'] + device_type = "Wired" + is_provision = prov_dict.get("provision") + resync_retry_count = prov_dict.get("resync_retry_count", 200) + # This resync retry interval will be in seconds which will check device status at given interval + resync_retry_interval = prov_dict.get("resync_retry_interval", 2) + + device_ids = self.get_device_ids([device_ip]) + device_id = device_ids[0] + + try: + response = self.get_sites(site_name) + self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") + site = response.get("response") + site_id = site[0].get("id") + + except Exception as e: + self.status = "failed" + self.msg = ("'An exception occurred: Site '{0}' does not exist in the Cisco Catalyst Center").format(site_name) + self.log(self.msg, "ERROR") + self.check_return_status() + + assign_network_device_to_site = { + 'deviceIds': [device_id], + 'siteId': site_id, + } + + provision_params = [{ + "siteId": site_id, + "networkDeviceId": device_id + }] + + if not site_name or not device_ip: + self.status = "failed" + self.msg = "Site and Device IP are required for Provisioning of Wired Devices." + self.log(self.msg, "ERROR") + self.result['response'] = self.msg + return self + + # device provision or not response = self.dnac._exec( family="sda", - function='provision_wired_device', + function='get_provisioned_devices', op_modifies=True, - params=provision_wired_params, + params={"networkDeviceId":device_id}, ) + get_provision_response = response.get("response") - if response.get("status") == "failed": - description = response.get("description") - error_msg = "Cannot do Provisioning for device {0} beacuse of {1}".format(device_ip, description) - self.log(error_msg, "ERROR") - continue - - task_id = response.get("taskId") - - while True: - execution_details = self.get_task_details(task_id) - progress = execution_details.get("progress") - - if 'TASK_PROVISION' in progress: - self.handle_successful_provisioning(device_ip, execution_details, device_type) - provision_count += 1 - break - elif execution_details.get("isError"): - self.handle_failed_provisioning(device_ip, execution_details, device_type) - break + #if the responce is has anything then it should go for reprovision + if get_provision_response: + self.log("{0} Device {1} is already provisioned !!".format(device_type, device_ip), "INFO") + self.status = "success" + already_provision_count += 1 + self.result['changed'] = False + self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) + self.log(self.msg, "INFO") - except Exception as e: - # Not returning from here as there might be possiblity that for some devices it comes into exception - # but for others it gets provision successfully or If some devices are already provsioned - self.handle_provisioning_exception(device_ip, e, device_type) + else: + if is_provision == True: + try: + self.log(assign_network_device_to_site) + response = self.dnac._exec( + family="site_design", + function='assign_network_devices_to_a_site', + op_modifies=True, + params=assign_network_device_to_site, + ) + self.log("Received API response from 'assign_network_devices_to_a_site': {0}".format(str(response)), "DEBUG") + task_id = response.get("response").get("taskId") + self.log(task_id) + while True: + execution_details = self.get_task_details(task_id) + progress = execution_details.get("progress") + if "success" in progress: + self.log("Assigning site for the device {0} was compleated... ".format(device_ip)) + break + elif execution_details.get("isError"): + error_msg = "Assigning site failed for the device {0} ".format(device_ip) + self.log(error_msg, "ERROR") + + self.log(provision_params) + response = self.dnac._exec( + family="sda", + function='provision_devices', + op_modifies=True, + params={"payload": provision_params}, + ) + self.log("Received API response from 'provision_devices': {0}".format(str(response)), "DEBUG") + task_id = response.get("response").get("taskId") + + while True: + execution_details = self.get_task_details(task_id) + progress = execution_details.get("progress") + data = execution_details.get("data") + + if 'processcfs_complete=true' in data: + self.handle_successful_provisioning(device_ip, execution_details, device_type) + provision_count += 1 + break + elif execution_details.get("isError"): + self.handle_failed_provisioning(device_ip, execution_details, device_type) + break + + except: + self.log("exception") + else: + try: + self.log(assign_network_device_to_site) + response = self.dnac._exec( + family="site_design", + function='assign_network_devices_to_a_site', + op_modifies=True, + params=assign_network_device_to_site, + ) + self.log("Received API response from 'assign_network_devices_to_a_site': {0}".format(str(response)), "DEBUG") + task_id = response.get("response").get("taskId") + self.log(task_id) + while True: + execution_details = self.get_task_details(task_id) + progress = execution_details.get("progress") + if "success" in progress: + self.log("Assigning site for the device {0} was Successfull... ".format(device_ip)) + break + elif execution_details.get("isError"): + error_msg = "Assigning site failed for the device {0} ".format(device_ip) + self.log(error_msg, "ERROR") + except: + self.log("exception") # Check If all the devices are already provsioned, return from here only if already_provision_count == total_devices_to_provisioned: @@ -2120,16 +2302,15 @@ def get_have(self, config): if device_ip_address not in device_in_ccc: device_not_in_ccc.append(device_ip_address) - if support_for_provisioning_wireless: - if self.config[0].get('provision_wireless_device'): - provision_wireless_list = self.config[0].get('provision_wireless_device') + if self.config[0].get('provision_wireless_device'): + provision_wireless_list = self.config[0].get('provision_wireless_device') - for prov_dict in provision_wireless_list: - device_ip_address = prov_dict['device_ip'] - if device_ip_address not in want_device and device_ip_address not in devices_in_playbook: - devices_in_playbook.append(device_ip_address) - if device_ip_address not in device_in_ccc and device_ip_address not in device_not_in_ccc: - device_not_in_ccc.append(device_ip_address) + for prov_dict in provision_wireless_list: + device_ip_address = prov_dict['device_ip'] + if device_ip_address not in want_device and device_ip_address not in devices_in_playbook: + devices_in_playbook.append(device_ip_address) + if device_ip_address not in device_in_ccc and device_ip_address not in device_not_in_ccc: + device_not_in_ccc.append(device_ip_address) self.log("Device(s) {0} exists in Cisco Catalyst Center".format(str(device_in_ccc)), "INFO") have["want_device"] = want_device @@ -3492,9 +3673,9 @@ def get_diff_merged(self, config): # Once Wireless device get added we will assign device to site and Provisioned it # Defer this feature as API issue is there once it's fixed we will addresses it in upcoming release iac2.0 - if support_for_provisioning_wireless: - if self.config[0].get('provision_wireless_device'): - self.provisioned_wireless_devices().check_return_status() + + if self.config[0].get('provision_wireless_device'): + self.provisioned_wireless_devices().check_return_status() if device_resynced: self.resync_devices().check_return_status() diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 398e473e12..56143e3951 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -145,8 +145,17 @@ dnac_log: True state: merged config: - - site_name_hierarchy: Global/USA/San Francisco/BGL_18 - management_ip_address: 204.192.3.40 + - provision_wired_device: + - device_ip: "1.1.1.1" + site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: True + - device_ip: "2.2.2.2" + site_name: "Global/USA/San Francisco/BGL_18/floor_test" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: True - name: Assign a wired device to a site cisco.dnac.provision_workflow_manager: @@ -160,8 +169,19 @@ dnac_log: True state: merged config: - - site_name_hierarchy: Global/USA/San Francisco/BGL_18 - management_ip_address: 204.192.3.40 + # - site_name_hierarchy: Global/USA/San Francisco/BGL_18 + # management_ip_address: 204.192.3.40 + # provisioning: False + - provision_wired_device: + - device_ip: "1.1.1.1" + site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" + resync_retry_count: 200 + resync_retry_interval: 2 + provisioning: False + - device_ip: "2.2.2.2" + site_name: "Global/USA/San Francisco/BGL_18/floor_test" + resync_retry_count: 200 + resync_retry_interval: 2 provisioning: False - name: Provision a wireless device to a site @@ -253,62 +273,74 @@ class Provision(DnacBase): """ def __init__(self, module): super().__init__(module) + self.supported_states = ["merged", "deleted"] + self.payload = module.params + self.dnac_version = int(self.payload.get("dnac_version").replace(".", "")) + self.version_2_3_5_3, self.version_2_3_7_6, self.version_2_2_3_3 = 2353, 2376, 2233 - def validate_input(self, state=None): + def validate_input(self): """ Validate the fields provided in the playbook. Checks the configuration provided in the playbook against a predefined specification to ensure it adheres to the expected structure and data types. - Args: + Parameters: self: The instance of the class containing the 'config' attribute to be validated. Returns: The method returns an instance of the class with updated attributes: - self.msg: A message describing the validation result. - self.status: The status of the validation (either 'success' or 'failed'). - - self.validated_config: If successful, a validated version of the - 'config' parameter. + - self.validated_config: If successful, a validated version of the 'config' parameter. Example: To use this method, create an instance of the class and call 'validate_input' on it. - If the validation succeeds, 'self.status' will be 'success' and - 'self.validated_config' will contain the validated configuration. If it fails, - 'self.status' will be 'failed', and 'self.msg' will describe the validation issues. + If the validation succeeds, 'self.status' will be 'success' and 'self.validated_config' + will contain the validated configuration. If it fails, 'self.status' will be 'failed', and + 'self.msg' will describe the validation issues. """ - if not self.config: - self.msg = "config not available in playbook for validation" - self.status = "success" - return self - - provision_spec = { - "management_ip_address": {'type': 'str', 'required': True}, - "site_name_hierarchy": {'type': 'str', 'required': False}, - "managed_ap_locations": {'type': 'list', 'required': False, - 'elements': 'str'}, - "dynamic_interfaces": {'type': 'list', 'required': False, - 'elements': 'dict'}, - "provisioning": {'type': 'bool', 'required': False, "default": True} + temp_spec = { + 'provision_wired_device': { + 'type': 'list', + 'elements': 'dict', + 'device_ip': {'type': 'str'}, + 'site_name': {'type': 'str'}, + 'resync_retry_count': {'default': 200, 'type': 'int'}, + 'resync_retry_interval': {'default': 2, 'type': 'int'}, + "provisioning": {'type':'bool'} + }, + 'provision_wireless_device': { + 'type': 'list', + 'elements': 'dict', + 'device_ip': {'type': 'str'}, + 'site_name': {'type': 'str'}, + 'resync_retry_count': {'default': 200, 'type': 'int'}, + 'resync_retry_interval': {'default': 2, 'type': 'int'}, + "managed_ap_locations": {'type': 'list', 'element': 'str'}, + 'dynamic_interfaces': {'type': 'list', 'element': 'dict'} + } } - if state == "merged": - provision_spec["site_name_hierarchy"] = {'type': 'str', 'required': True} - # Validate provision params - valid_provision, invalid_params = validate_list_of_dicts( - self.config, provision_spec + # Validate device params + valid_temp, invalid_params = validate_list_of_dicts( + self.config, temp_spec ) + if invalid_params: - self.msg = "Invalid parameters in playbook: {0}".format( - "\n".join(invalid_params)) - self.log(str(self.msg), "ERROR") + self.msg = "Invalid parameters in playbook: {0}".format(invalid_params) + self.log(self.msg, "ERROR") self.status = "failed" + self.result['response'] = self.msg return self - self.validated_config = valid_provision - self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_provision)) - self.log(str(self.msg), "INFO") + self.validated_config = valid_temp + self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_temp)) + self.log(self.msg, "INFO") self.status = "success" + return self + + def get_dev_type(self): """ Fetches the type of device (wired/wireless) @@ -418,7 +450,7 @@ def get_serial_number(self): return serial_number - def get_task_status(self, task_id=None): + def get_tasks_status(self, task_id): """ Fetches the status of the task once any provision API is called @@ -435,33 +467,87 @@ def get_task_status(self, task_id=None): """ result = False - params = {"task_id": task_id} + params = {"id": task_id} while True: - response = self.dnac_apply['exec']( + get_tasks_response = self.dnac_apply['exec']( family="task", - function='get_task_by_id', + function='get_tasks_by_id', params=params, op_modifies=True ) - self.log("Response collected from 'get_task_by_id' API is {0}".format(str(response)), "DEBUG") - response = response.response - self.log("Task status for the task id {0} is {1}".format(str(task_id), str(response.get("progress"))), "INFO") - if response.get('isError') or re.search( - 'failed', response.get('progress'), flags=re.IGNORECASE - ): + self.log("Response collected from 'get_tasks_by_id' API is {0}".format(str(get_tasks_response)), "DEBUG") + get_tasks_response = get_tasks_response.get("response") + + get_task_details_response = self.dnac_apply['exec']( + family="task", + function='get_task_details_by_id', + params=params, + op_modifies=True + ) + self.log("Response collected from 'get_tasks_by_id' API is {0}".format(str(get_task_details_response)), "DEBUG") + get_task_details_response = get_task_details_response.get("response") + + self.log("Task status for the task id {0} is {1}".format(str(task_id), str(get_tasks_response.get("status"))), "INFO") + + if get_tasks_response.get("status") == "FAILURE": msg = 'Provision task with id {0} has not completed - Reason: {1}'.format( - task_id, response.get("failureReason")) + task_id, get_task_details_response.get("failureReason")) self.module.fail_json(msg=msg) return False - if response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and response.get("isError") is False: + if get_task_details_response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and get_tasks_response.get("status") == "SUCCESS": result = True break time.sleep(3) - self.result.update(dict(provision_task=response)) + self.result.update(dict(provision_task=get_tasks_response)) return result + + # def get_task_status(self, task_id=None): + # """ + # Fetches the status of the task once any provision API is called + + # Parameters: + # - self: The instance of the class containing the 'config' attribute + # to be validated. + # - task_id: Task_id of the provisioning task. + # Returns: + # The method returns the status of the task_id used to track provisioning. + # Returns True if task is not failed otheriwse returns False. + # Example: + # Post creation of the provision task, this method fetheches the task + # status. + + # """ + # result = False + # params = {"task_id": task_id} + # while True: + # response = self.dnac_apply['exec']( + # family="task", + # function='get_task_by_id', + # params=params, + # op_modifies=True + # ) + # self.log("Response collected from 'get_task_by_id' API is {0}".format(str(response)), "DEBUG") + # response = response.response + # self.log("Task status for the task id {0} is {1}".format(str(task_id), str(response.get("progress"))), "INFO") + # if response.get('isError') or re.search( + # 'failed', response.get('progress'), flags=re.IGNORECASE + # ): + # msg = 'Provision task with id {0} has not completed - Reason: {1}'.format( + # task_id, response.get("failureReason")) + # self.module.fail_json(msg=msg) + # return False + + # if response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and response.get("isError") is False: + # result = True + # break + + # time.sleep(3) + # self.result.update(dict(provision_task=response)) + # return result + def get_execution_status_site(self, execution_id=None): """ Fetches the status of the BAPI once site assignment API is called @@ -549,84 +635,105 @@ def get_execution_status_wireless(self, execution_id=None): self.result.update(dict(assignment_task=response)) return result - def get_site_type(self, site_name_hierarchy=None): + def get_site_type(self, site_name): """ - Fetches the type of site - + Get the type of a site in Cisco Catalyst Center. Parameters: - - self: The instance of the class containing the 'config' attribute - to be validated. - - site_name_hierarchy: Name of the site collected from the input. + self (object): An instance of a class used for interacting with Cisco Catalyst Center. + site_name (str): The name of the site for which to retrieve the type. Returns: - - site_type: A string indicating the type of the site (area/building/floor). - Example: - Post creation of the validated input, this method gets the - type of the site. + site_type (str or None): The type of the specified site, or None if the site is not found. + Description: + This function queries Cisco Catalyst Center to retrieve the type of a specified site. It uses the + get_site API with the provided site name, extracts the site type from the response, and returns it. + If the specified site is not found, the function returns None, and an appropriate log message is generated. """ try: - response = self.dnac_apply['exec']( - family="sites", - function='get_site', - params={"name": site_name_hierarchy}, - op_modifies=True - ) - except Exception: - self.log("Exception occurred as \ - site '{0}' was not found".format(site_name_hierarchy), "CRITICAL") - self.module.fail_json(msg="Site not found", response=[]) - - if response: - self.log("Received site details\ - for '{0}': {1}".format(site_name_hierarchy, str(response)), "DEBUG") - site = response.get("response") - site_additional_info = site[0].get("additionalInfo") - for item in site_additional_info: - if item["nameSpace"] == "Location": - site_type = item.get("attributes").get("type") - self.log("Site type for site name '{1}' : {0}".format(site_type, site_name_hierarchy), "INFO") + if self.dnac_version <= self.version_2_3_5_3: + site_type = None + response = self.dnac_apply['exec']( + family="sites", + function='get_site', + params={"name": site_name}, + ) + + if not response: + self.msg = "Site '{0}' not found".format(site_name) + self.log(self.msg, "INFO") + return site_type + + self.log("Received API response from 'get_site': {0}".format(str(response)), "DEBUG") + site = response.get("response") + site_additional_info = site[0].get("additionalInfo") + + for item in site_additional_info: + if item["nameSpace"] == "Location": + site_type = item.get("attributes").get("type") + else: + site_type = None + response = self.get_sites(site_name) + + if not response: + self.msg = "Site '{0}' not found".format(site_name) + self.log(self.msg, "INFO") + return site_type + + self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") + site = response.get("response") + site_type = site[0].get("type") + + except Exception as e: + self.msg = "Error while fetching the site '{0}' and the specified site was not found in Cisco Catalyst Center.".format(site_name) + self.log(self.msg, "ERROR") + self.module.fail_json(msg=self.msg, response=[self.msg]) return site_type - def get_site_details(self, site_name_hierarchy=None): + def get_site_details(self, site_name): """ - Fetches the id and existance of the site - Parameters: - - self: The instance of the class containing the 'config' attribute - to be validated. - - site_name_hierarchy: Name of the site collected from the input. + self (object): An instance of a class used for interacting with Cisco Catalyst Center. Returns: - - site_id: A string indicating the id of the site. - - site_exits: A boolean value indicating the existance of the site. - Example: - Post creation of the validated input, this method gets the - id of the site. + tuple: A tuple containing two values: + - site_exists (bool): A boolean indicating whether the site exists (True) or not (False). + - site_id (str or None): The ID of the site if it exists, or None if the site is not found. + Description: + This method checks the existence of a site in the Catalyst Center. If the site is found,it sets 'site_exists' to True, + retrieves the site's ID, and returns both values in a tuple. If the site does not exist, 'site_exists' is set + to False, and 'site_id' is None. If an exception occurs during the site lookup, an exception is raised. """ site_exists = False site_id = None + response = None + try: - response = self.dnac_apply['exec']( - family="sites", - function='get_site', - params={"name": site_name_hierarchy}, - op_modifies=True - ) - except Exception: - self.log("Exception occurred as \ - site '{0}' was not found".format(self.want.get("site_name")), "CRITICAL") - self.module.fail_json(msg="Site not found", response=[]) - - if response: - self.log("Received site details\ - for '{0}': {1}".format(site_name_hierarchy, str(response)), "DEBUG") - site = response.get("response") - site_additional_info = site[0].get("additionalInfo") - if len(site) == 1: + if self.dnac_version <= self.version_2_3_5_3: + response = self.dnac._exec( + family="sites", + function='get_site', + op_modifies=True, + params={"name": site_name}, + ) + if response: + self.log("Received API response from 'get_site': {0}".format(str(response)), "DEBUG") + site = response.get("response") + site_id = site[0].get("id") + site_exists = True + else: + response = self.get_sites(site_name) + self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") + site = response.get("response") site_id = site[0].get("id") site_exists = True - self.log("Site Name: {1}, Site ID: {0}".format(site_id, site_name_hierarchy), "INFO") + + except Exception as e: + 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) @@ -719,7 +826,7 @@ def get_wired_params(self): site_name = self.validated_config[0].get("site_name_hierarchy") - (site_exits, site_id) = self.get_site_details(site_name_hierarchy=site_name) + (site_exits, site_id) = self.get_site_details(site_name) if site_exits is False: msg = "Site {0} doesn't exist".format(site_name) @@ -895,95 +1002,109 @@ class instance for further use. device_type = self.want.get("device_type") if device_type == "wired": - try: - status_response = self.dnac_apply['exec']( - family="sda", - function="get_provisioned_wired_device", - op_modifies=True, - params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] - }, - ) - except Exception: - status_response = {} - self.log("Wired device's status Response collected from 'get_provisioned_wired_device' API is:{0}".format(str(status_response)), "DEBUG") - status = status_response.get("status") - self.log("The provisioned status of the wired device is {0}".format(status), "INFO") - - if status == "success": + if self.dnac_version >= self.version_2_3_5_3: try: - response = self.dnac_apply['exec']( + status_response = self.dnac_apply['exec']( family="sda", - function="re_provision_wired_device", + function="get_provisioned_wired_device", op_modifies=True, - params=self.want["prov_params"], + params={ + "device_management_ip_address": self.validated_config[0]["management_ip_address"] + }, ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") - return self - - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - else: - if self.validated_config[0].get("provisioning") is True: + except Exception: + status_response = {} + self.log("Wired device's status Response collected from 'get_provisioned_wired_device' API is:{0}".format(str(status_response)), "DEBUG") + status = status_response.get("status") + self.log("The provisioned status of the wired device is {0}".format(status), "INFO") + + if status == "success": try: response = self.dnac_apply['exec']( family="sda", - function="provision_wired_device", + function="re_provision_wired_device", op_modifies=True, params=self.want["prov_params"], ) - self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") - except Exception as e: - self.msg = "Error in provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - - else: - uuid = self.get_device_id() - if self.is_device_assigned_to_site(uuid) is True: - self.result["changed"] = False - self.result['msg'] = "Device is already assigned to the desired site" - self.result['diff'] = self.want - self.result['response'] = self.want.get("prov_params").get("site_id") - self.log(self.result['msg'], "INFO") - return self - - try: - response = self.dnac_apply['exec']( - family="sites", - function="assign_devices_to_site", - op_modifies=True, - params={ - "site_id": self.want.get("prov_params").get("site_id"), - "payload": self.want.get("prov_params") - }, - ) - self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") - execution_id = response.get("executionId") - assignment_info = self.get_execution_status_site(execution_id=execution_id) + self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) self.result["changed"] = True - self.result['msg'] = "Site assignment done successfully" + self.result['msg'] = "Re-Provision done Successfully" self.result['diff'] = self.validated_config - self.result['response'] = execution_id + self.result['response'] = task_id self.log(self.result['msg'], "INFO") return self + except Exception as e: - self.msg = "Error in site assignment due to {0}".format(str(e)) + self.msg = "Error in re-provisioning due to {0}".format(str(e)) self.log(self.msg, "ERROR") self.status = "failed" return self - + else: + if self.validated_config[0].get("provisioning") is True: + try: + response = self.dnac_apply['exec']( + family="sda", + function="provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") + except Exception as e: + self.msg = "Error in provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + + else: + uuid = self.get_device_id() + if self.is_device_assigned_to_site(uuid) is True: + self.result["changed"] = False + self.result['msg'] = "Device is already assigned to the desired site" + self.result['diff'] = self.want + self.result['response'] = self.want.get("prov_params").get("site_id") + self.log(self.result['msg'], "INFO") + return self + + try: + response = self.dnac_apply['exec']( + family="sites", + function="assign_devices_to_site", + op_modifies=True, + params={ + "site_id": self.want.get("prov_params").get("site_id"), + "payload": self.want.get("prov_params") + }, + ) + self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") + execution_id = response.get("executionId") + assignment_info = self.get_execution_status_site(execution_id=execution_id) + self.result["changed"] = True + self.result['msg'] = "Site assignment done successfully" + self.result['diff'] = self.validated_config + self.result['response'] = execution_id + self.log(self.result['msg'], "INFO") + return self + except Exception as e: + self.msg = "Error in site assignment due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + else: + device_id = self.get_device_id() + assign_network_device_to_site = { + 'deviceIds': [device_id], + 'siteId': self.want.get("prov_params").get("site_id"), + } + + response = self.dnac._exec( + family="site_design", + function='assign_network_devices_to_a_site', + op_modifies=True, + params=assign_network_device_to_site, + ) + pass elif device_type == "wireless": try: response = self.dnac_apply['exec']( From 7c658d27a559ffb2c6fbbe7044ea73da53944cfa Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 21 Sep 2024 13:31:07 +0530 Subject: [PATCH 02/28] started coding --- playbooks/dnac.log | 134 +++++ playbooks/provision_workflow_manager.yml | 30 ++ plugins/modules/provision_workflow_manager.py | 481 +++++++----------- 3 files changed, 344 insertions(+), 301 deletions(-) create mode 100644 playbooks/provision_workflow_manager.yml diff --git a/playbooks/dnac.log b/playbooks/dnac.log index e69de29bb2..b7a4c6b718 100644 --- a/playbooks/dnac.log +++ b/playbooks/dnac.log @@ -0,0 +1,134 @@ +09-21-2024 12:43:44 DEBUG Provision: __init__: 88: Logging configured and initiated + +09-21-2024 12:43:44 DEBUG Provision: __init__: 94: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.7.6', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} + +09-21-2024 12:43:44 INFO Provision: validate_input: 308: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.1.2.2', 'site_name_hierarchy': 'Global/USA/San Jose/BLDG23', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.1.2.4', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/USA/SAN JOSE/BLD23', 'managed_ap_locations': ['Global/Chennai/LTTS/FLOOR1'], 'dynamic_interfaces': None, 'provisioning': True}] + +09-21-2024 12:43:44 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.1.2.2', 'site_name_hierarchy': 'Global/USA/San Jose/BLDG23', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.1.2.4', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/USA/SAN JOSE/BLD23', 'managed_ap_locations': ['Global/Chennai/LTTS/FLOOR1'], 'dynamic_interfaces': None, 'provisioning': True}] + +09-21-2024 12:43:45 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716246, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:43:45 INFO Provision: get_dev_type: 348: The device type is wired + +09-21-2024 12:43:46 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} + +09-21-2024 12:43:46 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 + +09-21-2024 12:43:46 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} + +09-21-2024 12:43:46 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:43:46 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:43:46 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:43:46 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success + +09-21-2024 12:43:58 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136d-f85b-7dc0-85b5-5647946c58f9', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136d-f85b-7dc0-85b5-5647946c58f9', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/cc69d0de-336b-4e6b-a67f-4f6bcba2923f', 'executionId': 'cc69d0de-336b-4e6b-a67f-4f6bcba2923f'} + +09-21-2024 12:43:58 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902832980, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902827099, 'data': 'workflow_id=26a7a326-32e9-4991-b981-14c46f6dda77;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902832980, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136d-f85b-7dc0-85b5-5647946c58f9'}, 'version': '1.0'} + +09-21-2024 12:43:58 INFO Provision: get_task_status: 448: Task status for the task id 0192136d-f85b-7dc0-85b5-5647946c58f9 is TASK_MODIFY_PUT + +09-21-2024 12:43:58 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully + +09-21-2024 12:43:58 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:43:58 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} + +09-21-2024 12:43:58 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716259, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:43:58 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a + +09-21-2024 12:44:00 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:44:00 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success + +09-21-2024 12:44:00 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned + +09-21-2024 12:44:00 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:00 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716262, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:44:00 INFO Provision: get_dev_type: 348: The device type is wired + +09-21-2024 12:44:01 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} + +09-21-2024 12:44:01 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 + +09-21-2024 12:44:01 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} + +09-21-2024 12:44:01 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:01 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:05 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:44:05 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success + +09-21-2024 12:44:16 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136e-43c3-7e70-9a0c-bac19f9d91a5', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136e-43c3-7e70-9a0c-bac19f9d91a5', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/5fb584f9-db76-488b-968b-7fda544fb170', 'executionId': '5fb584f9-db76-488b-968b-7fda544fb170'} + +09-21-2024 12:44:17 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902852234, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902846403, 'data': 'workflow_id=0553213d-f20b-49b6-bc77-c996bcbfd68d;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902852234, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136e-43c3-7e70-9a0c-bac19f9d91a5'}, 'version': '1.0'} + +09-21-2024 12:44:17 INFO Provision: get_task_status: 448: Task status for the task id 0192136e-43c3-7e70-9a0c-bac19f9d91a5 is TASK_MODIFY_PUT + +09-21-2024 12:44:17 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully + +09-21-2024 12:44:17 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:17 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} + +09-21-2024 12:44:17 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716278, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:44:17 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a + +09-21-2024 12:44:18 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:44:18 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success + +09-21-2024 12:44:18 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned + +09-21-2024 12:44:18 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:18 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716279, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:44:18 INFO Provision: get_dev_type: 348: The device type is wired + +09-21-2024 12:44:19 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} + +09-21-2024 12:44:19 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 + +09-21-2024 12:44:19 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} + +09-21-2024 12:44:19 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:19 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:19 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:44:19 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success + +09-21-2024 12:44:30 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136e-7a22-7264-aa11-0f5bfc2d3680', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136e-7a22-7264-aa11-0f5bfc2d3680', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/71c21f30-1b29-4acd-8425-8d0dc8d0b98c', 'executionId': '71c21f30-1b29-4acd-8425-8d0dc8d0b98c'} + +09-21-2024 12:44:31 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902865832, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902860322, 'data': 'workflow_id=c37f2f7d-bd10-4633-8bad-bac68b86d34a;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902865832, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136e-7a22-7264-aa11-0f5bfc2d3680'}, 'version': '1.0'} + +09-21-2024 12:44:31 INFO Provision: get_task_status: 448: Task status for the task id 0192136e-7a22-7264-aa11-0f5bfc2d3680 is TASK_MODIFY_PUT + +09-21-2024 12:44:31 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully + +09-21-2024 12:44:31 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + +09-21-2024 12:44:31 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} + +09-21-2024 12:44:32 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716293, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} + +09-21-2024 12:44:32 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a + +09-21-2024 12:44:32 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-21-2024 12:44:32 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success + +09-21-2024 12:44:32 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned + +09-21-2024 12:44:32 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison + diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml new file mode 100644 index 0000000000..ede63fb471 --- /dev/null +++ b/playbooks/provision_workflow_manager.yml @@ -0,0 +1,30 @@ +--- +- name: Configure device credentials on Cisco DNA Center + hosts: localhost + connection: local + gather_facts: no + vars_files: + - "credentials.yml" + tasks: + - name: Add/Update/Resync/Delete the devices in Cisco DNA Center Inventory. + cisco.dnac.provision_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: DEBUG + dnac_log: true + config_verify: true + state: merged + config: + - site_name_hierarchy: Global/USA/San Jose/BLDG23 + management_ip_address: 204.1.2.2 + - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 + management_ip_address: 204.1.2.4 + - site_name_hierarchy: Global/USA/SAN JOSE/BLD23 + management_ip_address: 204.192.3.40 + managed_ap_locations: + - Global/Chennai/LTTS/FLOOR1 \ No newline at end of file diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 56143e3951..141bcfa98e 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -145,17 +145,8 @@ dnac_log: True state: merged config: - - provision_wired_device: - - device_ip: "1.1.1.1" - site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: True - - device_ip: "2.2.2.2" - site_name: "Global/USA/San Francisco/BGL_18/floor_test" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: True + - site_name_hierarchy: Global/USA/San Francisco/BGL_18 + management_ip_address: 204.192.3.40 - name: Assign a wired device to a site cisco.dnac.provision_workflow_manager: @@ -169,19 +160,8 @@ dnac_log: True state: merged config: - # - site_name_hierarchy: Global/USA/San Francisco/BGL_18 - # management_ip_address: 204.192.3.40 - # provisioning: False - - provision_wired_device: - - device_ip: "1.1.1.1" - site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: False - - device_ip: "2.2.2.2" - site_name: "Global/USA/San Francisco/BGL_18/floor_test" - resync_retry_count: 200 - resync_retry_interval: 2 + - site_name_hierarchy: Global/USA/San Francisco/BGL_18 + management_ip_address: 204.192.3.40 provisioning: False - name: Provision a wireless device to a site @@ -273,74 +253,62 @@ class Provision(DnacBase): """ def __init__(self, module): super().__init__(module) - self.supported_states = ["merged", "deleted"] - self.payload = module.params - self.dnac_version = int(self.payload.get("dnac_version").replace(".", "")) - self.version_2_3_5_3, self.version_2_3_7_6, self.version_2_2_3_3 = 2353, 2376, 2233 + def validate_input(self, state=None): - def validate_input(self): """ Validate the fields provided in the playbook. Checks the configuration provided in the playbook against a predefined specification to ensure it adheres to the expected structure and data types. - Parameters: + Args: self: The instance of the class containing the 'config' attribute to be validated. Returns: The method returns an instance of the class with updated attributes: - self.msg: A message describing the validation result. - self.status: The status of the validation (either 'success' or 'failed'). - - self.validated_config: If successful, a validated version of the 'config' parameter. + - self.validated_config: If successful, a validated version of the + 'config' parameter. Example: To use this method, create an instance of the class and call 'validate_input' on it. - If the validation succeeds, 'self.status' will be 'success' and 'self.validated_config' - will contain the validated configuration. If it fails, 'self.status' will be 'failed', and - 'self.msg' will describe the validation issues. + If the validation succeeds, 'self.status' will be 'success' and + 'self.validated_config' will contain the validated configuration. If it fails, + 'self.status' will be 'failed', and 'self.msg' will describe the validation issues. """ - temp_spec = { - 'provision_wired_device': { - 'type': 'list', - 'elements': 'dict', - 'device_ip': {'type': 'str'}, - 'site_name': {'type': 'str'}, - 'resync_retry_count': {'default': 200, 'type': 'int'}, - 'resync_retry_interval': {'default': 2, 'type': 'int'}, - "provisioning": {'type':'bool'} - }, - 'provision_wireless_device': { - 'type': 'list', - 'elements': 'dict', - 'device_ip': {'type': 'str'}, - 'site_name': {'type': 'str'}, - 'resync_retry_count': {'default': 200, 'type': 'int'}, - 'resync_retry_interval': {'default': 2, 'type': 'int'}, - "managed_ap_locations": {'type': 'list', 'element': 'str'}, - 'dynamic_interfaces': {'type': 'list', 'element': 'dict'} - } + if not self.config: + self.msg = "config not available in playbook for validation" + self.status = "success" + return self + + provision_spec = { + "management_ip_address": {'type': 'str', 'required': True}, + "site_name_hierarchy": {'type': 'str', 'required': False}, + "managed_ap_locations": {'type': 'list', 'required': False, + 'elements': 'str'}, + "dynamic_interfaces": {'type': 'list', 'required': False, + 'elements': 'dict'}, + "provisioning": {'type': 'bool', 'required': False, "default": True} } + if state == "merged": + provision_spec["site_name_hierarchy"] = {'type': 'str', 'required': True} - # Validate device params - valid_temp, invalid_params = validate_list_of_dicts( - self.config, temp_spec + # Validate provision params + valid_provision, invalid_params = validate_list_of_dicts( + self.config, provision_spec ) - if invalid_params: - self.msg = "Invalid parameters in playbook: {0}".format(invalid_params) - self.log(self.msg, "ERROR") + self.msg = "Invalid parameters in playbook: {0}".format( + "\n".join(invalid_params)) + self.log(str(self.msg), "ERROR") self.status = "failed" - self.result['response'] = self.msg return self - self.validated_config = valid_temp - self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_temp)) - self.log(self.msg, "INFO") + self.validated_config = valid_provision + self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_provision)) + self.log(str(self.msg), "INFO") self.status = "success" - return self - - def get_dev_type(self): """ Fetches the type of device (wired/wireless) @@ -450,7 +418,7 @@ def get_serial_number(self): return serial_number - def get_tasks_status(self, task_id): + def get_task_status(self, task_id=None): """ Fetches the status of the task once any provision API is called @@ -467,87 +435,33 @@ def get_tasks_status(self, task_id): """ result = False - params = {"id": task_id} + params = {"task_id": task_id} while True: - get_tasks_response = self.dnac_apply['exec']( - family="task", - function='get_tasks_by_id', - params=params, - op_modifies=True - ) - self.log("Response collected from 'get_tasks_by_id' API is {0}".format(str(get_tasks_response)), "DEBUG") - get_tasks_response = get_tasks_response.get("response") - - get_task_details_response = self.dnac_apply['exec']( + response = self.dnac_apply['exec']( family="task", - function='get_task_details_by_id', + function='get_task_by_id', params=params, op_modifies=True ) - self.log("Response collected from 'get_tasks_by_id' API is {0}".format(str(get_task_details_response)), "DEBUG") - get_task_details_response = get_task_details_response.get("response") - - self.log("Task status for the task id {0} is {1}".format(str(task_id), str(get_tasks_response.get("status"))), "INFO") - - if get_tasks_response.get("status") == "FAILURE": + self.log("Response collected from 'get_task_by_id' API is {0}".format(str(response)), "DEBUG") + response = response.response + self.log("Task status for the task id {0} is {1}".format(str(task_id), str(response.get("progress"))), "INFO") + if response.get('isError') or re.search( + 'failed', response.get('progress'), flags=re.IGNORECASE + ): msg = 'Provision task with id {0} has not completed - Reason: {1}'.format( - task_id, get_task_details_response.get("failureReason")) + task_id, response.get("failureReason")) self.module.fail_json(msg=msg) return False - if get_task_details_response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and get_tasks_response.get("status") == "SUCCESS": + if response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and response.get("isError") is False: result = True break time.sleep(3) - self.result.update(dict(provision_task=get_tasks_response)) + self.result.update(dict(provision_task=response)) return result - - # def get_task_status(self, task_id=None): - # """ - # Fetches the status of the task once any provision API is called - - # Parameters: - # - self: The instance of the class containing the 'config' attribute - # to be validated. - # - task_id: Task_id of the provisioning task. - # Returns: - # The method returns the status of the task_id used to track provisioning. - # Returns True if task is not failed otheriwse returns False. - # Example: - # Post creation of the provision task, this method fetheches the task - # status. - - # """ - # result = False - # params = {"task_id": task_id} - # while True: - # response = self.dnac_apply['exec']( - # family="task", - # function='get_task_by_id', - # params=params, - # op_modifies=True - # ) - # self.log("Response collected from 'get_task_by_id' API is {0}".format(str(response)), "DEBUG") - # response = response.response - # self.log("Task status for the task id {0} is {1}".format(str(task_id), str(response.get("progress"))), "INFO") - # if response.get('isError') or re.search( - # 'failed', response.get('progress'), flags=re.IGNORECASE - # ): - # msg = 'Provision task with id {0} has not completed - Reason: {1}'.format( - # task_id, response.get("failureReason")) - # self.module.fail_json(msg=msg) - # return False - - # if response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and response.get("isError") is False: - # result = True - # break - - # time.sleep(3) - # self.result.update(dict(provision_task=response)) - # return result - def get_execution_status_site(self, execution_id=None): """ Fetches the status of the BAPI once site assignment API is called @@ -635,105 +549,84 @@ def get_execution_status_wireless(self, execution_id=None): self.result.update(dict(assignment_task=response)) return result - def get_site_type(self, site_name): + def get_site_type(self, site_name_hierarchy=None): """ - Get the type of a site in Cisco Catalyst Center. + Fetches the type of site + Parameters: - self (object): An instance of a class used for interacting with Cisco Catalyst Center. - site_name (str): The name of the site for which to retrieve the type. + - self: The instance of the class containing the 'config' attribute + to be validated. + - site_name_hierarchy: Name of the site collected from the input. Returns: - site_type (str or None): The type of the specified site, or None if the site is not found. - Description: - This function queries Cisco Catalyst Center to retrieve the type of a specified site. It uses the - get_site API with the provided site name, extracts the site type from the response, and returns it. - If the specified site is not found, the function returns None, and an appropriate log message is generated. + - site_type: A string indicating the type of the site (area/building/floor). + Example: + Post creation of the validated input, this method gets the + type of the site. """ try: - if self.dnac_version <= self.version_2_3_5_3: - site_type = None - response = self.dnac_apply['exec']( - family="sites", - function='get_site', - params={"name": site_name}, - ) - - if not response: - self.msg = "Site '{0}' not found".format(site_name) - self.log(self.msg, "INFO") - return site_type - - self.log("Received API response from 'get_site': {0}".format(str(response)), "DEBUG") - site = response.get("response") - site_additional_info = site[0].get("additionalInfo") - - for item in site_additional_info: - if item["nameSpace"] == "Location": - site_type = item.get("attributes").get("type") - else: - site_type = None - response = self.get_sites(site_name) - - if not response: - self.msg = "Site '{0}' not found".format(site_name) - self.log(self.msg, "INFO") - return site_type - - self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") - site = response.get("response") - site_type = site[0].get("type") - - except Exception as e: - self.msg = "Error while fetching the site '{0}' and the specified site was not found in Cisco Catalyst Center.".format(site_name) - self.log(self.msg, "ERROR") - self.module.fail_json(msg=self.msg, response=[self.msg]) + response = self.dnac_apply['exec']( + family="sites", + function='get_site', + params={"name": site_name_hierarchy}, + op_modifies=True + ) + except Exception: + self.log("Exception occurred as \ + site '{0}' was not found".format(site_name_hierarchy), "CRITICAL") + self.module.fail_json(msg="Site not found", response=[]) + + if response: + self.log("Received site details\ + for '{0}': {1}".format(site_name_hierarchy, str(response)), "DEBUG") + site = response.get("response") + site_additional_info = site[0].get("additionalInfo") + for item in site_additional_info: + if item["nameSpace"] == "Location": + site_type = item.get("attributes").get("type") + self.log("Site type for site name '{1}' : {0}".format(site_type, site_name_hierarchy), "INFO") return site_type - def get_site_details(self, site_name): + def get_site_details(self, site_name_hierarchy=None): """ + Fetches the id and existance of the site + Parameters: - self (object): An instance of a class used for interacting with Cisco Catalyst Center. + - self: The instance of the class containing the 'config' attribute + to be validated. + - site_name_hierarchy: Name of the site collected from the input. Returns: - tuple: A tuple containing two values: - - site_exists (bool): A boolean indicating whether the site exists (True) or not (False). - - site_id (str or None): The ID of the site if it exists, or None if the site is not found. - Description: - This method checks the existence of a site in the Catalyst Center. If the site is found,it sets 'site_exists' to True, - retrieves the site's ID, and returns both values in a tuple. If the site does not exist, 'site_exists' is set - to False, and 'site_id' is None. If an exception occurs during the site lookup, an exception is raised. + - site_id: A string indicating the id of the site. + - site_exits: A boolean value indicating the existance of the site. + Example: + Post creation of the validated input, this method gets the + id of the site. """ site_exists = False site_id = None - response = None - try: - if self.dnac_version <= self.version_2_3_5_3: - response = self.dnac._exec( - family="sites", - function='get_site', - op_modifies=True, - params={"name": site_name}, - ) - if response: - self.log("Received API response from 'get_site': {0}".format(str(response)), "DEBUG") - site = response.get("response") - site_id = site[0].get("id") - site_exists = True - else: - response = self.get_sites(site_name) - self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") - site = response.get("response") + response = self.dnac_apply['exec']( + family="sites", + function='get_site', + params={"name": site_name_hierarchy}, + op_modifies=True + ) + except Exception: + self.log("Exception occurred as \ + site '{0}' was not found".format(self.want.get("site_name")), "CRITICAL") + self.module.fail_json(msg="Site not found", response=[]) + + if response: + self.log("Received site details\ + for '{0}': {1}".format(site_name_hierarchy, str(response)), "DEBUG") + site = response.get("response") + site_additional_info = site[0].get("additionalInfo") + if len(site) == 1: site_id = site[0].get("id") site_exists = True - - except Exception as e: - 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() + self.log("Site Name: {1}, Site ID: {0}".format(site_id, site_name_hierarchy), "INFO") return (site_exists, site_id) @@ -826,7 +719,7 @@ def get_wired_params(self): site_name = self.validated_config[0].get("site_name_hierarchy") - (site_exits, site_id) = self.get_site_details(site_name) + (site_exits, site_id) = self.get_site_details(site_name_hierarchy=site_name) if site_exits is False: msg = "Site {0} doesn't exist".format(site_name) @@ -1002,109 +895,95 @@ class instance for further use. device_type = self.want.get("device_type") if device_type == "wired": - if self.dnac_version >= self.version_2_3_5_3: + try: + status_response = self.dnac_apply['exec']( + family="sda", + function="get_provisioned_wired_device", + op_modifies=True, + params={ + "device_management_ip_address": self.validated_config[0]["management_ip_address"] + }, + ) + except Exception: + status_response = {} + self.log("Wired device's status Response collected from 'get_provisioned_wired_device' API is:{0}".format(str(status_response)), "DEBUG") + status = status_response.get("status") + self.log("The provisioned status of the wired device is {0}".format(status), "INFO") + + if status == "success": try: - status_response = self.dnac_apply['exec']( + response = self.dnac_apply['exec']( family="sda", - function="get_provisioned_wired_device", + function="re_provision_wired_device", op_modifies=True, - params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] - }, + params=self.want["prov_params"], ) - except Exception: - status_response = {} - self.log("Wired device's status Response collected from 'get_provisioned_wired_device' API is:{0}".format(str(status_response)), "DEBUG") - status = status_response.get("status") - self.log("The provisioned status of the wired device is {0}".format(status), "INFO") - - if status == "success": + self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Re-Provision done Successfully" + self.result['diff'] = self.validated_config + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + return self + + except Exception as e: + self.msg = "Error in re-provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + else: + if self.validated_config[0].get("provisioning") is True: try: response = self.dnac_apply['exec']( family="sda", - function="re_provision_wired_device", + function="provision_wired_device", op_modifies=True, params=self.want["prov_params"], ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) + self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") + except Exception as e: + self.msg = "Error in provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + + else: + uuid = self.get_device_id() + if self.is_device_assigned_to_site(uuid) is True: + self.result["changed"] = False + self.result['msg'] = "Device is already assigned to the desired site" + self.result['diff'] = self.want + self.result['response'] = self.want.get("prov_params").get("site_id") + self.log(self.result['msg'], "INFO") + return self + + try: + response = self.dnac_apply['exec']( + family="sites", + function="assign_devices_to_site", + op_modifies=True, + params={ + "site_id": self.want.get("prov_params").get("site_id"), + "payload": self.want.get("prov_params") + }, + ) + self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") + execution_id = response.get("executionId") + assignment_info = self.get_execution_status_site(execution_id=execution_id) self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" + self.result['msg'] = "Site assignment done successfully" self.result['diff'] = self.validated_config - self.result['response'] = task_id + self.result['response'] = execution_id self.log(self.result['msg'], "INFO") return self - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) + self.msg = "Error in site assignment due to {0}".format(str(e)) self.log(self.msg, "ERROR") self.status = "failed" return self - else: - if self.validated_config[0].get("provisioning") is True: - try: - response = self.dnac_apply['exec']( - family="sda", - function="provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") - except Exception as e: - self.msg = "Error in provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - - else: - uuid = self.get_device_id() - if self.is_device_assigned_to_site(uuid) is True: - self.result["changed"] = False - self.result['msg'] = "Device is already assigned to the desired site" - self.result['diff'] = self.want - self.result['response'] = self.want.get("prov_params").get("site_id") - self.log(self.result['msg'], "INFO") - return self - - try: - response = self.dnac_apply['exec']( - family="sites", - function="assign_devices_to_site", - op_modifies=True, - params={ - "site_id": self.want.get("prov_params").get("site_id"), - "payload": self.want.get("prov_params") - }, - ) - self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") - execution_id = response.get("executionId") - assignment_info = self.get_execution_status_site(execution_id=execution_id) - self.result["changed"] = True - self.result['msg'] = "Site assignment done successfully" - self.result['diff'] = self.validated_config - self.result['response'] = execution_id - self.log(self.result['msg'], "INFO") - return self - except Exception as e: - self.msg = "Error in site assignment due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - else: - device_id = self.get_device_id() - assign_network_device_to_site = { - 'deviceIds': [device_id], - 'siteId': self.want.get("prov_params").get("site_id"), - } - - response = self.dnac._exec( - family="site_design", - function='assign_network_devices_to_a_site', - op_modifies=True, - params=assign_network_device_to_site, - ) - pass + elif device_type == "wireless": try: response = self.dnac_apply['exec']( @@ -1357,4 +1236,4 @@ def main(): if __name__ == '__main__': - main() + main() \ No newline at end of file From a4deacef918d146ef63c7c7204d2fc28cfa23e9a Mon Sep 17 00:00:00 2001 From: skesali Date: Tue, 24 Sep 2024 18:19:13 +0530 Subject: [PATCH 03/28] API changes for device_configs_backup_workflow_manager module --- .../device_configs_backup_workflow_manager.py | 225 ++++++++++++------ 1 file changed, 150 insertions(+), 75 deletions(-) diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index 79c4f71d6a..018aa2b7a0 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -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""" --- @@ -394,6 +394,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,112 +519,182 @@ 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) + 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): + # """ + # Retrieves device IDs from a specified site in Cisco Catalyst Center. + # Parameters: + # site_name (str): The name of the site. + # site_id (str): The ID of the site. + # Returns: + # dict: A dictionary mapping management IP addresses to instance UUIDs of reachable devices that are not Unified APs. + # 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. + # """ + # 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") + + # # 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") + + # # 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") + + # 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") + + # # 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") + + # return mgmt_ip_to_instance_id_map + def get_device_ids_from_site(self, site_name, site_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") + # Determine the API call based on the DNAC version + 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) return mgmt_ip_to_instance_id_map + def get_device_list_params(self, config): """ Generates a dictionary of device parameters for querying Cisco Catalyst Center. From c27b21c409c82cd4eafc04e291878194c4121d27 Mon Sep 17 00:00:00 2001 From: skesali Date: Tue, 24 Sep 2024 18:37:46 +0530 Subject: [PATCH 04/28] API changes for device_configs_backup_workflow_manager module --- .../device_configs_backup_workflow_manager.py | 90 ++----------------- 1 file changed, 8 insertions(+), 82 deletions(-) diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index 018aa2b7a0..670ffb4d52 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -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,17 +114,19 @@ - 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 @@ -131,8 +134,9 @@ 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""" @@ -538,81 +542,6 @@ def validate_site_exists(self, site_name): return (site_exists, site_id) - # def get_device_ids_from_site(self, site_name, site_id): - # """ - # Retrieves device IDs from a specified site in Cisco Catalyst Center. - # Parameters: - # site_name (str): The name of the site. - # site_id (str): The ID of the site. - # Returns: - # dict: A dictionary mapping management IP addresses to instance UUIDs of reachable devices that are not Unified APs. - # 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. - # """ - # 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") - - # # 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") - - # # 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") - - # 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") - - # # 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") - - # return mgmt_ip_to_instance_id_map - def get_device_ids_from_site(self, site_name, site_id): """ Retrieves the management IP addresses and their corresponding instance UUIDs of devices associated with a specific site in Cisco Catalyst Center. @@ -636,9 +565,7 @@ def get_device_ids_from_site(self, site_name, site_id): "site_id": site_id, } - # Attempt to retrieve device information associated with the site try: - # Determine the API call based on the DNAC version if self.dnac_version <= self.version_2_3_5_3: response = self.dnac._exec( family="sites", @@ -694,7 +621,6 @@ def get_device_ids_from_site(self, site_name, site_id): return mgmt_ip_to_instance_id_map - def get_device_list_params(self, config): """ Generates a dictionary of device parameters for querying Cisco Catalyst Center. From 772b61b891a3af92fa8d92fc76b05c17da284616 Mon Sep 17 00:00:00 2001 From: skesali Date: Wed, 25 Sep 2024 09:21:32 +0530 Subject: [PATCH 05/28] new changes for network_config --- .../device_configs_backup_workflow_manager.py | 235 +++++++++--------- 1 file changed, 115 insertions(+), 120 deletions(-) diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index 670ffb4d52..a8c94847b5 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -398,11 +398,6 @@ 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): """ @@ -506,120 +501,120 @@ def update_result(self, status, changed, msg, log_level, data=None): return self - def validate_site_exists(self, site_name): - """ - Checks the existence of a site in Cisco Catalyst Center. - Parameters: - site_name (str): The name of the site to be checked. - Returns: - tuple: A tuple containing two values: - - site_exists (bool): Indicates whether the site exists (True) or not (False). - - site_id (str or None): The ID of the site if it exists, or None if the site is not found. - Description: - This method queries Cisco Catalyst Center to determine if a site with the provided name exists. - If the site is found, it sets "site_exists" to True and retrieves the sites ID. - If the site does not exist, "site_exists" is set to False, and "site_id" is None. - If an exception occurs during the site lookup, an error message is logged, and the module fails. - """ - site_exists = False - site_id = None - response = None - - try: - response = self.get_site(site_name) - if response is None: - raise ValueError - site = response.get("response") - site_id = site[0].get("id") - site_exists = True - - except Exception as e: - 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): - """ - 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 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 their instance UUIDs. - - Description: - 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 = {} - - site_params = { - "site_id": site_id, - } - - try: - 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") - - else: - response = self.dnac._exec( - family="site_design", - function="get_site_assigned_network_devices", - op_modifies=True, - params=site_params, - ) - - 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: - self.log("Unable to fetch the device(s) associated with the site '{0}' due to {1}".format(site_name, str(e)), "ERROR") - - if not mgmt_ip_to_instance_id_map: - 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) - - return mgmt_ip_to_instance_id_map + # def validate_site_exists(self, site_name): + # """ + # Checks the existence of a site in Cisco Catalyst Center. + # Parameters: + # site_name (str): The name of the site to be checked. + # Returns: + # tuple: A tuple containing two values: + # - site_exists (bool): Indicates whether the site exists (True) or not (False). + # - site_id (str or None): The ID of the site if it exists, or None if the site is not found. + # Description: + # This method queries Cisco Catalyst Center to determine if a site with the provided name exists. + # If the site is found, it sets "site_exists" to True and retrieves the sites ID. + # If the site does not exist, "site_exists" is set to False, and "site_id" is None. + # If an exception occurs during the site lookup, an error message is logged, and the module fails. + # """ + # site_exists = False + # site_id = None + # response = None + + # try: + # response = self.get_site(site_name) + # if response is None: + # raise ValueError + # site = response.get("response") + # site_id = site[0].get("id") + # site_exists = True + + # except Exception as e: + # 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): + # """ + # 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 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 their instance UUIDs. + + # Description: + # 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 = {} + + # site_params = { + # "site_id": site_id, + # } + + # try: + # 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") + + # else: + # response = self.dnac._exec( + # family="site_design", + # function="get_site_assigned_network_devices", + # op_modifies=True, + # params=site_params, + # ) + + # 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: + # self.log("Unable to fetch the device(s) associated with the site '{0}' due to {1}".format(site_name, str(e)), "ERROR") + + # if not mgmt_ip_to_instance_id_map: + # 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) + + # return mgmt_ip_to_instance_id_map def get_device_list_params(self, config): """ @@ -769,7 +764,7 @@ 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.validate_site_exists(site_name) + (site_exists, site_id) = self.get_site_id(site_name) if site_exists: site_mgmt_ip_to_instance_id_map = self.get_device_ids_from_site(site_name, site_id) self.log("Retrieved following Device Id(s) of device(s): {0} from the provided site: {1}".format( From 951cafb876ad33f482f971960209fc7a93c3e222 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Wed, 25 Sep 2024 13:14:48 +0530 Subject: [PATCH 06/28] 4 major bugs fixed --- playbooks/dnac.log | 134 --------------- playbooks/provision_workflow_manager.yml | 14 +- plugins/modules/provision_workflow_manager.py | 162 +++++++++--------- 3 files changed, 89 insertions(+), 221 deletions(-) diff --git a/playbooks/dnac.log b/playbooks/dnac.log index b7a4c6b718..e69de29bb2 100644 --- a/playbooks/dnac.log +++ b/playbooks/dnac.log @@ -1,134 +0,0 @@ -09-21-2024 12:43:44 DEBUG Provision: __init__: 88: Logging configured and initiated - -09-21-2024 12:43:44 DEBUG Provision: __init__: 94: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.7.6', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} - -09-21-2024 12:43:44 INFO Provision: validate_input: 308: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.1.2.2', 'site_name_hierarchy': 'Global/USA/San Jose/BLDG23', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.1.2.4', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/USA/SAN JOSE/BLD23', 'managed_ap_locations': ['Global/Chennai/LTTS/FLOOR1'], 'dynamic_interfaces': None, 'provisioning': True}] - -09-21-2024 12:43:44 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.1.2.2', 'site_name_hierarchy': 'Global/USA/San Jose/BLDG23', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.1.2.4', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True}, {'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/USA/SAN JOSE/BLD23', 'managed_ap_locations': ['Global/Chennai/LTTS/FLOOR1'], 'dynamic_interfaces': None, 'provisioning': True}] - -09-21-2024 12:43:45 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716246, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:43:45 INFO Provision: get_dev_type: 348: The device type is wired - -09-21-2024 12:43:46 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} - -09-21-2024 12:43:46 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 - -09-21-2024 12:43:46 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} - -09-21-2024 12:43:46 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:43:46 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:43:46 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:43:46 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success - -09-21-2024 12:43:58 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136d-f85b-7dc0-85b5-5647946c58f9', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136d-f85b-7dc0-85b5-5647946c58f9', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/cc69d0de-336b-4e6b-a67f-4f6bcba2923f', 'executionId': 'cc69d0de-336b-4e6b-a67f-4f6bcba2923f'} - -09-21-2024 12:43:58 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902832980, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902827099, 'data': 'workflow_id=26a7a326-32e9-4991-b981-14c46f6dda77;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902832980, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136d-f85b-7dc0-85b5-5647946c58f9'}, 'version': '1.0'} - -09-21-2024 12:43:58 INFO Provision: get_task_status: 448: Task status for the task id 0192136d-f85b-7dc0-85b5-5647946c58f9 is TASK_MODIFY_PUT - -09-21-2024 12:43:58 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully - -09-21-2024 12:43:58 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:43:58 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} - -09-21-2024 12:43:58 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716259, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:43:58 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a - -09-21-2024 12:44:00 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:44:00 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success - -09-21-2024 12:44:00 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned - -09-21-2024 12:44:00 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:00 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716262, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:44:00 INFO Provision: get_dev_type: 348: The device type is wired - -09-21-2024 12:44:01 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} - -09-21-2024 12:44:01 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 - -09-21-2024 12:44:01 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} - -09-21-2024 12:44:01 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:01 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:05 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:44:05 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success - -09-21-2024 12:44:16 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136e-43c3-7e70-9a0c-bac19f9d91a5', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136e-43c3-7e70-9a0c-bac19f9d91a5', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/5fb584f9-db76-488b-968b-7fda544fb170', 'executionId': '5fb584f9-db76-488b-968b-7fda544fb170'} - -09-21-2024 12:44:17 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902852234, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902846403, 'data': 'workflow_id=0553213d-f20b-49b6-bc77-c996bcbfd68d;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902852234, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136e-43c3-7e70-9a0c-bac19f9d91a5'}, 'version': '1.0'} - -09-21-2024 12:44:17 INFO Provision: get_task_status: 448: Task status for the task id 0192136e-43c3-7e70-9a0c-bac19f9d91a5 is TASK_MODIFY_PUT - -09-21-2024 12:44:17 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully - -09-21-2024 12:44:17 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:17 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} - -09-21-2024 12:44:17 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716278, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:44:17 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a - -09-21-2024 12:44:18 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:44:18 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success - -09-21-2024 12:44:18 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned - -09-21-2024 12:44:18 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:18 DEBUG Provision: get_dev_type: 338: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716279, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:44:18 INFO Provision: get_dev_type: 348: The device type is wired - -09-21-2024 12:44:19 DEBUG Provision: get_site_details: 622: Received site details for 'Global/USA/San Jose/BLDG23': {'response': [{'parentId': '19ef7e22-457d-4bc8-ab7d-4446f9fb196c', 'additionalInfo': [{'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'country': 'United States', 'address': 'Cisco - Building 23, 560 McCarthy Blvd, Milpitas, California 95035, United States', 'latitude': '37.41864', 'addressInheritedFrom': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'type': 'building', 'longitude': '-121.919296'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'BLDG23', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/d42e6d8b-73b0-4ec3-99d3-fda53e339c83/19ef7e22-457d-4bc8-ab7d-4446f9fb196c/5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'}]} - -09-21-2024 12:44:19 INFO Provision: get_site_details: 629: Site Name: Global/USA/San Jose/BLDG23, Site ID: 5a9bb391-fb8f-47a5-8dd9-31c6fa7d8289 - -09-21-2024 12:44:19 INFO Provision: get_wired_params: 744: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23'} - -09-21-2024 12:44:19 INFO Provision: get_want: 836: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:19 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:19 DEBUG Provision: get_diff_merged: 909: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:44:19 INFO Provision: get_diff_merged: 911: The provisioned status of the wired device is success - -09-21-2024 12:44:30 DEBUG Provision: get_diff_merged: 921: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '0192136e-7a22-7264-aa11-0f5bfc2d3680', 'taskStatusUrl': '/dna/intent/api/v1/task/0192136e-7a22-7264-aa11-0f5bfc2d3680', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/71c21f30-1b29-4acd-8425-8d0dc8d0b98c', 'executionId': '71c21f30-1b29-4acd-8425-8d0dc8d0b98c'} - -09-21-2024 12:44:31 DEBUG Provision: get_task_status: 446: Response collected from 'get_task_by_id' API is {'response': {'version': 1726902865832, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1726902860322, 'data': 'workflow_id=c37f2f7d-bd10-4633-8bad-bac68b86d34a;cfs_id=e9ca4277-fd59-4a87-8770-cc3d948f0b16;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=true', 'lastUpdate': 1726902865832, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '0192136e-7a22-7264-aa11-0f5bfc2d3680'}, 'version': '1.0'} - -09-21-2024 12:44:31 INFO Provision: get_task_status: 448: Task status for the task id 0192136e-7a22-7264-aa11-0f5bfc2d3680 is TASK_MODIFY_PUT - -09-21-2024 12:44:31 INFO Provision: get_diff_merged: 928: Re-Provision done Successfully - -09-21-2024 12:44:31 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - -09-21-2024 12:44:31 INFO Provision: verify_diff_merged: 1102: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'active_validation': False}} - -09-21-2024 12:44:32 DEBUG Provision: get_device_id: 372: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.12.2, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2023 by Cisco Systems, Inc. Compiled Tue 14-Nov-23 05:56 by mcpre netconf enabled', 'lastUpdateTime': 1726897159281, 'macAddress': '90:88:55:07:59:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.12.2', 'serialNumber': 'FJC271924K0, FJC271924EQ', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.1.2.2', 'lastManagedResyncReasons': 'Link Up/Down Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Link Up/Down Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '8 days, 5:23:35.26', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:16:19', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'NY-EN-9300.cisco.local', 'locationName': None, 'managementIpAddress': '204.1.2.2', 'platformId': 'C9300-48UXM, C9300-48UXM', 'lastUpdated': '2024-09-21 05:39:19', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-21 05:35:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 716293, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a', 'id': 'e5cc9398-afbf-40a2-a8b1-e9cf0635c28a'}, 'version': '1.0'} - -09-21-2024 12:44:32 INFO Provision: get_device_id: 376: Device ID of the device with IP address 204.1.2.2 is e5cc9398-afbf-40a2-a8b1-e9cf0635c28a - -09-21-2024 12:44:32 DEBUG Provision: verify_diff_merged: 1128: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.1.2.2', 'siteNameHierarchy': 'Global/USA/San Jose/BLDG23', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-21-2024 12:44:32 INFO Provision: verify_diff_merged: 1130: The provisioned status of the wired device is success - -09-21-2024 12:44:32 INFO Provision: verify_diff_merged: 1133: Requested wired device is alread provisioned - -09-21-2024 12:44:32 DEBUG Provision: check_return_status: 238: status: success, msg: Successfully collected all parameters from playbook for comparison - diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index ede63fb471..33c2bae42b 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -6,7 +6,7 @@ vars_files: - "credentials.yml" tasks: - - name: Add/Update/Resync/Delete the devices in Cisco DNA Center Inventory. + - name: Provision a wireless device to a site cisco.dnac.provision_workflow_manager: dnac_host: "{{dnac_host}}" dnac_username: "{{dnac_username}}" @@ -15,16 +15,10 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log_level: DEBUG - dnac_log: true - config_verify: true + dnac_log: True state: merged config: - site_name_hierarchy: Global/USA/San Jose/BLDG23 - management_ip_address: 204.1.2.2 + management_ip_address: 2.2.2.2 - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 - management_ip_address: 204.1.2.4 - - site_name_hierarchy: Global/USA/SAN JOSE/BLD23 - management_ip_address: 204.192.3.40 - managed_ap_locations: - - Global/Chennai/LTTS/FLOOR1 \ No newline at end of file + management_ip_address: 1.1.1.1 diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 141bcfa98e..76d922119a 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -296,6 +296,8 @@ def validate_input(self, state=None): valid_provision, invalid_params = validate_list_of_dicts( self.config, provision_spec ) + self.log("self.config") + self.log(self.config) if invalid_params: self.msg = "Invalid parameters in playbook: {0}".format( "\n".join(invalid_params)) @@ -304,6 +306,8 @@ def validate_input(self, state=None): return self self.validated_config = valid_provision + self.log("self.validated_config") + self.log(self.validated_config) self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_provision)) self.log(str(self.msg), "INFO") self.status = "success" @@ -328,7 +332,7 @@ def get_dev_type(self): dev_response = self.dnac_apply['exec']( family="devices", function='get_network_device_by_ip', - params={"ip_address": self.validated_config[0]["management_ip_address"]}, + params={"ip_address": self.validated_config["management_ip_address"]}, op_modifies=True ) except Exception as e: @@ -365,7 +369,7 @@ def get_device_id(self): dev_response = self.dnac_apply['exec']( family="devices", function='get_network_device_by_ip', - params={"ip_address": self.validated_config[0]["management_ip_address"]}, + params={"ip_address": self.validated_config["management_ip_address"]}, op_modifies=True ) @@ -373,7 +377,7 @@ def get_device_id(self): dev_dict = dev_response.get("response") device_id = dev_dict.get("id") - self.log("Device ID of the device with IP address {0} is {1}".format(self.validated_config[0]["management_ip_address"], device_id), "INFO") + self.log("Device ID of the device with IP address {0} is {1}".format(self.validated_config["management_ip_address"], device_id), "INFO") return device_id def get_serial_number(self): @@ -394,7 +398,7 @@ def get_serial_number(self): response = self.dnac_apply['exec']( family="devices", function='get_network_device_by_ip', - params={"ip_address": self.validated_config[0]["management_ip_address"]}, + params={"ip_address": self.validated_config["management_ip_address"]}, op_modifies=True ) @@ -590,43 +594,36 @@ def get_site_type(self, site_name_hierarchy=None): def get_site_details(self, site_name_hierarchy=None): """ - Fetches the id and existance of the site - Parameters: - - self: The instance of the class containing the 'config' attribute - to be validated. - - site_name_hierarchy: Name of the site collected from the input. + self (object): An instance of a class used for interacting with Cisco Catalyst Center. Returns: - - site_id: A string indicating the id of the site. - - site_exits: A boolean value indicating the existance of the site. - Example: - Post creation of the validated input, this method gets the - id of the site. + tuple: A tuple containing two values: + - site_exists (bool): A boolean indicating whether the site exists (True) or not (False). + - site_id (str or None): The ID of the site if it exists, or None if the site is not found. + Description: + This method checks the existence of a site in the Catalyst Center. If the site is found,it sets 'site_exists' to True, + retrieves the site's ID, and returns both values in a tuple. If the site does not exist, 'site_exists' is set + to False, and 'site_id' is None. If an exception occurs during the site lookup, an exception is raised. """ site_exists = False site_id = None - try: - response = self.dnac_apply['exec']( - family="sites", - function='get_site', - params={"name": site_name_hierarchy}, - op_modifies=True - ) - except Exception: - self.log("Exception occurred as \ - site '{0}' was not found".format(self.want.get("site_name")), "CRITICAL") - self.module.fail_json(msg="Site not found", response=[]) + response = None - if response: - self.log("Received site details\ - for '{0}': {1}".format(site_name_hierarchy, str(response)), "DEBUG") + try: + response = self.get_site(site_name_hierarchy) + if response is None: + raise ValueError site = response.get("response") - site_additional_info = site[0].get("additionalInfo") - if len(site) == 1: - site_id = site[0].get("id") - site_exists = True - self.log("Site Name: {1}, Site ID: {0}".format(site_id, site_name_hierarchy), "INFO") + site_id = site[0].get("id") + site_exists = True + + except Exception as e: + self.status = "failed" + self.msg = ("An exception occurred: Site '{0}' does not exist in the Cisco Catalyst Center.".format(site_name_hierarchy)) + self.result['response'] = self.msg + self.log(self.msg, "ERROR") + self.check_return_status() return (site_exists, site_id) @@ -677,7 +674,7 @@ def get_site_assign(self): Post creation of the validated input, this method tells whether devices are associated with a site. """ - site_name_hierarchy = self.validated_config[0].get("site_name_hierarchy") + site_name_hierarchy = self.validated_config.get("site_name_hierarchy") site_exists, site_id = self.get_site_details(site_name_hierarchy=site_name_hierarchy) serial_number = self.get_serial_number() if site_exists: @@ -716,8 +713,9 @@ def get_wired_params(self): paramters and stores it for further processing and calling the parameters in other APIs. """ - - site_name = self.validated_config[0].get("site_name_hierarchy") + self.log("self.validated_config") + self.log(self.validated_config) + site_name = self.validated_config.get("site_name_hierarchy") (site_exits, site_id) = self.get_site_details(site_name_hierarchy=site_name) @@ -726,16 +724,16 @@ def get_wired_params(self): self.log(msg, "CRITICAL") self.module.fail_json(msg=msg) - if self.validated_config[0].get("provisioning") is True: + if self.validated_config.get("provisioning") is True: wired_params = { - "deviceManagementIpAddress": self.validated_config[0]["management_ip_address"], + "deviceManagementIpAddress": self.validated_config["management_ip_address"], "siteNameHierarchy": site_name } else: wired_params = { "device": [ { - "ip": self.validated_config[0]["management_ip_address"] + "ip": self.validated_config["management_ip_address"] } ], "site_id": site_id @@ -765,8 +763,8 @@ def get_wireless_params(self): wireless_params = [ { - "site": self.validated_config[0].get("site_name_hierarchy"), - "managedAPLocations": self.validated_config[0].get("managed_ap_locations"), + "site": self.validated_config.get("site_name_hierarchy"), + "managedAPLocations": self.validated_config.get("managed_ap_locations"), } ] @@ -776,14 +774,14 @@ def get_wireless_params(self): self.log(msg, "CRITICAL") self.module.fail_json(msg=msg, response=[]) - for ap_loc in self.validated_config[0].get("managed_ap_locations"): + for ap_loc in self.validated_config.get("managed_ap_locations"): if self.get_site_type(site_name_hierarchy=ap_loc) != "floor": self.log("Managed AP Location must be a floor", "CRITICAL") self.module.fail_json(msg="Managed AP Location must be a floor", response=[]) wireless_params[0]["dynamicInterfaces"] = [] - if self.validated_config[0].get("dynamic_interfaces"): - for interface in self.validated_config[0].get("dynamic_interfaces"): + if self.validated_config.get("dynamic_interfaces"): + for interface in self.validated_config.get("dynamic_interfaces"): interface_dict = { "interfaceIPAddress": interface.get("interface_ip_address"), "interfaceNetmaskInCIDR": interface.get("interface_netmask_in_c_i_d_r"), @@ -796,7 +794,7 @@ def get_wireless_params(self): response = self.dnac_apply['exec']( family="devices", function='get_network_device_by_ip', - params={"ip_address": self.validated_config[0]["management_ip_address"]}, + params={"ip_address": self.validated_config["management_ip_address"]}, op_modifies=True ) @@ -805,7 +803,7 @@ def get_wireless_params(self): self.log("Parameters collected for the provisioning of wireless device:{0}".format(wireless_params), "INFO") return wireless_params - def get_want(self): + def get_want(self, config): """ Get all provision related informantion from the playbook Args: @@ -822,12 +820,15 @@ def get_want(self): before calling the APIs """ + self.validated_config = config self.want = {} self.want["device_type"] = self.get_dev_type() if self.want["device_type"] == "wired": self.want["prov_params"] = self.get_wired_params() elif self.want["device_type"] == "wireless": self.want["prov_params"] = self.get_wireless_params() + # self.log("self.want['prov_params']") + # self.log(self.want["prov_params"]) else: self.log("Passed devices are neither wired or wireless devices", "WARNING") @@ -866,14 +867,14 @@ def perform_wireless_reprovision(self): execution_id = response.get("executionId") self.get_execution_status_wireless(execution_id=execution_id) self.result["changed"] = True - self.result['msg'] = "Wireless device with IP address {0} got re-provisioned successfully".format(self.validated_config[0]["management_ip_address"]) + self.result['msg'] = "Wireless device with IP address {0} got re-provisioned successfully".format(self.validated_config["management_ip_address"]) self.result['diff'] = self.validated_config self.result['response'] = execution_id self.log(self.result['msg'], "INFO") return self except Exception as e: self.log("Parameters are {0}".format(self.want)) - self.msg = "Error in wireless re-provisioning of {0} due to {1}".format(self.validated_config[0]["management_ip_address"], e) + self.msg = "Error in wireless re-provisioning of {0} due to {1}".format(self.validated_config["management_ip_address"], e) self.log(self.msg, "ERROR") self.status = "failed" return self @@ -901,7 +902,7 @@ class instance for further use. function="get_provisioned_wired_device", op_modifies=True, params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] + "device_management_ip_address": self.validated_config.get("management_ip_address") }, ) except Exception: @@ -911,30 +912,37 @@ class instance for further use. self.log("The provisioned status of the wired device is {0}".format(status), "INFO") if status == "success": - try: - response = self.dnac_apply['exec']( - family="sda", - function="re_provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") - return self + if self.validated_config.get("provisioning") is True: + try: + response = self.dnac_apply['exec']( + family="sda", + function="re_provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Re-Provision done Successfully" + self.result['diff'] = self.validated_config + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + return self - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) + except Exception as e: + self.msg = "Error in re-provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + else: + self.msg = ("Cannot assign a provisioned device to the site. " + "Unprovision the device and then try assigning it to the site.") self.log(self.msg, "ERROR") self.status = "failed" return self else: - if self.validated_config[0].get("provisioning") is True: + if self.validated_config.get("provisioning") is True: try: response = self.dnac_apply['exec']( family="sda", @@ -996,14 +1004,14 @@ class instance for further use. execution_id = response.get("executionId") self.get_execution_status_wireless(execution_id=execution_id) self.result["changed"] = True - self.result['msg'] = "Wireless device with IP {0} got provisioned successfully".format(self.validated_config[0]["management_ip_address"]) + self.result['msg'] = "Wireless device with IP {0} got provisioned successfully".format(self.validated_config["management_ip_address"]) self.result['diff'] = self.validated_config self.result['response'] = execution_id self.log(self.result['msg'], "INFO") return self except Exception as e: self.log("Parameters are {0}".format(self.want)) - self.msg = "Error in wireless provisioning of {0} due to {1}".format(self.validated_config[0]["management_ip_address"], e) + self.msg = "Error in wireless provisioning of {0} due to {1}".format(self.validated_config["management_ip_address"], e) self.log(self.msg, "ERROR") self.status = "failed" return self @@ -1049,7 +1057,7 @@ def get_diff_deleted(self): function="get_provisioned_wired_device", op_modifies=True, params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] + "device_management_ip_address": self.validated_config["management_ip_address"] }, ) @@ -1070,7 +1078,7 @@ def get_diff_deleted(self): function="delete_provisioned_wired_device", op_modifies=True, params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] + "device_management_ip_address": self.validated_config["management_ip_address"] }, ) self.log("Response collected from the 'delete_provisioned_wired_device' API is : {0}".format(str(response)), "DEBUG") @@ -1103,8 +1111,8 @@ def verify_diff_merged(self): # Code to validate Cisco Catalyst Center config for merged state device_type = self.want.get("device_type") - provisioning = self.validated_config[0].get("provisioning") - site_name_hierarchy = self.validated_config[0].get("site_name_hierarchy") + provisioning = self.validated_config.get("provisioning") + site_name_hierarchy = self.validated_config.get("site_name_hierarchy") uuid = self.get_device_id() if provisioning is False: if self.is_device_assigned_to_site(uuid) is True: @@ -1120,7 +1128,7 @@ def verify_diff_merged(self): function="get_provisioned_wired_device", op_modifies=True, params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] + "device_management_ip_address": self.validated_config["management_ip_address"] }, ) except Exception: @@ -1166,7 +1174,7 @@ def verify_diff_deleted(self): function="get_provisioned_wired_device", op_modifies=True, params={ - "device_management_ip_address": self.validated_config[0]["management_ip_address"] + "device_management_ip_address": self.validated_config["management_ip_address"] }, ) except Exception: @@ -1227,7 +1235,7 @@ def main(): for config in ccc_provision.validated_config: ccc_provision.reset_values() - ccc_provision.get_want().check_return_status() + ccc_provision.get_want(config).check_return_status() ccc_provision.get_diff_state_apply[state]().check_return_status() if config_verify: ccc_provision.verify_diff_state_apply[state]().check_return_status() From a31fe3df58b11e9b2ecd2fe5c5eff47bce077d06 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Wed, 25 Sep 2024 15:19:13 +0530 Subject: [PATCH 07/28] testing compleated for 4 bugs --- plugins/modules/provision_workflow_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 76d922119a..0099c93c9f 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -__author__ = ("Abinash Mishra, Madhan Sankaranarayanan") +__author__ = ("Abinash Mishra, Madhan Sankaranarayanan, Syed Khadeer Ahmed, Ajith Andrew J") DOCUMENTATION = r""" --- @@ -21,6 +21,8 @@ - cisco.dnac.workflow_manager_params author: Abinash Mishra (@abimishr) Madhan Sankaranarayanan (@madhansansel) + Syed Khadeer Ahmed(@syed-khadeerahmed) + Ajith Andrew J (@ajithandrewj) options: config_verify: description: Set to True to verify the Cisco Catalyst Center config after applying the playbook config. From 264cca2cb429214e92c41e48493883805145dcb9 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Wed, 25 Sep 2024 17:33:02 +0530 Subject: [PATCH 08/28] bugs fixed --- plugins/modules/provision_workflow_manager.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 0099c93c9f..78c1c3718d 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -829,8 +829,6 @@ def get_want(self, config): self.want["prov_params"] = self.get_wired_params() elif self.want["device_type"] == "wireless": self.want["prov_params"] = self.get_wireless_params() - # self.log("self.want['prov_params']") - # self.log(self.want["prov_params"]) else: self.log("Passed devices are neither wired or wireless devices", "WARNING") @@ -939,7 +937,7 @@ class instance for further use. return self else: self.msg = ("Cannot assign a provisioned device to the site. " - "Unprovision the device and then try assigning it to the site.") + "Unprovision the device and then try assigning it to the site.") self.log(self.msg, "ERROR") self.status = "failed" return self @@ -1246,4 +1244,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() From 4fe5c18ee09c9bf2939605981b6537a2cc684f78 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Wed, 25 Sep 2024 17:38:20 +0530 Subject: [PATCH 09/28] bugs fixed --- plugins/modules/inventory_workflow_manager.py | 383 +++++------------- 1 file changed, 101 insertions(+), 282 deletions(-) diff --git a/plugins/modules/inventory_workflow_manager.py b/plugins/modules/inventory_workflow_manager.py index 6a9045a145..61fbef9f0f 100644 --- a/plugins/modules/inventory_workflow_manager.py +++ b/plugins/modules/inventory_workflow_manager.py @@ -545,48 +545,10 @@ site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" resync_retry_count: 200 resync_retry_interval: 2 - provisioning: True - device_ip: "2.2.2.2" site_name: "Global/USA/San Francisco/BGL_18/floor_test" resync_retry_count: 200 resync_retry_interval: 2 - provisioning: True - -- name: Associate Wired Devices to a site - cisco.dnac.inventory_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: False - state: merged - config: - - provision_wired_device: - - device_ip: "1.1.1.1" - site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: False - - device_ip: "2.2.2.2" - site_name: "Global/USA/San Francisco/BGL_18/floor_test" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: False - - provision_wireless_device: - - device_ip: "1.1.1.1" - site_name: "Global/USA/San Francisco/BGL_18/floor_pnp" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: False - - device_ip: "2.2.2.2" - site_name: "Global/USA/San Francisco/BGL_18/floor_test" - resync_retry_count: 200 - resync_retry_interval: 2 - provisioning: False - name: Update Device Role with IP Address cisco.dnac.inventory_workflow_manager: @@ -768,6 +730,8 @@ DnacBase, validate_list_of_dicts, ) +# Defer this feature as API issue is there once it's fixed we will addresses it in upcoming release iac2.0 +support_for_provisioning_wireless = False class Inventory(DnacBase): @@ -776,9 +740,6 @@ class Inventory(DnacBase): def __init__(self, module): super().__init__(module) self.supported_states = ["merged", "deleted"] - self.payload = module.params - self.dnac_version = int(self.payload.get("dnac_version").replace(".", "")) - self.version_2_3_5_3, self.version_2_3_7_6, self.version_2_2_3_3 = 2353, 2376, 2233 def validate_input(self): """ @@ -866,17 +827,6 @@ def validate_input(self): 'site_name': {'type': 'str'}, 'resync_retry_count': {'default': 200, 'type': 'int'}, 'resync_retry_interval': {'default': 2, 'type': 'int'}, - "provisioning": {'type':'bool'} - }, - 'provision_wireless_device': { - 'type': 'list', - 'elements': 'dict', - 'device_ip': {'type': 'str'}, - 'site_name': {'type': 'str'}, - 'resync_retry_count': {'default': 200, 'type': 'int'}, - 'resync_retry_interval': {'default': 2, 'type': 'int'}, - "managed_ap_locations": {'type': 'list', 'element': 'str'}, - 'dynamic_interfaces': {'type': 'list', 'element': 'dict'} } } @@ -1724,239 +1674,107 @@ def provisioned_wired_device(self): total_devices_to_provisioned = len(provision_wired_list) device_ip_list = [] provision_count, already_provision_count = 0, 0 - if self.dnac_version <= self.version_2_3_5_3: - for prov_dict in provision_wired_list: - managed_flag = False - device_ip = prov_dict['device_ip'] - device_ip_list.append(device_ip) - site_name = prov_dict['site_name'] - device_type = "Wired" - resync_retry_count = prov_dict.get("resync_retry_count", 200) - # This resync retry interval will be in seconds which will check device status at given interval - resync_retry_interval = prov_dict.get("resync_retry_interval", 2) - - if not site_name or not device_ip: - self.status = "failed" - self.msg = "Site and Device IP are required for Provisioning of Wired Devices." - self.log(self.msg, "ERROR") - self.result['response'] = self.msg - return self - - provision_wired_params = { - 'deviceManagementIpAddress': device_ip, - 'siteNameHierarchy': site_name - } - - # Check the provisioning status of device - device_prov_status = self.get_provision_wired_device(device_ip) - if device_prov_status == 2: - self.status = "success" - already_provision_count += 1 - self.result['changed'] = False - self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) - self.log(self.msg, "INFO") - continue - if device_prov_status == 3: - self.status = "failed" - error_msg = "Cannot do Provisioning for device {0}.".format(device_ip) - self.log(error_msg, "ERROR") - continue - - # Check till device comes into managed state - while resync_retry_count: - response = self.get_device_response(device_ip) - self.log("Device is in {0} state waiting for Managed State.".format(response['managementState']), "DEBUG") - - if ( - response.get('managementState') == "Managed" - and response.get('collectionStatus') == "Managed" - and response.get("hostname") - ): - msg = """Device '{0}' comes to managed state and ready for provisioning with the resync_retry_count - '{1}' left having resync interval of {2} seconds""".format(device_ip, resync_retry_count, resync_retry_interval) - self.log(msg, "INFO") - managed_flag = True - break - if response.get('collectionStatus') == "Partial Collection Failure" or response.get('collectionStatus') == "Could Not Synchronize": - device_status = response.get('collectionStatus') - msg = """Device '{0}' comes to '{1}' state and never goes for provisioning with the resync_retry_count - '{2}' left having resync interval of {3} seconds""".format(device_ip, device_status, resync_retry_count, resync_retry_interval) - self.log(msg, "INFO") - managed_flag = False - break - - time.sleep(resync_retry_interval) - resync_retry_count = resync_retry_count - 1 - if not managed_flag: - self.log("""Device {0} is not transitioning to the managed state, so provisioning operation cannot - be performed.""".format(device_ip), "WARNING") - continue - - try: - response = self.dnac._exec( - family="sda", - function='provision_wired_device', - op_modifies=True, - params=provision_wired_params, - ) - - if response.get("status") == "failed": - description = response.get("description") - error_msg = "Cannot do Provisioning for device {0} beacuse of {1}".format(device_ip, description) - self.log(error_msg, "ERROR") - continue - - task_id = response.get("taskId") - - while True: - execution_details = self.get_task_details(task_id) - progress = execution_details.get("progress") - - if 'TASK_PROVISION' in progress: - self.handle_successful_provisioning(device_ip, execution_details, device_type) - provision_count += 1 - break - elif execution_details.get("isError"): - self.handle_failed_provisioning(device_ip, execution_details, device_type) - break - - except Exception as e: - # Not returning from here as there might be possiblity that for some devices it comes into exception - # but for others it gets provision successfully or If some devices are already provsioned - self.handle_provisioning_exception(device_ip, e, device_type) - else: - for prov_dict in provision_wired_list: - device_ip = prov_dict['device_ip'] - device_ip_list.append(device_ip) - site_name = prov_dict['site_name'] - device_type = "Wired" - is_provision = prov_dict.get("provision") - resync_retry_count = prov_dict.get("resync_retry_count", 200) - # This resync retry interval will be in seconds which will check device status at given interval - resync_retry_interval = prov_dict.get("resync_retry_interval", 2) + for prov_dict in provision_wired_list: + managed_flag = False + device_ip = prov_dict['device_ip'] + device_ip_list.append(device_ip) + site_name = prov_dict['site_name'] + device_type = "Wired" + resync_retry_count = prov_dict.get("resync_retry_count", 200) + # This resync retry interval will be in seconds which will check device status at given interval + resync_retry_interval = prov_dict.get("resync_retry_interval", 2) - device_ids = self.get_device_ids([device_ip]) - device_id = device_ids[0] + if not site_name or not device_ip: + self.status = "failed" + self.msg = "Site and Device IP are required for Provisioning of Wired Devices." + self.log(self.msg, "ERROR") + self.result['response'] = self.msg + return self - try: - response = self.get_sites(site_name) - self.log("Received API response from 'get_sites': {0}".format(str(response)), "DEBUG") - site = response.get("response") - site_id = site[0].get("id") + provision_wired_params = { + 'deviceManagementIpAddress': device_ip, + 'siteNameHierarchy': site_name + } - except Exception as e: - self.status = "failed" - self.msg = ("'An exception occurred: Site '{0}' does not exist in the Cisco Catalyst Center").format(site_name) - self.log(self.msg, "ERROR") - self.check_return_status() + # Check the provisioning status of device + device_prov_status = self.get_provision_wired_device(device_ip) + if device_prov_status == 2: + self.status = "success" + already_provision_count += 1 + self.result['changed'] = False + self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) + self.log(self.msg, "INFO") + continue + if device_prov_status == 3: + self.status = "failed" + error_msg = "Cannot do Provisioning for device {0}.".format(device_ip) + self.log(error_msg, "ERROR") + continue - assign_network_device_to_site = { - 'deviceIds': [device_id], - 'siteId': site_id, - } + # Check till device comes into managed state + while resync_retry_count: + response = self.get_device_response(device_ip) + self.log("Device is in {0} state waiting for Managed State.".format(response['managementState']), "DEBUG") + + if ( + response.get('managementState') == "Managed" + and response.get('collectionStatus') == "Managed" + and response.get("hostname") + ): + msg = """Device '{0}' comes to managed state and ready for provisioning with the resync_retry_count + '{1}' left having resync interval of {2} seconds""".format(device_ip, resync_retry_count, resync_retry_interval) + self.log(msg, "INFO") + managed_flag = True + break + if response.get('collectionStatus') == "Partial Collection Failure" or response.get('collectionStatus') == "Could Not Synchronize": + device_status = response.get('collectionStatus') + msg = """Device '{0}' comes to '{1}' state and never goes for provisioning with the resync_retry_count + '{2}' left having resync interval of {3} seconds""".format(device_ip, device_status, resync_retry_count, resync_retry_interval) + self.log(msg, "INFO") + managed_flag = False + break - provision_params = [{ - "siteId": site_id, - "networkDeviceId": device_id - }] + time.sleep(resync_retry_interval) + resync_retry_count = resync_retry_count - 1 - if not site_name or not device_ip: - self.status = "failed" - self.msg = "Site and Device IP are required for Provisioning of Wired Devices." - self.log(self.msg, "ERROR") - self.result['response'] = self.msg - return self + if not managed_flag: + self.log("""Device {0} is not transitioning to the managed state, so provisioning operation cannot + be performed.""".format(device_ip), "WARNING") + continue - # device provision or not + try: response = self.dnac._exec( family="sda", - function='get_provisioned_devices', + function='provision_wired_device', op_modifies=True, - params={"networkDeviceId":device_id}, + params=provision_wired_params, ) - get_provision_response = response.get("response") - #if the responce is has anything then it should go for reprovision - if get_provision_response: - self.log("{0} Device {1} is already provisioned !!".format(device_type, device_ip), "INFO") - self.status = "success" - already_provision_count += 1 - self.result['changed'] = False - self.msg = "Device '{0}' is already provisioned in the Cisco Catalyst Center".format(device_ip) - self.log(self.msg, "INFO") + if response.get("status") == "failed": + description = response.get("description") + error_msg = "Cannot do Provisioning for device {0} beacuse of {1}".format(device_ip, description) + self.log(error_msg, "ERROR") + continue - else: - if is_provision == True: - try: - self.log(assign_network_device_to_site) - response = self.dnac._exec( - family="site_design", - function='assign_network_devices_to_a_site', - op_modifies=True, - params=assign_network_device_to_site, - ) - self.log("Received API response from 'assign_network_devices_to_a_site': {0}".format(str(response)), "DEBUG") - task_id = response.get("response").get("taskId") - self.log(task_id) - while True: - execution_details = self.get_task_details(task_id) - progress = execution_details.get("progress") - if "success" in progress: - self.log("Assigning site for the device {0} was compleated... ".format(device_ip)) - break - elif execution_details.get("isError"): - error_msg = "Assigning site failed for the device {0} ".format(device_ip) - self.log(error_msg, "ERROR") - - self.log(provision_params) - response = self.dnac._exec( - family="sda", - function='provision_devices', - op_modifies=True, - params={"payload": provision_params}, - ) - self.log("Received API response from 'provision_devices': {0}".format(str(response)), "DEBUG") - task_id = response.get("response").get("taskId") - - while True: - execution_details = self.get_task_details(task_id) - progress = execution_details.get("progress") - data = execution_details.get("data") - - if 'processcfs_complete=true' in data: - self.handle_successful_provisioning(device_ip, execution_details, device_type) - provision_count += 1 - break - elif execution_details.get("isError"): - self.handle_failed_provisioning(device_ip, execution_details, device_type) - break - - except: - self.log("exception") - else: - try: - self.log(assign_network_device_to_site) - response = self.dnac._exec( - family="site_design", - function='assign_network_devices_to_a_site', - op_modifies=True, - params=assign_network_device_to_site, - ) - self.log("Received API response from 'assign_network_devices_to_a_site': {0}".format(str(response)), "DEBUG") - task_id = response.get("response").get("taskId") - self.log(task_id) - while True: - execution_details = self.get_task_details(task_id) - progress = execution_details.get("progress") - if "success" in progress: - self.log("Assigning site for the device {0} was Successfull... ".format(device_ip)) - break - elif execution_details.get("isError"): - error_msg = "Assigning site failed for the device {0} ".format(device_ip) - self.log(error_msg, "ERROR") - except: - self.log("exception") + task_id = response.get("taskId") + + while True: + execution_details = self.get_task_details(task_id) + progress = execution_details.get("progress") + + if 'TASK_PROVISION' in progress: + self.handle_successful_provisioning(device_ip, execution_details, device_type) + provision_count += 1 + break + elif execution_details.get("isError"): + self.handle_failed_provisioning(device_ip, execution_details, device_type) + break + + except Exception as e: + # Not returning from here as there might be possiblity that for some devices it comes into exception + # but for others it gets provision successfully or If some devices are already provsioned + self.handle_provisioning_exception(device_ip, e, device_type) # Check If all the devices are already provsioned, return from here only if already_provision_count == total_devices_to_provisioned: @@ -2302,15 +2120,16 @@ def get_have(self, config): if device_ip_address not in device_in_ccc: device_not_in_ccc.append(device_ip_address) - if self.config[0].get('provision_wireless_device'): - provision_wireless_list = self.config[0].get('provision_wireless_device') + if support_for_provisioning_wireless: + if self.config[0].get('provision_wireless_device'): + provision_wireless_list = self.config[0].get('provision_wireless_device') - for prov_dict in provision_wireless_list: - device_ip_address = prov_dict['device_ip'] - if device_ip_address not in want_device and device_ip_address not in devices_in_playbook: - devices_in_playbook.append(device_ip_address) - if device_ip_address not in device_in_ccc and device_ip_address not in device_not_in_ccc: - device_not_in_ccc.append(device_ip_address) + for prov_dict in provision_wireless_list: + device_ip_address = prov_dict['device_ip'] + if device_ip_address not in want_device and device_ip_address not in devices_in_playbook: + devices_in_playbook.append(device_ip_address) + if device_ip_address not in device_in_ccc and device_ip_address not in device_not_in_ccc: + device_not_in_ccc.append(device_ip_address) self.log("Device(s) {0} exists in Cisco Catalyst Center".format(str(device_in_ccc)), "INFO") have["want_device"] = want_device @@ -3673,9 +3492,9 @@ def get_diff_merged(self, config): # Once Wireless device get added we will assign device to site and Provisioned it # Defer this feature as API issue is there once it's fixed we will addresses it in upcoming release iac2.0 - - if self.config[0].get('provision_wireless_device'): - self.provisioned_wireless_devices().check_return_status() + if support_for_provisioning_wireless: + if self.config[0].get('provision_wireless_device'): + self.provisioned_wireless_devices().check_return_status() if device_resynced: self.resync_devices().check_return_status() From 289e66ea2110506d7276a06e3f17b48c740e667c Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Wed, 25 Sep 2024 17:43:24 +0530 Subject: [PATCH 10/28] bugs fixed --- plugins/modules/provision_workflow_manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 78c1c3718d..794a692894 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -298,8 +298,6 @@ def validate_input(self, state=None): valid_provision, invalid_params = validate_list_of_dicts( self.config, provision_spec ) - self.log("self.config") - self.log(self.config) if invalid_params: self.msg = "Invalid parameters in playbook: {0}".format( "\n".join(invalid_params)) @@ -308,8 +306,6 @@ def validate_input(self, state=None): return self self.validated_config = valid_provision - self.log("self.validated_config") - self.log(self.validated_config) self.msg = "Successfully validated playbook configuration parameters using 'validate_input': {0}".format(str(valid_provision)) self.log(str(self.msg), "INFO") self.status = "success" From 1470053d595b78fd581a9d3b91620557b4bbbba9 Mon Sep 17 00:00:00 2001 From: skesali Date: Wed, 25 Sep 2024 20:56:45 +0530 Subject: [PATCH 11/28] Latest changes for the device_configs_backup_workflow_manager --- plugins/module_utils/dnac.py | 288 +++++++++++++++++- .../device_configs_backup_workflow_manager.py | 69 ++--- 2 files changed, 319 insertions(+), 38 deletions(-) diff --git a/plugins/module_utils/dnac.py b/plugins/module_utils/dnac.py index d9aebb45f5..3d87a8b7c2 100644 --- a/plugins/module_utils/dnac.py +++ b/plugins/module_utils/dnac.py @@ -35,6 +35,7 @@ import re import socket import time +import traceback class DnacBase(): @@ -542,6 +543,107 @@ def check_string_dictionary(self, task_details_data): except json.JSONDecodeError: pass return None + + def get_device_ip_from_device_id(self, site_id): + """ + Retrieve the management IP addresses and their corresponding instance UUIDs of devices associated with a specific site in Cisco Catalyst Center. + + Args: + site_id (str): The ID of the site to be retrieved. + + Returns: + dict: A dictionary mapping management IP addresses to their instance UUIDs, or an empty dict if no devices found. + """ + + mgmt_ip_to_instance_id_map = {} + + try: + response = self.get_device_ids_from_site(site_id) + + if not response: + raise ValueError("No response received from get_device_ids_from_site") + + self.log("Received API response from 'get_device_ids_from_site': {0}".format(str(response)), "DEBUG") + + for device_id in response: + 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") + instance_uuid = device_response.get("response", {}).get("instanceUuid") + if management_ip and instance_uuid: + mgmt_ip_to_instance_id_map[management_ip] = instance_uuid + else: + self.log("Management IP or instance UUID not found for device ID: {0}".format(device_id), "WARNING") + + except Exception as e: + self.log("Unable to fetch the device(s) associated with the site '{0}' due to {1}".format(site_id, str(e)), "ERROR") + return {} + + if not mgmt_ip_to_instance_id_map: + self.log("No reachable devices found at Site: {0}".format(site_id), "INFO") + + return mgmt_ip_to_instance_id_map + + def get_device_ids_from_site(self, site_id): + """ + Retrieve device IDs associated with a specific site in Cisco Catalyst Center. + + Args: + site_id (str): The unique identifier of the site. + + Returns: + list: A list of device IDs associated with the site. + Returns an empty list if no devices are found or if an error occurs. + """ + + device_ids = [] + + if self.dnac_version <= self.version_2_3_5_3: + try: + response = self.dnac._exec( + family="sites", + function="get_membership", + op_modifies=True, + params={"site_id": site_id}, + ) + + if response and "device" in response: + for device in response.get("device", []): + for item in device.get("response", []): + device_ids.append(item.get("instanceUuid")) + + self.log("Retrieved device IDs from membership for site '{0}': {1}".format(site_id, device_ids), "DEBUG") + + except Exception as e: + self.log("Error retrieving device IDs from membership for site '{0}': {1}".format(site_id, str(e)), "ERROR") + + else: + try: + response = self.dnac._exec( + family="site_design", + function="get_site_assigned_network_devices", + op_modifies=True, + params={"site_id": site_id}, + ) + + if response and "response" in response: + for device in response.get("response", []): + device_ids.append(device.get("deviceId")) + + self.log("Retrieved device IDs from assigned devices for site '{0}': {1}".format(site_id, device_ids), "DEBUG") + + except Exception as e: + self.log("Error retrieving device IDs from assigned devices for site '{0}': {1}".format(site_id, str(e)), "ERROR") + + if not device_ids: + self.log("No devices found for site '{0}'".format(site_id), "INFO") + + return device_ids def get_site_id(self, site_name): """ @@ -1001,7 +1103,7 @@ def get_task_details_by_id(self, task_id): Call the API 'get_task_details_by_id' to get the details along with the failure reason. Return the details. """ - + # Need to handle exception task_details = None response = self.dnac._exec( family="task", @@ -1030,7 +1132,7 @@ def get_tasks_by_id(self, task_id): Call the API 'get_tasks_by_id' to get the status of the task. Return the details along with the status of the task. """ - + # Need to handle exception task_status = None response = self.dnac._exec( family="task", @@ -1051,7 +1153,7 @@ def get_tasks_by_id(self, task_id): def check_tasks_response_status(self, response, api_name): """ - Get the site id from the site name. + Get the task response status from taskId Parameters: self: The current object details. @@ -1115,6 +1217,184 @@ def check_tasks_response_status(self, response, api_name): return self + def set_operation_result(self, operation_status, is_changed, status_message, log_level, additional_info=None): + """ + Update the result of the operation with the provided status, message, and log level. + Parameters: + - operation_status (str): The status of the operation ("success" or "failed"). + - is_changed (bool): Indicates whether the operation caused changes. + - status_message (str): The message describing the result of the operation. + - log_level (str): The log level at which the message should be logged ("INFO", "ERROR", "CRITICAL", etc.). + - additional_info (dict, optional): Additional data related to the operation result. + Returns: + self (object): An instance of the class. + Note: + - If the status is "failed", the "failed" key in the result dictionary will be set to True. + - If data is provided, it will be included in the result dictionary. + """ + # Update the result attributes with the provided values + self.status = operation_status + self.result.update({ + "status": operation_status, + "msg": status_message, + "response": status_message, + "changed": is_changed, + "failed": operation_status == "failed", + "data": additional_info or {} # Include additional_info if provided, else an empty dictionary + }) + + # Log the message at the specified log level + self.log(status_message, log_level) + + return self + + def fail_and_exit(self, msg): + """Helper method to update the result as failed and exit.""" + self.set_operation_result("failed", False, msg, "ERROR") + self.check_return_status() + + def log_traceback(self): + """ + Logs the full traceback of the current exception. + """ + # Capture the full traceback + full_traceback = traceback.format_exc() + + # Log the traceback + self.log("Traceback: {0}".format(full_traceback), "DEBUG") + + def check_timeout_and_exit(self, loop_start_time, task_id, task_name): + """ + Check if the elapsed time exceeds the specified timeout period and exit the while loop if it does. + Parameters: + - loop_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. + Returns: + bool: True if the elapsed time exceeds the timeout period, False otherwise. + """ + # If the elapsed time exceeds the timeout period + elapsed_time = time.time() - loop_start_time + if elapsed_time > self.params.get("dnac_api_task_timeout"): + self.msg = "Task {0} with task id {1} has not completed within the timeout period of {2} seconds.".format( + task_name, task_id, int(elapsed_time)) + + # Update the result with failure status and log the error message + self.set_operation_result("failed", False, self.msg, "ERROR") + return True + + return False + + def get_taskid_post_api_call(self, api_family, api_function, api_parameters): + """ + Executes the specified API call with given parameters and logs responses. + + Parameters: + api_family (str): The API family (e.g., "sda"). + api_function (str): The API function (e.g., "add_port_assignments"). + api_parameters (dict): The parameters for the API call. + """ + try: + self.log("Entered {0} method".format(api_function), "DEBUG") + + # Execute the API call + response = self.dnac._exec( + family=api_family, + function=api_function, + op_modifies=True, + params=api_parameters, + ) + + self.log( + "Response received from API call to Function: '{0}' from Family: '{1}' is Response: {2}".format( + api_function, api_family, str(response) + ), + "DEBUG" + ) + + # Process the response if available + response_data = response.get("response") + if not response_data: + self.log( + "No response received from API call to Function: '{0}' from Family: '{1}'.".format( + api_function, api_family + ), "WARNING" + ) + return None + + # Update result and extract task ID + self.result.update(dict(response=response_data)) + task_id = response_data.get("taskId") + self.log( + "Task ID received from API call to Function: '{0}' from Family: '{1}', Task ID: {2}".format( + api_function, api_family, task_id + ), "INFO" + ) + return task_id + + except Exception as e: + # Log an error message and fail if an exception occurs + self.log_traceback() + self.msg = ( + "An error occurred while executing API call to Function: '{0}' from Family: '{1}'. " + "Parameters: {2}. Exception: {3}.".format(api_function, api_family, api_parameters, str(e)) + ) + self.fail_and_exit(self.msg) + + def get_task_status_from_tasks_by_id(self, task_id, task_name, task_params, success_msg): + """ + Retrieves and monitors the status of a task by its task ID. + + This function continuously checks the status of a specified task using its task ID. + If the task completes successfully, it updates the message and status accordingly. + If the task fails or times out, it handles the error and updates the status and message. + + Parameters: + - task_id (str): The unique identifier of the task to monitor. + - task_name (str): The name of the task being monitored. + - task_params (dict): Additional parameters related to the task. + - success_msg (str): The success message to set if the task completes successfully. + + Returns: + - self: The instance of the class with updated status and message. + """ + loop_start_time = time.time() + + while True: + response = self.get_tasks_by_id(task_id) + + # Check if response is returned + if not response: + self.msg = "Error retrieving task status for '{0}' with task_id '{1}'".format(task_name, task_id) + self.set_operation_result("failed", False, self.msg, "ERROR") + break + + status = response.get("status") + end_time = response.get("endTime") + + # Check if the elapsed time exceeds the timeout + if self.check_timeout_and_exit(loop_start_time, task_id, task_name): + break + + # Check if the task has completed (either success or failure) + if end_time: + if status == "FAILURE": + get_task_details_response = self.get_task_details_by_id(task_id) + failure_reason = get_task_details_response.get("failureReason", "Unknown reason") + self.msg = ( + "Task {0} failed with Task ID: {1} for parameters: {2}. " + "Failure reason: {3}".format(task_name, task_id, task_params, failure_reason) + ) + self.set_operation_result("failed", False, self.msg, "ERROR") + break + elif status == "SUCCESS": + self.msg = success_msg + self.set_operation_result("success", True, self.msg, "INFO") + break + + time.sleep(self.params.get("dnac_task_poll_interval")) + return self + def is_list_complex(x): return isinstance(x[0], dict) or isinstance(x[0], list) @@ -1227,7 +1507,7 @@ def validate_str(item, param_spec, param_name, invalid_params): Example `param_spec`: { "type": "str", - "length_max": 255 # Optional: maximum allowed length + "length_max": 255 # Optional: maximum allowed length } """ diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index a8c94847b5..336dc0f456 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -501,41 +501,41 @@ def update_result(self, status, changed, msg, log_level, data=None): return self - # def validate_site_exists(self, site_name): - # """ - # Checks the existence of a site in Cisco Catalyst Center. - # Parameters: - # site_name (str): The name of the site to be checked. - # Returns: - # tuple: A tuple containing two values: - # - site_exists (bool): Indicates whether the site exists (True) or not (False). - # - site_id (str or None): The ID of the site if it exists, or None if the site is not found. - # Description: - # This method queries Cisco Catalyst Center to determine if a site with the provided name exists. - # If the site is found, it sets "site_exists" to True and retrieves the sites ID. - # If the site does not exist, "site_exists" is set to False, and "site_id" is None. - # If an exception occurs during the site lookup, an error message is logged, and the module fails. - # """ - # site_exists = False - # site_id = None - # response = None + def validate_site_exists(self, site_name): + """ + Checks the existence of a site in Cisco Catalyst Center. + Parameters: + site_name (str): The name of the site to be checked. + Returns: + tuple: A tuple containing two values: + - site_exists (bool): Indicates whether the site exists (True) or not (False). + - site_id (str or None): The ID of the site if it exists, or None if the site is not found. + Description: + This method queries Cisco Catalyst Center to determine if a site with the provided name exists. + If the site is found, it sets "site_exists" to True and retrieves the sites ID. + If the site does not exist, "site_exists" is set to False, and "site_id" is None. + If an exception occurs during the site lookup, an error message is logged, and the module fails. + """ + site_exists = False + site_id = None + response = None - # try: - # response = self.get_site(site_name) - # if response is None: - # raise ValueError - # site = response.get("response") - # site_id = site[0].get("id") - # site_exists = True + try: + response = self.get_site(site_name) + if response is None: + raise ValueError + site = response.get("response") + site_id = site[0].get("id") + site_exists = True - # except Exception as e: - # 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() + except Exception as e: + 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) + return (site_exists, site_id) # def get_device_ids_from_site(self, site_name, site_id): # """ @@ -568,7 +568,8 @@ def update_result(self, status, changed, msg, log_level, data=None): # op_modifies=True, # params=site_params, # ) - # self.log("Response received post 'get_membership' API Call: {0}".format(str(response)), "DEBUG") + # self.log(response) + # self.log("Response received post 'get_membership' API Call: {0}".format(str(self.pprint(response))), "DEBUG") # devices = response.get("device", []) # for item in devices: @@ -766,7 +767,7 @@ def get_device_id_list(self, config): 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_ids_from_site(site_name, site_id) + 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) From 33515189f4b14e3d0003982bab0a3d5c916e7804 Mon Sep 17 00:00:00 2001 From: skesali Date: Wed, 25 Sep 2024 21:00:20 +0530 Subject: [PATCH 12/28] Latest changes for the device_configs_backup_workflow_manager --- .../device_configs_backup_workflow_manager.py | 80 ------------------- 1 file changed, 80 deletions(-) diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index 336dc0f456..a9cabcdaa7 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -537,86 +537,6 @@ def validate_site_exists(self, site_name): return (site_exists, site_id) - # def get_device_ids_from_site(self, site_name, site_id): - # """ - # 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 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 their instance UUIDs. - - # Description: - # 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 = {} - - # site_params = { - # "site_id": site_id, - # } - - # try: - # 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) - # self.log("Response received post 'get_membership' API Call: {0}".format(str(self.pprint(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") - - # else: - # response = self.dnac._exec( - # family="site_design", - # function="get_site_assigned_network_devices", - # op_modifies=True, - # params=site_params, - # ) - - # 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: - # self.log("Unable to fetch the device(s) associated with the site '{0}' due to {1}".format(site_name, str(e)), "ERROR") - - # if not mgmt_ip_to_instance_id_map: - # 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) - - # return mgmt_ip_to_instance_id_map - def get_device_list_params(self, config): """ Generates a dictionary of device parameters for querying Cisco Catalyst Center. From 7832fe481c9def63f87bd54aae7f4110cc95bb53 Mon Sep 17 00:00:00 2001 From: Ajith Andrew J Date: Thu, 26 Sep 2024 10:19:12 +0530 Subject: [PATCH 13/28] User and Role bug fixed --- plugins/modules/user_role_workflow_manager.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/modules/user_role_workflow_manager.py b/plugins/modules/user_role_workflow_manager.py index bdf5091f75..4dc7486a4e 100644 --- a/plugins/modules/user_role_workflow_manager.py +++ b/plugins/modules/user_role_workflow_manager.py @@ -1600,15 +1600,18 @@ def create_user(self, user_params): - Returns the API response from the "create_user" function. """ self.log("Create user with 'user_params' argument...", "DEBUG") - decrypt_password_response = self.decrypt_password(user_params['password'], self.key.get("generate_key")) - if decrypt_password_response and "error_message" in decrypt_password_response: - self.msg = decrypt_password_response.get("error_message") - self.log(self.msg, "ERROR") - self.status = "failed" - return self + if user_params.get('password'): + decrypt_password_response = self.decrypt_password(user_params['password'], self.key.get("generate_key")) + + if decrypt_password_response and "error_message" in decrypt_password_response: + self.msg = decrypt_password_response.get("error_message") + self.log(self.msg, "ERROR") + self.status = "failed" + return self + + user_params['password'] = decrypt_password_response.get("decrypt_password") - user_params['password'] = decrypt_password_response.get("decrypt_password") required_keys = ['username', 'password'] missing_keys = [] From fb2c7bc8b8fa362d227aebf8f4e1b4ec746cf0cd Mon Sep 17 00:00:00 2001 From: skesali Date: Thu, 26 Sep 2024 10:29:04 +0530 Subject: [PATCH 14/28] Latest modifications for the device_configs_backup_workflow_manager --- .../device_configs_backup_workflow_manager.py | 94 +++---------------- 1 file changed, 13 insertions(+), 81 deletions(-) diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index a9cabcdaa7..42aa97e79f 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -467,76 +467,6 @@ def validate_input(self): self.status = "success" return self - def update_result(self, status, changed, msg, log_level, data=None): - """ - Update the result of the operation with the provided status, message, and log level. - Parameters: - - status (str): The status of the operation ("success" or "failed"). - - changed (bool): Indicates whether the operation caused changes. - - msg (str): The message describing the result of the operation. - - log_level (str): The log level at which the message should be logged ("INFO", "ERROR", "CRITICAL", etc.). - - data (dict, optional): Additional data related to the operation result. - Returns: - self (object): An instance of the class. - Note: - - If the status is "failed", the "failed" key in the result dictionary will be set to True. - - If data is provided, it will be included in the result dictionary. - """ - # Update the result attributes with the provided values - self.status = status - self.result["status"] = status - self.result["msg"] = msg - self.result["changed"] = changed - - # Log the message at the specified log level - self.log(msg, log_level) - - # If the status is "failed", set the "failed" key to True - if status == "failed": - self.result["failed"] = True - - # If additional data is provided, include it in the result dictionary - if data: - self.result["data"] = data - - return self - - def validate_site_exists(self, site_name): - """ - Checks the existence of a site in Cisco Catalyst Center. - Parameters: - site_name (str): The name of the site to be checked. - Returns: - tuple: A tuple containing two values: - - site_exists (bool): Indicates whether the site exists (True) or not (False). - - site_id (str or None): The ID of the site if it exists, or None if the site is not found. - Description: - This method queries Cisco Catalyst Center to determine if a site with the provided name exists. - If the site is found, it sets "site_exists" to True and retrieves the sites ID. - If the site does not exist, "site_exists" is set to False, and "site_id" is None. - If an exception occurs during the site lookup, an error message is logged, and the module fails. - """ - site_exists = False - site_id = None - response = None - - try: - response = self.get_site(site_name) - if response is None: - raise ValueError - site = response.get("response") - site_id = site[0].get("id") - site_exists = True - - except Exception as e: - 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_list_params(self, config): """ Generates a dictionary of device parameters for querying Cisco Catalyst Center. @@ -841,6 +771,7 @@ def export_device_configurations(self, export_device_configurations_params): task ID of the export operation. If an error occurs, it logs an error message, updates the result, and checks the return status. """ + task_id = self.get_taskid_post_api_call("configuration_archive", "export_device_configurations", export_device_configurations_params) try: # Make an API call to export device configurations response = self.dnac._exec( @@ -868,9 +799,9 @@ def export_device_configurations(self, export_device_configurations_params): "An error occurred while Exporting Device Configurations from the Cisco Catalyst Center. " "export_device_configurations_params: {0} Error: {1}".format(export_device_configurations_params, str(e)) ) - self.update_result("failed", False, self.msg, "ERROR") + 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. @@ -899,6 +830,7 @@ def exit_while_loop(self, start_time, task_id, task_name, response): return False + def download_file(self, additional_status_url=None): """ Downloads a file from Cisco Catalyst Center and stores it locally. @@ -932,7 +864,7 @@ def download_file(self, additional_status_url=None): return None except Exception as e: self.msg = "The Backup Config file with File ID: {0} could not be downloaded due to the following error: {1}".format(file_id, e) - self.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") self.check_return_status() def unzip_data(self, file_id, file_data): @@ -969,7 +901,7 @@ def unzip_data(self, file_id, file_data): return True except Exception as e: self.msg = "Error in unzipping Backup Config file with file ID: {0}. Error: {1}".format(file_id, e) - self.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") self.check_return_status() def get_export_device_config_task_status(self, task_id): @@ -994,7 +926,7 @@ def get_export_device_config_task_status(self, task_id): # 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.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") return self # Check if the elapsed time exceeds the timeout @@ -1010,7 +942,7 @@ def get_export_device_config_task_status(self, task_id): task_name, self.want.get("export_device_configurations_params"), failure_reason ) ) - self.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") return self # Check if task completed successfully and exit the loop @@ -1031,7 +963,7 @@ def get_export_device_config_task_status(self, task_id): 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.update_result("Failed", True, self.msg, "CRITICAL") + self.set_operation_result("Failed", True, self.msg, "CRITICAL") return self # Unzip the downloaded file @@ -1046,10 +978,10 @@ def get_export_device_config_task_status(self, task_id): "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.update_result("success", True, self.msg, "INFO") + 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.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") return self @@ -1087,7 +1019,7 @@ def get_want(self, config): self.log("Retrived the Device ID list based on the provided parameters: {0}".format(mgmt_ip_to_instance_id_map), "DEBUG") if not mgmt_ip_to_instance_id_map: self.msg = "No reachable devices found among the provided parameters: {0}".format(config) - self.update_result("failed", False, self.msg, "WARNING") + self.set_operation_result("failed", False, self.msg, "WARNING") return self self.log("Based on provided parameters, retrieved Device Id(s) of {0} device(s): {1} ".format( @@ -1162,7 +1094,7 @@ def verify_diff_merged(self): "The Device Config Backup operation may not have been successful since the backup files " "were not found at the specified path. Error: {0}".format(str(e)) ) - self.update_result("failed", False, self.msg, "ERROR") + self.set_operation_result("failed", False, self.msg, "ERROR") return self # Check if there are any files modified within the window From 9ec411dce8bd19608194f5b1bd5260d0e14ba3b6 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Thu, 26 Sep 2024 13:12:30 +0530 Subject: [PATCH 15/28] 1st review comments compleated, new feature added and minor bug fix in get_diff_deleted. --- playbooks/provision_workflow_manager.yml | 13 +- plugins/modules/provision_workflow_manager.py | 115 ++++++++---------- 2 files changed, 59 insertions(+), 69 deletions(-) diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index 33c2bae42b..f527a86851 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -1,12 +1,11 @@ ---- -- name: Configure device credentials on Cisco DNA Center +- name: Configure device credentials on Cisco Catalyst Center hosts: localhost connection: local gather_facts: no vars_files: - "credentials.yml" tasks: - - name: Provision a wireless device to a site + - name: Provision a wired device to a site cisco.dnac.provision_workflow_manager: dnac_host: "{{dnac_host}}" dnac_username: "{{dnac_username}}" @@ -16,9 +15,11 @@ dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" dnac_log: True + dnac_log_level: DEBUG + config_verify: True + dnac_api_task_timeout: 1000 + dnac_task_poll_interval: 1 state: merged config: - - site_name_hierarchy: Global/USA/San Jose/BLDG23 - management_ip_address: 2.2.2.2 - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 - management_ip_address: 1.1.1.1 + management_ip_address: 1.1.1.1 \ No newline at end of file diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 794a692894..1aded59644 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -52,6 +52,15 @@ type: bool required: false default: true + force_provisioning: + description: + - Determines whether to force reprovisioning of a device. + - Applicable only for wired devices. + - Set to 'true' to enforce reprovisioning, even if the device is already provisioned. + - Set to 'false' to skip provisioning for devices that are already provisioned. + type: bool + required: false + default: false site_name_hierarchy: description: Name of site where the device needs to be added. type: str @@ -289,7 +298,8 @@ def validate_input(self, state=None): 'elements': 'str'}, "dynamic_interfaces": {'type': 'list', 'required': False, 'elements': 'dict'}, - "provisioning": {'type': 'bool', 'required': False, "default": True} + "provisioning": {'type': 'bool', 'required': False, "default": True}, + "force_provisioning": {'type': 'bool', 'required': False, "default": False} } if state == "merged": provision_spec["site_name_hierarchy"] = {'type': 'str', 'required': True} @@ -456,7 +466,11 @@ def get_task_status(self, task_id=None): self.module.fail_json(msg=msg) return False - if response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] and response.get("isError") is False: + if ( + response.get('progress') in ["TASK_PROVISION", "TASK_MODIFY_PUT"] + and response.get("isError") is False + ) or "deleted successfully" in response.get('progress'): + result = True break @@ -590,41 +604,6 @@ def get_site_type(self, site_name_hierarchy=None): return site_type - def get_site_details(self, site_name_hierarchy=None): - """ - Parameters: - self (object): An instance of a class used for interacting with Cisco Catalyst Center. - Returns: - tuple: A tuple containing two values: - - site_exists (bool): A boolean indicating whether the site exists (True) or not (False). - - site_id (str or None): The ID of the site if it exists, or None if the site is not found. - Description: - This method checks the existence of a site in the Catalyst Center. If the site is found,it sets 'site_exists' to True, - retrieves the site's ID, and returns both values in a tuple. If the site does not exist, 'site_exists' is set - to False, and 'site_id' is None. If an exception occurs during the site lookup, an exception is raised. - """ - - site_exists = False - site_id = None - response = None - - try: - response = self.get_site(site_name_hierarchy) - if response is None: - raise ValueError - site = response.get("response") - site_id = site[0].get("id") - site_exists = True - - except Exception as e: - self.status = "failed" - self.msg = ("An exception occurred: Site '{0}' does not exist in the Cisco Catalyst Center.".format(site_name_hierarchy)) - self.result['response'] = self.msg - self.log(self.msg, "ERROR") - self.check_return_status() - - return (site_exists, site_id) - def is_device_assigned_to_site(self, uuid): """ Checks if a device, specified by its UUID, is assigned to any site. @@ -673,7 +652,7 @@ def get_site_assign(self): """ site_name_hierarchy = self.validated_config.get("site_name_hierarchy") - site_exists, site_id = self.get_site_details(site_name_hierarchy=site_name_hierarchy) + site_exists, site_id = self.get_site_id(site_name_hierarchy) serial_number = self.get_serial_number() if site_exists: site_response = self.dnac_apply['exec']( @@ -715,7 +694,7 @@ def get_wired_params(self): self.log(self.validated_config) site_name = self.validated_config.get("site_name_hierarchy") - (site_exits, site_id) = self.get_site_details(site_name_hierarchy=site_name) + (site_exits, site_id) = self.get_site_id(site_name) if site_exits is False: msg = "Site {0} doesn't exist".format(site_name) @@ -908,35 +887,45 @@ class instance for further use. self.log("The provisioned status of the wired device is {0}".format(status), "INFO") if status == "success": - if self.validated_config.get("provisioning") is True: - try: - response = self.dnac_apply['exec']( - family="sda", - function="re_provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") - return self - - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) + if self.validated_config.get("force_provisioning") is True: + if self.validated_config.get("provisioning") is True: + try: + response = self.dnac_apply['exec']( + family="sda", + function="re_provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Re-Provision done Successfully" + self.result['diff'] = self.validated_config + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + return self + + except Exception as e: + self.msg = "Error in re-provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self + else: + self.msg = ("Cannot assign a provisioned device to the site. " + "Unprovision the device and then try assigning it to the site.") self.log(self.msg, "ERROR") self.status = "failed" return self else: - self.msg = ("Cannot assign a provisioned device to the site. " - "Unprovision the device and then try assigning it to the site.") - self.log(self.msg, "ERROR") - self.status = "failed" + self.result["changed"] = False + msg = "Device '{0}' is already provisioned.".format(self.validated_config.get("management_ip_address")) + self.result['msg'] = msg + self.result['diff'] = self.want + self.result['response'] = msg + self.log(msg, "INFO") return self + else: if self.validated_config.get("provisioning") is True: try: From c150047029b4455ae238e1d83637a60e1b3b6651 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Thu, 26 Sep 2024 13:15:56 +0530 Subject: [PATCH 16/28] 1st review comments compleated, example added for force provisioning --- plugins/modules/provision_workflow_manager.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 1aded59644..99813fc2be 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -159,6 +159,22 @@ - site_name_hierarchy: Global/USA/San Francisco/BGL_18 management_ip_address: 204.192.3.40 +- name: Re-Provision a wired device to a site forcefully + cisco.dnac.provision_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: True + state: merged + config: + - site_name_hierarchy: Global/USA/San Francisco/BGL_18 + management_ip_address: 204.192.3.40 + force_provisioning: True + - name: Assign a wired device to a site cisco.dnac.provision_workflow_manager: dnac_host: "{{dnac_host}}" From a68ccd7fb1c4e7168bb662b81351aafc8146ed53 Mon Sep 17 00:00:00 2001 From: skesali Date: Thu, 26 Sep 2024 16:53:14 +0530 Subject: [PATCH 17/28] Sanity check for the dnac.py and device backup files --- plugins/module_utils/dnac.py | 7 +++---- plugins/modules/device_configs_backup_workflow_manager.py | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/module_utils/dnac.py b/plugins/module_utils/dnac.py index 0a66b35df0..27ae7a7ed0 100644 --- a/plugins/module_utils/dnac.py +++ b/plugins/module_utils/dnac.py @@ -557,7 +557,7 @@ def check_string_dictionary(self, task_details_data): except json.JSONDecodeError: pass return None - + def get_device_ip_from_device_id(self, site_id): """ Retrieve the management IP addresses and their corresponding instance UUIDs of devices associated with a specific site in Cisco Catalyst Center. @@ -611,7 +611,7 @@ def get_device_ids_from_site(self, site_id): site_id (str): The unique identifier of the site. Returns: - list: A list of device IDs associated with the site. + list: A list of device IDs associated with the site. Returns an empty list if no devices are found or if an error occurs. """ @@ -1329,7 +1329,7 @@ def get_taskid_post_api_call(self, api_family, api_function, api_parameters): try: self.log("Requested payload for the the function: '{0}' is: '{1}'".format(api_function, api_parameters), "INFO") - + # Execute the API call response = self.dnac._exec( family=api_family, @@ -1374,7 +1374,6 @@ def get_taskid_post_api_call(self, api_family, api_function, api_parameters): ) self.fail_and_exit(self.msg) - def get_task_status_from_tasks_by_id(self, task_id, task_name, success_msg): """ Retrieves and monitors the status of a task by its task ID. diff --git a/plugins/modules/device_configs_backup_workflow_manager.py b/plugins/modules/device_configs_backup_workflow_manager.py index 42aa97e79f..3011ffc3b2 100644 --- a/plugins/modules/device_configs_backup_workflow_manager.py +++ b/plugins/modules/device_configs_backup_workflow_manager.py @@ -801,7 +801,7 @@ 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. @@ -830,7 +830,6 @@ def exit_while_loop(self, start_time, task_id, task_name, response): return False - def download_file(self, additional_status_url=None): """ Downloads a file from Cisco Catalyst Center and stores it locally. From 2a544682825c804b4586eb96802610a4ea7a33db Mon Sep 17 00:00:00 2001 From: Ajith Andrew J Date: Thu, 26 Sep 2024 22:18:30 +0530 Subject: [PATCH 18/28] User and Role comments fixed --- plugins/module_utils/dnac.py | 4 +++- plugins/modules/user_role_workflow_manager.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/dnac.py b/plugins/module_utils/dnac.py index 1299a59aec..5cdd9536cd 100644 --- a/plugins/module_utils/dnac.py +++ b/plugins/module_utils/dnac.py @@ -695,8 +695,10 @@ def decrypt_password(self, encrypted_password, key): """ try: fernet = Fernet(key) - decrypted_password = fernet.decrypt(encrypted_password.encode()).decode() + decrypted_password = fernet.decrypt(encrypted_password).decode() return {"decrypt_password": decrypted_password} + except Exception.InvalidToken: + return {"error_message": "Invalid decryption token."} except Exception as e: return {"error_message": "Exception occurred while decrypting password: {0}".format(e)} diff --git a/plugins/modules/user_role_workflow_manager.py b/plugins/modules/user_role_workflow_manager.py index 4dc7486a4e..0a3fdc5730 100644 --- a/plugins/modules/user_role_workflow_manager.py +++ b/plugins/modules/user_role_workflow_manager.py @@ -1604,7 +1604,7 @@ def create_user(self, user_params): if user_params.get('password'): decrypt_password_response = self.decrypt_password(user_params['password'], self.key.get("generate_key")) - if decrypt_password_response and "error_message" in decrypt_password_response: + if "error_message" in decrypt_password_response: self.msg = decrypt_password_response.get("error_message") self.log(self.msg, "ERROR") self.status = "failed" From aabd8ab4846ad9111c88d37d3e6b60d65f03b13a Mon Sep 17 00:00:00 2001 From: MUTHU-RAKESH-27 <19cs127@psgitech.ac.in> Date: Thu, 26 Sep 2024 23:26:50 +0530 Subject: [PATCH 19/28] Changed the pan_address and primary_server_address key value for ISE servers --- .../network_settings_workflow_manager.py | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/plugins/modules/network_settings_workflow_manager.py b/plugins/modules/network_settings_workflow_manager.py index 283647273a..8667fee2db 100644 --- a/plugins/modules/network_settings_workflow_manager.py +++ b/plugins/modules/network_settings_workflow_manager.py @@ -674,7 +674,7 @@ def validate_input(self): None Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ @@ -2030,26 +2030,29 @@ def get_want_network(self, network_management_details): return self primary_server_address = network_aaa.get("primary_server_address") - if primary_server_address: - want_network_settings.get("network_aaa").update({ - "network": primary_server_address - }) - else: + if not primary_server_address: self.msg = "Missing required parameter 'primary_server_address' in network_aaa." self.status = "failed" return self if server_type == "ISE": + want_network_settings.get("network_aaa").update({ + "ipAddress": primary_server_address + }) pan_address = network_aaa.get("pan_address") if pan_address: want_network_settings.get("network_aaa").update({ - "ipAddress": pan_address + "network": pan_address }) else: self.msg = "Missing required parameter 'pan_address' for ISE server in network_aaa." self.status = "failed" return self + else: + want_network_settings.get("network_aaa").update({ + "network": primary_server_address + }) secondary_server_address = network_aaa.get("secondary_server_address") if secondary_server_address: want_network_settings.get("network_aaa").update({ @@ -2104,26 +2107,28 @@ def get_want_network(self, network_management_details): return self primary_server_address = client_and_endpoint_aaa.get("primary_server_address") - if primary_server_address: - want_network_settings.get("clientAndEndpoint_aaa").update({ - "network": primary_server_address - }) - else: + if not primary_server_address: self.msg = "Missing required parameter 'primary_server_address' in client_and_endpoint_aaa." self.status = "failed" return self if server_type == "ISE": + want_network_settings.get("clientAndEndpoint_aaa").update({ + "ipAddress": primary_server_address + }) pan_address = client_and_endpoint_aaa.get("pan_address") if pan_address: want_network_settings.get("clientAndEndpoint_aaa").update({ - "ipAddress": pan_address + "network": pan_address }) else: self.msg = "Missing required parameter 'pan_address' for ISE server in client_and_endpoint_aaa." self.status = "failed" return self else: + want_network_settings.get("clientAndEndpoint_aaa").update({ + "network": primary_server_address + }) secondary_server_address = client_and_endpoint_aaa.get("secondary_server_address") if secondary_server_address: want_network_settings.get("clientAndEndpoint_aaa").update({ @@ -2187,7 +2192,7 @@ def get_want(self, config): config (list of dict) - Playbook details Returns: - None + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ if config.get("global_pool_details"): @@ -2215,7 +2220,7 @@ def update_global_pool(self, global_pool): global_pool (list of dict) - Global Pool playbook details Returns: - None + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ create_global_pool = [] @@ -2319,7 +2324,7 @@ def update_global_pool(self, global_pool): result_global_pool.get("msg").update({name: "Global Pool Updated Successfully"}) self.log("Global pool configuration operations completed successfully.", "INFO") - return + return self def update_reserve_pool(self, reserve_pool): """ @@ -2331,7 +2336,7 @@ def update_reserve_pool(self, reserve_pool): reserve_pool (list of dict) - Playbook details containing Reserve Pool information. Returns: - None + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ reserve_pool_index = -1 @@ -2421,7 +2426,7 @@ def update_reserve_pool(self, reserve_pool): .update({name: "Reserved Ip Subpool updated successfully."}) self.log("Updated reserved IP subpool successfully", "INFO") - return + return self def update_network(self, network_management): """ @@ -2432,7 +2437,7 @@ def update_network(self, network_management): network_management (list of dict) - Playbook details containing Network Management information. Returns: - None + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ network_management_index = 0 for item in network_management: @@ -2490,7 +2495,7 @@ def update_network(self, network_management): .update({"Network Details": self.want.get("wantNetwork")[network_management_index].get("settings")}) network_management_index += 1 - return + return self def get_diff_merged(self, config): """ @@ -2502,20 +2507,20 @@ def get_diff_merged(self, config): Global Pool, Reserve Pool, and Network Management information. Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ global_pool = config.get("global_pool_details") if global_pool is not None: - self.update_global_pool(global_pool) + self.update_global_pool(global_pool).check_return_status() reserve_pool = config.get("reserve_pool_details") if reserve_pool is not None: - self.update_reserve_pool(reserve_pool) + self.update_reserve_pool(reserve_pool).check_return_status() network_management = config.get("network_management_details") if network_management is not None: - self.update_network(network_management) + self.update_network(network_management).check_return_status() return self @@ -2527,7 +2532,7 @@ def delete_reserve_pool(self, reserve_pool_details): reserve_pool_details (list of dict) - Reserverd pool playbook details. Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ reserve_pool_index = -1 @@ -2583,7 +2588,7 @@ def delete_global_pool(self, global_pool_details): global_pool_details (dict) - Global pool details of the playbook Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ result_global_pool = self.result.get("response")[0].get("globalPool") @@ -2636,7 +2641,7 @@ def get_diff_deleted(self, config): config (list of dict) - Playbook details Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ reserve_pool_details = config.get("reserve_pool_details") @@ -2659,7 +2664,7 @@ def verify_diff_merged(self, config): Reserved Pool, and Network Management configuration. Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ self.all_reserved_pool_details = {} @@ -2742,7 +2747,7 @@ def verify_diff_deleted(self, config): Reserved Pool, and Network Management configuration. Returns: - self + self - The current object with Global Pool, Reserved Pool, Network Servers information. """ self.all_reserved_pool_details = {} From 035af97c9ead2769295a9f7e23c384e17cd75691 Mon Sep 17 00:00:00 2001 From: MUTHU-RAKESH-27 <19cs127@psgitech.ac.in> Date: Fri, 27 Sep 2024 12:29:19 +0530 Subject: [PATCH 20/28] Checked the project name for verifying the absence of the template --- plugins/modules/template_workflow_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/template_workflow_manager.py b/plugins/modules/template_workflow_manager.py index a4aa9fef7d..5bc116a126 100644 --- a/plugins/modules/template_workflow_manager.py +++ b/plugins/modules/template_workflow_manager.py @@ -2613,7 +2613,7 @@ def verify_diff_deleted(self, config): family="configuration_templates", function="gets_the_templates_available", op_modifies=True, - params={"projectNames": config.get("projectName")}, + params={"projectNames": config.get("configuration_templates").get("project_name")}, ) if template_list and isinstance(template_list, list): templateName = config.get("configuration_templates").get("template_name") From e26788b9ea516d385ab1ed004f16a8327626af53 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 28 Sep 2024 11:17:41 +0530 Subject: [PATCH 21/28] 2ed Review comments compleated --- plugins/modules/provision_workflow_manager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 99813fc2be..51ec062929 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -886,6 +886,8 @@ class instance for further use. """ device_type = self.want.get("device_type") + to_force_provisioning = self.validated_config.get("force_provisioning") + to_provisioning = self.validated_config.get("provisioning") if device_type == "wired": try: status_response = self.dnac_apply['exec']( @@ -903,8 +905,8 @@ class instance for further use. self.log("The provisioned status of the wired device is {0}".format(status), "INFO") if status == "success": - if self.validated_config.get("force_provisioning") is True: - if self.validated_config.get("provisioning") is True: + if to_force_provisioning is True: + if to_provisioning is True: try: response = self.dnac_apply['exec']( family="sda", @@ -943,7 +945,7 @@ class instance for further use. return self else: - if self.validated_config.get("provisioning") is True: + if to_provisioning is True: try: response = self.dnac_apply['exec']( family="sda", From 4abee63528b0b5f4eb089ec7db3cb5e9f7282d0c Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 28 Sep 2024 13:32:56 +0530 Subject: [PATCH 22/28] 2ed Review comments compleated --- playbooks/provision_workflow_manager.yml | 4 +-- plugins/modules/provision_workflow_manager.py | 30 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index f527a86851..d4b10022aa 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -14,9 +14,9 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true dnac_log_level: DEBUG - config_verify: True + config_verify: true dnac_api_task_timeout: 1000 dnac_task_poll_interval: 1 state: merged diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 51ec062929..3923a3dbce 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -25,9 +25,9 @@ Ajith Andrew J (@ajithandrewj) options: config_verify: - description: Set to True to verify the Cisco Catalyst Center config after applying the playbook config. + description: Set to true to verify the Cisco Catalyst Center config after applying the playbook config. type: bool - default: False + default: false state: description: The state of Cisco Catalyst Center after module completion. type: str @@ -47,14 +47,15 @@ provisioning: description: - Specifies whether the user intends to perform site assignment only or full provisioning for a wired device. - - Set to 'False' to carry out site assignment only. - - Set to 'True' to proceed with provisioning to a site. + - Set to 'false' to carry out site assignment only. + - Set to 'true' to proceed with provisioning to a site. type: bool required: false default: true force_provisioning: description: - Determines whether to force reprovisioning of a device. + - Note that reprovisioning cannot change the device's site assignment. - Applicable only for wired devices. - Set to 'true' to enforce reprovisioning, even if the device is already provisioned. - Set to 'false' to skip provisioning for devices that are already provisioned. @@ -64,6 +65,7 @@ site_name_hierarchy: description: Name of site where the device needs to be added. type: str + required: true managed_ap_locations: description: - Location of the sites allocated for the APs. @@ -131,7 +133,7 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: merged config: - site_name_hierarchy: Global/USA/San Francisco/BGL_18 @@ -153,7 +155,7 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: merged config: - site_name_hierarchy: Global/USA/San Francisco/BGL_18 @@ -168,12 +170,12 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: merged config: - site_name_hierarchy: Global/USA/San Francisco/BGL_18 management_ip_address: 204.192.3.40 - force_provisioning: True + force_provisioning: true - name: Assign a wired device to a site cisco.dnac.provision_workflow_manager: @@ -184,12 +186,12 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: merged config: - site_name_hierarchy: Global/USA/San Francisco/BGL_18 management_ip_address: 204.192.3.40 - provisioning: False + provisioning: false - name: Provision a wireless device to a site cisco.dnac.provision_workflow_manager: @@ -200,9 +202,9 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: merged - config_verify: True + config_verify: true config: - site_name_hierarchy: Global/USA/RTP/BLD11 management_ip_address: 204.192.12.201 @@ -218,9 +220,9 @@ dnac_port: "{{dnac_port}}" dnac_version: "{{dnac_version}}" dnac_debug: "{{dnac_debug}}" - dnac_log: True + dnac_log: true state: deleted - config_verify: True + config_verify: true config: - management_ip_address: 204.1.2.2 From f90c3ecb672e0913eec8c52c33748dea7a5bef36 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 28 Sep 2024 14:26:59 +0530 Subject: [PATCH 23/28] 2ed Review comments compleated --- playbooks/dnac.log | 124 ++++++++++++++++++ playbooks/provision_workflow_manager.yml | 2 +- plugins/modules/provision_workflow_manager.py | 98 +++++++------- 3 files changed, 173 insertions(+), 51 deletions(-) diff --git a/playbooks/dnac.log b/playbooks/dnac.log index e69de29bb2..e2e8605f81 100644 --- a/playbooks/dnac.log +++ b/playbooks/dnac.log @@ -0,0 +1,124 @@ +09-28-2024 14:19:24 DEBUG Provision: __init__: 110: Logging configured and initiated + +09-28-2024 14:19:24 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} + +09-28-2024 14:19:24 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': False, 'force_provisioning': True}] + +09-28-2024 14:19:24 DEBUG Provision: check_return_status: 271: Line No: 1238 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': False, 'force_provisioning': True}] + +09-28-2024 14:19:25 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1327652, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} + +09-28-2024 14:19:25 INFO Provision: get_dev_type: 378: The device type is wired + +09-28-2024 14:19:26 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:19:26 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:19:26 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'device': [{'ip': '204.192.3.40'}], 'site_id': '03072c33-bd11-4914-9c0e-3c53379b2813'} + +09-28-2024 14:19:26 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:19:26 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:19:27 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-28-2024 14:19:27 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success + +09-28-2024 14:19:27 ERROR Provision: get_diff_merged: 936: Cannot assign a provisioned device to the site. Unprovision the device and then try assigning it to the site. + +09-28-2024 14:19:27 DEBUG Provision: check_return_status: 271: Line No: 1243 status: failed, msg: Cannot assign a provisioned device to the site. Unprovision the device and then try assigning it to the site. + +09-28-2024 14:26:15 DEBUG Provision: __init__: 110: Logging configured and initiated + +09-28-2024 14:26:15 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} + +09-28-2024 14:26:15 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': True}] + +09-28-2024 14:26:15 DEBUG Provision: check_return_status: 271: Line No: 1237 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': True}] + +09-28-2024 14:26:16 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328063, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} + +09-28-2024 14:26:16 INFO Provision: get_dev_type: 378: The device type is wired + +09-28-2024 14:26:16 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:26:16 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:26:16 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'} + +09-28-2024 14:26:16 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:16 DEBUG Provision: check_return_status: 271: Line No: 1241 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:17 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-28-2024 14:26:17 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success + +09-28-2024 14:26:28 DEBUG Provision: get_diff_merged: 918: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '019237d8-57d4-72e5-904f-ef068c9b1f10', 'taskStatusUrl': '/dna/intent/api/v1/task/019237d8-57d4-72e5-904f-ef068c9b1f10', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/717808b3-0f10-486d-90ae-74364b8379d2', 'executionId': '717808b3-0f10-486d-90ae-74364b8379d2'} + +09-28-2024 14:26:29 DEBUG Provision: get_task_status: 476: Response collected from 'get_task_by_id' API is {'response': {'version': 1727513778437, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1727513778132, 'data': 'workflow_id=56f8ffeb-f118-4c46-8e0e-506d6030b7b6;cfs_id=0;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=false', 'lastUpdate': 1727513778437, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '019237d8-57d4-72e5-904f-ef068c9b1f10'}, 'version': '1.0'} + +09-28-2024 14:26:29 INFO Provision: get_task_status: 478: Task status for the task id 019237d8-57d4-72e5-904f-ef068c9b1f10 is TASK_MODIFY_PUT + +09-28-2024 14:26:29 INFO Provision: get_diff_merged: 925: Re-Provision done Successfully + +09-28-2024 14:26:29 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:29 INFO Provision: verify_diff_merged: 1113: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'active_validation': False}} + +09-28-2024 14:26:29 DEBUG Provision: get_device_id: 402: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328076, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} + +09-28-2024 14:26:29 INFO Provision: get_device_id: 406: Device ID of the device with IP address 204.192.3.40 is 1e8ffc57-8a44-459f-b880-6b49d6b63aa4 + +09-28-2024 14:26:30 DEBUG Provision: verify_diff_merged: 1139: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-28-2024 14:26:30 INFO Provision: verify_diff_merged: 1141: The provisioned status of the wired device is success + +09-28-2024 14:26:30 INFO Provision: verify_diff_merged: 1144: Requested wired device is alread provisioned + +09-28-2024 14:26:30 DEBUG Provision: check_return_status: 271: Line No: 1244 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:40 DEBUG Provision: __init__: 110: Logging configured and initiated + +09-28-2024 14:26:40 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} + +09-28-2024 14:26:40 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': False}] + +09-28-2024 14:26:40 DEBUG Provision: check_return_status: 271: Line No: 1237 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': False}] + +09-28-2024 14:26:41 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328088, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} + +09-28-2024 14:26:41 INFO Provision: get_dev_type: 378: The device type is wired + +09-28-2024 14:26:41 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:26:41 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} + +09-28-2024 14:26:41 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'} + +09-28-2024 14:26:41 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:41 DEBUG Provision: check_return_status: 271: Line No: 1241 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:42 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-28-2024 14:26:42 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success + +09-28-2024 14:26:42 INFO Provision: get_diff_merged: 945: Device '204.192.3.40' is already provisioned. + +09-28-2024 14:26:42 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison + +09-28-2024 14:26:42 INFO Provision: verify_diff_merged: 1113: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}} + +09-28-2024 14:26:42 DEBUG Provision: get_device_id: 402: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328089, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} + +09-28-2024 14:26:42 INFO Provision: get_device_id: 406: Device ID of the device with IP address 204.192.3.40 is 1e8ffc57-8a44-459f-b880-6b49d6b63aa4 + +09-28-2024 14:26:43 DEBUG Provision: verify_diff_merged: 1139: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} + +09-28-2024 14:26:43 INFO Provision: verify_diff_merged: 1141: The provisioned status of the wired device is success + +09-28-2024 14:26:43 INFO Provision: verify_diff_merged: 1144: Requested wired device is alread provisioned + +09-28-2024 14:26:43 DEBUG Provision: check_return_status: 271: Line No: 1244 status: success, msg: Successfully collected all parameters from playbook for comparison + diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index d4b10022aa..34156ae7eb 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -22,4 +22,4 @@ state: merged config: - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 - management_ip_address: 1.1.1.1 \ No newline at end of file + management_ip_address: 204.192.3.40 diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 3923a3dbce..09999cbdbe 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -708,8 +708,7 @@ def get_wired_params(self): paramters and stores it for further processing and calling the parameters in other APIs. """ - self.log("self.validated_config") - self.log(self.validated_config) + site_name = self.validated_config.get("site_name_hierarchy") (site_exits, site_id) = self.get_site_id(site_name) @@ -946,56 +945,55 @@ class instance for further use. self.log(msg, "INFO") return self - else: - if to_provisioning is True: - try: - response = self.dnac_apply['exec']( - family="sda", - function="provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") - except Exception as e: - self.msg = "Error in provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self + if to_provisioning is True: + try: + response = self.dnac_apply['exec']( + family="sda", + function="provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") + except Exception as e: + self.msg = "Error in provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self - else: - uuid = self.get_device_id() - if self.is_device_assigned_to_site(uuid) is True: - self.result["changed"] = False - self.result['msg'] = "Device is already assigned to the desired site" - self.result['diff'] = self.want - self.result['response'] = self.want.get("prov_params").get("site_id") - self.log(self.result['msg'], "INFO") - return self + else: + uuid = self.get_device_id() + if self.is_device_assigned_to_site(uuid) is True: + self.result["changed"] = False + self.result['msg'] = "Device is already assigned to the desired site" + self.result['diff'] = self.want + self.result['response'] = self.want.get("prov_params").get("site_id") + self.log(self.result['msg'], "INFO") + return self - try: - response = self.dnac_apply['exec']( - family="sites", - function="assign_devices_to_site", - op_modifies=True, - params={ - "site_id": self.want.get("prov_params").get("site_id"), - "payload": self.want.get("prov_params") - }, - ) - self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") - execution_id = response.get("executionId") - assignment_info = self.get_execution_status_site(execution_id=execution_id) - self.result["changed"] = True - self.result['msg'] = "Site assignment done successfully" - self.result['diff'] = self.validated_config - self.result['response'] = execution_id - self.log(self.result['msg'], "INFO") - return self - except Exception as e: - self.msg = "Error in site assignment due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self + try: + response = self.dnac_apply['exec']( + family="sites", + function="assign_devices_to_site", + op_modifies=True, + params={ + "site_id": self.want.get("prov_params").get("site_id"), + "payload": self.want.get("prov_params") + }, + ) + self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") + execution_id = response.get("executionId") + assignment_info = self.get_execution_status_site(execution_id=execution_id) + self.result["changed"] = True + self.result['msg'] = "Site assignment done successfully" + self.result['diff'] = self.validated_config + self.result['response'] = execution_id + self.log(self.result['msg'], "INFO") + return self + except Exception as e: + self.msg = "Error in site assignment due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self elif device_type == "wireless": try: From 1d8822e4bf1164ab833863c3762674543b70a269 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 28 Sep 2024 14:27:22 +0530 Subject: [PATCH 24/28] 2ed Review comments compleated --- playbooks/dnac.log | 124 --------------------------------------------- 1 file changed, 124 deletions(-) diff --git a/playbooks/dnac.log b/playbooks/dnac.log index e2e8605f81..e69de29bb2 100644 --- a/playbooks/dnac.log +++ b/playbooks/dnac.log @@ -1,124 +0,0 @@ -09-28-2024 14:19:24 DEBUG Provision: __init__: 110: Logging configured and initiated - -09-28-2024 14:19:24 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} - -09-28-2024 14:19:24 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': False, 'force_provisioning': True}] - -09-28-2024 14:19:24 DEBUG Provision: check_return_status: 271: Line No: 1238 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': False, 'force_provisioning': True}] - -09-28-2024 14:19:25 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1327652, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} - -09-28-2024 14:19:25 INFO Provision: get_dev_type: 378: The device type is wired - -09-28-2024 14:19:26 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:19:26 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:19:26 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'device': [{'ip': '204.192.3.40'}], 'site_id': '03072c33-bd11-4914-9c0e-3c53379b2813'} - -09-28-2024 14:19:26 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:19:26 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:19:27 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-28-2024 14:19:27 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success - -09-28-2024 14:19:27 ERROR Provision: get_diff_merged: 936: Cannot assign a provisioned device to the site. Unprovision the device and then try assigning it to the site. - -09-28-2024 14:19:27 DEBUG Provision: check_return_status: 271: Line No: 1243 status: failed, msg: Cannot assign a provisioned device to the site. Unprovision the device and then try assigning it to the site. - -09-28-2024 14:26:15 DEBUG Provision: __init__: 110: Logging configured and initiated - -09-28-2024 14:26:15 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} - -09-28-2024 14:26:15 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': True}] - -09-28-2024 14:26:15 DEBUG Provision: check_return_status: 271: Line No: 1237 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': True}] - -09-28-2024 14:26:16 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328063, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} - -09-28-2024 14:26:16 INFO Provision: get_dev_type: 378: The device type is wired - -09-28-2024 14:26:16 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:26:16 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:26:16 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'} - -09-28-2024 14:26:16 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:16 DEBUG Provision: check_return_status: 271: Line No: 1241 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:17 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-28-2024 14:26:17 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success - -09-28-2024 14:26:28 DEBUG Provision: get_diff_merged: 918: Reprovisioning response collected from 're_provision_wired_device' API is: {'status': 'pending', 'description': 'API execution is in progress.', 'taskId': '019237d8-57d4-72e5-904f-ef068c9b1f10', 'taskStatusUrl': '/dna/intent/api/v1/task/019237d8-57d4-72e5-904f-ef068c9b1f10', 'executionStatusUrl': '/dna/intent/api/v1/dnacaap/management/execution-status/717808b3-0f10-486d-90ae-74364b8379d2', 'executionId': '717808b3-0f10-486d-90ae-74364b8379d2'} - -09-28-2024 14:26:29 DEBUG Provision: get_task_status: 476: Response collected from 'get_task_by_id' API is {'response': {'version': 1727513778437, 'progress': 'TASK_MODIFY_PUT', 'startTime': 1727513778132, 'data': 'workflow_id=56f8ffeb-f118-4c46-8e0e-506d6030b7b6;cfs_id=0;rollback_status=not_supported;rollback_taskid=0;failure_task=NA;processcfs_complete=false', 'lastUpdate': 1727513778437, 'serviceType': 'NCSP', 'isError': False, 'instanceTenantId': '66e48af26fe687300375675e', 'id': '019237d8-57d4-72e5-904f-ef068c9b1f10'}, 'version': '1.0'} - -09-28-2024 14:26:29 INFO Provision: get_task_status: 478: Task status for the task id 019237d8-57d4-72e5-904f-ef068c9b1f10 is TASK_MODIFY_PUT - -09-28-2024 14:26:29 INFO Provision: get_diff_merged: 925: Re-Provision done Successfully - -09-28-2024 14:26:29 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:29 INFO Provision: verify_diff_merged: 1113: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'active_validation': False}} - -09-28-2024 14:26:29 DEBUG Provision: get_device_id: 402: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328076, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} - -09-28-2024 14:26:29 INFO Provision: get_device_id: 406: Device ID of the device with IP address 204.192.3.40 is 1e8ffc57-8a44-459f-b880-6b49d6b63aa4 - -09-28-2024 14:26:30 DEBUG Provision: verify_diff_merged: 1139: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-28-2024 14:26:30 INFO Provision: verify_diff_merged: 1141: The provisioned status of the wired device is success - -09-28-2024 14:26:30 INFO Provision: verify_diff_merged: 1144: Requested wired device is alread provisioned - -09-28-2024 14:26:30 DEBUG Provision: check_return_status: 271: Line No: 1244 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:40 DEBUG Provision: __init__: 110: Logging configured and initiated - -09-28-2024 14:26:40 DEBUG Provision: __init__: 116: Cisco Catalyst Center parameters: {'dnac_host': '172.23.241.186', 'dnac_port': '443', 'dnac_username': 'admin', 'dnac_password': 'Maglev123', 'dnac_version': '2.3.5.3', 'dnac_verify': False, 'dnac_debug': True, 'dnac_log': True, 'dnac_log_level': 'DEBUG', 'dnac_log_file_path': 'dnac.log', 'dnac_log_append': True} - -09-28-2024 14:26:40 INFO Provision: validate_input: 338: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': False}] - -09-28-2024 14:26:40 DEBUG Provision: check_return_status: 271: Line No: 1237 status: success, msg: Successfully validated playbook configuration parameters using 'validate_input': [{'management_ip_address': '204.192.3.40', 'site_name_hierarchy': 'Global/Chennai/LTTS/FLOOR1', 'managed_ap_locations': None, 'dynamic_interfaces': None, 'provisioning': True, 'force_provisioning': False}] - -09-28-2024 14:26:41 DEBUG Provision: get_dev_type: 368: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328088, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} - -09-28-2024 14:26:41 INFO Provision: get_dev_type: 378: The device type is wired - -09-28-2024 14:26:41 DEBUG Provision: get_site: 725: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:26:41 DEBUG Provision: get_site_id: 684: Received API response from 'get_site': {'response': [{'parentId': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'additionalInfo': [{'nameSpace': 'mapsSummary', 'attributes': {'rfModel': '106106', 'floorIndex': '1'}}, {'nameSpace': 'UMBRELLA', 'attributes': {'umbrellaReady': 'true', 'member.umbrellaNotReady.direct': '0', 'member.umbrellaReady.direct': '2', 'member.umbrellaReadyNotEnabled.direct': '2', 'member.umbrellaEnabled.direct': '0'}}, {'nameSpace': 'mapGeometry', 'attributes': {'offsetX': '0.0', 'offsetY': '0.0', 'length': '100.0', 'width': '100.0', 'height': '10.0'}}, {'nameSpace': 'ETA', 'attributes': {'member.compatibleWithNaasOnly.direct': '0', 'member.etaCapable.direct': '1', 'member.etaReady.direct': '1', 'member.etaEnabledNaasOnly.direct': '0', 'ETAReady': 'true', 'member.etaNotReady.direct': '0', 'member.etaReadyNotEnabled.direct': '1', 'member.etaEnabled.direct': '0'}}, {'nameSpace': 'Location', 'attributes': {'address': '164692, Ценогора, Arkhangelsk, Russia', 'addressInheritedFrom': 'c3293908-c136-45b9-b74f-d5dfaabb26a9', 'type': 'floor'}}], 'name': 'FLOOR1', 'instanceTenantId': '66e48af26fe687300375675e', 'id': '03072c33-bd11-4914-9c0e-3c53379b2813', 'siteHierarchy': '50f15f14-4c73-47a7-9dc3-cb10eb9508bd/b0292a67-36ff-49ad-9135-5e79b056019e/c3293908-c136-45b9-b74f-d5dfaabb26a9/03072c33-bd11-4914-9c0e-3c53379b2813', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}]} - -09-28-2024 14:26:41 INFO Provision: get_wired_params: 736: Parameters collected for the provisioning of wired device:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'} - -09-28-2024 14:26:41 INFO Provision: get_want: 829: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:41 DEBUG Provision: check_return_status: 271: Line No: 1241 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:42 DEBUG Provision: get_diff_merged: 904: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-28-2024 14:26:42 INFO Provision: get_diff_merged: 906: The provisioned status of the wired device is success - -09-28-2024 14:26:42 INFO Provision: get_diff_merged: 945: Device '204.192.3.40' is already provisioned. - -09-28-2024 14:26:42 DEBUG Provision: check_return_status: 271: Line No: 1242 status: success, msg: Successfully collected all parameters from playbook for comparison - -09-28-2024 14:26:42 INFO Provision: verify_diff_merged: 1113: Desired State (want): {'device_type': 'wired', 'prov_params': {'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1'}} - -09-28-2024 14:26:42 DEBUG Provision: get_device_id: 402: The device response from 'get_network_device_by_ip' API is {'response': {'memorySize': 'NA', 'family': 'Switches and Hubs', 'description': 'Cisco IOS Software [Dublin], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.14.1, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2024 by Cisco Systems, Inc. Compiled Fri 05-Apr-24 00:08 by mcpre', 'lastUpdateTime': 1727508593707, 'macAddress': 'd4:ad:bd:c1:67:00', 'deviceSupportLevel': 'Supported', 'softwareType': 'IOS-XE', 'softwareVersion': '17.14.1', 'serialNumber': 'FJC2327U0S2', 'collectionInterval': 'Global Default', 'dnsResolvedManagementAddress': '204.192.3.40', 'lastManagedResyncReasons': 'Config Change Event', 'managementState': 'Managed', 'pendingSyncRequestsCount': '0', 'reasonsForDeviceResync': 'Config Change Event', 'reasonsForPendingSyncRequests': '', 'syncRequestedByApp': '', 'inventoryStatusDetail': '', 'upTime': '15 days, 7:28:13.23', 'roleSource': 'AUTO', 'interfaceCount': '0', 'bootDateTime': '2024-09-13 00:01:53', 'reachabilityFailureReason': '', 'reachabilityStatus': 'Reachable', 'series': 'Cisco Catalyst 9300 Series Switches', 'snmpContact': '', 'snmpLocation': '', 'apManagerInterfaceIp': '', 'collectionStatus': 'Managed', 'hostname': 'DC-FR-9300.cisco.com', 'locationName': None, 'managementIpAddress': '204.192.3.40', 'platformId': 'C9300-24U', 'lastUpdated': '2024-09-28 07:29:53', 'associatedWlcIp': '', 'apEthernetMacAddress': None, 'errorCode': None, 'errorDescription': None, 'lastDeviceResyncStartTime': '2024-09-28 07:29:51', 'lineCardCount': '0', 'lineCardId': '', 'managedAtleastOnce': True, 'tagCount': '0', 'tunnelUdpPort': None, 'uptimeSeconds': 1328089, 'vendor': 'Cisco', 'waasDeviceMode': None, 'type': 'Cisco Catalyst 9300 Switch', 'location': None, 'role': 'ACCESS', 'instanceTenantId': '66e48af26fe687300375675e', 'instanceUuid': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4', 'id': '1e8ffc57-8a44-459f-b880-6b49d6b63aa4'}, 'version': '1.0'} - -09-28-2024 14:26:42 INFO Provision: get_device_id: 406: Device ID of the device with IP address 204.192.3.40 is 1e8ffc57-8a44-459f-b880-6b49d6b63aa4 - -09-28-2024 14:26:43 DEBUG Provision: verify_diff_merged: 1139: Wired device's status Response collected from 'get_provisioned_wired_device' API is:{'deviceManagementIpAddress': '204.192.3.40', 'siteNameHierarchy': 'Global/Chennai/LTTS/FLOOR1', 'status': 'success', 'description': 'Wired Provisioned device detail retrieved successfully.'} - -09-28-2024 14:26:43 INFO Provision: verify_diff_merged: 1141: The provisioned status of the wired device is success - -09-28-2024 14:26:43 INFO Provision: verify_diff_merged: 1144: Requested wired device is alread provisioned - -09-28-2024 14:26:43 DEBUG Provision: check_return_status: 271: Line No: 1244 status: success, msg: Successfully collected all parameters from playbook for comparison - From 70a0cf21881a48b67e6e711dbf306c7ab3000c14 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sat, 28 Sep 2024 18:52:43 +0530 Subject: [PATCH 25/28] 2ed Review comments compleated --- playbooks/provision_workflow_manager.yml | 2 +- plugins/modules/provision_workflow_manager.py | 63 +++++++++---------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index 34156ae7eb..d4b10022aa 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -22,4 +22,4 @@ state: merged config: - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 - management_ip_address: 204.192.3.40 + management_ip_address: 1.1.1.1 \ No newline at end of file diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 09999cbdbe..16b2558371 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -55,7 +55,7 @@ force_provisioning: description: - Determines whether to force reprovisioning of a device. - - Note that reprovisioning cannot change the device's site assignment. + - Note Can't re-provision a device to a different site. - Applicable only for wired devices. - Set to 'true' to enforce reprovisioning, even if the device is already provisioned. - Set to 'false' to skip provisioning for devices that are already provisioned. @@ -906,37 +906,7 @@ class instance for further use. self.log("The provisioned status of the wired device is {0}".format(status), "INFO") if status == "success": - if to_force_provisioning is True: - if to_provisioning is True: - try: - response = self.dnac_apply['exec']( - family="sda", - function="re_provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") - return self - - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - else: - self.msg = ("Cannot assign a provisioned device to the site. " - "Unprovision the device and then try assigning it to the site.") - self.log(self.msg, "ERROR") - self.status = "failed" - return self - else: + if to_force_provisioning is False: self.result["changed"] = False msg = "Device '{0}' is already provisioned.".format(self.validated_config.get("management_ip_address")) self.result['msg'] = msg @@ -944,6 +914,35 @@ class instance for further use. self.result['response'] = msg self.log(msg, "INFO") return self + if to_provisioning is False: + self.msg = ("Cannot assign a provisioned device to the site. " + "Unprovision the device and then try assigning it to the site.") + self.log(self.msg, "ERROR") + self.status = "failed" + return self + + try: + response = self.dnac_apply['exec']( + family="sda", + function="re_provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Re-Provision done Successfully" + self.result['diff'] = self.validated_config + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + return self + + except Exception as e: + self.msg = "Error in re-provisioning due to {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + return self if to_provisioning is True: try: From 2fe80f9a36739412801993bd65e446f430b15fa4 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sun, 29 Sep 2024 00:05:15 +0530 Subject: [PATCH 26/28] 3rd review comments comepleated have broke down the get diff merged funtion --- playbooks/provision_workflow_manager.yml | 2 +- plugins/modules/provision_workflow_manager.py | 393 +++++++++++------- 2 files changed, 253 insertions(+), 142 deletions(-) diff --git a/playbooks/provision_workflow_manager.yml b/playbooks/provision_workflow_manager.yml index d4b10022aa..5850fb5810 100644 --- a/playbooks/provision_workflow_manager.yml +++ b/playbooks/provision_workflow_manager.yml @@ -22,4 +22,4 @@ state: merged config: - site_name_hierarchy: Global/Chennai/LTTS/FLOOR1 - management_ip_address: 1.1.1.1 \ No newline at end of file + management_ip_address: 1.1.1.1 diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 16b2558371..47290a02d9 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -55,7 +55,8 @@ force_provisioning: description: - Determines whether to force reprovisioning of a device. - - Note Can't re-provision a device to a different site. + - A device cannot be re-provisioned to a different site. + - The 'provisioning' option should not be set to 'false' for 'force_provisioning' to take effect. - Applicable only for wired devices. - Set to 'true' to enforce reprovisioning, even if the device is already provisioned. - Set to 'false' to skip provisioning for devices that are already provisioned. @@ -63,7 +64,7 @@ required: false default: false site_name_hierarchy: - description: Name of site where the device needs to be added. + description: Name of the site where the device will be added. This parameter is required for provisioning the device and assigning it to a site. type: str required: true managed_ap_locations: @@ -321,6 +322,17 @@ def validate_input(self, state=None): } if state == "merged": provision_spec["site_name_hierarchy"] = {'type': 'str', 'required': True} + missing_params = [] + for config_item in self.config: + if "site_name_hierarchy" not in config_item or config_item["site_name_hierarchy"] is None: + missing_params.append("site_name_hierarchy") + if "management_ip_address" not in config_item or config_item["management_ip_address"] is None: + missing_params.append("management_ip_address") + + if missing_params: + self.msg = f"Missing or invalid required parameter(s): {', '.join(missing_params)}" + self.status = "failed" + return self # Validate provision params valid_provision, invalid_params = validate_list_of_dicts( @@ -873,166 +885,265 @@ def perform_wireless_reprovision(self): def get_diff_merged(self): """ - Add to provision database + Process and merge device provisioning differences. + Args: self: An instance of a class used for interacting with Cisco Catalyst Center. + Returns: - object: An instance of the class with updated results and status - based on the processing of differences. + self: An instance of the class with updated results and status based on + the processing of device provisioning differences. + Description: - The function processes the differences and, depending on the - changes required, it may add, update,or resynchronize devices in - Cisco Catalyst Center. The updated results and status are stored in the - class instance for further use. + This function identifies differences in device provisioning parameters and + processes them accordingly. It handles wired and wireless devices by checking + their current provisioning status and determining the necessary actions—whether + to provision, reprovision, or assign devices to a site. The function logs the + outcomes of these operations and updates the instance with the results, status, + and relevant task IDs. If any errors occur during processing, they are logged, + and the status is updated to reflect the failure. """ device_type = self.want.get("device_type") to_force_provisioning = self.validated_config.get("force_provisioning") to_provisioning = self.validated_config.get("provisioning") + self.device_ip = self.validated_config["management_ip_address"] + self.site_name = self.validated_config["site_name_hierarchy"] + if device_type == "wired": - try: - status_response = self.dnac_apply['exec']( - family="sda", - function="get_provisioned_wired_device", - op_modifies=True, - params={ - "device_management_ip_address": self.validated_config.get("management_ip_address") - }, - ) - except Exception: - status_response = {} - self.log("Wired device's status Response collected from 'get_provisioned_wired_device' API is:{0}".format(str(status_response)), "DEBUG") - status = status_response.get("status") - self.log("The provisioned status of the wired device is {0}".format(status), "INFO") + self.provision_wired_device(to_provisioning, to_force_provisioning) + else: + self.provision_wireless_device() + return self - if status == "success": - if to_force_provisioning is False: - self.result["changed"] = False - msg = "Device '{0}' is already provisioned.".format(self.validated_config.get("management_ip_address")) - self.result['msg'] = msg - self.result['diff'] = self.want - self.result['response'] = msg - self.log(msg, "INFO") - return self - if to_provisioning is False: - self.msg = ("Cannot assign a provisioned device to the site. " - "Unprovision the device and then try assigning it to the site.") - self.log(self.msg, "ERROR") - self.status = "failed" - return self - - try: - response = self.dnac_apply['exec']( - family="sda", - function="re_provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Reprovisioning response collected from 're_provision_wired_device' API is: {0}".format(response), "DEBUG") - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Re-Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") - return self - - except Exception as e: - self.msg = "Error in re-provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - - if to_provisioning is True: - try: - response = self.dnac_apply['exec']( - family="sda", - function="provision_wired_device", - op_modifies=True, - params=self.want["prov_params"], - ) - self.log("Provisioning response collected from 'provision_wired_device' API is: {0}".format(response), "DEBUG") - except Exception as e: - self.msg = "Error in provisioning due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self + def provision_wired_device(self, to_provisioning, to_force_provisioning): + """ + Handle wired device provisioning. - else: - uuid = self.get_device_id() - if self.is_device_assigned_to_site(uuid) is True: - self.result["changed"] = False - self.result['msg'] = "Device is already assigned to the desired site" - self.result['diff'] = self.want - self.result['response'] = self.want.get("prov_params").get("site_id") - self.log(self.result['msg'], "INFO") - return self - - try: - response = self.dnac_apply['exec']( - family="sites", - function="assign_devices_to_site", - op_modifies=True, - params={ - "site_id": self.want.get("prov_params").get("site_id"), - "payload": self.want.get("prov_params") - }, - ) - self.log("Assignment response collected from 'assign_devices_to_site' API is: {0}".format(response), "DEBUG") - execution_id = response.get("executionId") - assignment_info = self.get_execution_status_site(execution_id=execution_id) - self.result["changed"] = True - self.result['msg'] = "Site assignment done successfully" - self.result['diff'] = self.validated_config - self.result['response'] = execution_id - self.log(self.result['msg'], "INFO") - return self - except Exception as e: - self.msg = "Error in site assignment due to {0}".format(str(e)) - self.log(self.msg, "ERROR") - self.status = "failed" - return self - - elif device_type == "wireless": - try: - response = self.dnac_apply['exec']( - family="wireless", - function="provision", - op_modifies=True, - params={"payload": self.want.get("prov_params")} - ) - self.log("Wireless provisioning response collected from 'provision' API is: {0}".format(str(response)), "DEBUG") - execution_id = response.get("executionId") - self.get_execution_status_wireless(execution_id=execution_id) - self.result["changed"] = True - self.result['msg'] = "Wireless device with IP {0} got provisioned successfully".format(self.validated_config["management_ip_address"]) - self.result['diff'] = self.validated_config - self.result['response'] = execution_id - self.log(self.result['msg'], "INFO") + Args: + self: An instance of a class used for interacting with Cisco Catalyst Center. + to_provisioning (bool): Indicates if the device should be provisioned. + to_force_provisioning (bool): Indicates if the device should be forcefully reprovisioned. + + Returns: + self: An instance of the class with updated results and status based on + the provisioning operation. + + Description: + This function manages the provisioning of a wired device in Cisco Catalyst Center. + It checks the current provisioning status of the device and, based on the flags + `to_provisioning` and `to_force_provisioning`, decides whether to provision, reprovision, + or skip the process. The function sends appropriate API requests, logs the outcomes, + and updates the instance with provisioning status, task details, and any changes made. + In case of errors, it logs them and sets the status to 'failed'. + """ + + try: + status_response = self.dnac_apply['exec']( + family="sda", + function="get_provisioned_wired_device", + op_modifies=True, + params={ + "device_management_ip_address": self.validated_config.get("management_ip_address") + }, + ) + except Exception: + status_response = {} + + status = status_response.get("status") + self.log("The provisioned status of the wired device is {0}".format(status), "INFO") + + if status == "success": + if not to_force_provisioning: + self.result["changed"] = False + msg = "Device '{0}' is already provisioned.".format(self.validated_config.get("management_ip_address")) + self.result['msg'] = msg + self.result['response'] = msg + self.log(msg, "INFO") return self - except Exception as e: - self.log("Parameters are {0}".format(self.want)) - self.msg = "Error in wireless provisioning of {0} due to {1}".format(self.validated_config["management_ip_address"], e) + + if not to_provisioning: + self.msg = ("Cannot assign a provisioned device to the site. " + "The device is already provisioned. " + "To re-provision the device, ensure that both 'provisioning' and 'force_provisioning' are set to 'true'. " + "Alternatively, unprovision the device and try again.") self.log(self.msg, "ERROR") self.status = "failed" return self - else: - self.result['msg'] = "Passed device is neither wired nor wireless" - self.log(self.result['msg'], "ERROR") - self.result['response'] = self.want.get("prov_params") + self.reprovision_wired_device() return self - task_id = response.get("taskId") - self.get_task_status(task_id=task_id) - self.result["changed"] = True - self.result['msg'] = "Provision done Successfully" - self.result['diff'] = self.validated_config - self.result['response'] = task_id - self.log(self.result['msg'], "INFO") + # Provision if status is not success + if not to_provisioning: + self.assign_device_to_site() + else: + self.initialize_wired_provisioning() + return self + def reprovision_wired_device(self): + """ + Reprovision a wired device. + + Args: + self: An instance of a class used for interacting with Cisco Catalyst Center. + + Returns: + self: An instance of the class with updated results and status after the + wired device has been reprovisioned. + + Description: + This function handles the reprovisioning of a wired device in Cisco Catalyst Center. + It sends an API request to the 're_provision_wired_device' endpoint using the device's + provisioning parameters. The function tracks the task status and updates the class instance + with the reprovisioning status, task ID, and other relevant details. If an error occurs during + the reprovisioning process, it logs the error and adjusts the status accordingly. + """ + + try: + response = self.dnac_apply['exec']( + family="sda", + function="re_provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + self.log(self.want["prov_params"]) + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Re-Provision for device '{0}' done successfully".format(self.device_ip) + self.result['diff'] = self.validated_config + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + except Exception as e: + self.msg = "Error in re-provisioning device '{0}' due to {1}".format(self.device_ip, str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + + def initialize_wired_provisioning(self): + """ + Provision a wired device. + + Args: + self: An instance of a class used for interacting with Cisco Catalyst Center. + + Returns: + self: An instance of the class with updated results and status after the wired + device has been provisioned. + + Description: + This function handles the provisioning of a wired device in Cisco Catalyst Center. + It sends an API request to the 'provision_wired_device' endpoint with the required + parameters. If provisioning is successful, the class instance is updated with the + provisioning status, task ID, and execution details. In case of any errors during + provisioning, it logs the error and updates the status accordingly. + """ + + try: + response = self.dnac_apply['exec']( + family="sda", + function="provision_wired_device", + op_modifies=True, + params=self.want["prov_params"], + ) + task_id = response.get("taskId") + self.get_task_status(task_id=task_id) + self.result["changed"] = True + self.result['msg'] = "Provisioning of the device '{0}' completed successfully.".format(self.device_ip) + self.result['response'] = task_id + self.log(self.result['msg'], "INFO") + except Exception as e: + self.msg = "Error in provisioning device '{0}' due to {1}".format(self.device_ip, str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + + def assign_device_to_site(self): + """ + Assign a device to a site. + + Args: + self: An instance of a class used for interacting with Cisco Catalyst Center. + + Returns: + self: An instance of the class with updated results and status after the device + has been assigned to the specified site. + + Description: + This function assigns a device to a specific site in Cisco Catalyst Center. + It sends an API request to the 'assign_devices_to_site' endpoint with the required + site ID and device information. If the assignment is successful, it logs the + status and updates the class instance with the execution details. In case of failure, + it logs the error and updates the status accordingly. + """ + + uuid = self.get_device_id() + if self.is_device_assigned_to_site(uuid) is True: + self.result["changed"] = False + self.result['msg'] = "Device '{0}' is already assigned to the desired site".format(self.device_ip) + self.result['response'] = self.want.get("prov_params").get("site_id") + self.log(self.result['msg'], "INFO") + return self + try: + response = self.dnac_apply['exec']( + family="sites", + function="assign_devices_to_site", + op_modifies=True, + params={ + "site_id": self.want.get("prov_params").get("site_id"), + "payload": self.want.get("prov_params") + }, + ) + execution_id = response.get("executionId") + self.get_execution_status_site(execution_id=execution_id) + self.result["changed"] = True + self.msg = "Successfully assigned site {1} to device {0}.".format(self.device_ip, self.site_name) + self.result['msg'] = self.msg + self.result['response'] = execution_id + self.log(self.result['msg'], "INFO") + except Exception as e: + self.msg = "Error in site assignment: {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + + def provision_wireless_device(self): + """ + Provision a wireless device. + + Args: + self: An instance of a class used for interacting with Cisco Catalyst Center. + want (dict): A dictionary containing the provisioning parameters for the wireless device. + + Returns: + self: An instance of the class with updated results and status based on + the provisioning operation. + + Description: + This function is responsible for provisioning a wireless device in Cisco Catalyst Center. + It sends a request using the 'provision' API and handles the execution status. + If an error occurs during the provisioning process, it logs the error and updates + the instance status accordingly. + """ + + try: + response = self.dnac_apply['exec']( + family="wireless", + function="provision", + op_modifies=True, + params={"payload": self.want.get("prov_params")} + ) + execution_id = response.get("executionId") + self.get_execution_status_wireless(execution_id=execution_id) + self.result["changed"] = True + self.result['msg'] = "Wireless device provisioned successfully" + self.result['diff'] = self.validated_config + self.result['response'] = execution_id + self.log(self.result['msg'], "INFO") + except Exception as e: + self.msg = "Error in wireless provisioning: {0}".format(str(e)) + self.log(self.msg, "ERROR") + self.status = "failed" + def get_diff_deleted(self): """ Delete from provision database From a1dd4887283e8cead4ae52cf1a78372f4c6dd09a Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sun, 29 Sep 2024 00:12:07 +0530 Subject: [PATCH 27/28] 3rd review comments comepleated have broke down the get diff merged funtion --- plugins/modules/provision_workflow_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index 47290a02d9..dbeb988dcd 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -330,7 +330,7 @@ def validate_input(self, state=None): missing_params.append("management_ip_address") if missing_params: - self.msg = f"Missing or invalid required parameter(s): {', '.join(missing_params)}" + self.msg = "Missing or invalid required parameter(s): {0}".format(', '.join(missing_params)) self.status = "failed" return self From cc8412ed37d4728a4526e5b3dcef95d0733413d7 Mon Sep 17 00:00:00 2001 From: Syed-khadeerahmed Date: Sun, 29 Sep 2024 14:44:59 +0530 Subject: [PATCH 28/28] 4th review comments compleated have added return properly for all the APIs --- plugins/modules/provision_workflow_manager.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/plugins/modules/provision_workflow_manager.py b/plugins/modules/provision_workflow_manager.py index dbeb988dcd..f3a580ac34 100644 --- a/plugins/modules/provision_workflow_manager.py +++ b/plugins/modules/provision_workflow_manager.py @@ -914,6 +914,7 @@ def get_diff_merged(self): self.provision_wired_device(to_provisioning, to_force_provisioning) else: self.provision_wireless_device() + return self def provision_wired_device(self, to_provisioning, to_force_provisioning): @@ -939,7 +940,7 @@ def provision_wired_device(self, to_provisioning, to_force_provisioning): """ try: - status_response = self.dnac_apply['exec']( + response = self.dnac_apply['exec']( family="sda", function="get_provisioned_wired_device", op_modifies=True, @@ -947,10 +948,16 @@ def provision_wired_device(self, to_provisioning, to_force_provisioning): "device_management_ip_address": self.validated_config.get("management_ip_address") }, ) - except Exception: - status_response = {} + self.log("Received API response from 'get_provisioned_wired_device': {0}".format(response), "DEBUG") - status = status_response.get("status") + except Exception as e: + self.msg = "Error in get_provisioned_wired_device device '{0}' due to {1}".format(self.device_ip, str(e)) + self.log(self.msg, "ERROR") + self.result['response'] = self.msg + self.status = "failed" + self.check_return_status() + + status = response.get("status") self.log("The provisioned status of the wired device is {0}".format(status), "INFO") if status == "success": @@ -1016,10 +1023,13 @@ def reprovision_wired_device(self): self.result['diff'] = self.validated_config self.result['response'] = task_id self.log(self.result['msg'], "INFO") + return self except Exception as e: self.msg = "Error in re-provisioning device '{0}' due to {1}".format(self.device_ip, str(e)) self.log(self.msg, "ERROR") + self.result['response'] = self.msg self.status = "failed" + self.check_return_status() def initialize_wired_provisioning(self): """ @@ -1053,10 +1063,13 @@ def initialize_wired_provisioning(self): self.result['msg'] = "Provisioning of the device '{0}' completed successfully.".format(self.device_ip) self.result['response'] = task_id self.log(self.result['msg'], "INFO") + return self except Exception as e: self.msg = "Error in provisioning device '{0}' due to {1}".format(self.device_ip, str(e)) self.log(self.msg, "ERROR") self.status = "failed" + self.result['response'] = self.msg + self.check_return_status() def assign_device_to_site(self): """ @@ -1101,10 +1114,13 @@ def assign_device_to_site(self): self.result['msg'] = self.msg self.result['response'] = execution_id self.log(self.result['msg'], "INFO") + return self except Exception as e: self.msg = "Error in site assignment: {0}".format(str(e)) self.log(self.msg, "ERROR") self.status = "failed" + self.result['response'] = self.msg + self.check_return_status() def provision_wireless_device(self): """ @@ -1139,10 +1155,13 @@ def provision_wireless_device(self): self.result['diff'] = self.validated_config self.result['response'] = execution_id self.log(self.result['msg'], "INFO") + return self except Exception as e: self.msg = "Error in wireless provisioning: {0}".format(str(e)) self.log(self.msg, "ERROR") self.status = "failed" + self.result['response'] = self.msg + self.check_return_status() def get_diff_deleted(self): """