GET /api/v1/cabling gives each located machine's ports (from its inventory: name, speed, link, the PCI card they sit on) and the switch port each is plugged into, from the LLDP its commissioning saw (lldpcli's neighbours, either JSON shape). Switches get a place: NetBox's (the device's site, location, rack and position, read with the layout on every reconcile) or one set by hand (POST /api/v1/switches, which NetBox does not move); a switch with neither is drawn where most of its cables are, at the highest free unit (on top of the rack when none is free), and marked as estimated. In 3D every server now has a back too: power supplies, the BMC port, USB and VGA, and its network cards with their ports (SFP cages from 10G, RJ45 below), per sled for a multi-node chassis. Switches stand at the back with their ports in two rows. Cables run from each port out of the back, down the rack's side, over the top to another rack's switch, into the switch port; one colour per switch, grey without link, and up through the ceiling to a switch in another room. Hovering a cable names both ends and the VLAN; with a rack selected the other racks' cables fade. "Cables" turns them off, "Turn around" looks from the other side. The side panel lists the room's switches with their place (place… sets it) and, for a selected rack, every port and the switch port it goes to, in 2D as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.8 KiB
Go
65 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
// lldpcli -f json show neighbors details: one interface is an object, several a list
|
|
const lldpOne = `{"lldp": {"eno1": {"via": "LLDP", "chassis": {"leaf-1.dc1.example": {"id": {"type": "mac", "value": "0c:29:ef:00:00:01"},
|
|
"descr": "Dell EMC Networking OS10", "mgmt-ip": ["10.0.0.11", "fe80::1"]}}, "port": {"id": {"type": "ifname", "value": "ethernet1/1/12"}, "descr": "node7 eno1"},
|
|
"vlan": {"vlan-id": "100", "pvid": true}}}}`
|
|
const lldpList = `{"lldp": [{"eno1": {"chassis": {"leaf-1": {"descr": "x"}}, "port": {"id": {"value": "Ethernet12"}}}},
|
|
{"eno2": [{"chassis": {"leaf-2": {"mgmt-ip": "10.0.0.12"}}, "port": {"id": {"value": "Ethernet12"}}, "vlan": [{"vlan-id": "100"}, {"vlan-id": "200"}]}]}]}`
|
|
|
|
func TestLLDPShapes(t *testing.T) {
|
|
var inv obj
|
|
if err := json.Unmarshal([]byte(lldpOne), &inv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := lldpOf(inv)
|
|
if n := got["eno1"]; n.Switch != "leaf-1.dc1.example" || n.Port != "ethernet1/1/12" || n.VLAN != "100" || n.MgmtIP != "10.0.0.11" || n.PortDescr != "node7 eno1" {
|
|
t.Fatalf("one: %+v", n)
|
|
}
|
|
if err := json.Unmarshal([]byte(lldpList), &inv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got = lldpOf(inv)
|
|
if got["eno1"].Switch != "leaf-1" || got["eno2"].Switch != "leaf-2" || got["eno2"].VLAN != "100" || got["eno2"].MgmtIP != "10.0.0.12" {
|
|
t.Fatalf("list: %+v", got)
|
|
}
|
|
if len(lldpOf(obj{"lldp": nil})) != 0 {
|
|
t.Fatal("no lldp should be no neighbours")
|
|
}
|
|
}
|
|
|
|
func TestPortsOf(t *testing.T) {
|
|
inv := `{"network": [{"name": "eno2", "mac": "aa:00:00:00:00:02", "bus_info": "0000:3b:00.1", "speed": "25000Mb/s", "link": true},
|
|
{"name": "ens18", "bus_info": "virtio1", "speed": "Unknown!"}, {"name": "eno1", "bus_info": "0000:3b:00.0", "link": false}],
|
|
"lldp": {"eno2": {"chassis": {"leaf-1": {}}, "port": {"id": {"value": "Ethernet3"}}}}}`
|
|
ports, _ := portsOf(&Machine{Inventory: json.RawMessage(inv)})
|
|
if len(ports) != 3 {
|
|
t.Fatalf("ports: %+v", ports)
|
|
}
|
|
byName := map[string]Port{}
|
|
for _, p := range ports {
|
|
byName[p.Name] = p
|
|
}
|
|
if p := byName["eno2"]; p.Card != "0000:3b:00" || p.Switch != "leaf-1" || p.SwitchPort != "Ethernet3" || p.Link == nil || !*p.Link {
|
|
t.Fatalf("eno2: %+v", p)
|
|
}
|
|
if p := byName["ens18"]; p.Card != "" || p.Speed != "" {
|
|
t.Fatalf("ens18: %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestSwitchPlaces(t *testing.T) {
|
|
var devs []obj
|
|
_ = json.Unmarshal([]byte(`[{"name": "leaf-1", "site": {"name": "dc1"}, "location": {"name": "Hall 1"}, "rack": {"name": "R01"}, "position": 42.0, "device_type": {"model": "S5248F-ON"}},
|
|
{"name": "spare", "site": {"name": "dc1"}, "rack": null}]`), &devs)
|
|
got := switchPlaces(devs)
|
|
if len(got) != 1 || got[0] != (Switch{Name: "leaf-1", Building: "dc1", Room: "Hall 1", Rack: "R01", Unit: 42, Height: 1, Model: "S5248F-ON", Source: "netbox"}) {
|
|
t.Fatalf("%+v", got)
|
|
}
|
|
}
|