Skip to content

Commit 80860a2

Browse files
authored
Access Point CG bug fix (#450)
2 parents f43ddc1 + 283b58d commit 80860a2

3 files changed

Lines changed: 431 additions & 313 deletions

File tree

plugins/modules/accesspoint_playbook_config_generator.py

Lines changed: 135 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -324,44 +324,36 @@
324324
RETURN = r"""
325325
# Case_1: Success Scenario
326326
response_1:
327-
description: >-
328-
A dictionary with the response returned by the Cisco Catalyst
329-
Center Python SDK
327+
description: A dictionary with the response returned by the Cisco Catalyst Center Python SDK
330328
returned: always
331329
type: dict
332330
sample: >
333331
{
334-
"response": {
335-
"YAML config generation Task succeeded for module
336-
'accesspoint_workflow_manager'.": {
337-
"file_path":
338-
"tmp/accesspoint_workflow_playbook_templatebase.yml"
339-
}
332+
"msg": {
333+
"configurations_count": 7,
334+
"file_mode": "overwrite",
335+
"file_path": "tmp/accesspoint_workflow_playbook.yml",
336+
"message": "YAML configuration file generated successfully for module 'accesspoint_workflow_manager'",
337+
"status": "success"
340338
},
341-
"msg": {
342-
"YAML config generation Task succeeded for module
343-
'accesspoint_workflow_manager'.": {
344-
"file_path":
345-
"tmp/accesspoint_workflow_playbook_templatebase.yml"
346-
}
347-
}
339+
"response": {
340+
"configurations_count": 7,
341+
"file_mode": "overwrite",
342+
"file_path": "tmp/accesspoint_workflow_playbook.yml",
343+
"message": "YAML configuration file generated successfully for module 'accesspoint_workflow_manager'",
344+
"status": "success"
345+
},
346+
"status": "success"
348347
}
349-
350348
# Case_2: Error Scenario
351349
response_2:
352-
description: >-
353-
A string with the response returned by the Cisco Catalyst
354-
Center Python SDK
350+
description: A string with the response returned by the Cisco Catalyst Center Python SDK
355351
returned: always
356352
type: dict
357353
sample: >
358354
{
359-
"response": "No configurations or components to process for
360-
module 'accesspoint_workflow_manager'.
361-
Verify input filters or configuration.",
362-
"msg": "No configurations or components to process for module
363-
'accesspoint_workflow_manager'.
364-
Verify input filters or configuration."
355+
"msg": "Validation failed: global_filters is required when config is provided.",
356+
"response": "Validation failed: global_filters is required when config is provided."
365357
}
366358
"""
367359

@@ -2215,7 +2207,7 @@ def get_diff_gathered(self):
22152207
"DEBUG"
22162208
)
22172209

2218-
# Check for unprocessed APs and set failure status if any exist
2210+
# Check for unprocessed APs and log warning without failing the module
22192211
if self.have.get("unprocessed"):
22202212
unprocessed_count = len(self.have.get("unprocessed"))
22212213
self.msg = (
@@ -2225,10 +2217,9 @@ def get_diff_gathered(self):
22252217
f"{str(self.have.get('unprocessed'))}. Verify filter values match existing APs "
22262218
"and check Catalyst Center inventory for missing configurations."
22272219
)
2228-
self.set_operation_result("failed", False, self.msg, "WARNING")
22292220

