Controlla raggiungibilita share backup

This commit is contained in:
2026-07-02 11:07:01 +02:00
parent 169ede14dc
commit afe08a89cb
6 changed files with 82 additions and 6 deletions

View File

@@ -10,7 +10,7 @@ from pathlib import Path
from .config import AppConfig from .config import AppConfig
from .registry import ChangeEvent, ChangeRegistry from .registry import ChangeEvent, ChangeRegistry
from .server_check import check_server from .server_check import check_backup_target
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -23,7 +23,7 @@ class BackupResult:
def run_backup(config: AppConfig, logger: logging.Logger) -> BackupResult: def run_backup(config: AppConfig, logger: logging.Logger) -> BackupResult:
status = check_server(config.backup) status = check_backup_target(config.backup)
if not status.reachable: if not status.reachable:
message = f"Server backup non raggiungibile: {status.message}" message = f"Server backup non raggiungibile: {status.message}"
logger.error(message) logger.error(message)

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import json import json
import socket import socket
import subprocess
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@@ -31,6 +32,49 @@ def check_server(config: BackupConfig) -> ServerStatus:
return ServerStatus(False, checked_at, str(exc)) return ServerStatus(False, checked_at, str(exc))
def check_backup_target(config: BackupConfig) -> ServerStatus:
if config.engine != "robocopy":
return check_server(config)
checked_at = datetime.now(timezone.utc).isoformat()
if not config.remote_destinations:
return ServerStatus(False, checked_at, "No backup shares configured")
server_status = check_server(config)
if not server_status.reachable:
return server_status
unreachable = [
destination
for destination in config.remote_destinations
if not share_is_reachable(destination, config.server_check.timeout_seconds)
]
if unreachable:
return ServerStatus(
False,
checked_at,
"Backup share unreachable: " + ", ".join(unreachable),
)
return ServerStatus(True, checked_at, "Backup shares reachable")
def share_is_reachable(destination: str, timeout_seconds: int) -> bool:
probe = destination.rstrip("\\/") + "\\."
command = ["cmd.exe", "/d", "/c", f'if exist "{probe}" (exit /b 0) else (exit /b 1)']
try:
completed = subprocess.run(
command,
capture_output=True,
text=True,
timeout=max(timeout_seconds, 1),
check=False,
)
except (OSError, subprocess.TimeoutExpired):
return False
return completed.returncode == 0
def write_status(path: Path, status: ServerStatus, pending_count: int) -> None: def write_status(path: Path, status: ServerStatus, pending_count: int) -> None:
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
payload = asdict(status) payload = asdict(status)

View File

@@ -9,7 +9,7 @@ from tkinter import messagebox
from .config import load_config from .config import load_config
from .logging_setup import close_logger, configure_logging from .logging_setup import close_logger, configure_logging
from .registry import ChangeRegistry from .registry import ChangeRegistry
from .server_check import ServerStatus, check_server, write_status from .server_check import ServerStatus, check_backup_target, write_status
ALERT_MESSAGE = "Accendi il server di backup o verifica che sia connesso alla rete" ALERT_MESSAGE = "Accendi il server di backup o verifica che sia connesso alla rete"
@@ -19,7 +19,7 @@ def run_server_notifier(always_alert: bool = False) -> int:
config = load_config() config = load_config()
logger = configure_logging(config.storage.logs_dir, "server-notifier") logger = configure_logging(config.storage.logs_dir, "server-notifier")
try: try:
status = check_server(config.backup) status = check_backup_target(config.backup)
pending_count = ChangeRegistry(config.storage.change_registry).pending_count() pending_count = ChangeRegistry(config.storage.change_registry).pending_count()
write_status(config.storage.status_file, status, pending_count) write_status(config.storage.status_file, status, pending_count)

View File

@@ -39,7 +39,7 @@
<Exec> <Exec>
<Command>C:\Python314\python.exe</Command> <Command>C:\Python314\python.exe</Command>
<Arguments>-m bakrest.tray_app --nogui</Arguments> <Arguments>-m bakrest.tray_app --nogui</Arguments>
<WorkingDirectory>C:\devel\bak&amp;rest</WorkingDirectory> <WorkingDirectory>C:\programmi\bak-and-rest</WorkingDirectory>
</Exec> </Exec>
</Actions> </Actions>
</Task> </Task>

View File

@@ -50,7 +50,7 @@
<Exec> <Exec>
<Command>C:\Python314\python.exe</Command> <Command>C:\Python314\python.exe</Command>
<Arguments>-m bakrest.server_notifier</Arguments> <Arguments>-m bakrest.server_notifier</Arguments>
<WorkingDirectory>C:\devel\bak&amp;rest</WorkingDirectory> <WorkingDirectory>C:\programmi\bak-and-rest</WorkingDirectory>
</Exec> </Exec>
</Actions> </Actions>
</Task> </Task>

View File

@@ -0,0 +1,32 @@
from __future__ import annotations
import subprocess
from bakrest.server_check import share_is_reachable
def test_share_is_reachable_returns_true_on_zero_exit(monkeypatch) -> None:
def fake_run(*args, **kwargs):
return subprocess.CompletedProcess(args[0], 0)
monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
assert share_is_reachable("\\\\server\\share", 3)
def test_share_is_reachable_returns_false_on_nonzero_exit(monkeypatch) -> None:
def fake_run(*args, **kwargs):
return subprocess.CompletedProcess(args[0], 1)
monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
assert not share_is_reachable("\\\\server\\share", 3)
def test_share_is_reachable_returns_false_on_timeout(monkeypatch) -> None:
def fake_run(*args, **kwargs):
raise subprocess.TimeoutExpired(args[0], 3)
monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
assert not share_is_reachable("\\\\server\\share", 3)