From ad459bb3609079808a73c3d8a7e318a771a1dea9 Mon Sep 17 00:00:00 2001 From: siddardh Date: Thu, 25 May 2023 16:46:00 +0530 Subject: [PATCH 01/15] Rebasing and First pass of Qusiby with functionality working for single results of uperf-data --- lib/pbench/client/__init__.py | 1 + lib/pbench/server/api/__init__.py | 8 ++ lib/pbench/server/api/resources/quisby.py | 92 +++++++++++++++++++ .../unit/server/test_endpoint_configure.py | 4 + 4 files changed, 105 insertions(+) create mode 100644 lib/pbench/server/api/resources/quisby.py diff --git a/lib/pbench/client/__init__.py b/lib/pbench/client/__init__.py index 8870dd812f..7b91083655 100644 --- a/lib/pbench/client/__init__.py +++ b/lib/pbench/client/__init__.py @@ -49,6 +49,7 @@ class API(Enum): DATASETS_VALUES = "datasets_values" ENDPOINTS = "endpoints" KEY = "key" + QUISBY = "quisby" RELAY = "relay" SERVER_AUDIT = "server_audit" SERVER_SETTINGS = "server_settings" diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index 7942e06e55..587e18ab90 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -34,6 +34,7 @@ ) from pbench.server.api.resources.query_apis.datasets_search import DatasetsSearch from pbench.server.api.resources.relay import Relay +from pbench.server.api.resources.quisby import QuisbyData from pbench.server.api.resources.server_audit import ServerAudit from pbench.server.api.resources.server_settings import ServerSettings from pbench.server.api.resources.upload import Upload @@ -133,6 +134,13 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): endpoint="key", resource_class_args=(config,), ) + api.add_resource( + QuisbyData, + f"{base_uri}/quisby/", + f"{base_uri}/quisby//", + endpoint="quisby", + resource_class_args=(config,), + ) api.add_resource( ServerAudit, f"{base_uri}/server/audit", diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py new file mode 100644 index 0000000000..7c6018cd06 --- /dev/null +++ b/lib/pbench/server/api/resources/quisby.py @@ -0,0 +1,92 @@ +from http import HTTPStatus +from urllib.request import Request + +from flask import current_app +from flask.wrappers import Response +from pquisby.lib.benchmarks.uperf.uperf import extract_uperf_data + +from pbench.server import OperationCode, PbenchServerConfig +from pbench.server.api.resources import ( + APIAbort, + ApiAuthorizationType, + ApiBase, + ApiContext, + APIInternalError, + ApiMethod, + ApiParams, + ApiSchema, + Parameter, + ParamType, + Schema, +) +from pbench.server.cache_manager import ( + CacheManager, + TarballNotFound, + TarballUnpackError, +) +from pbench.server.database import Dataset + + +class QuisbyData(ApiBase): + """ + API class to retrieve inventory files from a dataset + """ + + def __init__(self, config: PbenchServerConfig): + super().__init__( + config, + ApiSchema( + ApiMethod.GET, + OperationCode.READ, + uri_schema=Schema( + Parameter("dataset", ParamType.DATASET, required=True), + ), + authorization=ApiAuthorizationType.DATASET, + ), + ) + + def _get( + self, params: ApiParams, request: Request, context: ApiContext + ) -> Response: + """ + This function returns the contents of the requested file as a byte stream. + + Args: + params: includes the uri parameters, which provide the dataset and target. + request: Original incoming Request object + context: API context dictionary + + Raises: + APIAbort, reporting either "NOT_FOUND" or "UNSUPPORTED_MEDIA_TYPE" + + + GET /api/v1/quisby/{dataset} + """ + + dataset = params.uri["dataset"] + + cache_m = CacheManager(self.config, current_app.logger) + try: + tarball = cache_m.find_dataset(dataset.resource_id) + except TarballNotFound as e: + raise APIAbort(HTTPStatus.NOT_FOUND, str(e)) + + name = Dataset.stem(tarball.tarball_path) + + print(name) + try: + file = tarball.extract(tarball.tarball_path, f"{name}/result.csv") + except TarballUnpackError as e: + raise APIInternalError(str(e)) from e + + split_rows = file.split("\n") + csv_data = [] + for row in split_rows: + csv_data.append(row.split(",")) + + try: + return_val, json_data = extract_uperf_data("localhost", csv_data) + except Exception as e: + raise APIInternalError(str(e)) from e + + return json_data diff --git a/lib/pbench/test/unit/server/test_endpoint_configure.py b/lib/pbench/test/unit/server/test_endpoint_configure.py index ac167d1e45..83abba5b40 100644 --- a/lib/pbench/test/unit/server/test_endpoint_configure.py +++ b/lib/pbench/test/unit/server/test_endpoint_configure.py @@ -106,6 +106,10 @@ def check_config(self, client, server_config, host, my_headers={}): "template": f"{uri}/key/{{key}}", "params": {"key": {"type": "string"}}, }, + "quisby": { + "template": f"{uri}/quisby/{{dataset}}", + "params": {"dataset": {"type": "string"}}, + }, "relay": { "template": f"{uri}/relay/{{uri}}", "params": {"uri": {"type": "path"}}, From a3373b1ee21e84b99001a6c7e10a6f682afa4d42 Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 31 May 2023 12:06:46 +0530 Subject: [PATCH 02/15] Working quisby api with basic test infrastructure --- lib/pbench/server/api/resources/quisby.py | 5 +- .../test/unit/server/test_quisby_results.py | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 lib/pbench/test/unit/server/test_quisby_results.py diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 7c6018cd06..17a2734c7d 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -3,7 +3,7 @@ from flask import current_app from flask.wrappers import Response -from pquisby.lib.benchmarks.uperf.uperf import extract_uperf_data +from pquisby.lib.process_result import extract_data from pbench.server import OperationCode, PbenchServerConfig from pbench.server.api.resources import ( @@ -73,7 +73,6 @@ def _get( name = Dataset.stem(tarball.tarball_path) - print(name) try: file = tarball.extract(tarball.tarball_path, f"{name}/result.csv") except TarballUnpackError as e: @@ -85,7 +84,7 @@ def _get( csv_data.append(row.split(",")) try: - return_val, json_data = extract_uperf_data("localhost", csv_data) + return_val, json_data = extract_data("uperf", "localhost", csv_data) except Exception as e: raise APIInternalError(str(e)) from e diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py new file mode 100644 index 0000000000..45cbf3bb40 --- /dev/null +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -0,0 +1,75 @@ +from http import HTTPStatus +from pathlib import Path + +import pytest +import requests + +from pbench.server.database.models.datasets import Dataset, DatasetNotFound + + +class TestQuisbyResults: + @pytest.fixture() + def query_get_as(self, client, server_config, more_datasets, pbench_drb_token): + """ + Helper fixture to perform the API query and validate an expected + return status. + + Args: + client: Flask test API client fixture + server_config: Pbench config fixture + more_datasets: Dataset construction fixture + pbench_drb_token: Authenticated user token fixture + """ + + def query_api( + dataset: str, target: str, expected_status: HTTPStatus + ) -> requests.Response: + try: + dataset_id = Dataset.query(name=dataset).resource_id + except DatasetNotFound: + dataset_id = dataset # Allow passing deliberately bad value + headers = {"authorization": f"bearer {pbench_drb_token}"} + response = client.get( + f"{server_config.rest_uri}/quisby/{dataset_id}/", + headers=headers, + ) + assert response.status_code == expected_status + return response + + return query_api + + def mock_find_dataset(self, dataset): + class Tarball(object): + unpacked = Path("/dataset/") + tarball_path = Path("/dataset_tarball") + + # Validate the resource_id + Dataset.query(resource_id=dataset) + return Tarball + + def test_get_no_dataset(self, query_get_as): + response = query_get_as( + "nonexistent-dataset", "metadata.log", HTTPStatus.NOT_FOUND + ) + assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} + + def test_dataset_not_present(self, query_get_as): + response = query_get_as("fio_2", "metadata.log", HTTPStatus.NOT_FOUND) + assert response.json == { + "message": "The dataset tarball named 'random_md5_string4' is not present in the cache manager" + } + + def test_unauthorized_access(self, query_get_as): + response = query_get_as("test", "metadata.log", HTTPStatus.FORBIDDEN) + assert response.json == { + "message": "User drb is not authorized to READ a resource owned by test with private access" + } + + # def test_dataset_is_not_unpacked(self, query_get_as, monkeypatch): + # def mock_extract_csv(self, dataset): + # pass + # + # monkeypatch.setattr(Tarball, "extract", mock_extract_csv) + # + # response = query_get_as("fio_2", "1-default", HTTPStatus.NOT_FOUND) + # assert response.json == {"message": "The dataset is not unpacked"} From f9d800e6d4d198f5a5a113eafc3f0befc04fb869 Mon Sep 17 00:00:00 2001 From: siddardh Date: Fri, 2 Jun 2023 13:35:15 +0530 Subject: [PATCH 03/15] Some debugging code --- lib/pbench/server/api/resources/quisby.py | 15 ++++++++------- server/requirements.txt | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 17a2734c7d..60fe8cbc30 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -3,7 +3,9 @@ from flask import current_app from flask.wrappers import Response -from pquisby.lib.process_result import extract_data +# from pquisby.lib.process_result import extract_data + +from pquisby.lib.benchmarks.uperf.uperf import extract_uperf_data from pbench.server import OperationCode, PbenchServerConfig from pbench.server.api.resources import ( @@ -78,13 +80,12 @@ def _get( except TarballUnpackError as e: raise APIInternalError(str(e)) from e - split_rows = file.split("\n") - csv_data = [] - for row in split_rows: - csv_data.append(row.split(",")) - try: - return_val, json_data = extract_data("uperf", "localhost", csv_data) + # return_val, json_data = extract_data("uperf",dataset.name, "localhost", "csv",file) + # return_val, json_data = extract_data("uperf", "localhost", csv_data) + + ret_val, json_data = extract_uperf_data(dataset.name, "system_name", file) + except Exception as e: raise APIInternalError(str(e)) from e diff --git a/server/requirements.txt b/server/requirements.txt index b433249642..59f383a03d 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -12,7 +12,6 @@ flask-restful>=0.3.9 flask-sqlalchemy gunicorn humanize -pquisby psycopg2 pyesbulk>=2.0.1 PyJwt[crypto] @@ -21,3 +20,4 @@ requests # TODO CVE-2023-32681 (>=2.31.0) sdnotify sqlalchemy>=1.4.23 sqlalchemy_utils>=0.37.6 +tttpquisby \ No newline at end of file From e4d5f49ff4a4495a23795a277f5de0b22696c62f Mon Sep 17 00:00:00 2001 From: siddardh Date: Mon, 5 Jun 2023 17:38:21 +0530 Subject: [PATCH 04/15] Quisby for single dataset --- lib/pbench/server/api/resources/quisby.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 60fe8cbc30..c2ecee8086 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -3,9 +3,7 @@ from flask import current_app from flask.wrappers import Response -# from pquisby.lib.process_result import extract_data - -from pquisby.lib.benchmarks.uperf.uperf import extract_uperf_data +from pquisby.lib.post_processing import extract_data from pbench.server import OperationCode, PbenchServerConfig from pbench.server.api.resources import ( @@ -31,7 +29,7 @@ class QuisbyData(ApiBase): """ - API class to retrieve inventory files from a dataset + API class to retrieve Quisby data for a dataset """ def __init__(self, config: PbenchServerConfig): @@ -51,7 +49,7 @@ def _get( self, params: ApiParams, request: Request, context: ApiContext ) -> Response: """ - This function returns the contents of the requested file as a byte stream. + This function returns the Quisby data for the requested dataset. Args: params: includes the uri parameters, which provide the dataset and target. @@ -59,7 +57,7 @@ def _get( context: API context dictionary Raises: - APIAbort, reporting either "NOT_FOUND" or "UNSUPPORTED_MEDIA_TYPE" + APIAbort, reporting either "NOT_FOUND" GET /api/v1/quisby/{dataset} @@ -80,13 +78,10 @@ def _get( except TarballUnpackError as e: raise APIInternalError(str(e)) from e - try: - # return_val, json_data = extract_data("uperf",dataset.name, "localhost", "csv",file) - # return_val, json_data = extract_data("uperf", "localhost", csv_data) - - ret_val, json_data = extract_uperf_data(dataset.name, "system_name", file) + json_data = extract_data("uperf",dataset.name, "localhost", "stream",file) - except Exception as e: - raise APIInternalError(str(e)) from e + if json_data["status"]=="success": + return json_data + else: + raise APIInternalError("Unexpected failure from Quisby processing") - return json_data From 8601f35b0ef278381f2326b1589768513eaa1d6a Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 7 Jun 2023 21:40:57 +0530 Subject: [PATCH 05/15] Added few unit test cases --- lib/pbench/server/api/resources/quisby.py | 13 ++-- .../test/unit/server/test_quisby_results.py | 62 +++++++++++++------ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index c2ecee8086..25f5a3b19d 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -1,7 +1,7 @@ from http import HTTPStatus from urllib.request import Request -from flask import current_app +from flask import current_app, jsonify from flask.wrappers import Response from pquisby.lib.post_processing import extract_data @@ -70,18 +70,17 @@ def _get( tarball = cache_m.find_dataset(dataset.resource_id) except TarballNotFound as e: raise APIAbort(HTTPStatus.NOT_FOUND, str(e)) - name = Dataset.stem(tarball.tarball_path) try: file = tarball.extract(tarball.tarball_path, f"{name}/result.csv") except TarballUnpackError as e: raise APIInternalError(str(e)) from e + json_data = extract_data("uperf", dataset.name, "localhost", "stream", file) - json_data = extract_data("uperf",dataset.name, "localhost", "stream",file) - - if json_data["status"]=="success": - return json_data + if json_data["status"] == "success": + response = jsonify(json_data) + response.status_code = HTTPStatus.OK + return response else: raise APIInternalError("Unexpected failure from Quisby processing") - diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py index 45cbf3bb40..e644ad1112 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -1,15 +1,17 @@ from http import HTTPStatus from pathlib import Path +import tarfile import pytest import requests +from pbench.server.cache_manager import CacheManager, Tarball from pbench.server.database.models.datasets import Dataset, DatasetNotFound class TestQuisbyResults: @pytest.fixture() - def query_get_as(self, client, server_config, more_datasets, pbench_drb_token): + def query_get_as(self, client, server_config, more_datasets, get_token_func): """ Helper fixture to perform the API query and validate an expected return status. @@ -22,13 +24,13 @@ def query_get_as(self, client, server_config, more_datasets, pbench_drb_token): """ def query_api( - dataset: str, target: str, expected_status: HTTPStatus + dataset: str, user, expected_status: HTTPStatus ) -> requests.Response: try: dataset_id = Dataset.query(name=dataset).resource_id except DatasetNotFound: dataset_id = dataset # Allow passing deliberately bad value - headers = {"authorization": f"bearer {pbench_drb_token}"} + headers = {"authorization": f"bearer {get_token_func(user)}"} response = client.get( f"{server_config.rest_uri}/quisby/{dataset_id}/", headers=headers, @@ -40,36 +42,60 @@ def query_api( def mock_find_dataset(self, dataset): class Tarball(object): - unpacked = Path("/dataset/") - tarball_path = Path("/dataset_tarball") + tarball_path = Path( + "uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57.tar.xz" + ) + + def extract(tarball_path, path): + mod_path = Path(__file__).parent + relative_path_2 = "../../functional/server/tarballs/uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57.tar.xz" + uperf_tarball_path = (mod_path / relative_path_2).resolve() + tarball_path_1 = Path(uperf_tarball_path) + tar = tarfile.open(tarball_path_1, "r:*") + + return ( + tar.extractfile( + "uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57/result.csv" + ) + .read() + .decode() + ) # Validate the resource_id Dataset.query(resource_id=dataset) return Tarball def test_get_no_dataset(self, query_get_as): - response = query_get_as( - "nonexistent-dataset", "metadata.log", HTTPStatus.NOT_FOUND - ) + response = query_get_as("nonexistent-dataset", "drb", HTTPStatus.NOT_FOUND) assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} def test_dataset_not_present(self, query_get_as): - response = query_get_as("fio_2", "metadata.log", HTTPStatus.NOT_FOUND) + response = query_get_as("fio_2", "drb", HTTPStatus.NOT_FOUND) assert response.json == { "message": "The dataset tarball named 'random_md5_string4' is not present in the cache manager" } def test_unauthorized_access(self, query_get_as): - response = query_get_as("test", "metadata.log", HTTPStatus.FORBIDDEN) + response = query_get_as("test", "drb", HTTPStatus.FORBIDDEN) assert response.json == { "message": "User drb is not authorized to READ a resource owned by test with private access" } - # def test_dataset_is_not_unpacked(self, query_get_as, monkeypatch): - # def mock_extract_csv(self, dataset): - # pass - # - # monkeypatch.setattr(Tarball, "extract", mock_extract_csv) - # - # response = query_get_as("fio_2", "1-default", HTTPStatus.NOT_FOUND) - # assert response.json == {"message": "The dataset is not unpacked"} + def test_quisby_success(self, query_get_as, monkeypatch): + monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) + + response = query_get_as("uperf_1", "test", HTTPStatus.OK) + assert response.json["status"] == "success" + assert response.json["jsonData"] + + def test_quisby_failure(self, query_get_as, monkeypatch): + + # Need to refine it + def extract_csv(self): + return "IncorrectData" + + monkeypatch.setattr(Tarball, "extract", extract_csv) + monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) + + response = query_get_as("uperf_1", "test", HTTPStatus.OK) + print(response.json) From e7536790630b4ad554ade437eafea7d88aa67df2 Mon Sep 17 00:00:00 2001 From: siddardh Date: Thu, 8 Jun 2023 11:19:27 +0530 Subject: [PATCH 06/15] Migrate from test quisby to OG pquisby branch... --- server/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/requirements.txt b/server/requirements.txt index 59f383a03d..5d0b62843e 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -12,6 +12,7 @@ flask-restful>=0.3.9 flask-sqlalchemy gunicorn humanize +pquisby psycopg2 pyesbulk>=2.0.1 PyJwt[crypto] @@ -19,5 +20,4 @@ python-dateutil requests # TODO CVE-2023-32681 (>=2.31.0) sdnotify sqlalchemy>=1.4.23 -sqlalchemy_utils>=0.37.6 -tttpquisby \ No newline at end of file +sqlalchemy_utils>=0.37.6 \ No newline at end of file From 4202556c06c611cc024f331e15009475678f1c20 Mon Sep 17 00:00:00 2001 From: siddardh Date: Tue, 13 Jun 2023 23:01:15 +0530 Subject: [PATCH 07/15] Added and fixed Unit tests and updated new pqusiby package and integrated that change in it --- lib/pbench/server/api/__init__.py | 2 +- lib/pbench/server/api/resources/quisby.py | 7 +- .../test/unit/server/test_quisby_results.py | 66 +++++++++---------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index 587e18ab90..95d89100f2 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -33,8 +33,8 @@ SampleValues, ) from pbench.server.api.resources.query_apis.datasets_search import DatasetsSearch -from pbench.server.api.resources.relay import Relay from pbench.server.api.resources.quisby import QuisbyData +from pbench.server.api.resources.relay import Relay from pbench.server.api.resources.server_audit import ServerAudit from pbench.server.api.resources.server_settings import ServerSettings from pbench.server.api.resources.upload import Upload diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 25f5a3b19d..23e88fef45 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -3,7 +3,7 @@ from flask import current_app, jsonify from flask.wrappers import Response -from pquisby.lib.post_processing import extract_data +from pquisby.lib.post_processing import BenchmarkName, InputType, QuisbyProcessing from pbench.server import OperationCode, PbenchServerConfig from pbench.server.api.resources import ( @@ -76,7 +76,10 @@ def _get( file = tarball.extract(tarball.tarball_path, f"{name}/result.csv") except TarballUnpackError as e: raise APIInternalError(str(e)) from e - json_data = extract_data("uperf", dataset.name, "localhost", "stream", file) + + json_data = QuisbyProcessing.extract_data( + self, BenchmarkName.UPERF, dataset.name, InputType.STREAM, file + ) if json_data["status"] == "success": response = jsonify(json_data) diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py index e644ad1112..f3918b6808 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -1,11 +1,11 @@ from http import HTTPStatus from pathlib import Path -import tarfile +from pquisby.lib.post_processing import QuisbyProcessing import pytest import requests -from pbench.server.cache_manager import CacheManager, Tarball +from pbench.server.cache_manager import CacheManager from pbench.server.database.models.datasets import Dataset, DatasetNotFound @@ -40,31 +40,6 @@ def query_api( return query_api - def mock_find_dataset(self, dataset): - class Tarball(object): - tarball_path = Path( - "uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57.tar.xz" - ) - - def extract(tarball_path, path): - mod_path = Path(__file__).parent - relative_path_2 = "../../functional/server/tarballs/uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57.tar.xz" - uperf_tarball_path = (mod_path / relative_path_2).resolve() - tarball_path_1 = Path(uperf_tarball_path) - tar = tarfile.open(tarball_path_1, "r:*") - - return ( - tar.extractfile( - "uperf_rhel8.1_4.18.0-107.el8_snap4_25gb_virt_2019.06.21T01.28.57/result.csv" - ) - .read() - .decode() - ) - - # Validate the resource_id - Dataset.query(resource_id=dataset) - return Tarball - def test_get_no_dataset(self, query_get_as): response = query_get_as("nonexistent-dataset", "drb", HTTPStatus.NOT_FOUND) assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} @@ -82,20 +57,39 @@ def test_unauthorized_access(self, query_get_as): } def test_quisby_success(self, query_get_as, monkeypatch): - monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) + def mock_find_dataset(self, dataset): + class Tarball(object): + tarball_path = Path("/dataset/tarball.tar.xz") + + def extract(tarball_path, path): + return "CSV_file_as_a_byte_stream" + + return Tarball + + def mock_extract_data(test_name, dataset_name, input_type, data): + return {"status": "success", "json_data": "quisby_data"} + + monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.OK) assert response.json["status"] == "success" - assert response.json["jsonData"] + assert response.json["json_data"] def test_quisby_failure(self, query_get_as, monkeypatch): + def mock_find_dataset(self, dataset): + class Tarball(object): + tarball_path = Path("/dataset/tarball.tar.xz") - # Need to refine it - def extract_csv(self): - return "IncorrectData" + def extract(tarball_path, path): + return "IncorrectData" - monkeypatch.setattr(Tarball, "extract", extract_csv) - monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) + return Tarball - response = query_get_as("uperf_1", "test", HTTPStatus.OK) - print(response.json) + def mock_extract_data(test_name, dataset_name, input_type, data): + return {"status": "failed", "exception": "Unsupported Media Type"} + + monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) + + query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) From 70152acf64e6102a4365a35cf898707d8115ba74 Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 14 Jun 2023 12:29:05 +0530 Subject: [PATCH 08/15] Code clean up and some enhancements --- lib/pbench/server/api/resources/quisby.py | 23 +++++++++++++++---- .../test/unit/server/test_quisby_results.py | 14 +++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 23e88fef45..694e55f400 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -57,7 +57,8 @@ def _get( context: API context dictionary Raises: - APIAbort, reporting either "NOT_FOUND" + APIAbort, reporting "NOT_FOUND" + APIInternalError, reporting the failure message GET /api/v1/quisby/{dataset} @@ -77,12 +78,24 @@ def _get( except TarballUnpackError as e: raise APIInternalError(str(e)) from e - json_data = QuisbyProcessing.extract_data( - self, BenchmarkName.UPERF, dataset.name, InputType.STREAM, file + metadata = self._get_dataset_metadata( + dataset, ["dataset.metalog.pbench.script"] ) + benchmark = metadata["dataset.metalog.pbench.script"] - if json_data["status"] == "success": - response = jsonify(json_data) + if benchmark == "uperf": + benchmark_type = BenchmarkName.UPERF + elif benchmark == "fio": + benchmark_type = BenchmarkName.FIO + else: + raise APIAbort(HTTPStatus.NOT_FOUND, "Unsupported Benchmark") + + get_quisby_data = QuisbyProcessing.extract_data( + self, benchmark_type, dataset.name, InputType.STREAM, file + ) + + if get_quisby_data["status"] == "success": + response = jsonify(get_quisby_data) response.status_code = HTTPStatus.OK return response else: diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py index f3918b6808..5e780424df 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -5,6 +5,7 @@ import pytest import requests +from pbench.server.api.resources import ApiBase from pbench.server.cache_manager import CacheManager from pbench.server.database.models.datasets import Dataset, DatasetNotFound @@ -40,6 +41,9 @@ def query_api( return query_api + def mock_get_dataset_metadata(self, dataset, key): + return {"dataset.metalog.pbench.script": "uperf"} + def test_get_no_dataset(self, query_get_as): response = query_get_as("nonexistent-dataset", "drb", HTTPStatus.NOT_FOUND) assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} @@ -66,10 +70,13 @@ def extract(tarball_path, path): return Tarball - def mock_extract_data(test_name, dataset_name, input_type, data): + def mock_extract_data(self, test_name, dataset_name, input_type, data): return {"status": "success", "json_data": "quisby_data"} monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr( + ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata + ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.OK) @@ -86,10 +93,13 @@ def extract(tarball_path, path): return Tarball - def mock_extract_data(test_name, dataset_name, input_type, data): + def mock_extract_data(self, test_name, dataset_name, input_type, data): return {"status": "failed", "exception": "Unsupported Media Type"} monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr( + ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata + ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) From 60bc732d2cca65d3592968dfb6390b4a3eeae576 Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 14 Jun 2023 12:38:12 +0530 Subject: [PATCH 09/15] Refactored and added more unit test coverage --- .../test/unit/server/test_quisby_results.py | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py index 5e780424df..2f62be01c9 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -41,6 +41,15 @@ def query_api( return query_api + def mock_find_dataset(self, dataset): + class Tarball(object): + tarball_path = Path("/dataset/tarball.tar.xz") + + def extract(tarball_path, path): + return "CSV_file_as_a_byte_stream" + + return Tarball + def mock_get_dataset_metadata(self, dataset, key): return {"dataset.metalog.pbench.script": "uperf"} @@ -61,19 +70,10 @@ def test_unauthorized_access(self, query_get_as): } def test_quisby_success(self, query_get_as, monkeypatch): - def mock_find_dataset(self, dataset): - class Tarball(object): - tarball_path = Path("/dataset/tarball.tar.xz") - - def extract(tarball_path, path): - return "CSV_file_as_a_byte_stream" - - return Tarball - def mock_extract_data(self, test_name, dataset_name, input_type, data): return {"status": "success", "json_data": "quisby_data"} - monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr( ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata ) @@ -84,7 +84,7 @@ def mock_extract_data(self, test_name, dataset_name, input_type, data): assert response.json["json_data"] def test_quisby_failure(self, query_get_as, monkeypatch): - def mock_find_dataset(self, dataset): + def mock_find_dataset_with_incorrect_data(self, dataset): class Tarball(object): tarball_path = Path("/dataset/tarball.tar.xz") @@ -96,10 +96,21 @@ def extract(tarball_path, path): def mock_extract_data(self, test_name, dataset_name, input_type, data): return {"status": "failed", "exception": "Unsupported Media Type"} - monkeypatch.setattr(CacheManager, "find_dataset", mock_find_dataset) + monkeypatch.setattr( + CacheManager, "find_dataset", mock_find_dataset_with_incorrect_data + ) monkeypatch.setattr( ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) + + def test_unsupported_benchmark(self, query_get_as, monkeypatch): + def mock_get_metadata(self, dataset, key): + return {"dataset.metalog.pbench.script": "linpack"} + + monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) + monkeypatch.setattr(ApiBase, "_get_dataset_metadata", mock_get_metadata) + response = query_get_as("uperf_1", "test", HTTPStatus.NOT_FOUND) + response.json["message"] = "Unsupported Benchmark" From 3edbcd31ef08ad214c8e7658fce6af10366e5adf Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 14 Jun 2023 13:47:42 +0530 Subject: [PATCH 10/15] Logging error information --- lib/pbench/server/api/resources/quisby.py | 16 +++++++++------- .../test/unit/server/test_quisby_results.py | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/quisby.py index 694e55f400..81ae362aec 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/quisby.py @@ -52,14 +52,12 @@ def _get( This function returns the Quisby data for the requested dataset. Args: - params: includes the uri parameters, which provide the dataset and target. + params: includes the uri parameters, which provide the dataset. request: Original incoming Request object context: API context dictionary Raises: - APIAbort, reporting "NOT_FOUND" - APIInternalError, reporting the failure message - + APIAbort, reporting "NOT_FOUND" and "INTERNAL_SERVER_ERROR" GET /api/v1/quisby/{dataset} """ @@ -71,8 +69,8 @@ def _get( tarball = cache_m.find_dataset(dataset.resource_id) except TarballNotFound as e: raise APIAbort(HTTPStatus.NOT_FOUND, str(e)) - name = Dataset.stem(tarball.tarball_path) + name = Dataset.stem(tarball.tarball_path) try: file = tarball.extract(tarball.tarball_path, f"{name}/result.csv") except TarballUnpackError as e: @@ -82,7 +80,6 @@ def _get( dataset, ["dataset.metalog.pbench.script"] ) benchmark = metadata["dataset.metalog.pbench.script"] - if benchmark == "uperf": benchmark_type = BenchmarkName.UPERF elif benchmark == "fio": @@ -99,4 +96,9 @@ def _get( response.status_code = HTTPStatus.OK return response else: - raise APIInternalError("Unexpected failure from Quisby processing") + current_app.logger.error( + "Quisby processing failure. Exception :{}", get_quisby_data["exception"] + ) + raise APIAbort( + HTTPStatus.INTERNAL_SERVER_ERROR, "Unexpected failure from Quisby" + ) diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_quisby_results.py index 2f62be01c9..c942d0b1f5 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_quisby_results.py @@ -103,8 +103,8 @@ def mock_extract_data(self, test_name, dataset_name, input_type, data): ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) - - query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) + response = query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) + response.json["message"] = "Unexpected failure from Quisby" def test_unsupported_benchmark(self, query_get_as, monkeypatch): def mock_get_metadata(self, dataset, key): From 97dec77e441c14f3dfe4cd996505276a8790ade4 Mon Sep 17 00:00:00 2001 From: siddardh Date: Fri, 16 Jun 2023 16:33:52 +0530 Subject: [PATCH 11/15] Modifying error message to not expose `cache manager` to API caller --- lib/pbench/server/cache_manager.py | 4 ++-- lib/pbench/test/unit/server/test_cache_manager.py | 12 +++--------- .../test/unit/server/test_datasets_inventory.py | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/pbench/server/cache_manager.py b/lib/pbench/server/cache_manager.py index aea677f0b9..caaacd0caa 100644 --- a/lib/pbench/server/cache_manager.py +++ b/lib/pbench/server/cache_manager.py @@ -49,7 +49,7 @@ def __init__(self, tarball: str): self.tarball = tarball def __str__(self) -> str: - return f"The dataset tarball named {self.tarball!r} is not present in the cache manager" + return f"The dataset tarball named {self.tarball!r} not found" class DuplicateTarball(CacheManagerError): @@ -59,7 +59,7 @@ def __init__(self, tarball: str): self.tarball = tarball def __str__(self) -> str: - return f"A dataset tarball named {self.tarball!r} is already present in the cache manager" + return f"A dataset tarball named {self.tarball!r} is already present" class MetadataError(CacheManagerError): diff --git a/lib/pbench/test/unit/server/test_cache_manager.py b/lib/pbench/test/unit/server/test_cache_manager.py index fe096db460..791d6ce7e9 100644 --- a/lib/pbench/test/unit/server/test_cache_manager.py +++ b/lib/pbench/test/unit/server/test_cache_manager.py @@ -236,7 +236,7 @@ def test_create_bad( cm.create(tarball.tarball_path) assert ( str(exc.value) - == "A dataset tarball named 'pbench-user-benchmark_some + config_2021.05.01T12.42.42' is already present in the cache manager" + == "A dataset tarball named 'pbench-user-benchmark_some + config_2021.05.01T12.42.42' is already present" ) assert tarball.metadata == fake_get_metadata(tarball.tarball_path) assert exc.value.tarball == tarball.name @@ -924,10 +924,7 @@ def mock_run(args, **kwargs): assert tarball == cm[md5] with pytest.raises(TarballNotFound) as exc: cm["foobar"] - assert ( - str(exc.value) - == "The dataset tarball named 'foobar' is not present in the cache manager" - ) + assert str(exc.value) == "The dataset tarball named 'foobar' not found" # Test __contains__ assert md5 in cm @@ -946,10 +943,7 @@ def mock_run(args, **kwargs): # Try to find a dataset that doesn't exist with pytest.raises(TarballNotFound) as exc: cm.find_dataset("foobar") - assert ( - str(exc.value) - == "The dataset tarball named 'foobar' is not present in the cache manager" - ) + assert str(exc.value) == "The dataset tarball named 'foobar' not found" assert exc.value.tarball == "foobar" # Unpack the dataset, creating INCOMING and RESULTS links diff --git a/lib/pbench/test/unit/server/test_datasets_inventory.py b/lib/pbench/test/unit/server/test_datasets_inventory.py index c48319eb22..947ec9dda5 100644 --- a/lib/pbench/test/unit/server/test_datasets_inventory.py +++ b/lib/pbench/test/unit/server/test_datasets_inventory.py @@ -59,7 +59,7 @@ def test_get_no_dataset(self, query_get_as): def test_dataset_not_present(self, query_get_as): response = query_get_as("fio_2", "metadata.log", HTTPStatus.NOT_FOUND) assert response.json == { - "message": "The dataset tarball named 'random_md5_string4' is not present in the cache manager" + "message": "The dataset tarball named 'random_md5_string4' not found" } def test_unauthorized_access(self, query_get_as): From 776a7e887c625532366dcda04275300abd00ae39 Mon Sep 17 00:00:00 2001 From: siddardh Date: Fri, 16 Jun 2023 16:49:14 +0530 Subject: [PATCH 12/15] Rename quisby to visualize and addressed review comments --- lib/pbench/server/api/__init__.py | 10 ++++---- .../api/resources/{quisby.py => visualize.py} | 24 +++++++------------ .../unit/server/test_endpoint_configure.py | 4 ++-- ...st_quisby_results.py => test_visualize.py} | 16 ++++++------- 4 files changed, 24 insertions(+), 30 deletions(-) rename lib/pbench/server/api/resources/{quisby.py => visualize.py} (77%) rename lib/pbench/test/unit/server/{test_quisby_results.py => test_visualize.py} (90%) diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index 95d89100f2..fecb9ea562 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -33,11 +33,11 @@ SampleValues, ) from pbench.server.api.resources.query_apis.datasets_search import DatasetsSearch -from pbench.server.api.resources.quisby import QuisbyData from pbench.server.api.resources.relay import Relay from pbench.server.api.resources.server_audit import ServerAudit from pbench.server.api.resources.server_settings import ServerSettings from pbench.server.api.resources.upload import Upload +from pbench.server.api.resources.visualize import Visualize import pbench.server.auth.auth as Auth from pbench.server.database import init_db from pbench.server.database.database import Database @@ -135,10 +135,10 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): resource_class_args=(config,), ) api.add_resource( - QuisbyData, - f"{base_uri}/quisby/", - f"{base_uri}/quisby//", - endpoint="quisby", + Visualize, + f"{base_uri}/visualize/", + f"{base_uri}/visualize//", + endpoint="visualize", resource_class_args=(config,), ) api.add_resource( diff --git a/lib/pbench/server/api/resources/quisby.py b/lib/pbench/server/api/resources/visualize.py similarity index 77% rename from lib/pbench/server/api/resources/quisby.py rename to lib/pbench/server/api/resources/visualize.py index 81ae362aec..a786fdf86f 100644 --- a/lib/pbench/server/api/resources/quisby.py +++ b/lib/pbench/server/api/resources/visualize.py @@ -27,9 +27,9 @@ from pbench.server.database import Dataset -class QuisbyData(ApiBase): +class Visualize(ApiBase): """ - API class to retrieve Quisby data for a dataset + API class to retrieve data using Quisby to visualize """ def __init__(self, config: PbenchServerConfig): @@ -49,7 +49,7 @@ def _get( self, params: ApiParams, request: Request, context: ApiContext ) -> Response: """ - This function returns the Quisby data for the requested dataset. + This function is using Quisby to process results into a form that supports visualization Args: params: includes the uri parameters, which provide the dataset. @@ -59,7 +59,7 @@ def _get( Raises: APIAbort, reporting "NOT_FOUND" and "INTERNAL_SERVER_ERROR" - GET /api/v1/quisby/{dataset} + GET /api/v1/visualize/{dataset} """ dataset = params.uri["dataset"] @@ -82,23 +82,17 @@ def _get( benchmark = metadata["dataset.metalog.pbench.script"] if benchmark == "uperf": benchmark_type = BenchmarkName.UPERF - elif benchmark == "fio": - benchmark_type = BenchmarkName.FIO else: - raise APIAbort(HTTPStatus.NOT_FOUND, "Unsupported Benchmark") + raise APIAbort(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Benchmark") get_quisby_data = QuisbyProcessing.extract_data( self, benchmark_type, dataset.name, InputType.STREAM, file ) if get_quisby_data["status"] == "success": - response = jsonify(get_quisby_data) - response.status_code = HTTPStatus.OK - return response + return jsonify(get_quisby_data) + else: - current_app.logger.error( - "Quisby processing failure. Exception :{}", get_quisby_data["exception"] - ) - raise APIAbort( - HTTPStatus.INTERNAL_SERVER_ERROR, "Unexpected failure from Quisby" + raise APIInternalError( + f"Quisby processing failure. Exception : { get_quisby_data['exception']}" ) diff --git a/lib/pbench/test/unit/server/test_endpoint_configure.py b/lib/pbench/test/unit/server/test_endpoint_configure.py index 83abba5b40..e2fdbbeafb 100644 --- a/lib/pbench/test/unit/server/test_endpoint_configure.py +++ b/lib/pbench/test/unit/server/test_endpoint_configure.py @@ -106,8 +106,8 @@ def check_config(self, client, server_config, host, my_headers={}): "template": f"{uri}/key/{{key}}", "params": {"key": {"type": "string"}}, }, - "quisby": { - "template": f"{uri}/quisby/{{dataset}}", + "visualize": { + "template": f"{uri}/visualize/{{dataset}}", "params": {"dataset": {"type": "string"}}, }, "relay": { diff --git a/lib/pbench/test/unit/server/test_quisby_results.py b/lib/pbench/test/unit/server/test_visualize.py similarity index 90% rename from lib/pbench/test/unit/server/test_quisby_results.py rename to lib/pbench/test/unit/server/test_visualize.py index c942d0b1f5..4f56cc9653 100644 --- a/lib/pbench/test/unit/server/test_quisby_results.py +++ b/lib/pbench/test/unit/server/test_visualize.py @@ -10,7 +10,7 @@ from pbench.server.database.models.datasets import Dataset, DatasetNotFound -class TestQuisbyResults: +class TestVisualize: @pytest.fixture() def query_get_as(self, client, server_config, more_datasets, get_token_func): """ @@ -21,7 +21,7 @@ def query_get_as(self, client, server_config, more_datasets, get_token_func): client: Flask test API client fixture server_config: Pbench config fixture more_datasets: Dataset construction fixture - pbench_drb_token: Authenticated user token fixture + get_token_func: Pbench token fixture """ def query_api( @@ -33,7 +33,7 @@ def query_api( dataset_id = dataset # Allow passing deliberately bad value headers = {"authorization": f"bearer {get_token_func(user)}"} response = client.get( - f"{server_config.rest_uri}/quisby/{dataset_id}/", + f"{server_config.rest_uri}/visualize/{dataset_id}/", headers=headers, ) assert response.status_code == expected_status @@ -60,7 +60,7 @@ def test_get_no_dataset(self, query_get_as): def test_dataset_not_present(self, query_get_as): response = query_get_as("fio_2", "drb", HTTPStatus.NOT_FOUND) assert response.json == { - "message": "The dataset tarball named 'random_md5_string4' is not present in the cache manager" + "message": "The dataset tarball named 'random_md5_string4' not found" } def test_unauthorized_access(self, query_get_as): @@ -69,7 +69,7 @@ def test_unauthorized_access(self, query_get_as): "message": "User drb is not authorized to READ a resource owned by test with private access" } - def test_quisby_success(self, query_get_as, monkeypatch): + def test_successful_get(self, query_get_as, monkeypatch): def mock_extract_data(self, test_name, dataset_name, input_type, data): return {"status": "success", "json_data": "quisby_data"} @@ -81,9 +81,9 @@ def mock_extract_data(self, test_name, dataset_name, input_type, data): response = query_get_as("uperf_1", "test", HTTPStatus.OK) assert response.json["status"] == "success" - assert response.json["json_data"] + assert response.json["json_data"] == "quisby_data" - def test_quisby_failure(self, query_get_as, monkeypatch): + def test_unsuccessful_get_with_incorrect_data(self, query_get_as, monkeypatch): def mock_find_dataset_with_incorrect_data(self, dataset): class Tarball(object): tarball_path = Path("/dataset/tarball.tar.xz") @@ -112,5 +112,5 @@ def mock_get_metadata(self, dataset, key): monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr(ApiBase, "_get_dataset_metadata", mock_get_metadata) - response = query_get_as("uperf_1", "test", HTTPStatus.NOT_FOUND) + response = query_get_as("uperf_1", "test", HTTPStatus.UNSUPPORTED_MEDIA_TYPE) response.json["message"] = "Unsupported Benchmark" From f30611fba314f9522896a2e40b32ef544b5a2b36 Mon Sep 17 00:00:00 2001 From: siddardh Date: Fri, 16 Jun 2023 17:35:57 +0530 Subject: [PATCH 13/15] Review comments --- lib/pbench/client/__init__.py | 2 +- lib/pbench/server/api/__init__.py | 1 - lib/pbench/server/api/resources/visualize.py | 2 +- lib/pbench/test/unit/server/test_visualize.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pbench/client/__init__.py b/lib/pbench/client/__init__.py index 7b91083655..fbae00786c 100644 --- a/lib/pbench/client/__init__.py +++ b/lib/pbench/client/__init__.py @@ -49,11 +49,11 @@ class API(Enum): DATASETS_VALUES = "datasets_values" ENDPOINTS = "endpoints" KEY = "key" - QUISBY = "quisby" RELAY = "relay" SERVER_AUDIT = "server_audit" SERVER_SETTINGS = "server_settings" UPLOAD = "upload" + VISUALIZE = "visualize" class PbenchServerClient: diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index fecb9ea562..24ca328781 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -137,7 +137,6 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): api.add_resource( Visualize, f"{base_uri}/visualize/", - f"{base_uri}/visualize//", endpoint="visualize", resource_class_args=(config,), ) diff --git a/lib/pbench/server/api/resources/visualize.py b/lib/pbench/server/api/resources/visualize.py index a786fdf86f..33569830f2 100644 --- a/lib/pbench/server/api/resources/visualize.py +++ b/lib/pbench/server/api/resources/visualize.py @@ -94,5 +94,5 @@ def _get( else: raise APIInternalError( - f"Quisby processing failure. Exception : { get_quisby_data['exception']}" + f"Quisby processing failure. Exception: {get_quisby_data['exception']}" ) diff --git a/lib/pbench/test/unit/server/test_visualize.py b/lib/pbench/test/unit/server/test_visualize.py index 4f56cc9653..3005150218 100644 --- a/lib/pbench/test/unit/server/test_visualize.py +++ b/lib/pbench/test/unit/server/test_visualize.py @@ -33,7 +33,7 @@ def query_api( dataset_id = dataset # Allow passing deliberately bad value headers = {"authorization": f"bearer {get_token_func(user)}"} response = client.get( - f"{server_config.rest_uri}/visualize/{dataset_id}/", + f"{server_config.rest_uri}/visualize/{dataset_id}", headers=headers, ) assert response.status_code == expected_status From 74209fabd0d7b0ec49784bc92a98b327b0a391dd Mon Sep 17 00:00:00 2001 From: siddardh Date: Tue, 20 Jun 2023 23:38:29 +0530 Subject: [PATCH 14/15] Address review comments --- lib/pbench/server/api/__init__.py | 12 +++--- lib/pbench/server/api/resources/visualize.py | 38 +++++++++---------- lib/pbench/server/cache_manager.py | 2 +- .../test/unit/server/test_cache_manager.py | 4 +- .../unit/server/test_datasets_inventory.py | 2 +- .../unit/server/test_endpoint_configure.py | 8 ++-- lib/pbench/test/unit/server/test_visualize.py | 28 ++++++++------ 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index 24ca328781..d9e15b5ea6 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -134,12 +134,6 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): endpoint="key", resource_class_args=(config,), ) - api.add_resource( - Visualize, - f"{base_uri}/visualize/", - endpoint="visualize", - resource_class_args=(config,), - ) api.add_resource( ServerAudit, f"{base_uri}/server/audit", @@ -166,6 +160,12 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): endpoint="upload", resource_class_args=(config,), ) + api.add_resource( + Visualize, + f"{base_uri}/datasets//visualize", + endpoint="visualize", + resource_class_args=(config,), + ) def get_server_config() -> PbenchServerConfig: diff --git a/lib/pbench/server/api/resources/visualize.py b/lib/pbench/server/api/resources/visualize.py index 33569830f2..648bcf1459 100644 --- a/lib/pbench/server/api/resources/visualize.py +++ b/lib/pbench/server/api/resources/visualize.py @@ -29,7 +29,7 @@ class Visualize(ApiBase): """ - API class to retrieve data using Quisby to visualize + This class implements the Server API used to retrieve data for visualization. """ def __init__(self, config: PbenchServerConfig): @@ -63,12 +63,22 @@ def _get( """ dataset = params.uri["dataset"] - cache_m = CacheManager(self.config, current_app.logger) + try: tarball = cache_m.find_dataset(dataset.resource_id) except TarballNotFound as e: - raise APIAbort(HTTPStatus.NOT_FOUND, str(e)) + raise APIAbort( + HTTPStatus.NOT_FOUND, f"No dataset with ID '{e.tarball}' found" + ) from e + + metadata = self._get_dataset_metadata( + dataset, ["dataset.metalog.pbench.script"] + ) + benchmark = metadata["dataset.metalog.pbench.script"].upper() + benchmark_type = BenchmarkName.__members__.get(benchmark) + if not benchmark_type: + raise APIAbort(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Benchmark") name = Dataset.stem(tarball.tarball_path) try: @@ -76,23 +86,13 @@ def _get( except TarballUnpackError as e: raise APIInternalError(str(e)) from e - metadata = self._get_dataset_metadata( - dataset, ["dataset.metalog.pbench.script"] - ) - benchmark = metadata["dataset.metalog.pbench.script"] - if benchmark == "uperf": - benchmark_type = BenchmarkName.UPERF - else: - raise APIAbort(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Benchmark") - - get_quisby_data = QuisbyProcessing.extract_data( - self, benchmark_type, dataset.name, InputType.STREAM, file + pquisby_obj = QuisbyProcessing() + get_quisby_data = pquisby_obj.extract_data( + benchmark_type, dataset.name, InputType.STREAM, file ) - if get_quisby_data["status"] == "success": - return jsonify(get_quisby_data) - - else: + if get_quisby_data["status"] != "success": raise APIInternalError( f"Quisby processing failure. Exception: {get_quisby_data['exception']}" - ) + ) from None + return jsonify(get_quisby_data) diff --git a/lib/pbench/server/cache_manager.py b/lib/pbench/server/cache_manager.py index caaacd0caa..d594a49efd 100644 --- a/lib/pbench/server/cache_manager.py +++ b/lib/pbench/server/cache_manager.py @@ -49,7 +49,7 @@ def __init__(self, tarball: str): self.tarball = tarball def __str__(self) -> str: - return f"The dataset tarball named {self.tarball!r} not found" + return f"The dataset tarball named {self.tarball!r} is not found" class DuplicateTarball(CacheManagerError): diff --git a/lib/pbench/test/unit/server/test_cache_manager.py b/lib/pbench/test/unit/server/test_cache_manager.py index 791d6ce7e9..93333e1ea6 100644 --- a/lib/pbench/test/unit/server/test_cache_manager.py +++ b/lib/pbench/test/unit/server/test_cache_manager.py @@ -924,7 +924,7 @@ def mock_run(args, **kwargs): assert tarball == cm[md5] with pytest.raises(TarballNotFound) as exc: cm["foobar"] - assert str(exc.value) == "The dataset tarball named 'foobar' not found" + assert str(exc.value) == "The dataset tarball named 'foobar' is not found" # Test __contains__ assert md5 in cm @@ -943,7 +943,7 @@ def mock_run(args, **kwargs): # Try to find a dataset that doesn't exist with pytest.raises(TarballNotFound) as exc: cm.find_dataset("foobar") - assert str(exc.value) == "The dataset tarball named 'foobar' not found" + assert str(exc.value) == "The dataset tarball named 'foobar' is not found" assert exc.value.tarball == "foobar" # Unpack the dataset, creating INCOMING and RESULTS links diff --git a/lib/pbench/test/unit/server/test_datasets_inventory.py b/lib/pbench/test/unit/server/test_datasets_inventory.py index 947ec9dda5..8f046b2c0d 100644 --- a/lib/pbench/test/unit/server/test_datasets_inventory.py +++ b/lib/pbench/test/unit/server/test_datasets_inventory.py @@ -59,7 +59,7 @@ def test_get_no_dataset(self, query_get_as): def test_dataset_not_present(self, query_get_as): response = query_get_as("fio_2", "metadata.log", HTTPStatus.NOT_FOUND) assert response.json == { - "message": "The dataset tarball named 'random_md5_string4' not found" + "message": "The dataset tarball named 'random_md5_string4' is not found" } def test_unauthorized_access(self, query_get_as): diff --git a/lib/pbench/test/unit/server/test_endpoint_configure.py b/lib/pbench/test/unit/server/test_endpoint_configure.py index e2fdbbeafb..9b2b0bea27 100644 --- a/lib/pbench/test/unit/server/test_endpoint_configure.py +++ b/lib/pbench/test/unit/server/test_endpoint_configure.py @@ -106,10 +106,6 @@ def check_config(self, client, server_config, host, my_headers={}): "template": f"{uri}/key/{{key}}", "params": {"key": {"type": "string"}}, }, - "visualize": { - "template": f"{uri}/visualize/{{dataset}}", - "params": {"dataset": {"type": "string"}}, - }, "relay": { "template": f"{uri}/relay/{{uri}}", "params": {"uri": {"type": "path"}}, @@ -123,6 +119,10 @@ def check_config(self, client, server_config, host, my_headers={}): "template": f"{uri}/upload/{{filename}}", "params": {"filename": {"type": "string"}}, }, + "visualize": { + "template": f"{uri}/datasets/{{dataset}}/visualize", + "params": {"dataset": {"type": "string"}}, + }, }, } diff --git a/lib/pbench/test/unit/server/test_visualize.py b/lib/pbench/test/unit/server/test_visualize.py index 3005150218..0fce225d98 100644 --- a/lib/pbench/test/unit/server/test_visualize.py +++ b/lib/pbench/test/unit/server/test_visualize.py @@ -6,7 +6,7 @@ import requests from pbench.server.api.resources import ApiBase -from pbench.server.cache_manager import CacheManager +from pbench.server.cache_manager import CacheManager, Tarball from pbench.server.database.models.datasets import Dataset, DatasetNotFound @@ -33,7 +33,7 @@ def query_api( dataset_id = dataset # Allow passing deliberately bad value headers = {"authorization": f"bearer {get_token_func(user)}"} response = client.get( - f"{server_config.rest_uri}/visualize/{dataset_id}", + f"{server_config.rest_uri}/datasets/{dataset_id}/visualize", headers=headers, ) assert response.status_code == expected_status @@ -41,18 +41,21 @@ def query_api( return query_api - def mock_find_dataset(self, dataset): + def mock_find_dataset(self, _dataset: str) -> Tarball: class Tarball(object): tarball_path = Path("/dataset/tarball.tar.xz") - def extract(tarball_path, path): + def extract(_tarball_path: Path, _path: str) -> str: return "CSV_file_as_a_byte_stream" return Tarball - def mock_get_dataset_metadata(self, dataset, key): + def mock_get_dataset_metadata(self, _dataset, _key): return {"dataset.metalog.pbench.script": "uperf"} + def mock_extract_data(self, test_name, dataset_name, input_type, data): + return {"status": "success", "json_data": "quisby_data"} + def test_get_no_dataset(self, query_get_as): response = query_get_as("nonexistent-dataset", "drb", HTTPStatus.NOT_FOUND) assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} @@ -60,7 +63,7 @@ def test_get_no_dataset(self, query_get_as): def test_dataset_not_present(self, query_get_as): response = query_get_as("fio_2", "drb", HTTPStatus.NOT_FOUND) assert response.json == { - "message": "The dataset tarball named 'random_md5_string4' not found" + "message": "No dataset with ID 'random_md5_string4' found" } def test_unauthorized_access(self, query_get_as): @@ -70,14 +73,12 @@ def test_unauthorized_access(self, query_get_as): } def test_successful_get(self, query_get_as, monkeypatch): - def mock_extract_data(self, test_name, dataset_name, input_type, data): - return {"status": "success", "json_data": "quisby_data"} monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr( ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata ) - monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) + monkeypatch.setattr(QuisbyProcessing, "extract_data", self.mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.OK) assert response.json["status"] == "success" @@ -104,13 +105,16 @@ def mock_extract_data(self, test_name, dataset_name, input_type, data): ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) - response.json["message"] = "Unexpected failure from Quisby" + assert response.json.get("message").startswith( + "Internal Pbench Server Error: log reference " + ) def test_unsupported_benchmark(self, query_get_as, monkeypatch): def mock_get_metadata(self, dataset, key): - return {"dataset.metalog.pbench.script": "linpack"} + return {"dataset.metalog.pbench.script": "hammerDB"} monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr(ApiBase, "_get_dataset_metadata", mock_get_metadata) + monkeypatch.setattr(QuisbyProcessing, "extract_data", self.mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.UNSUPPORTED_MEDIA_TYPE) - response.json["message"] = "Unsupported Benchmark" + response.json["message"] == "Unsupported Benchmark" From a2adb93532936a8a061aab6f84b79f86ba0a5ce7 Mon Sep 17 00:00:00 2001 From: siddardh Date: Wed, 21 Jun 2023 15:52:46 +0530 Subject: [PATCH 15/15] Next set of review comments --- lib/pbench/client/__init__.py | 2 +- lib/pbench/server/api/__init__.py | 14 ++++----- .../{visualize.py => datasets_visualize.py} | 11 ++++--- ...isualize.py => test_datasets_visualize.py} | 31 ++++++++++++------- .../unit/server/test_endpoint_configure.py | 8 ++--- 5 files changed, 37 insertions(+), 29 deletions(-) rename lib/pbench/server/api/resources/{visualize.py => datasets_visualize.py} (92%) rename lib/pbench/test/unit/server/{test_visualize.py => test_datasets_visualize.py} (83%) diff --git a/lib/pbench/client/__init__.py b/lib/pbench/client/__init__.py index fbae00786c..c7dd0426e7 100644 --- a/lib/pbench/client/__init__.py +++ b/lib/pbench/client/__init__.py @@ -47,13 +47,13 @@ class API(Enum): DATASETS_NAMESPACE = "datasets_namespace" DATASETS_SEARCH = "datasets_search" DATASETS_VALUES = "datasets_values" + DATASETS_VISUALIZE = "datasets_visualize" ENDPOINTS = "endpoints" KEY = "key" RELAY = "relay" SERVER_AUDIT = "server_audit" SERVER_SETTINGS = "server_settings" UPLOAD = "upload" - VISUALIZE = "visualize" class PbenchServerClient: diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index d9e15b5ea6..32ebe56065 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -17,6 +17,7 @@ from pbench.server.api.resources.datasets_inventory import DatasetsInventory from pbench.server.api.resources.datasets_list import DatasetsList from pbench.server.api.resources.datasets_metadata import DatasetsMetadata +from pbench.server.api.resources.datasets_visualize import DatasetsVisualize from pbench.server.api.resources.endpoint_configure import EndpointConfig from pbench.server.api.resources.query_apis.dataset import Datasets from pbench.server.api.resources.query_apis.datasets.datasets_contents import ( @@ -37,7 +38,6 @@ from pbench.server.api.resources.server_audit import ServerAudit from pbench.server.api.resources.server_settings import ServerSettings from pbench.server.api.resources.upload import Upload -from pbench.server.api.resources.visualize import Visualize import pbench.server.auth.auth as Auth from pbench.server.database import init_db from pbench.server.database.database import Database @@ -120,6 +120,12 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): endpoint="datasets_search", resource_class_args=(config,), ) + api.add_resource( + DatasetsVisualize, + f"{base_uri}/datasets//visualize", + endpoint="datasets_visualize", + resource_class_args=(config,), + ) api.add_resource( EndpointConfig, f"{base_uri}/endpoints", @@ -160,12 +166,6 @@ def register_endpoints(api: Api, app: Flask, config: PbenchServerConfig): endpoint="upload", resource_class_args=(config,), ) - api.add_resource( - Visualize, - f"{base_uri}/datasets//visualize", - endpoint="visualize", - resource_class_args=(config,), - ) def get_server_config() -> PbenchServerConfig: diff --git a/lib/pbench/server/api/resources/visualize.py b/lib/pbench/server/api/resources/datasets_visualize.py similarity index 92% rename from lib/pbench/server/api/resources/visualize.py rename to lib/pbench/server/api/resources/datasets_visualize.py index 648bcf1459..87673ada79 100644 --- a/lib/pbench/server/api/resources/visualize.py +++ b/lib/pbench/server/api/resources/datasets_visualize.py @@ -27,7 +27,7 @@ from pbench.server.database import Dataset -class Visualize(ApiBase): +class DatasetsVisualize(ApiBase): """ This class implements the Server API used to retrieve data for visualization. """ @@ -78,7 +78,9 @@ def _get( benchmark = metadata["dataset.metalog.pbench.script"].upper() benchmark_type = BenchmarkName.__members__.get(benchmark) if not benchmark_type: - raise APIAbort(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported Benchmark") + raise APIAbort( + HTTPStatus.UNSUPPORTED_MEDIA_TYPE, f"Unsupported Benchmark: {benchmark}" + ) name = Dataset.stem(tarball.tarball_path) try: @@ -86,13 +88,12 @@ def _get( except TarballUnpackError as e: raise APIInternalError(str(e)) from e - pquisby_obj = QuisbyProcessing() - get_quisby_data = pquisby_obj.extract_data( + get_quisby_data = QuisbyProcessing().extract_data( benchmark_type, dataset.name, InputType.STREAM, file ) if get_quisby_data["status"] != "success": raise APIInternalError( f"Quisby processing failure. Exception: {get_quisby_data['exception']}" - ) from None + ) return jsonify(get_quisby_data) diff --git a/lib/pbench/test/unit/server/test_visualize.py b/lib/pbench/test/unit/server/test_datasets_visualize.py similarity index 83% rename from lib/pbench/test/unit/server/test_visualize.py rename to lib/pbench/test/unit/server/test_datasets_visualize.py index 0fce225d98..92e14d1a9d 100644 --- a/lib/pbench/test/unit/server/test_visualize.py +++ b/lib/pbench/test/unit/server/test_datasets_visualize.py @@ -5,6 +5,7 @@ import pytest import requests +from pbench.server import JSON from pbench.server.api.resources import ApiBase from pbench.server.cache_manager import CacheManager, Tarball from pbench.server.database.models.datasets import Dataset, DatasetNotFound @@ -50,12 +51,9 @@ def extract(_tarball_path: Path, _path: str) -> str: return Tarball - def mock_get_dataset_metadata(self, _dataset, _key): + def mock_get_dataset_metadata(self, _dataset, _key) -> JSON: return {"dataset.metalog.pbench.script": "uperf"} - def mock_extract_data(self, test_name, dataset_name, input_type, data): - return {"status": "success", "json_data": "quisby_data"} - def test_get_no_dataset(self, query_get_as): response = query_get_as("nonexistent-dataset", "drb", HTTPStatus.NOT_FOUND) assert response.json == {"message": "Dataset 'nonexistent-dataset' not found"} @@ -73,28 +71,30 @@ def test_unauthorized_access(self, query_get_as): } def test_successful_get(self, query_get_as, monkeypatch): + def mock_extract_data(self, test_name, dataset_name, input_type, data) -> JSON: + return {"status": "success", "json_data": "quisby_data"} monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr( ApiBase, "_get_dataset_metadata", self.mock_get_dataset_metadata ) - monkeypatch.setattr(QuisbyProcessing, "extract_data", self.mock_extract_data) + monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.OK) assert response.json["status"] == "success" assert response.json["json_data"] == "quisby_data" def test_unsuccessful_get_with_incorrect_data(self, query_get_as, monkeypatch): - def mock_find_dataset_with_incorrect_data(self, dataset): + def mock_find_dataset_with_incorrect_data(self, dataset) -> Tarball: class Tarball(object): tarball_path = Path("/dataset/tarball.tar.xz") - def extract(tarball_path, path): + def extract(tarball_path, path) -> str: return "IncorrectData" return Tarball - def mock_extract_data(self, test_name, dataset_name, input_type, data): + def mock_extract_data(self, test_name, dataset_name, input_type, data) -> JSON: return {"status": "failed", "exception": "Unsupported Media Type"} monkeypatch.setattr( @@ -105,16 +105,23 @@ def mock_extract_data(self, test_name, dataset_name, input_type, data): ) monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.INTERNAL_SERVER_ERROR) - assert response.json.get("message").startswith( + assert response.json["message"].startswith( "Internal Pbench Server Error: log reference " ) def test_unsupported_benchmark(self, query_get_as, monkeypatch): - def mock_get_metadata(self, dataset, key): + flag = True + + def mock_extract_data(*args, **kwargs) -> JSON: + nonlocal flag + flag = False + + def mock_get_metadata(self, dataset, key) -> JSON: return {"dataset.metalog.pbench.script": "hammerDB"} monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset) monkeypatch.setattr(ApiBase, "_get_dataset_metadata", mock_get_metadata) - monkeypatch.setattr(QuisbyProcessing, "extract_data", self.mock_extract_data) + monkeypatch.setattr(QuisbyProcessing, "extract_data", mock_extract_data) response = query_get_as("uperf_1", "test", HTTPStatus.UNSUPPORTED_MEDIA_TYPE) - response.json["message"] == "Unsupported Benchmark" + assert response.json["message"] == "Unsupported Benchmark: HAMMERDB" + assert flag is True diff --git a/lib/pbench/test/unit/server/test_endpoint_configure.py b/lib/pbench/test/unit/server/test_endpoint_configure.py index 9b2b0bea27..a1c14cb61d 100644 --- a/lib/pbench/test/unit/server/test_endpoint_configure.py +++ b/lib/pbench/test/unit/server/test_endpoint_configure.py @@ -101,6 +101,10 @@ def check_config(self, client, server_config, host, my_headers={}): "dataset_view": {"type": "string"}, }, }, + "datasets_visualize": { + "template": f"{uri}/datasets/{{dataset}}/visualize", + "params": {"dataset": {"type": "string"}}, + }, "endpoints": {"template": f"{uri}/endpoints", "params": {}}, "key": { "template": f"{uri}/key/{{key}}", @@ -119,10 +123,6 @@ def check_config(self, client, server_config, host, my_headers={}): "template": f"{uri}/upload/{{filename}}", "params": {"filename": {"type": "string"}}, }, - "visualize": { - "template": f"{uri}/datasets/{{dataset}}/visualize", - "params": {"dataset": {"type": "string"}}, - }, }, }