Repository navigation
Conversation
The deployment guide's routed out-of-band network is two chains: mgmt firewall i links to mgmt server i (interface and BMC) and to mgmt spine i's own mgmt port, and mgmt server i uplinks to mgmt spine i only. The planner drew and counted every mgmt server against every mgmt spine and had no mgmt firewall at all. The firewall is a vendor-neutral 1U server with 6x 1G RJ45 (pfSense or Debian with FRR); the Teltonika RUTXR1 of earlier setups is end-of-life (last order 2027-02-03). Its 100 W power draw is an estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every switch has a single management interface, but the topology drew a line from each spine to every mgmt spine. The spines now take turns on the mgmt spines, as in the deployment guide (spine01 on mgmt-spine01). The BOM already counted one cable per spine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link from a switch's eth0 into the management network looked exactly like a front-panel data link. TopoLink.mgmtPort now marks the links with an end on a dedicated management interface (leaf to mgmt leaf, spine to mgmt spine, mgmt firewall to mgmt spine); the diagram draws those red and the other management links green. Production stays blue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZoomPane captured the pointer on every press, which retargets the click to the pan container: no click on a diagram box or node ever arrived, so "click a rack to open its editor" did nothing. Capture now starts after the pointer has moved PAN_THRESHOLD pixels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A palette adds partitions, racks, rack groups, server groups, spines, exits, routers and external networks at the current selection, and an inspector shows the selected section's editor. Every palette item is an existing store action, so the topology, BOM and validation stay derived from the Plan and every add is undoable. The selection is view state and never part of the Plan. The placement rules live in views/topology/build.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 4x25G breakout serves four ports of a single switch port, but a dual-attached group spreads its ports over the leaf pair. The BOM and the leaf capacity check rounded the group's ports up to fours as if one breakout could span both leaves, so any group whose node count is not a multiple of four came out short: 9 nodes need 3 + 3 breakouts, not 5; 10 nodes need 6, not 5. nodeSwitchPorts() is now the single rule for BOM lines (servers and on-prem control plane nodes) and leafPortsNeeded(). Groups of eight MicroCloud nodes, as in the templates, are unaffected. Found by laying out the physical cable plan and comparing it with the BOM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sonicPorts maps a switch model's front-panel ports to SONiC interface names, per port group in the order of `ports`. Taken from sonic-buildimage port_config.ini for the AS7726-32X (Ethernet0..124, step 4) and the AS4630-54TE (Ethernet0..47 1G, 48..51 25G, 52 and 56 100G). Models without an upstream port map (AS4625-54T) have none, so nothing guesses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The routed management network needs addresses the IP plan did not size: a loopback per mgmt spine and mgmt leaf, and four /30 per side of the management network (firewall to mgmt server, to its BMC and to the mgmt spine's mgmt port, mgmt server to mgmt spine), as in the deployment guide's routed out-of-band network. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deriveCabling() names both ends of every cable in a partition: hostnames modelled on a production inventory (<partition>-r01leaf01, -spine01, -mgmtspine01, -mgmtserver01, -mgmtfw01), SONiC port names from the catalog, 4x25G breakouts, and the deployment guide's port names for mgmt firewall and mgmt server. Its tests hold the cable counts per type against deriveBom() for the default plan, every template and a plan with every cabled feature. Superspines, an L2 management network, a control-plane rack of its own and switch models without a port map are reported, not cabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
toYaml() writes plain data with every string as a JSON string, which is a valid YAML double-quoted scalar, so "no", "on" and "1.10" stay strings and Jinja expressions survive. Undefined values are left out rather than written as a placeholder. No new dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deriveInventory() turns the cable plan and the IP plan's infrastructure ranges into an inventory in the layout of a production partition: a group per partition with role child groups (spines, exits, leaves, storage leaves, mgmt spines, mgmt leaves, mgmt servers, mgmt firewalls) and the generic role groups above them. Hosts carry the variables metal-roles v0.35.0 reads (sonic-config, metal-core, mgmt-server, pixiecore, ztp): loopbacks, ASNs, mgmt interfaces, BGP ports, breakouts, PXE subnets and DHCP relays; planner_ports documents the cable plan per device. Inputs a plan cannot know (control plane endpoints, release, DNS/NTP, BMC credentials, upstream peers, keys) stay undefined and are listed in the file header, so the roles' own asserts stop a run instead of running with a placeholder. Checked with ansible-inventory, a variable-name match against metal-roles, and by rendering sonic-config's frr.conf and sonic-config.yaml templates for spine, exit, leaf, mgmt spine and mgmt leaf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The switches relay PXE (vlan4000) and management DHCP to the mgmt servers, but the inventory gave those servers no dhcp_subnets. The dhcp role has no asserts, so a run would have succeeded with no leases for PXE, BMCs or switch ZTP. Each mgmt server now serves every relayed network with the relaying SVI as router and its own share of the dynamic range (two dhcpd without failover must not lease the same address), plus the ONIE and SONiC ZTP options from the dhcp and ztp role examples. Checked by rendering the dhcp role's dhcpd.conf template. A rack with more nodes than its leaves' PXE subnets hold is reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Production partition playbooks address some roles by other group names: sonic_leaves, mgmt_spine_switches, mgmt_leaf_switches, sonic_mgmtleaves and mgmt_servers. The generic groups now include them, so those plays find their hosts instead of skipping with an unmatched-pattern warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Downloads <plan>-inventory.yaml, or explains in a toast why the plan cannot be exported yet. Client-side only, like the other exports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan editor sections switched to four columns at the md window
breakpoint. Reused in the topology builder's 28rem inspector, that
squeezed four fields into a narrow card and cut off select values
("Leaf-spi", "Redunda"). The sections are now CSS containers and their
grids switch at the @2XL container breakpoint (42rem): four columns in
the Plan tab as before, two in the inspector.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Taken unchanged from 3073fa6 on drag-and-drop-rack (the model function, its tests and the store action, without the Racks view drag-and-drop), so the topology builder can move server groups with the same rule. Nodes merge into a matching group in the target rack; a group moved in full disappears from its source rack.
A server group node can be dragged onto another rack of its partition: a ghost follows the pointer, the rack under it is outlined as the drop target, and releasing there moves all of the group's nodes with moveChassis, merging into a matching group in the target rack. The target rack is selected afterwards and the move is undoable. A press only becomes a drag after DRAG_THRESHOLD, so a click still selects, and the hover fade is off while dragging so the drop targets stay visible. moveGroupArgs() holds the drop rules: same partition, another plan rack, never the central or the control-plane rack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A leaf-spine-superspine partition needs pods to be a 5-stage Clos at all: with every leaf on every spine, the superspines carried no traffic. Partition.pods and Rack.podId describe them; derive/pods.ts is the one place that says which racks a pod holds (racks without a known pod go to the first, and a partition without pods has an implicit Pod 1), how many spines there are (spineCount per pod plus the border pod in the central rack) and how many superspines each plane gets, following RFC 7938 section 3.2.3. Version 1 files need no reshaping. The format still goes to version 2 so that an older planner refuses a file with pods instead of silently dropping them.
In a leaf-spine-superspine partition every leaf was linked to every spine and every spine to every superspine, so the superspine tier carried no traffic. The topology now follows RFC 7938 section 3.2.3: a rack's leaves uplink only to its pod's spines, and spine j of every pod, including the border pod in the central rack, connects to plane j of the superspines only. Exits and storage leaves stay on the border pod's spines, the dedicated cluster for external connectivity (section 5.2.4). Leaf-spine partitions are unchanged. The diagram does not lay out pods yet; that follows separately.
With pods, the BOM ordered spineCount spines in all and cabled every leaf to them; the superspine tier was sized against the whole partition. Now: - spinePorts() in bom.ts is the one rule for the links a spine terminates, per group: the central rack's spines (exits, storage leaves, control-plane rack leaves) and each pod's (its own leaves), each plus one link per superspine of its plane. The BOM and the spine port budget both use it. - The BOM, the central rack elevation, the IP plan's loopbacks and mgmt addresses count every pod's spines (spinesTotal). - The spine tier ratio compares the busiest pod's leaf links against its plane's superspines. - Validation reports a superspine count that does not split into one plane per spine, superspines without a port for every pod, pods without racks, and on every superspine partition that the design follows RFC 7938 and is not a metal-stack reference architecture. podPlan.fixture.ts is the shared hand-computed test plan.
The cable plan and the Ansible export refused leaf-spine-superspine partitions. The cable plan now lays out pods and planes: a pod's racks uplink to the pod's own spines (<partition>-p01spine01), spine j of the central rack and of every pod to plane j of the superspines from its top ports, and each superspine takes one port per pod. The cabling test adds a two-pod plan, so the independent cable-plan-vs-BOM comparison now covers five stages as well. The inventory gains a superspines group and follows RFC 7938 section 5.2.1 for ASNs: the superspines share one, the spines of each pod share their own, the central rack's spines keep theirs. A new test checks that every switch of the cable plan is a host; without it, missing superspines went unnoticed by the per-host checks. Checked with ansible-inventory (24 hosts, no warnings) and by rendering sonic-config's frr.conf for a superspine, a pod spine and a pod leaf.
With pods the diagram had no place for the pod spines. Each pod is now a box above its racks that holds the pod's spines: a leaf's uplinks stay inside its pod, and only spine j of each pod rises to plane j of the superspines in the central rack. Leaf-spine partitions and the management and central views, which show no pods, are laid out as before.
The central rack section of a leaf-spine-superspine partition lists its
pods: rename, remove (its racks move back to the first pod) and "Add
pod", whose first use turns the implicit Pod 1 into a real one so the
existing racks stay put. Each rack picks its pod next to its rack type.
Every change is a store action and undoable.
The spine and superspine info texts described the old full mesh ("each
spine connects once to every superspine"); they now describe pods and
planes, and the superspine text says it follows RFC 7938 and is not part
of the metal-stack documentation.
Review findings on the pod model: - The spine tier ratio ignored the central rack's spines, which carry every pod's storage traffic (exits, storage leaves and control-plane rack leaves on their way to the superspines). It now takes the busiest spine of all groups; requiredSuperspines follows. - The mgmt spine copper check counted every central device against a single mgmt spine, although the cable plan spreads them over the mgmt spines in turn. With pods that turned into false errors (a plan the cable plan lays out with 28 cables per mgmt spine was refused for 49). It now counts each mgmt spine's share plus its own mgmt server, and includes on-prem control plane nodes, which the cable plan puts there. - New warnings: a superspine partition with only one pod gains nothing over leaf-spine, and a rack whose podId names no pod (a hand-edited or imported file) silently counts to the first pod.
The pod spines sit in the central rack, so the central view now keeps the pods and their spines instead of dropping them. The pods.ts header cited RFC 7938 section 3.2.3 for "Every Tier 2 device connects to a single group of Tier 1 devices"; that sentence is in section 3.2.4 (3.2.3 holds Figure 3). The border pod is now described as adapted from section 5.2.4, whose Tier 3 are WAN routers.
On rehydrate, a stored plan that does not migrate or parse (for example one written by a newer planner) was replaced by an empty plan and that was persisted: the original was gone without a message. With plan format 2 this becomes reachable as soon as an older build opens storage written by this one. merge now copies the original to metal-stack-planner/plan-unreadable first. The key and helper are defined above the store: rehydration runs while the module is still being evaluated, and a const further down was in its temporal dead zone, which the try/catch swallowed. Checked in Chromium: a stored v1 plan keeps its name and racks; a stored format 3 plan is replaced and kept under the backup key.
An exported inventory with a leaf-spine-superspine partition says in its header that the design follows RFC 7938, is not a metal-stack reference architecture, and is untested in a lab. The cabling test gains a plan that combines pods with a rack group, storage leaves, two links per leaf and spine pair and an odd node count; mutating the plane mapping in the cable plan makes the cable-vs-BOM comparison fail (44 vs 32 trunks).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dont merge, just to show some ideas
What changes for an existing plan
Opening a plan you already have, without touching anything:
metal-stack-planner/plan-unreadablebefore it is replaced.Management network
Topology builder
moveChassisfrom Sven-Ric'sdrag-and-drop-rackbranch: only the model function, its tests and the store action, in a commit under his name.ZoomPanecaptured the pointer on every press, so no click on a diagram element ever arrived. It now captures only once a press moves past 4 px.Cable plan and Ansible export
derive/deploy/cabling.tsnames both ends of every cable in a partition:<partition>-r01leaf01,-spine01,-mgmtspine01,-mgmtserver01,-mgmtfw01.port_config.inifor the AS7726-32X and AS4630-54TE. A model without a port map is reported, never guessed.Its tests compare the cable counts per type with
deriveBom()for the default plan, every template, and plans with every cabled feature.Export Ansible downloads
<plan>-inventory.yaml:dhcp_subnetson the mgmt servers cover every network the switches relay, each server with its own share of the range. The ONIE and SONiC ZTP options are included.inventory.ts.planner_portsdocuments the cables per device.Leaf-spine-superspine: pods and planes
Before, every leaf linked to every spine and every spine to every superspine, so the superspine tier carried no traffic. Now it follows the 5-stage Clos of RFC 7938:
derive/pods.tsis the single source for this, andspinePorts()inbom.tsthe single rule for spine links. They drive:Pods are edited in the central rack section, and each rack picks its pod. The diagram draws each pod as a box with its spines above its racks.
Partition.podsRack.podIdspineCountsuperspineCountspineCount: one plane per spineVerification
npm run test(368), lint, format check and build pass.ansible-inventorywith 0 warnings (it exits 0 even on YAML errors, so warnings were counted). Variable names were matched against metal-roles atv0.35.0-2-g75c0fbe. sonic-config'sfrr.conf.j2andsonic-config.yaml.j2and dhcp'sdhcpd.conf.j2were rendered with the generated host variables.8802e79.Known limits
p01spine01). Removing or reordering a pod renames them in the export.🤖 Generated with Claude Code