Add host-poller: chassis IPMI/iDRAC + CPU + on-metal drive temps
New third producer container (host-poller) owning the server chassis subsystem, separate from the SAS-shelf poller: - services/host_sensors.py: in-band IPMI (ipmitool sdr) for Inlet/Exhaust/etc. + CPU package temps from coretemp hwmon - host_poller.py: own single-instance lock + heartbeat (host_poll keys), restart-storm guard, paced; writes jbod:host_sensors + jbod:host_drives - move host/on-metal drive collection off the SAS poller to host-poller (reads jbod:zfs_map for annotation) - store: generalized lock/meta with a key param; host_sensors + host-poll keys - mqtt_publisher: publish env/CPU/host-drive temps as entities on the hub device (JBOD Monitor) - Dockerfile host-poller target (ipmitool+smartmontools); compose.infra adds host-poller; build.sh gains host-poller/all
This commit is contained in:
@@ -23,9 +23,12 @@ K_SES = "jbod:ses:{}"
|
||||
K_ZFS = "jbod:zfs_map"
|
||||
K_INVENTORY = "jbod:inventory"
|
||||
K_HOST_DRIVES = "jbod:host_drives"
|
||||
K_HOST_SENSORS = "jbod:host_sensors"
|
||||
K_META = "jbod:poll:meta"
|
||||
K_HOST_META = "jbod:host_poll:meta"
|
||||
K_LED_QUEUE = "jbod:led:queue"
|
||||
K_LOCK = "jbod:poll:lock"
|
||||
K_HOST_LOCK = "jbod:host_poll:lock"
|
||||
|
||||
|
||||
# ── writes (poller) ─────────────────────────────────────────────────────
|
||||
@@ -49,9 +52,13 @@ async def set_host_drives(host_drives: list) -> None:
|
||||
await cache_set(K_HOST_DRIVES, host_drives, DATA_TTL)
|
||||
|
||||
|
||||
async def set_meta(meta: dict) -> None:
|
||||
async def set_host_sensors(sensors: dict) -> None:
|
||||
await cache_set(K_HOST_SENSORS, sensors, DATA_TTL)
|
||||
|
||||
|
||||
async def set_meta(meta: dict, key: str = K_META) -> None:
|
||||
# Meta has no TTL — it's the heartbeat; staleness is judged from its ts.
|
||||
await cache_set(K_META, meta, 0)
|
||||
await cache_set(key, meta, 0)
|
||||
|
||||
|
||||
# ── reads (consumers) ───────────────────────────────────────────────────
|
||||
@@ -75,8 +82,12 @@ async def get_host_drives() -> list:
|
||||
return await cache_get(K_HOST_DRIVES) or []
|
||||
|
||||
|
||||
async def get_meta() -> dict | None:
|
||||
return await cache_get(K_META)
|
||||
async def get_host_sensors() -> dict | None:
|
||||
return await cache_get(K_HOST_SENSORS)
|
||||
|
||||
|
||||
async def get_meta(key: str = K_META) -> dict | None:
|
||||
return await cache_get(key)
|
||||
|
||||
|
||||
# ── locate-LED command queue ────────────────────────────────────────────
|
||||
@@ -126,20 +137,20 @@ _REFRESH_LUA = (
|
||||
)
|
||||
|
||||
|
||||
async def acquire_lock(token: str, ttl: int = 30) -> bool:
|
||||
"""Atomically claim the poller lock (or re-own it). True if held."""
|
||||
async def acquire_lock(token: str, ttl: int = 30, key: str = K_LOCK) -> bool:
|
||||
"""Atomically claim a single-instance lock (or re-own it). True if held."""
|
||||
client = get_client()
|
||||
if client is None:
|
||||
return True # no Redis → no coordination possible; run anyway
|
||||
return bool(await client.eval(_ACQUIRE_LUA, 1, K_LOCK, token, ttl))
|
||||
return bool(await client.eval(_ACQUIRE_LUA, 1, key, token, ttl))
|
||||
|
||||
|
||||
async def refresh_lock(token: str, ttl: int = 30) -> bool:
|
||||
async def refresh_lock(token: str, ttl: int = 30, key: str = K_LOCK) -> bool:
|
||||
"""Atomically extend the lock iff we still own it."""
|
||||
client = get_client()
|
||||
if client is None:
|
||||
return True
|
||||
return bool(await client.eval(_REFRESH_LUA, 1, K_LOCK, token, ttl))
|
||||
return bool(await client.eval(_REFRESH_LUA, 1, key, token, ttl))
|
||||
|
||||
|
||||
def now() -> float:
|
||||
|
||||
Reference in New Issue
Block a user