Files
Netbox/README.md
T
dcstephenson 83bb4cf708 Regenerate: department VLANs tagged on all 42 field switch uplinks
Per user: Device.tenant dictates department. Set the one switch missing a
tenant (bs-cr13-sw01 -> AMI), then tagged each FS.com IES3110-8TFP-R
switch's SFP uplink(s) with its department VLAN, alongside the existing
214 (Switch Management) native VLAN:

  AMI -> 203, Security -> 209, Gas/Water/Waste Water -> 215 (Gas and
  Water, per user), Electric -> 217

63 interfaces updated, 0 skipped. Connector labels now show both, e.g.
'10 · V214+215T ↔ 2'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NM3nivbufxEWiKKmatEtEe
2026-09-15 13:49:22 -05:00

122 lines
7.1 KiB
Markdown

# Netbox
Generated documentation for the field-switch network, sourced from this
utility's live [NetBox](https://netbox.dev/) instance. NetBox is the source
of truth here, not this repo -- everything under `topology/` is a build
artifact, regenerated from NetBox and committed so history shows what the
*physical network* changed, not just what someone drew.
## topology/
| file | what it is |
| --- | --- |
| `export_topology.py` | Run inside the NetBox Django shell. Walks every field/distribution switch's cable path (through patch panels and splice modules), then pulls every real `Cable` among the devices it found so each edge carries the exact port name on both ends, plus that port's 802.1Q mode/untagged/tagged VLANs where the port is a real interface. Also exports every `ipam.VLAN`. Read-only. |
| `topo_full.json` | Latest exported snapshot: devices (id, name, role, site, status), edges (`{a, a_port, a_vlan, b, b_port, b_vlan}`, one per cable), and `vlans` (every VLAN in NetBox). |
| `generate_drawio.py` | Pure-Python renderer: `topo_full.json` &rarr; `Field-Switch-Topology.drawio`. No NetBox access needed. |
| `Field-Switch-Topology.drawio` | The diagram. Open at [app.diagrams.net](https://app.diagrams.net) (or the desktop app, or the VS Code draw.io extension) -- free, no license needed. |
| `render_png.js` + `render_host.html` | Renders the `.drawio` through a real draw.io instance (embed.diagrams.net, driven headlessly with Puppeteer) to produce `Field-Switch-Topology.png` -- an actual render, not an approximation. Needs `npm install puppeteer` and network access. |
| `Field-Switch-Topology.png` | Static preview of the diagram, for anyone who wants a quick look without opening draw.io (viewing it in the Gitea file browser, a PR diff, etc.). |
Two distribution switches (`fche-sw17a`, `dmo-lob-sw35a`) anchor two
fabrics; everything downstream is field switches daisy-chained through
patch panels and fiber splice modules. A handful of switches sit outside
both fabrics for a documented reason (spare/inventory, decommissioning, or
planned/not-yet-installed) -- those are called out in their own box on the
diagram rather than silently dropped.
Every connector is labeled with the port at each end (e.g. `9 ↔ 1`), and
switches/patch panels look the part: switches get draw.io's built-in network
switch icon, patch panels get a small rack-unit look (header strip + port
ticks) rather than a plain box.
A **VLAN table** sits at the top of the diagram (VLAN / Name / Description /
Subnet / Site). As of this writing NetBox has no VLANs defined here, so it
renders as an explicit "not configured yet" placeholder rather than being
silently omitted or -- worse -- filled in with invented VLAN numbers. Add
VLANs and interface tagged/untagged assignments in NetBox and regenerate
(no code changes needed): the table populates for real, and every connector
whose port has VLAN config picks up a short suffix like `9·V10 ↔ 1·V10`.
Deliberately left out: literal switch CLI config dumps -- those belong in
the switches' NetBox config-context/notes, not pasted into a topology
diagram where they'd immediately go stale.
Current convention (documented in NetBox, not hardcoded anywhere in these
scripts): every FS.com `IES3110-8TFP-R` field switch trunks VLAN 214
(Switch Management) as the native/untagged VLAN on both its SFP uplink
ports (named `9`/`10` or `Gi1/9`/`Gi1/10` depending on the device), tagged
alongside whatever department VLAN that switch belongs to -- derived from
the device's NetBox **Tenant** (`AMI`/`Electric`/`Gas`/`Water`/`Waste
Water`/`Security`), not stored on the interface directly:
| Tenant | VLAN |
| --- | --- |
| AMI | 203 |
| Security | 209 |
| Gas, Water, Waste Water | 215 (Gas and Water) |
| Electric | 217 |
That mapping lives only in whoever ran the one-off assignment script, not
in `export_topology.py` -- if a new switch's Tenant changes, its tagged
VLAN has to be re-assigned in NetBox to match; the diagram just reads
whatever `interface.tagged_vlans` already says.
Connectors in the `.drawio` file are glued by shape id (draw.io's native
`source`/`target` reference on each edge) -- drag a switch or panel in the
app and its links follow, port-tick decorations included since they're
parented to their panel's body cell. That's different from this project's
earlier Visio (`.vsdx`) attempt, where glue had to be hand-written as cell
formulas because the format has no simpler mechanism; draw.io does this
natively, which is a big part of why this repo uses it instead.
## Regenerating
From the NetBox host (needs `docker exec` access to the NetBox container):
```sh
docker exec netbox-netbox-1 python3 /opt/netbox/netbox/manage.py shell \
-c "$(cat topology/export_topology.py)" 2>/dev/null > topology/topo_full.json
python3 topology/generate_drawio.py topology/topo_full.json topology/Field-Switch-Topology.drawio
# optional -- refreshes the PNG preview to match; first run: npm install puppeteer
node topology/render_png.js topology/Field-Switch-Topology.drawio topology/Field-Switch-Topology.png
```
Commit all three (`topo_full.json`, `.drawio`, `.png`) together. The diff on
`topo_full.json` is the real changelog -- it shows exactly which device or
cable changed between two snapshots; the `.drawio`/`.png` diffs are mostly
noise (coordinates, pixels) but are kept in sync so the rendered diagram
never drifts from what NetBox actually says.
## Gotchas learned the hard way
- **NetBox's cached cable path can go stale.** If a switch's
`connected_endpoints` comes back empty even though it's obviously
cabled, try `manage.py trace_paths --force` before assuming the cable
data itself is wrong -- it recomputes the path cache and doesn't touch
any cable/termination records.
- **A patch panel's front&harr;rear pass-through has to be modeled
explicitly** (a `PortMapping` row). A splice module can have a
perfectly good cable on both sides of a port and still fail to trace if
that mapping is missing -- check `FrontPort.objects.get(...).mappings.count()`
before assuming the far end really is disconnected.
- **Two devices can be joined by more than one real cable.** Most FS.com
field switches (`IES3110-8TFP-R`) run both SFP ports (9 and 10) as
redundant trunk links, often both landing on the same local patch panel
before fanning out from its rear ports -- so `export_topology.py` keys
edges by cable, never by device pair, or a second real cable between
the same two devices silently disappears. Layout has the same trap one
level up: dedupe a node's *neighbors* before recursing in the tree
layout, or a parallel edge gets walked twice and the second pass finds
its whole subtree already `visited`, so it gets recorded as a phantom
extra leaf -- inflates the page height for no real reason, one phantom
leaf per redundant cable.
## Possible next step
`export_topology.py` talks to NetBox via the Django ORM (`docker exec` +
`manage.py shell`), which only works run from the NetBox host itself.
Swapping it for a NetBox REST API client (device/cable/interface
endpoints + an API token) would let this regenerate from anywhere,
including CI, without shell access to the container.