From a75244f4b6819c92b8b85bde0c04c6215b922914 Mon Sep 17 00:00:00 2001 From: Whisperity Date: Mon, 19 Aug 2024 19:11:13 +0200 Subject: [PATCH] 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.58.0.tgz | Bin 64458 -> 0 bytes .../dist/codechecker-api-6.59.0.tgz | Bin 0 -> 69705 bytes web/api/js/codechecker-api-node/package.json | 2 +- .../dist/codechecker_api.tar.gz | Bin 60379 -> 62389 bytes web/api/py/codechecker_api/setup.py | 2 +- .../dist/codechecker_api_shared.tar.gz | Bin 7977 -> 3723 bytes web/api/py/codechecker_api_shared/setup.py | 2 +- web/api/report_server.thrift | 82 ++- web/api/tasks.thrift | 162 +++++ web/client/codechecker_client/client.py | 4 +- .../codechecker_client/helpers/results.py | 8 +- web/codechecker_web/shared/version.py | 2 +- .../codechecker_server/api/authentication.py | 4 +- web/server/codechecker_server/api/common.py | 49 ++ .../codechecker_server/api/report_server.py | 49 +- web/server/codechecker_server/api/tasks.py | 391 ++++++++++++ web/server/codechecker_server/cmd/server.py | 88 ++- .../database/config_db_model.py | 197 +++++- ...ented_keeping_track_of_background_tasks.py | 79 +++ web/server/codechecker_server/routing.py | 33 +- web/server/codechecker_server/server.py | 563 +++++++++++++++--- .../codechecker_server/session_manager.py | 37 +- .../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 | 12 +- 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 | 494 +++++++++++++++ web/tests/libtest/env.py | 38 +- web/tests/libtest/thrift_client_to_db.py | 27 + 46 files changed, 2879 insertions(+), 356 deletions(-) create mode 100644 codechecker_common/process.py delete mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.58.0.tgz create mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.59.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/73b04c41885b_implemented_keeping_track_of_background_tasks.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 00e8ffca07..290d56bb5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,8 +20,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 6b22ca4231..6f7e8a22dd 100644 --- a/analyzer/codechecker_analyzer/analysis_manager.py +++ b/analyzer/codechecker_analyzer/analysis_manager.py @@ -13,16 +13,15 @@ import shutil import signal import sys -import time import traceback import zipfile 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 \ @@ -341,42 +340,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 ad820b8a05..261e2312b2 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. @@ -63,11 +64,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 @@ -94,30 +97,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 e389b8d1a0..c9075d5599 100644 --- a/codechecker_common/util.py +++ b/codechecker_common/util.py @@ -8,9 +8,12 @@ """ Util module. """ +import datetime +import hashlib import itertools import json import os +import random from typing import TextIO import portalocker @@ -112,3 +115,19 @@ def path_for_fake_root(full_path: str, root_path: str = '/') -> str: def strtobool(value: str) -> bool: """Parse a string value to a boolean.""" return value.lower() in ('y', 'yes', 't', 'true', 'on', '1') + + +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 846599b76a..bca75ee459 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 e145755563..d322ab77b0 100644 --- a/web/api/Makefile +++ b/web/api/Makefile @@ -37,10 +37,11 @@ build: clean target_dirs thrift:$(THRIFT_VERSION) \ /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.58.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.58.0.tgz deleted file mode 100644 index a8cf8ab10bd14f2623023e3167d40998ccc5f5d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64458 zcmV)%K#jj2iwFP!00002|LnbMbK6LgD7v5hE4YrhYbi#fCh>;d9XHgrJgu{~?6K^b zGuPp8&>|#rOp#iGvRgCT|NYkc0c7DtfC9zka~K&>lv|gBQuwbUt07>7~by*nA$dH9a1r zU&piA;3gT*2P?GJRkFNE2jlrC|2`O}g9#oonGC+%4=$2R+Q|s@zg*m1e;vew#TEL;6t!Di-rXegRd!yBWo}}Z zi`)C<^!n>+u=p`gmIE{;1UOyY55{+^uZ!jMUt~Pi4+OFLI$jOXpx4VW0-RryMrjQuI z{~VIx7HE6u#`BKuR)~C|`JyvG$FxjR-XrSrOEO2Ba5+Ussr9bRQy%$07I%*a2M8Ab zwS0W2o;vz}cu&4hCwI87vfs?OSWXHeZA#N6vDO%c?+$w&+)A{A>j%)?GgoF8FHJD9rrg(I8 zj_KlR^#h| z{P;S({HhQGbXY6I1JKP}Cf}#zC@~hJtzq|Mkf0r2Ecw4s7wnkT+r&Dl3)tn z7qX0-WSPwF(LI=dBOCVxVGG8S`S>O|`0)@VtCV!+{iWR2%3pDhM zlkN2G3!)v2cyPIcU>BJ=nI5|eWFSJvb7BQQWy0N$%A&NMiVc;1ODN%0<%MzkcdY`V6n^Yy?i09rVn1x&t z9?4qJ-!SQ0O6iD;TuC~;o)g-SP!XRI*;o)0F|m4lj`n*zQ19yT zNxTerpf=@A^e%)VJ{bG zzbU)M0a{Fq{4W4@UGnH+tJmW==TXC(G4ud(K|xF63}_kX@x zCg@*NB-wpmTw+R|W9Li-!~=453qhbuc8d`?N9rU!zrFi{V9_=rOue0r5vGe@(3~l+ zP024zvQQN_Rn8a0m`KaH5dnTqhzmO*S2>}#V@!5;;CK!YSaQ3h^8!-D|#}55-?Gj$%TZ_ z@Ri{$pohdq9pB#0u)?{Rqf;lljHwr!+U0CKMf*+LDvLwANIFq>S5VG5y1!{UUQWsV zyjmjq!*!Zu%DJC<^$*fRqzEtO3FC=~03w-0$n^v)3&0mdbzH`8(e$a9r%#Rz@dF