22302221
self.log(
2231-
f"Setting workflow status to 'failed' due to {unprocessed_count} unprocessed AP(s). "
2222+
f"Continuing workflow with warnings due to {unprocessed_count} unprocessed AP(s). "
22322223
"This indicates filter mismatches or incomplete AP data in Catalyst Center. Review "
22332224
f"unprocessed list for troubleshooting: {self.have.get('unprocessed')}",
22342225
"WARNING"
@@ -2438,19 +2429,17 @@ def yaml_config_generator(self, yaml_config_generator):
24382429

24392430
# Validate final_list is not empty
24402431
if not final_list:
2441-
self.msg = (
2442-
f"No configurations or components found to process for module '{self.module_name}'. "
2443-
"This indicates either: (1) No access points discovered in Catalyst Center, "
2444-
"(2) Global filters didn't match any APs, or (3) Invalid configuration parameters. "
2445-
"Verify input filters match existing AP inventory and check Catalyst Center has "
2446-
"onboarded access points. No YAML file will be generated."
2447-
)
2448-
self.set_operation_result("success", False, self.msg, "INFO")
2432+
self.msg = {
2433+
"status": "ok",
2434+
"message": (
2435+
"No configurations found for module '{0}'. Verify filters applied and access "
2436+
"point data in Catalyst Center.".format(self.module_name)
2437+
)
2438+
}
2439+
self.set_operation_result("ok", False, self.msg, "INFO")
24492440

24502441
self.log(
2451-
"YAML generation workflow completed with no-op status. Final configuration list "
2452-
"is empty - no YAML playbook generated. Check filter criteria or Catalyst Center "
2453-
"AP inventory for troubleshooting.",
2442+
"YAML generation workflow completed. Final configuration list is empty.",
24542443
"INFO"
24552444
)
24562445
return self
@@ -2473,30 +2462,45 @@ def yaml_config_generator(self, yaml_config_generator):
24732462
"DEBUG"
24742463
)
24752464

2476-
if self.write_dict_to_yaml(final_dict, file_path, file_mode):
2465+
file_written = self.write_dict_to_yaml(final_dict, file_path, file_mode)
2466+
2467+
if file_written:
24772468
self.msg = {
2478-
f"YAML config generation task succeeded for module '{self.module_name}'.": {"file_path": file_path}
2469+
"status": "success",
2470+
"message": "YAML configuration file generated successfully for module '{0}'".format(
2471+
self.module_name
2472+
),
2473+
"file_path": file_path,
2474+
"file_mode": file_mode,
2475+
"configurations_count": len(final_list)
24792476
}
24802477
self.set_operation_result("success", True, self.msg, "INFO")
24812478

24822479
self.log(
2483-
f"YAML playbook file successfully created at '{file_path}'. File contains "
2484-
f"{len(final_list)} access point configuration(s) in Ansible-compatible format. "
2485-
"Playbook ready for automation workflows.",
2480+
"YAML configuration generation completed. File: {0}, Configs: {1}".format(
2481+
file_path,
2482+
len(final_list),
2483+
),
24862484
"INFO"
24872485
)
24882486
else:
24892487
self.msg = {
2490-
f"YAML config generation task failed for module '{self.module_name}'.": {"file_path": file_path}
2488+
"status": "ok",
2489+
"message": "YAML configuration file already up-to-date for module '{0}'. No changes written.".format(
2490+
self.module_name
2491+
),
2492+
"file_path": file_path,
2493+
"file_mode": file_mode,
2494+
"configurations_count": len(final_list)
24912495
}
2492-
self.set_operation_result("failed", True, self.msg, "ERROR")
2496+
self.set_operation_result("ok", False, self.msg, "INFO")
24932497

24942498
self.log(
2495-
f"YAML playbook file creation FAILED for path '{file_path}'. write_dict_to_yaml() "
2496-
"returned False, indicating file write error. Verify: (1) Parent directory exists, "
2497-
"(2) Write permissions sufficient, (3) Disk space available, (4) Path is valid. "
2498-
"Check system logs for detailed error information.",
2499-
"ERROR"
2499+
"YAML configuration unchanged. File: {0}, Configs: {1}".format(
2500+
file_path,
2501+
len(final_list),
2502+
),
2503+
"INFO"
25002504
)
25012505

25022506
return self
@@ -2631,14 +2635,11 @@ def process_global_filters(self, global_filters):
26312635

