From: Aleš Mrázek Date: Wed, 28 Aug 2024 09:01:34 +0000 (+0200) Subject: python/knot_resolver: constants reorganization X-Git-Tag: v6.0.9~18^2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8dc2ec0f1523431a45d5315d255dd1672efa0812;p=thirdparty%2Fknot-resolver.git python/knot_resolver: constants reorganization --- diff --git a/python/knot_resolver/__init__.py b/python/knot_resolver/__init__.py index 511e8d44e..33ce9e879 100644 --- a/python/knot_resolver/__init__.py +++ b/python/knot_resolver/__init__.py @@ -1,4 +1,4 @@ -from .datamodel.config_schema import KresConfig +from .datamodel import KresConfig __version__ = "0.1.0" diff --git a/python/knot_resolver/client/command.py b/python/knot_resolver/client/command.py index af59c42e7..5d1bc3c7e 100644 --- a/python/knot_resolver/client/command.py +++ b/python/knot_resolver/client/command.py @@ -5,8 +5,12 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple, Type, TypeVar from urllib.parse import quote -from knot_resolver.manager.constants import API_SOCK_ENV_VAR, CONFIG_FILE_ENV_VAR, DEFAULT_MANAGER_CONFIG_FILE -from knot_resolver.datamodel.config_schema import DEFAULT_MANAGER_API_SOCK +from knot_resolver.constants import ( + CONFIG_FILE_PATH_DEFAULT, + CONFIG_FILE_PATH_ENV_VAR, + API_SOCK_PATH_ENV_VAR, + API_SOCK_PATH_DEFAULT, +) from knot_resolver.datamodel.types import IPAddressPort from knot_resolver.utils.modeling import parsing from knot_resolver.utils.modeling.exceptions import DataValidationError @@ -70,8 +74,8 @@ def determine_socket(namespace: argparse.Namespace) -> SocketDesc: if len(namespace.socket) > 0: return SocketDesc(namespace.socket[0], "--socket argument") - config_path = os.getenv(CONFIG_FILE_ENV_VAR) - socket_env = os.getenv(API_SOCK_ENV_VAR) + config_path = os.getenv(CONFIG_FILE_PATH_ENV_VAR) + socket_env = os.getenv(API_SOCK_PATH_ENV_VAR) socket: Optional[SocketDesc] = None # 2) socket from config file ('--config' argument) @@ -82,15 +86,15 @@ def determine_socket(namespace: argparse.Namespace) -> SocketDesc: socket = get_socket_from_config(Path(config_path), False) # 4) socket from environment variable elif socket_env: - socket = SocketDesc(socket_env, f'Environment variable "{API_SOCK_ENV_VAR}"') + socket = SocketDesc(socket_env, f'Environment variable "{API_SOCK_PATH_ENV_VAR}"') # 5) socket from config file (default config file constant) else: - socket = get_socket_from_config(DEFAULT_MANAGER_CONFIG_FILE, True) + socket = get_socket_from_config(CONFIG_FILE_PATH_DEFAULT, True) if socket: return socket # 6) socket default - return SocketDesc(DEFAULT_MANAGER_API_SOCK, f'Default value "{DEFAULT_MANAGER_API_SOCK}"') + return SocketDesc(str(API_SOCK_PATH_DEFAULT), f'Default value "{API_SOCK_PATH_DEFAULT}"') class CommandArgs: diff --git a/python/knot_resolver/constants.py b/python/knot_resolver/constants.py new file mode 100644 index 000000000..f65876f1f --- /dev/null +++ b/python/knot_resolver/constants.py @@ -0,0 +1,41 @@ +import logging + +from importlib.metadata import version +from importlib.util import find_spec +from pathlib import Path + +# Installed Knot Resolver build options from Meson is semi-optional. +# They are needed to run the resolver, but not for its unit tests. +if find_spec("knot_resolver_build_options"): + import knot_resolver_build_options as build_conf # type: ignore[import-not-found] +else: + build_conf = None + +VERSION = version("knot_resolver") if find_spec("knot_resolver") else "6" +WORKERS_MAX_DEFAULT = 256 +LOGGING_LEVEL_STARTUP = logging.DEBUG +PID_FILE_NAME = "knot-resolver.pid" + +FIX_COUNTER_ATTEMPTS_MAX = 2 +FIX_COUNTER_DECREASE_INTERVAL_SEC = 30 * 60 +WATCHDOG_INTERVAL_SEC: float = 5 + +USER_DEFAULT = build_conf.user if build_conf else "knot-resolver" +GROUP_DEFAULT = build_conf.group if build_conf else "knot-resolver" + +RUN_DIR_DEFAULT: Path = build_conf.run_dir if build_conf else Path("/var/run/knot-resolver") +ETC_DIR_DEFAULT: Path = build_conf.etc_dir if build_conf else Path("/etc/knot-resolver") +CONFIG_FILE_PATH_DEFAULT = ETC_DIR_DEFAULT / "config.yaml" +CONFIG_FILE_PATH_ENV_VAR = "KRES_MANAGER_CONFIG" +API_SOCK_PATH_DEFAULT = RUN_DIR_DEFAULT / "kres-api.sock" +API_SOCK_PATH_ENV_VAR = "KRES_MANAGER_API_SOCK" + + +def kresd_executable() -> Path: + assert build_conf is not None + return build_conf.sbin_dir / "kresd" + + +def kres_cache_gc_executable() -> Path: + assert build_conf is not None + return build_conf.sbin_dir / "kres-cache-gc" diff --git a/python/knot_resolver/controller/supervisord/config_file.py b/python/knot_resolver/controller/supervisord/config_file.py index d9a79f9e4..409c53875 100644 --- a/python/knot_resolver/controller/supervisord/config_file.py +++ b/python/knot_resolver/controller/supervisord/config_file.py @@ -6,11 +6,13 @@ from pathlib import Path from jinja2 import Template from typing_extensions import Literal +from knot_resolver.constants import ( + kres_cache_gc_executable, + kresd_executable, +) from knot_resolver.manager.constants import ( - kres_gc_executable, - kresd_cache_dir, + kres_cache_dir, kresd_config_file_supervisord_pattern, - kresd_executable, policy_loader_config_file, supervisord_config_file, supervisord_config_file_tmp, @@ -96,7 +98,7 @@ class ProcessTypeConfig: return ProcessTypeConfig( # type: ignore[call-arg] logfile=supervisord_subprocess_log_dir(config) / "gc.log", workdir=cwd, - command=f"{kres_gc_executable()} -c {kresd_cache_dir(config)}{kres_cache_gc_args(config)}", + command=f"{kres_cache_gc_executable()} -c {kres_cache_dir(config)}{kres_cache_gc_args(config)}", environment="", ) diff --git a/python/knot_resolver/datamodel/config_schema.py b/python/knot_resolver/datamodel/config_schema.py index 1ee300d85..e703f7a92 100644 --- a/python/knot_resolver/datamodel/config_schema.py +++ b/python/knot_resolver/datamodel/config_schema.py @@ -5,7 +5,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union from typing_extensions import Literal -from knot_resolver.manager.constants import MAX_WORKERS +from knot_resolver.constants import RUN_DIR_DEFAULT, API_SOCK_PATH_DEFAULT, WORKERS_MAX_DEFAULT + from knot_resolver.datamodel.cache_schema import CacheSchema from knot_resolver.datamodel.dns64_schema import Dns64Schema from knot_resolver.datamodel.dnssec_schema import DnssecSchema @@ -25,10 +26,6 @@ from knot_resolver.utils.modeling import ConfigSchema from knot_resolver.utils.modeling.base_schema import lazy_default from knot_resolver.utils.modeling.exceptions import AggregateDataValidationError, DataValidationError -_DEFAULT_RUNDIR = "/var/run/knot-resolver" - -DEFAULT_MANAGER_API_SOCK = _DEFAULT_RUNDIR + "/manager.sock" - logger = logging.getLogger(__name__) @@ -47,7 +44,7 @@ def _default_max_worker_count() -> int: c = _cpu_count() if c: return c * 10 - return MAX_WORKERS + return WORKERS_MAX_DEFAULT def _get_views_tags(views: List[ViewSchema]) -> List[str]: @@ -114,10 +111,10 @@ class KresConfig(ConfigSchema): version: int = 1 nsid: Optional[EscapedStr] = None hostname: Optional[EscapedStr] = None - rundir: WritableDir = lazy_default(WritableDir, _DEFAULT_RUNDIR) + rundir: WritableDir = lazy_default(WritableDir, str(RUN_DIR_DEFAULT)) workers: Union[Literal["auto"], IntPositive] = IntPositive(1) max_workers: IntPositive = IntPositive(_default_max_worker_count()) - management: ManagementSchema = lazy_default(ManagementSchema, {"unix-socket": DEFAULT_MANAGER_API_SOCK}) + management: ManagementSchema = lazy_default(ManagementSchema, {"unix-socket": str(API_SOCK_PATH_DEFAULT)}) webmgmt: Optional[WebmgmtSchema] = None options: OptionsSchema = OptionsSchema() network: NetworkSchema = NetworkSchema() @@ -239,4 +236,4 @@ def get_rundir_without_validation(data: Dict[str, Any]) -> WritableDir: Used for initial manager startup. """ - return WritableDir(data["rundir"] if "rundir" in data else _DEFAULT_RUNDIR, object_path="/rundir") + return WritableDir(data["rundir"] if "rundir" in data else RUN_DIR_DEFAULT, object_path="/rundir") diff --git a/python/knot_resolver/datamodel/types/files.py b/python/knot_resolver/datamodel/types/files.py index 920d90b13..2dde21e16 100644 --- a/python/knot_resolver/datamodel/types/files.py +++ b/python/knot_resolver/datamodel/types/files.py @@ -6,7 +6,7 @@ from pathlib import Path from pwd import getpwnam from typing import Any, Dict, Tuple, Type, TypeVar -from knot_resolver.manager.constants import kresd_group, kresd_user +from knot_resolver.constants import USER_DEFAULT, GROUP_DEFAULT from knot_resolver.datamodel.globals import get_resolve_root, get_strict_validation from knot_resolver.utils.modeling.base_value_type import BaseValueType @@ -157,14 +157,8 @@ def _kres_accessible(dest_path: Path, perm_mode: _PermissionMode) -> bool: _PermissionMode.EXECUTE: [stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH], } - username = kresd_user() - groupname = kresd_group() - - if username is None or groupname is None: - return True - - user_uid = getpwnam(username).pw_uid - user_gid = getgrnam(groupname).gr_gid + user_uid = getpwnam(USER_DEFAULT).pw_uid + user_gid = getgrnam(GROUP_DEFAULT).gr_gid dest_stat = os.stat(dest_path) dest_uid = dest_stat.st_uid @@ -201,7 +195,7 @@ class ReadableFile(File): super().__init__(source_value, parents=parents, object_path=object_path) if self.strict_validation and not _kres_accessible(self._value, _PermissionMode.READ): - raise ValueError(f"{kresd_user()}:{kresd_group()} has insufficient permissions to read '{self._value}'") + raise ValueError(f"{USER_DEFAULT}:{GROUP_DEFAULT} has insufficient permissions to read '{self._value}'") class WritableDir(Dir): @@ -220,7 +214,7 @@ class WritableDir(Dir): self._value, _PermissionMode.WRITE | _PermissionMode.EXECUTE ): raise ValueError( - f"{kresd_user()}:{kresd_group()} has insufficient permissions to write/execute '{self._value}'" + f"{USER_DEFAULT}:{GROUP_DEFAULT} has insufficient permissions to write/execute '{self._value}'" ) @@ -241,5 +235,5 @@ class WritableFilePath(FilePath): self._value.parent, _PermissionMode.WRITE | _PermissionMode.EXECUTE ): raise ValueError( - f"{kresd_user()}:{kresd_group()} has insufficient permissions to write/execute'{self._value.parent}'" + f"{USER_DEFAULT}:{GROUP_DEFAULT} has insufficient permissions to write/execute'{self._value.parent}'" ) diff --git a/python/knot_resolver/manager/constants.py b/python/knot_resolver/manager/constants.py index 5a7317a59..c1a668849 100644 --- a/python/knot_resolver/manager/constants.py +++ b/python/knot_resolver/manager/constants.py @@ -1,49 +1,13 @@ -import importlib.util -import logging from pathlib import Path from typing import TYPE_CHECKING, Optional -# Install config is semi-optional - only needed to actually run Manager, but not -# for its unit tests. -if importlib.util.find_spec("knot_resolver_build_options"): - import knot_resolver_build_options # type: ignore[import-not-found] -else: - knot_resolver_build_options = None - if TYPE_CHECKING: from knot_resolver.manager.config_store import ConfigStore from knot_resolver.datamodel.config_schema import KresConfig from knot_resolver.controller.interface import KresID -STARTUP_LOG_LEVEL = logging.DEBUG -DEFAULT_MANAGER_CONFIG_FILE = Path("/etc/knot-resolver/config.yaml") -CONFIG_FILE_ENV_VAR = "KRES_MANAGER_CONFIG" -API_SOCK_ENV_VAR = "KRES_MANAGER_API_SOCK" -MANAGER_FIX_ATTEMPT_MAX_COUNTER = 2 -FIX_COUNTER_DECREASE_INTERVAL_SEC = 30 * 60 -PID_FILE_NAME = "manager.pid" -MAX_WORKERS = 256 - - -def kresd_executable() -> Path: - assert knot_resolver_build_options is not None - return knot_resolver_build_options.sbin_dir / "kresd" - - -def kres_gc_executable() -> Path: - assert knot_resolver_build_options is not None - return knot_resolver_build_options.sbin_dir / "kres-cache-gc" - -def kresd_user(): - return None if knot_resolver is None else knot_resolver.user - - -def kresd_group(): - return None if knot_resolver is None else knot_resolver.group - - -def kresd_cache_dir(config: "KresConfig") -> Path: +def kres_cache_dir(config: "KresConfig") -> Path: return config.cache.storage.to_path() @@ -79,12 +43,6 @@ def supervisord_subprocess_log_dir(_config: "KresConfig") -> Path: return Path("logs") -WATCHDOG_INTERVAL: float = 5 -""" -Used in KresdManager. It's a number of seconds in between system health checks. -""" - - class _UserConstants: """ Class for accessing constants, which are technically not constants as they are user configurable. diff --git a/python/knot_resolver/manager/kres_manager.py b/python/knot_resolver/manager/kres_manager.py index 3dbc1079d..606f5b243 100644 --- a/python/knot_resolver/manager/kres_manager.py +++ b/python/knot_resolver/manager/kres_manager.py @@ -12,10 +12,10 @@ from knot_resolver.manager.config_store import ( only_on_real_changes_update, only_on_real_changes_verifier, ) -from knot_resolver.manager.constants import ( +from knot_resolver.constants import ( + FIX_COUNTER_ATTEMPTS_MAX, FIX_COUNTER_DECREASE_INTERVAL_SEC, - MANAGER_FIX_ATTEMPT_MAX_COUNTER, - WATCHDOG_INTERVAL, + WATCHDOG_INTERVAL_SEC, ) from knot_resolver.manager.exceptions import SubprocessControllerException from knot_resolver.controller.interface import ( @@ -58,7 +58,7 @@ class _FixCounter: return str(self._counter) def is_too_high(self) -> bool: - return self._counter >= MANAGER_FIX_ATTEMPT_MAX_COUNTER + return self._counter >= FIX_COUNTER_ATTEMPTS_MAX async def _deny_max_worker_changes(config_old: KresConfig, config_new: KresConfig) -> Result[None, str]: @@ -369,7 +369,7 @@ class KresManager: # pylint: disable=too-many-instance-attributes async def _watchdog(self) -> None: # pylint: disable=too-many-branches while True: - await asyncio.sleep(WATCHDOG_INTERVAL) + await asyncio.sleep(WATCHDOG_INTERVAL_SEC) self._fix_counter.try_decrease() diff --git a/python/knot_resolver/manager/log.py b/python/knot_resolver/manager/log.py index a22898a5a..d34e57fe9 100644 --- a/python/knot_resolver/manager/log.py +++ b/python/knot_resolver/manager/log.py @@ -4,8 +4,8 @@ import os import sys from typing import Optional +from knot_resolver.constants import LOGGING_LEVEL_STARTUP from knot_resolver.manager.config_store import ConfigStore, only_on_real_changes_update -from knot_resolver.manager.constants import STARTUP_LOG_LEVEL from knot_resolver.datamodel.config_schema import KresConfig from knot_resolver.datamodel.logging_schema import LogTargetEnum @@ -99,7 +99,7 @@ async def logger_init(config_store: ConfigStore) -> None: def logger_startup() -> None: - logging.getLogger().setLevel(STARTUP_LOG_LEVEL) + logging.getLogger().setLevel(LOGGING_LEVEL_STARTUP) err_handler = logging.StreamHandler(sys.stderr) err_handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) logging.getLogger().addHandler(logging.handlers.MemoryHandler(10_000, logging.ERROR, err_handler)) diff --git a/python/knot_resolver/manager/main.py b/python/knot_resolver/manager/main.py index 5facc470b..315af01bf 100644 --- a/python/knot_resolver/manager/main.py +++ b/python/knot_resolver/manager/main.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import NoReturn from knot_resolver import compat -from knot_resolver.manager.constants import CONFIG_FILE_ENV_VAR, DEFAULT_MANAGER_CONFIG_FILE +from knot_resolver.constants import CONFIG_FILE_PATH_DEFAULT, CONFIG_FILE_PATH_ENV_VAR from knot_resolver.manager.log import logger_startup from knot_resolver.manager.server import start_server @@ -20,7 +20,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "-c", "--config", - help="Config file to load. Overrides default config location at '" + str(DEFAULT_MANAGER_CONFIG_FILE) + "'", + help="Config file to load. Overrides default config location at '" + str(CONFIG_FILE_PATH_ENV_VAR) + "'", type=str, nargs=1, required=False, @@ -37,13 +37,13 @@ def main() -> NoReturn: args = parse_args() # where to look for config - config_env = os.getenv(CONFIG_FILE_ENV_VAR) + config_env = os.getenv(CONFIG_FILE_PATH_ENV_VAR) if args.config is not None: config_path = Path(args.config[0]) elif config_env is not None: config_path = Path(config_env) else: - config_path = DEFAULT_MANAGER_CONFIG_FILE + config_path = CONFIG_FILE_PATH_DEFAULT exit_code = compat.asyncio.run(start_server(config=config_path)) sys.exit(exit_code) diff --git a/python/knot_resolver/manager/server.py b/python/knot_resolver/manager/server.py index cf31a3fc8..a25b5eb2a 100644 --- a/python/knot_resolver/manager/server.py +++ b/python/knot_resolver/manager/server.py @@ -19,10 +19,11 @@ from aiohttp.web_runner import AppRunner, TCPSite, UnixSite from typing_extensions import Literal import knot_resolver.utils.custom_atexit as atexit +from knot_resolver.constants import CONFIG_FILE_PATH_DEFAULT, PID_FILE_NAME from knot_resolver.manager import log, statistics from knot_resolver.compat import asyncio as asyncio_compat from knot_resolver.manager.config_store import ConfigStore -from knot_resolver.manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants +from knot_resolver.manager.constants import init_user_constants from knot_resolver.datamodel.cache_schema import CacheClearRPCSchema from knot_resolver.datamodel.config_schema import KresConfig, get_rundir_without_validation from knot_resolver.datamodel.globals import Context, set_global_validation_context @@ -504,7 +505,7 @@ async def _sigterm_while_shutting_down(): sys.exit(128 + signal.SIGTERM) -async def start_server(config: Path = DEFAULT_MANAGER_CONFIG_FILE) -> int: +async def start_server(config: Path = CONFIG_FILE_PATH_DEFAULT) -> int: # This function is quite long, but it describes how manager runs. So let's silence pylint # pylint: disable=too-many-statements