Skip to content

Commit 3a60801

Browse files
committed
Add filesystem_freeze module and corresponding unit tests; update ansible-lint exclusions and input-api.yaml
1 parent a166609 commit 3a60801

18 files changed

+388
-190
lines changed

.ansible-lint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ exclude_paths:
22
- src/modules
33
- src/module_utils
44
- .github
5+
- src/roles/ha_db_hana/tasks/files/
56

67
skip_list:
78
- no-handler

src/module_utils/sap_automation_qa.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import subprocess
1212
from typing import Optional, Dict, Any
1313
import xml.etree.ElementTree as ET
14-
import yaml
1514

1615

1716
class TelemetryDataDestination(Enum):
@@ -36,6 +35,9 @@ class TestStatus(Enum):
3635

3736

3837
class Parameters:
38+
"""
39+
This class is used to store the parameters for the test case
40+
"""
3941

4042
def __init__(self, category, id, name, value, expected_value, status):
4143
self.category = category
@@ -45,7 +47,13 @@ def __init__(self, category, id, name, value, expected_value, status):
4547
self.expected_value = expected_value
4648
self.status = status
4749

48-
def to_dict(self):
50+
def to_dict(self) -> Dict[str, Any]:
51+
"""
52+
This method is used to convert the parameters to a dictionary
53+
54+
:return: Dictionary containing the parameters
55+
:rtype: dict
56+
"""
4957
return {
5058
"category": self.category,
5159
"id": self.id,
@@ -117,9 +125,7 @@ def handle_error(self, exception: Exception, stderr: str = None):
117125
self.result["message"] = error_message
118126
self.result["logs"].append(error_message)
119127

120-
def execute_command_subprocess(
121-
self, command: str, shell_command: bool = False
122-
) -> str:
128+
def execute_command_subprocess(self, command: str, shell_command: bool = False) -> str:
123129
"""
124130
Executes a shell command using subprocess with a timeout and logs output or errors.
125131
@@ -130,9 +136,7 @@ def execute_command_subprocess(
130136
:return: Standard output from the command
131137
:rtype: str
132138
"""
133-
command_string = (
134-
command if isinstance(command, str) else " ".join(command).replace("'", "")
135-
)
139+
command_string = command if isinstance(command, str) else " ".join(command).replace("'", "")
136140
self.log(
137141
logging.INFO,
138142
f"Executing command: {command_string}",
@@ -159,7 +163,12 @@ def execute_command_subprocess(
159163

160164
def parse_xml_output(self, xml_output: str) -> Optional[ET.Element]:
161165
"""
162-
Parses the XML output of a command.
166+
Parses the XML output and returns the root element.
167+
168+
:param xml_output: XML output to parse
169+
:type xml_output: str
170+
:return: The root element of the XML output
171+
:rtype: Optional[ET.Element]
163172
"""
164173
if xml_output.startswith("<"):
165174
return ET.fromstring(xml_output)

src/modules/check_indexserver.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
This module is used to check if SAP HANA indexserver is configured.
66
"""
77

8+
import logging
89
from ansible.module_utils.basic import AnsibleModule
910

1011
try:
@@ -65,22 +66,28 @@ def check_indexserver(self) -> None:
6566
)
6667
return
6768

68-
global_ini_path = (
69-
f"/usr/sap/{self.database_sid}/SYS/global/hdb/custom/config/global.ini"
70-
)
71-
69+
global_ini_path = f"/usr/sap/{self.database_sid}/SYS/global/hdb/custom/config/global.ini"
70+
global_ini = []
7271
try:
7372
with open(global_ini_path, "r", encoding="utf-8") as file:
7473
global_ini = [line.strip() for line in file.readlines()]
7574

76-
for os_props in (
77-
os_props_list if isinstance(os_props_list, list) else [os_props_list]
78-
):
75+
self.log(
76+
logging.INFO,
77+
f"Successfully read the global.ini file: {global_ini}",
78+
)
79+
80+
for os_props in os_props_list if isinstance(os_props_list, list) else [os_props_list]:
7981
section_title = list(os_props.keys())[0]
8082
if section_title in global_ini:
8183
section_start = global_ini.index(section_title)
8284
properties_slice = global_ini[section_start + 1 : section_start + 4]
8385

86+
self.log(
87+
logging.INFO,
88+
f"Extracted properties: {properties_slice}",
89+
)
90+
8491
extracted_properties = {
8592
prop.split("=")[0].strip(): prop.split("=")[1].strip()
8693
for prop in properties_slice

src/modules/filesystem_freeze.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
Custom ansible module for formatting the packages list
66
"""
7+
import logging
78
from typing import Dict, Any
89
from ansible.module_utils.basic import AnsibleModule
910

@@ -49,14 +50,18 @@ def run(self) -> Dict[str, Any]:
4950
"""
5051
file_system = self._find_filesystem()
5152

53+
self.log(
54+
logging.INFO,
55+
f"Found the filesystem mounted on /hana/shared: {file_system}",
56+
)
57+
5258
if file_system:
53-
read_only_output = self.execute_command_subprocess(
54-
FREEZE_FILESYSTEM(file_system)
55-
)
59+
read_only_output = self.execute_command_subprocess(FREEZE_FILESYSTEM(file_system))
60+
self.log(logging.INFO, read_only_output)
5661
self.result.update(
5762
{
5863
"changed": True,
59-
"message": "The file system (/hana/shared) was successfully mounted as read-only.",
64+
"message": "The file system (/hana/shared) was successfully mounted read-only.",
6065
"status": TestStatus.SUCCESS.value,
6166
"details": read_only_output,
6267
}

src/modules/get_azure_lb.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import logging
99
import ast
1010
from typing import Dict
11-
from ansible.module_utils.basic import AnsibleModule
1211
from azure.identity import ManagedIdentityCredential
1312
from azure.mgmt.network import NetworkManagementClient
13+
from ansible.module_utils.basic import AnsibleModule
1414

1515
try:
1616
from ansible.module_utils.sap_automation_qa import (
@@ -55,8 +55,9 @@ def _create_network_client(self):
5555

5656
def get_load_balancers(self) -> list:
5757
"""
58-
Get all load balancers in a specific resource group.
58+
Get the list of load balancers in a specific resource group.
5959
60+
:return: List of load balancers
6061
:rtype: list
6162
"""
6263
try:
@@ -73,8 +74,9 @@ def get_load_balancers(self) -> list:
7374

7475
def get_load_balancers_details(self) -> dict:
7576
"""
76-
Get the details of the DB/SCS/ERS load balancers in a specific resource group.
77+
Get the details of the load balancers in a specific resource group.
7778
79+
:return: Dictionary containing the result of the test case.
7880
:rtype: dict
7981
"""
8082
self._create_network_client()
@@ -125,11 +127,13 @@ def check_parameters(entity, parameters_dict, entity_type):
125127

126128
try:
127129
if found_load_balancer:
130+
self.log(
131+
logging.INFO,
132+
f"Found load balancer {found_load_balancer['name']}",
133+
)
128134
self.result[
129135
"message"
130-
] += (
131-
f"Validating load balancer parameters {found_load_balancer['name']}"
132-
)
136+
] += f"Validating load balancer parameters {found_load_balancer['name']}"
133137
for rule in found_load_balancer["load_balancing_rules"]:
134138
try:
135139
check_parameters(
@@ -155,16 +159,13 @@ def check_parameters(entity, parameters_dict, entity_type):
155159
self.handle_error(e)
156160
self.result[
157161
"message"
158-
] += (
159-
f"Failed to validate load balancer probe parameters. {e} \n"
160-
)
162+
] += f"Failed to validate load balancer probe parameters. {e} \n"
161163
continue
162164

163165
failed_parameters = [
164166
param
165167
for param in parameters
166-
if param.get("status", TestStatus.ERROR.value)
167-
== TestStatus.ERROR.value
168+
if param.get("status", TestStatus.ERROR.value) == TestStatus.ERROR.value
168169
]
169170
self.result.update(
170171
{
@@ -176,9 +177,7 @@ def check_parameters(entity, parameters_dict, entity_type):
176177
),
177178
}
178179
)
179-
self.result[
180-
"message"
181-
] += "Successfully validated load balancer parameters"
180+
self.result["message"] += "Successfully validated load balancer parameters"
182181
else:
183182
self.result["message"] += "No load balancer found"
184183

src/modules/location_constraints.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ def __init__(self, ansible_os_family: str):
3131
}
3232
)
3333

34-
def remove_location_constraints(
35-
self, location_constraints: List[ET.Element]
36-
) -> None:
34+
def remove_location_constraints(self, location_constraints: List[ET.Element]) -> None:
3735
"""
3836
Removes the specified location constraints.
3937
@@ -43,8 +41,15 @@ def remove_location_constraints(
4341
for location_constraint in location_constraints:
4442
rsc = location_constraint.attrib.get("rsc")
4543
if rsc:
46-
self.execute_command_subprocess(RSC_CLEAR[self.ansible_os_family](rsc))
47-
self.result["changed"] = True
44+
command_output = self.execute_command_subprocess(
45+
RSC_CLEAR[self.ansible_os_family](rsc)
46+
)
47+
self.result.update(
48+
{
49+
"details": command_output,
50+
"changed": True,
51+
}
52+
)
4853
else:
4954
self.result["changed"] = False
5055

@@ -58,11 +63,7 @@ def location_constraints_exists(self) -> List[ET.Element]:
5863
try:
5964
xml_output = self.execute_command_subprocess(CONSTRAINTS)
6065
self.result["details"] = xml_output
61-
return (
62-
ET.fromstring(xml_output).findall(".//rsc_location")
63-
if xml_output
64-
else []
65-
)
66+
return ET.fromstring(xml_output).findall(".//rsc_location") if xml_output else []
6667
except Exception as e:
6768
self.handle_exception(e)
6869

src/modules/log_parser.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,25 +98,25 @@ def parse_logs(self) -> None:
9898
)
9999
log_time = log_time.replace(year=start_dt.year)
100100
elif self.ansible_os_family == "SUSE":
101-
log_time = datetime.strptime(
102-
line.split(".")[0], "%Y-%m-%dT%H:%M:%S"
103-
)
101+
log_time = datetime.strptime(line.split(".")[0], "%Y-%m-%dT%H:%M:%S")
104102
else:
105103
continue
106104

107105
if start_dt <= log_time <= end_dt and any(
108106
keyword in line for keyword in self.keywords
109107
):
110108
self.result["filtered_logs"].append(
111-
line.translate(
112-
str.maketrans({"\\": "", '"': "", "'": ""})
113-
)
109+
line.translate(str.maketrans({"\\": "", '"': "", "'": ""}))
114110
)
115111
except ValueError:
116112
continue
117113

118-
self.result["filtered_logs"] = json.dumps(self.result["filtered_logs"])
119-
self.result["status"] = TestStatus.SUCCESS.value
114+
self.result.update(
115+
{
116+
"filtered_logs": json.dumps(self.result["filtered_logs"]),
117+
"status": TestStatus.SUCCESS.value,
118+
}
119+
)
120120
except FileNotFoundError as ex:
121121
self.handle_error(ex)
122122
except Exception as e:

src/modules/send_telemetry_data.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ def __init__(self, module_params: Dict[str, Any]):
4848
self.result.update(
4949
{
5050
"telemetry_data": module_params["test_group_json_data"],
51-
"telemetry_data_destination": module_params[
52-
"telemetry_data_destination"
53-
],
51+
"telemetry_data_destination": module_params["telemetry_data_destination"],
5452
"start": datetime.now(),
5553
"end": datetime.now(),
5654
"data_sent": False,
@@ -112,6 +110,10 @@ def send_telemetry_data_to_azuredataexplorer(self, telemetry_json_data: str) ->
112110
)
113111
client = QueuedIngestClient(kcsb)
114112
response = client.ingest_from_dataframe(data_frame, ingestion_properties)
113+
self.log(
114+
logging.INFO,
115+
f"Response from Kusto: {response}",
116+
)
115117
return response
116118

117119
def send_telemetry_data_to_azureloganalytics(
@@ -143,6 +145,10 @@ def send_telemetry_data_to_azureloganalytics(
143145
},
144146
timeout=30,
145147
)
148+
self.log(
149+
logging.INFO,
150+
f"Response from Log Analytics: {response}",
151+
)
146152
return response
147153

148154
def validate_params(self) -> bool:
@@ -151,9 +157,7 @@ def validate_params(self) -> bool:
151157
152158
:return: True if the parameters are valid, False otherwise.
153159
"""
154-
telemetry_data_destination = self.module_params.get(
155-
"telemetry_data_destination"
156-
)
160+
telemetry_data_destination = self.module_params.get("telemetry_data_destination")
157161

158162
if telemetry_data_destination == TelemetryDataDestination.LOG_ANALYTICS.value:
159163
if (
@@ -169,9 +173,7 @@ def validate_params(self) -> bool:
169173
"adx_cluster_fqdn",
170174
"adx_client_id",
171175
]
172-
missing_params = [
173-
param for param in required_params if param not in self.module_params
174-
]
176+
missing_params = [param for param in required_params if param not in self.module_params]
175177
if missing_params:
176178
return False
177179
return True
@@ -209,9 +211,7 @@ def send_telemetry_data(self) -> None:
209211
TelemetryDataDestination.KUSTO.value,
210212
TelemetryDataDestination.LOG_ANALYTICS.value,
211213
]:
212-
self.log(
213-
logging.INFO, "Validating parameters for telemetry data destination "
214-
)
214+
self.log(logging.INFO, "Validating parameters for telemetry data destination ")
215215

216216
if not self.validate_params():
217217
self.result["status"] = (

src/vars/input-api.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,6 @@ commands:
157157
- name: crm_report_cmd
158158
SUSE: "crm_report -f '{{ test_group_start_time }}' /tmp/{{ test_group_invocation_id }}"
159159
REDHAT: "crm_report -f '{{ test_group_start_time }}' --dest /tmp/{{ test_group_invocation_id }} -yes"
160+
161+
sap_sid: "HDB"
162+
db_sid: "HDB"

tests/module_utils/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)