26322636
# Validate AP configuration inventory exists
26332637
if not self.have.get("all_ap_config"):
2634-
self.msg = (
2635-
"No access points configuration found in Cisco Catalyst Center inventory. "
2636-
"self.have['all_ap_config'] is empty or None. Cannot proceed with filter processing. "
2637-
"Verify: (1) APs onboarded to Catalyst Center, (2) get_current_config() executed "
2638-
"successfully, (3) API permissions sufficient. Halting workflow with critical error."
2638+
self.log(
2639+
"No access point configurations available for filter processing. Returning empty.",
2640+
"DEBUG"
26392641
)
2640-
self.log(self.msg, "WARNING")
2641-
self.fail_and_exit(self.msg)
2642+
return []
26422643

26432644
self.log(
26442645
f"AP configuration inventory validation passed. Found {len(self.have.get('all_ap_config'))} "
@@ -2680,7 +2681,7 @@ def process_global_filters(self, global_filters):
26802681
f"Site filter {site_index}/{len(site_list)}: Site hierarchy '{floor}' NOT "
26812682
f"found in AP configurations. No APs assigned to this site. Adding to "
26822683
f"unprocessed list.",
2683-
"WARNING"
2684+
"DEBUG"
26842685
)
26852686
unprocessed_aps.append(f"{floor}: Unable to find the configuration for the site hierarchy in the catalyst center.")
26862687
continue
@@ -2722,11 +2723,10 @@ def process_global_filters(self, global_filters):
27222723
if not ap_exist:
27232724
self.log(
27242725
"No provisioned access points found in Catalyst Center inventory. No APs "
2725-
"have rf_profile='HIGH'. Halting workflow with critical error.",
2726-
"WARNING"
2726+
"have rf_profile='HIGH'. Returning empty.",
2727+
"DEBUG"
27272728
)
2728-
self.msg = "No provisioned access points found in the catalyst center."
2729-
self.fail_and_exit(self.msg)
2729+
return []
27302730

27312731
provisioned_aps = []
27322732
for ap_index, each_ap in enumerate(ap_exist, start=1):
@@ -2765,27 +2765,49 @@ def process_global_filters(self, global_filters):
27652765
self.log(
27662766
f"Provision hostname filter {host_index}/{len(provision_hostname_list)}: "
27672767
f"Hostname '{each_host}' NOT found in AP configurations. Adding to unprocessed list.",
2768-
"WARNING"
2768+
"DEBUG"
27692769
)
27702770
unprocessed_aps.append(f"{each_host}: Unable to find the hostname in the catalyst center.")
27712771
continue
27722772

2773+
selected_ap = ap_exist[0]
2774+
if selected_ap.get("rf_profile") is None or selected_ap.get("site") is None:
2775+
self.log(
2776+
f"Provision hostname filter {host_index}/{len(provision_hostname_list)}: "
2777+
f"Found AP '{each_host}', but it is not provisioned to any floor site. "
2778+
"Adding to unprocessed list.",
2779+
"DEBUG"
2780+
)
2781+
unprocessed_aps.append(
2782+
f"{each_host}: AP found but not provisioned to any floor site."
2783+
)
2784+
continue
2785+
27732786
self.log(
27742787
f"Provision hostname filter {host_index}/{len(provision_hostname_list)}: "
27752788
f"Found AP '{each_host}'. Extracting minimal provision config.",
27762789
"INFO"
27772790
)
27782791

27792792
provisioned_aps.append({
2780-
"mac_address": ap_exist[0].get("mac_address"),
2781-
"rf_profile": ap_exist[0].get("rf_profile"),
2782-
"site": ap_exist[0].get("site")
2793+
"mac_address": selected_ap.get("mac_address"),
2794+
"rf_profile": selected_ap.get("rf_profile"),
2795+
"site": selected_ap.get("site")
27832796
})
27842797

27852798
if not provisioned_aps:
2786-
self.msg = "No provisioned access points found in the catalyst center."
2787-
self.log(self.msg, "WARNING")
2788-
self.fail_and_exit(self.msg)
2799+
self.log(
2800+
"No provisioned APs matched provision_hostname_list. Returning empty.",
2801+
"DEBUG"
2802+
)
2803+
2804+
if unprocessed_aps:
2805+
self.msg = {
2806+
"The following access points could not be processed:": unprocessed_aps
2807+
}
2808+
self.have["unprocessed"] = unprocessed_aps
2809+
2810+
return []
27892811

27902812
final_list = provisioned_aps
27912813

@@ -2847,7 +2869,7 @@ def process_global_filters(self, global_filters):
28472869
self.log(
28482870
f"AP config filter {ap_name_index}/{len(accesspoint_config_list)}: AP name "
28492871
f"'{each_ap_name}' NOT found in configurations. Adding to unprocessed list.",
2850-
"WARNING"
2872+
"DEBUG"
28512873
)
28522874
unprocessed_aps.append(f"{each_ap_name}: Unable to find the hostname in the catalyst center.")
28532875
continue
@@ -2866,9 +2888,19 @@ def process_global_filters(self, global_filters):
28662888
ap_config_list.extend(ap_exist)
28672889

28682890
if not ap_config_list:
2869-
self.msg = f"No access points found matching the provided list. {accesspoint_config_list}."
2870-
self.log(self.msg, "WARNING")
2871-
self.fail_and_exit(self.msg)
2891+
self.log(
2892+
f"No access points found matching the provided list: {accesspoint_config_list}. "
2893+
"Returning empty.",
2894+
"DEBUG"
2895+
)
2896+
2897+
if unprocessed_aps:
2898+
self.msg = {
2899+
"The following access points could not be processed:": unprocessed_aps
2900+
}
2901+
self.have["unprocessed"] = unprocessed_aps
2902+
2903+
return []
28722904

28732905
final_list = ap_config_list
28742906

@@ -2915,7 +2947,7 @@ def process_global_filters(self, global_filters):
29152947
self.log(
29162948
f"Combined filter {host_index}/{len(accesspoint_provision_config_list)}: "
29172949
f"Hostname '{each_host_name}' NOT found in configurations. Adding to unprocessed list.",
2918-
"WARNING"
2950+
"DEBUG"
29192951
)
29202952
unprocessed_aps.append(f"{each_host_name}: Unable to find the hostname in the catalyst center.")
29212953
continue
@@ -2929,9 +2961,18 @@ def process_global_filters(self, global_filters):
29292961
collected_aps.extend(ap_exist)
29302962

29312963
if not collected_aps:
2932-
self.msg = "No access points found matching the provided hostname list."
2933-
self.log(self.msg, "WARNING")
2934-
self.fail_and_exit(self.msg)
2964+
self.log(
2965+
"No access points found matching the provided hostname list. Returning empty.",
2966+
"DEBUG"
2967+
)
2968+
2969+
if unprocessed_aps:
2970+
self.msg = {
2971+
"The following access points could not be processed:": unprocessed_aps
2972+
}
2973+
self.have["unprocessed"] = unprocessed_aps
2974+
2975+
return []
29352976

29362977
final_list = collected_aps
29372978

@@ -2978,7 +3019,7 @@ def process_global_filters(self, global_filters):
29783019
self.log(
29793020
f"MAC filter {mac_index}/{len(accesspoint_provision_config_mac_list)}: MAC "
29803021
f"address '{each_mac}' NOT found in configurations. Adding to unprocessed list.",
2981-
"WARNING"
3022+
"DEBUG"
29823023
)
29833024
unprocessed_aps.append(
29843025
f"{each_mac}: Unable to find configuration for the MAC address in the catalyst center.")
@@ -2993,9 +3034,18 @@ def process_global_filters(self, global_filters):
29933034
collected_aps.extend(ap_exist)
29943035

29953036
if not collected_aps:
2996-
self.msg = "No access points found matching the provided mac address list."
2997-
self.log(self.msg, "WARNING")
2998-
self.fail_and_exit(self.msg)
3037+
self.log(
3038+
"No access points found matching the provided mac address list. Returning empty.",
3039+
"DEBUG"
3040+
)
3041+
3042+
if unprocessed_aps:
3043+
self.msg = {
3044+
"The following access points could not be processed:": unprocessed_aps
3045+
}
3046+
self.have["unprocessed"] = unprocessed_aps
3047+
3048+
return []
29993049

30003050
final_list = collected_aps
30013051

@@ -3025,7 +3075,7 @@ def process_global_filters(self, global_filters):
30253075
f"Filter processing completed with {len(unprocessed_aps)} unprocessed AP identifier(s). "
30263076
"These APs/sites/MACs did not match any configurations in Catalyst Center inventory. "
30273077
f"Unprocessed list: {self.msg}",
3028-
"WARNING"
3078+
"DEBUG"
30293079
)
30303080
self.have["unprocessed"] = unprocessed_aps
30313081

@@ -3035,7 +3085,7 @@ def process_global_filters(self, global_filters):
30353085
"No access points matched the provided global filter criteria. Final filtered list "
30363086
"is empty. This may indicate: (1) Filter values don't match existing APs, "
30373087
"(2) Filters too restrictive, or (3) No APs in Catalyst Center. Returning None.",
3038-
"WARNING"
3088+
"DEBUG"
30393089
)
30403090
return None
30413091

0 commit comments

Comments
 (0)