Resolve multipath dm devices to underlying sd devices for ZFS pool map
This commit is contained in:
@@ -2,6 +2,7 @@ import asyncio
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,16 +71,71 @@ async def get_zfs_pool_map() -> dict[str, dict]:
|
||||
parts = stripped.split()
|
||||
dev_path = parts[0]
|
||||
try:
|
||||
real = os.path.realpath(dev_path)
|
||||
dev_name = os.path.basename(real)
|
||||
# Strip partition numbers (sda1 -> sda)
|
||||
dev_name = re.sub(r'\d+$', '', dev_name)
|
||||
pool_map[dev_name] = {
|
||||
info = {
|
||||
"pool": current_pool,
|
||||
"vdev": current_vdev or current_pool,
|
||||
}
|
||||
# Resolve symlink and map the device
|
||||
real = os.path.realpath(dev_path)
|
||||
dev_name = os.path.basename(real)
|
||||
|
||||
# Map the resolved device (strip partition suffix)
|
||||
base_dev = _strip_partition(dev_name)
|
||||
pool_map[base_dev] = info
|
||||
|
||||
# For device-mapper (multipath), also map the
|
||||
# underlying slave sd devices.
|
||||
if base_dev.startswith("dm-"):
|
||||
for slave in _resolve_dm_slaves(base_dev):
|
||||
pool_map[slave] = info
|
||||
except Exception:
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
logger.debug("zpool not available")
|
||||
return pool_map
|
||||
|
||||
|
||||
def _strip_partition(dev_name: str) -> str:
|
||||
"""Strip partition suffix from a device name.
|
||||
|
||||
sda1 -> sda, nvme0n1p1 -> nvme0n1, dm-14 stays dm-14 (it's a
|
||||
separate dm device for the partition, resolve via slaves).
|
||||
"""
|
||||
# dm devices: partition dm devices are separate dm-N entries,
|
||||
# resolve via /sys/block/dm-N/slaves to find parent dm device
|
||||
if dev_name.startswith("dm-"):
|
||||
parent = _get_dm_parent(dev_name)
|
||||
return parent if parent else dev_name
|
||||
|
||||
# NVMe: nvme0n1p1 -> nvme0n1
|
||||
m = re.match(r'^(nvme\d+n\d+)p\d+$', dev_name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
# Standard: sda1 -> sda
|
||||
return re.sub(r'\d+$', '', dev_name)
|
||||
|
||||
|
||||
def _get_dm_parent(dm_name: str) -> str | None:
|
||||
"""For a dm partition device, find the parent dm device via slaves."""
|
||||
slave_dir = Path(f"/sys/block/{dm_name}/slaves")
|
||||
if slave_dir.is_dir():
|
||||
slaves = list(slave_dir.iterdir())
|
||||
if len(slaves) == 1 and slaves[0].name.startswith("dm-"):
|
||||
return slaves[0].name
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_dm_slaves(dm_name: str) -> list[str]:
|
||||
"""Get underlying sd device names for a device-mapper device."""
|
||||
slaves = []
|
||||
slave_dir = Path(f"/sys/block/{dm_name}/slaves")
|
||||
if slave_dir.is_dir():
|
||||
for s in slave_dir.iterdir():
|
||||
name = s.name
|
||||
if name.startswith("dm-"):
|
||||
# Nested dm — recurse
|
||||
slaves.extend(_resolve_dm_slaves(name))
|
||||
else:
|
||||
slaves.append(name)
|
||||
return slaves
|
||||
|
||||
Reference in New Issue
Block a user