From 92b526db8131eae05a5787d3ebe9f5ee59fe5366 Mon Sep 17 00:00:00 2001 From: Pere Diaz Bou Date: Tue, 26 Oct 2021 14:42:17 +0200 Subject: [PATCH] monitoring/grafana: Grafana query tester Signed-off-by: Pere Diaz Bou (cherry picked from commit 44d3e4c264506154373ffaeb13d6c924c580e6b5) --- monitoring/grafana/dashboards/.pylintrc | 1 + monitoring/grafana/dashboards/CMakeLists.txt | 6 +- .../grafana/dashboards/requirements-lint.txt | 18 ++ .../grafana/dashboards/tests/__init__.py | 187 ++++++++++++++++++ .../dashboards/tests/features/__init__.py | 0 .../tests/features/ceph-cluster.feature | 10 + .../dashboards/tests/features/environment.py | 135 +++++++++++++ .../tests/features/hosts_overview.feature | 28 +++ .../dashboards/tests/features/self.feature | 68 +++++++ .../tests/features/steps/__init__.py | 1 + .../grafana/dashboards/tests/requirements.txt | 12 ++ monitoring/grafana/dashboards/tests/util.py | 68 +++++++ monitoring/grafana/dashboards/tox.ini | 24 ++- .../dashboard/ci/check_grafana_dashboards.py | 16 ++ 14 files changed, 571 insertions(+), 3 deletions(-) create mode 120000 monitoring/grafana/dashboards/.pylintrc create mode 100644 monitoring/grafana/dashboards/requirements-lint.txt create mode 100644 monitoring/grafana/dashboards/tests/__init__.py create mode 100644 monitoring/grafana/dashboards/tests/features/__init__.py create mode 100644 monitoring/grafana/dashboards/tests/features/ceph-cluster.feature create mode 100644 monitoring/grafana/dashboards/tests/features/environment.py create mode 100644 monitoring/grafana/dashboards/tests/features/hosts_overview.feature create mode 100644 monitoring/grafana/dashboards/tests/features/self.feature create mode 100644 monitoring/grafana/dashboards/tests/features/steps/__init__.py create mode 100644 monitoring/grafana/dashboards/tests/requirements.txt create mode 100644 monitoring/grafana/dashboards/tests/util.py diff --git a/monitoring/grafana/dashboards/.pylintrc b/monitoring/grafana/dashboards/.pylintrc new file mode 120000 index 0000000000000..aa04b020cb4c1 --- /dev/null +++ b/monitoring/grafana/dashboards/.pylintrc @@ -0,0 +1 @@ +../../../src/pybind/mgr/dashboard/.pylintrc \ No newline at end of file diff --git a/monitoring/grafana/dashboards/CMakeLists.txt b/monitoring/grafana/dashboards/CMakeLists.txt index 65037cbd843ad..091f6ceeb2834 100644 --- a/monitoring/grafana/dashboards/CMakeLists.txt +++ b/monitoring/grafana/dashboards/CMakeLists.txt @@ -12,7 +12,9 @@ endif() if(WITH_GRAFANA) include(AddCephTest) - add_tox_test(grafana TOX_ENVS grafonnet-check) + add_tox_test(grafana-check TOX_ENVS grafonnet-check) + add_tox_test(grafana-query-test TOX_ENVS promql-query-test) + add_tox_test(grafana-lint TOX_ENVS lint) set(ver 0.1.0) set(name grafonnet-lib) include(ExternalProject) @@ -27,7 +29,7 @@ if(WITH_GRAFANA) ${name}) ExternalProject_Get_Property(${name} SOURCE_DIR) set_property( - TEST run-tox-grafana + TEST run-tox-grafana-check run-tox-grafana-query-test run-tox-grafana-lint APPEND PROPERTY ENVIRONMENT GRAFONNET_PATH=${SOURCE_DIR}/grafonnet) diff --git a/monitoring/grafana/dashboards/requirements-lint.txt b/monitoring/grafana/dashboards/requirements-lint.txt new file mode 100644 index 0000000000000..f9a3c772f79dd --- /dev/null +++ b/monitoring/grafana/dashboards/requirements-lint.txt @@ -0,0 +1,18 @@ +attrs==21.2.0 +behave==1.2.6 +py==1.10.0 +pyparsing==2.4.7 +PyYAML==6.0 +types-PyYAML==6.0.0 +typing-extensions==3.10.0.2 +termcolor==1.1.0 +types-termcolor==1.1.2 +dataclasses==0.6 +types-dataclasses==0.6.1 +six==1.16.0 +toml==0.10.2 +pylint==2.6.0 +isort==5.10.0 +mypy==0.910 +mypy-extensions==0.4.3 +prettytable==2.4.0 diff --git a/monitoring/grafana/dashboards/tests/__init__.py b/monitoring/grafana/dashboards/tests/__init__.py new file mode 100644 index 0000000000000..204a5f0d721b2 --- /dev/null +++ b/monitoring/grafana/dashboards/tests/__init__.py @@ -0,0 +1,187 @@ +import re +import subprocess +import sys +import tempfile +from dataclasses import asdict, dataclass, field +from typing import Any, List + +import yaml + + +@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: '{