docs: add cabling section for fabric topology

- Document NetBox Cable model usage
- Add complete spine-leaf cabling matrix
- Add MLAG peer-link cabling
- Add host dual-homing connections
- Include pynetbox examples for cable retrieval
- Reference arista-evpn-vxlan-clab topology

Relates to #5
This commit is contained in:
2026-01-10 14:57:55 +00:00
parent 0d174cf6b8
commit cfb9c8730e

View File

@@ -11,6 +11,14 @@ The fabric-orchestrator uses NetBox as the **source of truth** for EVPN-VXLAN fa
- NetBox 4.4.x - NetBox 4.4.x
- NetBox BGP Plugin v0.17.x - NetBox BGP Plugin v0.17.x
### Reference Topology
This data model represents the fabric defined in [arista-evpn-vxlan-clab](https://gitea.arnodo.fr/Damien/arista-evpn-vxlan-clab):
- 2 Spine switches (spine1, spine2)
- 8 Leaf switches in 4 MLAG pairs (leaf1-2, leaf3-4, leaf5-6, leaf7-8)
- 4 Hosts dual-homed to MLAG pairs
--- ---
## Data Model Summary ## Data Model Summary
@@ -22,6 +30,7 @@ The fabric-orchestrator uses NetBox as the **source of truth** for EVPN-VXLAN fa
| **Devices** | `dcim.Device` | `/api/dcim/devices/` | | **Devices** | `dcim.Device` | `/api/dcim/devices/` |
| **Interfaces** | `dcim.Interface` | `/api/dcim/interfaces/` | | **Interfaces** | `dcim.Interface` | `/api/dcim/interfaces/` |
| **LAGs/Port-Channels** | `dcim.Interface` (type=LAG) | `/api/dcim/interfaces/` | | **LAGs/Port-Channels** | `dcim.Interface` (type=LAG) | `/api/dcim/interfaces/` |
| **Cables** | `dcim.Cable` | `/api/dcim/cables/` |
| **VLANs** | `ipam.VLAN` | `/api/ipam/vlans/` | | **VLANs** | `ipam.VLAN` | `/api/ipam/vlans/` |
| **VLAN Groups** | `ipam.VLANGroup` | `/api/ipam/vlan-groups/` | | **VLAN Groups** | `ipam.VLANGroup` | `/api/ipam/vlan-groups/` |
| **VRFs** | `ipam.VRF` | `/api/ipam/vrfs/` | | **VRFs** | `ipam.VRF` | `/api/ipam/vrfs/` |
@@ -152,6 +161,138 @@ Based on the reference topology:
--- ---
## Cabling
NetBox's native `dcim.Cable` model represents physical connections between devices. This enables topology visualization (via plugins like `netbox-topology-views`) and validation of the fabric infrastructure.
### Cable Model
| Field | Description |
|-------|-------------|
| `a_terminations` | Source interface(s) |
| `b_terminations` | Destination interface(s) |
| `type` | Cable type (e.g., `cat6a`, `mmf-om4`, `dac-passive`) |
| `status` | `connected`, `planned`, `decommissioning` |
| `label` | Optional identifier |
| `length` | Cable length (with unit) |
### Spine-Leaf Cabling Matrix
Based on the [arista-evpn-vxlan-clab](https://gitea.arnodo.fr/Damien/arista-evpn-vxlan-clab) topology:
#### Spine1 to Leafs
| Spine1 Interface | Leaf | Leaf Interface | Description |
|------------------|------|----------------|-------------|
| Ethernet1 | leaf1 | Ethernet11 | Underlay uplink |
| Ethernet2 | leaf2 | Ethernet11 | Underlay uplink |
| Ethernet3 | leaf3 | Ethernet11 | Underlay uplink |
| Ethernet4 | leaf4 | Ethernet11 | Underlay uplink |
| Ethernet5 | leaf5 | Ethernet11 | Underlay uplink |
| Ethernet6 | leaf6 | Ethernet11 | Underlay uplink |
| Ethernet7 | leaf7 | Ethernet11 | Underlay uplink |
| Ethernet8 | leaf8 | Ethernet11 | Underlay uplink |
#### Spine2 to Leafs
| Spine2 Interface | Leaf | Leaf Interface | Description |
|------------------|------|----------------|-------------|
| Ethernet1 | leaf1 | Ethernet12 | Underlay uplink |
| Ethernet2 | leaf2 | Ethernet12 | Underlay uplink |
| Ethernet3 | leaf3 | Ethernet12 | Underlay uplink |
| Ethernet4 | leaf4 | Ethernet12 | Underlay uplink |
| Ethernet5 | leaf5 | Ethernet12 | Underlay uplink |
| Ethernet6 | leaf6 | Ethernet12 | Underlay uplink |
| Ethernet7 | leaf7 | Ethernet12 | Underlay uplink |
| Ethernet8 | leaf8 | Ethernet12 | Underlay uplink |
### MLAG Peer-Link Cabling
Each MLAG pair has a dedicated peer-link connection:
| MLAG Pair | Device A | Interface A | Device B | Interface B | Description |
|-----------|----------|-------------|----------|-------------|-------------|
| VTEP1 | leaf1 | Ethernet10 | leaf2 | Ethernet10 | MLAG peer-link |
| VTEP2 | leaf3 | Ethernet10 | leaf4 | Ethernet10 | MLAG peer-link |
| VTEP3 | leaf5 | Ethernet10 | leaf6 | Ethernet10 | MLAG peer-link |
| VTEP4 | leaf7 | Ethernet10 | leaf8 | Ethernet10 | MLAG peer-link |
> Note: In production, peer-links typically use multiple interfaces in a Port-Channel for redundancy (e.g., Ethernet10 + Ethernet13 → Port-Channel10).
### Host Dual-Homing Cabling
Hosts are dual-homed to MLAG pairs using LACP bonding:
| Host | Leaf A | Leaf A Interface | Leaf B | Leaf B Interface | MLAG ID |
|------|--------|------------------|--------|------------------|---------|
| host1 | leaf1 | Ethernet1 | leaf2 | Ethernet1 | 1 |
| host2 | leaf3 | Ethernet1 | leaf4 | Ethernet1 | 1 |
| host3 | leaf5 | Ethernet1 | leaf6 | Ethernet1 | 1 |
| host4 | leaf7 | Ethernet1 | leaf8 | Ethernet1 | 1 |
### Cabling Conventions
| Interface Range | Purpose |
|-----------------|---------|
| Ethernet1-9 | Host-facing (downlinks) |
| Ethernet10 | MLAG peer-link |
| Ethernet11-12 | Spine uplinks |
### Cable Retrieval via API
```python
import pynetbox
nb = pynetbox.api('http://netbox.example.com', token='your-token')
# Get all cables for a device
cables = nb.dcim.cables.filter(device='leaf1')
for cable in cables:
a_term = cable.a_terminations[0]
b_term = cable.b_terminations[0]
print(f"{a_term.device.name}:{a_term.name} <-> {b_term.device.name}:{b_term.name}")
# Get spine-leaf cables only
spine_cables = nb.dcim.cables.filter(device='spine1')
# Get peer-link cables (filter by interface name pattern)
leaf1_interfaces = nb.dcim.interfaces.filter(device='leaf1', name='Ethernet10')
for iface in leaf1_interfaces:
if iface.cable:
cable = nb.dcim.cables.get(iface.cable.id)
print(f"Peer-link: {cable}")
# Validate expected connections
def validate_spine_leaf_cabling(nb, spine_name, expected_leafs):
"""Validate that spine has cables to all expected leafs."""
cables = nb.dcim.cables.filter(device=spine_name)
connected_devices = set()
for cable in cables:
for term in cable.b_terminations:
connected_devices.add(term.device.name)
missing = set(expected_leafs) - connected_devices
if missing:
print(f"WARNING: {spine_name} missing connections to: {missing}")
return len(missing) == 0
# Example validation
validate_spine_leaf_cabling(nb, 'spine1',
['leaf1', 'leaf2', 'leaf3', 'leaf4', 'leaf5', 'leaf6', 'leaf7', 'leaf8'])
```
### Topology Visualization
With cables properly defined, the `netbox-topology-views` plugin can automatically generate fabric diagrams. The plugin uses cable data to draw connections between devices.
Key benefits:
- **Auto-generated diagrams**: No manual drawing required
- **Real-time updates**: Topology reflects current NetBox data
- **Drill-down**: Click devices/cables for details
- **Export**: SVG/PNG export for documentation
---
## BGP Configuration ## BGP Configuration
### ASN Assignments ### ASN Assignments
@@ -258,7 +399,7 @@ MLAG configuration uses Custom Fields since NetBox doesn't have native MLAG supp
### VRF Definition ### VRF Definition
| VRF Name | RD | Import RT | Export RT | L3 VNI (custom field) | | VRF Name | RD | Import RT | Export RT | L3 VNI (custom field) |
|----------|-------|-----------|-----------|----------------------| |----------|--------|-----------|-----------|----------------------|
| gold | `10.0.250.X:1` | `1:100001` | `1:100001` | 100001 | | gold | `10.0.250.X:1` | `1:100001` | `1:100001` | 100001 |
> Note: RD uses device's Loopback0 IP for uniqueness > Note: RD uses device's Loopback0 IP for uniqueness
@@ -313,48 +454,48 @@ l3vni = vrf.custom_fields.get('l3vni')
## Relationship Diagram ## Relationship Diagram
``` ```
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────
│ NetBox │ NetBox │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────
│ │
│ ┌────────────┐ ┌────────────────┐ ┌───────────────┐ │ │ ┌────────────┐ ┌────────────────┐ ┌───────────────┐
│ │ Device │─────│ Interface │─────│ IP Address │ │ │ │ Device │─────│ Interface │─────│ IP Address │
│ │ (leaf1) │ │ (Loopback0) │ │(10.0.250.11) │ │ │ │ (leaf1) │ │ (Loopback0) │ │(10.0.250.11) │
│ │ cf:asn │ │ cf:mlag_peer_ │ │ cf:virtual_ip │ │ │ │ cf:asn │ │ cf:mlag_peer_ │ │ cf:virtual_ip │
│ └────────────┘ │ link │ └───────────────┘ │ │ └────────────┘ │ link │ └───────────────┘
│ └────────────────┘ │ │ │ └────────────────┘
│ │ │ │
│ custom_fields │ │ │ custom_fields
▼ │ │ ▼
│ ┌────────────┐ │ ┌────────────┐ ┌────────────────┐
│ │ cf:asn │ │ │ cf:asn │ │ Cable
│ │ (65001) │ │ │ (65001) │ │ (connections)
│ └────────────┘ │ └────────────┘ └────────────────┘
│ │
│ ┌──────────────────┐ ┌───────────────────┐ │ │ ┌──────────────────┐ ┌───────────────────┐ │
│ │ BGP Session │─────│ Peer Group │ │ │ │ BGP Session │─────│ Peer Group │ │
│ │ (plugin) │ │ (plugin) │ │ │ │ (plugin) │ │ (plugin) │ │
│ └──────────────────┘ └───────────────────┘ │ │ └──────────────────┘ └───────────────────┘ │
│ │
│ ┌────────────┐ ┌───────────────────────┐ ┌────────────┐ │ │ ┌────────────┐ ┌───────────────────────┐ ┌────────────┐
│ │ VLAN │─────│ L2VPN Termination │─────│ L2VPN │ │ │ │ VLAN │─────│ L2VPN Termination │─────│ L2VPN │
│ │ (40) │ │ │ │ (EVPN) │ │ │ │ (40) │ │ │ │ (EVPN) │
│ └────────────┘ └───────────────────────┘ └────────────┘ │ │ └────────────┘ └───────────────────────┘ └────────────┘
│ identifier │ │ │ identifier
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ 100040 │ │ │ │ 100040 │ │
│ │ (VNI) │ │ │ │ (VNI) │ │
│ └────────────┘ │ │ └────────────┘ │
│ │
│ ┌────────────┐ ┌───────────────────┐ │ │ ┌────────────┐ ┌───────────────────┐ │
│ │ VRF │─────│ Route Target │ │ │ │ VRF │─────│ Route Target │ │
│ │ (gold) │ │ (1:100001) │ │ │ │ (gold) │ │ (1:100001) │ │
│ │ cf:l3vni │ └───────────────────┘ │ │ │ cf:l3vni │ └───────────────────┘ │
│ └────────────┘ │ │ └────────────┘
│ │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────
``` ```
--- ---
@@ -367,6 +508,7 @@ l3vni = vrf.custom_fields.get('l3vni')
|----------|--------|---------| |----------|--------|---------|
| `/api/dcim/devices/` | `role`, `name`, `site` | `?role=leaf` | | `/api/dcim/devices/` | `role`, `name`, `site` | `?role=leaf` |
| `/api/dcim/interfaces/` | `device`, `type`, `name` | `?device=leaf1&type=virtual` | | `/api/dcim/interfaces/` | `device`, `type`, `name` | `?device=leaf1&type=virtual` |
| `/api/dcim/cables/` | `device`, `site`, `rack` | `?device=leaf1` |
| `/api/ipam/ip-addresses/` | `device`, `interface`, `vrf` | `?device=leaf1&vrf=gold` | | `/api/ipam/ip-addresses/` | `device`, `interface`, `vrf` | `?device=leaf1&vrf=gold` |
| `/api/ipam/vlans/` | `vid`, `site`, `group` | `?vid=40` | | `/api/ipam/vlans/` | `vid`, `site`, `group` | `?vid=40` |
| `/api/vpn/l2vpns/` | `type`, `name` | `?type=evpn` | | `/api/vpn/l2vpns/` | `type`, `name` | `?type=evpn` |
@@ -386,6 +528,7 @@ l3vni = vrf.custom_fields.get('l3vni')
## Next Steps ## Next Steps
1. **Custom Fields Setup** - Create all custom fields listed above in NetBox 1. **Custom Fields Setup** - Create all custom fields listed above in NetBox
2. **Data Population** - Enter fabric topology data into NetBox 2. **Cabling Setup** - Create cables for spine-leaf, peer-links, and host connections
3. **NetBox Client** - Update `src/netbox/client.py` to use custom fields 3. **Data Population** - Enter fabric topology data into NetBox
4. **Validation** - Verify data retrieval matches expected fabric config 4. **NetBox Client** - Update `src/netbox/client.py` to use custom fields and cables
5. **Validation** - Verify data retrieval matches expected fabric config