feat(link-info): move from meshviewer-ffrgb to runtime
This commit is contained in:
parent
cc71150d53
commit
2015fdd668
|
@ -14,6 +14,7 @@ func TestToInflux(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
node := &runtime.Node{
|
||||
Online: true,
|
||||
Statistics: &data.Statistics{
|
||||
NodeID: "deadbeef",
|
||||
LoadAverage: 0.5,
|
||||
|
@ -105,6 +106,7 @@ func TestToInflux(t *testing.T) {
|
|||
}
|
||||
|
||||
neighbour := &runtime.Node{
|
||||
Online: true,
|
||||
Nodeinfo: &data.Nodeinfo{
|
||||
NodeID: "foobar",
|
||||
Network: data.Network{
|
||||
|
@ -132,6 +134,7 @@ func TestToInflux(t *testing.T) {
|
|||
|
||||
// do not add a empty statistics of a node
|
||||
droppednode := &runtime.Node{
|
||||
Online: true,
|
||||
Nodeinfo: &data.Nodeinfo{
|
||||
NodeID: "notfound",
|
||||
Network: data.Network{
|
||||
|
|
|
@ -4,18 +4,10 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bdlm/log"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
LINK_TYPE_WIRELESS = "wifi"
|
||||
LINK_TYPE_TUNNEL = "vpn"
|
||||
LINK_TYPE_FALLBACK = "other"
|
||||
)
|
||||
|
||||
func transform(nodes *runtime.Nodes) *Meshviewer {
|
||||
|
||||
meshviewer := &Meshviewer{
|
||||
|
@ -25,7 +17,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
|
|||
}
|
||||
|
||||
links := make(map[string]*Link)
|
||||
typeList := make(map[string]string)
|
||||
|
||||
nodes.RLock()
|
||||
defer nodes.RUnlock()
|
||||
|
@ -38,19 +29,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
|
|||
continue
|
||||
}
|
||||
|
||||
if nodeinfo := nodeOrigin.Nodeinfo; nodeinfo != nil {
|
||||
if meshes := nodeinfo.Network.Mesh; meshes != nil {
|
||||
for _, mesh := range meshes {
|
||||
for _, addr := range mesh.Interfaces.Wireless {
|
||||
typeList[addr] = LINK_TYPE_WIRELESS
|
||||
}
|
||||
for _, addr := range mesh.Interfaces.Tunnel {
|
||||
typeList[addr] = LINK_TYPE_TUNNEL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, linkOrigin := range nodes.NodeLinks(nodeOrigin) {
|
||||
var key string
|
||||
// keep source and target in the same order
|
||||
|
@ -62,36 +40,11 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
|
|||
}
|
||||
|
||||
if link := links[key]; link != nil {
|
||||
linkType, linkTypeFound := typeList[linkOrigin.SourceAddress]
|
||||
if !linkTypeFound {
|
||||
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
|
||||
}
|
||||
|
||||
if switchSourceTarget {
|
||||
link.TargetTQ = linkOrigin.TQ
|
||||
|
||||
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
|
||||
if !linkTypeFound {
|
||||
linkType, linkTypeFound = typeList[linkOrigin.SourceAddress]
|
||||
}
|
||||
} else {
|
||||
link.SourceTQ = linkOrigin.TQ
|
||||
}
|
||||
|
||||
if linkTypeFound && linkType != link.Type {
|
||||
if link.Type == LINK_TYPE_FALLBACK {
|
||||
link.Type = linkType
|
||||
} else {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"link": fmt.Sprintf("%s-%s", linkOrigin.SourceAddress, linkOrigin.TargetAddress),
|
||||
"prev": link.Type,
|
||||
"new": linkType,
|
||||
"source": typeList[linkOrigin.SourceAddress],
|
||||
"target": typeList[linkOrigin.TargetAddress],
|
||||
}).Warn("different linktypes")
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
link := &Link{
|
||||
|
@ -101,11 +54,7 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
|
|||
TargetAddress: linkOrigin.TargetAddress,
|
||||
SourceTQ: linkOrigin.TQ,
|
||||
TargetTQ: 0,
|
||||
}
|
||||
|
||||
linkType, linkTypeFound := typeList[linkOrigin.SourceAddress]
|
||||
if !linkTypeFound {
|
||||
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
|
||||
Type: linkOrigin.Type.String(),
|
||||
}
|
||||
|
||||
if switchSourceTarget {
|
||||
|
@ -115,22 +64,13 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
|
|||
link.TargetTQ = linkOrigin.TQ
|
||||
link.Target = linkOrigin.SourceID
|
||||
link.TargetAddress = linkOrigin.SourceAddress
|
||||
|
||||
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
|
||||
if !linkTypeFound {
|
||||
linkType, linkTypeFound = typeList[linkOrigin.SourceAddress]
|
||||
}
|
||||
}
|
||||
|
||||
if linkTypeFound {
|
||||
link.Type = linkType
|
||||
} else {
|
||||
link.Type = LINK_TYPE_FALLBACK
|
||||
}
|
||||
links[key] = link
|
||||
meshviewer.Links = append(meshviewer.Links, link)
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
meshviewer.Links = append(meshviewer.Links, link)
|
||||
}
|
||||
return meshviewer
|
||||
}
|
||||
|
|
|
@ -128,8 +128,8 @@ func TestTransform(t *testing.T) {
|
|||
Other []string `json:"other,omitempty"`
|
||||
Tunnel []string `json:"tunnel,omitempty"`
|
||||
}{
|
||||
Wireless: []string{"node:b:mac:wifi"},
|
||||
Other: []string{"node:b:mac:lan"},
|
||||
Wireless: []string{"node:d:mac:wifi"},
|
||||
Other: []string{"node:d:mac:lan"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -155,28 +155,41 @@ func TestTransform(t *testing.T) {
|
|||
meshviewer := transform(nodes)
|
||||
assert.NotNil(meshviewer)
|
||||
assert.Len(meshviewer.Nodes, 4)
|
||||
/*
|
||||
links:
|
||||
a:wifi <-> b:wifi 153 / 204
|
||||
a:lan -> b:lan 51
|
||||
c:lan <-> b:lan 102 / 204
|
||||
d:lan -> c:lan 204 (but offline)
|
||||
d:wifi -> a:wifi 204 (but offline)
|
||||
*/
|
||||
links := meshviewer.Links
|
||||
assert.Len(links, 3)
|
||||
|
||||
counter := 0
|
||||
for _, link := range links {
|
||||
switch link.SourceAddress {
|
||||
case "node:a:mac:lan":
|
||||
assert.Equal("other", link.Type)
|
||||
assert.Equal("node:b:mac:lan", link.TargetAddress)
|
||||
assert.Equal(float32(0.2), link.SourceTQ)
|
||||
assert.Equal(float32(0), link.TargetTQ)
|
||||
assert.Equal("node:b:mac:lan", link.TargetAddress, "a:lan -> b:lan")
|
||||
assert.Equal("other", link.Type, "a:lan -> b:lan")
|
||||
assert.Equal(float32(0.2), link.SourceTQ, "a:lan -> b:lan")
|
||||
assert.Equal(float32(0), link.TargetTQ, "a:lan -> b:lan")
|
||||
counter++
|
||||
case "node:a:mac:wifi":
|
||||
assert.Equal("wifi", link.Type)
|
||||
assert.Equal("node:b:mac:wifi", link.TargetAddress)
|
||||
assert.Equal(float32(0.6), link.SourceTQ)
|
||||
assert.Equal(float32(0.8), link.TargetTQ)
|
||||
assert.Equal("node:b:mac:wifi", link.TargetAddress, "a:wifi <-> b:wifi")
|
||||
assert.Equal("wifi", link.Type, "a:wifi <-> b:wifi")
|
||||
assert.Equal(float32(0.6), link.SourceTQ, "a:wifi <-> b:wifi")
|
||||
assert.Equal(float32(0.8), link.TargetTQ, "a:wifi <-> b:wifi")
|
||||
counter++
|
||||
case "node:b:mac:lan":
|
||||
assert.Equal("other", link.Type)
|
||||
assert.Equal("node:c:mac:lan", link.TargetAddress)
|
||||
assert.Equal(float32(0.8), link.SourceTQ)
|
||||
assert.Equal(float32(0.4), link.TargetTQ)
|
||||
assert.Equal("other", link.Type, "b:lan <-> c:lan")
|
||||
assert.Equal("node:c:mac:lan", link.TargetAddress, "b:lan <-> c:lan")
|
||||
assert.Equal(float32(0.8), link.SourceTQ, "b:lan <-> c:lan")
|
||||
assert.Equal(float32(0.4), link.TargetTQ, "b:lan <-> c:lan")
|
||||
counter++
|
||||
default:
|
||||
assert.False(true, "invalid link.SourceAddress found")
|
||||
}
|
||||
}
|
||||
assert.Equal(3, counter, "not found every link")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package runtime
|
||||
|
||||
type LinkType int
|
||||
|
||||
const (
|
||||
UnknownLinkType LinkType = iota
|
||||
WirelessLinkType
|
||||
TunnelLinkType
|
||||
OtherLinkType
|
||||
)
|
||||
|
||||
func (lt LinkType) String() string {
|
||||
switch lt {
|
||||
case WirelessLinkType:
|
||||
return "wifi"
|
||||
case TunnelLinkType:
|
||||
return "vpn"
|
||||
case OtherLinkType:
|
||||
return "other"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type LinkProtocol int
|
||||
|
||||
const (
|
||||
UnknownLinkProtocol LinkProtocol = iota
|
||||
BatadvLinkProtocol
|
||||
BabelLinkProtocol
|
||||
LLDPLinkProtocol
|
||||
)
|
||||
|
||||
func (lp LinkProtocol) String() string {
|
||||
switch lp {
|
||||
case BatadvLinkProtocol:
|
||||
return "batadv"
|
||||
case BabelLinkProtocol:
|
||||
return "babel"
|
||||
case LLDPLinkProtocol:
|
||||
return "lldp"
|
||||
}
|
||||
return "unkown"
|
||||
}
|
||||
|
||||
// Link represents a link between two nodes
|
||||
type Link struct {
|
||||
SourceID string
|
||||
SourceHostname string
|
||||
SourceAddress string
|
||||
TargetID string
|
||||
TargetAddress string
|
||||
TargetHostname string
|
||||
TQ float32
|
||||
Type LinkType
|
||||
Protocol LinkProtocol
|
||||
}
|
|
@ -19,17 +19,6 @@ type Node struct {
|
|||
CustomFields map[string]interface{} `json:"custom_fields"`
|
||||
}
|
||||
|
||||
// Link represents a link between two nodes
|
||||
type Link struct {
|
||||
SourceID string
|
||||
SourceHostname string
|
||||
SourceAddress string
|
||||
TargetID string
|
||||
TargetAddress string
|
||||
TargetHostname string
|
||||
TQ float32
|
||||
}
|
||||
|
||||
// IsGateway returns whether the node is a gateway
|
||||
func (node *Node) IsGateway() bool {
|
||||
if info := node.Nodeinfo; info != nil {
|
||||
|
|
109
runtime/nodes.go
109
runtime/nodes.go
|
@ -14,18 +14,22 @@ import (
|
|||
|
||||
// Nodes struct: cache DB of Node's structs
|
||||
type Nodes struct {
|
||||
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID
|
||||
ifaceToNodeID map[string]string // mapping from MAC address to NodeID
|
||||
config *NodesConfig
|
||||
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID
|
||||
ifaceToNodeID map[string]string // mapping from MAC address to NodeID
|
||||
ifaceToLinkType map[string]LinkType // mapping from MAC address to LinkType
|
||||
ifaceToLinkProtocol map[string]LinkProtocol // mapping from MAC address to LinkProtocol
|
||||
config *NodesConfig
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewNodes create Nodes structs
|
||||
func NewNodes(config *NodesConfig) *Nodes {
|
||||
nodes := &Nodes{
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
config: config,
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
ifaceToLinkType: make(map[string]LinkType),
|
||||
ifaceToLinkProtocol: make(map[string]LinkProtocol),
|
||||
config: config,
|
||||
}
|
||||
|
||||
if config.StatePath != "" {
|
||||
|
@ -48,7 +52,7 @@ func (nodes *Nodes) AddNode(node *Node) {
|
|||
nodes.Lock()
|
||||
defer nodes.Unlock()
|
||||
nodes.List[nodeinfo.NodeID] = node
|
||||
nodes.readIfaces(nodeinfo, false)
|
||||
nodes.readIfaces(nodeinfo, node.Neighbours, false)
|
||||
}
|
||||
|
||||
// Update a Node
|
||||
|
@ -65,7 +69,7 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node {
|
|||
nodes.List[nodeID] = node
|
||||
}
|
||||
if res.Nodeinfo != nil {
|
||||
nodes.readIfaces(res.Nodeinfo, true)
|
||||
nodes.readIfaces(res.Nodeinfo, res.Neighbours, true)
|
||||
}
|
||||
nodes.Unlock()
|
||||
|
||||
|
@ -110,7 +114,7 @@ func (nodes *Nodes) GetNodeIDbyAddress(addr string) string {
|
|||
func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
|
||||
// Store link data
|
||||
neighbours := node.Neighbours
|
||||
if neighbours == nil || neighbours.NodeID == "" {
|
||||
if neighbours == nil || neighbours.NodeID == "" || !node.Online {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -133,6 +137,11 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
|
|||
if node.Nodeinfo != nil {
|
||||
link.SourceHostname = node.Nodeinfo.Hostname
|
||||
}
|
||||
if lt, ok := nodes.ifaceToLinkType[sourceMAC]; ok && lt != OtherLinkType {
|
||||
link.Type = lt
|
||||
} else if lt, ok := nodes.ifaceToLinkType[neighbourMAC]; ok {
|
||||
link.Type = lt
|
||||
}
|
||||
|
||||
result = append(result, link)
|
||||
}
|
||||
|
@ -141,27 +150,39 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
|
|||
for _, iface := range neighbours.Babel {
|
||||
for neighbourIP, link := range iface.Neighbours {
|
||||
if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" {
|
||||
result = append(result, Link{
|
||||
link := Link{
|
||||
SourceID: neighbours.NodeID,
|
||||
SourceAddress: iface.LinkLocalAddress,
|
||||
TargetID: neighbourID,
|
||||
TargetAddress: neighbourIP,
|
||||
TQ: 1.0 - (float32(link.Cost) / 65535.0),
|
||||
})
|
||||
}
|
||||
if lt, ok := nodes.ifaceToLinkType[iface.LinkLocalAddress]; ok && lt != OtherLinkType {
|
||||
link.Type = lt
|
||||
} else if lt, ok := nodes.ifaceToLinkType[neighbourIP]; ok {
|
||||
link.Type = lt
|
||||
}
|
||||
result = append(result, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
for portmac, neighmacs := range neighbours.LLDP {
|
||||
for _, neighmac := range neighmacs {
|
||||
if neighbourID := nodes.ifaceToNodeID[neighmac]; neighbourID != "" {
|
||||
result = append(result, Link{
|
||||
for sourceMAC, neighmacs := range neighbours.LLDP {
|
||||
for _, neighbourMAC := range neighmacs {
|
||||
if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" {
|
||||
link := Link{
|
||||
SourceID: neighbours.NodeID,
|
||||
SourceAddress: portmac,
|
||||
SourceAddress: sourceMAC,
|
||||
TargetID: neighbourID,
|
||||
TargetAddress: neighmac,
|
||||
TargetAddress: neighbourMAC,
|
||||
// TODO maybe change LLDP for link quality / 100M or 1GE
|
||||
TQ: 1.0,
|
||||
})
|
||||
}
|
||||
if lt, ok := nodes.ifaceToLinkType[sourceMAC]; ok && lt != OtherLinkType {
|
||||
link.Type = lt
|
||||
} else if lt, ok := nodes.ifaceToLinkType[neighbourMAC]; ok {
|
||||
link.Type = lt
|
||||
}
|
||||
result = append(result, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,8 +228,18 @@ func (nodes *Nodes) expire() {
|
|||
}
|
||||
}
|
||||
|
||||
func updateIface[K string | LinkProtocol | LinkType](class string, addr string, dataMap map[string]K, value K, warning bool) {
|
||||
if oldValue := dataMap[addr]; oldValue != value {
|
||||
var empty K
|
||||
if oldValue != empty && warning {
|
||||
log.Warnf("override %s from %s to %s on %s", class, oldValue, value, addr)
|
||||
}
|
||||
dataMap[addr] = value
|
||||
}
|
||||
}
|
||||
|
||||
// adds the nodes interface addresses to the internal map
|
||||
func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) {
|
||||
func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, neighbours *data.Neighbours, warning bool) {
|
||||
nodeID := nodeinfo.NodeID
|
||||
network := nodeinfo.Network
|
||||
|
||||
|
@ -220,6 +251,15 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) {
|
|||
addresses := []string{network.Mac}
|
||||
|
||||
for _, iface := range network.Mesh {
|
||||
for _, addr := range iface.Interfaces.Wireless {
|
||||
updateIface("interface-type", addr, nodes.ifaceToLinkType, WirelessLinkType, warning)
|
||||
}
|
||||
for _, addr := range iface.Interfaces.Tunnel {
|
||||
updateIface("interface-type", addr, nodes.ifaceToLinkType, TunnelLinkType, warning)
|
||||
}
|
||||
for _, addr := range iface.Interfaces.Other {
|
||||
updateIface("interface-type", addr, nodes.ifaceToLinkType, OtherLinkType, warning)
|
||||
}
|
||||
addresses = append(addresses, iface.Addresses()...)
|
||||
}
|
||||
|
||||
|
@ -227,13 +267,32 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) {
|
|||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
if oldNodeID := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID {
|
||||
if oldNodeID != "" && warning {
|
||||
log.Warnf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, addr)
|
||||
}
|
||||
nodes.ifaceToNodeID[addr] = nodeID
|
||||
updateIface("nodeID", addr, nodes.ifaceToNodeID, nodeID, warning)
|
||||
}
|
||||
|
||||
if neighbours == nil || neighbours.NodeID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for sourceMAC, batadv := range neighbours.Batadv {
|
||||
updateIface("mesh-protocol", sourceMAC, nodes.ifaceToLinkProtocol, BatadvLinkProtocol, warning)
|
||||
for neighbourMAC := range batadv.Neighbours {
|
||||
updateIface("mesh-protocol", neighbourMAC, nodes.ifaceToLinkProtocol, BatadvLinkProtocol, warning)
|
||||
}
|
||||
}
|
||||
for _, iface := range neighbours.Babel {
|
||||
updateIface("mesh-protocol", iface.LinkLocalAddress, nodes.ifaceToLinkProtocol, BabelLinkProtocol, warning)
|
||||
for neighbourIP := range iface.Neighbours {
|
||||
updateIface("mesh-protocol", neighbourIP, nodes.ifaceToLinkProtocol, BabelLinkProtocol, warning)
|
||||
}
|
||||
}
|
||||
for portmac, neighmacs := range neighbours.LLDP {
|
||||
updateIface("mesh-protocol", portmac, nodes.ifaceToLinkProtocol, LLDPLinkProtocol, warning)
|
||||
for _, neighmac := range neighmacs {
|
||||
updateIface("mesh-protocol", neighmac, nodes.ifaceToLinkProtocol, LLDPLinkProtocol, warning)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (nodes *Nodes) load() {
|
||||
|
@ -246,7 +305,7 @@ func (nodes *Nodes) load() {
|
|||
nodes.Lock()
|
||||
for _, node := range nodes.List {
|
||||
if node.Nodeinfo != nil {
|
||||
nodes.readIfaces(node.Nodeinfo, false)
|
||||
nodes.readIfaces(node.Nodeinfo, node.Neighbours, false)
|
||||
}
|
||||
}
|
||||
nodes.Unlock()
|
||||
|
|
|
@ -18,9 +18,11 @@ func TestExpire(t *testing.T) {
|
|||
// to get default (100%) path of testing
|
||||
// config.PruneAfter.Duration = time.Hour * 24 * 6
|
||||
nodes := &Nodes{
|
||||
config: config,
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
config: config,
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
ifaceToLinkType: make(map[string]LinkType),
|
||||
ifaceToLinkProtocol: make(map[string]LinkProtocol),
|
||||
}
|
||||
|
||||
nodes.Update("expire", &data.ResponseData{}) // should expire
|
||||
|
@ -89,8 +91,10 @@ func TestLoadAndSave(t *testing.T) {
|
|||
func TestUpdateNodes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
nodes := &Nodes{
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
ifaceToLinkType: make(map[string]LinkType),
|
||||
ifaceToLinkProtocol: make(map[string]LinkProtocol),
|
||||
}
|
||||
assert.Len(nodes.List, 0)
|
||||
|
||||
|
@ -156,8 +160,10 @@ func TestLinksNodes(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
nodes := &Nodes{
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
List: make(map[string]*Node),
|
||||
ifaceToNodeID: make(map[string]string),
|
||||
ifaceToLinkType: make(map[string]LinkType),
|
||||
ifaceToLinkProtocol: make(map[string]LinkProtocol),
|
||||
}
|
||||
assert.Len(nodes.List, 0)
|
||||
|
||||
|
|
Loading…
Reference in New Issue