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