5 from dataclasses
import asdict
, dataclass
, field
6 from typing
import Any
, List
10 from .util
import replace_grafana_expr_variables
27 exp_samples
: List
[ExprSample
] = field(default_factory
=list)
32 input_series
: List
[InputSeries
] = field(default_factory
=list)
33 promql_expr_test
: List
[PromqlExprTest
] = field(default_factory
=list)
38 evaluation_interval
: str = '1m'
39 tests
: List
[Test
] = field(default_factory
=list)
44 Base class to provide prometheus query test capabilities. After setting up
45 the query test with its input and expected output it's expected to run promtool.
47 https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/#test-yml
49 The workflow of testing would be something like:
51 # add prometheus query to test
52 self.set_expression('bonding_slaves > 0')
54 # add some prometheus input series
55 self.add_series('bonding_slaves{master="bond0"}', '2')
56 self.add_series('bonding_slaves{master="bond1"}', '3')
57 self.add_series('node_network_receive_bytes{instance="127.0.0.1",
58 device="eth1"}', "10 100 230 22")
60 # expected output of the query
61 self.add_exp_samples('bonding_slaves{master="bond0"}', 2)
62 self.add_exp_samples('bonding_slaves{master="bond1"}', 3)
64 # at last, always call promtool with:
65 self.assertTrue(self.run_promtool())
66 # assertTrue means it expect promtool to succeed
70 self
.test_output_file
= tempfile
.NamedTemporaryFile('w+')
72 self
.test_file
= TestFile()
74 self
.promql_expr_test
= PromqlExprTest()
75 self
.test
.promql_expr_test
.append(self
.promql_expr_test
)
76 self
.test_file
.tests
.append(self
.test
)
81 self
.test_output_file
.close()
84 def set_evaluation_interval(self
, interval
: int, unit
: str = 'm') -> None:
86 Set the evaluation interval of the time series
89 interval (int): number of units.
90 unit (str): unit type: 'ms', 's', 'm', etc...
92 self
.test_file
.evaluation_interval
= f
'{interval}{unit}'
94 def set_interval(self
, interval
: int, unit
: str = 'm') -> None:
96 Set the duration of the time series
99 interval (int): number of units.
100 unit (str): unit type: 'ms', 's', 'm', etc...
102 self
.test
.interval
= f
'{interval}{unit}'
104 def set_expression(self
, expr
: str) -> None:
106 Set the prometheus expression/query used to filter data.
109 expr(str): expression/query.
111 self
.promql_expr_test
.expr
= expr
113 def add_series(self
, series
: str, values
: str) -> None:
115 Add a series to the input.
118 series(str): Prometheus series.
119 Notation: '<metric name>{<label name>=<label value>, ...}'
120 values(str): Value of the series.
122 input_series
= InputSeries(series
=series
, values
=values
)
123 self
.test
.input_series
.append(input_series
)
125 def set_eval_time(self
, eval_time
: int, unit
: str = 'm') -> None:
127 Set the time when the expression will be evaluated
130 interval (int): number of units.
131 unit (str): unit type: 'ms', 's', 'm', etc...
133 self
.promql_expr_test
.eval_time
= f
'{eval_time}{unit}'
135 def add_exp_samples(self
, sample
: str, values
: Any
) -> None:
137 Add an expected sample/output of the query given the series/input
140 sample(str): Expected sample.
141 Notation: '<metric name>{<label name>=<label value>, ...}'
142 values(Any): Value of the sample.
144 expr_sample
= ExprSample(labels
=sample
, value
=values
)
145 self
.promql_expr_test
.exp_samples
.append(expr_sample
)
147 def set_variable(self
, variable
: str, value
: str):
149 If a query makes use of grafonnet variables, for example
150 '$osd_hosts', you should change this to a real value. Example:
153 > self.set_expression('bonding_slaves{master="$osd_hosts"} > 0')
154 > self.set_variable('osd_hosts', '127.0.0.1')
156 > bonding_slaves{master="127.0.0.1"} > 0
159 variable(str): Variable name
160 value(str): Value to replace variable with
163 self
.variables
[variable
] = value
165 def run_promtool(self
):
167 Run promtool to test the query after setting up the input, output
168 and extra parameters.
171 bool: True if successful, False otherwise.
174 for variable
, value
in self
.variables
.items():
175 expr
= self
.promql_expr_test
.expr
176 new_expr
= replace_grafana_expr_variables(expr
, variable
, value
)
177 self
.set_expression(new_expr
)
179 test_as_dict
= asdict(self
.test_file
)
180 yaml
.dump(test_as_dict
, self
.test_output_file
)
182 args
= f
'promtool test rules {self.test_output_file.name}'.split()
184 subprocess
.run(args
, check
=True)
186 except subprocess
.CalledProcessError
as process_error
:
187 print(yaml
.dump(test_as_dict
))
188 print(process_error
.stderr
)