diff --git a/src/derive/rackLayout.test.ts b/src/derive/rackLayout.test.ts index d437c15..fa2685a 100644 --- a/src/derive/rackLayout.test.ts +++ b/src/derive/rackLayout.test.ts @@ -74,6 +74,26 @@ describe('three-rack', () => { expect([left.usedU, mid.usedU, right.usedU]).toEqual([15, 15, 15]) }) + it('spreads a storage group across the racks even when workers fill them unevenly', () => { + const plan = createEmptyPlan() + const rack = plan.partitions[0].racks[0] + rack.kind = 'three-rack' + // 13 worker chassis x 3U pack to 15/15/12U; without the per-group spread + // the emptiest rack would then take two of the three 2U storage systems. + rack.servers[0].count = 13 * 8 + rack.servers.push({ + id: 'storage', + role: 'storage', + modelId: 'server-superserver-tn12', + count: 3, + uplink: '2x25G', + }) + const layout = deriveRackLayout(plan)[0] + for (const physical of layout.racks.slice(1)) { + expect(physical.slots.filter((s) => s.kind === 'storage')).toHaveLength(1) + } + }) + it('flags overflow when the three racks are full', () => { const plan = createEmptyPlan() const rack = plan.partitions[0].racks[0] diff --git a/src/derive/rackLayout.ts b/src/derive/rackLayout.ts index 73f2e51..fdfb55b 100644 --- a/src/derive/rackLayout.ts +++ b/src/derive/rackLayout.ts @@ -63,9 +63,11 @@ interface Item { /** One physical rack of a plan rack: a single rack has one (no position); * a three-rack has left/mid/right, with the switches in the middle and * chassis distributed evenly by used height (each chassis goes to the - * physical rack with the fewest used units; ties favor mid, then left, - * then right). A chassis that fits nowhere lands in the least-used rack, - * where the height check flags it. */ + * physical rack holding the fewest chassis of its group, the least-used + * one among those; ties favor mid, then left, then right). Spreading each + * group first keeps e.g. storage systems across the racks even when the + * worker chassis fill them unevenly. A chassis that fits nowhere lands in + * the chosen rack, where the height check flags it. */ export interface PhysicalRack { position?: RackPosition name: string @@ -109,8 +111,13 @@ export function physicalRacks(rack: Rack, fabric: FabricConfig): PhysicalRack[] const mid = { items: [] as Item[], used: switches.reduce((u, i) => u + i.units, 0) } const left = { items: [] as Item[], used: 0 } const right = { items: [] as Item[], used: 0 } + const chassisOfGroup = (bin: { items: Item[] }, groupId?: string) => + bin.items.filter((item) => item.groupId === groupId).length for (const c of chassis) { - const target = [mid, left, right].reduce((best, r) => (r.used < best.used ? r : best)) + const target = [mid, left, right].reduce((best, r) => { + const spread = chassisOfGroup(r, c.groupId) - chassisOfGroup(best, c.groupId) + return spread < 0 || (spread === 0 && r.used < best.used) ? r : best + }) target.items.push(c) target.used += c.units }