|
| 1 | +"""Cleanup tasks. |
| 2 | +
|
| 3 | +This module is intended for removing all VMs and all VDIs on local storage |
| 4 | +from existing remote targets. |
| 5 | +""" |
| 6 | +from __future__ import annotations |
| 7 | + |
| 8 | +from concurrent.futures import ThreadPoolExecutor |
| 9 | + |
| 10 | +from lib.common import safe_split, wait_for_not |
| 11 | +from lib.pool import NotAMasterHostError, Pool |
| 12 | +from lib.sr import SR |
| 13 | +from lib.tools.inventory import Inventory |
| 14 | +from lib.vdi import VDI |
| 15 | +from lib.vm import VM |
| 16 | + |
| 17 | +from .. import logger |
| 18 | + |
| 19 | +def clean_pools(inventory: Inventory, dry_run: bool = False) -> None: |
| 20 | + """Remove all VMs and all orphan VDIs on local storage from pool(s). |
| 21 | +
|
| 22 | + .. note:: |
| 23 | +
|
| 24 | + Every non-master hosts in inventory will be ignored |
| 25 | +
|
| 26 | + *For each pool's master host declared in inventory, destroy all its VMs |
| 27 | + (with all their VDIs, regardless of their location), then destroy all the |
| 28 | + orphan VDIs left on the local (non-shared) SRs.* |
| 29 | +
|
| 30 | + :param Inventory inventory: |
| 31 | + Each host (key) holds its own config data (values, eg: `enablerepos`). |
| 32 | + :param bool dry_run: |
| 33 | + When True, only log what would be removed without actually deleting. |
| 34 | + """ |
| 35 | + inventory_hosts = inventory["hosts"] |
| 36 | + pools: list[Pool] = [] |
| 37 | + for host in inventory_hosts: |
| 38 | + try: |
| 39 | + pools.append(Pool(host)) |
| 40 | + except NotAMasterHostError: |
| 41 | + logger.warning(f"[{host}] Skipping: not a master host") |
| 42 | + |
| 43 | + with ThreadPoolExecutor() as executor: |
| 44 | + futures = {executor.submit(clean_pool, p, dry_run): p for p in pools} |
| 45 | + for future in futures: |
| 46 | + pool = futures[future] |
| 47 | + try: |
| 48 | + future.result() |
| 49 | + except Exception as exc: |
| 50 | + logger.error(f"Cleaning pool has failed! The master {pool.master} cannot be cleaned.") |
| 51 | + raise exc |
| 52 | + |
| 53 | +def clean_pool(pool: Pool, dry_run: bool) -> None: |
| 54 | + """Remove all VMs and all orphan VDIs on local SRs from a single pool.""" |
| 55 | + master = pool.master |
| 56 | + log_prefix = 'Would remove' if dry_run else 'Removing' |
| 57 | + |
| 58 | + vm_uuids = safe_split(master.xe( |
| 59 | + 'vm-list', |
| 60 | + {'is-control-domain': False, 'is-a-template': False}, |
| 61 | + minimal=True, |
| 62 | + )) |
| 63 | + for vm_uuid in vm_uuids: |
| 64 | + vm = VM(vm_uuid, master) |
| 65 | + logger.info(f"[{master}] {log_prefix} VM {vm.uuid} ({vm.name()})") |
| 66 | + if not dry_run: |
| 67 | + vm.destroy(verify=True) |
| 68 | + |
| 69 | + sr_uuids = local_sr_uuids(pool) |
| 70 | + for sr_uuid in sr_uuids: |
| 71 | + for vdi_uuid in SR(sr_uuid, pool).vdi_uuids(managed=True): |
| 72 | + vdi = VDI(vdi_uuid, sr=SR(sr_uuid, pool)) |
| 73 | + logger.info(f"[{master}] {log_prefix} orphan VDI {vdi.uuid} from local SR {sr_uuid}") |
| 74 | + if not dry_run: |
| 75 | + vdi.destroy() |
| 76 | + |
| 77 | + if not dry_run: |
| 78 | + for sr_uuid in sr_uuids: |
| 79 | + wait_for_not( |
| 80 | + lambda: len(SR(sr_uuid, pool).vdi_uuids(managed=True)) > 0, |
| 81 | + f"Wait for local SR {sr_uuid} to be empty", |
| 82 | + ) |
| 83 | + |
| 84 | +def local_sr_uuids(pool: Pool) -> list[str]: |
| 85 | + """Return the UUIDs of the pool's local (non-shared, user) SRs.""" |
| 86 | + uuids = safe_split(pool.master.xe('sr-list', {'content-type': 'user'}, minimal=True)) |
| 87 | + return [uuid for uuid in uuids if not SR(uuid, pool).is_shared()] |
0 commit comments