From 048af0e4983fd70a816499cd77876b7b3e5d196f Mon Sep 17 00:00:00 2001 From: Whisperity Date: Mon, 19 Aug 2024 19:11:13 +0200 Subject: [PATCH 1/2] feat(server): Asynchronous server-side background task execution This patch implements the whole support ecosystem for server-side background tasks, in order to help lessen the load (and blocking) of API handlers in the web-server for long-running operations. A **Task** is represented by two things in strict co-existence: a lightweight, `pickle`-able implementation in the server's code (a subclass of `AbstractTask`) and a corresponding `BackgroundTask` database entity, which resides in the "configuration" database (shared across all products). A Task is created by API request handlers and then the user is instructed to retain the `TaskToken`: the task's unique identifier. Following, the server will dispatch execution of the object into a background worker process, and keep status synchronisation via the database. Even in a service cluster deployment, load balancing will not interfere with users' ability to query a task's status. While normal users can only query the status of a single task (which is usually automatically done by client code, and not the user manually executing something); product administrators, and especially server administrators have the ability to query an arbitrary set of tasks using the potential filters, with a dedicated API function (`getTasks()`) for this purpose. Tasks can be cancelled only by `SUPERUSER`s, at which point a special binary flag is set in the status record. However, to prevent complicating inter-process communication, cancellation is supposed to be implemented by `AbstractTask` subclasses in a co-operative way. The execution of tasks in a process and a `Task`'s ability to "communicate" with its execution environment is achieved through the new `TaskManager` instance, which is created for every process of a server's deployment. Unfortunately, tasks can die gracelessly if the server is terminated (either internally, or even externally). For this reason, the `DROPPED` status will indicate that the server has terminated prior to, or during a task's execution, and it was unable to produce results. The server was refactored significantly around the handling of subprocesses in order to support various server shutdown scenarios. Servers will start `background_worker_processes` number of task handling subprocesses, which are distinct from the already existing "API handling" subprocesses. By default, if unconfigured, `background_worker_processes` is equal to `worker_processes` (the number of API processes to spawn), which is equal to `$(nproc)` (CPU count in the system). This patch includes a `TestingDummyTask` demonstrative subclass of `AbstractTask` which counts up to an input number of seconds, and each second it gracefully checks whether it is being killed. The corresponding testing API endpoint, `createDummyTask()` can specify whether the task should simulate a failing status. This endpoint can only be used from, but is used extensively, the unit testing of the project. This patch does not include "nice" or "ergonomic" facilities for admins to manage the tasks, and so far, only the server-side of the corresponding API calls are supported. --- .github/workflows/test.yml | 4 +- .../codechecker_analyzer/analysis_manager.py | 39 +- bin/CodeChecker | 49 +- .../compatibility/multiprocessing.py | 15 +- codechecker_common/logger.py | 54 +- codechecker_common/process.py | 49 ++ codechecker_common/util.py | 19 + docs/web/server_config.md | 10 +- docs/web/user_guide.md | 19 +- web/api/Makefile | 9 +- web/api/codechecker_api_shared.thrift | 31 +- .../dist/codechecker-api-6.65.0.tgz | Bin 65065 -> 0 bytes .../dist/codechecker-api-6.66.0.tgz | Bin 0 -> 70399 bytes web/api/js/codechecker-api-node/package.json | 2 +- .../dist/codechecker_api.tar.gz | Bin 58808 -> 69915 bytes web/api/py/codechecker_api/setup.py | 2 +- .../dist/codechecker_api_shared.tar.gz | Bin 3799 -> 8199 bytes web/api/py/codechecker_api_shared/setup.py | 2 +- web/api/report_server.thrift | 82 ++- web/api/tasks.thrift | 160 +++++ web/client/codechecker_client/client.py | 4 +- .../codechecker_client/helpers/results.py | 8 +- web/codechecker_web/shared/version.py | 2 +- web/server/codechecker_server/api/common.py | 49 ++ .../codechecker_server/api/report_server.py | 49 +- web/server/codechecker_server/api/tasks.py | 382 ++++++++++++ web/server/codechecker_server/cli/server.py | 88 ++- .../database/config_db_model.py | 197 +++++- ...mplemented_keeping_track_of_background_.py | 81 +++ web/server/codechecker_server/routing.py | 31 +- web/server/codechecker_server/server.py | 560 +++++++++++++++--- .../codechecker_server/session_manager.py | 28 +- .../task_executors/__init__.py | 7 + .../task_executors/abstract_task.py | 183 ++++++ .../codechecker_server/task_executors/main.py | 142 +++++ .../task_executors/task_manager.py | 229 +++++++ web/server/codechecker_server/tmp.py | 37 -- web/server/config/server_config.json | 2 + web/server/vue-cli/package-lock.json | 253 ++++---- web/server/vue-cli/package.json | 2 +- .../instance_manager/test_instances.py | 3 +- web/tests/functional/tasks/__init__.py | 7 + .../functional/tasks/test_task_management.py | 484 +++++++++++++++ web/tests/libtest/env.py | 38 +- web/tests/libtest/thrift_client_to_db.py | 27 + 45 files changed, 2991 insertions(+), 448 deletions(-) create mode 100644 codechecker_common/process.py delete mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz create mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz create mode 100644 web/api/tasks.thrift create mode 100644 web/server/codechecker_server/api/common.py create mode 100644 web/server/codechecker_server/api/tasks.py create mode 100644 web/server/codechecker_server/migrations/config/versions/5e1501dfd333_implemented_keeping_track_of_background_.py create mode 100644 web/server/codechecker_server/task_executors/__init__.py create mode 100644 web/server/codechecker_server/task_executors/abstract_task.py create mode 100644 web/server/codechecker_server/task_executors/main.py create mode 100644 web/server/codechecker_server/task_executors/task_manager.py delete mode 100644 web/server/codechecker_server/tmp.py create mode 100644 web/tests/functional/tasks/__init__.py create mode 100644 web/tests/functional/tasks/test_task_management.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e76acb2455..c790568c25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,8 @@ jobs: - name: Install dependencies run: | pip install $(grep -iE "pylint|pycodestyle" analyzer/requirements_py/dev/requirements.txt) - - name: Run tests - run: make pylint pycodestyle + - name: Run pycodestyle & pylint + run: make -k pycodestyle pylint tools: name: Tools (report-converter, etc.) diff --git a/analyzer/codechecker_analyzer/analysis_manager.py b/analyzer/codechecker_analyzer/analysis_manager.py index 721bd82602..f81c205f1c 100644 --- a/analyzer/codechecker_analyzer/analysis_manager.py +++ b/analyzer/codechecker_analyzer/analysis_manager.py @@ -13,7 +13,6 @@ import shutil import signal import sys -import time import traceback import zipfile @@ -21,9 +20,9 @@ from threading import Timer import multiprocess -import psutil from codechecker_common.logger import get_logger +from codechecker_common.process import kill_process_tree from codechecker_common.review_status_handler import ReviewStatusHandler from codechecker_statistics_collector.collectors.special_return_value import \ @@ -342,42 +341,6 @@ def handle_failure( os.remove(plist_file) -def kill_process_tree(parent_pid, recursive=False): - """Stop the process tree try it gracefully first. - - Try to stop the parent and child processes gracefuly - first if they do not stop in time send a kill signal - to every member of the process tree. - - There is a similar function in the web part please - consider to update that in case of changing this. - """ - proc = psutil.Process(parent_pid) - children = proc.children(recursive) - - # Send a SIGTERM (Ctrl-C) to the main process - proc.terminate() - - # If children processes don't stop gracefully in time, - # slaughter them by force. - _, still_alive = psutil.wait_procs(children, timeout=5) - for p in still_alive: - p.kill() - - # Wait until this process is running. - n = 0 - timeout = 10 - while proc.is_running(): - if n > timeout: - LOG.warning("Waiting for process %s to stop has been timed out" - "(timeout = %s)! Process is still running!", - parent_pid, timeout) - break - - time.sleep(1) - n += 1 - - def setup_process_timeout(proc, timeout, failure_callback=None): """ diff --git a/bin/CodeChecker b/bin/CodeChecker index bf4b8cb52d..bbea727b83 100755 --- a/bin/CodeChecker +++ b/bin/CodeChecker @@ -6,10 +6,10 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # # ------------------------------------------------------------------------- - """ Used to kickstart CodeChecker. -Save original environment without modifications. + +Saves original environment without modifications. Used to run the logging in the same env. """ # This is for enabling CodeChecker as a filename (i.e. module name). @@ -25,9 +25,10 @@ import sys import tempfile PROC_PID = None +EXIT_CODE = None -def run_codechecker(checker_env, subcommand=None): +def run_codechecker(checker_env, subcommand=None) -> int: """ Run the CodeChecker. * checker_env - CodeChecker will be run in the checker env. @@ -62,11 +63,13 @@ def run_codechecker(checker_env, subcommand=None): global PROC_PID PROC_PID = proc.pid - proc.wait() - sys.exit(proc.returncode) + global EXIT_CODE + EXIT_CODE = proc.wait() + + return EXIT_CODE -def main(subcommand=None): +def main(subcommand=None) -> int: original_env = os.environ.copy() checker_env = original_env @@ -93,30 +96,32 @@ def main(subcommand=None): print('Saving original build environment failed.') print(ex) - def signal_term_handler(signum, _frame): + def signal_handler(signum, _frame): + """ + Forwards the received signal to the CodeChecker subprocess started by + this `main` script. + """ global PROC_PID if PROC_PID and sys.platform != "win32": - os.kill(PROC_PID, signal.SIGINT) - - _remove_tmp() - sys.exit(128 + signum) - - signal.signal(signal.SIGTERM, signal_term_handler) - signal.signal(signal.SIGINT, signal_term_handler) - - def signal_reload_handler(_sig, _frame): - global PROC_PID - if PROC_PID: - os.kill(PROC_PID, signal.SIGHUP) + try: + os.kill(PROC_PID, signum) + except ProcessLookupError: + pass + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) if sys.platform != "win32": - signal.signal(signal.SIGHUP, signal_reload_handler) + signal.signal(signal.SIGHUP, signal_handler) + signal.signal(signal.SIGCHLD, signal_handler) try: - run_codechecker(checker_env, subcommand) + global EXIT_CODE + EXIT_CODE = run_codechecker(checker_env, subcommand) finally: _remove_tmp() + return EXIT_CODE + if __name__ == "__main__": - main(None) + sys.exit(main(None) or 0) diff --git a/codechecker_common/compatibility/multiprocessing.py b/codechecker_common/compatibility/multiprocessing.py index 14ef7ebebe..eaee9a78e7 100644 --- a/codechecker_common/compatibility/multiprocessing.py +++ b/codechecker_common/compatibility/multiprocessing.py @@ -13,8 +13,15 @@ # pylint: disable=no-name-in-module # pylint: disable=unused-import if sys.platform in ["darwin", "win32"]: - from multiprocess import Pool # type: ignore - from multiprocess import cpu_count + from multiprocess import \ + Pool, Process, \ + Queue, \ + Value, \ + cpu_count else: - from concurrent.futures import ProcessPoolExecutor as Pool # type: ignore - from multiprocessing import cpu_count + from concurrent.futures import ProcessPoolExecutor as Pool + from multiprocessing import \ + Process, \ + Queue, \ + Value, \ + cpu_count diff --git a/codechecker_common/logger.py b/codechecker_common/logger.py index 8c860dee6e..35702fb0b8 100644 --- a/codechecker_common/logger.py +++ b/codechecker_common/logger.py @@ -6,16 +6,18 @@ # # ------------------------------------------------------------------------- - import argparse +import datetime import json import logging from logging import config from pathlib import Path import os +import sys +from typing import Optional -# The logging leaves can be accesses without -# importing the logging module in other modules. +# The logging leaves can be accesses without importing the logging module in +# other modules. DEBUG = logging.DEBUG INFO = logging.INFO WARNING = logging.WARNING @@ -25,14 +27,24 @@ CMDLINE_LOG_LEVELS = ['info', 'debug_analyzer', 'debug'] -DEBUG_ANALYZER = logging.DEBUG_ANALYZER = 15 # type: ignore +DEBUG_ANALYZER = 15 logging.addLevelName(DEBUG_ANALYZER, 'DEBUG_ANALYZER') +_Levels = {"DEBUG": DEBUG, + "DEBUG_ANALYZER": DEBUG_ANALYZER, + "INFO": INFO, + "WARNING": WARNING, + "ERROR": ERROR, + "CRITICAL": CRITICAL, + "NOTSET": NOTSET, + } + + class CCLogger(logging.Logger): def debug_analyzer(self, msg, *args, **kwargs): - if self.isEnabledFor(logging.DEBUG_ANALYZER): - self._log(logging.DEBUG_ANALYZER, msg, args, **kwargs) + if self.isEnabledFor(DEBUG_ANALYZER): + self._log(DEBUG_ANALYZER, msg, args, **kwargs) logging.setLoggerClass(CCLogger) @@ -113,6 +125,36 @@ def validate_loglvl(log_level): return log_level +def raw_sprint_log(logger: logging.Logger, level: str, message: str) \ + -> Optional[str]: + """ + Formats a raw log `message` using the date format of the specified + `logger`, without actually invoking the logging infrastructure. + """ + if not logger.isEnabledFor(_Levels[level]): + return None + + formatter = logger.handlers[0].formatter if len(logger.handlers) > 0 \ + else None + datefmt = formatter.datefmt if formatter else None + time = datetime.datetime.now().strftime(datefmt) if datefmt \ + else str(datetime.datetime.now()) + + return f"[{validate_loglvl(level)} {time}] - {message}" + + +def signal_log(logger: logging.Logger, level: str, message: str): + """ + Simulates a log output and logs a message within a signal handler, without + triggering a `RuntimeError` due to reentrancy in `print`-like method calls. + """ + formatted = raw_sprint_log(logger, level, message) + if not formatted: + return + + os.write(sys.stderr.fileno(), f"{formatted}\n".encode()) + + class LogCfgServer: """ Initialize a log configuration server for dynamic log configuration. diff --git a/codechecker_common/process.py b/codechecker_common/process.py new file mode 100644 index 0000000000..86da476d2a --- /dev/null +++ b/codechecker_common/process.py @@ -0,0 +1,49 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +import time + +import psutil + +from .logger import get_logger + + +LOG = get_logger("system") + + +def kill_process_tree(parent_pid, recursive=False): + """ + Stop the process tree, gracefully at first. + + Try to stop the parent and child processes gracefuly first. + If they do not stop in time, send a kill signal to every member of the + process tree. + """ + proc = psutil.Process(parent_pid) + children = proc.children(recursive) + + # Send a SIGTERM to the main process. + proc.terminate() + + # If children processes don't stop gracefully in time, slaughter them + # by force. + _, still_alive = psutil.wait_procs(children, timeout=5) + for p in still_alive: + p.kill() + + # Wait until this process is running. + n = 0 + timeout = 10 + while proc.is_running(): + if n > timeout: + LOG.warning("Waiting for process %s to stop has been timed out" + "(timeout = %s)! Process is still running!", + parent_pid, timeout) + break + + time.sleep(1) + n += 1 diff --git a/codechecker_common/util.py b/codechecker_common/util.py index c060bbd8ad..af0c1773d3 100644 --- a/codechecker_common/util.py +++ b/codechecker_common/util.py @@ -8,6 +8,8 @@ """ Util module. """ +import datetime +import hashlib import itertools import json import re @@ -15,6 +17,7 @@ import yaml import os import pathlib +import random from typing import List, TextIO import portalocker @@ -197,3 +200,19 @@ def __init__(self, *pathsegments): if not path.exists(): raise FileNotFoundError(f"File does not exist: {path.absolute()}") + + +def generate_random_token(num_bytes: int = 32) -> str: + """ + Returns a random-generated string usable as a token with `num_bytes` + hexadecimal characters in the output. + """ + prefix = str(os.getpid()).encode() + suffix = str(datetime.datetime.now()).encode() + + hash_value = ''.join( + [hashlib.sha256(prefix + os.urandom(num_bytes * 2) + suffix) + .hexdigest() + for _ in range(0, -(num_bytes // -64))]) + idx = random.randrange(0, len(hash_value) - num_bytes + 1) + return hash_value[idx:(idx + num_bytes)] diff --git a/docs/web/server_config.md b/docs/web/server_config.md index add9bddcb7..7eb0e4f468 100644 --- a/docs/web/server_config.md +++ b/docs/web/server_config.md @@ -17,7 +17,7 @@ Table of Contents * [Size of the compilation database](#size-of-the-compilation-database) * [Authentication](#authentication) -## Number of worker processes +## Number of API worker processes The `worker_processes` section of the config file controls how many processes will be started on the server to process API requests. @@ -25,6 +25,14 @@ will be started on the server to process API requests. The server needs to be restarted if the value is changed in the config file. +### Number of task worker processes +The `background_worker_processes` section of the config file controls how many +processes will be started on the server to process background jobs. + +*Default value*: Fallback to same amount as `worker_processes`. + +The server needs to be restarted if the value is changed in the config file. + ## Run limitation The `max_run_count` section of the config file controls how many runs can be stored on the server for a product. diff --git a/docs/web/user_guide.md b/docs/web/user_guide.md index 4d5c9cde7e..385e4d10ee 100644 --- a/docs/web/user_guide.md +++ b/docs/web/user_guide.md @@ -145,8 +145,9 @@ or via the `CodeChecker cmd` command-line client. ``` usage: CodeChecker server [-h] [-w WORKSPACE] [-f CONFIG_DIRECTORY] - [--host LISTEN_ADDRESS] [-v PORT] [--not-host-only] - [--skip-db-cleanup] [--config CONFIG_FILE] + [--machine-id MACHINE_ID] [--host LISTEN_ADDRESS] + [-v PORT] [--not-host-only] [--skip-db-cleanup] + [--config CONFIG_FILE] [--sqlite SQLITE_FILE | --postgresql] [--dbaddress DBADDRESS] [--dbport DBPORT] [--dbusername DBUSERNAME] [--dbname DBNAME] @@ -172,6 +173,20 @@ optional arguments: specific configuration (such as authentication settings, and TLS/SSL certificates) from. (default: /home//.codechecker) + --machine-id MACHINE_ID + A unique identifier to be used to identify the machine + running subsequent instances of the "same" server + process. This value is only used internally to + maintain normal function and bookkeeping of executed + tasks following an unclean server shutdown, e.g., + after a crash or system-level interference. If + unspecified, defaults to a reasonable default value + that is generated from the computer's hostname, as + reported by the operating system. In most scenarios, + there is no need to fine-tune this, except if + subsequent executions of the "same" server is achieved + in distinct environments, e.g., if the server + otherwise is running in a container. --host LISTEN_ADDRESS The IP address or hostname of the server on which it should listen for connections. For IPv6 listening, diff --git a/web/api/Makefile b/web/api/Makefile index 981bfaafe8..deccaccc4e 100644 --- a/web/api/Makefile +++ b/web/api/Makefile @@ -38,10 +38,11 @@ build: clean target_dirs ${THRIFT_DOCKER_IMAGE}:$(THRIFT_DOCKER_TAG) \ /bin/bash -c " \ thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/authentication.thrift && \ - thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/products.thrift && \ - thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/report_server.thrift && \ - thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/configuration.thrift && \ - thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/server_info.thrift" + thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/configuration.thrift && \ + thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/products.thrift && \ + thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/report_server.thrift && \ + thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/server_info.thrift && \ + thrift $(THRIFT_OPTS) $(TARGET_PY) $(TARGET_JS) /data/tasks.thrift" # Create tarball from the API JavaScript part which will be commited in the # repository and installed as a dependency. diff --git a/web/api/codechecker_api_shared.thrift b/web/api/codechecker_api_shared.thrift index 167f8ab40b..3e01ea5fbd 100644 --- a/web/api/codechecker_api_shared.thrift +++ b/web/api/codechecker_api_shared.thrift @@ -4,15 +4,24 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // ------------------------------------------------------------------------- +/** + * Helper enum for expressing a three-way boolean in a filter. + */ +enum Ternary { + BOTH = 0, // Indicates a query where both set and unset booleans are matched. + OFF = 1, // Indicates a query where the filter matches an UNSET boolean. + ON = 2, // Indicates a query where the filter matches a SET boolean. +} + enum ErrorCode { DATABASE, IOERROR, GENERAL, - AUTH_DENIED, // Authentication denied. We do not allow access to the service. - UNAUTHORIZED, // Authorization denied. User does not have right to perform an action. - API_MISMATCH, // The client attempted to query an API version that is not supported by the server. - SOURCE_FILE, // The client sent a source code which contains errors (e.g.: source code comment errors). - REPORT_FORMAT, // The client sent a report with wrong format (e.g. report annotation has bad type in a .plist) + AUTH_DENIED, // Authentication denied. We do not allow access to the service. + UNAUTHORIZED, // Authorization denied. User does not have right to perform an action. + API_MISMATCH, // The client attempted to query an API version that is not supported by the server. + SOURCE_FILE, // The client sent a source code which contains errors (e.g., source code comment errors). + REPORT_FORMAT, // The client sent a report with wrong format (e.g., report annotation has bad type in a .plist). } exception RequestFailed { @@ -30,7 +39,7 @@ exception RequestFailed { * PRODUCT: These permissions are configured per-product. * The extra data field looks like the following object: * { i64 productID } -*/ + */ enum Permission { SUPERUSER = 1, // scope: SYSTEM PERMISSION_VIEW = 2, // scope: SYSTEM @@ -42,8 +51,8 @@ enum Permission { } /** -* Status information about the database backend. -*/ + * Status information about the database backend. + */ enum DBStatus { OK, // Everything is ok with the database. MISSING, // The database is missing. @@ -54,3 +63,9 @@ enum DBStatus { SCHEMA_INIT_ERROR, // Failed to create initial database schema. SCHEMA_UPGRADE_FAILED // Failed to upgrade schema. } + +/** + * Common token type identifying a background task. + * (Main implementation for task management API is in tasks.thrift.) + */ +typedef string TaskToken; diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz deleted file mode 100644 index f6ea5b695655ef2b63e37cea0991918f208ec9fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65065 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 diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f0f744886d15b063aeab51bad9c04a4024354eed GIT binary patch literal 70399 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 literal 0 HcmV?d00001 diff --git a/web/api/js/codechecker-api-node/package.json b/web/api/js/codechecker-api-node/package.json index 1034752021..262f3560ae 100644 --- a/web/api/js/codechecker-api-node/package.json +++ b/web/api/js/codechecker-api-node/package.json @@ -1,6 +1,6 @@ { "name": "codechecker-api", - "version": "6.65.0", + "version": "6.66.0", "description": "Generated node.js compatible API stubs for CodeChecker server.", "main": "lib", "homepage": "https://github.com/Ericsson/codechecker", diff --git a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz index 7cec872ee045b4f927fbc49539c60b78a94cb76c..339556add9e6a571ff9e93ab2505bb8ef0403918 100644 GIT binary patch literal 69915 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%0A0h`z6?Assf6&HjwoeXKqJtHeFl>T z2Si(+XIRTZ{a6D13VA5oIwmPPc1;kVt9hfC2IrT_R-=@VBQ-H+hy6*O7&|4Gmo@io zkb|ht3H7g{XIF4txTwL8LX|}9JX~JPAOsAha+Dv+(sr%Sc(h}P*Ck%OAW7PL{^1ZB zBvHC$uD|x;=li6;ElDEw9v)gA#yF~Vjr|x!NH~Q`zzb#I`yEl<0q_qWMwux#L~j=Q zB0_(_Ly!B=JPoHXv(%f#@x)3>OX8{F3b>@y$I6pbH31|6)56W%D;m&?_8>`|cUcIO zzl{-*X@m-F?m-9%c4kR?+9Xqx=u$S)crT{2Ah^g5Ja81i#3PHtEpyUAkI4hMU$!$? zdvQw>>Ir91pMe!IgfQ2~B|@N_Qw2QSf=1VHTb+rHD9c{dp%HAuGmw-_+>WG$ZzPo+PbI<)N`OEu{mPcl76`2C10HA-@M*ogKhEon}eC? zg7jBmeAE{cT;lLv>nofT()4RH$v-=yV6ls!lyF<$ku9({SxRUkZb$OXSrukNY1B`$ofBak(G|R zgB*G6P79RJGc?pGkjrMPqw;WJ$YOr2U|{~`K+~Db2M5rBRPf6Ij0l)j(K7d>j@%)3 z7;R0e*Lt2i9cnxM$F24aA?C;T5DDzu)UO5J*?P#!+mhWvbZ6iYk^7_tbxt zDhaA{2*#Mh!^OJ^+NN|7qE&Vx+i8%&h|<<@s1qwyUIMB%7?rr4Bw5LAU5dU0*%%Q3 zJ`*P>g{R#^FFv72aa3SCxJRs6)K=JZRb^Ys=ImUqxh1*C5Ufq$EJac)cA6xS*_0-<6HmdA0B{6xOH@X!jC;riQvW=X7C@F(LsI ztk!X2PN&124TePc)Yl~!Z&Ye3E!LHl9A7k?1ySSjV>O1yr)w<+VqGa0iPGPe6T|Jeag6y1 zLY~I>@o;n{Zn%5^h{PtRr&?pq)e)G&+J?SC5Okm}|e{D0t}C*nbz3 zG3|&Hi_{sG?taw8{k5$Fg-}+n(I6oyl|F&SnvWafJZ2Mez<-o%z*QUyTT1mMLMJIx zu1MLCZjeQ> zu)al|A$cs}JZb(0JC%%PQw{d7WUewy=eD@t#9cAV1Ca5EHNU$6$wYZEjPjzWq9S~?RZ4luFF!i|cVQxVUF#aj0fBAm?thXX&DJWv(X-UvE%_l`w*#%* zt-P;h{7%N39vmEQ6*r8lV_!m^MZZFkWKPK1l>*t(6j6xlNq#o?`x3ZgOjFgdKb7;8 zr_X5^RnC=F4MQd<+)*_V@7^lyRiQ<%p-X@s3tImZ2t#{_YE0#W%~t@F_wcuYp`U+b z-d5Y#?t^*n^I2?gAi#b%qbMfF3`UA{a6*;~?)(Qcd!Ak07+qW(#0cn9GBzX!3f+V9 z@`CU*|>nTE>s1}pAq%loj77a?ZRNI=o+NOh!Ai|ru< z<6JZLjDV`ChR0rXdJJvEf|GJ|{s3k!fxy~9+Z~JU%t*F`B#eK<=mnN`LVr_CPl3WN z&1CnBwTqAUKFycpFits6U3s*t7$7ngrTPFPg#bVTn*0GpoAp{v?4qicDiq<$nxU5> zG}EFcd3vev?Z2~3G`_F^R1?@)luyk0e)Ri!l2XN>zMYp3mYu&eFbjZ(pGT&0w z2jRg2_)A3mG}EIs|0+|Di`3?^Qme>Ugift1RMga_Dpbgm17rcR>5#I)tMN%NvL1+q zv@r$CSv<%%X(>29@R>>4D{q$&&uFi~ohIRJj;fb_d6 zAs3i9TFyM2FqKl$vq|xZe(OxDbas|1V0H!JaNN0R zc-teL2_R6{5Z5N(kqT5n^xV>xh`FLXomp7o!{Nq-#6ZVa)N1_1p*~fULC0w3&d=bEIRU9~a^Q#xa$4e*X?-EFw68u`&x6Sd5S@5QEA1T(Q+s^*Fl-}YO3sKMWz z(P&Kv2n4O-2IPAQRX}1d3g-^?PUG8)1Ms6b=8w^|y}{g8t=%;IYiI^U7eiVL!{K1l zcNdP(*#|vKccjtZM0|N3#gSPc1nYB2l&o)=_|STsc*QOXo*Ku?+yOylaHF8abJi7vAXV>Eqsm#aPZUvg0xDHx7v3ZEUGs#$NH~>Zs zM*T;dy1pvB&~u}B_UK#Y+vj7{m(wZGoI&3P#_Hj{Gah%l` z=ltG^I*fyPXjvn1;>qU)?JwO#$gY0d5Xcr13u7Uj3~>MuWOcWtdPa46Umdx1FI;#w zr@nT)FJt?SEjFOXlz2ukCsF=(@S)BM$0hvb`Yii|CHm10)nE{|Ek^%Wa|1~ad?(a1 z#4X%@vW5>v@_5y>e2b=aQnUN7xsbv24|<47CoD7C<7)FgYlzNya%&`SG}=HPX;Zwv zkE}WyjNf@-1gZVyPj}qm)+|@t*CW^P&qnYn-Kc6gUU)Rax@KQx5QmkkL3JPkO1fv+ z*I-uy(P^}L$c!Ke3S}(_$WuM1?NOnLkpL(`M+pwcB;dYMW>dc`g-Qcs^w?&7BRwr17+9+4B7clL zaqbIcuB2-3M{ZH&&DYRDmK~!9$26pS{9MnQhEJ4LZ zEh&OhN4qkDktCmSOy*TrTKPIpEPqnTMn?I9vSv(b#Us1+fs{ZIkgz$9FjCXkDzcx` zrYFW-?oXWQ6Abybg>gC}>tl<}C^mGi%Fzvp8NXH%Wx~4<GMK6GhPZw)8tv_4ZT zz7D7imd$M^b`(6k*E>9(_ZgNqyNQ{~%#BiQ0V~Px&V?w+f5aX@49zt1<6i67sywR8 zyVT&Kr=$4usH9rHwaKS(VXm-5tx-d=3>BK4#Z66jrr*3&+1h?TJKps3Pg*mBKkNns z4mis(XQaj*fn=@#!e*Dxj9Gf~-IXuC|Vz4|n^A)AP&eXgpPOJ&b10 zJ~`SBPRrA+?`%j;-Ep+<8`;O_iAU!|m-W&~xX;)pzMY<)3kXq0uTM<;_myf4&A6*s z7SBB8wvVQX%OwuF`<>b4=bDuKS8cD&b>lm0Z^s{l6ux-gw_NT7pQ4+?zI9l2aB=2? zyOLG1XV9KOi%ibHH+GrgJx6UNt5w@GA7RHW=kle|o>vJH*En3`7hfPp3p(Pg*;1I6 zTF}56FlVl;yENqvn`uy<_uB=M3%6G(JVy7&=?{`+?G)Cz-6sm$79ljPL)H(E0;fgx zx3*!N9Pc{h9|U9@Fj_Y0kA6*_Saz2D*L455f-5`l%r$189CsfamRUR;FMleq-_)t= zZd^)(!)|tG{ApIc@TSVbU4FIN|9xGx(V82L;6B~ynirAIV>l_|iTwE4Y=f0!rK{Bo zM?lQ8|BJsT*Pgj=&Fe0sM5ddaePd26XUwlub7qQRF9bv(#d{F%DuZdNe0`lf@)xiX1F-`!T!g&3`Sml~rwX!E* zRDMf(e#dBXZL~1RMoOc%uNetpD@zJgfiev0m`zdETt0`pplqa?bDaiFw4)P&Jlnm@+`S->jse+1He zhAahul)bL{R>;~Nlo1R19sntmq0I}jyPmq?Sf8$BW%tF3Kl^L14Br`aFg>V$2)zy5 z`M+SoJk&NF@*wym>BZ`C$(`w@%2B?7S0C?>8nd@+>>xFGFuS_}XVvER=SR^l`rt60 z1^TN=dc3cTdyzlA4o@8AQDm~YJUpVLKK8gOx51ZpV!6FcChhHovJ$c%N59^{tJg9J z3POX7iA%$fJ3WVKV{P)ZKTz;X0kNo*R4f&{DtVMXpuUUZm|#vnvg{kJ3MvITN7xkx zhBXv55UQFOAsw*pdZd@{rQdm;hb`k4nA-HdD}Gc9qk>X%-buua;b=MZ0?N` zE>D4RWcKkhjeid#RV(SUUBc|d8#xl@a_ASBi-Zv#uS-3A5>`QizUbnAwWaC=2OVp}aB>+W9Q2aDcrZH}8QJ3%}%^ zB{T#dY>w*7-pIQGjhn7!|Gl8Pg3(Gvj&@!8PdqH(6ly^){ACnZ%}$`vE+yu(KDW)O z5TlY|cDjK*s%T3(hF3sA!MXJ#XX3^I`>1 z;5%630km`V~!Ez`UC^`uT%6!MDMzgItcVbvu<4$e>pMx6ZXd;cA)!ZF$&5Z z4WP5rBAg*~NBDyBL+5D9<)leMk1ktclNg3#(3P5pZD4FTejPi})Y2`!XC?D|O};+I zc5j3S8>&WAqo->IK}DWS-6Y`=!`G_`?N|L+puo00PdROr!{42#>bkDoW7pnm2ccn2 zSv`>9yj|^PfHCd<;8yiE6Z|U&aat>c^33J|uW_*|)^E!rVhi>RmaL^ug=Z{MklRKS z!#;C0IC|1D9!`&o7t9pVPg5?HtKc%j_ocN+NpI6(~-6C3Nt(8vnLcD?EeAIKrz1_L#@F20b)ET#Mn9W zM;2)n`A64|;tfj2f-cSmOA**ZK%ad@pAv&;xL(5|vh^n|%%`8O-;AE_2bIWv!_=gyQ4*+{N z0K2fK8Q`^HncAYxVik?Br(tzN0Q>F(*a6P>7|s_Y0tWMitB(GgMnUkoxaaRGT)Z6c z133*q9m2gU!@cw6MWVE0&netHj`VPER8d{HcN}Y4Lv74=9B&!(9mf#!4J>;6bD|7(;AKL2WTip1kO#mGsgNc@6MF{;%mlAr+`;r^E=m-GOw{xhN)Dpk&(*E z6tX1b@SHqYoJyKqf)?vUB?Ajkiz$o?!pl8kqr@y2niG$xG7ofJ&~*(#t+M1~L9OyN zI~x{NdAZ<}B_SzfQg3&c;@GgXKI_P$VZLKFXkY^abro!2)%xm)>8nZPrmv25eHB$y zr>~BNirUa9^YLi7r4?*61S=S*T76Wt4&DYf@*3K}MjqI}Krc8_y+BQOqh4(x1+ugm z@3{rcEg`aG2J6h$)rvU_%u-;MYS}C`iWtlv`A#r@6zeb_Ra6V}lQzs!qvQb0QeZa$ zFy9K8uS=D^=f;!K$jNvz8g+x^JLmg-;QImBZ-?uPX~zpUcES8_Pt5z(_#N1JK<$v& z91@!YQaxm(3i-`}@&#zvDKs?lzP-qH{wlk>yTn~sy`8MSZAIy(Il{?nN%^%Ysy^=z zhmdGzc$KbE?b%{}wVtduEAjAp@-?rnH6~bU55LG7#4i|I#P5x-kq9+aA- z4i`r&B~7dtOjeY)Umjb4zd%|yBdy0iC#}a$r1dx`kk;c`(s~@WL0XT)10b#IzLP3L z>b@5DBpSPU5{(O`*UQz8ljvI_@efP9^x8uAIQ43#@V~Cp1<_-C0OPoHN z=M_F%Qv2CF-*&Sg{ZYS@=kMY>0fVv|2F0ZG9HOl+^a2-KA1c<7-=2h4>`5F^SQ|Uv z#_JL10~W(4)ml1;QnZ=$c;!%1JVMLcXY{58JNXP8DA;}iR!|UsfnOO8hPFuMjV`S< zV+3o;(o~B!jl$+jFz>hUoHf;5I+-HjM{6oqy+F}`(poy18FVMN9b8gO8_md3ppnW- zhcD*CM;>e_(Bf9d3%K1LbGxR^A2YkA8@n+3vs%;*QQP5IntM})g^0jhoXOI?J|>WP zE0>5>Sx%OBEF_giTBTf^G#j3%ny>~R1|V@E!mv^?_qk&3xhdv;K{5C06?5O;8(z>9 zjELF?!HD1$(pyerA0rY~-Ml&?G4zA_h{O_&+z%WzazB88M$mwcO#_m=TgC=vJ7mjT-Ey~{?;8TVplz?n+D^XL`A=xLc*#pR@U zTG0Mrds+|>10V+C4=W}pae@`Uoz93C6Vr4)ylJBbnh!Sq{+{$+&GCpiL>5CuvMz=0 z>iS$uV6XrMwU2@xD0|h>5iKj!YdM~YAX)W<_S^f1d|=qmoK;KLs0*vdInRYPQHz7pj(4YxBaG5%n5+ROj7D5oPg%Av#7D5o}3jtMBw-AB|z3K`z@_jD(PHLaF z$X88!t}dA%I)Lz<1{$`qhh9oQD-+&H&vniv#I5vHfPk~b{R4uMbyNv0(0TWgCf$2^ zaq+IAa|(nJGw|hNF#{`5Kk~ZB%F96pBn7ckKvEFH$xZuyaude}*8CeS=p2qy%wDQyVea<%sY*$QMU%N-?e<1A^Ee65$qh%qlfA?@`O4w&LrdA5Nb zG$c{SiYWXnt1(7yI3hL0g_?wX`Yfpl#yVnBN1{pwRb~{2CMjj5!HoeJR)|)rWR^mY zGfPhAcZZ>0V3umn?+(MDiF<|o{B~!dp$Z`U2%I{@=mQ6@5BQJh zaS^hlrePSlsRMda)4ZZObs!qH;c*ciLe|k~ogjljqCwa(O=VnsV;?WBStOkB1R1q%)a}aX(Cg&ooP3A5nAJJ=r9M}ln3eP zhLPGJov>ONhHw;6QNgNGt)q^ajvB{KI%+c1byQSQosOD#xi+S(*4mJi#M?3?CGj96 z1>CrLt7;v5TuS0Mj7v#;h)V$-;h1#MM}m_R-|MMR-{hj zQv!%j0sYG*FMyCoYGwd&{je=GE}BS9l;L3 z4Ty0IVtm982^zT_5;Q6t5;UqmBxn@1L5zrW&@fHz+AUrP9oY$?)8Cc1C|YtTTfWF z@sP7@i4)5<_6jUpQp>W9{We&(u@5ZUQR2D7CD_J+6TvnPfM5emW{t^=+}a+{YQU%g zF6%3!_Wtx^BS!6C|1QC0-w7_u^D+M*fAR>Saj5q8{p6tuLL2D_4b@YD(2&Fp$K!oS z%zT67el#4GqgV;aM#GQ}Q#ADKkgOQ1Xc%c@l~-0iRvghr!x+8tbUvB=T4k$T)NPjO zIqG}GE9JtCk}pU@q5xr3zPwNqFl?HlpXoe6pN_q>25h6*2VS>Oi`Kyrmo5AHK8NgX>_9?(w$&;$LR3)=u%|9vDz|^Q{ zh$Fw)lO;=!JXp?K+bU8<`3AH-sE7xxBAOZdW@J4UZ1suOWl5%|!Z1LlLOzWBJH7Q) zx<(~z?Etvw`E(0Q0Ii|a79j4cF9W@^_x6BlPY!8jKw9X2o?9c!q^D)!>E~0&(Dd`E z>gM3pyE(9e06PG7NQb@1^He(KMZRpuP7{{_X_^*&1}ZmkAfo{k^puRQCB(!WQr1LO zP9Vl6be-}D7(W0>oq?pzIX|Y44h{SaTR~fWvy=iL*IkS$$Tj zF;X?V;)Jdio8@hq^JaM#A3IX$4wX8e%D>JQ^L0As#{y#cn9aYsV@vW14lSj3@N4WP zWl)n>AvagPDJm>LtkE8ysK%jnDL_3FBgm1vt6EJ z7x)#emKLBi(18yeU=QG}VGlB(Te{M0!6GSUO#qT&F3?t9U@ncNWqFJ9^<2oKDV4_y zSAVlh!}%JsZ8a+>208F6k7YyaR(T`faKPahhZ`gThT(?mE<7uj1tyji7x2|pr!B~0 zvBEiK`OKFWgy|@=DgX|IE&ypDbk#)xj4m*`{f4|i=>nw-P_j!XDRH{SI}W6-TF+jw zx-Vzw>*oH5PK0htY;7G83%Cym)JGvu0p#okg>eGK)uvD!&I_G z1LXr-KTtk=JpkJSY)@~25D+~;^Z<_Q2**hrkABgC-r-g36|?g$o2RSux!|6tb<#4P zb4IGc@v^iH>9jq!J!AEaJ@(2QNo;dl&Sk5ppNwG$FdYf~_TXre*!KDz?155RwT0UT9q52W0bfabBK~EuL z+~NyKWU3q$8+aW0O;reBqCq);)TU-_;5*RQO(WkX z_dL#g=?CSVk~`+}oUM`1a<)dkUCvgyb9&y^ysnyDq_~4)iw^Qm2E%>G_$rP0XWAK_ zfHW93NOD@5gw!-w=_`Ex3O|4xr(iMZy;iqp8Beje9e3NnJ|c6!1oeB8zF@mp8b)(ZNaG!&Kt37ux4@WhLu|2U4ekDty$c zuw*%_d&n)Vs&w3j^jz6ZJqhdHB~#6ivi>7Wf4^E!)|=JYw;bob%2v5*H#HdID)nDb zJP%9p1W>pWP*}U0C@Q+Bi_XOQVE^$zm|0ha`@ z;Za~i=UfuWA~I}{y9dry2Yl9de8yOY1`pM+3^Y5>-W|1mi~Az?&heNPh9Pks?AJZ& z>*Qke*Tlbz~+b+6(_Lj0qH?7lfG&?b0Q^yocWKq1luz^Syh$ySXA;7+ zqRC?M?aWLL!1Hm2TJEWCODRvsNms1= zR(n&cTpCmXPz9#XN(9Kslad=99GqnGl;sP#KA9&sLy9%UERtmjJq>*W+8BU6(8fR; z1NuE|^b2G$ki~!yJ4T3%A}+mkpu{jfN=kI*a+9A^ZkwBPoh~^_L@?()YM;pGaG&vG zmlk(-t8~3p8c*~pK@!t<@eBB6{1 z-jm6-0FKW%eZjrKH>%Q$k)oYv7xUXmohBBK-qS03t1^`ueWvr?2HTqmm>75r`#`vT zbGc}Ol(zjliPf};n^W87Qph2-Eh^RsTT@}RWimG|MXa?KTCmVo-;{Zd37aijO9X*^ zt`1a6H8KSCMh0BUrIB>`+qRMvQ=1vqHQd;1LxW~in>{ZaHqV~NqP3NLdEs!k`SQRD0t!#3 z3Qy;D5RyV`=$<^UJoN(32M~ICzJ-zmd7;;Nf;=$CfcCK0+JlrCpzLOiNrq;`E2ISA zIq>v)E7RZYjrHbiv6-)(OgJssW$G*~fYQMrveyO?fOLBT>4YddKq4FP$T8s&F;&fw zMBIN|Ub6{S0HzQyNKcuk3yNiHn0|bN~n-866~}1Hg0O zfCp000nGzQtvjSvqa z;7N||#B?@V{M2|#BX9%i4xk*wLqa?xpt-|Da}Wjz%pIV+j?kSH{it3#(4fc7Lq6x6 zI6I0o1!*@=e}BJi_C8)Ihkl~gGTz^0#uox^11j*bs6c=qIDiI# z#Q_!v;LgFq9bj>Q#Q~Vs9ZXXXeeDH{lgFi!s$MN_Em<5=ZYvgtRZ+^~NDpZ51Kieo z7N^!bN}e9z90ic6X$A~c;+YsCKC!GBB39Dzh)!PK(b0&P2LJ;AhUBAAXFJJ7pWGOx8J##Wt{EBSgl|pK!xtK!gE~ z>>ZAjvzqBwYltuo9>txNX$Y4&oY{v}J^NFMt zQ#hcA+^RgBs1lMnGKX!kg!P0V05UkS)8@p^Xw2|f6g%Ouc&OtsR8busi@oxe$*qg| zd7`o9d7^R)3ulVPRcDGytxR^wlA1Zb0RL$}3QLkz`eF zI35*Bu4`+IF|Z7cn~w}tQ2Ads*)*MH^Yn5vOD&nwudM7AxLnjx2F;Zx^j88vKX%4c znI1J5JqZ{JPKHhxJqdLLj4GcUGz|G@XnW+TsIv@sCNakw;^7hkHHm>h1*m2Z)eK7qBQ?WyCkjmMO{8lzX{qNow^IvO{^w$d1=xOOydpQ!yz zKRo(Y?4>o>8BITc&b@@rol~_)N>(woT(GIUjwNmT9Yf;r5J5rmm!~th3=CE>xl~NC z+U(g?R`SG>FXoB@Ykn1BJofX>u_sGbA9=7`MX*&9j1mCU2v8$rF=A3A8gaf@NE9hw zo#izX!9=yl-&W8n8qmQXC%4}xU(?^{nl948mAV2z{KJCyfID`DI}GdWDeM(Tu~!&A zJ>Dq{*9-eZm8=FP2(}4`oP}f`$FXA`dvGa0gyVq-k~(R;(^jaao>KO5Grzhh=%x}>QJoLac+?mU*^U@DWYH!EH3;f3_tYqC8>0Y=c2zRw%0Qq`Prz5?rj!kbs zS?>|6EY0Ri_ID+p%pL-`*F(5hNwd1KtQ;y~N~4@(%~y(emlL|F=nL4`>dwMbU{G9XL9ru--O6dBD=`9ZOf8j76Ex zkdFlbVG}@Dl`Bw#wxyzIA!&@?C~1tSz%g%(r~t4eV9DvT;<7w>QoPnVRY%yGW&5SY?oU{y6f=7Ct=ADOIMv-PjD#bypU{2+%PxHEN>!_OP?9Da#Y4nNN; z-N&-8`=K;>yoHQE{Kc6Z7dSVur<=}u|s z+&+TDPnB_mOAHDr0$*|a=Nb$%eH2xg?^qe}5m@;_{_;6oWY!jn7OT}7BvDvZCw2M{ zds9`n-1Q%@)PPd4uS$h%t1;cFvDRQ|Ajt@M*%Q>;NVV9NHRp^}^?voWTYC=uIm_;| zHT$u+yIZB}M>X}32Jl~`1&?h9Qf{*bNh8B<@z%)$1^NxxkDA$!e6G~^PN#CmzJ4lq zUQwO>$Zz9RZd41{kHCJ^LifV$^G`4d{TTTtqV1c@ca84a3!3h3G+iTDJ4gea?{t7Y zDh39+Lo?8^rV=q~+fZCeOK2pCm?bO57jl~FbDxKTA{KnZOS3|)+> zLE#AMU|45V4WeC!jw#mKAl}Ma8${e%8^mC(1#4|zb=W<#*Rtpv7n5y}Fq3UCax&Rc z$--*052|A&8`^t=5!ib{tvWWfO0xKt+;g+}^1wYBt+&^PU!t&Gttacv3PSlHln+d? z9hzc8p$d(iOtE25(1pgerr0oS!xS5aV2TA(Ea+JWre~Gx?+0lu3?nCNVHkn65W@LD z6Wbe2tVAYZO|0bJ(TDXJ#s#cUpl^W{s>%u#Cfo`Yj+p8d#x7QVK|^y-6oR=2l#Jt3G9-gf>D_*Z z`CXXTPdox^4=6GHRbu*L?TN?C+LH{owD!c~hSr{B z2-Y4@GLBElP^>*Acl&Mad6lk_u8=L}VDJHh4;XwpH~1u6>qtB&gHICaY6q&Q&ft^8 zZ5Vu#7z{pO@Bv-vz;vbZE&d>FK1t$a^GOo0`GCy_)Umx$$I8VL*2l_kAHC(rJTzeZ z0o@CXKUKz`WXz2}BYt3VGIBBgjQoNYHmWoJi~?KZ&nVo=_%jN)@n;l*@dp%|UMn%$ra7ZSgst5XQ1w#5u3Ws{Zp7rm+vECNMR@p@R9}`Y^gvlfGxGkmO75OEp^Nf4n+*M}kL^OWIUd`E zN;b3XqGp0+7u2WYQJ*ApZsFW?iq88RI!83r!4++>(&N>*M` zjV(7Bj#{wgqFTU~3$|Ra<$^62^y9+e)(39RO@?C^b1o_d=3Fr6f;kuXh#ns!u3L1A zm%*spwNY0cbFk|I(a=-7ZVk~OW>5{&;026n@PAjG$eb zHe0h+0@??&qJ>$}A9Aqo6e61VUIFaahKMG98zG{o7BDMzX;uUvzf&Mzy`deXt;lx@ z{X@mTRs^X2NKk$CvX^0g^^=EYS+K+Oz~ck5hxn|D_^ia|@mYz_eFuG)_^ia&ybPC=eCBBq~Y-JZ33M?tyKzYul({;K4Ks z)8;6l_B9y1$5%q-Yod#ua$=m5i* zN6E%59AFqbA7B_aJ-{%A0}MgEIzshIG6R?1?fZd-lj-zov02`xx#oqQnXm1v!1=Uc zf8*)MeoWKV?J|4FgM{7noV<3fy;+muevs-Glg-(DH)O!mo zS-TZ5mQJgkX&8@bTr6f_mH;Szcu>4^n?$~vs*DnN=_ob_;Nbx(_6sU962A0S4FgZs zkhHXlK0UjNu+sWt8DF(hOK*Ebma)20Bv|rrMVkQp9eZq2*`%onRf@TbQ0$h;F)n0s zjO#Nw#-n|35-J}zH6$wDNybRPp}TR9G42~C7;0Q_)}Yi>i*pzVV^M^+jH&vvz>rv^ z%`=v3;xSu{(+Jj+3{3+3GBDG%@WuCGdr zn($m-iCeC(q>$??sn7M5k&F%DC#H!fXWm4D*@ddyN{zXJ!*``Bc6jjam&Fz zD&$~K>T|G<{Jn8(6oaK&mq$K$GairewN&yOAf>dtY%!WB+Me&CsI$xtI}(|!#lwxg z6i0!hm*OY@FGYZw&FrD2>knH*OYb^)D=Lf7-9^#ZD4-Mpbb^~=HR4Pd;v9u;h_jGx zcNEqm&hh?)(wLkXM`Lhi1d6ubczPcl8nrl}YKO-0u->7u1VxXBjwpIO1b;}t|3``c zC7;NO`|f^`wTOS8c<48a<;xkG1?Nai04ELVL614ob+*H=)x}02Yz~-Y{O5F6IW!N06z`*)E%D|)?z7AEj+nzytUFm;IILt0*76N zP)9?LBh--#LLCkL0zw_tA=J?@*lP|OwGTLK;INMee;+!ZJsO6td^RfThPqKf;Ilzr zIC_1dhTAqg+c%#39{mT+`DJ>!ncETC%gy|QbfVjO@|7R%Mr05B`OoYjSDH8xzF6Lh zQp@cA<7EBq<1)R=ex|EdkA$Nw2h#%_~F z@zLY^J8z#dn(Ve{t}?oux}>d_#+tvc*cJ0f7{D=iU{q+9eL+ldIO&cqXgKFi5@82D z7-1`eAq4h84@T5N*fZOl9wq2GZh??8Y?*b;)QB=uqhaL6OpP^WDzB)Hnev8v!%XQz zrczYiaJLjy0_S)FrL`kUtDtZhm@9^-tQizi&N1&0E$x_kh?aI?jdH|f+f`J?@gz@F z(y@!Eq|;%g6bD%!S4zq`p594IyB=9eZo71sAsyT9(sG6FhK;&QOFMR#mUihbK{M7a z9(gG(twq!ztpzUUw8nG{W4++C3N(5=cBjYnn#*`{&*QYGUZgQ$?s<-cx#x94n0Ix; zZvrR$0_(mD*1Z64PM#DlIcAv?=*H?`UxE~@j`bBgulYz{Tr4aU6FZoxSV)RRS_T%j z6BP5WM5I8h*%l0f*uuyVlS&hZql}X>vjMm&0rlNeSEchfv`pZKN`PYxun>pDLUisy zgVscq15LhUAZZ_9;9g>2=3-*FSmR5ASIz!q=#x8gy81Q0{kB}p7aPkIeH1IdoGrd^ zT$opS@vz<^KvJreBSBtm84Oel6&l7jd#N6+sQ?7Q2SS}iN8txi4K(HTa<%z#pRL#F z6v>PWtYfZ9EQo4RfSmn=oP~YkX{j9nFDOS_LSEL<7$()QolPOH!3EHYY+=Q!h_-Osk>gbtZbP1zU|xCJe>QA5MqukV0k*(z0<7&RzgY`w zO%&$=LR#QX0mya^WIN|k*)}M1wy{`6xbYT#f0E=l9_9jEc} zg6D&qALLJV8p_D?)!x3JJTyq?38LLVeUM_!^vmIZ!A+MOFDa7W&#K`feS zEI!j0{sWb#HPvM3LEVNN(!i!1HpuFnNkM|Bib&zIgGT%Wz7qV?AcORGlAU6V02ltt zFC31@AXjCMj9j*`s!l2&L?VT~sj6F$K^l;GpOLwgIlAVV8a4<^h+@KMED8uFmIMO1 zus6x0vCDNM&ywbOq**@W*0MfpnVl`uGZzyrgAM@^e~|UKSNUxLm+t_VS8X1U6SM6s zivE6PQ3UYZSMXf5yicA~-F@8jaMelM{03+jcyz$P;A00qc7Q&6fIh*u4p>wGf{y_N z8G~AVzfZn&*1YKXRQmBY%SY-Ly0#59dOj5xQL3|o9mO}L#z?p1;>8vMKI}b5TAd!3 zO`gX{j_bG5TzNuw*J=QK`{D*rK{lRGU8u(MshafGt5151OF4l&dA>Tdl^0f<>^EvM zFAN|LKwgQdyvlvmf$1apaM>a&0zIAL69a0e}NX*(yg_-{+39e!v`M zeb2>F){k^YSyWGrqpTnA!@-x>Z`RL!cCifkXSx(Jq`p`sxmfP9>&M(>*H6G@7s%QE zV)Z?9)@2q%l~ro&vg?ny%kJ38WtYk$R-3)eo|P;v7uK#uWG*=Sf(FxD4W`n~cPj^9 z{jfH5{(de(2+le0jr(u=rXm}ryq^Gll)jn5V-0_SpmKD&ZdvWS7#z=+e{80K;0R3vA(#e04YW`L%`)=jiTS!q zM%S!aI@c!F1l=THVND1~(XhESA*@s-!Jq(2A2_>%vpXmR2dxl*&pX%^08n-dD4A)Y z?9Sny0VVat^7N;>EfZwkRc6QJckduCCZszsO$=$~)+fijgUp!y%fMccT?P`PlonJM}lll1{B_{}_ z_}HL;9~%@5)jqzMM1WFVvE=|0fmnJZV#)GEh??XT z+RU!WU=a>^28+ZggGJ~SGFT+F87xA7mj^VI4wstrBJ_8W^a5b@4q&xmC+rKwC=j+J z7VHtWD6HkR@NM#1;I!sEuZ3J|S#pao;E+{*ytqb+#~{%1_NO1vf~_XV0Cl0S>ViRb zoIEjH?26;X<$Ntqg|=X|706B!Fy*K4%M}D*j6oFu#uyl5fVPJVZGkQZx)|_Zhxm_i z#m37AN)&JAKotX33{>%vQ^g6Rio?*2Djw-nF{-GRDjpvKRXhf&_*f3xngdz9uVisF z+>R`chK^)$Gz793l!Ye8h|P_3vGMW&62^so0A&m+0Z_(383V*UWQYr-F_6Z913Sfm z{508lABf|->?dyOGpXw*Pp0W?AYFL!2lKxde}3`j|MhY5^DPq7>GBVq{2P*gW&a-z z{aE~+U-yQ-7yM!H^AC^kZ-eIS621Np$NA6q2lwmjKK(B*3CFQNj>6%K;gdhWza#ml z384RsU&Qf?;h(k3`1cw4{p#f@7JN59Vssgcd?acgepRpkI1Kaudr35u|BwI0f%gY5 z8v4E;L|*=!ABX7DVECBpKV4^EzAQJT&(+;O(0_k=@~6S+X1(~D&eJ7=FVn%7UxRDB z{|(L-_YY|2S)#q~h3~&Wh%tZq)y3e$#r5FztMlu@rw{L5UtJCUe(`B=dii>A^$Gpw z`t9j8zVhE6F8((78~c^te|2&G;jg$71gF*+nmmIQW(5B-c=&~`Ke<~j?kU~yf;J+% z&lPR5kD~4LJ(_uwuPOcYX1VxhI)8MSq_@Jj}A&N&bspR%*}D+~RgI zdvX1d{>9(KE|?2mU#7R4 ziHETN!~Wm5{h$6>sr9R)ux6$PF%ayEOmz^45@s2Cm9EpBH;4p#~F@5@;Mx%l|{1HKU83m0$R zpua=<{ONC}=kHEmzQfRoyy@n*(|4aWA(?;T^Qsq1@&uD_w@A9JzAb*o0t(6S zWjg2g7I*k65oFZpJwXyJ%?V_5droiv$kN|2kBxp#H36y#xrUZE|IkLKCC ziz~WHnvJJai8ijJDw~m&>rzLU)@2;yyvNmJjK@UULk4##`g@tOontmz{GMOltPqqy z`v%!c2D!D44Erj@HIXWiVw^uSc$h5L+3jXFS<=s_4R5|ke>;EuH$0qlbT2->A@2+0oe7v|izdrx%HQ!IpKU}~5aD9I9;q)EeQks1W@kaequ=i?WN`1SweMoJv z`G`^@v~S28p$apt5$X;hH-c{x;_F}KTYR?uW&ZNl`94pdP!~{|yHl(-=?nA>nh~>X zwa#aTz?c}}oC%%cDRKFjPS%qzlU16px5<1m`}I#WV<=Es6~6z~$Jb})Z_cF&aCQB6 z3<5mz^W8)RYd(5Z8YhUW}VhiKS%KJXUE6C%Hb8zRq! z{32?OJO}biq){@0$gk#2lQBeojWy72u80PzlZCVoO%(ZJ-aa%v~aUV);57kG~8OrRBx(_{(5a*g`jR^ZZxlzQCL}m#;rwTwY)C74YixT5MgfK3$&X z{o@n=?Cb`O(iOQBHbvUSd0A|LHaqiXXfu;FMw^tPNmlT@I5!M!QfCNmlR7(io79OR zwTVv&NkAcWHGP-eXY2pY=2J~fA^9*jh|A>?(Jljx1PIHXSo@k2i z^vVm!qU04;3r~NC4jjtfVZej0f36WAPNxKty+Zr7VP0Qci$QvSdUkmsw&XYG|NH!! zY|0I1>V-1v@a~H1)OS~0v(q zUB7;BF(>lU+B_h2Snd5+SQ;stKB=Sz$ew| zLRb;W6;Q46>f-DiUEz!Q=Ir9rhieP5CAB~wAbt1CB^GIoF0)iI7GkB<+%lW-mYb$y zExHlOf{Qjy6J11_$zrRtX$B$oagr??JAdD0%hj5Df~U8O`4;C(;L5nmH>s`6*_%Bp zHGq5ZvVwAZz`48k@t!2`p~6#|&6%A=CO%pWY3)$(HFAJs0siGX@GsAI1%op=SyHx+ z*|?PKk&OeE%7Y~7Boc455BU>iUEI*=aDGYw=N9VULxU$Xi z7JbQCnrl}Ucy5-8gdh2@kCXK``S;mkj&wJU`Myn7FK1}<&e1#+A%ORbb^2*JV;7L* zy8VtO_DdweZoh2<5cAe?gvgqwT%hga*W?CD;e@n>{EFJda@0v1Adi!LlQu^kIQaq5 zyux6z$Fvp~M_7cbh+b%8&K@ar%k(J#8y|Tt6h5u@CJ9OdEYBC)LBsi=wb2lg<)8;7 zY$1+Ulc0|h%p0}xA!U(%(4R4GWjG4k7pSYCt~OCuRrqUET=AA?ues*MtdskYVlJ8| z-{)Lfniq7`K3Q>RO`8;V?K0cUTzXB*7CIg@?l`Ssi@wtuT)MApUbg+03u}@3VS`A0 zxwu=nKHI+RWA$mt!e@&beKua+@uTtbF5T9)DcioQ#R~%vHyQx76304U!AQMHRHUfW_ z)JAn{$?WZ}v7n9YL>nQ})S!ppxnT_$P$8j%1b(Fsf+i4ulMA@XXQXWdQ5m&;etr8; z82}td3pl8ID_{=59Dq3ha{%Vp7IR1f6<`Oz4uBm-*zt1nHK(ax|48Q+8L!uq<@#MV zPn8R23-0fd&ZqKi^i#TdzxrC^ZR4Ibn>9>0QPY<33$$tSZ}~QAoZ?MW;cZh$zb(C7 z8zJQp@;Y_M*aM<<$`DGoNojdR*-g@PKDy2>JAmN8B}Sdid*c<;pvh!?JVuLy?OQCC z`(rdXY^A}EH^A+9ln}Ale_xV4@^DMQ!wq!4UFdu=nl)wq=X;uUAOkCD?fJq>I)z=Z z9nGn|sG!(RBZvvHc^>k{<2`xS>C$wZ=ozQ^a=_9}Maj|+Nc4z-6odctBfUg0*2KXA_YkJDp z>=8{715E)m#SUo-1j>M<0FvV1Ns1P=_~@{-_!zW!(BdCKi!Y{&bn+41(r<$t8Pb)a zulS(0;K)H*>&gO@db;l1%Y*kz`5wR?0DI6Uf8D*}I)a1aI>JNaI-&#PI@;k5gfKs7 z|CHpwh@TM_ zJ~gEDjv2Y+uo$@!Fmk}iJw~`T*}>EGB%3vNt$AS1sVh#g{Y-B+`Qe9hn&Fhfgd8BS zYq#W(l$ATdCSEQ$Gx_b(Rq=X$k-fi+FjH#R`^i-_?R|#ci$|446o-U8C^bwSBOtYU zPinn8zq+o_(3Cm(`5(pypWUQ+ox$_L%_{q6I#j#(E?cclJ(Q*8O%~>25`Fxy=`&i1 zxl#GQn{<}mN9ZL=6p_f2fRuNVRK^Cr9fykQHUSK6JcKdtYvPdjq?w7#!@+ELx7 z^#k-CdiGqCZ|VolzbWgs@;&`v7vB?zzFj0Ak<|kO$0s2ZTh&(-<;riq2CE1^v4ps? z{OJspn5FY)T647S=8Fqeu=GQ&35sV8^9nyI4|-*ric`HI>!ct7Yw}4ht&O4YK88xQ zWy2WpVN@P38Oi7oMpD{+?FUj6*2Xc|W*kMWWy2Ut!zi9E8O6BsQRJJP?Fh1h+USMu zqsMA28@Ys!9DA~4)RN9d4VbWAF=40k$?VrZ(`A0_Et(D4ZF4$|(yZKv3~iPv_b6L! zZg11o>TWYr@4uns>lREHeXno^!xalxGyZ1n3I6Ib5=>OAbd6h#*?Z)kat{&tt$OFx z8sv5u2L-6F-cw(-?g?Tp_W!uo8s8GO><-0OOqK+D<1JRB=ockZ4dKf4ecwsn4|ILs zuhsX1V3%v5YOoZRAOM!&SXhE0bTUu{Ope>?7POq>DZ{k1qk){>b^!Q52fuO&Ai~<( zApjS%_9`O9oLq{8khXRz;zjM?brS2O2PFz!jGC&K5uX(HCGm#U?J|47t#Ves zasS0p)MNno?|cOK4>knS!PR^Wo8Q2YJJk;62D^|v*Fk__mlOdBQ5Pykz0Ly2WrgD^TEvz{8vJ$zaUaKjQ5j=2F%}BWByPr z70e&fU&3L7&}Lo$={lijQJ{20K9hH&PfRPVq4iMjyYuB5wP{lz0Pmnz!MK(2Fk;z* z9v5%8y?8K!^=jb|2241FAr%gx?;spP-NQ5V>x4tH4^{yzAV#Pq25WjS$bQkM>wtdYD6*?L;>S$inv6p#Kmo9R>&bFJZpmiKLE9f-;B%RKB4vMnY zA>mS4$7PeNbPlTq6?2RVCxzYD3@Yq2pG-=d_1rR4!iNE&!m4<24&bCD71E#F7-)Kj z!6NuQH_V0O=Yt>lAJLGTr%dIUsjAXYnCp48)|0yD=u4|Q>*`G`DOrv-e^k^lFkV!! zo#h`@;O0nO(5LKOs`V-gLoseN6vc`hhoo4M%g`8JK5btkfA}qyy{xv_i)CK6=_8Xwwx$% ziSdXM&6tC#sY|Rg+M5^;eYDhcJfq#F_K3mK0+yB?Sy~Jlg4ycw!G+|fL)OoF<*V zp`5UO4S`8{{{)?wNe^eD9=VIV z4r!|5EE~lJ(H@Hr%atk8d-0Xo4LlN=))Z|_in9P>k$^CTSR~+g+om;p;20maG9FZr zAM{8>?J)n@;jfW+d-;|j<=3jH# z5S+rXE@9j0yilRLVWaNmMIAeu7j-hkWDYYqFI4Da)Xe^uInUv|s7n_!dx?^1;v}vY zCz+EhQP941z=2e_@HvuZp$1YFu0aM;75f``F@neoW8}s7(E~5yga%&3qc#IC;>0oV zB2G#IFX9mdUVzCJOs*Ct*EiV=EgZAa^?iE(111hGzQ;f7-!hnGY-~^?=@# zr{A3q{&@P~)gRG|XCw;w_iX)bkWOyD(GK9+UZ(5c({zriN(VFa@eD38>=0=@CwnlH7&2keUd1!D?o1CRMdBcEZ`lv5PNu66kc}xXu?l3HQern~sv=gPnwjedAx5^O&S-BIi;v!toAIp4TOHT`6 z@4YRi>Non$*0}Jh&cC35LZE+dJluT9NZ;Q1g}0BG**!cU*Te|?u3Pmf?cQe|l;zzh z%MAEE?b21k+gDeK_LAHB`XRlH^%w6!=o8|s<&vnv{Y2oBUrA_+y z0Nc?2DA~ZTG^Nq`Sa&$n{%`_fl|Y!RkO>eX%S2%RLu6~xAgU6-yIhE%%kM&$=UD2g z$)Jd(xxGO#FZNV9Jfb4f2<;Ej+uW2@BEs#}Rb0+dE2pKMUEgtOCx3S$q^*72aWTgb zBU0G0ucWY(>$l61NmJ8#NzFowrtXI&O&w24lEkYunZb){N$H{nN$EskP^FJtl_ux# zTyAFR6;h?%=aav&K_5*z_V;4Bjd31B5~}#3no5oA$Pc7v4RZ2(pD$W>7~QT9d= zt!^!uz1=ky!1d0+by2#StY-OKv9_9_Le?<5_@$fSlzfKTHV~Cj+vnG}50wEx0)T`r znW|8^YbieNb}}D)N%N?*DIV%OTn^!S^wPXWM#C>rovZxq1U-CB*ZLdLM>u(58;U2# z(A7uc@gO{0GmXp>0eMPX9}T7ZPjn#%7jGx4ZwP)3PUmQ9=l3TwQhaeopBQ|@kGw#` zlMcw~o8*bx#e6+MADe%UHGV)IoyC0i3lAT1ahx(Kx=~{SxC}-ZQE4G!I^!tm!AFy~w*MZ82-!T=X|@?BIJg zpRNiO(stq^>HWj{7k;liOnAJ^mj)1js%_iO>tmCuwC(f7 z>1wsO%_e!KfBdCUKTtJmIn0wb)bw=`a70sly5MijoBVC^Bj2ch4JN#9I*o7>GfvXv zeaqMBgoF@~x1k%nw!-JFh$@xWhPWorE7jJ=e;USMi~PY-D}=|e(hSY)Cw?0mQkvpd zeND0N;z8}zrXf+8{}nd}YPZepD;+-2zK%WaRhy;N;g2JB4$;Wz9HNna4$-Lg9HP&$F(8L-f*}#iscKi9;u=biadNBL;o0%$&dRDe^Yxhp6 zaC_0ua;+s{x!zEOKr4E4!!T%6-YpIq73?ImSKCJIKZIP|Juo__@TBh`S6ccdeCbOl znto-ZG}?w~@lM8*ZE|ilbbSOv_sL8vEFHZ-B!8n+L*pqNxK81qlOZ>4EY>j;#X1}; z--cR%aR$bjZiUUy!r~i?N;&1uRGex! zl0)K>*H(-H&_ocOu1vtAh*7W3My0GZDb(epNs*3nQnea=UM43XO1Rf5W~Fkug=*<~ zkTkErHCU2Y;I7c&Rqm$DVV7+^XOnNp*(3=E$GlBkX)-QcYZb~=i*?Mfu@*q_M~ve0 zS>S$_?JdbdFD`u9e3hX+xsP^(S*jIzzxt|b1fq^R^tAXj%jQzPtJ&tBpKH5Hk*vv> z?cStEOw%>$6Ou+(N+2t_oK8>I;*~$;pQso4k=yUm`PcO~snjH!t@KlMlEyouIHmG zz4@o|TN!S-U_Rc36MmJd+$Hi=3pL1WqakaM+Wz5%_q2`mu66WUAv||1R-p{+`$A4Ws9Sn;+y)7Lni$Lsfa_ z2Yyz0H+#d8=H8swQ{mtIJ^kek$NS*keDh;6^SnLsv=(3RGaV*W9cyNdzqRqyQ7|SS zpfQ=5cb;~bd)Dh9_pEY+HD;)s&aci(y5^~3<+nMxIxp|iZN@~|cAA!R%vME9J9e0s zcK^zRYjXR}xU6ILDyfF8zBR64i%Fg{OOCS~6?IH~Eh^YRU>RxwI2=N4BlZ3#n}!)weM($qm z4u@kjk##mlT2M!eJ}SbI!Lg^P16X_miLs>3ylt5k*C7zirHeh7G3J_7Gg zKceoJZ|$6@Q3tFn5Kp|p5RCU7N5*;H^T7}DmxyYV3NmNRCNX-fM*EM!N}e=$3u~?Y zTUg1e;k69~1fSbp`P{ygCHN(#I-C05-e=tT^?vfx-Nh8u!#yZbMP;6o z4VpLk3E<;s!N)7~Eza{StZ0}T!;UX08eI|Oa)*k$V-Hf92;j(TD+3_>S-Az=!bwX? z?ExrqVv?iVMQr55pmjYk{ zk;u*25N5Sdf_UA539C*GQ!Rrg($i`?6xUZ4y$JB+c=Gnt`qHv_qhRrq`XZe>)3%d| zhfVyZ-NVm!)HgssyYxCVu`qo8yPC<121v90kt~@l4Ul9mI~lecWtky!-?5Q^fO?34 zs!s0Nr;~LIcr5ow5B4G;6ua zLsm$ETr5d|QT~~&KFzcL*`!i!N=r9$sn*-Y{R5H*^L75nsycCu^bXk@fR|c4q;r~4 zRc_DiES=0Z4 zYguWOd^RP?W{hcNis@#QDIh_4eGbSdU-`{ufn1~7WIPX~R5|iQsLN?1ze$-3;t)@D zGKlwRvq8z@qkZmT{eQ$NyTV=daXGB3d3~Z9EHq2zhyHyk`j@fMV z8D|heV$=-NuuMKl7=b_evv^yYuc`o(+D8Xi5opqF-tIz!3zOR<_FPi(kPb}?z0uc% zL0+;)iwnglM|oF@>&Gl$&5&VHNYhyt&;>n+d^i-(~`K{8pwy zFg*Oj$`n9dGZTW4&4j>nnh8Oq&jeIa?M#UG@Ss6$BE$!9Y+21jkd&sHX%G(iG)SDL zLFg5xK~g&nLf`h7LDnPnTZR5^zg67wnUD>>7e{oV8=?yfh%T&0bm3^5V zwD8S}R6@;Xi#P%c-4Iw*Kwx1#0*kCq^VB+~ez|DJXlc~OYSUPqy6?vEQsjo0q5@uu z>hV&%KX^$QFLgS^J4Q_7Hj+mgh`T*Xby(B*pO4vMuZ81OWm2=dGgNIe7uJi6X3AB} zA$9BqspA5qj_V$=IzRnd`~Dmp5tqN93Mbi7B@y*6a(d>-#u8})}R=tpMX zghx@8{5JHv3MYxj>4e|PCL=p))U3)e*ZIb7I^VdU^Ns6uzHzSeeVk;=s?5do$;zF* ztmE3++OZ1f2dx>$8WOhtkSlUyCq>R578JR0ts>_SeS4l=^h)_?sIFanG>FXDS!4!r zF@I`KZ;JheToRn-1$oRvzQn46Q^ndS$t%WcG$a+vGi>?8fX5NAN1LP%qh;;$deMTF z9kR{q)lK?eGW5Rt&`Sw@%LYH>gD)O#KK!BU@P}Ovzf+DusjT(^f~`LQP8eAB1tR?m z(E<1Zd=KK!xk@W-7De|t%e$SsXAD4}K!bhDwS~=!9ETRQFnzoWhhFiX6EoCtozu0m7DrgS)k3X;pPnZ#Q7h=_lDe4|>`G2L3do{l^qPZyJpbk4 zt`dm8Jlb`VFkd;eSU2jDYB;w)!9y;t58xJ{U9GruECp|%7HB?!UD*S#8}^`!(+GHK zjmVI*LbY>md-jLRW-^s`lIn8^a*K6~hY+l`I)$Jx-4pleuGCvP+e0bezVv+a$?nS4 zc-?R{x*^y0YiQ*)IMU#}Bdr1_0jgVctwpF-fj+CoTL@9D>e7iN@lRTj=5x!Hgz}<} zV0zJ}>OzETb!Z^cA2tAyhHAFjs<-V{Sw(u^ngHaoy7u906v~}j0cX7VG&~Q5OyAga z2#UcPMjmdoeQi&^IPcE}w}ZC|pe-Z-y{*Ew zr2w`zAtbBz7EtYRW$5oeOl)>EQQ$XA4OHi7z9+!;79edOA+5YuJ?#=Jdi#nM+w;%F zL~F%qOlWzlo|sNnhmDho!3E9U0*(b7n=T3Y{~MaJciAnrr@TtnldSm(mxhB0WP^Db z>P_c4hU2X7G+QaZZ)b~@__f%q^O2h(N#YKaO*!=CmX?EP+E$T@o;FrWFrT+p#|}(y zBUi7qyVZTFgncSMbo*2z{ZpZeYCqN3_EWJQY2Rvm0J(Z=mX}Aw2&W1fj+>W z^pQU)(~+Kb9}#Uwu*jMB-FOx`fO<=Z@2&j39j%da6^vVd`&oup>&8O~!Y9<&@@>>3rzE|+0?8W>_{^l}n6xW++ z6eE9w+(UL#yie&d*D0jeYh%D3q3sa^0-WWMJLzVJ<)O>hNB@b$+OZJg;GhR1Y=?N4 zgx^6AN3=c3Or?Bx1T(NSyI^SqVlpr?nZ3r5+52l}PMQW4+SWt~F6K&+;KHut2rlf> zRVI^d`%23dIvX_VEG_NWTUy#l*X2^zTHR$O3tbMIae%D6D+9>NyL1~1Slb@wg;dfj zswchjLZBrdbEGNB#cS4)->3Iq&@>+`?vO@{=Hm)~)4^n!k0>hjfL@VPQS-qcPd~i+ zBYNWu^ZJ9|v-P(@I=THuTaIgcnXZ3N(>bat9n7-TdXO92(2ldpuPm`l`{Tu>sD+d| zzZ;}KC$}V6AAuM&>GCIYO=*y=(VJ$o!3M25{LtW@JcYjgax=#+`KuRC%Juz~99h7B zi1)E8Us6cs8 zO^#b3<>cM}ph>x;9r9WrodX;VApLfet)cWVmK+_~bABu5BUn_o3{+E@M@A@;N}{Kl zIb(#~q=TG0yNrM5OE8T|RZCyVsMi zJknK7lDuWs!<7`WO5Q3`AClz#JY=5oc*Mcx50pw+%Mk~G+xBsldm zi^!*%nH6zjJ#r(~qXMxW)f4Mc;xMyNf3%skV{Sb>tRv}x+u4mn7Xw*kX*gGIc{tnR zct~Ys%tbVkPaQgM%Jf7X9TCU`v3SzedB@=Ge?$}O&~Or_<|$}M0w zSxKoqpnx8EU=W`$`x6}Wx=O2Ib9j}mOB4pC*TezSYvQ5$O|H@=YE2yKY7MHVPOXU} zTeSvv=*48pRGK)dQ)y81+tz46G1!4(VBBJ!n$IJ1NYzh@S;u~mVlLo~EKS>H;!?r_ z^19Ln%U7-m=;E5naBeIP+rrN^gSl~B)f6}%>Zl>&)nc=}P0!ddc2-stIfXtRWo}SO zSGUXTA$PoQDT_Y&h}?7{>bFj$&uWqy%=uBJ+CnD)X7(OtLOT7%Bf-`Y1h;E)1KcG6 zIX6MhhMo5~4+Nk9&9aZUlw-g(F6-D!1J-Ov`b$#6n)erF9mB4r8XP+=)d2d!(drAQ zc`n9Pwt6$kW}9Vtj^>Ju*A-5azl{f#d@O*$6^hpcUEcX zAw8mq+9EFK0utyw5_o_5vGPzwK7Ic6?-Il7cVc)QJs;fsAb)bNIIAP6I;%t2tdsn_ z4mnZpNuxrzePkWi6d*&PdhRCU@H(hIQ+16z$Er8^x$8mwf2zA8$05wGZ-{_L@(&QT+Im4fLBG z=r;lbn$ApN`=tHBr=%cPY>;HA(rWlhR@ETcWth80u(}Q25#O<+!wBs_KgeIg-J6wS z*uP24I;+lJ!D1y(s@`GQmlcyfsC-bGkdaR&5;M?*;U)7N>>o$x^L(} zF66(pp3IA?Gs<_HQSwM@O)H&M;~|?>V{v+iC}%&f#zWV6H69kfA!}Osm)`Cpg6iK( z{yyiks?v3q1x-so-Xf8)SpJf(;h=-_>6RhVazHeHhr|((msG|>vvBCbExEp87X&`y z?@;>$1j3PfnzPxpH;(4D&6Q{TW3)PGq19sfK^SQ-jVfaIDT^j`3nsO86vw3;gCfPs za%d|rtTx$i)MQ@R4UN;)Wsk*4LAAv}gBH_*06PJ8qJ>Y7aBYk`=>e1Os8XB?ZbXdY zw(=&xdcDVbbs2;C#ON?xFyN3K;}9y2o_2Qr!&WM*#>wXanyP7kFumu;!Va>gcd)2FCTZ8Pk_;JR6yOZ|_&s_|kG%caJeJ`T7w zeH=1t`q+1|rjH8=(#C$BHGQ1yjWvBdDjzLr#Dd|FjaV?`2Z4;oRwE`xEf@xcQ5)C! zv!Ia+hT%Sp92>o0n7c)obht)k?VN1JqtA2*m{S6(qJf)Dsbhk1?R=&alsi6*N8tEy zblBej`t|L`Iv=)L-qFKbtHm`twpxqRshMoAvGxR^1zWM(Nb)1N&*fmHWj>EB+RxpX|1HsnTzii&5K9G9rLDj{$Lh zI;%RQgU>`osXD1EQ0eTjKPXZxB}m{b366svjIc$@S{0L^Yk;oNT-Pvp)evE7?My?o z7TqO6(==r1+awD@g{r{BqBXfr$~meFw6vqbKuf!HmkB=G?$UCOTsJN4*j-xMo#2;6 zn;XwB)le9M(1Pxl>e-6*m+Aq<@wlB~tl~z~fvbdF6YBd6m?i%Nh82 zQdr^gcvAa#^Dm0~tt2-^#p}-SEKh6-YTMyC9F@dFFsbW?l-o%JSxDPQD=!TI@B`)w z?}thz$jl)TB&yBT72Xd@6852Xb(OG>eK;b@Y(E<=^;FHkS+2xZWwI2FC^^nF@FV_h z@N1>ux=9`C)v&p=%4wOwzeo)g*zQ-C5G-bNeY_UqGr)xF& zVeWsIZV_;wi5Ppumx{Kh>I78YA}v)SX;PUUhF_RX8#-r+W+($Rs69Ycg^!}4>L?DB z1aQ3J#i0dV@W{g#vb{>ecog1iEhKgn;^Q$sT?mJ}ssj>j{{s(n#(Do73PRmMUrSmSt9KguTO3=a5lZ?Y-%(yGLdQ&upnj%SP-}= zV2Q4Pp^9o1uu%hzR_~0a9*uUP9<^)!W^20U8-?7fA{aY;qcA9ZqjBvw3d6lolJPjH zpFa$D`}uGCm)V7t6TPt7#-^?+MYy`s!HW4{;pZBwL1FBu28D4aLrrCo^^C;DwAA}SgVg(^ zASi6dtFT!fOT&_@evsS-XpYfsjzE;F)${F_pL(d3Dz3MuQ$HMRH$_v|Zj6O(N@RHy9V2_@_-VL z7A5|5lTFbkGfx}i#ILNxhpnRPlxoUgp#G5I-GK4{<>}J|a1G#^Cb)(RKLfH+=alW% zu9SAfHcB0iTf9;SZ1YFE&0lV2&3E`Edib1&k*u@zj1xke<*dEa7|DA$DTH6df1x)u zKAw?RB)6R1WYtRjV7JISc%KGvd?R3uI;_Fx8gNN`X+g?0DoXmFZmq`QLK2-dJ{J*B zIS{&~9Eb`j2g3T41ChTsJ{SD_HpT5Cf-D8cqJ89;%%FJC4aVAG85eU@HArDc9fK5h zp-##EVcSz&%8@d~WgYv8%er(k-y3W@iOM);qeCSfJBUi|_DD_QN)o!el2n|eiG4ew zpm2OaC$rhj3V$Gu06tDKbR+-o3ssGNhASkx@Ze8_A^!Qy z)8)g{yKK42V{D#&x_*?C4U6e6qd%fu_yL6Zx0Re3wtJYtUd(=4>%5 zX|+1rNfB}CT(N{c$$5#gSCr1~`_Jy2`MnL{pO<*flj__x}Ib+wCI$dc3+xk15Y2An9cPBEic<=Sqzir{p0!I%>-fZ zlj?avD^Gu>x0^g1ns&6Lek3z0Yu9HGubs?weD-Ov^5Rmce2G5i=u7f6fF+}IMsC&C z9GXAnDfNuHT2IzUQOR!6_bgs>#Hv(jYKT_pEJl+ko!YG7&Fw8(Eq9w{tC)@`dCeO- z{nddbji)WP7)u0gi2S2}xNJeo&D<6uFY((_TZBv#NHl@Qq-oR5azZm3G!=czbhViw z0lUE{+(Q1LKAqi98;U=irt|4EU8lEs`|@eKS+SQsleQF6N#TO&6PHR%I;XEEUrS{s zE;%lg%y|XsW%*m>!Wh$;F;qJ^DJ;wKS*r<&;)<>oq-<^$m$aJU!nmxx5=~|Eg{1vQ ztK$M!fJBYqqNA)qGv-72j4oX~>(cL7x^SwcT8UbDw?N8sqm@Gi)OwlS<85h~-eo^i zO9@rFuYV>B<3)QFqLODx#EOokxcnO!zV44YzcZr zm7Pzes_>L4-?t2F01yAuyi*ugea^wc`5blb$7IGofw~&XMhmSVyszScVm}-OcEvI& zUH|jui=Qm5+fwLZ#yBNQv7Ga(6iDXfUM}~_L~r^dOMjYpA%}j1YuSTDM5<4jaK3(OYZC;t*W(@%P`}0CrPGd$l6|- zEv2@}Ro0+!Uu|uPPDiGkwJv9zR`bQ_YPGn{CTpg z-voplF>V27X*$t{Cp_n-a>F zjF-gWnum-|Ag`#9XhELF_S4zqjdX=#zF1>gsfi|0zwL}FZA4_4E^~iEZYz?mxd@{B zY;IwsxS#yA+2UoWe)2^A<lT;Y_mPgqn}V#}b_(PeB=UkM;nz{(bwj8-S@49#Gp zzDpWFXydx{?UUA3+%TRw@yo1foPQu>mBpZ$_h0NT^oli-+6kKM#kBO#(jvvJm}_L8 z=Gp&jN=g+rUeR;=c~{(uMQzFe*03hUZCM!J9?*01wF{wB*s{4$(s%98#l6TZQ3deh)!E?tdG1%|K`#pI-&ic&ch?|`+l-U zl5TZ6|MhW^l_)Sio!!1ZyZMZ0R11^aeqprDu!m5kxqD!>Et<}IKQKyMOIhTX6MmAO zhdDp#_akX-)|B7UO*N&YIPI$Z%TZIGH@8MLP<-s^&8l9K3Gy3y-CcHzry)W9yyImL zIF5x1EUYNs2D(LIZ0d)z1hAy@JR)=2c;6@iD${IbSC3hpCyDVbl=;PGoqIh`v-#H& zjov&dgbN9p0OE(s3ePN;cO^|+%E(l^N0l=&%XCtuofn)K@I?Ba&8Jq}-)1hEleSP5 zG7Gy;iGnb<-#Ya{r~6*|b8<`eJPUIQ(KxS(rKhRW2-Z4F%amHS$-B^qH}o12Q!syD zdAagM^DtSae9^EUZajOwFS>th%3Wo5ch_YWhZ2p?5+|F#aub|a;xd)HQm)SPMUt*A zQ=~Mk(E2=iV!YgS$ieGtN^ja&TPs2*&KOdZ<0q7h-6IR(0+9#k*oxVD%X(1ZRk+Gl zZxCeIEO9Iabt_aacI4|*;;1Y;D7)Dn2xn>Z#xxL?WR2N4Y&Kc!qzww&-OK^QK#5z- zY4-K<&&Ia5;d++QLUj9oj}~q%52#59A^%p8xq+aUoT40;K|%9QR&nY%E3a+4>Y? zX#(NduSeWs(t9M>?P3f`OIeE@?F3wf_$+t4sg-~RNN&*lU;cV5Jp{LNje`alIyUPg z<a9T0(LkofT!YO6IXB@QyI*PSfz zmsx|7j^e9Vptg4PiEBR(!wZE=hp!g%vm0zFI8L=~xpTX4YW1El#+LI`iYHI_pN_59 zb0=Ehc?2n^x_#z&LNi_ChH6OvhJYa|J3OAF7>_6idar|QVXYKNzrMRA} zbcq9=rs?%$^?h~oBmQ%P^`9hB{(mothVuXUn)jli@B2aIh3GjyisIoP2GL`#|8$*w z`Lf)UK38`ilqvYG?zp~r(=|4bT!2Z9* z{_lIk!v2p$0_^{f@XzzYK^`ygU#)Beb)o`o5KjPc^ltyJ`RvvEe-cKqr|$njkc0{B z|BvzSPfz|dINhulU(fZ)2}WDA1YxLvw z&)XE&ivB)*c$iVoVt!evJx_7GxLwR%Tz{m0@i(yx=7QIk>Fowh@E<8Fz^HNZ*k+SW z*_}_H&R;!wLVv7&t=ONl#n-Q>*QCG;0=xcSd(Ya`NRH%u_OGa5V#0U=j9q?#R!XU4Fux`- z0g{F;BXd2eQovHYgv??k;tmH%sKCN;C=xM;X-1T zrUyNjj_1rlAm4$#Pe4jqh$U>hq_*bMrta~sou98U3Y4X}F}kvOhFaW0-^gj;lhu6w zB(?}wK6eGh)qiwXvk5w^ma~0=Kr9MTCDFxlde5d{5$xjO7sg%5t3-4B;G4jEXfH_3 zuQ0+jph%HlF8zf|BN-RsuXUH9T?v}J^TD4ew}@3MZB0gT!#-F{Mq*2(4fS4blD+_p zze;t5`@|!7^b`w>AUCg5MaPoCg$T;B7OWok&(8KlW`#65-UFKT0`LeG?aDQ(m==`Y z3bRCd73SHX#8px$mam!@YmrnL_p_D%C~_#mJ>|N1S<++)W7(kQBpZ}R7$7BRkr*kZ z9+>mIG$KC-|9d>Rygt1+mnHQ(M5qduDE($Q`(%~GhlknW&BK~@-A1`Ao9q0>H*4rlYk9(R0C3yYv?(yEvdR%5()QGMbYWfH4Qe4|NnY7#$#8hK_LGgKl%LusaoP*qhUspO`pkq|z=A_-xC zShGZ7k5MF&A`u4AsFIATB;;8ZW%9#(LkfCFp-D$gf+|v=Bl@U7kJD6?jIw~9k`d^Y z6`>F(36`0QUx#!fl@ap#g|wQ6$wsxws!bVlqu;XXH<@t>N{%|DxKES#r1k_aXS{#v z(OFDpQl2y-th5{sApJ{uza*$XIZIgOKYzu(y%j$mcRs_)oTw_TCAuuq@%YI|`~(c) ziV=e&tdN979+x;&axGP}EJidWX>^BXH&4&bMoF!}K2BGQaa3PNk)e7aGD?%iBPrn8 zCF+Q1qg={JPleV!srD+`rid85#WRrSD-6d$mtq~~#}y-&a%-`8RjHq}H|JUwgI@PE_x#=U)s&Vz|aCP(gXbY?}*_?-g_oS{l(%RIMHEUJuU-+3O z^M39>jL1?i3&hpn^6X#XkYITfHmj=PpIlZoE0~Hv*{dG;kB*0~bHLkbReHa}2fPef zj&F$zU$+3+(&8^HyFE+0q_SS>CL$sSe3yx!dxKTL=WtnswRkyFFLY4K+1StLqoyyg zF8Hnz9Wx3V5^omZ`x0NL`HTeSQE{i|*MqC`{@G}7b#-wCRUE#XbK%TLUiPv!+pVO~ zBnx1V(;U%dR2H<#EKQLmn(C^FP(Ypd&!tQJjVTg;SM(bjJ!1688`UHJiz<eaIYpInqzg=$KqV!et_U@_9 zU88f1&Ute>M{yi(Q}O(4eH@%!4z8>ZH|NKQBSWiyeqs?f+0E5p$i2uB|A}<6mF;Bp zgTR~KtpdRI!=$}-R!hG3gLpXO9MNo(8sC?p`Z(nsY+(-u>NmlwDQ>;oQ%yIzcl=-k zXEdDEqDS9?k_3NGaeUSo5H(5IwkPV4<15$*x&V$BRWK-_OXy(iZb6g|*%Nz9^yCQh ze>B>Qp#GfbkP8+15FbxgJ+$8*p{Bw5RQ-pxLZ>vtGcZm-Nzsz zmC^}}S(qPX3>8U$RF8GWQ-KOwtkV8ddCef5A#5d)LfzsJz8wbcQvX^jM_f#ljc&?+ zLAS$s=26`))H}oBvA_7eUp%}m@&!EYS@_CZWp3R=11NkMZUBuY(=m#Vh6K)`x%*k& zN`Sa`fAK3AxhzERFQo5~G@)4B>i@ZL;La))k!bKIUyj`1QQ8%y_=N=n*I?%zSS!NX zz-UEdhU@YYvB?EUYZ3_}uE7y$uiH{(yHZk1ispZ%vK@Ezm(FmY9}8Jc2WdZkU$%P- z8x-Mp2%%UpuC;cC@a>`;5ugWe;_Y)|l)QSQq?XYrfhKc;Q4-3K5XF*KJND?!dR9h$ zgDbgj$^^*qrTk1CG-lKbn^8p5YYeH>BUv$~6orf2YFn$6Vkb|Q^AEbwzSl@~UV25~ z2+?&7(IP-m%8eQK9?Up0w`4<(dWLkS+-@y$<#srkZW#vcecQ2jb}Q0-n?vKrTt?B0#LDcSl7MFYt#xfwgjwxjip>GS4HA3x50#6%>o z6+z)L84ygYjO}1-2c7NU%l;p~nDd8a4~qiM5^6r+a{QSUM|daBSf0y8jgc3?aG$sA4+ zs6U**^-}y)0%W2oqy03es)@o^5-c6o>1c$4@qrmb@P{=7pWOjQGLF<@O}tQ1$ZPiu zkfw(QTz6)CTgEbaJ(kh4d$&k>=6LTGo-&df%Q%*dZT2>7Gv-=~6s0(q zXiD>NDn)aezl^g;7m}RWVJM zX~Rx(Kyl#-X@(HbWboOWuu(;MenvCZSaC05#c8y$&dEikHn*FLz;TNRN_vu)3rz&E zy&`6uAg;O1P7bPAo^}??Gv-{My79a7^#H0I|U zG(Wi;QCprKDoQqY!-=We-rcG{k>vs26*^xjO~A|&3VyUi=hcJi!T(%3-#@|?26)fY z2FdeXi~GM;v)kd{sJvdY>y4kR@7v7({Z8}0+qN}FpKiT+r+L)2k6O)IwO0DcJn!UD z6yJWk+HO~C2TC#ieN282frky22_OfHzqa99yyie%zkG_B|LtZI{a^32YU2O+Y1f)R z)mt^&wi~TFs@JI1n{fWuwmJVnFul9;RLR%NrKDn^YFRrb@$?3IqbAC1$4y}J(+*tjqfi=8=|6G6UU*i&# zW5)U?`-R@0T);&eBY}n-x}Vgyd~7Cnt>qIem$KSZB6v!B{Jk#TlL~)*@Z5ic#R)W8 z;17Q5>7M?IR0%t>Bi*q(tD>604Ns$i9VeNk;|)_=49oAuwU|7)%P^fO7j_@}P_ZMg2Y<@Mk0G}|4s{+spx zt*!s`&qQy7%!PpMzl4lnC8Cps1njfjGBh!VffKBhb4>lSvy0>Y_22|nC$5pf`QL8_ zH@JXo(p}x0pTj|dzis^c_~O&$*?@pFaLI@M=^6gnBo+GS$AdFc(!wPtR~MI;_;;H) zl1BgNpWO_Gqw9;&dH>U3h*EEICsM6z?Qptw_IP<6mNV*vVjC7y8impZEaj96e?GR*u>WDSIvyhMwlU3DX*Fv zhwD%9s6DogX@0am;StMl8+6xe>l3cAQnx~SeSOlk_VQMU-$Vg-4T$n+i)y%==$5y2 zP?7mz-ruz?yqnC>&fQZA80oB?t+N!oXn!eqp#UIaQ0G2#ioxVarAB(B5&ix4c^(0PfLpd(V%$|O}1j)I~k;V2|u)Namys2RgWy57!s zs2M{SKGck%i*%P;#(De803#jlWa=;jFW6%SUZl&a1)R6%3cNf^57TF<0$)%Ginee8 zWhj8l9XLwwfDB$x>SU9+fb>ZS77t96zPV>JkHyZC<>)3_4KYpaTFR8$y&}m zAR@F9Ut7--U@IXoK@RUO$boIH>Tjjj?zFPE*0*W!XgQO~6df>dX7D2t%7JeMj}8`O zAFdQu;KCXLYTt4fv!`k)ZqIh%dUI!n(6f@cGk%;dz^Srxd+aXY`U@t-H$uav_zog( zOW6)(jm2#$N~qK>uu39wrBkxoT4+tzSF`K&9(KKwLq;Wm%aZIg1bFs*r<>W#5@*ZX z-_1(9Ek@M~#;5kSd}`X|SL0w4=6zmMF$B#N2rDRzHFLa%GdVBG9I~LljKdfF+~x4a zJ!X8)7W14jaK*H5WVt#=LSRh$cWc@U88fx^{3j4NGW|N8tU~kOy~ltAqvd#bV0QZFCp(bX89vTTz8nCYU9{8WP%dm@26fnt;Hy-;T0 zx|$ido|+lB-uYohAG1gv_!mwd+}uP`=+LB09(%ja#{RI#*xR-`_J?U>Z#Oc;DQG(* zjgsAX*->h3V3b6=K4q9XbeQaBpXWlW{Pq)$pUcnvI5ZFfB;BeZ7GHs zv&A96vqme!G3=P`NF&vFVN0sBxp)Toqi-8$)HfN7A%1wiu6T^?^L-OB z89H~yYYEDT@vHneVHsJ&VYbfv#`@I9IGMIWeshE5Z=d$JQdyO{VuO{`P zLKHpJKmhX}W*~5hJ&78d`3`rS_=Qh*oTxmvi}O6h8OQey=X+Q;Gw(YEdEa>!-Vh2xFl9LMP5n0?5ZjjLtm^I<_gA3h78x$mgh86Kc=yP28WI9`sknxTv1_7Uec ztC`4fv=9=wXW?4t%gM_&4yo|1Ra{Cr${5}?Yn*qyYF24&){04Mv-T`}7hEVsLm(p> zxt(2NQS=OFdx$WO?{&`iux_%{LQ|PQJ`4ABevP7$Y61>9x~P*qT1~*QR?uDe-BCF1 zj-x0ZddE?$5q-DL$V5>Tjlx@VT%wMUqQQhQqmm^SFM?vNn8J0&MTaDRK%NfEdSQ~q zhev}$>5!c#SaPi)(mGG_c1VXr=}{xSM>;HPb!m+u(%?`!)XdSLHD^)iVksU1sw9MM zrQfpqzqszsmCV>rs z!i!yWaO589-G-cQmS2t%%^ORusorf$`bfH>Bnm_Bvfgb)h*K{K(NRdQz20qWl9RCu zHgsTZ0@jIu3HMa!;5r2EQ0`y!iic<~4+-Rv3>mpvAqsdz2fQXjYOXZUB>!d7QkQ|X z*9d69sod1n?7?dIz6KO!5Y~vhHejq$(iZ2Q2qeFp6fkznkj12qiKH7;n zC_4W;omOoAPc-Ml{9mx1U2k;SKUpo4|I6fmH2UABezqyI1e_vx_! z^71KobQd`A-GL070jqy`YWczHj$DIs9OePTuU3p`W9Y^2+a4>g#_z8aS4cr|d2L1t_zPaASmsm@0?XX}4Yc{>}P`Ir`_!*C?QUq}D zq`I(o51uohPZtl?nX`CU!GS>4=@7!d?40!*r7Q5~YU%-hpA>ejzxC{DXRmYu{INI1 z$u#$mT|{KCk6Z^`D*)dE$R_X0#QnA?olVDJ;e-7T(;0ksc6vNG9}YsSCsRM@QhxP% zBq?dBbp1H>t)(;mf(RNz?9OySF5n51no3ewx)sFd^KQlwtyC?mblZmwLChpMX$VWR zbvpQM)%6|@Dri@4<-@iEmlHy7_kna&e~>lS*nh_UGxndc|FYVD!;71%)zMZXV9StKaF z(gdI#Noc`5jb_uu7eBeSM-7DJT7kP9&5$jmt9F1aOK6tRq~R#5eL{aVwa1}JA-~KD zj?ab!ezxi)Rk*G$G_UTb4=XQIHzJkCV!;r2U!g&k6)>LuNbPv?FUj`hCuaezIb`#u zbwLXt3Z{ZHRS+0^=PtNVY=Iy4E)`oI10H#6U?|Jz^xP5jTxt^eBipH`=J z)HJ%_{jUGy5XkucVN>%zas6+$<@ld^yV)@3zqj(-Ldy={vG=(YI1i({)pUk(B;z0G zwiftztzeo&kliZZ2l*n|KDqxHYhu+93vjpyET z=N2CAz;$Q7HJzhaDN=$mQ!XYW8d~F*NAtiKzGahY%|I*g>D<3E*^Sn&Z(X0!@~8zPQ`M3>z>@ChFThlE%mH> zoL&IkYX@h`h+a|*%gbcqluP9NG`S@S-RjdTD^skzhu=}qo`8nDaBzrQubdF#Mo|cq z^~%L!@F*s=qoXki(i)-TguSv9q!k0wQwvD7l{f2^Ge%fJ%ot|Oqt5)^ExI}80wZ*0 z{Cc~et&i?EYwqBz>r*GAp`LBe?CfCF2Gqr-LVDI0 zwtq0hiO_kCa!O<#>kh7)5}8MtN*ca?1U?YCfYYx3x^RPEEyAjab@ybImvG+TD+On> za@86FfPX>p6mXT7SQ?2{Md2na~Nh!j=_DU6Fs+q?;<}r_X%wrz&n8!TkF^_r7W1h6<{{i=V J;fet05&(TJW`_U( literal 58808 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

