Add zvol existence check and creation for iSCSI extents
client.py:
- check_iscsi_zvols(): queries pool.dataset.query for VOLUME type,
returns list of missing zvol names
- create_zvol(): creates a single zvol via pool.dataset.create
- create_missing_zvols(): opens a fresh connection and creates a
batch of zvols from a {name: volsize_bytes} dict
summary.py:
- Add zvols_to_check and missing_zvols list fields
- Report shows a WARNING block listing missing zvols when present
migrate.py:
- _migrate_iscsi_extents() populates summary.zvols_to_check with
the dataset name for each DISK-type extent during dry run
cli.py:
- Add _parse_size() to parse human-friendly size strings
(100G, 500GiB, 1T, etc.) to bytes
- run() calls check_iscsi_zvols() during dry run and stores results
in summary.missing_zvols
- Wizard prompts for size and creates missing zvols after the dry
run report, before asking the user to confirm the live run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -306,3 +306,79 @@ async def create_missing_datasets(
|
||||
) as client:
|
||||
for path in paths:
|
||||
await create_dataset(client, path)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# iSCSI zvol utilities
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async def check_iscsi_zvols(
|
||||
client: TrueNASClient,
|
||||
zvol_names: list[str],
|
||||
) -> list[str]:
|
||||
"""
|
||||
Return the subset of *zvol_names* that do not exist on the destination.
|
||||
Names are the dataset path without the leading 'zvol/' prefix
|
||||
(e.g. 'tank/VMWARE001'). Returns [] when the query itself fails.
|
||||
"""
|
||||
if not zvol_names:
|
||||
return []
|
||||
|
||||
unique = sorted(set(zvol_names))
|
||||
log.info("Checking %d zvol(s) against destination datasets …", len(unique))
|
||||
try:
|
||||
datasets = await client.call(
|
||||
"pool.dataset.query", [[["type", "=", "VOLUME"]]]
|
||||
) or []
|
||||
except RuntimeError as exc:
|
||||
log.warning("Could not query zvols (skipping check): %s", exc)
|
||||
return []
|
||||
|
||||
existing = {d["name"] for d in datasets}
|
||||
missing = [n for n in unique if n not in existing]
|
||||
if missing:
|
||||
for n in missing:
|
||||
log.warning(" MISSING zvol: %s", n)
|
||||
else:
|
||||
log.info(" All iSCSI zvols exist on destination.")
|
||||
return missing
|
||||
|
||||
|
||||
async def create_zvol(
|
||||
client: TrueNASClient,
|
||||
name: str,
|
||||
volsize: int,
|
||||
) -> bool:
|
||||
"""
|
||||
Create a ZFS volume (zvol) on the destination.
|
||||
*name* is the dataset path (e.g. 'tank/VMWARE001').
|
||||
*volsize* is the size in bytes.
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
log.info("Creating zvol %r (%d bytes) …", name, volsize)
|
||||
try:
|
||||
await client.call("pool.dataset.create", [{
|
||||
"name": name,
|
||||
"type": "VOLUME",
|
||||
"volsize": volsize,
|
||||
}])
|
||||
log.info(" Created: %s", name)
|
||||
return True
|
||||
except RuntimeError as exc:
|
||||
log.error(" Failed to create zvol %r: %s", name, exc)
|
||||
return False
|
||||
|
||||
|
||||
async def create_missing_zvols(
|
||||
host: str,
|
||||
port: int,
|
||||
api_key: str,
|
||||
zvols: dict[str, int],
|
||||
verify_ssl: bool = False,
|
||||
) -> None:
|
||||
"""Open a fresh connection and create zvols from {name: volsize_bytes}."""
|
||||
async with TrueNASClient(
|
||||
host=host, port=port, api_key=api_key, verify_ssl=verify_ssl,
|
||||
) as client:
|
||||
for name, volsize in zvols.items():
|
||||
await create_zvol(client, name, volsize)
|
||||
|
||||
Reference in New Issue
Block a user