@cryptotaxi247 / netdata / commits / e1fae9304

chore(go.d): retire legacy Go topology payloads (#22614)

Ilya Mashchenko committed Jun 3, 2026 at 14:17 UTC e1fae9304827db54792daebbbd38baed71dd7fbb
59 files changed +9027 -1549
.agents/skills/project-create-topology/SKILL.md
+3 -2
@@ -41,8 +41,9 @@ developer-facing and must stay in this project skill, not under
41 ## Core Rules
42
43 - Production payloads carry canonical topology facts for the aggregator and UI.
44 -- Go producers MUST use `src/go/pkg/topology/v1`. The non-v1
45 - `src/go/pkg/topology` payload model is legacy for new producers.
44 +- Go producers MUST use `src/go/pkg/topology/v1`. The non-v1 root
45 + `src/go/pkg/topology` payload model has been retired and MUST NOT be
46 + reintroduced for production topology payloads.
47 - Test-only projection code may reconstruct compatibility payload shapes to
48 prove parity.
49 - Never add compatibility reconstruction fields, old-schema adapter names, or
.agents/skills/project-writing-collectors/SKILL.md
+3 -2
@@ -478,8 +478,9 @@ Topology is its own data type — directed/undirected graphs of nodes and links.
478 - **Live socket topology** (`src/collectors/network-viewer.plugin/`) — local L3/L4 sockets and their inferred connections.
479 - **Streaming graph** (`src/streaming/`) — Netdata parent/child topology.
480 - **Topology library** at `src/go/pkg/topology/v1` — production Go payload
481 - helpers for new topology producers. The non-v1 `src/go/pkg/topology/` payload
482 - model is legacy and must not be used for new topology work.
481 + helpers for new topology producers. The non-v1 root
482 + `src/go/pkg/topology/` payload model has been retired and must not be
483 + reintroduced for topology work.
484
485 Topology is consumed via Functions (`topology:*` family), not via metrics. The cardinality of network edges is too high for time-series storage and the use case is interactive lookup.
486
.codacy.yml
+2 -2
@@ -15,7 +15,7 @@ exclude_paths:
15 - src/aclk/mqtt_websockets/**
16 - TODO-netflow-code-organization.md
17 - TODO-netflow-plugin-refactor.md
18 - - src/go/pkg/topology/engine/parity/README.md
19 - - src/go/pkg/topology/engine/parity/evidence/**
18 + - src/go/pkg/l2topology/parity/README.md
19 + - src/go/pkg/l2topology/parity/evidence/**
20 - .agents/**
21 - docs/netdata-ai/skills/**
src/go/pkg/l2topology/graph_types.go renamed
+2 -84
@@ -1,6 +1,6 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -package topology
3 +package l2topology
4
5 import "time"
6
@@ -54,88 +54,6 @@ type Link struct {
54 Metrics map[string]any `json:"metrics,omitempty"`
55 }
56
57 -// Presentation defines how the UI should render this topology.
58 -// Sent in the info response, not in data responses.
59 -
60 -type PresentationSummaryField struct {
61 - Key string `json:"key"`
62 - Label string `json:"label"`
63 - Sources []string `json:"sources"`
64 -}
65 -
66 -type PresentationTableColumn struct {
67 - Key string `json:"key"`
68 - Label string `json:"label"`
69 - Type string `json:"type,omitempty"`
70 -}
71 -
72 -type PresentationTable struct {
73 - Label string `json:"label"`
74 - Source string `json:"source"`
75 - BulletSource bool `json:"bullet_source,omitempty"`
76 - Order int `json:"order,omitempty"`
77 - Columns []PresentationTableColumn `json:"columns"`
78 -}
79 -
80 -type PresentationModalTab struct {
81 - ID string `json:"id"`
82 - Label string `json:"label"`
83 - Type string `json:"type,omitempty"`
84 -}
85 -
86 -type PresentationActorType struct {
87 - Label string `json:"label"`
88 - ColorSlot string `json:"color_slot"`
89 - Opacity float64 `json:"opacity,omitempty"`
90 - Border bool `json:"border"`
91 - Role string `json:"role,omitempty"`
92 - SizeByLinks bool `json:"size_by_links,omitempty"`
93 - ShowPortBullets bool `json:"show_port_bullets,omitempty"`
94 - IconSVG string `json:"icon_svg,omitempty"`
95 - SummaryFields []PresentationSummaryField `json:"summary_fields"`
96 - Tables map[string]PresentationTable `json:"tables"`
97 - ModalTabs []PresentationModalTab `json:"modal_tabs"`
98 -}
99 -
100 -type PresentationLinkType struct {
101 - Label string `json:"label"`
102 - ColorSlot string `json:"color_slot"`
103 - Opacity float64 `json:"opacity,omitempty"`
104 - Width float64 `json:"width,omitempty"`
105 - Dash bool `json:"dash,omitempty"`
106 -}
107 -
108 -type PresentationPortType struct {
109 - Label string `json:"label"`
110 - ColorSlot string `json:"color_slot"`
111 - Opacity float64 `json:"opacity,omitempty"`
112 -}
113 -
114 -type PresentationPortField struct {
115 - Key string `json:"key"`
116 - Label string `json:"label"`
117 -}
118 -
119 -type PresentationLegendEntry struct {
120 - Type string `json:"type"`
121 - Label string `json:"label"`
122 -}
123 -
124 -type PresentationLegend struct {
125 - Actors []PresentationLegendEntry `json:"actors"`
126 - Links []PresentationLegendEntry `json:"links"`
127 - Ports []PresentationLegendEntry `json:"ports,omitempty"`
128 -}
129 -
130 -type Presentation struct {
131 - ActorTypes map[string]PresentationActorType `json:"actor_types"`
132 - LinkTypes map[string]PresentationLinkType `json:"link_types"`
133 - PortTypes map[string]PresentationPortType `json:"port_types,omitempty"`
134 - PortFields []PresentationPortField `json:"port_fields,omitempty"`
135 - Legend PresentationLegend `json:"legend"`
136 - ActorClickBehavior string `json:"actor_click_behavior"`
137 -}
138 -
57 type FlowExporter struct {
58 IP string `json:"ip,omitempty"`
59 Name string `json:"name,omitempty"`
@@ -164,7 +82,7 @@ type IPPolicy struct {
82 LiveTopN *LiveTopN `json:"live_top_n,omitempty"`
83 }
84
167 -type Data struct {
85 +type Graph struct {
86 SchemaVersion string `json:"schema_version"`
87 Source string `json:"source,omitempty"`
88 Layer string `json:"layer,omitempty"`
src/go/pkg/l2topology/mac_oui_lookup.go
+1 -3
@@ -7,8 +7,6 @@ import (
7 "sort"
8 "strings"
9 "sync"
10 -
11 - "github.com/netdata/netdata/go/plugins/pkg/topology"
10 )
11
12 //go:embed mac_oui_vendors.tsv
@@ -116,7 +114,7 @@ func lookupTopologyVendorByMACInIndex(index topologyOUIVendorIndex, mac string)
114 return "", ""
115 }
116
119 -func inferTopologyVendorFromMatch(match topology.Match) (vendor string, prefix string) {
117 +func inferTopologyVendorFromMatch(match Match) (vendor string, prefix string) {
118 candidates := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
119 for _, value := range match.MacAddresses {
120 if mac := normalizeMAC(value); mac != "" {
src/go/pkg/l2topology/mac_oui_lookup_test.go
+2 -4
@@ -3,11 +3,9 @@
3 package l2topology
4
5 import (
6 + "github.com/stretchr/testify/require"
7 "strings"
8 "testing"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
10 - "github.com/stretchr/testify/require"
9 )
10
11 func TestBuildTopologyOUIVendorIndex_IgnoresInvalidLinesAndKeepsFirstDuplicate(t *testing.T) {
@@ -49,7 +47,7 @@ func TestLookupTopologyVendorByMACInIndex_PrefersLongestPrefixAndNormalizesMAC(t
47 }
48
49 func TestInferTopologyVendorFromMatch_UsesDeterministicCandidateOrder(t *testing.T) {
52 - match := topology.Match{
50 + match := Match{
51 MacAddresses: []string{
52 "28:6f:b9:00:00:22",
53 "08:ea:44:11:22:33",
src/go/pkg/l2topology/parity/README.md
+3 -3
@@ -2,7 +2,7 @@
2
3 ## Scope
4 - Validate topology engine parity against imported Enlinkd fixtures and assertion inventories.
5 -- Evidence files live in `src/go/pkg/topology/engine/parity/evidence`.
5 +- Evidence files live in `src/go/pkg/l2topology/parity/evidence`.
6
7 ## Prerequisites
8 - Upstream checkout available at `/tmp/topology-library-repos/enlinkd`.
@@ -54,7 +54,7 @@ Outputs:
54
55 ## Run Go Test Gates
56 ```bash
57 -go test ./pkg/topology/engine/... ./plugin/go.d/collector/snmp ./tools/topology-parity-evidence -count=1
57 +go test ./pkg/l2topology/... ./plugin/go.d/collector/snmp ./tools/topology-parity-evidence -count=1
58 ```
59
60 Expected:
@@ -67,7 +67,7 @@ Expected:
67 2. `suite` has failed scenarios:
68 - Open `parity-summary.json`.
69 - Inspect failing scenario IDs and manifests.
70 - - Re-run targeted tests in `./pkg/topology/engine/parity`.
70 + - Re-run targeted tests in `./pkg/l2topology/parity`.
71 3. Coverage totals regress:
72 - Check `assertion-mapping.csv` uniqueness and status values.
73 - Reconcile `not-applicable-approved.csv` with mapping rows using `not-applicable-approved`.
src/go/pkg/l2topology/topology_adapter.go
+8 -10
@@ -6,12 +6,10 @@ import (
6 "net/netip"
7 "strings"
8 "time"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
13 -// TopologyDataOptions controls conversion from Result to topology.Data.
14 -type TopologyDataOptions struct {
11 +// GraphOptions controls conversion from Result to the internal graph projection.
12 +type GraphOptions struct {
13 SchemaVersion string
14 Source string
15 Layer string
@@ -56,8 +54,8 @@ type endpointActorAccumulator struct {
54 }
55
56 type projectedSegments struct {
59 - actors []topology.Actor
60 - links []topology.Link
57 + actors []Actor
58 + links []Link
59 linksFdb int
60 bidirectionalCount int
61 endpointLinksCandidates int
@@ -221,9 +219,9 @@ func topologyInferenceStrategyConfigFor(strategy string) topologyInferenceStrate
219 }
220 }
221
224 -// ToTopologyData converts an engine result to the shared topology schema.
225 -func ToTopologyData(result Result, opts TopologyDataOptions) topology.Data {
226 - builder := newTopologyDataBuilder(result, opts)
222 +// ToGraph converts an engine result to the internal graph projection.
223 +func ToGraph(result Result, opts GraphOptions) Graph {
224 + builder := newGraphBuilder(result, opts)
225 builder.prepareIndexes()
226 builder.collectBridgeTopologyInputs()
227 builder.buildDeviceActors()
@@ -232,5 +230,5 @@ func ToTopologyData(result Result, opts TopologyDataOptions) topology.Data {
230 builder.buildSegmentTopology()
231 builder.finalizeGraph()
232 builder.buildStats()
235 - return builder.data()
233 + return builder.graph()
234 }
src/go/pkg/l2topology/topology_adapter_actor_collapse.go
+9 -11
@@ -5,11 +5,9 @@ package l2topology
5 import (
6 "sort"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func collapseActorsByIP(actors []topology.Actor) []topology.Actor {
10 +func collapseActorsByIP(actors []Actor) []Actor {
11 if len(actors) <= 1 {
12 return actors
13 }
@@ -98,7 +96,7 @@ func collapseActorsByIP(actors []topology.Actor) []topology.Actor {
96 actors[rep] = merged
97 }
98
101 - out := make([]topology.Actor, 0, len(actors))
99 + out := make([]Actor, 0, len(actors))
100 for idx, actor := range actors {
101 if !keep[idx] {
102 continue
@@ -108,12 +106,12 @@ func collapseActorsByIP(actors []topology.Actor) []topology.Actor {
106 return out
107 }
108
111 -func eliminateNonIPInferredActors(actors []topology.Actor, links []topology.Link) ([]topology.Actor, []topology.Link) {
109 +func eliminateNonIPInferredActors(actors []Actor, links []Link) ([]Actor, []Link) {
110 if len(actors) == 0 {
111 return actors, links
112 }
113 removedIdentityKeys := make(map[string]struct{})
116 - filteredActors := make([]topology.Actor, 0, len(actors))
114 + filteredActors := make([]Actor, 0, len(actors))
115 for _, actor := range actors {
116 if topologyActorIsInferred(actor) && len(normalizedTopologyActorIPs(actor)) == 0 {
117 for _, key := range topologyMatchIdentityKeys(actor.Match) {
@@ -127,7 +125,7 @@ func eliminateNonIPInferredActors(actors []topology.Actor, links []topology.Link
125 return actors, links
126 }
127
130 - filteredLinks := make([]topology.Link, 0, len(links))
128 + filteredLinks := make([]Link, 0, len(links))
129 for _, link := range links {
130 srcKeys := topologyMatchIdentityKeys(link.Src.Match)
131 dstKeys := topologyMatchIdentityKeys(link.Dst.Match)
@@ -154,7 +152,7 @@ func topologyIdentityKeysOverlap(keys []string, set map[string]struct{}) bool {
152 return false
153 }
154
157 -func normalizedTopologyActorIPs(actor topology.Actor) []string {
155 +func normalizedTopologyActorIPs(actor Actor) []string {
156 if len(actor.Match.IPAddresses) == 0 {
157 return nil
158 }
@@ -175,7 +173,7 @@ func normalizedTopologyActorIPs(actor topology.Actor) []string {
173 return out
174 }
175
178 -func compareTopologyActorCollapsePriority(left, right topology.Actor) int {
176 +func compareTopologyActorCollapsePriority(left, right Actor) int {
177 leftDevice := IsDeviceActorType(left.ActorType)
178 rightDevice := IsDeviceActorType(right.ActorType)
179 if leftDevice != rightDevice {
@@ -197,7 +195,7 @@ func compareTopologyActorCollapsePriority(left, right topology.Actor) int {
195 return strings.Compare(leftKey, rightKey)
196 }
197
200 -func mergeTopologyActorMatch(base, other topology.Match) topology.Match {
198 +func mergeTopologyActorMatch(base, other Match) Match {
199 base.ChassisIDs = mergeTopologyStringLists(base.ChassisIDs, other.ChassisIDs)
200 base.MacAddresses = mergeTopologyStringLists(base.MacAddresses, other.MacAddresses)
201 base.IPAddresses = mergeTopologyStringLists(base.IPAddresses, other.IPAddresses)
@@ -274,7 +272,7 @@ func mergeTopologyActorAttributes(base, extra map[string]any) map[string]any {
272 return base
273 }
274
277 -func topologyActorIsInferred(actor topology.Actor) bool {
275 +func topologyActorIsInferred(actor Actor) bool {
276 if strings.EqualFold(strings.TrimSpace(actor.ActorType), "endpoint") {
277 return true
278 }
src/go/pkg/l2topology/topology_adapter_builder.go
+18 -20
@@ -5,13 +5,11 @@ package l2topology
5 import (
6 "strings"
7 "time"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -type topologyDataBuilder struct {
10 +type graphBuilder struct {
11 result Result
14 - opts TopologyDataOptions
12 + opts GraphOptions
13
14 schemaVersion string
15 source string
@@ -28,7 +26,7 @@ type topologyDataBuilder struct {
26 reporterAliases map[string][]string
27 ifaceSummaryByDevice map[string]topologyDeviceInterfaceSummary
28
31 - actors []topology.Actor
29 + actors []Actor
30 actorIndex map[string]struct{}
31 actorMACIndex map[string]struct{}
32
@@ -36,7 +34,7 @@ type topologyDataBuilder struct {
34 endpointActors builtEndpointActors
35 segmentProjection projectedSegments
36
39 - links []topology.Link
37 + links []Link
38 segmentSuppressed int
39 unlinkedSuppressed int
40 linkCounts topologyLinkCounts
@@ -44,8 +42,8 @@ type topologyDataBuilder struct {
42 stats map[string]any
43 }
44
47 -func newTopologyDataBuilder(result Result, opts TopologyDataOptions) *topologyDataBuilder {
48 - builder := &topologyDataBuilder{
45 +func newGraphBuilder(result Result, opts GraphOptions) *graphBuilder {
46 + builder := &graphBuilder{
47 result: result,
48 opts: opts,
49 }
@@ -82,7 +80,7 @@ func newTopologyDataBuilder(result Result, opts TopologyDataOptions) *topologyDa
80 return builder
81 }
82
85 -func (b *topologyDataBuilder) prepareIndexes() {
83 +func (b *graphBuilder) prepareIndexes() {
84 b.deviceByID = make(map[string]Device, len(b.result.Devices))
85 b.ifaceByDeviceIndex = make(map[string]Interface, len(b.result.Interfaces))
86 b.ifIndexByDeviceName = make(map[string]int, len(b.result.Interfaces))
@@ -102,7 +100,7 @@ func (b *topologyDataBuilder) prepareIndexes() {
100 }
101 }
102
105 -func (b *topologyDataBuilder) collectBridgeTopologyInputs() {
103 +func (b *graphBuilder) collectBridgeTopologyInputs() {
104 b.bridgeLinks = collectBridgeLinkRecords(b.result.Adjacencies, b.ifIndexByDeviceName, b.strategyConfig)
105 b.reporterAliases = buildFDBReporterAliases(b.deviceByID, b.ifaceByDeviceIndex)
106 if b.strategyConfig.enableFDBPairwiseLinks {
@@ -131,8 +129,8 @@ func (b *topologyDataBuilder) collectBridgeTopologyInputs() {
129 )
130 }
131
134 -func (b *topologyDataBuilder) buildDeviceActors() {
135 - b.actors = make([]topology.Actor, 0, len(b.result.Devices))
132 +func (b *graphBuilder) buildDeviceActors() {
133 + b.actors = make([]Actor, 0, len(b.result.Devices))
134 b.actorIndex = make(map[string]struct{}, len(b.result.Devices)*2)
135 b.actorMACIndex = make(map[string]struct{}, len(b.result.Devices))
136
@@ -163,7 +161,7 @@ func (b *topologyDataBuilder) buildDeviceActors() {
161 }
162 }
163
166 -func (b *topologyDataBuilder) projectAdjacencyTopology() {
164 +func (b *graphBuilder) projectAdjacencyTopology() {
165 b.projectedAdjacencies = projectAdjacencyLinks(
166 b.result.Adjacencies,
167 b.layer,
@@ -174,7 +172,7 @@ func (b *topologyDataBuilder) projectAdjacencyTopology() {
172 )
173 }
174
177 -func (b *topologyDataBuilder) buildEndpointTopology() {
175 +func (b *graphBuilder) buildEndpointTopology() {
176 b.endpointActors = buildEndpointActors(
177 b.result.Attachments,
178 b.result.Enrichments,
@@ -187,7 +185,7 @@ func (b *topologyDataBuilder) buildEndpointTopology() {
185 b.actors = append(b.actors, b.endpointActors.actors...)
186 }
187
190 -func (b *topologyDataBuilder) buildSegmentTopology() {
188 +func (b *graphBuilder) buildSegmentTopology() {
189 b.segmentProjection = projectSegmentTopology(
190 b.result.Attachments,
191 b.result.Adjacencies,
@@ -214,10 +212,10 @@ func (b *topologyDataBuilder) buildSegmentTopology() {
212 b.actors = append(b.actors, b.segmentProjection.actors...)
213 }
214
217 -func (b *topologyDataBuilder) finalizeGraph() {
215 +func (b *graphBuilder) finalizeGraph() {
216 sortTopologyActors(b.actors)
217
220 - b.links = make([]topology.Link, 0, len(b.projectedAdjacencies.links)+len(b.segmentProjection.links))
218 + b.links = make([]Link, 0, len(b.projectedAdjacencies.links)+len(b.segmentProjection.links))
219 b.links = append(b.links, b.projectedAdjacencies.links...)
220 b.links = append(b.links, b.segmentProjection.links...)
221 sortTopologyLinks(b.links)
@@ -258,7 +256,7 @@ func (b *topologyDataBuilder) finalizeGraph() {
256 }
257 }
258
261 -func (b *topologyDataBuilder) buildStats() {
259 +func (b *graphBuilder) buildStats() {
260 b.stats = cloneAnyMap(b.result.Stats)
261 if b.stats == nil {
262 b.stats = make(map[string]any)
@@ -285,8 +283,8 @@ func (b *topologyDataBuilder) buildStats() {
283 b.stats["inference_strategy"] = b.strategyConfig.id
284 }
285
288 -func (b *topologyDataBuilder) data() topology.Data {
289 - return topology.Data{
286 +func (b *graphBuilder) graph() Graph {
287 + return Graph{
288 SchemaVersion: b.schemaVersion,
289 Source: b.source,
290 Layer: b.layer,
src/go/pkg/l2topology/topology_adapter_device_actor.go
+3 -5
@@ -4,8 +4,6 @@ package l2topology
4
5 import (
6 "strings"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
7 )
8
9 func topologyDeviceInferred(dev Device) bool {
@@ -20,8 +18,8 @@ func topologyDeviceInferred(dev Device) bool {
18 }
19 }
20
23 -func buildDeviceActorMatch(dev Device, reporterAliases []string) topology.Match {
24 - match := topology.Match{
21 +func buildDeviceActorMatch(dev Device, reporterAliases []string) Match {
22 + match := Match{
23 SysObjectID: strings.TrimSpace(dev.SysObject),
24 SysName: strings.TrimSpace(dev.Hostname),
25 }
@@ -71,7 +69,7 @@ func buildDeviceActorAttributes(
69 dev Device,
70 localDeviceID string,
71 ifaceSummary topologyDeviceInterfaceSummary,
74 - match topology.Match,
72 + match Match,
73 ) map[string]any {
74 discovered := strings.TrimSpace(localDeviceID) == "" || dev.ID != localDeviceID
75
src/go/pkg/l2topology/topology_adapter_device_endpoint.go
+2 -4
@@ -5,11 +5,9 @@ package l2topology
5 import (
6 "strconv"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func adjacencySideToEndpoint(dev Device, port string, ifIndexByDeviceName map[string]int, ifaceByDeviceIndex map[string]Interface) topology.LinkEndpoint {
10 +func adjacencySideToEndpoint(dev Device, port string, ifIndexByDeviceName map[string]int, ifaceByDeviceIndex map[string]Interface) LinkEndpoint {
11 match := buildDeviceActorMatch(dev, nil)
12
13 port = strings.TrimSpace(port)
@@ -58,7 +56,7 @@ func adjacencySideToEndpoint(dev Device, port string, ifIndexByDeviceName map[st
56 }
57 }
58
61 - return topology.LinkEndpoint{
59 + return LinkEndpoint{
60 Match: match,
61 Attributes: pruneTopologyAttributes(attrs),
62 }
src/go/pkg/l2topology/topology_adapter_devices.go
+2 -4
@@ -2,19 +2,17 @@
2
3 package l2topology
4
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
6 -
5 func deviceToTopologyActor(
6 dev Device,
7 source, layer, localDeviceID string,
8 ifaceSummary topologyDeviceInterfaceSummary,
9 reporterAliases []string,
12 -) topology.Actor {
10 +) Actor {
11 match := buildDeviceActorMatch(dev, reporterAliases)
12 attrs := buildDeviceActorAttributes(dev, localDeviceID, ifaceSummary, match)
13 tables := buildDeviceActorTables(ifaceSummary)
14
17 - return topology.Actor{
15 + return Actor{
16 ActorType: resolveDeviceActorType(dev.Labels),
17 Layer: layer,
18 Source: source,
src/go/pkg/l2topology/topology_adapter_display.go
+13 -15
@@ -4,8 +4,6 @@ package l2topology
4
5 import (
6 "strings"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
7 )
8
9 type topologyDisplayNameResolver struct {
@@ -18,7 +16,7 @@ type topologyDisplayName struct {
16 source string
17 }
18
21 -func applyTopologyDisplayNames(actors []topology.Actor, links []topology.Link, lookup func(ip string) string) {
19 +func applyTopologyDisplayNames(actors []Actor, links []Link, lookup func(ip string) string) {
20 resolver := topologyDisplayNameResolver{
21 lookup: lookup,
22 cache: make(map[string]string),
@@ -85,7 +83,7 @@ func applyTopologyDisplayNames(actors []topology.Actor, links []topology.Link, l
83 }
84 }
85
88 -func topologySetActorDisplay(actor *topology.Actor, display topologyDisplayName) {
86 +func topologySetActorDisplay(actor *Actor, display topologyDisplayName) {
87 if actor == nil {
88 return
89 }
@@ -110,7 +108,7 @@ func topologySetActorDisplay(actor *topology.Actor, display topologyDisplayName)
108 actor.Attributes = pruneTopologyAttributes(attrs)
109 }
110
113 -func topologySetEndpointDisplayAndCanonicalPortName(endpoint *topology.LinkEndpoint, display topologyDisplayName) string {
111 +func topologySetEndpointDisplayAndCanonicalPortName(endpoint *LinkEndpoint, display topologyDisplayName) string {
112 if endpoint == nil {
113 return ""
114 }
@@ -128,7 +126,7 @@ func topologySetEndpointDisplayAndCanonicalPortName(endpoint *topology.LinkEndpo
126 return name
127 }
128
131 -func topologyEndpointDisplayName(endpoint topology.LinkEndpoint, actorDisplayByMatch map[string]string, resolver *topologyDisplayNameResolver) topologyDisplayName {
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"}
@@ -137,7 +135,7 @@ func topologyEndpointDisplayName(endpoint topology.LinkEndpoint, actorDisplayByM
135 return topologyDisplayNameFromMatch(endpoint.Match, resolver)
136 }
137
140 -func topologyActorDisplayName(actor topology.Actor, deviceDisplayByID map[string]string, resolver *topologyDisplayNameResolver) topologyDisplayName {
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"}
@@ -155,7 +153,7 @@ func topologyActorDisplayName(actor topology.Actor, deviceDisplayByID map[string
153 return topologyDisplayName{}
154 }
155
158 -func topologyFallbackActorDisplayName(actor topology.Actor) topologyDisplayName {
156 +func topologyFallbackActorDisplayName(actor Actor) topologyDisplayName {
157 if matchKey := canonicalTopologyMatchKey(actor.Match); matchKey != "" {
158 return topologyDisplayName{name: matchKey, source: "fallback_match"}
159 }
@@ -169,11 +167,11 @@ func topologyFallbackActorDisplayName(actor topology.Actor) topologyDisplayName
167 return topologyDisplayName{name: actorType + ":[unset]", source: "fallback"}
168 }
169
172 -func topologyActorDeviceID(actor topology.Actor) string {
170 +func topologyActorDeviceID(actor Actor) string {
171 return topologyAttrString(actor.Attributes, "device_id")
172 }
173
176 -func topologyDisplayNameFromMatch(match topology.Match, resolver *topologyDisplayNameResolver) topologyDisplayName {
174 +func topologyDisplayNameFromMatch(match Match, resolver *topologyDisplayNameResolver) topologyDisplayName {
175 if dns := topologyMatchPreferredDNSName(match, resolver); dns != "" {
176 return topologyDisplayName{name: dns, source: "dns"}
177 }
@@ -192,7 +190,7 @@ func topologyDisplayNameFromMatch(match topology.Match, resolver *topologyDispla
190 return topologyDisplayName{}
191 }
192
195 -func topologyMatchPreferredDNSName(match topology.Match, resolver *topologyDisplayNameResolver) string {
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 != "" {
@@ -216,11 +214,11 @@ func topologyMatchPreferredDNSName(match topology.Match, resolver *topologyDispl
214 return names[0]
215 }
216
219 -func topologyMatchPreferredSysName(match topology.Match) string {
217 +func topologyMatchPreferredSysName(match Match) string {
218 return strings.TrimSpace(match.SysName)
219 }
220
223 -func topologyMatchPreferredHostname(match topology.Match) string {
221 +func topologyMatchPreferredHostname(match Match) string {
222 hostnames := uniqueTopologyStrings(match.Hostnames)
223 if len(hostnames) == 0 {
224 return ""
@@ -228,7 +226,7 @@ func topologyMatchPreferredHostname(match topology.Match) string {
226 return hostnames[0]
227 }
228
231 -func topologyMatchPreferredIP(match topology.Match) string {
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 != "" {
@@ -242,7 +240,7 @@ func topologyMatchPreferredIP(match topology.Match) string {
240 return ips[0]
241 }
242
245 -func topologyMatchPreferredMAC(match topology.Match) string {
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 != "" {
src/go/pkg/l2topology/topology_adapter_display_segment.go
+1 -3
@@ -4,8 +4,6 @@ package l2topology
4
5 import (
6 "strings"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
7 )
8
9 type topologySegmentPortRef struct {
@@ -15,7 +13,7 @@ type topologySegmentPortRef struct {
13 bridgePort string
14 }
15
18 -func topologySegmentDisplayName(actor topology.Actor, deviceDisplayByID map[string]string) string {
16 +func topologySegmentDisplayName(actor Actor, deviceDisplayByID map[string]string) string {
17 attrs := actor.Attributes
18 if len(attrs) == 0 {
19 return ""
src/go/pkg/l2topology/topology_adapter_endpoints.go
+7 -9
@@ -7,14 +7,12 @@ import (
7 "sort"
8 "strconv"
9 "strings"
10 -
11 - "github.com/netdata/netdata/go/plugins/pkg/topology"
10 )
11
12 type builtEndpointActors struct {
15 - actors []topology.Actor
13 + actors []Actor
14 count int
17 - matchByEndpointID map[string]topology.Match
15 + matchByEndpointID map[string]Match
16 labelsByEndpointID map[string]map[string]string
17 }
18
@@ -92,7 +90,7 @@ func buildEndpointActors(
90
91 if len(accumulators) == 0 {
92 return builtEndpointActors{
95 - matchByEndpointID: map[string]topology.Match{},
93 + matchByEndpointID: map[string]Match{},
94 labelsByEndpointID: map[string]map[string]string{},
95 }
96 }
@@ -103,9 +101,9 @@ func buildEndpointActors(
101 }
102 sort.Strings(keys)
103
106 - actors := make([]topology.Actor, 0, len(keys))
104 + actors := make([]Actor, 0, len(keys))
105 endpointCount := 0
108 - matchByEndpointID := make(map[string]topology.Match, len(keys))
106 + matchByEndpointID := make(map[string]Match, len(keys))
107 labelsByEndpointID := make(map[string]map[string]string, len(keys))
108 for _, endpointID := range keys {
109 acc := accumulators[endpointID]
@@ -113,7 +111,7 @@ func buildEndpointActors(
111 continue
112 }
113
116 - match := topology.Match{}
114 + match := Match{}
115 if acc.mac != "" {
116 match.ChassisIDs = []string{acc.mac}
117 match.MacAddresses = []string{acc.mac}
@@ -145,7 +143,7 @@ func buildEndpointActors(
143 attrs["vendor_derived_confidence"] = "low"
144 attrs["vendor_derived_match_prefix"] = derivedPrefix
145 }
148 - actor := topology.Actor{
146 + actor := Actor{
147 ActorType: "endpoint",
148 Layer: layer,
149 Source: source,
src/go/pkg/l2topology/topology_adapter_identity.go
+3 -5
@@ -6,8 +6,6 @@ import (
6 "sort"
7 "strconv"
8 "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
11 var interfaceNameLookupSanitizer = strings.NewReplacer(
@@ -173,7 +171,7 @@ func buildDeviceIdentityKeySetByID(
171 return out
172 }
173
176 -func topologyMatchIdentityKeys(match topology.Match) []string {
174 +func topologyMatchIdentityKeys(match Match) []string {
175 seen := make(map[string]struct{}, 8)
176 add := func(kind, value string) {
177 value = strings.TrimSpace(value)
@@ -234,7 +232,7 @@ func topologyMatchIdentityKeys(match topology.Match) []string {
232 return keys
233 }
234
237 -func topologyMatchHardwareIdentityKeys(match topology.Match) []string {
235 +func topologyMatchHardwareIdentityKeys(match Match) []string {
236 seen := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
237 add := func(value string) {
238 if mac := normalizeMAC(value); mac != "" {
@@ -261,7 +259,7 @@ func topologyMatchHardwareIdentityKeys(match topology.Match) []string {
259 }
260
261 func endpointMatchOverlappingKnownDeviceIDs(
264 - endpointMatch topology.Match,
262 + endpointMatch Match,
263 deviceIdentityByID map[string]topologyIdentityKeySet,
264 ) []string {
265 if len(deviceIdentityByID) == 0 {
src/go/pkg/l2topology/topology_adapter_identity_assignment.go
+11 -13
@@ -6,8 +6,6 @@ import (
6 "fmt"
7 "sort"
8 "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
11 type topologyMatchLookup struct {
@@ -16,16 +14,16 @@ type topologyMatchLookup struct {
14 }
15
16 type topologyActorSortEntry struct {
19 - actor topology.Actor
17 + actor Actor
18 key string
19 }
20
21 type topologyLinkSortEntry struct {
24 - link topology.Link
22 + link Link
23 key string
24 }
25
28 -func canonicalTopologyMatchKey(match topology.Match) string {
26 +func canonicalTopologyMatchKey(match Match) string {
27 if key := canonicalTopologyPrimaryMACKey(match); key != "" {
28 return "mac:" + key
29 }
@@ -50,7 +48,7 @@ func canonicalTopologyMatchKey(match topology.Match) string {
48 return ""
49 }
50
53 -func assignTopologyActorIDsAndLinkEndpoints(actors []topology.Actor, links []topology.Link) {
51 +func assignTopologyActorIDsAndLinkEndpoints(actors []Actor, links []Link) {
52 if len(actors) == 0 {
53 return
54 }
@@ -95,7 +93,7 @@ func assignTopologyActorIDsAndLinkEndpoints(actors []topology.Actor, links []top
93 }
94 }
95
98 -func newTopologyMatchLookup(match topology.Match) topologyMatchLookup {
96 +func newTopologyMatchLookup(match Match) topologyMatchLookup {
97 return topologyMatchLookup{
98 canonical: canonicalTopologyMatchKey(match),
99 identityKeys: topologyMatchIdentityKeys(match),
@@ -131,7 +129,7 @@ func resolveTopologyEndpointActorID(lookup topologyMatchLookup, byCanonicalMatch
129 return ""
130 }
131
134 -func enrichTopologyPortTablesWithLinkCounts(actors []topology.Actor, links []topology.Link) {
132 +func enrichTopologyPortTablesWithLinkCounts(actors []Actor, links []Link) {
133 type actorPort struct {
134 actorID string
135 portName string
@@ -174,7 +172,7 @@ func enrichTopologyPortTablesWithLinkCounts(actors []topology.Actor, links []top
172 }
173 }
174
177 -func canonicalTopologyPrimaryMACKey(match topology.Match) string {
175 +func canonicalTopologyPrimaryMACKey(match Match) string {
176 set := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
177 for _, value := range match.MacAddresses {
178 if mac := normalizeMAC(value); mac != "" {
@@ -268,7 +266,7 @@ func canonicalTopologyStringListKey(values []string) string {
266 return strings.Join(out, ",")
267 }
268
271 -func topologyLinkSortKey(link topology.Link) string {
269 +func topologyLinkSortKey(link Link) string {
270 return strings.Join([]string{
271 link.Protocol,
272 link.Direction,
@@ -284,7 +282,7 @@ func topologyLinkSortKey(link topology.Link) string {
282 }, keySep)
283 }
284
287 -func topologyActorSortKey(actor topology.Actor) string {
285 +func topologyActorSortKey(actor Actor) string {
286 return strings.Join([]string{
287 actor.ActorType,
288 canonicalTopologyMatchKey(actor.Match),
@@ -304,7 +302,7 @@ func topologyAttrKey(attrs map[string]any, key string) string {
302 return fmt.Sprint(value)
303 }
304
307 -func sortTopologyActors(actors []topology.Actor) {
305 +func sortTopologyActors(actors []Actor) {
306 if len(actors) < 2 {
307 return
308 }
@@ -326,7 +324,7 @@ func sortTopologyActors(actors []topology.Actor) {
324 }
325 }
326
329 -func sortTopologyLinks(links []topology.Link) {
327 +func sortTopologyLinks(links []Link) {
328 if len(links) < 2 {
329 return
330 }
src/go/pkg/l2topology/topology_adapter_identity_assignment_test.go
+21 -23
@@ -3,10 +3,8 @@
3 package l2topology
4
5 import (
6 - "testing"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
6 "github.com/stretchr/testify/require"
7 + "testing"
8 )
9
10 func TestCanonicalTopologyKeyHelpers_NormalizeDeterministically(t *testing.T) {
@@ -21,12 +19,12 @@ func TestCanonicalTopologyKeyHelpers_NormalizeDeterministically(t *testing.T) {
19 }
20
21 func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
24 - actors := []topology.Actor{
22 + actors := []Actor{
23 {
24 ActorType: "device",
25 Layer: "l2",
26 Source: "snmp",
29 - Match: topology.Match{
27 + Match: Match{
28 MacAddresses: []string{"00:11:22:33:44:55"},
29 Hostnames: []string{"switch-a"},
30 },
@@ -35,7 +33,7 @@ func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
33 ActorType: "device",
34 Layer: "l2",
35 Source: "snmp",
38 - Match: topology.Match{
36 + Match: Match{
37 MacAddresses: []string{"00-11-22-33-44-55"},
38 Hostnames: []string{"switch-a-duplicate"},
39 },
@@ -44,7 +42,7 @@ func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
42 ActorType: "endpoint",
43 Layer: "l2",
44 Source: "derived",
47 - Match: topology.Match{
45 + Match: Match{
46 IPAddresses: []string{"10.0.0.2"},
47 },
48 },
@@ -52,22 +50,22 @@ func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
50 ActorType: "segment",
51 Layer: "l2",
52 Source: "derived",
55 - Match: topology.Match{},
53 + Match: Match{},
54 },
55 }
58 - links := []topology.Link{
56 + links := []Link{
57 {
58 Protocol: "lldp",
59 Direction: "outbound",
60 State: "up",
63 - Src: topology.LinkEndpoint{
64 - Match: topology.Match{MacAddresses: []string{"00:11:22:33:44:55"}},
61 + Src: LinkEndpoint{
62 + Match: Match{MacAddresses: []string{"00:11:22:33:44:55"}},
63 Attributes: map[string]any{
64 "if_name": "eth0",
65 },
66 },
69 - Dst: topology.LinkEndpoint{
70 - Match: topology.Match{IPAddresses: []string{"10.0.0.2"}},
67 + Dst: LinkEndpoint{
68 + Match: Match{IPAddresses: []string{"10.0.0.2"}},
69 Attributes: map[string]any{
70 "if_name": "eth9",
71 },
@@ -77,14 +75,14 @@ func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
75 Protocol: "lldp",
76 Direction: "outbound",
77 State: "up",
80 - Src: topology.LinkEndpoint{
81 - Match: topology.Match{IPAddresses: []string{"10.0.0.2"}},
78 + Src: LinkEndpoint{
79 + Match: Match{IPAddresses: []string{"10.0.0.2"}},
80 Attributes: map[string]any{
81 "if_name": "eth9",
82 },
83 },
86 - Dst: topology.LinkEndpoint{
87 - Match: topology.Match{MacAddresses: []string{"00:11:22:33:44:55"}},
84 + Dst: LinkEndpoint{
85 + Match: Match{MacAddresses: []string{"00:11:22:33:44:55"}},
86 Attributes: map[string]any{
87 "if_name": "eth0",
88 },
@@ -119,7 +117,7 @@ func TestAssignTopologyActorIDsAndLinkEndpoints_IsDeterministic(t *testing.T) {
117 }
118
119 func TestEnrichTopologyPortTablesWithLinkCounts_AddsCountsToMatchingPorts(t *testing.T) {
122 - actors := []topology.Actor{
120 + actors := []Actor{
121 {
122 ActorID: "device-a",
123 Tables: map[string][]map[string]any{
@@ -138,18 +136,18 @@ func TestEnrichTopologyPortTablesWithLinkCounts_AddsCountsToMatchingPorts(t *tes
136 },
137 },
138 }
141 - links := []topology.Link{
139 + links := []Link{
140 {
141 SrcActorID: "device-a",
142 DstActorID: "device-b",
145 - Src: topology.LinkEndpoint{Attributes: map[string]any{"if_name": "eth0"}},
146 - Dst: topology.LinkEndpoint{Attributes: map[string]any{"if_name": "xe-0/0/0"}},
143 + Src: LinkEndpoint{Attributes: map[string]any{"if_name": "eth0"}},
144 + Dst: LinkEndpoint{Attributes: map[string]any{"if_name": "xe-0/0/0"}},
145 },
146 {
147 SrcActorID: "device-a",
148 DstActorID: "device-b",
151 - Src: topology.LinkEndpoint{Attributes: map[string]any{"if_name": "eth0"}},
152 - Dst: topology.LinkEndpoint{Attributes: map[string]any{"if_name": "xe-0/0/0"}},
149 + Src: LinkEndpoint{Attributes: map[string]any{"if_name": "eth0"}},
150 + Dst: LinkEndpoint{Attributes: map[string]any{"if_name": "xe-0/0/0"}},
151 },
152 }
153
src/go/pkg/l2topology/topology_adapter_projection.go
+2 -4
@@ -2,12 +2,10 @@
2
3 package l2topology
4
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
6 -
5 type builtAdjacencyLink struct {
6 adj Adjacency
7 protocol string
10 - link topology.Link
8 + link Link
9 }
10
11 type pairedLinkAccumulator struct {
@@ -15,7 +13,7 @@ type pairedLinkAccumulator struct {
13 }
14
15 type projectedLinks struct {
18 - links []topology.Link
16 + links []Link
17 lldp int
18 cdp int
19 bidirectionalCount int
src/go/pkg/l2topology/topology_adapter_projection_pairs.go
+6 -8
@@ -5,8 +5,6 @@ package l2topology
5 import (
6 "strings"
7 "time"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
10 func projectAdjacencyLinks(
@@ -18,7 +16,7 @@ func projectAdjacencyLinks(
16 ifaceByDeviceIndex map[string]Interface,
17 ) projectedLinks {
18 out := projectedLinks{
21 - links: make([]topology.Link, 0, len(adjacencies)),
19 + links: make([]Link, 0, len(adjacencies)),
20 }
21 if len(adjacencies) == 0 {
22 return out
@@ -166,11 +164,11 @@ func backfillPairGroupMissingEndpointPorts(entries []*builtAdjacencyLink) {
164 }
165 }
166
169 -func endpointHasKnownCanonicalPort(endpoint topology.LinkEndpoint) bool {
167 +func endpointHasKnownCanonicalPort(endpoint LinkEndpoint) bool {
168 return strings.TrimSpace(topologyCanonicalPortName(endpoint.Attributes)) != ""
169 }
170
173 -func backfillEndpointPortFromPeer(endpoint topology.LinkEndpoint, peer topology.LinkEndpoint) topology.LinkEndpoint {
171 +func backfillEndpointPortFromPeer(endpoint LinkEndpoint, peer LinkEndpoint) LinkEndpoint {
172 if endpointHasKnownCanonicalPort(endpoint) || !endpointHasKnownCanonicalPort(peer) {
173 return endpoint
174 }
@@ -220,14 +218,14 @@ func adjacencyToTopologyLink(
218 deviceByID map[string]Device,
219 ifIndexByDeviceName map[string]int,
220 ifaceByDeviceIndex map[string]Interface,
223 -) topology.Link {
221 +) Link {
222 src := adjacencySideToEndpoint(deviceByID[adj.SourceID], adj.SourcePort, ifIndexByDeviceName, ifaceByDeviceIndex)
223 dst := adjacencySideToEndpoint(deviceByID[adj.TargetID], adj.TargetPort, ifIndexByDeviceName, ifaceByDeviceIndex)
224 if rawAddress := strings.TrimSpace(adj.Labels["remote_address_raw"]); rawAddress != "" {
225 dst.Match.IPAddresses = uniqueTopologyStrings(append(dst.Match.IPAddresses, rawAddress))
226 }
227
230 - link := topology.Link{
228 + link := Link{
229 Layer: layer,
230 Protocol: protocol,
231 LinkType: protocol,
@@ -286,7 +284,7 @@ func buildPairedLinkMetrics(sourceLabels, targetLabels map[string]string) map[st
284 return metrics
285 }
286
289 -func mergeEndpointIPHints(base, extra topology.LinkEndpoint) topology.LinkEndpoint {
287 +func mergeEndpointIPHints(base, extra LinkEndpoint) LinkEndpoint {
288 if len(extra.Match.IPAddresses) == 0 {
289 return base
290 }
src/go/pkg/l2topology/topology_adapter_pruning.go
+8 -10
@@ -5,11 +5,9 @@ package l2topology
5 import (
6 "sort"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func pruneSegmentArtifacts(actors []topology.Actor, links []topology.Link) ([]topology.Actor, []topology.Link, int) {
10 +func pruneSegmentArtifacts(actors []Actor, links []Link) ([]Actor, []Link, int) {
11 if len(actors) == 0 || len(links) == 0 {
12 return actors, links, 0
13 }
@@ -132,7 +130,7 @@ func pruneSegmentArtifacts(actors []topology.Actor, links []topology.Link) ([]to
130 return actors, links, 0
131 }
132
135 - filteredActors := make([]topology.Actor, 0, len(actors))
133 + filteredActors := make([]Actor, 0, len(actors))
134 for _, actor := range actors {
135 key := canonicalTopologyMatchKey(actor.Match)
136 if key == "" {
@@ -145,7 +143,7 @@ func pruneSegmentArtifacts(actors []topology.Actor, links []topology.Link) ([]to
143 filteredActors = append(filteredActors, actor)
144 }
145
148 - filteredLinks := make([]topology.Link, 0, len(links))
146 + filteredLinks := make([]Link, 0, len(links))
147 for _, link := range links {
148 src := canonicalTopologyMatchKey(link.Src.Match)
149 dst := canonicalTopologyMatchKey(link.Dst.Match)
@@ -186,7 +184,7 @@ type topologyLinkCounts struct {
184 unidirectional int
185 }
186
189 -func summarizeTopologyLinks(links []topology.Link) topologyLinkCounts {
187 +func summarizeTopologyLinks(links []Link) topologyLinkCounts {
188 var counts topologyLinkCounts
189 for _, link := range links {
190 switch strings.ToLower(strings.TrimSpace(link.Protocol)) {
@@ -211,10 +209,10 @@ func summarizeTopologyLinks(links []topology.Link) topologyLinkCounts {
209 }
210
211 func pruneManagedOverlapUnlinkedEndpointActors(
214 - actors []topology.Actor,
215 - links []topology.Link,
212 + actors []Actor,
213 + links []Link,
214 suppressedEndpointIDs map[string]struct{},
217 -) ([]topology.Actor, int) {
215 +) ([]Actor, int) {
216 if len(actors) == 0 || len(suppressedEndpointIDs) == 0 {
217 return actors, 0
218 }
@@ -256,7 +254,7 @@ func pruneManagedOverlapUnlinkedEndpointActors(
254 }
255 }
256
259 - filtered := make([]topology.Actor, 0, len(actors))
257 + filtered := make([]Actor, 0, len(actors))
258 suppressedCount := 0
259 for _, actor := range actors {
260 if !strings.EqualFold(strings.TrimSpace(actor.ActorType), "endpoint") {
src/go/pkg/l2topology/topology_adapter_regression_test.go
+20 -22
@@ -3,10 +3,8 @@
3 package l2topology
4
5 import (
6 - "testing"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
6 "github.com/stretchr/testify/require"
7 + "testing"
8 )
9
10 func TestBackfillPairGroupMissingEndpointPortsCopiesPeerInterfaceAttributes(t *testing.T) {
@@ -16,9 +14,9 @@ func TestBackfillPairGroupMissingEndpointPortsCopiesPeerInterfaceAttributes(t *t
14 SourceID: "device-a",
15 TargetID: "device-b",
16 },
19 - link: topology.Link{
20 - Src: topology.LinkEndpoint{Attributes: map[string]any{}},
21 - Dst: topology.LinkEndpoint{Attributes: map[string]any{}},
17 + link: Link{
18 + Src: LinkEndpoint{Attributes: map[string]any{}},
19 + Dst: LinkEndpoint{Attributes: map[string]any{}},
20 },
21 },
22 {
@@ -26,13 +24,13 @@ func TestBackfillPairGroupMissingEndpointPortsCopiesPeerInterfaceAttributes(t *t
24 SourceID: "device-b",
25 TargetID: "device-a",
26 },
29 - link: topology.Link{
30 - Src: topology.LinkEndpoint{Attributes: map[string]any{
27 + link: Link{
28 + Src: LinkEndpoint{Attributes: map[string]any{
29 "if_index": 2,
30 "if_name": "Gi0/2",
31 "port_id": "Gi0/2",
32 }},
35 - Dst: topology.LinkEndpoint{Attributes: map[string]any{
33 + Dst: LinkEndpoint{Attributes: map[string]any{
34 "if_index": 1,
35 "if_name": "Gi0/1",
36 "port_id": "Gi0/1",
@@ -58,9 +56,9 @@ func TestBackfillPairGroupMissingEndpointPortsSkipsAmbiguousReverseCandidates(t
56 SourceID: "device-a",
57 TargetID: "device-b",
58 },
61 - link: topology.Link{
62 - Src: topology.LinkEndpoint{Attributes: map[string]any{}},
63 - Dst: topology.LinkEndpoint{Attributes: map[string]any{}},
59 + link: Link{
60 + Src: LinkEndpoint{Attributes: map[string]any{}},
61 + Dst: LinkEndpoint{Attributes: map[string]any{}},
62 },
63 },
64 {
@@ -68,11 +66,11 @@ func TestBackfillPairGroupMissingEndpointPortsSkipsAmbiguousReverseCandidates(t
66 SourceID: "device-b",
67 TargetID: "device-a",
68 },
71 - link: topology.Link{
72 - Src: topology.LinkEndpoint{Attributes: map[string]any{
69 + link: Link{
70 + Src: LinkEndpoint{Attributes: map[string]any{
71 "if_name": "Gi0/2",
72 }},
75 - Dst: topology.LinkEndpoint{Attributes: map[string]any{
73 + Dst: LinkEndpoint{Attributes: map[string]any{
74 "if_name": "Gi0/1",
75 }},
76 },
@@ -82,11 +80,11 @@ func TestBackfillPairGroupMissingEndpointPortsSkipsAmbiguousReverseCandidates(t
80 SourceID: "device-b",
81 TargetID: "device-a",
82 },
85 - link: topology.Link{
86 - Src: topology.LinkEndpoint{Attributes: map[string]any{
83 + link: Link{
84 + Src: LinkEndpoint{Attributes: map[string]any{
85 "if_name": "Gi0/22",
86 }},
89 - Dst: topology.LinkEndpoint{Attributes: map[string]any{
87 + Dst: LinkEndpoint{Attributes: map[string]any{
88 "if_name": "Gi0/11",
89 }},
90 },
@@ -100,12 +98,12 @@ func TestBackfillPairGroupMissingEndpointPortsSkipsAmbiguousReverseCandidates(t
98 }
99
100 func TestBackfillEndpointPortFromPeerPreservesExistingCanonicalPort(t *testing.T) {
103 - endpoint := topology.LinkEndpoint{
101 + endpoint := LinkEndpoint{
102 Attributes: map[string]any{
103 "if_name": "Gi0/10",
104 },
105 }
108 - peer := topology.LinkEndpoint{
106 + peer := LinkEndpoint{
107 Attributes: map[string]any{
108 "if_index": 7,
109 "if_name": "Gi0/7",
@@ -124,7 +122,7 @@ func TestSegmentProjectionBuilderPruneSegmentsWithoutLinksRemovesEmptySegments(t
122 builder := &segmentProjectionBuilder{
123 segmentIDs: []string{"segment-a", "segment-b"},
124 out: projectedSegments{
127 - actors: []topology.Actor{
125 + actors: []Actor{
126 {
127 ActorID: "segment-a",
128 ActorType: "segment",
@@ -140,7 +138,7 @@ func TestSegmentProjectionBuilderPruneSegmentsWithoutLinksRemovesEmptySegments(t
138 },
139 },
140 },
143 - links: []topology.Link{
141 + links: []Link{
142 {
143 Protocol: "fdb",
144 Direction: "bidirectional",
src/go/pkg/l2topology/topology_adapter_reporter_hints.go
+1 -3
@@ -6,12 +6,10 @@ import (
6 "sort"
7 "strconv"
8 "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
11 func collectTopologyEndpointIDs(
14 - endpointMatchByID map[string]topology.Match,
12 + endpointMatchByID map[string]Match,
13 endpointLabelsByID map[string]map[string]string,
14 endpointSegmentCandidates map[string][]string,
15 rawFDBObservations fdbReporterObservation,
src/go/pkg/l2topology/topology_adapter_segment_actor.go
+12 -14
@@ -6,11 +6,9 @@ import (
6 "sort"
7 "strconv"
8 "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
13 -func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, layer string, source string) (topology.Match, topology.Actor) {
11 +func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, layer string, source string) (Match, Actor) {
12 parentDevices := make(map[string]struct{})
13 ifNames := make(map[string]struct{})
14 ifIndexes := make(map[string]struct{})
@@ -36,7 +34,7 @@ func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, lay
34 }
35 }
36
39 - match := topology.Match{
37 + match := Match{
38 Hostnames: []string{"segment:" + segmentID},
39 }
40
@@ -60,7 +58,7 @@ func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, lay
58 }
59 }
60
63 - actor := topology.Actor{
61 + actor := Actor{
62 ActorType: "segment",
63 Layer: layer,
64 Source: source,
@@ -74,36 +72,36 @@ func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, lay
72 return match, actor
73 }
74
77 -func endpointMatchFromID(endpointID string) topology.Match {
75 +func endpointMatchFromID(endpointID string) Match {
76 kind, value, ok := strings.Cut(strings.TrimSpace(endpointID), ":")
77 if !ok {
80 - return topology.Match{}
78 + return Match{}
79 }
80 switch strings.ToLower(strings.TrimSpace(kind)) {
81 case "mac":
82 mac := normalizeMAC(value)
83 if mac == "" {
86 - return topology.Match{}
84 + return Match{}
85 }
88 - return topology.Match{
86 + return Match{
87 ChassisIDs: []string{mac},
88 MacAddresses: []string{mac},
89 }
90 case "ip":
91 addr := normalizeTopologyIP(value)
92 if addr == "" {
95 - return topology.Match{}
93 + return Match{}
94 }
97 - return topology.Match{
95 + return Match{
96 IPAddresses: []string{addr},
97 }
98 }
101 - return topology.Match{}
99 + return Match{}
100 }
101
102 func annotateEndpointActorsWithDirectOwners(
105 - actors []topology.Actor,
106 - endpointMatchByID map[string]topology.Match,
103 + actors []Actor,
104 + endpointMatchByID map[string]Match,
105 owners map[string]fdbEndpointOwner,
106 deviceByID map[string]Device,
107 ) {
src/go/pkg/l2topology/topology_adapter_segments.go
+1 -3
@@ -6,8 +6,6 @@ import (
6 "sort"
7 "strings"
8 "time"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
11 func projectSegmentTopology(
@@ -21,7 +19,7 @@ func projectSegmentTopology(
19 ifIndexByDeviceName map[string]int,
20 bridgeLinks []bridgeBridgeLinkRecord,
21 reporterAliases map[string][]string,
24 - endpointMatchByID map[string]topology.Match,
22 + endpointMatchByID map[string]Match,
23 endpointLabelsByID map[string]map[string]string,
24 actorIndex map[string]struct{},
25 probabilisticConnectivity bool,
src/go/pkg/l2topology/topology_adapter_segments_builder.go
+5 -7
@@ -4,8 +4,6 @@ package l2topology
4
5 import (
6 "time"
7 -
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
7 )
8
9 type segmentProjectionBuilder struct {
@@ -19,14 +17,14 @@ type segmentProjectionBuilder struct {
17 ifIndexByDeviceName map[string]int
18 bridgeLinks []bridgeBridgeLinkRecord
19 reporterAliases map[string][]string
22 - endpointMatchByID map[string]topology.Match
20 + endpointMatchByID map[string]Match
21 endpointLabelsByID map[string]map[string]string
22 actorIndex map[string]struct{}
23 probabilisticConnectivity bool
24 strategyConfig topologyInferenceStrategyConfig
25 out projectedSegments
26 segmentIDs []string
29 - segmentMatchByID map[string]topology.Match
27 + segmentMatchByID map[string]Match
28 segmentByID map[string]*bridgeDomainSegment
29 deviceSegmentEdgeSeen map[string]struct{}
30 endpointSegmentEdgeSeen map[string]struct{}
@@ -64,7 +62,7 @@ func newSegmentProjectionBuilder(
62 ifIndexByDeviceName map[string]int,
63 bridgeLinks []bridgeBridgeLinkRecord,
64 reporterAliases map[string][]string,
67 - endpointMatchByID map[string]topology.Match,
65 + endpointMatchByID map[string]Match,
66 endpointLabelsByID map[string]map[string]string,
67 actorIndex map[string]struct{},
68 probabilisticConnectivity bool,
@@ -87,8 +85,8 @@ func newSegmentProjectionBuilder(
85 probabilisticConnectivity: probabilisticConnectivity,
86 strategyConfig: strategyConfig,
87 out: projectedSegments{
90 - actors: make([]topology.Actor, 0),
91 - links: make([]topology.Link, 0),
88 + actors: make([]Actor, 0),
89 + links: make([]Link, 0),
90 },
91 }
92 }
src/go/pkg/l2topology/topology_adapter_segments_builder_emit.go
+9 -11
@@ -5,8 +5,6 @@ package l2topology
5 import (
6 "sort"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
10 func (b *segmentProjectionBuilder) emitLinks() {
@@ -19,7 +17,7 @@ func (b *segmentProjectionBuilder) emitLinks() {
17 if segment == nil {
18 continue
19 }
22 - segmentEndpoint := topology.LinkEndpoint{
20 + segmentEndpoint := LinkEndpoint{
21 Match: b.segmentMatchByID[segmentID],
22 Attributes: map[string]any{
23 "segment_id": segmentID,
@@ -57,7 +55,7 @@ func (b *segmentProjectionBuilder) emitLinks() {
55 if segment.portIdentityKey(port) == segment.portIdentityKey(segment.designatedPort) {
56 metrics["designated"] = true
57 }
60 - b.out.links = append(b.out.links, topology.Link{
58 + b.out.links = append(b.out.links, Link{
59 Layer: b.layer,
60 Protocol: "bridge",
61 LinkType: "bridge",
@@ -120,7 +118,7 @@ func (b *segmentProjectionBuilder) emitLinks() {
118 edgeKey := segmentID + "|managed-device|" + matchedDeviceID
119 if _, seen := b.endpointSegmentEdgeSeen[edgeKey]; !seen {
120 b.endpointSegmentEdgeSeen[edgeKey] = struct{}{}
123 - b.out.links = append(b.out.links, topology.Link{
121 + b.out.links = append(b.out.links, Link{
122 Layer: b.layer,
123 Protocol: "fdb",
124 LinkType: "fdb",
@@ -181,13 +179,13 @@ func (b *segmentProjectionBuilder) emitLinks() {
179 linkState = "probable"
180 }
181 }
184 - b.out.links = append(b.out.links, topology.Link{
182 + b.out.links = append(b.out.links, Link{
183 Layer: b.layer,
184 Protocol: "fdb",
185 LinkType: "fdb",
186 Direction: "bidirectional",
187 Src: adjacencySideToEndpoint(device, localPort, b.ifIndexByDeviceName, b.ifaceByDeviceIndex),
190 - Dst: topology.LinkEndpoint{Match: endpointMatch},
188 + Dst: LinkEndpoint{Match: endpointMatch},
189 DiscoveredAt: topologyTimePtr(b.collectedAt),
190 LastSeen: topologyTimePtr(b.collectedAt),
191 State: linkState,
@@ -228,13 +226,13 @@ func (b *segmentProjectionBuilder) emitLinks() {
226 }
227 }
228
231 - b.out.links = append(b.out.links, topology.Link{
229 + b.out.links = append(b.out.links, Link{
230 Layer: b.layer,
231 Protocol: "fdb",
232 LinkType: "fdb",
233 Direction: "bidirectional",
234 Src: segmentEndpoint,
237 - Dst: topology.LinkEndpoint{Match: endpointMatch},
235 + Dst: LinkEndpoint{Match: endpointMatch},
236 DiscoveredAt: topologyTimePtr(b.collectedAt),
237 LastSeen: topologyTimePtr(b.collectedAt),
238 State: linkState,
@@ -255,7 +253,7 @@ func (b *segmentProjectionBuilder) pruneSegmentsWithoutLinks(segmentsWithAnyLink
253 return
254 }
255
258 - filteredActors := make([]topology.Actor, 0, len(b.out.actors))
256 + filteredActors := make([]Actor, 0, len(b.out.actors))
257 for _, actor := range b.out.actors {
258 segmentID := topologyAttrString(actor.Attributes, "segment_id")
259 if segmentID == "" {
@@ -267,7 +265,7 @@ func (b *segmentProjectionBuilder) pruneSegmentsWithoutLinks(segmentsWithAnyLink
265 }
266 b.out.actors = filteredActors
267
270 - filteredLinks := make([]topology.Link, 0, len(b.out.links))
268 + filteredLinks := make([]Link, 0, len(b.out.links))
269 b.out.linksFdb = 0
270 b.out.bidirectionalCount = 0
271 b.out.endpointLinksEmitted = 0
src/go/pkg/l2topology/topology_adapter_segments_builder_init.go
+1 -3
@@ -6,8 +6,6 @@ import (
6 "sort"
7 "strconv"
8 "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 )
10
11 func (b *segmentProjectionBuilder) initializeSegments() bool {
@@ -27,7 +25,7 @@ func (b *segmentProjectionBuilder) initializeSegments() bool {
25 return false
26 }
27
30 - b.segmentMatchByID = make(map[string]topology.Match)
28 + b.segmentMatchByID = make(map[string]Match)
29 b.segmentByID = make(map[string]*bridgeDomainSegment)
30 for _, domain := range model.domains {
31 if domain == nil {
src/go/pkg/l2topology/topology_adapter_test.go
+143 -145
@@ -3,17 +3,15 @@
3 package l2topology
4
5 import (
6 + "github.com/stretchr/testify/require"
7 "net/netip"
8 "slices"
9 "strings"
10 "testing"
11 "time"
11 -
12 - "github.com/netdata/netdata/go/plugins/pkg/topology"
13 - "github.com/stretchr/testify/require"
12 )
13
16 -func TestToTopologyData_ProjectsResult(t *testing.T) {
14 +func TestToGraph_ProjectsResult(t *testing.T) {
15 collectedAt := time.Date(2026, time.February, 20, 4, 5, 6, 0, time.UTC)
16
17 result := Result{
@@ -64,7 +62,7 @@ func TestToTopologyData_ProjectsResult(t *testing.T) {
62 },
63 }
64
67 - data := ToTopologyData(result, TopologyDataOptions{
65 + data := ToGraph(result, GraphOptions{
66 SchemaVersion: "2.0",
67 Source: "snmp",
68 Layer: "2",
@@ -127,7 +125,7 @@ func TestToTopologyData_ProjectsResult(t *testing.T) {
125 require.Equal(t, 1, data.Stats["endpoints_total"])
126 }
127
130 -func TestToTopologyData_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.T) {
128 +func TestToGraph_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.T) {
129 result := Result{
130 Devices: []Device{
131 {
@@ -229,7 +227,7 @@ func TestToTopologyData_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.
227 },
228 }
229
232 - data := ToTopologyData(result, TopologyDataOptions{
230 + data := ToGraph(result, GraphOptions{
231 Source: "snmp",
232 Layer: "2",
233 View: "summary",
@@ -292,7 +290,7 @@ func TestToTopologyData_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.
290 require.Equal(t, []string{"fdb"}, port4["topology_role_sources"])
291 }
292
295 -func TestToTopologyData_IgnoresIgnoredFDBStatusForLinkModeClassification(t *testing.T) {
293 +func TestToGraph_IgnoresIgnoredFDBStatusForLinkModeClassification(t *testing.T) {
294 result := Result{
295 Devices: []Device{
296 {
@@ -317,7 +315,7 @@ func TestToTopologyData_IgnoresIgnoredFDBStatusForLinkModeClassification(t *test
315 },
316 }
317
320 - data := ToTopologyData(result, TopologyDataOptions{
318 + data := ToGraph(result, GraphOptions{
319 Source: "snmp",
320 Layer: "2",
321 View: "summary",
@@ -340,7 +338,7 @@ func TestToTopologyData_IgnoresIgnoredFDBStatusForLinkModeClassification(t *test
338 require.False(t, hasRoleSources)
339 }
340
343 -func TestToTopologyData_ClassifiesSTPCorroboratedManagedAliasAsSwitchFacing(t *testing.T) {
341 +func TestToGraph_ClassifiesSTPCorroboratedManagedAliasAsSwitchFacing(t *testing.T) {
342 result := Result{
343 Devices: []Device{
344 {
@@ -382,7 +380,7 @@ func TestToTopologyData_ClassifiesSTPCorroboratedManagedAliasAsSwitchFacing(t *t
380 },
381 }
382
385 - data := ToTopologyData(result, TopologyDataOptions{
383 + data := ToGraph(result, GraphOptions{
384 Source: "snmp",
385 Layer: "2",
386 View: "summary",
@@ -399,7 +397,7 @@ func TestToTopologyData_ClassifiesSTPCorroboratedManagedAliasAsSwitchFacing(t *t
397 require.Equal(t, []string{"stp", "fdb", "fdb_managed_alias"}, port1["topology_role_sources"])
398 }
399
402 -func TestToTopologyData_EnrichesPortStatusesWithNeighborsFDBAndSTP(t *testing.T) {
400 +func TestToGraph_EnrichesPortStatusesWithNeighborsFDBAndSTP(t *testing.T) {
401 result := Result{
402 Devices: []Device{
403 {
@@ -521,7 +519,7 @@ func TestToTopologyData_EnrichesPortStatusesWithNeighborsFDBAndSTP(t *testing.T)
519 },
520 }
521
524 - data := ToTopologyData(result, TopologyDataOptions{
522 + data := ToGraph(result, GraphOptions{
523 Source: "snmp",
524 Layer: "2",
525 View: "summary",
@@ -588,7 +586,7 @@ func TestToTopologyData_EnrichesPortStatusesWithNeighborsFDBAndSTP(t *testing.T)
586 require.False(t, hasNeighbors)
587 }
588
591 -func TestToTopologyData_InfersVendorFromMACOUI(t *testing.T) {
589 +func TestToGraph_InfersVendorFromMACOUI(t *testing.T) {
590 result := Result{
591 Devices: []Device{
592 {
@@ -614,7 +612,7 @@ func TestToTopologyData_InfersVendorFromMACOUI(t *testing.T) {
612 },
613 }
614
617 - data := ToTopologyData(result, TopologyDataOptions{
615 + data := ToGraph(result, GraphOptions{
616 Source: "snmp",
617 Layer: "2",
618 View: "summary",
@@ -668,7 +666,7 @@ func TestDeviceToTopologyActor_DoesNotOverrideExplicitVendor(t *testing.T) {
666 require.NotEmpty(t, actor.Attributes["vendor_derived_match_prefix"])
667 }
668
671 -func TestToTopologyData_DefaultDiscoveredCountWithoutLocalID(t *testing.T) {
669 +func TestToGraph_DefaultDiscoveredCountWithoutLocalID(t *testing.T) {
670 result := Result{
671 Devices: []Device{
672 {ID: "a", Hostname: "a"},
@@ -677,11 +675,11 @@ func TestToTopologyData_DefaultDiscoveredCountWithoutLocalID(t *testing.T) {
675 },
676 }
677
680 - data := ToTopologyData(result, TopologyDataOptions{})
678 + data := ToGraph(result, GraphOptions{})
679 require.Equal(t, 2, data.Stats["devices_discovered"])
680 }
681
684 -func TestToTopologyData_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T) {
682 +func TestToGraph_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T) {
683 result := Result{
684 Devices: []Device{
685 {
@@ -708,7 +706,7 @@ func TestToTopologyData_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T
706 },
707 }
708
711 - data := ToTopologyData(result, TopologyDataOptions{
709 + data := ToGraph(result, GraphOptions{
710 Source: "snmp",
711 Layer: "2",
712 View: "summary",
@@ -732,7 +730,7 @@ func TestToTopologyData_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T
730 require.True(t, dstExists)
731 }
732
735 -func TestToTopologyData_DeterministicAcrossRepeatedCalls(t *testing.T) {
733 +func TestToGraph_DeterministicAcrossRepeatedCalls(t *testing.T) {
734 collectedAt := time.Date(2026, time.February, 20, 4, 5, 6, 0, time.UTC)
735
736 result := Result{
@@ -783,7 +781,7 @@ func TestToTopologyData_DeterministicAcrossRepeatedCalls(t *testing.T) {
781 },
782 }
783
786 - opts := TopologyDataOptions{
784 + opts := GraphOptions{
785 SchemaVersion: "2.0",
786 Source: "snmp",
787 Layer: "2",
@@ -792,14 +790,14 @@ func TestToTopologyData_DeterministicAcrossRepeatedCalls(t *testing.T) {
790 LocalDeviceID: "local-device",
791 }
792
795 - baseline := ToTopologyData(result, opts)
793 + baseline := ToGraph(result, opts)
794 for range 10 {
797 - next := ToTopologyData(result, opts)
795 + next := ToGraph(result, opts)
796 require.Equal(t, baseline, next)
797 }
798 }
799
802 -func TestToTopologyData_DeduplicatesEndpointActorOverlappingManagedDevice(t *testing.T) {
800 +func TestToGraph_DeduplicatesEndpointActorOverlappingManagedDevice(t *testing.T) {
801 result := Result{
802 Devices: []Device{
803 {
@@ -826,7 +824,7 @@ func TestToTopologyData_DeduplicatesEndpointActorOverlappingManagedDevice(t *tes
824 },
825 }
826
829 - data := ToTopologyData(result, TopologyDataOptions{
827 + data := ToGraph(result, GraphOptions{
828 Source: "snmp",
829 Layer: "2",
830 View: "summary",
@@ -842,10 +840,10 @@ func TestToTopologyData_DeduplicatesEndpointActorOverlappingManagedDevice(t *tes
840 }
841
842 func TestCanonicalTopologyMatchKey_NormalizesEquivalentMACRepresentations(t *testing.T) {
845 - raw := topology.Match{
843 + raw := Match{
844 ChassisIDs: []string{"7049a26572cd"},
845 }
848 - colon := topology.Match{
846 + colon := Match{
847 ChassisIDs: []string{"70:49:A2:65:72:CD"},
848 }
849
@@ -853,7 +851,7 @@ func TestCanonicalTopologyMatchKey_NormalizesEquivalentMACRepresentations(t *tes
851 require.Equal(t, "mac:70:49:a2:65:72:cd", canonicalTopologyMatchKey(raw))
852 }
853
856 -func TestToTopologyData_UsesDeterministicPrimaryManagementIP(t *testing.T) {
854 +func TestToGraph_UsesDeterministicPrimaryManagementIP(t *testing.T) {
855 result := Result{
856 Devices: []Device{
857 {
@@ -869,7 +867,7 @@ func TestToTopologyData_UsesDeterministicPrimaryManagementIP(t *testing.T) {
867 },
868 }
869
872 - data := ToTopologyData(result, TopologyDataOptions{
870 + data := ToGraph(result, GraphOptions{
871 Source: "snmp",
872 Layer: "2",
873 View: "summary",
@@ -881,7 +879,7 @@ func TestToTopologyData_UsesDeterministicPrimaryManagementIP(t *testing.T) {
879 require.Equal(t, []string{"10.0.0.2", "10.0.0.9"}, actor.Attributes["management_addresses"])
880 }
881
884 -func TestToTopologyData_KeepsDistinctActorsWhenMACDiffersDespiteSameSecondaryIdentity(t *testing.T) {
882 +func TestToGraph_KeepsDistinctActorsWhenMACDiffersDespiteSameSecondaryIdentity(t *testing.T) {
883 result := Result{
884 Devices: []Device{
885 {
@@ -899,7 +897,7 @@ func TestToTopologyData_KeepsDistinctActorsWhenMACDiffersDespiteSameSecondaryIde
897 },
898 }
899
902 - data := ToTopologyData(result, TopologyDataOptions{
900 + data := ToGraph(result, GraphOptions{
901 Source: "snmp",
902 Layer: "2",
903 View: "summary",
@@ -916,7 +914,7 @@ func TestToTopologyData_KeepsDistinctActorsWhenMACDiffersDespiteSameSecondaryIde
914 require.Contains(t, macs, "00:11:22:33:44:66")
915 }
916
919 -func TestToTopologyData_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.T) {
917 +func TestToGraph_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.T) {
918 result := Result{
919 Devices: []Device{
920 {
@@ -962,7 +960,7 @@ func TestToTopologyData_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.
960 },
961 }
962
965 - data := ToTopologyData(result, TopologyDataOptions{
963 + data := ToGraph(result, GraphOptions{
964 Source: "snmp",
965 Layer: "2",
966 View: "summary",
@@ -988,7 +986,7 @@ func TestToTopologyData_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.
986 require.Equal(t, 0, data.Stats["links_unidirectional"])
987 }
988
991 -func TestToTopologyData_MergesPairedAdjacenciesPreservesRawAddressHints(t *testing.T) {
989 +func TestToGraph_MergesPairedAdjacenciesPreservesRawAddressHints(t *testing.T) {
990 result := Result{
991 Devices: []Device{
992 {
@@ -1032,7 +1030,7 @@ func TestToTopologyData_MergesPairedAdjacenciesPreservesRawAddressHints(t *testi
1030 },
1031 }
1032
1035 - data := ToTopologyData(result, TopologyDataOptions{
1033 + data := ToGraph(result, GraphOptions{
1034 Source: "snmp",
1035 Layer: "2",
1036 View: "summary",
@@ -1047,7 +1045,7 @@ func TestToTopologyData_MergesPairedAdjacenciesPreservesRawAddressHints(t *testi
1045 require.Contains(t, link.Metrics, "dst_remote_address_raw")
1046 }
1047
1050 -func TestToTopologyData_MergesReversePairsWithoutDirectionalPairLabels(t *testing.T) {
1048 +func TestToGraph_MergesReversePairsWithoutDirectionalPairLabels(t *testing.T) {
1049 result := Result{
1050 Devices: []Device{
1051 {
@@ -1093,7 +1091,7 @@ func TestToTopologyData_MergesReversePairsWithoutDirectionalPairLabels(t *testin
1091 },
1092 }
1093
1096 - data := ToTopologyData(result, TopologyDataOptions{
1094 + data := ToGraph(result, GraphOptions{
1095 Source: "snmp",
1096 Layer: "2",
1097 View: "summary",
@@ -1116,7 +1114,7 @@ func TestToTopologyData_MergesReversePairsWithoutDirectionalPairLabels(t *testin
1114 require.Equal(t, "swp07", topologyAttrString(link.Dst.Attributes, "port_name"))
1115 }
1116
1119 -func TestToTopologyData_UnknownAdjacencyPortsRemainUnsetWithoutZeroFallback(t *testing.T) {
1117 +func TestToGraph_UnknownAdjacencyPortsRemainUnsetWithoutZeroFallback(t *testing.T) {
1118 result := Result{
1119 Devices: []Device{
1120 {
@@ -1146,7 +1144,7 @@ func TestToTopologyData_UnknownAdjacencyPortsRemainUnsetWithoutZeroFallback(t *t
1144 },
1145 }
1146
1149 - data := ToTopologyData(result, TopologyDataOptions{
1147 + data := ToGraph(result, GraphOptions{
1148 Source: "snmp",
1149 Layer: "2",
1150 View: "summary",
@@ -1162,7 +1160,7 @@ func TestToTopologyData_UnknownAdjacencyPortsRemainUnsetWithoutZeroFallback(t *t
1160 require.Contains(t, topologyMetricString(link.Metrics, "display_name"), ":[unset]")
1161 }
1162
1165 -func TestToTopologyData_DropsAmbiguousEndpointSegmentLinks(t *testing.T) {
1163 +func TestToGraph_DropsAmbiguousEndpointSegmentLinks(t *testing.T) {
1164 result := Result{
1165 Devices: []Device{
1166 {
@@ -1188,7 +1186,7 @@ func TestToTopologyData_DropsAmbiguousEndpointSegmentLinks(t *testing.T) {
1186 },
1187 }
1188
1191 - data := ToTopologyData(result, TopologyDataOptions{
1189 + data := ToGraph(result, GraphOptions{
1190 Source: "snmp",
1191 Layer: "2",
1192 View: "summary",
@@ -1216,7 +1214,7 @@ func TestToTopologyData_DropsAmbiguousEndpointSegmentLinks(t *testing.T) {
1214 require.Equal(t, 2, data.Stats["segments_suppressed"])
1215 }
1216
1219 -func TestToTopologyData_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing.T) {
1217 +func TestToGraph_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing.T) {
1218 result := Result{
1219 Devices: []Device{
1220 {
@@ -1242,14 +1240,14 @@ func TestToTopologyData_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing
1240 },
1241 }
1242
1245 - strictData := ToTopologyData(result, TopologyDataOptions{
1243 + strictData := ToGraph(result, GraphOptions{
1244 Source: "snmp",
1245 Layer: "2",
1246 View: "summary",
1247 })
1248 require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:cd"), 0)
1249
1252 - data := ToTopologyData(result, TopologyDataOptions{
1250 + data := ToGraph(result, GraphOptions{
1251 Source: "snmp",
1252 Layer: "2",
1253 View: "summary",
@@ -1268,7 +1266,7 @@ func TestToTopologyData_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing
1266 require.Equal(t, 1, data.Stats["links_fdb_endpoint_suppressed"])
1267 }
1268
1271 -func TestToTopologyData_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t *testing.T) {
1269 +func TestToGraph_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t *testing.T) {
1270 result := Result{
1271 Devices: []Device{
1272 {
@@ -1286,7 +1284,7 @@ func TestToTopologyData_ProbableConnectivityDoesNotReclassifyStrictSinglePortEnd
1284 },
1285 }
1286
1289 - strictData := ToTopologyData(result, TopologyDataOptions{
1287 + strictData := ToGraph(result, GraphOptions{
1288 Source: "snmp",
1289 Layer: "2",
1290 View: "summary",
@@ -1296,7 +1294,7 @@ func TestToTopologyData_ProbableConnectivityDoesNotReclassifyStrictSinglePortEnd
1294 require.Equal(t, "", strings.TrimSpace(strictFDBLinks[0].State))
1295 require.Equal(t, "", strings.TrimSpace(topologyMetricString(strictFDBLinks[0].Metrics, "inference")))
1296
1299 - data := ToTopologyData(result, TopologyDataOptions{
1297 + data := ToGraph(result, GraphOptions{
1298 Source: "snmp",
1299 Layer: "2",
1300 View: "summary",
@@ -1311,7 +1309,7 @@ func TestToTopologyData_ProbableConnectivityDoesNotReclassifyStrictSinglePortEnd
1309 require.Equal(t, 0, data.Stats["links_probable"])
1310 }
1311
1314 -func TestToTopologyData_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *testing.T) {
1312 +func TestToGraph_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *testing.T) {
1313 result := Result{
1314 Devices: []Device{
1315 {
@@ -1337,14 +1335,14 @@ func TestToTopologyData_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *test
1335 },
1336 }
1337
1340 - strictData := ToTopologyData(result, TopologyDataOptions{
1338 + strictData := ToGraph(result, GraphOptions{
1339 Source: "snmp",
1340 Layer: "2",
1341 View: "summary",
1342 })
1343 require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:cf"), 0)
1344
1347 - data := ToTopologyData(result, TopologyDataOptions{
1345 + data := ToGraph(result, GraphOptions{
1346 Source: "snmp",
1347 Layer: "2",
1348 View: "summary",
@@ -1358,7 +1356,7 @@ func TestToTopologyData_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *test
1356 require.Equal(t, "probable_segment", topologyMetricString(fdbLinks[0].Metrics, "attachment_mode"))
1357 }
1358
1361 -func TestToTopologyData_ProbableConnectivityAvoidsExtraBridgePathForLLDPPeers(t *testing.T) {
1359 +func TestToGraph_ProbableConnectivityAvoidsExtraBridgePathForLLDPPeers(t *testing.T) {
1360 result := Result{
1361 Devices: []Device{
1362 {
@@ -1404,14 +1402,14 @@ func TestToTopologyData_ProbableConnectivityAvoidsExtraBridgePathForLLDPPeers(t
1402 },
1403 }
1404
1407 - strictData := ToTopologyData(result, TopologyDataOptions{
1405 + strictData := ToGraph(result, GraphOptions{
1406 Source: "snmp",
1407 Layer: "2",
1408 View: "summary",
1409 })
1410 require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:aa"), 0)
1411
1414 - data := ToTopologyData(result, TopologyDataOptions{
1412 + data := ToGraph(result, GraphOptions{
1413 Source: "snmp",
1414 Layer: "2",
1415 View: "summary",
@@ -1443,7 +1441,7 @@ func TestToTopologyData_ProbableConnectivityAvoidsExtraBridgePathForLLDPPeers(t
1441 }
1442 }
1443
1446 -func TestToTopologyData_ProbableConnectivityConnectsZeroCandidateEndpointUsingReporterHints(t *testing.T) {
1444 +func TestToGraph_ProbableConnectivityConnectsZeroCandidateEndpointUsingReporterHints(t *testing.T) {
1445 result := Result{
1446 Devices: []Device{
1447 {
@@ -1473,14 +1471,14 @@ func TestToTopologyData_ProbableConnectivityConnectsZeroCandidateEndpointUsingRe
1471 },
1472 }
1473
1476 - strictData := ToTopologyData(result, TopologyDataOptions{
1474 + strictData := ToGraph(result, GraphOptions{
1475 Source: "snmp",
1476 Layer: "2",
1477 View: "summary",
1478 })
1479 require.Len(t, findFDBLinksByEndpointIP(strictData.Links, "10.0.0.99"), 0)
1480
1483 - data := ToTopologyData(result, TopologyDataOptions{
1481 + data := ToGraph(result, GraphOptions{
1482 Source: "snmp",
1483 Layer: "2",
1484 View: "summary",
@@ -1502,7 +1500,7 @@ func TestToTopologyData_ProbableConnectivityConnectsZeroCandidateEndpointUsingRe
1500 }
1501 }
1502
1505 -func TestToTopologyData_ProbableConnectivityCreatesPortlessAttachmentForZeroCandidateEndpoint(t *testing.T) {
1503 +func TestToGraph_ProbableConnectivityCreatesPortlessAttachmentForZeroCandidateEndpoint(t *testing.T) {
1504 result := Result{
1505 Devices: []Device{
1506 {
@@ -1532,7 +1530,7 @@ func TestToTopologyData_ProbableConnectivityCreatesPortlessAttachmentForZeroCand
1530 },
1531 }
1532
1535 - data := ToTopologyData(result, TopologyDataOptions{
1533 + data := ToGraph(result, GraphOptions{
1534 Source: "snmp",
1535 Layer: "2",
1536 View: "summary",
@@ -1562,7 +1560,7 @@ func TestToTopologyData_ProbableConnectivityCreatesPortlessAttachmentForZeroCand
1560 require.Equal(t, 1, bridgeCount)
1561 }
1562
1565 -func TestToTopologyData_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwnership(t *testing.T) {
1563 +func TestToGraph_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwnership(t *testing.T) {
1564 result := Result{
1565 Devices: []Device{
1566 {
@@ -1597,7 +1595,7 @@ func TestToTopologyData_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwn
1595 },
1596 }
1597
1600 - baseline := ToTopologyData(result, TopologyDataOptions{
1598 + baseline := ToGraph(result, GraphOptions{
1599 Source: "snmp",
1600 Layer: "2",
1601 View: "summary",
@@ -1605,7 +1603,7 @@ func TestToTopologyData_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwn
1603 require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baseline.Stats["inference_strategy"])
1604 require.Greater(t, baseline.Stats["links_fdb_endpoint_emitted"].(int), 0)
1605
1608 - stpData := ToTopologyData(result, TopologyDataOptions{
1606 + stpData := ToGraph(result, GraphOptions{
1607 Source: "snmp",
1608 Layer: "2",
1609 View: "summary",
@@ -1615,7 +1613,7 @@ func TestToTopologyData_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwn
1613 require.Greater(t, stpData.Stats["links_fdb_endpoint_emitted"].(int), 0)
1614 }
1615
1618 -func TestToTopologyData_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *testing.T) {
1616 +func TestToGraph_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *testing.T) {
1617 result := Result{
1618 Devices: []Device{
1619 {ID: "sw-a", Hostname: "sw-a", ChassisID: "00:00:00:00:00:aa"},
@@ -1650,7 +1648,7 @@ func TestToTopologyData_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *test
1648 },
1649 }
1650
1653 - baseline := ToTopologyData(result, TopologyDataOptions{
1651 + baseline := ToGraph(result, GraphOptions{
1652 Source: "snmp",
1653 Layer: "2",
1654 View: "summary",
@@ -1658,7 +1656,7 @@ func TestToTopologyData_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *test
1656 require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baseline.Stats["inference_strategy"])
1657 require.Equal(t, 0, baseline.Stats["links_fdb_endpoint_emitted"])
1658
1661 - data := ToTopologyData(result, TopologyDataOptions{
1659 + data := ToGraph(result, GraphOptions{
1660 Source: "snmp",
1661 Layer: "2",
1662 View: "summary",
@@ -1815,7 +1813,7 @@ func TestEnsureManagedProbableReporterHint_FallsBackToFirstManagedDevice(t *test
1813 require.Equal(t, "0", updated.ifName)
1814 }
1815
1818 -func TestToTopologyData_ProbableConnectivityRecoversUnmanagedOverlapSuppression(t *testing.T) {
1816 +func TestToGraph_ProbableConnectivityRecoversUnmanagedOverlapSuppression(t *testing.T) {
1817 result := Result{
1818 Devices: []Device{
1819 {
@@ -1843,14 +1841,14 @@ func TestToTopologyData_ProbableConnectivityRecoversUnmanagedOverlapSuppression(
1841 },
1842 }
1843
1846 - strictData := ToTopologyData(result, TopologyDataOptions{
1844 + strictData := ToGraph(result, GraphOptions{
1845 Source: "snmp",
1846 Layer: "2",
1847 View: "summary",
1848 })
1849 require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "cc:cc:cc:cc:cc:cc"), 0)
1850
1853 - data := ToTopologyData(result, TopologyDataOptions{
1851 + data := ToGraph(result, GraphOptions{
1852 Source: "snmp",
1853 Layer: "2",
1854 View: "summary",
@@ -1865,7 +1863,7 @@ func TestToTopologyData_ProbableConnectivityRecoversUnmanagedOverlapSuppression(
1863 require.Equal(t, "low", topologyMetricString(fdbLinks[0].Metrics, "confidence"))
1864 }
1865
1868 -func TestToTopologyData_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *testing.T) {
1866 +func TestToGraph_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *testing.T) {
1867 result := Result{
1868 Devices: []Device{
1869 {
@@ -1910,14 +1908,14 @@ func TestToTopologyData_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *te
1908 },
1909 }
1910
1913 - withoutCollapse := ToTopologyData(result, TopologyDataOptions{
1911 + withoutCollapse := ToGraph(result, GraphOptions{
1912 Source: "snmp",
1913 Layer: "2",
1914 View: "summary",
1915 })
1916 require.NotNil(t, findActorByMAC(withoutCollapse.Actors, "9c:6b:00:7b:98:c7"))
1917
1920 - withCollapse := ToTopologyData(result, TopologyDataOptions{
1918 + withCollapse := ToGraph(result, GraphOptions{
1919 Source: "snmp",
1920 Layer: "2",
1921 View: "summary",
@@ -1928,7 +1926,7 @@ func TestToTopologyData_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *te
1926 require.Equal(t, 1, withCollapse.Stats["actors_unlinked_suppressed"])
1927 }
1928
1931 -func TestToTopologyData_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *testing.T) {
1929 +func TestToGraph_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *testing.T) {
1930 result := Result{
1931 Devices: []Device{
1932 {
@@ -1952,7 +1950,7 @@ func TestToTopologyData_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *test
1950 },
1951 }
1952
1955 - data := ToTopologyData(result, TopologyDataOptions{
1953 + data := ToGraph(result, GraphOptions{
1954 Source: "snmp",
1955 Layer: "2",
1956 View: "summary",
@@ -1974,7 +1972,7 @@ func TestToTopologyData_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *test
1972 }
1973 }
1974
1977 -func TestToTopologyData_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) {
1975 +func TestToGraph_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) {
1976 result := Result{
1977 Devices: []Device{
1978 {
@@ -1999,7 +1997,7 @@ func TestToTopologyData_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) {
1997 },
1998 }
1999
2002 - data := ToTopologyData(result, TopologyDataOptions{
2000 + data := ToGraph(result, GraphOptions{
2001 Source: "snmp",
2002 Layer: "2",
2003 View: "summary",
@@ -2012,7 +2010,7 @@ func TestToTopologyData_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) {
2010 require.Equal(t, 0, data.Stats["links_fdb_endpoint_suppressed"])
2011 }
2012
2015 -func TestToTopologyData_DeviceActorIncludesInterfaceMACAliases(t *testing.T) {
2013 +func TestToGraph_DeviceActorIncludesInterfaceMACAliases(t *testing.T) {
2014 result := Result{
2015 Devices: []Device{
2016 {
@@ -2029,7 +2027,7 @@ func TestToTopologyData_DeviceActorIncludesInterfaceMACAliases(t *testing.T) {
2027 },
2028 }
2029
2032 - data := ToTopologyData(result, TopologyDataOptions{
2030 + data := ToGraph(result, GraphOptions{
2031 Source: "snmp",
2032 Layer: "2",
2033 View: "summary",
@@ -2044,7 +2042,7 @@ func TestToTopologyData_DeviceActorIncludesInterfaceMACAliases(t *testing.T) {
2042 )
2043 }
2044
2047 -func TestToTopologyData_KeepsUnlinkedEndpointsAndDevices(t *testing.T) {
2045 +func TestToGraph_KeepsUnlinkedEndpointsAndDevices(t *testing.T) {
2046 result := Result{
2047 Devices: []Device{
2048 {
@@ -2062,7 +2060,7 @@ func TestToTopologyData_KeepsUnlinkedEndpointsAndDevices(t *testing.T) {
2060 },
2061 }
2062
2065 - data := ToTopologyData(result, TopologyDataOptions{
2063 + data := ToGraph(result, GraphOptions{
2064 Source: "snmp",
2065 Layer: "2",
2066 View: "summary",
@@ -2085,7 +2083,7 @@ func TestToTopologyData_KeepsUnlinkedEndpointsAndDevices(t *testing.T) {
2083 require.Equal(t, 0, data.Stats["actors_unlinked_suppressed"])
2084 }
2085
2088 -func TestToTopologyData_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t *testing.T) {
2086 +func TestToGraph_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t *testing.T) {
2087 result := Result{
2088 Devices: []Device{
2089 {
@@ -2122,7 +2120,7 @@ func TestToTopologyData_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t
2120 },
2121 }
2122
2125 - data := ToTopologyData(result, TopologyDataOptions{
2123 + data := ToGraph(result, GraphOptions{
2124 Source: "snmp",
2125 Layer: "2",
2126 View: "summary",
@@ -2134,7 +2132,7 @@ func TestToTopologyData_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t
2132 require.Equal(t, 0, data.Stats["actors_unlinked_suppressed"])
2133 }
2134
2137 -func TestToTopologyData_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) {
2135 +func TestToGraph_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) {
2136 result := Result{
2137 Devices: []Device{
2138 {
@@ -2153,7 +2151,7 @@ func TestToTopologyData_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) {
2151 },
2152 }
2153
2156 - data := ToTopologyData(result, TopologyDataOptions{
2154 + data := ToGraph(result, GraphOptions{
2155 Source: "snmp",
2156 Layer: "2",
2157 View: "summary",
@@ -2193,7 +2191,7 @@ func TestToTopologyData_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) {
2191 }
2192
2193 func TestTopologyDisplayNameFromMatch_PrefersSysNameBeforeIP(t *testing.T) {
2196 - display := topologyDisplayNameFromMatch(topology.Match{
2194 + display := topologyDisplayNameFromMatch(Match{
2195 SysName: "MikroTik-router",
2196 IPAddresses: []string{"10.20.4.1"},
2197 }, &topologyDisplayNameResolver{
@@ -2206,7 +2204,7 @@ func TestTopologyDisplayNameFromMatch_PrefersSysNameBeforeIP(t *testing.T) {
2204 }
2205
2206 func TestTopologyDisplayNameFromMatch_PrefersHostnameBeforeIPWhenSysNameMissing(t *testing.T) {
2209 - display := topologyDisplayNameFromMatch(topology.Match{
2207 + display := topologyDisplayNameFromMatch(Match{
2208 Hostnames: []string{"nova"},
2209 IPAddresses: []string{"10.20.4.22"},
2210 }, &topologyDisplayNameResolver{
@@ -2218,7 +2216,7 @@ func TestTopologyDisplayNameFromMatch_PrefersHostnameBeforeIPWhenSysNameMissing(
2216 require.Equal(t, "hostname", display.source)
2217 }
2218
2221 -func TestToTopologyData_SegmentDisplayNameUsesParentPortPattern(t *testing.T) {
2219 +func TestToGraph_SegmentDisplayNameUsesParentPortPattern(t *testing.T) {
2220 result := Result{
2221 Devices: []Device{
2222 {
@@ -2237,7 +2235,7 @@ func TestToTopologyData_SegmentDisplayNameUsesParentPortPattern(t *testing.T) {
2235 },
2236 }
2237
2240 - data := ToTopologyData(result, TopologyDataOptions{
2238 + data := ToGraph(result, GraphOptions{
2239 Source: "snmp",
2240 Layer: "2",
2241 View: "summary",
@@ -2256,7 +2254,7 @@ func TestToTopologyData_SegmentDisplayNameUsesParentPortPattern(t *testing.T) {
2254 require.Equal(t, "switch-a.example.net.gi0/3.segment", segment.Attributes["display_name"])
2255 }
2256
2259 -func TestToTopologyData_FDBOwnerInferencePrefersNonLLDPSide(t *testing.T) {
2257 +func TestToGraph_FDBOwnerInferencePrefersNonLLDPSide(t *testing.T) {
2258 result := Result{
2259 Devices: []Device{
2260 {
@@ -2295,7 +2293,7 @@ func TestToTopologyData_FDBOwnerInferencePrefersNonLLDPSide(t *testing.T) {
2293 },
2294 }
2295
2298 - data := ToTopologyData(result, TopologyDataOptions{
2296 + data := ToGraph(result, GraphOptions{
2297 Source: "snmp",
2298 Layer: "2",
2299 View: "summary",
@@ -2310,7 +2308,7 @@ func TestToTopologyData_FDBOwnerInferencePrefersNonLLDPSide(t *testing.T) {
2308 require.Equal(t, []string{"Gi0/2"}, segmentActor.Attributes["if_names"])
2309 }
2310
2313 -func TestToTopologyData_FDBOwnerInferenceUsesSingleMACPortRule(t *testing.T) {
2311 +func TestToGraph_FDBOwnerInferenceUsesSingleMACPortRule(t *testing.T) {
2312 result := Result{
2313 Devices: []Device{
2314 {
@@ -2335,7 +2333,7 @@ func TestToTopologyData_FDBOwnerInferenceUsesSingleMACPortRule(t *testing.T) {
2333 },
2334 }
2335
2338 - data := ToTopologyData(result, TopologyDataOptions{
2336 + data := ToGraph(result, GraphOptions{
2337 Source: "snmp",
2338 Layer: "2",
2339 View: "summary",
@@ -2358,7 +2356,7 @@ func TestToTopologyData_FDBOwnerInferenceUsesSingleMACPortRule(t *testing.T) {
2356 require.Equal(t, "single_port_mac", endpointActor.Labels["attached_by"])
2357 }
2358
2361 -func TestToTopologyData_FDBOwnerInferenceSuppressesManagedAliasSwitchFacingPorts(t *testing.T) {
2359 +func TestToGraph_FDBOwnerInferenceSuppressesManagedAliasSwitchFacingPorts(t *testing.T) {
2360 result := Result{
2361 Devices: []Device{
2362 {
@@ -2391,7 +2389,7 @@ func TestToTopologyData_FDBOwnerInferenceSuppressesManagedAliasSwitchFacingPorts
2389 },
2390 }
2391
2394 - data := ToTopologyData(result, TopologyDataOptions{
2392 + data := ToGraph(result, GraphOptions{
2393 Source: "snmp",
2394 Layer: "2",
2395 View: "summary",
@@ -2414,7 +2412,7 @@ func TestToTopologyData_FDBOwnerInferenceSuppressesManagedAliasSwitchFacingPorts
2412 require.Equal(t, "switch-a", eeSrc.Match.SysName)
2413 }
2414
2417 -func TestToTopologyData_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) {
2415 +func TestToGraph_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) {
2416 result := Result{
2417 Devices: []Device{
2418 {
@@ -2449,7 +2447,7 @@ func TestToTopologyData_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) {
2447 },
2448 }
2449
2452 - data := ToTopologyData(result, TopologyDataOptions{
2450 + data := ToGraph(result, GraphOptions{
2451 Source: "snmp",
2452 Layer: "2",
2453 View: "summary",
@@ -2482,7 +2480,7 @@ func TestToTopologyData_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) {
2480 require.Equal(t, 0, data.Stats["links_fdb_endpoint_suppressed"])
2481 }
2482
2485 -func TestToTopologyData_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) {
2483 +func TestToGraph_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) {
2484 result := Result{
2485 Devices: []Device{
2486 {
@@ -2508,7 +2506,7 @@ func TestToTopologyData_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) {
2506 },
2507 }
2508
2511 - data := ToTopologyData(result, TopologyDataOptions{
2509 + data := ToGraph(result, GraphOptions{
2510 Source: "snmp",
2511 Layer: "2",
2512 View: "summary",
@@ -2520,36 +2518,36 @@ func TestToTopologyData_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) {
2518 }
2519
2520 func TestPruneSegmentArtifacts_SuppressesLLDPDuplicateSegmentPath(t *testing.T) {
2523 - actors := []topology.Actor{
2521 + actors := []Actor{
2522 {
2523 ActorType: "device",
2526 - Match: topology.Match{IPAddresses: []string{"10.0.0.1"}, SysName: "switch-a"},
2524 + Match: Match{IPAddresses: []string{"10.0.0.1"}, SysName: "switch-a"},
2525 },
2526 {
2527 ActorType: "device",
2530 - Match: topology.Match{IPAddresses: []string{"10.0.0.2"}, SysName: "switch-b"},
2528 + Match: Match{IPAddresses: []string{"10.0.0.2"}, SysName: "switch-b"},
2529 },
2530 {
2531 ActorType: "segment",
2534 - Match: topology.Match{Hostnames: []string{"segment:dup"}},
2532 + Match: Match{Hostnames: []string{"segment:dup"}},
2533 },
2534 }
2535
2538 - links := []topology.Link{
2536 + links := []Link{
2537 {
2538 Protocol: "lldp",
2541 - Src: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.0.1"}}},
2542 - Dst: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.0.2"}}},
2539 + Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.1"}}},
2540 + Dst: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.2"}}},
2541 },
2542 {
2543 Protocol: "bridge",
2546 - Src: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.0.1"}}},
2547 - Dst: topology.LinkEndpoint{Match: topology.Match{Hostnames: []string{"segment:dup"}}},
2544 + Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.1"}}},
2545 + Dst: LinkEndpoint{Match: Match{Hostnames: []string{"segment:dup"}}},
2546 },
2547 {
2548 Protocol: "bridge",
2551 - Src: topology.LinkEndpoint{Match: topology.Match{Hostnames: []string{"segment:dup"}}},
2552 - Dst: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.0.2"}}},
2549 + Src: LinkEndpoint{Match: Match{Hostnames: []string{"segment:dup"}}},
2550 + Dst: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.2"}}},
2551 },
2552 }
2553
@@ -2561,36 +2559,36 @@ func TestPruneSegmentArtifacts_SuppressesLLDPDuplicateSegmentPath(t *testing.T)
2559 }
2560
2561 func TestPruneSegmentArtifacts_SuppressesCDPDuplicateSegmentPath(t *testing.T) {
2564 - actors := []topology.Actor{
2562 + actors := []Actor{
2563 {
2564 ActorType: "device",
2567 - Match: topology.Match{IPAddresses: []string{"10.0.1.1"}, SysName: "switch-a"},
2565 + Match: Match{IPAddresses: []string{"10.0.1.1"}, SysName: "switch-a"},
2566 },
2567 {
2568 ActorType: "device",
2571 - Match: topology.Match{IPAddresses: []string{"10.0.1.2"}, SysName: "switch-b"},
2569 + Match: Match{IPAddresses: []string{"10.0.1.2"}, SysName: "switch-b"},
2570 },
2571 {
2572 ActorType: "segment",
2575 - Match: topology.Match{Hostnames: []string{"segment:dup-cdp"}},
2573 + Match: Match{Hostnames: []string{"segment:dup-cdp"}},
2574 },
2575 }
2576
2579 - links := []topology.Link{
2577 + links := []Link{
2578 {
2579 Protocol: "cdp",
2582 - Src: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.1.1"}}},
2583 - Dst: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.1.2"}}},
2580 + Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.1.1"}}},
2581 + Dst: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.1.2"}}},
2582 },
2583 {
2584 Protocol: "bridge",
2587 - Src: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.1.1"}}},
2588 - Dst: topology.LinkEndpoint{Match: topology.Match{Hostnames: []string{"segment:dup-cdp"}}},
2585 + Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.1.1"}}},
2586 + Dst: LinkEndpoint{Match: Match{Hostnames: []string{"segment:dup-cdp"}}},
2587 },
2588 {
2589 Protocol: "bridge",
2592 - Src: topology.LinkEndpoint{Match: topology.Match{Hostnames: []string{"segment:dup-cdp"}}},
2593 - Dst: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.1.2"}}},
2590 + Src: LinkEndpoint{Match: Match{Hostnames: []string{"segment:dup-cdp"}}},
2591 + Dst: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.1.2"}}},
2592 },
2593 }
2594
@@ -2602,22 +2600,22 @@ func TestPruneSegmentArtifacts_SuppressesCDPDuplicateSegmentPath(t *testing.T) {
2600 }
2601
2602 func TestPruneSegmentArtifacts_SuppressesSegmentsWithSingleNeighbor(t *testing.T) {
2605 - actors := []topology.Actor{
2603 + actors := []Actor{
2604 {
2605 ActorType: "device",
2608 - Match: topology.Match{IPAddresses: []string{"10.0.0.1"}, SysName: "router-a"},
2606 + Match: Match{IPAddresses: []string{"10.0.0.1"}, SysName: "router-a"},
2607 },
2608 {
2609 ActorType: "segment",
2612 - Match: topology.Match{Hostnames: []string{"segment:orphan"}},
2610 + Match: Match{Hostnames: []string{"segment:orphan"}},
2611 },
2612 }
2613
2616 - links := []topology.Link{
2614 + links := []Link{
2615 {
2616 Protocol: "bridge",
2619 - Src: topology.LinkEndpoint{Match: topology.Match{IPAddresses: []string{"10.0.0.1"}}},
2620 - Dst: topology.LinkEndpoint{Match: topology.Match{Hostnames: []string{"segment:orphan"}}},
2617 + Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.1"}}},
2618 + Dst: LinkEndpoint{Match: Match{Hostnames: []string{"segment:orphan"}}},
2619 },
2620 }
2621
@@ -2627,7 +2625,7 @@ func TestPruneSegmentArtifacts_SuppressesSegmentsWithSingleNeighbor(t *testing.T
2625 require.Len(t, filteredLinks, 0)
2626 }
2627
2630 -func TestToTopologyData_DeterministicTransitRuleSuppressesFDBOnLLDPPortInExperimental(t *testing.T) {
2628 +func TestToGraph_DeterministicTransitRuleSuppressesFDBOnLLDPPortInExperimental(t *testing.T) {
2629 result := Result{
2630 Devices: []Device{
2631 {
@@ -2659,7 +2657,7 @@ func TestToTopologyData_DeterministicTransitRuleSuppressesFDBOnLLDPPortInExperim
2657 },
2658 }
2659
2662 - data := ToTopologyData(result, TopologyDataOptions{
2660 + data := ToGraph(result, GraphOptions{
2661 Source: "snmp",
2662 Layer: "2",
2663 View: "summary",
@@ -2671,7 +2669,7 @@ func TestToTopologyData_DeterministicTransitRuleSuppressesFDBOnLLDPPortInExperim
2669 require.Nil(t, findActorByType(data.Actors, "segment"))
2670 }
2671
2674 -func TestToTopologyData_DeterministicTransitRuleMatchesNumericLLDPPortToIfIndex(t *testing.T) {
2672 +func TestToGraph_DeterministicTransitRuleMatchesNumericLLDPPortToIfIndex(t *testing.T) {
2673 result := Result{
2674 Devices: []Device{
2675 {
@@ -2703,7 +2701,7 @@ func TestToTopologyData_DeterministicTransitRuleMatchesNumericLLDPPortToIfIndex(
2701 },
2702 }
2703
2706 - data := ToTopologyData(result, TopologyDataOptions{
2704 + data := ToGraph(result, GraphOptions{
2705 Source: "snmp",
2706 Layer: "2",
2707 View: "summary",
@@ -2722,7 +2720,7 @@ func TestToTopologyData_DeterministicTransitRuleMatchesNumericLLDPPortToIfIndex(
2720 require.Equal(t, "ether4", lldpLink.Dst.Attributes["port_id"])
2721 }
2722
2725 -func TestToTopologyData_SwitchFacingPortDoesNotSuppressEndpointOwnership(t *testing.T) {
2723 +func TestToGraph_SwitchFacingPortDoesNotSuppressEndpointOwnership(t *testing.T) {
2724 result := Result{
2725 Devices: []Device{
2726 {
@@ -2749,7 +2747,7 @@ func TestToTopologyData_SwitchFacingPortDoesNotSuppressEndpointOwnership(t *test
2747 },
2748 }
2749
2752 - data := ToTopologyData(result, TopologyDataOptions{
2750 + data := ToGraph(result, GraphOptions{
2751 Source: "snmp",
2752 Layer: "2",
2753 View: "summary",
@@ -2817,7 +2815,7 @@ func TestSuppressInferredBridgeLinksOnDeterministicDiscovery(t *testing.T) {
2815 require.Equal(t, "switch-c", filtered[1].port.deviceID)
2816 }
2817
2820 -func TestToTopologyData_FDBOwnerInferenceUsesReporterMatrixRule(t *testing.T) {
2818 +func TestToGraph_FDBOwnerInferenceUsesReporterMatrixRule(t *testing.T) {
2819 result := Result{
2820 Devices: []Device{
2821 {
@@ -2857,7 +2855,7 @@ func TestToTopologyData_FDBOwnerInferenceUsesReporterMatrixRule(t *testing.T) {
2855 },
2856 }
2857
2860 - data := ToTopologyData(result, TopologyDataOptions{
2858 + data := ToGraph(result, GraphOptions{
2859 Source: "snmp",
2860 Layer: "2",
2861 View: "summary",
@@ -2892,8 +2890,8 @@ func findNeighborByProtocol(neighbors []map[string]any, protocol string) map[str
2890 return nil
2891 }
2892
2895 -func findFDBLinksByEndpointMAC(links []topology.Link, mac string) []topology.Link {
2896 - out := make([]topology.Link, 0)
2893 +func findFDBLinksByEndpointMAC(links []Link, mac string) []Link {
2894 + out := make([]Link, 0)
2895 for _, link := range links {
2896 if link.Protocol != "fdb" {
2897 continue
@@ -2905,8 +2903,8 @@ func findFDBLinksByEndpointMAC(links []topology.Link, mac string) []topology.Lin
2903 return out
2904 }
2905
2908 -func findFDBLinksByEndpointIP(links []topology.Link, ip string) []topology.Link {
2909 - out := make([]topology.Link, 0)
2906 +func findFDBLinksByEndpointIP(links []Link, ip string) []Link {
2907 + out := make([]Link, 0)
2908 for _, link := range links {
2909 if link.Protocol != "fdb" {
2910 continue
@@ -2918,7 +2916,7 @@ func findFDBLinksByEndpointIP(links []topology.Link, ip string) []topology.Link
2916 return out
2917 }
2918
2921 -func topologyLinkSignatures(links []topology.Link) map[string]struct{} {
2919 +func topologyLinkSignatures(links []Link) map[string]struct{} {
2920 out := make(map[string]struct{}, len(links))
2921 for _, link := range links {
2922 srcKey := canonicalTopologyMatchKey(link.Src.Match)
@@ -2939,8 +2937,8 @@ func topologyLinkSignatures(links []topology.Link) map[string]struct{} {
2937 return out
2938 }
2939
2942 -func findFDBLinksByDstSysName(links []topology.Link, sysName string) []topology.Link {
2943 - out := make([]topology.Link, 0)
2940 +func findFDBLinksByDstSysName(links []Link, sysName string) []Link {
2941 + out := make([]Link, 0)
2942 for _, link := range links {
2943 if link.Protocol != "fdb" {
2944 continue
@@ -2953,7 +2951,7 @@ func findFDBLinksByDstSysName(links []topology.Link, sysName string) []topology.
2951 return out
2952 }
2953
2956 -func findActorByMatch(actors []topology.Actor, match topology.Match) *topology.Actor {
2954 +func findActorByMatch(actors []Actor, match Match) *Actor {
2955 target := canonicalTopologyMatchKey(match)
2956 if target == "" {
2957 return nil
@@ -2966,7 +2964,7 @@ func findActorByMatch(actors []topology.Actor, match topology.Match) *topology.A
2964 return nil
2965 }
2966
2969 -func findActorBySysName(actors []topology.Actor, sysName string) *topology.Actor {
2967 +func findActorBySysName(actors []Actor, sysName string) *Actor {
2968 for i := range actors {
2969 if actors[i].Match.SysName == sysName {
2970 return &actors[i]
@@ -2975,7 +2973,7 @@ func findActorBySysName(actors []topology.Actor, sysName string) *topology.Actor
2973 return nil
2974 }
2975
2978 -func findActorByMAC(actors []topology.Actor, mac string) *topology.Actor {
2976 +func findActorByMAC(actors []Actor, mac string) *Actor {
2977 for i := range actors {
2978 if slices.Contains(actors[i].Match.MacAddresses, mac) {
2979 return &actors[i]
@@ -2984,7 +2982,7 @@ func findActorByMAC(actors []topology.Actor, mac string) *topology.Actor {
2982 return nil
2983 }
2984
2987 -func findActorByIP(actors []topology.Actor, ip string) *topology.Actor {
2985 +func findActorByIP(actors []Actor, ip string) *Actor {
2986 for i := range actors {
2987 if slices.Contains(actors[i].Match.IPAddresses, ip) {
2988 return &actors[i]
@@ -2993,7 +2991,7 @@ func findActorByIP(actors []topology.Actor, ip string) *topology.Actor {
2991 return nil
2992 }
2993
2996 -func findActorByType(actors []topology.Actor, actorType string) *topology.Actor {
2994 +func findActorByType(actors []Actor, actorType string) *Actor {
2995 for i := range actors {
2996 if actors[i].ActorType == actorType {
2997 return &actors[i]
@@ -3002,7 +3000,7 @@ func findActorByType(actors []topology.Actor, actorType string) *topology.Actor
3000 return nil
3001 }
3002
3005 -func findLinkByProtocol(links []topology.Link, protocol string) *topology.Link {
3003 +func findLinkByProtocol(links []Link, protocol string) *Link {
3004 for i := range links {
3005 if links[i].Protocol == protocol {
3006 return &links[i]
src/go/pkg/topology/v1/table.go
+50
@@ -32,6 +32,56 @@ func EmptyTable() Table {
32 }
33 }
34
35 +type TableBuilder struct {
36 + columns []Column
37 + values [][]any
38 + rowsErr error
39 +}
40 +
41 +func NewTableBuilder(columns ...Column) *TableBuilder {
42 + return &TableBuilder{
43 + columns: append([]Column(nil), columns...),
44 + values: make([][]any, len(columns)),
45 + }
46 +}
47 +
48 +func (b *TableBuilder) Add(values ...any) int {
49 + row := b.Rows()
50 + if len(values) != len(b.columns) {
51 + b.rowsErr = fmt.Errorf("row has %d values for %d columns", len(values), len(b.columns))
52 + return row
53 + }
54 + for i, value := range values {
55 + b.values[i] = append(b.values[i], value)
56 + }
57 + return row
58 +}
59 +
60 +func (b *TableBuilder) Rows() int {
61 + if len(b.values) == 0 {
62 + return 0
63 + }
64 + return len(b.values[0])
65 +}
66 +
67 +func (b *TableBuilder) Table() (Table, error) {
68 + if b.rowsErr != nil {
69 + return Table{}, b.rowsErr
70 + }
71 + if len(b.columns) == 0 {
72 + return EmptyTable(), nil
73 + }
74 + encodings := make([]ColumnEncoding, len(b.values))
75 + for i, values := range b.values {
76 + encoding := Values(values...)
77 + if encoding.Values == nil {
78 + encoding.Values = []any{}
79 + }
80 + encodings[i] = encoding
81 + }
82 + return NewTable(b.Rows(), b.columns, encodings)
83 +}
84 +
85 func (table Table) Validate() error {
86 if table.Rows < 0 {
87 return fmt.Errorf("rows is negative: %d", table.Rows)
src/go/pkg/topology/v1/table_test.go
+42
@@ -152,6 +152,48 @@ func TestNewTableAllowsNullableNull(t *testing.T) {
152 assert.Equal(t, 1, table.Rows)
153 }
154
155 +func TestTableBuilderBuildsValuesTable(t *testing.T) {
156 + builder := NewTableBuilder(
157 + NewColumn("id", "string"),
158 + NewColumn("connected", "bool"),
159 + )
160 +
161 + row := builder.Add("actor-1", true)
162 + builder.Add("actor-2", false)
163 + table, err := builder.Table()
164 +
165 + require.NoError(t, err)
166 + assert.Equal(t, 0, row)
167 + assert.Equal(t, 2, builder.Rows())
168 + assert.Equal(t, 2, table.Rows)
169 + require.Len(t, table.Values, 2)
170 + assert.Equal(t, Values("actor-1", "actor-2"), table.Values[0])
171 + assert.Equal(t, Values(true, false), table.Values[1])
172 +}
173 +
174 +func TestTableBuilderRejectsRowWidthMismatch(t *testing.T) {
175 + builder := NewTableBuilder(
176 + NewColumn("id", "string"),
177 + NewColumn("name", "string"),
178 + )
179 +
180 + row := builder.Add("actor-1")
181 + _, err := builder.Table()
182 +
183 + require.Error(t, err)
184 + assert.Equal(t, 0, row)
185 + assert.Contains(t, err.Error(), "row has 1 values for 2 columns")
186 +}
187 +
188 +func TestTableBuilderBuildsEmptyZeroColumnTable(t *testing.T) {
189 + builder := NewTableBuilder()
190 +
191 + table, err := builder.Table()
192 +
193 + require.NoError(t, err)
194 + assert.Equal(t, EmptyTable(), table)
195 +}
196 +
197 func TestStringDictionaryDeduplicatesValues(t *testing.T) {
198 dict := NewStringDictionary()
199
src/go/plugin/framework/docs/changing-framework-code.md
+1 -1
@@ -25,7 +25,7 @@ This guide applies when changing or extending any of these areas:
25 - `src/go/pkg/netdataapi`
26 - `src/go/pkg/metrix`
27 - `src/go/pkg/funcapi`
28 -- `src/go/pkg/topology`
28 +- `src/go/pkg/topology/v1`
29 - `src/go/pkg/matcher`
30 - `src/go/pkg/stm`
31 - shared collector/runtime helpers under `src/go/pkg/` when their semantics are
src/go/plugin/go.d/collector/cato_networks/topology.go
+25 -72
@@ -18,9 +18,9 @@ const (
18
19 func buildTopology(accountID string, sites map[string]*siteState, order []string, collectedAt time.Time) (*topologyv1.Data, error) {
20 stringsDict := topologyv1.NewStringDictionary()
21 - actors := newTopologyTableBuilder(catoTopologyActorColumns()...)
22 - links := newTopologyTableBuilder(catoTopologyLinkColumns()...)
23 - interfaces := newTopologyTableBuilder(catoTopologyInterfaceColumns()...)
21 + actors := topologyv1.NewTableBuilder(catoTopologyActorColumns()...)
22 + links := topologyv1.NewTableBuilder(catoTopologyLinkColumns()...)
23 + interfaces := topologyv1.NewTableBuilder(catoTopologyInterfaceColumns()...)
24
25 actorIndexes := make(map[string]int, len(order)*2)
26 popSeen := make(map[string]bool)
@@ -49,15 +49,15 @@ func buildTopology(accountID string, sites map[string]*siteState, order []string
49 addBGPPeerTopology(actors, links, stringsDict, actorIndexes, site, siteActorIndex)
50 }
51
52 - actorTable, err := actors.table()
52 + actorTable, err := actors.Table()
53 if err != nil {
54 return nil, fmt.Errorf("actors table: %w", err)
55 }
56 - linkTable, err := links.table()
56 + linkTable, err := links.Table()
57 if err != nil {
58 return nil, fmt.Errorf("links table: %w", err)
59 }
60 - interfaceTable, err := interfaces.table()
60 + interfaceTable, err := interfaces.Table()
61 if err != nil {
62 return nil, fmt.Errorf("interfaces table: %w", err)
63 }
@@ -93,14 +93,14 @@ func buildTopology(accountID string, sites map[string]*siteState, order []string
93 },
94 Stats: map[string]any{
95 "sites": len(order),
96 - "links": links.rows(),
96 + "links": links.Rows(),
97 },
98 }
99 return data, nil
100 }
101
102 -func addSiteActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary, accountID string, site *siteState, siteActorID string) int {
103 - return table.add(
102 +func addSiteActor(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, accountID string, site *siteState, siteActorID string) int {
103 + return table.Add(
104 dict.Ref(catofunc.ActorTypeSite),
105 dict.Ref(topologyLayer),
106 siteActorID,
@@ -127,8 +127,8 @@ func addSiteActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary
127 )
128 }
129
130 -func addPopActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary, accountID, popName, popActorID string) int {
131 - return table.add(
130 +func addPopActor(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, accountID, popName, popActorID string) int {
131 + return table.Add(
132 dict.Ref(catofunc.ActorTypePop),
133 dict.Ref(topologyLayer),
134 popActorID,
@@ -155,8 +155,8 @@ func addPopActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary,
155 )
156 }
157
158 -func addSitePopLink(table *topologyTableBuilder, dict *topologyv1.StringDictionary, site *siteState, siteActor, popActor int) {
159 - table.add(
158 +func addSitePopLink(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, site *siteState, siteActor, popActor int) {
159 + table.Add(
160 dict.Ref(catofunc.LinkTypeTunnel),
161 siteActor,
162 popActor,
@@ -177,7 +177,7 @@ func addSitePopLink(table *topologyTableBuilder, dict *topologyv1.StringDictiona
177 )
178 }
179
180 -func addDeviceTopology(actors, links *topologyTableBuilder, dict *topologyv1.StringDictionary, actorIndexes map[string]int, popSeen map[string]bool, accountID string, site *siteState) map[string]int {
180 +func addDeviceTopology(actors, links *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, actorIndexes map[string]int, popSeen map[string]bool, accountID string, site *siteState) map[string]int {
181 deviceActors := make(map[string]int, len(site.Devices))
182
183 sortedDevices := append([]deviceState(nil), site.Devices...)
@@ -218,7 +218,7 @@ func addDeviceTopology(actors, links *topologyTableBuilder, dict *topologyv1.Str
218 return deviceActors
219 }
220
221 -func addDeviceActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary, accountID string, site *siteState, dev deviceState, deviceID, deviceActorID string) int {
221 +func addDeviceActor(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, accountID string, site *siteState, dev deviceState, deviceID, deviceActorID string) int {
222 displayName := deviceDisplayName(dev)
223 if displayName == "" {
224 displayName = deviceID
@@ -227,7 +227,7 @@ func addDeviceActor(table *topologyTableBuilder, dict *topologyv1.StringDictiona
227 if popName == "" {
228 popName = site.PopName
229 }
230 - return table.add(
230 + return table.Add(
231 dict.Ref(catofunc.ActorTypeDevice),
232 dict.Ref(topologyLayer),
233 deviceActorID,
@@ -254,8 +254,8 @@ func addDeviceActor(table *topologyTableBuilder, dict *topologyv1.StringDictiona
254 )
255 }
256
257 -func addDevicePopLink(table *topologyTableBuilder, dict *topologyv1.StringDictionary, dev deviceState, deviceActor, popActor int) {
258 - table.add(
257 +func addDevicePopLink(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, dev deviceState, deviceActor, popActor int) {
258 + table.Add(
259 dict.Ref(catofunc.LinkTypeTunnel),
260 deviceActor,
261 popActor,
@@ -276,7 +276,7 @@ func addDevicePopLink(table *topologyTableBuilder, dict *topologyv1.StringDictio
276 )
277 }
278
279 -func addBGPPeerTopology(actors, links *topologyTableBuilder, dict *topologyv1.StringDictionary, actorIndexes map[string]int, site *siteState, siteActor int) {
279 +func addBGPPeerTopology(actors, links *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, actorIndexes map[string]int, site *siteState, siteActor int) {
280 seen := make(map[string]bool)
281
282 for _, peer := range site.BGPPeers {
@@ -294,12 +294,12 @@ func addBGPPeerTopology(actors, links *topologyTableBuilder, dict *topologyv1.St
294 }
295 }
296
297 -func addBGPPeerActor(table *topologyTableBuilder, dict *topologyv1.StringDictionary, site *siteState, peer bgpPeerState, peerActorID string) int {
297 +func addBGPPeerActor(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, site *siteState, peer bgpPeerState, peerActorID string) int {
298 displayName := peer.RemoteIP
299 if displayName == "" {
300 displayName = peer.RemoteASN
301 }
302 - return table.add(
302 + return table.Add(
303 dict.Ref(catofunc.ActorTypeBGPPeer),
304 dict.Ref(topologyLayer),
305 peerActorID,
@@ -326,8 +326,8 @@ func addBGPPeerActor(table *topologyTableBuilder, dict *topologyv1.StringDiction
326 )
327 }
328
329 -func addBGPLink(table *topologyTableBuilder, dict *topologyv1.StringDictionary, siteActor, peerActor int, peer bgpPeerState) {
330 - table.add(
329 +func addBGPLink(table *topologyv1.TableBuilder, dict *topologyv1.StringDictionary, siteActor, peerActor int, peer bgpPeerState) {
330 + table.Add(
331 dict.Ref(catofunc.LinkTypeBGP),
332 siteActor,
333 peerActor,
@@ -348,7 +348,7 @@ func addBGPLink(table *topologyTableBuilder, dict *topologyv1.StringDictionary,
348 )
349 }
350
351 -func addInterfaceTopologyTable(interfaces *topologyTableBuilder, siteActor int, deviceActors map[string]int, site *siteState) {
351 +func addInterfaceTopologyTable(interfaces *topologyv1.TableBuilder, siteActor int, deviceActors map[string]int, site *siteState) {
352 ifaceKeys := make([]string, 0, len(site.Interfaces))
353 for key := range site.Interfaces {
354 ifaceKeys = append(ifaceKeys, key)
@@ -379,7 +379,7 @@ func addInterfaceTopologyTable(interfaces *topologyTableBuilder, siteActor int,
379 actor = devActor
380 }
381 }
382 - interfaces.add(
382 + interfaces.Add(
383 actor,
384 iface.ID,
385 iface.Name,
@@ -510,50 +510,3 @@ func catoTopologyInterfaceColumns() []topologyv1.Column {
510 topologyv1.NewColumn("downstream_bandwidth", "int"),
511 }
512 }
513 -
514 -type topologyTableBuilder struct {
515 - columns []topologyv1.Column
516 - values [][]any
517 - rowsErr error
518 -}
519 -
520 -func newTopologyTableBuilder(columns ...topologyv1.Column) *topologyTableBuilder {
521 - return &topologyTableBuilder{
522 - columns: append([]topologyv1.Column(nil), columns...),
523 - values: make([][]any, len(columns)),
524 - }
525 -}
526 -
527 -func (b *topologyTableBuilder) add(values ...any) int {
528 - row := b.rows()
529 - if len(values) != len(b.columns) {
530 - b.rowsErr = fmt.Errorf("row has %d values for %d columns", len(values), len(b.columns))
531 - return row
532 - }
533 - for i, value := range values {
534 - b.values[i] = append(b.values[i], value)
535 - }
536 - return row
537 -}
538 -
539 -func (b *topologyTableBuilder) rows() int {
540 - if len(b.values) == 0 {
541 - return 0
542 - }
543 - return len(b.values[0])
544 -}
545 -
546 -func (b *topologyTableBuilder) table() (topologyv1.Table, error) {
547 - if b.rowsErr != nil {
548 - return topologyv1.Table{}, b.rowsErr
549 - }
550 - encodings := make([]topologyv1.ColumnEncoding, len(b.values))
551 - for i, values := range b.values {
552 - encoding := topologyv1.Values(values...)
553 - if encoding.Values == nil {
554 - encoding.Values = []any{}
555 - }
556 - encodings[i] = encoding
557 - }
558 - return topologyv1.NewTable(b.rows(), b.columns, encodings)
559 -}
src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md
+14 -8
@@ -652,7 +652,7 @@ Current BGP peer and peer-family details from cached normalized SNMP data. Each
652
653 Provides the agent-wide SNMP topology view built from all currently running topology-enabled SNMP jobs.
654
655 -This function reads cached LLDP/CDP data collected by the independent topology refresh loop and returns a topology schema (devices, links, and stats). No additional SNMP requests are triggered when calling this function.
655 +This function reads cached LLDP/CDP, bridge, FDB, ARP, and STP data collected by the independent topology refresh loop and returns a netdata.topology.v1 payload with compact actor, link, evidence, and detail tables. No additional SNMP requests are triggered when calling this function.
656
657 Use cases:
658 - Discover Layer 2 neighbors and link mapping
@@ -684,16 +684,22 @@ No additional configuration is required.
684
685 #### Returns
686
687 -Agent-wide topology data in a JSON schema suitable for cross-agent aggregation.
687 +Agent-wide SNMP topology data using the netdata.topology.v1 schema, suitable for cross-agent aggregation.
688
689 | Column | Type | Unit | Visibility | Description |
690 |:-------|:-----|:-----|:-----------|:------------|
691 -| schema_version | integer | | | Topology schema version. |
692 -| agent_id | string | | | Netdata Agent or vnode identifier that collected the data. |
693 -| collected_at | string | | | Collection timestamp in RFC 3339 format. |
694 -| devices | array | | | List of devices (local and discovered). |
695 -| links | array | | | List of discovered links (LLDP/CDP). |
696 -| stats | object | | | Summary stats (device/link counts). |
691 +| schema_version | string | | | Topology schema version. |
692 +| producer | object | | | Producer metadata identifying the SNMP L2 topology source, plugin, and local node when available. |
693 +| collected_at | datetime | | | Collection timestamp in RFC 3339 format. |
694 +| view | object | | | Topology view metadata, including selected mode and focus parameters when present. |
695 +| dictionaries | object | | | Compact-table dictionaries used by actors, links, evidence, and detail tables. |
696 +| types | object | | | Actor, link, evidence, table, and presentation type registry. |
697 +| presentation | object | | | Graph-level presentation metadata. |
698 +| actors | object | | | Compact actor table for managed devices, discovered devices, inferred endpoints, and network segments. |
699 +| links | object | | | Compact link table for LLDP, CDP, bridge, FDB, STP, ARP, SNMP, and inferred L2 relationships. |
700 +| evidence | object | | | Relationship evidence tables backing the rendered links. |
701 +| tables | object | | | Actor detail, port, path, and label tables used by topology modals. |
702 +| stats | object | | | Summary stats for collected observations, actors, links, and pruning/filtering decisions. |
703
704 ### Licenses
705
src/go/plugin/go.d/collector/snmp/metadata.yaml
+37 -13
@@ -875,7 +875,7 @@ modules:
875 description: |
876 Provides the agent-wide SNMP topology view built from all currently running topology-enabled SNMP jobs.
877
878 - This function reads cached LLDP/CDP data collected by the independent topology refresh loop and returns a topology schema (devices, links, and stats). No additional SNMP requests are triggered when calling this function.
878 + This function reads cached LLDP/CDP, bridge, FDB, ARP, and STP data collected by the independent topology refresh loop and returns a netdata.topology.v1 payload with compact actor, link, evidence, and detail tables. No additional SNMP requests are triggered when calling this function.
879
880 Use cases:
881 - Discover Layer 2 neighbors and link mapping
@@ -969,32 +969,56 @@ modules:
969 - id: "10"
970 name: "10"
971 returns:
972 - description: Agent-wide topology data in a JSON schema suitable for cross-agent aggregation.
972 + description: Agent-wide SNMP topology data using the netdata.topology.v1 schema, suitable for cross-agent aggregation.
973 columns:
974 - name: schema_version
975 - type: integer
975 + type: string
976 unit: ""
977 description: Topology schema version.
978 - - name: agent_id
979 - type: string
978 + - name: producer
979 + type: object
980 unit: ""
981 - description: Netdata Agent or vnode identifier that collected the data.
981 + description: Producer metadata identifying the SNMP L2 topology source, plugin, and local node when available.
982 - name: collected_at
983 - type: string
983 + type: datetime
984 unit: ""
985 description: Collection timestamp in RFC 3339 format.
986 - - name: devices
987 - type: array
986 + - name: view
987 + type: object
988 + unit: ""
989 + description: Topology view metadata, including selected mode and focus parameters when present.
990 + - name: dictionaries
991 + type: object
992 + unit: ""
993 + description: Compact-table dictionaries used by actors, links, evidence, and detail tables.
994 + - name: types
995 + type: object
996 unit: ""
989 - description: List of devices (local and discovered).
997 + description: Actor, link, evidence, table, and presentation type registry.
998 + - name: presentation
999 + type: object
1000 + unit: ""
1001 + description: Graph-level presentation metadata.
1002 + - name: actors
1003 + type: object
1004 + unit: ""
1005 + description: Compact actor table for managed devices, discovered devices, inferred endpoints, and network segments.
1006 - name: links
991 - type: array
1007 + type: object
1008 + unit: ""
1009 + description: Compact link table for LLDP, CDP, bridge, FDB, STP, ARP, SNMP, and inferred L2 relationships.
1010 + - name: evidence
1011 + type: object
1012 + unit: ""
1013 + description: Relationship evidence tables backing the rendered links.
1014 + - name: tables
1015 + type: object
1016 unit: ""
993 - description: List of discovered links (LLDP/CDP).
1017 + description: Actor detail, port, path, and label tables used by topology modals.
1018 - name: stats
1019 type: object
1020 unit: ""
997 - description: Summary stats (device/link counts).
1021 + description: Summary stats for collected observations, actors, links, and pruning/filtering decisions.
1022 performance: |
1023 Uses cached SNMP data only, no additional SNMP requests are triggered:<br/>• Responses are instantaneous from memory cache<br/>• Large devices with many discovered neighbors may return many rows
1024 security: |
src/go/plugin/go.d/collector/snmp_topology/func_topology_presentation.go
+2 -28
@@ -2,33 +2,7 @@
2
3 package snmptopology
4
5 -import (
6 - "github.com/netdata/netdata/go/plugins/pkg/funcapi"
7 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 -)
9 -
10 -func snmpTopologyPresentation() *topology.Presentation {
11 - deviceSummaryFields := topologyDeviceSummaryFields()
12 - deviceTables := topologyDeviceTables()
13 - linkOnlyTables := topologyLinkOnlyTables(deviceTables["links"])
14 - infoOnlyTabs := topologyInfoOnlyTabs()
15 -
16 - return &topology.Presentation{
17 - ActorTypes: topologyPresentationActorTypes(
18 - deviceSummaryFields,
19 - deviceTables,
20 - linkOnlyTables,
21 - infoOnlyTabs,
22 - topologySegmentSummaryFields(),
23 - topologyEndpointSummaryFields(),
24 - ),
25 - LinkTypes: topologyPresentationLinkTypes(),
26 - PortFields: topologyPresentationPortFields(),
27 - PortTypes: topologyPresentationPortTypes(),
28 - Legend: topologyPresentationLegend(),
29 - ActorClickBehavior: "highlight_connections",
30 - }
31 -}
5 +import "github.com/netdata/netdata/go/plugins/pkg/funcapi"
6
7 func topologyMethodConfig() funcapi.MethodConfig {
8 return funcapi.MethodConfig{
@@ -47,5 +21,5 @@ func topologyMethodConfig() funcapi.MethodConfig {
21 topologyManagedFocusParamConfig(nil),
22 topologyDepthParamConfig(),
23 },
50 - }.WithPresentation(snmpTopologyPresentation())
24 + }
25 }
src/go/plugin/go.d/collector/snmp_topology/func_topology_presentation_schema.go deleted
-96
@@ -1,96 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package snmptopology
4 -
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
6 -
7 -func topologyDeviceSummaryFields() []topology.PresentationSummaryField {
8 - return []topology.PresentationSummaryField{
9 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
10 - {Key: "vendor", Label: "Vendor", Sources: []string{"attributes.vendor", "attributes.vendor_derived"}},
11 - {Key: "model", Label: "Model", Sources: []string{"attributes.model"}},
12 - {Key: "sys_descr", Label: "Description", Sources: []string{"attributes.sys_descr", "match.sys_name"}},
13 - {Key: "sys_location", Label: "Location", Sources: []string{"attributes.sys_location"}},
14 - {Key: "sys_contact", Label: "Contact", Sources: []string{"attributes.sys_contact"}},
15 - {Key: "protocols", Label: "Protocols", Sources: []string{"attributes.protocols", "attributes.learned_sources"}},
16 - {Key: "capabilities", Label: "Capabilities", Sources: []string{"attributes.capabilities"}},
17 - {Key: "ports_total", Label: "Ports", Sources: []string{"attributes.ports_total"}},
18 - {Key: "vlan_count", Label: "VLANs", Sources: []string{"attributes.vlan_count"}},
19 - {Key: "fdb_total_macs", Label: "FDB MACs", Sources: []string{"attributes.fdb_total_macs"}},
20 - {Key: "lldp_neighbor_count", Label: "LLDP Neighbors", Sources: []string{"attributes.lldp_neighbor_count"}},
21 - {Key: "cdp_neighbor_count", Label: "CDP Neighbors", Sources: []string{"attributes.cdp_neighbor_count"}},
22 - {Key: "chart_id_prefix", Label: "Chart Prefix", Sources: []string{"attributes.chart_id_prefix"}},
23 - {Key: "netdata_host_id", Label: "Netdata Host", Sources: []string{"attributes.netdata_host_id"}},
24 - {Key: "source", Label: "Source", Sources: []string{"source"}},
25 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
26 - }
27 -}
28 -
29 -func topologyDeviceTables() map[string]topology.PresentationTable {
30 - return map[string]topology.PresentationTable{
31 - "ports": {
32 - Label: "Ports",
33 - Source: "data",
34 - BulletSource: true,
35 - Order: 1,
36 - Columns: []topology.PresentationTableColumn{
37 - {Key: "name", Label: "Port"},
38 - {Key: "oper_status", Label: "Status", Type: "badge"},
39 - {Key: "admin_status", Label: "Admin"},
40 - {Key: "port_type", Label: "Type", Type: "badge"},
41 - {Key: "link_mode", Label: "Mode", Type: "badge"},
42 - {Key: "topology_role", Label: "Role", Type: "badge"},
43 - {Key: "stp_state", Label: "STP", Type: "badge"},
44 - {Key: "vlan_ids", Label: "VLANs", Type: "count"},
45 - {Key: "fdb_mac_count", Label: "FDB", Type: "number"},
46 - {Key: "link_count", Label: "Links", Type: "number"},
47 - {Key: "neighbor_count", Label: "Neighbors", Type: "number"},
48 - },
49 - },
50 - "links": {
51 - Label: "Links",
52 - Source: "links",
53 - Order: 2,
54 - Columns: []topology.PresentationTableColumn{
55 - {Key: "localPort", Label: "Local Port"},
56 - {Key: "remoteLabel", Label: "Remote Actor", Type: "actor_link"},
57 - {Key: "remotePort", Label: "Remote Port"},
58 - {Key: "protocol", Label: "Protocol"},
59 - {Key: "direction", Label: "Direction"},
60 - },
61 - },
62 - }
63 -}
64 -
65 -func topologySegmentSummaryFields() []topology.PresentationSummaryField {
66 - return []topology.PresentationSummaryField{
67 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
68 - {Key: "learned_sources", Label: "Discovered By", Sources: []string{"attributes.learned_sources"}},
69 - {Key: "ports_total", Label: "Ports", Sources: []string{"attributes.ports_total"}},
70 - {Key: "endpoints_total", Label: "Endpoints", Sources: []string{"attributes.endpoints_total"}},
71 - {Key: "source", Label: "Source", Sources: []string{"source"}},
72 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
73 - }
74 -}
75 -
76 -func topologyEndpointSummaryFields() []topology.PresentationSummaryField {
77 - return []topology.PresentationSummaryField{
78 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
79 - {Key: "vendor", Label: "Vendor", Sources: []string{"attributes.vendor", "attributes.vendor_derived"}},
80 - {Key: "learned_sources", Label: "Discovered By", Sources: []string{"attributes.learned_sources"}},
81 - {Key: "source", Label: "Source", Sources: []string{"source"}},
82 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
83 - }
84 -}
85 -
86 -func topologyInfoOnlyTabs() []topology.PresentationModalTab {
87 - return []topology.PresentationModalTab{
88 - {ID: "info", Label: "Info"},
89 - }
90 -}
91 -
92 -func topologyLinkOnlyTables(linkTable topology.PresentationTable) map[string]topology.PresentationTable {
93 - return map[string]topology.PresentationTable{
94 - "links": linkTable,
95 - }
96 -}
src/go/plugin/go.d/collector/snmp_topology/func_topology_presentation_test.go
+5 -196
@@ -5,204 +5,13 @@ package snmptopology
5 import (
6 "testing"
7
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 "github.com/stretchr/testify/require"
9 )
10
12 -func TestSNMPTopologyPresentationMatchesExpectedContract(t *testing.T) {
13 - require.Equal(t, expectedSNMPTopologyPresentation(), snmpTopologyPresentation())
14 -}
15 -
16 -func expectedSNMPTopologyPresentation() *topology.Presentation {
17 - deviceSummaryFields := []topology.PresentationSummaryField{
18 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
19 - {Key: "vendor", Label: "Vendor", Sources: []string{"attributes.vendor", "attributes.vendor_derived"}},
20 - {Key: "model", Label: "Model", Sources: []string{"attributes.model"}},
21 - {Key: "sys_descr", Label: "Description", Sources: []string{"attributes.sys_descr", "match.sys_name"}},
22 - {Key: "sys_location", Label: "Location", Sources: []string{"attributes.sys_location"}},
23 - {Key: "sys_contact", Label: "Contact", Sources: []string{"attributes.sys_contact"}},
24 - {Key: "protocols", Label: "Protocols", Sources: []string{"attributes.protocols", "attributes.learned_sources"}},
25 - {Key: "capabilities", Label: "Capabilities", Sources: []string{"attributes.capabilities"}},
26 - {Key: "ports_total", Label: "Ports", Sources: []string{"attributes.ports_total"}},
27 - {Key: "vlan_count", Label: "VLANs", Sources: []string{"attributes.vlan_count"}},
28 - {Key: "fdb_total_macs", Label: "FDB MACs", Sources: []string{"attributes.fdb_total_macs"}},
29 - {Key: "lldp_neighbor_count", Label: "LLDP Neighbors", Sources: []string{"attributes.lldp_neighbor_count"}},
30 - {Key: "cdp_neighbor_count", Label: "CDP Neighbors", Sources: []string{"attributes.cdp_neighbor_count"}},
31 - {Key: "chart_id_prefix", Label: "Chart Prefix", Sources: []string{"attributes.chart_id_prefix"}},
32 - {Key: "netdata_host_id", Label: "Netdata Host", Sources: []string{"attributes.netdata_host_id"}},
33 - {Key: "source", Label: "Source", Sources: []string{"source"}},
34 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
35 - }
36 -
37 - deviceLinkTable := topology.PresentationTable{
38 - Label: "Links",
39 - Source: "links",
40 - Order: 2,
41 - Columns: []topology.PresentationTableColumn{
42 - {Key: "localPort", Label: "Local Port"},
43 - {Key: "remoteLabel", Label: "Remote Actor", Type: "actor_link"},
44 - {Key: "remotePort", Label: "Remote Port"},
45 - {Key: "protocol", Label: "Protocol"},
46 - {Key: "direction", Label: "Direction"},
47 - },
48 - }
49 -
50 - deviceTables := map[string]topology.PresentationTable{
51 - "ports": {
52 - Label: "Ports",
53 - Source: "data",
54 - BulletSource: true,
55 - Order: 1,
56 - Columns: []topology.PresentationTableColumn{
57 - {Key: "name", Label: "Port"},
58 - {Key: "oper_status", Label: "Status", Type: "badge"},
59 - {Key: "admin_status", Label: "Admin"},
60 - {Key: "port_type", Label: "Type", Type: "badge"},
61 - {Key: "link_mode", Label: "Mode", Type: "badge"},
62 - {Key: "topology_role", Label: "Role", Type: "badge"},
63 - {Key: "stp_state", Label: "STP", Type: "badge"},
64 - {Key: "vlan_ids", Label: "VLANs", Type: "count"},
65 - {Key: "fdb_mac_count", Label: "FDB", Type: "number"},
66 - {Key: "link_count", Label: "Links", Type: "number"},
67 - {Key: "neighbor_count", Label: "Neighbors", Type: "number"},
68 - },
69 - },
70 - "links": deviceLinkTable,
71 - }
72 -
73 - linkOnlyTables := map[string]topology.PresentationTable{
74 - "links": deviceLinkTable,
75 - }
76 -
77 - infoOnlyTabs := []topology.PresentationModalTab{
78 - {ID: "info", Label: "Info"},
79 - }
80 -
81 - segmentSummaryFields := []topology.PresentationSummaryField{
82 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
83 - {Key: "learned_sources", Label: "Discovered By", Sources: []string{"attributes.learned_sources"}},
84 - {Key: "ports_total", Label: "Ports", Sources: []string{"attributes.ports_total"}},
85 - {Key: "endpoints_total", Label: "Endpoints", Sources: []string{"attributes.endpoints_total"}},
86 - {Key: "source", Label: "Source", Sources: []string{"source"}},
87 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
88 - }
89 -
90 - endpointSummaryFields := []topology.PresentationSummaryField{
91 - {Key: "actor_type", Label: "Type", Sources: []string{"actor_type"}},
92 - {Key: "vendor", Label: "Vendor", Sources: []string{"attributes.vendor", "attributes.vendor_derived"}},
93 - {Key: "learned_sources", Label: "Discovered By", Sources: []string{"attributes.learned_sources"}},
94 - {Key: "source", Label: "Source", Sources: []string{"source"}},
95 - {Key: "layer", Label: "Layer", Sources: []string{"layer"}},
96 - }
97 -
98 - deviceType := func(label, colorSlot string) topology.PresentationActorType {
99 - return topology.PresentationActorType{
100 - Label: label,
101 - ColorSlot: colorSlot,
102 - Border: true,
103 - Role: "actor",
104 - SizeByLinks: true,
105 - ShowPortBullets: true,
106 - SummaryFields: deviceSummaryFields,
107 - Tables: deviceTables,
108 - ModalTabs: infoOnlyTabs,
109 - }
110 - }
11 +func TestSNMPTopologyMethodConfigDoesNotUseLegacyPresentation(t *testing.T) {
12 + method := topologyMethodConfig()
13
112 - return &topology.Presentation{
113 - ActorTypes: map[string]topology.PresentationActorType{
114 - "device": deviceType("Device", "primary"),
115 - "router": deviceType("Router", "primary"),
116 - "switch": deviceType("Switch", "primary"),
117 - "firewall": deviceType("Firewall", "warning"),
118 - "access_point": deviceType("Access Point", "info"),
119 - "server": deviceType("Server", "secondary"),
120 - "storage": deviceType("Storage", "secondary"),
121 - "load_balancer": deviceType("Load Balancer", "info"),
122 - "printer": deviceType("Printer", "neutral"),
123 - "phone": deviceType("Phone", "neutral"),
124 - "ups": deviceType("UPS", "structural"),
125 - "camera": deviceType("Camera", "neutral"),
126 - "segment": {
127 - Label: "Network segment",
128 - ColorSlot: "dim",
129 - SummaryFields: segmentSummaryFields,
130 - Tables: linkOnlyTables,
131 - ModalTabs: infoOnlyTabs,
132 - },
133 - "endpoint": {
134 - Label: "Inferred endpoint",
135 - ColorSlot: "derived",
136 - Border: true,
137 - Role: "endpoint",
138 - SummaryFields: endpointSummaryFields,
139 - Tables: linkOnlyTables,
140 - ModalTabs: infoOnlyTabs,
141 - },
142 - },
143 - LinkTypes: map[string]topology.PresentationLinkType{
144 - "lldp": {Label: "LLDP", ColorSlot: "accent", Width: 2},
145 - "cdp": {Label: "CDP", ColorSlot: "accent", Width: 2},
146 - "bridge": {Label: "Bridge", ColorSlot: "neutral"},
147 - "fdb": {Label: "FDB", ColorSlot: "neutral"},
148 - "stp": {Label: "STP", ColorSlot: "muted"},
149 - "arp": {Label: "ARP", ColorSlot: "muted"},
150 - "snmp": {Label: "SNMP", ColorSlot: "primary"},
151 - "probable": {Label: "Probable", ColorSlot: "dim"},
152 - },
153 - PortFields: []topology.PresentationPortField{
154 - {Key: "type", Label: "Type"},
155 - {Key: "role", Label: "Role"},
156 - {Key: "status", Label: "Status"},
157 - {Key: "mode", Label: "Mode"},
158 - {Key: "sources", Label: "Sources"},
159 - },
160 - PortTypes: map[string]topology.PresentationPortType{
161 - "lldp": {Label: "lldp/cdp", ColorSlot: "accent"},
162 - "switch_facing": {Label: "switch-facing", ColorSlot: "primary"},
163 - "host_facing": {Label: "host-facing", ColorSlot: "secondary"},
164 - "host_candidate": {Label: "host-candidate", ColorSlot: "info"},
165 - "trunk": {Label: "trunk", ColorSlot: "warning"},
166 - "access": {Label: "access", ColorSlot: "derived"},
167 - "topology": {Label: "unclassified", ColorSlot: "neutral"},
168 - "idle": {Label: "idle", ColorSlot: "muted"},
169 - "unknown": {Label: "unknown", ColorSlot: "dim"},
170 - },
171 - Legend: topology.PresentationLegend{
172 - Actors: []topology.PresentationLegendEntry{
173 - {Type: "router", Label: "Router"},
174 - {Type: "switch", Label: "Switch"},
175 - {Type: "firewall", Label: "Firewall"},
176 - {Type: "access_point", Label: "Access Point"},
177 - {Type: "server", Label: "Server"},
178 - {Type: "storage", Label: "Storage"},
179 - {Type: "load_balancer", Label: "Load Balancer"},
180 - {Type: "printer", Label: "Printer"},
181 - {Type: "phone", Label: "IP Phone"},
182 - {Type: "ups", Label: "UPS / PDU"},
183 - {Type: "camera", Label: "Camera / Media"},
184 - {Type: "device", Label: "Other device"},
185 - {Type: "endpoint", Label: "Inferred endpoint"},
186 - {Type: "segment", Label: "Network segment"},
187 - },
188 - Links: []topology.PresentationLegendEntry{
189 - {Type: "lldp", Label: "LLDP"},
190 - {Type: "cdp", Label: "CDP"},
191 - {Type: "snmp", Label: "SNMP"},
192 - {Type: "bridge", Label: "Bridge"},
193 - {Type: "probable", Label: "Probable"},
194 - },
195 - Ports: []topology.PresentationLegendEntry{
196 - {Type: "lldp", Label: "lldp/cdp"},
197 - {Type: "switch_facing", Label: "switch-facing"},
198 - {Type: "host_facing", Label: "host-facing"},
199 - {Type: "host_candidate", Label: "host-candidate"},
200 - {Type: "trunk", Label: "trunk"},
201 - {Type: "access", Label: "access"},
202 - {Type: "topology", Label: "unclassified"},
203 - {Type: "idle", Label: "idle"},
204 - },
205 - },
206 - ActorClickBehavior: "highlight_connections",
207 - }
14 + require.Equal(t, topologyMethodID, method.ID)
15 + require.Equal(t, "topology", method.ResponseType)
16 + require.Nil(t, method.Presentation())
17 }
src/go/plugin/go.d/collector/snmp_topology/func_topology_presentation_types.go deleted
-134
@@ -1,134 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package snmptopology
4 -
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
6 -
7 -func topologyPresentationActorTypes(
8 - deviceSummaryFields []topology.PresentationSummaryField,
9 - deviceTables map[string]topology.PresentationTable,
10 - linkOnlyTables map[string]topology.PresentationTable,
11 - infoOnlyTabs []topology.PresentationModalTab,
12 - segmentSummaryFields []topology.PresentationSummaryField,
13 - endpointSummaryFields []topology.PresentationSummaryField,
14 -) map[string]topology.PresentationActorType {
15 - deviceType := func(label, colorSlot string) topology.PresentationActorType {
16 - return topology.PresentationActorType{
17 - Label: label,
18 - ColorSlot: colorSlot,
19 - Border: true,
20 - Role: "actor",
21 - SizeByLinks: true,
22 - ShowPortBullets: true,
23 - SummaryFields: deviceSummaryFields,
24 - Tables: deviceTables,
25 - ModalTabs: infoOnlyTabs,
26 - }
27 - }
28 -
29 - return map[string]topology.PresentationActorType{
30 - "device": deviceType("Device", "primary"),
31 - "router": deviceType("Router", "primary"),
32 - "switch": deviceType("Switch", "primary"),
33 - "firewall": deviceType("Firewall", "warning"),
34 - "access_point": deviceType("Access Point", "info"),
35 - "server": deviceType("Server", "secondary"),
36 - "storage": deviceType("Storage", "secondary"),
37 - "load_balancer": deviceType("Load Balancer", "info"),
38 - "printer": deviceType("Printer", "neutral"),
39 - "phone": deviceType("Phone", "neutral"),
40 - "ups": deviceType("UPS", "structural"),
41 - "camera": deviceType("Camera", "neutral"),
42 - "segment": {
43 - Label: "Network segment",
44 - ColorSlot: "dim",
45 - SummaryFields: segmentSummaryFields,
46 - Tables: linkOnlyTables,
47 - ModalTabs: infoOnlyTabs,
48 - },
49 - "endpoint": {
50 - Label: "Inferred endpoint",
51 - ColorSlot: "derived",
52 - Border: true,
53 - Role: "endpoint",
54 - SummaryFields: endpointSummaryFields,
55 - Tables: linkOnlyTables,
56 - ModalTabs: infoOnlyTabs,
57 - },
58 - }
59 -}
60 -
61 -func topologyPresentationLinkTypes() map[string]topology.PresentationLinkType {
62 - return map[string]topology.PresentationLinkType{
63 - "lldp": {Label: "LLDP", ColorSlot: "accent", Width: 2},
64 - "cdp": {Label: "CDP", ColorSlot: "accent", Width: 2},
65 - "bridge": {Label: "Bridge", ColorSlot: "neutral"},
66 - "fdb": {Label: "FDB", ColorSlot: "neutral"},
67 - "stp": {Label: "STP", ColorSlot: "muted"},
68 - "arp": {Label: "ARP", ColorSlot: "muted"},
69 - "snmp": {Label: "SNMP", ColorSlot: "primary"},
70 - "probable": {Label: "Probable", ColorSlot: "dim"},
71 - }
72 -}
73 -
74 -func topologyPresentationPortFields() []topology.PresentationPortField {
75 - return []topology.PresentationPortField{
76 - {Key: "type", Label: "Type"},
77 - {Key: "role", Label: "Role"},
78 - {Key: "status", Label: "Status"},
79 - {Key: "mode", Label: "Mode"},
80 - {Key: "sources", Label: "Sources"},
81 - }
82 -}
83 -
84 -func topologyPresentationPortTypes() map[string]topology.PresentationPortType {
85 - return map[string]topology.PresentationPortType{
86 - "lldp": {Label: "lldp/cdp", ColorSlot: "accent"},
87 - "switch_facing": {Label: "switch-facing", ColorSlot: "primary"},
88 - "host_facing": {Label: "host-facing", ColorSlot: "secondary"},
89 - "host_candidate": {Label: "host-candidate", ColorSlot: "info"},
90 - "trunk": {Label: "trunk", ColorSlot: "warning"},
91 - "access": {Label: "access", ColorSlot: "derived"},
92 - "topology": {Label: "unclassified", ColorSlot: "neutral"},
93 - "idle": {Label: "idle", ColorSlot: "muted"},
94 - "unknown": {Label: "unknown", ColorSlot: "dim"},
95 - }
96 -}
97 -
98 -func topologyPresentationLegend() topology.PresentationLegend {
99 - return topology.PresentationLegend{
100 - Actors: []topology.PresentationLegendEntry{
101 - {Type: "router", Label: "Router"},
102 - {Type: "switch", Label: "Switch"},
103 - {Type: "firewall", Label: "Firewall"},
104 - {Type: "access_point", Label: "Access Point"},
105 - {Type: "server", Label: "Server"},
106 - {Type: "storage", Label: "Storage"},
107 - {Type: "load_balancer", Label: "Load Balancer"},
108 - {Type: "printer", Label: "Printer"},
109 - {Type: "phone", Label: "IP Phone"},
110 - {Type: "ups", Label: "UPS / PDU"},
111 - {Type: "camera", Label: "Camera / Media"},
112 - {Type: "device", Label: "Other device"},
113 - {Type: "endpoint", Label: "Inferred endpoint"},
114 - {Type: "segment", Label: "Network segment"},
115 - },
116 - Links: []topology.PresentationLegendEntry{
117 - {Type: "lldp", Label: "LLDP"},
118 - {Type: "cdp", Label: "CDP"},
119 - {Type: "snmp", Label: "SNMP"},
120 - {Type: "bridge", Label: "Bridge"},
121 - {Type: "probable", Label: "Probable"},
122 - },
123 - Ports: []topology.PresentationLegendEntry{
124 - {Type: "lldp", Label: "lldp/cdp"},
125 - {Type: "switch_facing", Label: "switch-facing"},
126 - {Type: "host_facing", Label: "host-facing"},
127 - {Type: "host_candidate", Label: "host-candidate"},
128 - {Type: "trunk", Label: "trunk"},
129 - {Type: "access", Label: "access"},
130 - {Type: "topology", Label: "unclassified"},
131 - {Type: "idle", Label: "idle"},
132 - },
133 - }
134 -}
src/go/plugin/go.d/collector/snmp_topology/topology_local_actor_match.go
+2 -6
@@ -2,13 +2,9 @@
2
3 package snmptopology
4
5 -import (
6 - "strings"
5 +import "strings"
6
8 - "github.com/netdata/netdata/go/plugins/pkg/topology"
9 -)
10 -
11 -func matchLocalTopologyActor(match topology.Match, local topologyDevice) bool {
7 +func matchLocalTopologyActor(match topologyMatch, local topologyDevice) bool {
8 localChassisID := strings.TrimSpace(local.ChassisID)
9 if localChassisID != "" {
10 for _, chassisID := range match.ChassisIDs {
src/go/plugin/go.d/collector/snmp_topology/topology_match_keys.go
+2 -4
@@ -5,11 +5,9 @@ package snmptopology
5 import (
6 "fmt"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func canonicalMatchKey(match topology.Match) string {
10 +func canonicalMatchKey(match topologyMatch) string {
11 if key := canonicalPrimaryMACListKey(match); key != "" {
12 return "mac:" + key
13 }
@@ -34,7 +32,7 @@ func canonicalMatchKey(match topology.Match) string {
32 return ""
33 }
34
37 -func topologyLinkSortKey(link topology.Link) string {
35 +func topologyLinkSortKey(link topologyLink) string {
36 return strings.Join([]string{
37 link.Protocol,
38 link.Direction,
src/go/plugin/go.d/collector/snmp_topology/topology_match_keys_identity.go
+1 -3
@@ -5,11 +5,9 @@ package snmptopology
5 import (
6 "sort"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func topologyMatchIdentityKeys(match topology.Match) []string {
10 +func topologyMatchIdentityKeys(match topologyMatch) []string {
11 seen := make(map[string]struct{}, 8)
12 add := func(kind, value string) {
13 value = strings.TrimSpace(value)
src/go/plugin/go.d/collector/snmp_topology/topology_match_keys_lists.go
+1 -3
@@ -5,11 +5,9 @@ package snmptopology
5 import (
6 "sort"
7 "strings"
8 -
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
8 )
9
12 -func canonicalPrimaryMACListKey(match topology.Match) string {
10 +func canonicalPrimaryMACListKey(match topologyMatch) string {
11 seen := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
12 for _, value := range match.MacAddresses {
13 if mac := normalizeMAC(value); mac != "" {
src/go/plugin/go.d/collector/snmp_topology/topology_registry_build.go
+1 -1
@@ -100,7 +100,7 @@ func buildSNMPL2TopologyData(
100 return topologyData{}, false
101 }
102
103 - data := topologyengine.ToTopologyData(result, topologyengine.TopologyDataOptions{
103 + data := topologyengine.ToGraph(result, topologyengine.GraphOptions{
104 SchemaVersion: topologySchemaVersion,
105 Source: "snmp",
106 Layer: "2",
src/go/plugin/go.d/collector/snmp_topology/topology_snapshot_builder.go
+1 -1
@@ -96,7 +96,7 @@ func (c *topologyCache) snapshot() (topologyData, bool) {
96 return topologyData{}, false
97 }
98
99 - data := topologyengine.ToTopologyData(result, topologyengine.TopologyDataOptions{
99 + data := topologyengine.ToGraph(result, topologyengine.GraphOptions{
100 SchemaVersion: topologySchemaVersion,
101 Source: "snmp",
102 Layer: "2",
src/go/plugin/go.d/collector/snmp_topology/topology_types.go
+8 -8
@@ -2,17 +2,17 @@
2
3 package snmptopology
4
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
5 +import topologyengine "github.com/netdata/netdata/go/plugins/pkg/l2topology"
6
7 const topologySchemaVersion = "2.0"
8
9 -type topologyData = topology.Data
10 -type topologyActor = topology.Actor
11 -type topologyMatch = topology.Match
12 -type topologyLink = topology.Link
13 -type topologyLinkEndpoint = topology.LinkEndpoint
14 -type topologyFlow = topology.Flow
15 -type topologyIPPolicy = topology.IPPolicy
9 +type topologyData = topologyengine.Graph
10 +type topologyActor = topologyengine.Actor
11 +type topologyMatch = topologyengine.Match
12 +type topologyLink = topologyengine.Link
13 +type topologyLinkEndpoint = topologyengine.LinkEndpoint
14 +type topologyFlow = topologyengine.Flow
15 +type topologyIPPolicy = topologyengine.IPPolicy
16
17 type topologyManagementAddress struct {
18 Address string `json:"address"`
src/go/plugin/go.d/collector/vsphere/func_readiness_test.go
-1
@@ -36,7 +36,6 @@ func TestVSphereMethods(t *testing.T) {
36 wantName: "vSphere Topology",
37 responseType: "topology",
38 alias: "topology:vsphere",
39 - presentation: true,
39 },
40 }
41
src/go/plugin/go.d/collector/vsphere/func_topology.go
+138 -230
@@ -4,23 +4,29 @@ package vsphere
4
5 import (
6 "context"
7 - "sort"
7 "strings"
8 "time"
9
10 "github.com/netdata/netdata/go/plugins/pkg/funcapi"
12 - "github.com/netdata/netdata/go/plugins/pkg/topology"
13 - rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"
11 + topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1"
12 )
13
14 const (
15 topologyMethodID = "topology:vsphere"
16 topologyMethodHelp = "Reports cached vSphere inventory topology for datacenters, clusters, hosts, VMs, datastores, networks, datastore clusters, and resource pools."
17
20 - vsphereTopologySchemaVersion = "2.0"
21 - vsphereTopologySource = "vsphere"
22 - vsphereTopologyLayer = "virtualization"
23 - vsphereTopologyView = "inventory"
18 + vsphereTopologySource = "vsphere"
19 + vsphereTopologyLayer = "virtualization"
20 + vsphereTopologyView = "vsphere-inventory"
21 +
22 + vsphereTopologyDetailTable = "vsphere_object_detail"
23 + vsphereTopologyLabelsTable = "actor_labels"
24 + vsphereTopologyOwnershipLink = "vsphere_ownership"
25 + vsphereTopologyRunsOnLink = "vsphere_runs_on"
26 + vsphereTopologyNetworkLink = "vsphere_connected_to"
27 + vsphereOwnershipEvidenceType = "vsphere_ownership_evidence"
28 + vsphereRunsOnEvidenceType = "vsphere_runs_on_evidence"
29 + vsphereNetworkConnectionEvidence = "vsphere_network_connection_evidence"
30 )
31
32 type funcTopology struct {
@@ -39,7 +45,7 @@ func vsphereTopologyMethodConfig() funcapi.MethodConfig {
45 Help: topologyMethodHelp,
46 RequireCloud: true,
47 ResponseType: "topology",
42 - }.WithPresentation(vsphereTopologyPresentation())
48 + }
49 }
50
51 func (f *funcTopology) MethodParams(context.Context, string) ([]funcapi.ParamConfig, error) {
@@ -71,250 +77,201 @@ func (f *funcTopology) Cleanup(context.Context) {
77 // No per-invocation resources are allocated by the topology function.
78 }
79
74 -func (c *Collector) topologyData(agentID string) (topology.Data, bool) {
80 +func (c *Collector) topologyData(agentID string) (topologyv1.Data, bool) {
81 c.collectionLock.RLock()
82 defer c.collectionLock.RUnlock()
83
84 if c.resources == nil {
79 - return topology.Data{}, false
85 + return topologyv1.Data{}, false
86 }
87
82 - actors := make([]topology.Actor, 0, topologyActorCount(c.resources))
83 - links := make([]topology.Link, 0, topologyLinkCount(c.resources))
88 + builder := newVSphereTopologyBuilder()
89
90 for _, dc := range sortedDatacenters(c.resources.DataCenters) {
86 - actors = append(actors, vsphereTopologyActor("vsphere_datacenter", dc.ID, dc.Name, nil, nil))
91 + builder.addActor("vsphere_datacenter", dc.ID, dc.Name, "", vsphereActorDetail{
92 + objectType: "datacenter",
93 + }, nil)
94 }
95 for _, cluster := range sortedClusters(c.resources.Clusters) {
89 - actors = append(actors, vsphereTopologyActor("vsphere_cluster", cluster.ID, cluster.Name, map[string]any{
90 - "overall_status": cluster.OverallStatus,
91 - "drs_enabled": cluster.DrsEnabled,
92 - "ha_enabled": cluster.HaEnabled,
93 - "vsan_enabled": cluster.VSANEnabled,
94 - }, map[string]string{
96 + builder.addActor("vsphere_cluster", cluster.ID, cluster.Name, cluster.Hier.DC.ID, vsphereActorDetail{
97 + objectType: "cluster",
98 + datacenter: cluster.Hier.DC.Name,
99 + overallStatus: cluster.OverallStatus,
100 + drsEnabled: cluster.DrsEnabled,
101 + haEnabled: cluster.HaEnabled,
102 + vsanEnabled: cluster.VSANEnabled,
103 + cpuCapacityMHz: int64(cluster.TotalCpu),
104 + memoryCapacityMiB: cluster.TotalMemory / 1024 / 1024,
105 + }, mergeStringMaps(cluster.Labels, map[string]string{
106 "datacenter": cluster.Hier.DC.Name,
107 }))
108 if c.resources.DataCenters.Get(cluster.Hier.DC.ID) != nil {
98 - links = append(links, vsphereTopologyLink(cluster.Hier.DC.ID, cluster.ID, "contains", "Datacenter contains cluster"))
109 + builder.addLink(cluster.Hier.DC.ID, cluster.ID, vsphereTopologyOwnershipLink, "contains")
110 }
111 }
112 for _, host := range sortedHosts(c.resources.Hosts) {
102 - actors = append(actors, vsphereTopologyActor("vsphere_host", host.ID, host.Name, map[string]any{
103 - "connection_state": host.ConnectionState,
104 - "power_state": host.PowerState,
105 - "in_maintenance_mode": host.InMaintenanceMode,
106 - "overall_status": host.OverallStatus,
107 - }, map[string]string{
113 + parentID := firstExistingResourceID(func(id string) bool {
114 + if id == host.Hier.Cluster.ID {
115 + return c.resources.Clusters.Get(id) != nil
116 + }
117 + return c.resources.DataCenters.Get(id) != nil
118 + }, host.Hier.Cluster.ID, host.Hier.DC.ID)
119 + builder.addActor("vsphere_host", host.ID, host.Name, parentID, vsphereActorDetail{
120 + objectType: "host",
121 + datacenter: host.Hier.DC.Name,
122 + cluster: host.Hier.Cluster.Name,
123 + connectionState: host.ConnectionState,
124 + powerState: host.PowerState,
125 + inMaintenanceMode: host.InMaintenanceMode,
126 + overallStatus: host.OverallStatus,
127 + }, mergeStringMaps(host.Labels, map[string]string{
128 "datacenter": host.Hier.DC.Name,
129 "cluster": host.Hier.Cluster.Name,
130 }))
131 if c.resources.Clusters.Get(host.Hier.Cluster.ID) != nil {
112 - links = append(links, vsphereTopologyLink(host.Hier.Cluster.ID, host.ID, "contains", "Cluster contains ESXi host"))
132 + builder.addLink(host.Hier.Cluster.ID, host.ID, vsphereTopologyOwnershipLink, "contains")
133 } else if c.resources.DataCenters.Get(host.Hier.DC.ID) != nil {
114 - links = append(links, vsphereTopologyLink(host.Hier.DC.ID, host.ID, "contains", "Datacenter contains ESXi host"))
134 + builder.addLink(host.Hier.DC.ID, host.ID, vsphereTopologyOwnershipLink, "contains")
135 }
136 }
137 for _, vm := range sortedVMs(c.resources.VMs) {
118 - actors = append(actors, vsphereTopologyActor("vsphere_vm", vm.ID, vm.Name, map[string]any{
119 - "connection_state": vm.ConnectionState,
120 - "power_state": vm.PowerState,
121 - "overall_status": vm.OverallStatus,
122 - "tools_running_status": vm.ToolsRunningStatus,
123 - "tools_version_status": vm.ToolsVersionStatus,
124 - "consolidation_needed": vm.ConsolidationNeeded,
125 - "snapshot_count": vm.SnapshotCount,
126 - "snapshot_chain_depth": vm.SnapshotMaxChainDepth,
127 - "configured_vcpus": vm.ConfigCPU,
128 - "configured_memory_mib": vm.ConfigMemory,
129 - }, map[string]string{
138 + parentID := firstExistingResourceID(func(id string) bool {
139 + switch id {
140 + case vm.Hier.Host.ID:
141 + return c.resources.Hosts.Get(id) != nil
142 + case vm.Hier.Cluster.ID:
143 + return c.resources.Clusters.Get(id) != nil
144 + default:
145 + return c.resources.DataCenters.Get(id) != nil
146 + }
147 + }, vm.Hier.Host.ID, vm.Hier.Cluster.ID, vm.Hier.DC.ID)
148 + builder.addActor("vsphere_vm", vm.ID, vm.Name, parentID, vsphereActorDetail{
149 + objectType: "vm",
150 + datacenter: vm.Hier.DC.Name,
151 + cluster: vm.Hier.Cluster.Name,
152 + host: vm.Hier.Host.Name,
153 + connectionState: vm.ConnectionState,
154 + powerState: vm.PowerState,
155 + overallStatus: vm.OverallStatus,
156 + toolsRunningStatus: vm.ToolsRunningStatus,
157 + toolsVersionStatus: vm.ToolsVersionStatus,
158 + consolidationNeeded: vm.ConsolidationNeeded,
159 + snapshotCount: vm.SnapshotCount,
160 + snapshotChainDepth: vm.SnapshotMaxChainDepth,
161 + configuredVCPUs: vm.ConfigCPU,
162 + configuredMemoryMiB: vm.ConfigMemory,
163 + }, mergeStringMaps(vm.Labels, map[string]string{
164 "datacenter": vm.Hier.DC.Name,
165 "cluster": vm.Hier.Cluster.Name,
166 "host": vm.Hier.Host.Name,
167 }))
168 switch {
169 case c.resources.Hosts.Get(vm.Hier.Host.ID) != nil:
136 - links = append(links, vsphereTopologyLink(vm.Hier.Host.ID, vm.ID, "runs", "ESXi host runs VM"))
170 + builder.addLink(vm.ID, vm.Hier.Host.ID, vsphereTopologyRunsOnLink, "runs_on")
171 case c.resources.Clusters.Get(vm.Hier.Cluster.ID) != nil:
138 - links = append(links, vsphereTopologyLink(vm.Hier.Cluster.ID, vm.ID, "contains", "Cluster contains VM"))
172 + builder.addLink(vm.Hier.Cluster.ID, vm.ID, vsphereTopologyOwnershipLink, "contains")
173 case c.resources.DataCenters.Get(vm.Hier.DC.ID) != nil:
140 - links = append(links, vsphereTopologyLink(vm.Hier.DC.ID, vm.ID, "contains", "Datacenter contains VM"))
174 + builder.addLink(vm.Hier.DC.ID, vm.ID, vsphereTopologyOwnershipLink, "contains")
175 }
176 }
177 for _, datastore := range sortedDatastores(c.resources.Datastores) {
144 - attrs := map[string]any{
145 - "type": datastore.Type,
146 - "overall_status": datastore.OverallStatus,
147 - "accessible": datastore.Accessible,
148 - "maintenance_mode": datastore.MaintenanceMode,
149 - "capacity_bytes": datastore.Capacity,
150 - "free_space_bytes": datastore.FreeSpace,
151 - "uncommitted_bytes": datastore.Uncommitted,
152 - "multiple_host_access": datastore.MultipleHostAccess,
153 - }
154 - actors = append(actors, vsphereTopologyActor("vsphere_datastore", datastore.ID, datastore.Name, attrs, map[string]string{
178 + builder.addActor("vsphere_datastore", datastore.ID, datastore.Name, datastore.Hier.DC.ID, vsphereActorDetail{
179 + objectType: "datastore",
180 + datacenter: datastore.Hier.DC.Name,
181 + overallStatus: datastore.OverallStatus,
182 + datastoreType: datastore.Type,
183 + accessible: datastore.Accessible,
184 + maintenanceMode: datastore.MaintenanceMode,
185 + capacityBytes: datastore.Capacity,
186 + freeSpaceBytes: datastore.FreeSpace,
187 + uncommittedBytes: datastore.Uncommitted,
188 + multipleHostAccess: optionalBool(datastore.MultipleHostAccess),
189 + }, mergeStringMaps(datastore.Labels, map[string]string{
190 "datacenter": datastore.Hier.DC.Name,
191 "type": datastore.Type,
192 }))
193 if c.resources.DataCenters.Get(datastore.Hier.DC.ID) != nil {
159 - links = append(links, vsphereTopologyLink(datastore.Hier.DC.ID, datastore.ID, "contains", "Datacenter contains datastore"))
194 + builder.addLink(datastore.Hier.DC.ID, datastore.ID, vsphereTopologyOwnershipLink, "contains")
195 }
196 }
197 for _, network := range sortedNetworks(c.resources.Networks) {
163 - actors = append(actors, vsphereTopologyActor("vsphere_network", network.ID, network.Name, map[string]any{
164 - "type": network.Type,
165 - "accessible": network.Accessible,
166 - "ip_pool_name": network.IPPoolName,
167 - "overall_status": network.OverallStatus,
168 - "hosts": len(network.HostIDs),
169 - "vms": len(network.VMIDs),
170 - }, map[string]string{
198 + builder.addActor("vsphere_network", network.ID, network.Name, network.Hier.DC.ID, vsphereActorDetail{
199 + objectType: "network",
200 + datacenter: network.Hier.DC.Name,
201 + networkType: network.Type,
202 + accessible: network.Accessible,
203 + ipPoolName: network.IPPoolName,
204 + overallStatus: network.OverallStatus,
205 + networkHosts: len(network.HostIDs),
206 + networkVMs: len(network.VMIDs),
207 + }, mergeStringMaps(network.Labels, map[string]string{
208 "datacenter": network.Hier.DC.Name,
209 "type": network.Type,
210 }))
211 if c.resources.DataCenters.Get(network.Hier.DC.ID) != nil {
175 - links = append(links, vsphereTopologyLink(network.Hier.DC.ID, network.ID, "contains", "Datacenter contains network"))
212 + builder.addLink(network.Hier.DC.ID, network.ID, vsphereTopologyOwnershipLink, "contains")
213 }
177 - for _, hostID := range network.HostIDs {
214 + for _, hostID := range sortedStrings(network.HostIDs) {
215 if c.resources.Hosts.Get(hostID) != nil {
179 - links = append(links, vsphereTopologyLink(hostID, network.ID, "connects", "ESXi host connects to network"))
216 + builder.addLink(hostID, network.ID, vsphereTopologyNetworkLink, "connected_to")
217 }
218 }
182 - for _, vmID := range network.VMIDs {
219 + for _, vmID := range sortedStrings(network.VMIDs) {
220 if c.resources.VMs.Get(vmID) != nil {
184 - links = append(links, vsphereTopologyLink(vmID, network.ID, "connects", "VM connects to network"))
221 + builder.addLink(vmID, network.ID, vsphereTopologyNetworkLink, "connected_to")
222 }
223 }
224 }
225 for _, pod := range sortedStoragePods(c.resources.StoragePods) {
189 - actors = append(actors, vsphereTopologyActor("vsphere_datastore_cluster", pod.ID, pod.Name, map[string]any{
190 - "capacity_bytes": pod.Capacity,
191 - "free_space_bytes": pod.FreeSpace,
192 - "storage_drs_enabled": optionalBool(pod.StorageDRSEnabled),
193 - }, map[string]string{
226 + builder.addActor("vsphere_datastore_cluster", pod.ID, pod.Name, pod.Hier.DC.ID, vsphereActorDetail{
227 + objectType: "datastore_cluster",
228 + datacenter: pod.Hier.DC.Name,
229 + overallStatus: pod.OverallStatus,
230 + capacityBytes: pod.Capacity,
231 + freeSpaceBytes: pod.FreeSpace,
232 + storageDRSEnabled: optionalBool(pod.StorageDRSEnabled),
233 + }, mergeStringMaps(pod.Labels, map[string]string{
234 "datacenter": pod.Hier.DC.Name,
235 }))
236 if c.resources.DataCenters.Get(pod.Hier.DC.ID) != nil {
197 - links = append(links, vsphereTopologyLink(pod.Hier.DC.ID, pod.ID, "contains", "Datacenter contains datastore cluster"))
237 + builder.addLink(pod.Hier.DC.ID, pod.ID, vsphereTopologyOwnershipLink, "contains")
238 }
239 }
240 for _, pool := range sortedResourcePools(c.resources.ResourcePools) {
201 - actors = append(actors, vsphereTopologyActor("vsphere_resource_pool", pool.ID, pool.Name, map[string]any{
202 - "overall_status": pool.OverallStatus,
203 - "cpu_limit_mhz": pool.CpuLimit,
204 - "mem_limit_mb": pool.MemLimit,
205 - }, map[string]string{
241 + builder.addActor("vsphere_resource_pool", pool.ID, pool.Name, pool.Hier.Cluster.ID, vsphereActorDetail{
242 + objectType: "resource_pool",
243 + datacenter: pool.Hier.DC.Name,
244 + cluster: pool.Hier.Cluster.Name,
245 + resourcePool: pool.Name,
246 + overallStatus: pool.OverallStatus,
247 + cpuLimitMHz: pool.CpuLimit,
248 + memoryLimitMiB: pool.MemLimit,
249 + cpuReservation: pool.CpuReservation,
250 + memReservation: pool.MemReservation,
251 + }, mergeStringMaps(pool.Labels, map[string]string{
252 "datacenter": pool.Hier.DC.Name,
253 "cluster": pool.Hier.Cluster.Name,
254 "resource_pool": pool.Name,
255 }))
256 if c.resources.Clusters.Get(pool.Hier.Cluster.ID) != nil {
211 - links = append(links, vsphereTopologyLink(pool.Hier.Cluster.ID, pool.ID, "contains", "Cluster contains resource pool"))
257 + builder.addLink(pool.Hier.Cluster.ID, pool.ID, vsphereTopologyOwnershipLink, "contains")
258 }
259 }
260
215 - sort.SliceStable(actors, func(i, j int) bool { return actors[i].ActorID < actors[j].ActorID })
216 - sort.SliceStable(links, func(i, j int) bool {
217 - if links[i].SrcActorID != links[j].SrcActorID {
218 - return links[i].SrcActorID < links[j].SrcActorID
219 - }
220 - if links[i].DstActorID != links[j].DstActorID {
221 - return links[i].DstActorID < links[j].DstActorID
222 - }
223 - return links[i].LinkType < links[j].LinkType
261 + data, err := builder.data(strings.TrimSpace(agentID), time.Now().UTC(), map[string]any{
262 + "datacenters": len(c.resources.DataCenters),
263 + "clusters": len(c.resources.Clusters),
264 + "hosts": len(c.resources.Hosts),
265 + "vms": len(c.resources.VMs),
266 + "datastores": len(c.resources.Datastores),
267 + "networks": len(c.resources.Networks),
268 + "datastore_clusters": len(c.resources.StoragePods),
269 + "resource_pools": len(c.resources.ResourcePools),
270 })
225 -
226 - return topology.Data{
227 - SchemaVersion: vsphereTopologySchemaVersion,
228 - Source: vsphereTopologySource,
229 - Layer: vsphereTopologyLayer,
230 - AgentID: strings.TrimSpace(agentID),
231 - CollectedAt: time.Now().UTC(),
232 - View: vsphereTopologyView,
233 - Actors: actors,
234 - Links: links,
235 - Stats: map[string]any{
236 - "datacenters": len(c.resources.DataCenters),
237 - "clusters": len(c.resources.Clusters),
238 - "hosts": len(c.resources.Hosts),
239 - "vms": len(c.resources.VMs),
240 - "datastores": len(c.resources.Datastores),
241 - "networks": len(c.resources.Networks),
242 - "datastore_clusters": len(c.resources.StoragePods),
243 - "resource_pools": len(c.resources.ResourcePools),
244 - "actors": len(actors),
245 - "links": len(links),
246 - },
247 - }, true
248 -}
249 -
250 -func vsphereTopologyActor(actorType, id, name string, attrs map[string]any, labels map[string]string) topology.Actor {
251 - attrs = cleanTopologyAnyMap(attrs)
252 - attrs["name"] = name
253 - attrs["vsphere_id"] = id
254 -
255 - return topology.Actor{
256 - ActorID: vsphereTopologyActorID(actorType, id),
257 - ActorType: actorType,
258 - Layer: vsphereTopologyLayer,
259 - Source: vsphereTopologySource,
260 - Match: topology.Match{},
261 - Attributes: attrs,
262 - Labels: cleanTopologyStringMap(labels),
263 - }
264 -}
265 -
266 -func vsphereTopologyLink(srcID, dstID, linkType, label string) topology.Link {
267 - srcActorID := vsphereTopologyActorIDForResource(srcID)
268 - dstActorID := vsphereTopologyActorIDForResource(dstID)
269 - return topology.Link{
270 - Layer: vsphereTopologyLayer,
271 - Protocol: vsphereTopologySource,
272 - LinkType: linkType,
273 - Direction: "parent_to_child",
274 - SrcActorID: srcActorID,
275 - DstActorID: dstActorID,
276 - Src: vsphereTopologyEndpoint(srcActorID),
277 - Dst: vsphereTopologyEndpoint(dstActorID),
278 - Metrics: map[string]any{
279 - "label": label,
280 - },
281 - }
282 -}
283 -
284 -func vsphereTopologyEndpoint(actorID string) topology.LinkEndpoint {
285 - return topology.LinkEndpoint{
286 - Match: topology.Match{},
287 - Attributes: map[string]any{
288 - "actor_id": actorID,
289 - },
290 - }
291 -}
292 -
293 -func vsphereTopologyActorID(actorType, id string) string {
294 - return actorType + ":" + id
295 -}
296 -
297 -func vsphereTopologyActorIDForResource(id string) string {
298 - switch {
299 - case strings.HasPrefix(id, "datacenter-"):
300 - return vsphereTopologyActorID("vsphere_datacenter", id)
301 - case strings.HasPrefix(id, "domain-"):
302 - return vsphereTopologyActorID("vsphere_cluster", id)
303 - case strings.HasPrefix(id, "host-"):
304 - return vsphereTopologyActorID("vsphere_host", id)
305 - case strings.HasPrefix(id, "vm-"):
306 - return vsphereTopologyActorID("vsphere_vm", id)
307 - case strings.HasPrefix(id, "datastore-"):
308 - return vsphereTopologyActorID("vsphere_datastore", id)
309 - case strings.HasPrefix(id, "network-"), strings.HasPrefix(id, "dvportgroup-"), strings.HasPrefix(id, "opaqueNetwork-"):
310 - return vsphereTopologyActorID("vsphere_network", id)
311 - case strings.HasPrefix(id, "group-p"):
312 - return vsphereTopologyActorID("vsphere_datastore_cluster", id)
313 - case strings.HasPrefix(id, "resgroup-"):
314 - return vsphereTopologyActorID("vsphere_resource_pool", id)
315 - default:
316 - return id
271 + if err != nil {
272 + return topologyv1.Data{}, false
273 }
274 + return data, true
275 }
276
277 func optionalBool(value *bool) any {
@@ -323,52 +280,3 @@ func optionalBool(value *bool) any {
280 }
281 return *value
282 }
326 -
327 -func cleanTopologyStringMap(in map[string]string) map[string]string {
328 - out := make(map[string]string, len(in))
329 - for k, v := range in {
330 - k = strings.TrimSpace(k)
331 - v = strings.TrimSpace(v)
332 - if k != "" && v != "" {
333 - out[k] = v
334 - }
335 - }
336 - if len(out) == 0 {
337 - return nil
338 - }
339 - return out
340 -}
341 -
342 -func cleanTopologyAnyMap(in map[string]any) map[string]any {
343 - out := make(map[string]any, len(in)+2)
344 - for k, v := range in {
345 - k = strings.TrimSpace(k)
346 - if k == "" || v == nil {
347 - continue
348 - }
349 - if s, ok := v.(string); ok {
350 - s = strings.TrimSpace(s)
351 - if s == "" {
352 - continue
353 - }
354 - out[k] = s
355 - continue
356 - }
357 - out[k] = v
358 - }
359 - return out
360 -}
361 -
362 -func topologyActorCount(resources *rs.Resources) int {
363 - return len(resources.DataCenters) + len(resources.Clusters) + len(resources.Hosts) + len(resources.VMs) +
364 - len(resources.Datastores) + len(resources.Networks) + len(resources.StoragePods) + len(resources.ResourcePools)
365 -}
366 -
367 -func topologyLinkCount(resources *rs.Resources) int {
368 - count := len(resources.Clusters) + len(resources.Hosts) + len(resources.VMs) +
369 - len(resources.Datastores) + len(resources.Networks) + len(resources.StoragePods) + len(resources.ResourcePools)
370 - for _, network := range resources.Networks {
371 - count += len(network.HostIDs) + len(network.VMIDs)
372 - }
373 - return count
374 -}
src/go/plugin/go.d/collector/vsphere/func_topology_presentation.go
+201 -56
@@ -2,28 +2,18 @@
2
3 package vsphere
4
5 -import "github.com/netdata/netdata/go/plugins/pkg/topology"
5 +import topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1"
6
7 -func vsphereTopologyPresentation() *topology.Presentation {
8 - return &topology.Presentation{
9 - ActorTypes: map[string]topology.PresentationActorType{
10 - "vsphere_datacenter": vsphereActorType("Datacenter", "blue", "name", "vsphere_id"),
11 - "vsphere_cluster": vsphereActorType("Cluster", "green", "name", "overall_status", "drs_enabled", "ha_enabled", "vsan_enabled"),
12 - "vsphere_host": vsphereActorType("ESXi Host", "orange", "name", "connection_state", "power_state", "overall_status"),
13 - "vsphere_vm": vsphereActorType("VM", "purple", "name", "connection_state", "power_state", "snapshot_count"),
14 - "vsphere_datastore": vsphereActorType("Datastore", "cyan", "name", "type", "accessible", "maintenance_mode"),
15 - "vsphere_network": vsphereActorType("Network", "yellow", "name", "type", "accessible", "hosts", "vms"),
16 - "vsphere_datastore_cluster": vsphereActorType("Datastore Cluster", "teal", "name", "storage_drs_enabled"),
17 - "vsphere_resource_pool": vsphereActorType("Resource Pool", "gray", "name", "overall_status"),
18 - },
19 - LinkTypes: map[string]topology.PresentationLinkType{
20 - "contains": {Label: "Contains", ColorSlot: "gray", Width: 1},
21 - "connects": {Label: "Connects",
22 - ColorSlot: "green", Width: 1},
23 - "runs": {Label: "Runs", ColorSlot: "blue", Width: 1},
7 +func vsphereTopologyPresentation() *topologyv1.Presentation {
8 + return &topologyv1.Presentation{
9 + ProfileVersion: "vsphere.inventory.v1",
10 + Selection: &topologyv1.SelectionPresentation{
11 + ActorClick: &topologyv1.ActorClickPresentation{
12 + Mode: "highlight_connections",
13 + },
14 },
25 - Legend: topology.PresentationLegend{
26 - Actors: []topology.PresentationLegendEntry{
15 + Legend: &topologyv1.PresentationLegend{
16 + Actors: []topologyv1.LegendEntry{
17 {Type: "vsphere_datacenter", Label: "Datacenter"},
18 {Type: "vsphere_cluster", Label: "Cluster"},
19 {Type: "vsphere_host", Label: "ESXi Host"},
@@ -33,51 +23,206 @@ func vsphereTopologyPresentation() *topology.Presentation {
23 {Type: "vsphere_datastore_cluster", Label: "Datastore Cluster"},
24 {Type: "vsphere_resource_pool", Label: "Resource Pool"},
25 },
36 - Links: []topology.PresentationLegendEntry{
37 - {Type: "contains", Label: "Contains"},
38 - {Type: "connects", Label: "Connects"},
39 - {Type: "runs", Label: "Runs"},
26 + Links: []topologyv1.LegendEntry{
27 + {Type: vsphereTopologyOwnershipLink, Label: "Contains"},
28 + {Type: vsphereTopologyRunsOnLink, Label: "Runs on"},
29 + {Type: vsphereTopologyNetworkLink, Label: "Connected to"},
30 },
31 },
42 - ActorClickBehavior: "highlight_connections",
32 }
33 }
34
46 -func vsphereActorType(label, colorSlot string, summaryKeys ...string) topology.PresentationActorType {
47 - return topology.PresentationActorType{
48 - Label: label,
49 - ColorSlot: colorSlot,
50 - Border: true,
51 - SummaryFields: vsphereSummaryFields(summaryKeys...),
35 +func vsphereActorPresentation(label, icon, colorSlot string) *topologyv1.ActorPresentation {
36 + return &topologyv1.ActorPresentation{
37 + Label: label,
38 + Role: "actor",
39 + Icon: icon,
40 + ColorSlot: colorSlot,
41 + Border: &topologyv1.BorderPresentation{
42 + Enabled: new(true),
43 + Style: "solid",
44 + },
45 + LabelPolicy: &topologyv1.LabelPolicy{
46 + Columns: []string{"name"},
47 + Fallback: "type_label",
48 + MaxLength: 80,
49 + Array: "reject",
50 + },
51 + Hover: &topologyv1.HoverPresentation{
52 + Fields: []topologyv1.PresentationField{
53 + {Key: "name", Label: "Name"},
54 + {Key: "object_type", Label: "Type"},
55 + {Key: "vsphere_moid", Label: "vSphere MoRef"},
56 + {Key: "overall_status", Label: "Status"},
57 + {Key: "power_state", Label: "Power"},
58 + },
59 + },
60 + Modal: vsphereActorModal(),
61 }
62 }
63
55 -func vsphereSummaryFields(keys ...string) []topology.PresentationSummaryField {
56 - fields := make([]topology.PresentationSummaryField, 0, len(keys))
57 - for _, key := range keys {
58 - fields = append(fields, topology.PresentationSummaryField{
59 - Key: key,
60 - Label: vsphereSummaryFieldLabels[key],
61 - Sources: []string{"attributes"},
62 - })
64 +func vsphereLinkPresentation(label, colorSlot, arrow, distance string) *topologyv1.LinkPresentation {
65 + return &topologyv1.LinkPresentation{
66 + Label: label,
67 + ColorSlot: colorSlot,
68 + LineStyle: "solid",
69 + Width: "normal",
70 + Curve: "auto",
71 + Arrow: arrow,
72 + Hover: &topologyv1.HoverPresentation{
73 + Fields: []topologyv1.PresentationField{
74 + {Key: "relationship", Label: "Relationship"},
75 + {Key: "evidence_count", Label: "Evidence"},
76 + },
77 + },
78 + Layout: &topologyv1.LinkLayoutPresentation{
79 + Strength: "normal",
80 + Distance: distance,
81 + },
82 }
64 - return fields
83 }
84
67 -var vsphereSummaryFieldLabels = map[string]string{
68 - "accessible": "Accessible",
69 - "connection_state": "Connection State",
70 - "drs_enabled": "DRS Enabled",
71 - "ha_enabled": "HA Enabled",
72 - "hosts": "Hosts",
73 - "maintenance_mode": "Maintenance Mode",
74 - "name": "Name",
75 - "overall_status": "Overall Status",
76 - "power_state": "Power State",
77 - "snapshot_count": "Snapshots",
78 - "storage_drs_enabled": "Storage DRS Enabled",
79 - "type": "Type",
80 - "vms": "VMs",
81 - "vsan_enabled": "vSAN Enabled",
82 - "vsphere_id": "vSphere ID",
85 +func vsphereActorModal() *topologyv1.ModalPresentation {
86 + return &topologyv1.ModalPresentation{
87 + Enabled: new(true),
88 + Labels: &topologyv1.ModalLabelsPresentation{
89 + Enabled: new(true),
90 + Table: vsphereTopologyLabelsTable,
91 + ActorColumn: "actor",
92 + KeyColumn: "key",
93 + ValueColumn: "value",
94 + SourceColumn: "source",
95 + KindColumn: "kind",
96 + ValueIndexColumn: "value_index",
97 + Identification: &topologyv1.ModalLabelIdentificationPresentation{
98 + Enabled: new(true),
99 + Fields: []topologyv1.ModalLabelIdentificationField{
100 + {Key: "object_type", Label: "Type", MaxValues: 1},
101 + {Key: "datacenter", Label: "Datacenter", MaxValues: 1},
102 + {Key: "cluster", Label: "Cluster", MaxValues: 1},
103 + {Key: "host", Label: "Host", MaxValues: 1},
104 + {Key: "resource_pool", Label: "Resource Pool", MaxValues: 1},
105 + },
106 + },
107 + },
108 + MiniTopology: &topologyv1.ModalMiniTopologyPresentation{
109 + Enabled: new(true),
110 + Depth: 1,
111 + IncludeLinkTypes: []string{
112 + vsphereTopologyOwnershipLink,
113 + vsphereTopologyRunsOnLink,
114 + vsphereTopologyNetworkLink,
115 + },
116 + },
117 + Sections: []topologyv1.ModalSection{
118 + {
119 + ID: "details",
120 + Label: "Details",
121 + Order: 10,
122 + Source: topologyv1.ModalSource{
123 + Kind: "actor_table",
124 + Table: vsphereTopologyDetailTable,
125 + },
126 + OwnerFilter: &topologyv1.ModalOwnerFilter{
127 + Mode: "actor_column",
128 + ActorColumn: "actor",
129 + },
130 + Columns: vsphereDetailModalColumns(),
131 + EmptyLabel: "No vSphere details",
132 + },
133 + {
134 + ID: "relationships",
135 + Label: "Relationships",
136 + Order: 20,
137 + Source: topologyv1.ModalSource{
138 + Kind: "links",
139 + },
140 + OwnerFilter: &topologyv1.ModalOwnerFilter{
141 + Mode: "incident_link",
142 + SrcActorColumn: "src_actor",
143 + DstActorColumn: "dst_actor",
144 + },
145 + Columns: vsphereRelationshipModalColumns(),
146 + EmptyLabel: "No vSphere relationships",
147 + },
148 + },
149 + }
150 +}
151 +
152 +func vsphereDetailModalColumns() []topologyv1.ModalColumn {
153 + return []topologyv1.ModalColumn{
154 + vsphereModalDirectColumn("object_type", "Type", "object_type", "badge"),
155 + vsphereModalDirectColumn("name", "Name", "name", "text"),
156 + vsphereModalDirectColumn("vsphere_moid", "vSphere MoRef", "vsphere_moid", "text"),
157 + vsphereModalDirectColumn("datacenter", "Datacenter", "datacenter", "text"),
158 + vsphereModalDirectColumn("cluster", "Cluster", "cluster", "text"),
159 + vsphereModalDirectColumn("host", "Host", "host", "text"),
160 + vsphereModalDirectColumn("resource_pool", "Resource Pool", "resource_pool", "text"),
161 + vsphereModalDirectColumn("connection_state", "Connection", "connection_state", "badge"),
162 + vsphereModalDirectColumn("power_state", "Power", "power_state", "badge"),
163 + vsphereModalDirectColumn("overall_status", "Status", "overall_status", "badge"),
164 + vsphereModalDirectColumn("accessible", "Accessible", "accessible", "badge"),
165 + vsphereModalDirectColumn("in_maintenance_mode", "In Maintenance", "in_maintenance_mode", "badge"),
166 + vsphereModalDirectColumn("maintenance_mode", "Maintenance", "maintenance_mode", "badge"),
167 + vsphereModalDirectColumn("datastore_type", "Datastore Type", "datastore_type", "badge"),
168 + vsphereModalDirectColumn("network_type", "Network Type", "network_type", "badge"),
169 + vsphereModalDirectColumnWithVisibility("ip_pool_name", "IP Pool", "ip_pool_name", "text", "expanded"),
170 + vsphereModalDirectColumn("snapshot_count", "Snapshots", "snapshot_count", "number"),
171 + vsphereModalDirectColumnWithVisibility("snapshot_chain_depth", "Snapshot Chain", "snapshot_chain_depth", "number", "expanded"),
172 + vsphereModalDirectColumn("configured_vcpus", "vCPUs", "configured_vcpus", "number"),
173 + vsphereModalDirectColumn("configured_memory_mib", "Memory MiB", "configured_memory_mib", "number"),
174 + vsphereModalDirectColumn("capacity_bytes", "Capacity", "capacity_bytes", "number"),
175 + vsphereModalDirectColumn("free_space_bytes", "Free Space", "free_space_bytes", "number"),
176 + vsphereModalDirectColumnWithVisibility("uncommitted_bytes", "Uncommitted", "uncommitted_bytes", "number", "expanded"),
177 + vsphereModalDirectColumn("cpu_capacity_mhz", "CPU Capacity MHz", "cpu_capacity_mhz", "number"),
178 + vsphereModalDirectColumn("memory_capacity_mib", "Memory Capacity MiB", "memory_capacity_mib", "number"),
179 + vsphereModalDirectColumn("cpu_limit_mhz", "CPU Limit MHz", "cpu_limit_mhz", "number"),
180 + vsphereModalDirectColumn("memory_limit_mib", "Memory Limit MiB", "memory_limit_mib", "number"),
181 + vsphereModalDirectColumnWithVisibility("cpu_reservation_mhz", "CPU Reservation MHz", "cpu_reservation_mhz", "number", "expanded"),
182 + vsphereModalDirectColumnWithVisibility("memory_reservation_mib", "Memory Reservation MiB", "memory_reservation_mib", "number", "expanded"),
183 + vsphereModalDirectColumn("network_hosts", "Hosts", "network_hosts", "number"),
184 + vsphereModalDirectColumn("network_vms", "VMs", "network_vms", "number"),
185 + vsphereModalDirectColumn("drs_enabled", "DRS", "drs_enabled", "badge"),
186 + vsphereModalDirectColumn("ha_enabled", "HA", "ha_enabled", "badge"),
187 + vsphereModalDirectColumn("vsan_enabled", "vSAN", "vsan_enabled", "badge"),
188 + vsphereModalDirectColumn("storage_drs_enabled", "Storage DRS", "storage_drs_enabled", "badge"),
189 + vsphereModalDirectColumn("multiple_host_access", "Multiple Host Access", "multiple_host_access", "badge"),
190 + vsphereModalDirectColumn("consolidation_needed", "Consolidation", "consolidation_needed", "badge"),
191 + vsphereModalDirectColumnWithVisibility("tools_running_status", "Tools Running", "tools_running_status", "badge", "expanded"),
192 + vsphereModalDirectColumnWithVisibility("tools_version_status", "Tools Version", "tools_version_status", "badge", "expanded"),
193 + }
194 +}
195 +
196 +func vsphereRelationshipModalColumns() []topologyv1.ModalColumn {
197 + return []topologyv1.ModalColumn{
198 + vsphereModalDirectColumn("relationship", "Relationship", "relationship", "badge"),
199 + {
200 + ID: "related_object",
201 + Label: "Related Object",
202 + Projection: topologyv1.ModalProjection{
203 + Kind: "opposite_actor",
204 + SrcActorColumn: "src_actor",
205 + DstActorColumn: "dst_actor",
206 + },
207 + Cell: "actor_link",
208 + },
209 + vsphereModalDirectColumn("evidence_count", "Evidence", "evidence_count", "number"),
210 + }
211 +}
212 +
213 +func vsphereModalDirectColumn(id, label, sourceColumn, cell string) topologyv1.ModalColumn {
214 + return vsphereModalDirectColumnWithVisibility(id, label, sourceColumn, cell, "")
215 +}
216 +
217 +func vsphereModalDirectColumnWithVisibility(id, label, sourceColumn, cell, visibility string) topologyv1.ModalColumn {
218 + return topologyv1.ModalColumn{
219 + ID: id,
220 + Label: label,
221 + Projection: topologyv1.ModalProjection{
222 + Kind: "direct",
223 + Column: sourceColumn,
224 + },
225 + Cell: cell,
226 + Visibility: visibility,
227 + }
228 }
src/go/plugin/go.d/collector/vsphere/func_topology_test.go
+192 -65
@@ -4,11 +4,15 @@ package vsphere
4
5 import (
6 "context"
7 + "encoding/json"
8 + "os"
9 + "path/filepath"
10 "testing"
11
9 - "github.com/netdata/netdata/go/plugins/pkg/topology"
12 + topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1"
13 rs "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/vsphere/resources"
14
15 + "github.com/santhosh-tekuri/jsonschema/v6"
16 "github.com/stretchr/testify/require"
17 )
18
@@ -33,10 +37,11 @@ func TestFuncTopology_Handle(t *testing.T) {
37 },
38 want: 200,
39 check: func(t *testing.T, raw any) {
36 - data, ok := raw.(topology.Data)
40 + data, ok := raw.(topologyv1.Data)
41 require.True(t, ok)
38 - require.Empty(t, data.Actors)
39 - require.Empty(t, data.Links)
42 + validateVSphereTopologyV1Data(t, data)
43 + require.Equal(t, 0, data.Actors.Rows)
44 + require.Equal(t, 0, data.Links.Rows)
45 require.EqualValues(t, 0, data.Stats["hosts"])
46 require.EqualValues(t, 0, data.Stats["vms"])
47 },
@@ -62,7 +67,7 @@ func TestFuncTopology_Handle(t *testing.T) {
67 }
68 }
69
65 -func TestFuncTopology_HandleWithInventoryCache(t *testing.T) {
70 +func newVSphereTopologyTestCollector() *Collector {
71 collr := New()
72 collr.resources = &rs.Resources{
73 DataCenters: rs.DataCenters{
@@ -141,60 +146,60 @@ func TestFuncTopology_HandleWithInventoryCache(t *testing.T) {
146 },
147 },
148 }
149 + return collr
150 +}
151 +
152 +func TestFuncTopology_HandleWithInventoryCache(t *testing.T) {
153 + collr := newVSphereTopologyTestCollector()
154 handler := &funcTopology{collector: collr, agentID: "vsphere_vcenter1"}
155
156 resp := handler.Handle(context.Background(), "topology:vsphere", nil)
157
158 require.Equal(t, 200, resp.Status)
159 require.Equal(t, "topology", resp.ResponseType)
150 - data, ok := resp.Data.(topology.Data)
160 + data, ok := resp.Data.(topologyv1.Data)
161 require.True(t, ok)
152 - require.Equal(t, "vsphere", data.Source)
153 - require.Equal(t, "virtualization", data.Layer)
154 - require.Equal(t, "inventory", data.View)
155 - require.Equal(t, "vsphere_vcenter1", data.AgentID)
156 - require.Len(t, data.Actors, 8)
157 - require.Len(t, data.Links, 9)
158 -
159 - actors := topologyActorsByID(data.Actors)
160 - require.Contains(t, actors, "vsphere_datacenter:datacenter-1")
161 - require.Contains(t, actors, "vsphere_cluster:domain-c1")
162 - require.Contains(t, actors, "vsphere_host:host-1")
163 - require.Contains(t, actors, "vsphere_vm:vm-1")
164 - require.Contains(t, actors, "vsphere_network:network-1")
165 - require.Equal(t, "VM1", actors["vsphere_vm:vm-1"].Attributes["name"])
166 - require.EqualValues(t, 2, actors["vsphere_vm:vm-1"].Attributes["snapshot_count"])
167 - require.Equal(t, "VM Network", actors["vsphere_network:network-1"].Attributes["name"])
168 -
169 - require.Contains(t, topologyLinkKeys(data.Links), "vsphere_host:host-1->vsphere_vm:vm-1:runs")
170 - require.Contains(t, topologyLinkKeys(data.Links), "vsphere_host:host-1->vsphere_network:network-1:connects")
171 - require.Contains(t, topologyLinkKeys(data.Links), "vsphere_vm:vm-1->vsphere_network:network-1:connects")
172 -}
162 + validateVSphereTopologyV1Data(t, data)
163 + require.Equal(t, topologyv1.SchemaVersion, data.SchemaVersion)
164 + require.Equal(t, "vsphere", data.Producer.Source)
165 + require.Equal(t, "go.d/vsphere", data.Producer.Plugin)
166 + require.Equal(t, "vsphere_vcenter1", data.Producer.Instance)
167 + require.Equal(t, "virtualization", data.Types.ActorTypes["vsphere_vm"].Layer)
168 + require.Equal(t, 8, data.Actors.Rows)
169 + require.Equal(t, 9, data.Links.Rows)
170
174 -func TestVSphereTopologyActorIDForResource(t *testing.T) {
175 - tests := map[string]struct {
176 - id string
177 - want string
178 - }{
179 - "opaque network": {
180 - id: "opaqueNetwork-1",
181 - want: "vsphere_network:opaqueNetwork-1",
182 - },
183 - "standard network": {
184 - id: "network-1",
185 - want: "vsphere_network:network-1",
186 - },
187 - "distributed port group": {
188 - id: "dvportgroup-1",
189 - want: "vsphere_network:dvportgroup-1",
190 - },
191 - }
171 + actors := topologyTableRows(t, data.Actors, data.Dictionaries)
172 + requireTopologyRow(t, actors, "vsphere_moid", "datacenter-1")
173 + requireTopologyRow(t, actors, "vsphere_moid", "domain-c1")
174 + requireTopologyRow(t, actors, "vsphere_moid", "host-1")
175 + vm := requireTopologyRow(t, actors, "vsphere_moid", "vm-1")
176 + network := requireTopologyRow(t, actors, "vsphere_moid", "network-1")
177 + require.Equal(t, "vsphere_vm", vm["type"])
178 + require.Equal(t, "vm", vm["object_type"])
179 + require.Equal(t, "VM1", vm["name"])
180 + require.Equal(t, "network", network["object_type"])
181 + require.Equal(t, "VM Network", network["name"])
182
193 - for name, tc := range tests {
194 - t.Run(name, func(t *testing.T) {
195 - require.Equal(t, tc.want, vsphereTopologyActorIDForResource(tc.id))
196 - })
197 - }
183 + require.NotNil(t, data.Tables)
184 + details := topologyTableRows(t, data.Tables.Actor[vsphereTopologyDetailTable].Table, data.Dictionaries)
185 + vmDetail := requireTopologyRow(t, details, "vsphere_moid", "vm-1")
186 + require.EqualValues(t, 2, vmDetail["snapshot_count"])
187 + require.EqualValues(t, 3, vmDetail["snapshot_chain_depth"])
188 +
189 + links := topologyTableRows(t, data.Links, data.Dictionaries)
190 + keys := topologyLinkKeys(t, actors, links)
191 + require.Contains(t, keys, "datacenter:datacenter-1->cluster:domain-c1:vsphere_ownership")
192 + require.Contains(t, keys, "cluster:domain-c1->host:host-1:vsphere_ownership")
193 + require.Contains(t, keys, "vm:vm-1->host:host-1:vsphere_runs_on")
194 + require.Contains(t, keys, "host:host-1->network:network-1:vsphere_connected_to")
195 + require.Contains(t, keys, "vm:vm-1->network:network-1:vsphere_connected_to")
196 +
197 + evidence := topologyTableRows(t, data.Evidence[vsphereRunsOnEvidenceType].Table, data.Dictionaries)
198 + runEvidence := requireTopologyRow(t, evidence, "relationship", "runs_on")
199 + require.Equal(t, "vm", runEvidence["source_type"])
200 + require.Equal(t, "vm-1", runEvidence["source_moid"])
201 + require.Equal(t, "host", runEvidence["target_type"])
202 + require.Equal(t, "host-1", runEvidence["target_moid"])
203 }
204
205 func TestFuncTopology_DoesNotLinkToFilteredActors(t *testing.T) {
@@ -230,26 +235,148 @@ func TestFuncTopology_DoesNotLinkToFilteredActors(t *testing.T) {
235 data, ok := collr.topologyData("agent")
236
237 require.True(t, ok)
233 - require.Len(t, data.Actors, 3)
234 - keys := topologyLinkKeys(data.Links)
235 - require.Contains(t, keys, "vsphere_datacenter:datacenter-1->vsphere_host:host-1:contains")
236 - require.Contains(t, keys, "vsphere_datacenter:datacenter-1->vsphere_vm:vm-1:contains")
237 - require.NotContains(t, keys, "vsphere_cluster:domain-c-filtered->vsphere_host:host-1:contains")
238 - require.NotContains(t, keys, "vsphere_host:host-filtered->vsphere_vm:vm-1:runs")
238 + validateVSphereTopologyV1Data(t, data)
239 + require.Equal(t, 3, data.Actors.Rows)
240 + actors := topologyTableRows(t, data.Actors, data.Dictionaries)
241 + keys := topologyLinkKeys(t, actors, topologyTableRows(t, data.Links, data.Dictionaries))
242 + require.Contains(t, keys, "datacenter:datacenter-1->host:host-1:vsphere_ownership")
243 + require.Contains(t, keys, "datacenter:datacenter-1->vm:vm-1:vsphere_ownership")
244 + require.NotContains(t, keys, "cluster:domain-c-filtered->host:host-1:vsphere_ownership")
245 + require.NotContains(t, keys, "vm:vm-1->host:host-filtered:vsphere_runs_on")
246 }
247
241 -func topologyActorsByID(actors []topology.Actor) map[string]topology.Actor {
242 - out := make(map[string]topology.Actor, len(actors))
243 - for _, actor := range actors {
244 - out[actor.ActorID] = actor
248 +func validateVSphereTopologyV1Data(t *testing.T, data topologyv1.Data) {
249 + t.Helper()
250 +
251 + payload, err := json.Marshal(data)
252 + require.NoError(t, err)
253 +
254 + var decodedData map[string]any
255 + require.NoError(t, json.Unmarshal(payload, &decodedData))
256 + require.NoError(t, topologyv1.ValidateDecodedData(decodedData))
257 +
258 + schemaBytes, err := os.ReadFile(filepath.Clean(filepath.Join("..", "..", "..", "..", "..", "plugins.d", "FUNCTION_TOPOLOGY_SCHEMA.json")))
259 + require.NoError(t, err)
260 +
261 + var schemaDoc any
262 + require.NoError(t, json.Unmarshal(schemaBytes, &schemaDoc))
263 +
264 + compiler := jsonschema.NewCompiler()
265 + require.NoError(t, compiler.AddResource("schema.json", schemaDoc))
266 + schema, err := compiler.Compile("schema.json")
267 + require.NoError(t, err)
268 +
269 + response := map[string]any{
270 + "status": 200,
271 + "type": topologyv1.ResponseType,
272 + "data": decodedData,
273 + }
274 + require.NoError(t, schema.Validate(response))
275 +}
276 +
277 +func topologyTableRows(t *testing.T, table topologyv1.Table, dictionaries topologyv1.Dictionaries) []map[string]any {
278 + t.Helper()
279 +
280 + rows := make([]map[string]any, table.Rows)
281 + for row := range rows {
282 + rows[row] = map[string]any{"_row": row}
283 + }
284 +
285 + for columnIndex, column := range table.Columns {
286 + values := topologyColumnValues(t, table.Rows, table.Values[columnIndex])
287 + for rowIndex, value := range values {
288 + if column.Type == "string_ref" && value != nil {
289 + value = resolveTopologyStringRef(t, dictionaries, column.Dictionary, value)
290 + }
291 + rows[rowIndex][column.ID] = value
292 + }
293 + }
294 + return rows
295 +}
296 +
297 +func topologyColumnValues(t *testing.T, rows int, encoding topologyv1.ColumnEncoding) []any {
298 + t.Helper()
299 +
300 + switch value := encoding.(type) {
301 + case topologyv1.ConstEncoding:
302 + values := make([]any, rows)
303 + for i := range values {
304 + values[i] = value.Value
305 + }
306 + return values
307 + case topologyv1.ValuesEncoding:
308 + require.Len(t, value.Values, rows)
309 + return value.Values
310 + case topologyv1.DictEncoding:
311 + require.Len(t, value.Indexes, rows)
312 + values := make([]any, rows)
313 + for i, index := range value.Indexes {
314 + require.GreaterOrEqual(t, index, 0)
315 + require.Less(t, index, len(value.Values))
316 + values[i] = value.Values[index]
317 + }
318 + return values
319 + default:
320 + t.Fatalf("unsupported topology column encoding %T", encoding)
321 + return nil
322 + }
323 +}
324 +
325 +func resolveTopologyStringRef(t *testing.T, dictionaries topologyv1.Dictionaries, name string, value any) string {
326 + t.Helper()
327 +
328 + index, ok := value.(int)
329 + require.True(t, ok)
330 + values := dictionaries[name]
331 + require.Less(t, index, len(values))
332 + text, ok := values[index].(string)
333 + require.True(t, ok)
334 + return text
335 +}
336 +
337 +func requireTopologyRow(t *testing.T, rows []map[string]any, key string, value any) map[string]any {
338 + t.Helper()
339 +
340 + for _, row := range rows {
341 + if row[key] == value {
342 + return row
343 + }
344 }
246 - return out
345 + t.Fatalf("topology row with %s=%v not found", key, value)
346 + return nil
347 }
348
249 -func topologyLinkKeys(links []topology.Link) map[string]struct{} {
250 - out := make(map[string]struct{}, len(links))
349 +func topologyLinkKeys(t *testing.T, actors, links []map[string]any) map[string]struct{} {
350 + t.Helper()
351 +
352 + actorNames := make(map[int]string, len(actors))
353 + for _, actor := range actors {
354 + row, ok := actor["_row"].(int)
355 + require.True(t, ok)
356 + actorNames[row] = actor["object_type"].(string) + ":" + actor["vsphere_moid"].(string)
357 + }
358 +
359 + keys := make(map[string]struct{}, len(links))
360 for _, link := range links {
252 - out[link.SrcActorID+"->"+link.DstActorID+":"+link.LinkType] = struct{}{}
361 + src := topologyIntValue(t, link["src_actor"])
362 + dst := topologyIntValue(t, link["dst_actor"])
363 + keys[actorNames[src]+"->"+actorNames[dst]+":"+link["type"].(string)] = struct{}{}
364 + }
365 + return keys
366 +}
367 +
368 +func topologyIntValue(t *testing.T, value any) int {
369 + t.Helper()
370 +
371 + switch v := value.(type) {
372 + case int:
373 + return v
374 + case int64:
375 + return int(v)
376 + case uint64:
377 + return int(v)
378 + default:
379 + t.Fatalf("unexpected integer value %T", value)
380 + return 0
381 }
254 - return out
382 }
src/go/plugin/go.d/collector/vsphere/func_topology_v1_builder.go new
+719
@@ -0,0 +1,719 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package vsphere
4 +
5 +import (
6 + "fmt"
7 + "maps"
8 + "sort"
9 + "strings"
10 + "time"
11 +
12 + topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1"
13 +)
14 +
15 +type vsphereActorDetail struct {
16 + objectType string
17 + parentObjectType string
18 + parentID string
19 + moid string
20 + name string
21 + datacenter string
22 + cluster string
23 + host string
24 + resourcePool string
25 + connectionState string
26 + powerState string
27 + overallStatus string
28 + accessible any
29 + inMaintenanceMode any
30 + maintenanceMode string
31 + datastoreType string
32 + networkType string
33 + ipPoolName string
34 + storageDRSEnabled any
35 + multipleHostAccess any
36 + snapshotCount any
37 + snapshotChainDepth any
38 + configuredVCPUs any
39 + configuredMemoryMiB any
40 + capacityBytes any
41 + freeSpaceBytes any
42 + uncommittedBytes any
43 + cpuCapacityMHz any
44 + memoryCapacityMiB any
45 + cpuLimitMHz any
46 + memoryLimitMiB any
47 + cpuReservation any
48 + memReservation any
49 + networkHosts any
50 + networkVMs any
51 + drsEnabled any
52 + haEnabled any
53 + vsanEnabled any
54 + consolidationNeeded any
55 + toolsRunningStatus string
56 + toolsVersionStatus string
57 +}
58 +
59 +type vsphereTopologyBuilder struct {
60 + strings *topologyv1.StringDictionary
61 + actorIndexes map[string]int
62 + actorObjectTypes map[string]string
63 + actors *topologyv1.TableBuilder
64 + links *topologyv1.TableBuilder
65 + evidence map[string]*topologyv1.TableBuilder
66 + details *topologyv1.TableBuilder
67 + labels *topologyv1.TableBuilder
68 +}
69 +
70 +func newVSphereTopologyBuilder() *vsphereTopologyBuilder {
71 + return &vsphereTopologyBuilder{
72 + strings: topologyv1.NewStringDictionary(),
73 + actorIndexes: make(map[string]int),
74 + actorObjectTypes: make(map[string]string),
75 + actors: topologyv1.NewTableBuilder(vsphereTopologyActorColumns()...),
76 + links: topologyv1.NewTableBuilder(vsphereTopologyLinkColumns()...),
77 + evidence: map[string]*topologyv1.TableBuilder{
78 + vsphereOwnershipEvidenceType: topologyv1.NewTableBuilder(vsphereTopologyEvidenceColumns()...),
79 + vsphereRunsOnEvidenceType: topologyv1.NewTableBuilder(vsphereTopologyEvidenceColumns()...),
80 + vsphereNetworkConnectionEvidence: topologyv1.NewTableBuilder(vsphereTopologyEvidenceColumns()...),
81 + },
82 + details: topologyv1.NewTableBuilder(vsphereTopologyDetailColumns()...),
83 + labels: topologyv1.NewTableBuilder(vsphereTopologyLabelColumns()...),
84 + }
85 +}
86 +
87 +func (b *vsphereTopologyBuilder) addActor(actorType, moid, name, parentID string, detail vsphereActorDetail, labels map[string]string) int {
88 + actorType = cleanTopologyString(actorType)
89 + moid = cleanTopologyString(moid)
90 + if actorType == "" || moid == "" {
91 + return -1
92 + }
93 + if row, ok := b.actorIndexes[moid]; ok {
94 + return row
95 + }
96 +
97 + objectType := cleanTopologyString(detail.objectType)
98 + if objectType == "" {
99 + objectType = strings.TrimPrefix(actorType, "vsphere_")
100 + }
101 + name = cleanTopologyString(name)
102 + if name == "" {
103 + name = moid
104 + }
105 +
106 + parentID = cleanTopologyString(parentID)
107 + parentObjectType := ""
108 + if _, ok := b.actorIndexes[parentID]; !ok {
109 + parentID = ""
110 + } else {
111 + parentObjectType = b.actorObjectTypes[parentID]
112 + }
113 +
114 + row := b.actors.Add(
115 + b.stringRef(actorType),
116 + b.stringRef(objectType),
117 + b.stringRef(moid),
118 + b.stringRef(name),
119 + b.nullableStringRef(parentObjectType),
120 + b.nullableStringRef(parentID),
121 + b.nullableStringRef(detail.overallStatus),
122 + b.nullableStringRef(detail.powerState),
123 + )
124 + b.actorIndexes[moid] = row
125 + b.actorObjectTypes[moid] = objectType
126 +
127 + detail.objectType = objectType
128 + detail.parentObjectType = parentObjectType
129 + detail.parentID = parentID
130 + detail.moid = moid
131 + detail.name = name
132 + b.addActorDetail(row, detail)
133 + b.addActorLabels(row, detail, labels)
134 +
135 + return row
136 +}
137 +
138 +func (b *vsphereTopologyBuilder) addLink(srcID, dstID, linkType, relationship string) int {
139 + srcID = cleanTopologyString(srcID)
140 + dstID = cleanTopologyString(dstID)
141 + linkType = cleanTopologyString(linkType)
142 + relationship = cleanTopologyString(relationship)
143 + if relationship == "" {
144 + relationship = linkType
145 + }
146 +
147 + src, ok := b.actorIndexes[srcID]
148 + if !ok {
149 + return -1
150 + }
151 + dst, ok := b.actorIndexes[dstID]
152 + if !ok {
153 + return -1
154 + }
155 + evidenceType := vsphereEvidenceTypeForLink(linkType)
156 + evidence := b.evidence[evidenceType]
157 + if evidence == nil {
158 + return -1
159 + }
160 +
161 + link := b.links.Add(
162 + src,
163 + dst,
164 + b.stringRef(linkType),
165 + b.stringRef(relationship),
166 + 1,
167 + )
168 + evidence.Add(
169 + link,
170 + b.stringRef(b.actorObjectTypes[srcID]),
171 + b.stringRef(srcID),
172 + b.stringRef(b.actorObjectTypes[dstID]),
173 + b.stringRef(dstID),
174 + b.stringRef(relationship),
175 + )
176 +
177 + return link
178 +}
179 +
180 +func (b *vsphereTopologyBuilder) data(agentID string, collectedAt time.Time, resourceStats map[string]any) (topologyv1.Data, error) {
181 + actorTable, err := b.actors.Table()
182 + if err != nil {
183 + return topologyv1.Data{}, fmt.Errorf("build vSphere topology actors table: %w", err)
184 + }
185 + linkTable, err := b.links.Table()
186 + if err != nil {
187 + return topologyv1.Data{}, fmt.Errorf("build vSphere topology links table: %w", err)
188 + }
189 + detailTable, err := b.details.Table()
190 + if err != nil {
191 + return topologyv1.Data{}, fmt.Errorf("build vSphere topology detail table: %w", err)
192 + }
193 + labelTable, err := b.labels.Table()
194 + if err != nil {
195 + return topologyv1.Data{}, fmt.Errorf("build vSphere topology labels table: %w", err)
196 + }
197 +
198 + evidence, evidenceRows, err := b.evidenceMap()
199 + if err != nil {
200 + return topologyv1.Data{}, err
201 + }
202 + if agentID = cleanTopologyString(agentID); agentID == "" {
203 + agentID = "unknown"
204 + }
205 + if collectedAt.IsZero() {
206 + collectedAt = time.Now().UTC()
207 + }
208 +
209 + stats := make(map[string]any, len(resourceStats)+5)
210 + maps.Copy(stats, resourceStats)
211 + stats["actor_rows"] = actorTable.Rows
212 + stats["link_rows"] = linkTable.Rows
213 + stats["evidence_rows"] = evidenceRows
214 + stats["detail_rows"] = detailTable.Rows
215 + stats["label_rows"] = labelTable.Rows
216 +
217 + return topologyv1.Data{
218 + SchemaVersion: topologyv1.SchemaVersion,
219 + Producer: topologyv1.Producer{
220 + Source: vsphereTopologySource,
221 + Instance: agentID,
222 + Plugin: "go.d/vsphere",
223 + Capabilities: []string{"inventory", "topology-v1"},
224 + },
225 + CollectedAt: collectedAt,
226 + View: &topologyv1.View{
227 + ID: vsphereTopologyView,
228 + Scope: "vsphere_object",
229 + Mode: "detailed",
230 + SupportedModes: []string{"detailed"},
231 + GroupBy: []string{"object_type"},
232 + },
233 + Dictionaries: topologyv1.Dictionaries{
234 + "strings": b.strings.Values(),
235 + },
236 + Types: vsphereTopologyTypes(),
237 + Presentation: vsphereTopologyPresentation(),
238 + Actors: actorTable,
239 + Links: linkTable,
240 + Evidence: evidence,
241 + Tables: &topologyv1.DetailTables{
242 + Actor: map[string]topologyv1.DetailTable{
243 + vsphereTopologyDetailTable: {
244 + Type: vsphereTopologyDetailTable,
245 + Table: detailTable,
246 + },
247 + vsphereTopologyLabelsTable: {
248 + Type: vsphereTopologyLabelsTable,
249 + Table: labelTable,
250 + },
251 + },
252 + },
253 + Stats: stats,
254 + }, nil
255 +}
256 +
257 +func (b *vsphereTopologyBuilder) evidenceMap() (topologyv1.EvidenceMap, int, error) {
258 + sections := topologyv1.EvidenceMap{}
259 + totalRows := 0
260 + for _, evidenceType := range []string{vsphereOwnershipEvidenceType, vsphereRunsOnEvidenceType, vsphereNetworkConnectionEvidence} {
261 + table, err := b.evidence[evidenceType].Table()
262 + if err != nil {
263 + return nil, 0, fmt.Errorf("build vSphere topology %s table: %w", evidenceType, err)
264 + }
265 + sections[evidenceType] = topologyv1.EvidenceSection{
266 + Type: evidenceType,
267 + Table: table,
268 + }
269 + totalRows += table.Rows
270 + }
271 + return sections, totalRows, nil
272 +}
273 +
274 +func (b *vsphereTopologyBuilder) addActorDetail(actor int, detail vsphereActorDetail) {
275 + b.details.Add(
276 + actor,
277 + b.stringRef(detail.objectType),
278 + b.stringRef(detail.moid),
279 + b.stringRef(detail.name),
280 + b.nullableStringRef(detail.parentObjectType),
281 + b.nullableStringRef(detail.parentID),
282 + b.nullableStringRef(detail.datacenter),
283 + b.nullableStringRef(detail.cluster),
284 + b.nullableStringRef(detail.host),
285 + b.nullableStringRef(detail.resourcePool),
286 + b.nullableStringRef(detail.connectionState),
287 + b.nullableStringRef(detail.powerState),
288 + b.nullableStringRef(detail.overallStatus),
289 + nullableBool(detail.accessible),
290 + nullableBool(detail.inMaintenanceMode),
291 + b.nullableStringRef(detail.maintenanceMode),
292 + b.nullableStringRef(detail.datastoreType),
293 + b.nullableStringRef(detail.networkType),
294 + b.nullableStringRef(detail.ipPoolName),
295 + nullableBool(detail.storageDRSEnabled),
296 + nullableBool(detail.multipleHostAccess),
297 + nullableUint(detail.snapshotCount),
298 + nullableUint(detail.snapshotChainDepth),
299 + nullableUint(detail.configuredVCPUs),
300 + nullableUint(detail.configuredMemoryMiB),
301 + nullableUint(detail.capacityBytes),
302 + nullableUint(detail.freeSpaceBytes),
303 + nullableUint(detail.uncommittedBytes),
304 + nullableInt(detail.cpuCapacityMHz),
305 + nullableInt(detail.memoryCapacityMiB),
306 + nullableInt(detail.cpuLimitMHz),
307 + nullableInt(detail.memoryLimitMiB),
308 + nullableInt(detail.cpuReservation),
309 + nullableInt(detail.memReservation),
310 + nullableUint(detail.networkHosts),
311 + nullableUint(detail.networkVMs),
312 + nullableBool(detail.drsEnabled),
313 + nullableBool(detail.haEnabled),
314 + nullableBool(detail.vsanEnabled),
315 + nullableBool(detail.consolidationNeeded),
316 + b.nullableStringRef(detail.toolsRunningStatus),
317 + b.nullableStringRef(detail.toolsVersionStatus),
318 + )
319 +}
320 +
321 +func (b *vsphereTopologyBuilder) addActorLabels(actor int, detail vsphereActorDetail, labels map[string]string) {
322 + b.addActorLabel(actor, "name", detail.name, "vsphere", "identity", nil)
323 + b.addActorLabel(actor, "object_type", detail.objectType, "vsphere", "identity", nil)
324 + b.addActorLabel(actor, "vsphere_moid", detail.moid, "vsphere", "identity", nil)
325 + b.addActorLabel(actor, "datacenter", detail.datacenter, "vsphere", "hierarchy", nil)
326 + b.addActorLabel(actor, "cluster", detail.cluster, "vsphere", "hierarchy", nil)
327 + b.addActorLabel(actor, "host", detail.host, "vsphere", "hierarchy", nil)
328 + b.addActorLabel(actor, "resource_pool", detail.resourcePool, "vsphere", "hierarchy", nil)
329 +
330 + keys := make([]string, 0, len(labels))
331 + for key := range labels {
332 + key = cleanTopologyString(key)
333 + if key != "" {
334 + keys = append(keys, key)
335 + }
336 + }
337 + sort.Strings(keys)
338 + for _, key := range keys {
339 + b.addActorLabel(actor, key, labels[key], "vsphere", "label", nil)
340 + }
341 +}
342 +
343 +func (b *vsphereTopologyBuilder) addActorLabel(actor int, key, value, source, kind string, valueIndex any) {
344 + key = cleanTopologyString(key)
345 + value = cleanTopologyString(value)
346 + if key == "" || value == "" {
347 + return
348 + }
349 + b.labels.Add(
350 + actor,
351 + b.stringRef(key),
352 + b.stringRef(value),
353 + b.nullableStringRef(source),
354 + b.nullableStringRef(kind),
355 + nullableUint(valueIndex),
356 + )
357 +}
358 +
359 +func (b *vsphereTopologyBuilder) stringRef(value string) int {
360 + return b.strings.Ref(cleanTopologyString(value))
361 +}
362 +
363 +func (b *vsphereTopologyBuilder) nullableStringRef(value string) any {
364 + value = cleanTopologyString(value)
365 + if value == "" {
366 + return nil
367 + }
368 + return b.strings.Ref(value)
369 +}
370 +
371 +func vsphereTopologyActorColumns() []topologyv1.Column {
372 + return []topologyv1.Column{
373 + topologyv1.NewColumn("type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
374 + topologyv1.NewColumn("object_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("identity")),
375 + topologyv1.NewColumn("vsphere_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("identity")),
376 + topologyv1.NewColumn("name", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
377 + topologyv1.NewColumn("parent_object_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("parent_identity")),
378 + topologyv1.NewColumn("parent_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("parent_identity")),
379 + topologyv1.NewColumn("overall_status", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
380 + topologyv1.NewColumn("power_state", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
381 + }
382 +}
383 +
384 +func vsphereTopologyLinkColumns() []topologyv1.Column {
385 + return []topologyv1.Column{
386 + topologyv1.NewColumn("src_actor", "actor_ref", topologyv1.WithRole("reference")),
387 + topologyv1.NewColumn("dst_actor", "actor_ref", topologyv1.WithRole("reference")),
388 + topologyv1.NewColumn("type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
389 + topologyv1.NewColumn("relationship", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
390 + topologyv1.NewColumn("evidence_count", "uint", topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
391 + }
392 +}
393 +
394 +func vsphereTopologyEvidenceColumns() []topologyv1.Column {
395 + return []topologyv1.Column{
396 + topologyv1.NewColumn("link", "link_ref", topologyv1.WithRole("reference")),
397 + topologyv1.NewColumn("source_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
398 + topologyv1.NewColumn("source_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
399 + topologyv1.NewColumn("target_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
400 + topologyv1.NewColumn("target_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
401 + topologyv1.NewColumn("relationship", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("group_key")),
402 + }
403 +}
404 +
405 +func vsphereTopologyDetailColumns() []topologyv1.Column {
406 + return []topologyv1.Column{
407 + topologyv1.NewColumn("actor", "actor_ref", topologyv1.WithRole("reference")),
408 + topologyv1.NewColumn("object_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
409 + topologyv1.NewColumn("vsphere_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("identity")),
410 + topologyv1.NewColumn("name", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
411 + topologyv1.NewColumn("parent_object_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("parent_identity")),
412 + topologyv1.NewColumn("parent_moid", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("parent_identity")),
413 + topologyv1.NewColumn("datacenter", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
414 + topologyv1.NewColumn("cluster", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
415 + topologyv1.NewColumn("host", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
416 + topologyv1.NewColumn("resource_pool", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
417 + topologyv1.NewColumn("connection_state", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
418 + topologyv1.NewColumn("power_state", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
419 + topologyv1.NewColumn("overall_status", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
420 + topologyv1.NewColumn("accessible", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
421 + topologyv1.NewColumn("in_maintenance_mode", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
422 + topologyv1.NewColumn("maintenance_mode", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
423 + topologyv1.NewColumn("datastore_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
424 + topologyv1.NewColumn("network_type", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
425 + topologyv1.NewColumn("ip_pool_name", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
426 + topologyv1.NewColumn("storage_drs_enabled", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
427 + topologyv1.NewColumn("multiple_host_access", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
428 + topologyv1.NewColumn("snapshot_count", "uint", topologyv1.WithNullable(), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
429 + topologyv1.NewColumn("snapshot_chain_depth", "uint", topologyv1.WithNullable(), topologyv1.WithRole("metric"), topologyv1.WithAggregation("max")),
430 + topologyv1.NewColumn("configured_vcpus", "uint", topologyv1.WithNullable(), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
431 + topologyv1.NewColumn("configured_memory_mib", "uint", topologyv1.WithNullable(), topologyv1.WithUnit("MiB"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
432 + topologyv1.NewColumn("capacity_bytes", "uint", topologyv1.WithNullable(), topologyv1.WithUnit("bytes"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
433 + topologyv1.NewColumn("free_space_bytes", "uint", topologyv1.WithNullable(), topologyv1.WithUnit("bytes"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
434 + topologyv1.NewColumn("uncommitted_bytes", "uint", topologyv1.WithNullable(), topologyv1.WithUnit("bytes"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
435 + topologyv1.NewColumn("cpu_capacity_mhz", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MHz"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
436 + topologyv1.NewColumn("memory_capacity_mib", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MiB"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
437 + topologyv1.NewColumn("cpu_limit_mhz", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MHz"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("last")),
438 + topologyv1.NewColumn("memory_limit_mib", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MiB"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("last")),
439 + topologyv1.NewColumn("cpu_reservation_mhz", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MHz"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("last")),
440 + topologyv1.NewColumn("memory_reservation_mib", "int", topologyv1.WithNullable(), topologyv1.WithUnit("MiB"), topologyv1.WithRole("metric"), topologyv1.WithAggregation("last")),
441 + topologyv1.NewColumn("network_hosts", "uint", topologyv1.WithNullable(), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
442 + topologyv1.NewColumn("network_vms", "uint", topologyv1.WithNullable(), topologyv1.WithRole("metric"), topologyv1.WithAggregation("sum")),
443 + topologyv1.NewColumn("drs_enabled", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
444 + topologyv1.NewColumn("ha_enabled", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
445 + topologyv1.NewColumn("vsan_enabled", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
446 + topologyv1.NewColumn("consolidation_needed", "bool", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
447 + topologyv1.NewColumn("tools_running_status", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
448 + topologyv1.NewColumn("tools_version_status", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
449 + }
450 +}
451 +
452 +func vsphereTopologyLabelColumns() []topologyv1.Column {
453 + return []topologyv1.Column{
454 + topologyv1.NewColumn("actor", "actor_ref", topologyv1.WithRole("reference")),
455 + topologyv1.NewColumn("key", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
456 + topologyv1.NewColumn("value", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithRole("attribute")),
457 + topologyv1.NewColumn("source", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
458 + topologyv1.NewColumn("kind", "string_ref", topologyv1.WithDictionary("strings"), topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
459 + topologyv1.NewColumn("value_index", "uint", topologyv1.WithNullable(), topologyv1.WithRole("attribute")),
460 + }
461 +}
462 +
463 +func vsphereTopologyTypes() topologyv1.TypeRegistry {
464 + return topologyv1.TypeRegistry{
465 + ActorTypes: map[string]topologyv1.ActorType{
466 + "vsphere_datacenter": vsphereActorType("Datacenter", "datacenter", "blue"),
467 + "vsphere_cluster": vsphereActorType("Cluster", "cluster", "green"),
468 + "vsphere_host": vsphereActorType("ESXi Host", "host", "orange"),
469 + "vsphere_vm": vsphereActorType("VM", "vm", "purple"),
470 + "vsphere_datastore": vsphereActorType("Datastore", "datastore", "cyan"),
471 + "vsphere_network": vsphereActorType("Network", "network", "yellow"),
472 + "vsphere_datastore_cluster": vsphereActorType("Datastore Cluster", "datastore_cluster", "teal"),
473 + "vsphere_resource_pool": vsphereActorType("Resource Pool", "resource_pool", "gray"),
474 + },
475 + LinkTypes: map[string]topologyv1.LinkType{
476 + vsphereTopologyOwnershipLink: vsphereLinkType(
477 + "hierarchical",
478 + "ownership",
479 + "ownership",
480 + "preserve",
481 + vsphereOwnershipEvidenceType,
482 + vsphereLinkPresentation("Contains", "gray", "forward", "closer"),
483 + ),
484 + vsphereTopologyRunsOnLink: vsphereLinkType(
485 + "directed",
486 + "dependency",
487 + "normal",
488 + "preserve",
489 + vsphereRunsOnEvidenceType,
490 + vsphereLinkPresentation("Runs on", "blue", "forward", "normal"),
491 + ),
492 + vsphereTopologyNetworkLink: vsphereLinkType(
493 + "undirected",
494 + "observation",
495 + "normal",
496 + "canonicalize_unordered",
497 + vsphereNetworkConnectionEvidence,
498 + vsphereLinkPresentation("Connected to", "green", "none", "farther"),
499 + ),
500 + },
501 + EvidenceTypes: map[string]topologyv1.EvidenceType{
502 + vsphereOwnershipEvidenceType: vsphereEvidenceType(vsphereTopologyOwnershipLink),
503 + vsphereRunsOnEvidenceType: vsphereEvidenceType(vsphereTopologyRunsOnLink),
504 + vsphereNetworkConnectionEvidence: vsphereEvidenceType(vsphereTopologyNetworkLink),
505 + },
506 + TableTypes: map[string]topologyv1.TableType{
507 + vsphereTopologyDetailTable: vsphereDetailTableType(),
508 + vsphereTopologyLabelsTable: vsphereLabelsTableType(),
509 + },
510 + }
511 +}
512 +
513 +func vsphereActorType(label, icon, colorSlot string) topologyv1.ActorType {
514 + return topologyv1.ActorType{
515 + Layer: vsphereTopologyLayer,
516 + Identity: []string{"object_type", "vsphere_moid"},
517 + MergeIdentity: []string{"object_type", "vsphere_moid"},
518 + ParentIdentity: []string{"parent_object_type", "parent_moid"},
519 + Search: &topologyv1.ActorSearchPolicy{
520 + Columns: []string{"name", "vsphere_moid"},
521 + LabelKeys: []string{"datacenter", "cluster", "host", "resource_pool"},
522 + },
523 + Presentation: vsphereActorPresentation(label, icon, colorSlot),
524 + }
525 +}
526 +
527 +func vsphereLinkType(orientation, directionRole, semanticRole, aggregationDirection, evidenceType string, presentation *topologyv1.LinkPresentation) topologyv1.LinkType {
528 + return topologyv1.LinkType{
529 + Orientation: orientation,
530 + DirectionRole: directionRole,
531 + SemanticRole: semanticRole,
532 + Aggregation: topologyv1.LinkAggregation{
533 + Direction: aggregationDirection,
534 + Evidence: "append",
535 + Metrics: map[string]string{
536 + "evidence_count": "sum",
537 + },
538 + },
539 + EvidenceTypes: []string{evidenceType},
540 + Presentation: presentation,
541 + }
542 +}
543 +
544 +func vsphereEvidenceType(linkType string) topologyv1.EvidenceType {
545 + return topologyv1.EvidenceType{
546 + LinkType: linkType,
547 + Role: "relationship_evidence",
548 + Columns: vsphereTopologyEvidenceColumns(),
549 + MatchColumns: []string{"source_type", "source_moid", "target_type", "target_moid", "relationship"},
550 + }
551 +}
552 +
553 +func vsphereDetailTableType() topologyv1.TableType {
554 + return topologyv1.TableType{
555 + Role: "actor_detail",
556 + Owner: "actor",
557 + Aggregation: "last",
558 + Columns: vsphereTopologyDetailColumns(),
559 + Presentation: &topologyv1.TableTypePresentation{
560 + Label: "vSphere object details",
561 + Order: 1,
562 + DefaultVisibility: "expanded",
563 + Columns: vsphereDetailModalColumns(),
564 + },
565 + }
566 +}
567 +
568 +func vsphereLabelsTableType() topologyv1.TableType {
569 + return topologyv1.TableType{
570 + Role: "actor_inventory",
571 + Owner: "actor",
572 + Aggregation: "set",
573 + Columns: vsphereTopologyLabelColumns(),
574 + Presentation: &topologyv1.TableTypePresentation{
575 + Label: "Labels",
576 + Order: 0,
577 + Columns: []topologyv1.ModalColumn{
578 + vsphereModalDirectColumn("key", "Label", "key", "text"),
579 + vsphereModalDirectColumn("value", "Value", "value", "text"),
580 + vsphereModalDirectColumn("source", "Source", "source", "badge"),
581 + vsphereModalDirectColumn("kind", "Kind", "kind", "badge"),
582 + },
583 + },
584 + }
585 +}
586 +
587 +func vsphereEvidenceTypeForLink(linkType string) string {
588 + switch linkType {
589 + case vsphereTopologyOwnershipLink:
590 + return vsphereOwnershipEvidenceType
591 + case vsphereTopologyRunsOnLink:
592 + return vsphereRunsOnEvidenceType
593 + case vsphereTopologyNetworkLink:
594 + return vsphereNetworkConnectionEvidence
595 + default:
596 + return ""
597 + }
598 +}
599 +
600 +func firstExistingResourceID(exists func(string) bool, ids ...string) string {
601 + if exists == nil {
602 + return ""
603 + }
604 + for _, id := range ids {
605 + id = cleanTopologyString(id)
606 + if id != "" && exists(id) {
607 + return id
608 + }
609 + }
610 + return ""
611 +}
612 +
613 +func mergeStringMaps(maps ...map[string]string) map[string]string {
614 + merged := make(map[string]string)
615 + for _, values := range maps {
616 + for key, value := range values {
617 + key = cleanTopologyString(key)
618 + value = cleanTopologyString(value)
619 + if key == "" || value == "" {
620 + continue
621 + }
622 + merged[key] = value
623 + }
624 + }
625 + return merged
626 +}
627 +
628 +func sortedStrings(values []string) []string {
629 + seen := make(map[string]struct{}, len(values))
630 + for _, value := range values {
631 + value = cleanTopologyString(value)
632 + if value != "" {
633 + seen[value] = struct{}{}
634 + }
635 + }
636 +
637 + sorted := make([]string, 0, len(seen))
638 + for value := range seen {
639 + sorted = append(sorted, value)
640 + }
641 + sort.Strings(sorted)
642 + return sorted
643 +}
644 +
645 +func cleanTopologyString(value string) string {
646 + return strings.TrimSpace(value)
647 +}
648 +
649 +func nullableBool(value any) any {
650 + switch v := value.(type) {
651 + case nil:
652 + return nil
653 + case bool:
654 + return v
655 + case *bool:
656 + if v == nil {
657 + return nil
658 + }
659 + return *v
660 + default:
661 + return nil
662 + }
663 +}
664 +
665 +func nullableUint(value any) any {
666 + switch v := value.(type) {
667 + case nil:
668 + return nil
669 + case int:
670 + if v < 0 {
671 + return nil
672 + }
673 + return v
674 + case int16:
675 + if v < 0 {
676 + return nil
677 + }
678 + return int64(v)
679 + case int32:
680 + if v < 0 {
681 + return nil
682 + }
683 + return int64(v)
684 + case int64:
685 + if v < 0 {
686 + return nil
687 + }
688 + return v
689 + case uint:
690 + return int64(v)
691 + case uint32:
692 + return int64(v)
693 + case uint64:
694 + return v
695 + default:
696 + return nil
697 + }
698 +}
699 +
700 +func nullableInt(value any) any {
701 + switch v := value.(type) {
702 + case nil:
703 + return nil
704 + case int:
705 + return v
706 + case int16:
707 + return int64(v)
708 + case int32:
709 + return int64(v)
710 + case int64:
711 + return v
712 + case uint:
713 + return int64(v)
714 + case uint32:
715 + return int64(v)
716 + default:
717 + return nil
718 + }
719 +}
src/go/plugin/go.d/collector/vsphere/integrations/vmware_vcenter_server.md
+10 -7
@@ -881,18 +881,21 @@ No additional configuration is required.
881
882 #### Returns
883
884 -Cached vSphere inventory topology payload. Actors represent discovered inventory objects and links represent parent-child, host-runs-VM, or host/VM-connects-network relationships.
884 +Cached vSphere inventory topology payload using the netdata.topology.v1 schema. Actors represent discovered inventory objects and links represent inventory ownership, VM-to-host runtime placement, and host/VM network attachment relationships.
885
886 | Column | Type | Unit | Visibility | Description |
887 |:-------|:-----|:-----|:-----------|:------------|
888 | schema_version | string | | | Topology payload schema version. |
889 -| source | string | | | Topology source identifier. |
890 -| layer | string | | | Topology layer identifier. |
891 -| agent_id | string | | | Netdata Agent identifier for the node serving the function. |
889 +| producer | object | | | Producer metadata identifying the vSphere topology source, selected collector job, plugin, and local node when available. |
890 | collected_at | datetime | | | Time when the cached topology response was built. |
893 -| view | string | | | Topology view identifier. |
894 -| actors | array | | | vSphere inventory actors, including datacenters, clusters, ESXi hosts, VMs, datastores, optional networks, datastore clusters, and resource pools. |
895 -| links | array | | | Topology links between vSphere inventory actors. |
891 +| view | object | | | Topology view metadata. |
892 +| dictionaries | object | | | Compact-table dictionaries used by actors, links, evidence, and detail tables. |
893 +| types | object | | | Actor, link, evidence, table, and presentation type registry. |
894 +| presentation | object | | | Graph-level presentation metadata. |
895 +| actors | object | | | Compact actor table for vSphere inventory objects, including datacenters, clusters, ESXi hosts, VMs, datastores, optional networks, datastore clusters, and resource pools. |
896 +| links | object | | | Compact link table for relationships between vSphere inventory actors. |
897 +| evidence | object | | | Relationship evidence tables backing the rendered links. |
898 +| tables | object | | | Actor detail and label tables used by topology modals. |
899 | stats | object | | | Counts of discovered inventory objects, actors, and links included in the response. |
900
901
src/go/plugin/go.d/collector/vsphere/metadata.yaml
+30 -18
@@ -503,40 +503,52 @@ modules:
503 default: ""
504 options: []
505 returns:
506 - description: Cached vSphere inventory topology payload. Actors represent discovered inventory objects and links represent parent-child, host-runs-VM, or host/VM-connects-network relationships.
506 + description: Cached vSphere inventory topology payload using the netdata.topology.v1 schema. Actors represent discovered inventory objects and links represent inventory ownership, VM-to-host runtime placement, and host/VM network attachment relationships.
507 columns:
508 - name: schema_version
509 type: string
510 unit: ""
511 description: Topology payload schema version.
512 - - name: source
513 - type: string
514 - unit: ""
515 - description: Topology source identifier.
516 - - name: layer
517 - type: string
518 - unit: ""
519 - description: Topology layer identifier.
520 - - name: agent_id
521 - type: string
512 + - name: producer
513 + type: object
514 unit: ""
523 - description: Netdata Agent identifier for the node serving the function.
515 + description: Producer metadata identifying the vSphere topology source, selected collector job, plugin, and local node when available.
516 - name: collected_at
517 type: datetime
518 unit: ""
519 description: Time when the cached topology response was built.
520 - name: view
529 - type: string
521 + type: object
522 + unit: ""
523 + description: Topology view metadata.
524 + - name: dictionaries
525 + type: object
526 unit: ""
531 - description: Topology view identifier.
527 + description: Compact-table dictionaries used by actors, links, evidence, and detail tables.
528 + - name: types
529 + type: object
530 + unit: ""
531 + description: Actor, link, evidence, table, and presentation type registry.
532 + - name: presentation
533 + type: object
534 + unit: ""
535 + description: Graph-level presentation metadata.
536 - name: actors
533 - type: array
537 + type: object
538 unit: ""
535 - description: vSphere inventory actors, including datacenters, clusters, ESXi hosts, VMs, datastores, optional networks, datastore clusters, and resource pools.
539 + description: Compact actor table for vSphere inventory objects, including datacenters, clusters, ESXi hosts, VMs, datastores, optional networks, datastore clusters, and resource pools.
540 - name: links
537 - type: array
541 + type: object
542 + unit: ""
543 + description: Compact link table for relationships between vSphere inventory actors.
544 + - name: evidence
545 + type: object
546 + unit: ""
547 + description: Relationship evidence tables backing the rendered links.
548 + - name: tables
549 + type: object
550 unit: ""
539 - description: Topology links between vSphere inventory actors.
551 + description: Actor detail and label tables used by topology modals.
552 - name: stats
553 type: object
554 unit: ""
src/go/plugin/go.d/docs/how-to-write-a-collector.md
+3 -3
@@ -304,9 +304,9 @@ deps so the boundary is compile-enforced.
304
305 ## Topology
306
307 -New topology producers MUST use `src/go/pkg/topology/v1`, not legacy topology
308 -payloads. New producers MUST NOT import the non-v1 `src/go/pkg/topology`
309 -payload model.
307 +New topology producers MUST use `src/go/pkg/topology/v1`. The non-v1 root
308 +`src/go/pkg/topology` payload model has been retired and MUST NOT be
309 +reintroduced for topology payloads.
310
311 Rules:
312
src/go/tools/functions-validation/fixtures/topology-v1/vsphere.json
+7174 -101
@@ -1,209 +1,7282 @@
1 {
2 "status": 200,
3 "type": "topology",
4 - "has_history": false,
5 - "update_every": 60,
4 + "update_every": 30,
5 "data": {
6 "schema_version": "netdata.topology.v1",
7 "producer": {
8 "source": "vsphere",
10 - "instance": "sample-vcenter",
11 - "node_id": "vcenter-node",
12 - "machine_guid": "vcenter-machine",
13 - "agent_version": "test",
14 - "plugin": "go.d.plugin",
15 - "capabilities": ["topology-v1"]
9 + "instance": "vsphere_vcenter1",
10 + "plugin": "go.d/vsphere",
11 + "capabilities": [
12 + "inventory",
13 + "topology-v1"
14 + ]
15 },
16 "collected_at": "2026-05-09T00:00:00Z",
17 "view": {
18 "id": "vsphere-inventory",
19 "scope": "vsphere_object",
20 "mode": "detailed",
22 - "group_by": ["vsphere_object"]
21 + "supported_modes": [
22 + "detailed"
23 + ],
24 + "group_by": [
25 + "object_type"
26 + ]
27 },
28 "dictionaries": {
29 "strings": [
30 "vsphere_datacenter",
31 + "datacenter",
32 + "datacenter-1",
33 + "DC1",
34 + "name",
35 + "vsphere",
36 + "identity",
37 + "object_type",
38 + "vsphere_moid",
39 "vsphere_cluster",
28 - "vsphere_host",
29 - "vsphere_vm",
30 - "vsphere_datastore",
31 - "dc-1",
40 + "cluster",
41 "domain-c1",
33 - "host-101",
34 - "vm-501",
35 - "datastore-301",
42 + "Cluster1",
43 + "green",
44 + "hierarchy",
45 + "label",
46 "vsphere_ownership",
37 - "vsphere_dependency",
38 - "vsphere_relationship",
39 - "vsphere_object_detail",
47 "contains",
48 + "vsphere_host",
49 + "host",
50 + "host-1",
51 + "Host1",
52 + "poweredOn",
53 + "connected",
54 + "vsphere_vm",
55 + "vm",
56 + "vm-1",
57 + "VM1",
58 + "vsphere_runs_on",
59 "runs_on",
42 - "uses",
43 - "powered_on",
44 - "/dc-1/cluster-a/host-a/vm-a"
60 + "vsphere_datastore",
61 + "datastore",
62 + "datastore-1",
63 + "Datastore1",
64 + "normal",
65 + "VMFS",
66 + "type",
67 + "vsphere_network",
68 + "network",
69 + "network-1",
70 + "VM Network",
71 + "Network",
72 + "vsphere_connected_to",
73 + "connected_to",
74 + "vsphere_datastore_cluster",
75 + "datastore_cluster",
76 + "group-p1",
77 + "Pod1",
78 + "vsphere_resource_pool",
79 + "resource_pool",
80 + "resgroup-1",
81 + "Resources"
82 ]
83 },
84 "types": {
85 "actor_types": {
86 + "vsphere_cluster": {
87 + "layer": "virtualization",
88 + "identity": [
89 + "object_type",
90 + "vsphere_moid"
91 + ],
92 + "merge_identity": [
93 + "object_type",
94 + "vsphere_moid"
95 + ],
96 + "parent_identity": [
97 + "parent_object_type",
98 + "parent_moid"
99 + ],
100 + "search": {
101 + "columns": [
102 + "name",
103 + "vsphere_moid"
104 + ],
105 + "label_keys": [
106 + "datacenter",
107 + "cluster",
108 + "host",
109 + "resource_pool"
110 + ]
111 + },
112 + "presentation": {
113 + "label": "Cluster",
114 + "role": "actor",
115 + "icon": "cluster",
116 + "color_slot": "green",
117 + "border": {
118 + "enabled": true,
119 + "style": "solid"
120 + },
121 + "label_policy": {
122 + "columns": [
123 + "name"
124 + ],
125 + "fallback": "type_label",
126 + "max_length": 80,
127 + "array": "reject"
128 + },
129 + "hover": {
130 + "fields": [
131 + {
132 + "key": "name",
133 + "label": "Name"
134 + },
135 + {
136 + "key": "object_type",
137 + "label": "Type"
138 + },
139 + {
140 + "key": "vsphere_moid",
141 + "label": "vSphere MoRef"
142 + },
143 + {
144 + "key": "overall_status",
145 + "label": "Status"
146 + },
147 + {
148 + "key": "power_state",
149 + "label": "Power"
150 + }
151 + ]
152 + },
153 + "modal": {
154 + "enabled": true,
155 + "labels": {
156 + "enabled": true,
157 + "table": "actor_labels",
158 + "actor_column": "actor",
159 + "key_column": "key",
160 + "value_column": "value",
161 + "source_column": "source",
162 + "kind_column": "kind",
163 + "value_index_column": "value_index",
164 + "identification": {
165 + "enabled": true,
166 + "fields": [
167 + {
168 + "key": "object_type",
169 + "label": "Type",
170 + "max_values": 1
171 + },
172 + {
173 + "key": "datacenter",
174 + "label": "Datacenter",
175 + "max_values": 1
176 + },
177 + {
178 + "key": "cluster",
179 + "label": "Cluster",
180 + "max_values": 1
181 + },
182 + {
183 + "key": "host",
184 + "label": "Host",
185 + "max_values": 1
186 + },
187 + {
188 + "key": "resource_pool",
189 + "label": "Resource Pool",
190 + "max_values": 1
191 + }
192 + ]
193 + }
194 + },
195 + "mini_topology": {
196 + "enabled": true,
197 + "depth": 1,
198 + "include_link_types": [
199 + "vsphere_ownership",
200 + "vsphere_runs_on",
201 + "vsphere_connected_to"
202 + ]
203 + },
204 + "sections": [
205 + {
206 + "id": "details",
207 + "label": "Details",
208 + "order": 10,
209 + "source": {
210 + "kind": "actor_table",
211 + "table": "vsphere_object_detail"
212 + },
213 + "owner_filter": {
214 + "mode": "actor_column",
215 + "actor_column": "actor"
216 + },
217 + "columns": [
218 + {
219 + "id": "object_type",
220 + "label": "Type",
221 + "projection": {
222 + "kind": "direct",
223 + "column": "object_type"
224 + },
225 + "cell": "badge"
226 + },
227 + {
228 + "id": "name",
229 + "label": "Name",
230 + "projection": {
231 + "kind": "direct",
232 + "column": "name"
233 + },
234 + "cell": "text"
235 + },
236 + {
237 + "id": "vsphere_moid",
238 + "label": "vSphere MoRef",
239 + "projection": {
240 + "kind": "direct",
241 + "column": "vsphere_moid"
242 + },
243 + "cell": "text"
244 + },
245 + {
246 + "id": "datacenter",
247 + "label": "Datacenter",
248 + "projection": {
249 + "kind": "direct",
250 + "column": "datacenter"
251 + },
252 + "cell": "text"
253 + },
254 + {
255 + "id": "cluster",
256 + "label": "Cluster",
257 + "projection": {
258 + "kind": "direct",
259 + "column": "cluster"
260 + },
261 + "cell": "text"
262 + },
263 + {
264 + "id": "host",
265 + "label": "Host",
266 + "projection": {
267 + "kind": "direct",
268 + "column": "host"
269 + },
270 + "cell": "text"
271 + },
272 + {
273 + "id": "resource_pool",
274 + "label": "Resource Pool",
275 + "projection": {
276 + "kind": "direct",
277 + "column": "resource_pool"
278 + },
279 + "cell": "text"
280 + },
281 + {
282 + "id": "connection_state",
283 + "label": "Connection",
284 + "projection": {
285 + "kind": "direct",
286 + "column": "connection_state"
287 + },
288 + "cell": "badge"
289 + },
290 + {
291 + "id": "power_state",
292 + "label": "Power",
293 + "projection": {
294 + "kind": "direct",
295 + "column": "power_state"
296 + },
297 + "cell": "badge"
298 + },
299 + {
300 + "id": "overall_status",
301 + "label": "Status",
302 + "projection": {
303 + "kind": "direct",
304 + "column": "overall_status"
305 + },
306 + "cell": "badge"
307 + },
308 + {
309 + "id": "accessible",
310 + "label": "Accessible",
311 + "projection": {
312 + "kind": "direct",
313 + "column": "accessible"
314 + },
315 + "cell": "badge"
316 + },
317 + {
318 + "id": "in_maintenance_mode",
319 + "label": "In Maintenance",
320 + "projection": {
321 + "kind": "direct",
322 + "column": "in_maintenance_mode"
323 + },
324 + "cell": "badge"
325 + },
326 + {
327 + "id": "maintenance_mode",
328 + "label": "Maintenance",
329 + "projection": {
330 + "kind": "direct",
331 + "column": "maintenance_mode"
332 + },
333 + "cell": "badge"
334 + },
335 + {
336 + "id": "datastore_type",
337 + "label": "Datastore Type",
338 + "projection": {
339 + "kind": "direct",
340 + "column": "datastore_type"
341 + },
342 + "cell": "badge"
343 + },
344 + {
345 + "id": "network_type",
346 + "label": "Network Type",
347 + "projection": {
348 + "kind": "direct",
349 + "column": "network_type"
350 + },
351 + "cell": "badge"
352 + },
353 + {
354 + "id": "ip_pool_name",
355 + "label": "IP Pool",
356 + "projection": {
357 + "kind": "direct",
358 + "column": "ip_pool_name"
359 + },
360 + "cell": "text",
361 + "visibility": "expanded"
362 + },
363 + {
364 + "id": "snapshot_count",
365 + "label": "Snapshots",
366 + "projection": {
367 + "kind": "direct",
368 + "column": "snapshot_count"
369 + },
370 + "cell": "number"
371 + },
372 + {
373 + "id": "snapshot_chain_depth",
374 + "label": "Snapshot Chain",
375 + "projection": {
376 + "kind": "direct",
377 + "column": "snapshot_chain_depth"
378 + },
379 + "cell": "number",
380 + "visibility": "expanded"
381 + },
382 + {
383 + "id": "configured_vcpus",
384 + "label": "vCPUs",
385 + "projection": {
386 + "kind": "direct",
387 + "column": "configured_vcpus"
388 + },
389 + "cell": "number"
390 + },
391 + {
392 + "id": "configured_memory_mib",
393 + "label": "Memory MiB",
394 + "projection": {
395 + "kind": "direct",
396 + "column": "configured_memory_mib"
397 + },
398 + "cell": "number"
399 + },
400 + {
401 + "id": "capacity_bytes",
402 + "label": "Capacity",
403 + "projection": {
404 + "kind": "direct",
405 + "column": "capacity_bytes"
406 + },
407 + "cell": "number"
408 + },
409 + {
410 + "id": "free_space_bytes",
411 + "label": "Free Space",
412 + "projection": {
413 + "kind": "direct",
414 + "column": "free_space_bytes"
415 + },
416 + "cell": "number"
417 + },
418 + {
419 + "id": "uncommitted_bytes",
420 + "label": "Uncommitted",
421 + "projection": {
422 + "kind": "direct",
423 + "column": "uncommitted_bytes"
424 + },
425 + "cell": "number",
426 + "visibility": "expanded"
427 + },
428 + {
429 + "id": "cpu_capacity_mhz",
430 + "label": "CPU Capacity MHz",
431 + "projection": {
432 + "kind": "direct",
433 + "column": "cpu_capacity_mhz"
434 + },
435 + "cell": "number"
436 + },
437 + {
438 + "id": "memory_capacity_mib",
439 + "label": "Memory Capacity MiB",
440 + "projection": {
441 + "kind": "direct",
442 + "column": "memory_capacity_mib"
443 + },
444 + "cell": "number"
445 + },
446 + {
447 + "id": "cpu_limit_mhz",
448 + "label": "CPU Limit MHz",
449 + "projection": {
450 + "kind": "direct",
451 + "column": "cpu_limit_mhz"
452 + },
453 + "cell": "number"
454 + },
455 + {
456 + "id": "memory_limit_mib",
457 + "label": "Memory Limit MiB",
458 + "projection": {
459 + "kind": "direct",
460 + "column": "memory_limit_mib"
461 + },
462 + "cell": "number"
463 + },
464 + {
465 + "id": "cpu_reservation_mhz",
466 + "label": "CPU Reservation MHz",
467 + "projection": {
468 + "kind": "direct",
469 + "column": "cpu_reservation_mhz"
470 + },
471 + "cell": "number",
472 + "visibility": "expanded"
473 + },
474 + {
475 + "id": "memory_reservation_mib",
476 + "label": "Memory Reservation MiB",
477 + "projection": {
478 + "kind": "direct",
479 + "column": "memory_reservation_mib"
480 + },
481 + "cell": "number",
482 + "visibility": "expanded"
483 + },
484 + {
485 + "id": "network_hosts",
486 + "label": "Hosts",
487 + "projection": {
488 + "kind": "direct",
489 + "column": "network_hosts"
490 + },
491 + "cell": "number"
492 + },
493 + {
494 + "id": "network_vms",
495 + "label": "VMs",
496 + "projection": {
497 + "kind": "direct",
498 + "column": "network_vms"
499 + },
500 + "cell": "number"
501 + },
502 + {
503 + "id": "drs_enabled",
504 + "label": "DRS",
505 + "projection": {
506 + "kind": "direct",
507 + "column": "drs_enabled"
508 + },
509 + "cell": "badge"
510 + },
511 + {
512 + "id": "ha_enabled",
513 + "label": "HA",
514 + "projection": {
515 + "kind": "direct",
516 + "column": "ha_enabled"
517 + },
518 + "cell": "badge"
519 + },
520 + {
521 + "id": "vsan_enabled",
522 + "label": "vSAN",
523 + "projection": {
524 + "kind": "direct",
525 + "column": "vsan_enabled"
526 + },
527 + "cell": "badge"
528 + },
529 + {
530 + "id": "storage_drs_enabled",
531 + "label": "Storage DRS",
532 + "projection": {
533 + "kind": "direct",
534 + "column": "storage_drs_enabled"
535 + },
536 + "cell": "badge"
537 + },
538 + {
539 + "id": "multiple_host_access",
540 + "label": "Multiple Host Access",
541 + "projection": {
542 + "kind": "direct",
543 + "column": "multiple_host_access"
544 + },
545 + "cell": "badge"
546 + },
547 + {
548 + "id": "consolidation_needed",
549 + "label": "Consolidation",
550 + "projection": {
551 + "kind": "direct",
552 + "column": "consolidation_needed"
553 + },
554 + "cell": "badge"
555 + },
556 + {
557 + "id": "tools_running_status",
558 + "label": "Tools Running",
559 + "projection": {
560 + "kind": "direct",
561 + "column": "tools_running_status"
562 + },
563 + "cell": "badge",
564 + "visibility": "expanded"
565 + },
566 + {
567 + "id": "tools_version_status",
568 + "label": "Tools Version",
569 + "projection": {
570 + "kind": "direct",
571 + "column": "tools_version_status"
572 + },
573 + "cell": "badge",
574 + "visibility": "expanded"
575 + }
576 + ],
577 + "empty_label": "No vSphere details"
578 + },
579 + {
580 + "id": "relationships",
581 + "label": "Relationships",
582 + "order": 20,
583 + "source": {
584 + "kind": "links"
585 + },
586 + "owner_filter": {
587 + "mode": "incident_link",
588 + "src_actor_column": "src_actor",
589 + "dst_actor_column": "dst_actor"
590 + },
591 + "columns": [
592 + {
593 + "id": "relationship",
594 + "label": "Relationship",
595 + "projection": {
596 + "kind": "direct",
597 + "column": "relationship"
598 + },
599 + "cell": "badge"
600 + },
601 + {
602 + "id": "related_object",
603 + "label": "Related Object",
604 + "projection": {
605 + "kind": "opposite_actor",
606 + "src_actor_column": "src_actor",
607 + "dst_actor_column": "dst_actor"
608 + },
609 + "cell": "actor_link"
610 + },
611 + {
612 + "id": "evidence_count",
613 + "label": "Evidence",
614 + "projection": {
615 + "kind": "direct",
616 + "column": "evidence_count"
617 + },
618 + "cell": "number"
619 + }
620 + ],
621 + "empty_label": "No vSphere relationships"
622 + }
623 + ]
624 + }
625 + }
626 + },
627 "vsphere_datacenter": {
628 "layer": "virtualization",
51 - "identity": ["vsphere_moid"],
52 - "merge_identity": ["vsphere_moid"],
53 - "aggregation_scopes": ["vsphere_object"]
629 + "identity": [
630 + "object_type",
631 + "vsphere_moid"
632 + ],
633 + "merge_identity": [
634 + "object_type",
635 + "vsphere_moid"
636 + ],
637 + "parent_identity": [
638 + "parent_object_type",
639 + "parent_moid"
640 + ],
641 + "search": {
642 + "columns": [
643 + "name",
644 + "vsphere_moid"
645 + ],
646 + "label_keys": [
647 + "datacenter",
648 + "cluster",
649 + "host",
650 + "resource_pool"
651 + ]
652 + },
653 + "presentation": {
654 + "label": "Datacenter",
655 + "role": "actor",
656 + "icon": "datacenter",
657 + "color_slot": "blue",
658 + "border": {
659 + "enabled": true,
660 + "style": "solid"
661 + },
662 + "label_policy": {
663 + "columns": [
664 + "name"
665 + ],
666 + "fallback": "type_label",
667 + "max_length": 80,
668 + "array": "reject"
669 + },
670 + "hover": {
671 + "fields": [
672 + {
673 + "key": "name",
674 + "label": "Name"
675 + },
676 + {
677 + "key": "object_type",
678 + "label": "Type"
679 + },
680 + {
681 + "key": "vsphere_moid",
682 + "label": "vSphere MoRef"
683 + },
684 + {
685 + "key": "overall_status",
686 + "label": "Status"
687 + },
688 + {
689 + "key": "power_state",
690 + "label": "Power"
691 + }
692 + ]
693 + },
694 + "modal": {
695 + "enabled": true,
696 + "labels": {
697 + "enabled": true,
698 + "table": "actor_labels",
699 + "actor_column": "actor",
700 + "key_column": "key",
701 + "value_column": "value",
702 + "source_column": "source",
703 + "kind_column": "kind",
704 + "value_index_column": "value_index",
705 + "identification": {
706 + "enabled": true,
707 + "fields": [
708 + {
709 + "key": "object_type",
710 + "label": "Type",
711 + "max_values": 1
712 + },
713 + {
714 + "key": "datacenter",
715 + "label": "Datacenter",
716 + "max_values": 1
717 + },
718 + {
719 + "key": "cluster",
720 + "label": "Cluster",
721 + "max_values": 1
722 + },
723 + {
724 + "key": "host",
725 + "label": "Host",
726 + "max_values": 1
727 + },
728 + {
729 + "key": "resource_pool",
730 + "label": "Resource Pool",
731 + "max_values": 1
732 + }
733 + ]
734 + }
735 + },
736 + "mini_topology": {
737 + "enabled": true,
738 + "depth": 1,
739 + "include_link_types": [
740 + "vsphere_ownership",
741 + "vsphere_runs_on",
742 + "vsphere_connected_to"
743 + ]
744 + },
745 + "sections": [
746 + {
747 + "id": "details",
748 + "label": "Details",
749 + "order": 10,
750 + "source": {
751 + "kind": "actor_table",
752 + "table": "vsphere_object_detail"
753 + },
754 + "owner_filter": {
755 + "mode": "actor_column",
756 + "actor_column": "actor"
757 + },
758 + "columns": [
759 + {
760 + "id": "object_type",
761 + "label": "Type",
762 + "projection": {
763 + "kind": "direct",
764 + "column": "object_type"
765 + },
766 + "cell": "badge"
767 + },
768 + {
769 + "id": "name",
770 + "label": "Name",
771 + "projection": {
772 + "kind": "direct",
773 + "column": "name"
774 + },
775 + "cell": "text"
776 + },
777 + {
778 + "id": "vsphere_moid",
779 + "label": "vSphere MoRef",
780 + "projection": {
781 + "kind": "direct",
782 + "column": "vsphere_moid"
783 + },
784 + "cell": "text"
785 + },
786 + {
787 + "id": "datacenter",
788 + "label": "Datacenter",
789 + "projection": {
790 + "kind": "direct",
791 + "column": "datacenter"
792 + },
793 + "cell": "text"
794 + },
795 + {
796 + "id": "cluster",
797 + "label": "Cluster",
798 + "projection": {
799 + "kind": "direct",
800 + "column": "cluster"
801 + },
802 + "cell": "text"
803 + },
804 + {
805 + "id": "host",
806 + "label": "Host",
807 + "projection": {
808 + "kind": "direct",
809 + "column": "host"
810 + },
811 + "cell": "text"
812 + },
813 + {
814 + "id": "resource_pool",
815 + "label": "Resource Pool",
816 + "projection": {
817 + "kind": "direct",
818 + "column": "resource_pool"
819 + },
820 + "cell": "text"
821 + },
822 + {
823 + "id": "connection_state",
824 + "label": "Connection",
825 + "projection": {
826 + "kind": "direct",
827 + "column": "connection_state"
828 + },
829 + "cell": "badge"
830 + },
831 + {
832 + "id": "power_state",
833 + "label": "Power",
834 + "projection": {
835 + "kind": "direct",
836 + "column": "power_state"
837 + },
838 + "cell": "badge"
839 + },
840 + {
841 + "id": "overall_status",
842 + "label": "Status",
843 + "projection": {
844 + "kind": "direct",
845 + "column": "overall_status"
846 + },
847 + "cell": "badge"
848 + },
849 + {
850 + "id": "accessible",
851 + "label": "Accessible",
852 + "projection": {
853 + "kind": "direct",
854 + "column": "accessible"
855 + },
856 + "cell": "badge"
857 + },
858 + {
859 + "id": "in_maintenance_mode",
860 + "label": "In Maintenance",
861 + "projection": {
862 + "kind": "direct",
863 + "column": "in_maintenance_mode"
864 + },
865 + "cell": "badge"
866 + },
867 + {
868 + "id": "maintenance_mode",
869 + "label": "Maintenance",
870 + "projection": {
871 + "kind": "direct",
872 + "column": "maintenance_mode"
873 + },
874 + "cell": "badge"
875 + },
876 + {
877 + "id": "datastore_type",
878 + "label": "Datastore Type",
879 + "projection": {
880 + "kind": "direct",
881 + "column": "datastore_type"
882 + },
883 + "cell": "badge"
884 + },
885 + {
886 + "id": "network_type",
887 + "label": "Network Type",
888 + "projection": {
889 + "kind": "direct",
890 + "column": "network_type"
891 + },
892 + "cell": "badge"
893 + },
894 + {
895 + "id": "ip_pool_name",
896 + "label": "IP Pool",
897 + "projection": {
898 + "kind": "direct",
899 + "column": "ip_pool_name"
900 + },
901 + "cell": "text",
902 + "visibility": "expanded"
903 + },
904 + {
905 + "id": "snapshot_count",
906 + "label": "Snapshots",
907 + "projection": {
908 + "kind": "direct",
909 + "column": "snapshot_count"
910 + },
911 + "cell": "number"
912 + },
913 + {
914 + "id": "snapshot_chain_depth",
915 + "label": "Snapshot Chain",
916 + "projection": {
917 + "kind": "direct",
918 + "column": "snapshot_chain_depth"
919 + },
920 + "cell": "number",
921 + "visibility": "expanded"
922 + },
923 + {
924 + "id": "configured_vcpus",
925 + "label": "vCPUs",
926 + "projection": {
927 + "kind": "direct",
928 + "column": "configured_vcpus"
929 + },
930 + "cell": "number"
931 + },
932 + {
933 + "id": "configured_memory_mib",
934 + "label": "Memory MiB",
935 + "projection": {
936 + "kind": "direct",
937 + "column": "configured_memory_mib"
938 + },
939 + "cell": "number"
940 + },
941 + {
942 + "id": "capacity_bytes",
943 + "label": "Capacity",
944 + "projection": {
945 + "kind": "direct",
946 + "column": "capacity_bytes"
947 + },
948 + "cell": "number"
949 + },
950 + {
951 + "id": "free_space_bytes",
952 + "label": "Free Space",
953 + "projection": {
954 + "kind": "direct",
955 + "column": "free_space_bytes"
956 + },
957 + "cell": "number"
958 + },
959 + {
960 + "id": "uncommitted_bytes",
961 + "label": "Uncommitted",
962 + "projection": {
963 + "kind": "direct",
964 + "column": "uncommitted_bytes"
965 + },
966 + "cell": "number",
967 + "visibility": "expanded"
968 + },
969 + {
970 + "id": "cpu_capacity_mhz",
971 + "label": "CPU Capacity MHz",
972 + "projection": {
973 + "kind": "direct",
974 + "column": "cpu_capacity_mhz"
975 + },
976 + "cell": "number"
977 + },
978 + {
979 + "id": "memory_capacity_mib",
980 + "label": "Memory Capacity MiB",
981 + "projection": {
982 + "kind": "direct",
983 + "column": "memory_capacity_mib"
984 + },
985 + "cell": "number"
986 + },
987 + {
988 + "id": "cpu_limit_mhz",
989 + "label": "CPU Limit MHz",
990 + "projection": {
991 + "kind": "direct",
992 + "column": "cpu_limit_mhz"
993 + },
994 + "cell": "number"
995 + },
996 + {
997 + "id": "memory_limit_mib",
998 + "label": "Memory Limit MiB",
999 + "projection": {
1000 + "kind": "direct",
1001 + "column": "memory_limit_mib"
1002 + },
1003 + "cell": "number"
1004 + },
1005 + {
1006 + "id": "cpu_reservation_mhz",
1007 + "label": "CPU Reservation MHz",
1008 + "projection": {
1009 + "kind": "direct",
1010 + "column": "cpu_reservation_mhz"
1011 + },
1012 + "cell": "number",
1013 + "visibility": "expanded"
1014 + },
1015 + {
1016 + "id": "memory_reservation_mib",
1017 + "label": "Memory Reservation MiB",
1018 + "projection": {
1019 + "kind": "direct",
1020 + "column": "memory_reservation_mib"
1021 + },
1022 + "cell": "number",
1023 + "visibility": "expanded"
1024 + },
1025 + {
1026 + "id": "network_hosts",
1027 + "label": "Hosts",
1028 + "projection": {
1029 + "kind": "direct",
1030 + "column": "network_hosts"
1031 + },
1032 + "cell": "number"
1033 + },
1034 + {
1035 + "id": "network_vms",
1036 + "label": "VMs",
1037 + "projection": {
1038 + "kind": "direct",
1039 + "column": "network_vms"
1040 + },
1041 + "cell": "number"
1042 + },
1043 + {
1044 + "id": "drs_enabled",
1045 + "label": "DRS",
1046 + "projection": {
1047 + "kind": "direct",
1048 + "column": "drs_enabled"
1049 + },
1050 + "cell": "badge"
1051 + },
1052 + {
1053 + "id": "ha_enabled",
1054 + "label": "HA",
1055 + "projection": {
1056 + "kind": "direct",
1057 + "column": "ha_enabled"
1058 + },
1059 + "cell": "badge"
1060 + },
1061 + {
1062 + "id": "vsan_enabled",
1063 + "label": "vSAN",
1064 + "projection": {
1065 + "kind": "direct",
1066 + "column": "vsan_enabled"
1067 + },
1068 + "cell": "badge"
1069 + },
1070 + {
1071 + "id": "storage_drs_enabled",
1072 + "label": "Storage DRS",
1073 + "projection": {
1074 + "kind": "direct",
1075 + "column": "storage_drs_enabled"
1076 + },
1077 + "cell": "badge"
1078 + },
1079 + {
1080 + "id": "multiple_host_access",
1081 + "label": "Multiple Host Access",
1082 + "projection": {
1083 + "kind": "direct",
1084 + "column": "multiple_host_access"
1085 + },
1086 + "cell": "badge"
1087 + },
1088 + {
1089 + "id": "consolidation_needed",
1090 + "label": "Consolidation",
1091 + "projection": {
1092 + "kind": "direct",
1093 + "column": "consolidation_needed"
1094 + },
1095 + "cell": "badge"
1096 + },
1097 + {
1098 + "id": "tools_running_status",
1099 + "label": "Tools Running",
1100 + "projection": {
1101 + "kind": "direct",
1102 + "column": "tools_running_status"
1103 + },
1104 + "cell": "badge",
1105 + "visibility": "expanded"
1106 + },
1107 + {
1108 + "id": "tools_version_status",
1109 + "label": "Tools Version",
1110 + "projection": {
1111 + "kind": "direct",
1112 + "column": "tools_version_status"
1113 + },
1114 + "cell": "badge",
1115 + "visibility": "expanded"
1116 + }
1117 + ],
1118 + "empty_label": "No vSphere details"
1119 + },
1120 + {
1121 + "id": "relationships",
1122 + "label": "Relationships",
1123 + "order": 20,
1124 + "source": {
1125 + "kind": "links"
1126 + },
1127 + "owner_filter": {
1128 + "mode": "incident_link",
1129 + "src_actor_column": "src_actor",
1130 + "dst_actor_column": "dst_actor"
1131 + },
1132 + "columns": [
1133 + {
1134 + "id": "relationship",
1135 + "label": "Relationship",
1136 + "projection": {
1137 + "kind": "direct",
1138 + "column": "relationship"
1139 + },
1140 + "cell": "badge"
1141 + },
1142 + {
1143 + "id": "related_object",
1144 + "label": "Related Object",
1145 + "projection": {
1146 + "kind": "opposite_actor",
1147 + "src_actor_column": "src_actor",
1148 + "dst_actor_column": "dst_actor"
1149 + },
1150 + "cell": "actor_link"
1151 + },
1152 + {
1153 + "id": "evidence_count",
1154 + "label": "Evidence",
1155 + "projection": {
1156 + "kind": "direct",
1157 + "column": "evidence_count"
1158 + },
1159 + "cell": "number"
1160 + }
1161 + ],
1162 + "empty_label": "No vSphere relationships"
1163 + }
1164 + ]
1165 + }
1166 + }
1167 + },
1168 + "vsphere_datastore": {
1169 + "layer": "virtualization",
1170 + "identity": [
1171 + "object_type",
1172 + "vsphere_moid"
1173 + ],
1174 + "merge_identity": [
1175 + "object_type",
1176 + "vsphere_moid"
1177 + ],
1178 + "parent_identity": [
1179 + "parent_object_type",
1180 + "parent_moid"
1181 + ],
1182 + "search": {
1183 + "columns": [
1184 + "name",
1185 + "vsphere_moid"
1186 + ],
1187 + "label_keys": [
1188 + "datacenter",
1189 + "cluster",
1190 + "host",
1191 + "resource_pool"
1192 + ]
1193 + },
1194 + "presentation": {
1195 + "label": "Datastore",
1196 + "role": "actor",
1197 + "icon": "datastore",
1198 + "color_slot": "cyan",
1199 + "border": {
1200 + "enabled": true,
1201 + "style": "solid"
1202 + },
1203 + "label_policy": {
1204 + "columns": [
1205 + "name"
1206 + ],
1207 + "fallback": "type_label",
1208 + "max_length": 80,
1209 + "array": "reject"
1210 + },
1211 + "hover": {
1212 + "fields": [
1213 + {
1214 + "key": "name",
1215 + "label": "Name"
1216 + },
1217 + {
1218 + "key": "object_type",
1219 + "label": "Type"
1220 + },
1221 + {
1222 + "key": "vsphere_moid",
1223 + "label": "vSphere MoRef"
1224 + },
1225 + {
1226 + "key": "overall_status",
1227 + "label": "Status"
1228 + },
1229 + {
1230 + "key": "power_state",
1231 + "label": "Power"
1232 + }
1233 + ]
1234 + },
1235 + "modal": {
1236 + "enabled": true,
1237 + "labels": {
1238 + "enabled": true,
1239 + "table": "actor_labels",
1240 + "actor_column": "actor",
1241 + "key_column": "key",
1242 + "value_column": "value",
1243 + "source_column": "source",
1244 + "kind_column": "kind",
1245 + "value_index_column": "value_index",
1246 + "identification": {
1247 + "enabled": true,
1248 + "fields": [
1249 + {
1250 + "key": "object_type",
1251 + "label": "Type",
1252 + "max_values": 1
1253 + },
1254 + {
1255 + "key": "datacenter",
1256 + "label": "Datacenter",
1257 + "max_values": 1
1258 + },
1259 + {
1260 + "key": "cluster",
1261 + "label": "Cluster",
1262 + "max_values": 1
1263 + },
1264 + {
1265 + "key": "host",
1266 + "label": "Host",
1267 + "max_values": 1
1268 + },
1269 + {
1270 + "key": "resource_pool",
1271 + "label": "Resource Pool",
1272 + "max_values": 1
1273 + }
1274 + ]
1275 + }
1276 + },
1277 + "mini_topology": {
1278 + "enabled": true,
1279 + "depth": 1,
1280 + "include_link_types": [
1281 + "vsphere_ownership",
1282 + "vsphere_runs_on",
1283 + "vsphere_connected_to"
1284 + ]
1285 + },
1286 + "sections": [
1287 + {
1288 + "id": "details",
1289 + "label": "Details",
1290 + "order": 10,
1291 + "source": {
1292 + "kind": "actor_table",
1293 + "table": "vsphere_object_detail"
1294 + },
1295 + "owner_filter": {
1296 + "mode": "actor_column",
1297 + "actor_column": "actor"
1298 + },
1299 + "columns": [
1300 + {
1301 + "id": "object_type",
1302 + "label": "Type",
1303 + "projection": {
1304 + "kind": "direct",
1305 + "column": "object_type"
1306 + },
1307 + "cell": "badge"
1308 + },
1309 + {
1310 + "id": "name",
1311 + "label": "Name",
1312 + "projection": {
1313 + "kind": "direct",
1314 + "column": "name"
1315 + },
1316 + "cell": "text"
1317 + },
1318 + {
1319 + "id": "vsphere_moid",
1320 + "label": "vSphere MoRef",
1321 + "projection": {
1322 + "kind": "direct",
1323 + "column": "vsphere_moid"
1324 + },
1325 + "cell": "text"
1326 + },
1327 + {
1328 + "id": "datacenter",
1329 + "label": "Datacenter",
1330 + "projection": {
1331 + "kind": "direct",
1332 + "column": "datacenter"
1333 + },
1334 + "cell": "text"
1335 + },
1336 + {
1337 + "id": "cluster",
1338 + "label": "Cluster",
1339 + "projection": {
1340 + "kind": "direct",
1341 + "column": "cluster"
1342 + },
1343 + "cell": "text"
1344 + },
1345 + {
1346 + "id": "host",
1347 + "label": "Host",
1348 + "projection": {
1349 + "kind": "direct",
1350 + "column": "host"
1351 + },
1352 + "cell": "text"
1353 + },
1354 + {
1355 + "id": "resource_pool",
1356 + "label": "Resource Pool",
1357 + "projection": {
1358 + "kind": "direct",
1359 + "column": "resource_pool"
1360 + },
1361 + "cell": "text"
1362 + },
1363 + {
1364 + "id": "connection_state",
1365 + "label": "Connection",
1366 + "projection": {
1367 + "kind": "direct",
1368 + "column": "connection_state"
1369 + },
1370 + "cell": "badge"
1371 + },
1372 + {
1373 + "id": "power_state",
1374 + "label": "Power",
1375 + "projection": {
1376 + "kind": "direct",
1377 + "column": "power_state"
1378 + },
1379 + "cell": "badge"
1380 + },
1381 + {
1382 + "id": "overall_status",
1383 + "label": "Status",
1384 + "projection": {
1385 + "kind": "direct",
1386 + "column": "overall_status"
1387 + },
1388 + "cell": "badge"
1389 + },
1390 + {
1391 + "id": "accessible",
1392 + "label": "Accessible",
1393 + "projection": {
1394 + "kind": "direct",
1395 + "column": "accessible"
1396 + },
1397 + "cell": "badge"
1398 + },
1399 + {
1400 + "id": "in_maintenance_mode",
1401 + "label": "In Maintenance",
1402 + "projection": {
1403 + "kind": "direct",
1404 + "column": "in_maintenance_mode"
1405 + },
1406 + "cell": "badge"
1407 + },
1408 + {
1409 + "id": "maintenance_mode",
1410 + "label": "Maintenance",
1411 + "projection": {
1412 + "kind": "direct",
1413 + "column": "maintenance_mode"
1414 + },
1415 + "cell": "badge"
1416 + },
1417 + {
1418 + "id": "datastore_type",
1419 + "label": "Datastore Type",
1420 + "projection": {
1421 + "kind": "direct",
1422 + "column": "datastore_type"
1423 + },
1424 + "cell": "badge"
1425 + },
1426 + {
1427 + "id": "network_type",
1428 + "label": "Network Type",
1429 + "projection": {
1430 + "kind": "direct",
1431 + "column": "network_type"
1432 + },
1433 + "cell": "badge"
1434 + },
1435 + {
1436 + "id": "ip_pool_name",
1437 + "label": "IP Pool",
1438 + "projection": {
1439 + "kind": "direct",
1440 + "column": "ip_pool_name"
1441 + },
1442 + "cell": "text",
1443 + "visibility": "expanded"
1444 + },
1445 + {
1446 + "id": "snapshot_count",
1447 + "label": "Snapshots",
1448 + "projection": {
1449 + "kind": "direct",
1450 + "column": "snapshot_count"
1451 + },
1452 + "cell": "number"
1453 + },
1454 + {
1455 + "id": "snapshot_chain_depth",
1456 + "label": "Snapshot Chain",
1457 + "projection": {
1458 + "kind": "direct",
1459 + "column": "snapshot_chain_depth"
1460 + },
1461 + "cell": "number",
1462 + "visibility": "expanded"
1463 + },
1464 + {
1465 + "id": "configured_vcpus",
1466 + "label": "vCPUs",
1467 + "projection": {
1468 + "kind": "direct",
1469 + "column": "configured_vcpus"
1470 + },
1471 + "cell": "number"
1472 + },
1473 + {
1474 + "id": "configured_memory_mib",
1475 + "label": "Memory MiB",
1476 + "projection": {
1477 + "kind": "direct",
1478 + "column": "configured_memory_mib"
1479 + },
1480 + "cell": "number"
1481 + },
1482 + {
1483 + "id": "capacity_bytes",
1484 + "label": "Capacity",
1485 + "projection": {
1486 + "kind": "direct",
1487 + "column": "capacity_bytes"
1488 + },
1489 + "cell": "number"
1490 + },
1491 + {
1492 + "id": "free_space_bytes",
1493 + "label": "Free Space",
1494 + "projection": {
1495 + "kind": "direct",
1496 + "column": "free_space_bytes"
1497 + },
1498 + "cell": "number"
1499 + },
1500 + {
1501 + "id": "uncommitted_bytes",
1502 + "label": "Uncommitted",
1503 + "projection": {
1504 + "kind": "direct",
1505 + "column": "uncommitted_bytes"
1506 + },
1507 + "cell": "number",
1508 + "visibility": "expanded"
1509 + },
1510 + {
1511 + "id": "cpu_capacity_mhz",
1512 + "label": "CPU Capacity MHz",
1513 + "projection": {
1514 + "kind": "direct",
1515 + "column": "cpu_capacity_mhz"
1516 + },
1517 + "cell": "number"
1518 + },
1519 + {
1520 + "id": "memory_capacity_mib",
1521 + "label": "Memory Capacity MiB",
1522 + "projection": {
1523 + "kind": "direct",
1524 + "column": "memory_capacity_mib"
1525 + },
1526 + "cell": "number"
1527 + },
1528 + {
1529 + "id": "cpu_limit_mhz",
1530 + "label": "CPU Limit MHz",
1531 + "projection": {
1532 + "kind": "direct",
1533 + "column": "cpu_limit_mhz"
1534 + },
1535 + "cell": "number"
1536 + },
1537 + {
1538 + "id": "memory_limit_mib",
1539 + "label": "Memory Limit MiB",
1540 + "projection": {
1541 + "kind": "direct",
1542 + "column": "memory_limit_mib"
1543 + },
1544 + "cell": "number"
1545 + },
1546 + {
1547 + "id": "cpu_reservation_mhz",
1548 + "label": "CPU Reservation MHz",
1549 + "projection": {
1550 + "kind": "direct",
1551 + "column": "cpu_reservation_mhz"
1552 + },
1553 + "cell": "number",
1554 + "visibility": "expanded"
1555 + },
1556 + {
1557 + "id": "memory_reservation_mib",
1558 + "label": "Memory Reservation MiB",
1559 + "projection": {
1560 + "kind": "direct",
1561 + "column": "memory_reservation_mib"
1562 + },
1563 + "cell": "number",
1564 + "visibility": "expanded"
1565 + },
1566 + {
1567 + "id": "network_hosts",
1568 + "label": "Hosts",
1569 + "projection": {
1570 + "kind": "direct",
1571 + "column": "network_hosts"
1572 + },
1573 + "cell": "number"
1574 + },
1575 + {
1576 + "id": "network_vms",
1577 + "label": "VMs",
1578 + "projection": {
1579 + "kind": "direct",
1580 + "column": "network_vms"
1581 + },
1582 + "cell": "number"
1583 + },
1584 + {
1585 + "id": "drs_enabled",
1586 + "label": "DRS",
1587 + "projection": {
1588 + "kind": "direct",
1589 + "column": "drs_enabled"
1590 + },
1591 + "cell": "badge"
1592 + },
1593 + {
1594 + "id": "ha_enabled",
1595 + "label": "HA",
1596 + "projection": {
1597 + "kind": "direct",
1598 + "column": "ha_enabled"
1599 + },
1600 + "cell": "badge"
1601 + },
1602 + {
1603 + "id": "vsan_enabled",
1604 + "label": "vSAN",
1605 + "projection": {
1606 + "kind": "direct",
1607 + "column": "vsan_enabled"
1608 + },
1609 + "cell": "badge"
1610 + },
1611 + {
1612 + "id": "storage_drs_enabled",
1613 + "label": "Storage DRS",
1614 + "projection": {
1615 + "kind": "direct",
1616 + "column": "storage_drs_enabled"
1617 + },
1618 + "cell": "badge"
1619 + },
1620 + {
1621 + "id": "multiple_host_access",
1622 + "label": "Multiple Host Access",
1623 + "projection": {
1624 + "kind": "direct",
1625 + "column": "multiple_host_access"
1626 + },
1627 + "cell": "badge"
1628 + },
1629 + {
1630 + "id": "consolidation_needed",
1631 + "label": "Consolidation",
1632 + "projection": {
1633 + "kind": "direct",
1634 + "column": "consolidation_needed"
1635 + },
1636 + "cell": "badge"
1637 + },
1638 + {
1639 + "id": "tools_running_status",
1640 + "label": "Tools Running",
1641 + "projection": {
1642 + "kind": "direct",
1643 + "column": "tools_running_status"
1644 + },
1645 + "cell": "badge",
1646 + "visibility": "expanded"
1647 + },
1648 + {
1649 + "id": "tools_version_status",
1650 + "label": "Tools Version",
1651 + "projection": {
1652 + "kind": "direct",
1653 + "column": "tools_version_status"
1654 + },
1655 + "cell": "badge",
1656 + "visibility": "expanded"
1657 + }
1658 + ],
1659 + "empty_label": "No vSphere details"
1660 + },
1661 + {
1662 + "id": "relationships",
1663 + "label": "Relationships",
1664 + "order": 20,
1665 + "source": {
1666 + "kind": "links"
1667 + },
1668 + "owner_filter": {
1669 + "mode": "incident_link",
1670 + "src_actor_column": "src_actor",
1671 + "dst_actor_column": "dst_actor"
1672 + },
1673 + "columns": [
1674 + {
1675 + "id": "relationship",
1676 + "label": "Relationship",
1677 + "projection": {
1678 + "kind": "direct",
1679 + "column": "relationship"
1680 + },
1681 + "cell": "badge"
1682 + },
1683 + {
1684 + "id": "related_object",
1685 + "label": "Related Object",
1686 + "projection": {
1687 + "kind": "opposite_actor",
1688 + "src_actor_column": "src_actor",
1689 + "dst_actor_column": "dst_actor"
1690 + },
1691 + "cell": "actor_link"
1692 + },
1693 + {
1694 + "id": "evidence_count",
1695 + "label": "Evidence",
1696 + "projection": {
1697 + "kind": "direct",
1698 + "column": "evidence_count"
1699 + },
1700 + "cell": "number"
1701 + }
1702 + ],
1703 + "empty_label": "No vSphere relationships"
1704 + }
1705 + ]
1706 + }
1707 + }
1708 },
55 - "vsphere_cluster": {
1709 + "vsphere_datastore_cluster": {
1710 "layer": "virtualization",
57 - "identity": ["vsphere_moid"],
58 - "merge_identity": ["vsphere_moid"],
59 - "parent_identity": ["parent_moid"],
60 - "aggregation_scopes": ["vsphere_object"]
1711 + "identity": [
1712 + "object_type",
1713 + "vsphere_moid"
1714 + ],
1715 + "merge_identity": [
1716 + "object_type",
1717 + "vsphere_moid"
1718 + ],
1719 + "parent_identity": [
1720 + "parent_object_type",
1721 + "parent_moid"
1722 + ],
1723 + "search": {
1724 + "columns": [
1725 + "name",
1726 + "vsphere_moid"
1727 + ],
1728 + "label_keys": [
1729 + "datacenter",
1730 + "cluster",
1731 + "host",
1732 + "resource_pool"
1733 + ]
1734 + },
1735 + "presentation": {
1736 + "label": "Datastore Cluster",
1737 + "role": "actor",
1738 + "icon": "datastore_cluster",
1739 + "color_slot": "teal",
1740 + "border": {
1741 + "enabled": true,
1742 + "style": "solid"
1743 + },
1744 + "label_policy": {
1745 + "columns": [
1746 + "name"
1747 + ],
1748 + "fallback": "type_label",
1749 + "max_length": 80,
1750 + "array": "reject"
1751 + },
1752 + "hover": {
1753 + "fields": [
1754 + {
1755 + "key": "name",
1756 + "label": "Name"
1757 + },
1758 + {
1759 + "key": "object_type",
1760 + "label": "Type"
1761 + },
1762 + {
1763 + "key": "vsphere_moid",
1764 + "label": "vSphere MoRef"
1765 + },
1766 + {
1767 + "key": "overall_status",
1768 + "label": "Status"
1769 + },
1770 + {
1771 + "key": "power_state",
1772 + "label": "Power"
1773 + }
1774 + ]
1775 + },
1776 + "modal": {
1777 + "enabled": true,
1778 + "labels": {
1779 + "enabled": true,
1780 + "table": "actor_labels",
1781 + "actor_column": "actor",
1782 + "key_column": "key",
1783 + "value_column": "value",
1784 + "source_column": "source",
1785 + "kind_column": "kind",
1786 + "value_index_column": "value_index",
1787 + "identification": {
1788 + "enabled": true,
1789 + "fields": [
1790 + {
1791 + "key": "object_type",
1792 + "label": "Type",
1793 + "max_values": 1
1794 + },
1795 + {
1796 + "key": "datacenter",
1797 + "label": "Datacenter",
1798 + "max_values": 1
1799 + },
1800 + {
1801 + "key": "cluster",
1802 + "label": "Cluster",
1803 + "max_values": 1
1804 + },
1805 + {
1806 + "key": "host",
1807 + "label": "Host",
1808 + "max_values": 1
1809 + },
1810 + {
1811 + "key": "resource_pool",
1812 + "label": "Resource Pool",
1813 + "max_values": 1
1814 + }
1815 + ]
1816 + }
1817 + },
1818 + "mini_topology": {
1819 + "enabled": true,
1820 + "depth": 1,
1821 + "include_link_types": [
1822 + "vsphere_ownership",
1823 + "vsphere_runs_on",
1824 + "vsphere_connected_to"
1825 + ]
1826 + },
1827 + "sections": [
1828 + {
1829 + "id": "details",
1830 + "label": "Details",
1831 + "order": 10,
1832 + "source": {
1833 + "kind": "actor_table",
1834 + "table": "vsphere_object_detail"
1835 + },
1836 + "owner_filter": {
1837 + "mode": "actor_column",
1838 + "actor_column": "actor"
1839 + },
1840 + "columns": [
1841 + {
1842 + "id": "object_type",
1843 + "label": "Type",
1844 + "projection": {
1845 + "kind": "direct",
1846 + "column": "object_type"
1847 + },
1848 + "cell": "badge"
1849 + },
1850 + {
1851 + "id": "name",
1852 + "label": "Name",
1853 + "projection": {
1854 + "kind": "direct",
1855 + "column": "name"
1856 + },
1857 + "cell": "text"
1858 + },
1859 + {
1860 + "id": "vsphere_moid",
1861 + "label": "vSphere MoRef",
1862 + "projection": {
1863 + "kind": "direct",
1864 + "column": "vsphere_moid"
1865 + },
1866 + "cell": "text"
1867 + },
1868 + {
1869 + "id": "datacenter",
1870 + "label": "Datacenter",
1871 + "projection": {
1872 + "kind": "direct",
1873 + "column": "datacenter"
1874 + },
1875 + "cell": "text"
1876 + },
1877 + {
1878 + "id": "cluster",
1879 + "label": "Cluster",
1880 + "projection": {
1881 + "kind": "direct",
1882 + "column": "cluster"
1883 + },
1884 + "cell": "text"
1885 + },
1886 + {
1887 + "id": "host",
1888 + "label": "Host",
1889 + "projection": {
1890 + "kind": "direct",
1891 + "column": "host"
1892 + },
1893 + "cell": "text"
1894 + },
1895 + {
1896 + "id": "resource_pool",
1897 + "label": "Resource Pool",
1898 + "projection": {
1899 + "kind": "direct",
1900 + "column": "resource_pool"
1901 + },
1902 + "cell": "text"
1903 + },
1904 + {
1905 + "id": "connection_state",
1906 + "label": "Connection",
1907 + "projection": {
1908 + "kind": "direct",
1909 + "column": "connection_state"
1910 + },
1911 + "cell": "badge"
1912 + },
1913 + {
1914 + "id": "power_state",
1915 + "label": "Power",
1916 + "projection": {
1917 + "kind": "direct",
1918 + "column": "power_state"
1919 + },
1920 + "cell": "badge"
1921 + },
1922 + {
1923 + "id": "overall_status",
1924 + "label": "Status",
1925 + "projection": {
1926 + "kind": "direct",
1927 + "column": "overall_status"
1928 + },
1929 + "cell": "badge"
1930 + },
1931 + {
1932 + "id": "accessible",
1933 + "label": "Accessible",
1934 + "projection": {
1935 + "kind": "direct",
1936 + "column": "accessible"
1937 + },
1938 + "cell": "badge"
1939 + },
1940 + {
1941 + "id": "in_maintenance_mode",
1942 + "label": "In Maintenance",
1943 + "projection": {
1944 + "kind": "direct",
1945 + "column": "in_maintenance_mode"
1946 + },
1947 + "cell": "badge"
1948 + },
1949 + {
1950 + "id": "maintenance_mode",
1951 + "label": "Maintenance",
1952 + "projection": {
1953 + "kind": "direct",
1954 + "column": "maintenance_mode"
1955 + },
1956 + "cell": "badge"
1957 + },
1958 + {
1959 + "id": "datastore_type",
1960 + "label": "Datastore Type",
1961 + "projection": {
1962 + "kind": "direct",
1963 + "column": "datastore_type"
1964 + },
1965 + "cell": "badge"
1966 + },
1967 + {
1968 + "id": "network_type",
1969 + "label": "Network Type",
1970 + "projection": {
1971 + "kind": "direct",
1972 + "column": "network_type"
1973 + },
1974 + "cell": "badge"
1975 + },
1976 + {
1977 + "id": "ip_pool_name",
1978 + "label": "IP Pool",
1979 + "projection": {
1980 + "kind": "direct",
1981 + "column": "ip_pool_name"
1982 + },
1983 + "cell": "text",
1984 + "visibility": "expanded"
1985 + },
1986 + {
1987 + "id": "snapshot_count",
1988 + "label": "Snapshots",
1989 + "projection": {
1990 + "kind": "direct",
1991 + "column": "snapshot_count"
1992 + },
1993 + "cell": "number"
1994 + },
1995 + {
1996 + "id": "snapshot_chain_depth",
1997 + "label": "Snapshot Chain",
1998 + "projection": {
1999 + "kind": "direct",
2000 + "column": "snapshot_chain_depth"
2001 + },
2002 + "cell": "number",
2003 + "visibility": "expanded"
2004 + },
2005 + {
2006 + "id": "configured_vcpus",
2007 + "label": "vCPUs",
2008 + "projection": {
2009 + "kind": "direct",
2010 + "column": "configured_vcpus"
2011 + },
2012 + "cell": "number"
2013 + },
2014 + {
2015 + "id": "configured_memory_mib",
2016 + "label": "Memory MiB",
2017 + "projection": {
2018 + "kind": "direct",
2019 + "column": "configured_memory_mib"
2020 + },
2021 + "cell": "number"
2022 + },
2023 + {
2024 + "id": "capacity_bytes",
2025 + "label": "Capacity",
2026 + "projection": {
2027 + "kind": "direct",
2028 + "column": "capacity_bytes"
2029 + },
2030 + "cell": "number"
2031 + },
2032 + {
2033 + "id": "free_space_bytes",
2034 + "label": "Free Space",
2035 + "projection": {
2036 + "kind": "direct",
2037 + "column": "free_space_bytes"
2038 + },
2039 + "cell": "number"
2040 + },
2041 + {
2042 + "id": "uncommitted_bytes",
2043 + "label": "Uncommitted",
2044 + "projection": {
2045 + "kind": "direct",
2046 + "column": "uncommitted_bytes"
2047 + },
2048 + "cell": "number",
2049 + "visibility": "expanded"
2050 + },
2051 + {
2052 + "id": "cpu_capacity_mhz",
2053 + "label": "CPU Capacity MHz",
2054 + "projection": {
2055 + "kind": "direct",
2056 + "column": "cpu_capacity_mhz"
2057 + },
2058 + "cell": "number"
2059 + },
2060 + {
2061 + "id": "memory_capacity_mib",
2062 + "label": "Memory Capacity MiB",
2063 + "projection": {
2064 + "kind": "direct",
2065 + "column": "memory_capacity_mib"
2066 + },
2067 + "cell": "number"
2068 + },
2069 + {
2070 + "id": "cpu_limit_mhz",
2071 + "label": "CPU Limit MHz",
2072 + "projection": {
2073 + "kind": "direct",
2074 + "column": "cpu_limit_mhz"
2075 + },
2076 + "cell": "number"
2077 + },
2078 + {
2079 + "id": "memory_limit_mib",
2080 + "label": "Memory Limit MiB",
2081 + "projection": {
2082 + "kind": "direct",
2083 + "column": "memory_limit_mib"
2084 + },
2085 + "cell": "number"
2086 + },
2087 + {
2088 + "id": "cpu_reservation_mhz",
2089 + "label": "CPU Reservation MHz",
2090 + "projection": {
2091 + "kind": "direct",
2092 + "column": "cpu_reservation_mhz"
2093 + },
2094 + "cell": "number",
2095 + "visibility": "expanded"
2096 + },
2097 + {
2098 + "id": "memory_reservation_mib",
2099 + "label": "Memory Reservation MiB",
2100 + "projection": {
2101 + "kind": "direct",
2102 + "column": "memory_reservation_mib"
2103 + },
2104 + "cell": "number",
2105 + "visibility": "expanded"
2106 + },
2107 + {
2108 + "id": "network_hosts",
2109 + "label": "Hosts",
2110 + "projection": {
2111 + "kind": "direct",
2112 + "column": "network_hosts"
2113 + },
2114 + "cell": "number"
2115 + },
2116 + {
2117 + "id": "network_vms",
2118 + "label": "VMs",
2119 + "projection": {
2120 + "kind": "direct",
2121 + "column": "network_vms"
2122 + },
2123 + "cell": "number"
2124 + },
2125 + {
2126 + "id": "drs_enabled",
2127 + "label": "DRS",
2128 + "projection": {
2129 + "kind": "direct",
2130 + "column": "drs_enabled"
2131 + },
2132 + "cell": "badge"
2133 + },
2134 + {
2135 + "id": "ha_enabled",
2136 + "label": "HA",
2137 + "projection": {
2138 + "kind": "direct",
2139 + "column": "ha_enabled"
2140 + },
2141 + "cell": "badge"
2142 + },
2143 + {
2144 + "id": "vsan_enabled",
2145 + "label": "vSAN",
2146 + "projection": {
2147 + "kind": "direct",
2148 + "column": "vsan_enabled"
2149 + },
2150 + "cell": "badge"
2151 + },
2152 + {
2153 + "id": "storage_drs_enabled",
2154 + "label": "Storage DRS",
2155 + "projection": {
2156 + "kind": "direct",
2157 + "column": "storage_drs_enabled"
2158 + },
2159 + "cell": "badge"
2160 + },
2161 + {
2162 + "id": "multiple_host_access",
2163 + "label": "Multiple Host Access",
2164 + "projection": {
2165 + "kind": "direct",
2166 + "column": "multiple_host_access"
2167 + },
2168 + "cell": "badge"
2169 + },
2170 + {
2171 + "id": "consolidation_needed",
2172 + "label": "Consolidation",
2173 + "projection": {
2174 + "kind": "direct",
2175 + "column": "consolidation_needed"
2176 + },
2177 + "cell": "badge"
2178 + },
2179 + {
2180 + "id": "tools_running_status",
2181 + "label": "Tools Running",
2182 + "projection": {
2183 + "kind": "direct",
2184 + "column": "tools_running_status"
2185 + },
2186 + "cell": "badge",
2187 + "visibility": "expanded"
2188 + },
2189 + {
2190 + "id": "tools_version_status",
2191 + "label": "Tools Version",
2192 + "projection": {
2193 + "kind": "direct",
2194 + "column": "tools_version_status"
2195 + },
2196 + "cell": "badge",
2197 + "visibility": "expanded"
2198 + }
2199 + ],
2200 + "empty_label": "No vSphere details"
2201 + },
2202 + {
2203 + "id": "relationships",
2204 + "label": "Relationships",
2205 + "order": 20,
2206 + "source": {
2207 + "kind": "links"
2208 + },
2209 + "owner_filter": {
2210 + "mode": "incident_link",
2211 + "src_actor_column": "src_actor",
2212 + "dst_actor_column": "dst_actor"
2213 + },
2214 + "columns": [
2215 + {
2216 + "id": "relationship",
2217 + "label": "Relationship",
2218 + "projection": {
2219 + "kind": "direct",
2220 + "column": "relationship"
2221 + },
2222 + "cell": "badge"
2223 + },
2224 + {
2225 + "id": "related_object",
2226 + "label": "Related Object",
2227 + "projection": {
2228 + "kind": "opposite_actor",
2229 + "src_actor_column": "src_actor",
2230 + "dst_actor_column": "dst_actor"
2231 + },
2232 + "cell": "actor_link"
2233 + },
2234 + {
2235 + "id": "evidence_count",
2236 + "label": "Evidence",
2237 + "projection": {
2238 + "kind": "direct",
2239 + "column": "evidence_count"
2240 + },
2241 + "cell": "number"
2242 + }
2243 + ],
2244 + "empty_label": "No vSphere relationships"
2245 + }
2246 + ]
2247 + }
2248 + }
2249 },
2250 "vsphere_host": {
2251 "layer": "virtualization",
64 - "identity": ["vsphere_moid"],
65 - "merge_identity": ["vsphere_moid"],
66 - "parent_identity": ["parent_moid"],
67 - "aggregation_scopes": ["vsphere_object"]
2252 + "identity": [
2253 + "object_type",
2254 + "vsphere_moid"
2255 + ],
2256 + "merge_identity": [
2257 + "object_type",
2258 + "vsphere_moid"
2259 + ],
2260 + "parent_identity": [
2261 + "parent_object_type",
2262 + "parent_moid"
2263 + ],
2264 + "search": {
2265 + "columns": [
2266 + "name",
2267 + "vsphere_moid"
2268 + ],
2269 + "label_keys": [
2270 + "datacenter",
2271 + "cluster",
2272 + "host",
2273 + "resource_pool"
2274 + ]
2275 + },
2276 + "presentation": {
2277 + "label": "ESXi Host",
2278 + "role": "actor",
2279 + "icon": "host",
2280 + "color_slot": "orange",
2281 + "border": {
2282 + "enabled": true,
2283 + "style": "solid"
2284 + },
2285 + "label_policy": {
2286 + "columns": [
2287 + "name"
2288 + ],
2289 + "fallback": "type_label",
2290 + "max_length": 80,
2291 + "array": "reject"
2292 + },
2293 + "hover": {
2294 + "fields": [
2295 + {
2296 + "key": "name",
2297 + "label": "Name"
2298 + },
2299 + {
2300 + "key": "object_type",
2301 + "label": "Type"
2302 + },
2303 + {
2304 + "key": "vsphere_moid",
2305 + "label": "vSphere MoRef"
2306 + },
2307 + {
2308 + "key": "overall_status",
2309 + "label": "Status"
2310 + },
2311 + {
2312 + "key": "power_state",
2313 + "label": "Power"
2314 + }
2315 + ]
2316 + },
2317 + "modal": {
2318 + "enabled": true,
2319 + "labels": {
2320 + "enabled": true,
2321 + "table": "actor_labels",
2322 + "actor_column": "actor",
2323 + "key_column": "key",
2324 + "value_column": "value",
2325 + "source_column": "source",
2326 + "kind_column": "kind",
2327 + "value_index_column": "value_index",
2328 + "identification": {
2329 + "enabled": true,
2330 + "fields": [
2331 + {
2332 + "key": "object_type",
2333 + "label": "Type",
2334 + "max_values": 1
2335 + },
2336 + {
2337 + "key": "datacenter",
2338 + "label": "Datacenter",
2339 + "max_values": 1
2340 + },
2341 + {
2342 + "key": "cluster",
2343 + "label": "Cluster",
2344 + "max_values": 1
2345 + },
2346 + {
2347 + "key": "host",
2348 + "label": "Host",
2349 + "max_values": 1
2350 + },
2351 + {
2352 + "key": "resource_pool",
2353 + "label": "Resource Pool",
2354 + "max_values": 1
2355 + }
2356 + ]
2357 + }
2358 + },
2359 + "mini_topology": {
2360 + "enabled": true,
2361 + "depth": 1,
2362 + "include_link_types": [
2363 + "vsphere_ownership",
2364 + "vsphere_runs_on",
2365 + "vsphere_connected_to"
2366 + ]
2367 + },
2368 + "sections": [
2369 + {
2370 + "id": "details",
2371 + "label": "Details",
2372 + "order": 10,
2373 + "source": {
2374 + "kind": "actor_table",
2375 + "table": "vsphere_object_detail"
2376 + },
2377 + "owner_filter": {
2378 + "mode": "actor_column",
2379 + "actor_column": "actor"
2380 + },
2381 + "columns": [
2382 + {
2383 + "id": "object_type",
2384 + "label": "Type",
2385 + "projection": {
2386 + "kind": "direct",
2387 + "column": "object_type"
2388 + },
2389 + "cell": "badge"
2390 + },
2391 + {
2392 + "id": "name",
2393 + "label": "Name",
2394 + "projection": {
2395 + "kind": "direct",
2396 + "column": "name"
2397 + },
2398 + "cell": "text"
2399 + },
2400 + {
2401 + "id": "vsphere_moid",
2402 + "label": "vSphere MoRef",
2403 + "projection": {
2404 + "kind": "direct",
2405 + "column": "vsphere_moid"
2406 + },
2407 + "cell": "text"
2408 + },
2409 + {
2410 + "id": "datacenter",
2411 + "label": "Datacenter",
2412 + "projection": {
2413 + "kind": "direct",
2414 + "column": "datacenter"
2415 + },
2416 + "cell": "text"
2417 + },
2418 + {
2419 + "id": "cluster",
2420 + "label": "Cluster",
2421 + "projection": {
2422 + "kind": "direct",
2423 + "column": "cluster"
2424 + },
2425 + "cell": "text"
2426 + },
2427 + {
2428 + "id": "host",
2429 + "label": "Host",
2430 + "projection": {
2431 + "kind": "direct",
2432 + "column": "host"
2433 + },
2434 + "cell": "text"
2435 + },
2436 + {
2437 + "id": "resource_pool",
2438 + "label": "Resource Pool",
2439 + "projection": {
2440 + "kind": "direct",
2441 + "column": "resource_pool"
2442 + },
2443 + "cell": "text"
2444 + },
2445 + {
2446 + "id": "connection_state",
2447 + "label": "Connection",
2448 + "projection": {
2449 + "kind": "direct",
2450 + "column": "connection_state"
2451 + },
2452 + "cell": "badge"
2453 + },
2454 + {
2455 + "id": "power_state",
2456 + "label": "Power",
2457 + "projection": {
2458 + "kind": "direct",
2459 + "column": "power_state"
2460 + },
2461 + "cell": "badge"
2462 + },
2463 + {
2464 + "id": "overall_status",
2465 + "label": "Status",
2466 + "projection": {
2467 + "kind": "direct",
2468 + "column": "overall_status"
2469 + },
2470 + "cell": "badge"
2471 + },
2472 + {
2473 + "id": "accessible",
2474 + "label": "Accessible",
2475 + "projection": {
2476 + "kind": "direct",
2477 + "column": "accessible"
2478 + },
2479 + "cell": "badge"
2480 + },
2481 + {
2482 + "id": "in_maintenance_mode",
2483 + "label": "In Maintenance",
2484 + "projection": {
2485 + "kind": "direct",
2486 + "column": "in_maintenance_mode"
2487 + },
2488 + "cell": "badge"
2489 + },
2490 + {
2491 + "id": "maintenance_mode",
2492 + "label": "Maintenance",
2493 + "projection": {
2494 + "kind": "direct",
2495 + "column": "maintenance_mode"
2496 + },
2497 + "cell": "badge"
2498 + },
2499 + {
2500 + "id": "datastore_type",
2501 + "label": "Datastore Type",
2502 + "projection": {
2503 + "kind": "direct",
2504 + "column": "datastore_type"
2505 + },
2506 + "cell": "badge"
2507 + },
2508 + {
2509 + "id": "network_type",
2510 + "label": "Network Type",
2511 + "projection": {
2512 + "kind": "direct",
2513 + "column": "network_type"
2514 + },
2515 + "cell": "badge"
2516 + },
2517 + {
2518 + "id": "ip_pool_name",
2519 + "label": "IP Pool",
2520 + "projection": {
2521 + "kind": "direct",
2522 + "column": "ip_pool_name"
2523 + },
2524 + "cell": "text",
2525 + "visibility": "expanded"
2526 + },
2527 + {
2528 + "id": "snapshot_count",
2529 + "label": "Snapshots",
2530 + "projection": {
2531 + "kind": "direct",
2532 + "column": "snapshot_count"
2533 + },
2534 + "cell": "number"
2535 + },
2536 + {
2537 + "id": "snapshot_chain_depth",
2538 + "label": "Snapshot Chain",
2539 + "projection": {
2540 + "kind": "direct",
2541 + "column": "snapshot_chain_depth"
2542 + },
2543 + "cell": "number",
2544 + "visibility": "expanded"
2545 + },
2546 + {
2547 + "id": "configured_vcpus",
2548 + "label": "vCPUs",
2549 + "projection": {
2550 + "kind": "direct",
2551 + "column": "configured_vcpus"
2552 + },
2553 + "cell": "number"
2554 + },
2555 + {
2556 + "id": "configured_memory_mib",
2557 + "label": "Memory MiB",
2558 + "projection": {
2559 + "kind": "direct",
2560 + "column": "configured_memory_mib"
2561 + },
2562 + "cell": "number"
2563 + },
2564 + {
2565 + "id": "capacity_bytes",
2566 + "label": "Capacity",
2567 + "projection": {
2568 + "kind": "direct",
2569 + "column": "capacity_bytes"
2570 + },
2571 + "cell": "number"
2572 + },
2573 + {
2574 + "id": "free_space_bytes",
2575 + "label": "Free Space",
2576 + "projection": {
2577 + "kind": "direct",
2578 + "column": "free_space_bytes"
2579 + },
2580 + "cell": "number"
2581 + },
2582 + {
2583 + "id": "uncommitted_bytes",
2584 + "label": "Uncommitted",
2585 + "projection": {
2586 + "kind": "direct",
2587 + "column": "uncommitted_bytes"
2588 + },
2589 + "cell": "number",
2590 + "visibility": "expanded"
2591 + },
2592 + {
2593 + "id": "cpu_capacity_mhz",
2594 + "label": "CPU Capacity MHz",
2595 + "projection": {
2596 + "kind": "direct",
2597 + "column": "cpu_capacity_mhz"
2598 + },
2599 + "cell": "number"
2600 + },
2601 + {
2602 + "id": "memory_capacity_mib",
2603 + "label": "Memory Capacity MiB",
2604 + "projection": {
2605 + "kind": "direct",
2606 + "column": "memory_capacity_mib"
2607 + },
2608 + "cell": "number"
2609 + },
2610 + {
2611 + "id": "cpu_limit_mhz",
2612 + "label": "CPU Limit MHz",
2613 + "projection": {
2614 + "kind": "direct",
2615 + "column": "cpu_limit_mhz"
2616 + },
2617 + "cell": "number"
2618 + },
2619 + {
2620 + "id": "memory_limit_mib",
2621 + "label": "Memory Limit MiB",
2622 + "projection": {
2623 + "kind": "direct",
2624 + "column": "memory_limit_mib"
2625 + },
2626 + "cell": "number"
2627 + },
2628 + {
2629 + "id": "cpu_reservation_mhz",
2630 + "label": "CPU Reservation MHz",
2631 + "projection": {
2632 + "kind": "direct",
2633 + "column": "cpu_reservation_mhz"
2634 + },
2635 + "cell": "number",
2636 + "visibility": "expanded"
2637 + },
2638 + {
2639 + "id": "memory_reservation_mib",
2640 + "label": "Memory Reservation MiB",
2641 + "projection": {
2642 + "kind": "direct",
2643 + "column": "memory_reservation_mib"
2644 + },
2645 + "cell": "number",
2646 + "visibility": "expanded"
2647 + },
2648 + {
2649 + "id": "network_hosts",
2650 + "label": "Hosts",
2651 + "projection": {
2652 + "kind": "direct",
2653 + "column": "network_hosts"
2654 + },
2655 + "cell": "number"
2656 + },
2657 + {
2658 + "id": "network_vms",
2659 + "label": "VMs",
2660 + "projection": {
2661 + "kind": "direct",
2662 + "column": "network_vms"
2663 + },
2664 + "cell": "number"
2665 + },
2666 + {
2667 + "id": "drs_enabled",
2668 + "label": "DRS",
2669 + "projection": {
2670 + "kind": "direct",
2671 + "column": "drs_enabled"
2672 + },
2673 + "cell": "badge"
2674 + },
2675 + {
2676 + "id": "ha_enabled",
2677 + "label": "HA",
2678 + "projection": {
2679 + "kind": "direct",
2680 + "column": "ha_enabled"
2681 + },
2682 + "cell": "badge"
2683 + },
2684 + {
2685 + "id": "vsan_enabled",
2686 + "label": "vSAN",
2687 + "projection": {
2688 + "kind": "direct",
2689 + "column": "vsan_enabled"
2690 + },
2691 + "cell": "badge"
2692 + },
2693 + {
2694 + "id": "storage_drs_enabled",
2695 + "label": "Storage DRS",
2696 + "projection": {
2697 + "kind": "direct",
2698 + "column": "storage_drs_enabled"
2699 + },
2700 + "cell": "badge"
2701 + },
2702 + {
2703 + "id": "multiple_host_access",
2704 + "label": "Multiple Host Access",
2705 + "projection": {
2706 + "kind": "direct",
2707 + "column": "multiple_host_access"
2708 + },
2709 + "cell": "badge"
2710 + },
2711 + {
2712 + "id": "consolidation_needed",
2713 + "label": "Consolidation",
2714 + "projection": {
2715 + "kind": "direct",
2716 + "column": "consolidation_needed"
2717 + },
2718 + "cell": "badge"
2719 + },
2720 + {
2721 + "id": "tools_running_status",
2722 + "label": "Tools Running",
2723 + "projection": {
2724 + "kind": "direct",
2725 + "column": "tools_running_status"
2726 + },
2727 + "cell": "badge",
2728 + "visibility": "expanded"
2729 + },
2730 + {
2731 + "id": "tools_version_status",
2732 + "label": "Tools Version",
2733 + "projection": {
2734 + "kind": "direct",
2735 + "column": "tools_version_status"
2736 + },
2737 + "cell": "badge",
2738 + "visibility": "expanded"
2739 + }
2740 + ],
2741 + "empty_label": "No vSphere details"
2742 + },
2743 + {
2744 + "id": "relationships",
2745 + "label": "Relationships",
2746 + "order": 20,
2747 + "source": {
2748 + "kind": "links"
2749 + },
2750 + "owner_filter": {
2751 + "mode": "incident_link",
2752 + "src_actor_column": "src_actor",
2753 + "dst_actor_column": "dst_actor"
2754 + },
2755 + "columns": [
2756 + {
2757 + "id": "relationship",
2758 + "label": "Relationship",
2759 + "projection": {
2760 + "kind": "direct",
2761 + "column": "relationship"
2762 + },
2763 + "cell": "badge"
2764 + },
2765 + {
2766 + "id": "related_object",
2767 + "label": "Related Object",
2768 + "projection": {
2769 + "kind": "opposite_actor",
2770 + "src_actor_column": "src_actor",
2771 + "dst_actor_column": "dst_actor"
2772 + },
2773 + "cell": "actor_link"
2774 + },
2775 + {
2776 + "id": "evidence_count",
2777 + "label": "Evidence",
2778 + "projection": {
2779 + "kind": "direct",
2780 + "column": "evidence_count"
2781 + },
2782 + "cell": "number"
2783 + }
2784 + ],
2785 + "empty_label": "No vSphere relationships"
2786 + }
2787 + ]
2788 + }
2789 + }
2790 },
69 - "vsphere_vm": {
2791 + "vsphere_network": {
2792 + "layer": "virtualization",
2793 + "identity": [
2794 + "object_type",
2795 + "vsphere_moid"
2796 + ],
2797 + "merge_identity": [
2798 + "object_type",
2799 + "vsphere_moid"
2800 + ],
2801 + "parent_identity": [
2802 + "parent_object_type",
2803 + "parent_moid"
2804 + ],
2805 + "search": {
2806 + "columns": [
2807 + "name",
2808 + "vsphere_moid"
2809 + ],
2810 + "label_keys": [
2811 + "datacenter",
2812 + "cluster",
2813 + "host",
2814 + "resource_pool"
2815 + ]
2816 + },
2817 + "presentation": {
2818 + "label": "Network",
2819 + "role": "actor",
2820 + "icon": "network",
2821 + "color_slot": "yellow",
2822 + "border": {
2823 + "enabled": true,
2824 + "style": "solid"
2825 + },
2826 + "label_policy": {
2827 + "columns": [
2828 + "name"
2829 + ],
2830 + "fallback": "type_label",
2831 + "max_length": 80,
2832 + "array": "reject"
2833 + },
2834 + "hover": {
2835 + "fields": [
2836 + {
2837 + "key": "name",
2838 + "label": "Name"
2839 + },
2840 + {
2841 + "key": "object_type",
2842 + "label": "Type"
2843 + },
2844 + {
2845 + "key": "vsphere_moid",
2846 + "label": "vSphere MoRef"
2847 + },
2848 + {
2849 + "key": "overall_status",
2850 + "label": "Status"
2851 + },
2852 + {
2853 + "key": "power_state",
2854 + "label": "Power"
2855 + }
2856 + ]
2857 + },
2858 + "modal": {
2859 + "enabled": true,
2860 + "labels": {
2861 + "enabled": true,
2862 + "table": "actor_labels",
2863 + "actor_column": "actor",
2864 + "key_column": "key",
2865 + "value_column": "value",
2866 + "source_column": "source",
2867 + "kind_column": "kind",
2868 + "value_index_column": "value_index",
2869 + "identification": {
2870 + "enabled": true,
2871 + "fields": [
2872 + {
2873 + "key": "object_type",
2874 + "label": "Type",
2875 + "max_values": 1
2876 + },
2877 + {
2878 + "key": "datacenter",
2879 + "label": "Datacenter",
2880 + "max_values": 1
2881 + },
2882 + {
2883 + "key": "cluster",
2884 + "label": "Cluster",
2885 + "max_values": 1
2886 + },
2887 + {
2888 + "key": "host",
2889 + "label": "Host",
2890 + "max_values": 1
2891 + },
2892 + {
2893 + "key": "resource_pool",
2894 + "label": "Resource Pool",
2895 + "max_values": 1
2896 + }
2897 + ]
2898 + }
2899 + },
2900 + "mini_topology": {
2901 + "enabled": true,
2902 + "depth": 1,
2903 + "include_link_types": [
2904 + "vsphere_ownership",
2905 + "vsphere_runs_on",
2906 + "vsphere_connected_to"
2907 + ]
2908 + },
2909 + "sections": [
2910 + {
2911 + "id": "details",
2912 + "label": "Details",
2913 + "order": 10,
2914 + "source": {
2915 + "kind": "actor_table",
2916 + "table": "vsphere_object_detail"
2917 + },
2918 + "owner_filter": {
2919 + "mode": "actor_column",
2920 + "actor_column": "actor"
2921 + },
2922 + "columns": [
2923 + {
2924 + "id": "object_type",
2925 + "label": "Type",
2926 + "projection": {
2927 + "kind": "direct",
2928 + "column": "object_type"
2929 + },
2930 + "cell": "badge"
2931 + },
2932 + {
2933 + "id": "name",
2934 + "label": "Name",
2935 + "projection": {
2936 + "kind": "direct",
2937 + "column": "name"
2938 + },
2939 + "cell": "text"
2940 + },
2941 + {
2942 + "id": "vsphere_moid",
2943 + "label": "vSphere MoRef",
2944 + "projection": {
2945 + "kind": "direct",
2946 + "column": "vsphere_moid"
2947 + },
2948 + "cell": "text"
2949 + },
2950 + {
2951 + "id": "datacenter",
2952 + "label": "Datacenter",
2953 + "projection": {
2954 + "kind": "direct",
2955 + "column": "datacenter"
2956 + },
2957 + "cell": "text"
2958 + },
2959 + {
2960 + "id": "cluster",
2961 + "label": "Cluster",
2962 + "projection": {
2963 + "kind": "direct",
2964 + "column": "cluster"
2965 + },
2966 + "cell": "text"
2967 + },
2968 + {
2969 + "id": "host",
2970 + "label": "Host",
2971 + "projection": {
2972 + "kind": "direct",
2973 + "column": "host"
2974 + },
2975 + "cell": "text"
2976 + },
2977 + {
2978 + "id": "resource_pool",
2979 + "label": "Resource Pool",
2980 + "projection": {
2981 + "kind": "direct",
2982 + "column": "resource_pool"
2983 + },
2984 + "cell": "text"
2985 + },
2986 + {
2987 + "id": "connection_state",
2988 + "label": "Connection",
2989 + "projection": {
2990 + "kind": "direct",
2991 + "column": "connection_state"
2992 + },
2993 + "cell": "badge"
2994 + },
2995 + {
2996 + "id": "power_state",
2997 + "label": "Power",
2998 + "projection": {
2999 + "kind": "direct",
3000 + "column": "power_state"
3001 + },
3002 + "cell": "badge"
3003 + },
3004 + {
3005 + "id": "overall_status",
3006 + "label": "Status",
3007 + "projection": {
3008 + "kind": "direct",
3009 + "column": "overall_status"
3010 + },
3011 + "cell": "badge"
3012 + },
3013 + {
3014 + "id": "accessible",
3015 + "label": "Accessible",
3016 + "projection": {
3017 + "kind": "direct",
3018 + "column": "accessible"
3019 + },
3020 + "cell": "badge"
3021 + },
3022 + {
3023 + "id": "in_maintenance_mode",
3024 + "label": "In Maintenance",
3025 + "projection": {
3026 + "kind": "direct",
3027 + "column": "in_maintenance_mode"
3028 + },
3029 + "cell": "badge"
3030 + },
3031 + {
3032 + "id": "maintenance_mode",
3033 + "label": "Maintenance",
3034 + "projection": {
3035 + "kind": "direct",
3036 + "column": "maintenance_mode"
3037 + },
3038 + "cell": "badge"
3039 + },
3040 + {
3041 + "id": "datastore_type",
3042 + "label": "Datastore Type",
3043 + "projection": {
3044 + "kind": "direct",
3045 + "column": "datastore_type"
3046 + },
3047 + "cell": "badge"
3048 + },
3049 + {
3050 + "id": "network_type",
3051 + "label": "Network Type",
3052 + "projection": {
3053 + "kind": "direct",
3054 + "column": "network_type"
3055 + },
3056 + "cell": "badge"
3057 + },
3058 + {
3059 + "id": "ip_pool_name",
3060 + "label": "IP Pool",
3061 + "projection": {
3062 + "kind": "direct",
3063 + "column": "ip_pool_name"
3064 + },
3065 + "cell": "text",
3066 + "visibility": "expanded"
3067 + },
3068 + {
3069 + "id": "snapshot_count",
3070 + "label": "Snapshots",
3071 + "projection": {
3072 + "kind": "direct",
3073 + "column": "snapshot_count"
3074 + },
3075 + "cell": "number"
3076 + },
3077 + {
3078 + "id": "snapshot_chain_depth",
3079 + "label": "Snapshot Chain",
3080 + "projection": {
3081 + "kind": "direct",
3082 + "column": "snapshot_chain_depth"
3083 + },
3084 + "cell": "number",
3085 + "visibility": "expanded"
3086 + },
3087 + {
3088 + "id": "configured_vcpus",
3089 + "label": "vCPUs",
3090 + "projection": {
3091 + "kind": "direct",
3092 + "column": "configured_vcpus"
3093 + },
3094 + "cell": "number"
3095 + },
3096 + {
3097 + "id": "configured_memory_mib",
3098 + "label": "Memory MiB",
3099 + "projection": {
3100 + "kind": "direct",
3101 + "column": "configured_memory_mib"
3102 + },
3103 + "cell": "number"
3104 + },
3105 + {
3106 + "id": "capacity_bytes",
3107 + "label": "Capacity",
3108 + "projection": {
3109 + "kind": "direct",
3110 + "column": "capacity_bytes"
3111 + },
3112 + "cell": "number"
3113 + },
3114 + {
3115 + "id": "free_space_bytes",
3116 + "label": "Free Space",
3117 + "projection": {
3118 + "kind": "direct",
3119 + "column": "free_space_bytes"
3120 + },
3121 + "cell": "number"
3122 + },
3123 + {
3124 + "id": "uncommitted_bytes",
3125 + "label": "Uncommitted",
3126 + "projection": {
3127 + "kind": "direct",
3128 + "column": "uncommitted_bytes"
3129 + },
3130 + "cell": "number",
3131 + "visibility": "expanded"
3132 + },
3133 + {
3134 + "id": "cpu_capacity_mhz",
3135 + "label": "CPU Capacity MHz",
3136 + "projection": {
3137 + "kind": "direct",
3138 + "column": "cpu_capacity_mhz"
3139 + },
3140 + "cell": "number"
3141 + },
3142 + {
3143 + "id": "memory_capacity_mib",
3144 + "label": "Memory Capacity MiB",
3145 + "projection": {
3146 + "kind": "direct",
3147 + "column": "memory_capacity_mib"
3148 + },
3149 + "cell": "number"
3150 + },
3151 + {
3152 + "id": "cpu_limit_mhz",
3153 + "label": "CPU Limit MHz",
3154 + "projection": {
3155 + "kind": "direct",
3156 + "column": "cpu_limit_mhz"
3157 + },
3158 + "cell": "number"
3159 + },
3160 + {
3161 + "id": "memory_limit_mib",
3162 + "label": "Memory Limit MiB",
3163 + "projection": {
3164 + "kind": "direct",
3165 + "column": "memory_limit_mib"
3166 + },
3167 + "cell": "number"
3168 + },
3169 + {
3170 + "id": "cpu_reservation_mhz",
3171 + "label": "CPU Reservation MHz",
3172 + "projection": {
3173 + "kind": "direct",
3174 + "column": "cpu_reservation_mhz"
3175 + },
3176 + "cell": "number",
3177 + "visibility": "expanded"
3178 + },
3179 + {
3180 + "id": "memory_reservation_mib",
3181 + "label": "Memory Reservation MiB",
3182 + "projection": {
3183 + "kind": "direct",
3184 + "column": "memory_reservation_mib"
3185 + },
3186 + "cell": "number",
3187 + "visibility": "expanded"
3188 + },
3189 + {
3190 + "id": "network_hosts",
3191 + "label": "Hosts",
3192 + "projection": {
3193 + "kind": "direct",
3194 + "column": "network_hosts"
3195 + },
3196 + "cell": "number"
3197 + },
3198 + {
3199 + "id": "network_vms",
3200 + "label": "VMs",
3201 + "projection": {
3202 + "kind": "direct",
3203 + "column": "network_vms"
3204 + },
3205 + "cell": "number"
3206 + },
3207 + {
3208 + "id": "drs_enabled",
3209 + "label": "DRS",
3210 + "projection": {
3211 + "kind": "direct",
3212 + "column": "drs_enabled"
3213 + },
3214 + "cell": "badge"
3215 + },
3216 + {
3217 + "id": "ha_enabled",
3218 + "label": "HA",
3219 + "projection": {
3220 + "kind": "direct",
3221 + "column": "ha_enabled"
3222 + },
3223 + "cell": "badge"
3224 + },
3225 + {
3226 + "id": "vsan_enabled",
3227 + "label": "vSAN",
3228 + "projection": {
3229 + "kind": "direct",
3230 + "column": "vsan_enabled"
3231 + },
3232 + "cell": "badge"
3233 + },
3234 + {
3235 + "id": "storage_drs_enabled",
3236 + "label": "Storage DRS",
3237 + "projection": {
3238 + "kind": "direct",
3239 + "column": "storage_drs_enabled"
3240 + },
3241 + "cell": "badge"
3242 + },
3243 + {
3244 + "id": "multiple_host_access",
3245 + "label": "Multiple Host Access",
3246 + "projection": {
3247 + "kind": "direct",
3248 + "column": "multiple_host_access"
3249 + },
3250 + "cell": "badge"
3251 + },
3252 + {
3253 + "id": "consolidation_needed",
3254 + "label": "Consolidation",
3255 + "projection": {
3256 + "kind": "direct",
3257 + "column": "consolidation_needed"
3258 + },
3259 + "cell": "badge"
3260 + },
3261 + {
3262 + "id": "tools_running_status",
3263 + "label": "Tools Running",
3264 + "projection": {
3265 + "kind": "direct",
3266 + "column": "tools_running_status"
3267 + },
3268 + "cell": "badge",
3269 + "visibility": "expanded"
3270 + },
3271 + {
3272 + "id": "tools_version_status",
3273 + "label": "Tools Version",
3274 + "projection": {
3275 + "kind": "direct",
3276 + "column": "tools_version_status"
3277 + },
3278 + "cell": "badge",
3279 + "visibility": "expanded"
3280 + }
3281 + ],
3282 + "empty_label": "No vSphere details"
3283 + },
3284 + {
3285 + "id": "relationships",
3286 + "label": "Relationships",
3287 + "order": 20,
3288 + "source": {
3289 + "kind": "links"
3290 + },
3291 + "owner_filter": {
3292 + "mode": "incident_link",
3293 + "src_actor_column": "src_actor",
3294 + "dst_actor_column": "dst_actor"
3295 + },
3296 + "columns": [
3297 + {
3298 + "id": "relationship",
3299 + "label": "Relationship",
3300 + "projection": {
3301 + "kind": "direct",
3302 + "column": "relationship"
3303 + },
3304 + "cell": "badge"
3305 + },
3306 + {
3307 + "id": "related_object",
3308 + "label": "Related Object",
3309 + "projection": {
3310 + "kind": "opposite_actor",
3311 + "src_actor_column": "src_actor",
3312 + "dst_actor_column": "dst_actor"
3313 + },
3314 + "cell": "actor_link"
3315 + },
3316 + {
3317 + "id": "evidence_count",
3318 + "label": "Evidence",
3319 + "projection": {
3320 + "kind": "direct",
3321 + "column": "evidence_count"
3322 + },
3323 + "cell": "number"
3324 + }
3325 + ],
3326 + "empty_label": "No vSphere relationships"
3327 + }
3328 + ]
3329 + }
3330 + }
3331 + },
3332 + "vsphere_resource_pool": {
3333 "layer": "virtualization",
71 - "identity": ["vsphere_moid"],
72 - "merge_identity": ["vsphere_moid"],
73 - "parent_identity": ["parent_moid"],
74 - "aggregation_scopes": ["vsphere_object"]
3334 + "identity": [
3335 + "object_type",
3336 + "vsphere_moid"
3337 + ],
3338 + "merge_identity": [
3339 + "object_type",
3340 + "vsphere_moid"
3341 + ],
3342 + "parent_identity": [
3343 + "parent_object_type",
3344 + "parent_moid"
3345 + ],
3346 + "search": {
3347 + "columns": [
3348 + "name",
3349 + "vsphere_moid"
3350 + ],
3351 + "label_keys": [
3352 + "datacenter",
3353 + "cluster",
3354 + "host",
3355 + "resource_pool"
3356 + ]
3357 + },
3358 + "presentation": {
3359 + "label": "Resource Pool",
3360 + "role": "actor",
3361 + "icon": "resource_pool",
3362 + "color_slot": "gray",
3363 + "border": {
3364 + "enabled": true,
3365 + "style": "solid"
3366 + },
3367 + "label_policy": {
3368 + "columns": [
3369 + "name"
3370 + ],
3371 + "fallback": "type_label",
3372 + "max_length": 80,
3373 + "array": "reject"
3374 + },
3375 + "hover": {
3376 + "fields": [
3377 + {
3378 + "key": "name",
3379 + "label": "Name"
3380 + },
3381 + {
3382 + "key": "object_type",
3383 + "label": "Type"
3384 + },
3385 + {
3386 + "key": "vsphere_moid",
3387 + "label": "vSphere MoRef"
3388 + },
3389 + {
3390 + "key": "overall_status",
3391 + "label": "Status"
3392 + },
3393 + {
3394 + "key": "power_state",
3395 + "label": "Power"
3396 + }
3397 + ]
3398 + },
3399 + "modal": {
3400 + "enabled": true,
3401 + "labels": {
3402 + "enabled": true,
3403 + "table": "actor_labels",
3404 + "actor_column": "actor",
3405 + "key_column": "key",
3406 + "value_column": "value",
3407 + "source_column": "source",
3408 + "kind_column": "kind",
3409 + "value_index_column": "value_index",
3410 + "identification": {
3411 + "enabled": true,
3412 + "fields": [
3413 + {
3414 + "key": "object_type",
3415 + "label": "Type",
3416 + "max_values": 1
3417 + },
3418 + {
3419 + "key": "datacenter",
3420 + "label": "Datacenter",
3421 + "max_values": 1
3422 + },
3423 + {
3424 + "key": "cluster",
3425 + "label": "Cluster",
3426 + "max_values": 1
3427 + },
3428 + {
3429 + "key": "host",
3430 + "label": "Host",
3431 + "max_values": 1
3432 + },
3433 + {
3434 + "key": "resource_pool",
3435 + "label": "Resource Pool",
3436 + "max_values": 1
3437 + }
3438 + ]
3439 + }
3440 + },
3441 + "mini_topology": {
3442 + "enabled": true,
3443 + "depth": 1,
3444 + "include_link_types": [
3445 + "vsphere_ownership",
3446 + "vsphere_runs_on",
3447 + "vsphere_connected_to"
3448 + ]
3449 + },
3450 + "sections": [
3451 + {
3452 + "id": "details",
3453 + "label": "Details",
3454 + "order": 10,
3455 + "source": {
3456 + "kind": "actor_table",
3457 + "table": "vsphere_object_detail"
3458 + },
3459 + "owner_filter": {
3460 + "mode": "actor_column",
3461 + "actor_column": "actor"
3462 + },
3463 + "columns": [
3464 + {
3465 + "id": "object_type",
3466 + "label": "Type",
3467 + "projection": {
3468 + "kind": "direct",
3469 + "column": "object_type"
3470 + },
3471 + "cell": "badge"
3472 + },
3473 + {
3474 + "id": "name",
3475 + "label": "Name",
3476 + "projection": {
3477 + "kind": "direct",
3478 + "column": "name"
3479 + },
3480 + "cell": "text"
3481 + },
3482 + {
3483 + "id": "vsphere_moid",
3484 + "label": "vSphere MoRef",
3485 + "projection": {
3486 + "kind": "direct",
3487 + "column": "vsphere_moid"
3488 + },
3489 + "cell": "text"
3490 + },
3491 + {
3492 + "id": "datacenter",
3493 + "label": "Datacenter",
3494 + "projection": {
3495 + "kind": "direct",
3496 + "column": "datacenter"
3497 + },
3498 + "cell": "text"
3499 + },
3500 + {
3501 + "id": "cluster",
3502 + "label": "Cluster",
3503 + "projection": {
3504 + "kind": "direct",
3505 + "column": "cluster"
3506 + },
3507 + "cell": "text"
3508 + },
3509 + {
3510 + "id": "host",
3511 + "label": "Host",
3512 + "projection": {
3513 + "kind": "direct",
3514 + "column": "host"
3515 + },
3516 + "cell": "text"
3517 + },
3518 + {
3519 + "id": "resource_pool",
3520 + "label": "Resource Pool",
3521 + "projection": {
3522 + "kind": "direct",
3523 + "column": "resource_pool"
3524 + },
3525 + "cell": "text"
3526 + },
3527 + {
3528 + "id": "connection_state",
3529 + "label": "Connection",
3530 + "projection": {
3531 + "kind": "direct",
3532 + "column": "connection_state"
3533 + },
3534 + "cell": "badge"
3535 + },
3536 + {
3537 + "id": "power_state",
3538 + "label": "Power",
3539 + "projection": {
3540 + "kind": "direct",
3541 + "column": "power_state"
3542 + },
3543 + "cell": "badge"
3544 + },
3545 + {
3546 + "id": "overall_status",
3547 + "label": "Status",
3548 + "projection": {
3549 + "kind": "direct",
3550 + "column": "overall_status"
3551 + },
3552 + "cell": "badge"
3553 + },
3554 + {
3555 + "id": "accessible",
3556 + "label": "Accessible",
3557 + "projection": {
3558 + "kind": "direct",
3559 + "column": "accessible"
3560 + },
3561 + "cell": "badge"
3562 + },
3563 + {
3564 + "id": "in_maintenance_mode",
3565 + "label": "In Maintenance",
3566 + "projection": {
3567 + "kind": "direct",
3568 + "column": "in_maintenance_mode"
3569 + },
3570 + "cell": "badge"
3571 + },
3572 + {
3573 + "id": "maintenance_mode",
3574 + "label": "Maintenance",
3575 + "projection": {
3576 + "kind": "direct",
3577 + "column": "maintenance_mode"
3578 + },
3579 + "cell": "badge"
3580 + },
3581 + {
3582 + "id": "datastore_type",
3583 + "label": "Datastore Type",
3584 + "projection": {
3585 + "kind": "direct",
3586 + "column": "datastore_type"
3587 + },
3588 + "cell": "badge"
3589 + },
3590 + {
3591 + "id": "network_type",
3592 + "label": "Network Type",
3593 + "projection": {
3594 + "kind": "direct",
3595 + "column": "network_type"
3596 + },
3597 + "cell": "badge"
3598 + },
3599 + {
3600 + "id": "ip_pool_name",
3601 + "label": "IP Pool",
3602 + "projection": {
3603 + "kind": "direct",
3604 + "column": "ip_pool_name"
3605 + },
3606 + "cell": "text",
3607 + "visibility": "expanded"
3608 + },
3609 + {
3610 + "id": "snapshot_count",
3611 + "label": "Snapshots",
3612 + "projection": {
3613 + "kind": "direct",
3614 + "column": "snapshot_count"
3615 + },
3616 + "cell": "number"
3617 + },
3618 + {
3619 + "id": "snapshot_chain_depth",
3620 + "label": "Snapshot Chain",
3621 + "projection": {
3622 + "kind": "direct",
3623 + "column": "snapshot_chain_depth"
3624 + },
3625 + "cell": "number",
3626 + "visibility": "expanded"
3627 + },
3628 + {
3629 + "id": "configured_vcpus",
3630 + "label": "vCPUs",
3631 + "projection": {
3632 + "kind": "direct",
3633 + "column": "configured_vcpus"
3634 + },
3635 + "cell": "number"
3636 + },
3637 + {
3638 + "id": "configured_memory_mib",
3639 + "label": "Memory MiB",
3640 + "projection": {
3641 + "kind": "direct",
3642 + "column": "configured_memory_mib"
3643 + },
3644 + "cell": "number"
3645 + },
3646 + {
3647 + "id": "capacity_bytes",
3648 + "label": "Capacity",
3649 + "projection": {
3650 + "kind": "direct",
3651 + "column": "capacity_bytes"
3652 + },
3653 + "cell": "number"
3654 + },
3655 + {
3656 + "id": "free_space_bytes",
3657 + "label": "Free Space",
3658 + "projection": {
3659 + "kind": "direct",
3660 + "column": "free_space_bytes"
3661 + },
3662 + "cell": "number"
3663 + },
3664 + {
3665 + "id": "uncommitted_bytes",
3666 + "label": "Uncommitted",
3667 + "projection": {
3668 + "kind": "direct",
3669 + "column": "uncommitted_bytes"
3670 + },
3671 + "cell": "number",
3672 + "visibility": "expanded"
3673 + },
3674 + {
3675 + "id": "cpu_capacity_mhz",
3676 + "label": "CPU Capacity MHz",
3677 + "projection": {
3678 + "kind": "direct",
3679 + "column": "cpu_capacity_mhz"
3680 + },
3681 + "cell": "number"
3682 + },
3683 + {
3684 + "id": "memory_capacity_mib",
3685 + "label": "Memory Capacity MiB",
3686 + "projection": {
3687 + "kind": "direct",
3688 + "column": "memory_capacity_mib"
3689 + },
3690 + "cell": "number"
3691 + },
3692 + {
3693 + "id": "cpu_limit_mhz",
3694 + "label": "CPU Limit MHz",
3695 + "projection": {
3696 + "kind": "direct",
3697 + "column": "cpu_limit_mhz"
3698 + },
3699 + "cell": "number"
3700 + },
3701 + {
3702 + "id": "memory_limit_mib",
3703 + "label": "Memory Limit MiB",
3704 + "projection": {
3705 + "kind": "direct",
3706 + "column": "memory_limit_mib"
3707 + },
3708 + "cell": "number"
3709 + },
3710 + {
3711 + "id": "cpu_reservation_mhz",
3712 + "label": "CPU Reservation MHz",
3713 + "projection": {
3714 + "kind": "direct",
3715 + "column": "cpu_reservation_mhz"
3716 + },
3717 + "cell": "number",
3718 + "visibility": "expanded"
3719 + },
3720 + {
3721 + "id": "memory_reservation_mib",
3722 + "label": "Memory Reservation MiB",
3723 + "projection": {
3724 + "kind": "direct",
3725 + "column": "memory_reservation_mib"
3726 + },
3727 + "cell": "number",
3728 + "visibility": "expanded"
3729 + },
3730 + {
3731 + "id": "network_hosts",
3732 + "label": "Hosts",
3733 + "projection": {
3734 + "kind": "direct",
3735 + "column": "network_hosts"
3736 + },
3737 + "cell": "number"
3738 + },
3739 + {
3740 + "id": "network_vms",
3741 + "label": "VMs",
3742 + "projection": {
3743 + "kind": "direct",
3744 + "column": "network_vms"
3745 + },
3746 + "cell": "number"
3747 + },
3748 + {
3749 + "id": "drs_enabled",
3750 + "label": "DRS",
3751 + "projection": {
3752 + "kind": "direct",
3753 + "column": "drs_enabled"
3754 + },
3755 + "cell": "badge"
3756 + },
3757 + {
3758 + "id": "ha_enabled",
3759 + "label": "HA",
3760 + "projection": {
3761 + "kind": "direct",
3762 + "column": "ha_enabled"
3763 + },
3764 + "cell": "badge"
3765 + },
3766 + {
3767 + "id": "vsan_enabled",
3768 + "label": "vSAN",
3769 + "projection": {
3770 + "kind": "direct",
3771 + "column": "vsan_enabled"
3772 + },
3773 + "cell": "badge"
3774 + },
3775 + {
3776 + "id": "storage_drs_enabled",
3777 + "label": "Storage DRS",
3778 + "projection": {
3779 + "kind": "direct",
3780 + "column": "storage_drs_enabled"
3781 + },
3782 + "cell": "badge"
3783 + },
3784 + {
3785 + "id": "multiple_host_access",
3786 + "label": "Multiple Host Access",
3787 + "projection": {
3788 + "kind": "direct",
3789 + "column": "multiple_host_access"
3790 + },
3791 + "cell": "badge"
3792 + },
3793 + {
3794 + "id": "consolidation_needed",
3795 + "label": "Consolidation",
3796 + "projection": {
3797 + "kind": "direct",
3798 + "column": "consolidation_needed"
3799 + },
3800 + "cell": "badge"
3801 + },
3802 + {
3803 + "id": "tools_running_status",
3804 + "label": "Tools Running",
3805 + "projection": {
3806 + "kind": "direct",
3807 + "column": "tools_running_status"
3808 + },
3809 + "cell": "badge",
3810 + "visibility": "expanded"
3811 + },
3812 + {
3813 + "id": "tools_version_status",
3814 + "label": "Tools Version",
3815 + "projection": {
3816 + "kind": "direct",
3817 + "column": "tools_version_status"
3818 + },
3819 + "cell": "badge",
3820 + "visibility": "expanded"
3821 + }
3822 + ],
3823 + "empty_label": "No vSphere details"
3824 + },
3825 + {
3826 + "id": "relationships",
3827 + "label": "Relationships",
3828 + "order": 20,
3829 + "source": {
3830 + "kind": "links"
3831 + },
3832 + "owner_filter": {
3833 + "mode": "incident_link",
3834 + "src_actor_column": "src_actor",
3835 + "dst_actor_column": "dst_actor"
3836 + },
3837 + "columns": [
3838 + {
3839 + "id": "relationship",
3840 + "label": "Relationship",
3841 + "projection": {
3842 + "kind": "direct",
3843 + "column": "relationship"
3844 + },
3845 + "cell": "badge"
3846 + },
3847 + {
3848 + "id": "related_object",
3849 + "label": "Related Object",
3850 + "projection": {
3851 + "kind": "opposite_actor",
3852 + "src_actor_column": "src_actor",
3853 + "dst_actor_column": "dst_actor"
3854 + },
3855 + "cell": "actor_link"
3856 + },
3857 + {
3858 + "id": "evidence_count",
3859 + "label": "Evidence",
3860 + "projection": {
3861 + "kind": "direct",
3862 + "column": "evidence_count"
3863 + },
3864 + "cell": "number"
3865 + }
3866 + ],
3867 + "empty_label": "No vSphere relationships"
3868 + }
3869 + ]
3870 + }
3871 + }
3872 },
76 - "vsphere_datastore": {
77 - "layer": "storage",
78 - "identity": ["vsphere_moid"],
79 - "merge_identity": ["vsphere_moid"],
80 - "aggregation_scopes": ["vsphere_object"]
3873 + "vsphere_vm": {
3874 + "layer": "virtualization",
3875 + "identity": [
3876 + "object_type",
3877 + "vsphere_moid"
3878 + ],
3879 + "merge_identity": [
3880 + "object_type",
3881 + "vsphere_moid"
3882 + ],
3883 + "parent_identity": [
3884 + "parent_object_type",
3885 + "parent_moid"
3886 + ],
3887 + "search": {
3888 + "columns": [
3889 + "name",
3890 + "vsphere_moid"
3891 + ],
3892 + "label_keys": [
3893 + "datacenter",
3894 + "cluster",
3895 + "host",
3896 + "resource_pool"
3897 + ]
3898 + },
3899 + "presentation": {
3900 + "label": "VM",
3901 + "role": "actor",
3902 + "icon": "vm",
3903 + "color_slot": "purple",
3904 + "border": {
3905 + "enabled": true,
3906 + "style": "solid"
3907 + },
3908 + "label_policy": {
3909 + "columns": [
3910 + "name"
3911 + ],
3912 + "fallback": "type_label",
3913 + "max_length": 80,
3914 + "array": "reject"
3915 + },
3916 + "hover": {
3917 + "fields": [
3918 + {
3919 + "key": "name",
3920 + "label": "Name"
3921 + },
3922 + {
3923 + "key": "object_type",
3924 + "label": "Type"
3925 + },
3926 + {
3927 + "key": "vsphere_moid",
3928 + "label": "vSphere MoRef"
3929 + },
3930 + {
3931 + "key": "overall_status",
3932 + "label": "Status"
3933 + },
3934 + {
3935 + "key": "power_state",
3936 + "label": "Power"
3937 + }
3938 + ]
3939 + },
3940 + "modal": {
3941 + "enabled": true,
3942 + "labels": {
3943 + "enabled": true,
3944 + "table": "actor_labels",
3945 + "actor_column": "actor",
3946 + "key_column": "key",
3947 + "value_column": "value",
3948 + "source_column": "source",
3949 + "kind_column": "kind",
3950 + "value_index_column": "value_index",
3951 + "identification": {
3952 + "enabled": true,
3953 + "fields": [
3954 + {
3955 + "key": "object_type",
3956 + "label": "Type",
3957 + "max_values": 1
3958 + },
3959 + {
3960 + "key": "datacenter",
3961 + "label": "Datacenter",
3962 + "max_values": 1
3963 + },
3964 + {
3965 + "key": "cluster",
3966 + "label": "Cluster",
3967 + "max_values": 1
3968 + },
3969 + {
3970 + "key": "host",
3971 + "label": "Host",
3972 + "max_values": 1
3973 + },
3974 + {
3975 + "key": "resource_pool",
3976 + "label": "Resource Pool",
3977 + "max_values": 1
3978 + }
3979 + ]
3980 + }
3981 + },
3982 + "mini_topology": {
3983 + "enabled": true,
3984 + "depth": 1,
3985 + "include_link_types": [
3986 + "vsphere_ownership",
3987 + "vsphere_runs_on",
3988 + "vsphere_connected_to"
3989 + ]
3990 + },
3991 + "sections": [
3992 + {
3993 + "id": "details",
3994 + "label": "Details",
3995 + "order": 10,
3996 + "source": {
3997 + "kind": "actor_table",
3998 + "table": "vsphere_object_detail"
3999 + },
4000 + "owner_filter": {
4001 + "mode": "actor_column",
4002 + "actor_column": "actor"
4003 + },
4004 + "columns": [
4005 + {
4006 + "id": "object_type",
4007 + "label": "Type",
4008 + "projection": {
4009 + "kind": "direct",
4010 + "column": "object_type"
4011 + },
4012 + "cell": "badge"
4013 + },
4014 + {
4015 + "id": "name",
4016 + "label": "Name",
4017 + "projection": {
4018 + "kind": "direct",
4019 + "column": "name"
4020 + },
4021 + "cell": "text"
4022 + },
4023 + {
4024 + "id": "vsphere_moid",
4025 + "label": "vSphere MoRef",
4026 + "projection": {
4027 + "kind": "direct",
4028 + "column": "vsphere_moid"
4029 + },
4030 + "cell": "text"
4031 + },
4032 + {
4033 + "id": "datacenter",
4034 + "label": "Datacenter",
4035 + "projection": {
4036 + "kind": "direct",
4037 + "column": "datacenter"
4038 + },
4039 + "cell": "text"
4040 + },
4041 + {
4042 + "id": "cluster",
4043 + "label": "Cluster",
4044 + "projection": {
4045 + "kind": "direct",
4046 + "column": "cluster"
4047 + },
4048 + "cell": "text"
4049 + },
4050 + {
4051 + "id": "host",
4052 + "label": "Host",
4053 + "projection": {
4054 + "kind": "direct",
4055 + "column": "host"
4056 + },
4057 + "cell": "text"
4058 + },
4059 + {
4060 + "id": "resource_pool",
4061 + "label": "Resource Pool",
4062 + "projection": {
4063 + "kind": "direct",
4064 + "column": "resource_pool"
4065 + },
4066 + "cell": "text"
4067 + },
4068 + {
4069 + "id": "connection_state",
4070 + "label": "Connection",
4071 + "projection": {
4072 + "kind": "direct",
4073 + "column": "connection_state"
4074 + },
4075 + "cell": "badge"
4076 + },
4077 + {
4078 + "id": "power_state",
4079 + "label": "Power",
4080 + "projection": {
4081 + "kind": "direct",
4082 + "column": "power_state"
4083 + },
4084 + "cell": "badge"
4085 + },
4086 + {
4087 + "id": "overall_status",
4088 + "label": "Status",
4089 + "projection": {
4090 + "kind": "direct",
4091 + "column": "overall_status"
4092 + },
4093 + "cell": "badge"
4094 + },
4095 + {
4096 + "id": "accessible",
4097 + "label": "Accessible",
4098 + "projection": {
4099 + "kind": "direct",
4100 + "column": "accessible"
4101 + },
4102 + "cell": "badge"
4103 + },
4104 + {
4105 + "id": "in_maintenance_mode",
4106 + "label": "In Maintenance",
4107 + "projection": {
4108 + "kind": "direct",
4109 + "column": "in_maintenance_mode"
4110 + },
4111 + "cell": "badge"
4112 + },
4113 + {
4114 + "id": "maintenance_mode",
4115 + "label": "Maintenance",
4116 + "projection": {
4117 + "kind": "direct",
4118 + "column": "maintenance_mode"
4119 + },
4120 + "cell": "badge"
4121 + },
4122 + {
4123 + "id": "datastore_type",
4124 + "label": "Datastore Type",
4125 + "projection": {
4126 + "kind": "direct",
4127 + "column": "datastore_type"
4128 + },
4129 + "cell": "badge"
4130 + },
4131 + {
4132 + "id": "network_type",
4133 + "label": "Network Type",
4134 + "projection": {
4135 + "kind": "direct",
4136 + "column": "network_type"
4137 + },
4138 + "cell": "badge"
4139 + },
4140 + {
4141 + "id": "ip_pool_name",
4142 + "label": "IP Pool",
4143 + "projection": {
4144 + "kind": "direct",
4145 + "column": "ip_pool_name"
4146 + },
4147 + "cell": "text",
4148 + "visibility": "expanded"
4149 + },
4150 + {
4151 + "id": "snapshot_count",
4152 + "label": "Snapshots",
4153 + "projection": {
4154 + "kind": "direct",
4155 + "column": "snapshot_count"
4156 + },
4157 + "cell": "number"
4158 + },
4159 + {
4160 + "id": "snapshot_chain_depth",
4161 + "label": "Snapshot Chain",
4162 + "projection": {
4163 + "kind": "direct",
4164 + "column": "snapshot_chain_depth"
4165 + },
4166 + "cell": "number",
4167 + "visibility": "expanded"
4168 + },
4169 + {
4170 + "id": "configured_vcpus",
4171 + "label": "vCPUs",
4172 + "projection": {
4173 + "kind": "direct",
4174 + "column": "configured_vcpus"
4175 + },
4176 + "cell": "number"
4177 + },
4178 + {
4179 + "id": "configured_memory_mib",
4180 + "label": "Memory MiB",
4181 + "projection": {
4182 + "kind": "direct",
4183 + "column": "configured_memory_mib"
4184 + },
4185 + "cell": "number"
4186 + },
4187 + {
4188 + "id": "capacity_bytes",
4189 + "label": "Capacity",
4190 + "projection": {
4191 + "kind": "direct",
4192 + "column": "capacity_bytes"
4193 + },
4194 + "cell": "number"
4195 + },
4196 + {
4197 + "id": "free_space_bytes",
4198 + "label": "Free Space",
4199 + "projection": {
4200 + "kind": "direct",
4201 + "column": "free_space_bytes"
4202 + },
4203 + "cell": "number"
4204 + },
4205 + {
4206 + "id": "uncommitted_bytes",
4207 + "label": "Uncommitted",
4208 + "projection": {
4209 + "kind": "direct",
4210 + "column": "uncommitted_bytes"
4211 + },
4212 + "cell": "number",
4213 + "visibility": "expanded"
4214 + },
4215 + {
4216 + "id": "cpu_capacity_mhz",
4217 + "label": "CPU Capacity MHz",
4218 + "projection": {
4219 + "kind": "direct",
4220 + "column": "cpu_capacity_mhz"
4221 + },
4222 + "cell": "number"
4223 + },
4224 + {
4225 + "id": "memory_capacity_mib",
4226 + "label": "Memory Capacity MiB",
4227 + "projection": {
4228 + "kind": "direct",
4229 + "column": "memory_capacity_mib"
4230 + },
4231 + "cell": "number"
4232 + },
4233 + {
4234 + "id": "cpu_limit_mhz",
4235 + "label": "CPU Limit MHz",
4236 + "projection": {
4237 + "kind": "direct",
4238 + "column": "cpu_limit_mhz"
4239 + },
4240 + "cell": "number"
4241 + },
4242 + {
4243 + "id": "memory_limit_mib",
4244 + "label": "Memory Limit MiB",
4245 + "projection": {
4246 + "kind": "direct",
4247 + "column": "memory_limit_mib"
4248 + },
4249 + "cell": "number"
4250 + },
4251 + {
4252 + "id": "cpu_reservation_mhz",
4253 + "label": "CPU Reservation MHz",
4254 + "projection": {
4255 + "kind": "direct",
4256 + "column": "cpu_reservation_mhz"
4257 + },
4258 + "cell": "number",
4259 + "visibility": "expanded"
4260 + },
4261 + {
4262 + "id": "memory_reservation_mib",
4263 + "label": "Memory Reservation MiB",
4264 + "projection": {
4265 + "kind": "direct",
4266 + "column": "memory_reservation_mib"
4267 + },
4268 + "cell": "number",
4269 + "visibility": "expanded"
4270 + },
4271 + {
4272 + "id": "network_hosts",
4273 + "label": "Hosts",
4274 + "projection": {
4275 + "kind": "direct",
4276 + "column": "network_hosts"
4277 + },
4278 + "cell": "number"
4279 + },
4280 + {
4281 + "id": "network_vms",
4282 + "label": "VMs",
4283 + "projection": {
4284 + "kind": "direct",
4285 + "column": "network_vms"
4286 + },
4287 + "cell": "number"
4288 + },
4289 + {
4290 + "id": "drs_enabled",
4291 + "label": "DRS",
4292 + "projection": {
4293 + "kind": "direct",
4294 + "column": "drs_enabled"
4295 + },
4296 + "cell": "badge"
4297 + },
4298 + {
4299 + "id": "ha_enabled",
4300 + "label": "HA",
4301 + "projection": {
4302 + "kind": "direct",
4303 + "column": "ha_enabled"
4304 + },
4305 + "cell": "badge"
4306 + },
4307 + {
4308 + "id": "vsan_enabled",
4309 + "label": "vSAN",
4310 + "projection": {
4311 + "kind": "direct",
4312 + "column": "vsan_enabled"
4313 + },
4314 + "cell": "badge"
4315 + },
4316 + {
4317 + "id": "storage_drs_enabled",
4318 + "label": "Storage DRS",
4319 + "projection": {
4320 + "kind": "direct",
4321 + "column": "storage_drs_enabled"
4322 + },
4323 + "cell": "badge"
4324 + },
4325 + {
4326 + "id": "multiple_host_access",
4327 + "label": "Multiple Host Access",
4328 + "projection": {
4329 + "kind": "direct",
4330 + "column": "multiple_host_access"
4331 + },
4332 + "cell": "badge"
4333 + },
4334 + {
4335 + "id": "consolidation_needed",
4336 + "label": "Consolidation",
4337 + "projection": {
4338 + "kind": "direct",
4339 + "column": "consolidation_needed"
4340 + },
4341 + "cell": "badge"
4342 + },
4343 + {
4344 + "id": "tools_running_status",
4345 + "label": "Tools Running",
4346 + "projection": {
4347 + "kind": "direct",
4348 + "column": "tools_running_status"
4349 + },
4350 + "cell": "badge",
4351 + "visibility": "expanded"
4352 + },
4353 + {
4354 + "id": "tools_version_status",
4355 + "label": "Tools Version",
4356 + "projection": {
4357 + "kind": "direct",
4358 + "column": "tools_version_status"
4359 + },
4360 + "cell": "badge",
4361 + "visibility": "expanded"
4362 + }
4363 + ],
4364 + "empty_label": "No vSphere details"
4365 + },
4366 + {
4367 + "id": "relationships",
4368 + "label": "Relationships",
4369 + "order": 20,
4370 + "source": {
4371 + "kind": "links"
4372 + },
4373 + "owner_filter": {
4374 + "mode": "incident_link",
4375 + "src_actor_column": "src_actor",
4376 + "dst_actor_column": "dst_actor"
4377 + },
4378 + "columns": [
4379 + {
4380 + "id": "relationship",
4381 + "label": "Relationship",
4382 + "projection": {
4383 + "kind": "direct",
4384 + "column": "relationship"
4385 + },
4386 + "cell": "badge"
4387 + },
4388 + {
4389 + "id": "related_object",
4390 + "label": "Related Object",
4391 + "projection": {
4392 + "kind": "opposite_actor",
4393 + "src_actor_column": "src_actor",
4394 + "dst_actor_column": "dst_actor"
4395 + },
4396 + "cell": "actor_link"
4397 + },
4398 + {
4399 + "id": "evidence_count",
4400 + "label": "Evidence",
4401 + "projection": {
4402 + "kind": "direct",
4403 + "column": "evidence_count"
4404 + },
4405 + "cell": "number"
4406 + }
4407 + ],
4408 + "empty_label": "No vSphere relationships"
4409 + }
4410 + ]
4411 + }
4412 + }
4413 }
4414 },
4415 "link_types": {
4416 + "vsphere_connected_to": {
4417 + "orientation": "undirected",
4418 + "direction_role": "observation",
4419 + "semantic_role": "normal",
4420 + "aggregation": {
4421 + "direction": "canonicalize_unordered",
4422 + "evidence": "append",
4423 + "metrics": {
4424 + "evidence_count": "sum"
4425 + }
4426 + },
4427 + "evidence_types": [
4428 + "vsphere_network_connection_evidence"
4429 + ],
4430 + "presentation": {
4431 + "label": "Connected to",
4432 + "color_slot": "green",
4433 + "line_style": "solid",
4434 + "width": "normal",
4435 + "curve": "auto",
4436 + "arrow": "none",
4437 + "hover": {
4438 + "fields": [
4439 + {
4440 + "key": "relationship",
4441 + "label": "Relationship"
4442 + },
4443 + {
4444 + "key": "evidence_count",
4445 + "label": "Evidence"
4446 + }
4447 + ]
4448 + },
4449 + "layout": {
4450 + "strength": "normal",
4451 + "distance": "farther"
4452 + }
4453 + }
4454 + },
4455 "vsphere_ownership": {
4456 "orientation": "hierarchical",
4457 "direction_role": "ownership",
4458 + "semantic_role": "ownership",
4459 "aggregation": {
4460 "direction": "preserve",
89 - "evidence": "append"
4461 + "evidence": "append",
4462 + "metrics": {
4463 + "evidence_count": "sum"
4464 + }
4465 },
91 - "evidence_types": ["vsphere_relationship"]
4466 + "evidence_types": [
4467 + "vsphere_ownership_evidence"
4468 + ],
4469 + "presentation": {
4470 + "label": "Contains",
4471 + "color_slot": "gray",
4472 + "line_style": "solid",
4473 + "width": "normal",
4474 + "curve": "auto",
4475 + "arrow": "forward",
4476 + "hover": {
4477 + "fields": [
4478 + {
4479 + "key": "relationship",
4480 + "label": "Relationship"
4481 + },
4482 + {
4483 + "key": "evidence_count",
4484 + "label": "Evidence"
4485 + }
4486 + ]
4487 + },
4488 + "layout": {
4489 + "strength": "normal",
4490 + "distance": "closer"
4491 + }
4492 + }
4493 },
93 - "vsphere_dependency": {
4494 + "vsphere_runs_on": {
4495 "orientation": "directed",
4496 "direction_role": "dependency",
4497 + "semantic_role": "normal",
4498 "aggregation": {
4499 "direction": "preserve",
98 - "evidence": "append"
4500 + "evidence": "append",
4501 + "metrics": {
4502 + "evidence_count": "sum"
4503 + }
4504 },
100 - "evidence_types": ["vsphere_relationship"]
4505 + "evidence_types": [
4506 + "vsphere_runs_on_evidence"
4507 + ],
4508 + "presentation": {
4509 + "label": "Runs on",
4510 + "color_slot": "blue",
4511 + "line_style": "solid",
4512 + "width": "normal",
4513 + "curve": "auto",
4514 + "arrow": "forward",
4515 + "hover": {
4516 + "fields": [
4517 + {
4518 + "key": "relationship",
4519 + "label": "Relationship"
4520 + },
4521 + {
4522 + "key": "evidence_count",
4523 + "label": "Evidence"
4524 + }
4525 + ]
4526 + },
4527 + "layout": {
4528 + "strength": "normal",
4529 + "distance": "normal"
4530 + }
4531 + }
4532 }
4533 },
4534 "evidence_types": {
104 - "vsphere_relationship": {
4535 + "vsphere_network_connection_evidence": {
4536 + "link_type": "vsphere_connected_to",
4537 + "role": "relationship_evidence",
4538 + "columns": [
4539 + {
4540 + "id": "link",
4541 + "type": "link_ref",
4542 + "role": "reference"
4543 + },
4544 + {
4545 + "id": "source_type",
4546 + "type": "string_ref",
4547 + "dictionary": "strings",
4548 + "role": "group_key"
4549 + },
4550 + {
4551 + "id": "source_moid",
4552 + "type": "string_ref",
4553 + "dictionary": "strings",
4554 + "role": "group_key"
4555 + },
4556 + {
4557 + "id": "target_type",
4558 + "type": "string_ref",
4559 + "dictionary": "strings",
4560 + "role": "group_key"
4561 + },
4562 + {
4563 + "id": "target_moid",
4564 + "type": "string_ref",
4565 + "dictionary": "strings",
4566 + "role": "group_key"
4567 + },
4568 + {
4569 + "id": "relationship",
4570 + "type": "string_ref",
4571 + "dictionary": "strings",
4572 + "role": "group_key"
4573 + }
4574 + ],
4575 + "match_columns": [
4576 + "source_type",
4577 + "source_moid",
4578 + "target_type",
4579 + "target_moid",
4580 + "relationship"
4581 + ]
4582 + },
4583 + "vsphere_ownership_evidence": {
4584 "link_type": "vsphere_ownership",
4585 "role": "relationship_evidence",
107 - "match_columns": ["source_moid", "target_moid", "relationship"],
4586 "columns": [
109 - {"id": "link", "type": "link_ref", "role": "reference"},
110 - {"id": "source_moid", "type": "string_ref", "dictionary": "strings", "role": "group_key"},
111 - {"id": "target_moid", "type": "string_ref", "dictionary": "strings", "role": "group_key"},
112 - {"id": "relationship", "type": "string_ref", "dictionary": "strings", "role": "group_key"}
4587 + {
4588 + "id": "link",
4589 + "type": "link_ref",
4590 + "role": "reference"
4591 + },
4592 + {
4593 + "id": "source_type",
4594 + "type": "string_ref",
4595 + "dictionary": "strings",
4596 + "role": "group_key"
4597 + },
4598 + {
4599 + "id": "source_moid",
4600 + "type": "string_ref",
4601 + "dictionary": "strings",
4602 + "role": "group_key"
4603 + },
4604 + {
4605 + "id": "target_type",
4606 + "type": "string_ref",
4607 + "dictionary": "strings",
4608 + "role": "group_key"
4609 + },
4610 + {
4611 + "id": "target_moid",
4612 + "type": "string_ref",
4613 + "dictionary": "strings",
4614 + "role": "group_key"
4615 + },
4616 + {
4617 + "id": "relationship",
4618 + "type": "string_ref",
4619 + "dictionary": "strings",
4620 + "role": "group_key"
4621 + }
4622 + ],
4623 + "match_columns": [
4624 + "source_type",
4625 + "source_moid",
4626 + "target_type",
4627 + "target_moid",
4628 + "relationship"
4629 + ]
4630 + },
4631 + "vsphere_runs_on_evidence": {
4632 + "link_type": "vsphere_runs_on",
4633 + "role": "relationship_evidence",
4634 + "columns": [
4635 + {
4636 + "id": "link",
4637 + "type": "link_ref",
4638 + "role": "reference"
4639 + },
4640 + {
4641 + "id": "source_type",
4642 + "type": "string_ref",
4643 + "dictionary": "strings",
4644 + "role": "group_key"
4645 + },
4646 + {
4647 + "id": "source_moid",
4648 + "type": "string_ref",
4649 + "dictionary": "strings",
4650 + "role": "group_key"
4651 + },
4652 + {
4653 + "id": "target_type",
4654 + "type": "string_ref",
4655 + "dictionary": "strings",
4656 + "role": "group_key"
4657 + },
4658 + {
4659 + "id": "target_moid",
4660 + "type": "string_ref",
4661 + "dictionary": "strings",
4662 + "role": "group_key"
4663 + },
4664 + {
4665 + "id": "relationship",
4666 + "type": "string_ref",
4667 + "dictionary": "strings",
4668 + "role": "group_key"
4669 + }
4670 + ],
4671 + "match_columns": [
4672 + "source_type",
4673 + "source_moid",
4674 + "target_type",
4675 + "target_moid",
4676 + "relationship"
4677 ]
4678 }
4679 },
4680 "table_types": {
4681 + "actor_labels": {
4682 + "role": "actor_inventory",
4683 + "owner": "actor",
4684 + "aggregation": "set",
4685 + "columns": [
4686 + {
4687 + "id": "actor",
4688 + "type": "actor_ref",
4689 + "role": "reference"
4690 + },
4691 + {
4692 + "id": "key",
4693 + "type": "string_ref",
4694 + "dictionary": "strings",
4695 + "role": "attribute"
4696 + },
4697 + {
4698 + "id": "value",
4699 + "type": "string_ref",
4700 + "dictionary": "strings",
4701 + "role": "attribute"
4702 + },
4703 + {
4704 + "id": "source",
4705 + "type": "string_ref",
4706 + "dictionary": "strings",
4707 + "nullable": true,
4708 + "role": "attribute"
4709 + },
4710 + {
4711 + "id": "kind",
4712 + "type": "string_ref",
4713 + "dictionary": "strings",
4714 + "nullable": true,
4715 + "role": "attribute"
4716 + },
4717 + {
4718 + "id": "value_index",
4719 + "type": "uint",
4720 + "nullable": true,
4721 + "role": "attribute"
4722 + }
4723 + ],
4724 + "presentation": {
4725 + "label": "Labels",
4726 + "columns": [
4727 + {
4728 + "id": "key",
4729 + "label": "Label",
4730 + "projection": {
4731 + "kind": "direct",
4732 + "column": "key"
4733 + },
4734 + "cell": "text"
4735 + },
4736 + {
4737 + "id": "value",
4738 + "label": "Value",
4739 + "projection": {
4740 + "kind": "direct",
4741 + "column": "value"
4742 + },
4743 + "cell": "text"
4744 + },
4745 + {
4746 + "id": "source",
4747 + "label": "Source",
4748 + "projection": {
4749 + "kind": "direct",
4750 + "column": "source"
4751 + },
4752 + "cell": "badge"
4753 + },
4754 + {
4755 + "id": "kind",
4756 + "label": "Kind",
4757 + "projection": {
4758 + "kind": "direct",
4759 + "column": "kind"
4760 + },
4761 + "cell": "badge"
4762 + }
4763 + ]
4764 + }
4765 + },
4766 "vsphere_object_detail": {
4767 "role": "actor_detail",
4768 "owner": "actor",
4769 "aggregation": "last",
4770 "columns": [
122 - {"id": "actor", "type": "actor_ref", "role": "reference"},
123 - {"id": "power_state", "type": "string_ref", "dictionary": "strings"},
124 - {"id": "inventory_path", "type": "string_ref", "dictionary": "strings"}
125 - ]
4771 + {
4772 + "id": "actor",
4773 + "type": "actor_ref",
4774 + "role": "reference"
4775 + },
4776 + {
4777 + "id": "object_type",
4778 + "type": "string_ref",
4779 + "dictionary": "strings",
4780 + "role": "attribute"
4781 + },
4782 + {
4783 + "id": "vsphere_moid",
4784 + "type": "string_ref",
4785 + "dictionary": "strings",
4786 + "role": "identity"
4787 + },
4788 + {
4789 + "id": "name",
4790 + "type": "string_ref",
4791 + "dictionary": "strings",
4792 + "role": "attribute"
4793 + },
4794 + {
4795 + "id": "parent_object_type",
4796 + "type": "string_ref",
4797 + "dictionary": "strings",
4798 + "nullable": true,
4799 + "role": "parent_identity"
4800 + },
4801 + {
4802 + "id": "parent_moid",
4803 + "type": "string_ref",
4804 + "dictionary": "strings",
4805 + "nullable": true,
4806 + "role": "parent_identity"
4807 + },
4808 + {
4809 + "id": "datacenter",
4810 + "type": "string_ref",
4811 + "dictionary": "strings",
4812 + "nullable": true,
4813 + "role": "attribute"
4814 + },
4815 + {
4816 + "id": "cluster",
4817 + "type": "string_ref",
4818 + "dictionary": "strings",
4819 + "nullable": true,
4820 + "role": "attribute"
4821 + },
4822 + {
4823 + "id": "host",
4824 + "type": "string_ref",
4825 + "dictionary": "strings",
4826 + "nullable": true,
4827 + "role": "attribute"
4828 + },
4829 + {
4830 + "id": "resource_pool",
4831 + "type": "string_ref",
4832 + "dictionary": "strings",
4833 + "nullable": true,
4834 + "role": "attribute"
4835 + },
4836 + {
4837 + "id": "connection_state",
4838 + "type": "string_ref",
4839 + "dictionary": "strings",
4840 + "nullable": true,
4841 + "role": "attribute"
4842 + },
4843 + {
4844 + "id": "power_state",
4845 + "type": "string_ref",
4846 + "dictionary": "strings",
4847 + "nullable": true,
4848 + "role": "attribute"
4849 + },
4850 + {
4851 + "id": "overall_status",
4852 + "type": "string_ref",
4853 + "dictionary": "strings",
4854 + "nullable": true,
4855 + "role": "attribute"
4856 + },
4857 + {
4858 + "id": "accessible",
4859 + "type": "bool",
4860 + "nullable": true,
4861 + "role": "attribute"
4862 + },
4863 + {
4864 + "id": "in_maintenance_mode",
4865 + "type": "bool",
4866 + "nullable": true,
4867 + "role": "attribute"
4868 + },
4869 + {
4870 + "id": "maintenance_mode",
4871 + "type": "string_ref",
4872 + "dictionary": "strings",
4873 + "nullable": true,
4874 + "role": "attribute"
4875 + },
4876 + {
4877 + "id": "datastore_type",
4878 + "type": "string_ref",
4879 + "dictionary": "strings",
4880 + "nullable": true,
4881 + "role": "attribute"
4882 + },
4883 + {
4884 + "id": "network_type",
4885 + "type": "string_ref",
4886 + "dictionary": "strings",
4887 + "nullable": true,
4888 + "role": "attribute"
4889 + },
4890 + {
4891 + "id": "ip_pool_name",
4892 + "type": "string_ref",
4893 + "dictionary": "strings",
4894 + "nullable": true,
4895 + "role": "attribute"
4896 + },
4897 + {
4898 + "id": "storage_drs_enabled",
4899 + "type": "bool",
4900 + "nullable": true,
4901 + "role": "attribute"
4902 + },
4903 + {
4904 + "id": "multiple_host_access",
4905 + "type": "bool",
4906 + "nullable": true,
4907 + "role": "attribute"
4908 + },
4909 + {
4910 + "id": "snapshot_count",
4911 + "type": "uint",
4912 + "nullable": true,
4913 + "role": "metric",
4914 + "aggregation": "sum"
4915 + },
4916 + {
4917 + "id": "snapshot_chain_depth",
4918 + "type": "uint",
4919 + "nullable": true,
4920 + "role": "metric",
4921 + "aggregation": "max"
4922 + },
4923 + {
4924 + "id": "configured_vcpus",
4925 + "type": "uint",
4926 + "nullable": true,
4927 + "role": "metric",
4928 + "aggregation": "sum"
4929 + },
4930 + {
4931 + "id": "configured_memory_mib",
4932 + "type": "uint",
4933 + "nullable": true,
4934 + "unit": "MiB",
4935 + "role": "metric",
4936 + "aggregation": "sum"
4937 + },
4938 + {
4939 + "id": "capacity_bytes",
4940 + "type": "uint",

This file is too large to show in full.

src/go/tools/functions-validation/validate/main_test.go
+1 -1
@@ -19,7 +19,7 @@ func TestTopologyV1FixturesValidate(t *testing.T) {
19 t.Fatalf("glob fixtures: %v", err)
20 }
21 if len(fixtures) != 4 {
22 - t.Fatalf("expected 4 topology fixtures, got %d: %v", len(fixtures), fixtures)
22 + t.Fatalf("expected 4 topology fixtures, got %d", len(fixtures))
23 }
24
25 for _, fixture := range fixtures {
src/go/tools/topology-parity-evidence/main.go
+10 -10
@@ -960,7 +960,7 @@ func runPhase2(opts options) error {
960 {
961 name: "lldp",
962 selections: []testSelection{{
963 - packagePath: "./pkg/topology/engine",
963 + packagePath: "./pkg/l2topology",
964 tests: []string{
965 "TestMatchLLDPLinksEnlinkdPassOrder_Precedence",
966 "TestMatchLLDPLinksEnlinkdPassOrder_FallbackPasses/port-description",
@@ -974,7 +974,7 @@ func runPhase2(opts options) error {
974 {
975 name: "cdp",
976 selections: []testSelection{{
977 - packagePath: "./pkg/topology/engine",
977 + packagePath: "./pkg/l2topology",
978 tests: []string{
979 "TestMatchCDPLinksEnlinkdPassOrder_DefaultAndParsedTarget",
980 "TestMatchCDPLinksEnlinkdPassOrder_SkipsSelfTarget",
@@ -984,7 +984,7 @@ func runPhase2(opts options) error {
984 {
985 name: "bridge_fdb_arp",
986 selections: []testSelection{{
987 - packagePath: "./pkg/topology/engine",
987 + packagePath: "./pkg/l2topology",
988 tests: []string{
989 "TestBuildL2ResultFromObservations_FDBAttachments",
990 "TestBuildL2ResultFromObservations_FDBDropsDuplicateMACAcrossPorts",
@@ -997,15 +997,15 @@ func runPhase2(opts options) error {
997 name: "updater",
998 selections: []testSelection{
999 {
1000 - packagePath: "./pkg/topology/engine",
1000 + packagePath: "./pkg/l2topology",
1001 tests: []string{
1002 "TestBuildL2ResultFromObservations_AnnotatesPairMetadata",
1003 },
1004 },
1005 {
1006 - packagePath: "./pkg/topology/engine",
1006 + packagePath: "./pkg/l2topology",
1007 tests: []string{
1008 - "TestToTopologyData_MergesPairedAdjacenciesIntoBidirectionalLink",
1008 + "TestToGraph_MergesPairedAdjacenciesIntoBidirectionalLink",
1009 },
1010 },
1011 },
@@ -1541,12 +1541,12 @@ func runRequiredGoTests() []goTestSummary {
1541
1542 commands := []testCmd{
1543 {
1544 - packageLabel: "./pkg/topology/engine/parity",
1545 - args: []string{"test", "./pkg/topology/engine/parity"},
1544 + packageLabel: "./pkg/l2topology/parity",
1545 + args: []string{"test", "./pkg/l2topology/parity"},
1546 },
1547 {
1548 - packageLabel: "./pkg/topology/engine",
1549 - args: []string{"test", "./pkg/topology/engine"},
1548 + packageLabel: "./pkg/l2topology",
1549 + args: []string{"test", "./pkg/l2topology"},
1550 },
1551 {
1552 packageLabel: "./tools/topology-parity-evidence",
src/plugins.d/FUNCTION_TOPOLOGY_IMPLEMENTATION_SCOPE.md
+35 -31
@@ -63,7 +63,7 @@ Required helpers:
63
64 Likely homes:
65
66 -- Go: `src/go/pkg/topology/` or `src/go/pkg/funcapi/`
66 +- Go: `src/go/pkg/topology/v1` or `src/go/pkg/funcapi/`
67 - C: small helper module for network-viewer, or a local builder until a shared
68 C helper is justified
69 - Rust: SDK helper if a Rust topology producer is added
@@ -121,23 +121,17 @@ Likely homes:
121 `topology:snmp`:
122
123 - producer paths: `src/go/plugin/go.d/collector/snmp_topology/` and
124 - `src/go/pkg/topology/`;
125 -- the Function handler now adapts the current SNMP topology snapshot to
124 + `src/go/pkg/l2topology/`;
125 +- the L2 engine builds an internal, non-payload `l2topology.Graph` projection
126 + from `l2topology.Result`;
127 +- the Function handler adapts the current SNMP topology snapshot to
128 `netdata.topology.v1` through
127 - `src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go`, while the
128 - deeper engine still builds the older shared Go topology model;
129 -- the older shared Go topology data model stores top-level actor/link arrays at
130 - `src/go/pkg/topology/types.go:167`;
131 -- older actor tables are nested under actor rows at
132 - `src/go/pkg/topology/types.go:24`;
133 -- the older link model carries `Direction`, `Src`, `Dst`, and metrics at
134 - `src/go/pkg/topology/types.go:42`;
135 -- the older presentation table metadata still uses `Source` only at
136 - `src/go/pkg/topology/types.go:72`, with SNMP ports/links examples at
137 - `src/go/plugin/go.d/collector/snmp_topology/func_topology_presentation_schema.go:29`;
129 + `src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go`;
130 +- the old method-level Go presentation adapter has been retired; presentation
131 + metadata is emitted in the v1 payload type registry and `data.presentation`;
132 - current L2 emission uses directions such as `bidirectional` and
139 - `unidirectional` at `src/go/pkg/topology/engine/topology_adapter_segments_builder_emit.go:60`
140 - and `src/go/pkg/topology/engine/topology_adapter_projection_pairs.go:230`;
133 + `unidirectional` at `src/go/pkg/l2topology/topology_adapter_segments_builder_emit.go:60`
134 + and `src/go/pkg/l2topology/topology_adapter_projection_pairs.go:230`;
135 - migration target: use `observed_bidirectional` or unordered aggregation policy
136 where discovery direction is noise, preserve LLDP/CDP/FDB/ARP/STP evidence,
137 keep interface inventory as actor detail/inventory, and move metric query
@@ -145,12 +139,12 @@ Likely homes:
139
140 vSphere:
141
148 -- producer path is in a separate PR worktree and must be updated in place only
149 - after telling the user;
150 -- migration target: use stable managed object ids for actor identity, model
151 - containment as hierarchical/ownership links, model VM-host/network/datastore
152 - relationships as graph links plus evidence where needed, and expose
153 - utilization/state through overlay templates/refs.
142 +- producer path: `src/go/plugin/go.d/collector/vsphere/`;
143 +- the Function emits `netdata.topology.v1` directly from the Go collector;
144 +- actor identity uses the vSphere managed-object type plus managed-object id;
145 +- inventory containment is modeled as hierarchical ownership links;
146 +- VM-to-host and host/VM-to-network relationships are graph links with typed
147 + evidence.
148
149 ### Cloud Frontend
150
@@ -266,7 +260,7 @@ Current state:
260 Producer paths:
261
262 - `src/go/plugin/go.d/collector/snmp_topology/`
269 -- `src/go/pkg/topology/engine/`
263 +- `src/go/pkg/l2topology/`
264
265 Required behavior:
266
@@ -290,20 +284,23 @@ Current state:
284
285 - initial Function payload migration is implemented through a v1 adapter in
286 `src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go`;
287 +- L2 graph synthesis is internal to `src/go/pkg/l2topology` and uses
288 + `l2topology.Graph`, not the legacy Go topology payload package;
289 - the adapter emits compact actor, link, evidence, actor metadata, and
290 actor-detail tables and preserves nested custom actor cells with `json`
291 columns where needed;
292 - modal-composition producer work now emits `actor_labels`, promoted
293 scalar/count actor fields, stable `actor_ports` rows, structured endpoint
298 - evidence, and modal recipes. Remaining SNMP work is to migrate metric lookup
299 - fragments into first-class overlay templates/refs instead of only preserving
300 - them in actor/detail data, plus integrated UI/aggregator QA.
294 + evidence, modal recipes, and payload-level presentation metadata. Remaining
295 + SNMP work is to migrate metric lookup fragments into first-class overlay
296 + templates/refs instead of only preserving them in actor/detail data, plus
297 + integrated UI/aggregator QA.
298
299 ### vSphere Topology
300
304 -Producer worktree:
301 +Producer path:
302
306 -- the separate vSphere topology worktree used for that PR
303 +- `src/go/plugin/go.d/collector/vsphere/`
304
305 Required behavior:
306
@@ -314,10 +311,17 @@ Required behavior:
311 as graph links plus typed evidence where needed;
312 - use overlay templates for refreshable utilization/state metrics.
313
317 -Coordination constraint:
314 +Current state:
315
319 -- do not edit the vSphere worktree before telling the user, because another
320 - agent is working in that directory.
316 +- vSphere emits `netdata.topology.v1` directly from
317 + `src/go/plugin/go.d/collector/vsphere/func_topology.go`;
318 +- the producer builds compact actor, link, evidence, actor-detail, and
319 + `actor_labels` tables with `src/go/pkg/topology/v1`;
320 +- actor identity uses vSphere managed-object type plus managed-object id;
321 +- containment, VM-to-host, and network relationships have explicit link types,
322 + direction roles, and evidence types;
323 +- the old method-level Go presentation adapter is retired. Presentation
324 + metadata lives in the v1 type registry and `data.presentation`.
325
326 ## Cloud Frontend Scope
327