From 0feac1746d6259cb68c2c2289fc7515668282ecd Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 15 Jul 2026 16:59:05 -0400 Subject: [PATCH] fix: show LXC storage in --dry-run summary The LXC storage check runs in every mode, but its results were never printed in the --dry-run summary, so a --dry-run appeared to skip LXC storage entirely. Add an "LXC Storage:" section to the summary (per-container usage %, a warning flag over the LXC_WARNING threshold, and the issue count) via a small, testable helper `_format_lxc_dry_run()`. Adds 4 regression tests. Closes #23 Ref: https://code.lotusguild.org/LotusGuild/hwmonDaemon/issues/23 Co-Authored-By: Claude Opus 4.8 --- hwmonDaemon.py | 29 +++++++++++++++++++++++++++++ tests/test_hwmon.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/hwmonDaemon.py b/hwmonDaemon.py index 6c74768..9cedd92 100644 --- a/hwmonDaemon.py +++ b/hwmonDaemon.py @@ -1038,6 +1038,10 @@ class SystemHealthMonitor: if health_report['system_health']['issues']: logger.info(f"\nSystem Issues: {len(health_report['system_health']['issues'])} found") + # LXC container storage (previously omitted from the dry-run summary; issue #23) + for line in self._format_lxc_dry_run(health_report.get('lxc_health', {})): + logger.info(line) + # PBS status pbs = health_report.get('pbs_health', {}) if pbs.get('is_pbs_node'): @@ -3998,6 +4002,31 @@ class SystemHealthMonitor: logger.error(f"Failed to write Prometheus metrics: {e}") return False + def _format_lxc_dry_run(self, lxc_health: Dict[str, Any]) -> List[str]: + """Build the LXC-storage lines for the --dry-run summary. + + The LXC storage check runs in every mode, but its results were previously + absent from the dry-run summary output, making it look like LXC storage was + not checked (issue #23). Returns a list of log lines (possibly empty). + """ + lines: List[str] = [] + containers = lxc_health.get('containers', []) + if containers: + lines.append("\nLXC Storage:") + warn = self.CONFIG['THRESHOLDS']['LXC_WARNING'] + for c in containers: + for fs in c.get('filesystems', []): + flag = "⚠️ " if fs.get('usage_percent', 0) >= warn else "" + lines.append( + f" {flag}CT{c['vmid']} {fs['mountpoint']}: " + f"{fs['usage_percent']:.1f}% used" + ) + if lxc_health.get('issues'): + lines.append(f" Issues: {len(lxc_health['issues'])} found") + elif lxc_health.get('status') == 'ERROR': + lines.append(f"\nLXC Storage: check error ({len(lxc_health.get('issues', []))} issue(s))") + return lines + def _check_lxc_storage(self) -> Dict[str, Any]: """ Check storage utilization for all running LXC containers diff --git a/tests/test_hwmon.py b/tests/test_hwmon.py index d6e127b..62261e1 100644 --- a/tests/test_hwmon.py +++ b/tests/test_hwmon.py @@ -503,3 +503,36 @@ class TestAttributeThresholds: """Age is still *tracked* (just not ticketed) — new-drive detection must work.""" assert monitor._is_new_drive(100) is True # ~4 days assert monitor._is_new_drive(64860) is False # ~7.4 years + + +# ── _format_lxc_dry_run (LXC storage shown in --dry-run summary; issue #23) ──── + +class TestLxcDryRunSummary: + def test_includes_container_usage_and_flags_over_threshold(self, monitor): + """Regression for issue #23: dry-run summary must surface LXC storage.""" + lxc = { + 'status': 'WARNING', + 'issues': ['LXC 105 high storage usage: 84.0% on /'], + 'containers': [ + {'vmid': '105', 'filesystems': [{'mountpoint': '/', 'usage_percent': 84.0}]}, + ], + } + text = '\n'.join(monitor._format_lxc_dry_run(lxc)) + assert 'LXC Storage:' in text + assert 'CT105 /' in text and '84.0% used' in text + assert '⚠️' in text # flagged: over the 80% warning threshold + assert 'Issues: 1 found' in text + + def test_healthy_container_not_flagged(self, monitor): + lxc = {'status': 'OK', 'issues': [], + 'containers': [{'vmid': '119', 'filesystems': [{'mountpoint': '/', 'usage_percent': 26.0}]}]} + text = '\n'.join(monitor._format_lxc_dry_run(lxc)) + assert 'CT119 /' in text and '26.0% used' in text + assert '⚠️' not in text + + def test_empty_when_no_containers(self, monitor): + assert monitor._format_lxc_dry_run({'status': 'OK', 'containers': [], 'issues': []}) == [] + + def test_error_status_is_reported(self, monitor): + lines = monitor._format_lxc_dry_run({'status': 'ERROR', 'containers': [], 'issues': ['boom']}) + assert any('check error' in ln for ln in lines)