master
go 186 lines 5.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "strings"
7 )
8
9 func topologyDeviceInferred(dev Device) bool {
10 if len(dev.Labels) == 0 {
11 return false
12 }
13 switch strings.ToLower(strings.TrimSpace(dev.Labels["inferred"])) {
14 case "1", "true", "yes", "on":
15 return true
16 default:
17 return false
18 }
19 }
20
21 func buildDeviceActorMatch(dev Device, reporterAliases []string) Match {
22 match := Match{
23 SysObjectID: strings.TrimSpace(dev.SysObject),
24 SysName: strings.TrimSpace(dev.Hostname),
25 }
26
27 macSet := make(map[string]struct{}, 1+len(reporterAliases))
28 chassis := strings.TrimSpace(dev.ChassisID)
29 if chassis != "" {
30 match.ChassisIDs = []string{chassis}
31 if mac := normalizeMAC(chassis); mac != "" {
32 macSet[mac] = struct{}{}
33 }
34 }
35 for _, alias := range reporterAliases {
36 alias = strings.TrimSpace(alias)
37 if alias == "" {
38 continue
39 }
40 if after, ok := strings.CutPrefix(alias, "mac:"); ok {
41 if mac := normalizeMAC(after); mac != "" {
42 macSet[mac] = struct{}{}
43 }
44 continue
45 }
46 if mac := normalizeMAC(alias); mac != "" {
47 macSet[mac] = struct{}{}
48 }
49 }
50 if len(macSet) > 0 {
51 match.MacAddresses = sortedTopologySet(macSet)
52 }
53
54 if len(dev.Addresses) > 0 {
55 ips := make([]string, 0, len(dev.Addresses))
56 for _, addr := range dev.Addresses {
57 if !addr.IsValid() {
58 continue
59 }
60 ips = append(ips, addr.String())
61 }
62 match.IPAddresses = uniqueTopologyStrings(ips)
63 }
64
65 return match
66 }
67
68 func buildDeviceActorAttributes(
69 dev Device,
70 localDeviceID string,
71 ifaceSummary topologyDeviceInterfaceSummary,
72 match Match,
73 ) map[string]any {
74 discovered := strings.TrimSpace(localDeviceID) == "" || dev.ID != localDeviceID
75
76 attrs := map[string]any{
77 "device_id": dev.ID,
78 "discovered": discovered,
79 "inferred": topologyDeviceInferred(dev),
80 "management_ip": firstAddress(dev.Addresses),
81 "management_addresses": addressStrings(dev.Addresses),
82 "protocols": labelsCSVToSlice(dev.Labels, "protocols_observed"),
83 "protocols_collected": labelsCSVToSlice(dev.Labels, "protocols_observed"),
84 "capabilities": labelsCSVToSlice(dev.Labels, "capabilities"),
85 "capabilities_supported": labelsCSVToSlice(dev.Labels, "capabilities_supported"),
86 "capabilities_enabled": labelsCSVToSlice(dev.Labels, "capabilities_enabled"),
87 }
88 derivedVendor, derivedPrefix := inferTopologyVendorFromMatch(match)
89 if derivedVendor != "" {
90 attrs["vendor_derived"] = derivedVendor
91 attrs["vendor_derived_source"] = "mac_oui"
92 attrs["vendor_derived_confidence"] = "low"
93 attrs["vendor_derived_match_prefix"] = derivedPrefix
94 }
95 if vendor := strings.TrimSpace(dev.Labels["vendor"]); vendor != "" {
96 attrs["vendor"] = vendor
97 attrs["vendor_source"] = "labels"
98 attrs["vendor_confidence"] = "high"
99 } else if derivedVendor != "" {
100 attrs["vendor"] = derivedVendor
101 attrs["vendor_source"] = "mac_oui"
102 attrs["vendor_confidence"] = "low"
103 attrs["vendor_match_prefix"] = derivedPrefix
104 }
105 if ifaceSummary.portsTotal > 0 {
106 attrs["ports_total"] = ifaceSummary.portsTotal
107 }
108 if len(ifaceSummary.ifIndexes) > 0 {
109 attrs["if_indexes"] = ifaceSummary.ifIndexes
110 }
111 if len(ifaceSummary.ifNames) > 0 {
112 attrs["if_names"] = ifaceSummary.ifNames
113 }
114 if ifaceSummary.portsUp > 0 {
115 attrs["ports_up"] = ifaceSummary.portsUp
116 }
117 if ifaceSummary.portsDown > 0 {
118 attrs["ports_down"] = ifaceSummary.portsDown
119 }
120 if ifaceSummary.portsAdminDown > 0 {
121 attrs["ports_admin_down"] = ifaceSummary.portsAdminDown
122 }
123 if ifaceSummary.totalBandwidthBps > 0 {
124 attrs["total_bandwidth_bps"] = ifaceSummary.totalBandwidthBps
125 }
126 if ifaceSummary.fdbTotalMACs > 0 {
127 attrs["fdb_total_macs"] = ifaceSummary.fdbTotalMACs
128 }
129 if ifaceSummary.vlanCount > 0 {
130 attrs["vlan_count"] = ifaceSummary.vlanCount
131 }
132 if ifaceSummary.lldpNeighborCount > 0 {
133 attrs["lldp_neighbor_count"] = ifaceSummary.lldpNeighborCount
134 }
135 if ifaceSummary.cdpNeighborCount > 0 {
136 attrs["cdp_neighbor_count"] = ifaceSummary.cdpNeighborCount
137 }
138 if len(ifaceSummary.adminStatusCount) > 0 {
139 attrs["if_admin_status_counts"] = ifaceSummary.adminStatusCount
140 }
141 if len(ifaceSummary.operStatusCount) > 0 {
142 attrs["if_oper_status_counts"] = ifaceSummary.operStatusCount
143 }
144 if len(ifaceSummary.linkModeCount) > 0 {
145 attrs["if_link_mode_counts"] = ifaceSummary.linkModeCount
146 }
147 if len(ifaceSummary.roleCount) > 0 {
148 attrs["if_topology_role_counts"] = ifaceSummary.roleCount
149 }
150 if len(ifaceSummary.portStatuses) > 0 {
151 attrs["if_statuses"] = ifaceSummary.portStatuses
152 }
153 return attrs
154 }
155
156 func buildDeviceActorTables(ifaceSummary topologyDeviceInterfaceSummary) map[string][]map[string]any {
157 if len(ifaceSummary.portStatuses) == 0 {
158 return nil
159 }
160
161 rows := make([]map[string]any, 0, len(ifaceSummary.portStatuses))
162 for _, ps := range ifaceSummary.portStatuses {
163 row := make(map[string]any, len(ps)+2)
164 for k, v := range ps {
165 switch k {
166 case "if_name":
167 row["name"] = v
168 case "if_type":
169 row["port_type"] = v
170 default:
171 row[k] = v
172 }
173 }
174 if neighbors, ok := ps["neighbors"]; ok {
175 switch nb := neighbors.(type) {
176 case []map[string]any:
177 row["neighbor_count"] = len(nb)
178 case []any:
179 row["neighbor_count"] = len(nb)
180 }
181 }
182 rows = append(rows, row)
183 }
184
185 return map[string][]map[string]any{"ports": rows}
186 }