diff --git a/swh/icinga_plugins/tests/test_vault.py b/swh/icinga_plugins/tests/test_vault.py --- a/swh/icinga_plugins/tests/test_vault.py +++ b/swh/icinga_plugins/tests/test_vault.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2021 The Software Heritage developers +# Copyright (C) 2019-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -9,6 +9,8 @@ from .web_scenario import WebScenario +ROOT_URL = "mock://swh-web.example.org" + dir_id = "ab" * 20 response_pending = { @@ -19,7 +21,7 @@ } response_done = { - "fetch_url": f"/api/1/vault/directory/{dir_id}/raw/", + "fetch_url": f"{ROOT_URL}/api/1/vault/directory/{dir_id}/raw/", "id": 9, "obj_id": dir_id, "obj_type": "directory", @@ -52,11 +54,17 @@ def test_vault_immediate_success(requests_mock, mocker, mocked_time): scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) scenario.add_step("get", url, response_done) + scenario.add_step( + "get", + f"{ROOT_URL}/api/1/vault/directory/{dir_id}/raw/", + "this-is-a-tarball", + headers={"Content-Type": "application/gzip", "Content-Length": "100000"}, + ) scenario.install_mock(requests_mock) @@ -85,12 +93,18 @@ def test_vault_delayed_success(requests_mock, mocker, mocked_time): scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) scenario.add_step("get", url, response_pending) scenario.add_step("get", url, response_done) + scenario.add_step( + "get", + f"{ROOT_URL}/api/1/vault/directory/{dir_id}/raw/", + "this-is-a-tarball", + headers={"Content-Type": "application/gzip", "Content-Length": "100000"}, + ) scenario.install_mock(requests_mock) @@ -119,7 +133,7 @@ def test_vault_failure(requests_mock, mocker, mocked_time): scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) @@ -153,7 +167,7 @@ def test_vault_unknown_status(requests_mock, mocker, mocked_time): scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) @@ -187,7 +201,7 @@ def test_vault_timeout(requests_mock, mocker, mocked_time): scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) @@ -224,12 +238,18 @@ test that vault_check requests another one.""" scenario = WebScenario() - url = f"mock://swh-web.example.org/api/1/vault/directory/{dir_id}/" + url = f"{ROOT_URL}/api/1/vault/directory/{dir_id}/" scenario.add_step("get", url, {}, status_code=200) scenario.add_step("get", url, {}, status_code=404) scenario.add_step("post", url, response_pending) scenario.add_step("get", url, response_done) + scenario.add_step( + "get", + f"{ROOT_URL}/api/1/vault/directory/{dir_id}/raw/", + "this-is-a-tarball", + headers={"Content-Type": "application/gzip", "Content-Length": "100000"}, + ) scenario.install_mock(requests_mock) diff --git a/swh/icinga_plugins/tests/web_scenario.py b/swh/icinga_plugins/tests/web_scenario.py --- a/swh/icinga_plugins/tests/web_scenario.py +++ b/swh/icinga_plugins/tests/web_scenario.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 The Software Heritage developers +# Copyright (C) 2019-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -9,23 +9,24 @@ Tests can build successive steps by calling :py:meth:`WebScenario.add_step` with specifications of what endpoints should be called and in what order.""" -from dataclasses import dataclass +import dataclasses import json -from typing import Callable, List, Optional, Set +from typing import Callable, Dict, List, Optional, Set import requests_mock -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class Step: expected_method: str expected_url: str response: object status_code: int = 200 + headers: Dict[str, str] = dataclasses.field(default_factory=dict) callback: Optional[Callable[[], int]] = None -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class Endpoint: method: str url: str @@ -82,6 +83,7 @@ self._current_step += 1 context.status_code = step.status_code + context.headers.update(step.headers) if step.callback: step.callback() diff --git a/swh/icinga_plugins/vault.py b/swh/icinga_plugins/vault.py --- a/swh/icinga_plugins/vault.py +++ b/swh/icinga_plugins/vault.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 The Software Heritage developers +# Copyright (C) 2019-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -73,28 +73,53 @@ ) return 2 - if result["status"] == "done": - (status_code, status) = self.get_status(total_time) + if result["status"] == "failed": self.print_result( - status, + "CRITICAL", f"cooking directory {dir_id.hex()} took {total_time:.2f}s " - f"and succeeded.", + f'and failed with: {result["progress_message"]}', total_time=total_time, ) - return status_code - elif result["status"] == "failed": + return 2 + elif result["status"] != "done": self.print_result( "CRITICAL", f"cooking directory {dir_id.hex()} took {total_time:.2f}s " - f'and failed with: {result["progress_message"]}', + f'and resulted in unknown status: {result["status"]}', total_time=total_time, ) return 2 - else: + + response = requests.get(result["fetch_url"]) + if response.status_code != 200: self.print_result( "CRITICAL", - f"cooking directory {dir_id.hex()} took {total_time:.2f}s " - f'and resulted in unknown status: {result["status"]}', + f"Unexpected status code when downloading bundle: {response.status}", total_time=total_time, ) return 2 + content_type = response.headers["Content-Type"] + if content_type != "application/gzip": + self.print_result( + "CRITICAL", + f"Unexpected Content-Type when downloading bundle: {content_type}", + total_time=total_time, + ) + return 2 + content_length = response.headers["Content-Length"] + if int(content_length) < 10_000: # 10KB + self.print_result( + "CRITICAL", + f"Content-Length too small when downloading bundle: {content_type}", + total_time=total_time, + ) + return 2 + + (status_code, status) = self.get_status(total_time) + self.print_result( + status, + f"cooking directory {dir_id.hex()} took {total_time:.2f}s " + f"and succeeded.", + total_time=total_time, + ) + return status_code