Controlla raggiungibilita share backup
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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&rest</WorkingDirectory>
|
<WorkingDirectory>C:\programmi\bak-and-rest</WorkingDirectory>
|
||||||
</Exec>
|
</Exec>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Task>
|
</Task>
|
||||||
|
|||||||
@@ -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&rest</WorkingDirectory>
|
<WorkingDirectory>C:\programmi\bak-and-rest</WorkingDirectory>
|
||||||
</Exec>
|
</Exec>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Task>
|
</Task>
|
||||||
|
|||||||
32
tests/test_server_check.py
Normal file
32
tests/test_server_check.py
Normal 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)
|
||||||
Reference in New Issue
Block a user