diff --git a/node_cli/cli/schains.py b/node_cli/cli/schains.py index cd4a7e00..5fa3fca1 100644 --- a/node_cli/cli/schains.py +++ b/node_cli/cli/schains.py @@ -25,6 +25,7 @@ from node_cli.core.schains import ( describe, get_schain_firewall_rules, + restore_schain_from_snapshot, show_config, show_dkg_info, show_schains, @@ -95,3 +96,10 @@ def repair(schain_name: str, snapshot_from: Optional[str] = None) -> None: ) def info_(schain_name: str, json_format: bool) -> None: describe(schain_name, raw=json_format) + + +@schains.command('restore', help='Restore schain from local snapshot') +@click.argument('schain_name') +@click.argument('snapshot_path') +def restore(schain_name: str, snapshot_path: str) -> None: + restore_schain_from_snapshot(schain_name, snapshot_path) diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 8e0612da..b32e7687 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -357,26 +357,30 @@ def is_base_containers_alive(): return len(skale_containers) >= BASE_CONTAINERS_AMOUNT -def get_node_info(format): +def get_node_info_plain(): status, payload = get_request( blueprint=BLUEPRINT_NAME, method='info' ) if status == 'ok': - node_info = payload['node_info'] - if format == 'json': - print(node_info) - elif node_info['status'] == NodeStatuses.NOT_CREATED.value: - print(TEXTS['service']['node_not_registered']) - else: - print_node_info( - node_info, - get_node_status(int(node_info['status'])) - ) + return payload['node_info'] else: error_exit(payload, exit_code=CLIExitCodes.BAD_API_RESPONSE) +def get_node_info(format): + node_info = get_node_info_plain() + if format == 'json': + print(node_info) + elif node_info['status'] == NodeStatuses.NOT_CREATED.value: + print(TEXTS['service']['node_not_registered']) + else: + print_node_info( + node_info, + get_node_status(int(node_info['status'])) + ) + + def get_node_status(status): node_status = NodeStatuses(status).name return TEXTS['node']['status'][node_status] diff --git a/node_cli/core/schains.py b/node_cli/core/schains.py index 84a558ad..7941bda1 100644 --- a/node_cli/core/schains.py +++ b/node_cli/core/schains.py @@ -1,8 +1,12 @@ import logging +import os import pprint +import shutil +from pathlib import Path from typing import Optional +from node_cli.core.node import get_node_info_plain from node_cli.utils.helper import get_request, post_request, error_exit from node_cli.utils.exit_codes import CLIExitCodes from node_cli.utils.print_formatters import ( @@ -11,6 +15,8 @@ print_schain_info, print_schains ) +from node_cli.utils.helper import run_cmd +from lvmpy.src.core import mount, volume_mountpoint logger = logging.getLogger(__name__) @@ -98,3 +104,58 @@ def describe(schain: str, raw=False) -> None: print_schain_info(payload, raw=raw) else: error_exit(payload, exit_code=CLIExitCodes.BAD_API_RESPONSE) + + +def btrfs_set_readonly_false(subvolume_path: str) -> None: + run_cmd(['btrfs', 'property', 'set', '-ts', subvolume_path, 'ro', 'false']) + + +def btrfs_receive_binary(src_path: str, binary_path: str) -> None: + run_cmd(['btrfs', 'receive', '-f', binary_path, src_path]) + + +def get_block_number_from_path(snapshot_path: str) -> int: + stem = Path(snapshot_path).stem + try: + int(stem.split('-')[-1]) + except ValueError: + return -1 + + +def get_node_id() -> int: + info = get_node_info_plain() + return info['node_id'] + + +def migrate_prices_and_blocks(src_path: str, node_id: int) -> None: + prices_path = os.path.join(f'prices_{node_id}.db') + shutil.move(Path(src_path).glob('prices_*.db'), prices_path) + blocks_path = os.path.join(f'blocks_{node_id}.db') + shutil.move(Path(src_path).glob('blocks_*.db'), blocks_path) + + +def fillin_snapshot_folder(src_path: str, block_number: int) -> None: + snapshot_folder_path = os.path.join( + src_path, 'snapshots', str(block_number)) + os.makedirs(snapshot_folder_path, exist_ok=True) + for subvolume_path in os.listdir(src_path): + # TODO: Finalise + snapshot_path_for_subvolume = snapshot_folder_path(subvolume_path) + btrfs_set_readonly_false(subvolume_path) + + +def restore_schain_from_snapshot(schain: str, snapshot_path: str) -> None: + block_number = get_block_number_from_path(snapshot_path) + if block_number == -1: + logger.error('Invalid snapshot path format') + return + node_id = get_node_id() + + mount(schain) + src_path = volume_mountpoint(schain) + btrfs_receive_binary(src_path, snapshot_path) + + for subvolume_path in os.listdir(src_path): + btrfs_set_readonly_false(subvolume_path) + + migrate_prices_and_blocks(src_path, node_id)