Skip to content

Commit 80f6174

Browse files
committed
Create chargeback role in obeservability FVT jobs
1 parent 98372d7 commit 80f6174

File tree

9 files changed

+263
-0
lines changed

9 files changed

+263
-0
lines changed

roles/observe_chargeback/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
observe_chargeback
2+
=========
3+
4+
Test chargeback in Openstack
5+
6+
Requirements
7+
------------
8+
9+
Role Variables
10+
--------------
11+
12+
For chargeback_tests.yml
13+
14+
chargeback_test_id
15+
- polarion id for test
16+
chargeback_user_list
17+
- List of users to check for resource usage
18+
19+
Dependencies
20+
------------
21+
22+
Openstack on Openshift deployed and telemetry enabled for Openstack.
23+
24+
Example Playbook
25+
----------------
26+
27+
Each tasks/playbook.yml should be called independently via "ansible.builtin.import_role" with appropriate vars passed:
28+
29+
- name: "Verify chargeback reporting"
30+
hosts: controller
31+
gather_facts: no
32+
vars:
33+
chargeback_user_list:
34+
- "user1"
35+
- "user2"
36+
37+
tasks:
38+
- name: "Verify chargeback data"
39+
ansible.builtin.import_role:
40+
name: observe_chargeback
41+
42+
43+
License
44+
-------
45+
46+
Apache 2
47+
48+
Author Information
49+
------------------
50+
51+
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
chargeback_test_id: ""
3+
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import logging
2+
import argparse
3+
from datetime import datetime, timezone, timedelta
4+
from pathlib import Path
5+
from typing import Union
6+
7+
# --- Configure logging with a default level that can be changed ---
8+
logging.basicConfig(
9+
level=logging.INFO,
10+
format='%(asctime)s - %(levelname)s - %(message)s',
11+
datefmt='%Y-%m-%d %H:%M:%S'
12+
)
13+
logger = logging.getLogger()
14+
15+
# --- Define constants for template placeholders ---
16+
PLACEHOLDER_NANO = "NANOSECONDS"
17+
PLACEHOLDER_START = "STARTTIME"
18+
PLACEHOLDER_END = "ENDTIME"
19+
20+
def _format_timestamp(epoch_seconds: float) -> str:
21+
"""
22+
Converts an epoch timestamp into a human-readable UTC string.
23+
24+
Args:
25+
epoch_seconds (float): The timestamp in seconds since the epoch.
26+
27+
Returns:
28+
str: The formatted datetime string (e.g., "2023-10-26T14:30:00 UTC").
29+
"""
30+
try:
31+
dt_object = datetime.fromtimestamp(epoch_seconds, tz=timezone.utc)
32+
return dt_object.strftime("%Y-%m-%dT%H:%M:%S %Z")
33+
except (ValueError, TypeError):
34+
logger.warning(f"Invalid epoch value provided: {epoch_seconds}")
35+
return "INVALID_TIMESTAMP"
36+
37+
def generate_loki_data(
38+
header_path: Path,
39+
template_path: Path,
40+
footer_path: Path,
41+
output_path: Path,
42+
start_time: datetime,
43+
end_time: datetime,
44+
time_step_seconds: int
45+
):
46+
"""
47+
Generates synthetic Loki log data from templates and writes it to a file.
48+
49+
Args:
50+
header_path (Path): Path to the header template file.
51+
template_path (Path): Path to the log entry template file.
52+
footer_path (Path): Path to the footer template file.
53+
output_path (Path): Path for the generated output JSON file.
54+
start_time (datetime): The start time for data generation.
55+
end_time (datetime): The end time for data generation.
56+
time_step_seconds (int): The duration of each log entry in seconds.
57+
"""
58+
# --- Step 1: Load template files ---
59+
try:
60+
logger.info(f"Loading header template from: {header_path}")
61+
header_template = header_path.read_text()
62+
63+
logger.info(f"Loading log template from: {template_path}")
64+
log_template = template_path.read_text()
65+
66+
logger.info(f"Loading footer template from: {footer_path}")
67+
footer_template = footer_path.read_text()
68+
except FileNotFoundError as e:
69+
logger.error(f"Error loading template file: {e}. Aborting.")
70+
raise # Re-raise the exception to be caught in main()
71+
72+
# --- Step 2: Generate data and write to file ---
73+
logger.info(
74+
f"Generating data from {start_time.strftime('%Y-%m-%d')} to "
75+
f"{end_time.strftime('%Y-%m-%d')} with a {time_step_seconds}s step."
76+
)
77+
start_epoch = int(start_time.timestamp())
78+
end_epoch = int(end_time.timestamp())
79+
logger.debug(f"Time range in epoch seconds: {start_epoch} to {end_epoch}")
80+
81+
try:
82+
with output_path.open('w') as f_out:
83+
f_out.write(header_template)
84+
85+
# Loop through the time range and generate each log entry
86+
for current_epoch in range(start_epoch, end_epoch, time_step_seconds):
87+
end_of_step_epoch = current_epoch + time_step_seconds - 1
88+
89+
# Prepare replacement values
90+
nanoseconds = int(current_epoch * 1_000_000_000)
91+
start_str = _format_timestamp(current_epoch)
92+
end_str = _format_timestamp(end_of_step_epoch)
93+
94+
logger.debug(f"Processing epoch: {current_epoch} -> nanoseconds: {nanoseconds}")
95+
logger.debug(f" - Start time: {start_str}")
96+
logger.debug(f" - End time: {end_str}")
97+
98+
# Perform replacement on the template
99+
log_entry = log_template.replace(PLACEHOLDER_NANO, str(nanoseconds))
100+
log_entry = log_entry.replace(PLACEHOLDER_START, start_str)
101+
log_entry = log_entry.replace(PLACEHOLDER_END, end_str)
102+
103+
f_out.write(log_entry)
104+
105+
# Append the footer at the end of the file
106+
f_out.write(footer_template)
107+
108+
logger.info(f"Successfully generated synthetic data to '{output_path}'")
109+
except IOError as e:
110+
logger.error(f"Failed to write to output file '{output_path}': {e}")
111+
except Exception as e:
112+
logger.error(f"An unexpected error occurred during generation: {e}")
113+
114+
def main():
115+
"""Main entry point for the script."""
116+
parser = argparse.ArgumentParser(
117+
description="Generate synthetic Loki log data from templates.",
118+
formatter_class=argparse.ArgumentDefaultsHelpFormatter
119+
)
120+
# --- Required File Path Arguments ---
121+
parser.add_argument("-o", "--output", required=True, help="Path to the output file.")
122+
parser.add_argument("--header", required=True, help="Path to the header template file.")
123+
parser.add_argument("--template", required=True, help="Path to the log entry template file.")
124+
parser.add_argument("--footer", required=True, help="Path to the footer template file.")
125+
126+
# --- Optional Generation Arguments ---
127+
parser.add_argument("--days", type=int, default=30, help="How many days of data to generate, ending today.")
128+
parser.add_argument("--step", type=int, default=300, help="Time step in seconds for each log entry.")
129+
130+
# --- Optional Utility Arguments ---
131+
parser.add_argument("--debug", action="store_true", help="Enable debug level logging for verbose output.")
132+
133+
args = parser.parse_args()
134+
135+
if args.debug:
136+
logger.setLevel(logging.DEBUG)
137+
logger.debug("Debug mode enabled.")
138+
139+
# Define the time range for data generation
140+
end_time_utc = datetime.now(timezone.utc)
141+
start_time_utc = end_time_utc - timedelta(days=args.days)
142+
logger.debug(f"Time range calculated: {start_time_utc} to {end_time_utc}")
143+
144+
# Run the generator
145+
try:
146+
generate_loki_data(
147+
header_path=Path(args.header),
148+
template_path=Path(args.template),
149+
footer_path=Path(args.footer),
150+
output_path=Path(args.output),
151+
start_time=start_time_utc,
152+
end_time=end_time_utc,
153+
time_step_seconds=args.step
154+
)
155+
except FileNotFoundError:
156+
logger.error("Process aborted because a template file was not found.")
157+
except Exception as e:
158+
logger.critical(f"A critical, unhandled error stopped the script: {e}")
159+
160+
161+
if __name__ == "__main__":
162+
main()
163+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
]}]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"streams": [{ "stream": { "service": "cloudkitty" }, "values": [
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
"NANOSECONDS",
3+
"{\"start\": \"STARTTIME\", \"end\": \"ENDTIME\", \"type\": \"image.size\", \"unit\": \"MiB\", \"description\": null, \"qty\": 20.6875, \"price\": 0.0206875, \"groupby\": {\"id\": \"cd65d30f-8b94-4fa3-95dc-e3b429f479b2\", \"project_id\": \"0030775de80e4d84a4fd0d73e0a1b3a7\", \"user_id\": null, \"week_of_the_year\": \"37\", \"day_of_the_year\": \"258\", \"month\": \"9\", \"year\": \"2025\"}, \"metadata\": {\"container_format\": \"bare\", \"disk_format\": \"qcow2\"}}"
4+
],
5+
[
6+
"NANOSECONDS",
7+
"{\"start\": \"STARTTIME\", \"end\": \"ENDTIME\", \"type\": \"instance\", \"unit\": \"instance\", \"description\": null, \"qty\": 1.0, \"price\": 0.3, \"groupby\": {\"id\": \"de168c31-ed44-4a1a-a079-51bd238a91d6\", \"project_id\": \"9cf5bcfc61a24682acc448af2d062ad2\", \"user_id\": \"c29ab6e886354bbd88ee9899e62d1d40\", \"week_of_the_year\": \"37\", \"day_of_the_year\": \"258\", \"month\": \"9\", \"year\": \"2025\"}, \"metadata\": {\"flavor_name\": \"m1.tiny\", \"flavor_id\": \"1\", \"vcpus\": \"\"}}"
8+
],
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
galaxy_info:
3+
author: Alex Yefimov
4+
description: Tests the chargeback feature is set up in OpenStack running running in OpenShift
5+
company: Red Hat
6+
7+
license: Apache-2.0
8+
9+
min_ansible_version: "2.1"
10+
11+
galaxy_tags: []
12+
13+
dependencies: []
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
- name: Verify OpenStack Rating Module are correctly enabled, disabled
3+
hosts: localhost
4+
connection: local
5+
gather_facts: false
6+
7+
tasks:
8+
- name: Verify that exactly 2 rating modules are enabled
9+
become: true
10+
ansible.builtin.shell:
11+
cmd: "[[ $(openstack rating module list | grep True | wc -l) -eq 2 ]]"
12+
changed_when: false
13+
14+
- name: Verify that exactly 1 'pyscripts' module is disabled
15+
become: true
16+
ansible.builtin.shell:
17+
cmd: "[[ $(openstack rating module list | grep pyscripts | grep False | wc -l) -eq 1 ]]"
18+
changed_when: false
19+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
- name: "Verify chargeback tests"
3+
when: chargeback_user_list is defined
4+
ansible.builtin.include_tasks: "chargeback_tests.yml"

0 commit comments

Comments
 (0)