From e34f9d832e3fb86c671d7b3dc3e389933a333ea9 Mon Sep 17 00:00:00 2001 From: David Schroeder Date: Tue, 22 Sep 2026 22:28:41 -0500 Subject: [PATCH] orphan disk search update --- README.md | 8 +++++--- defaults.inc | 2 +- inc/orphan-disks.py | 13 ++++++------- tests/test-orphan-disks.py | 13 +++++++------ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9d299d8..e97bce5 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,12 @@ aliases, and backend-specific backing dependencies require review. No guest or storage changes are made. Offline nodes, inactive storage, and failed reads are reported as incomplete -coverage; missing guest configurations prevent orphan classification. Changes -to guest configurations during the scan require a rerun. Scan when migration, +coverage; missing guest configurations prevent orphan classification. +Inactive/disabled storage warning lines are hidden in the terminal display. +Changes to guest configurations during the scan require a rerun. Scan when migration, replication, and disk operations are idle because the inventory is not atomic. -CSV export includes coverage warnings and refuses to overwrite an existing file. +CSV export contains disk rows only, omits coverage warnings, and refuses to +overwrite an existing file. Exit status is 0 for a completed scan, 2 for incomplete coverage or changed configurations, and 1 for a fatal scan/export error. diff --git a/defaults.inc b/defaults.inc index 3f33875..e97c70e 100755 --- a/defaults.inc +++ b/defaults.inc @@ -3,7 +3,7 @@ action="${1:-}" FOLDER='/opt/idssys/ta-proxmenu' -VERS='2026.9.22-8' +VERS='2026.9.22-10' noupdate=' ' diff --git a/inc/orphan-disks.py b/inc/orphan-disks.py index 98319a8..d92e8b9 100755 --- a/inc/orphan-disks.py +++ b/inc/orphan-disks.py @@ -182,6 +182,8 @@ def render(rows, warnings, coverage): for entry in coverage: print(' ' + entry) for warning in warnings: + if warning.endswith(': inactive or disabled; not scanned'): + continue print('WARNING: ' + warning) group = None for row in sorted(rows, key=lambda r: (r['node'], r['storage'], r['volume'])): @@ -195,16 +197,13 @@ def render(rows, warnings, coverage): print('No unreferenced or unused volumes found in the successfully scanned storage.') -def export_csv(path, rows, warnings): +def export_csv(path, rows): # Exclusive creation avoids overwriting existing files, including symlink targets. with open(path, 'x', newline='') as handle: - writer = csv.DictWriter(handle, fieldnames=FIELDS + ['disk_size', 'coverage_warnings']) + writer = csv.DictWriter(handle, fieldnames=FIELDS + ['disk_size']) writer.writeheader() for row in rows: - writer.writerow(dict(row, disk_size=format_size(row['size_bytes'], row.get('size_is_approximate', False)), - coverage_warnings='; '.join(warnings))) - if warnings and not rows: - writer.writerow({'status': 'Incomplete scan', 'coverage_warnings': '; '.join(warnings)}) + writer.writerow(dict(row, disk_size=format_size(row['size_bytes'], row.get('size_is_approximate', False)))) def main(): @@ -220,7 +219,7 @@ def main(): if args.interactive and sys.stdin.isatty() and not destination: destination = input('\nCSV export path (Enter to skip): ').strip() if destination: - export_csv(destination, rows, warnings) + export_csv(destination, rows) print('CSV saved: ' + destination) return 2 if warnings else 0 except (OSError, RuntimeError, ValueError, subprocess.TimeoutExpired) as exc: diff --git a/tests/test-orphan-disks.py b/tests/test-orphan-disks.py index b11b9a3..47c80bf 100644 --- a/tests/test-orphan-disks.py +++ b/tests/test-orphan-disks.py @@ -82,12 +82,13 @@ class ReportTests(unittest.TestCase): self.assertEqual(len(coverage), 3) self.assertNotIn('/nodes/b/storage/shared/content', calls) - def test_csv_preserves_warnings_and_refuses_overwrite(self): + def test_csv_omits_warnings_and_refuses_overwrite(self): path = self.root / 'report.csv' - disks.export_csv(path, [], ['node offline']) - self.assertIn('node offline', path.read_text()) + disks.export_csv(path, []) + self.assertNotIn('coverage_warnings', path.read_text()) + self.assertEqual(len(path.read_text().splitlines()), 1) with self.assertRaises(FileExistsError): - disks.export_csv(path, [], []) + disks.export_csv(path, []) def test_disk_sizes_in_display_and_csv(self): for index, (size, expected) in enumerate([ @@ -103,7 +104,7 @@ class ReportTests(unittest.TestCase): disks.render([row], [], []) self.assertIn('Disk size: ' + expected, output.getvalue()) path = self.root / f'sizes-{index}.csv' - disks.export_csv(path, [row], []) + disks.export_csv(path, [row]) with path.open(newline='') as handle: exported = next(csv.DictReader(handle)) self.assertEqual(exported['disk_size'], expected) @@ -135,7 +136,7 @@ class ReportTests(unittest.TestCase): disks.render(rows, warnings, coverage) self.assertIn('Disk size: ~25.91 GiB', output.getvalue()) path = self.root / 'approximate.csv' - disks.export_csv(path, rows, warnings) + disks.export_csv(path, rows) with path.open(newline='') as handle: exported = list(csv.DictReader(handle)) self.assertEqual([row['disk_size'] for row in exported],