Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,40 @@ consistent — you edit the plan, everything else follows from it.
physical racks sharing the middle rack's leaf pair and management leaf. Every physical rack
gets its own number by default (a group takes three), and names stay editable.
- **Rack formats** — switch each rack or rack group between 19-inch EIA (U) and 21-inch
ORV3 (OU) under Advanced, consistently across all rack types. Partition defaults seed new racks and set the central rack format. Gigabyte
ORV3 (OU) under Advanced, consistently across all rack types. Selecting an ORV3 node automatically
changes its compute rack or rack group to ORV3. If that rack already has 19-inch servers selected,
a confirmation warns about the format change and additional mounting adapters in the BOM;
cancelling keeps the plan unchanged. Partition defaults seed new racks and set the central rack format. Gigabyte
TO25-S12 and TO25-Z12 sleds use separately counted TO25-BT0 two-node trays, with one BMC
link per sled. ORV3 racks also accept 19-inch equipment: the BOM estimates one mounting
adapter kit for 1U chassis/devices and two for 2U and taller. Counts use
physical chassis, including partially filled ones, rather than nodes. Choose shelves or
rails to match each device's height, depth and load. Switching keeps existing selections;
21-inch nodes in 19-inch racks remain incompatible. Existing saved plans default to 19-inch racks.
ORV3 power shelves and bus bars are planned separately; enter usable rack height after
reserving space for them and any extra space required by the selected mounting hardware. TO25 power values are
The BOM includes one GIGABYTE DOA3-ST0 frame with its integrated 48V bus bar per physical
ORV3 rack, including all three members of a rack group, central racks and separate on-prem
control-plane racks. Delta shelves have six slots for 5.5 kW PSU modules and include a power
management controller. Each feed gets `ceil(estimated equipment watts / 5500)` PSU modules
for N+N redundancy, with `ceil(modules per feed / 6)` shelves per feed. The BOM lists shelves,
fitted PSU modules, blanking panels for empty slots and one complete AC input cable set per shelf
separately. For example, a 3 kW load gets two shelves with one 5.5 kW PSU each and ten blanks.
Diagrams show fitted capacity per shelf; the rack power budget remains a separate limit. The load uses
the rack elevation's server, GPU and switch estimates, including partial trays and each rack
group's actual equipment distribution. Empty racks get a frame but no power shelves. Changing
the rack power budget only changes validation, not shelf quantities. Shared rack
infrastructure belongs to the Production BOM scope. Prices for the previous fully populated
assembly are retained but do not apply to the new shelf and PSU lines. Supplier-specific component and cable
ordering codes remain blank pending confirmation. Battery backup is optional and excluded.
Hardware references: [GIGABYTE rack specifications](https://www.gigabyte.com/Enterprise/Data-Center/DOA3-ST0)
and [Delta power solutions](https://www.deltaww.com/en-US/products/power-solutions).
Rack diagrams show each power shelf as a 1OU slot labeled by feed A or B. The bus bar
remains included in the BOM but is hidden in the diagrams. Power shelves sit above switches
and servers as a schematic default. Used height
includes power shelves, so enter mounting height without subtracting shelf space manually.
The reference frame provides 43OU including shelves; validation flags greater mounting heights.
Allow separately for any extra space required by the selected mounting hardware.
Confirm bus bar capacity, shelf configuration, site feeds and AC distribution for 19-inch
devices with the supplier. TO25 power values are
planning estimates. SP5 CPU/RAM parts for the Z12 and TO25 E1.S/boot-drive options are not
yet modeled; the planner flags those omissions instead of adding incompatible default parts.
- **CPU sockets and RAM** — modeled dual-socket servers offer a count selector for one or two identical CPUs,
Expand Down
85 changes: 74 additions & 11 deletions src/derive/bom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import {
import { configBuckets, positionListLabel } from '../model/nodeConfig'
import { resolveNodeCompute } from '../model/sizes'
import { controlPlaneLeafCount, hasOwnRack, inCentralRack } from './controlPlane'
import { orv3Infrastructure } from './orv3'
import { deriveRackLayout, type RackElevation } from './rackLayout'
import { chassisCount } from './servers'

export { chassisCount } from './servers'

// The BOM is always derived from the Plan, never stored. Every quantity rule
// lives here and gets a unit test in bom.test.ts. Every rule also records a
Expand Down Expand Up @@ -55,6 +60,13 @@ import { controlPlaneLeafCount, hasOwnRack, inCentralRack } from './controlPlane
// `nos` and the switch's `licenseClass` (catalog `nosLicenseId`).
// - Mounting: height-based adapter estimates per 19-inch chassis/device in
// an ORv3 rack. Adapters inherit the mounted device's network and location.
// - ORv3 infrastructure: one frame with bus bar per physical rack, populated
// power shelves, fitted PSU modules, slot blanks and input cable sets. N+N
// PSU modules on independent feeds cover the rack's
// estimated equipment load on either feed, using the same physical rack
// distribution and power estimates as the elevations. Empty racks need no
// shelves. Shared infrastructure is assigned
// to production so the two network scopes remain additive.
// - Spares: a fixed number (Plan.sparesPerLine) of spares per transceiver
// and cable line, added to the plan-wide BOM only (not per partition).

Expand Down Expand Up @@ -206,12 +218,6 @@ class BomBuilder {
}
}

/** Server chassis needed for a group: nodes divided by nodes-per-chassis. */
export function chassisCount(group: ServerGroup): number {
const nodesPerChassis = catalog[group.modelId]?.nodesPerChassis ?? 1
return Math.ceil(group.count / nodesPerChassis)
}

/** BMC ports: per chassis by default, per sled on ORv3 platforms. */
export function rackBmcPorts(rack: Rack): number {
return rack.servers.reduce(
Expand Down Expand Up @@ -428,6 +434,50 @@ function addMgmtLinks(bom: BomBuilder, partition: Partition, central: BomBuilder
/** Section the central-rack rules are reported under. */
const CENTRAL_RACK = 'Central rack'

function addOrv3Infrastructure(bom: BomBuilder, rack: RackElevation): void {
const infrastructure = orv3Infrastructure(rack)
if (!infrastructure) return
const {
frameId,
shelfId,
inputCableSetId,
shelvesPerFeed,
shelfCount,
psuId,
blankId,
psuSlots,
psuCapacityWatts,
psusPerFeed,
psuCount,
blankCount,
} = infrastructure
// Keep rack-group contributions together while identifying the physical
// member whose equipment determines this quantity.
const perRack = bom.on('production').at(rack.group?.name ?? rack.name, rack.widthInches)
perRack.add(frameId, 1, `${rack.name}: 1 physical ORV3 rack × 1 frame with bus bar`)
const shelves = shelfCount
perRack.add(
shelfId,
shelves,
`${rack.name}: ${shelvesPerFeed} shelves per feed × 2 feeds (N+N, ${psusPerFeed} PSUs per feed / ${psuSlots} slots per shelf, rounded up; controller included, PSUs ordered separately)`,
)
perRack.add(
psuId,
psuCount,
`${rack.name}: ${psusPerFeed} PSUs per feed × 2 feeds (N+N, ceil(${rack.powerWatts} W estimated equipment load / ${psuCapacityWatts} W per PSU))`,
)
perRack.add(
blankId,
blankCount,
`${rack.name}: ${shelves} shelves × ${psuSlots} PSU slots minus ${psuCount} fitted PSUs`,
)
perRack.add(
inputCableSetId,
shelves,
`${rack.name}: ${shelves} power shelves × 1 complete AC input cable set`,
)
}

/** The on-prem control-plane cluster: its nodes, their uplinks and, for a
* control-plane rack of its own, the leaf pair and mgmt leaf that serve
* them. A KaaS control plane orders nothing. Emitted inside the host
Expand Down Expand Up @@ -498,8 +548,14 @@ function addControlPlane(
}
}

function addPartition(bom: BomBuilder, plan: Plan, partition: Partition): void {
function addPartition(
bom: BomBuilder,
plan: Plan,
partition: Partition,
racks: RackElevation[],
): void {
const { fabric } = partition
for (const rack of racks) addOrv3Infrastructure(bom, rack)
// Network and location are tagged once here, so every rule below inherits
// the network it belongs to and the section it is reported under.
const prodCentral = bom.on('production').at(CENTRAL_RACK, partition.rackDefaults.widthInches)
Expand Down Expand Up @@ -604,8 +660,14 @@ function spareLines(lines: BomLine[], perLine: number): BomLine[] {
export function deriveBom(plan: Plan, scope: BomScope = 'all'): BomLine[] {
const multi = plan.partitions.length > 1
const bom = new BomBuilder(scope)
for (const partition of plan.partitions) {
addPartition(multi ? bom.inPartition(partition.name) : bom, plan, partition)
const layouts = deriveRackLayout(plan)
for (const [index, partition] of plan.partitions.entries()) {
addPartition(
multi ? bom.inPartition(partition.name) : bom,
plan,
partition,
layouts[index].racks,
)
}
const lines = bom.build()
return [...lines, ...spareLines(lines, plan.sparesPerLine)]
Expand All @@ -616,9 +678,10 @@ export function deriveBomByPartition(
plan: Plan,
scope: BomScope = 'all',
): { partition: Partition; lines: BomLine[] }[] {
return plan.partitions.map((partition) => {
const layouts = deriveRackLayout(plan)
return plan.partitions.map((partition, index) => {
const bom = new BomBuilder(scope)
addPartition(bom, plan, partition)
addPartition(bom, plan, partition, layouts[index].racks)
return { partition, lines: bom.build() }
})
}
Loading
Loading