From c190221f392c52342297b0d20c6145c51e9700f7 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 13 Feb 2026 18:39:00 +0000 Subject: [PATCH 01/10] update settings structure - wip --- .github/copilot-instructions.md | 9 ++-- .gitignore | 1 + node_cli/cli/node.py | 18 ++++--- node_cli/configs/__init__.py | 11 ++-- node_cli/configs/{user.py => _user.py} | 6 +-- node_cli/core/host.py | 27 +--------- node_cli/core/node.py | 72 +++++--------------------- node_cli/operations/base.py | 10 ++-- node_cli/operations/common.py | 13 ----- node_cli/operations/fair.py | 9 ++-- node_cli/utils/docker_utils.py | 3 -- node_cli/utils/helper.py | 11 ++-- node_cli/utils/node_type.py | 6 +-- node_cli/utils/print_formatters.py | 3 -- node_cli/utils/settings.py | 47 +++++++++++++++++ pyproject.toml | 29 ++++++----- scripts/export_env.sh | 8 +++ scripts/run_tests.sh | 10 +--- tests/cli/node_test.py | 4 +- tests/conftest.py | 2 +- tests/core/core_node_test.py | 11 +--- tests/utils/settings_test.py | 20 +++++++ 22 files changed, 155 insertions(+), 175 deletions(-) rename node_cli/configs/{user.py => _user.py} (98%) create mode 100644 node_cli/utils/settings.py create mode 100644 scripts/export_env.sh create mode 100644 tests/utils/settings_test.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a5c8ebdf..5df55bfe 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,7 +11,10 @@ - no redundant code - move repeated logic into helper functions - use type hints to specify the expected types of function arguments and return values -- check `ruff.toml` for formatting rules -- always lint changes using `ruff check` +- check `pyproject.toml` for formatting rules +- always lint changes using `uv run ruff check` - tests should be placed in `tests/` directory, follow the existing structure and code style -- to run a test always use `bash scripts/run_tests.sh tests/path_to_test.py -k [TEST_NAME]` command \ No newline at end of file +- always use `uv` to run all commands in the repo (e.g., `uv run ruff`, `uv run pytest`, etc.) +- for running tests, export environment variables in the terminal before running the tests: `. ./scripts/export_env.sh` + +- additional external context is located in context directory \ No newline at end of file diff --git a/.gitignore b/.gitignore index 87a0d8f8..65232943 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,4 @@ tests/.skale/node_data/node_options.json tests/.skale/config/nginx.conf.j2 .zed +uv.lock \ No newline at end of file diff --git a/node_cli/cli/node.py b/node_cli/cli/node.py index 55304356..f13825d9 100644 --- a/node_cli/cli/node.py +++ b/node_cli/cli/node.py @@ -17,8 +17,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from typing import get_args + import click +from skale.core.types import EnvType from node_cli.cli.info import TYPE from node_cli.core.node import ( cleanup as cleanup_skale, @@ -38,7 +41,6 @@ run_checks, ) from node_cli.configs import DEFAULT_NODE_BASE_PORT -from node_cli.configs.user import ALLOWED_ENV_TYPES from node_cli.core.node_options import upsert_node_mode from node_cli.utils.decorators import check_inited from node_cli.utils.helper import abort_if_false, streamed_cmd, IP_TYPE @@ -85,10 +87,10 @@ def register_node(name, ip, port, domain): @node.command('init', help='Initialize SKALE node') -@click.argument('env_file') +@click.argument('config_file') @streamed_cmd -def init_node(env_file): - init(env_filepath=env_file, node_type=TYPE) +def init_node(config_file): + init(config_file=config_file, node_type=TYPE) @node.command('update', help='Update node from .env file') @@ -101,12 +103,12 @@ def init_node(env_file): ) @click.option('--pull-config', 'pull_config_for_schain', hidden=True, type=str) @click.option('--unsafe', 'unsafe_ok', help='Allow unsafe update', hidden=True, is_flag=True) -@click.argument('env_file') +@click.argument('config_file') @streamed_cmd -def update_node(env_file, pull_config_for_schain, unsafe_ok): +def update_node(config_file, pull_config_for_schain, unsafe_ok): update( node_mode=NodeMode.ACTIVE, - env_filepath=env_file, + env_filepath=config_file, pull_config_for_schain=pull_config_for_schain, node_type=TYPE, unsafe_ok=unsafe_ok, @@ -227,7 +229,7 @@ def _set_domain_name(domain): @click.option( '--network', '-n', - type=click.Choice(ALLOWED_ENV_TYPES), + type=click.Choice(get_args(EnvType)), default='mainnet', help='Network to check', ) diff --git a/node_cli/configs/__init__.py b/node_cli/configs/__init__.py index 35d776d8..f9fc0aa5 100644 --- a/node_cli/configs/__init__.py +++ b/node_cli/configs/__init__.py @@ -19,6 +19,7 @@ import os import sys +from pathlib import Path from node_cli.utils.global_config import read_g_config @@ -43,6 +44,11 @@ NODE_DATA_PATH = os.path.join(SKALE_DIR, 'node_data') SCHAIN_NODE_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains') NODE_CLI_STATUS_FILENAME = 'node_cli.status' + +SETTINGS_DIR = Path(NODE_DATA_PATH) / 'settings' +NODE_SETTINGS_PATH = SETTINGS_DIR / 'node.toml' +INTERNAL_SETTINGS_PATH = SETTINGS_DIR / 'internal.toml' + NODE_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'node_config.json') CONTAINER_CONFIG_PATH = os.path.join(SKALE_DIR, 'config') CONTAINER_CONFIG_TMP_PATH = os.path.join(SKALE_TMP_DIR, 'config') @@ -52,8 +58,6 @@ INIT_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') SKALE_RUN_DIR = '/var/run/skale' -SGX_CERTIFICATES_DIR_NAME = 'sgx_certs' - COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose.yml') FAIR_COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose-fair.yml') STATIC_PARAMS_FILEPATH = os.path.join(CONTAINER_CONFIG_PATH, 'static_params.yaml') @@ -95,9 +99,6 @@ IPTABLES_RULES_STATE_FILEPATH = os.path.join(IPTABLES_DIR, 'rules.v4') DEFAULT_SSH_PORT = 22 -FLASK_SECRET_KEY_FILENAME = 'flask_db_key.txt' -FLASK_SECRET_KEY_FILE = os.path.join(NODE_DATA_PATH, FLASK_SECRET_KEY_FILENAME) - DOCKER_CONFIG_FILEPATH = '/etc/docker/daemon.json' HIDE_STREAM_LOG = os.getenv('HIDE_STREAM_LOG') diff --git a/node_cli/configs/user.py b/node_cli/configs/_user.py similarity index 98% rename from node_cli/configs/user.py rename to node_cli/configs/_user.py index a4f52c2c..6ab7b6f5 100644 --- a/node_cli/configs/user.py +++ b/node_cli/configs/_user.py @@ -52,6 +52,7 @@ class ValidationResult(NamedTuple): class BaseUserConfig(ABC): node_version: str env_type: str + endpoint: str filebeat_host: str block_device: str @@ -89,7 +90,6 @@ def validate_params(cls, params: Dict) -> ValidationResult: @dataclass class FairUserConfig(BaseUserConfig): fair_contracts: str - boot_endpoint: str sgx_server_url: str enforce_btrfs: str = '' telegraf: str = '' @@ -99,13 +99,11 @@ class FairUserConfig(BaseUserConfig): @dataclass class PassiveFairUserConfig(BaseUserConfig): fair_contracts: str - boot_endpoint: str enforce_btrfs: str = '' @dataclass class FairBootUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str ima_contracts: str sgx_server_url: str @@ -114,7 +112,6 @@ class FairBootUserConfig(BaseUserConfig): @dataclass class SkaleUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str ima_contracts: str docker_lvmpy_version: str @@ -132,7 +129,6 @@ class SkaleUserConfig(BaseUserConfig): @dataclass class PassiveSkaleUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str schain_name: str = '' ima_contracts: str = '' diff --git a/node_cli/core/host.py b/node_cli/core/host.py index da4be640..920b651e 100644 --- a/node_cli/core/host.py +++ b/node_cli/core/host.py @@ -39,6 +39,7 @@ SGX_CERTS_PATH, REPORTS_PATH, REDIS_DATA_PATH, + SETTINGS_DIR, SCHAINS_DATA_PATH, LOG_PATH, REMOVED_CONTAINERS_FOLDER_PATH, @@ -50,7 +51,6 @@ NGINX_CONFIG_FILEPATH, ) from node_cli.configs.cli_logger import LOG_DATA_PATH -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH from node_cli.core.nftables import NFTablesManager from node_cli.utils.helper import safe_mkdir @@ -73,20 +73,6 @@ def fix_url(url): return False -def get_flask_secret_key() -> str: - secret_key_filepath = os.path.join(NODE_DATA_PATH, 'flask_db_key.txt') - - if not os.path.exists(secret_key_filepath): - error_exit(f'Flask secret key file not found at {secret_key_filepath}') - - try: - with open(secret_key_filepath, 'r') as key_file: - secret_key = key_file.read().strip() - return secret_key - except (IOError, OSError) as e: - error_exit(f'Failed to read Flask secret key: {e}') - - def prepare_host(env_filepath: str, env_type: str, allocation: bool = False) -> None: if not env_filepath or not env_type: error_exit('Missing required parameters for host initialization') @@ -121,6 +107,7 @@ def make_dirs(): LOG_PATH, REPORTS_PATH, REDIS_DATA_PATH, + SETTINGS_DIR, SKALE_RUN_DIR, SKALE_STATE_DIR, SKALE_TMP_DIR, @@ -128,16 +115,6 @@ def make_dirs(): safe_mkdir(dir_path) -def save_env_params(env_filepath: str) -> None: - copyfile(env_filepath, SKALE_DIR_ENV_FILEPATH) - - -def link_env_file(): - if not (os.path.islink(CONFIGS_ENV_FILEPATH) or os.path.isfile(CONFIGS_ENV_FILEPATH)): - logger.info('Creating symlink %s → %s', SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH) - os.symlink(SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH) - - def init_logs_dir(): safe_mkdir(LOG_DATA_PATH) safe_mkdir(REMOVED_CONTAINERS_FOLDER_PATH) diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 2128d43f..191d2077 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -43,9 +43,8 @@ TM_INIT_TIMEOUT, ) from node_cli.configs.cli_logger import LOG_DATA_PATH as CLI_LOG_DATA_PATH -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, get_validated_user_config from node_cli.core.checks import run_checks as run_host_checks -from node_cli.core.host import get_flask_secret_key, is_node_inited, save_env_params +from node_cli.core.host import is_node_inited from node_cli.core.resources import update_resource_allocation from node_cli.core.node_options import ( active_fair, @@ -152,11 +151,11 @@ def register_node(name, p2p_ip, public_ip, port, domain_name): @check_not_inited -def init(env_filepath: str, node_type: NodeType) -> None: +def init(config_file: str, node_type: NodeType) -> None: node_mode = NodeMode.ACTIVE - env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode) + env = compose_node_env(node_type=node_type, node_mode=node_mode) - init_op(env_filepath=env_filepath, env=env, node_mode=node_mode) + init_op(env_filepath=config_file, env=env, node_mode=node_mode) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode): @@ -169,7 +168,7 @@ def init(env_filepath: str, node_type: NodeType) -> None: @check_not_inited def restore(backup_path, env_filepath, node_type: NodeType, no_snapshot=False, config_only=False): node_mode = NodeMode.ACTIVE - env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode) + env = compose_node_env(node_type=node_type, node_mode=node_mode) if env is None: return save_env_params(env_filepath) @@ -211,7 +210,7 @@ def update_passive(env_filepath: str) -> None: prev_version = CliMetaManager().get_meta_info().version if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0': migrate_2_6() - env = compose_node_env(env_filepath, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) + env = compose_node_env(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) update_ok = update_passive_op(env_filepath, env) if update_ok: logger.info('Waiting for containers initialization') @@ -227,69 +226,22 @@ def update_passive(env_filepath: str) -> None: @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, - save=False, - node_type=NodeType.SKALE, - node_mode=node_mode, - skip_user_conf_validation=True, - ) + env = compose_node_env(NodeType.SKALE, node_mode) cleanup_skale_op(node_mode=node_mode, env=env, prune=prune) logger.info('SKALE node was cleaned up, all containers and data removed') -def compose_node_env( - env_filepath: str, - node_type: NodeType, - node_mode: NodeMode, - inited_node: bool = False, - sync_schains: Optional[bool] = None, - pull_config_for_schain: Optional[str] = None, - save: bool = True, - is_fair_boot: bool = False, - skip_user_conf_validation: bool = False, -) -> dict[str, str]: - if env_filepath is not None: - user_config = get_validated_user_config( - node_type=node_type, - node_mode=node_mode, - env_filepath=env_filepath, - is_fair_boot=is_fair_boot, - skip_user_conf_validation=skip_user_conf_validation, - ) - if save: - save_env_params(env_filepath) - else: - user_config = get_validated_user_config( - node_type=node_type, - node_mode=node_mode, - env_filepath=INIT_ENV_FILEPATH, - is_fair_boot=is_fair_boot, - skip_user_conf_validation=skip_user_conf_validation, - ) - +def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str]: if node_mode == NodeMode.PASSIVE or node_type == NodeType.FAIR: mnt_dir = SCHAINS_MNT_DIR_SINGLE_CHAIN else: mnt_dir = SCHAINS_MNT_DIR_REGULAR - env = { 'SKALE_DIR': SKALE_DIR, 'SCHAINS_MNT_DIR': mnt_dir, 'FILESTORAGE_MAPPING': FILESTORAGE_MAPPING, 'SKALE_LIB_PATH': SKALE_STATE_DIR, - **user_config.to_env(), } - - if inited_node and not node_mode == NodeMode.PASSIVE: - env['FLASK_SECRET_KEY'] = get_flask_secret_key() - - if sync_schains and not node_mode == NodeMode.PASSIVE: - env['BACKUP_RUN'] = 'True' - - if pull_config_for_schain: - env['PULL_CONFIG_FOR_SCHAIN'] = pull_config_for_schain - return {k: v for k, v in env.items() if v != ''} @@ -313,10 +265,10 @@ def update( migrate_2_6() logger.info('Node update started') env = compose_node_env( - env_filepath, - inited_node=True, - sync_schains=False, - pull_config_for_schain=pull_config_for_schain, + # env_filepath, + # inited_node=True, + # sync_schains=False, + # pull_config_for_schain=pull_config_for_schain, node_type=node_type, node_mode=node_mode, ) diff --git a/node_cli/operations/base.py b/node_cli/operations/base.py index 1a64d585..713a6775 100644 --- a/node_cli/operations/base.py +++ b/node_cli/operations/base.py @@ -53,7 +53,7 @@ cleanup_no_lvm_datadir, update_node_cli_schain_status, ) -from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive +from node_cli.operations.common import configure_filebeat, unpack_backup_archive from node_cli.operations.config_repo import ( download_skale_node, sync_skale_node, @@ -76,6 +76,7 @@ from node_cli.utils.meta import CliMetaManager, FairCliMetaManager from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_failed_requirements_checks +from node_cli.utils.settings import save_settings logger = logging.getLogger(__name__) @@ -134,6 +135,7 @@ def update(env_filepath: str, env: Dict, node_mode: NodeMode) -> bool: generate_nginx_config() prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) + save_settings(node_type=NodeType.SKALE, node_mode=node_mode) init_shared_space_volume(env['ENV_TYPE']) meta_manager = CliMetaManager() @@ -170,12 +172,12 @@ def init(env_filepath: str, env: dict, node_mode: NodeMode) -> None: configure_nftables(enable_monitoring=enable_monitoring) prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.SKALE, node_mode=node_mode) link_env_file() mark_active_node() configure_filebeat() - configure_flask() generate_nginx_config() lvmpy_install(env) @@ -222,7 +224,7 @@ def init_passive( NodeMode.PASSIVE, env['ENV_TYPE'], CONTAINER_CONFIG_PATH, - check_type=CheckType.PREINSTALL + check_type=CheckType.PREINSTALL, ) if failed_checks: print_failed_requirements_checks(failed_checks) @@ -280,7 +282,7 @@ def update_passive(env_filepath: str, env: Dict) -> bool: NodeMode.PASSIVE, env['ENV_TYPE'], CONTAINER_CONFIG_PATH, - check_type=CheckType.PREINSTALL + check_type=CheckType.PREINSTALL, ) if failed_checks: print_failed_requirements_checks(failed_checks) diff --git a/node_cli/operations/common.py b/node_cli/operations/common.py index 7c876fa8..c595a3b8 100644 --- a/node_cli/operations/common.py +++ b/node_cli/operations/common.py @@ -19,7 +19,6 @@ import logging import os -import secrets import shutil import stat import tarfile @@ -27,7 +26,6 @@ from node_cli.configs import ( FILEBEAT_CONFIG_PATH, - FLASK_SECRET_KEY_FILE, G_CONF_HOME, SRC_FILEBEAT_CONFIG_PATH, ) @@ -43,17 +41,6 @@ def configure_filebeat(): logger.info('Filebeat configured') -def configure_flask(): - if os.path.isfile(FLASK_SECRET_KEY_FILE): - logger.info('Flask secret key already exists') - else: - logger.info('Generating Flask secret key...') - flask_secret_key = secrets.token_urlsafe(16) - with open(FLASK_SECRET_KEY_FILE, 'w') as f: - f.write(flask_secret_key) - logger.info('Flask secret key generated and saved') - - def unpack_backup_archive(backup_path: str) -> None: logger.info('Unpacking backup archive...') with tarfile.open(backup_path) as tar: diff --git a/node_cli/operations/fair.py b/node_cli/operations/fair.py index f1e62305..096d1623 100644 --- a/node_cli/operations/fair.py +++ b/node_cli/operations/fair.py @@ -46,7 +46,7 @@ ) from node_cli.migrations.fair.from_boot import migrate_nftables_from_boot from node_cli.operations.base import checked_host, turn_off -from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive +from node_cli.operations.common import configure_filebeat, unpack_backup_archive from node_cli.operations.config_repo import ( sync_skale_node, update_images, @@ -70,6 +70,7 @@ from node_cli.utils.meta import FairCliMetaManager from node_cli.utils.print_formatters import print_failed_requirements_checks from node_cli.utils.node_type import NodeMode, NodeType +from node_cli.utils.settings import save_settings logger = logging.getLogger(__name__) @@ -93,11 +94,11 @@ def init_fair_boot(env_filepath: str, env: dict) -> None: configure_nftables(enable_monitoring=enable_monitoring) prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) link_env_file() mark_active_node() configure_filebeat() - configure_flask() generate_nginx_config() prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') @@ -131,10 +132,10 @@ def init( configure_nftables() configure_filebeat() - configure_flask() generate_nginx_config() prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=node_mode) link_env_file() prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') @@ -186,6 +187,7 @@ def update_fair_boot(env_filepath: str, env: dict, node_mode: NodeMode = NodeMod prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') prepare_host(env_filepath, env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream @@ -231,6 +233,7 @@ def update( generate_nginx_config() prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) + save_settings(node_type=NodeType.FAIR, node_mode=node_mode) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True' diff --git a/node_cli/utils/docker_utils.py b/node_cli/utils/docker_utils.py index 695a1253..37e3fab9 100644 --- a/node_cli/utils/docker_utils.py +++ b/node_cli/utils/docker_utils.py @@ -338,9 +338,6 @@ def compose_up( run_cmd(cmd=get_up_compose_cmd(node_type=node_type, node_mode=node_mode), env=env) return - if 'SGX_CERTIFICATES_DIR_NAME' not in env: - env['SGX_CERTIFICATES_DIR_NAME'] = SGX_CERTIFICATES_DIR_NAME - if active_fair(node_type, node_mode): logger.info('Running fair base set of containers') if is_fair_boot: diff --git a/node_cli/utils/helper.py b/node_cli/utils/helper.py index dd1a7151..9d96a3ca 100644 --- a/node_cli/utils/helper.py +++ b/node_cli/utils/helper.py @@ -30,6 +30,7 @@ import urllib.parse import urllib.request import uuid +from pathlib import Path from functools import wraps from logging import Formatter, StreamHandler from typing import Any, NoReturn, Optional @@ -85,13 +86,13 @@ def write_json(path: str, content: dict) -> None: json.dump(content, outfile, indent=4) -def save_json(path: str, content: dict) -> None: +def save_json(path: str | Path, content: dict) -> None: tmp_path = get_tmp_path(path) write_json(tmp_path, content) shutil.move(tmp_path, path) -def init_file(path, content=None): +def init_file(path: str | Path, content=None): if not os.path.exists(path): write_json(path, content) @@ -339,7 +340,7 @@ def cleanup_dir_content(folder: str) -> None: shutil.rmtree(file_path) -def safe_mkdir(path: str, print_res: bool = False) -> None: +def safe_mkdir(path: str | Path, print_res: bool = False) -> None: if os.path.exists(path): logger.debug(f'Directory {path} already exists') return @@ -411,8 +412,8 @@ def convert(self, value, param, ctx): IP_TYPE = IpType() -def get_tmp_path(path: str) -> str: - base, ext = os.path.splitext(path) +def get_tmp_path(path: str | Path) -> str: + base, ext = os.path.splitext(str(path)) salt = uuid.uuid4().hex[:5] return base + salt + '.tmp' + ext diff --git a/node_cli/utils/node_type.py b/node_cli/utils/node_type.py index bf3f6d4f..754a1d69 100644 --- a/node_cli/utils/node_type.py +++ b/node_cli/utils/node_type.py @@ -20,9 +20,9 @@ from enum import Enum -class NodeType(Enum): - SKALE = 0 - FAIR = 1 +class NodeType(str, Enum): + SKALE = 'skale' + FAIR = 'fair' class NodeMode(str, Enum): diff --git a/node_cli/utils/print_formatters.py b/node_cli/utils/print_formatters.py index 7edec113..1da07d51 100644 --- a/node_cli/utils/print_formatters.py +++ b/node_cli/utils/print_formatters.py @@ -339,9 +339,6 @@ def format_timestamp(value): return str(value) -1 - - def print_chain_record(record): print( inspect.cleandoc(f""" diff --git a/node_cli/utils/settings.py b/node_cli/utils/settings.py new file mode 100644 index 00000000..c0b3aa76 --- /dev/null +++ b/node_cli/utils/settings.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# This file is part of node-cli +# +# Copyright (C) 2026 SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from skale.core.settings import ( + SETTINGS_MAP, + write_node_settings_file, + write_internal_settings_file, + InternalSettings, + SkaleSettings, + SkalePassiveSettings, + FairSettings, + FairBaseSettings, +) + +from node_cli.configs import NODE_SETTINGS_PATH, INTERNAL_SETTINGS_PATH + +from node_cli.utils.node_type import NodeMode, NodeType + +InternalSettings.model_config['toml_file'] = INTERNAL_SETTINGS_PATH +SkaleSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +SkalePassiveSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +FairSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +FairBaseSettings.model_config['toml_file'] = NODE_SETTINGS_PATH + + +def save_settings(node_type: NodeType, node_mode: NodeMode) -> None: + write_internal_settings_file(path=INTERNAL_SETTINGS_PATH, data={}) # todof: fix + settings_type = SETTINGS_MAP[(node_type.value, node_mode.value)] + write_node_settings_file( + path=NODE_SETTINGS_PATH, settings_type=settings_type, data={} + ) # todof: fix diff --git a/pyproject.toml b/pyproject.toml index cf813bfa..3f2e25d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,15 +10,13 @@ readme = "README.md" requires-python = ">=3.13" license = { file = "LICENSE" } keywords = ["skale", "cli"] -authors = [ - { name = "SKALE Labs", email = "support@skalelabs.com" } -] +authors = [{ name = "SKALE Labs", email = "support@skalelabs.com" }] classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Affero General Public License v3", - "Natural Language :: English", - "Programming Language :: Python :: 3.13", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Natural Language :: English", + "Programming Language :: Python :: 3.13", ] dependencies = [ @@ -40,13 +38,14 @@ dependencies = [ "MarkupSafe==3.0.3", "Flask==3.1.2", "itsdangerous==2.2.0", - "cryptography==46.0.3", + "cryptography==46.0.5", "filelock==3.20.0", "sh==2.2.2", "python-crontab==3.3.0", "requests-mock==1.12.1", - "redis==7.1.0", - "PyInstaller==6.16.0", + "redis==7.1.1", + "PyInstaller==6.18.0", + "skale.py==7.12dev2", ] [project.urls] @@ -78,11 +77,13 @@ target-version = "py313" [tool.ruff.format] quote-style = "single" +[tool.uv] +prerelease = "allow" + + [tool.pytest.ini_options] log_cli = false log_cli_level = "INFO" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_cli_date_format = "%Y-%m-%d %H:%M:%S" -filterwarnings = [ - "ignore::DeprecationWarning", -] +filterwarnings = ["ignore::DeprecationWarning"] diff --git a/scripts/export_env.sh b/scripts/export_env.sh new file mode 100644 index 00000000..af30b4ab --- /dev/null +++ b/scripts/export_env.sh @@ -0,0 +1,8 @@ +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +PROJECT_DIR=$(dirname $DIR) + +export LVMPY_LOG_DIR="$PROJECT_DIR/tests/" +export HIDE_STREAM_LOG=true +export TEST_HOME_DIR="$PROJECT_DIR/tests/" +export GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale" +export DOTENV_FILEPATH='tests/test-env' \ No newline at end of file diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index efc72c6c..23592396 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -1,11 +1,3 @@ #!/usr/bin/env bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PROJECT_DIR=$(dirname $DIR) - -LVMPY_LOG_DIR="$PROJECT_DIR/tests/" \ - HIDE_STREAM_LOG=true \ - TEST_HOME_DIR="$PROJECT_DIR/tests/" \ - GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale" \ - DOTENV_FILEPATH='tests/test-env' \ - py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@ +py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@ diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py index b31cddc0..bb5aa6d0 100644 --- a/tests/cli/node_test.py +++ b/tests/cli/node_test.py @@ -422,7 +422,6 @@ def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None}) with ( mock.patch('subprocess.run', new=subprocess_run_mock), - mock.patch('node_cli.core.node.get_flask_secret_key'), mock.patch('node_cli.core.node.turn_on_op'), mock.patch('node_cli.core.node.is_base_containers_alive'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True), @@ -475,6 +474,7 @@ def test_node_version(meta_file_v2): == "{'version': '0.1.1', 'config_stream': 'develop', 'docker_lvmpy_version': '1.1.2'}\n" ) + def test_cleanup_node(mocked_g_config): pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True) @@ -493,4 +493,4 @@ def test_cleanup_node(mocked_g_config): ): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 - cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={}) \ No newline at end of file + cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={}) diff --git a/tests/conftest.py b/tests/conftest.py index 5434e697..cc5a5fa3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -380,7 +380,7 @@ def fair_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') try: test_env = """ - BOOT_ENDPOINT=http://localhost:8545 + ENDPOINT=http://localhost:8545 NODE_VERSION='main' FILEBEAT_HOST=127.0.0.1:3010 SGX_SERVER_URL=http://127.0.0.1 diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py index 953e7ca5..0fe113b2 100644 --- a/tests/core/core_node_test.py +++ b/tests/core/core_node_test.py @@ -243,7 +243,6 @@ def test_compose_node_env( inited_node, sync_schains, expected_mnt_dir, - expect_flask_key, expect_backup_run, ): user_config_path = request.getfixturevalue(test_user_conf) @@ -251,7 +250,6 @@ def test_compose_node_env( with ( mock.patch('node_cli.configs.user.validate_alias_or_address'), mock.patch('node_cli.core.node.save_env_params'), - mock.patch('node_cli.core.node.get_flask_secret_key', return_value='mock_secret'), ): result_env = compose_node_env( env_filepath=user_config_path.as_posix(), @@ -264,11 +262,6 @@ def test_compose_node_env( ) assert result_env['SCHAINS_MNT_DIR'] == expected_mnt_dir - assert ( - 'FLASK_SECRET_KEY' in result_env and result_env['FLASK_SECRET_KEY'] is not None - ) == expect_flask_key - if expect_flask_key: - assert result_env['FLASK_SECRET_KEY'] == 'mock_secret' should_have_backup = sync_schains and node_mode != NodeMode.PASSIVE assert ('BACKUP_RUN' in result_env and result_env['BACKUP_RUN'] == 'True') == should_have_backup @@ -358,7 +351,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n with ( mock.patch('subprocess.run', new=subprocess_run_mock), mock.patch('node_cli.core.node.update_op'), - mock.patch('node_cli.core.node.get_flask_secret_key'), mock.patch('node_cli.core.node.save_env_params'), mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.core.host.prepare_host'), @@ -500,4 +492,5 @@ def test_cleanup_success( skip_user_conf_validation=True, ) mock_cleanup_skale_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False) \ No newline at end of file + node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + ) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py new file mode 100644 index 00000000..1d9deeb1 --- /dev/null +++ b/tests/utils/settings_test.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of node-cli +# +# Copyright (C) 2026 SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from node_cli.utils.node_type import NodeMode, NodeType From 6e9c17318e3080358d281dbfe7efe7f421cd472b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 11:09:42 +0000 Subject: [PATCH 02/10] new settings structure --- node_cli/cli/fair_boot.py | 2 +- node_cli/cli/fair_node.py | 30 +-- node_cli/cli/node.py | 4 +- node_cli/cli/passive_fair_node.py | 18 +- node_cli/cli/schains.py | 16 +- node_cli/configs/__init__.py | 4 - node_cli/configs/_user.py | 212 ----------------- node_cli/core/checks.py | 4 +- node_cli/core/host.py | 10 +- node_cli/core/node.py | 101 ++++---- node_cli/core/resources.py | 18 +- node_cli/core/schains.py | 14 +- node_cli/core/static_config.py | 8 +- node_cli/fair/active.py | 35 ++- node_cli/fair/boot.py | 30 +-- node_cli/fair/common.py | 72 ++---- node_cli/fair/record/chain_record.py | 19 +- node_cli/operations/base.py | 254 ++++++++++++--------- node_cli/operations/config_repo.py | 11 +- node_cli/operations/docker_lvmpy.py | 6 +- node_cli/operations/fair.py | 205 ++++++++++------- node_cli/utils/docker_utils.py | 10 +- node_cli/utils/helper.py | 10 - node_cli/utils/settings.py | 54 ++++- tests/cli/fair_cli_test.py | 6 +- tests/cli/node_test.py | 4 +- tests/configs/configs_env_validate_test.py | 72 ------ tests/conftest.py | 6 + tests/core/core_node_test.py | 88 ++----- tests/fair/fair_node_test.py | 13 +- tests/fixtures/__init__.py | 0 tests/fixtures/settings.py | 131 +++++++++++ tests/utils/settings_test.py | 1 - 33 files changed, 683 insertions(+), 785 deletions(-) delete mode 100644 node_cli/configs/_user.py create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/settings.py diff --git a/node_cli/cli/fair_boot.py b/node_cli/cli/fair_boot.py index f5dce9b1..d5ff6039 100644 --- a/node_cli/cli/fair_boot.py +++ b/node_cli/cli/fair_boot.py @@ -88,6 +88,6 @@ def signature_boot(validator_id): @streamed_cmd def update_node(env_file, pull_config_for_schain): update( - env_filepath=env_file, + config_file=env_file, pull_config_for_schain=pull_config_for_schain, ) diff --git a/node_cli/cli/fair_node.py b/node_cli/cli/fair_node.py index 56ab9d75..efdf3149 100644 --- a/node_cli/cli/fair_node.py +++ b/node_cli/cli/fair_node.py @@ -57,10 +57,10 @@ def fair_node_info(format): @node.command('init', help='Initialize regular Fair node') -@click.argument('env_filepath') +@click.argument('config_file') @streamed_cmd -def init_node(env_filepath: str): - init_fair(node_mode=NodeMode.ACTIVE, env_filepath=env_filepath) +def init_node(config_file: str): + init_fair(node_mode=NodeMode.ACTIVE, config_file=config_file) @node.command('register', help=TEXTS['fair']['node']['register']['help']) @@ -70,7 +70,7 @@ def register(ip: str) -> None: @node.command('update', help='Update Fair node') -@click.argument('env_filepath') +@click.argument('config_file') @click.option( '--yes', is_flag=True, @@ -88,10 +88,10 @@ def register(ip: str) -> None: is_flag=True, ) @streamed_cmd -def update_node(env_filepath: str, pull_config_for_schain, force_skaled_start: bool): +def update_node(config_file: str, pull_config_for_schain, force_skaled_start: bool): update_fair( node_mode=NodeMode.ACTIVE, - env_filepath=env_filepath, + config_file=config_file, pull_config_for_schain=pull_config_for_schain, force_skaled_start=force_skaled_start, ) @@ -106,7 +106,7 @@ def backup_node(backup_folder_path): @node.command('restore', help='Restore Fair node from a backup file.') @click.argument('backup_path') -@click.argument('env_file') +@click.argument('config_file') @click.option( '--config-only', help='Only restore configuration files in .skale and artifacts', @@ -114,12 +114,12 @@ def backup_node(backup_folder_path): hidden=True, ) @streamed_cmd -def restore_node(backup_path, env_file, config_only): - restore_fair(backup_path, env_file, config_only) +def restore_node(backup_path, config_file, config_only): + restore_fair(backup_path, config_file, config_only) @node.command('migrate', help='Switch from boot to regular Fair node.') -@click.argument('env_filepath') +@click.argument('config_file') @click.option( '--yes', is_flag=True, @@ -128,8 +128,8 @@ def restore_node(backup_path, env_file, config_only): prompt='Are you sure you want to migrate to regular Fair node? The action cannot be undone', ) @streamed_cmd -def migrate_node(env_filepath: str) -> None: - migrate_from_boot(env_filepath=env_filepath) +def migrate_node(config_file: str) -> None: + migrate_from_boot(config_file=config_file) @node.command('repair', help='Toggle fair chain repair mode') @@ -221,7 +221,7 @@ def turn_off_node() -> None: expose_value=False, prompt='Are you sure you want to turn on the node?', ) -@click.argument('env_filepath') +@click.argument('config_file') @streamed_cmd -def turn_on_node(env_filepath: str) -> None: - turn_on_fair(env_file=env_filepath, node_type=TYPE) +def turn_on_node(config_file: str) -> None: + turn_on_fair(env_file=config_file, node_type=TYPE) diff --git a/node_cli/cli/node.py b/node_cli/cli/node.py index f13825d9..cb798a1c 100644 --- a/node_cli/cli/node.py +++ b/node_cli/cli/node.py @@ -108,7 +108,7 @@ def init_node(config_file): def update_node(config_file, pull_config_for_schain, unsafe_ok): update( node_mode=NodeMode.ACTIVE, - env_filepath=config_file, + config_file=config_file, pull_config_for_schain=pull_config_for_schain, node_type=TYPE, unsafe_ok=unsafe_ok, @@ -145,7 +145,7 @@ def backup_node(backup_folder_path): def restore_node(backup_path, env_file, no_snapshot, config_only): restore( backup_path=backup_path, - env_filepath=env_file, + config_file=env_file, no_snapshot=no_snapshot, config_only=config_only, node_type=TYPE, diff --git a/node_cli/cli/passive_fair_node.py b/node_cli/cli/passive_fair_node.py index 83eb1bd1..7bac3581 100644 --- a/node_cli/cli/passive_fair_node.py +++ b/node_cli/cli/passive_fair_node.py @@ -49,7 +49,7 @@ def passive_node(): @passive_node.command('init', help='Initialize a passive Fair node') -@click.argument('env_filepath') +@click.argument('config_file') @click.option('--id', required=True, type=int, help=TEXTS['fair']['node']['setup']['id']) @click.option('--indexer', help=TEXTS['passive_node']['init']['indexer'], is_flag=True) @click.option('--archive', help=TEXTS['passive_node']['init']['archive'], is_flag=True) @@ -61,7 +61,7 @@ def passive_node(): ) @streamed_cmd def init_passive_node( - env_filepath: str, id: int, indexer: bool, archive: bool, snapshot: str | None + config_file: str, id: int, indexer: bool, archive: bool, snapshot: str | None ): if indexer and archive: error_exit('Cannot use both --indexer and --archive options') @@ -69,7 +69,7 @@ def init_passive_node( error_exit('Cannot use any for indexer/archive node') init_fair( node_mode=NodeMode.PASSIVE, - env_filepath=env_filepath, + config_file=config_file, node_id=id, indexer=indexer, archive=archive, @@ -78,7 +78,7 @@ def init_passive_node( @passive_node.command('update', help='Update Fair node') -@click.argument('env_filepath') +@click.argument('config_file') @click.option( '--yes', is_flag=True, @@ -96,10 +96,10 @@ def init_passive_node( is_flag=True, ) @streamed_cmd -def update_node(env_filepath: str, pull_config_for_schain, force_skaled_start: bool): +def update_node(config_file: str, pull_config_for_schain, force_skaled_start: bool): update_fair( node_mode=NodeMode.PASSIVE, - env_filepath=env_filepath, + config_file=config_file, pull_config_for_schain=pull_config_for_schain, force_skaled_start=force_skaled_start, ) @@ -146,7 +146,7 @@ def turn_off_node() -> None: expose_value=False, prompt='Are you sure you want to turn on the node?', ) -@click.argument('env_filepath') +@click.argument('config_file') @streamed_cmd -def turn_on_node(env_filepath: str) -> None: - turn_on_fair(env_file=env_filepath, node_type=TYPE) +def turn_on_node(config_file: str) -> None: + turn_on_fair(env_file=config_file, node_type=TYPE) diff --git a/node_cli/cli/schains.py b/node_cli/cli/schains.py index 803ea754..40f1ab0c 100644 --- a/node_cli/cli/schains.py +++ b/node_cli/cli/schains.py @@ -21,6 +21,8 @@ import click +from skale.core.settings import get_settings + from node_cli.utils.helper import abort_if_false, URL_TYPE from node_cli.core.schains import ( describe, @@ -104,8 +106,12 @@ def info_(schain_name: str, json_format: bool) -> None: @click.argument('schain_name') @click.argument('snapshot_path') @click.option('--schain-type', default='medium') -@click.option('--env-type', default=None) -def restore( - schain_name: str, snapshot_path: str, schain_type: str, env_type: Optional[str] -) -> None: - restore_schain_from_snapshot(schain_name, snapshot_path, node_type=TYPE) +def restore(schain_name: str, snapshot_path: str, schain_type: str) -> None: + settings = get_settings() + restore_schain_from_snapshot( + schain_name, + snapshot_path, + node_type=TYPE, + env_type=settings.env_type, + schain_type=schain_type, + ) diff --git a/node_cli/configs/__init__.py b/node_cli/configs/__init__.py index f9fc0aa5..7ed6afee 100644 --- a/node_cli/configs/__init__.py +++ b/node_cli/configs/__init__.py @@ -55,7 +55,6 @@ CONTRACTS_PATH = os.path.join(SKALE_DIR, 'contracts_info') REPORTS_PATH = os.path.join(SKALE_DIR, 'reports') BACKUP_CONTRACTS_PATH = os.path.join(SKALE_DIR, '.old_contracts_info') -INIT_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') SKALE_RUN_DIR = '/var/run/skale' COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose.yml') @@ -77,9 +76,6 @@ SGX_CERTS_PATH = os.path.join(NODE_DATA_PATH, 'sgx_certs') SCHAINS_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains') -CURRENT_FILE_LOCATION = os.path.dirname(os.path.realpath(__file__)) -DOTENV_FILEPATH = os.path.join(os.path.dirname(CURRENT_FILE_LOCATION), '.env') - SRC_FILEBEAT_CONFIG_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'filebeat.yml') FILEBEAT_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'filebeat.yml') diff --git a/node_cli/configs/_user.py b/node_cli/configs/_user.py deleted file mode 100644 index 6ab7b6f5..00000000 --- a/node_cli/configs/_user.py +++ /dev/null @@ -1,212 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of node-cli -# -# Copyright (C) 2019-Present SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import inspect -import os -from abc import ABC -from dataclasses import dataclass -from typing import Dict, NamedTuple - -from dotenv.main import DotEnv - -from node_cli.configs import CONTAINER_CONFIG_PATH, SKALE_DIR -from node_cli.configs.alias_address_validation import ContractType, validate_alias_or_address -from node_cli.utils.helper import error_exit -from node_cli.utils.node_type import NodeMode, NodeType -from node_cli.core.node_options import ( - active_fair, - active_skale, - passive_skale, - passive_fair, -) - -SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') -CONFIGS_ENV_FILEPATH = os.path.join(CONTAINER_CONFIG_PATH, '.env') - -ALLOWED_ENV_TYPES = ['mainnet', 'testnet', 'qanet', 'devnet'] - - -class ValidationResult(NamedTuple): - result: bool - missing: set - extra: set - - -@dataclass(kw_only=True) -class BaseUserConfig(ABC): - node_version: str - env_type: str - endpoint: str - filebeat_host: str - block_device: str - - container_configs_dir: str = '' - skip_docker_config: str = '' - skip_docker_cleanup: str = '' - - def to_env(self) -> Dict[str, str]: - result = {} - for field_name, field_value in self.__dict__.items(): - upper_key = field_name.upper() - result[upper_key] = str(field_value) if field_value is not None else '' - return result - - @classmethod - def validate_params(cls, params: Dict) -> ValidationResult: - parameters = inspect.signature(cls.__init__).parameters - missing = [] - keys = params.keys() - expected_keys = { - name.upper() - for name, value in parameters.items() - if name != 'self' and value.default == inspect._empty - } - optional_keys = { - name.upper() - for name, value in parameters.items() - if name != 'self' and value.default != inspect._empty - } - missing = expected_keys - keys - extra = keys - expected_keys - optional_keys - return ValidationResult(missing == set() and extra == set(), missing, extra) - - -@dataclass -class FairUserConfig(BaseUserConfig): - fair_contracts: str - sgx_server_url: str - enforce_btrfs: str = '' - telegraf: str = '' - influx_url: str = '' - - -@dataclass -class PassiveFairUserConfig(BaseUserConfig): - fair_contracts: str - enforce_btrfs: str = '' - - -@dataclass -class FairBootUserConfig(BaseUserConfig): - manager_contracts: str - ima_contracts: str - sgx_server_url: str - enforce_btrfs: str = '' - - -@dataclass -class SkaleUserConfig(BaseUserConfig): - manager_contracts: str - ima_contracts: str - docker_lvmpy_version: str - sgx_server_url: str - monitoring_containers: str = '' - telegraf: str = '' - influx_url: str = '' - tg_api_key: str = '' - tg_chat_id: str = '' - disable_dry_run: str = '' - default_gas_limit: str = '' - default_gas_price_wei: str = '' - bite: str = '' - - -@dataclass -class PassiveSkaleUserConfig(BaseUserConfig): - manager_contracts: str - schain_name: str = '' - ima_contracts: str = '' - enforce_btrfs: str = '' - bite: str = '' - - -def get_validated_user_config( - node_type: NodeType, - node_mode: NodeMode, - env_filepath: str = SKALE_DIR_ENV_FILEPATH, - is_fair_boot: bool = False, - skip_user_conf_validation: bool = False, -) -> BaseUserConfig: - params = parse_env_file(env_filepath) - user_config_class = get_user_config_class( - node_type=node_type, - node_mode=node_mode, - is_fair_boot=is_fair_boot, - ) - _, missing_params, extra_params = user_config_class.validate_params(params) - - if len(missing_params) > 0: - error_exit(f'Missing required parameters: {missing_params}') - - if len(extra_params) > 0: - error_exit(f'Extra parameters: {extra_params}') - - params = to_lower_keys(params) - user_config = user_config_class(**params) - if not skip_user_conf_validation: - validate_user_config(user_config) - - return user_config - - -def validate_user_config(user_config: BaseUserConfig) -> None: - validate_env_type(env_type=user_config.env_type) - - if not isinstance(user_config, FairUserConfig) and not isinstance( - user_config, PassiveFairUserConfig - ): - validate_alias_or_address( - user_config.manager_contracts, ContractType.MANAGER, user_config.endpoint - ) - - if isinstance(user_config, (SkaleUserConfig, FairBootUserConfig)): - validate_alias_or_address(user_config.ima_contracts, ContractType.IMA, user_config.endpoint) - - -def to_lower_keys(params: Dict[str, str]) -> Dict[str, str]: - return {key.lower(): value for key, value in params.items()} - - -def parse_env_file(env_filepath: str) -> Dict: - if not os.path.isfile(env_filepath): - error_exit(f'Failed to load environment from {env_filepath}') - return DotEnv(env_filepath).dict() - - -def get_user_config_class( - node_type: NodeType, - node_mode: NodeMode, - is_fair_boot: bool, -) -> type[BaseUserConfig]: - if node_type == NodeType.FAIR and is_fair_boot: - user_config_class = FairBootUserConfig - elif passive_fair(node_type, node_mode): - user_config_class = PassiveFairUserConfig - elif active_fair(node_type, node_mode): - user_config_class = FairUserConfig - elif passive_skale(node_type, node_mode): - user_config_class = PassiveSkaleUserConfig - elif active_skale(node_type, node_mode): - user_config_class = SkaleUserConfig - return user_config_class - - -def validate_env_type(env_type: str) -> None: - if env_type not in ALLOWED_ENV_TYPES: - error_exit(f'Allowed ENV_TYPE values are {ALLOWED_ENV_TYPES}. Actual: "{env_type}"') diff --git a/node_cli/core/checks.py b/node_cli/core/checks.py index a4c439c9..e7677847 100644 --- a/node_cli/core/checks.py +++ b/node_cli/core/checks.py @@ -48,6 +48,8 @@ from debian import debian_support from packaging.version import parse as version_parse +from skale.core.types import EnvType + from node_cli.configs import ( CHECK_REPORT_PATH, CONTAINER_CONFIG_PATH, @@ -471,7 +473,7 @@ def run_checks( disk: str, node_type: NodeType, node_mode: NodeMode, - env_type: str = 'mainnet', + env_type: EnvType = 'mainnet', config_path: str = CONTAINER_CONFIG_PATH, check_type: CheckType = CheckType.ALL, ) -> ResultList: diff --git a/node_cli/core/host.py b/node_cli/core/host.py index 920b651e..3a4b036a 100644 --- a/node_cli/core/host.py +++ b/node_cli/core/host.py @@ -19,9 +19,11 @@ import logging import os -from shutil import copyfile, chown +from shutil import chown from urllib.parse import urlparse +from skale.core.types import EnvType + from node_cli.core.resources import update_resource_allocation from node_cli.utils.helper import error_exit @@ -73,15 +75,11 @@ def fix_url(url): return False -def prepare_host(env_filepath: str, env_type: str, allocation: bool = False) -> None: - if not env_filepath or not env_type: - error_exit('Missing required parameters for host initialization') - +def prepare_host(env_type: EnvType, allocation: bool = False) -> None: try: logger.info('Preparing host started') make_dirs() chown(REDIS_DATA_PATH, user=999, group=1000) - save_env_params(env_filepath) if allocation: update_resource_allocation(env_type) diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 191d2077..8bef033c 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -33,7 +33,6 @@ BACKUP_ARCHIVE_NAME, CONTAINER_CONFIG_PATH, FILESTORAGE_MAPPING, - INIT_ENV_FILEPATH, LOG_PATH, RESTORE_SLEEP_TIMEOUT, SCHAINS_MNT_DIR_REGULAR, @@ -88,6 +87,8 @@ print_node_cmd_error, print_node_info, ) +from node_cli.utils.settings import validate_and_save_node_settings +from skale.core.settings import get_settings from node_cli.utils.texts import safe_load_texts logger = logging.getLogger(__name__) @@ -153,49 +154,62 @@ def register_node(name, p2p_ip, public_ip, port, domain_name): @check_not_inited def init(config_file: str, node_type: NodeType) -> None: node_mode = NodeMode.ACTIVE - env = compose_node_env(node_type=node_type, node_mode=node_mode) + settings = validate_and_save_node_settings(config_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) - init_op(env_filepath=config_file, env=env, node_mode=node_mode) + init_op(settings=settings, compose_env=compose_env, node_mode=node_mode) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode): error_exit('Containers are not running', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR) logger.info('Generating resource allocation file ...') - update_resource_allocation(env['ENV_TYPE']) + update_resource_allocation(settings.env_type) logger.info('Init procedure finished') @check_not_inited -def restore(backup_path, env_filepath, node_type: NodeType, no_snapshot=False, config_only=False): +def restore( + backup_path: str, + config_file: str, + node_type: NodeType, + no_snapshot: bool = False, + config_only: bool = False, +): node_mode = NodeMode.ACTIVE - env = compose_node_env(node_type=node_type, node_mode=node_mode) - if env is None: - return - save_env_params(env_filepath) - env['SKALE_DIR'] = SKALE_DIR - - if not no_snapshot: - logger.info('Adding BACKUP_RUN to env ...') - env['BACKUP_RUN'] = 'True' # should be str + settings = validate_and_save_node_settings(config_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) - restored_ok = restore_op(env, backup_path, node_type=node_type, config_only=config_only) + restored_ok = restore_op( + settings=settings, + compose_env=compose_env, + backup_path=backup_path, + node_type=node_type, + config_only=config_only, + backup_run=not no_snapshot, + ) if not restored_ok: error_exit('Restore operation failed', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR) time.sleep(RESTORE_SLEEP_TIMEOUT) logger.info('Generating resource allocation file ...') - update_resource_allocation(env['ENV_TYPE']) + update_resource_allocation(settings.env_type) print('Node is restored from backup') @check_not_inited def init_passive( - env_filepath: str, indexer: bool, archive: bool, snapshot: bool, snapshot_from: Optional[str] + config_file: str, indexer: bool, archive: bool, snapshot: bool, snapshot_from: Optional[str] ) -> None: node_mode = NodeMode.PASSIVE - env = compose_node_env(env_filepath, node_type=NodeType.SKALE, node_mode=node_mode) - if env is None: - return - init_passive_op(env_filepath, env, indexer, archive, snapshot, snapshot_from) + settings = validate_and_save_node_settings(config_file, NodeType.SKALE, node_mode) + compose_env = compose_node_env(node_type=NodeType.SKALE, node_mode=node_mode) + init_passive_op( + settings=settings, + compose_env=compose_env, + indexer=indexer, + archive=archive, + snapshot=snapshot, + snapshot_from=snapshot_from, + ) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=NodeType.SKALE, node_mode=node_mode): @@ -205,13 +219,14 @@ def init_passive( @check_inited @check_user -def update_passive(env_filepath: str) -> None: +def update_passive(config_file: str) -> None: logger.info('Node update started') prev_version = CliMetaManager().get_meta_info().version if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0': migrate_2_6() - env = compose_node_env(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) - update_ok = update_passive_op(env_filepath, env) + settings = validate_and_save_node_settings(config_file, NodeType.SKALE, NodeMode.PASSIVE) + compose_env = compose_node_env(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) + update_ok = update_passive_op(settings=settings, compose_env=compose_env) if update_ok: logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) @@ -248,7 +263,7 @@ def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str] @check_inited @check_user def update( - env_filepath: str, + config_file: str, pull_config_for_schain: Optional[str], node_type: NodeType, node_mode: NodeMode, @@ -264,15 +279,9 @@ def update( if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0': migrate_2_6() logger.info('Node update started') - env = compose_node_env( - # env_filepath, - # inited_node=True, - # sync_schains=False, - # pull_config_for_schain=pull_config_for_schain, - node_type=node_type, - node_mode=node_mode, - ) - update_ok = update_op(env_filepath, env, node_mode=node_mode) + settings = validate_and_save_node_settings(config_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) + update_ok = update_op(settings=settings, compose_env=compose_env, node_mode=node_mode) if update_ok: logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) @@ -385,24 +394,24 @@ def turn_off(node_type: NodeType, maintenance_on: bool = False, unsafe_ok: bool error_exit(error_msg, exit_code=CLIExitCodes.UNSAFE_UPDATE) if maintenance_on: set_maintenance_mode_on() - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, save=False, node_type=node_type, node_mode=node_mode - ) - turn_off_op(node_type=node_type, node_mode=node_mode, env=env) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) + turn_off_op(compose_env=compose_env, node_type=node_type, node_mode=node_mode) @check_inited @check_user -def turn_on(maintenance_off, sync_schains, env_file, node_type: NodeType) -> None: +def turn_on(maintenance_off: bool, sync_schains: bool, env_file: str, node_type: NodeType) -> None: node_mode = upsert_node_mode() - env = compose_node_env( - env_file, - inited_node=True, - sync_schains=sync_schains, + settings = validate_and_save_node_settings(env_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) + backup_run = sync_schains and node_mode != NodeMode.PASSIVE + turn_on_op( + settings=settings, + compose_env=compose_env, node_type=node_type, node_mode=node_mode, + backup_run=backup_run, ) - turn_on_op(env=env, node_type=node_type, node_mode=node_mode) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode): @@ -497,8 +506,8 @@ def run_checks( return if disk is None: - env_config = get_validated_user_config(node_type=node_type, node_mode=node_mode) - disk = env_config.block_device + settings = get_settings() + disk = settings.block_device failed_checks = run_host_checks(disk, node_type, node_mode, network, container_config_path) if not failed_checks: print('Requirements checking successfully finished!') diff --git a/node_cli/core/resources.py b/node_cli/core/resources.py index 430eee86..9a02d0b9 100644 --- a/node_cli/core/resources.py +++ b/node_cli/core/resources.py @@ -24,7 +24,9 @@ import psutil -from node_cli.configs.user import get_validated_user_config +from skale.core.types import EnvType + +from node_cli.utils.settings import validate_and_save_node_settings from node_cli.utils.docker_utils import ensure_volume from node_cli.utils.schain_types import SchainTypes from node_cli.utils.helper import write_json, read_json, run_cmd, safe_load_yml @@ -73,7 +75,7 @@ def get_resource_allocation_info(): return None -def compose_resource_allocation_config(env_type: str, params_by_env_type: Dict = None) -> Dict: +def compose_resource_allocation_config(env_type: EnvType, params_by_env_type: Dict = None) -> Dict: params_by_env_type = params_by_env_type or safe_load_yml(STATIC_PARAMS_FILEPATH) common_config = params_by_env_type['common'] schain_cpu_alloc, ima_cpu_alloc = get_cpu_alloc(common_config) @@ -93,22 +95,20 @@ def compose_resource_allocation_config(env_type: str, params_by_env_type: Dict = def generate_resource_allocation_config( - env_file, + env_file: str, node_type: NodeType, node_mode: NodeMode, - force=False, + force: bool = False, ) -> None: if not force and os.path.isfile(RESOURCE_ALLOCATION_FILEPATH): msg = 'Resource allocation file already exists' logger.debug(msg) print(msg) return - user_config = get_validated_user_config( - node_type=node_type, node_mode=node_mode, env_filepath=env_file - ) + settings = validate_and_save_node_settings(env_file, node_type, node_mode) logger.info('Generating resource allocation file ...') try: - update_resource_allocation(user_config.env_type) + update_resource_allocation(settings.env_type) except Exception as e: logger.exception(e) print("Can't generate resource allocation file, check out CLI logs") @@ -116,7 +116,7 @@ def generate_resource_allocation_config( print(f'Resource allocation file generated: {RESOURCE_ALLOCATION_FILEPATH}') -def update_resource_allocation(env_type: str) -> None: +def update_resource_allocation(env_type: EnvType) -> None: resource_allocation_config = compose_resource_allocation_config(env_type) write_json(RESOURCE_ALLOCATION_FILEPATH, resource_allocation_config) diff --git a/node_cli/core/schains.py b/node_cli/core/schains.py index 64c16031..174430e7 100644 --- a/node_cli/core/schains.py +++ b/node_cli/core/schains.py @@ -26,6 +26,8 @@ from pathlib import Path from typing import Dict, Optional +from skale.core.types import EnvType + from lvmpy.src.core import mount, volume_mountpoint from node_cli.configs import ( ALLOCATION_FILEPATH, @@ -34,7 +36,6 @@ SCHAIN_NODE_DATA_PATH, SCHAINS_MNT_DIR_SINGLE_CHAIN, ) -from node_cli.configs.user import get_validated_user_config from node_cli.utils.docker_utils import ensure_volume, is_volume_exists from node_cli.utils.exit_codes import CLIExitCodes from node_cli.utils.helper import ( @@ -214,12 +215,9 @@ def restore_schain_from_snapshot( schain: str, snapshot_path: str, node_type: NodeType, - env_type: Optional[str] = None, + env_type: EnvType, schain_type: str = 'medium', ) -> None: - if env_type is None: - user_config = get_validated_user_config(node_type=node_type) - env_type = user_config.env_type ensure_schain_volume(schain, schain_type, env_type) block_number = get_block_number_from_path(snapshot_path) if block_number == -1: @@ -242,12 +240,12 @@ def get_schains_by_artifacts() -> str: return '\n'.join(os.listdir(SCHAIN_NODE_DATA_PATH)) -def get_schain_volume_size(schain_type: str, env_type: str) -> int: +def get_schain_volume_size(schain_type: str, env_type: EnvType) -> int: alloc = safe_load_yml(ALLOCATION_FILEPATH) return alloc[env_type]['disk'][schain_type] -def ensure_schain_volume(schain: str, schain_type: str, env_type: str) -> None: +def ensure_schain_volume(schain: str, schain_type: str, env_type: EnvType) -> None: if not is_volume_exists(schain): size = get_schain_volume_size(schain_type, env_type) ensure_volume(schain, size) @@ -313,4 +311,4 @@ def cleanup_lvm_datadir(): cleanup_dir_content('/mnt/') logger.info('Removing LVM volume group "schains"...') run_cmd(['sudo', 'lvremove', '-f', 'schains'], check_code=False) - logger.info('Active node cleanup finished.') \ No newline at end of file + logger.info('Active node cleanup finished.') diff --git a/node_cli/core/static_config.py b/node_cli/core/static_config.py index a93a7a60..c2b90641 100644 --- a/node_cli/core/static_config.py +++ b/node_cli/core/static_config.py @@ -28,10 +28,12 @@ ) from node_cli.utils.node_type import NodeType +from skale.core.types import EnvType + def get_static_params( node_type: NodeType, - env_type: str = 'mainnet', + env_type: EnvType = 'mainnet', config_path: str = CONTAINER_CONFIG_PATH, ) -> dict: if node_type == NodeType.FAIR: @@ -46,7 +48,7 @@ def get_static_params( return ydata['envs'][env_type] -def get_fair_chain_name(env: dict) -> str: +def get_fair_chain_name(env_type: EnvType) -> str: node_type = NodeType.FAIR - params = get_static_params(node_type, env['ENV_TYPE']) + params = get_static_params(node_type, env_type) return params['info']['chain_name'] diff --git a/node_cli/fair/active.py b/node_cli/fair/active.py index 963686fe..2ec42bde 100644 --- a/node_cli/fair/active.py +++ b/node_cli/fair/active.py @@ -22,8 +22,8 @@ import time from typing import cast -from node_cli.configs import DEFAULT_SKALED_BASE_PORT, RESTORE_SLEEP_TIMEOUT, SKALE_DIR -from node_cli.core.host import is_node_inited, save_env_params +from node_cli.configs import DEFAULT_SKALED_BASE_PORT, RESTORE_SLEEP_TIMEOUT +from node_cli.core.host import is_node_inited from node_cli.core.node import compose_node_env, is_base_containers_alive from node_cli.operations import ( FairUpdateType, @@ -35,6 +35,7 @@ from node_cli.utils.helper import error_exit, get_request, post_request from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_node_cmd_error, print_node_info_fair +from node_cli.utils.settings import validate_and_save_node_settings from node_cli.utils.texts import safe_load_texts logger = logging.getLogger(__name__) @@ -63,19 +64,14 @@ def get_node_info(format): @check_inited @check_user def migrate_from_boot( - env_filepath: str, + config_file: str, ) -> None: logger.info('Migrating from boot to fair node...') - env = compose_node_env( - env_filepath, - inited_node=True, - sync_schains=False, - node_type=NodeType.FAIR, - node_mode=NodeMode.ACTIVE, - ) + settings = validate_and_save_node_settings(config_file, NodeType.FAIR, NodeMode.ACTIVE) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) migrate_ok = update_fair_op( - env_filepath, - env, + settings=settings, + compose_env=compose_env, node_mode=NodeMode.ACTIVE, update_type=FairUpdateType.FROM_BOOT, force_skaled_start=False, @@ -145,16 +141,17 @@ def exit() -> None: @check_not_inited -def restore(backup_path, env_filepath, config_only=False): +def restore(backup_path: str, config_file: str, config_only: bool = False): node_mode = NodeMode.ACTIVE - env = compose_node_env(env_filepath, node_type=NodeType.FAIR, node_mode=node_mode) - if env is None: - return - save_env_params(env_filepath) - env['SKALE_DIR'] = SKALE_DIR + settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode) restored_ok = restore_fair_op( - node_mode=node_mode, env=env, backup_path=backup_path, config_only=config_only + node_mode=node_mode, + settings=settings, + compose_env=compose_env, + backup_path=backup_path, + config_only=config_only, ) if not restored_ok: error_exit('Restore operation failed', exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR) diff --git a/node_cli/fair/boot.py b/node_cli/fair/boot.py index a6415f75..d9da98fa 100644 --- a/node_cli/fair/boot.py +++ b/node_cli/fair/boot.py @@ -30,22 +30,19 @@ from node_cli.utils.helper import error_exit from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_node_cmd_error +from node_cli.utils.settings import validate_and_save_node_settings logger = logging.getLogger(__name__) @check_not_inited -def init(env_filepath: str) -> None: +def init(config_file: str) -> None: node_mode = NodeMode.ACTIVE node_type = NodeType.FAIR - env = compose_node_env( - env_filepath, - node_type=node_type, - node_mode=node_mode, - is_fair_boot=True, - ) + settings = validate_and_save_node_settings(config_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) - init_fair_boot_op(env_filepath, env, node_mode) + init_fair_boot_op(settings=settings, compose_env=compose_env, node_mode=node_mode) logger.info('Waiting for fair containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode, is_fair_boot=True): @@ -55,19 +52,16 @@ def init(env_filepath: str) -> None: @check_inited @check_user -def update(env_filepath: str, pull_config_for_schain: str) -> None: +def update(config_file: str, pull_config_for_schain: str) -> None: logger.info('Fair boot node update started') node_mode = upsert_node_mode(node_mode=NodeMode.ACTIVE) - env = compose_node_env( - env_filepath, - inited_node=True, - sync_schains=False, - pull_config_for_schain=pull_config_for_schain, - node_type=NodeType.FAIR, - node_mode=node_mode, - is_fair_boot=True, + settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode) + migrate_ok = update_fair_boot_op( + settings=settings, + compose_env=compose_env, + node_mode=NodeMode.ACTIVE, ) - migrate_ok = update_fair_boot_op(env_filepath, env, node_mode=NodeMode.ACTIVE) if migrate_ok: logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py index 968891d4..3b506c74 100644 --- a/node_cli/fair/common.py +++ b/node_cli/fair/common.py @@ -20,9 +20,7 @@ import logging import time -from node_cli.configs import INIT_TIMEOUT, SKALE_DIR, TM_INIT_TIMEOUT -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH -from node_cli.core.host import save_env_params +from node_cli.configs import INIT_TIMEOUT, TM_INIT_TIMEOUT from node_cli.core.node import compose_node_env, is_base_containers_alive from node_cli.core.node_options import upsert_node_mode from node_cli.fair.passive import setup_fair_passive @@ -40,7 +38,9 @@ from node_cli.utils.helper import error_exit from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_node_cmd_error +from node_cli.utils.settings import validate_and_save_node_settings from node_cli.utils.texts import safe_load_texts +from skale.core.settings import get_settings logger = logging.getLogger(__name__) TEXTS = safe_load_texts() @@ -49,21 +49,18 @@ @check_not_inited def init( node_mode: NodeMode, - env_filepath: str, + config_file: str, node_id: int | None = None, indexer: bool = False, archive: bool = False, snapshot: str | None = None, ) -> None: - env = compose_node_env(env_filepath, node_type=NodeType.FAIR, node_mode=node_mode) - if env is None: - return - save_env_params(env_filepath) - env['SKALE_DIR'] = SKALE_DIR + settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode) init_ok = init_fair_op( - env_filepath, - env, + settings=settings, + compose_env=compose_env, node_mode=node_mode, indexer=indexer, archive=archive, @@ -82,14 +79,8 @@ def init( @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, - save=False, - node_type=NodeType.FAIR, - node_mode=node_mode, - skip_user_conf_validation=True, - ) - cleanup_fair_op(node_mode=node_mode, env=env, prune=prune) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode) + cleanup_fair_op(node_mode=node_mode, compose_env=compose_env, prune=prune) logger.info('Fair node was cleaned up, all containers and data removed') @@ -97,29 +88,23 @@ def cleanup(node_mode: NodeMode, prune: bool = False) -> None: @check_user def update( node_mode: NodeMode, - env_filepath: str, + config_file: str, pull_config_for_schain: str | None = None, force_skaled_start: bool = False, ) -> None: logger.info( 'Updating fair node: %s, pull_config_for_schain: %s, force_skaled_start: %s', - env_filepath, + config_file, pull_config_for_schain, force_skaled_start, ) node_mode = upsert_node_mode(node_mode=node_mode) - env = compose_node_env( - env_filepath, - inited_node=True, - sync_schains=False, - node_type=NodeType.FAIR, - node_mode=node_mode, - pull_config_for_schain=pull_config_for_schain, - ) + settings = validate_and_save_node_settings(config_file, NodeType.FAIR, node_mode) + compose_env = compose_node_env(node_type=NodeType.FAIR, node_mode=node_mode) update_ok = update_fair_op( - env_filepath, - env, + settings=settings, + compose_env=compose_env, node_mode=node_mode, update_type=FairUpdateType.REGULAR, force_skaled_start=force_skaled_start, @@ -133,34 +118,25 @@ def update( def repair_chain(snapshot_from: str = 'any') -> None: - node_mode = upsert_node_mode() - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, save=False, node_type=NodeType.FAIR, node_mode=node_mode - ) - repair_fair_op(env=env, snapshot_from=snapshot_from) + settings = get_settings() + repair_fair_op(env_type=settings.env_type, snapshot_from=snapshot_from) @check_inited @check_user def turn_off(node_type: NodeType) -> None: node_mode = upsert_node_mode() - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, save=False, node_type=node_type, node_mode=node_mode - ) - turn_off_op(node_type=node_type, node_mode=node_mode, env=env) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) + turn_off_op(compose_env=compose_env, node_type=node_type, node_mode=node_mode) @check_inited @check_user -def turn_on(env_file, node_type: NodeType) -> None: +def turn_on(env_file: str, node_type: NodeType) -> None: node_mode = upsert_node_mode() - env = compose_node_env( - env_file, - inited_node=True, - node_type=node_type, - node_mode=node_mode, - ) - turn_on_op(env=env, node_type=node_type, node_mode=node_mode) + settings = validate_and_save_node_settings(env_file, node_type, node_mode) + compose_env = compose_node_env(node_type=node_type, node_mode=node_mode) + turn_on_op(settings=settings, compose_env=compose_env, node_type=node_type, node_mode=node_mode) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode): diff --git a/node_cli/fair/record/chain_record.py b/node_cli/fair/record/chain_record.py index d4ac740c..16ff8a72 100644 --- a/node_cli/fair/record/chain_record.py +++ b/node_cli/fair/record/chain_record.py @@ -22,6 +22,8 @@ from typing import cast from datetime import datetime +from skale.core.types import EnvType + from node_cli.core.static_config import get_fair_chain_name from node_cli.fair.record.redis_record import FlatRedisRecord, FieldInfo @@ -78,18 +80,17 @@ def set_force_skaled_start(self, value: bool) -> None: self._set_field('force_skaled_start', value) -def get_fair_chain_record(env: dict) -> ChainRecord: - return ChainRecord(get_fair_chain_name(env)) +def get_fair_chain_record(env_type: EnvType) -> ChainRecord: + return ChainRecord(get_fair_chain_name(env_type)) -def migrate_chain_record(env: dict) -> None: - version = env['NODE_VERSION'] - logger.info('Migrating fair chain record, setting config version to %s', version) - record = get_fair_chain_record(env) - record.set_config_version(version) +def migrate_chain_record(env_type: EnvType, node_version: str) -> None: + logger.info('Migrating fair chain record, setting config version to %s', node_version) + record = get_fair_chain_record(env_type) + record.set_config_version(node_version) -def update_chain_record(env: dict, force_skaled_start: bool) -> None: - record = get_fair_chain_record(env) +def update_chain_record(env_type: EnvType, force_skaled_start: bool) -> None: + record = get_fair_chain_record(env_type) record.set_force_skaled_start(force_skaled_start) logger.info('Updated fair chain record with force_skaled_start=%s', force_skaled_start) diff --git a/node_cli/operations/base.py b/node_cli/operations/base.py index 713a6775..c164b11a 100644 --- a/node_cli/operations/base.py +++ b/node_cli/operations/base.py @@ -20,10 +20,12 @@ import functools import logging import time -from typing import Dict, Optional +from typing import Optional import distro +from skale.core.settings import BaseNodeSettings, SkalePassiveSettings, SkaleSettings, get_settings + from node_cli.cli.info import TYPE, VERSION from node_cli.configs import ( CONTAINER_CONFIG_PATH, @@ -37,7 +39,6 @@ from node_cli.core.docker_config import cleanup_docker_configuration, configure_docker from node_cli.core.host import ( ensure_btrfs_kernel_module_autoloaded, - link_env_file, prepare_host, ) from node_cli.core.nftables import configure_nftables @@ -72,24 +73,26 @@ remove_dynamic_containers, system_prune, ) -from node_cli.utils.helper import cleanup_dir_content, rm_dir, str_to_bool +from node_cli.utils.helper import cleanup_dir_content, rm_dir from node_cli.utils.meta import CliMetaManager, FairCliMetaManager from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_failed_requirements_checks -from node_cli.utils.settings import save_settings +from node_cli.utils.settings import save_internal_settings logger = logging.getLogger(__name__) def checked_host(func): @functools.wraps(func) - def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs): - download_skale_node(env.get('NODE_VERSION'), env.get('CONTAINER_CONFIGS_DIR')) + def wrapper( + settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode, *args, **kwargs + ): + download_skale_node(settings.node_version, settings.container_configs_dir or None) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_TMP_PATH, check_type=CheckType.PREINSTALL, ) @@ -97,15 +100,15 @@ def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs): print_failed_requirements_checks(failed_checks) return False - result = func(env_filepath, env, node_mode, *args, **kwargs) + result = func(settings, compose_env, node_mode, *args, **kwargs) if not result: return result failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.POSTINSTALL, ) @@ -118,111 +121,116 @@ def wrapper(env_filepath: str, env: Dict, node_mode: NodeMode, *args, **kwargs): @checked_host -def update(env_filepath: str, env: Dict, node_mode: NodeMode) -> bool: - compose_rm(node_type=NodeType.SKALE, node_mode=node_mode, env=env) +def update(settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode) -> bool: + compose_rm(node_type=NodeType.SKALE, node_mode=node_mode, env=compose_env) remove_dynamic_containers() sync_skale_node() ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) - lvmpy_install(env) + lvmpy_install(settings.block_device) generate_nginx_config() - prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) - save_settings(node_type=NodeType.SKALE, node_mode=node_mode) - init_shared_space_volume(env['ENV_TYPE']) + prepare_host(settings.env_type, allocation=True) + save_internal_settings(node_type=NodeType.SKALE, node_mode=node_mode) + init_shared_space_volume(settings.env_type) meta_manager = CliMetaManager() current_stream = meta_manager.get_meta_info().config_stream - skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True' - if not skip_cleanup and current_stream != env['NODE_VERSION']: + if not settings.skip_docker_cleanup and current_stream != settings.node_version: logger.info( 'Stream version was changed from %s to %s', current_stream, - env['NODE_VERSION'], + settings.node_version, ) docker_cleanup() + skale_settings = get_settings(SkaleSettings) meta_manager.update_meta( VERSION, - env['NODE_VERSION'], - env['DOCKER_LVMPY_VERSION'], + settings.node_version, + skale_settings.docker_lvmpy_version, distro.id(), distro.version(), ) - update_images(env=env, node_type=NodeType.SKALE, node_mode=node_mode) - compose_up(env=env, node_type=NodeType.SKALE, node_mode=node_mode) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.SKALE, + node_mode=node_mode, + ) + compose_up(env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=node_mode) return True @checked_host -def init(env_filepath: str, env: dict, node_mode: NodeMode) -> None: +def init(settings: BaseNodeSettings, compose_env: dict, node_mode: NodeMode) -> None: sync_skale_node() ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) - prepare_host(env_filepath, env_type=env['ENV_TYPE']) - save_settings(node_type=NodeType.SKALE, node_mode=node_mode) - link_env_file() + prepare_host(env_type=settings.env_type) + save_internal_settings(node_type=NodeType.SKALE, node_mode=node_mode) mark_active_node() configure_filebeat() generate_nginx_config() - lvmpy_install(env) - init_shared_space_volume(env['ENV_TYPE']) + lvmpy_install(settings.block_device) + init_shared_space_volume(settings.env_type) + skale_settings = get_settings(SkaleSettings) meta_manager = CliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], - env['DOCKER_LVMPY_VERSION'], + settings.node_version, + skale_settings.docker_lvmpy_version, distro.id(), distro.version(), ) - update_resource_allocation(env_type=env['ENV_TYPE']) - update_images(env=env, node_type=NodeType.SKALE, node_mode=node_mode) - compose_up(env=env, node_type=NodeType.SKALE, node_mode=node_mode) + update_resource_allocation(env_type=settings.env_type) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.SKALE, + node_mode=node_mode, + ) + compose_up(env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=node_mode) def init_passive( - env_filepath: str, - env: dict, + settings: BaseNodeSettings, + compose_env: dict, indexer: bool, archive: bool, snapshot: bool, snapshot_from: Optional[str], ) -> None: - cleanup_volume_artifacts(env['BLOCK_DEVICE']) - download_skale_node(env.get('NODE_VERSION'), env.get('CONTAINER_CONFIGS_DIR')) + cleanup_volume_artifacts(settings.block_device) + download_skale_node(settings.node_version, settings.container_configs_dir or None) sync_skale_node() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) - prepare_host( - env_filepath, - env_type=env['ENV_TYPE'], - ) + prepare_host(env_type=settings.env_type) + save_internal_settings(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, NodeMode.PASSIVE, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.PREINSTALL, ) @@ -232,55 +240,64 @@ def init_passive( set_passive_node_options(archive=archive, indexer=indexer) ensure_filestorage_mapping() - link_env_file() generate_nginx_config() - prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') + passive_settings = get_settings(SkalePassiveSettings) + prepare_block_device(settings.block_device, force=passive_settings.enforce_btrfs) meta_manager = CliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, None, distro.id(), distro.version(), ) - update_resource_allocation(env_type=env['ENV_TYPE']) + update_resource_allocation(env_type=settings.env_type) - schain_name = env['SCHAIN_NAME'] - if snapshot or snapshot_from: + if passive_settings.schain_name and (snapshot or snapshot_from): ts = int(time.time()) - update_node_cli_schain_status(schain_name, repair_ts=ts, snapshot_from=snapshot_from) + update_node_cli_schain_status( + passive_settings.schain_name, repair_ts=ts, snapshot_from=snapshot_from + ) - update_images(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) - compose_up(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.SKALE, + node_mode=NodeMode.PASSIVE, + ) + compose_up( + env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE + ) -def update_passive(env_filepath: str, env: Dict) -> bool: - compose_rm(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) +def update_passive(settings: BaseNodeSettings, compose_env: dict) -> bool: + compose_rm(env=compose_env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) remove_dynamic_containers() - cleanup_volume_artifacts(env['BLOCK_DEVICE']) - download_skale_node(env['NODE_VERSION'], env.get('CONTAINER_CONFIGS_DIR')) + cleanup_volume_artifacts(settings.block_device) + download_skale_node(settings.node_version, settings.container_configs_dir or None) sync_skale_node() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) ensure_filestorage_mapping() - prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') + passive_settings = get_settings(SkalePassiveSettings) + prepare_block_device(settings.block_device, force=passive_settings.enforce_btrfs) generate_nginx_config() - prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) + prepare_host(settings.env_type, allocation=True) + save_internal_settings(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, NodeMode.PASSIVE, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.PREINSTALL, ) @@ -290,56 +307,82 @@ def update_passive(env_filepath: str, env: Dict) -> bool: meta_manager = CliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, None, distro.id(), distro.version(), ) - update_images(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) - compose_up(env=env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.SKALE, + node_mode=NodeMode.PASSIVE, + ) + compose_up( + env=compose_env, settings=settings, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE + ) return True -def turn_off(env: dict, node_type: NodeType, node_mode: NodeMode) -> None: +def turn_off(compose_env: dict, node_type: NodeType, node_mode: NodeMode) -> None: logger.info('Turning off the node...') - compose_rm(env=env, node_type=node_type, node_mode=node_mode) + compose_rm(env=compose_env, node_type=node_type, node_mode=node_mode) remove_dynamic_containers() logger.info('Node was successfully turned off') -def turn_on(env: dict, node_type: NodeType, node_mode: NodeMode) -> None: +def turn_on( + settings: BaseNodeSettings, + compose_env: dict, + node_type: NodeType, + node_mode: NodeMode, + backup_run: bool = False, +) -> None: logger.info('Turning on the node...') if node_type == NodeType.FAIR: meta_manager = FairCliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) else: + skale_settings = get_settings((SkaleSettings, SkalePassiveSettings)) + docker_lvmpy_version = ( + skale_settings.docker_lvmpy_version + if isinstance(skale_settings, SkaleSettings) + else None + ) meta_manager = CliMetaManager() meta_manager.update_meta( - VERSION, env['NODE_VERSION'], env['DOCKER_LVMPY_VERSION'], distro.id(), distro.version() + VERSION, settings.node_version, docker_lvmpy_version, distro.id(), distro.version() ) - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) + save_internal_settings(node_type=node_type, node_mode=node_mode, backup_run=backup_run) logger.info('Launching containers on the node...') - compose_up(env=env, node_type=node_type, node_mode=node_mode) + compose_up(env=compose_env, settings=settings, node_type=node_type, node_mode=node_mode) -def restore(env, backup_path, node_type: NodeType, config_only=False): +def restore( + settings: BaseNodeSettings, + compose_env: dict, + backup_path: str, + node_type: NodeType, + config_only: bool = False, + backup_run: bool = False, +) -> bool: node_mode = upsert_node_mode(node_mode=NodeMode.ACTIVE) unpack_backup_archive(backup_path) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.PREINSTALL, ) @@ -349,32 +392,32 @@ def restore(env, backup_path, node_type: NodeType, config_only=False): ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) - link_env_file() - lvmpy_install(env) - init_shared_space_volume(env['ENV_TYPE']) + lvmpy_install(settings.block_device) + init_shared_space_volume(settings.env_type) + skale_settings = get_settings(SkaleSettings) meta_manager = CliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], - env['DOCKER_LVMPY_VERSION'], + settings.node_version, + skale_settings.docker_lvmpy_version, distro.id(), distro.version(), ) + save_internal_settings(node_type=node_type, node_mode=node_mode, backup_run=backup_run) if not config_only: - compose_up(env=env, node_type=node_type, node_mode=node_mode) + compose_up(env=compose_env, settings=settings, node_type=node_type, node_mode=node_mode) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.POSTINSTALL, ) @@ -384,19 +427,20 @@ def restore(env, backup_path, node_type: NodeType, config_only=False): return True -def cleanup_passive(env, schain_name: str) -> None: - turn_off(env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) +def cleanup_passive(compose_env: dict, schain_name: str) -> None: + turn_off(compose_env, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) cleanup_no_lvm_datadir(chain_name=schain_name) rm_dir(GLOBAL_SKALE_DIR) rm_dir(SKALE_DIR) -def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None: - turn_off(env, node_type=NodeType.SKALE, node_mode=node_mode) +def cleanup( + node_mode: NodeMode, compose_env: dict, schain_name: Optional[str] = None, prune: bool = False +) -> None: + turn_off(compose_env, node_type=NodeType.SKALE, node_mode=node_mode) if prune: system_prune() if node_mode == NodeMode.PASSIVE: - schain_name = env['SCHAIN_NAME'] cleanup_no_lvm_datadir(chain_name=schain_name) else: cleanup_lvm_datadir() diff --git a/node_cli/operations/config_repo.py b/node_cli/operations/config_repo.py index b8456db6..19cf284a 100644 --- a/node_cli/operations/config_repo.py +++ b/node_cli/operations/config_repo.py @@ -33,12 +33,13 @@ logger = logging.getLogger(__name__) -def update_images(env: dict, node_type: NodeType, node_mode: NodeMode) -> None: - local = env.get('CONTAINER_CONFIGS_DIR') != '' - if local: - compose_build(env=env, node_type=node_type, node_mode=node_mode) +def update_images( + compose_env: dict, container_configs_dir: str, node_type: NodeType, node_mode: NodeMode +) -> None: + if container_configs_dir: + compose_build(env=compose_env, node_type=node_type, node_mode=node_mode) else: - compose_pull(env=env, node_type=node_type, node_mode=node_mode) + compose_pull(env=compose_env, node_type=node_type, node_mode=node_mode) def download_skale_node(stream: Optional[str] = None, src: Optional[str] = None) -> None: diff --git a/node_cli/operations/docker_lvmpy.py b/node_cli/operations/docker_lvmpy.py index c21328f8..eb307d55 100644 --- a/node_cli/operations/docker_lvmpy.py +++ b/node_cli/operations/docker_lvmpy.py @@ -56,12 +56,10 @@ def sync_docker_lvmpy_repo(env): sync_repo(DOCKER_LVMPY_REPO_URL, DOCKER_LVMPY_PATH, env['DOCKER_LVMPY_VERSION']) -def lvmpy_install(env): +def lvmpy_install(block_device: str) -> None: ensure_filestorage_mapping() logging.info('Configuring and starting lvmpy') - setup_lvmpy( - block_device=env['BLOCK_DEVICE'], volume_group=VOLUME_GROUP, exec_start=LVMPY_RUN_CMD - ) + setup_lvmpy(block_device=block_device, volume_group=VOLUME_GROUP, exec_start=LVMPY_RUN_CMD) init_healing_cron() logger.info('docker-lvmpy is configured and started') diff --git a/node_cli/operations/fair.py b/node_cli/operations/fair.py index 096d1623..015abfe0 100644 --- a/node_cli/operations/fair.py +++ b/node_cli/operations/fair.py @@ -23,6 +23,9 @@ import distro +from skale.core.settings import BaseNodeSettings, FairBaseSettings, FairSettings, get_settings +from skale.core.types import EnvType + from node_cli.cli.info import TYPE, VERSION from node_cli.configs import ( CONTAINER_CONFIG_PATH, @@ -33,7 +36,7 @@ from node_cli.core.checks import CheckType from node_cli.core.checks import run_checks as run_host_checks from node_cli.core.docker_config import cleanup_docker_configuration, configure_docker -from node_cli.core.host import ensure_btrfs_kernel_module_autoloaded, link_env_file, prepare_host +from node_cli.core.host import ensure_btrfs_kernel_module_autoloaded, prepare_host from node_cli.core.nftables import configure_nftables from node_cli.core.nginx import generate_nginx_config from node_cli.core.schains import cleanup_no_lvm_datadir @@ -66,11 +69,11 @@ system_prune, wait_for_container, ) -from node_cli.utils.helper import cleanup_dir_content, rm_dir, str_to_bool +from node_cli.utils.helper import cleanup_dir_content, rm_dir from node_cli.utils.meta import FairCliMetaManager from node_cli.utils.print_formatters import print_failed_requirements_checks from node_cli.utils.node_type import NodeMode, NodeType -from node_cli.utils.settings import save_settings +from node_cli.utils.settings import save_internal_settings logger = logging.getLogger(__name__) @@ -82,42 +85,56 @@ class FairUpdateType(Enum): @checked_host -def init_fair_boot(env_filepath: str, env: dict) -> None: +def init_fair_boot( + settings: BaseNodeSettings, + compose_env: dict, + node_mode: NodeMode, +) -> None: sync_skale_node() - cleanup_volume_artifacts(env['BLOCK_DEVICE']) + cleanup_volume_artifacts(settings.block_device) ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) - prepare_host(env_filepath, env_type=env['ENV_TYPE']) - save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) - link_env_file() + prepare_host(env_type=settings.env_type) + save_internal_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mark_active_node() configure_filebeat() generate_nginx_config() - prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') + fair_settings = get_settings((FairSettings, FairBaseSettings)) + prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs) meta_manager = FairCliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) - update_images(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.FAIR, + node_mode=NodeMode.ACTIVE, + ) - compose_up(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE, is_fair_boot=True) + compose_up( + env=compose_env, + settings=settings, + node_type=NodeType.FAIR, + node_mode=NodeMode.ACTIVE, + is_fair_boot=True, + ) @checked_host def init( - env_filepath: str, - env: dict, + settings: BaseNodeSettings, + compose_env: dict, node_mode: NodeMode, indexer: bool, archive: bool, @@ -125,24 +142,33 @@ def init( ) -> bool: sync_skale_node() ensure_btrfs_kernel_module_autoloaded() - cleanup_volume_artifacts(env['BLOCK_DEVICE']) + cleanup_volume_artifacts(settings.block_device) - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() configure_nftables() configure_filebeat() generate_nginx_config() - prepare_host(env_filepath, env_type=env['ENV_TYPE']) - save_settings(node_type=NodeType.FAIR, node_mode=node_mode) - link_env_file() + prepare_host(env_type=settings.env_type) + save_internal_settings(node_type=NodeType.FAIR, node_mode=node_mode) - prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') + fair_settings = get_settings((FairSettings, FairBaseSettings)) + prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs) - update_images(env=env, node_type=NodeType.FAIR, node_mode=node_mode) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.FAIR, + node_mode=node_mode, + ) compose_up( - env=env, node_type=NodeType.FAIR, node_mode=node_mode, services=list(REDIS_SERVICE_DICT) + env=compose_env, + settings=settings, + node_type=NodeType.FAIR, + node_mode=node_mode, + services=list(REDIS_SERVICE_DICT), ) upsert_node_mode(node_mode=node_mode) @@ -152,131 +178,159 @@ def init( if snapshot: logger.info('Waiting %s seconds for redis to start', REDIS_START_TIMEOUT) time.sleep(REDIS_START_TIMEOUT) - trigger_skaled_snapshot_mode(env=env, snapshot_from=snapshot) + trigger_skaled_snapshot_mode(env_type=settings.env_type, snapshot_from=snapshot) meta_manager = FairCliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) - compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode) + compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode) wait_for_container(BASE_PASSIVE_FAIR_COMPOSE_SERVICES['api']) time.sleep(REDIS_START_TIMEOUT) return True @checked_host -def update_fair_boot(env_filepath: str, env: dict, node_mode: NodeMode = NodeMode.ACTIVE) -> bool: - compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=env) +def update_fair_boot( + settings: BaseNodeSettings, + compose_env: dict, + node_mode: NodeMode = NodeMode.ACTIVE, +) -> bool: + compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=compose_env) remove_dynamic_containers() - cleanup_volume_artifacts(env['BLOCK_DEVICE']) + cleanup_volume_artifacts(settings.block_device) sync_skale_node() ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) + configure_nftables(enable_monitoring=settings.monitoring_containers) generate_nginx_config() - prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') + fair_settings = get_settings((FairSettings, FairBaseSettings)) + prepare_block_device(settings.block_device, force=fair_settings.enforce_btrfs) - prepare_host(env_filepath, env['ENV_TYPE']) - save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) + prepare_host(settings.env_type) + save_internal_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream - skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True' - if not skip_cleanup and current_stream != env['NODE_VERSION']: + if not settings.skip_docker_cleanup and current_stream != settings.node_version: logger.info( 'Stream version was changed from %s to %s', current_stream, - env['NODE_VERSION'], + settings.node_version, ) docker_cleanup() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) - update_images(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) - compose_up(env=env, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE, is_fair_boot=True) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.FAIR, + node_mode=NodeMode.ACTIVE, + ) + compose_up( + env=compose_env, + settings=settings, + node_type=NodeType.FAIR, + node_mode=NodeMode.ACTIVE, + is_fair_boot=True, + ) return True @checked_host def update( - env_filepath: str, - env: dict, + settings: BaseNodeSettings, + compose_env: dict, node_mode: NodeMode, update_type: FairUpdateType, force_skaled_start: bool, ) -> bool: - compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=env) + compose_rm(node_type=NodeType.FAIR, node_mode=node_mode, env=compose_env) if update_type not in (FairUpdateType.INFRA_ONLY, FairUpdateType.FROM_BOOT): remove_dynamic_containers() sync_skale_node() ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() configure_nftables() generate_nginx_config() - prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) - save_settings(node_type=NodeType.FAIR, node_mode=node_mode) + prepare_host(settings.env_type, allocation=True) + save_internal_settings(node_type=NodeType.FAIR, node_mode=node_mode) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream - skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True' - if not skip_cleanup and current_stream != env['NODE_VERSION']: + if not settings.skip_docker_cleanup and current_stream != settings.node_version: logger.info( 'Stream version was changed from %s to %s', current_stream, - env['NODE_VERSION'], + settings.node_version, ) docker_cleanup() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) - fair_chain_name = get_fair_chain_name(env) + fair_chain_name = get_fair_chain_name(settings.env_type) if update_type == FairUpdateType.FROM_BOOT: migrate_nftables_from_boot(chain_name=fair_chain_name) - update_images(env=env, node_type=NodeType.FAIR, node_mode=node_mode) + update_images( + compose_env=compose_env, + container_configs_dir=settings.container_configs_dir, + node_type=NodeType.FAIR, + node_mode=node_mode, + ) compose_up( - env=env, node_type=NodeType.FAIR, node_mode=node_mode, services=list(REDIS_SERVICE_DICT) + env=compose_env, + settings=settings, + node_type=NodeType.FAIR, + node_mode=node_mode, + services=list(REDIS_SERVICE_DICT), ) wait_for_container(REDIS_SERVICE_DICT['redis']) time.sleep(REDIS_START_TIMEOUT) if update_type == FairUpdateType.FROM_BOOT: - migrate_chain_record(env) - update_chain_record(env, force_skaled_start=force_skaled_start) - compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode) + migrate_chain_record(settings.env_type, settings.node_version) + update_chain_record(settings.env_type, force_skaled_start=force_skaled_start) + compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode) return True -def restore(node_mode: NodeMode, env, backup_path, config_only=False): +def restore( + node_mode: NodeMode, + settings: BaseNodeSettings, + compose_env: dict, + backup_path: str, + config_only: bool = False, +) -> bool: unpack_backup_archive(backup_path) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.PREINSTALL, ) @@ -286,30 +340,27 @@ def restore(node_mode: NodeMode, env, backup_path, config_only=False): ensure_btrfs_kernel_module_autoloaded() - if env.get('SKIP_DOCKER_CONFIG') != 'True': + if not settings.skip_docker_config: configure_docker() - enable_monitoring = str_to_bool(env.get('MONITORING_CONTAINERS', 'False')) - configure_nftables(enable_monitoring=enable_monitoring) - - link_env_file() + configure_nftables(enable_monitoring=settings.monitoring_containers) meta_manager = FairCliMetaManager() meta_manager.update_meta( VERSION, - env['NODE_VERSION'], + settings.node_version, distro.id(), distro.version(), ) if not config_only: - compose_up(env=env, node_type=NodeType.FAIR, node_mode=node_mode) + compose_up(env=compose_env, settings=settings, node_type=NodeType.FAIR, node_mode=node_mode) failed_checks = run_host_checks( - env['BLOCK_DEVICE'], + settings.block_device, TYPE, node_mode, - env['ENV_TYPE'], + settings.env_type, CONTAINER_CONFIG_PATH, check_type=CheckType.POSTINSTALL, ) @@ -319,8 +370,8 @@ def restore(node_mode: NodeMode, env, backup_path, config_only=False): return True -def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None: - turn_off(env, node_type=NodeType.FAIR, node_mode=node_mode) +def cleanup(node_mode: NodeMode, compose_env: dict, prune: bool = False) -> None: + turn_off(compose_env, node_type=NodeType.FAIR, node_mode=node_mode) if prune: system_prune() cleanup_no_lvm_datadir() @@ -330,15 +381,15 @@ def cleanup(node_mode: NodeMode, env: dict, prune: bool = False) -> None: cleanup_docker_configuration() -def trigger_skaled_snapshot_mode(env: dict, snapshot_from: str = 'any') -> None: - record = get_fair_chain_record(env) +def trigger_skaled_snapshot_mode(env_type: EnvType, snapshot_from: str = 'any') -> None: + record = get_fair_chain_record(env_type) if not snapshot_from: snapshot_from = 'any' logger.info('Triggering skaled snapshot mode, snapshot_from: %s', snapshot_from) record.set_snapshot_from(snapshot_from) -def repair(env: dict, snapshot_from: str = 'any') -> None: +def repair(env_type: EnvType, snapshot_from: str = 'any') -> None: logger.info('Starting fair node repair') container_name = 'sk_admin' if is_admin_running(): @@ -349,7 +400,7 @@ def repair(env: dict, snapshot_from: str = 'any') -> None: logger.info('Cleaning up datadir') cleanup_no_lvm_datadir() logger.info('Requesting fair node repair') - trigger_skaled_snapshot_mode(env=env, snapshot_from=snapshot_from) + trigger_skaled_snapshot_mode(env_type=env_type, snapshot_from=snapshot_from) logger.info('Starting admin') start_container_by_name(container_name=container_name) logger.info('Fair node repair completed successfully') diff --git a/node_cli/utils/docker_utils.py b/node_cli/utils/docker_utils.py index 37e3fab9..8f0e1bb1 100644 --- a/node_cli/utils/docker_utils.py +++ b/node_cli/utils/docker_utils.py @@ -29,15 +29,16 @@ from docker.errors import NotFound from docker.models.containers import Container +from skale.core.settings import BaseNodeSettings + from node_cli.configs import ( COMPOSE_PATH, FAIR_COMPOSE_PATH, NGINX_CONTAINER_NAME, REMOVED_CONTAINERS_FOLDER_PATH, - SGX_CERTIFICATES_DIR_NAME, ) from node_cli.core.node_options import active_fair, active_skale, passive_fair, passive_skale -from node_cli.utils.helper import run_cmd, str_to_bool +from node_cli.utils.helper import run_cmd from node_cli.utils.node_type import NodeMode, NodeType logger = logging.getLogger(__name__) @@ -327,6 +328,7 @@ def get_up_compose_cmd( def compose_up( env, + settings: BaseNodeSettings, node_type: NodeType, node_mode: NodeMode, is_fair_boot: bool = False, @@ -365,7 +367,7 @@ def compose_up( logger.debug('Launching skale node containers with env %s', env) run_cmd(cmd=get_up_compose_cmd(node_type=node_type, node_mode=node_mode), env=env) - if 'TG_API_KEY' in env and 'TG_CHAT_ID' in env: + if settings.tg_api_key and settings.tg_chat_id: logger.info('Running containers for Telegram notifications') run_cmd( cmd=get_up_compose_cmd( @@ -376,7 +378,7 @@ def compose_up( env=env, ) - if str_to_bool(env.get('MONITORING_CONTAINERS', 'False')): + if settings.monitoring_containers: logger.info('Running monitoring containers') run_cmd( cmd=get_up_compose_cmd( diff --git a/node_cli/utils/helper.py b/node_cli/utils/helper.py index 9d96a3ca..d2c598fe 100644 --- a/node_cli/utils/helper.py +++ b/node_cli/utils/helper.py @@ -144,16 +144,6 @@ def get_username(): return os.environ.get('USERNAME') or os.environ.get('USER') -def str_to_bool(val: str) -> bool: - val = val.lower() - if val in ('y', 'yes', 't', 'true', 'on', '1'): - return True - elif val in ('n', 'no', 'f', 'false', 'off', '0'): - return False - else: - raise ValueError(f'Invalid truth value {val!r}') - - def error_exit(error_payload: Any, exit_code: CLIExitCodes = CLIExitCodes.FAILURE) -> NoReturn: """Print error message and exit the program with specified exit code. diff --git a/node_cli/utils/settings.py b/node_cli/utils/settings.py index c0b3aa76..e10e68ce 100644 --- a/node_cli/utils/settings.py +++ b/node_cli/utils/settings.py @@ -17,19 +17,23 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import tomllib + +from dotenv.main import DotEnv + from skale.core.settings import ( SETTINGS_MAP, - write_node_settings_file, - write_internal_settings_file, + BaseNodeSettings, + FairBaseSettings, + FairSettings, InternalSettings, - SkaleSettings, SkalePassiveSettings, - FairSettings, - FairBaseSettings, + SkaleSettings, + write_internal_settings_file, + write_node_settings_file, ) -from node_cli.configs import NODE_SETTINGS_PATH, INTERNAL_SETTINGS_PATH - +from node_cli.configs import INTERNAL_SETTINGS_PATH, NODE_SETTINGS_PATH, SKALE_DIR from node_cli.utils.node_type import NodeMode, NodeType InternalSettings.model_config['toml_file'] = INTERNAL_SETTINGS_PATH @@ -39,9 +43,35 @@ FairBaseSettings.model_config['toml_file'] = NODE_SETTINGS_PATH -def save_settings(node_type: NodeType, node_mode: NodeMode) -> None: - write_internal_settings_file(path=INTERNAL_SETTINGS_PATH, data={}) # todof: fix +def load_config_file(filepath: str) -> dict: + if filepath.endswith('.toml'): + with open(filepath, 'rb') as f: + return tomllib.load(f) + return {k.lower(): v for k, v in DotEnv(filepath).dict().items()} + + +def validate_and_save_node_settings( + config_filepath: str, + node_type: NodeType, + node_mode: NodeMode, +) -> BaseNodeSettings: + data = load_config_file(config_filepath) settings_type = SETTINGS_MAP[(node_type.value, node_mode.value)] - write_node_settings_file( - path=NODE_SETTINGS_PATH, settings_type=settings_type, data={} - ) # todof: fix + write_node_settings_file(path=NODE_SETTINGS_PATH, settings_type=settings_type, data=data) + return settings_type() + + +def save_internal_settings( + node_type: NodeType, + node_mode: NodeMode, + backup_run: bool = False, + pull_config_for_schain: str | None = None, +) -> None: + data = { + 'node_type': node_type.value, + 'node_mode': node_mode.value, + 'skale_dir_host': str(SKALE_DIR), + 'backup_run': backup_run, + 'pull_config_for_schain': pull_config_for_schain, + } + write_internal_settings_file(path=INTERNAL_SETTINGS_PATH, data=data) diff --git a/tests/cli/fair_cli_test.py b/tests/cli/fair_cli_test.py index 3c6b9ef5..2f93d1a7 100644 --- a/tests/cli/fair_cli_test.py +++ b/tests/cli/fair_cli_test.py @@ -20,6 +20,7 @@ from tests.helper import run_command, subprocess_run_mock from tests.resources_test import BIG_DISK_SIZE + @mock.patch('node_cli.cli.fair_node.restore_fair') def test_fair_node_restore(mock_restore_core, valid_env_file, tmp_path): runner = CliRunner() @@ -105,7 +106,7 @@ def test_fair_node_migrate(mock_migrate_core, valid_env_file): result = runner.invoke(migrate_node, ['--yes', valid_env_file]) assert result.exit_code == 0, f'Output: {result.output}\nException: {result.exception}' - mock_migrate_core.assert_called_once_with(env_filepath=valid_env_file) + mock_migrate_core.assert_called_once_with(config_file=valid_env_file) @mock.patch('node_cli.cli.fair_node.exit_fair') @@ -136,4 +137,5 @@ def test_cleanup_node(mocked_g_config, inited_node): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 cleanup_mock.assert_called_once_with( - node_mode=NodeMode.ACTIVE, prune=False, env={'SCHAIN_NAME': 'test'}) + node_mode=NodeMode.ACTIVE, prune=False, env={'SCHAIN_NAME': 'test'} + ) diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py index bb5aa6d0..63e96259 100644 --- a/tests/cli/node_test.py +++ b/tests/cli/node_test.py @@ -353,12 +353,12 @@ def test_restore(request, node_type, node_mode, test_user_conf, mocked_g_config, result = run_command(restore_node, [backup_path, user_conf_path]) assert result.exit_code == 0 assert 'Node is restored from backup\n' in result.output # noqa - assert mock_restore_op.call_args[0][0].get('BACKUP_RUN') == 'True' + assert mock_restore_op.call_args.kwargs.get('backup_run') is True result = run_command(restore_node, [backup_path, user_conf_path, '--no-snapshot']) assert result.exit_code == 0 assert 'Node is restored from backup\n' in result.output # noqa - assert mock_restore_op.call_args[0][0].get('BACKUP_RUN') is None + assert mock_restore_op.call_args.kwargs.get('backup_run') is False def test_maintenance_on(): diff --git a/tests/configs/configs_env_validate_test.py b/tests/configs/configs_env_validate_test.py index 2db057ff..52622f73 100644 --- a/tests/configs/configs_env_validate_test.py +++ b/tests/configs/configs_env_validate_test.py @@ -1,4 +1,3 @@ -import os from typing import Optional import pytest @@ -12,18 +11,6 @@ validate_contract_address, validate_contract_alias, ) -from node_cli.configs.user import ( - ALLOWED_ENV_TYPES, - FairBootUserConfig, - FairUserConfig, - PassiveFairUserConfig, - SkaleUserConfig, - PassiveSkaleUserConfig, - get_user_config_class, - get_validated_user_config, - validate_env_type, -) -from node_cli.utils.node_type import NodeType, NodeMode ENDPOINT = 'http://localhost:8545' @@ -37,44 +24,6 @@ def json(self): return self._json_data -@pytest.mark.parametrize( - 'node_type, node_mode, is_fair_boot, expected_type', - [ - (NodeType.SKALE, NodeMode.ACTIVE, False, SkaleUserConfig), - (NodeType.SKALE, NodeMode.PASSIVE, False, PassiveSkaleUserConfig), - (NodeType.FAIR, NodeMode.ACTIVE, True, FairBootUserConfig), - (NodeType.FAIR, NodeMode.ACTIVE, False, FairUserConfig), - (NodeType.FAIR, NodeMode.PASSIVE, False, PassiveFairUserConfig), - ], - ids=['skale_active', 'skale_passive', 'fair_boot', 'fair_active', 'fair_passive'], -) -def test_build_env_params_keys(node_type, node_mode, is_fair_boot, expected_type): - env_type = get_user_config_class( - node_type=node_type, node_mode=node_mode, is_fair_boot=is_fair_boot - ) - assert env_type == expected_type - - -@pytest.mark.parametrize( - 'env_types, should_fail', - [ - (ALLOWED_ENV_TYPES, False), - (['invalid'], True), - ], - ids=[ - 'correct_env', - 'invalid_env', - ], -) -def test_env_types(env_types, should_fail): - for env_type in env_types: - if should_fail: - with pytest.raises(SystemExit): - validate_env_type(env_type=env_type) - else: - validate_env_type(env_type=env_type) - - def test_get_chain_id_success(monkeypatch): fake_response = FakeResponse(200, {'result': '0x1'}) @@ -166,24 +115,3 @@ def test_validate_env_alias_or_address_with_alias(requests_mock): alias_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/mainnet/mainnet-ima/test-alias.json' requests_mock.get(alias_url, status_code=200) validate_alias_or_address('test-alias', ContractType.IMA, ENDPOINT) - - -def test_get_validated_env_config_missing_file(): - with pytest.raises(SystemExit): - get_validated_user_config( - env_filepath='nonexistent.env', node_type=NodeType.SKALE, node_mode=NodeMode.ACTIVE - ) - - -def test_get_validated_env_config_unreadable_file(tmp_path): - env_file = tmp_path / 'unreadable.env' - env_file.touch() - original_mode = env_file.stat().st_mode - try: - os.chmod(env_file, 0o000) - with pytest.raises(PermissionError): - get_validated_user_config( - env_filepath=str(env_file), node_type=NodeType.SKALE, node_mode=NodeMode.ACTIVE - ) - finally: - os.chmod(env_file, original_mode) diff --git a/tests/conftest.py b/tests/conftest.py index cc5a5fa3..e9d4b6cc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,6 +48,12 @@ from node_cli.utils.docker_utils import docker_client from node_cli.utils.global_config import generate_g_config_file from node_cli.utils.node_type import NodeMode +from tests.fixtures.settings import ( # noqa: F401 + fair_active_settings, + fair_passive_settings, + skale_active_settings, + skale_passive_settings, +) from tests.helper import TEST_META_V1, TEST_META_V2, TEST_META_V3, TEST_SCHAINS_MNT_DIR_SINGLE_CHAIN diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py index 0fe113b2..ead55528 100644 --- a/tests/core/core_node_test.py +++ b/tests/core/core_node_test.py @@ -10,9 +10,14 @@ import pytest import requests -from node_cli.configs import NODE_DATA_PATH, SCHAINS_MNT_DIR_REGULAR, SCHAINS_MNT_DIR_SINGLE_CHAIN +from node_cli.configs import ( + NODE_DATA_PATH, + SCHAINS_MNT_DIR_REGULAR, + SCHAINS_MNT_DIR_SINGLE_CHAIN, + SKALE_DIR, +) from node_cli.configs.resource_allocation import RESOURCE_ALLOCATION_FILEPATH -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH + from node_cli.core.node import ( cleanup, compose_node_env, @@ -33,6 +38,8 @@ ALPINE_IMAGE_NAME = 'alpine:3.12' CMD = 'sleep 60' +SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') + WRONG_CONTAINERS = [ 'WRONG_CONTAINER_1', 'skale_WRONG_CONTAINER_4', @@ -165,105 +172,42 @@ def test_is_base_containers_alive_empty(node_type, node_mode, is_boot): @pytest.mark.parametrize( - ( - 'node_type, node_mode, test_user_conf, is_boot, inited_node, sync_schains,' - 'expected_mnt_dir, expect_flask_key, expect_backup_run' - ), + 'node_type, node_mode, expected_mnt_dir', [ ( NodeType.SKALE, NodeMode.ACTIVE, - 'regular_user_conf', - False, - True, - False, SCHAINS_MNT_DIR_REGULAR, - True, - False, - ), - ( - NodeType.SKALE, - NodeMode.ACTIVE, - 'regular_user_conf', - False, - True, - True, - SCHAINS_MNT_DIR_REGULAR, - True, - True, ), ( NodeType.SKALE, NodeMode.PASSIVE, - 'passive_user_conf', - False, - False, - False, SCHAINS_MNT_DIR_SINGLE_CHAIN, - False, - False, ), ( NodeType.FAIR, NodeMode.ACTIVE, - 'fair_boot_user_conf', - True, - True, - False, SCHAINS_MNT_DIR_SINGLE_CHAIN, - True, - False, - ), - ( - NodeType.FAIR, - NodeMode.ACTIVE, - 'fair_user_conf', - False, - True, - False, - SCHAINS_MNT_DIR_SINGLE_CHAIN, - True, - False, ), ], ids=[ 'regular', - 'regular_passive_flag', 'passive', - 'fair_boot', - 'fair_regular', + 'fair', ], ) def test_compose_node_env( - request, node_type, node_mode, - test_user_conf, - is_boot, - inited_node, - sync_schains, expected_mnt_dir, - expect_backup_run, ): - user_config_path = request.getfixturevalue(test_user_conf) - - with ( - mock.patch('node_cli.configs.user.validate_alias_or_address'), - mock.patch('node_cli.core.node.save_env_params'), - ): - result_env = compose_node_env( - env_filepath=user_config_path.as_posix(), - inited_node=inited_node, - sync_schains=sync_schains, - node_type=node_type, - node_mode=node_mode, - is_fair_boot=is_boot, - save=True, - ) + result_env = compose_node_env( + node_type=node_type, + node_mode=node_mode, + ) assert result_env['SCHAINS_MNT_DIR'] == expected_mnt_dir - should_have_backup = sync_schains and node_mode != NodeMode.PASSIVE - assert ('BACKUP_RUN' in result_env and result_env['BACKUP_RUN'] == 'True') == should_have_backup + assert 'BACKUP_RUN' not in result_env @pytest.fixture diff --git a/tests/fair/fair_node_test.py b/tests/fair/fair_node_test.py index a0408caa..62fcf271 100644 --- a/tests/fair/fair_node_test.py +++ b/tests/fair/fair_node_test.py @@ -1,9 +1,9 @@ +import os from unittest import mock import pytest from node_cli.configs import SKALE_DIR -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH from node_cli.fair.boot import init as init_boot from node_cli.fair.boot import update from node_cli.fair.common import cleanup @@ -11,6 +11,8 @@ from node_cli.operations.fair import FairUpdateType from node_cli.utils.node_type import NodeMode, NodeType +SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') + @mock.patch('node_cli.fair.active.time.sleep') @mock.patch('node_cli.fair.active.restore_fair_op') @@ -182,7 +184,8 @@ def test_cleanup_success( skip_user_conf_validation=True, ) mock_cleanup_fair_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False) + node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + ) @mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) @@ -214,7 +217,8 @@ def test_cleanup_calls_operations_in_correct_order( save=False, node_type=mock.ANY, node_mode=NodeMode.ACTIVE, - skip_user_conf_validation=True), + skip_user_conf_validation=True, + ), mock.call.cleanup_fair_op(node_mode=NodeMode.ACTIVE, env=mock_env, prune=False), ] manager.assert_has_calls(expected_calls, any_order=False) @@ -240,7 +244,8 @@ def test_cleanup_continues_after_fair_op_error( mock_compose_env.assert_called_once() mock_cleanup_fair_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False) + node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + ) @mock.patch('node_cli.utils.decorators.is_user_valid', return_value=False) diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/settings.py b/tests/fixtures/settings.py new file mode 100644 index 00000000..11e53ede --- /dev/null +++ b/tests/fixtures/settings.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# +# This file is part of node-cli +# +# Copyright (C) 2026-Present SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import pytest +import tomli_w + +from skale.core.settings import get_internal_settings + +from node_cli.configs import INTERNAL_SETTINGS_PATH, NODE_SETTINGS_PATH + +SKALE_DIR_HOST = './skale-data/' + +INTERNAL_SKALE_ACTIVE = { + 'node_type': 'skale', + 'node_mode': 'active', + 'skale_dir_host': SKALE_DIR_HOST, +} + +INTERNAL_SKALE_PASSIVE = { + 'node_type': 'skale', + 'node_mode': 'passive', + 'skale_dir_host': SKALE_DIR_HOST, +} + +INTERNAL_FAIR_ACTIVE = { + 'node_type': 'fair', + 'node_mode': 'active', + 'skale_dir_host': SKALE_DIR_HOST, +} + +INTERNAL_FAIR_PASSIVE = { + 'node_type': 'fair', + 'node_mode': 'passive', + 'skale_dir_host': SKALE_DIR_HOST, +} + +_BASE_NODE = { + 'env_type': 'devnet', + 'endpoint': 'http://127.0.0.1:8545', + 'container_stop_timeout': 1, + 'tg_api_key': '123', + 'tg_chat_id': '-1231232', + 'node_version': '0.0.0', + 'block_device': '/dev/sda', +} + +NODE_SKALE_ACTIVE = { + **_BASE_NODE, + 'sgx_url': 'https://localhost:1026', + 'docker_lvmpy_version': '0.0.0', + 'manager_contracts': 'test-manager', + 'ima_contracts': 'test-ima', +} + +NODE_SKALE_PASSIVE = { + **_BASE_NODE, + 'manager_contracts': 'test-manager', + 'ima_contracts': 'test-ima', + 'schain_name': 'test-schain', + 'enforce_btrfs': False, +} + +NODE_FAIR_ACTIVE = { + **_BASE_NODE, + 'sgx_url': 'https://localhost:1026', + 'fair_contracts': 'test-fair', + 'enforce_btrfs': False, +} + +NODE_FAIR_PASSIVE = { + **_BASE_NODE, + 'fair_contracts': 'test-fair', + 'enforce_btrfs': False, +} + + +def _write_settings(internal: dict, node: dict) -> None: + INTERNAL_SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True) + INTERNAL_SETTINGS_PATH.write_bytes(tomli_w.dumps(internal).encode()) + NODE_SETTINGS_PATH.write_bytes(tomli_w.dumps(node).encode()) + get_internal_settings.cache_clear() + + +def _cleanup_settings() -> None: + INTERNAL_SETTINGS_PATH.unlink(missing_ok=True) + NODE_SETTINGS_PATH.unlink(missing_ok=True) + get_internal_settings.cache_clear() + + +@pytest.fixture +def skale_active_settings(): + _write_settings(INTERNAL_SKALE_ACTIVE, NODE_SKALE_ACTIVE) + yield + _cleanup_settings() + + +@pytest.fixture +def skale_passive_settings(): + _write_settings(INTERNAL_SKALE_PASSIVE, NODE_SKALE_PASSIVE) + yield + _cleanup_settings() + + +@pytest.fixture +def fair_active_settings(): + _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE) + yield + _cleanup_settings() + + +@pytest.fixture +def fair_passive_settings(): + _write_settings(INTERNAL_FAIR_PASSIVE, NODE_FAIR_PASSIVE) + yield + _cleanup_settings() diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 1d9deeb1..e7b3f4be 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -17,4 +17,3 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from node_cli.utils.node_type import NodeMode, NodeType From 070111caad2797fb794ad95aa1515154a09bb350 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 13:15:06 +0000 Subject: [PATCH 03/10] Update node-cli to use skale core package --- node_cli/cli/node.py | 2 +- node_cli/cli/schains.py | 2 +- node_cli/core/checks.py | 2 +- node_cli/core/host.py | 2 +- node_cli/core/node.py | 4 +- node_cli/core/resources.py | 2 +- node_cli/core/schains.py | 2 +- node_cli/core/static_config.py | 2 +- node_cli/fair/common.py | 2 +- node_cli/fair/record/chain_record.py | 2 +- node_cli/operations/base.py | 2 +- node_cli/operations/fair.py | 4 +- node_cli/utils/docker_utils.py | 2 +- node_cli/utils/settings.py | 2 +- pyproject.toml | 2 +- tests/cli/fair_cli_test.py | 2 +- tests/cli/node_test.py | 6 +-- tests/cli/passive_node_test.py | 6 +-- tests/core/core_node_test.py | 18 ++------ tests/fair/fair_node_test.py | 63 ++++++++-------------------- tests/fixtures/settings.py | 2 +- 21 files changed, 43 insertions(+), 88 deletions(-) diff --git a/node_cli/cli/node.py b/node_cli/cli/node.py index cb798a1c..b34fd155 100644 --- a/node_cli/cli/node.py +++ b/node_cli/cli/node.py @@ -21,7 +21,7 @@ import click -from skale.core.types import EnvType +from skale_core.types import EnvType from node_cli.cli.info import TYPE from node_cli.core.node import ( cleanup as cleanup_skale, diff --git a/node_cli/cli/schains.py b/node_cli/cli/schains.py index 40f1ab0c..6bb1fce2 100644 --- a/node_cli/cli/schains.py +++ b/node_cli/cli/schains.py @@ -21,7 +21,7 @@ import click -from skale.core.settings import get_settings +from skale_core.settings import get_settings from node_cli.utils.helper import abort_if_false, URL_TYPE from node_cli.core.schains import ( diff --git a/node_cli/core/checks.py b/node_cli/core/checks.py index e7677847..0424577f 100644 --- a/node_cli/core/checks.py +++ b/node_cli/core/checks.py @@ -48,7 +48,7 @@ from debian import debian_support from packaging.version import parse as version_parse -from skale.core.types import EnvType +from skale_core.types import EnvType from node_cli.configs import ( CHECK_REPORT_PATH, diff --git a/node_cli/core/host.py b/node_cli/core/host.py index 3a4b036a..a040b8c4 100644 --- a/node_cli/core/host.py +++ b/node_cli/core/host.py @@ -22,7 +22,7 @@ from shutil import chown from urllib.parse import urlparse -from skale.core.types import EnvType +from skale_core.types import EnvType from node_cli.core.resources import update_resource_allocation from node_cli.utils.helper import error_exit diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 8bef033c..0398010f 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -88,7 +88,7 @@ print_node_info, ) from node_cli.utils.settings import validate_and_save_node_settings -from skale.core.settings import get_settings +from skale_core.settings import get_settings from node_cli.utils.texts import safe_load_texts logger = logging.getLogger(__name__) @@ -242,7 +242,7 @@ def update_passive(config_file: str) -> None: def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) env = compose_node_env(NodeType.SKALE, node_mode) - cleanup_skale_op(node_mode=node_mode, env=env, prune=prune) + cleanup_skale_op(node_mode=node_mode, compose_env=env, prune=prune) logger.info('SKALE node was cleaned up, all containers and data removed') diff --git a/node_cli/core/resources.py b/node_cli/core/resources.py index 9a02d0b9..34f4cac5 100644 --- a/node_cli/core/resources.py +++ b/node_cli/core/resources.py @@ -24,7 +24,7 @@ import psutil -from skale.core.types import EnvType +from skale_core.types import EnvType from node_cli.utils.settings import validate_and_save_node_settings from node_cli.utils.docker_utils import ensure_volume diff --git a/node_cli/core/schains.py b/node_cli/core/schains.py index 174430e7..8aac42f1 100644 --- a/node_cli/core/schains.py +++ b/node_cli/core/schains.py @@ -26,7 +26,7 @@ from pathlib import Path from typing import Dict, Optional -from skale.core.types import EnvType +from skale_core.types import EnvType from lvmpy.src.core import mount, volume_mountpoint from node_cli.configs import ( diff --git a/node_cli/core/static_config.py b/node_cli/core/static_config.py index c2b90641..06e0e7f4 100644 --- a/node_cli/core/static_config.py +++ b/node_cli/core/static_config.py @@ -28,7 +28,7 @@ ) from node_cli.utils.node_type import NodeType -from skale.core.types import EnvType +from skale_core.types import EnvType def get_static_params( diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py index 3b506c74..37fdba67 100644 --- a/node_cli/fair/common.py +++ b/node_cli/fair/common.py @@ -40,7 +40,7 @@ from node_cli.utils.print_formatters import print_node_cmd_error from node_cli.utils.settings import validate_and_save_node_settings from node_cli.utils.texts import safe_load_texts -from skale.core.settings import get_settings +from skale_core.settings import get_settings logger = logging.getLogger(__name__) TEXTS = safe_load_texts() diff --git a/node_cli/fair/record/chain_record.py b/node_cli/fair/record/chain_record.py index 16ff8a72..b6d48886 100644 --- a/node_cli/fair/record/chain_record.py +++ b/node_cli/fair/record/chain_record.py @@ -22,7 +22,7 @@ from typing import cast from datetime import datetime -from skale.core.types import EnvType +from skale_core.types import EnvType from node_cli.core.static_config import get_fair_chain_name from node_cli.fair.record.redis_record import FlatRedisRecord, FieldInfo diff --git a/node_cli/operations/base.py b/node_cli/operations/base.py index c164b11a..803fe299 100644 --- a/node_cli/operations/base.py +++ b/node_cli/operations/base.py @@ -24,7 +24,7 @@ import distro -from skale.core.settings import BaseNodeSettings, SkalePassiveSettings, SkaleSettings, get_settings +from skale_core.settings import BaseNodeSettings, SkalePassiveSettings, SkaleSettings, get_settings from node_cli.cli.info import TYPE, VERSION from node_cli.configs import ( diff --git a/node_cli/operations/fair.py b/node_cli/operations/fair.py index 015abfe0..3a48fdbd 100644 --- a/node_cli/operations/fair.py +++ b/node_cli/operations/fair.py @@ -23,8 +23,8 @@ import distro -from skale.core.settings import BaseNodeSettings, FairBaseSettings, FairSettings, get_settings -from skale.core.types import EnvType +from skale_core.settings import BaseNodeSettings, FairBaseSettings, FairSettings, get_settings +from skale_core.types import EnvType from node_cli.cli.info import TYPE, VERSION from node_cli.configs import ( diff --git a/node_cli/utils/docker_utils.py b/node_cli/utils/docker_utils.py index 8f0e1bb1..98a4946c 100644 --- a/node_cli/utils/docker_utils.py +++ b/node_cli/utils/docker_utils.py @@ -29,7 +29,7 @@ from docker.errors import NotFound from docker.models.containers import Container -from skale.core.settings import BaseNodeSettings +from skale_core.settings import BaseNodeSettings from node_cli.configs import ( COMPOSE_PATH, diff --git a/node_cli/utils/settings.py b/node_cli/utils/settings.py index e10e68ce..8e7f957c 100644 --- a/node_cli/utils/settings.py +++ b/node_cli/utils/settings.py @@ -21,7 +21,7 @@ from dotenv.main import DotEnv -from skale.core.settings import ( +from skale_core.settings import ( SETTINGS_MAP, BaseNodeSettings, FairBaseSettings, diff --git a/pyproject.toml b/pyproject.toml index 3f2e25d1..4ab3258d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ dependencies = [ "requests-mock==1.12.1", "redis==7.1.1", "PyInstaller==6.18.0", - "skale.py==7.12dev2", + "skale.py-core==7.13.dev1", ] [project.urls] diff --git a/tests/cli/fair_cli_test.py b/tests/cli/fair_cli_test.py index 2f93d1a7..14e2aee9 100644 --- a/tests/cli/fair_cli_test.py +++ b/tests/cli/fair_cli_test.py @@ -137,5 +137,5 @@ def test_cleanup_node(mocked_g_config, inited_node): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 cleanup_mock.assert_called_once_with( - node_mode=NodeMode.ACTIVE, prune=False, env={'SCHAIN_NAME': 'test'} + node_mode=NodeMode.ACTIVE, prune=False, compose_env={'SCHAIN_NAME': 'test'} ) diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py index 63e96259..04317f98 100644 --- a/tests/cli/node_test.py +++ b/tests/cli/node_test.py @@ -347,7 +347,6 @@ def test_restore(request, node_type, node_mode, test_user_conf, mocked_g_config, return_value=CliMeta(version='2.4.0', config_stream='3.0.2'), ), patch('node_cli.operations.base.configure_nftables'), - patch('node_cli.configs.user.validate_alias_or_address'), ): user_conf_path = request.getfixturevalue(test_user_conf).as_posix() result = run_command(restore_node, [backup_path, user_conf_path]) @@ -389,10 +388,8 @@ def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None}) with ( mock.patch('subprocess.run', new=subprocess_run_mock), - mock.patch('node_cli.core.node.SKALE_DIR_ENV_FILEPATH', regular_user_conf.as_posix()), mock.patch('node_cli.core.node.turn_off_op'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True), - mock.patch('node_cli.configs.user.validate_alias_or_address'), mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE), ): result = run_command_mock( @@ -425,7 +422,6 @@ def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node mock.patch('node_cli.core.node.turn_on_op'), mock.patch('node_cli.core.node.is_base_containers_alive'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True), - mock.patch('node_cli.configs.user.validate_alias_or_address'), mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE), ): result = run_command_mock( @@ -493,4 +489,4 @@ def test_cleanup_node(mocked_g_config): ): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 - cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={}) + cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, compose_env={}) diff --git a/tests/cli/passive_node_test.py b/tests/cli/passive_node_test.py index c9ea9527..504c5876 100644 --- a/tests/cli/passive_node_test.py +++ b/tests/cli/passive_node_test.py @@ -45,7 +45,6 @@ def test_init_passive(mocked_g_config, clean_node_options, passive_user_conf): mock.patch('node_cli.core.resources.get_disk_size', return_value=BIG_DISK_SIZE), mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=False), - mock.patch('node_cli.configs.user.validate_alias_or_address'), ): result = run_command(_init_passive, [passive_user_conf.as_posix()]) @@ -77,7 +76,6 @@ def test_init_passive_archive(mocked_g_config, clean_node_options, passive_user_ mock.patch('node_cli.core.resources.get_disk_size', return_value=BIG_DISK_SIZE), mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=False), - mock.patch('node_cli.configs.user.validate_alias_or_address'), mock.patch('node_cli.cli.node.TYPE', NodeType.SKALE), ): result = run_command(_init_passive, [passive_user_conf.as_posix(), '--archive']) @@ -121,7 +119,6 @@ def test_update_passive(passive_user_conf, mocked_g_config): 'node_cli.core.node.CliMetaManager.get_meta_info', return_value=CliMeta(version='2.6.0', config_stream='3.0.2'), ), - mock.patch('node_cli.configs.user.validate_alias_or_address'), ): result = run_command(_update_passive, [passive_user_conf.as_posix(), '--yes']) assert result.exit_code == 0 @@ -146,4 +143,5 @@ def test_cleanup_node(mocked_g_config): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 cleanup_mock.assert_called_once_with( - node_mode=NodeMode.PASSIVE, prune=False, env={'SCHAIN_NAME': 'test'}) + node_mode=NodeMode.PASSIVE, prune=False, compose_env={'SCHAIN_NAME': 'test'} + ) diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py index ead55528..b9d66960 100644 --- a/tests/core/core_node_test.py +++ b/tests/core/core_node_test.py @@ -14,7 +14,6 @@ NODE_DATA_PATH, SCHAINS_MNT_DIR_REGULAR, SCHAINS_MNT_DIR_SINGLE_CHAIN, - SKALE_DIR, ) from node_cli.configs.resource_allocation import RESOURCE_ALLOCATION_FILEPATH @@ -38,8 +37,6 @@ ALPINE_IMAGE_NAME = 'alpine:3.12' CMD = 'sleep 60' -SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') - WRONG_CONTAINERS = [ 'WRONG_CONTAINER_1', 'skale_WRONG_CONTAINER_4', @@ -283,9 +280,8 @@ def test_init_node(regular_user_conf, no_resource_file): # todo: write new init mock.patch('node_cli.core.node.init_op'), mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True), mock.patch('node_cli.utils.helper.post_request', resp_mock), - mock.patch('node_cli.configs.user.validate_alias_or_address'), ): - init(env_filepath=regular_user_conf.as_posix(), node_type=NodeType.SKALE) + init(config_file=regular_user_conf.as_posix(), node_type=NodeType.SKALE) assert os.path.isfile(RESOURCE_ALLOCATION_FILEPATH) @@ -295,7 +291,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n with ( mock.patch('subprocess.run', new=subprocess_run_mock), mock.patch('node_cli.core.node.update_op'), - mock.patch('node_cli.core.node.save_env_params'), mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.core.host.prepare_host'), mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True), @@ -306,7 +301,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n 'node_cli.core.node.CliMetaManager.get_meta_info', return_value=CliMeta(version='2.6.0', config_stream='3.0.2'), ), - mock.patch('node_cli.configs.user.validate_alias_or_address'), ): with mock.patch( 'node_cli.utils.helper.requests.get', return_value=safe_update_api_response() @@ -428,13 +422,7 @@ def test_cleanup_success( cleanup(node_mode=NodeMode.ACTIVE) - mock_compose_env.assert_called_once_with( - SKALE_DIR_ENV_FILEPATH, - save=False, - node_type=NodeType.SKALE, - node_mode=NodeMode.ACTIVE, - skip_user_conf_validation=True, - ) + mock_compose_env.assert_called_once_with(NodeType.SKALE, NodeMode.ACTIVE) mock_cleanup_skale_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False ) diff --git a/tests/fair/fair_node_test.py b/tests/fair/fair_node_test.py index 62fcf271..d1908ef6 100644 --- a/tests/fair/fair_node_test.py +++ b/tests/fair/fair_node_test.py @@ -1,9 +1,7 @@ -import os from unittest import mock import pytest -from node_cli.configs import SKALE_DIR from node_cli.fair.boot import init as init_boot from node_cli.fair.boot import update from node_cli.fair.common import cleanup @@ -11,16 +9,12 @@ from node_cli.operations.fair import FairUpdateType from node_cli.utils.node_type import NodeMode, NodeType -SKALE_DIR_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') - @mock.patch('node_cli.fair.active.time.sleep') @mock.patch('node_cli.fair.active.restore_fair_op') -@mock.patch('node_cli.fair.active.save_env_params') @mock.patch('node_cli.fair.active.compose_node_env') def test_restore_fair( mock_compose_env, - mock_save_env, mock_restore_op, mock_sleep, valid_env_file, @@ -35,13 +29,12 @@ def test_restore_fair( restore(backup_path, valid_env_file) mock_compose_env.assert_called_once_with( - valid_env_file, node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE ) - mock_save_env.assert_called_once_with(valid_env_file) - expected_env = {**mock_env, 'SKALE_DIR': SKALE_DIR} mock_restore_op.assert_called_once_with( node_mode=NodeMode.ACTIVE, - env=expected_env, + settings=mock.ANY, + compose_env=mock_env, backup_path=backup_path, config_only=False, ) @@ -66,15 +59,12 @@ def test_init_fair_boot( init_boot(valid_env_file) mock_compose_env.assert_called_once_with( - valid_env_file, - node_type=NodeType.FAIR, - node_mode=NodeMode.ACTIVE, - is_fair_boot=True, + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE ) mock_init_op.assert_called_once_with( - valid_env_file, - mock_env, - NodeMode.ACTIVE, + settings=mock.ANY, + compose_env=mock_env, + node_mode=NodeMode.ACTIVE, ) mock_sleep.assert_called_once() mock_is_alive.assert_called_once_with( @@ -106,17 +96,11 @@ def test_update_fair_boot( update(valid_env_file, pull_config_for_schain) mock_compose_env.assert_called_once_with( - valid_env_file, - inited_node=True, - sync_schains=False, - pull_config_for_schain=pull_config_for_schain, - node_type=NodeType.FAIR, - node_mode=NodeMode.ACTIVE, - is_fair_boot=True, + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE ) mock_update_op.assert_called_once_with( - valid_env_file, - mock_env, + settings=mock.ANY, + compose_env=mock_env, node_mode=NodeMode.ACTIVE, ) mock_sleep.assert_called_once() @@ -144,15 +128,11 @@ def test_migrate_from_boot( migrate_from_boot(valid_env_file) mock_compose_env.assert_called_once_with( - valid_env_file, - inited_node=True, - sync_schains=False, - node_type=NodeType.FAIR, - node_mode=NodeMode.ACTIVE, + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE ) mock_migrate_op.assert_called_once_with( - valid_env_file, - mock_env, + settings=mock.ANY, + compose_env=mock_env, node_mode=NodeMode.ACTIVE, update_type=FairUpdateType.FROM_BOOT, force_skaled_start=False, @@ -177,14 +157,10 @@ def test_cleanup_success( cleanup(node_mode=NodeMode.ACTIVE) mock_compose_env.assert_called_once_with( - SKALE_DIR_ENV_FILEPATH, - save=False, - node_type=NodeType.FAIR, - node_mode=NodeMode.ACTIVE, - skip_user_conf_validation=True, + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE ) mock_cleanup_fair_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False ) @@ -213,13 +189,10 @@ def test_cleanup_calls_operations_in_correct_order( expected_calls = [ mock.call.compose_env( - mock.ANY, - save=False, - node_type=mock.ANY, + node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE, - skip_user_conf_validation=True, ), - mock.call.cleanup_fair_op(node_mode=NodeMode.ACTIVE, env=mock_env, prune=False), + mock.call.cleanup_fair_op(node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False), ] manager.assert_has_calls(expected_calls, any_order=False) @@ -244,7 +217,7 @@ def test_cleanup_continues_after_fair_op_error( mock_compose_env.assert_called_once() mock_cleanup_fair_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False ) diff --git a/tests/fixtures/settings.py b/tests/fixtures/settings.py index 11e53ede..55b080b2 100644 --- a/tests/fixtures/settings.py +++ b/tests/fixtures/settings.py @@ -20,7 +20,7 @@ import pytest import tomli_w -from skale.core.settings import get_internal_settings +from skale_core.settings import get_internal_settings from node_cli.configs import INTERNAL_SETTINGS_PATH, NODE_SETTINGS_PATH From 19d277e5d5d0371543669d049b7e04195ebc8246 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 15:58:40 +0000 Subject: [PATCH 04/10] remove unused modules, fix test pipeline --- node_cli/configs/alias_address_validation.py | 118 ------------------- node_cli/utils/helper.py | 4 - scripts/run_tests.sh | 5 + tests/configs/configs_env_validate_test.py | 117 ------------------ tests/conftest.py | 17 ++- 5 files changed, 12 insertions(+), 249 deletions(-) delete mode 100644 node_cli/configs/alias_address_validation.py delete mode 100644 tests/configs/configs_env_validate_test.py diff --git a/node_cli/configs/alias_address_validation.py b/node_cli/configs/alias_address_validation.py deleted file mode 100644 index ffdbb2ee..00000000 --- a/node_cli/configs/alias_address_validation.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of node-cli -# -# Copyright (C) 2025-Present SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from enum import Enum -from typing import Dict, Optional - -import requests - -from node_cli.utils.helper import error_exit, is_contract_address - - -METADATA_URL: str = ( - 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/' - 'refs/heads/deployments/metadata.json' -) - - -class ContractType(Enum): - """Contract types supported by the system using skale-contracts library.""" - - IMA = 'mainnet-ima' - MANAGER = 'skale-manager' - - -def validate_alias_or_address( - alias_or_address: str, contract_type: ContractType, endpoint: str -) -> None: - if is_contract_address(alias_or_address): - validate_contract_address(alias_or_address, endpoint) - else: - validate_contract_alias(alias_or_address, contract_type, endpoint) - - -def validate_contract_address(contract_address: str, endpoint: str) -> None: - try: - response = requests.post( - endpoint, - json={ - 'jsonrpc': '2.0', - 'method': 'eth_getCode', - 'params': [contract_address, 'latest'], - 'id': 1, - }, - ) - if response.status_code != 200: - error_exit(f'Failed to verify contract at address {contract_address}') - result = response.json().get('result') - if not result or result in ['0x', '0x0']: - error_exit(f'No contract code found at address {contract_address}') - except requests.RequestException as e: - error_exit(f'Failed to validate contract address: {str(e)}') - - -def get_deployment_url(alias: str, contract_type: ContractType, network_path: str) -> str: - return ( - f'https://raw.githubusercontent.com/skalenetwork/skale-contracts/' - f'refs/heads/deployments/{network_path}/{contract_type.value}/{alias}.json' - ) - - -def validate_contract_alias(alias: str, contract_type: ContractType, endpoint: str) -> None: - try: - chain_id = get_chain_id(endpoint) - metadata = get_network_metadata() - networks = metadata.get('networks', []) - network_path: Optional[str] = None - for net in networks: - if net.get('chainId') == chain_id: - network_path = net.get('path') - break - if not network_path: - error_exit(f'Network with chain ID {chain_id} not found in metadata') - if not isinstance(network_path, str): - error_exit(f'Invalid network path type: {network_path}') - deployment_url = get_deployment_url(alias, contract_type, network_path) - if requests.get(deployment_url).status_code != 200: - error_exit(f"Contract alias '{alias}' not found for {contract_type.value}") - except requests.RequestException as e: - error_exit(f"Failed to validate contract alias '{alias}': {str(e)}") - - -def get_chain_id(endpoint: str) -> int: - try: - response = requests.post( - endpoint, - json={'jsonrpc': '2.0', 'method': 'eth_chainId', 'params': [], 'id': 1}, - ) - if response.status_code != 200: - error_exit('Failed to get chain ID from endpoint') - return int(response.json()['result'], 16) - except requests.RequestException as e: - error_exit(f'Failed to get chain ID: {str(e)}') - - -def get_network_metadata() -> Dict: - try: - response = requests.get(METADATA_URL) - if response.status_code != 200: - error_exit('Failed to fetch networks metadata') - return response.json() - except requests.RequestException as e: - error_exit(f'Failed to fetch networks metadata: {str(e)}') diff --git a/node_cli/utils/helper.py b/node_cli/utils/helper.py index d2c598fe..8a642aee 100644 --- a/node_cli/utils/helper.py +++ b/node_cli/utils/helper.py @@ -416,10 +416,6 @@ def get_ssh_port(ssh_service_name='ssh'): return DEFAULT_SSH_PORT -def is_contract_address(value: str) -> bool: - return bool(re.fullmatch(r'0x[a-fA-F0-9]{40}', value)) - - def is_btrfs_subvolume(path: str) -> bool: """Check if the given path is a Btrfs subvolume.""" try: diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 23592396..1d3a3fe6 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -1,3 +1,8 @@ #!/usr/bin/env bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +PROJECT_DIR=$(dirname $DIR) + +. "$DIR/export_env.sh" + py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@ diff --git a/tests/configs/configs_env_validate_test.py b/tests/configs/configs_env_validate_test.py deleted file mode 100644 index 52622f73..00000000 --- a/tests/configs/configs_env_validate_test.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import Optional - -import pytest -import requests - -from node_cli.configs.alias_address_validation import ( - ContractType, - get_chain_id, - get_network_metadata, - validate_alias_or_address, - validate_contract_address, - validate_contract_alias, -) - -ENDPOINT = 'http://localhost:8545' - - -class FakeResponse: - def __init__(self, status_code: int, json_data: Optional[dict] = None): - self.status_code = status_code - self._json_data = json_data or {} - - def json(self): - return self._json_data - - -def test_get_chain_id_success(monkeypatch): - fake_response = FakeResponse(200, {'result': '0x1'}) - - def fake_post(url, json): - return fake_response - - monkeypatch.setattr(requests, 'post', fake_post) - assert get_chain_id(ENDPOINT) == 1 - - -def test_get_chain_id_failure(monkeypatch): - fake_response = FakeResponse(404) - - def fake_post(url, json): - return fake_response - - monkeypatch.setattr(requests, 'post', fake_post) - with pytest.raises(SystemExit): - get_chain_id(ENDPOINT) - - -@pytest.mark.parametrize( - 'metadata,status_code,should_raise', - [ - ({'networks': [{'chainId': 1, 'path': 'mainnet'}]}, 200, False), - (None, 404, True), - ], -) -def test_get_network_metadata(requests_mock, metadata, status_code, should_raise): - metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json' - requests_mock.get(metadata_url, json=metadata, status_code=status_code) - - if should_raise: - with pytest.raises(SystemExit): - get_network_metadata() - else: - assert get_network_metadata() == metadata - - -@pytest.mark.parametrize( - 'code,should_raise', - [ - ('0x123', False), - ('0x', True), - ], -) -def test_validate_contract_address(requests_mock, code, should_raise): - requests_mock.post(ENDPOINT, json={'result': code}) - addr = '0x' + 'a' * 40 - if should_raise: - with pytest.raises(SystemExit): - validate_contract_address(addr, ENDPOINT) - else: - validate_contract_address(addr, ENDPOINT) - - -@pytest.mark.parametrize( - 'networks,should_raise', - [ - ([{'chainId': 1, 'path': 'mainnet'}], False), - ([], True), - ], -) -def test_validate_contract_alias(requests_mock, networks, should_raise): - requests_mock.post(ENDPOINT, json={'result': '0x1'}) - metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json' - requests_mock.get(metadata_url, json={'networks': networks}, status_code=200) - - if not should_raise: - alias_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/mainnet/skale-manager/test-alias.json' - requests_mock.get(alias_url, status_code=200) - validate_contract_alias('test-alias', ContractType.MANAGER, ENDPOINT) - else: - with pytest.raises(SystemExit): - validate_contract_alias('test-alias', ContractType.MANAGER, ENDPOINT) - - -def test_validate_env_alias_or_address_with_address(requests_mock): - addr = '0x' + 'b' * 40 - requests_mock.post(ENDPOINT, json={'result': '0x1'}) - validate_alias_or_address(addr, ContractType.IMA, ENDPOINT) - - -def test_validate_env_alias_or_address_with_alias(requests_mock): - requests_mock.post(ENDPOINT, json={'result': '0x1'}) - metadata_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/metadata.json' - metadata = {'networks': [{'chainId': 1, 'path': 'mainnet'}]} - requests_mock.get(metadata_url, json=metadata, status_code=200) - alias_url = 'https://raw.githubusercontent.com/skalenetwork/skale-contracts/refs/heads/deployments/mainnet/mainnet-ima/test-alias.json' - requests_mock.get(alias_url, status_code=200) - validate_alias_or_address('test-alias', ContractType.IMA, ENDPOINT) diff --git a/tests/conftest.py b/tests/conftest.py index e9d4b6cc..7dc5cbda 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -296,13 +296,9 @@ def tmp_passive_datadir(): def valid_env_params(): return { 'ENDPOINT': 'http://localhost:8545', - 'IMA_ENDPOINT': 'http://127.0.01', - 'DB_USER': 'user', - 'DB_PASSWORD': 'pass', - 'DB_PORT': '3307', 'NODE_VERSION': 'master', 'FILEBEAT_HOST': '127.0.0.1:3010', - 'SGX_SERVER_URL': 'http://127.0.0.1', + 'SGX_URL': 'http://127.0.0.1', 'BLOCK_DEVICE': '/dev/sss', 'DOCKER_LVMPY_VERSION': 'master', 'ENV_TYPE': 'devnet', @@ -310,6 +306,7 @@ def valid_env_params(): 'ENFORCE_BTRFS': 'False', 'MANAGER_CONTRACTS': 'test-manager', 'IMA_CONTRACTS': 'test-ima', + 'FAIR_CONTRACTS': 'test-fair', } @@ -367,7 +364,7 @@ def regular_user_conf(tmp_path): ENDPOINT=http://localhost:8545 NODE_VERSION='main' FILEBEAT_HOST=127.0.0.1:3010 - SGX_SERVER_URL=http://127.0.0.1 + SGX_URL=http://127.0.0.1 BLOCK_DEVICE=/dev/sss DOCKER_LVMPY_VERSION='master' ENV_TYPE='devnet' @@ -389,7 +386,7 @@ def fair_user_conf(tmp_path): ENDPOINT=http://localhost:8545 NODE_VERSION='main' FILEBEAT_HOST=127.0.0.1:3010 - SGX_SERVER_URL=http://127.0.0.1 + SGX_URL=http://127.0.0.1 BLOCK_DEVICE=/dev/sss ENV_TYPE='devnet' ENFORCE_BTRFS=False @@ -410,11 +407,10 @@ def fair_boot_user_conf(tmp_path): ENDPOINT=http://localhost:8545 NODE_VERSION='main' FILEBEAT_HOST=127.0.0.1:3010 - SGX_SERVER_URL=http://127.0.0.1 + SGX_URL=http://127.0.0.1 BLOCK_DEVICE=/dev/sss ENV_TYPE='devnet' - MANAGER_CONTRACTS='test-manager' - IMA_CONTRACTS='test-ima' + FAIR_CONTRACTS='test-fair' """ with open(test_env_path, 'w') as env_file: env_file.write(test_env) @@ -436,6 +432,7 @@ def passive_user_conf(tmp_path): SCHAIN_NAME='test-schain' ENFORCE_BTRFS=False MANAGER_CONTRACTS='test-manager' + IMA_CONTRACTS='test-ima' """ with open(test_env_path, 'w') as env_file: env_file.write(test_env) From 24ffcb0863e8833ad20cbfe352a87e0d1494d412 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 16:57:50 +0000 Subject: [PATCH 05/10] fix passive node tests --- tests/.skale/node_data/settings/node.toml | 17 +++++++++++++ tests/cli/fair_passive_node_test.py | 27 +++++++++----------- tests/cli/passive_node_test.py | 5 +++- tests/conftest.py | 31 ++++++++++++++++++++--- 4 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 tests/.skale/node_data/settings/node.toml diff --git a/tests/.skale/node_data/settings/node.toml b/tests/.skale/node_data/settings/node.toml new file mode 100644 index 00000000..cbc85451 --- /dev/null +++ b/tests/.skale/node_data/settings/node.toml @@ -0,0 +1,17 @@ +env_type = "devnet" +endpoint = "http://localhost:8545/" +bite = false +container_stop_timeout = 300 +max_skaled_restart_count = 5 +disable_colors = false +node_version = "main" +block_device = "/dev/sss" +filebeat_host = "127.0.0.1:3010" +container_configs_dir = "" +skip_docker_config = false +skip_docker_cleanup = false +monitoring_containers = false +manager_contracts = "test-manager" +ima_contracts = "test-ima" +schain_name = "test-schain" +enforce_btrfs = false diff --git a/tests/cli/fair_passive_node_test.py b/tests/cli/fair_passive_node_test.py index a8b7818a..00c61ca0 100644 --- a/tests/cli/fair_passive_node_test.py +++ b/tests/cli/fair_passive_node_test.py @@ -15,15 +15,12 @@ init_default_logger() -def test_init_fair_passive(mocked_g_config, tmp_path): - env_file = tmp_path / 'test-env' - env_file.write_text('') +def test_init_fair_passive(mocked_g_config, fair_passive_settings, fair_passive_user_conf): pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True) with ( mock.patch('subprocess.run', new=subprocess_run_mock), mock.patch('node_cli.fair.common.init_fair_op', return_value=True), mock.patch('node_cli.fair.common.compose_node_env', return_value={}), - mock.patch('node_cli.fair.common.save_env_params'), mock.patch('node_cli.fair.passive.setup_fair_passive'), mock.patch('node_cli.fair.common.time.sleep'), mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True), @@ -34,7 +31,7 @@ def test_init_fair_passive(mocked_g_config, tmp_path): result = run_command( init_passive_node, [ - env_file.as_posix(), + fair_passive_user_conf.as_posix(), '--id', '1', ], @@ -42,15 +39,14 @@ def test_init_fair_passive(mocked_g_config, tmp_path): assert result.exit_code == 0 -def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path): - env_file = tmp_path / 'test-env' - env_file.write_text('') +def test_init_fair_passive_snapshot_any( + mocked_g_config, fair_passive_settings, fair_passive_user_conf +): pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True) with ( mock.patch('subprocess.run', new=subprocess_run_mock), mock.patch('node_cli.fair.common.init_fair_op', return_value=True), mock.patch('node_cli.fair.common.compose_node_env', return_value={}), - mock.patch('node_cli.fair.common.save_env_params'), mock.patch('node_cli.fair.passive.setup_fair_passive'), mock.patch('node_cli.fair.common.time.sleep'), mock.patch('node_cli.core.node.is_base_containers_alive', return_value=True), @@ -61,7 +57,7 @@ def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path): result = run_command( init_passive_node, [ - env_file.as_posix(), + fair_passive_user_conf.as_posix(), '--id', '2', '--snapshot', @@ -71,9 +67,9 @@ def test_init_fair_passive_snapshot_any(mocked_g_config, tmp_path): assert result.exit_code == 0 -def test_update_fair_passive(mocked_g_config, tmp_path, clean_node_options): - env_file = tmp_path / 'test-env' - env_file.write_text('') +def test_update_fair_passive( + mocked_g_config, fair_passive_settings, fair_passive_user_conf, clean_node_options +): pathlib.Path(NODE_DATA_PATH).mkdir(parents=True, exist_ok=True) with ( mock.patch('subprocess.run', new=subprocess_run_mock), @@ -84,7 +80,7 @@ def test_update_fair_passive(mocked_g_config, tmp_path, clean_node_options): mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True), ): - result = run_command(update_node, [env_file.as_posix(), '--yes']) + result = run_command(update_node, [fair_passive_user_conf.as_posix(), '--yes']) assert result.exit_code == 0 @@ -107,4 +103,5 @@ def test_cleanup_node(mocked_g_config): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 cleanup_mock.assert_called_once_with( - node_mode=NodeMode.PASSIVE, prune=False, env={'SCHAIN_NAME': 'test'}) + node_mode=NodeMode.PASSIVE, compose_env={'SCHAIN_NAME': 'test'}, prune=False + ) diff --git a/tests/cli/passive_node_test.py b/tests/cli/passive_node_test.py index 504c5876..5bb15227 100644 --- a/tests/cli/passive_node_test.py +++ b/tests/cli/passive_node_test.py @@ -65,9 +65,12 @@ def test_init_passive_archive(mocked_g_config, clean_node_options, passive_user_ mock.patch('node_cli.operations.base.sync_skale_node'), mock.patch('node_cli.operations.base.configure_docker'), mock.patch('node_cli.operations.base.prepare_host'), + mock.patch('node_cli.operations.base.save_internal_settings'), + mock.patch('node_cli.operations.base.run_host_checks', return_value=[]), + mock.patch('node_cli.operations.base.set_passive_node_options'), mock.patch('node_cli.operations.base.ensure_filestorage_mapping'), - mock.patch('node_cli.operations.base.link_env_file'), mock.patch('node_cli.operations.base.generate_nginx_config'), + mock.patch('node_cli.operations.base.get_settings'), mock.patch('node_cli.operations.base.prepare_block_device'), mock.patch('node_cli.operations.base.CliMetaManager.update_meta'), mock.patch('node_cli.operations.base.update_resource_allocation'), diff --git a/tests/conftest.py b/tests/conftest.py index 7dc5cbda..dd65b2cb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,6 +49,7 @@ from node_cli.utils.global_config import generate_g_config_file from node_cli.utils.node_type import NodeMode from tests.fixtures.settings import ( # noqa: F401 + _cleanup_settings, fair_active_settings, fair_passive_settings, skale_active_settings, @@ -300,12 +301,8 @@ def valid_env_params(): 'FILEBEAT_HOST': '127.0.0.1:3010', 'SGX_URL': 'http://127.0.0.1', 'BLOCK_DEVICE': '/dev/sss', - 'DOCKER_LVMPY_VERSION': 'master', 'ENV_TYPE': 'devnet', - 'SCHAIN_NAME': 'test', 'ENFORCE_BTRFS': 'False', - 'MANAGER_CONTRACTS': 'test-manager', - 'IMA_CONTRACTS': 'test-ima', 'FAIR_CONTRACTS': 'test-fair', } @@ -322,6 +319,7 @@ def valid_env_file(valid_env_params): finally: if file_name: os.unlink(file_name) + _cleanup_settings() @pytest.fixture @@ -376,6 +374,7 @@ def regular_user_conf(tmp_path): yield test_env_path finally: test_env_path.unlink() + _cleanup_settings() @pytest.fixture @@ -397,6 +396,7 @@ def fair_user_conf(tmp_path): yield test_env_path finally: test_env_path.unlink() + _cleanup_settings() @pytest.fixture @@ -417,6 +417,28 @@ def fair_boot_user_conf(tmp_path): yield test_env_path finally: test_env_path.unlink() + _cleanup_settings() + + +@pytest.fixture +def fair_passive_user_conf(tmp_path): + test_env_path = pathlib.Path(tmp_path / 'test-env') + try: + test_env = """ + ENDPOINT=http://localhost:8545 + NODE_VERSION='main' + FILEBEAT_HOST=127.0.0.1:3010 + BLOCK_DEVICE=/dev/sss + ENV_TYPE='devnet' + ENFORCE_BTRFS=False + FAIR_CONTRACTS='test-fair' + """ + with open(test_env_path, 'w') as env_file: + env_file.write(test_env) + yield test_env_path + finally: + test_env_path.unlink() + _cleanup_settings() @pytest.fixture @@ -439,6 +461,7 @@ def passive_user_conf(tmp_path): yield test_env_path finally: test_env_path.unlink() + _cleanup_settings() @pytest.fixture From 34a83f50366f1c8783574ea2d2e33958eb58cab4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 17:25:58 +0000 Subject: [PATCH 06/10] fix passive node tests --- node_cli/core/node.py | 2 ++ node_cli/fair/common.py | 1 + tests/.skale/node_data/settings/node.toml | 17 ----------------- tests/cli/passive_node_test.py | 1 - 4 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 tests/.skale/node_data/settings/node.toml diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 0398010f..7c9e2409 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -247,6 +247,7 @@ def cleanup(node_mode: NodeMode, prune: bool = False) -> None: def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str]: + st = get_settings() if node_mode == NodeMode.PASSIVE or node_type == NodeType.FAIR: mnt_dir = SCHAINS_MNT_DIR_SINGLE_CHAIN else: @@ -256,6 +257,7 @@ def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str] 'SCHAINS_MNT_DIR': mnt_dir, 'FILESTORAGE_MAPPING': FILESTORAGE_MAPPING, 'SKALE_LIB_PATH': SKALE_STATE_DIR, + 'FILEBEAT_HOST': st.filebeat_host, } return {k: v for k, v in env.items() if v != ''} diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py index 37fdba67..98f10a7f 100644 --- a/node_cli/fair/common.py +++ b/node_cli/fair/common.py @@ -76,6 +76,7 @@ def init( print('Fair node is initialized') +@check_inited @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) diff --git a/tests/.skale/node_data/settings/node.toml b/tests/.skale/node_data/settings/node.toml deleted file mode 100644 index cbc85451..00000000 --- a/tests/.skale/node_data/settings/node.toml +++ /dev/null @@ -1,17 +0,0 @@ -env_type = "devnet" -endpoint = "http://localhost:8545/" -bite = false -container_stop_timeout = 300 -max_skaled_restart_count = 5 -disable_colors = false -node_version = "main" -block_device = "/dev/sss" -filebeat_host = "127.0.0.1:3010" -container_configs_dir = "" -skip_docker_config = false -skip_docker_cleanup = false -monitoring_containers = false -manager_contracts = "test-manager" -ima_contracts = "test-ima" -schain_name = "test-schain" -enforce_btrfs = false diff --git a/tests/cli/passive_node_test.py b/tests/cli/passive_node_test.py index 5bb15227..e8419220 100644 --- a/tests/cli/passive_node_test.py +++ b/tests/cli/passive_node_test.py @@ -67,7 +67,6 @@ def test_init_passive_archive(mocked_g_config, clean_node_options, passive_user_ mock.patch('node_cli.operations.base.prepare_host'), mock.patch('node_cli.operations.base.save_internal_settings'), mock.patch('node_cli.operations.base.run_host_checks', return_value=[]), - mock.patch('node_cli.operations.base.set_passive_node_options'), mock.patch('node_cli.operations.base.ensure_filestorage_mapping'), mock.patch('node_cli.operations.base.generate_nginx_config'), mock.patch('node_cli.operations.base.get_settings'), From 7afb2318253bfb6b90ecbf6c13ef0ecd0c4f37ce Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 18:18:24 +0000 Subject: [PATCH 07/10] update fixtures --- tests/conftest.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index dd65b2cb..f42034bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,7 +49,16 @@ from node_cli.utils.global_config import generate_g_config_file from node_cli.utils.node_type import NodeMode from tests.fixtures.settings import ( # noqa: F401 + INTERNAL_FAIR_ACTIVE, + INTERNAL_FAIR_PASSIVE, + INTERNAL_SKALE_ACTIVE, + INTERNAL_SKALE_PASSIVE, + NODE_FAIR_ACTIVE, + NODE_FAIR_PASSIVE, + NODE_SKALE_ACTIVE, + NODE_SKALE_PASSIVE, _cleanup_settings, + _write_settings, fair_active_settings, fair_passive_settings, skale_active_settings, @@ -357,6 +366,7 @@ def set_env_var(name, value): @pytest.fixture def regular_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') + _write_settings(INTERNAL_SKALE_ACTIVE, NODE_SKALE_ACTIVE) try: test_env = """ ENDPOINT=http://localhost:8545 @@ -380,6 +390,7 @@ def regular_user_conf(tmp_path): @pytest.fixture def fair_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') + _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE) try: test_env = """ ENDPOINT=http://localhost:8545 @@ -402,6 +413,7 @@ def fair_user_conf(tmp_path): @pytest.fixture def fair_boot_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') + _write_settings(INTERNAL_FAIR_ACTIVE, NODE_FAIR_ACTIVE) try: test_env = """ ENDPOINT=http://localhost:8545 @@ -423,6 +435,7 @@ def fair_boot_user_conf(tmp_path): @pytest.fixture def fair_passive_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') + _write_settings(INTERNAL_FAIR_PASSIVE, NODE_FAIR_PASSIVE) try: test_env = """ ENDPOINT=http://localhost:8545 @@ -444,6 +457,7 @@ def fair_passive_user_conf(tmp_path): @pytest.fixture def passive_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') + _write_settings(INTERNAL_SKALE_PASSIVE, NODE_SKALE_PASSIVE) try: test_env = """ ENDPOINT=http://localhost:8545 From df5bdded17b2d896abeafbd28a5041b3b97aba0a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 19:18:13 +0000 Subject: [PATCH 08/10] fix fair node tests --- node_cli/fair/common.py | 1 - tests/cli/node_test.py | 4 ++-- tests/conftest.py | 22 ++++++++++++++++++++++ tests/fair/fair_node_test.py | 30 ++++++++++-------------------- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py index 98f10a7f..37fdba67 100644 --- a/node_cli/fair/common.py +++ b/node_cli/fair/common.py @@ -76,7 +76,6 @@ def init( print('Fair node is initialized') -@check_inited @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py index 04317f98..a0c064f0 100644 --- a/tests/cli/node_test.py +++ b/tests/cli/node_test.py @@ -384,7 +384,7 @@ def test_maintenance_off(mocked_g_config): ) -def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node_option): +def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node_option, skale_active_settings): resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None}) with ( mock.patch('subprocess.run', new=subprocess_run_mock), @@ -415,7 +415,7 @@ def test_turn_off_maintenance_on(mocked_g_config, regular_user_conf, active_node assert result.exit_code == CLIExitCodes.UNSAFE_UPDATE -def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node_option): +def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node_option, skale_active_settings): resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None}) with ( mock.patch('subprocess.run', new=subprocess_run_mock), diff --git a/tests/conftest.py b/tests/conftest.py index f42034bd..3d6cc858 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,6 +66,28 @@ ) from tests.helper import TEST_META_V1, TEST_META_V2, TEST_META_V3, TEST_SCHAINS_MNT_DIR_SINGLE_CHAIN +TIMEOUT_PATCHES = [ + 'node_cli.configs.TM_INIT_TIMEOUT', + 'node_cli.configs.RESTORE_SLEEP_TIMEOUT', + 'node_cli.configs.INIT_TIMEOUT', + 'node_cli.core.node.TM_INIT_TIMEOUT', + 'node_cli.core.node.RESTORE_SLEEP_TIMEOUT', + 'node_cli.fair.common.TM_INIT_TIMEOUT', + 'node_cli.fair.common.INIT_TIMEOUT', + 'node_cli.fair.boot.TM_INIT_TIMEOUT', + 'node_cli.fair.active.RESTORE_SLEEP_TIMEOUT', +] + + +@pytest.fixture(autouse=True, scope='session') +def _fast_timeouts(): + patchers = [mock.patch(target, 1) for target in TIMEOUT_PATCHES] + for p in patchers: + p.start() + yield + for p in patchers: + p.stop() + @pytest.fixture() def tmp_dir_path(): diff --git a/tests/fair/fair_node_test.py b/tests/fair/fair_node_test.py index d1908ef6..28ade830 100644 --- a/tests/fair/fair_node_test.py +++ b/tests/fair/fair_node_test.py @@ -28,9 +28,7 @@ def test_restore_fair( restore(backup_path, valid_env_file) - mock_compose_env.assert_called_once_with( - node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE - ) + mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mock_restore_op.assert_called_once_with( node_mode=NodeMode.ACTIVE, settings=mock.ANY, @@ -58,9 +56,7 @@ def test_init_fair_boot( init_boot(valid_env_file) - mock_compose_env.assert_called_once_with( - node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE - ) + mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mock_init_op.assert_called_once_with( settings=mock.ANY, compose_env=mock_env, @@ -95,9 +91,7 @@ def test_update_fair_boot( update(valid_env_file, pull_config_for_schain) - mock_compose_env.assert_called_once_with( - node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE - ) + mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mock_update_op.assert_called_once_with( settings=mock.ANY, compose_env=mock_env, @@ -127,9 +121,7 @@ def test_migrate_from_boot( migrate_from_boot(valid_env_file) - mock_compose_env.assert_called_once_with( - node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE - ) + mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mock_migrate_op.assert_called_once_with( settings=mock.ANY, compose_env=mock_env, @@ -146,19 +138,17 @@ def test_cleanup_success( mock_compose_env, mock_cleanup_fair_op, mock_is_user_valid, - inited_node, resource_alloc, meta_file_v3, active_node_option, + inited_node, ): mock_env = {'ENV_TYPE': 'devnet'} mock_compose_env.return_value = mock_env cleanup(node_mode=NodeMode.ACTIVE) - mock_compose_env.assert_called_once_with( - node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE - ) + mock_compose_env.assert_called_once_with(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) mock_cleanup_fair_op.assert_called_once_with( node_mode=NodeMode.ACTIVE, compose_env=mock_env, prune=False ) @@ -171,10 +161,10 @@ def test_cleanup_calls_operations_in_correct_order( mock_compose_env, mock_cleanup_fair_op, mock_is_user_valid, - inited_node, resource_alloc, meta_file_v3, active_node_option, + inited_node, ): from node_cli.fair.common import cleanup @@ -204,10 +194,10 @@ def test_cleanup_continues_after_fair_op_error( mock_compose_env, mock_cleanup_fair_op, mock_is_user_valid, - inited_node, resource_alloc, meta_file_v3, active_node_option, + inited_node, ): mock_env = {'ENV_TYPE': 'devnet'} mock_compose_env.return_value = mock_env @@ -224,9 +214,9 @@ def test_cleanup_continues_after_fair_op_error( @mock.patch('node_cli.utils.decorators.is_user_valid', return_value=False) def test_cleanup_fails_when_user_invalid( mock_is_user_valid, - inited_node, resource_alloc, meta_file_v3, + inited_node, ): """Test that cleanup fails when user validation fails""" import pytest @@ -253,10 +243,10 @@ def test_cleanup_logs_success_message( mock_compose_env, mock_cleanup_fair_op, mock_is_user_valid, - inited_node, resource_alloc, meta_file_v3, active_node_option, + inited_node, ): mock_env = {'ENV_TYPE': 'devnet'} mock_compose_env.return_value = mock_env From 6447de3be388d859fd4b172bc1f72d5b0dcb2684 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 19:59:55 +0000 Subject: [PATCH 09/10] fix node tests --- node_cli/fair/common.py | 1 + tests/.skale/config/docker-compose-fair.yml | 5 +++++ tests/core/core_node_test.py | 6 +----- tests/fair/fair_node_test.py | 7 ++++--- 4 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 tests/.skale/config/docker-compose-fair.yml diff --git a/node_cli/fair/common.py b/node_cli/fair/common.py index 37fdba67..98f10a7f 100644 --- a/node_cli/fair/common.py +++ b/node_cli/fair/common.py @@ -76,6 +76,7 @@ def init( print('Fair node is initialized') +@check_inited @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) diff --git a/tests/.skale/config/docker-compose-fair.yml b/tests/.skale/config/docker-compose-fair.yml new file mode 100644 index 00000000..c09f2c08 --- /dev/null +++ b/tests/.skale/config/docker-compose-fair.yml @@ -0,0 +1,5 @@ +services: + test: + container_name: test + image: alpine:latest + network_mode: host diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py index b9d66960..b28719d4 100644 --- a/tests/core/core_node_test.py +++ b/tests/core/core_node_test.py @@ -193,11 +193,7 @@ def test_is_base_containers_alive_empty(node_type, node_mode, is_boot): 'fair', ], ) -def test_compose_node_env( - node_type, - node_mode, - expected_mnt_dir, -): +def test_compose_node_env(node_type, node_mode, expected_mnt_dir, regular_user_conf): result_env = compose_node_env( node_type=node_type, node_mode=node_mode, diff --git a/tests/fair/fair_node_test.py b/tests/fair/fair_node_test.py index 28ade830..6554cd3f 100644 --- a/tests/fair/fair_node_test.py +++ b/tests/fair/fair_node_test.py @@ -227,11 +227,12 @@ def test_cleanup_fails_when_user_invalid( cleanup(node_mode=NodeMode.ACTIVE) -def test_cleanup_fails_when_not_inited(ensure_meta_removed, active_node_option): +def test_cleanup_fails_when_not_inited(ensure_meta_removed, active_node_option, fair_user_conf): import pytest - with pytest.raises(SystemExit): - cleanup(node_mode=NodeMode.ACTIVE) + with mock.patch('node_cli.operations.cleanup_fair_op', return_value=None): + with pytest.raises(SystemExit): + cleanup(node_mode=NodeMode.ACTIVE) @mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) From a31d653d99285702aff26643a999dd82c9dd7a5d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Feb 2026 20:30:13 +0000 Subject: [PATCH 10/10] fix nftables test --- tests/conftest.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3d6cc858..8b610d96 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -70,12 +70,6 @@ 'node_cli.configs.TM_INIT_TIMEOUT', 'node_cli.configs.RESTORE_SLEEP_TIMEOUT', 'node_cli.configs.INIT_TIMEOUT', - 'node_cli.core.node.TM_INIT_TIMEOUT', - 'node_cli.core.node.RESTORE_SLEEP_TIMEOUT', - 'node_cli.fair.common.TM_INIT_TIMEOUT', - 'node_cli.fair.common.INIT_TIMEOUT', - 'node_cli.fair.boot.TM_INIT_TIMEOUT', - 'node_cli.fair.active.RESTORE_SLEEP_TIMEOUT', ]