22# Licensed under the MIT License.
33
44"""
5- Python script to get and validate the status of a cluster.
6- This script uses the `crm_mon` command-line tool to retrieve the status of a cluster and performs
7- various validations on the cluster status.
8-
9- Methods:
10- check_node(node, sap_sid)
11- run_module()
12- main()
5+ Python script to get and validate the status of an SCS cluster.
136"""
147
158import logging
169import xml .etree .ElementTree as ET
17- from datetime import datetime
1810from typing import Dict , Any
1911from ansible .module_utils .basic import AnsibleModule
2012
2113try :
22- from ansible .module_utils .sap_automation_qa import SapAutomationQA , TestStatus
23- from ansible .module_utils .commands import (
24- STONITH_ACTION ,
25- PACEMAKER_STATUS ,
26- CLUSTER_STATUS ,
27- )
14+ from ansible .module_utils .get_cluster_status import BaseClusterStatusChecker
2815except ImportError :
29- from src .module_utils .sap_automation_qa import SapAutomationQA , TestStatus
30- from src .module_utils .commands import (
31- STONITH_ACTION ,
32- PACEMAKER_STATUS ,
33- CLUSTER_STATUS ,
34- )
16+ from src .module_utils .get_cluster_status import BaseClusterStatusChecker
3517
3618
37- class ClusterStatusChecker ( SapAutomationQA ):
19+ class SCSClusterStatusChecker ( BaseClusterStatusChecker ):
3820 """
39- Class to check the status of a pacemaker cluster in a SAP HANA environment.
21+ Class to check the status of a pacemaker cluster in an SAP SCS environment.
4022 """
4123
4224 def __init__ (self , sap_sid : str , ansible_os_family : str = "" ):
43- super ().__init__ ()
25+ super ().__init__ (ansible_os_family )
4426 self .sap_sid = sap_sid
45- self .ansible_os_family = ansible_os_family
4627 self .result .update (
4728 {
4829 "ascs_node" : "" ,
4930 "ers_node" : "" ,
50- "cluster_status" : "" ,
51- "start" : datetime .now (),
52- "end" : None ,
53- "pacemaker_status" : "" ,
54- "stonith_action" : "" ,
5531 }
5632 )
5733
58- def _get_stonith_action (self ) -> None :
59- """
60- Retrieves the STONITH action from the crm_attribute.
61- """
62- try :
63- stonith_action = self .execute_command_subprocess (STONITH_ACTION [self .ansible_os_family ])
64- stonith_action = (
65- stonith_action .split ("stonith-action:" )[- 1 ]
66- if self .ansible_os_family == "REDHAT"
67- else stonith_action
68- )
69- self .result ["stonith_action" ] = stonith_action .strip ()
70- except Exception :
71- self .result ["stonith_action" ] = "reboot"
72-
7334 def _process_node_attributes (self , node_attributes : ET .Element ) -> Dict [str , Any ]:
7435 """
75- Processes node attributes and identifies primary/secondary nodes.
36+ Processes node attributes and identifies ASCS and ERS nodes.
7637
77- :param node_attributes: The XML element containing node attributes.
38+ :param node_attributes: XML element containing node attributes.
7839 :type node_attributes: ET.Element
79- :return: A dictionary containing the primary and secondary node information, plus cluster status .
40+ :return: Dictionary with ASCS and ERS node information.
8041 :rtype: Dict[str, Any]
8142 """
82- attribute_name = f"runs_ers_ { self . sap_sid . upper () } "
43+ all_nodes = [ node . attrib . get ( "name" ) for node in node_attributes ]
8344 for node in node_attributes :
45+ node_name = node .attrib ["name" ]
8446 for attribute in node :
85- if attribute .attrib ["name" ] == attribute_name :
47+ if attribute .attrib ["name" ] == f"runs_ers_ { self . sap_sid . upper () } " :
8648 if attribute .attrib ["value" ] == "1" :
87- self .result ["ers_node" ] = node . attrib [ "name" ]
49+ self .result ["ers_node" ] = node_name
8850 else :
89- self .result ["ascs_node" ] = node .attrib ["name" ]
90- else :
91- continue
51+ self .result ["ascs_node" ] = node_name
52+
53+ if self .result ["ascs_node" ] == "" and self .result ["ers_node" ] != "" :
54+ self .result ["ascs_node" ] = next (
55+ (n for n in all_nodes if n != self .result ["ers_node" ]), ""
56+ )
9257
93- def run (self ) -> Dict [ str , str ] :
58+ def _is_cluster_ready (self ) -> bool :
9459 """
95- Main function that runs the Ansible module and performs the cluster status checks .
60+ Check if the cluster is ready by verifying the ASCS node .
9661
97- This function retrieves operation step from module arguments and performs following checks:
98- - Checks the status of the cluster using the `crm_mon` command.
99- - Validates the cluster status and checks if pacemakerd is running.
100- - Checks if the minimum required number of nodes are configured in the cluster.
101- - Checks if all nodes in the cluster are online.
102- - Checks the attributes of each node in the cluster.
62+ :return: True if the cluster is ready, False otherwise.
63+ :rtype: bool
64+ """
65+ return self .result ["ascs_node" ] != ""
66+
67+ def _is_cluster_stable (self ) -> bool :
68+ """
69+ Check if the cluster is stable by verifying both ASCS and ERS nodes.
10370
104- :return: A dictionary containing the result of the cluster status checks .
105- :rtype: Dict[str, str]
71+ :return: True if the cluster is stable, False otherwise .
72+ :rtype: bool
10673 """
107- self .log (logging .INFO , "Starting cluster status check" )
108-
109- self ._get_stonith_action ()
110-
111- try :
112- while self .result ["ascs_node" ] == "" :
113- self .result ["cluster_status" ] = self .execute_command_subprocess (CLUSTER_STATUS )
114- cluster_status_xml = ET .fromstring (self .result ["cluster_status" ])
115- self .log (logging .INFO , "Cluster status retrieved" )
116-
117- if self .execute_command_subprocess (PACEMAKER_STATUS ).strip () == "active" :
118- self .result ["pacemaker_status" ] = "running"
119- else :
120- self .result ["pacemaker_status" ] = "stopped"
121- self .log (logging .INFO , f"Pacemaker status: { self .result ['pacemaker_status' ]} " )
122-
123- if (
124- int (
125- cluster_status_xml .find ("summary" ).find ("nodes_configured" ).attrib ["number" ]
126- )
127- < 2
128- ):
129- self .result ["message" ] = (
130- "Pacemaker cluster isn't stable and does not have primary or secondary node"
131- )
132- self .log (logging .WARNING , self .result ["message" ])
133-
134- nodes = cluster_status_xml .find ("nodes" )
135- for node in nodes :
136- if node .attrib ["online" ] != "true" :
137- self .result ["message" ] = f"Node { node .attrib ['name' ]} is not online"
138- self .log (logging .WARNING , self .result ["message" ])
139-
140- self ._process_node_attributes (cluster_status_xml .find ("node_attributes" ))
141-
142- if self .result ["ascs_node" ] == "" or self .result ["ers_node" ] == "" :
143- self .result ["message" ] = (
144- "Pacemaker cluster isn't stable and does not have primary or secondary node"
145- )
146- self .log (logging .WARNING , self .result ["message" ])
147-
148- except Exception as e :
149- self .handle_error (e )
150- self .result ["end" ] = datetime .now ()
151- self .result ["status" ] = TestStatus .SUCCESS .value
152- self .log (logging .INFO , "Cluster status check completed" )
74+ return self .result ["ascs_node" ] != "" and self .result ["ers_node" ] != ""
15375
15476
15577def run_module () -> None :
@@ -163,7 +85,7 @@ def run_module() -> None:
16385
16486 module = AnsibleModule (argument_spec = module_args , supports_check_mode = True )
16587
166- checker = ClusterStatusChecker (
88+ checker = SCSClusterStatusChecker (
16789 sap_sid = module .params ["sap_sid" ],
16890 ansible_os_family = module .params ["ansible_os_family" ],
16991 )
0 commit comments