orphan disk search update
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
action="${1:-}"
|
||||
FOLDER='/opt/idssys/ta-proxmenu'
|
||||
VERS='2026.9.22-8'
|
||||
VERS='2026.9.22-10'
|
||||
|
||||
noupdate=' '
|
||||
|
||||
|
||||
+6
-7
@@ -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:
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user