mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
fix: keep PBS logs and maintenance jobs readable
- Draw ZFS ARC only from recorded history, leaving it out on PBS - Read the maintenance jobs of a local PBS from its API, with their last state - Show the OK state of PBS jobs in green whatever its case - Compress rotated logs at once in the Log2RAM and logrotate profiles - Offer the Log2RAM 1.8 and logrotate 1.2 update to existing installations
This commit is contained in:
@@ -4,6 +4,7 @@ journald then continues an old sequence and deletes its newest archives first.
|
||||
The post-install makes the sync remove that file from the disk copy."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
@@ -86,7 +87,18 @@ class Log2ramJournalSyncTests(unittest.TestCase):
|
||||
# An existing installation is told that it needs a reboot.
|
||||
self.assertIn('if pmx_log2ram_drop_stale_journal "$log2ram_bin"; then\n NECESSARY_REBOOT=1', text)
|
||||
# The version is what offers the update to existing installations.
|
||||
self.assertIn(f'{function}() {{\n local FUNC_VERSION="1.7"', text)
|
||||
version = re.search(rf'{function}\(\) {{\n local FUNC_VERSION="([0-9.]+)"', text)
|
||||
self.assertIsNotNone(version)
|
||||
self.assertGreaterEqual(tuple(map(int, version.group(1).split("."))), (1, 7))
|
||||
|
||||
def test_rotated_logs_are_compressed_at_once_on_a_tmpfs_var_log(self):
|
||||
# A rotated log kept uncompressed filled the 128M Log2RAM of a PBS.
|
||||
for name, function in FLOWS.items():
|
||||
with self.subTest(flow=name):
|
||||
text = (ROOT / "scripts/post_install" / name).read_text()
|
||||
self.assertNotIn("delaycompress", text)
|
||||
version = re.search(rf'{function}\(\) {{\n local FUNC_VERSION="([0-9.]+)"', text)
|
||||
self.assertGreaterEqual(tuple(map(int, version.group(1).split("."))), (1, 8))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -54,9 +54,10 @@ function taskState(task: Task): keyof typeof badgeTone {
|
||||
return "error"
|
||||
}
|
||||
|
||||
// PBS reports "OK", "WARNINGS: n" or the error text of the last run.
|
||||
function jobState(state?: string): keyof typeof badgeTone {
|
||||
if (!state) return "muted"
|
||||
if (state === "ok") return "ok"
|
||||
if (state.toLowerCase() === "ok") return "ok"
|
||||
if (state.toLowerCase().startsWith("warn")) return "warn"
|
||||
return "error"
|
||||
}
|
||||
|
||||
@@ -12242,18 +12242,6 @@ def _compute_node_metrics_payload(timeframe: str) -> dict:
|
||||
deadline = time.monotonic() + _NODE_METRICS_TIMEOUT
|
||||
local_node = get_proxmox_node_name()
|
||||
|
||||
zfs_arc_size = 0
|
||||
try:
|
||||
with open('/proc/spl/kstat/zfs/arcstats', 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('size'):
|
||||
parts = line.split()
|
||||
if len(parts) >= 3:
|
||||
zfs_arc_size = int(parts[2])
|
||||
break
|
||||
except (FileNotFoundError, PermissionError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
rrd_result = subprocess.run(
|
||||
_node_rrd_command(local_node, timeframe),
|
||||
@@ -12322,11 +12310,11 @@ def _compute_node_metrics_payload(timeframe: str) -> dict:
|
||||
'code': 'metrics_invalid_data',
|
||||
})
|
||||
|
||||
# Only the history the node recorded is drawn. A PBS RRD has no ARC field,
|
||||
# and the current size repeated over the period is not its history.
|
||||
for item in rrd_data:
|
||||
if 'arcsize' in item:
|
||||
item['zfsarc'] = item['arcsize']
|
||||
elif zfs_arc_size > 0 and ('zfsarc' not in item or item.get('zfsarc', 0) == 0):
|
||||
item['zfsarc'] = zfs_arc_size
|
||||
|
||||
def _values_from(items, field_key, scale=1.0):
|
||||
return [item[field_key] * scale for item in items
|
||||
|
||||
@@ -279,6 +279,20 @@ def _cli(*arguments):
|
||||
raise PBSError("Could not read PBS " + " ".join(arguments[:2]) + ". Check its service and permissions.") from exc
|
||||
|
||||
|
||||
def _local_api(path):
|
||||
"""The local PBS API, which reports what the job configuration lacks: the
|
||||
state of the last run and the next one."""
|
||||
try:
|
||||
result = subprocess.run(["proxmox-backup-debug", "api", "get", path, "--output-format", "json"],
|
||||
check=True, text=True, capture_output=True, timeout=8)
|
||||
data = json.loads(result.stdout)
|
||||
if not isinstance(data, list) or not all(isinstance(row, dict) for row in data):
|
||||
raise ValueError()
|
||||
return data
|
||||
except (OSError, subprocess.SubprocessError, ValueError) as exc:
|
||||
raise PBSError("Could not read PBS " + path + ". Check its service and permissions.") from exc
|
||||
|
||||
|
||||
def _local_datastores():
|
||||
stores = _cli("datastore", "list")
|
||||
result = []
|
||||
@@ -325,9 +339,9 @@ def get_status(identifier):
|
||||
services = _local_services()
|
||||
operations = {"datastores": _local_datastores,
|
||||
"tasks": lambda: _cli("task", "list", "--all", "true", "--limit", "20"),
|
||||
"sync": lambda: _cli("sync-job", "list"),
|
||||
"verify": lambda: _cli("verify-job", "list"),
|
||||
"prune": lambda: _cli("prune-job", "list")}
|
||||
"sync": lambda: _local_api("/admin/sync"),
|
||||
"verify": lambda: _local_api("/admin/verify"),
|
||||
"prune": lambda: _local_api("/admin/prune")}
|
||||
else:
|
||||
with _lock:
|
||||
server = next((row for row in _load_servers() if row["id"] == identifier), None)
|
||||
|
||||
@@ -152,9 +152,11 @@ class PBSManagerTests(unittest.TestCase):
|
||||
def test_local_status_reads_pbs_commands_and_filesystem_capacity(self):
|
||||
def run(command, **kwargs):
|
||||
self.assertNotIn("shell", kwargs)
|
||||
self.assertEqual(command[0], "proxmox-backup-manager")
|
||||
self.assertIn(command[0], ("proxmox-backup-manager", "proxmox-backup-debug"))
|
||||
self.assertEqual(command[-2:], ["--output-format", "json"])
|
||||
data = [{"name": "backup", "path": str(self.root)}] if command[1:3] == ["datastore", "list"] else []
|
||||
if command[:4] == ["proxmox-backup-debug", "api", "get", "/admin/verify"]:
|
||||
data = [{"id": "v-1", "store": "backup", "schedule": "daily", "last-run-state": "OK"}]
|
||||
return SimpleNamespace(stdout=json.dumps(data), returncode=0)
|
||||
with mock.patch.object(pbs, "get_platform", return_value={"product": "pbs", "version": "4.2.5"}), \
|
||||
mock.patch.object(pbs, "_local_services", return_value={key: "active" for key in pbs.PBS_SERVICES}), \
|
||||
@@ -165,6 +167,8 @@ class PBSManagerTests(unittest.TestCase):
|
||||
self.assertEqual(result["errors"], {})
|
||||
self.assertEqual(result["datastores"][0]["used"], 30)
|
||||
self.assertEqual(set(result["services"]), set(pbs.PBS_SERVICES))
|
||||
# Jobs come from the API, which carries the state of their last run.
|
||||
self.assertEqual(result["verify"][0]["last-run-state"], "OK")
|
||||
|
||||
def test_pbs_installed_next_to_pve_is_read_locally_with_its_own_version(self):
|
||||
def run(command, **kwargs):
|
||||
|
||||
@@ -392,7 +392,7 @@ EOF
|
||||
|
||||
# ==========================================================
|
||||
optimize_logrotate() {
|
||||
local FUNC_VERSION="1.1"
|
||||
local FUNC_VERSION="1.2"
|
||||
pmx_journal_context "optimize_logrotate" "$FUNC_VERSION"
|
||||
# description: Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).
|
||||
msg_info "$(translate "Optimizing logrotate configuration...")"
|
||||
@@ -408,7 +408,6 @@ su root adm
|
||||
rotate 7
|
||||
size 10M
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
@@ -941,7 +940,6 @@ _update_existing_log2ram_auto() {
|
||||
size 20M
|
||||
rotate 3
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
@@ -1041,7 +1039,7 @@ EOF
|
||||
|
||||
|
||||
install_log2ram_auto() {
|
||||
local FUNC_VERSION="1.7"
|
||||
local FUNC_VERSION="1.8"
|
||||
local existing_log2ram_bin=""
|
||||
pmx_journal_context "install_log2ram_auto" "$FUNC_VERSION"
|
||||
|
||||
@@ -1174,7 +1172,6 @@ install_log2ram_auto() {
|
||||
size 20M
|
||||
rotate 3
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
|
||||
@@ -2348,7 +2348,7 @@ setup_motd() {
|
||||
|
||||
|
||||
optimize_logrotate() {
|
||||
local FUNC_VERSION="1.1"
|
||||
local FUNC_VERSION="1.2"
|
||||
# description: Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).
|
||||
msg_info2 "$(translate "Optimizing logrotate configuration...")"
|
||||
|
||||
@@ -2369,7 +2369,6 @@ su root adm
|
||||
rotate 7
|
||||
size 10M
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
@@ -3264,7 +3263,6 @@ _update_existing_log2ram_custom() {
|
||||
size 20M
|
||||
rotate 3
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
@@ -3349,7 +3347,7 @@ EOF
|
||||
|
||||
|
||||
configure_log2ram() {
|
||||
local FUNC_VERSION="1.7"
|
||||
local FUNC_VERSION="1.8"
|
||||
local existing_log2ram_bin=""
|
||||
pmx_journal_context "configure_log2ram" "$FUNC_VERSION"
|
||||
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
|
||||
@@ -3487,7 +3485,6 @@ configure_log2ram() {
|
||||
size 20M
|
||||
rotate 3
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
copytruncate
|
||||
|
||||
Reference in New Issue
Block a user