oH${XMYP5-6upLH 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 KJY^^VfDr%(8!D^- diff --git a/web/api/py/codechecker_api/setup.py b/web/api/py/codechecker_api/setup.py index 83a1c5e38b..0cc7d8b380 100644 --- a/web/api/py/codechecker_api/setup.py +++ b/web/api/py/codechecker_api/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.65.0' +api_version = '6.66.0' setup( name='codechecker_api', diff --git a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz index 6a6c69b6237f62eb153aeee2f7fb7a3356b9e3df..f375d9f7423015db0b855d5331385444fd318be3 100644 GIT binary patch literal 8199 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 literal 3799 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 N@_%U-&|?6g001F=xF7%k diff --git a/web/api/py/codechecker_api_shared/setup.py b/web/api/py/codechecker_api_shared/setup.py index 4d05ca0fbe..d11dda94d0 100644 --- a/web/api/py/codechecker_api_shared/setup.py +++ b/web/api/py/codechecker_api_shared/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.65.0' +api_version = '6.66.0' setup( name='codechecker_api_shared', diff --git a/web/api/report_server.thrift b/web/api/report_server.thrift index bf27f2e74d..a30274b420 100644 --- a/web/api/report_server.thrift +++ b/web/api/report_server.thrift @@ -202,6 +202,17 @@ struct RunData { } typedef list RunDataList +struct SubmittedRunOptions { + 1: string runName, + 2: string tag, + 3: string version, // The version of CodeChecker with + // which the analysis was done. + 4: bool force, // If set, existing results in + // the run are removed first. + 5: list trimPathPrefixes, + 6: optional string description, +} + struct RunHistoryData { 1: i64 runId, // Unique id of the run. 2: string runName, // Name of the run. @@ -209,8 +220,7 @@ struct RunHistoryData { 4: string user, // User name who analysed the run. 5: string time, // Date time when the run was analysed. 6: i64 id, // Id of the run history tag. - // !!!DEPRECATED!!! This field will be empty so use the getCheckCommand() API function to get the check command for a run. - 7: string checkCommand, + 7: string checkCommand, // Check command. !!!DEPRECATED!!! This field will be empty so use the getCheckCommand API function to get the check command for a run. 8: string codeCheckerVersion, // CodeChecker client version of the latest analysis. 9: AnalyzerStatisticsData analyzerStatistics, // Statistics for analyzers. Only number of failed and successfully analyzed // files field will be set. To get full analyzer statistics please use the @@ -961,32 +971,47 @@ service codeCheckerDBAccess { //============================================ // The client can ask the server whether a file is already stored in the - // database. If it is, then it is not necessary to send it in the ZIP file - // with massStoreRun() function. This function requires a list of file hashes - // (sha256) and returns the ones which are not stored yet. + // database. + // If it is present, then it is not necessary to send the file in the ZIP + // to the massStoreRunAsynchronous() function. + // This function requires a list of file hashes (sha256) and returns the + // ones which are not stored yet. + // // PERMISSION: PRODUCT_STORE list getMissingContentHashes(1: list fileHashes) throws (1: codechecker_api_shared.RequestFailed requestError), // The client can ask the server whether a blame info is already stored in the - // database. If it is, then it is not necessary to send it in the ZIP file - // with massStoreRun() function. This function requires a list of file hashes - // (sha256) and returns the ones to which no blame info is stored yet. + // database. + // If it is, then it is not necessary to send the info in the ZIP file + // to the massStoreRunAsynchronous() function. + // This function requires a list of file hashes (sha256) and returns the + // ones to which no blame info is stored yet. + // // PERMISSION: PRODUCT_STORE list getMissingContentHashesForBlameInfo(1: list fileHashes) throws (1: codechecker_api_shared.RequestFailed requestError), // This function stores an entire run encapsulated and sent in a ZIP file. - // The ZIP file has to be compressed and sent as a base64 encoded string. The - // ZIP file must contain a "reports" and an optional "root" sub-folder. - // The former one is the output of 'CodeChecker analyze' command and the - // latter one contains the source files on absolute paths starting as if - // "root" was the "/" directory. The source files are not necessary to be - // wrapped in the ZIP file (see getMissingContentHashes() function). + // The ZIP file has to be compressed by ZLib and the compressed buffer + // sent as a Base64-encoded string. The ZIP file must contain a "reports" and + // an optional "root" sub-directory. The former one is the output of the + // 'CodeChecker analyze' command and the latter one contains the source files + // on absolute paths starting as if "root" was the "/" directory. The source + // files are not necessary to be wrapped in the ZIP file + // (see getMissingContentHashes() function). // // The "version" parameter is the used CodeChecker version which checked this // run. // The "force" parameter removes existing analysis results for a run. + // + // !DEPRECATED!: Use of this function is deprecated as the storing client + // process is prone to infinite hangs while waiting for the return value of + // the Thrift call if the network communication terminates during the time + // the server is processing the sent data, which might take a very long time. + // Appropriately modern clients are expected to use the + // massStoreRunAsynchronous() function and the Task API instead! + // // PERMISSION: PRODUCT_STORE i64 massStoreRun(1: string runName, 2: string tag, @@ -997,6 +1022,35 @@ service codeCheckerDBAccess { 7: optional string description) throws (1: codechecker_api_shared.RequestFailed requestError), + // This function stores an entire analysis run encapsulated and sent as a + // ZIP file. The ZIP file must be compressed by ZLib and sent as a + // Base64-encoded string. It must contain a "reports" and an optional "root" + // sub-directory. "reports" contains the output of the `CodeChecker analyze` + // command, while "root", if present, contains the source code of the project + // with their full paths, with the logical "root" replacing the original + // "/" directory. + // + // The source files are not necessary to be present in the ZIP, see + // getMissingContentHashes() for details. + // + // After performing an initial validation of the well-formedness of the + // submitted structure (ill-formedness is reported as an exception), the + // potentially lengthy processing of the data and the database operations are + // done asynchronously. + // + // This function returns a TaskToken, which SHOULD be used as the argument to + // the tasks::getTaskInfo() function such that clients retrieve the + // processing's state. Clients MAY decide to "detach", i.e., not to wait + // for the processing of the submitted data, and ignore the returned handle. + // Even if the client detached, the processing of the stored reports will + // likely eventually conclude. + // + // PERMISSION: PRODUCT_STORE + codechecker_api_shared.TaskToken massStoreRunAsynchronous( + 1: string zipfileBlob, // Base64-encoded string. + 2: SubmittedRunOptions storeOpts) + throws (1: codechecker_api_shared.RequestFailed requestError), + // Returns true if analysis statistics information can be sent to the server, // otherwise it returns false. // PERMISSION: PRODUCT_STORE diff --git a/web/api/tasks.thrift b/web/api/tasks.thrift new file mode 100644 index 0000000000..c92da79434 --- /dev/null +++ b/web/api/tasks.thrift @@ -0,0 +1,160 @@ +// ------------------------------------------------------------------------- +// Part of the CodeChecker project, under the Apache License v2.0 with +// LLVM Exceptions. See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ------------------------------------------------------------------------- + +include "codechecker_api_shared.thrift" + +namespace py codeCheckerServersideTasks_v6 +namespace js codeCheckerServersideTasks_v6 + +enum TaskStatus { + ALLOCATED, // Non-terminated state. Token registered but the job hasn't queued yet: the input is still processing. + ENQUEUED, // Non-terminated state. Job in the queue, and all inputs are meaningfully available. + RUNNING, // Non-terminated state. + COMPLETED, // Terminated state. Successfully ran to completion. + FAILED, // Terminated state. Job was running, but the execution failed. + CANCELLED, // Terminated state. Job was cancelled by an administrator, and the cancellation succeeded. + DROPPED, // Terminated state. Job was cancelled due to system reasons (server shutdown, crash, other interference). +} + +struct TaskInfo { + 1: codechecker_api_shared.TaskToken token, + 2: string taskKind, + 3: TaskStatus status, + // If the task is associated with a product, this ID can be used to query + // product information, see products.thirft service. + // The 'productID' is set to 0 if there is no product associated, meaning + // that the task is "global to the server". + 4: i64 productId, + 5: string actorUsername, + 6: string summary, + // Additional, human-readable comments, history, and log output from the + // tasks's processing. + 7: string comments, + 8: i64 enqueuedAtEpoch, + 9: i64 startedAtEpoch, + 10: i64 completedAtEpoch, + 11: i64 lastHeartbeatEpoch, + // Whether the administrator set this job for a co-operative cancellation. + 12: bool cancelFlagSet, +} + +/** + * TaskInfo with additional fields that is sent to administrators only. + */ +struct AdministratorTaskInfo { + 1: TaskInfo normalInfo, + 2: string machineId, // The hopefully unique identifier of the server + // that is/was processing the task. + 3: bool statusConsumed, // Whether the main actor of the task + // (see normalInfo.actorUsername) consumed the + // termination status of the job. +} + +/** + * Metastructure that holds the filters for getTasks(). + * The individual fields of the struct are in "AND" relation with each other. + * For list<> fields, elements of the list filter the same "column" of the + * task information table, and are considered in an "OR" relation. + */ +struct TaskFilter { + 1: list tokens, + 2: list machineIDs, + 3: list kinds, + 4: list statuses, + // If unset, it means "all", including those of no username. + // If an empty list, it means "only no username". + // Otherwise, it means OR the usernames specified. + 5: optional list usernames, + // If unset, it means "all", including those of no product ID. + // If an emtpy list, it means "only those with no associated product". + // Otherwise, it means OR the product IDs specified. + 6: optional list productIDs, + 7: i64 enqueuedBeforeEpoch, + 8: i64 enqueuedAfterEpoch, + 9: i64 startedBeforeEpoch, + 10: i64 startedAfterEpoch, + 11: i64 completedBeforeEpoch, + 12: i64 completedAfterEpoch, + 13: i64 heartbeatBeforeEpoch, + 14: i64 heartbeatAfterEpoch, + 15: codechecker_api_shared.Ternary cancelFlag, + 16: codechecker_api_shared.Ternary consumedFlag, +} + +service codeCheckerServersideTaskService { + // Retrieves the status of a task registered on the server, based on its + // identifying "token". + // + // Following this query, if the task is in any terminating states and the + // query was requested by the main actor, the status will be considered + // "consumed", and might be garbage collected by the server at a later + // point in time. + // + // If the server has authentication enabled, this query is only allowed to + // the following users: + // * The user who originally submitted the request that resulted in the + // creation of this job. + // * If the job is associated with a specific product, anyone with + // PRODUCT_ADMIN privileges for that product. + // * Users with SUPERUSER rights. + // + // PERMISSION: . + TaskInfo getTaskInfo( + 1: codechecker_api_shared.TaskToken token) + throws (1: codechecker_api_shared.RequestFailed requestError), + + // Returns privileged information about the tasks stored in the servers' + // databases, based on the given filter. + // + // This query does not set the "consumed" flag on the results, even if the + // querying user was a task's main actor. + // + // If the querying user only has PRODUCT_ADMIN rights, they are only allowed + // to query the tasks corresponding to a product they are PRODUCT_ADMIN of. + // + // PERMISSION: SUPERUSER, PRODUCT_ADMIN + list getTasks( + 1: TaskFilter filters) + throws (1: codechecker_api_shared.RequestFailed requestError), + + // Sets the specified task's "cancel" flag to TRUE, resulting in a request to + // the task's execution to co-operatively terminate itself. + // Returns whether the current RPC call was the one which set the flag. + // + // Tasks will generally terminate themselves at a safe point during their + // processing, but there are no guarantees that a specific task at any given + // point can reach such a safe point. + // There are no guarantees that a specific task is implemented in a way that + // it can ever be terminated via a "cancel" action. + // + // This method does not result in a communication via operating system + // primitives to the running server, and it is not capable of either + // completely shutting down a running server, or, conversely, to resurrect a + // hung server. + // + // Setting the "cancel" flag of an already cancelled task does nothing, and + // it is not possible to un-cancel a task. + // Setting the "cancel" flag of already terminated tasks does nothing. + // In both such cases, the RPC call will return "bool False". + // + // PERMISSION: SUPERUSER + bool cancelTask( + 1: codechecker_api_shared.TaskToken token) + throws (1: codechecker_api_shared.RequestFailed requestError), + + // Used for testing purposes only. + // This function will **ALWAYS** throw an exception when ran outside of a + // testing environment. + // + // The dummy task will increment a temporary counter in the background, with + // intermittent sleeping, up to approximately "timeout" number of seconds, + // after which point it will gracefully terminate. + // The result of the execution is unsuccessful if "shouldFail" is a true. + codechecker_api_shared.TaskToken createDummyTask( + 1: i32 timeout, + 2: bool shouldFail) + throws (1: codechecker_api_shared.RequestFailed requestError), +} diff --git a/web/client/codechecker_client/client.py b/web/client/codechecker_client/client.py index 96c3b3b90a..c27b1fccc6 100644 --- a/web/client/codechecker_client/client.py +++ b/web/client/codechecker_client/client.py @@ -214,7 +214,7 @@ def setup_product_client(protocol, host, port, auth_client=None, # Attach to the server-wide product service. product_client = ThriftProductHelper( protocol, host, port, - '/v' + CLIENT_API + '/Products', + f"/v{CLIENT_API}/Products", session_token, lambda: get_new_token(protocol, host, port, cred_manager)) else: @@ -269,6 +269,6 @@ def setup_client(product_url) -> ThriftResultsHelper: return ThriftResultsHelper( protocol, host, port, - '/' + product_name + '/v' + CLIENT_API + '/CodeCheckerService', + f"/{product_name}/v{CLIENT_API}/CodeCheckerService", session_token, lambda: get_new_token(protocol, host, port, cred_manager)) diff --git a/web/client/codechecker_client/helpers/results.py b/web/client/codechecker_client/helpers/results.py index c558cfe040..399623019e 100644 --- a/web/client/codechecker_client/helpers/results.py +++ b/web/client/codechecker_client/helpers/results.py @@ -9,7 +9,7 @@ Helper functions for Thrift api calls. """ -from codechecker_api.codeCheckerDBAccess_v6 import codeCheckerDBAccess +from codechecker_api.codeCheckerDBAccess_v6 import codeCheckerDBAccess, ttypes from codechecker_client.thrift_call import thrift_client_call from .base import BaseClientHelper @@ -181,6 +181,12 @@ def massStoreRun(self, name, tag, version, zipdir, force, trim_path_prefixes, description): pass + @thrift_client_call + def massStoreRunAsynchronous(self, zipfile_blob: str, + store_opts: ttypes.SubmittedRunOptions) \ + -> str: + pass + @thrift_client_call def allowsStoringAnalysisStatistics(self): pass diff --git a/web/codechecker_web/shared/version.py b/web/codechecker_web/shared/version.py index 09b942f2b4..9abaa27c6c 100644 --- a/web/codechecker_web/shared/version.py +++ b/web/codechecker_web/shared/version.py @@ -20,7 +20,7 @@ # The newest supported minor version (value) for each supported major version # (key) in this particular build. SUPPORTED_VERSIONS = { - 6: 65 + 6: 66 } # Used by the client to automatically identify the latest major and minor diff --git a/web/server/codechecker_server/api/common.py b/web/server/codechecker_server/api/common.py new file mode 100644 index 0000000000..2fc699a24f --- /dev/null +++ b/web/server/codechecker_server/api/common.py @@ -0,0 +1,49 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +import sqlalchemy + +from codechecker_api_shared.ttypes import RequestFailed, ErrorCode + +from codechecker_common.logger import get_logger + + +LOG = get_logger("server") + + +def exc_to_thrift_reqfail(function): + """ + Convert internal exceptions to a `RequestFailed` Thrift exception, which + can be sent back to the RPC client. + """ + func_name = function.__name__ + + def wrapper(*args, **kwargs): + try: + res = function(*args, **kwargs) + return res + except sqlalchemy.exc.SQLAlchemyError as alchemy_ex: + # Convert SQLAlchemy exceptions. + msg = str(alchemy_ex) + import traceback + traceback.print_exc() + + # pylint: disable=raise-missing-from + raise RequestFailed(ErrorCode.DATABASE, msg) + except RequestFailed as rf: + LOG.warning("%s:\n%s", func_name, rf.message) + raise + except Exception as ex: + import traceback + traceback.print_exc() + msg = str(ex) + LOG.warning("%s:\n%s", func_name, msg) + + # pylint: disable=raise-missing-from + raise RequestFailed(ErrorCode.GENERAL, msg) + + return wrapper diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index bcbc6e82da..424527ad09 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -45,7 +45,8 @@ ReviewStatusRuleSortType, Rule, RunData, RunFilter, RunHistoryData, \ RunReportCount, RunSortType, RunTagCount, \ ReviewStatus as API_ReviewStatus, \ - SourceComponentData, SourceFileData, SortMode, SortType + SourceComponentData, SourceFileData, SortMode, SortType, \ + SubmittedRunOptions from codechecker_common import util from codechecker_common.logger import get_logger @@ -70,6 +71,7 @@ Run, RunHistory, RunHistoryAnalysisInfo, RunLock, \ SourceComponent +from .common import exc_to_thrift_reqfail from .thrift_enum_helper import detection_status_enum, \ detection_status_str, report_status_enum, \ review_status_enum, review_status_str, report_extended_data_type_enum @@ -143,39 +145,6 @@ def slugify(text): return norm_text -def exc_to_thrift_reqfail(function): - """ - Convert internal exceptions to RequestFailed exception - which can be sent back on the thrift connections. - """ - func_name = function.__name__ - - def wrapper(*args, **kwargs): - try: - res = function(*args, **kwargs) - return res - - except sqlalchemy.exc.SQLAlchemyError as alchemy_ex: - # Convert SQLAlchemy exceptions. - msg = str(alchemy_ex) - import traceback - traceback.print_exc() - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, msg) - except codechecker_api_shared.ttypes.RequestFailed as rf: - LOG.warning("%s:\n%s", func_name, rf.message) - raise - except Exception as ex: - import traceback - traceback.print_exc() - msg = str(ex) - LOG.warning("%s:\n%s", func_name, msg) - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.GENERAL, msg) - - return wrapper - - def get_component_values( session: DBSession, component_name: str @@ -3988,6 +3957,18 @@ def massStoreRun(self, name, tag, version, b64zip, force, trim_path_prefixes, description) return m.store() + @exc_to_thrift_reqfail + @timeit + def massStoreRunAsynchronous(self, zipfile_blob: str, + store_opts: SubmittedRunOptions) -> str: + import pprint + LOG.info("massStoreRunAsynchronous() called with:\n\t - %d bytes " + "input\n\t - Options:\n\n%s", len(zipfile_blob), + pprint.pformat(store_opts.__dict__, indent=2, depth=8)) + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.GENERAL, + "massStoreRunAsynchronous() not implemented in this server build!") + @exc_to_thrift_reqfail @timeit def allowsStoringAnalysisStatistics(self): diff --git a/web/server/codechecker_server/api/tasks.py b/web/server/codechecker_server/api/tasks.py new file mode 100644 index 0000000000..eca624ca63 --- /dev/null +++ b/web/server/codechecker_server/api/tasks.py @@ -0,0 +1,382 @@ + +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Handle Thrift requests for background task management. +""" +import datetime +import os +import time +from typing import Dict, List, Optional + +from sqlalchemy.sql.expression import and_, or_ + +from codechecker_api_shared.ttypes import RequestFailed, ErrorCode, Ternary +from codechecker_api.codeCheckerServersideTasks_v6.ttypes import \ + AdministratorTaskInfo, TaskFilter, TaskInfo, TaskStatus + +from codechecker_common.logger import get_logger + +from codechecker_server.profiler import timeit + +from ..database.config_db_model import BackgroundTask as DBTask, Product +from ..database.database import DBSession, conv +from ..task_executors.abstract_task import AbstractTask, TaskCancelHonoured +from ..task_executors.task_manager import TaskManager +from .. import permissions +from .common import exc_to_thrift_reqfail + +LOG = get_logger("server") + + +class TestingDummyTask(AbstractTask): + """Implementation of task object created by ``createDummyTask()``.""" + def __init__(self, token: str, timeout: int, should_fail: bool): + super().__init__(token, None) + self.timeout = timeout + self.should_fail = should_fail + + def _implementation(self, tm: TaskManager) -> None: + counter: int = 0 + while counter < self.timeout: + tm.heartbeat(self) + + counter += 1 + LOG.debug("Dummy task ticking... [%d / %d]", + counter, self.timeout) + + if tm.should_cancel(self): + LOG.info("Dummy task '%s' was %s at tick [%d / %d]!", + self.token, + "KILLED BY SHUTDOWN" if tm.is_shutting_down + else "CANCELLED BY ADMIN", + counter, + self.timeout) + raise TaskCancelHonoured(self) + + time.sleep(1) + + if self.should_fail: + raise ValueError("Task self-failure as per the user's request.") + + +def _db_timestamp_to_posix_epoch(d: Optional[datetime.datetime]) \ + -> Optional[int]: + return int(d.replace(tzinfo=datetime.timezone.utc).timestamp()) if d \ + else None + + +def _posix_epoch_to_db_timestamp(s: Optional[int]) \ + -> Optional[datetime.datetime]: + return datetime.datetime.fromtimestamp(s, datetime.timezone.utc) if s \ + else None + + +def _make_task_info(t: DBTask) -> TaskInfo: + """Format API `TaskInfo` from `DBTask`.""" + return TaskInfo( + token=t.token, + taskKind=t.kind, + status=TaskStatus._NAMES_TO_VALUES[t.status.upper()], + productId=t.product_id or 0, + actorUsername=t.username, + summary=t.summary, + comments=t.comments, + enqueuedAtEpoch=_db_timestamp_to_posix_epoch(t.enqueued_at), + startedAtEpoch=_db_timestamp_to_posix_epoch(t.started_at), + completedAtEpoch=_db_timestamp_to_posix_epoch(t.finished_at), + lastHeartbeatEpoch=_db_timestamp_to_posix_epoch( + t.last_seen_at), + cancelFlagSet=t.cancel_flag, + ) + + +def _make_admin_task_info(t: DBTask) -> AdministratorTaskInfo: + """Format API `AdministratorTaskInfo` from `DBTask`.""" + return AdministratorTaskInfo( + normalInfo=_make_task_info(t), + machineId=t.machine_id, + statusConsumed=t.consumed, + ) + + +# These names are inherited from Thrift stubs. +# pylint: disable=invalid-name +class ThriftTaskHandler: + """ + Manages Thrift requests concerning the user-facing Background Tasks API. + """ + + def __init__(self, + configuration_database_sessionmaker, + task_manager: TaskManager, + auth_session): + self._config_db = configuration_database_sessionmaker + self._task_manager = task_manager + self._auth_session = auth_session + + def _get_username(self) -> Optional[str]: + """ + Returns the actually logged in user name. + """ + return self._auth_session.user if self._auth_session else None + + @exc_to_thrift_reqfail + @timeit + def getTaskInfo(self, token: str) -> TaskInfo: + """ + Returns the `TaskInfo` for the task identified by `token`. + """ + with DBSession(self._config_db) as session: + db_task: Optional[DBTask] = session.query(DBTask).get(token) + if not db_task: + raise RequestFailed(ErrorCode.GENERAL, + f"Task '{token}' does not exist!") + + has_right_to_query_status: bool = False + should_set_consumed_flag: bool = False + + if db_task.username == self._get_username(): + has_right_to_query_status = True + should_set_consumed_flag = db_task.is_in_terminated_state + elif db_task.product_id is not None: + associated_product: Optional[Product] = \ + session.query(Product).get(db_task.product_id) + if not associated_product: + LOG.error("No product with ID '%d', but a task is " + "associated with it.", + db_task.product_id) + else: + has_right_to_query_status = \ + permissions.require_permission( + permissions.PRODUCT_ADMIN, + {"config_db_session": session, + "productID": associated_product.id}, + self._auth_session) + + if not has_right_to_query_status: + has_right_to_query_status = permissions.require_permission( + permissions.SUPERUSER, + {"config_db_session": session}, + self._auth_session) + + if not has_right_to_query_status: + raise RequestFailed( + ErrorCode.UNAUTHORIZED, + "Only the task's submitter, a PRODUCT_ADMIN (of the " + "product the task is associated with), or a SUPERUSER " + "can getTaskInfo()!") + + info = _make_task_info(db_task) + + if should_set_consumed_flag: + db_task.consumed = True + session.commit() + + return info + + @exc_to_thrift_reqfail + @timeit + def getTasks(self, filters: TaskFilter) -> List[AdministratorTaskInfo]: + """Obtain tasks matching the `filters` for administrators.""" + with DBSession(self._config_db) as session: + if filters.productIDs is not None and not filters.productIDs: + if not permissions.require_permission( + permissions.SUPERUSER, + {"config_db_session": session}, + self._auth_session): + raise RequestFailed( + ErrorCode.UNAUTHORIZED, + "Querying service tasks (not associated with a " + "product) requires SUPERUSER privileges!") + if filters.productIDs: + no_admin_products = [ + prod_id for prod_id in filters.productIDs + if not permissions.require_permission( + permissions.PRODUCT_ADMIN, + {"config_db_session": session, "productID": prod_id}, + self._auth_session)] + if no_admin_products: + no_admin_products = [session.query(Product) + .get(product_id).endpoint + for product_id in no_admin_products] + # pylint: disable=consider-using-f-string + raise RequestFailed(ErrorCode.UNAUTHORIZED, + "Querying product tasks requires " + "PRODUCT_ADMIN rights, but it is " + "missing from product(s): '%s'!" + % ("', '".join(no_admin_products))) + + AND = [] + if filters.tokens: + AND.append(or_(*(DBTask.token.ilike(conv(token)) + for token in filters.tokens))) + + if filters.machineIDs: + AND.append(or_(*(DBTask.machine_id.ilike(conv(machine_id)) + for machine_id in filters.machineIDs))) + + if filters.kinds: + AND.append(or_(*(DBTask.kind.ilike(conv(kind)) + for kind in filters.kinds))) + + if filters.statuses: + AND.append(or_(DBTask.status.in_([ + TaskStatus._VALUES_TO_NAMES[status].lower() + for status in filters.statuses]))) + + if filters.usernames is not None: + if filters.usernames: + AND.append(or_(*(DBTask.username.ilike(conv(username)) + for username in filters.usernames))) + else: + AND.append(DBTask.username.is_(None)) + + if filters.productIDs is not None: + if filters.productIDs: + AND.append(or_(DBTask.product_id.in_(filters.productIDs))) + else: + AND.append(DBTask.product_id.is_(None)) + + if filters.enqueuedBeforeEpoch: + AND.append(DBTask.enqueued_at <= _posix_epoch_to_db_timestamp( + filters.enqueuedBeforeEpoch)) + + if filters.enqueuedAfterEpoch: + AND.append(DBTask.enqueued_at >= _posix_epoch_to_db_timestamp( + filters.enqueuedAfterEpoch)) + + if filters.startedBeforeEpoch: + AND.append(DBTask.started_at <= _posix_epoch_to_db_timestamp( + filters.startedBeforeEpoch)) + + if filters.startedAfterEpoch: + AND.append(DBTask.started_at >= _posix_epoch_to_db_timestamp( + filters.startedAfterEpoch)) + + if filters.completedBeforeEpoch: + AND.append(DBTask.finished_at <= _posix_epoch_to_db_timestamp( + filters.completedBeforeEpoch)) + + if filters.completedAfterEpoch: + AND.append(DBTask.finished_at >= _posix_epoch_to_db_timestamp( + filters.completedAfterEpoch)) + + if filters.heartbeatBeforeEpoch: + AND.append(DBTask.last_seen_at <= + _posix_epoch_to_db_timestamp( + filters.heartbeatBeforeEpoch)) + + if filters.heartbeatAfterEpoch: + AND.append(DBTask.last_seen_at >= + _posix_epoch_to_db_timestamp( + filters.heartbeatAfterEpoch)) + + if filters.cancelFlag: + if filters.cancelFlag == Ternary._NAMES_TO_VALUES["OFF"]: + AND.append(DBTask.cancel_flag.is_(False)) + elif filters.cancelFlag == Ternary._NAMES_TO_VALUES["ON"]: + AND.append(DBTask.cancel_flag.is_(True)) + + if filters.consumedFlag: + if filters.consumedFlag == Ternary._NAMES_TO_VALUES["OFF"]: + AND.append(DBTask.consumed.is_(False)) + elif filters.consumedFlag == Ternary._NAMES_TO_VALUES["ON"]: + AND.append(DBTask.consumed.is_(True)) + + ret: List[AdministratorTaskInfo] = [] + has_superuser: Optional[bool] = None + product_admin_rights: Dict[int, bool] = {} + for db_task in session.query(DBTask).filter(and_(*AND)).all(): + if not db_task.product_id: + # Tasks associated with the server, and not a specific + # product, should only be visible to SUPERUSERs. + if has_superuser is None: + has_superuser = permissions.require_permission( + permissions.SUPERUSER, + {"config_db_session": session}, + self._auth_session) + if not has_superuser: + continue + else: + # Tasks associated with a product should only be visible + # to PRODUCT_ADMINs of that product. + try: + if not product_admin_rights[db_task.product_id]: + continue + except KeyError: + product_admin_rights[db_task.product_id] = \ + permissions.require_permission( + permissions.PRODUCT_ADMIN, + {"config_db_session": session, + "productID": db_task.product_id}, + self._auth_session) + if not product_admin_rights[db_task.product_id]: + continue + + ret.append(_make_admin_task_info(db_task)) + + return ret + + @exc_to_thrift_reqfail + @timeit + def cancelTask(self, token: str) -> bool: + """ + Sets the ``cancel_flag`` of the task specified by `token` to `True` + in the database, **REQUESTING** that the task gracefully terminate + itself. + + There are no guarantees that tasks will respect this! + """ + with DBSession(self._config_db) as session: + if not permissions.require_permission( + permissions.SUPERUSER, + {"config_db_session": session}, + self._auth_session): + raise RequestFailed( + ErrorCode.UNAUTHORIZED, + "cancelTask() requires server-level SUPERUSER rights.") + + db_task: Optional[DBTask] = session.query(DBTask).get(token) + if not db_task: + raise RequestFailed(ErrorCode.GENERAL, + f"Task '{token}' does not exist!") + + if not db_task.can_be_cancelled: + return False + + db_task.add_comment("SUPERUSER requested cancellation.", + self._get_username()) + db_task.cancel_flag = True + session.commit() + + return True + + @exc_to_thrift_reqfail + @timeit + def createDummyTask(self, timeout: int, should_fail: bool) -> str: + """ + Used for testing purposes only. + + This function will **ALWAYS** throw an exception when ran outside of a + testing environment. + """ + if "TEST_WORKSPACE" not in os.environ: + raise RequestFailed(ErrorCode.GENERAL, + "createDummyTask() is only available in " + "testing environments!") + + token = self._task_manager.allocate_task_record( + "TaskService::DummyTask", + "Dummy task for testing purposes", + self._get_username(), + None) + + t = TestingDummyTask(token, timeout, should_fail) + self._task_manager.push_task(t) + + return token diff --git a/web/server/codechecker_server/cli/server.py b/web/server/codechecker_server/cli/server.py index c7781250b8..ae5bbc1d99 100644 --- a/web/server/codechecker_server/cli/server.py +++ b/web/server/codechecker_server/cli/server.py @@ -18,13 +18,11 @@ import signal import socket import sys -import time from typing import List, Optional, Tuple, cast from alembic import config from alembic import script from alembic.util import CommandError -import psutil from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import sessionmaker @@ -32,7 +30,7 @@ from codechecker_report_converter import twodim -from codechecker_common import arg, cmd_config, logger, util +from codechecker_common import arg, cmd_config, logger, process, util from codechecker_common.compatibility.multiprocessing import Pool, cpu_count from codechecker_server import instance_manager, server @@ -101,6 +99,25 @@ def add_arguments_to_parser(parser): "authentication settings, TLS certificate" " (cert.pem) and key (key.pem)) from.") + parser.add_argument("--machine-id", + type=str, + dest="machine_id", + default=argparse.SUPPRESS, + required=False, + help=""" +A unique identifier to be used to identify the machine running subsequent +instances of the "same" server process. +This value is only used internally to maintain normal function and bookkeeping +of executed tasks following an unclean server shutdown, e.g., after a crash or +system-level interference. + +If unspecified, defaults to a reasonable default value that is generated from +the computer's hostname, as reported by the operating system. +In most scenarios, there is no need to fine-tune this, except if subsequent +executions of the "same" server is achieved in distinct environments, e.g., +if the server otherwise is running in a container. +""") + parser.add_argument('--host', type=str, dest="listen_address", @@ -413,7 +430,7 @@ def arg_match(options): setattr(args, "instance_manager", True) # If everything is fine, do call the handler for the subcommand. - main(args) + return main(args) parser.set_defaults( func=__handle, func_process_config_file=cmd_config.process_config_file) @@ -751,42 +768,6 @@ def _get_migration_decisions() -> List[Tuple[str, str, bool]]: return 0 -def kill_process_tree(parent_pid, recursive=False): - """Stop the process tree try it gracefully first. - - Try to stop the parent and child processes gracefuly - first if they do not stop in time send a kill signal - to every member of the process tree. - - There is a similar function in the analyzer part please - consider to update that in case of changing this. - """ - proc = psutil.Process(parent_pid) - children = proc.children(recursive) - - # Send a SIGTERM (Ctrl-C) to the main process - proc.terminate() - - # If children processes don't stop gracefully in time, - # slaughter them by force. - _, still_alive = psutil.wait_procs(children, timeout=5) - for p in still_alive: - p.kill() - - # Wait until this process is running. - n = 0 - timeout = 10 - while proc.is_running(): - if n > timeout: - LOG.warning("Waiting for process %s to stop has been timed out" - "(timeout = %s)! Process is still running!", - parent_pid, timeout) - break - - time.sleep(1) - n += 1 - - def __instance_management(args): """Handles the instance-manager commands --list/--stop/--stop-all.""" @@ -831,7 +812,7 @@ def __instance_management(args): continue try: - kill_process_tree(i['pid']) + process.kill_process_tree(i['pid']) LOG.info("Stopped CodeChecker server running on port %s " "in workspace %s (PID: %s)", i['port'], i['workspace'], i['pid']) @@ -1086,16 +1067,21 @@ def server_init_start(args): 'doc_root': context.doc_root, 'version': context.package_git_tag} + # Create a machine ID if the user did not specify one. + machine_id = getattr(args, "machine_id", + f"{socket.gethostname()}:{args.view_port}") + try: - server.start_server(args.config_directory, - package_data, - args.view_port, - cfg_sql_server, - args.listen_address, - 'force_auth' in args, - args.skip_db_cleanup, - context, - environ) + return server.start_server(args.config_directory, + package_data, + args.view_port, + cfg_sql_server, + args.listen_address, + 'force_auth' in args, + args.skip_db_cleanup, + context, + environ, + machine_id) except socket.error as err: if err.errno == errno.EADDRINUSE: LOG.error("Server can't be started, maybe port number (%s) is " @@ -1132,4 +1118,4 @@ def main(args): except FileNotFoundError as fnerr: LOG.error(fnerr) sys.exit(1) - server_init_start(args) + return server_init_start(args) diff --git a/web/server/codechecker_server/database/config_db_model.py b/web/server/codechecker_server/database/config_db_model.py index d19ae4115b..b98b9d81d7 100644 --- a/web/server/codechecker_server/database/config_db_model.py +++ b/web/server/codechecker_server/database/config_db_model.py @@ -8,8 +8,9 @@ """ SQLAlchemy ORM model for the product configuration database. """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import sys +from typing import Optional from sqlalchemy import Boolean, CHAR, Column, DateTime, Enum, ForeignKey, \ Integer, MetaData, String, Text, UniqueConstraint @@ -230,6 +231,200 @@ def __init__(self, access_token, expires_at, refresh_token, self.auth_session_id = auth_session_id +class BackgroundTask(Base): + """ + Information about background tasks executed on a CodeChecker service, + potentially as part of a cluster, stored in the database. + These entities store the metadata for the task objects, but no information + about the actual "input" of the task exists in the database! + """ + __tablename__ = "background_tasks" + + _token_length = 64 + + machine_id = Column(String, index=True) + """ + A unique, implementation-specific identifier of the actual CodeChecker + server instance that knows how to execute the task. + """ + + token = Column(CHAR(length=_token_length), primary_key=True) + kind = Column(String, nullable=False, index=True) + status = Column(Enum( + # A job token (and thus a BackgroundTask record) was allocated, but + # the job is still under preparation. + "allocated", + + # The job is pending on the server, but the server has all the data + # available to eventually perform the job. + "enqueued", + + # The server is actually performing the job. + "running", + + # The server successfully finished completing the job. + "completed", + + # The execution of the job failed. + # In this stage, the "comments" field likely contains more information + # that is not machine-readable. + "failed", + + # The job never started, or its execution was terminated at the + # request of the administrators. + "cancelled", + + # The job never started, or its execution was terminated due to a + # system-level reason (such as the server's foced shutdown). + "dropped", + ), + nullable=False, + default="enqueued", + index=True) + + product_id = Column(Integer, + ForeignKey("products.id", + deferrable=False, + initially="IMMEDIATE", + ondelete="CASCADE"), + nullable=True, + index=True) + """ + If the job is tightly associated with a product, the ID of the `Product` + entity with which it is associated. + """ + + username = Column(String, nullable=True) + """ + The main actor who was responsible for the creation of the job task. + """ + + summary = Column(String, nullable=False) + comments = Column(Text, nullable=True) + + enqueued_at = Column(DateTime, nullable=True) + started_at = Column(DateTime, nullable=True) + finished_at = Column(DateTime, nullable=True) + + last_seen_at = Column(DateTime, nullable=True) + """ + Contains the timestamp, only when the job is not yet "finished", when the + job last synchronised against the database, e.g., when it last checked the + "cancel_flag" field. + + This is used for health checking whether the background worker is actually + doing something, as a second line of defence to uncover "dropped" jobs, + e.g., when the servers have failed and the new server can not identify + jobs from its "previous life". + """ + + consumed = Column(Boolean, nullable=False, + default=False, server_default=false()) + """ + Whether the status of the job was checked **BY THE MAIN ACTOR** (username). + """ + + cancel_flag = Column(Boolean, nullable=False, + default=False, server_default=false()) + """ + Whether a SUPERUSER has signalled that the job should be cancelled. + + Note, that cancelling is a co-operative action: jobs are never actually + "killed" on the O.S. level from the outside; rather, each job is expected + to be implemented in a way that they regularly query this bit, and if set, + act accordingly. + """ + + def __init__(self, + token: str, + kind: str, + summary: str, + machine_id: str, + user_name: Optional[str], + product: Optional[Product] = None, + ): + self.machine_id = machine_id + self.token = token + self.kind = kind + self.status = "allocated" + self.summary = summary + self.username = user_name + self.last_seen_at = datetime.now(timezone.utc) + + if product: + self.product_id = product.id + + def add_comment(self, comment: str, actor: Optional[str] = None): + if not self.comments: + self.comments = "" + elif self.comments: + self.comments += "\n----------\n" + + self.comments += f"{actor if actor else ''} " \ + f"at {str(datetime.now(timezone.utc))}:\n{comment}" + + def heartbeat(self): + """Update `last_seen_at`.""" + if self.status in ["enqueued", "running"]: + self.last_seen_at = datetime.now(timezone.utc) + + def set_enqueued(self): + """Marks the job as successfully enqueued.""" + if self.status != "allocated": + raise ValueError( + f"Invalid transition '{str(self.status)}' -> 'enqueued'") + + self.status = "enqueued" + self.enqueued_at = datetime.now(timezone.utc) + + def set_running(self): + """Marks the job as currently executing.""" + if self.status != "enqueued": + raise ValueError( + f"Invalid transition '{str(self.status)}' -> 'running'") + + self.status = "running" + self.started_at = datetime.now(timezone.utc) + + def set_finished(self, successfully: bool = True): + """Marks the job as successfully completed or failed.""" + new_status = "completed" if successfully else "failed" + if self.status != "running": + raise ValueError( + f"Invalid transition '{str(self.status)}' -> '{new_status}'") + + self.status = new_status + self.finished_at = datetime.now(timezone.utc) + + def set_abandoned(self, force_dropped_status: bool = False): + """ + Marks the job as cancelled or dropped based on whether the + cancel flag is set. + """ + new_status = "cancelled" \ + if not force_dropped_status and self.cancel_flag \ + else "dropped" + + self.status = new_status + self.finished_at = datetime.now(timezone.utc) + + @property + def is_in_terminated_state(self) -> bool: + """ + Returns whether the current task has finished execution in some way, + for some reason. + """ + return self.status not in ["allocated", "enqueued", "running"] + + @property + def can_be_cancelled(self) -> bool: + """ + Returns whether the task is in a state where setting `cancel_flag` + is meaningful. + """ + return not self.is_in_terminated_state and not self.cancel_flag + + IDENTIFIER = { 'identifier': "ConfigDatabase", 'orm_meta': CC_META diff --git a/web/server/codechecker_server/migrations/config/versions/5e1501dfd333_implemented_keeping_track_of_background_.py b/web/server/codechecker_server/migrations/config/versions/5e1501dfd333_implemented_keeping_track_of_background_.py new file mode 100644 index 0000000000..94f332ea4a --- /dev/null +++ b/web/server/codechecker_server/migrations/config/versions/5e1501dfd333_implemented_keeping_track_of_background_.py @@ -0,0 +1,81 @@ +""" +Implemented keeping track of background tasks through corresponding records +in the server-wide configuration database. + +Revision ID: 5e1501dfd333 +Revises: 7ed50f8b3fb8 +Create Date: 2025-06-13 14:05:15.517337 +""" + +from logging import getLogger + +from alembic import op +import sqlalchemy as sa + + +# Revision identifiers, used by Alembic. +revision = '5e1501dfd333' +down_revision = '7ed50f8b3fb8' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "background_tasks", + sa.Column("machine_id", sa.String(), nullable=True), + sa.Column("token", sa.CHAR(length=64), nullable=False), + sa.Column("kind", sa.String(), nullable=False), + sa.Column("status", sa.Enum("allocated", + "enqueued", + "running", + "completed", + "failed", + "cancelled", + "dropped", + name="background_task_statuses"), + nullable=False), + sa.Column("product_id", sa.Integer(), nullable=True), + sa.Column("summary", sa.String(), nullable=False), + sa.Column("comments", sa.Text(), nullable=True), + sa.Column("username", sa.String(), nullable=True), + sa.Column("enqueued_at", sa.DateTime(), nullable=True), + sa.Column("started_at", sa.DateTime(), nullable=True), + sa.Column("finished_at", sa.DateTime(), nullable=True), + sa.Column("last_seen_at", sa.DateTime(), nullable=True), + sa.Column("consumed", sa.Boolean(), nullable=False, + server_default=sa.false()), + sa.Column("cancel_flag", sa.Boolean(), nullable=False, + server_default=sa.false()), + + sa.ForeignKeyConstraint( + ["product_id"], ["products.id"], + name=op.f("fk_background_tasks_product_id_products"), + deferrable=False, + ondelete="CASCADE", + initially="IMMEDIATE"), + sa.PrimaryKeyConstraint("token", name=op.f("pk_background_tasks")) + ) + op.create_index(op.f("ix_background_tasks_kind"), "background_tasks", + ["kind"], unique=False) + op.create_index(op.f("ix_background_tasks_machine_id"), "background_tasks", + ["machine_id"], unique=False) + op.create_index(op.f("ix_background_tasks_product_id"), "background_tasks", + ["product_id"], unique=False) + op.create_index(op.f("ix_background_tasks_status"), "background_tasks", + ["status"], unique=False) + + +def downgrade(): + ctx = op.get_context() + dialect = ctx.dialect.name + + op.drop_index(op.f("ix_background_tasks_status"), "background_tasks") + op.drop_index(op.f("ix_background_tasks_product_id"), "background_tasks") + op.drop_index(op.f("ix_background_tasks_machine_id"), "background_tasks") + op.drop_index(op.f("ix_background_tasks_kind"), "background_tasks") + + op.drop_table("action_history") + + if dialect == "postgresql": + op.execute("DROP TYPE background_task_statuses;") diff --git a/web/server/codechecker_server/routing.py b/web/server/codechecker_server/routing.py index 1b6ba6aa51..813d5dd7d3 100644 --- a/web/server/codechecker_server/routing.py +++ b/web/server/codechecker_server/routing.py @@ -15,25 +15,28 @@ from codechecker_web.shared.version import SUPPORTED_VERSIONS -# A list of top-level path elements under the webserver root -# which should not be considered as a product route. -NON_PRODUCT_ENDPOINTS = ['index.html', - 'images', - 'docs', - 'live', - 'ready'] +# A list of top-level path elements under the webserver root which should not +# be considered as a product route. +NON_PRODUCT_ENDPOINTS = ["index.html", + "images", + "docs", + "live", + "ready", + ] # A list of top-level path elements in requests (such as Thrift endpoints) # which should not be considered as a product route. -NON_PRODUCT_ENDPOINTS += ['Authentication', - 'Products', - 'CodeCheckerService'] +NON_PRODUCT_ENDPOINTS += ["Authentication", + "Products", + "CodeCheckerService", + "Tasks", + ] # A list of top-level path elements under the webserver root which should -# be protected by authentication requirement when accessing the server. +# be protected by authentication requirements when accessing the server. PROTECTED_ENTRY_POINTS = ['', # Empty string in a request is 'index.html'. - 'index.html'] + "index.html"] def is_valid_product_endpoint(uripart): @@ -69,7 +72,6 @@ def is_supported_version(version): If supported, returns the major and minor version as a tuple. """ - version = version.lstrip('v') version_parts = version.split('.') if len(version_parts) < 2: @@ -116,9 +118,8 @@ def split_client_POST_request(path): Returns the product endpoint, the API version and the API service endpoint as a tuple of 3. """ - # A standard POST request from an API client looks like: - # http://localhost:8001/[product-name/]/ + # http://localhost:8001/[product-name/]v/ # where specifying the product name is optional. split_path = urlparse(path).path.split('/', 3) diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index 9b8db67e9c..a9016e4f34 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -12,6 +12,7 @@ import atexit +from collections import Counter import datetime from functools import partial from http.server import HTTPServer, SimpleHTTPRequestHandler @@ -21,9 +22,9 @@ import socket import ssl import sys -from typing import List, Optional, Tuple +import time +from typing import Dict, List, Optional, Tuple, cast -import multiprocess from sqlalchemy.orm import sessionmaker from sqlalchemy.engine.url import make_url from sqlalchemy.sql.expression import func @@ -43,11 +44,13 @@ codeCheckerProductService as ProductAPI_v6 from codechecker_api.ServerInfo_v6 import \ serverInfoService as ServerInfoAPI_v6 +from codechecker_api.codeCheckerServersideTasks_v6 import \ + codeCheckerServersideTaskService as TaskAPI_v6 from codechecker_common import util -from codechecker_common.logger import get_logger from codechecker_common.compatibility.multiprocessing import \ - Pool, cpu_count + Pool, Process, Queue, Value, cpu_count +from codechecker_common.logger import get_logger, signal_log from codechecker_web.shared import database_status from codechecker_web.shared.version import get_version_str @@ -59,11 +62,16 @@ from .api.report_server import ThriftRequestHandler as ReportHandler_v6 from .api.server_info_handler import \ ThriftServerInfoHandler as ServerInfoHandler_v6 +from .api.tasks import ThriftTaskHandler as TaskHandler_v6 from .database import database, db_cleanup from .database.config_db_model import Product as ORMProduct, \ Configuration as ORMConfiguration from .database.database import DBSession from .database.run_db_model import IDENTIFIER as RUN_META, Run, RunLock +from .task_executors.main import executor as background_task_executor +from .task_executors.task_manager import \ + TaskManager as BackgroundTaskManager, drop_all_incomplete_tasks + LOG = get_logger('server') @@ -83,8 +91,8 @@ def __init__(self, request, client_address, server): self.path = None super().__init__(request, client_address, server) - def log_message(self, *args): - """ Silencing http server. """ + def log_message(self, *_args): + """Silencing HTTP server.""" return def send_thrift_exception(self, error_msg, iprot, oprot, otrans): @@ -102,7 +110,7 @@ def send_thrift_exception(self, error_msg, iprot, oprot, otrans): result = otrans.getvalue() self.send_response(200) self.send_header("content-type", "application/x-thrift") - self.send_header("Content-Length", len(result)) + self.send_header("Content-Length", str(len(result))) self.end_headers() self.wfile.write(result) @@ -432,23 +440,23 @@ def do_POST(self): major_version, _ = version_supported if major_version == 6: - if request_endpoint == 'Authentication': + if request_endpoint == "Authentication": auth_handler = AuthHandler_v6( self.server.manager, self.auth_session, self.server.config_session) processor = AuthAPI_v6.Processor(auth_handler) - elif request_endpoint == 'Configuration': + elif request_endpoint == "Configuration": conf_handler = ConfigHandler_v6( self.auth_session, self.server.config_session, self.server.manager) processor = ConfigAPI_v6.Processor(conf_handler) - elif request_endpoint == 'ServerInfo': + elif request_endpoint == "ServerInfo": server_info_handler = ServerInfoHandler_v6(version) processor = ServerInfoAPI_v6.Processor( server_info_handler) - elif request_endpoint == 'Products': + elif request_endpoint == "Products": prod_handler = ProductHandler_v6( self.server, self.auth_session, @@ -456,7 +464,13 @@ def do_POST(self): product, version) processor = ProductAPI_v6.Processor(prod_handler) - elif request_endpoint == 'CodeCheckerService': + elif request_endpoint == "Tasks": + task_handler = TaskHandler_v6( + self.server.config_session, + self.server.task_manager, + self.auth_session) + processor = TaskAPI_v6.Processor(task_handler) + elif request_endpoint == "CodeCheckerService": # This endpoint is a product's report_server. if not product: error_msg = \ @@ -792,7 +806,10 @@ def __init__(self, pckg_data, context, check_env, - manager): + manager: session_manager.SessionManager, + machine_id: str, + task_queue: Queue, + server_shutdown_flag: Value): LOG.debug("Initializing HTTP server...") @@ -803,6 +820,7 @@ def __init__(self, self.context = context self.check_env = check_env self.manager = manager + self.address, self.port = server_address self.__products = {} # Create a database engine for the configuration database. @@ -811,6 +829,12 @@ def __init__(self, self.config_session = sessionmaker(bind=self.__engine) self.manager.set_database_connection(self.config_session) + self.__task_queue = task_queue + self.task_manager = BackgroundTaskManager(task_queue, + self.config_session, + server_shutdown_flag, + machine_id) + # Load the initial list of products and set up the server. cfg_sess = self.config_session() permissions.initialise_defaults('SYSTEM', { @@ -830,7 +854,7 @@ def __init__(self, cfg_sess.close() try: - HTTPServer.__init__(self, server_address, + HTTPServer.__init__(self, (self.address, self.port), RequestHandlerClass, bind_and_activate=True) ssl_key_file = os.path.join(config_directory, "key.pem") @@ -858,13 +882,23 @@ def __init__(self, server_side=True) else: LOG.info("Searching for SSL key at %s, cert at %s, " - "not found...", ssl_key_file, ssl_cert_file) + "not found!", ssl_key_file, ssl_cert_file) LOG.info("Falling back to simple, insecure HTTP.") except Exception as e: LOG.error("Couldn't start the server: %s", e.__str__()) raise + # If the server was started with the port 0, the OS will pick an + # available port. + # For this reason, we will update the port variable after server + # ininitialisation. + self.port = self.socket.getsockname()[1] + + @property + def formatted_address(self) -> str: + return f"{str(self.address)}:{self.port}" + def configure_keepalive(self): """ Enable keepalive on the socket and some TCP keepalive configuration @@ -907,17 +941,40 @@ def configure_keepalive(self): LOG.error('Failed to set TCP max keepalive probe: %s', ret) def terminate(self): - """ - Terminating the server. - """ + """Terminates the server and releases associated resources.""" try: self.server_close() + self.__task_queue.close() + self.__task_queue.join_thread() self.__engine.dispose() + + sys.exit(128 + signal.SIGINT) except Exception as ex: LOG.error("Failed to shut down the WEB server!") LOG.error(str(ex)) sys.exit(1) + def serve_forever_with_shutdown_handler(self): + """ + Calls `HTTPServer.serve_forever` but handles SIGINT (2) signals + gracefully such that the open resources are properly cleaned up. + """ + def _handler(signum: int, _frame): + if signum not in [signal.SIGINT]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by " + "'serve_forever_with_shutdown_handler'!") + return + + signal_log(LOG, "DEBUG", f"{os.getpid()}: Received " + f"{signal.Signals(signum).name} ({signum}), " + "performing shutdown ...") + self.terminate() + + signal.signal(signal.SIGINT, _handler) + return self.serve_forever() + def add_product(self, orm_product, init_db=False): """ Adds a product to the list of product databases connected to @@ -1085,17 +1142,21 @@ class CCSimpleHttpServerIPv6(CCSimpleHttpServer): address_family = socket.AF_INET6 + @property + def formatted_address(self) -> str: + return f"[{str(self.address)}]:{self.port}" + -def start_server(config_directory, package_data, port, config_sql_server, - listen_address, force_auth, skip_db_cleanup: bool, - context, check_env): +def start_server(config_directory: str, package_data, port: int, + config_sql_server, listen_address: str, + force_auth: bool, skip_db_cleanup: bool, + context, check_env, machine_id: str) -> int: """ - Start http server to handle web client and thrift requests. + Starts the HTTP server to handle Web client and Thrift requests, execute + background jobs. """ LOG.debug("Starting CodeChecker server...") - server_addr = (listen_address, port) - # The root user file is DEPRECATED AND IGNORED root_file = os.path.join(config_directory, 'root.user') if os.path.exists(root_file): @@ -1156,92 +1217,445 @@ def start_server(config_directory, package_data, port, config_sql_server, else: LOG.debug("Skipping db_cleanup, as requested.") + def _cleanup_incomplete_tasks(action: str) -> int: + config_session_factory = config_sql_server.create_engine() + try: + return drop_all_incomplete_tasks( + sessionmaker(bind=config_session_factory), + machine_id, action) + finally: + config_session_factory.dispose() + + dropped_tasks = _cleanup_incomplete_tasks( + "New server started with the same machine_id, assuming the old " + "server is dead and won't be able to finish the task.") + if dropped_tasks: + LOG.info("At server startup, dropped %d background tasks left behind " + "by a previous server instance matching machine ID '%s'.", + dropped_tasks, machine_id) + + api_processes: Dict[int, Process] = {} + requested_api_threads = cast(int, manager.worker_processes) \ + or cpu_count() + + bg_processes: Dict[int, Process] = {} + requested_bg_threads = cast(int, + manager.background_worker_processes) \ + or requested_api_threads + # Note that Queue under the hood uses OS-level primitives such as a socket + # or a pipe, where the read-write buffers have a **LIMITED** capacity, and + # are usually **NOT** backed by the full amount of available system memory. + bg_task_queue: Queue = Queue() + is_server_shutting_down = Value('B', False) + server_clazz = CCSimpleHttpServer - if ':' in server_addr[0]: + if ':' in listen_address: # IPv6 address specified for listening. # FIXME: Python>=3.8 automatically handles IPv6 if ':' is in the bind # address, see https://bugs.python.org/issue24209. server_clazz = CCSimpleHttpServerIPv6 - http_server = server_clazz(server_addr, + http_server = server_clazz((listen_address, port), RequestHandler, config_directory, config_sql_server, package_data, context, check_env, - manager) + manager, + machine_id, + bg_task_queue, + is_server_shutting_down) - # If the server was started with the port 0, the OS will pick an available - # port. For this reason we will update the port variable after server - # initialization. - port = http_server.socket.getsockname()[1] + try: + instance_manager.register(os.getpid(), + os.path.abspath( + context.codechecker_workspace), + port) + except IOError as ex: + LOG.debug(ex.strerror) - processes = [] + def unregister_handler(pid): + # Handle errors during instance unregistration. + # The workspace might be removed so updating the config content might + # fail. + try: + instance_manager.unregister(pid) + except IOError as ex: + LOG.debug(ex.strerror) - def signal_handler(signum, _): + atexit.register(unregister_handler, os.getpid()) + + def _start_process_with_no_signal_handling(**kwargs): """ - Handle SIGTERM to stop the server running. + Starts a `multiprocessing.Process` in a context where the signal + handling is temporarily disabled, such that the child process does not + inherit any signal handling from the parent. + + Child processes spawned after the main process set up its signals + MUST NOT inherit the signal handling because that would result in + multiple children firing on the SIGTERM handler, for example. + + For this reason, we temporarily disable the signal handling here by + returning to the initial defaults, and then restore the main process's + signal handling to be the usual one. """ - LOG.info("Shutting down the WEB server on [%s:%d]", - '[' + listen_address + ']' - if server_clazz is CCSimpleHttpServerIPv6 else listen_address, - port) - http_server.terminate() + signals_to_disable = [signal.SIGINT, signal.SIGTERM] + if sys.platform != "win32": + signals_to_disable += [signal.SIGCHLD, signal.SIGHUP] - # Terminate child processes. - for pp in processes: - pp.terminate() + existing_signal_handlers = {} + for signum in signals_to_disable: + existing_signal_handlers[signum] = signal.signal( + signum, signal.SIG_DFL) + p = Process(**kwargs) + p.start() + + for signum in signals_to_disable: + signal.signal(signum, existing_signal_handlers[signum]) + + return p + + # Save a process-wide but not shared counter in the main process for how + # many subprocesses of each kind had been spawned, as this will be used in + # the internal naming of the workers. + spawned_api_proc_count: int = 0 + spawned_bg_proc_count: int = 0 + + def spawn_api_process(): + """Starts a single HTTP API worker process for CodeChecker server.""" + nonlocal spawned_api_proc_count + spawned_api_proc_count += 1 + + p = _start_process_with_no_signal_handling( + target=http_server.serve_forever_with_shutdown_handler, + name=f"CodeChecker-API-{spawned_api_proc_count}") + api_processes[cast(int, p.pid)] = p + signal_log(LOG, "DEBUG", f"API handler child process {p.pid} started!") + return p + + LOG.info("Using %d API request handler processes ...", + requested_api_threads) + for _ in range(requested_api_threads): + spawn_api_process() + + def spawn_bg_process(): + """Starts a single Task worker process for CodeChecker server.""" + nonlocal spawned_bg_proc_count + spawned_bg_proc_count += 1 + + p = _start_process_with_no_signal_handling( + target=background_task_executor, + args=(bg_task_queue, + config_sql_server, + is_server_shutting_down, + machine_id, + ), + name=f"CodeChecker-Task-{spawned_bg_proc_count}") + bg_processes[cast(int, p.pid)] = p + signal_log(LOG, "DEBUG", f"Task child process {p.pid} started!") + return p + + LOG.info("Using %d Task handler processes ...", requested_bg_threads) + for _ in range(requested_bg_threads): + spawn_bg_process() + + termination_signal_timestamp = Value('d', 0) + + def forced_termination_signal_handler(signum: int, _frame): + """ + Handle SIGINT (2) and SIGTERM (15) received a second time to stop the + server ungracefully. + """ + if signum not in [signal.SIGINT, signal.SIGTERM]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by " + "'forced_termination_signal_handler'!") + return + if not is_server_shutting_down.value or \ + abs(termination_signal_timestamp.value) <= \ + sys.float_info.epsilon: + return + if time.time() - termination_signal_timestamp.value <= 2.0: + # Allow some time to pass between the handling of the normal + # termination vs. doing something in the "forced" handler, because + # a human's ^C keypress in a terminal can generate multiple SIGINTs + # in a quick succession. + return + + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + + signal_log(LOG, "WARNING", "Termination signal " + f"<{signal.Signals(signum).name} ({signum})> " + "received a second time, **FORCE** killing the WEB server " + f"on [{http_server.formatted_address}] ...") + + for p in list(api_processes.values()) + list(bg_processes.values()): + try: + p.kill() + except (OSError, ValueError): + pass + + # No mercy this time. sys.exit(128 + signum) - def reload_signal_handler(*_args, **_kwargs): + exit_code = Value('B', 0) + + def termination_signal_handler(signum: int, _frame): """ - Reloads server configuration file. + Handle SIGINT (2) and SIGTERM (15) to stop the server gracefully. """ + # Debounce termination signals at this point. + signal.signal(signal.SIGINT, forced_termination_signal_handler) + signal.signal(signal.SIGTERM, forced_termination_signal_handler) + + if is_server_shutting_down.value: + return + if signum not in [signal.SIGINT, signal.SIGTERM]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by 'termination_signal_handler'!") + return + + is_server_shutting_down.value = True + termination_signal_timestamp.value = time.time() + + exit_code.value = 128 + signum + signal_log(LOG, "INFO", "Shutting down the WEB server on " + f"[{http_server.formatted_address}] ... " + "Please allow some time for graceful clean-up!") + + # Terminate child processes. + # For these subprocesses, let the processes properly clean up after + # themselves in a graceful shutdown scenario. + # For this reason, we fire a bunch of SIGHUPs first, indicating + # that the main server process wants to exit, and then wait for + # the children to die once all of them got the signal. + for pid in api_processes: + try: + signal_log(LOG, "DEBUG", f"SIGINT! API child PID: {pid} ...") + os.kill(pid, signal.SIGINT) + except (OSError, ValueError): + pass + for pid in list(api_processes.keys()): + p = api_processes[pid] + try: + signal_log(LOG, "DEBUG", f"join() API child PID: {pid} ...") + p.join() + p.close() + except (OSError, ValueError): + pass + finally: + del api_processes[pid] + + bg_task_queue.close() + bg_task_queue.join_thread() + for pid in bg_processes: + try: + signal_log(LOG, "DEBUG", f"SIGHUP! Task child PID: {pid} ...") + os.kill(pid, signal.SIGHUP) + except (OSError, ValueError): + pass + for pid in list(bg_processes.keys()): + p = bg_processes[pid] + try: + signal_log(LOG, "DEBUG", f"join() Task child PID: {pid} ...") + p.join() + p.close() + except (OSError, ValueError): + pass + finally: + del bg_processes[pid] + + def reload_signal_handler(signum: int, _frame): + """ + Handle SIGHUP (1) to reload the server's configuration file to memory. + """ + if signum not in [signal.SIGHUP]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by 'reload_signal_handler'!") + return + + signal_log(LOG, "INFO", + "Received signal to reload server configuration ...") + manager.reload_config() - try: - instance_manager.register(os.getpid(), - os.path.abspath( - context.codechecker_workspace), - port) - except IOError as ex: - LOG.debug(ex.strerror) + signal_log(LOG, "INFO", "Server configuration reload: Done.") - LOG.info("Server waiting for client requests on [%s:%d]", - '[' + listen_address + ']' - if server_clazz is CCSimpleHttpServerIPv6 else listen_address, - port) + sigchild_event_counter = Value('I', 0) + is_already_handling_sigchild = Value('B', False) - def unregister_handler(pid): + def child_signal_handler(signum: int, _frame): """ - Handle errors during instance unregistration. - The workspace might be removed so updating the - config content might fail. + Handle SIGCHLD (17) that signals a child process's interruption or + death by creating a new child to ensure that the requested number of + workers are always alive. """ - try: - instance_manager.unregister(pid) - except IOError as ex: - LOG.debug(ex.strerror) + if is_already_handling_sigchild.value: + # Do not perform this handler recursively to prevent spawning too + # many children. + return + if is_server_shutting_down.value: + # Do not handle SIGCHLD events during normal shutdown, because + # our own subprocess termination calls would fire this handler. + return + if signum not in [signal.SIGCHLD]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by 'child_signal_handler'!") + return - atexit.register(unregister_handler, os.getpid()) + is_already_handling_sigchild.value = True - for _ in range(manager.worker_processes - 1): - p = multiprocess.Process(target=http_server.serve_forever) - processes.append(p) - p.start() + force_slow_path: bool = False + event_counter: int = sigchild_event_counter.value + if event_counter >= \ + min(requested_api_threads, requested_bg_threads) // 2: + force_slow_path = True + else: + sigchild_event_counter.value = event_counter + 1 - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) + # How many new processes need to be spawned for each type of worker + # process? + spawn_needs: Counter = Counter() + def _check_process_one(kind: str, proclist: Dict[int, Process], + pid: int): + try: + p = proclist[pid] + except KeyError: + return + + # Unfortunately, "Process.is_alive()" cannot be used here, because + # during the handling of SIGCHLD during a child's death, according + # to the state of Python's data structures, the child is still + # alive. + # We run a low-level non-blocking wait again, which will + # immediately return, but properly reap the child process if it has + # terminated. + try: + _, status_signal = os.waitpid(pid, os.WNOHANG) + if status_signal == 0: + # The process is still alive. + return + except ChildProcessError: + pass + + signal_log(LOG, "WARNING", + f"'{kind}' child process (PID {pid}, \"{p.name}\") " + "is not alive anymore!") + spawn_needs[kind] += 1 + + try: + del proclist[pid] + except KeyError: + # Due to the bunching up of signals and that Python runs the + # C-level signals with a custom logic inside the interpreter, + # coupled with the fact that PIDs can be reused, the same PID + # can be reported dead in a quick succession of signals, + # resulting in a KeyError here. + pass + + def _check_processes_many(kind: str, proclist: Dict[int, Process]): + for pid in sorted(proclist.keys()): + _check_process_one(kind, proclist, pid) + + # Try to find the type of the interrupted/dead process based on signal + # information first. + # This should be quicker and more deterministic. + try: + child_pid, child_signal = os.waitpid(-1, os.WNOHANG) + if child_signal == 0: + # Go to the slow path and check the children manually, we did + # not receive a reply from waitpid() with an actual dead child. + raise ChildProcessError() + + _check_process_one("api", api_processes, child_pid) + _check_process_one("background", bg_processes, child_pid) + except ChildProcessError: + # We have not gotten a PID, or it was not found, so we do not know + # who died; in this case, it is better to go on the slow path and + # query all our children individually. + spawn_needs.clear() # Forces the Counter to be empty. + + if force_slow_path: + # A clever sequence of child killings in variously sized batches + # can easily result in missing a few signals here and there, and + # missing a few dead children because 'os.waitpid()' allows us to + # fall into a false "fast path" situation. + # To remedy this, we every so often force a slow path to ensure + # the number of worker processes is as close to the requested + # amount of possible. + + # Forces the Counter to be empty, even if the fast path put an + # entry in there. + spawn_needs.clear() + + if not spawn_needs: + _check_processes_many("api", api_processes) + _check_processes_many("background", bg_processes) + + if force_slow_path: + sigchild_event_counter.value = 0 + signal_log(LOG, "WARNING", + "Too many children died since last full status " + "check, performing one ...") + + # If we came into the handler with a "forced slow path" situation, + # ensure that we spawn enough new processes to backfill the + # missing amount, even if due to the flakyness of signal handling, + # we might not have actually gotten "N" times SIGCHLD firings for + # the death of N children, if they happened in a bundle situation, + # e.g., kill N/4, then kill N/2, then kill 1 or 2, then kill the + # remaining. + spawn_needs["api"] = \ + util.clamp(0, requested_api_threads - len(api_processes), + requested_api_threads) + spawn_needs["background"] = \ + util.clamp(0, requested_bg_threads - len(bg_processes), + requested_bg_threads) + + for kind, num in spawn_needs.items(): + signal_log(LOG, "INFO", + f"(Re-)starting {num} '{kind}' child process(es) ...") + + if kind == "api": + for _ in range(num): + spawn_api_process() + elif kind == "background": + for _ in range(num): + spawn_bg_process() + + is_already_handling_sigchild.value = False + + signal.signal(signal.SIGINT, termination_signal_handler) + signal.signal(signal.SIGTERM, termination_signal_handler) if sys.platform != "win32": + signal.signal(signal.SIGCHLD, child_signal_handler) signal.signal(signal.SIGHUP, reload_signal_handler) - # Main process also acts as a worker. - http_server.serve_forever() + LOG.info("Server waiting for client requests on [%s]", + http_server.formatted_address) + + # We can not use a multiprocessing.Event here because that would result in + # a deadlock, as the process waiting on the event is the one receiving the + # shutdown signal. + while not is_server_shutting_down.value: + time.sleep(5) + + dropped_tasks = _cleanup_incomplete_tasks("Server shut down, task will " + "be never be completed.") + if dropped_tasks: + LOG.info("At server shutdown, dropped %d background tasks that will " + "never be completed.", dropped_tasks) - LOG.info("Webserver quit.") + LOG.info("CodeChecker server quit (main process).") + return exit_code.value def add_initial_run_database(config_sql_server, product_connection): diff --git a/web/server/codechecker_server/session_manager.py b/web/server/codechecker_server/session_manager.py index 2c1caceb43..6d310b2d7a 100644 --- a/web/server/codechecker_server/session_manager.py +++ b/web/server/codechecker_server/session_manager.py @@ -20,7 +20,7 @@ from codechecker_common.compatibility.multiprocessing import cpu_count from codechecker_common.logger import get_logger -from codechecker_common.util import load_json +from codechecker_common.util import generate_random_token, load_json from codechecker_web.shared.env import check_file_owner_rw from codechecker_web.shared.version import SESSION_COOKIE_NAME as _SCN @@ -61,18 +61,25 @@ def get_worker_processes(scfg_dict): """ Return number of worker processes from the config dictionary. - Return 'worker_processes' field from the config dictionary or returns the - default value if this field is not set or the value is negative. + Return 'worker_processes' and 'background_worker_processes' fields from + the config dictionary or returns the default value if this field is not + set or the value is negative. """ default = cpu_count() - worker_processes = scfg_dict.get('worker_processes', default) + worker_processes = scfg_dict.get("worker_processes", default) + background_worker_processes = scfg_dict.get("background_worker_processes", + default) - if worker_processes < 0: + if not worker_processes or worker_processes < 0: LOG.warning("Number of worker processes can not be negative! Default " "value will be used: %s", default) worker_processes = default + if not background_worker_processes or background_worker_processes < 0: + LOG.warning("Number of task worker processes can not be negative! " + "Default value will be used: %s", worker_processes) + background_worker_processes = worker_processes - return worker_processes + return worker_processes, background_worker_processes class _Session: @@ -184,7 +191,8 @@ def __init__(self, configuration_file, force_auth=False): # so it should NOT be handled by session_manager. A separate config # handler for the server's stuff should be created, that can properly # instantiate SessionManager with the found configuration. - self.__worker_processes = get_worker_processes(self.scfg_dict) + self.__worker_processes, self.__background_worker_processes = \ + get_worker_processes(self.scfg_dict) self.__max_run_count = self.scfg_dict.get('max_run_count', None) self.__store_config = self.scfg_dict.get('store', {}) self.__keepalive_config = self.scfg_dict.get('keepalive', {}) @@ -468,6 +476,10 @@ def is_enabled(self): def worker_processes(self): return self.__worker_processes + @property + def background_worker_processes(self) -> int: + return self.__background_worker_processes + def get_realm(self): return { "realm": self.__auth_config.get('realm_name'), @@ -822,7 +834,7 @@ def create_session(self, auth_string): return False # Generate a new token and create a local session. - token = generate_session_token() + token = generate_random_token(32) user_name = validation.get('username') groups = validation.get('groups', []) is_root = validation.get('root', False) diff --git a/web/server/codechecker_server/task_executors/__init__.py b/web/server/codechecker_server/task_executors/__init__.py new file mode 100644 index 0000000000..4259749345 --- /dev/null +++ b/web/server/codechecker_server/task_executors/__init__.py @@ -0,0 +1,7 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- diff --git a/web/server/codechecker_server/task_executors/abstract_task.py b/web/server/codechecker_server/task_executors/abstract_task.py new file mode 100644 index 0000000000..34e1cd4d7a --- /dev/null +++ b/web/server/codechecker_server/task_executors/abstract_task.py @@ -0,0 +1,183 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Contains the base class to be inherited and implemented by all background task +types. +""" +import os +import pathlib +import shutil +from typing import Optional + +from codechecker_common.logger import get_logger + +from ..database.config_db_model import BackgroundTask as DBTask + + +LOG = get_logger("server") + + +class TaskCancelHonoured(Exception): + """ + Specialised tag exception raised by `AbstractTask` implementations in a + checkpoint after having checked that their ``cancel_flag`` was set, in + order to terminate task-specific execution and to register the + cancellation's success by the `AbstractTask.execute` method. + + This exception should **NOT** be caught by user code. + """ + + def __init__(self, task_obj: "AbstractTask"): + super().__init__(f"Task '{task_obj.token}' honoured CANCEL request.") + self.task_obj = task_obj + + +class AbstractTask: + """ + Base class implementing common execution and bookkeeping methods to + facilitate the dispatch of tasks to background worker processes. + + Instances of this class **MUST** be marshallable by ``pickle``, as they + are transported over an IPC `Queue`. + It is important that instances do not grow too large, as the underlying + OS-level primitives of a `Queue` can get full, which can result in a + deadlock situation. + + The run-time contents of the instance should only contain the bare minimum + metadata required for the implementation to execute in the background. + + Implementors of subclasses **MAY REASONABLY ASSUME** that an + `AbstractTask` scheduled in the API handler process of a server will be + actually executed by a background worker in the same process group, on the + same machine instance. + """ + + def __init__(self, token: str, data_path: Optional[pathlib.Path]): + self._token = token + self._data_path = data_path + + @property + def token(self) -> str: + """Returns the task's identifying token, its primary ID.""" + return self._token + + @property + def data_path(self) -> Optional[pathlib.Path]: + """ + Returns the filesystem path where the task's input data is prepared. + """ + return self._data_path + + def destroy_data(self): + """ + Deletes the contents of `data_path`. + """ + if not self._data_path: + return + + try: + shutil.rmtree(self._data_path) + except Exception as ex: + LOG.warning("Failed to remove background task's data_dir at " + "'%s':\n%s", self.data_path, str(ex)) + + def _implementation(self, _task_manager: "TaskManager") -> None: + """ + Implemented by subclasses to perform the logic specific to the task. + + Subclasses should use the `task_manager` object, injected from the + context of the executed subprocess, to query and mutate service-level + information about the current task. + """ + raise NotImplementedError() + + def execute(self, task_manager: "TaskManager") -> None: + """ + Executes the `_implementation` of the task, overridden by subclasses, + to perform a task-specific business logic. + + This high-level wrapper deals with capturing `Exception`s, setting + appropriate status information in the database (through the + injected `task_manager`) and logging failures accordingly. + """ + if task_manager.should_cancel(self): + return + + try: + task_manager._mutate_task_record( + self, lambda dbt: dbt.set_running()) + except KeyError: + # KeyError is thrown if a task without a corresponding database + # record is attempted to be executed. + LOG.error("Failed to execute task '%s' due to database exception", + self.token) + except Exception as ex: + LOG.error("Failed to execute task '%s' due to database exception" + "\n%s", + self.token, str(ex)) + # For any other record, try to set the task abandoned due to an + # exception. + try: + task_manager._mutate_task_record( + self, lambda dbt: + dbt.set_abandoned(force_dropped_status=True)) + except Exception: + return + + LOG.debug("Task '%s' running on machine '%s' executor #%d", + self.token, task_manager.machine_id, os.getpid()) + + try: + self._implementation(task_manager) + LOG.debug("Task '%s' finished on machine '%s' executor #%d", + self.token, + task_manager.machine_id, + os.getpid()) + + try: + task_manager._mutate_task_record( + self, lambda dbt: dbt.set_finished(successfully=True)) + except Exception as ex: + LOG.error("Failed to set task '%s' finished due to " + "database exception:\n%s", + self.token, str(ex)) + except TaskCancelHonoured: + def _log_cancel_and_abandon(db_task: DBTask): + db_task.add_comment("CANCEL!\nCancel request of admin " + "honoured by task.", + "SYSTEM[AbstractTask::execute()]") + db_task.set_abandoned(force_dropped_status=False) + + def _log_drop_and_abandon(db_task: DBTask): + db_task.add_comment("SHUTDOWN!\nTask honoured graceful " + "cancel signal generated by " + "server shutdown.", + "SYSTEM[AbstractTask::execute()]") + db_task.set_abandoned(force_dropped_status=True) + + if not task_manager.is_shutting_down: + task_manager._mutate_task_record(self, _log_cancel_and_abandon) + else: + task_manager._mutate_task_record(self, _log_drop_and_abandon) + except Exception as ex: + LOG.error("Failed to execute task '%s' on machine '%s' " + "executor #%d: %s", + self.token, task_manager.machine_id, os.getpid(), + str(ex)) + import traceback + traceback.print_exc() + + def _log_exception_and_fail(db_task: DBTask): + db_task.add_comment( + f"FAILED!\nException during execution:\n{str(ex)}", + "SYSTEM[AbstractTask::execute()]") + db_task.set_finished(successfully=False) + + task_manager._mutate_task_record(self, _log_exception_and_fail) + finally: + self.destroy_data() diff --git a/web/server/codechecker_server/task_executors/main.py b/web/server/codechecker_server/task_executors/main.py new file mode 100644 index 0000000000..dc9dd4e543 --- /dev/null +++ b/web/server/codechecker_server/task_executors/main.py @@ -0,0 +1,142 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Implements a dedicated subprocess that deals with running `AbstractTask` +subclasses in the background. +""" +from datetime import timedelta +import os +from queue import Empty +import signal + +from sqlalchemy.orm import sessionmaker + +from codechecker_common.compatibility.multiprocessing import Queue, Value +from codechecker_common.logger import get_logger, signal_log + +from ..database.config_db_model import BackgroundTask as DBTask +from .abstract_task import AbstractTask +from .task_manager import TaskManager + + +WAIT_TIME_FOR_TASK_QUEUE_CLEARING_AT_SERVER_SHUTDOWN = timedelta(seconds=5) + +LOG = get_logger("server") + + +def executor(queue: Queue, + config_db_sql_server, + server_shutdown_flag: "Value", + machine_id: str): + """ + The "main()" function implementation for a background task executor + process. + + This process sets up the state of the local process, and then deals with + popping jobs from the queue and executing them in the local context. + """ + # First things first, a background worker process should NOT respect the + # termination signals received from the parent process, because it has to + # run its own cleanup logic before shutting down. + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + + kill_flag = Value('B', False) + + def executor_hangup_handler(signum: int, _frame): + """ + Handle SIGHUP (1) to do a graceful shutdown of the background worker. + """ + if signum not in [signal.SIGHUP]: + signal_log(LOG, "ERROR", "Signal " + f"<{signal.Signals(signum).name} ({signum})> " + "handling attempted by 'executor_hangup_handler'!") + return + + signal_log(LOG, "DEBUG", f"{os.getpid()}: Received " + f"{signal.Signals(signum).name} ({signum}), preparing for " + "shutdown ...") + kill_flag.value = True + + signal.signal(signal.SIGHUP, executor_hangup_handler) + + config_db_engine = config_db_sql_server.create_engine() + tm = TaskManager(queue, sessionmaker(bind=config_db_engine), kill_flag, + machine_id) + + while not kill_flag.value: + try: + # Do not block indefinitely when waiting for a job, to allow + # checking whether the kill flags were set. + t: AbstractTask = queue.get(block=True, timeout=1) + except Empty: + continue + + import pprint + LOG.info("Executor #%d received task object:\n\n%s:\n%s\n\n", + os.getpid(), t, pprint.pformat(t.__dict__)) + + t.execute(tm) + + # Once the main loop of task execution process has finished, there might + # still be tasks left in the queue. + # If the server is shutting down (this is distinguished from the local kill + # flag, because a 'SIGHUP' might arrive from any source, not just a valid + # graceful shutdown!), then these jobs would be lost if the process just + # exited, with no information reported to the database. + # We need set these tasks to dropped as much as possible. + def _log_shutdown_and_abandon(db_task: DBTask): + db_task.add_comment("SHUTDOWN!\nTask never started due to the " + "server shutdown!", "SYSTEM") + db_task.set_abandoned(force_dropped_status=True) + + def _drop_task_at_shutdown(t: AbstractTask): + try: + LOG.debug("Dropping task '%s' due to server shutdown...", t.token) + tm._mutate_task_record(t, _log_shutdown_and_abandon) + except Exception: + pass + finally: + t.destroy_data() + + if server_shutdown_flag.value: + # Unfortunately, it is not guaranteed which process will wake up first + # when popping objects from the queue. + # Blocking indefinitely would not be a solution here, because all + # producers (API threads) had likely already exited at this point. + # However, simply observing no elements for a short period of time is + # also not enough, as at the very last moments of a server's lifetime, + # one process might observe the queue to be empty, simply because + # another process stole the object that was put into it. + # + # To be on the safe side of things, we require to observe the queue to + # be *constantly* empty over a longer period of repetitive sampling. + empty_sample_count: int = 0 + while empty_sample_count < int( + WAIT_TIME_FOR_TASK_QUEUE_CLEARING_AT_SERVER_SHUTDOWN + .total_seconds()): + try: + t: AbstractTask = queue.get(block=True, timeout=1) + except Empty: + empty_sample_count += 1 + continue + + empty_sample_count = 0 + _drop_task_at_shutdown(t) + + queue.close() + queue.join_thread() + + try: + config_db_engine.dispose() + except Exception as ex: + LOG.error("Failed to shut down task executor!\n%s", str(ex)) + return + + LOG.debug("Task executor subprocess PID %d exited main loop.", + os.getpid()) diff --git a/web/server/codechecker_server/task_executors/task_manager.py b/web/server/codechecker_server/task_executors/task_manager.py new file mode 100644 index 0000000000..ddd3b31053 --- /dev/null +++ b/web/server/codechecker_server/task_executors/task_manager.py @@ -0,0 +1,229 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Contains status management and query methods to handle bookkeeping for +dispatched background tasks. +""" +import os +from pathlib import Path +import tempfile +from typing import Callable, Optional + +import sqlalchemy + +from codechecker_common.compatibility.multiprocessing import Queue, Value +from codechecker_common.logger import get_logger, signal_log +from codechecker_common.util import generate_random_token + +from ..database.config_db_model import BackgroundTask as DBTask, Product +from ..database.database import DBSession + +MAX_TOKEN_RANDOM_RETRIES = 10 + +LOG = get_logger("server") + + +class ExecutorInProgressShutdownError(Exception): + """ + Exception raised to indicate that the background executors are under + shutdown. + """ + def __init__(self): + super().__init__("Task executor is shutting down!") + + +class TaskManager: + """ + Handles the creation of "Task" status objects in the database and pushing + in-memory `AbstractTask` subclass instances to a `Queue`. + + This class is instantiatied for EVERY WORKER separately, and is not a + shared resource! + """ + + def __init__(self, q: Queue, config_db_session_factory, + executor_kill_flag: Value, machine_id: str): + self._queue = q + self._database_factory = config_db_session_factory + self._is_shutting_down = executor_kill_flag + self._machine_id = machine_id + + @property + def machine_id(self) -> str: + """Returns the ``machine_id`` the instance was constructed with.""" + return self._machine_id + + def allocate_task_record(self, kind: str, summary: str, + user_name: Optional[str], + product: Optional[Product] = None) -> str: + """ + Creates the token and the status record for a new task with the given + initial metadata. + + Returns the token of the task, which is a unique identifier of the + allocated record. + """ + try_count: int = 0 + while True: + with DBSession(self._database_factory) as session: + try: + token = generate_random_token(DBTask._token_length) + + task = DBTask(token, kind, summary, self.machine_id, + user_name, product) + session.add(task) + session.commit() + + return token + except sqlalchemy.exc.IntegrityError as ie: + # The only failure that can happen is the PRIMARY KEY's + # UNIQUE violation, which means we hit jackpot by + # generating an already used token! + try_count += 1 + + if try_count >= MAX_TOKEN_RANDOM_RETRIES: + raise KeyError( + "Failed to generate a unique ID for task " + f"{kind} ({summary}) after " + f"{MAX_TOKEN_RANDOM_RETRIES} retries!") from ie + + def create_task_data(self, token: str) -> Path: + """ + Creates a temporary directory which is **NOT** cleaned up + automatically, and suitable for putting arbitrary files underneath + to communicate large inputs (that should not be put in the `Queue`) + to the `execute` method of an `AbstractTask`. + """ + task_tmp_root = Path(tempfile.gettempdir()) / "codechecker_tasks" \ + / self.machine_id + os.makedirs(task_tmp_root, exist_ok=True) + + task_tmp_dir = tempfile.mkdtemp(prefix=f"{token}-") + return Path(task_tmp_dir) + + def _get_task_record(self, task_obj: "AbstractTask") -> DBTask: + """ + Retrieves the `DBTask` for the task identified by `task_obj`. + + This class should not be mutated, only the fields queried. + """ + with DBSession(self._database_factory) as session: + try: + db_task = session.query(DBTask).get(task_obj.token) + session.expunge(db_task) + return db_task + except sqlalchemy.exc.SQLAlchemyError as sql_err: + raise KeyError(f"No task record for token '{task_obj.token}' " + "in the database") from sql_err + + def _mutate_task_record(self, task_obj: "AbstractTask", + mutator: Callable[[DBTask], None]): + """ + Executes the given `mutator` function for the `DBTask` record + corresponding to the `task_obj` description available in memory. + """ + with DBSession(self._database_factory) as session: + try: + db_record = session.query(DBTask).get(task_obj.token) + except sqlalchemy.exc.SQLAlchemyError as sql_err: + raise KeyError(f"No task record for token '{task_obj.token}' " + "in the database") from sql_err + + try: + mutator(db_record) + except Exception: + session.rollback() + + import traceback + traceback.print_exc() + raise + + session.commit() + + def push_task(self, task_obj: "AbstractTask"): + """Enqueues the given `task_obj` onto the `Queue`.""" + if self.is_shutting_down: + raise ExecutorInProgressShutdownError() + + # Note, that the API handler process calling push_task() might be + # killed before writing to the queue, so an actually enqueued task + # (according to the DB) might never be consumed by a background + # process. + # As we have to COMMIT the status change before the actual processing + # in order to show the time stamp to the user(s), there is no better + # way to make this more atomic. + try: + self._mutate_task_record(task_obj, lambda dbt: dbt.set_enqueued()) + self._queue.put(task_obj) + except SystemExit as sex: + try: + signal_log(LOG, "WARNING", f"Process #{os.getpid()}: " + "push_task() killed via SystemExit during " + f"enqueue of task '{task_obj.token}'!") + + def _log_and_abandon(db_task: DBTask): + db_task.add_comment( + "SHUTDOWN!\nEnqueueing process terminated during the " + "ongoing enqueue! The task will never be executed!", + "SYSTEM[TaskManager::push_task()]") + db_task.set_abandoned(force_dropped_status=True) + + self._mutate_task_record(task_obj, _log_and_abandon) + finally: + raise sex + + @property + def is_shutting_down(self) -> bool: + """ + Returns whether the shutdown flag for the executor associated with the + `TaskManager` had been set. + """ + return self._is_shutting_down.value + + def should_cancel(self, task_obj: "AbstractTask") -> bool: + """ + Returns whether the task identified by `task_obj` should be + co-operatively cancelled. + """ + db_task = self._get_task_record(task_obj) + return self.is_shutting_down or \ + (db_task.status in ["enqueued", "running"] + and db_task.cancel_flag) + + def heartbeat(self, task_obj: "AbstractTask"): + """ + Triggers ``heartbeat()`` timestamp update in the database for + `task_obj`. + """ + self._mutate_task_record(task_obj, lambda dbt: dbt.heartbeat()) + + +def drop_all_incomplete_tasks(config_db_session_factory, machine_id: str, + action: str) -> int: + """ + Sets all tasks in the database (reachable via `config_db_session_factory`) + that were associated with the given `machine_id` to ``"dropped"`` status, + indicating that the status was changed during the `action`. + + Returns the number of `DBTask`s actually changed. + """ + count: int = 0 + with DBSession(config_db_session_factory) as session: + for t in session.query(DBTask) \ + .filter(DBTask.machine_id == machine_id, + DBTask.status.in_(["allocated", + "enqueued", + "running"])) \ + .all(): + count += 1 + t.add_comment(f"DROPPED!\n{action}", + "SYSTEM") + t.set_abandoned(force_dropped_status=True) + + session.commit() + return count diff --git a/web/server/codechecker_server/tmp.py b/web/server/codechecker_server/tmp.py deleted file mode 100644 index bbc5e77bea..0000000000 --- a/web/server/codechecker_server/tmp.py +++ /dev/null @@ -1,37 +0,0 @@ -# ------------------------------------------------------------------------- -# -# Part of the CodeChecker project, under the Apache License v2.0 with -# LLVM Exceptions. See LICENSE for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# -# ------------------------------------------------------------------------- -""" -Temporary directory module. -""" - - -import datetime -import hashlib -import os - - -from codechecker_common.logger import get_logger - -LOG = get_logger('system') - - -def get_tmp_dir_hash(): - """Generate a hash based on the current time and process id.""" - - pid = os.getpid() - time = datetime.datetime.now() - - data = str(pid) + str(time) - - dir_hash = hashlib.md5() - dir_hash.update(data.encode("utf-8")) - - LOG.debug('The generated temporary directory hash is %s.', - dir_hash.hexdigest()) - - return dir_hash.hexdigest() diff --git a/web/server/config/server_config.json b/web/server/config/server_config.json index a976543d80..2af90b1f7b 100644 --- a/web/server/config/server_config.json +++ b/web/server/config/server_config.json @@ -1,4 +1,6 @@ { + "background_worker_processes": null, + "worker_processes": null, "max_run_count": null, "store": { "analysis_statistics_dir": null, diff --git a/web/server/vue-cli/package-lock.json b/web/server/vue-cli/package-lock.json index 04d8540cd0..a24e002419 100644 --- a/web/server/vue-cli/package-lock.json +++ b/web/server/vue-cli/package-lock.json @@ -11,7 +11,7 @@ "@mdi/font": "^6.5.95", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz", "codemirror": "^5.65.0", "date-fns": "^2.28.0", "js-cookie": "^3.0.1", @@ -130,18 +130,18 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", - "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz", - "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -149,10 +149,10 @@ "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.3", - "@babel/parser": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.3", + "@babel/traverse": "^7.27.4", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -178,12 +178,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", - "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, "dependencies": { - "@babel/parser": "^7.27.3", + "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", @@ -453,22 +453,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz", - "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz", - "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dependencies": { "@babel/types": "^7.27.3" }, @@ -873,9 +873,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz", - "integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1411,9 +1411,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz", - "integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1702,9 +1702,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.3.tgz", - "integrity": "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "engines": { "node": ">=6.9.0" } @@ -1724,14 +1724,14 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz", - "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.3", + "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", @@ -1742,9 +1742,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -2982,9 +2982,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "dependencies": { "@types/connect": "*", @@ -3060,15 +3060,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", - "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -3123,9 +3123,9 @@ "dev": true }, "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true }, "node_modules/@types/http-proxy": { @@ -3193,12 +3193,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.15.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.23.tgz", - "integrity": "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", + "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", "dev": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/node-forge": { @@ -3244,9 +3244,9 @@ "dev": true }, "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -3263,9 +3263,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", "dev": true, "dependencies": { "@types/http-errors": "*", @@ -3344,9 +3344,9 @@ } }, "node_modules/@vue/compiler-sfc/node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", "funding": [ { "type": "opencollective", @@ -3362,7 +3362,7 @@ } ], "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3693,9 +3693,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4082,14 +4082,15 @@ } }, "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4563,9 +4564,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -4582,8 +4583,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -4754,9 +4755,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", "dev": true, "funding": [ { @@ -5057,9 +5058,9 @@ } }, "node_modules/codechecker-api": { - "version": "6.65.0", - "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz", - "integrity": "sha512-s+0o35UfqTFuFDD3jRDMmBuMIpLAe6xDZk/E7dOYhEEo5UWgrndLe7/796cLypoiCbxLLBlRUj0xUtyfHEwbxg==", + "version": "6.66.0", + "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz", + "integrity": "sha512-JnkWbQFQ9JJ1EVK5U6AYnY7kUVcB8UxqxrhYgmwwLNp7z93LhUOG/gRv//P7RtTkmR7BwHFEt5RwVRvXNl/Kvw==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" @@ -5396,9 +5397,9 @@ } }, "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", + "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", "dev": true, "hasInstallScript": true, "funding": { @@ -5407,12 +5408,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -5514,9 +5515,9 @@ } }, "node_modules/css-loader/node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", "dev": true, "funding": [ { @@ -5533,7 +5534,7 @@ } ], "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6269,9 +6270,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.159", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz", - "integrity": "sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA==", + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", "dev": true }, "node_modules/emittery": { @@ -6363,9 +6364,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.10", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", - "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -6395,7 +6396,9 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", + "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", @@ -6410,6 +6413,7 @@ "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -9290,6 +9294,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -14897,9 +14913,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "engines": { "node": ">= 0.4" @@ -15032,9 +15048,9 @@ } }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "dev": true, "dependencies": { "ip-address": "^9.0.5", @@ -15285,6 +15301,19 @@ "node": ">=0.10.0" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -15619,9 +15648,9 @@ } }, "node_modules/terser": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz", - "integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz", + "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -16064,9 +16093,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -16916,9 +16945,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz", - "integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz", + "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==", "dev": true, "engines": { "node": ">=10.13.0" diff --git a/web/server/vue-cli/package.json b/web/server/vue-cli/package.json index ff61c038b0..d4ddcf95a5 100644 --- a/web/server/vue-cli/package.json +++ b/web/server/vue-cli/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@mdi/font": "^6.5.95", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.65.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.66.0.tgz", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", "codemirror": "^5.65.0", diff --git a/web/tests/functional/instance_manager/test_instances.py b/web/tests/functional/instance_manager/test_instances.py index 0e7fc3a1d6..0851548100 100644 --- a/web/tests/functional/instance_manager/test_instances.py +++ b/web/tests/functional/instance_manager/test_instances.py @@ -10,7 +10,6 @@ Instance manager tests. """ - import os import shutil import subprocess @@ -178,7 +177,7 @@ def test_shutdown_record_keeping(self): EVENT_2.set() # Give the server some grace period to react to the kill command. - time.sleep(5) + time.sleep(30) test_cfg = env.import_test_cfg(self._test_workspace) codechecker_1 = test_cfg['codechecker_1'] diff --git a/web/tests/functional/tasks/__init__.py b/web/tests/functional/tasks/__init__.py new file mode 100644 index 0000000000..4259749345 --- /dev/null +++ b/web/tests/functional/tasks/__init__.py @@ -0,0 +1,7 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- diff --git a/web/tests/functional/tasks/test_task_management.py b/web/tests/functional/tasks/test_task_management.py new file mode 100644 index 0000000000..bf6f968dca --- /dev/null +++ b/web/tests/functional/tasks/test_task_management.py @@ -0,0 +1,484 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Contains tests of the ``"/Tasks"`` API endpoint to query, using the +``DummyTask``, normal task management related API functions. +""" +from copy import deepcopy +from datetime import datetime, timezone +import os +import pathlib +import shutil +import unittest +import time +from typing import List, Optional, cast + +import multiprocess + +from codechecker_api_shared.ttypes import RequestFailed, Ternary +from codechecker_api.codeCheckerServersideTasks_v6.ttypes import \ + AdministratorTaskInfo, TaskFilter, TaskInfo, TaskStatus + +from libtest import codechecker, env + + +# Stop events for the CodeChecker servers. +STOP_SERVER = multiprocess.Event() +STOP_SERVER_AUTH = multiprocess.Event() +STOP_SERVER_NO_AUTH = multiprocess.Event() + +TEST_WORKSPACE: Optional[str] = None + + +# Note: Test names in this file follow a strict ordinal convention, because +# the assertions are created with a specific execution history! + +class TaskManagementAPITests(unittest.TestCase): + def setup_class(self): + global TEST_WORKSPACE + TEST_WORKSPACE = env.get_workspace("tasks") + os.environ["TEST_WORKSPACE"] = TEST_WORKSPACE + + codechecker_cfg = { + "check_env": env.test_env(TEST_WORKSPACE), + "workspace": TEST_WORKSPACE, + "checkers": [], + "viewer_host": "localhost", + "viewer_port": env.get_free_port(), + "viewer_product": "tasks", + } + + # Run a normal server that is only used to manage the + # "test_package_product". + codechecker.start_server(codechecker_cfg, STOP_SERVER, + ["--machine-id", "workspace-manager"]) + + codechecker_cfg_no_auth = deepcopy(codechecker_cfg) + codechecker_cfg_no_auth.update({ + "viewer_port": env.get_free_port(), + }) + + # Run a normal server which does not require authentication. + codechecker.start_server(codechecker_cfg_no_auth, STOP_SERVER_NO_AUTH, + ["--machine-id", "unprivileged"]) + + codechecker_cfg_auth = deepcopy(codechecker_cfg) + codechecker_cfg_auth.update({ + "viewer_port": env.get_free_port(), + }) + + # Run a privileged server which does require authentication. + (pathlib.Path(TEST_WORKSPACE) / "root.user").unlink() + env.enable_auth(TEST_WORKSPACE) + codechecker.start_server(codechecker_cfg_auth, STOP_SERVER_AUTH, + ["--machine-id", "privileged"]) + + env.export_test_cfg(TEST_WORKSPACE, + {"codechecker_cfg": codechecker_cfg, + "codechecker_cfg_no_auth": + codechecker_cfg_no_auth, + "codechecker_cfg_auth": codechecker_cfg_auth}) + + codechecker.add_test_package_product(codechecker_cfg, TEST_WORKSPACE) + + def teardown_class(self): + # TODO: If environment variable is set keep the workspace and print + # out the path. + global TEST_WORKSPACE + + STOP_SERVER_NO_AUTH.set() + STOP_SERVER_NO_AUTH.clear() + STOP_SERVER_AUTH.set() + STOP_SERVER_AUTH.clear() + + codechecker.remove_test_package_product(TEST_WORKSPACE) + STOP_SERVER.set() + STOP_SERVER.clear() + + print(f"Removing: {TEST_WORKSPACE}") + shutil.rmtree(cast(str, TEST_WORKSPACE), ignore_errors=True) + + def setup_method(self, _): + test_workspace = os.environ["TEST_WORKSPACE"] + self._test_env = env.import_test_cfg(test_workspace) + + print(f"Running {self.__class__.__name__} tests in {test_workspace}") + + auth_server = self._test_env["codechecker_cfg_auth"] + no_auth_server = self._test_env["codechecker_cfg_no_auth"] + + self._auth_client = env.setup_auth_client(test_workspace, + auth_server["viewer_host"], + auth_server["viewer_port"]) + + root_token = self._auth_client.performLogin("Username:Password", + "root:root") + admin_token = self._auth_client.performLogin("Username:Password", + "admin:admin123") + + self._anonymous_task_client = env.setup_task_client( + test_workspace, + no_auth_server["viewer_host"], no_auth_server["viewer_port"]) + self._admin_task_client = env.setup_task_client( + test_workspace, + auth_server["viewer_host"], auth_server["viewer_port"], + session_token=admin_token) + self._privileged_task_client = env.setup_task_client( + test_workspace, + auth_server["viewer_host"], auth_server["viewer_port"], + session_token=root_token) + + def test_task_1_query_status(self): + task_token = self._anonymous_task_client.createDummyTask(10, False) + + time.sleep(5) + task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( + task_token) + self.assertEqual(task_info.token, task_token) + self.assertEqual(task_info.status, + TaskStatus._NAMES_TO_VALUES["RUNNING"]) + self.assertEqual(task_info.productId, 0) + self.assertIsNone(task_info.actorUsername) + self.assertIn("Dummy task", task_info.summary) + self.assertEqual(task_info.cancelFlagSet, False) + + time.sleep(10) # A bit more than exactly what remains of 10 seconds! + task_info = self._anonymous_task_client.getTaskInfo(task_token) + self.assertEqual(task_info.status, + TaskStatus._NAMES_TO_VALUES["COMPLETED"]) + self.assertEqual(task_info.cancelFlagSet, False) + self.assertIsNotNone(task_info.enqueuedAtEpoch) + self.assertIsNotNone(task_info.startedAtEpoch) + self.assertLessEqual(task_info.enqueuedAtEpoch, + task_info.startedAtEpoch) + self.assertIsNotNone(task_info.completedAtEpoch) + self.assertLess(task_info.startedAtEpoch, task_info.completedAtEpoch) + self.assertEqual(task_info.cancelFlagSet, False) + + def test_task_2_query_status_of_failed(self): + task_token = self._anonymous_task_client.createDummyTask(10, True) + + time.sleep(5) + task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( + task_token) + self.assertEqual(task_info.token, task_token) + self.assertEqual(task_info.status, + TaskStatus._NAMES_TO_VALUES["RUNNING"]) + self.assertEqual(task_info.cancelFlagSet, False) + + time.sleep(10) # A bit more than exactly what remains of 10 seconds! + task_info = self._anonymous_task_client.getTaskInfo(task_token) + self.assertEqual(task_info.status, + TaskStatus._NAMES_TO_VALUES["FAILED"]) + self.assertEqual(task_info.cancelFlagSet, False) + + def test_task_3_cancel(self): + task_token = self._anonymous_task_client.createDummyTask(10, False) + + time.sleep(3) + cancel_req: bool = self._privileged_task_client.cancelTask(task_token) + self.assertTrue(cancel_req) + + time.sleep(3) + cancel_req_2: bool = self._privileged_task_client.cancelTask( + task_token) + # The task was already cancelled, so cancel_req_2 is not the API call + # that cancelled the task. + self.assertFalse(cancel_req_2) + + time.sleep(5) # A bit more than exactly what remains of 10 seconds! + task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( + task_token) + self.assertEqual(task_info.status, + TaskStatus._NAMES_TO_VALUES["CANCELLED"]) + self.assertEqual(task_info.cancelFlagSet, True) + self.assertIn("root", task_info.comments) + self.assertIn("SUPERUSER requested cancellation.", task_info.comments) + self.assertIn("CANCEL!\nCancel request of admin honoured by task.", + task_info.comments) + self.assertIsNotNone(task_info.enqueuedAtEpoch) + self.assertIsNotNone(task_info.startedAtEpoch) + self.assertLessEqual(task_info.enqueuedAtEpoch, + task_info.startedAtEpoch) + self.assertIsNotNone(task_info.completedAtEpoch) + self.assertLess(task_info.startedAtEpoch, task_info.completedAtEpoch) + + def test_task_4_get_tasks_as_admin(self): + with self.assertRaises(RequestFailed): + self._admin_task_client.getTasks(TaskFilter( + # No SUPERUSER rights of test admin. + productIDs=[], + )) + with self.assertRaises(RequestFailed): + self._admin_task_client.getTasks(TaskFilter( + # Default product, no PRODUCT_ADMIN rights of test admin. + productIDs=[1] + )) + + # PRODUCT_ADMIN rights on test-specific product... + task_infos: List[AdministratorTaskInfo] = \ + self._admin_task_client.getTasks(TaskFilter(productIDs=[2])) + # ... but no product-specific tasks exist in this test suite. + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter()) + self.assertEqual(len(task_infos), 3) + + self.assertEqual(sum(1 for t in task_infos + if t.normalInfo.status == + TaskStatus._NAMES_TO_VALUES["COMPLETED"]), 1) + self.assertEqual(sum(1 for t in task_infos + if t.normalInfo.status == + TaskStatus._NAMES_TO_VALUES["FAILED"]), 1) + self.assertEqual(sum(1 for t in task_infos + if t.normalInfo.status == + TaskStatus._NAMES_TO_VALUES["CANCELLED"]), 1) + + def test_task_5_info_query_filters(self): + current_time_epoch = int(datetime.now(timezone.utc).timestamp()) + + task_infos: List[AdministratorTaskInfo] = \ + self._privileged_task_client.getTasks(TaskFilter( + machineIDs=["nonexistent"] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + machineIDs=["unprivileged"] + )) + self.assertEqual(len(task_infos), 3) + + tokens_from_previous_test = [t.normalInfo.token for t in task_infos] + + task_infos = self._admin_task_client.getTasks(TaskFilter( + tokens=tokens_from_previous_test + )) + # Admin client is not a SUPERUSER, it should not get the list of + # tasks visible only to superusers because they are "server-level". + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + machineIDs=["privileged"] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + startedBeforeEpoch=current_time_epoch + )) + self.assertEqual(len(task_infos), 3) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + startedAfterEpoch=current_time_epoch + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + cancelFlag=Ternary._NAMES_TO_VALUES["ON"] + )) + self.assertEqual(len(task_infos), 1) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + cancelFlag=Ternary._NAMES_TO_VALUES["OFF"] + )) + self.assertEqual(len(task_infos), 2) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + consumedFlag=Ternary._NAMES_TO_VALUES["ON"] + )) + self.assertEqual(len(task_infos), 3) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + consumedFlag=Ternary._NAMES_TO_VALUES["OFF"] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter()) + + current_time_epoch = int(datetime.now(timezone.utc).timestamp()) + for i in range(10): + target_api = self._anonymous_task_client if i % 2 == 0 \ + else self._admin_task_client + for j in range(10): + target_api.createDummyTask(1, bool(j % 2 == 0)) + + task_infos = self._privileged_task_client.getTasks(TaskFilter()) + self.assertEqual(len(task_infos), 103) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + )) + self.assertEqual(len(task_infos), 100) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + machineIDs=["unprivileged"] + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + machineIDs=["privileged"] + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + usernames=[], + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + usernames=["admin"], + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + usernames=["root"], + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch + )) + # Some tasks ought to have started at least. + self.assertGreater(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch + )) + # Some tasks ought to have also finished at least. + self.assertGreater(len(task_infos), 0) + + # Let every task terminate. We should only need 1 second per task, + # running likely in a multithreaded environment. + # Let's have some leeway, though... + time.sleep(2 * (100 * 1 // cast(int, os.cpu_count()))) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch + )) + # All tasks should have finished. + self.assertEqual(len(task_infos), 100) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch, + statuses=[TaskStatus._NAMES_TO_VALUES["COMPLETED"]] + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch, + statuses=[TaskStatus._NAMES_TO_VALUES["FAILED"]] + )) + self.assertEqual(len(task_infos), 50) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch, + cancelFlag=Ternary._NAMES_TO_VALUES["ON"] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch, + consumedFlag=Ternary._NAMES_TO_VALUES["ON"] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + machineIDs=["*privileged"] + )) + self.assertEqual(len(task_infos), 103) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + kinds=["*Dummy*"] + )) + self.assertEqual(len(task_infos), 103) + + # Try to consume the task status from the wrong user! + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedAfterEpoch=current_time_epoch, + completedAfterEpoch=current_time_epoch, + usernames=[], + statuses=[TaskStatus._NAMES_TO_VALUES["COMPLETED"]] + )) + self.assertEqual(len(task_infos), 25) + a_token: str = task_infos[0].normalInfo.token + with self.assertRaises(RequestFailed): + self._admin_task_client.getTaskInfo(a_token) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + machineIDs=["workspace-manager"] + )) + self.assertEqual(len(task_infos), 0) + + def test_task_6_dropping(self): + current_time_epoch = int(datetime.now(timezone.utc).timestamp()) + many_task_count = 4 * cast(int, os.cpu_count()) + for _ in range(many_task_count): + self._anonymous_task_client.createDummyTask(600, False) + + STOP_SERVER_NO_AUTH.set() + time.sleep(30) + STOP_SERVER_NO_AUTH.clear() + after_shutdown_time_epoch = int(datetime.now(timezone.utc) + .timestamp()) + + task_infos: List[AdministratorTaskInfo] = \ + self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + statuses=[ + TaskStatus._NAMES_TO_VALUES["ENQUEUED"], + TaskStatus._NAMES_TO_VALUES["RUNNING"], + TaskStatus._NAMES_TO_VALUES["COMPLETED"], + TaskStatus._NAMES_TO_VALUES["FAILED"], + TaskStatus._NAMES_TO_VALUES["CANCELLED"] + ] + )) + self.assertEqual(len(task_infos), 0) + + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + statuses=[TaskStatus._NAMES_TO_VALUES["DROPPED"]], + # System-level dropping is not a "cancellation" action! + cancelFlag=Ternary._NAMES_TO_VALUES["OFF"] + )) + self.assertEqual(len(task_infos), many_task_count) + dropped_task_infos = {ti.normalInfo.token: ti for ti in task_infos} + + # Some tasks will have started, and the server pulled out from under. + task_infos = self._privileged_task_client.getTasks(TaskFilter( + enqueuedAfterEpoch=current_time_epoch, + startedBeforeEpoch=after_shutdown_time_epoch, + statuses=[TaskStatus._NAMES_TO_VALUES["DROPPED"]] + )) + for ti in task_infos: + self.assertIn("SHUTDOWN!\nTask honoured graceful cancel signal " + "generated by server shutdown.", + ti.normalInfo.comments) + del dropped_task_infos[ti.normalInfo.token] + + # The rest could have never started. + for ti in dropped_task_infos.values(): + self.assertTrue("DROPPED!\n" in ti.normalInfo.comments or + "SHUTDOWN!\n" in ti.normalInfo.comments) diff --git a/web/tests/libtest/env.py b/web/tests/libtest/env.py index 29cd6b0cdd..46e214e73d 100644 --- a/web/tests/libtest/env.py +++ b/web/tests/libtest/env.py @@ -16,16 +16,18 @@ import shutil import socket import subprocess +from typing import cast from codechecker_common.util import load_json -from .thrift_client_to_db import get_auth_client -from .thrift_client_to_db import get_config_client -from .thrift_client_to_db import get_product_client -from .thrift_client_to_db import get_viewer_client +from .thrift_client_to_db import \ + get_auth_client, \ + get_config_client, \ + get_product_client, \ + get_task_client, \ + get_viewer_client -from functional import PKG_ROOT -from functional import REPO_ROOT +from functional import PKG_ROOT, REPO_ROOT from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -243,6 +245,30 @@ def setup_config_client(workspace, session_token=session_token, protocol=proto) +def setup_task_client(workspace, + host=None, port=None, + uri="/Tasks", + auto_handle_connection=True, + session_token=None, + protocol="http"): + if not host and not port: + codechecker_cfg = import_test_cfg(workspace)["codechecker_cfg"] + port = codechecker_cfg["viewer_port"] + host = codechecker_cfg["viewer_host"] + + if session_token is None: + session_token = get_session_token(workspace, host, port) + if session_token == "_PROHIBIT": + session_token = None + + return get_task_client(port=port, + host=cast(str, host), + uri=uri, + auto_handle_connection=auto_handle_connection, + session_token=session_token, + protocol=protocol) + + def repository_root(): return os.path.abspath(os.environ['REPO_ROOT']) diff --git a/web/tests/libtest/thrift_client_to_db.py b/web/tests/libtest/thrift_client_to_db.py index 2bdfdc79a6..e4d5a720d3 100644 --- a/web/tests/libtest/thrift_client_to_db.py +++ b/web/tests/libtest/thrift_client_to_db.py @@ -234,6 +234,26 @@ def __getattr__(self, attr): return partial(self._thrift_client_call, attr) +class CCTaskHelper(ThriftAPIHelper): + def __init__(self, proto, host, port, uri, auto_handle_connection=True, + session_token=None): + from codechecker_api.codeCheckerServersideTasks_v6 \ + import codeCheckerServersideTaskService + from codechecker_client.credential_manager import SESSION_COOKIE_NAME + + url = create_product_url(proto, host, port, f"/v{VERSION}{uri}") + transport = THttpClient.THttpClient(url) + protocol = TJSONProtocol.TJSONProtocol(transport) + client = codeCheckerServersideTaskService.Client(protocol) + if session_token: + headers = {'Cookie': f"{SESSION_COOKIE_NAME}={session_token}"} + transport.setCustomHeaders(headers) + super().__init__(transport, client, auto_handle_connection) + + def __getattr__(self, attr): + return partial(self._thrift_client_call, attr) + + def get_all_run_results( client, run_id=None, @@ -299,3 +319,10 @@ def get_config_client(port, host='localhost', uri='/Configuration', return CCConfigHelper(protocol, host, port, uri, auto_handle_connection, session_token) + + +def get_task_client(port, host="localhost", uri="/Tasks", + auto_handle_connection=True, session_token=None, + protocol="http"): + return CCTaskHelper(protocol, host, port, uri, auto_handle_connection, + session_token) From 9e1d88ffe533423c66046733d7613a79fdc5a9bd Mon Sep 17 00:00:00 2001 From: bruntib Date: Thu, 19 Jun 2025 15:30:36 +0200 Subject: [PATCH 2/2] [test] Shorten async tasks' tests The test files contain hard-coded sleep() operations. These are lowered. --- .../functional/tasks/test_task_management.py | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/web/tests/functional/tasks/test_task_management.py b/web/tests/functional/tasks/test_task_management.py index bf6f968dca..be172d8d27 100644 --- a/web/tests/functional/tasks/test_task_management.py +++ b/web/tests/functional/tasks/test_task_management.py @@ -12,7 +12,6 @@ from copy import deepcopy from datetime import datetime, timezone import os -import pathlib import shutil import unittest import time @@ -73,7 +72,6 @@ def setup_class(self): }) # Run a privileged server which does require authentication. - (pathlib.Path(TEST_WORKSPACE) / "root.user").unlink() env.enable_auth(TEST_WORKSPACE) codechecker.start_server(codechecker_cfg_auth, STOP_SERVER_AUTH, ["--machine-id", "privileged"]) @@ -134,9 +132,9 @@ def setup_method(self, _): session_token=root_token) def test_task_1_query_status(self): - task_token = self._anonymous_task_client.createDummyTask(10, False) + task_token = self._anonymous_task_client.createDummyTask(2, False) - time.sleep(5) + time.sleep(1) task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( task_token) self.assertEqual(task_info.token, task_token) @@ -147,7 +145,7 @@ def test_task_1_query_status(self): self.assertIn("Dummy task", task_info.summary) self.assertEqual(task_info.cancelFlagSet, False) - time.sleep(10) # A bit more than exactly what remains of 10 seconds! + time.sleep(2) # A bit more than exactly what remains of 2 seconds! task_info = self._anonymous_task_client.getTaskInfo(task_token) self.assertEqual(task_info.status, TaskStatus._NAMES_TO_VALUES["COMPLETED"]) @@ -161,9 +159,9 @@ def test_task_1_query_status(self): self.assertEqual(task_info.cancelFlagSet, False) def test_task_2_query_status_of_failed(self): - task_token = self._anonymous_task_client.createDummyTask(10, True) + task_token = self._anonymous_task_client.createDummyTask(2, True) - time.sleep(5) + time.sleep(1) task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( task_token) self.assertEqual(task_info.token, task_token) @@ -171,27 +169,27 @@ def test_task_2_query_status_of_failed(self): TaskStatus._NAMES_TO_VALUES["RUNNING"]) self.assertEqual(task_info.cancelFlagSet, False) - time.sleep(10) # A bit more than exactly what remains of 10 seconds! + time.sleep(2) # A bit more than exactly what remains of 2 seconds! task_info = self._anonymous_task_client.getTaskInfo(task_token) self.assertEqual(task_info.status, TaskStatus._NAMES_TO_VALUES["FAILED"]) self.assertEqual(task_info.cancelFlagSet, False) def test_task_3_cancel(self): - task_token = self._anonymous_task_client.createDummyTask(10, False) + task_token = self._anonymous_task_client.createDummyTask(3, False) - time.sleep(3) + time.sleep(1) cancel_req: bool = self._privileged_task_client.cancelTask(task_token) self.assertTrue(cancel_req) - time.sleep(3) + time.sleep(0.5) cancel_req_2: bool = self._privileged_task_client.cancelTask( task_token) # The task was already cancelled, so cancel_req_2 is not the API call # that cancelled the task. self.assertFalse(cancel_req_2) - time.sleep(5) # A bit more than exactly what remains of 10 seconds! + time.sleep(0.5) # A bit more than exactly what remains of 10 seconds! task_info: TaskInfo = self._anonymous_task_client.getTaskInfo( task_token) self.assertEqual(task_info.status, @@ -300,43 +298,44 @@ def test_task_5_info_query_filters(self): task_infos = self._privileged_task_client.getTasks(TaskFilter()) current_time_epoch = int(datetime.now(timezone.utc).timestamp()) - for i in range(10): + for i in range(2): target_api = self._anonymous_task_client if i % 2 == 0 \ else self._admin_task_client - for j in range(10): + time.sleep(1) + for j in range(2): target_api.createDummyTask(1, bool(j % 2 == 0)) task_infos = self._privileged_task_client.getTasks(TaskFilter()) - self.assertEqual(len(task_infos), 103) + self.assertEqual(len(task_infos), 7) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, )) - self.assertEqual(len(task_infos), 100) + self.assertEqual(len(task_infos), 4) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, machineIDs=["unprivileged"] )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, machineIDs=["privileged"] )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, usernames=[], )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, usernames=["admin"], )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, @@ -362,7 +361,7 @@ def test_task_5_info_query_filters(self): # Let every task terminate. We should only need 1 second per task, # running likely in a multithreaded environment. # Let's have some leeway, though... - time.sleep(2 * (100 * 1 // cast(int, os.cpu_count()))) + time.sleep(2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, @@ -370,7 +369,7 @@ def test_task_5_info_query_filters(self): completedAfterEpoch=current_time_epoch )) # All tasks should have finished. - self.assertEqual(len(task_infos), 100) + self.assertEqual(len(task_infos), 4) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, @@ -378,7 +377,7 @@ def test_task_5_info_query_filters(self): completedAfterEpoch=current_time_epoch, statuses=[TaskStatus._NAMES_TO_VALUES["COMPLETED"]] )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, @@ -386,7 +385,7 @@ def test_task_5_info_query_filters(self): completedAfterEpoch=current_time_epoch, statuses=[TaskStatus._NAMES_TO_VALUES["FAILED"]] )) - self.assertEqual(len(task_infos), 50) + self.assertEqual(len(task_infos), 2) task_infos = self._privileged_task_client.getTasks(TaskFilter( enqueuedAfterEpoch=current_time_epoch, @@ -407,12 +406,12 @@ def test_task_5_info_query_filters(self): task_infos = self._privileged_task_client.getTasks(TaskFilter( machineIDs=["*privileged"] )) - self.assertEqual(len(task_infos), 103) + self.assertEqual(len(task_infos), 7) task_infos = self._privileged_task_client.getTasks(TaskFilter( kinds=["*Dummy*"] )) - self.assertEqual(len(task_infos), 103) + self.assertEqual(len(task_infos), 7) # Try to consume the task status from the wrong user! task_infos = self._privileged_task_client.getTasks(TaskFilter( @@ -422,7 +421,7 @@ def test_task_5_info_query_filters(self): usernames=[], statuses=[TaskStatus._NAMES_TO_VALUES["COMPLETED"]] )) - self.assertEqual(len(task_infos), 25) + self.assertEqual(len(task_infos), 1) a_token: str = task_infos[0].normalInfo.token with self.assertRaises(RequestFailed): self._admin_task_client.getTaskInfo(a_token) @@ -439,7 +438,7 @@ def test_task_6_dropping(self): self._anonymous_task_client.createDummyTask(600, False) STOP_SERVER_NO_AUTH.set() - time.sleep(30) + time.sleep(4) STOP_SERVER_NO_AUTH.clear() after_shutdown_time_epoch = int(datetime.now(timezone.utc) .timestamp())