Lab 01 — Rack Data Model & Inventory Engine

Phase: 01 — Rack-Scale Foundations | Difficulty: ⭐⭐☆☆☆ | Time: 3–4 hours Language: Python (stdlib only) | Hardware: none

Concept primer: ../WARMUP.md Ch. 7 (failure domains) & Ch. 10 (what to model), ../HITCHHIKERS-GUIDE.md §1, §6.

Run

python3 solution.py     # builds a rack, validates it, prints blast radius, self-tests

0. The mission

Build the data model that every other phase plugs into. A rack manager is, at its core, a system that keeps an accurate model of physical reality and acts on it. If the model is wrong or thin, every feature above it leaks. You will build a typed object graph of a realistic AI rack, validate it against invariants, and answer the question that begins every incident: "if this fails, what goes with it?"

Two questions you must be able to answer at the end:

  1. Why is inventory a graph (powers/cools/connects edges), not a flat table?
  2. Why must identity (serial/asset tag) be separate from location (rack + U)?

1. What the code models

Concept (WARMUP)In the code
Durable identity ≠ location (Ch. 10)Identity holds asset_tag/serial/model; u_position lives on Node
Containment hierarchy (Ch. 10)RackNodeComponent; plus rack-level PDU/CDU/Switch
Relationship graph (Ch. 7)edges: (relation, source, target) for powers/cools/connects/manages
State (Ch. 10)Health, PowerState, Lifecycle enums
Invariants (Ch. 10)validate() — power, cooling, U-collision, mgmt-reachable, dangling edges
Blast radius (Ch. 7)find_blast_radius() — transitive closure over failure-propagating edges
Redfish shape (Ch. 8, Phase 03)to_redfish_like() — a Chassis/Systems/Power/Thermal tree

2. What the run shows

=== Rack rack-ai-01 (8 nodes) ===
  total power draw : 14.3 kW
  total heat load  : 14.3 kW
  ...
  validation       : PASS (all invariants hold)

--- Blast radius examples ---
  if PDU-A fails -> 4 node(s) affected   ← N+1 balance: PDU-A feeds the even nodes
  if CDU-1 fails -> 8 node(s) affected   ← single CDU = whole-rack cooling domain
  if TOR-1 fails -> 8 node(s) affected   ← single ToR = whole-rack data domain

Read those three blast-radius lines carefully — they are the failure-domain lesson (WARMUP Ch. 7). A PDU failure is half the rack (because power is balanced N+1 across two feeds); the CDU and ToR are single points of failure for the whole rack. That asymmetry is exactly what drives redundancy decisions and workload placement.

3. Extension exercises (do them — this is the lab)

  1. Model the cooling loop properly: today cools is a single edge from the CDU to each node. Add a Manifold/coolant loop object between CDU and nodes so a single manifold failure has a smaller blast radius than the whole CDU. Re-run blast radius and explain the new numbers.
  2. N+1-aware blast radius: find_blast_radius ignores redundancy. Add a find_blast_radius_redundant() that, for power, only counts a node as "down" if it loses all its power feeds. Then give each node a second powers edge to the other PDU and show that a single PDU failure now affects 0 nodes — the point of N+1.
  3. Declared vs discovered: add a discover() that returns a slightly different rack (a missing accelerator, a firmware drift) and a reconcile(declared, discovered) that reports the diff. This is the conceptual seed of the Phase 06 operator loop and Phase 10 RCA.
  4. Identity vs location bug: write a test that moves a node to a new U position but keeps its serial, and assert that queries by serial still resolve while location-based queries see the move. Then introduce the classic bug (update location but reuse a stale asset_tag) and show your validator catches the duplicate.

4. Common pitfalls

  1. Flattening the graph — if you store relationships as columns on a node row, you can't traverse them; blast radius and reconciliation become impossible. Keep edges.
  2. Putting u_position in Identity — then a part "changes identity" when it moves. Location is state of the slot, identity belongs to the part.
  3. Forgetting rack-level objects — PDUs, CDU, switches belong to no single node; model them at the rack level or you can't compute shared-resource blast radius.
  4. Validating only the happy path — the value of validate() is catching the broken configs (no power feed, U collision, dangling edge). Test those explicitly.

5. What this lab proves about you

You can design the schema a rack-management product is built on — the part that looks "boring" in an interview but is where real systems live or die. When asked "design the data model for a rack inventory system" (a near-certain system-design prompt), you don't hand-wave a table; you draw a graph with identity/location separation, invariants, and a blast-radius traversal, and you can say which objects are single points of failure and why. The Phase 13 capstone reuses this model directly.