Fix dry-run iSCSI ID map cascades; add pre-migration existence check
During dry run, "would create" iSCSI objects now populate id_map with a source-ID placeholder so downstream objects (targets, target-extents) can remap references without cascading failures. Adds query_existing_iscsi() and clear_iscsi_config() to migrate.py, and _prompt_clear_existing_iscsi() to the wizard: if the destination already has iSCSI config, the user is shown a summary and offered Keep/Remove before the dry run begins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ from .archive import parse_archive, list_archive_and_exit
|
||||
from .client import TrueNASClient, check_dataset_paths, create_missing_datasets, check_iscsi_zvols, create_missing_zvols
|
||||
from .colors import log, _bold, _bold_cyan, _bold_red, _bold_yellow, _cyan, _dim, _green, _yellow
|
||||
from .csv_source import parse_csv_sources
|
||||
from .migrate import migrate_smb_shares, migrate_nfs_shares, migrate_iscsi
|
||||
from .migrate import migrate_smb_shares, migrate_nfs_shares, migrate_iscsi, query_existing_iscsi, clear_iscsi_config
|
||||
from .summary import Summary
|
||||
|
||||
|
||||
@@ -214,6 +214,53 @@ def _prompt_iscsi_portals(iscsi: dict) -> None:
|
||||
print()
|
||||
|
||||
|
||||
def _prompt_clear_existing_iscsi(host: str, port: int, api_key: str) -> None:
|
||||
"""
|
||||
Check whether the destination already has iSCSI configuration.
|
||||
If so, summarise what exists and offer to remove it before migration.
|
||||
"""
|
||||
async def _check():
|
||||
async with TrueNASClient(host=host, port=port, api_key=api_key, verify_ssl=False) as client:
|
||||
return await query_existing_iscsi(client)
|
||||
|
||||
existing = asyncio.run(_check())
|
||||
counts = {k: len(v) for k, v in existing.items()}
|
||||
total = sum(counts.values())
|
||||
if total == 0:
|
||||
return
|
||||
|
||||
print(f"\n {_bold_yellow('WARNING:')} Destination already has iSCSI configuration:")
|
||||
labels = [
|
||||
("extents", "extent(s)"),
|
||||
("initiators", "initiator group(s)"),
|
||||
("portals", "portal(s)"),
|
||||
("targets", "target(s)"),
|
||||
("targetextents", "target-extent association(s)"),
|
||||
]
|
||||
for key, label in labels:
|
||||
n = counts[key]
|
||||
if n:
|
||||
print(f" • {n} {label}")
|
||||
print()
|
||||
print(f" {_dim('Keep existing: new objects will be skipped if conflicts are detected.')}")
|
||||
print(f" {_dim('Remove existing: ALL iSCSI config will be deleted before migration.')}")
|
||||
print()
|
||||
|
||||
raw = _prompt(" [K]eep existing / [R]emove all existing iSCSI config", default="K")
|
||||
if raw.strip().lower().startswith("r"):
|
||||
if _confirm(f" Remove ALL {total} iSCSI object(s) from {host}?"):
|
||||
async def _clear():
|
||||
async with TrueNASClient(host=host, port=port, api_key=api_key, verify_ssl=False) as client:
|
||||
await clear_iscsi_config(client)
|
||||
print()
|
||||
asyncio.run(_clear())
|
||||
print(f" {_bold_cyan('✓')} iSCSI configuration cleared.\n")
|
||||
else:
|
||||
print(f" {_yellow('–')} Removal cancelled — keeping existing config.\n")
|
||||
else:
|
||||
print(f" {_dim('Keeping existing iSCSI configuration.')}\n")
|
||||
|
||||
|
||||
def _select_shares(shares: list[dict], share_type: str) -> list[dict]:
|
||||
"""
|
||||
Display a numbered list of *shares* and return only those the user selects.
|
||||
@@ -377,6 +424,10 @@ def interactive_mode() -> None:
|
||||
if "iscsi" in migrate and archive_data.get("iscsi", {}).get("portals"):
|
||||
_prompt_iscsi_portals(archive_data["iscsi"])
|
||||
|
||||
# ── iSCSI pre-migration check ────────────────────────────────────────
|
||||
if "iscsi" in migrate:
|
||||
_prompt_clear_existing_iscsi(host, port, api_key)
|
||||
|
||||
# ── Select individual shares (common) ──────────────────────────────────────
|
||||
if "smb" in migrate and archive_data["smb_shares"]:
|
||||
archive_data["smb_shares"] = _select_shares(archive_data["smb_shares"], "SMB")
|
||||
|
||||
Reference in New Issue
Block a user