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:
@@ -11,6 +11,14 @@ The fabric-orchestrator uses NetBox as the **source of truth** for EVPN-VXLAN fa
|
||||
- NetBox 4.4.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
|
||||
@@ -22,6 +30,7 @@ The fabric-orchestrator uses NetBox as the **source of truth** for EVPN-VXLAN fa
|
||||
| **Devices** | `dcim.Device` | `/api/dcim/devices/` |
|
||||
| **Interfaces** | `dcim.Interface` | `/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/` |
|
||||
| **VLAN Groups** | `ipam.VLANGroup` | `/api/ipam/vlan-groups/` |
|
||||
| **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
|
||||
|
||||
### ASN Assignments
|
||||
@@ -258,7 +399,7 @@ MLAG configuration uses Custom Fields since NetBox doesn't have native MLAG supp
|
||||
### VRF Definition
|
||||
|
||||
| VRF Name | RD | Import RT | Export RT | L3 VNI (custom field) |
|
||||
|----------|-------|-----------|-----------|----------------------|
|
||||
|----------|--------|-----------|-----------|----------------------|
|
||||
| gold | `10.0.250.X:1` | `1:100001` | `1:100001` | 100001 |
|
||||
|
||||
> Note: RD uses device's Loopback0 IP for uniqueness
|
||||
@@ -313,9 +454,9 @@ l3vni = vrf.custom_fields.get('l3vni')
|
||||
## Relationship Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ NetBox │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────────┐ ┌───────────────┐ │
|
||||
│ │ Device │─────│ Interface │─────│ IP Address │ │
|
||||
@@ -323,13 +464,13 @@ l3vni = vrf.custom_fields.get('l3vni')
|
||||
│ │ cf:asn │ │ cf:mlag_peer_ │ │ cf:virtual_ip │ │
|
||||
│ └────────────┘ │ link │ └───────────────┘ │
|
||||
│ │ └────────────────┘ │
|
||||
│ │ │
|
||||
│ │ custom_fields │
|
||||
│ ▼ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ cf:asn │ │
|
||||
│ │ (65001) │ │
|
||||
│ └────────────┘ │
|
||||
│ │ │ │
|
||||
│ │ custom_fields │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌────────────────┐ │
|
||||
│ │ cf:asn │ │ Cable │ │
|
||||
│ │ (65001) │ │ (connections) │ │
|
||||
│ └────────────┘ └────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌───────────────────┐ │
|
||||
│ │ BGP Session │─────│ Peer Group │ │
|
||||
@@ -354,7 +495,7 @@ l3vni = vrf.custom_fields.get('l3vni')
|
||||
│ │ cf:l3vni │ └───────────────────┘ │
|
||||
│ └────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
@@ -367,6 +508,7 @@ l3vni = vrf.custom_fields.get('l3vni')
|
||||
|----------|--------|---------|
|
||||
| `/api/dcim/devices/` | `role`, `name`, `site` | `?role=leaf` |
|
||||
| `/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/vlans/` | `vid`, `site`, `group` | `?vid=40` |
|
||||
| `/api/vpn/l2vpns/` | `type`, `name` | `?type=evpn` |
|
||||
@@ -386,6 +528,7 @@ l3vni = vrf.custom_fields.get('l3vni')
|
||||
## Next Steps
|
||||
|
||||
1. **Custom Fields Setup** - Create all custom fields listed above in NetBox
|
||||
2. **Data Population** - Enter fabric topology data into NetBox
|
||||
3. **NetBox Client** - Update `src/netbox/client.py` to use custom fields
|
||||
4. **Validation** - Verify data retrieval matches expected fabric config
|
||||
2. **Cabling Setup** - Create cables for spine-leaf, peer-links, and host connections
|
||||
3. **Data Population** - Enter fabric topology data into NetBox
|
||||
4. **NetBox Client** - Update `src/netbox/client.py` to use custom fields and cables
|
||||
5. **Validation** - Verify data retrieval matches expected fabric config
|
||||
|
||||
Reference in New Issue
Block a user