import re import subprocess import sys import tempfile from dataclasses import asdict, dataclass, field from typing import Any, List import yaml from .util import replace_grafana_expr_variables @dataclass class InputSeries: series: str = '' values: str = '' @dataclass class ExprSample: labels: str = '' value: float = -1 @dataclass class PromqlExprTest: expr: str = '' eval_time: str = '1m' exp_samples: List[ExprSample] = field(default_factory=list) @dataclass class Test: interval: str = '1m' input_series: List[InputSeries] = field(default_factory=list) promql_expr_test: List[PromqlExprTest] = field(default_factory=list) @dataclass class TestFile: evaluation_interval: str = '1m' tests: List[Test] = field(default_factory=list) class PromqlTest: """ Base class to provide prometheus query test capabilities. After setting up the query test with its input and expected output it's expected to run promtool. https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/#test-yml The workflow of testing would be something like: # add prometheus query to test self.set_expression('bonding_slaves > 0') # add some prometheus input series self.add_series('bonding_slaves{master="bond0"}', '2') self.add_series('bonding_slaves{master="bond1"}', '3') self.add_series('node_network_receive_bytes{instance="127.0.0.1", device="eth1"}', "10 100 230 22") # expected output of the query self.add_exp_samples('bonding_slaves{master="bond0"}', 2) self.add_exp_samples('bonding_slaves{master="bond1"}', 3) # at last, always call promtool with: self.assertTrue(self.run_promtool()) # assertTrue means it expect promtool to succeed """ def __init__(self): self.test_output_file = tempfile.NamedTemporaryFile('w+') self.test_file = TestFile() self.test = Test() self.promql_expr_test = PromqlExprTest() self.test.promql_expr_test.append(self.promql_expr_test) self.test_file.tests.append(self.test) self.variables = {} def __del__(self): self.test_output_file.close() def set_evaluation_interval(self, interval: int, unit: str = 'm') -> None: """ Set the evaluation interval of the time series Args: interval (int): number of units. unit (str): unit type: 'ms', 's', 'm', etc... """ self.test_file.evaluation_interval = f'{interval}{unit}' def set_interval(self, interval: int, unit: str = 'm') -> None: """ Set the duration of the time series Args: interval (int): number of units. unit (str): unit type: 'ms', 's', 'm', etc... """ self.test.interval = f'{interval}{unit}' def set_expression(self, expr: str) -> None: """ Set the prometheus expression/query used to filter data. Args: expr(str): expression/query. """ self.promql_expr_test.expr = expr def add_series(self, series: str, values: str) -> None: """ Add a series to the input. Args: series(str): Prometheus series. Notation: '{