Wavelength Path Tracing¶
netbox_wdm/trace.py discovers end-to-end wavelength paths by walking the
NetBox cable plant. The user-facing summary is in
Wavelength Paths and Circuits; this page covers the
algorithm internals, the multi-terminated cable handling, the multi-
degree node logic, and the validity checks.
Entry points¶
| Function | Purpose |
|---|---|
trace_wavelength_path(channel) |
Returns a TraceResult (channels in order, plus is_complete/is_active/is_valid) for one start channel. |
rebuild_wavelength_paths_for_node(node) |
Wrapper that retraces every grid position on a node and persists the result as WdmWavelengthPath + WdmWavelengthPathChannel rows. Wrapped in transaction.atomic. |
rebuild_wavelength_paths_for_node is what the post-save and post-delete
signals call (via transaction.on_commit) when a relevant DCIM or WDM
object changes. There is no UI button or batch job; the algorithm runs
end-to-end on every change.
Walk shape¶
The trace alternates two operations:
- Internal pass-through. From a FrontPort on a non-WDM device, follow
the device's
dcim.PortMappingto the matching RearPort. - External cable. From a RearPort or FrontPort, follow the
dcim.Cableto the next device's port.
A path is a sequence of WDM nodes. The trace records one WdmChannel per
node along the way -- one entry per node, not one entry per cable.
Intermediate non-WDM devices (patch panels, EDFAs) appear in the cable
chain between two WDM channel entries but are not channel rows
themselves; the trace visualisation reads them out of the cable
terminations.
Origin discovery (_find_origin)¶
trace_wavelength_path always starts by finding the origin node for
the channel's grid position. It walks backward from the start channel
through RX line ports until no further predecessor exists.
For each candidate predecessor it verifies that:
- The predecessor's TX line port is the one connected to the current node's RX line port. If the cable lands on the predecessor's RX line port, it is not an origin -- it is a sibling on a misconfigured trunk.
- The predecessor has a
WdmChannelat the same grid position. A node that does not carry the wavelength is skipped.
On multi-degree nodes (ROADMs) every RX line port is tried. The first one that yields a valid predecessor is followed. Visited nodes are tracked to guarantee termination.
Forward walk¶
From the origin, the trace walks forward via TX line ports, recording each WDM node it hits. On multi-degree nodes it tries every TX line port and prefers the one that leads to an unvisited node. This is the mechanism that makes a single wavelength path span MUX-A -> ROADM -> MUX-B without code knowing in advance which of the ROADM's two TX directions is the express side.
The walk stops when:
- No further TX line port leads to an unvisited node, or
- The cable from the chosen TX is not connected (path ends but is still
recorded;
is_activewill become false), or - The far end is not a
WdmLinePort(path ends at a non-WDM device).
Cable-following helpers¶
NetBox represents cables as dcim.Cable plus a list of
dcim.CableTermination rows. The plugin's helpers all use the same
A/B + position-index pattern:
_follow_cable_from_rearport(rp)-- RearPort to RearPort (direct trunk cable). Looks up everyCableTerminationfor the cable, splits bycable_end, finds the input rear port's(side, index), returns the termination at the same index on the other side._follow_frontport_cable(fp)-- FrontPort to FrontPort. Same logic for FP-to-FP cables._follow_cable_from_rearport_to_frontport(rp)-- mixed rear-to-front cables (e.g. trunk into a patch panel). Position-matches FP terms on the far end._resolve_rearport_cable(rp, visited)-- glue function that follows a trunk cable, then a single pass-through device, then another trunk cable. This is what makes patch panel pairs invisible to the path hop count.
The position-index matching is what handles multi-terminated cables. A duplex link uses one cable with two A-side ports and two B-side ports; position 0 on the A side maps to position 0 on the B side, position 1 to position 1, and so on. Without this, the trace would not know which of the two parallel fibres to follow.
Pass-through device traversal¶
_resolve_rearport_cable handles the patch-panel case explicitly:
[WDM-A.RP] --(cable)--> [PP-A.FP] --(internal)--> [PP-A.RP]
--(cable)--> [PP-B.RP] --(internal)--> [PP-B.FP]
--(cable)--> [WDM-B.RP]
The function:
- Follows the first cable from the WDM RearPort to the patch panel FrontPort.
- Looks up the patch panel's
PortMappingto translate FP -> RP at the same position. - Follows the inter-PP trunk cable RP -> RP.
- Translates RP -> FP on the second PP via its
PortMapping. - Follows the second cable from the PP FrontPort to the WDM RearPort.
Each step's cable_end + index is matched, so multi-terminated cables
in the chain still pick the right fibre.
The visited set tracks every rear port the walk has touched, so a
cyclic patch plant (loopback into the same PP) terminates instead of
looping forever. The outer _get_far_end_node also caps total hops at
20.
Validity flags¶
trace_wavelength_path returns three booleans that map to fields on
WdmWavelengthPath:
is_complete. True when both the first and last channel in the path have a client front port assigned (mux_front_port_idordemux_front_port_idnon-null). False during install.is_active. True when every cable along the trace hasstatus = connectedand the path has at least two hops. The trace inspectsCable.statusas it follows each TX rear port.is_valid. True unless the trace detects a TX-to-TX miscable._check_far_end_rolechecks each far-endWdmLinePort.role; if a TX cable lands on a TX (instead of RX or BIDI) on the next node, the path is recorded but flagged invalid. This is the most common patching mistake -- forgetting to flip TX/RX at the far end.
Persisting paths (rebuild_wavelength_paths_for_node)¶
The rebuild loop iterates every distinct grid_position on the node and
calls trace_wavelength_path for one channel at each position. Then:
- If the trace produced fewer than 2 channels, any existing path whose channel set is a subset of the trace result is deleted. (This is how paths are torn down when a node loses connectivity.)
- Otherwise, an existing
WdmWavelengthPathwhosepath_channelsare the same channels in the same sequence is reused. Both directional paths in a duplex topology have the same set of channels but reverse sequences, so identity-by-set is not enough -- order matters. - A new path object is created when no match exists.
- Through-table rows (
WdmWavelengthPathChannel) are rewritten on every call.
The final cleanup deletes any WdmWavelengthPath left without
path_channels (orphan rows).
Signal scheduling and atomic blocks¶
The signal handlers in netbox_wdm/signals.py schedule
rebuild_wavelength_paths_for_node on transaction.on_commit. This is
correct for normal NetBox operation, where each save runs in its own
transaction.
Code that builds a topology and inspects paths in the same atomic block
(integration tests, the sample-data command) needs to invoke
rebuild_wavelength_paths_for_node directly -- otherwise the rebuild
fires only after the outer atomic returns, and the assertions run before
paths exist.
Performance characteristics¶
- The walk is bounded at 20 hops, so worst-case node-traversal is O(20) per grid position.
- Each cable lookup runs a few
CableTerminationqueries plus aRearPortorFrontPortfetch. Multi-terminated cables only widen the per-cable work, not the hop count. rebuild_wavelength_paths_for_noderuns one trace per grid position, so a 44-channel DWDM node is bounded at 44 traces per rebuild.- Tracing is read-mostly except for the persistence step; on a no-change rebuild only the through-table is rewritten and the underlying path row stays.
For very large topologies (hundreds of nodes, dozens of cables per rebuild) consider batching the rebuild via a queue rather than firing on every signal -- the trace is correct but the constant-cost overhead of loading channels and cable terminations adds up.
Where to look in code¶
| Concern | Location |
|---|---|
| Algorithm | netbox_wdm/trace.py |
| Signal hooks | netbox_wdm/signals.py |
| Models | netbox_wdm/models.py (WdmWavelengthPath, WdmWavelengthPathChannel) |
| Trace API endpoint | WdmChannelViewSet.trace in netbox_wdm/api/views.py |
| Trace dataclasses (used by the trace API and the visualisation) | netbox_wdm/dataclasses.py |