diff --git a/swh/icinga_plugins/cli.py b/swh/icinga_plugins/cli.py --- a/swh/icinga_plugins/cli.py +++ b/swh/icinga_plugins/cli.py @@ -56,6 +56,37 @@ sys.exit(VaultCheck(ctx.obj).main()) +@icinga_cli_group.group(name="check-savecodenow") +@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 save code now status.", +) +@click.pass_context +def check_scn(ctx, **kwargs): + ctx.obj.update(kwargs) + + +@check_scn.command(name="origin") +@click.pass_context +def check_scn_origin(ctx): + """Picks a random origin, requests a save code now via the api, and waits for + completion. + + """ + from .save_code_now import SaveCodeNowCheck + + sys.exit(SaveCodeNowCheck(ctx.obj).main()) + + @icinga_cli_group.group(name="check-deposit") @click.option( "--server", diff --git a/swh/icinga_plugins/save_code_now.py b/swh/icinga_plugins/save_code_now.py new file mode 100644 --- /dev/null +++ b/swh/icinga_plugins/save_code_now.py @@ -0,0 +1,143 @@ +# Copyright (C) 2021 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 random +import time +from typing import Dict, List + +import requests + +from swh.model.model import OriginVisitStatus +from swh.storage import get_storage + +from .base_check import BaseCheck + + +class OriginNotFound(Exception): + pass + + +class SaveCodeNowCheck(BaseCheck): + TYPE = "SAVECODENOW" + DEFAULT_WARNING_THRESHOLD = 300 + DEFAULT_CRITICAL_THRESHOLD = 600 + + 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_save_code_now(self, visit_type: str, url: str): + return f"{self._swh_web_url}/api/1/origin/save/{visit_type}/url/{url}/" + + def _pick_random_origin(self, visit_type: str) -> OriginVisitStatus: + # hack to work around P1007 to fix to use the real code below + # origin = self._swh_storage.origin_visit_status_get_random(visit_type) + from datetime import datetime, timezone + + now = datetime.now(tz=timezone.utc) + origins = [ + OriginVisitStatus( + origin=url, visit=1, date=now, status="full", snapshot=None + ) + for url in [ + "https://github.com/ai03-2725/Unified-Daughterboard", + "https://github.com/vsellier/easy-cozy", + "https://github.com/josefadamcik/SofleKeyboard", + "https://github.com/divnix/devos", + "https://github.com/SebaUbuntu/TWRP-device-tree-generator", + "https://github.com/abeker/OWASP-Top-10", + "https://github.com/SocialGouv/plops-monde-1001", + "https://github.com/betagouv/tutorat", + "https://github.com/BRGM/map2loop-2", + "https://github.com/abeker/OWASP-Top-10-Front", + "https://github.com/jart/cosmopolitan", + "https://github.com/mattdibi/redox-keyboard", + ] + ] + origin = random.choice(origins) + if origin is None: + raise OriginNotFound() + return origin + + def main(self): + # visit_type = random.choice(['hg', 'svn', 'git']) + visit_type = "git" + try: + ovs = self._pick_random_origin(visit_type) + except OriginNotFound: + self.print_result( + "CRITICAL", f"Recent origin with type {visit_type} not found.", + ) + return 2 + + start_time = time.time() + total_time = 0 + # trigger the save code now requests + scn_url = self._url_save_code_now(ovs.type, ovs.origin) + response = requests.post(scn_url) + assert response.status_code == 200, (response, response.text) + + result: Dict = response.json() + + status_key = "save_task_status" + request_date = result["save_request_date"] + + while result[status_key] not in ("succeeded", "failed"): + time.sleep(self._poll_interval) + response = requests.get(scn_url) + assert response.status_code == 200, (response, response.text) + raw_result: List[Dict] = response.json() + assert len(raw_result) > 0, f"Unexpected result: {raw_result}" + # bwarf + if len(raw_result) > 1: + # retrieve only the one status result we are interested in + result = next( + filter(lambda r: r["save_request_date"] == request_date, raw_result) + ) + else: + result = raw_result[0] + + total_time = time.time() - start_time + + if total_time > self.critical_threshold: + self.print_result( + "CRITICAL", + f"Save code now request for {ovs.origin} took more than " + f"{total_time:.2f}s and has status: " + f'{result["save_task_status"]}', + total_time=total_time, + ) + return 2 + + if result[status_key] == "running": + continue + + if result[status_key] == "succeeded": + (status_code, status) = self.get_status(total_time) + self.print_result( + status, + f"Save code now request for {ovs.origin} took {total_time:.2f}s " + f"and succeeded.", + total_time=total_time, + ) + return status_code + elif result[status_key] == "failed": + self.print_result( + "CRITICAL", + f"Save code now request for {ovs.origin} took {total_time:.2f}s " + f"and failed", + total_time=total_time, + ) + return 2 + else: + self.print_result( + "CRITICAL", + f"Save code now request for {ovs.origin} took {total_time:.2f}s " + f'and resulted in unknown status: {result["status_key"]}', + total_time=total_time, + ) + return 2 diff --git a/swh/icinga_plugins/tests/test_save_code_now.py b/swh/icinga_plugins/tests/test_save_code_now.py new file mode 100644 --- /dev/null +++ b/swh/icinga_plugins/tests/test_save_code_now.py @@ -0,0 +1,4 @@ +# Copyright (C) 2021 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