diff --git a/swh/icinga_plugins/tests/test_vault.py b/swh/icinga_plugins/tests/test_vault.py index ed45c46..ee99db4 100644 --- a/swh/icinga_plugins/tests/test_vault.py +++ b/swh/icinga_plugins/tests/test_vault.py @@ -1,168 +1,256 @@ # Copyright (C) 2019 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 import time from click.testing import CliRunner from swh.icinga_plugins.cli import cli from .web_scenario import WebScenario dir_id = 'ab'*20 response_pending = { "obj_id": dir_id, "obj_type": "directory", "progress_message": "foo", "status": "pending" } response_done = { "fetch_url": f"/api/1/vault/directory/{dir_id}/raw/", "id": 9, "obj_id": dir_id, "obj_type": "directory", "status": "done" } response_failed = { "obj_id": dir_id, "obj_type": "directory", "progress_message": "foobar", "status": "failed" } +response_unknown_status = { + "obj_id": dir_id, + "obj_type": "directory", + "progress_message": "what", + "status": "boo" +} + class FakeStorage: def __init__(self, foo, **kwargs): pass def directory_get_random(self): return bytes.fromhex(dir_id) def invoke(args, catch_exceptions=False): runner = CliRunner() result = runner.invoke(cli, args) if not catch_exceptions and result.exception: print(result.output) raise result.exception return result 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}/' scenario.add_step('get', url, {}, status_code=404) scenario.add_step('post', url, response_pending) scenario.add_step('get', url, response_done) scenario.install_mock(requests_mock) get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') get_storage_mock.side_effect = FakeStorage result = invoke([ 'check-vault', '--swh-web-url', 'mock://swh-web.example.org', '--swh-storage-url', 'foo://example.org', 'directory', ]) assert result.output == ( f"VAULT OK - cooking directory {dir_id} took " f"10.00s and succeeded.\n" f"| 'total_time' = 10.00s\n") assert result.exit_code == 0, result.output 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}/' 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.install_mock(requests_mock) get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') get_storage_mock.side_effect = FakeStorage result = invoke([ 'check-vault', '--swh-web-url', 'mock://swh-web.example.org', '--swh-storage-url', 'foo://example.org', 'directory', ]) assert result.output == ( f"VAULT OK - cooking directory {dir_id} took " f"20.00s and succeeded.\n" f"| 'total_time' = 20.00s\n") assert result.exit_code == 0, result.output def test_vault_failure(requests_mock, mocker, mocked_time): scenario = WebScenario() url = f'mock://swh-web.example.org/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_failed) scenario.install_mock(requests_mock) get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') get_storage_mock.side_effect = FakeStorage result = invoke([ 'check-vault', '--swh-web-url', 'mock://swh-web.example.org', '--swh-storage-url', 'foo://example.org', 'directory', ], catch_exceptions=True) assert result.output == ( f"VAULT CRITICAL - cooking directory {dir_id} took " f"10.00s and failed with: foobar\n" f"| 'total_time' = 10.00s\n") assert result.exit_code == 2, result.output +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}/' + + scenario.add_step('get', url, {}, status_code=404) + scenario.add_step('post', url, response_pending) + scenario.add_step('get', url, response_unknown_status) + + scenario.install_mock(requests_mock) + + get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') + get_storage_mock.side_effect = FakeStorage + + result = invoke([ + 'check-vault', + '--swh-web-url', 'mock://swh-web.example.org', + '--swh-storage-url', 'foo://example.org', + 'directory', + ], catch_exceptions=True) + + assert result.output == ( + f"VAULT CRITICAL - cooking directory {dir_id} took " + f"10.00s and resulted in unknown status: boo\n" + f"| 'total_time' = 10.00s\n") + assert result.exit_code == 2, result.output + + def test_vault_timeout(requests_mock, mocker, mocked_time): scenario = WebScenario() url = f'mock://swh-web.example.org/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_pending, callback=lambda: time.sleep(4000)) scenario.install_mock(requests_mock) get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') get_storage_mock.side_effect = FakeStorage result = invoke([ 'check-vault', '--swh-web-url', 'mock://swh-web.example.org', '--swh-storage-url', 'foo://example.org', 'directory', ], catch_exceptions=True) assert result.output == ( f"VAULT CRITICAL - cooking directory {dir_id} took more than " f"4020.00s and has status: foo\n" f"| 'total_time' = 4020.00s\n") assert result.exit_code == 2, result.output + + +def test_vault_cached_directory(requests_mock, mocker, mocked_time): + """First serves a directory that's already in the cache, to + test that vault_check requests another one.""" + scenario = WebScenario() + + url = f'mock://swh-web.example.org/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.install_mock(requests_mock) + + get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') + get_storage_mock.side_effect = FakeStorage + + result = invoke([ + 'check-vault', + '--swh-web-url', 'mock://swh-web.example.org', + '--swh-storage-url', 'foo://example.org', + 'directory', + ]) + + assert result.output == ( + f"VAULT OK - cooking directory {dir_id} took " + f"10.00s and succeeded.\n" + f"| 'total_time' = 10.00s\n") + assert result.exit_code == 0, result.output + + +def test_vault_no_directory(requests_mock, mocker, mocked_time): + """Tests with an empty storage""" + scenario = WebScenario() + scenario.install_mock(requests_mock) + + get_storage_mock = mocker.patch('swh.icinga_plugins.vault.get_storage') + get_storage_mock.side_effect = FakeStorage + mocker.patch( + f'{__name__}.FakeStorage.directory_get_random', return_value=None) + + result = invoke([ + 'check-vault', + '--swh-web-url', 'mock://swh-web.example.org', + '--swh-storage-url', 'foo://example.org', + 'directory', + ], catch_exceptions=True) + + assert result.output == ( + "VAULT CRITICAL - No directory exists in the archive.\n") + assert result.exit_code == 2, result.output diff --git a/swh/icinga_plugins/vault.py b/swh/icinga_plugins/vault.py index 3df0ad5..95c1883 100644 --- a/swh/icinga_plugins/vault.py +++ b/swh/icinga_plugins/vault.py @@ -1,98 +1,98 @@ # Copyright (C) 2019 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 import time import requests from swh.storage import get_storage from .base_check import BaseCheck class NoDirectory(Exception): pass class VaultCheck(BaseCheck): TYPE = 'VAULT' DEFAULT_WARNING_THRESHOLD = 0 DEFAULT_CRITICAL_THRESHOLD = 3600 def __init__(self, obj): super().__init__(obj) self._swh_storage = get_storage('remote', url=obj['swh_storage_url']) self._swh_web_url = obj['swh_web_url'] self._poll_interval = obj['poll_interval'] def _url_for_dir(self, dir_id): return self._swh_web_url + f'/api/1/vault/directory/{dir_id.hex()}/' def _pick_directory(self): dir_ = self._swh_storage.directory_get_random() if dir_ is None: raise NoDirectory() return dir_ def _pick_uncached_directory(self): while True: dir_id = self._pick_directory() response = requests.get(self._url_for_dir(dir_id)) if response.status_code == 404: return dir_id def main(self): try: dir_id = self._pick_uncached_directory() except NoDirectory: self.print_result( 'CRITICAL', - 'No directory exists in the archive') + 'No directory exists in the archive.') return 2 start_time = time.time() total_time = 0 response = requests.post(self._url_for_dir(dir_id)) assert response.status_code == 200, (response, response.text) result = response.json() while result['status'] in ('new', 'pending'): time.sleep(self._poll_interval) response = requests.get(self._url_for_dir(dir_id)) assert response.status_code == 200, (response, response.text) result = response.json() total_time = time.time() - start_time if total_time > self.critical_threshold: self.print_result( 'CRITICAL', f'cooking directory {dir_id.hex()} took more than ' f'{total_time:.2f}s and has status: ' f'{result["progress_message"]}', total_time=total_time) return 2 if result['status'] == 'done': (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 elif result['status'] == 'failed': self.print_result( 'CRITICAL', f'cooking directory {dir_id.hex()} took {total_time:.2f}s ' f'and failed with: {result["progress_message"]}', total_time=total_time) return 2 else: self.print_result( 'CRITICAL', f'cooking directory {dir_id.hex()} took {total_time:.2f}s ' f'and resulted in unknown status: {result["status"]}', total_time=total_time) return 2