master
go 285 lines 8.21 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "strings"
7 )
8
9 type topologyDisplayNameResolver struct {
10 lookup func(ip string) string
11 cache map[string]string
12 }
13
14 type topologyDisplayName struct {
15 name string
16 source string
17 }
18
19 func applyTopologyDisplayNames(actors []Actor, links []Link, lookup func(ip string) string) {
20 resolver := topologyDisplayNameResolver{
21 lookup: lookup,
22 cache: make(map[string]string),
23 }
24
25 deviceDisplayByID := make(map[string]string, len(actors))
26 displayByMatchKey := make(map[string]string, len(actors))
27
28 // First pass: materialize display names for non-segment actors so segment naming can reuse them.
29 for i := range actors {
30 if actors[i].ActorType == "segment" {
31 continue
32 }
33 display := topologyActorDisplayName(actors[i], nil, &resolver)
34 if display.name == "" {
35 display = topologyFallbackActorDisplayName(actors[i])
36 }
37 topologySetActorDisplay(&actors[i], display)
38 if matchKey := canonicalTopologyMatchKey(actors[i].Match); matchKey != "" {
39 displayByMatchKey[matchKey] = display.name
40 }
41 if IsDeviceActorType(actors[i].ActorType) {
42 if deviceID := topologyActorDeviceID(actors[i]); deviceID != "" {
43 deviceDisplayByID[deviceID] = display.name
44 }
45 }
46 }
47
48 // Second pass: segment display names depend on finalized device display names.
49 for i := range actors {
50 if actors[i].ActorType != "segment" {
51 continue
52 }
53 display := topologyActorDisplayName(actors[i], deviceDisplayByID, &resolver)
54 if display.name == "" {
55 display = topologyFallbackActorDisplayName(actors[i])
56 }
57 topologySetActorDisplay(&actors[i], display)
58 if matchKey := canonicalTopologyMatchKey(actors[i].Match); matchKey != "" {
59 displayByMatchKey[matchKey] = display.name
60 }
61 }
62
63 for i := range links {
64 src := topologyEndpointDisplayName(links[i].Src, displayByMatchKey, &resolver)
65 if src.name == "" {
66 src = topologyDisplayName{name: "[unset]", source: "fallback"}
67 }
68 srcPortName := topologySetEndpointDisplayAndCanonicalPortName(&links[i].Src, src)
69
70 dst := topologyEndpointDisplayName(links[i].Dst, displayByMatchKey, &resolver)
71 if dst.name == "" {
72 dst = topologyDisplayName{name: "[unset]", source: "fallback"}
73 }
74 dstPortName := topologySetEndpointDisplayAndCanonicalPortName(&links[i].Dst, dst)
75
76 linkName := topologyCanonicalLinkName(src.name, srcPortName, dst.name, dstPortName)
77 if links[i].Metrics == nil {
78 links[i].Metrics = make(map[string]any)
79 }
80 links[i].Metrics["display_name"] = linkName
81 links[i].Metrics["src_port_name"] = srcPortName
82 links[i].Metrics["dst_port_name"] = dstPortName
83 }
84 }
85
86 func topologySetActorDisplay(actor *Actor, display topologyDisplayName) {
87 if actor == nil {
88 return
89 }
90 labels := cloneStringMap(actor.Labels)
91 if labels == nil {
92 labels = make(map[string]string)
93 }
94 labels["display_name"] = display.name
95 if display.source != "" {
96 labels["display_source"] = display.source
97 }
98 actor.Labels = labels
99
100 attrs := cloneAnyMap(actor.Attributes)
101 if attrs == nil {
102 attrs = make(map[string]any)
103 }
104 attrs["display_name"] = display.name
105 if display.source != "" {
106 attrs["display_source"] = display.source
107 }
108 actor.Attributes = pruneTopologyAttributes(attrs)
109 }
110
111 func topologySetEndpointDisplayAndCanonicalPortName(endpoint *LinkEndpoint, display topologyDisplayName) string {
112 if endpoint == nil {
113 return ""
114 }
115 attrs := cloneAnyMap(endpoint.Attributes)
116 if attrs == nil {
117 attrs = make(map[string]any)
118 }
119 attrs["display_name"] = display.name
120 if display.source != "" {
121 attrs["display_source"] = display.source
122 }
123 name := topologyCanonicalPortName(attrs)
124 attrs["port_name"] = name
125 endpoint.Attributes = pruneTopologyAttributes(attrs)
126 return name
127 }
128
129 func topologyEndpointDisplayName(endpoint LinkEndpoint, actorDisplayByMatch map[string]string, resolver *topologyDisplayNameResolver) topologyDisplayName {
130 if key := canonicalTopologyMatchKey(endpoint.Match); key != "" {
131 if name := strings.TrimSpace(actorDisplayByMatch[key]); name != "" {
132 return topologyDisplayName{name: name, source: "actor"}
133 }
134 }
135 return topologyDisplayNameFromMatch(endpoint.Match, resolver)
136 }
137
138 func topologyActorDisplayName(actor Actor, deviceDisplayByID map[string]string, resolver *topologyDisplayNameResolver) topologyDisplayName {
139 if actor.ActorType == "segment" {
140 if name := topologySegmentDisplayName(actor, deviceDisplayByID); name != "" {
141 return topologyDisplayName{name: name, source: "segment"}
142 }
143 }
144
145 display := topologyDisplayNameFromMatch(actor.Match, resolver)
146 if display.name != "" {
147 return display
148 }
149
150 if segmentID := topologyAttrString(actor.Attributes, "segment_id"); segmentID != "" {
151 return topologyDisplayName{name: topologyCompactSegmentID(segmentID), source: "segment_id"}
152 }
153 return topologyDisplayName{}
154 }
155
156 func topologyFallbackActorDisplayName(actor Actor) topologyDisplayName {
157 if matchKey := canonicalTopologyMatchKey(actor.Match); matchKey != "" {
158 return topologyDisplayName{name: matchKey, source: "fallback_match"}
159 }
160 if segmentID := topologyAttrString(actor.Attributes, "segment_id"); segmentID != "" {
161 return topologyDisplayName{name: topologyCompactSegmentID(segmentID), source: "segment_id"}
162 }
163 actorType := strings.TrimSpace(actor.ActorType)
164 if actorType == "" {
165 actorType = "actor"
166 }
167 return topologyDisplayName{name: actorType + ":[unset]", source: "fallback"}
168 }
169
170 func topologyActorDeviceID(actor Actor) string {
171 return topologyAttrString(actor.Attributes, "device_id")
172 }
173
174 func topologyDisplayNameFromMatch(match Match, resolver *topologyDisplayNameResolver) topologyDisplayName {
175 if dns := topologyMatchPreferredDNSName(match, resolver); dns != "" {
176 return topologyDisplayName{name: dns, source: "dns"}
177 }
178 if sysName := topologyMatchPreferredSysName(match); sysName != "" {
179 return topologyDisplayName{name: sysName, source: "sys_name"}
180 }
181 if hostname := topologyMatchPreferredHostname(match); hostname != "" {
182 return topologyDisplayName{name: hostname, source: "hostname"}
183 }
184 if ip := topologyMatchPreferredIP(match); ip != "" {
185 return topologyDisplayName{name: ip, source: "ip"}
186 }
187 if mac := topologyMatchPreferredMAC(match); mac != "" {
188 return topologyDisplayName{name: mac, source: "mac"}
189 }
190 return topologyDisplayName{}
191 }
192
193 func topologyMatchPreferredDNSName(match Match, resolver *topologyDisplayNameResolver) string {
194 candidates := make(map[string]struct{})
195 for _, value := range match.DNSNames {
196 if normalized := normalizeDNSName(value); normalized != "" {
197 candidates[normalized] = struct{}{}
198 }
199 }
200 for _, value := range match.IPAddresses {
201 if resolver == nil {
202 continue
203 }
204 if ip := normalizeTopologyIP(value); ip != "" {
205 if resolved := resolver.resolve(ip); resolved != "" {
206 candidates[resolved] = struct{}{}
207 }
208 }
209 }
210 names := sortedTopologySet(candidates)
211 if len(names) == 0 {
212 return ""
213 }
214 return names[0]
215 }
216
217 func topologyMatchPreferredSysName(match Match) string {
218 return strings.TrimSpace(match.SysName)
219 }
220
221 func topologyMatchPreferredHostname(match Match) string {
222 hostnames := uniqueTopologyStrings(match.Hostnames)
223 if len(hostnames) == 0 {
224 return ""
225 }
226 return hostnames[0]
227 }
228
229 func topologyMatchPreferredIP(match Match) string {
230 ips := make([]string, 0, len(match.IPAddresses))
231 for _, value := range match.IPAddresses {
232 if ip := normalizeTopologyIP(value); ip != "" {
233 ips = append(ips, ip)
234 }
235 }
236 ips = uniqueTopologyStrings(ips)
237 if len(ips) == 0 {
238 return ""
239 }
240 return ips[0]
241 }
242
243 func topologyMatchPreferredMAC(match Match) string {
244 macs := make([]string, 0, len(match.MacAddresses)+len(match.ChassisIDs))
245 for _, value := range match.MacAddresses {
246 if mac := normalizeMAC(value); mac != "" {
247 macs = append(macs, mac)
248 }
249 }
250 for _, value := range match.ChassisIDs {
251 if mac := normalizeMAC(value); mac != "" {
252 macs = append(macs, mac)
253 }
254 }
255 macs = uniqueTopologyStrings(macs)
256 if len(macs) == 0 {
257 return ""
258 }
259 return macs[0]
260 }
261
262 func normalizeDNSName(name string) string {
263 name = strings.TrimSpace(name)
264 name = strings.TrimSuffix(name, ".")
265 if name == "" {
266 return ""
267 }
268 return strings.ToLower(name)
269 }
270
271 func (r *topologyDisplayNameResolver) resolve(ip string) string {
272 if r == nil || r.lookup == nil {
273 return ""
274 }
275 ip = normalizeTopologyIP(ip)
276 if ip == "" {
277 return ""
278 }
279 if name, ok := r.cache[ip]; ok {
280 return name
281 }
282 name := normalizeDNSName(r.lookup(ip))
283 r.cache[ip] = name
284 return name
285 }