From 26c737dec32391c996c727663c1d73342960d3bc Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 25 Jun 2022 03:29:20 +0200 Subject: [PATCH 1/5] Data source health check: Support data source `jaeger` --- README.md | 1 + docs/datasource-state.md | 1 - examples/datasource-health-probe.rst | 8 +++ grafana_client/elements/datasource.py | 18 +++++++ grafana_client/knowledge.py | 5 ++ test/elements/test_datasource_fixtures.py | 8 +++ test/elements/test_datasource_health.py | 59 +++++++++++++++++++++++ 7 files changed, 99 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a970c7..75bea00 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ implemented as of June 2022. - Elasticsearch - Graphite - InfluxDB +- Jaeger - OpenTSDB - PostgreSQL - Prometheus diff --git a/docs/datasource-state.md b/docs/datasource-state.md index 7d2f342..01b9598 100644 --- a/docs/datasource-state.md +++ b/docs/datasource-state.md @@ -19,7 +19,6 @@ stackdriver testdata ### UNKNOWN -jaeger loki mssql tempo diff --git a/examples/datasource-health-probe.rst b/examples/datasource-health-probe.rst index de4f259..d21d278 100644 --- a/examples/datasource-health-probe.rst +++ b/examples/datasource-health-probe.rst @@ -134,6 +134,14 @@ InfluxDB 2.x influx bucket list --org=example +Jaeger +====== +:: + + docker run --rm -it --name=jaeger --publish=16686:16686 jaegertracing/all-in-one:1 + python examples/datasource-health-probe.py --type=jaeger --url=http://host.docker.internal:16686 + + MariaDB / MySQL =============== :: diff --git a/grafana_client/elements/datasource.py b/grafana_client/elements/datasource.py index 8a51d57..3c346ba 100644 --- a/grafana_client/elements/datasource.py +++ b/grafana_client/elements/datasource.py @@ -340,6 +340,8 @@ def health_check(self, datasource: Union[DatasourceIdentifier, Dict]) -> Datasou else: if "results" in response: success, message = self.parse_health_response_results(response=response) + elif "data" in response: + success, message = self.parse_health_response_data(response=response) else: message = f"Response lacks expected keys 'results' or 'data'" @@ -542,3 +544,19 @@ def parse_health_response_results(response: Dict) -> Tuple[bool, str]: message = f"FATAL: Unknown response type '{type(results)}'. Expected: dictionary or list." return success, message + + @staticmethod + def parse_health_response_data(response: Dict) -> Tuple[bool, str]: + """ + Response from Jaeger:: + + {"data":["jaeger-query"],"total":1,"limit":0,"offset":0,"errors":null} + """ + success = False + message = str(response["data"]) + if "errors" in response and response["errors"]: + message = str(response["errors"]) + else: + success = True + + return success, message diff --git a/grafana_client/knowledge.py b/grafana_client/knowledge.py index 9c5fbe1..d71f62e 100644 --- a/grafana_client/knowledge.py +++ b/grafana_client/knowledge.py @@ -68,6 +68,8 @@ def datasource_factory(datasource: DatasourceModel) -> DatasourceModel: datasource.secureJsonFields = { "token": False, } + elif datasource.type == "jaeger": + datasource.access = "proxy" elif datasource.type == "opentsdb": datasource.access = "proxy" datasource.jsonData = { @@ -139,6 +141,8 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U ) else: raise KeyError(f"InfluxDB dialect '{dialect}' unknown") + elif datasource_type == "jaeger": + query = {} elif datasource_type == "mysql": query = { "refId": "test", @@ -205,6 +209,7 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U "influxdb": "SHOW RETENTION POLICIES on _internal", "influxdb+influxql": "SHOW RETENTION POLICIES on _internal", "influxdb+flux": "buckets()", + "jaeger": "url:///datasources/proxy/{datasource_id}/api/services", "mysql": "SELECT 1", "opentsdb": "url:///datasources/proxy/{datasource_id}/api/suggest?type=metrics&q=cpu&max=100", "postgres": "SELECT 1", diff --git a/test/elements/test_datasource_fixtures.py b/test/elements/test_datasource_fixtures.py index 56ce29f..1f58b8c 100644 --- a/test/elements/test_datasource_fixtures.py +++ b/test/elements/test_datasource_fixtures.py @@ -32,6 +32,14 @@ "jsonData": {"httpMode": "POST", "version": "InfluxQL"}, } +JAEGER_DATASOURCE = { + "id": 53, + "uid": "DbtFe237k", + "name": "Jaeger", + "type": "jaeger", + "access": "proxy", +} + MYSQL_DATASOURCE = { "id": 51, "uid": "7CpzLp37z", diff --git a/test/elements/test_datasource_health.py b/test/elements/test_datasource_health.py index 37e38db..e89146e 100644 --- a/test/elements/test_datasource_health.py +++ b/test/elements/test_datasource_health.py @@ -7,6 +7,7 @@ ELASTICSEARCH_DATASOURCE, GRAPHITE_DATASOURCE, INFLUXDB1_DATASOURCE, + JAEGER_DATASOURCE, MYSQL_DATASOURCE, OPENTSDB_DATASOURCE, POSTGRES_DATASOURCE, @@ -262,6 +263,64 @@ def test_health_check_influxdb1(self, m): ), ) + @requests_mock.Mocker() + def test_health_check_jaeger_success(self, m): + m.get( + "http://localhost/api/datasources/uid/DbtFe237k", + json=JAEGER_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/proxy/53/api/services", + json={"data": ["jaeger-query"], "total": 1, "limit": 0, "offset": 0, "errors": None}, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="DbtFe237k")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="DbtFe237k", + type="jaeger", + success=True, + status="OK", + message="['jaeger-query']", + duration=None, + response=None, + ), + ) + + @requests_mock.Mocker() + def test_health_check_jaeger_error_response_failure(self, m): + m.get( + "http://localhost/api/datasources/uid/DbtFe237k", + json=JAEGER_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/proxy/53/api/services", + json={ + "data": ["jaeger-query"], + "total": 1, + "limit": 0, + "offset": 0, + "errors": [{"code": 418, "message": "foobar"}], + }, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="DbtFe237k")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="DbtFe237k", + type="jaeger", + success=False, + status="ERROR", + message="[{'code': 418, 'message': 'foobar'}]", + duration=None, + response=None, + ), + ) + @requests_mock.Mocker() def test_health_check_mysql(self, m): m.get( From 9484f5fe2841d534121a12ba9229127cc6d07eaa Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Mon, 27 Jun 2022 00:38:02 +0200 Subject: [PATCH 2/5] Data source health check: Support data source `loki` --- README.md | 1 + docs/datasource-state.md | 1 - examples/datasource-health-probe.rst | 7 +++ grafana_client/elements/datasource.py | 12 +++++ grafana_client/knowledge.py | 5 +++ test/elements/test_datasource_fixtures.py | 8 ++++ test/elements/test_datasource_health.py | 53 +++++++++++++++++++++++ 7 files changed, 86 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75bea00..eebf4bd 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ implemented as of June 2022. - Graphite - InfluxDB - Jaeger +- Loki - OpenTSDB - PostgreSQL - Prometheus diff --git a/docs/datasource-state.md b/docs/datasource-state.md index 01b9598..e801ff9 100644 --- a/docs/datasource-state.md +++ b/docs/datasource-state.md @@ -19,7 +19,6 @@ stackdriver testdata ### UNKNOWN -loki mssql tempo zipkin diff --git a/examples/datasource-health-probe.rst b/examples/datasource-health-probe.rst index d21d278..519ef25 100644 --- a/examples/datasource-health-probe.rst +++ b/examples/datasource-health-probe.rst @@ -142,6 +142,13 @@ Jaeger python examples/datasource-health-probe.py --type=jaeger --url=http://host.docker.internal:16686 +Loki +==== +:: + + docker run --rm -it --name=loki --publish=3100:3100 grafana/loki:2.5.0 + python examples/datasource-health-probe.py --type=loki --url=http://host.docker.internal:3100 + MariaDB / MySQL =============== :: diff --git a/grafana_client/elements/datasource.py b/grafana_client/elements/datasource.py index 3c346ba..dfbcd00 100644 --- a/grafana_client/elements/datasource.py +++ b/grafana_client/elements/datasource.py @@ -331,6 +331,13 @@ def health_check(self, datasource: Union[DatasourceIdentifier, Dict]) -> Datasou reason = f"{ex.__class__.__name__}: {ex}" message = f"Invalid response. {reason}" + elif datasource_type == "loki": + if "status" in response and response["status"] == "success": + message = "Success" + success = True + else: + message = response["message"] + # With OpenTSDB, a 200 OK response with empty body is just fine. elif datasource_type == "opentsdb": message = "Success" @@ -551,6 +558,11 @@ def parse_health_response_data(response: Dict) -> Tuple[bool, str]: Response from Jaeger:: {"data":["jaeger-query"],"total":1,"limit":0,"offset":0,"errors":null} + + Response from Loki:: + + {"status":"success","data":["__name__"]} + """ success = False message = str(response["data"]) diff --git a/grafana_client/knowledge.py b/grafana_client/knowledge.py index d71f62e..4aefbcb 100644 --- a/grafana_client/knowledge.py +++ b/grafana_client/knowledge.py @@ -75,6 +75,8 @@ def datasource_factory(datasource: DatasourceModel) -> DatasourceModel: datasource.jsonData = { "tsdbVersion": 3, } + elif datasource.type == "loki": + datasource.access = "proxy" elif datasource.type == "mysql": datasource.user = "root" datasource.secureJsonData = { @@ -143,6 +145,8 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U raise KeyError(f"InfluxDB dialect '{dialect}' unknown") elif datasource_type == "jaeger": query = {} + elif datasource_type == "loki": + query = {} elif datasource_type == "mysql": query = { "refId": "test", @@ -210,6 +214,7 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U "influxdb+influxql": "SHOW RETENTION POLICIES on _internal", "influxdb+flux": "buckets()", "jaeger": "url:///datasources/proxy/{datasource_id}/api/services", + "loki": "url:///datasources/{datasource_id}/resources/labels?start=1656274994383000000&end=1656275594383000000", "mysql": "SELECT 1", "opentsdb": "url:///datasources/proxy/{datasource_id}/api/suggest?type=metrics&q=cpu&max=100", "postgres": "SELECT 1", diff --git a/test/elements/test_datasource_fixtures.py b/test/elements/test_datasource_fixtures.py index 1f58b8c..b562d14 100644 --- a/test/elements/test_datasource_fixtures.py +++ b/test/elements/test_datasource_fixtures.py @@ -40,6 +40,14 @@ "access": "proxy", } +LOKI_DATASOURCE = { + "id": 54, + "uid": "vCyglaq7z", + "name": "Loki", + "type": "loki", + "access": "proxy", +} + MYSQL_DATASOURCE = { "id": 51, "uid": "7CpzLp37z", diff --git a/test/elements/test_datasource_health.py b/test/elements/test_datasource_health.py index e89146e..95f7586 100644 --- a/test/elements/test_datasource_health.py +++ b/test/elements/test_datasource_health.py @@ -8,6 +8,7 @@ GRAPHITE_DATASOURCE, INFLUXDB1_DATASOURCE, JAEGER_DATASOURCE, + LOKI_DATASOURCE, MYSQL_DATASOURCE, OPENTSDB_DATASOURCE, POSTGRES_DATASOURCE, @@ -321,6 +322,58 @@ def test_health_check_jaeger_error_response_failure(self, m): ), ) + @requests_mock.Mocker() + def test_health_check_loki_success(self, m): + m.get( + "http://localhost/api/datasources/uid/vCyglaq7z", + json=LOKI_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/54/resources/labels", + json={"status": "success", "data": ["__name__"]}, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="vCyglaq7z")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="vCyglaq7z", + type="loki", + success=True, + status="OK", + message="Success", + duration=None, + response=None, + ), + ) + + @requests_mock.Mocker() + def test_health_check_loki_error_response_failure(self, m): + m.get( + "http://localhost/api/datasources/uid/vCyglaq7z", + json=LOKI_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/54/resources/labels", + json={"message": "Failed to call resource", "traceID": "00000000000000000000000000000000"}, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="vCyglaq7z")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="vCyglaq7z", + type="loki", + success=False, + status="ERROR", + message="Failed to call resource", + duration=None, + response=None, + ), + ) + @requests_mock.Mocker() def test_health_check_mysql(self, m): m.get( From ef3fb84d1caf4f8c2ab85f4c6bba54c45e25d611 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 28 Jun 2022 15:26:40 +0200 Subject: [PATCH 3/5] Data source health check: Support data source `tempo` --- README.md | 1 + docs/datasource-state.md | 1 - examples/datasource-health-probe.rst | 9 ++++ grafana_client/client.py | 6 ++- grafana_client/elements/datasource.py | 6 +++ grafana_client/knowledge.py | 5 +++ test/elements/test_datasource_fixtures.py | 8 ++++ test/elements/test_datasource_health.py | 54 +++++++++++++++++++++++ test/test_grafana_client.py | 3 +- 9 files changed, 90 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eebf4bd..953037e 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ implemented as of June 2022. - OpenTSDB - PostgreSQL - Prometheus +- Tempo - Testdata We are humbly asking the community to contribute adapters for other data diff --git a/docs/datasource-state.md b/docs/datasource-state.md index e801ff9..35e094b 100644 --- a/docs/datasource-state.md +++ b/docs/datasource-state.md @@ -20,7 +20,6 @@ testdata ### UNKNOWN mssql -tempo zipkin ### TODO diff --git a/examples/datasource-health-probe.rst b/examples/datasource-health-probe.rst index 519ef25..d542fca 100644 --- a/examples/datasource-health-probe.rst +++ b/examples/datasource-health-probe.rst @@ -181,6 +181,15 @@ Prometheus python examples/datasource-health-probe.py --type=prometheus --url=http://host.docker.internal:9090 +Tempo +===== +:: + + docker run --rm -it --name=tempo --publish=3200:80 grafana/tempo:1.4.1 \ + --target=all --storage.trace.backend=local --storage.trace.local.path=/var/tempo --auth.enabled=false + python examples/datasource-health-probe.py --type=tempo --url=http://host.docker.internal:3200 + + Testdata ======== :: diff --git a/grafana_client/client.py b/grafana_client/client.py index 62e8f66..af19263 100644 --- a/grafana_client/client.py +++ b/grafana_client/client.py @@ -146,6 +146,10 @@ def __request_runner(url, json=None, data=None, headers=None): response, "Client Error {0}: {1}".format(r.status_code, message), ) - return r.json() + # The "Tempo" data source responds with text/plain. + if r.headers.get("Content-Type", "").startswith("text/plain"): + return r.text + else: + return r.json() return __request_runner diff --git a/grafana_client/elements/datasource.py b/grafana_client/elements/datasource.py index dfbcd00..5c63155 100644 --- a/grafana_client/elements/datasource.py +++ b/grafana_client/elements/datasource.py @@ -343,6 +343,12 @@ def health_check(self, datasource: Union[DatasourceIdentifier, Dict]) -> Datasou message = "Success" success = True + # With Tempo, a 200 OK response with a non-empty body is probably just fine. + elif datasource_type == "tempo": + if len(response) >= 0: + message = "Success" + success = True + # Generic case, where the response has a top-level `results` or `data` key. else: if "results" in response: diff --git a/grafana_client/knowledge.py b/grafana_client/knowledge.py index 4aefbcb..de3f8ab 100644 --- a/grafana_client/knowledge.py +++ b/grafana_client/knowledge.py @@ -90,6 +90,8 @@ def datasource_factory(datasource: DatasourceModel) -> DatasourceModel: } elif datasource.type == "prometheus": datasource.access = "proxy" + elif datasource.type == "tempo": + datasource.access = "proxy" elif datasource.type == "testdata": pass else: @@ -196,6 +198,8 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U } elif datasource_type == "simpod-json-datasource": query = expression + elif datasource_type == "tempo": + query = {} elif datasource_type == "testdata": query = expression else: @@ -220,6 +224,7 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U "postgres": "SELECT 1", "prometheus": "1+1", "simpod-json-datasource": "url:///datasources/proxy/{datasource_id}", + "tempo": "url:///datasources/proxy/{datasource_id}/api/echo", "testdata": "url:///datasources/uid/{datasource_uid}", } diff --git a/test/elements/test_datasource_fixtures.py b/test/elements/test_datasource_fixtures.py index b562d14..2f12a6d 100644 --- a/test/elements/test_datasource_fixtures.py +++ b/test/elements/test_datasource_fixtures.py @@ -127,6 +127,14 @@ SUNANDMOON_DATASOURCE_INCOMPLETE = deepcopy(SUNANDMOON_DATASOURCE) del SUNANDMOON_DATASOURCE_INCOMPLETE["jsonData"]["latitude"] +TEMPO_DATASOURCE = { + "id": 55, + "uid": "aTk86s3nk", + "name": "Tempo", + "type": "tempo", + "access": "proxy", +} + TESTDATA_DATASOURCE = { "id": 45, "uid": "439fngqr2", diff --git a/test/elements/test_datasource_health.py b/test/elements/test_datasource_health.py index 95f7586..7d32f03 100644 --- a/test/elements/test_datasource_health.py +++ b/test/elements/test_datasource_health.py @@ -17,6 +17,7 @@ SIMPOD_JSON_DATASOURCE, SUNANDMOON_DATASOURCE, SUNANDMOON_DATASOURCE_INCOMPLETE, + TEMPO_DATASOURCE, TESTDATA_DATASOURCE, ) @@ -628,6 +629,59 @@ def test_health_check_sunandmoon_incomplete_failure(self, m): ), ) + @requests_mock.Mocker() + def test_health_check_tempo_success(self, m): + m.get( + "http://localhost/api/datasources/uid/aTk86s3nk", + json=TEMPO_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/proxy/55/api/echo", + headers={"Content-Type": "text/plain"}, + text="echo", + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="aTk86s3nk")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="aTk86s3nk", + type="tempo", + success=True, + status="OK", + message="Success", + duration=None, + response=None, + ), + ) + + @requests_mock.Mocker() + def test_health_check_tempo_error_response_failure(self, m): + m.get( + "http://localhost/api/datasources/uid/aTk86s3nk", + json=TEMPO_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/proxy/55/api/echo", + status_code=502, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="aTk86s3nk")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="aTk86s3nk", + type="tempo", + success=False, + status="ERROR", + message="Server Error 502: ", + duration=None, + response=None, + ), + ) + @requests_mock.Mocker() def test_health_check_testdata(self, m): m.get( diff --git a/test/test_grafana_client.py b/test/test_grafana_client.py index 278f27f..be846ad 100644 --- a/test/test_grafana_client.py +++ b/test/test_grafana_client.py @@ -13,9 +13,10 @@ class MockResponse: - def __init__(self, json_data, status_code): + def __init__(self, json_data, status_code, headers=None): self.json_data = json_data self.status_code = status_code + self.headers = headers or {"Content-Type": "application/json"} def json(self): return self.json_data From 94759d752fafe07a7e09b5d8c0bda6e183073c46 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 28 Jun 2022 17:51:23 +0200 Subject: [PATCH 4/5] Data source health check: Support data source `mssql` --- README.md | 1 + docs/datasource-state.md | 1 - examples/datasource-health-probe.rst | 20 +++++++++++++++++ grafana_client/knowledge.py | 25 +++++++++++++++++++++ test/elements/test_datasource_fixtures.py | 8 +++++++ test/elements/test_datasource_health.py | 27 +++++++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 953037e..9ec5754 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ implemented as of June 2022. - InfluxDB - Jaeger - Loki +- Microsoft SQL Server - OpenTSDB - PostgreSQL - Prometheus diff --git a/docs/datasource-state.md b/docs/datasource-state.md index 35e094b..1d53d9c 100644 --- a/docs/datasource-state.md +++ b/docs/datasource-state.md @@ -19,7 +19,6 @@ stackdriver testdata ### UNKNOWN -mssql zipkin ### TODO diff --git a/examples/datasource-health-probe.rst b/examples/datasource-health-probe.rst index d542fca..64bc600 100644 --- a/examples/datasource-health-probe.rst +++ b/examples/datasource-health-probe.rst @@ -157,6 +157,26 @@ MariaDB / MySQL python examples/datasource-health-probe.py --type=mysql --url=host.docker.internal:3306 +Microsoft SQL Server +==================== +:: + + # Start service. + docker run --rm -it --publish=1433:1433 \ + --env="ACCEPT_EULA=Y" --env="SA_PASSWORD=root123?" \ + mcr.microsoft.com/mssql/server:2022-latest + + # Create database `testdrive`. + docker run --rm -it --network=host mcr.microsoft.com/mssql/server:2022-latest /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "root123?" -Q "CREATE DATABASE testdrive;" + + # Invoke Grafana database probe. + python examples/datasource-health-probe.py --type=mssql --url=host.docker.internal:1433 + +Interactive client console:: + + docker run --rm -it --network=host mcr.microsoft.com/mssql/server:2022-latest /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P root123? + + OpenTSDB ======== :: diff --git a/grafana_client/knowledge.py b/grafana_client/knowledge.py index de3f8ab..b441345 100644 --- a/grafana_client/knowledge.py +++ b/grafana_client/knowledge.py @@ -77,6 +77,19 @@ def datasource_factory(datasource: DatasourceModel) -> DatasourceModel: } elif datasource.type == "loki": datasource.access = "proxy" + elif datasource.type == "mssql": + datasource.access = "proxy" + datasource.database = "testdrive" + datasource.user = "sa" + datasource.jsonData = { + "authenticationType": "SQL Server Authentication", + } + datasource.secureJsonData = { + "password": "root123?", + } + datasource.secureJsonFields = { + "password": False, + } elif datasource.type == "mysql": datasource.user = "root" datasource.secureJsonData = { @@ -149,6 +162,17 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U query = {} elif datasource_type == "loki": query = {} + elif datasource_type == "mssql": + query = { + "refId": "test", + "datasource": { + "type": datasource["type"], + "uid": datasource.get("uid"), + }, + "datasourceId": datasource.get("id"), + "format": "table", + "rawSql": expression, + } elif datasource_type == "mysql": query = { "refId": "test", @@ -219,6 +243,7 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U "influxdb+flux": "buckets()", "jaeger": "url:///datasources/proxy/{datasource_id}/api/services", "loki": "url:///datasources/{datasource_id}/resources/labels?start=1656274994383000000&end=1656275594383000000", + "mssql": "SELECT 1", "mysql": "SELECT 1", "opentsdb": "url:///datasources/proxy/{datasource_id}/api/suggest?type=metrics&q=cpu&max=100", "postgres": "SELECT 1", diff --git a/test/elements/test_datasource_fixtures.py b/test/elements/test_datasource_fixtures.py index 2f12a6d..c90a642 100644 --- a/test/elements/test_datasource_fixtures.py +++ b/test/elements/test_datasource_fixtures.py @@ -48,6 +48,14 @@ "access": "proxy", } +MSSQL_DATASOURCE = { + "id": 56, + "uid": "0pueH83nz", + "name": "MSSQL", + "type": "mssql", + "access": "proxy", +} + MYSQL_DATASOURCE = { "id": 51, "uid": "7CpzLp37z", diff --git a/test/elements/test_datasource_health.py b/test/elements/test_datasource_health.py index 7d32f03..741aa25 100644 --- a/test/elements/test_datasource_health.py +++ b/test/elements/test_datasource_health.py @@ -9,6 +9,7 @@ INFLUXDB1_DATASOURCE, JAEGER_DATASOURCE, LOKI_DATASOURCE, + MSSQL_DATASOURCE, MYSQL_DATASOURCE, OPENTSDB_DATASOURCE, POSTGRES_DATASOURCE, @@ -375,6 +376,32 @@ def test_health_check_loki_error_response_failure(self, m): ), ) + @requests_mock.Mocker() + def test_health_check_mssql(self, m): + m.get( + "http://localhost/api/datasources/uid/0pueH83nz", + json=MSSQL_DATASOURCE, + ) + m.post( + "http://localhost/api/ds/query", + json=DATAFRAME_RESPONSE_HEALTH_SELECT1, + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="0pueH83nz")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="0pueH83nz", + type="mssql", + success=True, + status="OK", + message="SELECT 1", + duration=None, + response=None, + ), + ) + @requests_mock.Mocker() def test_health_check_mysql(self, m): m.get( From 00046ce8830abfe67a750bd15f70fcc210635cae Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 28 Jun 2022 18:09:00 +0200 Subject: [PATCH 5/5] Data source health check: Support data source `zipkin` --- README.md | 1 + docs/datasource-state.md | 1 - examples/datasource-health-probe.rst | 7 ++++++ grafana_client/elements/datasource.py | 6 +++++ grafana_client/knowledge.py | 5 +++++ test/elements/test_datasource_fixtures.py | 8 +++++++ test/elements/test_datasource_health.py | 27 +++++++++++++++++++++++ 7 files changed, 54 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ec5754..ab816e2 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ implemented as of June 2022. - Prometheus - Tempo - Testdata +- Zipkin We are humbly asking the community to contribute adapters for other data sources. diff --git a/docs/datasource-state.md b/docs/datasource-state.md index 1d53d9c..2e2f1ea 100644 --- a/docs/datasource-state.md +++ b/docs/datasource-state.md @@ -19,7 +19,6 @@ stackdriver testdata ### UNKNOWN -zipkin ### TODO alertmanager diff --git a/examples/datasource-health-probe.rst b/examples/datasource-health-probe.rst index 64bc600..9c7afad 100644 --- a/examples/datasource-health-probe.rst +++ b/examples/datasource-health-probe.rst @@ -216,3 +216,10 @@ Testdata python examples/datasource-health-probe.py --type=testdata + +Zipkin +====== +:: + + docker run --rm -it --publish=9411:9411 openzipkin/zipkin:2.23 + python examples/datasource-health-probe.py --type=zipkin --url=http://host.docker.internal:9411 diff --git a/grafana_client/elements/datasource.py b/grafana_client/elements/datasource.py index 5c63155..fe635cf 100644 --- a/grafana_client/elements/datasource.py +++ b/grafana_client/elements/datasource.py @@ -349,6 +349,12 @@ def health_check(self, datasource: Union[DatasourceIdentifier, Dict]) -> Datasou message = "Success" success = True + # With Zipkin, a 200 OK response with a JSON body containing an empty array is probably just fine. + elif datasource_type == "zipkin": + if response == []: + message = "Success" + success = True + # Generic case, where the response has a top-level `results` or `data` key. else: if "results" in response: diff --git a/grafana_client/knowledge.py b/grafana_client/knowledge.py index b441345..f96b2f0 100644 --- a/grafana_client/knowledge.py +++ b/grafana_client/knowledge.py @@ -107,6 +107,8 @@ def datasource_factory(datasource: DatasourceModel) -> DatasourceModel: datasource.access = "proxy" elif datasource.type == "testdata": pass + elif datasource.type == "zipkin": + datasource.access = "proxy" else: raise NotImplementedError(f"Unknown data source type: {datasource.type}") return datasource @@ -226,6 +228,8 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U query = {} elif datasource_type == "testdata": query = expression + elif datasource_type == "zipkin": + query = {} else: raise NotImplementedError(f"Unknown data source type: {datasource_type}") return query @@ -251,6 +255,7 @@ def query_factory(datasource, expression: str, store: Optional[str] = None) -> U "simpod-json-datasource": "url:///datasources/proxy/{datasource_id}", "tempo": "url:///datasources/proxy/{datasource_id}/api/echo", "testdata": "url:///datasources/uid/{datasource_uid}", + "zipkin": "url:///datasources/proxy/{datasource_id}/api/v2/services", } diff --git a/test/elements/test_datasource_fixtures.py b/test/elements/test_datasource_fixtures.py index c90a642..3427946 100644 --- a/test/elements/test_datasource_fixtures.py +++ b/test/elements/test_datasource_fixtures.py @@ -151,6 +151,14 @@ "access": "proxy", } +ZIPKIN_DATASOURCE = { + "id": 57, + "uid": "3sXIv8q7k", + "name": "Zipkin", + "type": "zipkin", + "access": "proxy", +} + PROMETHEUS_DATA_RESPONSE = { "status": "success", diff --git a/test/elements/test_datasource_health.py b/test/elements/test_datasource_health.py index 741aa25..96c4a46 100644 --- a/test/elements/test_datasource_health.py +++ b/test/elements/test_datasource_health.py @@ -20,6 +20,7 @@ SUNANDMOON_DATASOURCE_INCOMPLETE, TEMPO_DATASOURCE, TESTDATA_DATASOURCE, + ZIPKIN_DATASOURCE, ) import requests_mock @@ -731,6 +732,32 @@ def test_health_check_testdata(self, m): ), ) + @requests_mock.Mocker() + def test_health_check_zipkin_success(self, m): + m.get( + "http://localhost/api/datasources/uid/3sXIv8q7k", + json=ZIPKIN_DATASOURCE, + ) + m.get( + "http://localhost/api/datasources/proxy/57/api/v2/services", + json=[], + ) + response = self.grafana.datasource.health_check(DatasourceIdentifier(uid="3sXIv8q7k")) + response.duration = None + response.response = None + self.assertEqual( + response, + DatasourceHealthResponse( + uid="3sXIv8q7k", + type="zipkin", + success=True, + status="OK", + message="Success", + duration=None, + response=None, + ), + ) + @requests_mock.Mocker() def test_health_check_zdict_valid_response_success(self, m): """