1111 BaseHAClusterValidator: Base validator class for cluster configurations.
1212"""
1313
14+ import logging
1415from abc import ABC
1516
1617try :
1718 from ansible .module_utils .sap_automation_qa import SapAutomationQA
1819 from ansible .module_utils .enums import OperatingSystemFamily , Parameters , TestStatus
19- from ansible .module_utils .commands import CIB_ADMIN
20+ from ansible .module_utils .commands import CIB_ADMIN , RECOMMENDATION_MESSAGES
2021except ImportError :
2122 from src .module_utils .sap_automation_qa import SapAutomationQA
2223 from src .module_utils .enums import OperatingSystemFamily , Parameters , TestStatus
23- from src .module_utils .commands import CIB_ADMIN
24+ from src .module_utils .commands import CIB_ADMIN , RECOMMENDATION_MESSAGES
2425
2526
2627class BaseHAClusterValidator (SapAutomationQA , ABC ):
@@ -79,6 +80,7 @@ def __init__(
7980 self .fencing_mechanism = fencing_mechanism
8081 self .constants = constants
8182 self .cib_output = cib_output
83+ self .missing_required_items = []
8284
8385 def _get_expected_value (self , category , name ):
8486 """
@@ -190,6 +192,15 @@ def _create_parameter(
190192
191193 status = self ._determine_parameter_status (value , expected_config )
192194
195+ if status == TestStatus .WARNING .value and not value :
196+ self ._handle_missing_required_parameter (
197+ expected_config = expected_config ,
198+ name = name ,
199+ category = category ,
200+ subcategory = subcategory ,
201+ op_name = op_name ,
202+ )
203+
193204 display_expected_value = None
194205 if expected_config is None :
195206 display_expected_value = ""
@@ -289,6 +300,47 @@ def _determine_parameter_status(self, value, expected_config):
289300 else TestStatus .ERROR .value
290301 )
291302
303+ def _handle_missing_required_parameter (
304+ self , expected_config , name , category , subcategory = None , op_name = None
305+ ):
306+ """
307+ Handle warnings for missing required parameters.
308+ Logs warning message and updates result when a required parameter has no value.
309+
310+ :param expected_config: The expected configuration (tuple or dict)
311+ :type expected_config: tuple or dict
312+ :param name: The parameter name
313+ :type name: str
314+ :param category: The parameter category
315+ :type category: str
316+ :param subcategory: The parameter subcategory, defaults to None
317+ :type subcategory: str, optional
318+ :param op_name: The operation name (if applicable), defaults to None
319+ :type op_name: str, optional
320+ """
321+ is_required = False
322+ if isinstance (expected_config , tuple ) and len (expected_config ) == 2 :
323+ is_required = expected_config [1 ]
324+ elif isinstance (expected_config , dict ):
325+ is_required = expected_config .get ("required" , False )
326+
327+ if is_required :
328+ param_display_name = f"{ op_name } _{ name } " if op_name else name
329+ category_display = f"{ category } _{ subcategory } " if subcategory else category
330+ self .missing_required_items .append (
331+ {
332+ "type" : "parameter" ,
333+ "name" : name ,
334+ "display_name" : param_display_name ,
335+ "category" : category_display ,
336+ }
337+ )
338+ warning_msg = (
339+ f"Required parameter '{ param_display_name } ' in category '{ category_display } ' "
340+ + "has no value configured."
341+ )
342+ self .log (logging .WARNING , warning_msg )
343+
292344 def _parse_nvpair_elements (self , elements , category , subcategory = None , op_name = None ):
293345 """
294346 Parse nvpair elements and create parameter dictionaries.
@@ -511,6 +563,8 @@ def validate_from_constants(self):
511563 overall_status = TestStatus .ERROR .value
512564 elif warning_parameters :
513565 overall_status = TestStatus .WARNING .value
566+ elif self .result .get ("status" ) == TestStatus .WARNING .value :
567+ overall_status = TestStatus .WARNING .value
514568 else :
515569 overall_status = TestStatus .SUCCESS .value
516570
@@ -520,7 +574,10 @@ def validate_from_constants(self):
520574 "status" : overall_status ,
521575 }
522576 )
523- self .result ["message" ] += "HA Parameter Validation completed successfully. "
577+ self .result ["message" ] += "HA parameter validation completed successfully. "
578+ recommendation_message = self ._generate_recommendation_message ()
579+ if recommendation_message :
580+ self .result ["message" ] += recommendation_message
524581
525582 def _validate_basic_constants (self , category ):
526583 """
@@ -618,6 +675,72 @@ def _validate_resource_constants(self):
618675 """
619676 return []
620677
678+ def _check_required_resources (self ):
679+ """
680+ Check if required resources are present in the cluster.
681+ Adds warnings to result message for missing required resources.
682+ """
683+ if "RESOURCE_DEFAULTS" not in self .constants :
684+ return
685+
686+ try :
687+ if self .cib_output :
688+ resource_scope = self ._get_scope_from_cib ("resources" )
689+ else :
690+ resource_scope = self .parse_xml_output (
691+ self .execute_command_subprocess (CIB_ADMIN (scope = "resources" ))
692+ )
693+ if resource_scope is None :
694+ return
695+
696+ for resource_type , resource_config in (
697+ self .constants ["RESOURCE_DEFAULTS" ].get (self .os_type , {}).items ()
698+ ):
699+ if not isinstance (resource_config , dict ):
700+ continue
701+ if resource_config .get ("required" , False ):
702+ if resource_type in self .RESOURCE_CATEGORIES :
703+ xpath = self .RESOURCE_CATEGORIES [resource_type ]
704+ elements = resource_scope .findall (xpath )
705+ if not elements :
706+ self .missing_required_items .append (
707+ {"type" : "resource" , "name" : resource_type , "xpath" : xpath }
708+ )
709+ self .result ["status" ] = TestStatus .WARNING .value
710+ except Exception as ex :
711+ self .result ["message" ] += f"Error checking required resources: { str (ex )} "
712+
713+ def _generate_recommendation_message (self ):
714+ """
715+ Generate recommendation message based on missing required items.
716+ Uses centralized RECOMMENDATION_MESSAGES dictionary for consistent messaging.
717+
718+ :return: Formatted recommendation message
719+ :rtype: str
720+ """
721+ recommendations = []
722+
723+ for item in self .missing_required_items :
724+ if item ["name" ] in RECOMMENDATION_MESSAGES :
725+ recommendations .append (RECOMMENDATION_MESSAGES [item ["name" ]])
726+ else :
727+ if item ["type" ] == "parameter" :
728+ recommendations .append (
729+ f"The '{ item ['display_name' ]} ' parameter in category "
730+ f"'{ item ['category' ]} ' is not configured."
731+ )
732+ elif item ["type" ] == "resource" :
733+ recommendations .append (
734+ f"The required resource '{ item ['name' ]} ' is not found in cluster config."
735+ )
736+
737+ if recommendations :
738+ recommendation_header = "\n \n Recommendation for warnings:\n "
739+ recommendation_body = "\n " .join (recommendations )
740+ return recommendation_header + recommendation_body
741+
742+ return ""
743+
621744 def _validate_constraint_constants (self ):
622745 """
623746 Validate constraint constants with offline validation support.
0 commit comments