Skip to content

Commit 5520880

Browse files
committed
Changed to branch concept, YAML structure simplified as changed to Dict.
1 parent 6bac8b1 commit 5520880

7 files changed

Lines changed: 80 additions & 83 deletions

File tree

.buildkite/scripts/health-report-tests/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Description
22
This package for integration tests of the Health Report API.
3-
Export `LS_VERSION` (major and minor version such as 8.x) to run on a specific branch. By default, it uses the main branch.
3+
Export `LS_BRANCH` to run on a specific branch. By default, it uses the main branch.
44

55
## How to run the Health Report Integration test?
66
### Prerequisites
@@ -12,4 +12,7 @@ python3 -mpip install -r .buildkite/scripts/health-report-tests/requirements.txt
1212
### Run the integration tests
1313
```shell
1414
python3 .buildkite/scripts/health-report-tests/main.py
15-
```
15+
```
16+
17+
### Troubleshooting
18+
- If you get `WARNING: pip is configured with locations that require TLS/SSL,...` warning message, make sure you have python >=3.12.4 installed.

.buildkite/scripts/health-report-tests/bootstrap.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,31 @@ def __init__(self) -> None:
1717
f"""
1818
A constructor of the {Bootstrap}.
1919
Returns:
20-
Resolves Logstash branch considering provided LS_VERSION
20+
Resolves Logstash branch considering provided LS_BRANCH
2121
Checks out git branch
2222
"""
23-
logstash_version = os.environ.get("LS_VERSION")
24-
if logstash_version is None:
23+
logstash_branch = os.environ.get("LS_BRANCH")
24+
if logstash_branch is None:
2525
# version is not specified, use the main branch, no need to git checkout
26-
print(f"LS_VERSION is not specified, using main branch.")
26+
print(f"LS_BRANCH is not specified, using main branch.")
2727
else:
28-
# LS_VERSION accepts major latest as a major.x or specific version as X.Y
29-
if logstash_version.find(".x") == -1:
30-
print(f"Using specified branch: {logstash_version}")
31-
util.git_check_out_branch(logstash_version)
28+
# LS_BRANCH accepts major latest as a major.x or specific branch as X.Y
29+
if logstash_branch.find(".x") == -1:
30+
print(f"Using specified branch: {logstash_branch}")
31+
util.git_check_out_branch(logstash_branch)
3232
else:
33-
major_version = logstash_version.split(".")[0]
33+
major_version = logstash_branch.split(".")[0]
3434
if major_version and major_version.isnumeric():
3535
resolved_version = self.__resolve_latest_stack_version_for(major_version)
3636
minor_version = resolved_version.split(".")[1]
3737
branch = major_version + "." + minor_version
3838
print(f"Using resolved branch: {branch}")
3939
util.git_check_out_branch(branch)
4040
else:
41-
raise ValueError(f"Invalid value set to LS_VERSION. Please set it properly (ex: 8.x or 9.0) and "
41+
raise ValueError(f"Invalid value set to LS_BRANCH. Please set it properly (ex: 8.x or 9.0) and "
4242
f"rerun again")
4343

44-
def __resolve_latest_stack_version_for(self, major_version: str) -> None:
44+
def __resolve_latest_stack_version_for(self, major_version: str) -> str:
4545
resolved_version = ""
4646
response = util.call_url_with_retry(self.ELASTIC_STACK_VERSIONS_URL)
4747
release_versions = response.json()["versions"]

.buildkite/scripts/health-report-tests/config_validator.py

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,45 @@
33

44

55
class ConfigValidator:
6-
7-
REQUIRED_KEYS: Dict[str, List[str]] = {
6+
REQUIRED_KEYS: Dict[str, List[Any]] = {
87
"config": ["pipeline.id", "config.string"],
9-
"expectation": ["status", "symptom", "diagnosis", "impacts", "details"],
10-
"diagnosis": ["cause"],
11-
"impacts": ["description", "impact_areas"],
12-
"details": ["run_state"],
8+
"expectation": ["status", "symptom", {"diagnosis": ["cause"]},
9+
{"impacts": ["description", "impact_areas"], "details": ["run_state"]}]
1310
}
1411

1512
def __init__(self):
1613
self.yaml_content = None
1714

18-
def __validate_keys(self, actual_keys: List[str], expected_keys: List[str], section: str) -> bool:
19-
"""Validate the keys at the current level."""
20-
missing_keys = set(expected_keys) - set(actual_keys)
21-
if len(missing_keys) == len(expected_keys):
22-
print(f"Missing keys in {section}: {missing_keys}")
23-
return False
24-
return True
25-
26-
def __validate_config(self, config_list: List[Dict[str, Any]]) -> bool:
27-
"""Validate the 'config' section."""
28-
for config_item in config_list:
29-
if not self.__validate_keys(list(config_item.keys()), self.REQUIRED_KEYS["config"], "config"):
15+
def __validate_keys(self, data: Dict[str, Any], required_keys: Dict[str, List[Any]]) -> bool:
16+
for key, required_list in required_keys.items():
17+
if key not in data:
18+
print(f"Missing top-level key: {key}")
3019
return False
20+
for item in required_list:
21+
if isinstance(item, str):
22+
if not self.__check_nested_key(data[key], item):
23+
print(f"Missing nested key: {item} in {key}")
24+
return False
25+
elif isinstance(item, dict):
26+
for sub_key, sub_value in item.items():
27+
if sub_key not in data[key]:
28+
print(f"Missing key: {sub_key} in {key}")
29+
return False
30+
# Recursively check the nested dictionary
31+
if not self.__validate_keys(data[key][sub_key], {sub_key: sub_value}):
32+
return False
3133
return True
3234

33-
def __validate_expectation(self, expectation_list: List[Dict[str, Any]]) -> bool:
34-
"""Validate the 'expectation' section."""
35-
for expectation_item in expectation_list:
36-
if not self.__validate_keys(list(expectation_item.keys()), self.REQUIRED_KEYS["expectation"], "expectation"):
35+
def __check_nested_key(self, data: Dict[str, Any], nested_key: str) -> bool:
36+
keys = nested_key.split('.')
37+
for key in keys:
38+
if key not in data:
3739
return False
38-
if "diagnosis" in expectation_item:
39-
for diagnosis in expectation_item["diagnosis"]:
40-
if not self.__validate_keys(list(diagnosis.keys()), self.REQUIRED_KEYS["diagnosis"], "diagnosis"):
41-
return False
42-
if "impacts" in expectation_item:
43-
for impact in expectation_item["impacts"]:
44-
if not self.__validate_keys(list(impact.keys()), self.REQUIRED_KEYS["impacts"], "impacts"):
45-
return False
46-
if "details" in expectation_item:
47-
for detail in expectation_item["details"]:
48-
if not self.__validate_keys(list(detail.keys()), self.REQUIRED_KEYS["details"], "details"):
49-
return False
5040
return True
5141

5242
def load(self, file_path: str) -> None:
5343
"""Load the YAML file content into self.yaml_content."""
54-
self.yaml_content: Union[List[Dict[str, Any]], None] = None
44+
self.yaml_content: [Dict[str, Any]] = None
5545
try:
5646
with open(file_path, 'r') as file:
5747
self.yaml_content = yaml.safe_load(file)
@@ -65,16 +55,19 @@ def is_valid(self) -> bool:
6555
print(f"YAML content is empty.")
6656
return False
6757

68-
if not isinstance(self.yaml_content, list):
69-
print(f"YAML structure is not as expected, it should start with a list.")
58+
if not isinstance(self.yaml_content, Dict):
59+
print(f"YAML structure is not as expected, it should start with a Dict.")
7060
return False
7161

62+
required_config_keys = list(self.REQUIRED_KEYS.keys())
7263
for item in self.yaml_content:
73-
if "config" in item and not self.__validate_config(item["config"]):
74-
return False
75-
76-
if "expectation" in item and not self.__validate_expectation(item["expectation"]):
64+
if item == "name":
65+
continue
66+
if item not in required_config_keys:
7767
return False
7868

79-
print(f"YAML file validation successful!")
69+
if self.__validate_keys(self.yaml_content, self.REQUIRED_KEYS):
70+
print("Valid YAML content detected.")
71+
else:
72+
print("YAML validation failed.")
8073
return True

.buildkite/scripts/health-report-tests/main.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from scenario_executor import ScenarioExecutor
88
from config_validator import ConfigValidator
99
import yaml
10-
import util
1110

1211

1312
class BootstrapContextManager:
@@ -18,15 +17,15 @@ def __init__(self):
1817
def __enter__(self):
1918
print(f"Starting Logstash Health Report Integration test.")
2019
self.bootstrap = Bootstrap()
21-
# self.bootstrap.build_logstash()
20+
self.bootstrap.build_logstash()
2221

2322
plugin_path = os.getcwd() + "/qa/support/logstash-integration-failure_injector/logstash-integration" \
2423
"-failure_injector-*.gem"
2524
matching_files = glob.glob(plugin_path)
2625
if len(matching_files) == 0:
2726
raise ValueError(f"Could not find logstash-integration-failure_injector plugin.")
2827

29-
# self.bootstrap.install_plugin(matching_files[0])
28+
self.bootstrap.install_plugin(matching_files[0])
3029
print(f"logstash-integration-failure_injector successfully installed.")
3130
return self.bootstrap
3231

@@ -56,13 +55,13 @@ def main():
5655

5756
for scenario_file in scenario_files:
5857
with open(scenario_file, 'r') as file:
59-
# scenario_content: Union[List[Dict[str, Any]], None] = None
58+
# scenario_content: Dict[str, Any] = None
6059
scenario_content = yaml.safe_load(file)
61-
scenario_name = util.get_element_of_array(scenario_content, 'name')
62-
config = util.get_element_of_array(scenario_content, 'config')
60+
scenario_name = scenario_content['name']
61+
config = scenario_content['config']
6362
if config is not None:
6463
bootstrap.apply_config(config)
65-
expectation = util.get_element_of_array(scenario_content, 'expectation')
64+
expectation = scenario_content['expectation']
6665
process = bootstrap.run_logstash()
6766
if process is not None:
6867
scenario_executor.on(scenario_name, expectation)

.buildkite/scripts/health-report-tests/scenario_executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ def __is_expected(self, scenario_content: list) -> None:
4040
def on(self, scenario_name: str, scenario_content: list) -> None:
4141
print(f"Testing the scenario: {scenario_content}")
4242
if self.__is_expected(scenario_content) is False:
43-
raise Exception(f"{scenario_name} failed.")
43+
raise Exception(f"{scenario_name} failed.")
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
- name: "Slow start pipeline"
2-
- config:
1+
name: "Slow start pipeline"
2+
config:
33
- pipeline.id: slow-start-pp
44
config.string: |
55
input { heartbeat {} }
66
filter { failure_injector { degrade_at => [register] } }
77
output { stdout {} }
8-
- expectation:
9-
- status: yellow
10-
- symptom: "The pipeline is degraded or at risk of becoming unhealthy; 1 area is impacted and 1 diagnosis is available."
11-
- diagnosis:
12-
- cause: "pipeline is loading"
13-
- impacts:
14-
- description: "pipeline is loading"
15-
- impact_areas: "pipeline_execution"
16-
- details:
17-
- run_state: "LOADING"
8+
expectation:
9+
- status: yellow
10+
- symptom: "The pipeline is degraded or at risk of becoming unhealthy; 1 area is impacted and 1 diagnosis is available."
11+
- diagnosis:
12+
- cause: "pipeline is loading"
13+
- impacts:
14+
- description: "pipeline is loading"
15+
- impact_areas: "pipeline_execution"
16+
- details:
17+
- run_state: "LOADING"

.buildkite/scripts/health-report-tests/util.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import requests
22
import subprocess
3-
from typing import Any, List, Dict, Union
43
from requests.adapters import HTTPAdapter, Retry
54

65

76
def call_url_with_retry(url: str, max_retries: int = 5, delay: int = 1) -> requests.Response:
7+
f"""
8+
Calls the given {url} with maximum of {max_retries} retries with {delay} delay.
9+
"""
810
schema = "https://" if "https://" in url else "http://"
911
session = requests.Session()
1012
# retry on most common failures such as connection timeout(408), etc...
@@ -13,21 +15,21 @@ def call_url_with_retry(url: str, max_retries: int = 5, delay: int = 1) -> reque
1315
return session.get(url)
1416

1517

16-
def git_check_out_branch(branch_name: str) -> bool:
18+
def git_check_out_branch(branch_name: str) -> None:
19+
f"""
20+
Checks out specified branch or fails with error if checkout operation fails.
21+
"""
1722
run_or_raise_error(["git", "checkout", branch_name],
1823
"Error occurred while checking out the " + branch_name + " branch")
1924

2025

2126
def run_or_raise_error(commands: list, error_message):
27+
f"""
28+
Executes the {list} commands and raises an {Exception} if opration fails.
29+
"""
2230
result = subprocess.run(commands, universal_newlines=True, stdout=subprocess.PIPE)
2331
if result.returncode != 0:
2432
full_error_message = (error_message + ", output: " + result.stdout.decode('utf-8')) \
2533
if result.stdout else error_message
2634
raise Exception(f"{full_error_message}")
2735

28-
29-
def get_element_of_array(data: Union[List[Dict[str, Any]], None], key: str) -> str:
30-
for element in data:
31-
if key in element:
32-
return element[key]
33-
return None

0 commit comments

Comments
 (0)