diff --git a/swh/icinga_plugins/base_check.py b/swh/icinga_plugins/base_check.py index 44088fe..b3f29dc 100644 --- a/swh/icinga_plugins/base_check.py +++ b/swh/icinga_plugins/base_check.py @@ -1,25 +1,25 @@ # 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 class BaseCheck: def __init__(self, obj): self.warning_threshold = obj.get( - '_warning_threshold', self.DEFAULT_WARNING_THRESHOLD) + 'warning_threshold', self.DEFAULT_WARNING_THRESHOLD) self.critical_threshold = obj.get( - '_critical_threshold', self.DEFAULT_CRITICAL_THRESHOLD) + 'critical_threshold', self.DEFAULT_CRITICAL_THRESHOLD) def get_status(self, value): if self.critical_threshold and value >= self.critical_threshold: return (2, 'CRITICAL') elif self.warning_threshold and value >= self.warning_threshold: return (1, 'WARNING') else: return (0, 'OK') def print_result(self, status_type, status_string, **metrics): print(f'{self.TYPE} {status_type} - {status_string}') for (metric_name, metric_value) in sorted(metrics.items()): print(f"| '{metric_name}' = {metric_value:.2f}s") diff --git a/swh/icinga_plugins/cli.py b/swh/icinga_plugins/cli.py index 36d1546..08b437d 100644 --- a/swh/icinga_plugins/cli.py +++ b/swh/icinga_plugins/cli.py @@ -1,77 +1,80 @@ # 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 sys import click from swh.core.cli import CONTEXT_SETTINGS from .deposit import DepositCheck from .vault import VaultCheck @click.group(name='icinga_plugins', context_settings=CONTEXT_SETTINGS) @click.option('-w', '--warning', type=int, help='Warning threshold.') @click.option('-c', '--critical', type=int, help='Critical threshold.') @click.pass_context -def cli(ctx, **kwargs): +def cli(ctx, warning, critical): """Main command for Icinga plugins """ ctx.ensure_object(dict) - ctx.obj.update(kwargs) + if warning: + ctx.obj['warning_threshold'] = int(warning) + if critical: + ctx.obj['critical_threshold'] = int(critical) @cli.group(name='check-vault') @click.option('--swh-storage-url', type=str, required=True, help='URL to an swh-storage HTTP API') @click.option('--swh-web-url', type=str, required=True, help='URL to an swh-web instance') @click.option('--poll-interval', type=int, default=10, help='Interval (in seconds) between two polls to the API, ' 'to check for cooking status.') @click.pass_context def check_vault(ctx, **kwargs): ctx.obj.update(kwargs) @check_vault.command(name='directory') @click.pass_context def check_vault_directory(ctx): """Picks a random directory, requests its cooking via swh-web, and waits for completion.""" sys.exit(VaultCheck(ctx.obj).main()) @cli.group(name='check-deposit') @click.option('--server', type=str, default='https://deposit.softwareheritage.org/1', help='URL to the SWORD server to test') @click.option('--username', type=str, required=True, help='Login for the SWORD server') @click.option('--password', type=str, required=True, help='Password for the SWORD server') @click.option('--collection', type=str, required=True, help='Software collection to use on the SWORD server') @click.option('--poll-interval', type=int, default=10, help='Interval (in seconds) between two polls to the API, ' 'to check for ingestion status.') @click.pass_context def check_deposit(ctx, **kwargs): ctx.obj.update(kwargs) @check_deposit.command(name='single') @click.option('--archive', type=click.Path(), required=True, help='Software artefact to upload') @click.option('--metadata', type=click.Path(), required=True, help='Metadata file for the software artefact.') @click.pass_context def check_deposit_single(ctx, **kwargs): """Checks the provided archive and metadata file and be deposited.""" ctx.obj.update(kwargs) sys.exit(DepositCheck(ctx.obj).main()) diff --git a/swh/icinga_plugins/tests/test_deposit.py b/swh/icinga_plugins/tests/test_deposit.py index fd81e15..eddfabd 100644 --- a/swh/icinga_plugins/tests/test_deposit.py +++ b/swh/icinga_plugins/tests/test_deposit.py @@ -1,306 +1,375 @@ # 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 io import os import tarfile import time from click.testing import CliRunner import pytest from swh.icinga_plugins.cli import cli from .web_scenario import WebScenario BASE_URL = 'mock://swh-deposit.example.org/1' COMMON_OPTIONS = [ '--server', BASE_URL, '--username', 'test', '--password', 'test', '--collection', 'testcol', ] SAMPLE_METADATA = ''' Test Software swh test-software No One ''' ENTRY_TEMPLATE = ''' 42 2019-12-19 18:11:00 foo.tar.gz {status} http://purl.org/net/sword/package/SimpleZip ''' STATUS_TEMPLATE = ''' 42 {status} {status_detail} ''' @pytest.fixture(scope='session') def tmp_path(tmp_path_factory): return tmp_path_factory.mktemp(__name__) @pytest.fixture(scope='session') def sample_metadata(tmp_path): """Returns a sample metadata file's path """ path = os.path.join(tmp_path, 'metadata.xml') with open(path, 'w') as fd: fd.write(SAMPLE_METADATA) return path @pytest.fixture(scope='session') def sample_archive(tmp_path): """Returns a sample archive's path """ path = os.path.join(tmp_path, 'archive.tar.gz') with tarfile.open(path, 'w:gz') as tf: tf.addfile( tarfile.TarInfo('hello.py'), io.BytesIO(b'print("Hello world")')) return path 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_deposit_immediate_success( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='done')) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ]) assert result.output == ( "DEPOSIT OK - Deposit took 0.00s and succeeded.\n" "| 'load_time' = 0.00s\n" "| 'total_time' = 0.00s\n" "| 'upload_time' = 0.00s\n" "| 'validation_time' = 0.00s\n") assert result.exit_code == 0, result.output def test_deposit_delays( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='deposited')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='verified', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='loading', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='done', status_detail='')) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ]) assert result.output == ( "DEPOSIT OK - Deposit took 30.00s and succeeded.\n" "| 'load_time' = 20.00s\n" "| 'total_time' = 30.00s\n" "| 'upload_time' = 0.00s\n" "| 'validation_time' = 10.00s\n") assert result.exit_code == 0, result.output +def test_deposit_delay_warning( + requests_mock, mocker, sample_archive, sample_metadata, mocked_time): + scenario = WebScenario() + + scenario.add_step( + 'post', BASE_URL + '/testcol/', + ENTRY_TEMPLATE.format(status='deposited')) + scenario.add_step( + 'get', BASE_URL + '/testcol/42/status/', + STATUS_TEMPLATE.format(status='verified', status_detail='')) + scenario.add_step( + 'get', BASE_URL + '/testcol/42/status/', + STATUS_TEMPLATE.format(status='done', status_detail='')) + + scenario.install_mock(requests_mock) + + result = invoke([ + '--warning', '15', + 'check-deposit', + *COMMON_OPTIONS, + 'single', + '--archive', sample_archive, + '--metadata', sample_metadata, + ], catch_exceptions=True) + + assert result.output == ( + "DEPOSIT WARNING - Deposit took 20.00s and succeeded.\n" + "| 'load_time' = 10.00s\n" + "| 'total_time' = 20.00s\n" + "| 'upload_time' = 0.00s\n" + "| 'validation_time' = 10.00s\n") + assert result.exit_code == 1, result.output + + +def test_deposit_delay_critical( + requests_mock, mocker, sample_archive, sample_metadata, mocked_time): + scenario = WebScenario() + + scenario.add_step( + 'post', BASE_URL + '/testcol/', + ENTRY_TEMPLATE.format(status='deposited')) + scenario.add_step( + 'get', BASE_URL + '/testcol/42/status/', + STATUS_TEMPLATE.format(status='verified', status_detail='')) + scenario.add_step( + 'get', BASE_URL + '/testcol/42/status/', + STATUS_TEMPLATE.format(status='done', status_detail=''), + callback=lambda: time.sleep(60)) + + scenario.install_mock(requests_mock) + + result = invoke([ + '--critical', '50', + 'check-deposit', + *COMMON_OPTIONS, + 'single', + '--archive', sample_archive, + '--metadata', sample_metadata, + ], catch_exceptions=True) + + assert result.output == ( + "DEPOSIT CRITICAL - Deposit took 80.00s and succeeded.\n" + "| 'load_time' = 70.00s\n" + "| 'total_time' = 80.00s\n" + "| 'upload_time' = 0.00s\n" + "| 'validation_time' = 10.00s\n") + assert result.exit_code == 2, result.output + + def test_deposit_timeout( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='deposited'), callback=lambda: time.sleep(1500)) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='verified', status_detail=''), callback=lambda: time.sleep(1500)) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='loading', status_detail=''), callback=lambda: time.sleep(1500)) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ], catch_exceptions=True) assert result.output == ( "DEPOSIT CRITICAL - Timed out while in status loading " "(4520.0s seconds since deposit started)\n" "| 'total_time' = 4520.00s\n" "| 'upload_time' = 1500.00s\n" "| 'validation_time' = 1510.00s\n") assert result.exit_code == 2, result.output def test_deposit_rejected( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='deposited')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='rejected', status_detail='booo')) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ], catch_exceptions=True) assert result.output == ( "DEPOSIT CRITICAL - Deposit was rejected: booo\n" "| 'total_time' = 10.00s\n" "| 'upload_time' = 0.00s\n" "| 'validation_time' = 10.00s\n") assert result.exit_code == 2, result.output def test_deposit_failed( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='deposited')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='verified', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='loading', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='failed', status_detail='booo')) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ], catch_exceptions=True) assert result.output == ( "DEPOSIT CRITICAL - Deposit loading failed: booo\n" "| 'load_time' = 20.00s\n" "| 'total_time' = 30.00s\n" "| 'upload_time' = 0.00s\n" "| 'validation_time' = 10.00s\n") assert result.exit_code == 2, result.output def test_deposit_unexpected_status( requests_mock, mocker, sample_archive, sample_metadata, mocked_time): scenario = WebScenario() scenario.add_step( 'post', BASE_URL + '/testcol/', ENTRY_TEMPLATE.format(status='deposited')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='verified', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='loading', status_detail='')) scenario.add_step( 'get', BASE_URL + '/testcol/42/status/', STATUS_TEMPLATE.format(status='what', status_detail='booo')) scenario.install_mock(requests_mock) result = invoke([ 'check-deposit', *COMMON_OPTIONS, 'single', '--archive', sample_archive, '--metadata', sample_metadata, ], catch_exceptions=True) assert result.output == ( "DEPOSIT CRITICAL - Deposit got unexpected status: what (booo)\n" "| 'load_time' = 20.00s\n" "| 'total_time' = 30.00s\n" "| 'upload_time' = 0.00s\n" "| 'validation_time' = 10.00s\n") assert result.exit_code == 2, result.output