|
1 | | -import json |
2 | 1 | import logging |
3 | 2 | import os |
4 | | -import time |
5 | 3 | from collections.abc import Iterable |
6 | 4 | from pathlib import Path |
7 | | -from typing import Literal, overload |
8 | 5 |
|
9 | 6 | from parted import Device, Disk, DiskException, FileSystem, Geometry, IOException, Partition, PartitionException, freshDisk, getAllDevices, getDevice, newDisk |
10 | 7 |
|
|
19 | 16 | DiskEncryption, |
20 | 17 | FilesystemType, |
21 | 18 | LsblkInfo, |
22 | | - LvmGroupInfo, |
23 | | - LvmPVInfo, |
24 | 19 | LvmVolume, |
25 | 20 | LvmVolumeGroup, |
26 | | - LvmVolumeInfo, |
27 | 21 | ModificationStatus, |
28 | 22 | PartitionFlag, |
29 | 23 | PartitionGUID, |
30 | 24 | PartitionModification, |
31 | 25 | PartitionTable, |
32 | | - SectorSize, |
33 | 26 | Size, |
34 | 27 | SubvolumeModification, |
35 | 28 | Unit, |
@@ -348,123 +341,12 @@ def format_encrypted( |
348 | 341 | info(f'luks2 locking device: {dev_path}') |
349 | 342 | luks_handler.lock() |
350 | 343 |
|
351 | | - def _lvm_info( |
352 | | - self, |
353 | | - cmd: str, |
354 | | - info_type: Literal['lv', 'vg', 'pvseg'], |
355 | | - ) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None: |
356 | | - raw_info = SysCommand(cmd).decode().split('\n') |
357 | | - |
358 | | - # for whatever reason the output sometimes contains |
359 | | - # "File descriptor X leaked leaked on vgs invocation |
360 | | - data = '\n'.join(raw for raw in raw_info if 'File descriptor' not in raw) |
361 | | - |
362 | | - debug(f'LVM info: {data}') |
363 | | - |
364 | | - reports = json.loads(data) |
365 | | - |
366 | | - for report in reports['report']: |
367 | | - if len(report[info_type]) != 1: |
368 | | - raise ValueError('Report does not contain any entry') |
369 | | - |
370 | | - entry = report[info_type][0] |
371 | | - |
372 | | - match info_type: |
373 | | - case 'pvseg': |
374 | | - return LvmPVInfo( |
375 | | - pv_name=Path(entry['pv_name']), |
376 | | - lv_name=entry['lv_name'], |
377 | | - vg_name=entry['vg_name'], |
378 | | - ) |
379 | | - case 'lv': |
380 | | - return LvmVolumeInfo( |
381 | | - lv_name=entry['lv_name'], |
382 | | - vg_name=entry['vg_name'], |
383 | | - lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default()), |
384 | | - ) |
385 | | - case 'vg': |
386 | | - return LvmGroupInfo( |
387 | | - vg_uuid=entry['vg_uuid'], |
388 | | - vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default()), |
389 | | - ) |
390 | | - |
391 | | - return None |
392 | | - |
393 | | - @overload |
394 | | - def _lvm_info_with_retry(self, cmd: str, info_type: Literal['lv']) -> LvmVolumeInfo | None: ... |
395 | | - |
396 | | - @overload |
397 | | - def _lvm_info_with_retry(self, cmd: str, info_type: Literal['vg']) -> LvmGroupInfo | None: ... |
398 | | - |
399 | | - @overload |
400 | | - def _lvm_info_with_retry(self, cmd: str, info_type: Literal['pvseg']) -> LvmPVInfo | None: ... |
401 | | - |
402 | | - def _lvm_info_with_retry( |
403 | | - self, |
404 | | - cmd: str, |
405 | | - info_type: Literal['lv', 'vg', 'pvseg'], |
406 | | - ) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None: |
407 | | - # Retry for up to 5 mins |
408 | | - max_retries = 100 |
409 | | - for attempt in range(max_retries): |
410 | | - try: |
411 | | - return self._lvm_info(cmd, info_type) |
412 | | - except ValueError: |
413 | | - if attempt < max_retries - 1: |
414 | | - debug(f'LVM info query failed (attempt {attempt + 1}/{max_retries}), retrying in 3 seconds...') |
415 | | - time.sleep(3) |
416 | | - |
417 | | - debug(f'LVM info query failed after {max_retries} attempts') |
418 | | - return None |
419 | | - |
420 | | - def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None: |
421 | | - cmd = f'lvs --reportformat json --unit B -S lv_name={lv_name}' |
422 | | - |
423 | | - return self._lvm_info_with_retry(cmd, 'lv') |
424 | | - |
425 | | - def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None: |
426 | | - cmd = f'vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}' |
427 | | - |
428 | | - return self._lvm_info_with_retry(cmd, 'vg') |
429 | | - |
430 | | - def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None: |
431 | | - cmd = f'pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json ' |
432 | | - |
433 | | - return self._lvm_info_with_retry(cmd, 'pvseg') |
434 | | - |
435 | | - def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None: |
436 | | - active_flag = 'y' if activate else 'n' |
437 | | - cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}' |
438 | | - |
439 | | - debug(f'lvchange volume: {cmd}') |
440 | | - SysCommand(cmd) |
441 | | - |
442 | 344 | def lvm_export_vg(self, vg: LvmVolumeGroup) -> None: |
443 | 345 | cmd = f'vgexport {vg.name}' |
444 | 346 |
|
445 | 347 | debug(f'vgexport: {cmd}') |
446 | 348 | SysCommand(cmd) |
447 | 349 |
|
448 | | - def lvm_import_vg(self, vg: LvmVolumeGroup) -> None: |
449 | | - # Check if the VG is actually exported before trying to import it |
450 | | - check_cmd = f'vgs --noheadings -o vg_exported {vg.name}' |
451 | | - |
452 | | - try: |
453 | | - result = SysCommand(check_cmd) |
454 | | - is_exported = result.decode().strip() == 'exported' |
455 | | - except SysCallError: |
456 | | - # VG might not exist yet, skip import |
457 | | - debug(f'Volume group {vg.name} not found, skipping import') |
458 | | - return |
459 | | - |
460 | | - if not is_exported: |
461 | | - debug(f'Volume group {vg.name} is already active (not exported), skipping import') |
462 | | - return |
463 | | - |
464 | | - cmd = f'vgimport {vg.name}' |
465 | | - debug(f'vgimport: {cmd}') |
466 | | - SysCommand(cmd) |
467 | | - |
468 | 350 | def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None: |
469 | 351 | val = amount.format_size(Unit.B, include_unit=False) |
470 | 352 | cmd = f'lvreduce -L -{val}B {vol_path}' |
|
0 commit comments