u9u56{WIAxyo}4m9hRWz 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%0D(vvt0cCTcadc@#JkI;iZYXdyr0D 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

W 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 diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..270aaf28fce6581ebdd7167ab89aa50f240f88ca GIT binary patch literal 69705 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 z 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
    G9cy$&Ck`jaukGFr4{tB0=L^i8#o;6V2hp>m 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
    hI_yS)Qt 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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 0bfd792add..86e4a596e9 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.58.0", + "version": "6.59.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 3875d3ef7f4af5433227592e105f35a32e274690..601a9c2e5c4c80328f68f760cd44d8542282b458 100644 GIT binary patch literal 62389 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

    z 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%0DHYssHpRGYe>wt7{33=O{ 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 literal 7977 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 diff --git a/web/api/py/codechecker_api_shared/setup.py b/web/api/py/codechecker_api_shared/setup.py index a4c2e70d02..90f09bf34e 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.58.0' +api_version = '6.59.0' setup( name='codechecker_api_shared', diff --git a/web/api/report_server.thrift b/web/api/report_server.thrift index 359372e28a..962a1c49d8 100644 --- a/web/api/report_server.thrift +++ b/web/api/report_server.thrift @@ -201,6 +201,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. @@ -208,8 +219,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 @@ -943,32 +953,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, @@ -979,6 +1004,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..e380d59ac3 --- /dev/null +++ b/web/api/tasks.thrift @@ -0,0 +1,162 @@ +// ------------------------------------------------------------------------- +// 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 empty, it means "all", including those of no username. + 5: list usernames, + // If True, it means filter for **only** "no username". + // Can not be set together with a non-empty "usernames". + 6: bool filterForNoUsername, + // If empty, it means "all", including those of no product ID. + 7: list productIDs, + // If True, it means filter for **only** "no product ID". + // Can not be set together with a non-empty "productIDs". + 8: bool filterForNoProductID, + 9: i64 enqueuedBeforeEpoch, + 10: i64 enqueuedAfterEpoch, + 11: i64 startedBeforeEpoch, + 12: i64 startedAfterEpoch, + 13: i64 completedBeforeEpoch, + 14: i64 completedAfterEpoch, + 15: i64 heartbeatBeforeEpoch, + 16: i64 heartbeatAfterEpoch, + 17: codechecker_api_shared.Ternary cancelFlag, + 18: 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 730a83446b..0bbe2f69fe 100644 --- a/web/client/codechecker_client/client.py +++ b/web/client/codechecker_client/client.py @@ -205,7 +205,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: @@ -260,6 +260,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 e5d544a750..2ac2d84ae7 100644 --- a/web/codechecker_web/shared/version.py +++ b/web/codechecker_web/shared/version.py @@ -18,7 +18,7 @@ # The newest supported minor version (value) for each supported major version # (key) in this particular build. SUPPORTED_VERSIONS = { - 6: 58 + 6: 59 } # Used by the client to automatically identify the latest major and minor diff --git a/web/server/codechecker_server/api/authentication.py b/web/server/codechecker_server/api/authentication.py index 1430ad9fd6..9e73923e45 100644 --- a/web/server/codechecker_server/api/authentication.py +++ b/web/server/codechecker_server/api/authentication.py @@ -19,6 +19,7 @@ AuthorisationList, HandshakeInformation, Permissions, SessionTokenData from codechecker_common.logger import get_logger +from codechecker_common.util import generate_random_token from codechecker_server.profiler import timeit @@ -28,7 +29,6 @@ from ..permissions import handler_from_scope_params as make_handler, \ require_manager, require_permission from ..server import permissions -from ..session_manager import generate_session_token LOG = get_logger('server') @@ -363,7 +363,7 @@ def newToken(self, description): """ self.__require_privilaged_access() with DBSession(self.__config_db) as session: - token = generate_session_token() + token = generate_random_token(32) user = self.getLoggedInUser() groups = ';'.join(self.__auth_session.groups) session_token = Session(token, user, groups, description, False) 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 c98cbc71c0..2348708b81 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -44,7 +44,8 @@ ReviewStatusRuleSortType, 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 @@ -69,6 +70,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 @@ -141,39 +143,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 @@ -3933,6 +3902,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..9abe4fb743 --- /dev/null +++ b/web/server/codechecker_server/api/tasks.py @@ -0,0 +1,391 @@ + +# +# 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.""" + if filters.filterForNoProductID and filters.productIDs: + raise RequestFailed(ErrorCode.GENERAL, + "Invalid request, do not set " + "\"no product ID\" and some product IDs in " + "the same filter!") + if filters.filterForNoUsername and filters.usernames: + raise RequestFailed(ErrorCode.GENERAL, + "Invalid request, do not set " + "\"no username\" and some usernames in the " + "same filter!") + + with DBSession(self._config_db) as session: + if filters.filterForNoProductID: + 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: + AND.append(or_(*(DBTask.username.ilike(conv(username)) + for username in filters.usernames))) + elif filters.filterForNoUsername: + AND.append(DBTask.username.is_(None)) + + if filters.productIDs: + AND.append(or_(DBTask.product_id.in_(filters.productIDs))) + elif filters.filterForNoProductID: + 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/cmd/server.py b/web/server/codechecker_server/cmd/server.py index 33bbbd20f1..7b49982669 100644 --- a/web/server/codechecker_server/cmd/server.py +++ b/web/server/codechecker_server/cmd/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", @@ -424,7 +441,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) @@ -762,42 +779,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.""" @@ -842,7 +823,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']) @@ -1106,16 +1087,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 " @@ -1152,4 +1138,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 00f0c4948e..e2ee5a550b 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 +from datetime import datetime, timezone import sys +from typing import Optional from sqlalchemy import Boolean, CHAR, Column, DateTime, Enum, ForeignKey, \ Integer, MetaData, String, Text @@ -158,6 +159,200 @@ def __init__(self, config_key, config_value): self.config_value = config_value +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/73b04c41885b_implemented_keeping_track_of_background_tasks.py b/web/server/codechecker_server/migrations/config/versions/73b04c41885b_implemented_keeping_track_of_background_tasks.py new file mode 100644 index 0000000000..45b3ab7bc1 --- /dev/null +++ b/web/server/codechecker_server/migrations/config/versions/73b04c41885b_implemented_keeping_track_of_background_tasks.py @@ -0,0 +1,79 @@ +""" +Implemented keeping track of background tasks through corresponding records +in the server-wide configuration database. + +Revision ID: 73b04c41885b +Revises: 00099e8bc212 +Create Date: 2023-09-21 14:24:27.395597 +""" + +from alembic import op +import sqlalchemy as sa + + +# Revision identifiers, used by Alembic. +revision = '73b04c41885b' +down_revision = '00099e8bc212' +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 79ac8d0686..34fbb82f87 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): @@ -68,9 +71,8 @@ def is_supported_version(version): If supported, returns the major and minor version as a tuple. """ - version = version.lstrip('v') - version_parts = version.split('.') + version_parts = version.split('.', 2) # We don't care if accidentally the version tag contains a revision number. major, minor = int(version_parts[0]), int(version_parts[1]) @@ -113,9 +115,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 40bdf6db4d..f10f28291e 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 hashlib import sha256 @@ -25,10 +26,10 @@ import ssl import sys import stat -from typing import List, Optional, Tuple +import time +from typing import Dict, List, Optional, Tuple, cast import urllib -import multiprocess from sqlalchemy.orm import sessionmaker from sqlalchemy.sql.expression import func from thrift.protocol import TJSONProtocol @@ -47,11 +48,14 @@ 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_common.util import generate_random_token from codechecker_web.shared import database_status from codechecker_web.shared.version import get_version_str @@ -63,12 +67,15 @@ 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 .tmp import get_tmp_dir_hash +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') @@ -85,8 +92,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): @@ -104,7 +111,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) @@ -369,22 +376,22 @@ 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) 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, @@ -392,7 +399,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 = \ @@ -745,7 +758,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...") @@ -756,6 +772,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. @@ -764,6 +781,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', { @@ -780,7 +803,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") @@ -806,13 +829,23 @@ def __init__(self, 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 @@ -855,17 +888,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 @@ -990,6 +1046,10 @@ class CCSimpleHttpServerIPv6(CCSimpleHttpServer): address_family = socket.AF_INET6 + @property + def formatted_address(self) -> str: + return f"[{str(self.address)}]:{self.port}" + def __make_root_file(root_file): """ @@ -1000,7 +1060,7 @@ def __make_root_file(root_file): LOG.debug("Generating initial superuser (root) credentials...") username = ''.join(sample("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6)) - password = get_tmp_dir_hash()[:8] + password = generate_random_token(8) LOG.info("A NEW superuser credential was generated for the server. " "This information IS SAVED, thus subsequent server starts " @@ -1028,16 +1088,16 @@ def __make_root_file(root_file): return secret -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) - root_file = os.path.join(config_directory, 'root.user') if not os.path.exists(root_file): LOG.warning("Server started without 'root.user' present in " @@ -1103,92 +1163,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) + + try: + instance_manager.register(os.getpid(), + os.path.abspath( + context.codechecker_workspace), + port) + except IOError as ex: + LOG.debug(ex.strerror) - # 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] + 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) - processes = [] + atexit.register(unregister_handler, os.getpid()) - def signal_handler(signum, _): + 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 276af909cd..662eaa62b0 100644 --- a/web/server/codechecker_server/session_manager.py +++ b/web/server/codechecker_server/session_manager.py @@ -11,16 +11,14 @@ import hashlib import json -import os import re -import uuid from datetime import datetime from typing import Optional 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 @@ -47,29 +45,29 @@ SESSION_COOKIE_NAME = _SCN -def generate_session_token(): - """ - Returns a random session token. - """ - return uuid.UUID(bytes=os.urandom(16)).hex - - 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: @@ -182,7 +180,8 @@ def __init__(self, configuration_file, root_sha, 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(scfg_dict) + self.__worker_processes, self.__background_worker_processes = \ + get_worker_processes(scfg_dict) self.__max_run_count = scfg_dict.get('max_run_count', None) self.__store_config = scfg_dict.get('store', {}) self.__keepalive_config = scfg_dict.get('keepalive', {}) @@ -328,6 +327,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'), @@ -622,7 +625,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 e42745f08d..a5ad4999c8 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 d908b8c278..d0943fd772 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.58.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz", "codemirror": "^5.65.0", "date-fns": "^2.28.0", "js-cookie": "^3.0.1", @@ -5113,9 +5113,9 @@ } }, "node_modules/codechecker-api": { - "version": "6.58.0", - "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.58.0.tgz", - "integrity": "sha512-N6qK5cnLt32jnJlSyyGMmW6FCzybDljyH1RrGOZ1Gk9n1vV7WluJbC9InYWsZ5lbK7xVyIrphTKXhqC4ARKF6g==", + "version": "6.59.0", + "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz", + "integrity": "sha512-BZCBDRjVFS5UerrXsoPNioQppTfrCdDgToHqfFfaQtk6FPVrER42LchfU+cZl254PgWh58H5bLfqdLyFfqntCg==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" @@ -21145,8 +21145,8 @@ "dev": true }, "codechecker-api": { - "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.58.0.tgz", - "integrity": "sha512-N6qK5cnLt32jnJlSyyGMmW6FCzybDljyH1RrGOZ1Gk9n1vV7WluJbC9InYWsZ5lbK7xVyIrphTKXhqC4ARKF6g==", + "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz", + "integrity": "sha512-BZCBDRjVFS5UerrXsoPNioQppTfrCdDgToHqfFfaQtk6FPVrER42LchfU+cZl254PgWh58H5bLfqdLyFfqntCg==", "requires": { "thrift": "0.13.0-hotfix.1" } diff --git a/web/server/vue-cli/package.json b/web/server/vue-cli/package.json index 2239777668..f31789b897 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.58.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.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..a53a1e1b4f --- /dev/null +++ b/web/tests/functional/tasks/test_task_management.py @@ -0,0 +1,494 @@ +# ------------------------------------------------------------------------- +# +# 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. + filterForNoProductID=True + )) + with self.assertRaises(RequestFailed): + self._admin_task_client.getTasks(TaskFilter( + # Default product, no PRODUCT_ADMIN rights of test admin. + productIDs=[1] + )) + with self.assertRaises(RequestFailed): + self._privileged_task_client.getTasks(TaskFilter( + productIDs=[1], + filterForNoProductID=True + )) + with self.assertRaises(RequestFailed): + self._privileged_task_client.getTasks(TaskFilter( + usernames=["foo", "bar"], + filterForNoUsername=True + )) + + # 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, + filterForNoUsername=True, + )) + 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, + filterForNoUsername=True, + 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 1610db8bef..f89e495992 100644 --- a/web/tests/libtest/env.py +++ b/web/tests/libtest/env.py @@ -18,16 +18,18 @@ import socket import stat 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 def get_free_port(): @@ -236,6 +238,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 de7788c929..2b5c5a11e8 100644 --- a/web/tests/libtest/thrift_client_to_db.py +++ b/web/tests/libtest/thrift_client_to_db.py @@ -238,6 +238,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, @@ -303,3 +323,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)