feat: add control board replacement workflow

This commit is contained in:
lussierj committed 2026-08-02 14:34:16 +00:00
1 parent 62c91a2c3e
commit bcf1b4e3cf
16 files changed
+2122 -279

No files matched your search

+257 -2
View File
@@ -6,13 +6,15 @@ from typing import Any
from uuid import UUID
import psycopg
from psycopg.errors import UniqueViolation
from psycopg.errors import CheckViolation, LockNotAvailable, UniqueViolation
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb
from app.config import settings
from app.repairs.exceptions import (
ActiveRepairConflict,
ControlBoardReplacementConflict,
MacOwnershipConflict,
RepairPersistenceError,
)
@@ -64,6 +66,26 @@ class RepairRepository:
try:
yield transaction_repository
connection.commit()
except UniqueViolation as exc:
connection.rollback()
if exc.diag.constraint_name in {
"uq_miner_mac_ownerships_owner",
"uq_miner_network_identities_normalized_mac",
"uq_miners_current_normalized_mac",
"uq_control_board_installations_current_miner",
"uq_control_board_installations_current_mac",
}:
raise ControlBoardReplacementConflict(
"Concurrent control-board replacement or MAC ownership conflict"
) from exc
raise
except CheckViolation as exc:
connection.rollback()
if exc.diag.constraint_name == "ck_current_control_board_projection":
raise ControlBoardReplacementConflict(
"Control-board projection would contradict authoritative history"
) from exc
raise
except Exception:
connection.rollback()
raise
@@ -97,7 +119,8 @@ class RepairRepository:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
SELECT id, lifecycle_status
SELECT id, asset_tag, lifecycle_status, current_mac, control_board_sn,
firmware_version
FROM miners
WHERE id = %s
FOR UPDATE
@@ -107,6 +130,27 @@ class RepairRepository:
row = cur.fetchone()
return dict(row) if row else None
def lock_miner_for_control_board_replacement(
self, miner_id: UUID
) -> dict | None:
with self._connection_scope() as connection:
with connection.cursor(row_factory=dict_row) as cur:
try:
cur.execute(
"""
SELECT id, asset_tag, lifecycle_status, current_mac,
control_board_sn, firmware_version
FROM miners WHERE id = %s FOR UPDATE NOWAIT
""",
(miner_id,),
)
except LockNotAvailable as exc:
raise ControlBoardReplacementConflict(
"A control-board replacement is already in progress"
) from exc
row = cur.fetchone()
return dict(row) if row else None
def get_active_repair(self, miner_id: UUID) -> dict | None:
with self._connection_scope() as connection:
with connection.cursor(row_factory=dict_row) as cur:
@@ -342,6 +386,217 @@ class RepairRepository:
row = cur.fetchone()
return dict(row) if row else None
def lock_active_repair_for_miner(self, miner_id: UUID) -> dict | None:
with self._connection_scope() as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
SELECT id, miner_id, status, reason, opened_at, opened_by
FROM repairs
WHERE miner_id = %s AND status = ANY(%s)
ORDER BY opened_at, id
LIMIT 1
FOR UPDATE
""",
(miner_id, list(ACTIVE_REPAIR_STATUSES)),
)
row = cur.fetchone()
return dict(row) if row else None
def lock_current_control_board(self, miner_id: UUID) -> dict | None:
with self._connection_scope() as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
SELECT * FROM miner_control_board_installations
WHERE miner_id = %s AND removed_at IS NULL
FOR UPDATE
""",
(miner_id,),
)
row = cur.fetchone()
return dict(row) if row else None
def create_legacy_control_board(self, miner: Mapping[str, Any]) -> dict:
with self._connection_scope(write=True) as connection:
with connection.cursor(row_factory=dict_row) as cur:
mac_address = miner.get("current_mac")
normalized_mac = (
"".join(ch for ch in str(mac_address).lower() if ch in "0123456789abcdef")
if mac_address else None
)
if normalized_mac and len(normalized_mac) == 12:
mac_address = ":".join(
normalized_mac[index:index + 2].upper()
for index in range(0, 12, 2)
)
elif normalized_mac:
normalized_mac = None
mac_address = None
cur.execute(
"""
INSERT INTO miner_control_board_installations (
miner_id, record_origin, mac_address, normalized_mac,
board_serial, firmware_version
)
VALUES (%s, 'legacy_projection', %s, %s, %s, %s)
RETURNING *
""",
(miner["id"], mac_address, normalized_mac,
miner.get("control_board_sn"), miner.get("firmware_version")),
)
return dict(cur.fetchone())
def claim_mac_ownership(
self, normalized_mac: str, miner_id: UUID, actor_user_id: UUID
) -> None:
with self._connection_scope(write=True) as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))",
(normalized_mac,),
)
cur.execute(
"SELECT miner_id FROM miner_mac_ownerships "
"WHERE normalized_mac = %s FOR UPDATE",
(normalized_mac,),
)
row = cur.fetchone()
if row and row["miner_id"] != miner_id:
raise MacOwnershipConflict(normalized_mac)
if row is None:
cur.execute(
"""
INSERT INTO miner_mac_ownerships (
normalized_mac, miner_id, claimed_at, claimed_by
) VALUES (%s, %s, now(), %s)
""",
(normalized_mac, miner_id, actor_user_id),
)
def close_control_board(
self, installation_id: UUID, *, repair_id: UUID, actor_user_id: UUID,
disposition: str
) -> dict:
with self._connection_scope(write=True) as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
UPDATE miner_control_board_installations
SET removed_at = now(), removed_by = %s,
removed_repair_id = %s, removed_disposition = %s
WHERE id = %s AND removed_at IS NULL
RETURNING *
""",
(actor_user_id, repair_id, disposition, installation_id),
)
row = cur.fetchone()
if row is None:
raise RepairPersistenceError(
"Current control-board installation was not closed"
)
return dict(row)
def create_control_board_installation(
self, *, miner_id: UUID, repair_id: UUID, mac_address: str,
normalized_mac: str, board_serial: str | None, board_model: str | None,
board_revision: str | None, firmware_version: str | None,
replacement_reason: str, notes: str | None, actor_user_id: UUID
) -> dict:
with self._connection_scope(write=True) as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
INSERT INTO miner_control_board_installations (
miner_id, record_origin, installed_repair_id, mac_address,
normalized_mac, board_serial, board_model, board_revision,
firmware_version, replacement_reason, installed_at,
installed_by, notes
) VALUES (
%s, 'replacement', %s, %s, %s, %s, %s, %s, %s, %s,
now(), %s, %s
) RETURNING *
""",
(miner_id, repair_id, mac_address, normalized_mac, board_serial,
board_model, board_revision, firmware_version,
replacement_reason, actor_user_id, notes),
)
return dict(cur.fetchone())
def set_current_control_board(
self, miner_id: UUID, *, mac_address: str, board_serial: str | None
) -> None:
with self._connection_scope(write=True) as connection:
with connection.cursor() as cur:
cur.execute(
"""
UPDATE miners SET current_mac = %s, control_board_sn = %s,
updated_at = now() WHERE id = %s
""",
(mac_address, board_serial, miner_id),
)
if cur.rowcount != 1:
raise RepairPersistenceError(
"Current miner control-board projection was not updated"
)
def ensure_network_identity_history(
self, miner_id: UUID, *, mac_address: str, normalized_mac: str
) -> None:
with self._connection_scope(write=True) as connection:
with connection.cursor() as cur:
cur.execute(
"""
INSERT INTO miner_network_identities (
miner_id, mac_address, source, is_current,
first_seen_at, last_seen_at, associated_at
)
SELECT %s, %s, 'legacy_projection', false, NULL, NULL, now()
WHERE NOT EXISTS (
SELECT 1 FROM miner_network_identities
WHERE miner_id = %s
AND regexp_replace(lower(mac_address), '[^0-9a-f]', '', 'g') = %s
)
""",
(miner_id, mac_address, miner_id, normalized_mac),
)
def set_current_network_identity(
self, miner_id: UUID, *, mac_address: str, normalized_mac: str
) -> dict:
with self._connection_scope(write=True) as connection:
with connection.cursor(row_factory=dict_row) as cur:
cur.execute(
"UPDATE miner_network_identities SET is_current = false, "
"updated_at = now() WHERE miner_id = %s AND is_current",
(miner_id,),
)
cur.execute(
"""
UPDATE miner_network_identities
SET is_current = true, associated_at = now(), updated_at = now()
WHERE miner_id = %s
AND regexp_replace(lower(mac_address), '[^0-9a-f]', '', 'g') = %s
RETURNING *
""",
(miner_id, normalized_mac),
)
row = cur.fetchone()
if row is None:
cur.execute(
"""
INSERT INTO miner_network_identities (
miner_id, mac_address, source, is_current,
first_seen_at, last_seen_at, associated_at
) VALUES (%s, %s, 'control_board_replacement', true,
NULL, NULL, now())
RETURNING *
""",
(miner_id, mac_address),
)
row = cur.fetchone()
return dict(row)
def get_repair_detail(self, repair_id: UUID) -> dict | None:
with self._connection_scope() as connection:
with connection.cursor(row_factory=dict_row) as cur: