diff --git a/docs/netdata-agent/configuration/secure-your-netdata-agent-with-bearer-token.md b/docs/netdata-agent/configuration/secure-your-netdata-agent-with-bearer-token.md index 0e1f2605baa70a..e60a34e6e04959 100644 --- a/docs/netdata-agent/configuration/secure-your-netdata-agent-with-bearer-token.md +++ b/docs/netdata-agent/configuration/secure-your-netdata-agent-with-bearer-token.md @@ -60,6 +60,8 @@ When enabled, bearer token protection secures **all data APIs**, including: - Functions (`/api/v3/function`, `/api/v3/functions`) - Dynamic configuration (`/api/v3/config`) +It also changes local MCP behavior: MCP requests over HTTP, SSE, and WebSocket require the local MCP API key when bearer token protection is enabled. + ## What Remains Public **Static web files** (HTML, CSS, JavaScript) in Netdata's web directory are **not protected**. This means: @@ -86,6 +88,15 @@ These APIs are required for the authentication flow and dashboard initialization **Note:** Other v1 and v2 APIs (like `/api/v2/info`, `/api/v3/versions`, `/api/v3/progress`) **are protected** by bearer token - only the specific endpoints listed above bypass protection. +## MCP Behavior with Bearer Protection + +For local Agent/Parent MCP endpoints (`/mcp`, `/sse`, MCP WebSocket): + +- If `[web].bearer token protection = yes`: anonymous MCP requests are rejected, and clients must pass the local MCP API key (`Authorization: Bearer `). +- If `[web].bearer token protection = no`: anonymous MCP requests continue to work with the default open-source access model (the MCP API key is still supported and unlocks sensitive operations). + +MCP network exposure is still controlled independently by `[web].allow mcp from`. + ## Requirements - Agent must be claimed to Netdata Cloud diff --git a/docs/netdata-agent/securing-netdata-agents.md b/docs/netdata-agent/securing-netdata-agents.md index 355ad8fd4bbfc3..3790157c804516 100644 --- a/docs/netdata-agent/securing-netdata-agents.md +++ b/docs/netdata-agent/securing-netdata-agents.md @@ -146,12 +146,15 @@ While `allow connections from` globally controls access to all Netdata services, [web] allow connections from = localhost * allow dashboard from = localhost * + allow mcp from = localhost * allow badges from = * allow streaming from = * allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* allow management from = localhost ``` +When `[web].bearer token protection = yes` is enabled, MCP requests also require the local MCP API key (`Authorization: Bearer `). Without the key, anonymous MCP requests are rejected. + **Additional Security Options:** - Review detailed access list options in the [Web Server documentation](/src/web/server/README.md#access-lists) diff --git a/docs/netdata-ai/mcp/README.md b/docs/netdata-ai/mcp/README.md index 29ffd536a76acd..1e8cc79158793f 100644 --- a/docs/netdata-ai/mcp/README.md +++ b/docs/netdata-ai/mcp/README.md @@ -34,7 +34,7 @@ AI assistants have different visibility depending on where they connect: | **Scope** | All nodes | Single agent/parent | | **Endpoint** | `app.netdata.cloud/api/v1/mcp` | `YOUR_IP:19999/mcp` | | **Transport** | Streamable HTTP | HTTP, SSE, WebSocket | -| **Authentication** | Cloud API token (`scope:mcp`) | Local MCP API key | +| **Authentication** | Cloud API token (`scope:mcp`) | Local MCP API key (mandatory when bearer protection is enabled) | | **Network access** | Internet only | Direct access to Netdata IP required | | **Local setup** | None | Bridge may be needed for some clients | @@ -177,7 +177,10 @@ Replace `YOUR_NETDATA_CLOUD_API_TOKEN` with your Connect directly to any Netdata Agent or Parent on your network. All Netdata Agents and Parents (v2.6.0+) include a built-in MCP server at `http://YOUR_IP:19999/mcp`. -Some MCP features — such as live process information, network connections, and full log access — are considered sensitive. These are protected by a local API key that Netdata generates automatically on startup. Without this key, AI assistants can still access metrics, alerts, and node information, but sensitive functions remain locked. +Netdata generates a local MCP API key automatically on startup. Authentication behavior depends on `[web].bearer token protection`: + +- **Bearer protection disabled (`no`)**: anonymous MCP access works for non-sensitive operations (metrics, alerts, node info). The API key unlocks sensitive operations. +- **Bearer protection enabled (`yes`)**: anonymous MCP access is rejected on all MCP transports (HTTP, SSE, WebSocket). The API key is required for all MCP requests. ### Transport Options @@ -191,6 +194,25 @@ Netdata implements the MCP protocol with multiple transport options: - **Direct Connection** (v2.7.2+): AI clients that support HTTP or SSE transports can connect directly to Netdata - **Bridge Required**: AI clients that only support stdio need the `nd-mcp` (stdio-to-websocket) or `mcp-remote` (stdio-to-http or stdio-to-sse) bridge +- **Preferred authentication**: pass the MCP API key via `Authorization: Bearer `. +- **Backward compatibility**: WebSocket also accepts `?api_key=`. Keep this only for legacy clients, since URL query strings are more likely to be logged by proxies and tools. + +### MCP Access Control in `netdata.conf` + +Use a dedicated MCP ACL to control network-level MCP access independently from dashboard and streaming: + +```ini +[web] + allow connections from = 10.* 192.168.* localhost + allow dashboard from = 10.* 192.168.* localhost + allow mcp from = 10.* 192.168.* localhost + allow streaming from = * + allow mcp by dns = heuristic +``` + +- `allow mcp from` controls `/mcp`, `/sse`, and MCP WebSocket protocol access. +- `allow connections from` is still the global first gate for all features. +- For internet-facing Parents, keep `allow mcp from` restricted to trusted networks. #### Official MCP Remote Client (mcp-remote) @@ -485,7 +507,7 @@ For more details, see the [official mcp-remote documentation](https://github.com ### Finding Your API Key -To access sensitive functions like logs and live system information, you need an API key. Netdata automatically generates an API key on startup. The key is stored in a file on the Netdata server you want to connect to. +You need the MCP API key when bearer protection is enabled. When bearer protection is disabled, the key is still needed for sensitive functions like logs and live system information. Netdata automatically generates an API key on startup. The key is stored in a file on the Netdata server you want to connect to. You need the API key of the Netdata you will connect to (usually a Netdata Parent). diff --git a/docs/netdata-ai/mcp/mcp-clients/claude-code.md b/docs/netdata-ai/mcp/mcp-clients/claude-code.md index 9f1e298fe97b5f..c63c5230ff8af6 100644 --- a/docs/netdata-ai/mcp/mcp-clients/claude-code.md +++ b/docs/netdata-ai/mcp/mcp-clients/claude-code.md @@ -20,7 +20,7 @@ Claude Code supports multiple MCP transport types, giving you flexibility in how - **v2.6.0 - v2.7.1**: Only WebSocket transport available, requires `nd-mcp` bridge - **v2.7.2+**: Direct HTTP/SSE support available (recommended) 3. **For WebSocket or stdio connections: `nd-mcp` bridge** - The stdio-to-websocket bridge. [Find its absolute path](/docs/netdata-ai/mcp/README.md#finding-the-nd-mcp-bridge). Not needed for direct HTTP connections on v2.7.2+. -4. **Optionally, the Netdata MCP API key** that unlocks full access to sensitive observability data (protected functions, full access to logs) on your Netdata. Each Netdata Agent or Parent has its own unique API key for MCP - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) +4. **Netdata MCP API key** - required when `[web].bearer token protection = yes`. When bearer protection is disabled, it is optional and unlocks sensitive observability data (protected functions, full access to logs). Each Netdata Agent or Parent has its own unique MCP API key - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) ## Configuration Methods diff --git a/docs/netdata-ai/mcp/mcp-clients/codex-cli.md b/docs/netdata-ai/mcp/mcp-clients/codex-cli.md index e029cd195ea411..0ce2956051b3f1 100644 --- a/docs/netdata-ai/mcp/mcp-clients/codex-cli.md +++ b/docs/netdata-ai/mcp/mcp-clients/codex-cli.md @@ -21,7 +21,7 @@ Codex CLI supports both stdio launchers and direct Streamable HTTP when the RMCP - **v2.6.0 - v2.7.1**: Only WebSocket transport available, requires `nd-mcp` bridge - **v2.7.2+**: Can use `npx mcp-remote` bridge for HTTP/SSE support 3. **Launcher** – Run Netdata through `nd-mcp` (always) or `npx mcp-remote` (useful when you want a single stdio launcher for multiple MCP clients). Direct HTTP is also available for v2.7.2+ when you enable the RMCP client. -4. **Optionally, the Netdata MCP API key** that unlocks full access to sensitive observability data (protected functions, full access to logs) on your Netdata. Each Netdata Agent or Parent has its own unique API key for MCP - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) +4. **Netdata MCP API key** - required when `[web].bearer token protection = yes`. When bearer protection is disabled, it is optional and unlocks sensitive observability data (protected functions, full access to logs). Each Netdata Agent or Parent has its own unique MCP API key - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) ## Installation diff --git a/docs/netdata-ai/mcp/mcp-clients/crush.md b/docs/netdata-ai/mcp/mcp-clients/crush.md index 9a44e3472ef626..6a0b19a8a61b52 100644 --- a/docs/netdata-ai/mcp/mcp-clients/crush.md +++ b/docs/netdata-ai/mcp/mcp-clients/crush.md @@ -20,7 +20,7 @@ Crush has comprehensive MCP transport support, making it highly flexible for con - **v2.6.0 - v2.7.1**: Only WebSocket transport available, requires `nd-mcp` bridge - **v2.7.2+**: Direct HTTP/SSE support available (recommended) 3. **For WebSocket or stdio connections: `nd-mcp` bridge** - The stdio-to-websocket bridge. [Find its absolute path](/docs/netdata-ai/mcp/README.md#finding-the-nd-mcp-bridge). Not needed for direct HTTP/SSE connections on v2.7.2+. -4. **Optionally, the Netdata MCP API key** that unlocks full access to sensitive observability data (protected functions, full access to logs) on your Netdata. Each Netdata Agent or Parent has its own unique API key for MCP - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) +4. **Netdata MCP API key** - required when `[web].bearer token protection = yes`. When bearer protection is disabled, it is optional and unlocks sensitive observability data (protected functions, full access to logs). Each Netdata Agent or Parent has its own unique MCP API key - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) > Export `ND_MCP_BEARER_TOKEN` with your MCP key before launching Crush so credentials never appear in command-line arguments or config files: > ```bash diff --git a/docs/netdata-ai/mcp/mcp-clients/gemini-cli.md b/docs/netdata-ai/mcp/mcp-clients/gemini-cli.md index 49c2ad2f0355e8..5766e07ea993dc 100644 --- a/docs/netdata-ai/mcp/mcp-clients/gemini-cli.md +++ b/docs/netdata-ai/mcp/mcp-clients/gemini-cli.md @@ -20,7 +20,7 @@ Gemini CLI supports all major MCP transport types, giving you maximum flexibilit - **v2.6.0 - v2.7.1**: Only WebSocket transport available, requires `nd-mcp` bridge - **v2.7.2+**: Direct HTTP/SSE support available (recommended) 3. **For WebSocket or stdio connections: `nd-mcp` bridge** - The stdio-to-websocket bridge. [Find its absolute path](/docs/netdata-ai/mcp/README.md#finding-the-nd-mcp-bridge). Not needed for direct HTTP/SSE connections on v2.7.2+. -4. **Optionally, the Netdata MCP API key** that unlocks full access to sensitive observability data (protected functions, full access to logs) on your Netdata. Each Netdata Agent or Parent has its own unique API key for MCP - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) +4. **Netdata MCP API key** - required when `[web].bearer token protection = yes`. When bearer protection is disabled, it is optional and unlocks sensitive observability data (protected functions, full access to logs). Each Netdata Agent or Parent has its own unique MCP API key - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) ## Installation diff --git a/docs/netdata-ai/mcp/mcp-clients/opencode.md b/docs/netdata-ai/mcp/mcp-clients/opencode.md index 41011583d46db4..6744afd0de0962 100644 --- a/docs/netdata-ai/mcp/mcp-clients/opencode.md +++ b/docs/netdata-ai/mcp/mcp-clients/opencode.md @@ -22,7 +22,7 @@ OpenCode supports both local and remote MCP servers: - **v2.6.0 - v2.7.1**: Only WebSocket transport available, requires `nd-mcp` bridge - **v2.7.2+**: Direct HTTP/SSE support available (recommended) 3. **For WebSocket or stdio connections: `nd-mcp` bridge** - The stdio-to-websocket bridge. [Find its absolute path](/docs/netdata-ai/mcp/README.md#finding-the-nd-mcp-bridge). Not needed for direct HTTP connections on v2.7.2+. -4. **Optionally, the Netdata MCP API key** that unlocks full access to sensitive observability data (protected functions, full access to logs) on your Netdata. Each Netdata Agent or Parent has its own unique API key for MCP - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) +4. **Netdata MCP API key** - required when `[web].bearer token protection = yes`. When bearer protection is disabled, it is optional and unlocks sensitive observability data (protected functions, full access to logs). Each Netdata Agent or Parent has its own unique MCP API key - [Find your Netdata MCP API key](/docs/netdata-ai/mcp/README.md#finding-your-api-key) > Export `ND_MCP_BEARER_TOKEN` with your MCP key before launching OpenCode to keep secrets out of configuration files: > ```bash diff --git a/docs/netdata-oss-limitations.md b/docs/netdata-oss-limitations.md index 74a8c9e63ea442..2e91cad3db7d7b 100644 --- a/docs/netdata-oss-limitations.md +++ b/docs/netdata-oss-limitations.md @@ -95,6 +95,8 @@ When accessing Netdata via Agent/Parent MCP: - **Without Cloud connection**: MCP can access public functions and metrics, but sensitive functions follow the same restrictions as the dashboard - **With Cloud connection**: MCP inherits the user's Cloud permissions, enabling access to sensitive functions for authenticated users +- **With `[web].bearer token protection = yes`**: local MCP requires the local MCP API key on all transports (HTTP, SSE, WebSocket); anonymous MCP requests are rejected +- **Network ACL**: local MCP exposure is controlled by `[web].allow mcp from` (in addition to the global `[web].allow connections from`) For MCP setup and configuration, see the [MCP documentation](/docs/netdata-ai/mcp/README.md). diff --git a/src/daemon/config/netdata-conf-web.c b/src/daemon/config/netdata-conf-web.c index 5e3089835a3ce9..2d537f8af1e46d 100644 --- a/src/daemon/config/netdata-conf-web.c +++ b/src/daemon/config/netdata-conf-web.c @@ -78,11 +78,17 @@ void netdata_conf_section_web(void) { NULL, SIMPLE_PATTERN_EXACT, true); web_allow_connections_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow connections by dns", "heuristic", web_allow_connections_from); + const char *allow_dashboard_default = + inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"); web_allow_dashboard_from = - simple_pattern_create(inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), - NULL, SIMPLE_PATTERN_EXACT, true); + simple_pattern_create(allow_dashboard_default, NULL, SIMPLE_PATTERN_EXACT, true); web_allow_dashboard_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow dashboard by dns", "heuristic", web_allow_dashboard_from); + web_allow_mcp_from = + simple_pattern_create(inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "allow mcp from", allow_dashboard_default), + NULL, SIMPLE_PATTERN_EXACT, true); + web_allow_mcp_dns = + make_dns_decision(CONFIG_SECTION_WEB, "allow mcp by dns", "heuristic", web_allow_mcp_from); web_allow_badges_from = simple_pattern_create(inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT, true); diff --git a/src/go/pkg/l2topology/l2_pipeline_builder.go b/src/go/pkg/l2topology/l2_pipeline_builder.go index 7ef26dab69f6a0..fe3db82d962b4d 100644 --- a/src/go/pkg/l2topology/l2_pipeline_builder.go +++ b/src/go/pkg/l2topology/l2_pipeline_builder.go @@ -93,22 +93,23 @@ func (s *l2BuildState) buildResult(identityAliasStats identityAliasReconcileStat collectedAt = collectedAt.UTC() } - stats := newL2ResultStats() - stats["devices_total"] = len(s.devices) - stats["links_total"] = len(s.adjacencies) - stats["links_lldp"] = s.linksLLDP - stats["links_cdp"] = s.linksCDP - stats["links_stp"] = s.linksSTP - stats["attachments_total"] = len(s.attachments) - stats["attachments_fdb"] = s.attachmentsFDB - stats["enrichments_total"] = len(s.enrichments) - stats["enrichments_arp_nd"] = s.enrichmentsARPND - stats["bridge_domains_total"] = len(s.bridgeDomains) - stats["endpoints_total"] = len(s.endpointIDs) - stats["identity_alias_endpoints_mapped"] = identityAliasStats.endpointsMapped - stats["identity_alias_endpoints_ambiguous_mac"] = identityAliasStats.endpointsAmbiguousMAC - stats["identity_alias_ips_merged"] = identityAliasStats.ipsMerged - stats["identity_alias_ips_conflict_skipped"] = identityAliasStats.ipsConflictSkipped + stats := ResultStats{ + DevicesTotal: len(s.devices), + LinksTotal: len(s.adjacencies), + LinksLLDP: s.linksLLDP, + LinksCDP: s.linksCDP, + LinksSTP: s.linksSTP, + AttachmentsTotal: len(s.attachments), + AttachmentsFDB: s.attachmentsFDB, + EnrichmentsTotal: len(s.enrichments), + EnrichmentsARPND: s.enrichmentsARPND, + BridgeDomainsTotal: len(s.bridgeDomains), + EndpointsTotal: len(s.endpointIDs), + IdentityAliasEndpointsMapped: identityAliasStats.endpointsMapped, + IdentityAliasEndpointsAmbiguousMAC: identityAliasStats.endpointsAmbiguousMAC, + IdentityAliasIPsMerged: identityAliasStats.ipsMerged, + IdentityAliasIPsConflictSkipped: identityAliasStats.ipsConflictSkipped, + } return Result{ CollectedAt: collectedAt, diff --git a/src/go/pkg/l2topology/l2_pipeline_stats.go b/src/go/pkg/l2topology/l2_pipeline_stats.go index 11d81166d34ce2..7705b577994fc5 100644 --- a/src/go/pkg/l2topology/l2_pipeline_stats.go +++ b/src/go/pkg/l2topology/l2_pipeline_stats.go @@ -2,22 +2,39 @@ package l2topology -func newL2ResultStats() map[string]any { - return map[string]any{ - "devices_total": 0, - "links_total": 0, - "links_lldp": 0, - "links_cdp": 0, - "links_stp": 0, - "attachments_total": 0, - "attachments_fdb": 0, - "enrichments_total": 0, - "enrichments_arp_nd": 0, - "bridge_domains_total": 0, - "endpoints_total": 0, - "identity_alias_endpoints_mapped": 0, - "identity_alias_endpoints_ambiguous_mac": 0, - "identity_alias_ips_merged": 0, - "identity_alias_ips_conflict_skipped": 0, - } +type ResultStats struct { + DevicesTotal int + LinksTotal int + LinksLLDP int + LinksCDP int + LinksSTP int + AttachmentsTotal int + AttachmentsFDB int + EnrichmentsTotal int + EnrichmentsARPND int + BridgeDomainsTotal int + EndpointsTotal int + IdentityAliasEndpointsMapped int + IdentityAliasEndpointsAmbiguousMAC int + IdentityAliasIPsMerged int + IdentityAliasIPsConflictSkipped int +} + +type ProjectionStats struct { + ResultStats + + DevicesDiscovered int + LinksBidirectional int + LinksUnidirectional int + LinksFDB int + LinksFDBEndpointCandidates int + LinksFDBEndpointEmitted int + LinksFDBEndpointSuppressed int + EndpointsAmbiguousSegments int + LinksARP int + LinksProbable int + SegmentsSuppressed int + ActorsTotal int + ActorsUnlinkedSuppressed int + InferenceStrategy string } diff --git a/src/go/pkg/l2topology/l2_pipeline_test.go b/src/go/pkg/l2topology/l2_pipeline_test.go index 14e6c58ede1eb8..45732dc2c46099 100644 --- a/src/go/pkg/l2topology/l2_pipeline_test.go +++ b/src/go/pkg/l2topology/l2_pipeline_test.go @@ -56,9 +56,9 @@ func TestBuildL2ResultFromObservations_LLDPAndCDP(t *testing.T) { require.Len(t, result.Devices, 2) require.Len(t, result.Interfaces, 2) require.Len(t, result.Adjacencies, 2) - require.Equal(t, 2, result.Stats["links_total"]) - require.Equal(t, 1, result.Stats["links_lldp"]) - require.Equal(t, 1, result.Stats["links_cdp"]) + require.Equal(t, 2, result.Stats.LinksTotal) + require.Equal(t, 1, result.Stats.LinksLLDP) + require.Equal(t, 1, result.Stats.LinksCDP) require.Equal(t, "cdp", result.Adjacencies[0].Protocol) require.Equal(t, "switch-a", result.Adjacencies[0].SourceID) @@ -93,8 +93,8 @@ func TestBuildL2ResultFromObservations_DefaultProtocols(t *testing.T) { require.NoError(t, err) require.Len(t, result.Adjacencies, 1) require.Equal(t, "switch-b", result.Adjacencies[0].TargetID) - require.Equal(t, 0, result.Stats["links_lldp"]) - require.Equal(t, 1, result.Stats["links_cdp"]) + require.Equal(t, 0, result.Stats.LinksLLDP) + require.Equal(t, 1, result.Stats.LinksCDP) } func TestBuildL2ResultFromObservations_UsesProvidedCollectedAt(t *testing.T) { @@ -249,9 +249,9 @@ func TestBuildL2ResultFromObservations_SkipsSelfAdjacencies(t *testing.T) { require.NoError(t, err) require.Len(t, result.Devices, 1) require.Empty(t, result.Adjacencies) - require.Equal(t, 0, result.Stats["links_total"]) - require.Equal(t, 0, result.Stats["links_lldp"]) - require.Equal(t, 0, result.Stats["links_cdp"]) + require.Equal(t, 0, result.Stats.LinksTotal) + require.Equal(t, 0, result.Stats.LinksLLDP) + require.Equal(t, 0, result.Stats.LinksCDP) } func TestBuildL2ResultFromObservations_CDPSysNameAndDeviceID(t *testing.T) { @@ -330,10 +330,10 @@ func TestBuildL2ResultFromObservations_FDBAttachments(t *testing.T) { require.Equal(t, "7", first.Labels["bridge_port"]) require.Equal(t, "Port3", first.Labels["if_name"]) - require.Equal(t, 2, result.Stats["attachments_total"]) - require.Equal(t, 2, result.Stats["attachments_fdb"]) - require.Equal(t, 1, result.Stats["bridge_domains_total"]) - require.Equal(t, 2, result.Stats["endpoints_total"]) + require.Equal(t, 2, result.Stats.AttachmentsTotal) + require.Equal(t, 2, result.Stats.AttachmentsFDB) + require.Equal(t, 1, result.Stats.BridgeDomainsTotal) + require.Equal(t, 2, result.Stats.EndpointsTotal) } func TestBuildL2ResultFromObservations_FDBDropsDuplicateMACAcrossPorts(t *testing.T) { @@ -357,7 +357,7 @@ func TestBuildL2ResultFromObservations_FDBDropsDuplicateMACAcrossPorts(t *testin require.NoError(t, err) require.Len(t, result.Attachments, 1) require.Equal(t, "mac:70:49:a2:65:72:ce", result.Attachments[0].EndpointID) - require.Equal(t, 1, result.Stats["attachments_fdb"]) + require.Equal(t, 1, result.Stats.AttachmentsFDB) } func TestBuildL2ResultFromObservations_FDBKeepsSameMACAcrossPortsWhenVLANDiffers(t *testing.T) { @@ -385,7 +385,7 @@ func TestBuildL2ResultFromObservations_FDBKeepsSameMACAcrossPortsWhenVLANDiffers require.Equal(t, "mac:70:49:a2:65:72:cd", first.EndpointID) require.Equal(t, "mac:70:49:a2:65:72:cd", second.EndpointID) require.NotEqual(t, first.Labels["vlan_id"], second.Labels["vlan_id"]) - require.Equal(t, 2, result.Stats["attachments_fdb"]) + require.Equal(t, 2, result.Stats.AttachmentsFDB) } func TestBuildL2ResultFromObservations_FDBSkipsSelfAndNonLearned(t *testing.T) { @@ -453,7 +453,7 @@ func TestBuildL2ResultFromObservations_STPAdjacency(t *testing.T) { require.Equal(t, "Port3", result.Adjacencies[0].SourcePort) require.Equal(t, "200", result.Adjacencies[0].Labels["vlan_id"]) require.Equal(t, "servers", result.Adjacencies[0].Labels["vlan_name"]) - require.Equal(t, 1, result.Stats["links_stp"]) + require.Equal(t, 1, result.Stats.LinksSTP) } func TestBuildL2ResultFromObservations_STPDoesNotCreateSyntheticActors(t *testing.T) { @@ -483,7 +483,7 @@ func TestBuildL2ResultFromObservations_STPDoesNotCreateSyntheticActors(t *testin require.NoError(t, err) require.Len(t, result.Devices, 1) require.Empty(t, result.Adjacencies) - require.Equal(t, 0, result.Stats["links_stp"]) + require.Equal(t, 0, result.Stats.LinksSTP) } func TestBuildL2ResultFromObservations_FDBBridgeDomainFallbackToBridgePort(t *testing.T) { @@ -602,8 +602,8 @@ func TestBuildL2ResultFromObservations_ARPNDEnrichment(t *testing.T) { require.Equal(t, "Port3,Port5", enrichment.Labels["if_names"]) require.Equal(t, "reachable", enrichment.Labels["states"]) require.Equal(t, "ipv4", enrichment.Labels["addr_types"]) - require.Equal(t, 1, result.Stats["enrichments_total"]) - require.Equal(t, 1, result.Stats["enrichments_arp_nd"]) + require.Equal(t, 1, result.Stats.EnrichmentsTotal) + require.Equal(t, 1, result.Stats.EnrichmentsARPND) } func TestBuildL2ResultFromObservations_SkipsMACLessARPNDEntries(t *testing.T) { @@ -638,7 +638,7 @@ func TestBuildL2ResultFromObservations_SkipsMACLessARPNDEntries(t *testing.T) { require.Len(t, result.Enrichments, 1) require.Equal(t, "mac:70:49:a2:65:72:cf", result.Enrichments[0].EndpointID) require.Equal(t, []string{"10.20.4.100"}, addressStrings(result.Enrichments[0].IPs)) - require.Equal(t, 1, result.Stats["endpoints_total"]) + require.Equal(t, 1, result.Stats.EndpointsTotal) } func TestBuildL2ResultFromObservations_ReconcilesARPAliasIntoLLDPDeviceIdentity(t *testing.T) { @@ -684,8 +684,8 @@ func TestBuildL2ResultFromObservations_ReconcilesARPAliasIntoLLDPDeviceIdentity( []string{"10.20.4.205", "fc00:f853:ccd:e793::1"}, deviceAddressStrings(*costaDesktop), ) - require.Equal(t, 1, result.Stats["identity_alias_endpoints_mapped"]) - require.Equal(t, 1, result.Stats["identity_alias_ips_merged"]) + require.Equal(t, 1, result.Stats.IdentityAliasEndpointsMapped) + require.Equal(t, 1, result.Stats.IdentityAliasIPsMerged) } func TestBuildL2ResultFromObservations_SkipsConflictingARPAliases(t *testing.T) { @@ -736,9 +736,9 @@ func TestBuildL2ResultFromObservations_SkipsConflictingARPAliases(t *testing.T) costaDesktop := findDeviceByHostname(result.Devices, "costa-desktop") require.NotNil(t, costaDesktop) require.Equal(t, []string{"fc00:f853:ccd:e793::1"}, deviceAddressStrings(*costaDesktop)) - require.Equal(t, 1, result.Stats["identity_alias_endpoints_mapped"]) - require.Equal(t, 0, result.Stats["identity_alias_ips_merged"]) - require.Equal(t, 1, result.Stats["identity_alias_ips_conflict_skipped"]) + require.Equal(t, 1, result.Stats.IdentityAliasEndpointsMapped) + require.Equal(t, 0, result.Stats.IdentityAliasIPsMerged) + require.Equal(t, 1, result.Stats.IdentityAliasIPsConflictSkipped) } func TestBuildL2ResultFromObservations_SkipsAmbiguousMACAliasOwnership(t *testing.T) { @@ -787,8 +787,8 @@ func TestBuildL2ResultFromObservations_SkipsAmbiguousMACAliasOwnership(t *testin require.NotNil(t, switchB) require.NotContains(t, deviceAddressStrings(*switchA), "10.0.0.50") require.NotContains(t, deviceAddressStrings(*switchB), "10.0.0.50") - require.Equal(t, 0, result.Stats["identity_alias_ips_merged"]) - require.Equal(t, 1, result.Stats["identity_alias_endpoints_ambiguous_mac"]) + require.Equal(t, 0, result.Stats.IdentityAliasIPsMerged) + require.Equal(t, 1, result.Stats.IdentityAliasEndpointsAmbiguousMAC) } func TestNormalizeMAC_PadsSingleNibbleTokens(t *testing.T) { diff --git a/src/go/pkg/l2topology/parity/l2_builder_test.go b/src/go/pkg/l2topology/parity/l2_builder_test.go index bc079fef2c43c6..1302f147f8274f 100644 --- a/src/go/pkg/l2topology/parity/l2_builder_test.go +++ b/src/go/pkg/l2topology/parity/l2_builder_test.go @@ -34,8 +34,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS8003(t *testing.T) { require.NoError(t, err) require.Len(t, result.Devices, 5) require.Len(t, result.Adjacencies, 12) - require.Equal(t, 12, result.Stats["links_lldp"]) - require.Equal(t, 0, result.Stats["links_cdp"]) + require.Equal(t, 12, result.Stats.LinksLLDP) + require.Equal(t, 0, result.Stats.LinksCDP) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -73,28 +73,28 @@ func TestBuildL2ResultFromWalks_CDP_NMS8000(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableCDP: true}) require.NoError(t, errStep1) - require.Equal(t, 3, step1.Stats["links_cdp"]) + require.Equal(t, 3, step1.Stats.LinksCDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableCDP: true}) require.NoError(t, errStep2) - require.Equal(t, 6, step2.Stats["links_cdp"]) + require.Equal(t, 6, step2.Stats.LinksCDP) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableCDP: true}) require.NoError(t, errStep3) - require.Equal(t, 9, step3.Stats["links_cdp"]) + require.Equal(t, 9, step3.Stats.LinksCDP) step4, errStep4 := buildResultFromScenarioPrefix(resolved, 4, BuildOptions{EnableCDP: true}) require.NoError(t, errStep4) - require.Equal(t, 11, step4.Stats["links_cdp"]) + require.Equal(t, 11, step4.Stats.LinksCDP) step5, errStep5 := buildResultFromScenarioPrefix(resolved, 5, BuildOptions{EnableCDP: true}) require.NoError(t, errStep5) - require.Equal(t, 13, step5.Stats["links_cdp"]) + require.Equal(t, 13, step5.Stats.LinksCDP) final := step5 require.Len(t, final.Adjacencies, 13) - require.Equal(t, 13, final.Stats["links_cdp"]) - require.Equal(t, 0, final.Stats["links_lldp"]) + require.Equal(t, 13, final.Stats.LinksCDP) + require.Equal(t, 0, final.Stats.LinksLLDP) expected := map[string]struct{}{ "cdp|nmmr1|Gi0/0|nmmr3|GigabitEthernet0/1": {}, @@ -144,28 +144,28 @@ func TestBuildL2ResultFromWalks_LLDP_NMS8000(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 3, step1.Stats["links_lldp"]) + require.Equal(t, 3, step1.Stats.LinksLLDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 6, step2.Stats["links_lldp"]) + require.Equal(t, 6, step2.Stats.LinksLLDP) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 8, step3.Stats["links_lldp"]) + require.Equal(t, 8, step3.Stats.LinksLLDP) step4, errStep4 := buildResultFromScenarioPrefix(resolved, 4, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep4) - require.Equal(t, 10, step4.Stats["links_lldp"]) + require.Equal(t, 10, step4.Stats.LinksLLDP) step5, errStep5 := buildResultFromScenarioPrefix(resolved, 5, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep5) - require.Equal(t, 12, step5.Stats["links_lldp"]) + require.Equal(t, 12, step5.Stats.LinksLLDP) final := step5 require.Len(t, final.Adjacencies, 12) - require.Equal(t, 12, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 12, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) expected := map[string]struct{}{ "lldp|nmmr1|Gi0/0|nmmr3|Gi0/1": {}, @@ -205,20 +205,20 @@ func TestBuildL2ResultFromWalks_LLDP_NMS13637(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 5, step1.Stats["links_lldp"]) + require.Equal(t, 5, step1.Stats.LinksLLDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 10, step2.Stats["links_lldp"]) + require.Equal(t, 10, step2.Stats.LinksLLDP) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 19, step3.Stats["links_lldp"]) + require.Equal(t, 19, step3.Stats.LinksLLDP) final := step3 require.Len(t, final.Adjacencies, 19) - require.Equal(t, 19, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 19, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -285,13 +285,13 @@ func TestBuildL2ResultFromWalks_LLDP_NMS10205B(t *testing.T) { sourceCounts := countAdjacenciesBySource(result.Adjacencies, "lldp") require.Equal(t, expected.sourceLinks, sourceCounts[expected.fixtureID]) - require.Equal(t, expected.totalLinks, result.Stats["links_lldp"]) + require.Equal(t, expected.totalLinks, result.Stats.LinksLLDP) require.Equal(t, expected.lldpElementCnt, countFixturesWithLLDPLocalElements(walks)) final = result } - require.Equal(t, 10, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 10, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) allWalks, err := loadScenarioWalkPrefix(resolved, len(resolved.Fixtures)) require.NoError(t, err) require.Equal(t, 6, countFixturesWithLLDPLocalElements(allWalks)) @@ -378,7 +378,7 @@ func TestBuildL2ResultFromWalks_LLDP_NMS17216(t *testing.T) { require.NoError(t, buildErr) sourceCounts := countAdjacenciesBySource(result.Adjacencies, "lldp") - require.Equal(t, expected.totalLinks, result.Stats["links_lldp"]) + require.Equal(t, expected.totalLinks, result.Stats.LinksLLDP) require.Equal(t, expected.sourceLinks, sourceCounts[expected.fixtureID]) require.Equal(t, expected.lldpElementCnt, countFixturesWithLLDPLocalElements(walks)) final = result @@ -386,8 +386,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS17216(t *testing.T) { require.Len(t, final.Devices, 5) require.Len(t, final.Adjacencies, 12) - require.Equal(t, 12, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 12, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 6, countBidirectionalPairs(final.Adjacencies, "lldp")) golden, err := LoadGoldenYAML(resolved.GoldenYAML) @@ -445,15 +445,15 @@ func TestBuildL2ResultFromWalks_CDP_NMS17216(t *testing.T) { require.NoError(t, buildErr) sourceCounts := countAdjacenciesBySource(result.Adjacencies, "cdp") - require.Equal(t, expected.totalLinks, result.Stats["links_cdp"]) + require.Equal(t, expected.totalLinks, result.Stats.LinksCDP) require.Equal(t, expected.sourceLinks, sourceCounts[expected.fixtureID]) final = result } require.Len(t, final.Devices, 9) require.Len(t, final.Adjacencies, 26) - require.Equal(t, 26, final.Stats["links_cdp"]) - require.Equal(t, 0, final.Stats["links_lldp"]) + require.Equal(t, 26, final.Stats.LinksCDP) + require.Equal(t, 0, final.Stats.LinksLLDP) require.Equal(t, map[string]int{ "Switch1": 5, @@ -497,13 +497,13 @@ func TestBuildL2ResultFromWalks_CDP_NMS17216_TopologyProjection(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableCDP: true}) require.NoError(t, errStep1) - require.Equal(t, 5, step1.Stats["links_cdp"]) - require.Equal(t, 0, step1.Stats["links_lldp"]) + require.Equal(t, 5, step1.Stats.LinksCDP) + require.Equal(t, 0, step1.Stats.LinksLLDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableCDP: true}) require.NoError(t, errStep2) - require.Equal(t, 11, step2.Stats["links_cdp"]) - require.Equal(t, 0, step2.Stats["links_lldp"]) + require.Equal(t, 11, step2.Stats.LinksCDP) + require.Equal(t, 0, step2.Stats.LinksLLDP) require.Equal(t, map[string]int{ "Switch1": 5, "Switch2": 6, @@ -538,13 +538,13 @@ func TestBuildL2ResultFromWalks_LLDP_NMS17216_TopologyProjection(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 4, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 4, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 10, step2.Stats["links_lldp"]) - require.Equal(t, 0, step2.Stats["links_cdp"]) + require.Equal(t, 10, step2.Stats.LinksLLDP) + require.Equal(t, 0, step2.Stats.LinksCDP) localStep2 := map[string]struct{}{ "Switch1": {}, @@ -560,8 +560,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS17216_TopologyProjection(t *testing.T) { step3, errStep3 := BuildL2ResultFromWalks(step3Walks, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 12, step3.Stats["links_lldp"]) - require.Equal(t, 0, step3.Stats["links_cdp"]) + require.Equal(t, 12, step3.Stats.LinksLLDP) + require.Equal(t, 0, step3.Stats.LinksCDP) localStep3 := map[string]struct{}{ "Switch1": {}, @@ -615,7 +615,7 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0123(t *testing.T) { result, buildErr := BuildL2ResultFromWalks(walks, BuildOptions{EnableLLDP: true}) require.NoError(t, buildErr) - require.Equal(t, expected.totalLinks, result.Stats["links_lldp"]) + require.Equal(t, expected.totalLinks, result.Stats.LinksLLDP) final = result } @@ -623,8 +623,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0123(t *testing.T) { require.NoError(t, err) require.Len(t, final.Adjacencies, 23) - require.Equal(t, 23, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 23, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 8, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -676,19 +676,19 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0002_CISCO_JUNIPER(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 1, step1.Stats["links_lldp"]) + require.Equal(t, 1, step1.Stats.LinksLLDP) step2First, errStep2First := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2First) step2Second, errStep2Second := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2Second) - require.Equal(t, step2First.Stats["links_lldp"], step2Second.Stats["links_lldp"]) + require.Equal(t, step2First.Stats.LinksLLDP, step2Second.Stats.LinksLLDP) final := step2Second require.Len(t, final.Adjacencies, 2) - require.Equal(t, 2, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 2, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, map[string]int{ "Rluck001": 1, @@ -731,27 +731,27 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0002_CISCO_ALCATEL(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 0, step1.Stats["links_lldp"]) + require.Equal(t, 0, step1.Stats.LinksLLDP) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 1, step2.Stats["links_lldp"]) + require.Equal(t, 1, step2.Stats.LinksLLDP) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 2, step3.Stats["links_lldp"]) + require.Equal(t, 2, step3.Stats.LinksLLDP) step4, errStep4 := buildResultFromScenarioPrefix(resolved, 4, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep4) - require.Equal(t, 4, step4.Stats["links_lldp"]) + require.Equal(t, 4, step4.Stats.LinksLLDP) step5, errStep5 := buildResultFromScenarioPrefix(resolved, 5, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep5) final := step5 require.Len(t, final.Adjacencies, 6) - require.Equal(t, 6, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 6, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -854,15 +854,15 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_NETWORK_ALL(t *testing.T) { require.NoError(t, buildErr) sourceCounts := countAdjacenciesBySource(result.Adjacencies, "lldp") - require.Equal(t, expected.totalLinks, result.Stats["links_lldp"]) + require.Equal(t, expected.totalLinks, result.Stats.LinksLLDP) require.Equal(t, expected.sourceLinks, sourceCounts[expected.fixtureID]) require.Equal(t, expected.lldpElementCnt, countFixturesWithLLDPLocalElements(allWalks[:i+1])) final = result } require.Len(t, final.Adjacencies, 73) - require.Equal(t, 73, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 73, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 18, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -927,20 +927,20 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_NETWORK_TWO_CONNECTED(t *testing.T) step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 3, step1.Stats["links_lldp"]) + require.Equal(t, 3, step1.Stats.LinksLLDP) require.Equal(t, map[string]int{"ms07": 3}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 8, step2.Stats["links_lldp"]) + require.Equal(t, 8, step2.Stats.LinksLLDP) require.Equal(t, map[string]int{"ms07": 3, "ms08": 5}, countAdjacenciesBySource(step2.Adjacencies, "lldp")) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) final := step2 require.Len(t, final.Adjacencies, 8) - require.Equal(t, 8, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 8, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) localDevices := map[string]struct{}{ "ms07": {}, @@ -988,20 +988,20 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_NETWORK_THREE_CONNECTED(t *testing. step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 5, step1.Stats["links_lldp"]) + require.Equal(t, 5, step1.Stats.LinksLLDP) require.Equal(t, map[string]int{"ms08": 5}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 10, step2.Stats["links_lldp"]) + require.Equal(t, 10, step2.Stats.LinksLLDP) require.Equal(t, map[string]int{"ms08": 5, "ms10": 5}, countAdjacenciesBySource(step2.Adjacencies, "lldp")) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) final := step2 require.Len(t, final.Adjacencies, 10) - require.Equal(t, 10, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 10, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) localDevices := map[string]struct{}{ "ms08": {}, @@ -1050,8 +1050,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_MICROSENSE(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 5) - require.Equal(t, 5, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 5, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"ms08": 5}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1110,8 +1110,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_MS16(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 5) - require.Equal(t, 5, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 5, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"ms16": 5}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1170,8 +1170,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS0000_PLANET(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 3) - require.Equal(t, 3, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 3, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"planet": 3}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1269,32 +1269,32 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_NETWORK_ALL(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 2, step1.Stats["links_lldp"]) + require.Equal(t, 2, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 4, step2.Stats["links_lldp"]) + require.Equal(t, 4, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks[:2])) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 5, step3.Stats["links_lldp"]) + require.Equal(t, 5, step3.Stats.LinksLLDP) require.Equal(t, 3, countFixturesWithLLDPLocalElements(allWalks[:3])) step4, errStep4 := buildResultFromScenarioPrefix(resolved, 4, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep4) - require.Equal(t, 9, step4.Stats["links_lldp"]) + require.Equal(t, 9, step4.Stats.LinksLLDP) require.Equal(t, 4, countFixturesWithLLDPLocalElements(allWalks[:4])) step5, errStep5 := buildResultFromScenarioPrefix(resolved, 5, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep5) - require.Equal(t, 12, step5.Stats["links_lldp"]) + require.Equal(t, 12, step5.Stats.LinksLLDP) require.Equal(t, 5, countFixturesWithLLDPLocalElements(allWalks[:5])) step6, errStep6 := buildResultFromScenarioPrefix(resolved, 6, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep6) - require.Equal(t, 19, step6.Stats["links_lldp"]) + require.Equal(t, 19, step6.Stats.LinksLLDP) require.Equal(t, 6, countFixturesWithLLDPLocalElements(allWalks[:6])) localPreQFX := map[string]struct{}{ @@ -1311,7 +1311,7 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_NETWORK_ALL(t *testing.T) { step7, errStep7 := buildResultFromScenarioPrefix(resolved, 7, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep7) - require.Equal(t, 34, step7.Stats["links_lldp"]) + require.Equal(t, 34, step7.Stats.LinksLLDP) require.Equal(t, 7, countFixturesWithLLDPLocalElements(allWalks)) localAll := map[string]struct{}{ @@ -1356,22 +1356,22 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW01_SW02_SW03(t *testing step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 17, step2.Stats["links_lldp"]) + require.Equal(t, 17, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks[:2])) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 3, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep3) - require.Equal(t, 19, step3.Stats["links_lldp"]) + require.Equal(t, 19, step3.Stats.LinksLLDP) require.Equal(t, 3, countFixturesWithLLDPLocalElements(allWalks[:3])) step4, errStep4 := buildResultFromScenarioPrefix(resolved, 4, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep4) - require.Equal(t, 20, step4.Stats["links_lldp"]) + require.Equal(t, 20, step4.Stats.LinksLLDP) require.Equal(t, 4, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1418,12 +1418,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW01(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 17, step2.Stats["links_lldp"]) + require.Equal(t, 17, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1468,12 +1468,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW02(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 17, step2.Stats["links_lldp"]) + require.Equal(t, 17, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1518,12 +1518,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW03(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 16, step2.Stats["links_lldp"]) + require.Equal(t, 16, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1568,12 +1568,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW04(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 19, step2.Stats["links_lldp"]) + require.Equal(t, 19, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1618,12 +1618,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW08(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 18, step2.Stats["links_lldp"]) + require.Equal(t, 18, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1666,12 +1666,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_TOPO_QFX_SW09(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 15, step1.Stats["links_lldp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks[:1])) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 22, step2.Stats["links_lldp"]) + require.Equal(t, 22, step2.Stats.LinksLLDP) require.Equal(t, 2, countFixturesWithLLDPLocalElements(allWalks)) localDevices := map[string]struct{}{ @@ -1717,8 +1717,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_QFX(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 15) - require.Equal(t, 15, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 15, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"E0281L-ScALBENGA2-QFX": 15}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1771,8 +1771,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW01(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 2) - require.Equal(t, 2, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 2, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_01_M": 2}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1812,8 +1812,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW02(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 2) - require.Equal(t, 2, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 2, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_02_M": 2}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1853,8 +1853,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW03(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 1) - require.Equal(t, 1, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 1, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_03_M": 1}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1893,8 +1893,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW04(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 4) - require.Equal(t, 4, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 4, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_04_M": 4}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1936,8 +1936,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW08(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 3) - require.Equal(t, 3, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 3, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_08_M": 3}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -1978,8 +1978,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS18541_MICROSENS_SW09(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) require.Len(t, step1.Adjacencies, 7) - require.Equal(t, 7, step1.Stats["links_lldp"]) - require.Equal(t, 0, step1.Stats["links_cdp"]) + require.Equal(t, 7, step1.Stats.LinksLLDP) + require.Equal(t, 0, step1.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(allWalks)) require.Equal(t, map[string]int{"SW_D6_09_M": 7}, countAdjacenciesBySource(step1.Adjacencies, "lldp")) @@ -2029,8 +2029,8 @@ func TestBuildL2ResultFromWalks_CDP_NMS7467(t *testing.T) { final, buildErr := BuildL2ResultFromWalks(walks, BuildOptions{EnableCDP: true}) require.NoError(t, buildErr) require.Len(t, final.Adjacencies, 5) - require.Equal(t, 5, final.Stats["links_cdp"]) - require.Equal(t, 0, final.Stats["links_lldp"]) + require.Equal(t, 5, final.Stats.LinksCDP) + require.Equal(t, 0, final.Stats.LinksLLDP) require.Equal(t, map[string]int{"ciscoswitch": 5}, countAdjacenciesBySource(final.Adjacencies, "cdp")) for _, adj := range final.Adjacencies { @@ -2087,8 +2087,8 @@ func TestBuildL2ResultFromWalks_MIXED_NMS7563_CISCO01(t *testing.T) { require.NoError(t, buildErr) require.Len(t, final.Devices, 2) require.Len(t, final.Adjacencies, 1) - require.Equal(t, 1, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 1, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(walks)) require.Equal(t, 0, countBidirectionalPairs(final.Adjacencies, "lldp")) @@ -2142,8 +2142,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS7563_HOMESERVER(t *testing.T) { require.NoError(t, buildErr) require.Len(t, final.Devices, 2) require.Len(t, final.Adjacencies, 1) - require.Equal(t, 1, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 1, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 1, countFixturesWithLLDPLocalElements(walks)) deviceByID := make(map[string]l2topology.Device, len(final.Devices)) @@ -2202,8 +2202,8 @@ func TestBuildL2ResultFromWalks_CDP_NMS7563_SWITCH02(t *testing.T) { require.NoError(t, buildErr) require.Len(t, final.Devices, 3) require.Len(t, final.Adjacencies, 3) - require.Equal(t, 3, final.Stats["links_cdp"]) - require.Equal(t, 0, final.Stats["links_lldp"]) + require.Equal(t, 3, final.Stats.LinksCDP) + require.Equal(t, 0, final.Stats.LinksLLDP) require.Equal(t, map[string]int{"switch02": 3}, countAdjacenciesBySource(final.Adjacencies, "cdp")) deviceByID := make(map[string]l2topology.Device, len(final.Devices)) @@ -2279,8 +2279,8 @@ func TestBuildL2ResultFromWalks_LLDP_NMS7777DW_NO_LINKS(t *testing.T) { final := step1 require.Len(t, final.Devices, 1) require.Len(t, final.Adjacencies, 0) - require.Equal(t, 0, final.Stats["links_lldp"]) - require.Equal(t, 0, final.Stats["links_cdp"]) + require.Equal(t, 0, final.Stats.LinksLLDP) + require.Equal(t, 0, final.Stats.LinksCDP) require.Equal(t, 0, countBidirectionalPairs(final.Adjacencies, "lldp")) golden, err := LoadGoldenYAML(resolved.GoldenYAML) @@ -2341,7 +2341,7 @@ func TestBuildL2ResultFromWalks_LLDP_NMS13923(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 49, step1.Stats["links_lldp"]) + require.Equal(t, 49, step1.Stats.LinksLLDP) require.Len(t, step1.Adjacencies, 49) require.Equal(t, 0, countBidirectionalPairs(step1.Adjacencies, "lldp")) @@ -2391,12 +2391,12 @@ func TestBuildL2ResultFromWalks_LLDP_NMS13593(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep1) - require.Equal(t, 3, step1.Stats["links_lldp"]) + require.Equal(t, 3, step1.Stats.LinksLLDP) require.Len(t, step1.Adjacencies, 3) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 2, BuildOptions{EnableLLDP: true}) require.NoError(t, errStep2) - require.Equal(t, 7, step2.Stats["links_lldp"]) + require.Equal(t, 7, step2.Stats.LinksLLDP) require.Len(t, step2.Adjacencies, 7) require.Equal(t, 0, countBidirectionalPairs(step2.Adjacencies, "lldp")) @@ -2573,7 +2573,7 @@ func TestBuildL2ResultFromWalks_FDB(t *testing.T) { require.Equal(t, "7", attachment.Labels["bridge_port"]) require.Equal(t, "learned", attachment.Labels["fdb_status"]) require.Equal(t, "Port3", attachment.Labels["if_name"]) - require.Equal(t, 1, result.Stats["attachments_fdb"]) + require.Equal(t, 1, result.Stats.AttachmentsFDB) } func TestBuildL2ResultFromWalks_ARPEnrichment(t *testing.T) { @@ -2607,7 +2607,7 @@ func TestBuildL2ResultFromWalks_ARPEnrichment(t *testing.T) { require.Equal(t, "Port3", enrichment.Labels["if_names"]) require.Equal(t, "dynamic", enrichment.Labels["states"]) require.Equal(t, "ipv4", enrichment.Labels["addr_types"]) - require.Equal(t, 1, result.Stats["enrichments_arp_nd"]) + require.Equal(t, 1, result.Stats.EnrichmentsARPND) } func TestParseCDPInterfaceGetter_NMS0002_RPICT001(t *testing.T) { @@ -2840,7 +2840,7 @@ func TestBuildL2ResultFromWalks_ARP_NMS102(t *testing.T) { require.Empty(t, result.Adjacencies) require.Empty(t, result.Attachments) require.Len(t, result.Enrichments, 6) - require.Equal(t, 6, result.Stats["enrichments_arp_nd"]) + require.Equal(t, 6, result.Stats.EnrichmentsARPND) expectedByIP := map[string]struct { endpointID string @@ -2977,7 +2977,7 @@ func TestBuildL2ResultFromWalks_FDB_NMS4930(t *testing.T) { require.NoError(t, err) require.Empty(t, result.Adjacencies) require.Len(t, result.Attachments, 17) - require.Equal(t, 17, result.Stats["attachments_fdb"]) + require.Equal(t, 17, result.Stats.AttachmentsFDB) expectedPortByEndpoint := map[string]int{ "mac:00:0c:29:dc:c0:76": 24, @@ -3042,7 +3042,7 @@ func TestBuildL2ResultFromWalks_BRIDGE_NMS4930_DLINK1(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep1) require.Empty(t, step1.Adjacencies) - require.Equal(t, 17, step1.Stats["attachments_fdb"]) + require.Equal(t, 17, step1.Stats.AttachmentsFDB) require.Len(t, step1.Attachments, 17) } @@ -3076,7 +3076,7 @@ func TestBuildL2ResultFromWalks_BRIDGE_NMS4930_DLINK2(t *testing.T) { step1, errStep1 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep1) require.Empty(t, step1.Adjacencies) - require.Equal(t, 11, step1.Stats["attachments_fdb"]) + require.Equal(t, 11, step1.Stats.AttachmentsFDB) require.Len(t, step1.Attachments, 11) } @@ -3127,25 +3127,25 @@ func TestBuildL2ResultFromWalks_BRIDGE_NMS7918_ASW01(t *testing.T) { require.NoError(t, errStep1) require.Len(t, step1.Devices, 1) require.Empty(t, step1.Adjacencies) - require.Equal(t, 0, step1.Stats["links_lldp"]) + require.Equal(t, 0, step1.Stats.LinksLLDP) require.Len(t, step1.Attachments, 40) - require.Equal(t, 40, step1.Stats["attachments_fdb"]) + require.Equal(t, 40, step1.Stats.AttachmentsFDB) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep2) require.Len(t, step2.Devices, 1) require.Empty(t, step2.Adjacencies) - require.Equal(t, 0, step2.Stats["links_lldp"]) + require.Equal(t, 0, step2.Stats.LinksLLDP) require.Len(t, step2.Attachments, 40) - require.Equal(t, 40, step2.Stats["attachments_fdb"]) + require.Equal(t, 40, step2.Stats.AttachmentsFDB) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep3) require.Len(t, step3.Devices, 1) require.Empty(t, step3.Adjacencies) - require.Equal(t, 0, step3.Stats["links_lldp"]) + require.Equal(t, 0, step3.Stats.LinksLLDP) require.Len(t, step3.Attachments, 40) - require.Equal(t, 40, step3.Stats["attachments_fdb"]) + require.Equal(t, 40, step3.Stats.AttachmentsFDB) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -3183,25 +3183,25 @@ func TestBuildL2ResultFromWalks_BRIDGE_NMS7918_SAMASW01(t *testing.T) { require.NoError(t, errStep1) require.Len(t, step1.Devices, 1) require.Empty(t, step1.Adjacencies) - require.Equal(t, 0, step1.Stats["links_lldp"]) + require.Equal(t, 0, step1.Stats.LinksLLDP) require.Len(t, step1.Attachments, 22) - require.Equal(t, 22, step1.Stats["attachments_fdb"]) + require.Equal(t, 22, step1.Stats.AttachmentsFDB) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep2) require.Len(t, step2.Devices, 1) require.Empty(t, step2.Adjacencies) - require.Equal(t, 0, step2.Stats["links_lldp"]) + require.Equal(t, 0, step2.Stats.LinksLLDP) require.Len(t, step2.Attachments, 22) - require.Equal(t, 22, step2.Stats["attachments_fdb"]) + require.Equal(t, 22, step2.Stats.AttachmentsFDB) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep3) require.Len(t, step3.Devices, 1) require.Empty(t, step3.Adjacencies) - require.Equal(t, 0, step3.Stats["links_lldp"]) + require.Equal(t, 0, step3.Stats.LinksLLDP) require.Len(t, step3.Attachments, 22) - require.Equal(t, 22, step3.Stats["attachments_fdb"]) + require.Equal(t, 22, step3.Stats.AttachmentsFDB) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -3239,25 +3239,25 @@ func TestBuildL2ResultFromWalks_BRIDGE_NMS7918_STCASW01(t *testing.T) { require.NoError(t, errStep1) require.Len(t, step1.Devices, 1) require.Empty(t, step1.Adjacencies) - require.Equal(t, 0, step1.Stats["links_lldp"]) + require.Equal(t, 0, step1.Stats.LinksLLDP) require.Len(t, step1.Attachments, 34) - require.Equal(t, 34, step1.Stats["attachments_fdb"]) + require.Equal(t, 34, step1.Stats.AttachmentsFDB) step2, errStep2 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep2) require.Len(t, step2.Devices, 1) require.Empty(t, step2.Adjacencies) - require.Equal(t, 0, step2.Stats["links_lldp"]) + require.Equal(t, 0, step2.Stats.LinksLLDP) require.Len(t, step2.Attachments, 34) - require.Equal(t, 34, step2.Stats["attachments_fdb"]) + require.Equal(t, 34, step2.Stats.AttachmentsFDB) step3, errStep3 := buildResultFromScenarioPrefix(resolved, 1, BuildOptions{EnableBridge: true}) require.NoError(t, errStep3) require.Len(t, step3.Devices, 1) require.Empty(t, step3.Adjacencies) - require.Equal(t, 0, step3.Stats["links_lldp"]) + require.Equal(t, 0, step3.Stats.LinksLLDP) require.Len(t, step3.Attachments, 34) - require.Equal(t, 34, step3.Stats["attachments_fdb"]) + require.Equal(t, 34, step3.Stats.AttachmentsFDB) golden, err := LoadGoldenYAML(resolved.GoldenYAML) require.NoError(t, err) @@ -3300,7 +3300,7 @@ func TestBuildL2ResultFromWalks_ARP_NMS7918_OSPWL01(t *testing.T) { require.Empty(t, step1.Adjacencies) require.Empty(t, step1.Attachments) require.Len(t, step1.Enrichments, 1) - require.Equal(t, 1, step1.Stats["enrichments_arp_nd"]) + require.Equal(t, 1, step1.Stats.EnrichmentsARPND) enrichment := step1.Enrichments[0] require.Equal(t, "00:13:19:bd:b4:40", enrichment.MAC) @@ -3351,7 +3351,7 @@ func TestBuildL2ResultFromWalks_ARP_NMS7918_OSPESS01(t *testing.T) { require.Empty(t, step1.Adjacencies) require.Empty(t, step1.Attachments) require.Len(t, step1.Enrichments, 4) - require.Equal(t, 4, step1.Stats["enrichments_arp_nd"]) + require.Equal(t, 4, step1.Stats.EnrichmentsARPND) require.Equal(t, 5, countEnrichmentIPs(step1.Enrichments)) byMAC := enrichmentByMAC(step1.Enrichments) @@ -3416,7 +3416,7 @@ func TestBuildL2ResultFromWalks_ARP_NMS7918_PE01(t *testing.T) { require.Empty(t, step1.Adjacencies) require.Empty(t, step1.Attachments) require.Len(t, step1.Enrichments, 37) - require.Equal(t, 37, step1.Stats["enrichments_arp_nd"]) + require.Equal(t, 37, step1.Stats.EnrichmentsARPND) require.Equal(t, 113, countEnrichmentIPs(step1.Enrichments)) byMAC := enrichmentByMAC(step1.Enrichments) diff --git a/src/go/pkg/l2topology/topology_adapter.go b/src/go/pkg/l2topology/topology_adapter.go index 3808b80a70096a..be3a92c93c1822 100644 --- a/src/go/pkg/l2topology/topology_adapter.go +++ b/src/go/pkg/l2topology/topology_adapter.go @@ -222,7 +222,7 @@ func topologyInferenceStrategyConfigFor(strategy string) topologyInferenceStrate } // ToGraph converts an engine result to the internal graph projection. -func ToGraph(result Result, opts GraphOptions) graph.Graph { +func ToGraph(result Result, opts GraphOptions) (graph.Graph, ProjectionStats) { builder := newGraphBuilder(result, opts) builder.prepareIndexes() builder.collectBridgeTopologyInputs() @@ -232,5 +232,5 @@ func ToGraph(result Result, opts GraphOptions) graph.Graph { builder.buildSegmentTopology() builder.finalizeGraph() builder.buildStats() - return builder.graph() + return builder.graph(), builder.stats } diff --git a/src/go/pkg/l2topology/topology_adapter_builder.go b/src/go/pkg/l2topology/topology_adapter_builder.go index f74e1f57465456..60ef15f24569c3 100644 --- a/src/go/pkg/l2topology/topology_adapter_builder.go +++ b/src/go/pkg/l2topology/topology_adapter_builder.go @@ -41,7 +41,7 @@ type graphBuilder struct { unlinkedSuppressed int linkCounts topologyLinkCounts probableLinks int - stats map[string]any + stats ProjectionStats } func newGraphBuilder(result Result, opts GraphOptions) *graphBuilder { @@ -259,30 +259,29 @@ func (b *graphBuilder) finalizeGraph() { } func (b *graphBuilder) buildStats() { - b.stats = cloneAnyMap(b.result.Stats) - if b.stats == nil { - b.stats = make(map[string]any) + b.stats = ProjectionStats{ + ResultStats: b.result.Stats, + + DevicesDiscovered: discoveredDeviceCount(b.result.Devices, b.opts.LocalDeviceID), + LinksBidirectional: b.linkCounts.bidirectional, + LinksUnidirectional: b.linkCounts.unidirectional, + LinksFDB: b.linkCounts.fdb, + LinksFDBEndpointCandidates: b.segmentProjection.endpointLinksCandidates, + LinksFDBEndpointEmitted: b.segmentProjection.endpointLinksEmitted, + LinksFDBEndpointSuppressed: b.segmentProjection.endpointLinksSuppressed, + EndpointsAmbiguousSegments: b.segmentProjection.endpointsWithAmbiguousSegment, + LinksARP: b.linkCounts.arp, + LinksProbable: b.probableLinks, + SegmentsSuppressed: b.segmentSuppressed, + ActorsTotal: len(b.actors), + ActorsUnlinkedSuppressed: b.unlinkedSuppressed, + InferenceStrategy: b.strategyConfig.id, } - - b.stats["devices_total"] = len(b.result.Devices) - b.stats["devices_discovered"] = discoveredDeviceCount(b.result.Devices, b.opts.LocalDeviceID) - b.stats["links_total"] = len(b.links) - b.stats["links_lldp"] = b.linkCounts.lldp - b.stats["links_cdp"] = b.linkCounts.cdp - b.stats["links_bidirectional"] = b.linkCounts.bidirectional - b.stats["links_unidirectional"] = b.linkCounts.unidirectional - b.stats["links_fdb"] = b.linkCounts.fdb - b.stats["links_fdb_endpoint_candidates"] = b.segmentProjection.endpointLinksCandidates - b.stats["links_fdb_endpoint_emitted"] = b.segmentProjection.endpointLinksEmitted - b.stats["links_fdb_endpoint_suppressed"] = b.segmentProjection.endpointLinksSuppressed - b.stats["endpoints_ambiguous_segments"] = b.segmentProjection.endpointsWithAmbiguousSegment - b.stats["links_arp"] = b.linkCounts.arp - b.stats["links_probable"] = b.probableLinks - b.stats["segments_suppressed"] = b.segmentSuppressed - b.stats["actors_total"] = len(b.actors) - b.stats["actors_unlinked_suppressed"] = b.unlinkedSuppressed - b.stats["endpoints_total"] = b.endpointActors.count - b.stats["inference_strategy"] = b.strategyConfig.id + b.stats.DevicesTotal = len(b.result.Devices) + b.stats.LinksTotal = len(b.links) + b.stats.LinksLLDP = b.linkCounts.lldp + b.stats.LinksCDP = b.linkCounts.cdp + b.stats.EndpointsTotal = b.endpointActors.count } func (b *graphBuilder) graph() graph.Graph { @@ -295,6 +294,5 @@ func (b *graphBuilder) graph() graph.Graph { View: b.view, Actors: b.actors, Links: b.links, - Stats: b.stats, } } diff --git a/src/go/pkg/l2topology/topology_adapter_test.go b/src/go/pkg/l2topology/topology_adapter_test.go index 28539287a548ee..89947e8e6bc874 100644 --- a/src/go/pkg/l2topology/topology_adapter_test.go +++ b/src/go/pkg/l2topology/topology_adapter_test.go @@ -64,7 +64,7 @@ func TestToGraph_ProjectsResult(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ SchemaVersion: "2.0", Source: "snmp", Layer: "2", @@ -114,17 +114,17 @@ func TestToGraph_ProjectsResult(t *testing.T) { require.Equal(t, "sw1", endpointActor.Attributes["attached_device"]) require.Equal(t, "Gi0/4", endpointActor.Attributes["attached_port"]) - require.Equal(t, 2, data.Stats["devices_total"]) - require.Equal(t, 1, data.Stats["devices_discovered"]) - require.Equal(t, 2, data.Stats["links_total"]) - require.Equal(t, 1, data.Stats["links_lldp"]) - require.Equal(t, 0, data.Stats["links_cdp"]) - require.Equal(t, 1, data.Stats["links_fdb"]) - require.Equal(t, 0, data.Stats["links_arp"]) - require.Equal(t, 1, data.Stats["links_bidirectional"]) - require.Equal(t, 1, data.Stats["links_unidirectional"]) - require.Equal(t, 3, data.Stats["actors_total"]) - require.Equal(t, 1, data.Stats["endpoints_total"]) + require.Equal(t, 2, stats.DevicesTotal) + require.Equal(t, 1, stats.DevicesDiscovered) + require.Equal(t, 2, stats.LinksTotal) + require.Equal(t, 1, stats.LinksLLDP) + require.Equal(t, 0, stats.LinksCDP) + require.Equal(t, 1, stats.LinksFDB) + require.Equal(t, 0, stats.LinksARP) + require.Equal(t, 1, stats.LinksBidirectional) + require.Equal(t, 1, stats.LinksUnidirectional) + require.Equal(t, 3, stats.ActorsTotal) + require.Equal(t, 1, stats.EndpointsTotal) } func TestToGraph_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.T) { @@ -229,7 +229,7 @@ func TestToGraph_ClassifiesPortLinkModesFromFDBAndSTPEvidence(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -317,7 +317,7 @@ func TestToGraph_IgnoresIgnoredFDBStatusForLinkModeClassification(t *testing.T) }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -382,7 +382,7 @@ func TestToGraph_ClassifiesSTPCorroboratedManagedAliasAsSwitchFacing(t *testing. }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -521,7 +521,7 @@ func TestToGraph_EnrichesPortStatusesWithNeighborsFDBAndSTP(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -614,7 +614,7 @@ func TestToGraph_InfersVendorFromMACOUI(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -677,8 +677,8 @@ func TestToGraph_DefaultDiscoveredCountWithoutLocalID(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{}) - require.Equal(t, 2, data.Stats["devices_discovered"]) + _, stats := ToGraph(result, GraphOptions{}) + require.Equal(t, 2, stats.DevicesDiscovered) } func TestToGraph_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T) { @@ -708,7 +708,7 @@ func TestToGraph_AssignsDeterministicActorIDsAndLinkActorIDs(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -792,9 +792,9 @@ func TestToGraph_DeterministicAcrossRepeatedCalls(t *testing.T) { LocalDeviceID: "local-device", } - baseline := ToGraph(result, opts) + baseline, _ := ToGraph(result, opts) for range 10 { - next := ToGraph(result, opts) + next, _ := ToGraph(result, opts) require.Equal(t, baseline, next) } } @@ -826,7 +826,7 @@ func TestToGraph_DeduplicatesEndpointActorOverlappingManagedDevice(t *testing.T) }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -834,11 +834,11 @@ func TestToGraph_DeduplicatesEndpointActorOverlappingManagedDevice(t *testing.T) require.Len(t, data.Actors, 1) require.Equal(t, "device", data.Actors[0].ActorType) - require.Equal(t, 0, data.Stats["endpoints_total"]) - require.Equal(t, 1, data.Stats["actors_total"]) - require.Equal(t, 0, data.Stats["links_total"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 1, data.Stats["segments_suppressed"]) + require.Equal(t, 0, stats.EndpointsTotal) + require.Equal(t, 1, stats.ActorsTotal) + require.Equal(t, 0, stats.LinksTotal) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) + require.Equal(t, 1, stats.SegmentsSuppressed) } func TestCanonicalTopologyMatchKey_NormalizesEquivalentMACRepresentations(t *testing.T) { @@ -869,7 +869,7 @@ func TestToGraph_UsesDeterministicPrimaryManagementIP(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -899,7 +899,7 @@ func TestToGraph_KeepsDistinctActorsWhenMACDiffersDespiteSameSecondaryIdentity(t }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -962,7 +962,7 @@ func TestToGraph_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -982,10 +982,10 @@ func TestToGraph_MergesPairedAdjacenciesIntoBidirectionalLink(t *testing.T) { require.Equal(t, lldpMatchPassDefault, link.Metrics[adjacencyLabelPairPass]) require.Equal(t, true, link.Metrics["pair_consistent"]) - require.Equal(t, 1, data.Stats["links_total"]) - require.Equal(t, 1, data.Stats["links_lldp"]) - require.Equal(t, 1, data.Stats["links_bidirectional"]) - require.Equal(t, 0, data.Stats["links_unidirectional"]) + require.Equal(t, 1, stats.LinksTotal) + require.Equal(t, 1, stats.LinksLLDP) + require.Equal(t, 1, stats.LinksBidirectional) + require.Equal(t, 0, stats.LinksUnidirectional) } func TestToGraph_MergesPairedAdjacenciesPreservesRawAddressHints(t *testing.T) { @@ -1032,7 +1032,7 @@ func TestToGraph_MergesPairedAdjacenciesPreservesRawAddressHints(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1093,16 +1093,16 @@ func TestToGraph_MergesReversePairsWithoutDirectionalPairLabels(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) - require.Equal(t, 1, data.Stats["links_total"]) - require.Equal(t, 1, data.Stats["links_lldp"]) - require.Equal(t, 1, data.Stats["links_bidirectional"]) - require.Equal(t, 0, data.Stats["links_unidirectional"]) + require.Equal(t, 1, stats.LinksTotal) + require.Equal(t, 1, stats.LinksLLDP) + require.Equal(t, 1, stats.LinksBidirectional) + require.Equal(t, 0, stats.LinksUnidirectional) require.Len(t, data.Links, 1) link := data.Links[0] @@ -1146,7 +1146,7 @@ func TestToGraph_UnknownAdjacencyPortsRemainUnsetWithoutZeroFallback(t *testing. }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1188,7 +1188,7 @@ func TestToGraph_DropsAmbiguousEndpointSegmentLinks(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1207,13 +1207,13 @@ func TestToGraph_DropsAmbiguousEndpointSegmentLinks(t *testing.T) { require.Equal(t, 0, bridgeLinks) require.Equal(t, 0, fdbLinks) - require.Equal(t, 0, data.Stats["links_total"]) - require.Equal(t, 0, data.Stats["links_fdb"]) - require.Equal(t, 2, data.Stats["links_fdb_endpoint_candidates"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 2, data.Stats["links_fdb_endpoint_suppressed"]) - require.Equal(t, 1, data.Stats["endpoints_ambiguous_segments"]) - require.Equal(t, 2, data.Stats["segments_suppressed"]) + require.Equal(t, 0, stats.LinksTotal) + require.Equal(t, 0, stats.LinksFDB) + require.Equal(t, 2, stats.LinksFDBEndpointCandidates) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) + require.Equal(t, 2, stats.LinksFDBEndpointSuppressed) + require.Equal(t, 1, stats.EndpointsAmbiguousSegments) + require.Equal(t, 2, stats.SegmentsSuppressed) } func TestToGraph_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing.T) { @@ -1242,14 +1242,14 @@ func TestToGraph_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing.T) { }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:cd"), 0) - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1263,9 +1263,9 @@ func TestToGraph_ProbableConnectivityConnectsAmbiguousEndpoint(t *testing.T) { require.Equal(t, "probable_segment", topologyMetricString(fdbLinks[0].Metrics, "attachment_mode")) require.Equal(t, "low", topologyMetricString(fdbLinks[0].Metrics, "confidence")) - require.Equal(t, 1, data.Stats["links_probable"]) - require.Equal(t, 1, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 1, data.Stats["links_fdb_endpoint_suppressed"]) + require.Equal(t, 1, stats.LinksProbable) + require.Equal(t, 1, stats.LinksFDBEndpointEmitted) + require.Equal(t, 1, stats.LinksFDBEndpointSuppressed) } func TestToGraph_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t *testing.T) { @@ -1286,7 +1286,7 @@ func TestToGraph_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1296,7 +1296,7 @@ func TestToGraph_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t require.Equal(t, "", strings.TrimSpace(strictFDBLinks[0].State)) require.Equal(t, "", strings.TrimSpace(topologyMetricString(strictFDBLinks[0].Metrics, "inference"))) - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1308,7 +1308,7 @@ func TestToGraph_ProbableConnectivityDoesNotReclassifyStrictSinglePortEndpoint(t require.Equal(t, "", strings.TrimSpace(fdbLinks[0].State)) require.Equal(t, "", strings.TrimSpace(topologyMetricString(fdbLinks[0].Metrics, "inference"))) require.Equal(t, "direct", topologyMetricString(fdbLinks[0].Metrics, "attachment_mode")) - require.Equal(t, 0, data.Stats["links_probable"]) + require.Equal(t, 0, stats.LinksProbable) } func TestToGraph_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *testing.T) { @@ -1337,14 +1337,14 @@ func TestToGraph_ProbableConnectivityConnectsUnlinkedLLDPEndpoint(t *testing.T) }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:cf"), 0) - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1404,14 +1404,14 @@ func TestToGraph_ProbableConnectivityAvoidsExtraBridgePathForLLDPPeers(t *testin }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "70:49:a2:65:72:aa"), 0) - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1473,14 +1473,14 @@ func TestToGraph_ProbableConnectivityConnectsZeroCandidateEndpointUsingReporterH }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.Len(t, findFDBLinksByEndpointIP(strictData.Links, "10.0.0.99"), 0) - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1532,7 +1532,7 @@ func TestToGraph_ProbableConnectivityCreatesPortlessAttachmentForZeroCandidateEn }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1597,22 +1597,22 @@ func TestToGraph_InferenceStrategy_STPParentDoesNotSuppressFDBEndpointOwnership( }, } - baseline := ToGraph(result, GraphOptions{ + _, baselineStats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) - require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baseline.Stats["inference_strategy"]) - require.Greater(t, baseline.Stats["links_fdb_endpoint_emitted"].(int), 0) + require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baselineStats.InferenceStrategy) + require.Greater(t, baselineStats.LinksFDBEndpointEmitted, 0) - stpData := ToGraph(result, GraphOptions{ + _, stpStats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", InferenceStrategy: topologyInferenceStrategySTPParentTree, }) - require.Equal(t, topologyInferenceStrategySTPParentTree, stpData.Stats["inference_strategy"]) - require.Greater(t, stpData.Stats["links_fdb_endpoint_emitted"].(int), 0) + require.Equal(t, topologyInferenceStrategySTPParentTree, stpStats.InferenceStrategy) + require.Greater(t, stpStats.LinksFDBEndpointEmitted, 0) } func TestToGraph_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *testing.T) { @@ -1650,24 +1650,24 @@ func TestToGraph_InferenceStrategy_CDPHybridPrefersCDPBridgeLinks(t *testing.T) }, } - baseline := ToGraph(result, GraphOptions{ + _, baselineStats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) - require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baseline.Stats["inference_strategy"]) - require.Equal(t, 0, baseline.Stats["links_fdb_endpoint_emitted"]) + require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, baselineStats.InferenceStrategy) + require.Equal(t, 0, baselineStats.LinksFDBEndpointEmitted) - data := ToGraph(result, GraphOptions{ + _, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", InferenceStrategy: topologyInferenceStrategyCDPFDBHybrid, }) - require.Equal(t, topologyInferenceStrategyCDPFDBHybrid, data.Stats["inference_strategy"]) - require.Equal(t, 1, data.Stats["links_cdp"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) + require.Equal(t, topologyInferenceStrategyCDPFDBHybrid, stats.InferenceStrategy) + require.Equal(t, 1, stats.LinksCDP) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) } func TestPickProbableSegmentAnchorPortID_PrefersManagedPortWhenOwnerPointsToUnmanaged(t *testing.T) { @@ -1843,14 +1843,14 @@ func TestToGraph_ProbableConnectivityRecoversUnmanagedOverlapSuppression(t *test }, } - strictData := ToGraph(result, GraphOptions{ + strictData, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.Len(t, findFDBLinksByEndpointMAC(strictData.Links, "cc:cc:cc:cc:cc:cc"), 0) - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1910,14 +1910,14 @@ func TestToGraph_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *testing.T }, } - withoutCollapse := ToGraph(result, GraphOptions{ + withoutCollapse, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.NotNil(t, findActorByMAC(withoutCollapse.Actors, "9c:6b:00:7b:98:c7")) - withCollapse := ToGraph(result, GraphOptions{ + withCollapse, withCollapseStats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1925,7 +1925,7 @@ func TestToGraph_CollapseByIPPrunesSuppressedManagedOverlapEndpoint(t *testing.T }) require.NotNil(t, findActorByMAC(withCollapse.Actors, "9c:6b:00:7b:98:c6")) require.Nil(t, findActorByMAC(withCollapse.Actors, "9c:6b:00:7b:98:c7")) - require.Equal(t, 1, withCollapse.Stats["actors_unlinked_suppressed"]) + require.Equal(t, 1, withCollapseStats.ActorsUnlinkedSuppressed) } func TestToGraph_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *testing.T) { @@ -1952,7 +1952,7 @@ func TestToGraph_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *testing.T) }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -1961,8 +1961,8 @@ func TestToGraph_ReplacesKnownDeviceEndpointWithManagedDeviceEdge(t *testing.T) fdbLinks := findFDBLinksByDstSysName(data.Links, "switch-b") require.Len(t, fdbLinks, 1) require.Equal(t, "managed_device_overlap", fdbLinks[0].Metrics["attachment_mode"]) - require.Equal(t, 1, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_suppressed"]) + require.Equal(t, 1, stats.LinksFDBEndpointEmitted) + require.Equal(t, 0, stats.LinksFDBEndpointSuppressed) for _, actor := range data.Actors { if actor.ActorType != "endpoint" { @@ -1999,7 +1999,7 @@ func TestToGraph_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2008,8 +2008,8 @@ func TestToGraph_KnownDeviceOverlapUsesInterfaceMACAlias(t *testing.T) { fdbLinks := findFDBLinksByDstSysName(data.Links, "router-a") require.Len(t, fdbLinks, 1) require.Equal(t, "managed_device_overlap", fdbLinks[0].Metrics["attachment_mode"]) - require.Equal(t, 1, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_suppressed"]) + require.Equal(t, 1, stats.LinksFDBEndpointEmitted) + require.Equal(t, 0, stats.LinksFDBEndpointSuppressed) } func TestToGraph_DeviceActorIncludesInterfaceMACAliases(t *testing.T) { @@ -2029,7 +2029,7 @@ func TestToGraph_DeviceActorIncludesInterfaceMACAliases(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2062,7 +2062,7 @@ func TestToGraph_KeepsUnlinkedEndpointsAndDevices(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2081,8 +2081,8 @@ func TestToGraph_KeepsUnlinkedEndpointsAndDevices(t *testing.T) { } require.Equal(t, 1, deviceCount) require.Equal(t, 1, endpointCount) - require.Equal(t, 0, data.Stats["links_total"]) - require.Equal(t, 0, data.Stats["actors_unlinked_suppressed"]) + require.Equal(t, 0, stats.LinksTotal) + require.Equal(t, 0, stats.ActorsUnlinkedSuppressed) } func TestToGraph_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t *testing.T) { @@ -2122,16 +2122,16 @@ func TestToGraph_KeepsUnlinkedEndpointWhenIdentityOverlapsLinkedDevice(t *testin }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", }) require.NotNil(t, findActorByMAC(data.Actors, "cc:cc:cc:cc:cc:cc")) - require.Equal(t, 3, data.Stats["actors_total"]) - require.Equal(t, 1, data.Stats["links_total"]) - require.Equal(t, 0, data.Stats["actors_unlinked_suppressed"]) + require.Equal(t, 3, stats.ActorsTotal) + require.Equal(t, 1, stats.LinksTotal) + require.Equal(t, 0, stats.ActorsUnlinkedSuppressed) } func TestToGraph_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) { @@ -2153,7 +2153,7 @@ func TestToGraph_DisplayNamesPreferDNSThenIPThenMAC(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2237,7 +2237,7 @@ func TestToGraph_SegmentDisplayNameUsesParentPortPattern(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2295,7 +2295,7 @@ func TestToGraph_FDBOwnerInferencePrefersNonLLDPSide(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2335,7 +2335,7 @@ func TestToGraph_FDBOwnerInferenceUsesSingleMACPortRule(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2391,7 +2391,7 @@ func TestToGraph_FDBOwnerInferenceSuppressesManagedAliasSwitchFacingPorts(t *tes }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2449,7 +2449,7 @@ func TestToGraph_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2475,11 +2475,11 @@ func TestToGraph_SuppressesFDBEndpointsOnLLDPPorts(t *testing.T) { require.Equal(t, 0, segmentActors) require.Equal(t, 0, bridgeLinks) require.Equal(t, 0, fdbLinks) - require.Equal(t, 1, data.Stats["links_total"]) - require.Equal(t, 0, data.Stats["links_fdb"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_candidates"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_suppressed"]) + require.Equal(t, 1, stats.LinksTotal) + require.Equal(t, 0, stats.LinksFDB) + require.Equal(t, 0, stats.LinksFDBEndpointCandidates) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) + require.Equal(t, 0, stats.LinksFDBEndpointSuppressed) } func TestToGraph_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) { @@ -2508,7 +2508,7 @@ func TestToGraph_KeepsChassisPlaceholderDevicesAsDevices(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2659,15 +2659,15 @@ func TestToGraph_DeterministicTransitRuleSuppressesFDBOnLLDPPortInExperimental(t }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", InferenceStrategy: topologyInferenceStrategyFDBMinimumKnowledge, }) - require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, data.Stats["inference_strategy"]) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) + require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, stats.InferenceStrategy) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) require.Nil(t, findActorByType(data.Actors, "segment")) } @@ -2703,14 +2703,14 @@ func TestToGraph_DeterministicTransitRuleMatchesNumericLLDPPortToIfIndex(t *test }, } - data := ToGraph(result, GraphOptions{ + data, stats := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", InferenceStrategy: topologyInferenceStrategyFDBMinimumKnowledge, }) - require.Equal(t, 0, data.Stats["links_fdb_endpoint_emitted"]) + require.Equal(t, 0, stats.LinksFDBEndpointEmitted) require.Nil(t, findActorByType(data.Actors, "segment")) lldpLink := findLinkByProtocol(data.Links, "lldp") require.NotNil(t, lldpLink) @@ -2749,7 +2749,7 @@ func TestToGraph_SwitchFacingPortDoesNotSuppressEndpointOwnership(t *testing.T) }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", @@ -2857,7 +2857,7 @@ func TestToGraph_FDBOwnerInferenceUsesReporterMatrixRule(t *testing.T) { }, } - data := ToGraph(result, GraphOptions{ + data, _ := ToGraph(result, GraphOptions{ Source: "snmp", Layer: "2", View: "summary", diff --git a/src/go/pkg/l2topology/types.go b/src/go/pkg/l2topology/types.go index aa5baa51e4085b..f9ce267e84d082 100644 --- a/src/go/pkg/l2topology/types.go +++ b/src/go/pkg/l2topology/types.go @@ -64,7 +64,7 @@ type Result struct { Adjacencies []Adjacency Attachments []Attachment Enrichments []Enrichment - Stats map[string]any + Stats ResultStats SourceLabels map[string]string } diff --git a/src/go/pkg/topology/graph/graph_types.go b/src/go/pkg/topology/graph/graph_types.go index 2335ea11610303..f384276ac5969f 100644 --- a/src/go/pkg/topology/graph/graph_types.go +++ b/src/go/pkg/topology/graph/graph_types.go @@ -29,7 +29,6 @@ type Actor struct { Match Match `json:"match"` ParentMatch *Match `json:"parent_match,omitempty"` Attributes map[string]any `json:"attributes,omitempty"` - Derived map[string]any `json:"derived,omitempty"` Labels map[string]string `json:"labels,omitempty"` Tables map[string][]map[string]any `json:"tables,omitempty"` } @@ -54,45 +53,13 @@ type Link struct { Metrics map[string]any `json:"metrics,omitempty"` } -type FlowExporter struct { - IP string `json:"ip,omitempty"` - Name string `json:"name,omitempty"` - SamplingRate int `json:"sampling_rate,omitempty"` - FlowVersion string `json:"flow_version,omitempty"` -} - -type Flow struct { - Timestamp time.Time `json:"timestamp"` - DurationSec int `json:"duration_sec,omitempty"` - Exporter *FlowExporter `json:"exporter,omitempty"` - Src LinkEndpoint `json:"src"` - Dst LinkEndpoint `json:"dst"` - Key map[string]any `json:"key,omitempty"` - Metrics map[string]any `json:"metrics,omitempty"` -} - -type LiveTopN struct { - Enabled bool `json:"enabled,omitempty"` - Limit int `json:"limit,omitempty"` - SortBy string `json:"sort_by,omitempty"` -} - -type IPPolicy struct { - PublicAllowlist []string `json:"public_allowlist,omitempty"` - LiveTopN *LiveTopN `json:"live_top_n,omitempty"` -} - type Graph struct { - SchemaVersion string `json:"schema_version"` - Source string `json:"source,omitempty"` - Layer string `json:"layer,omitempty"` - AgentID string `json:"agent_id"` - CollectedAt time.Time `json:"collected_at"` - View string `json:"view,omitempty"` - IPPolicy *IPPolicy `json:"ip_policy,omitempty"` - Actors []Actor `json:"actors,omitempty"` - Links []Link `json:"links,omitempty"` - Flows []Flow `json:"flows,omitempty"` - Stats map[string]any `json:"stats,omitempty"` - Metrics map[string]any `json:"metrics,omitempty"` + SchemaVersion string `json:"schema_version"` + Source string `json:"source,omitempty"` + Layer string `json:"layer,omitempty"` + AgentID string `json:"agent_id"` + CollectedAt time.Time `json:"collected_at"` + View string `json:"view,omitempty"` + Actors []Actor `json:"actors,omitempty"` + Links []Link `json:"links,omitempty"` } diff --git a/src/go/pkg/topology/graph/graph_types_test.go b/src/go/pkg/topology/graph/graph_types_test.go index f79b869b3975bd..84fd7141fa7f98 100644 --- a/src/go/pkg/topology/graph/graph_types_test.go +++ b/src/go/pkg/topology/graph/graph_types_test.go @@ -19,10 +19,6 @@ func TestGraphJSONShape(t *testing.T) { AgentID: "agent-id", CollectedAt: tm, View: "summary", - IPPolicy: &IPPolicy{ - PublicAllowlist: []string{"10.0.0.0/8"}, - LiveTopN: &LiveTopN{Enabled: true, Limit: 10, SortBy: "bytes"}, - }, Actors: []Actor{{ ActorID: "actor-a", ActorType: "router", @@ -46,7 +42,6 @@ func TestGraphJSONShape(t *testing.T) { }, ParentMatch: &Match{SysName: "parent"}, Attributes: map[string]any{"role": "core"}, - Derived: map[string]any{"score": float64(1)}, Labels: map[string]string{"site": "lab"}, Tables: map[string][]map[string]any{"ports": {{"if_name": "eth0"}}}, }}, @@ -64,17 +59,6 @@ func TestGraphJSONShape(t *testing.T) { LastSeen: &tm, Metrics: map[string]any{"cost": float64(10)}, }}, - Flows: []Flow{{ - Timestamp: tm, - DurationSec: 60, - Exporter: &FlowExporter{IP: "10.0.0.1", Name: "router-a", SamplingRate: 1000, FlowVersion: "v9"}, - Src: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.10"}}}, - Dst: LinkEndpoint{Match: Match{IPAddresses: []string{"10.0.0.20"}}}, - Key: map[string]any{"protocol": "tcp"}, - Metrics: map[string]any{"bytes": float64(42)}, - }}, - Stats: map[string]any{"links": float64(1)}, - Metrics: map[string]any{"refresh": float64(1)}, } bs, err := json.Marshal(payload) @@ -87,10 +71,6 @@ func TestGraphJSONShape(t *testing.T) { "agent_id":"agent-id", "collected_at":"2026-06-20T08:09:10Z", "view":"summary", - "ip_policy":{ - "public_allowlist":["10.0.0.0/8"], - "live_top_n":{"enabled":true,"limit":10,"sort_by":"bytes"} - }, "actors":[{ "actor_id":"actor-a", "actor_type":"router", @@ -114,7 +94,6 @@ func TestGraphJSONShape(t *testing.T) { }, "parent_match":{"sys_name":"parent"}, "attributes":{"role":"core"}, - "derived":{"score":1}, "labels":{"site":"lab"}, "tables":{"ports":[{"if_name":"eth0"}]} }], @@ -131,17 +110,6 @@ func TestGraphJSONShape(t *testing.T) { "discovered_at":"2026-06-20T08:09:10Z", "last_seen":"2026-06-20T08:09:10Z", "metrics":{"cost":10} - }], - "flows":[{ - "timestamp":"2026-06-20T08:09:10Z", - "duration_sec":60, - "exporter":{"ip":"10.0.0.1","name":"router-a","sampling_rate":1000,"flow_version":"v9"}, - "src":{"match":{"ip_addresses":["10.0.0.10"]}}, - "dst":{"match":{"ip_addresses":["10.0.0.20"]}}, - "key":{"protocol":"tcp"}, - "metrics":{"bytes":42} - }], - "stats":{"links":1}, - "metrics":{"refresh":1} + }] }`, string(bs)) } diff --git a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go index f6e78dd9153cc3..e77b84ad79b974 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go +++ b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1.go @@ -133,7 +133,7 @@ func snmpTopologyToV1(data topologyData) (topologyv1.Data, error) { Actors: actorRows, Links: linkRows, Evidence: evidenceSections, - Stats: cloneAnyMapForTopologyV1(data.Stats), + Stats: topologyStatsToV1(data.Stats), } if payload.CollectedAt.IsZero() { payload.CollectedAt = time.Now().UTC() diff --git a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_golden_test.go b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_golden_test.go new file mode 100644 index 00000000000000..2da7905d508644 --- /dev/null +++ b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_golden_test.go @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package snmptopology + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "testing" + "time" + + topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1" + "github.com/stretchr/testify/require" +) + +var updateSNMPTopologyV1Golden = flag.Bool( + "update-snmp-topology-v1-golden", + false, + "rewrite snmp_topology normalized topology.v1 golden testdata", +) + +const snmpTopologyV1GoldenPayloadPath = "testdata/topology_v1_normalized_golden.json" + +// Refresh with: +// go test -count=1 ./plugin/go.d/collector/snmp_topology -run TestSNMPTopologyToV1_NormalizedGolden -update-snmp-topology-v1-golden +func TestSNMPTopologyToV1_NormalizedGolden(t *testing.T) { + data, err := snmpTopologyToV1(snmpTopologyV1GoldenInput()) + require.NoError(t, err) + require.NoError(t, validateTopologyV1Data(data)) + + normalized := normalizeTopologyV1GoldenData(t, data) + actual := canonicalTopologyV1GoldenJSON(t, normalized) + if *updateSNMPTopologyV1Golden { + require.NoError(t, os.MkdirAll(filepath.Dir(snmpTopologyV1GoldenPayloadPath), 0o755)) + require.NoError(t, os.WriteFile(snmpTopologyV1GoldenPayloadPath, []byte(actual), 0o644)) + } + + expected, err := os.ReadFile(snmpTopologyV1GoldenPayloadPath) + require.NoError(t, err) + if string(expected) != actual { + t.Fatalf("normalized golden changed; inspect the payload below and rerun with -update-snmp-topology-v1-golden if intentional:\n%s", actual) + } +} + +func snmpTopologyV1GoldenInput() topologyData { + collectedAt := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC) + discoveredAt := time.Date(2026, time.January, 2, 2, 4, 5, 0, time.UTC) + lastSeen := time.Date(2026, time.January, 2, 3, 3, 5, 0, time.UTC) + + return topologyData{ + SchemaVersion: topologySchemaVersion, + Source: "snmp_topology_test", + Layer: "multi", + AgentID: "golden-agent", + CollectedAt: collectedAt, + View: "summary", + Actors: []topologyActor{ + { + ActorID: "switch-a", + ActorType: "switch", + Layer: "2", + Source: "snmp", + Match: topologyMatch{ + ChassisIDs: []string{"aa:bb:cc:00:00:01"}, + MacAddresses: []string{"aa:bb:cc:00:00:01"}, + IPAddresses: []string{"192.0.2.10"}, + Hostnames: []string{"switch-a.example.test"}, + DNSNames: []string{"switch-a.example.test"}, + SysObjectID: "1.3.6.1.4.1.9.1.1208", + SysName: "switch-a", + }, + Attributes: map[string]any{ + "capabilities": []string{"bridge", "router"}, + "cdp_neighbor_count": uint64(1), + "chart_id_prefix": "snmp_switch_a", + "display_name": "Switch A", + "endpoints_total": uint64(12), + "fdb_total_macs": uint64(42), + "lldp_neighbor_count": uint64(1), + "management_ip": "192.0.2.10", + "model": "Catalyst 9300", + "netdata_host_id": "host-switch-a", + "ports_total": uint64(2), + "protocols": []string{"lldp", "cdp", "snmp"}, + "sys_contact": "noc@example.test", + "sys_descr": "Synthetic switch fixture", + "sys_location": "lab-rack-1", + "vendor": "Cisco", + "vlan_count": uint64(3), + tagOSPFRouterID: "10.255.0.1", + "chart_context_prefix": "snmp.switch_a", + }, + Labels: map[string]string{ + "role": "distribution", + "site": "lab", + }, + Tables: map[string][]map[string]any{ + "ports": { + { + "admin_status": "up", + "duplex": "full", + "fdb_mac_count": uint64(12), + "if_alias": "to router-a", + "if_descr": "GigabitEthernet1/0/1", + "if_index": uint64(101), + "if_name": "Gi1/0/1", + "last_change": "2026-01-02T01:02:03Z", + "link_count": uint64(1), + "link_mode": "trunk", + "link_mode_confidence": "high", + "link_mode_sources": []string{"if_mib", "lldp"}, + "mac": "aa:bb:cc:00:01:01", + "neighbor_count": uint64(1), + "neighbors": []map[string]any{ + { + "protocol": "lldp", + "remote_chassis_id": "aa:bb:cc:00:00:02", + "remote_port": "Gi0/0", + }, + }, + "oper_status": "up", + "port_id": "Gi1/0/1", + "port_type": "ethernet", + "speed": uint64(1000000000), + "stp_state": "forwarding", + "topology_role": "uplink", + "topology_role_confidence": "medium", + "topology_role_sources": []string{"stp", "fdb"}, + "vendor_note": "kept in actor_ports.extra until typed ports replace it", + "vlan_ids": []string{"10", "20"}, + "vlans": []map[string]any{ + {"id": "10", "name": "users"}, + {"id": "20", "name": "servers"}, + }, + }, + }, + "inventory": { + { + "enabled": true, + "sensor_labels": []any{"ambient", "rack"}, + "slot": "1", + "temperature_c": 42.5, + }, + }, + }, + }, + { + ActorID: "router-a", + ActorType: "router", + Layer: "3", + Source: "snmp", + Match: topologyMatch{ + ChassisIDs: []string{"aa:bb:cc:00:00:02"}, + IPAddresses: []string{ + "198.51.100.1", + "203.0.113.1", + }, + SysObjectID: "1.3.6.1.4.1.9.1.1745", + SysName: "router-a", + }, + Attributes: map[string]any{ + "capabilities": []string{"router"}, + "display_name": "Router A", + "management_ip": "198.51.100.1", + "model": "ASR 1001", + "ports_total": uint64(4), + "protocols_collected": []string{"ip_mib", "ospf_mib", "bgp_mib"}, + "sys_descr": "Synthetic router A", + "vendor": "Cisco", + tagOSPFRouterID: "10.255.0.1", + }, + Labels: map[string]string{ + "role": "edge", + }, + Tables: map[string][]map[string]any{ + "bgp_peers": { + { + "admin_status": "enabled", + "bgp_version": "4", + "description": "router-b transit", + "established_uptime": uint64(7200), + "last_received_update_age": uint64(15), + "local_as": "64512", + "local_identifier": "10.255.0.1", + "local_ip": "203.0.113.1", + "neighbor_ip": "203.0.113.2", + "peer_identifier": "10.255.0.2", + "peer_type": "external", + "remote_actor_id": "router-b", + "remote_as": "64513", + "routing_instance": "default", + "source": "bgp_mib", + "state": "established", + }, + }, + "ospf_neighbors": { + { + "addressless_index": "0", + "local_ip": "198.51.100.1", + "local_router_id": "10.255.0.1", + "neighbor_ip": "198.51.100.2", + "neighbor_router_id": "10.255.0.2", + "remote_actor_id": "router-b", + "source": "ospf_mib", + "state": "full", + "subnet": "198.51.100.0/30", + }, + }, + "ports": { + { + "if_alias": "to switch-a", + "if_index": uint64(201), + "if_name": "Gi0/0", + "link_mode": "trunk", + "oper_status": "up", + "port_id": "Gi0/0", + "topology_role": "uplink", + "vendor_context": "router uplink debug field", + }, + }, + }, + }, + { + ActorID: "router-b", + ActorType: "router", + Layer: "3", + Source: "snmp", + Match: topologyMatch{ + ChassisIDs: []string{"aa:bb:cc:00:00:03"}, + IPAddresses: []string{"198.51.100.2", "203.0.113.2"}, + SysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.131", + SysName: "router-b", + }, + Attributes: map[string]any{ + "display_name": "Router B", + "management_ip": "198.51.100.2", + "model": "MX204", + "sys_descr": "Synthetic router B", + "vendor": "Juniper", + tagOSPFRouterID: "10.255.0.2", + }, + Tables: map[string][]map[string]any{ + "bgp_peers": { + { + "admin_status": "enabled", + "bgp_version": "4", + "description": "router-a transit", + "established_uptime": uint64(7100), + "last_received_update_age": uint64(20), + "local_as": "64513", + "local_identifier": "10.255.0.2", + "local_ip": "203.0.113.2", + "neighbor_ip": "203.0.113.1", + "peer_identifier": "10.255.0.1", + "peer_type": "external", + "remote_actor_id": "router-a", + "remote_as": "64512", + "routing_instance": "default", + "source": "bgp_mib", + "state": "established", + }, + }, + "ospf_neighbors": { + { + "addressless_index": "0", + "local_ip": "198.51.100.2", + "local_router_id": "10.255.0.2", + "neighbor_ip": "198.51.100.1", + "neighbor_router_id": "10.255.0.1", + "remote_actor_id": "router-a", + "source": "ospf_mib", + "state": "full", + "subnet": "198.51.100.0/30", + }, + }, + }, + }, + { + ActorID: "server-a", + ActorType: "endpoint", + Layer: "2", + Source: "fdb", + Match: topologyMatch{ + MacAddresses: []string{"00:11:22:33:44:55"}, + IPAddresses: []string{"192.0.2.50"}, + Hostnames: []string{"server-a"}, + }, + Attributes: map[string]any{ + "display_name": "Server A", + "learned_sources": []string{"fdb", "arp"}, + }, + Labels: map[string]string{ + "role": "application", + }, + }, + { + ActorID: "segment-198-51-100-0-30", + ActorType: "segment", + Layer: "3", + Source: "ip_mib", + Match: topologyMatch{ + IPAddresses: []string{"198.51.100.0"}, + }, + Attributes: map[string]any{ + "display_name": "198.51.100.0/30", + }, + }, + }, + Links: []topologyLink{ + { + Layer: "2", + Protocol: "lldp", + LinkType: "lldp", + Direction: "observed", + State: "up", + SrcActorID: "switch-a", + DstActorID: "router-a", + Src: topologyLinkEndpoint{ + Attributes: map[string]any{ + "if_index": uint64(101), + "if_name": "Gi1/0/1", + "management_ip": "192.0.2.10", + "port_id": "Gi1/0/1", + }, + }, + Dst: topologyLinkEndpoint{ + Attributes: map[string]any{ + "if_index": uint64(201), + "if_name": "Gi0/0", + "management_ip": "198.51.100.1", + "port_id": "Gi0/0", + }, + }, + DiscoveredAt: &discoveredAt, + LastSeen: &lastSeen, + Metrics: map[string]any{ + "attachment_mode": "direct", + "confidence": "high", + "inference": "lldp", + }, + }, + { + Layer: "2", + Protocol: "fdb", + LinkType: "bridge", + Direction: "observed", + State: "probable", + SrcActorID: "switch-a", + DstActorID: "server-a", + Src: topologyLinkEndpoint{ + Attributes: map[string]any{ + "if_index": uint64(102), + "if_name": "Gi1/0/2", + "port_id": "Gi1/0/2", + }, + }, + Dst: topologyLinkEndpoint{ + Attributes: map[string]any{ + "mac": "00:11:22:33:44:55", + }, + }, + Metrics: map[string]any{ + "attachment_mode": "probable_host", + "confidence": "low", + "inference": "probable", + }, + }, + { + Layer: "3", + Protocol: topologyL3SubnetLinkType, + LinkType: topologyL3SubnetLinkType, + Direction: "observed", + SrcActorID: "router-a", + DstActorID: "router-b", + Src: topologyLinkEndpoint{ + Attributes: map[string]any{ + "if_index": uint64(301), + "if_name": "xe-0/0/0", + "ip": "198.51.100.1", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": uint64(30), + "source": "ip_mib", + "subnet": "198.51.100.0/30", + }, + }, + Dst: topologyLinkEndpoint{ + Attributes: map[string]any{ + "if_index": uint64(401), + "if_name": "xe-0/0/1", + "ip": "198.51.100.2", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": uint64(30), + "source": "ip_mib", + "subnet": "198.51.100.0/30", + }, + }, + Metrics: map[string]any{ + "attachment_mode": "logical_l3_subnet", + "inference": "shared_subnet", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": uint64(30), + "source": "ip_mib", + "subnet": "198.51.100.0/30", + }, + }, + { + Layer: "3", + Protocol: topologyOSPFAdjacencyLinkType, + LinkType: topologyOSPFAdjacencyLinkType, + Direction: "observed", + State: "full", + SrcActorID: "router-a", + DstActorID: "router-b", + Src: topologyLinkEndpoint{ + Attributes: map[string]any{ + "ip": "198.51.100.1", + "router_id": "10.255.0.1", + "source": "ospf_mib", + "subnet": "198.51.100.0/30", + }, + }, + Dst: topologyLinkEndpoint{ + Attributes: map[string]any{ + "ip": "198.51.100.2", + "router_id": "10.255.0.2", + "source": "ospf_mib", + "subnet": "198.51.100.0/30", + }, + }, + Metrics: map[string]any{ + "attachment_mode": "logical_ospf_adjacency", + "inference": "ospf_neighbor", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": uint64(30), + "source": "ospf_mib", + "subnet": "198.51.100.0/30", + }, + }, + { + Layer: "3", + Protocol: topologyBGPAdjacencyLinkType, + LinkType: topologyBGPAdjacencyLinkType, + Direction: "observed", + State: "established", + SrcActorID: "router-a", + DstActorID: "router-b", + Src: topologyLinkEndpoint{ + Attributes: map[string]any{ + "as": "64512", + "bgp_identifier": "10.255.0.1", + "ip": "203.0.113.1", + "source": "bgp_mib", + }, + }, + Dst: topologyLinkEndpoint{ + Attributes: map[string]any{ + "as": "64513", + "bgp_identifier": "10.255.0.2", + "ip": "203.0.113.2", + "source": "bgp_mib", + }, + }, + Metrics: map[string]any{ + "attachment_mode": "logical_bgp_adjacency", + "inference": "bgp_peer", + "routing_instance": "default", + "source": "bgp_mib", + }, + }, + }, + Stats: topologyStats{ + Shape: topologyShapeStats{ + ActorsCollapsedByIP: 1, + ActorsMapTypeSuppressed: 2, + MapType: topologyMapTypeHighConfidenceInferred, + }, + HasShape: true, + Focus: topologyFocusStats{ + ManagedSNMPDeviceFocus: topologyManagedFocusAllDevices, + Depth: topologyFocusDepth{All: true}, + }, + HasFocus: true, + L3: topologyL3EnrichmentStats{ + emittedLinks: 1, + }, + HasL3: true, + OSPF: topologyOSPFEnrichmentStats{ + emittedLinks: 1, + }, + HasOSPF: true, + BGP: topologyBGPEnrichmentStats{ + emittedLinks: 1, + }, + HasBGP: true, + Recomputed: topologyRecomputedStats{ + ActorsTotal: 5, + LinksTotal: 5, + LinksProbable: 1, + L3SubnetVisibleLinks: 1, + OSPFAdjacencyVisibleLinks: 1, + BGPAdjacencyVisibleLinks: 1, + }, + HasComputed: true, + }, + } +} + +type normalizedTopologyV1GoldenData struct { + SchemaVersion string `json:"schema_version"` + Producer topologyv1.Producer `json:"producer"` + CollectedAt string `json:"collected_at"` + ValidAfter string `json:"valid_after,omitempty"` + ValidUntil string `json:"valid_until,omitempty"` + View *topologyv1.View `json:"view,omitempty"` + Types topologyv1.TypeRegistry `json:"types"` + Presentation *topologyv1.Presentation `json:"presentation,omitempty"` + Correlation any `json:"correlation,omitempty"` + Actors normalizedTopologyV1GoldenTable `json:"actors"` + Links normalizedTopologyV1GoldenTable `json:"links"` + Evidence map[string]normalizedTopologyV1GoldenRef `json:"evidence,omitempty"` + Tables *normalizedTopologyV1GoldenDetailTables `json:"tables,omitempty"` + Overlays any `json:"overlays,omitempty"` + Stats any `json:"stats,omitempty"` + Extensions any `json:"extensions,omitempty"` +} + +type normalizedTopologyV1GoldenDetailTables struct { + Actor map[string]normalizedTopologyV1GoldenRef `json:"actor,omitempty"` +} + +type normalizedTopologyV1GoldenRef struct { + Type string `json:"type"` + Table normalizedTopologyV1GoldenTable `json:"table"` +} + +type normalizedTopologyV1GoldenTable struct { + Columns []string `json:"columns"` + Rows []map[string]any `json:"rows"` +} + +type topologyV1GoldenRefs struct { + actors []string + links []string +} + +func normalizeTopologyV1GoldenData(t *testing.T, data topologyv1.Data) normalizedTopologyV1GoldenData { + t.Helper() + + refs := topologyV1GoldenRefs{} + actorRows := topologyV1GoldenTableRows(t, data, data.Actors, refs) + refs.actors = topologyV1GoldenActorRefs(t, actorRows) + linkRows := topologyV1GoldenTableRows(t, data, data.Links, refs) + refs.links = topologyV1GoldenLinkRefs(t, linkRows) + + normalized := normalizedTopologyV1GoldenData{ + SchemaVersion: data.SchemaVersion, + Producer: data.Producer, + CollectedAt: data.CollectedAt.UTC().Format(time.RFC3339Nano), + ValidAfter: topologyV1GoldenTimeString(data.ValidAfter), + ValidUntil: topologyV1GoldenTimeString(data.ValidUntil), + View: data.View, + Types: data.Types, + Presentation: data.Presentation, + Actors: normalizedTopologyV1GoldenTable{ + Columns: topologyV1GoldenColumnSummaries(data.Actors.Columns), + Rows: topologyV1GoldenSortedRows(actorRows), + }, + Links: normalizedTopologyV1GoldenTable{ + Columns: topologyV1GoldenColumnSummaries(data.Links.Columns), + Rows: topologyV1GoldenSortedRows(linkRows), + }, + Stats: topologyV1GoldenNormalizeValue(t, data.Stats), + } + if data.Correlation != nil { + normalized.Correlation = topologyV1GoldenNormalizeValue(t, data.Correlation) + } + if data.Overlays != nil { + normalized.Overlays = topologyV1GoldenNormalizeValue(t, data.Overlays) + } + if len(data.Extensions) > 0 { + normalized.Extensions = topologyV1GoldenNormalizeValue(t, data.Extensions) + } + + if len(data.Evidence) > 0 { + normalized.Evidence = make(map[string]normalizedTopologyV1GoldenRef, len(data.Evidence)) + for key, section := range data.Evidence { + normalized.Evidence[key] = normalizedTopologyV1GoldenRef{ + Type: section.Type, + Table: topologyV1GoldenNormalizeTable(t, data, section.Table, refs), + } + } + } + + if data.Tables != nil && len(data.Tables.Actor) > 0 { + normalized.Tables = &normalizedTopologyV1GoldenDetailTables{ + Actor: make(map[string]normalizedTopologyV1GoldenRef, len(data.Tables.Actor)), + } + for key, detail := range data.Tables.Actor { + normalized.Tables.Actor[key] = normalizedTopologyV1GoldenRef{ + Type: detail.Type, + Table: topologyV1GoldenNormalizeTable(t, data, detail.Table, refs), + } + } + } + + return normalized +} + +func topologyV1GoldenNormalizeTable( + t *testing.T, + data topologyv1.Data, + table topologyv1.Table, + refs topologyV1GoldenRefs, +) normalizedTopologyV1GoldenTable { + t.Helper() + + return normalizedTopologyV1GoldenTable{ + Columns: topologyV1GoldenColumnSummaries(table.Columns), + Rows: topologyV1GoldenSortedRows(topologyV1GoldenTableRows(t, data, table, refs)), + } +} + +func topologyV1GoldenTableRows( + t *testing.T, + data topologyv1.Data, + table topologyv1.Table, + refs topologyV1GoldenRefs, +) []map[string]any { + t.Helper() + + rows := make([]map[string]any, table.Rows) + for row := range rows { + rows[row] = make(map[string]any, len(table.Columns)) + } + for columnIndex, column := range table.Columns { + values := topologyV1DecodeColumnValues(t, table, columnIndex) + require.Len(t, values, table.Rows) + for rowIndex, value := range values { + decoded := topologyV1GoldenDecodeValue(t, data, column, value, refs) + if decoded != nil { + rows[rowIndex][column.ID] = decoded + } + } + } + return rows +} + +func topologyV1GoldenColumnSummaries(columns []topologyv1.Column) []string { + out := make([]string, len(columns)) + for i, column := range columns { + summary := column.ID + ":" + column.Type + if column.Dictionary != "" { + summary += ":dict=" + column.Dictionary + } + if column.Nullable { + summary += ":nullable" + } + if column.Role != "" { + summary += ":role=" + column.Role + } + if column.Aggregation != "" { + summary += ":aggregation=" + column.Aggregation + } + if column.Unit != "" { + summary += ":unit=" + column.Unit + } + out[i] = summary + } + return out +} + +func topologyV1GoldenDecodeValue( + t *testing.T, + data topologyv1.Data, + column topologyv1.Column, + value any, + refs topologyV1GoldenRefs, +) any { + t.Helper() + + if value == nil { + return nil + } + + switch column.Type { + case "string_ref", "ip_ref", "mac_ref": + return topologyV1GoldenDictionaryValue(t, data, column, value) + case "actor_ref": + return topologyV1GoldenRefValue(t, refs.actors, value, "actor") + case "link_ref": + return topologyV1GoldenRefValue(t, refs.links, value, "link") + default: + return topologyV1GoldenNormalizeValue(t, value) + } +} + +func topologyV1GoldenDictionaryValue(t *testing.T, data topologyv1.Data, column topologyv1.Column, value any) any { + t.Helper() + + ref := topologyV1GoldenIntValue(t, value) + dict := data.Dictionaries[column.Dictionary] + require.NotNilf(t, dict, "missing dictionary %q for column %q", column.Dictionary, column.ID) + require.GreaterOrEqual(t, ref, 0) + require.Less(t, ref, len(dict)) + return topologyV1GoldenNormalizeValue(t, dict[ref]) +} + +func topologyV1GoldenRefValue(t *testing.T, values []string, value any, kind string) any { + t.Helper() + + ref := topologyV1GoldenIntValue(t, value) + require.GreaterOrEqual(t, ref, 0) + require.Lessf(t, ref, len(values), "%s ref %d out of bounds", kind, ref) + return values[ref] +} + +func topologyV1GoldenIntValue(t *testing.T, value any) int { + t.Helper() + + switch v := value.(type) { + case int: + return v + case int8: + return int(v) + case int16: + return int(v) + case int32: + return int(v) + case int64: + return int(v) + case uint: + return int(v) + case uint8: + return int(v) + case uint16: + return int(v) + case uint32: + return int(v) + case uint64: + return int(v) + case float64: + return int(v) + default: + require.Failf(t, "invalid reference value", "got %T", value) + return 0 + } +} + +func topologyV1GoldenActorRefs(t *testing.T, rows []map[string]any) []string { + t.Helper() + + refs := make([]string, len(rows)) + for i, row := range rows { + id, ok := row["id"].(string) + require.Truef(t, ok && id != "", "actor row %d has invalid id %v", i, row["id"]) + refs[i] = id + } + return refs +} + +func topologyV1GoldenLinkRefs(t *testing.T, rows []map[string]any) []string { + t.Helper() + + refs := make([]string, len(rows)) + seen := make(map[string]int, len(rows)) + for i, row := range rows { + key := fmt.Sprintf("%v -> %v | %v | %v | %v | %v | %v", + row["src_actor"], + row["dst_actor"], + row["type"], + row["protocol"], + row["state"], + row["src_port_name"], + row["dst_port_name"], + ) + seen[key]++ + if seen[key] > 1 { + key = fmt.Sprintf("%s #%d", key, seen[key]) + } + refs[i] = key + } + return refs +} + +func topologyV1GoldenSortedRows(rows []map[string]any) []map[string]any { + out := append([]map[string]any(nil), rows...) + sort.Slice(out, func(i, j int) bool { + return topologyV1GoldenSortKey(out[i]) < topologyV1GoldenSortKey(out[j]) + }) + return out +} + +func topologyV1GoldenSortKey(row map[string]any) string { + bs, err := json.Marshal(row) + if err != nil { + panic(err) + } + return string(bs) +} + +func topologyV1GoldenNormalizeValue(t *testing.T, value any) any { + t.Helper() + + if value == nil { + return nil + } + bs, err := json.Marshal(value) + require.NoError(t, err) + var normalized any + require.NoError(t, json.Unmarshal(bs, &normalized)) + return normalized +} + +func canonicalTopologyV1GoldenJSON(t *testing.T, value any) string { + t.Helper() + + bs, err := json.Marshal(value) + require.NoError(t, err) + var normalized any + require.NoError(t, json.Unmarshal(bs, &normalized)) + + var out bytes.Buffer + enc := json.NewEncoder(&out) + enc.SetIndent("", " ") + require.NoError(t, enc.Encode(normalized)) + return out.String() +} + +func topologyV1GoldenTimeString(value *time.Time) string { + if value == nil { + return "" + } + return value.UTC().Format(time.RFC3339Nano) +} diff --git a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_stats_test.go b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_stats_test.go new file mode 100644 index 00000000000000..ad6d51bb40cc82 --- /dev/null +++ b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_stats_test.go @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package snmptopology + +import ( + "testing" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp" + "github.com/stretchr/testify/require" +) + +func TestSNMPTopologyToV1_RealPipelineStatsCensus(t *testing.T) { + registry := newTopologyRegistry() + + cacheA := newTopologyStatsCensusRouterCache(t, "router-a", "00:11:22:33:44:55", "10.0.0.1", "1.1.1.1", "198.51.100.1", "2") + cacheA.updateTopologyCacheEntry(ddsnmp.Metric{ + TopologyKind: ddsnmp.KindOSPFNeighbor, + Tags: map[string]string{ + tagOSPFNeighborRouterID: "2.2.2.2", + tagOSPFNeighborIP: "198.51.100.2", + tagOSPFNeighborAddresslessIndex: "0", + tagOSPFNeighborState: "full", + }, + }) + cacheA.bgpPeersByKey["peer-router-b"] = topologyBGPPeer{ + RoutingInstance: "default", + NeighborIP: "198.51.100.2", + RemoteAS: "65002", + LocalIP: "198.51.100.1", + LocalAS: "65001", + LocalIdentifier: "1.1.1.1", + PeerIdentifier: "2.2.2.2", + State: "established", + } + + cacheB := newTopologyStatsCensusRouterCache(t, "router-b", "aa:bb:cc:dd:ee:ff", "10.0.0.2", "2.2.2.2", "198.51.100.2", "7") + cacheB.bgpPeersByKey["peer-router-a"] = topologyBGPPeer{ + RoutingInstance: "default", + NeighborIP: "198.51.100.1", + RemoteAS: "65001", + LocalIP: "198.51.100.2", + LocalAS: "65002", + LocalIdentifier: "2.2.2.2", + PeerIdentifier: "1.1.1.1", + State: "established", + } + + registry.register(cacheA) + registry.register(cacheB) + + options := defaultTopologyQueryOptionsForTest() + options.MapType = topologyMapTypeAllDevicesLowConfidence + data, ok := snapshotTopologyRegistryForTestWithOptions(registry, options) + require.True(t, ok) + + payload, err := snmpTopologyToV1(data) + require.NoError(t, err) + require.NotNil(t, payload.Stats) + + require.ElementsMatch(t, []string{ + "devices_total", + "devices_discovered", + "links_total", + "links_lldp", + "links_cdp", + "links_stp", + "links_bidirectional", + "links_unidirectional", + "links_fdb", + "links_fdb_endpoint_candidates", + "links_fdb_endpoint_emitted", + "links_fdb_endpoint_suppressed", + "endpoints_ambiguous_segments", + "links_arp", + "links_probable", + "segments_suppressed", + "actors_total", + "actors_unlinked_suppressed", + "endpoints_total", + "inference_strategy", + "attachments_total", + "attachments_fdb", + "enrichments_total", + "enrichments_arp_nd", + "bridge_domains_total", + "identity_alias_endpoints_mapped", + "identity_alias_endpoints_ambiguous_mac", + "identity_alias_ips_merged", + "identity_alias_ips_conflict_skipped", + "actors_collapsed_by_ip", + "actors_non_ip_inferred_suppressed", + "actors_map_type_suppressed", + "segments_sparse_suppressed", + "map_type", + "managed_snmp_device_focus", + "depth", + "actors_focus_depth_filtered", + "links_focus_depth_filtered", + "l3_subnet_candidate_subnets", + "l3_subnet_candidate_links", + "l3_subnet_emitted_links", + "l3_subnet_suppressed_invalid", + "l3_subnet_suppressed_unsupported_prefix", + "l3_subnet_suppressed_duplicate_ip", + "l3_subnet_suppressed_self_link", + "l3_subnet_suppressed_unmatched", + "l3_subnet_suppressed_multi_access", + "l3_subnet_suppressed_unresolved_actor", + "l3_subnet_suppressed_self_actor", + "l3_subnet_suppressed_duplicate_link", + "l3_subnet_visible_links", + "ospf_neighbor_rows", + "ospf_neighbor_detail_rows", + "ospf_adjacency_emitted_links", + "ospf_adjacency_suppressed_non_full_state", + "ospf_adjacency_suppressed_unresolved_local", + "ospf_adjacency_suppressed_unresolved_neighbor", + "ospf_adjacency_suppressed_self_actor", + "ospf_adjacency_suppressed_duplicate_link", + "ospf_adjacency_visible_links", + "bgp_peer_rows", + "bgp_peer_detail_rows", + "bgp_adjacency_emitted_links", + "bgp_adjacency_suppressed_non_established_state", + "bgp_adjacency_suppressed_unresolved_local", + "bgp_adjacency_suppressed_unresolved_neighbor", + "bgp_adjacency_suppressed_self_actor", + "bgp_adjacency_suppressed_duplicate_link", + "bgp_adjacency_visible_links", + }, topologyStatsKeysForTest(payload.Stats)) + + require.Equal(t, topologyMapTypeAllDevicesLowConfidence, payload.Stats["map_type"]) + require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, payload.Stats["inference_strategy"]) + require.Equal(t, topologyManagedFocusAllDevices, payload.Stats["managed_snmp_device_focus"]) + require.Equal(t, topologyDepthAll, payload.Stats["depth"]) + require.Equal(t, 1, payload.Stats["l3_subnet_emitted_links"]) + require.Equal(t, 1, payload.Stats["l3_subnet_visible_links"]) + require.Equal(t, 1, payload.Stats["ospf_adjacency_emitted_links"]) + require.Equal(t, 1, payload.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 1, payload.Stats["bgp_adjacency_emitted_links"]) + require.Equal(t, 1, payload.Stats["bgp_adjacency_visible_links"]) + + stringStats := map[string]struct{}{ + "map_type": {}, + "inference_strategy": {}, + "managed_snmp_device_focus": {}, + "depth": {}, + } + for key, value := range payload.Stats { + if _, ok := stringStats[key]; ok { + require.IsType(t, "", value, "stat %q", key) + continue + } + require.IsType(t, 0, value, "stat %q", key) + } +} + +func TestSNMPTopologyToV1_RealPipelineStatsCensusNoProtocolDataEmitsZeroProtocolKeys(t *testing.T) { + registry := newTopologyRegistry() + cache := newTopologyCache() + cache.updateTime = time.Date(2026, time.June, 20, 12, 0, 0, 0, time.UTC) + cache.lastUpdate = cache.updateTime + cache.agentID = "agent-test" + cache.localDevice = topologyDevice{ + ChassisID: "00:11:22:33:44:55", + ChassisIDType: "macAddress", + SysName: "switch-a", + ManagementIP: "10.0.0.1", + } + registry.register(cache) + + options := defaultTopologyQueryOptionsForTest() + options.MapType = topologyMapTypeAllDevicesLowConfidence + data, ok := snapshotTopologyRegistryForTestWithOptions(registry, options) + require.True(t, ok) + + payload, err := snmpTopologyToV1(data) + require.NoError(t, err) + require.Equal(t, 0, payload.Stats["l3_subnet_candidate_links"]) + require.Equal(t, 0, payload.Stats["l3_subnet_emitted_links"]) + require.Equal(t, 0, payload.Stats["l3_subnet_visible_links"]) + require.Equal(t, 0, payload.Stats["ospf_neighbor_rows"]) + require.Equal(t, 0, payload.Stats["ospf_adjacency_emitted_links"]) + require.Equal(t, 0, payload.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 0, payload.Stats["bgp_peer_rows"]) + require.Equal(t, 0, payload.Stats["bgp_adjacency_emitted_links"]) + require.Equal(t, 0, payload.Stats["bgp_adjacency_visible_links"]) +} + +func TestTopologyStatsToV1_OmitsFocusKeysWhenFocusFilterReturnsEarly(t *testing.T) { + data := &topologyData{ + Actors: []topologyActor{ + {ActorID: "segment-a", ActorType: "segment", Match: topologyMatch{IPAddresses: []string{"10.0.0.1"}}}, + }, + } + + applyTopologyDepthFocusFilter(data, topologyQueryOptions{ + ManagedDeviceFocus: "ip:10.0.0.1", + Depth: 1, + }) + + stats := topologyStatsToV1(data.Stats) + require.Equal(t, 1, stats["actors_total"]) + require.Equal(t, 0, stats["links_total"]) + require.NotContains(t, stats, "managed_snmp_device_focus") + require.NotContains(t, stats, "depth") + require.NotContains(t, stats, "actors_focus_depth_filtered") + require.NotContains(t, stats, "links_focus_depth_filtered") +} + +func newTopologyStatsCensusRouterCache(t *testing.T, sysName, chassisID, managementIP, routerID, ifIP, ifIndex string) *topologyCache { + t.Helper() + + cache := newTopologyCache() + cache.updateTime = time.Date(2026, time.June, 20, 12, 0, 0, 0, time.UTC) + cache.lastUpdate = cache.updateTime + cache.agentID = "agent-test" + cache.localDevice = topologyDevice{ + ChassisID: chassisID, + ChassisIDType: "macAddress", + SysName: sysName, + ManagementIP: managementIP, + } + cache.updateTopologyProfileTags([]*ddsnmp.ProfileMetrics{{ + DeviceMetadata: map[string]ddsnmp.MetaTag{ + tagOSPFRouterID: {Value: routerID}, + }, + }}) + cache.updateIfIndexByIP(map[string]string{ + tagTopoIfIndex: ifIndex, + tagTopoIPAddr: ifIP, + tagTopoIPMask: "255.255.255.252", + }) + return cache +} + +func topologyStatsKeysForTest(stats map[string]any) []string { + keys := make([]string, 0, len(stats)) + for key := range stats { + keys = append(keys, key) + } + return keys +} diff --git a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_values.go b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_values.go index b2cb600d5257fb..5103bcb2638369 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_values.go +++ b/src/go/plugin/go.d/collector/snmp_topology/func_topology_v1_values.go @@ -5,7 +5,6 @@ package snmptopology import ( "fmt" topologyv1 "github.com/netdata/netdata/go/plugins/pkg/topology/v1" - "maps" "math" "reflect" "regexp" @@ -272,12 +271,3 @@ func firstNonEmptyString(values ...string) string { } return "" } - -func cloneAnyMapForTopologyV1(in map[string]any) map[string]any { - if len(in) == 0 { - return nil - } - out := make(map[string]any, len(in)) - maps.Copy(out, in) - return out -} diff --git a/src/go/plugin/go.d/collector/snmp_topology/testdata/topology_v1_normalized_golden.json b/src/go/plugin/go.d/collector/snmp_topology/testdata/topology_v1_normalized_golden.json new file mode 100644 index 00000000000000..3ab97b199868da --- /dev/null +++ b/src/go/plugin/go.d/collector/snmp_topology/testdata/topology_v1_normalized_golden.json @@ -0,0 +1,16819 @@ +{ + "actors": { + "columns": [ + "id:string_ref:dict=strings:role=identity", + "type:string_ref:dict=strings:role=group_key", + "layer:string_ref:dict=strings:role=group_key", + "source:string_ref:dict=strings", + "display_name:string_ref:dict=strings:nullable:role=attribute", + "chassis_ids:array:role=merge_identity", + "mac_addresses:array:role=merge_identity", + "ip_addresses:array:role=merge_identity", + "hostnames:array", + "dns_names:array", + "sys_object_id:string_ref:dict=strings:role=merge_identity", + "sys_name:string_ref:dict=strings:role=merge_identity", + "parent_devices:array:role=parent_identity", + "vendor:string_ref:dict=strings:nullable", + "model:string_ref:dict=strings:nullable", + "sys_descr:string_ref:dict=strings:nullable", + "sys_location:string_ref:dict=strings:nullable", + "sys_contact:string_ref:dict=strings:nullable", + "management_ip:string_ref:dict=strings:nullable", + "protocols:array:nullable", + "capabilities:array:nullable", + "ports_total:uint:nullable", + "vlan_count:uint:nullable", + "fdb_total_macs:uint:nullable", + "lldp_neighbor_count:uint:nullable", + "cdp_neighbor_count:uint:nullable", + "endpoints_total:uint:nullable", + "chart_id_prefix:string_ref:dict=strings:nullable", + "netdata_host_id:string_ref:dict=strings:nullable" + ], + "rows": [ + { + "capabilities": [ + "bridge", + "router" + ], + "cdp_neighbor_count": 1, + "chart_id_prefix": "snmp_switch_a", + "chassis_ids": [ + "aa:bb:cc:00:00:01" + ], + "display_name": "Switch A", + "dns_names": [ + "switch-a.example.test" + ], + "endpoints_total": 12, + "fdb_total_macs": 42, + "hostnames": [ + "switch-a.example.test" + ], + "id": "switch-a", + "ip_addresses": [ + "192.0.2.10" + ], + "layer": "network", + "lldp_neighbor_count": 1, + "mac_addresses": [ + "aa:bb:cc:00:00:01" + ], + "management_ip": "192.0.2.10", + "model": "Catalyst 9300", + "netdata_host_id": "host-switch-a", + "parent_devices": [], + "ports_total": 2, + "protocols": [ + "lldp", + "cdp", + "snmp" + ], + "source": "snmp", + "sys_contact": "noc@example.test", + "sys_descr": "Synthetic switch fixture", + "sys_location": "lab-rack-1", + "sys_name": "switch-a", + "sys_object_id": "1.3.6.1.4.1.9.1.1208", + "type": "switch", + "vendor": "Cisco", + "vlan_count": 3 + }, + { + "capabilities": [ + "router" + ], + "chassis_ids": [ + "aa:bb:cc:00:00:02" + ], + "display_name": "Router A", + "dns_names": [], + "hostnames": [], + "id": "router-a", + "ip_addresses": [ + "198.51.100.1", + "203.0.113.1" + ], + "layer": "network", + "mac_addresses": [], + "management_ip": "198.51.100.1", + "model": "ASR 1001", + "parent_devices": [], + "ports_total": 4, + "source": "snmp", + "sys_descr": "Synthetic router A", + "sys_name": "router-a", + "sys_object_id": "1.3.6.1.4.1.9.1.1745", + "type": "router", + "vendor": "Cisco" + }, + { + "chassis_ids": [ + "aa:bb:cc:00:00:03" + ], + "display_name": "Router B", + "dns_names": [], + "hostnames": [], + "id": "router-b", + "ip_addresses": [ + "198.51.100.2", + "203.0.113.2" + ], + "layer": "network", + "mac_addresses": [], + "management_ip": "198.51.100.2", + "model": "MX204", + "parent_devices": [], + "source": "snmp", + "sys_descr": "Synthetic router B", + "sys_name": "router-b", + "sys_object_id": "1.3.6.1.4.1.2636.1.1.1.2.131", + "type": "router", + "vendor": "Juniper" + }, + { + "chassis_ids": [], + "display_name": "198.51.100.0/30", + "dns_names": [], + "hostnames": [], + "id": "segment-198-51-100-0-30", + "ip_addresses": [ + "198.51.100.0" + ], + "layer": "network", + "mac_addresses": [], + "parent_devices": [], + "source": "ip_mib", + "sys_name": "", + "sys_object_id": "", + "type": "segment" + }, + { + "chassis_ids": [], + "display_name": "Server A", + "dns_names": [], + "hostnames": [ + "server-a" + ], + "id": "server-a", + "ip_addresses": [ + "192.0.2.50" + ], + "layer": "network", + "mac_addresses": [ + "00:11:22:33:44:55" + ], + "parent_devices": [], + "protocols": [ + "fdb", + "arp" + ], + "source": "fdb", + "sys_name": "", + "sys_object_id": "", + "type": "endpoint" + } + ] + }, + "collected_at": "2026-01-02T03:04:05Z", + "evidence": { + "bgp_adjacency": { + "table": { + "columns": [ + "link:link_ref:role=reference", + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "src_if_index:uint:nullable", + "dst_if_index:uint:nullable", + "src_management_ip:string_ref:dict=strings:nullable", + "dst_management_ip:string_ref:dict=strings:nullable", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "routing_instance:string_ref:dict=strings:nullable", + "local_identifier:string_ref:dict=strings:nullable", + "peer_identifier:string_ref:dict=strings:nullable", + "local_ip:string_ref:dict=strings:nullable", + "neighbor_ip:string_ref:dict=strings:nullable", + "local_as:string_ref:dict=strings:nullable", + "remote_as:string_ref:dict=strings:nullable", + "source:string_ref:dict=strings:nullable", + "src_endpoint:json:nullable", + "dst_endpoint:json:nullable", + "metrics:json:nullable" + ], + "rows": [ + { + "attachment_mode": "logical_bgp_adjacency", + "direction": "observed", + "dst_actor": "router-b", + "dst_endpoint": { + "as": "64513", + "bgp_identifier": "10.255.0.2", + "ip": "203.0.113.2", + "source": "bgp_mib" + }, + "inference": "bgp_peer", + "link": "router-a -\u003e router-b | bgp_adjacency | bgp_adjacency | established | \u003cnil\u003e | \u003cnil\u003e", + "local_as": "64512", + "local_identifier": "10.255.0.1", + "local_ip": "203.0.113.1", + "metrics": { + "attachment_mode": "logical_bgp_adjacency", + "inference": "bgp_peer", + "routing_instance": "default", + "source": "bgp_mib" + }, + "neighbor_ip": "203.0.113.2", + "peer_identifier": "10.255.0.2", + "protocol": "bgp_adjacency", + "remote_as": "64513", + "routing_instance": "default", + "source": "bgp_mib", + "src_actor": "router-a", + "src_endpoint": { + "as": "64512", + "bgp_identifier": "10.255.0.1", + "ip": "203.0.113.1", + "source": "bgp_mib" + }, + "state": "established" + } + ] + }, + "type": "bgp_adjacency" + }, + "l3_subnet": { + "table": { + "columns": [ + "link:link_ref:role=reference", + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "src_if_index:uint:nullable", + "dst_if_index:uint:nullable", + "src_management_ip:string_ref:dict=strings:nullable", + "dst_management_ip:string_ref:dict=strings:nullable", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "src_ip:string_ref:dict=strings:nullable", + "dst_ip:string_ref:dict=strings:nullable", + "subnet:string_ref:dict=strings:role=group_key", + "network:string_ref:dict=strings:nullable", + "netmask:string_ref:dict=strings:nullable", + "prefix:uint:nullable", + "source:string_ref:dict=strings:nullable", + "src_endpoint:json:nullable", + "dst_endpoint:json:nullable", + "metrics:json:nullable" + ], + "rows": [ + { + "attachment_mode": "logical_l3_subnet", + "direction": "observed", + "dst_actor": "router-b", + "dst_endpoint": { + "if_index": 401, + "if_name": "xe-0/0/1", + "ip": "198.51.100.2", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "source": "ip_mib", + "subnet": "198.51.100.0/30" + }, + "dst_if_index": 401, + "dst_ip": "198.51.100.2", + "dst_port_name": "xe-0/0/1", + "inference": "shared_subnet", + "link": "router-a -\u003e router-b | l3_subnet | l3_subnet | \u003cnil\u003e | xe-0/0/0 | xe-0/0/1", + "metrics": { + "attachment_mode": "logical_l3_subnet", + "inference": "shared_subnet", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "source": "ip_mib", + "subnet": "198.51.100.0/30" + }, + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "protocol": "l3_subnet", + "source": "ip_mib", + "src_actor": "router-a", + "src_endpoint": { + "if_index": 301, + "if_name": "xe-0/0/0", + "ip": "198.51.100.1", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "source": "ip_mib", + "subnet": "198.51.100.0/30" + }, + "src_if_index": 301, + "src_ip": "198.51.100.1", + "src_port_name": "xe-0/0/0", + "subnet": "198.51.100.0/30" + } + ] + }, + "type": "l3_subnet" + }, + "lldp": { + "table": { + "columns": [ + "link:link_ref:role=reference", + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "src_if_index:uint:nullable", + "dst_if_index:uint:nullable", + "src_management_ip:string_ref:dict=strings:nullable", + "dst_management_ip:string_ref:dict=strings:nullable", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "src_endpoint:json:nullable", + "dst_endpoint:json:nullable", + "metrics:json:nullable" + ], + "rows": [ + { + "attachment_mode": "direct", + "confidence": "high", + "direction": "observed", + "dst_actor": "router-a", + "dst_endpoint": { + "if_index": 201, + "if_name": "Gi0/0", + "management_ip": "198.51.100.1", + "port_id": "Gi0/0" + }, + "dst_if_index": 201, + "dst_management_ip": "198.51.100.1", + "dst_port_name": "Gi0/0", + "inference": "lldp", + "link": "switch-a -\u003e router-a | lldp | lldp | up | Gi1/0/1 | Gi0/0", + "metrics": { + "attachment_mode": "direct", + "confidence": "high", + "inference": "lldp" + }, + "protocol": "lldp", + "src_actor": "switch-a", + "src_endpoint": { + "if_index": 101, + "if_name": "Gi1/0/1", + "management_ip": "192.0.2.10", + "port_id": "Gi1/0/1" + }, + "src_if_index": 101, + "src_management_ip": "192.0.2.10", + "src_port_name": "Gi1/0/1", + "state": "up" + } + ] + }, + "type": "lldp" + }, + "ospf_adjacency": { + "table": { + "columns": [ + "link:link_ref:role=reference", + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "src_if_index:uint:nullable", + "dst_if_index:uint:nullable", + "src_management_ip:string_ref:dict=strings:nullable", + "dst_management_ip:string_ref:dict=strings:nullable", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "src_router_id:string_ref:dict=strings:nullable", + "dst_router_id:string_ref:dict=strings:nullable", + "src_ip:string_ref:dict=strings:nullable", + "dst_ip:string_ref:dict=strings:nullable", + "subnet:string_ref:dict=strings:nullable", + "network:string_ref:dict=strings:nullable", + "netmask:string_ref:dict=strings:nullable", + "prefix:uint:nullable", + "source:string_ref:dict=strings:nullable", + "src_endpoint:json:nullable", + "dst_endpoint:json:nullable", + "metrics:json:nullable" + ], + "rows": [ + { + "attachment_mode": "logical_ospf_adjacency", + "direction": "observed", + "dst_actor": "router-b", + "dst_endpoint": { + "ip": "198.51.100.2", + "router_id": "10.255.0.2", + "source": "ospf_mib", + "subnet": "198.51.100.0/30" + }, + "dst_ip": "198.51.100.2", + "dst_router_id": "10.255.0.2", + "inference": "ospf_neighbor", + "link": "router-a -\u003e router-b | ospf_adjacency | ospf_adjacency | full | \u003cnil\u003e | \u003cnil\u003e", + "metrics": { + "attachment_mode": "logical_ospf_adjacency", + "inference": "ospf_neighbor", + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "source": "ospf_mib", + "subnet": "198.51.100.0/30" + }, + "netmask": "255.255.255.252", + "network": "198.51.100.0", + "prefix": 30, + "protocol": "ospf_adjacency", + "source": "ospf_mib", + "src_actor": "router-a", + "src_endpoint": { + "ip": "198.51.100.1", + "router_id": "10.255.0.1", + "source": "ospf_mib", + "subnet": "198.51.100.0/30" + }, + "src_ip": "198.51.100.1", + "src_router_id": "10.255.0.1", + "state": "full", + "subnet": "198.51.100.0/30" + } + ] + }, + "type": "ospf_adjacency" + }, + "probable": { + "table": { + "columns": [ + "link:link_ref:role=reference", + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "src_if_index:uint:nullable", + "dst_if_index:uint:nullable", + "src_management_ip:string_ref:dict=strings:nullable", + "dst_management_ip:string_ref:dict=strings:nullable", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "src_endpoint:json:nullable", + "dst_endpoint:json:nullable", + "metrics:json:nullable" + ], + "rows": [ + { + "attachment_mode": "probable_host", + "confidence": "low", + "direction": "observed", + "dst_actor": "server-a", + "dst_endpoint": { + "mac": "00:11:22:33:44:55" + }, + "inference": "probable", + "link": "switch-a -\u003e server-a | probable | fdb | probable | Gi1/0/2 | \u003cnil\u003e", + "metrics": { + "attachment_mode": "probable_host", + "confidence": "low", + "inference": "probable" + }, + "protocol": "fdb", + "src_actor": "switch-a", + "src_endpoint": { + "if_index": 102, + "if_name": "Gi1/0/2", + "port_id": "Gi1/0/2" + }, + "src_if_index": 102, + "src_port_name": "Gi1/0/2", + "state": "probable" + } + ] + }, + "type": "probable" + } + }, + "links": { + "columns": [ + "src_actor:actor_ref:role=reference", + "dst_actor:actor_ref:role=reference", + "type:string_ref:dict=strings:role=group_key", + "protocol:string_ref:dict=strings:role=group_key", + "direction:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "src_port_name:string_ref:dict=strings:nullable", + "dst_port_name:string_ref:dict=strings:nullable", + "evidence_count:uint:aggregation=sum", + "discovered_at:timestamp:nullable:role=timestamp", + "last_seen:timestamp:nullable:role=timestamp" + ], + "rows": [ + { + "direction": "observed", + "discovered_at": "2026-01-02T02:04:05Z", + "dst_actor": "router-a", + "dst_port_name": "Gi0/0", + "evidence_count": 1, + "last_seen": "2026-01-02T03:03:05Z", + "protocol": "lldp", + "src_actor": "switch-a", + "src_port_name": "Gi1/0/1", + "state": "up", + "type": "lldp" + }, + { + "direction": "observed", + "dst_actor": "router-b", + "dst_port_name": "xe-0/0/1", + "evidence_count": 1, + "protocol": "l3_subnet", + "src_actor": "router-a", + "src_port_name": "xe-0/0/0", + "type": "l3_subnet" + }, + { + "direction": "observed", + "dst_actor": "router-b", + "evidence_count": 1, + "protocol": "bgp_adjacency", + "src_actor": "router-a", + "state": "established", + "type": "bgp_adjacency" + }, + { + "direction": "observed", + "dst_actor": "router-b", + "evidence_count": 1, + "protocol": "ospf_adjacency", + "src_actor": "router-a", + "state": "full", + "type": "ospf_adjacency" + }, + { + "direction": "observed", + "dst_actor": "server-a", + "evidence_count": 1, + "protocol": "fdb", + "src_actor": "switch-a", + "src_port_name": "Gi1/0/2", + "state": "probable", + "type": "probable" + } + ] + }, + "presentation": { + "legend": { + "actors": [ + { + "label": "Router", + "type": "router" + }, + { + "label": "Switch", + "type": "switch" + }, + { + "label": "Firewall", + "type": "firewall" + }, + { + "label": "Access Point", + "type": "access_point" + }, + { + "label": "Server", + "type": "server" + }, + { + "label": "Storage", + "type": "storage" + }, + { + "label": "Load Balancer", + "type": "load_balancer" + }, + { + "label": "Printer", + "type": "printer" + }, + { + "label": "IP Phone", + "type": "phone" + }, + { + "label": "UPS / PDU", + "type": "ups" + }, + { + "label": "Camera / Media", + "type": "camera" + }, + { + "label": "Other device", + "type": "device" + }, + { + "label": "Other", + "type": "custom" + }, + { + "label": "Inferred endpoint", + "type": "endpoint" + }, + { + "label": "Network segment", + "type": "segment" + } + ], + "links": [ + { + "label": "LLDP", + "type": "lldp" + }, + { + "label": "CDP", + "type": "cdp" + }, + { + "label": "SNMP", + "type": "snmp" + }, + { + "label": "Bridge", + "type": "bridge" + }, + { + "label": "L3 subnet", + "type": "l3_subnet" + }, + { + "label": "OSPF adjacency", + "type": "ospf_adjacency" + }, + { + "label": "BGP adjacency", + "type": "bgp_adjacency" + }, + { + "label": "Probable", + "type": "probable" + } + ], + "ports": [ + { + "label": "lldp/cdp", + "type": "lldp" + }, + { + "label": "switch-facing", + "type": "switch_facing" + }, + { + "label": "host-facing", + "type": "host_facing" + }, + { + "label": "host-candidate", + "type": "host_candidate" + }, + { + "label": "trunk", + "type": "trunk" + }, + { + "label": "access", + "type": "access" + }, + { + "label": "unclassified", + "type": "topology" + }, + { + "label": "idle", + "type": "idle" + }, + { + "label": "unknown", + "type": "unknown" + } + ] + }, + "port_fields": [ + { + "key": "type", + "label": "Type" + }, + { + "key": "role", + "label": "Role" + }, + { + "key": "status", + "label": "Status" + }, + { + "key": "mode", + "label": "Mode" + }, + { + "key": "sources", + "label": "Sources" + } + ], + "profile_version": "snmp-l2.v2", + "selection": { + "actor_click": { + "mode": "highlight_connections" + } + } + }, + "producer": { + "capabilities": [ + "lldp", + "cdp", + "fdb", + "stp", + "l3_subnet", + "ospf", + "bgp" + ], + "instance": "golden-agent", + "plugin": "go.d/snmp_topology", + "source": "snmp-l2" + }, + "schema_version": "netdata.topology.v1", + "stats": { + "actors_collapsed_by_ip": 1, + "actors_focus_depth_filtered": 0, + "actors_map_type_suppressed": 2, + "actors_non_ip_inferred_suppressed": 0, + "actors_total": 5, + "bgp_adjacency_emitted_links": 1, + "bgp_adjacency_suppressed_duplicate_link": 0, + "bgp_adjacency_suppressed_non_established_state": 0, + "bgp_adjacency_suppressed_self_actor": 0, + "bgp_adjacency_suppressed_unresolved_local": 0, + "bgp_adjacency_suppressed_unresolved_neighbor": 0, + "bgp_adjacency_visible_links": 1, + "bgp_peer_detail_rows": 0, + "bgp_peer_rows": 0, + "depth": "all", + "l3_subnet_candidate_links": 0, + "l3_subnet_candidate_subnets": 0, + "l3_subnet_emitted_links": 1, + "l3_subnet_suppressed_duplicate_ip": 0, + "l3_subnet_suppressed_duplicate_link": 0, + "l3_subnet_suppressed_invalid": 0, + "l3_subnet_suppressed_multi_access": 0, + "l3_subnet_suppressed_self_actor": 0, + "l3_subnet_suppressed_self_link": 0, + "l3_subnet_suppressed_unmatched": 0, + "l3_subnet_suppressed_unresolved_actor": 0, + "l3_subnet_suppressed_unsupported_prefix": 0, + "l3_subnet_visible_links": 1, + "links_focus_depth_filtered": 0, + "links_probable": 1, + "links_total": 5, + "managed_snmp_device_focus": "all_devices", + "map_type": "high_confidence_inferred", + "ospf_adjacency_emitted_links": 1, + "ospf_adjacency_suppressed_duplicate_link": 0, + "ospf_adjacency_suppressed_non_full_state": 0, + "ospf_adjacency_suppressed_self_actor": 0, + "ospf_adjacency_suppressed_unresolved_local": 0, + "ospf_adjacency_suppressed_unresolved_neighbor": 0, + "ospf_adjacency_visible_links": 1, + "ospf_neighbor_detail_rows": 0, + "ospf_neighbor_rows": 0, + "segments_sparse_suppressed": 0, + "segments_suppressed": 0 + }, + "tables": { + "actor": { + "actor_bgp_peers": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "remote_actor:actor_ref:nullable:role=reference", + "routing_instance:string_ref:dict=strings:nullable", + "neighbor_ip:string_ref:dict=strings:nullable", + "remote_as:string_ref:dict=strings:nullable", + "state:string_ref:dict=strings:nullable", + "admin_status:string_ref:dict=strings:nullable", + "local_ip:string_ref:dict=strings:nullable", + "local_as:string_ref:dict=strings:nullable", + "local_identifier:string_ref:dict=strings:nullable", + "peer_identifier:string_ref:dict=strings:nullable", + "peer_type:string_ref:dict=strings:nullable", + "bgp_version:string_ref:dict=strings:nullable", + "description:string_ref:dict=strings:nullable", + "established_uptime:uint:nullable", + "last_received_update_age:uint:nullable", + "source:string_ref:dict=strings:nullable" + ], + "rows": [ + { + "actor": "router-a", + "admin_status": "enabled", + "bgp_version": "4", + "description": "router-b transit", + "established_uptime": 7200, + "last_received_update_age": 15, + "local_as": "64512", + "local_identifier": "10.255.0.1", + "local_ip": "203.0.113.1", + "neighbor_ip": "203.0.113.2", + "peer_identifier": "10.255.0.2", + "peer_type": "external", + "remote_actor": "router-b", + "remote_as": "64513", + "routing_instance": "default", + "source": "bgp_mib", + "state": "established" + }, + { + "actor": "router-b", + "admin_status": "enabled", + "bgp_version": "4", + "description": "router-a transit", + "established_uptime": 7100, + "last_received_update_age": 20, + "local_as": "64513", + "local_identifier": "10.255.0.2", + "local_ip": "203.0.113.2", + "neighbor_ip": "203.0.113.1", + "peer_identifier": "10.255.0.1", + "peer_type": "external", + "remote_actor": "router-a", + "remote_as": "64512", + "routing_instance": "default", + "source": "bgp_mib", + "state": "established" + } + ] + }, + "type": "actor_bgp_peers" + }, + "actor_inventory": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "enabled:bool:nullable", + "sensor_labels:array:nullable", + "slot:string_ref:dict=strings:nullable", + "temperature_c:float:nullable" + ], + "rows": [ + { + "actor": "switch-a", + "enabled": true, + "sensor_labels": [ + "ambient", + "rack" + ], + "slot": "1", + "temperature_c": 42.5 + } + ] + }, + "type": "actor_inventory" + }, + "actor_labels": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "key:string_ref:dict=strings:role=attribute", + "value:string_ref:dict=strings:role=attribute", + "source:string_ref:dict=strings:nullable:role=attribute", + "kind:string_ref:dict=strings:nullable:role=attribute", + "value_index:uint:nullable:role=attribute" + ], + "rows": [ + { + "actor": "router-a", + "key": "actor_type", + "kind": "identity", + "source": "snmp-l2", + "value": "router" + }, + { + "actor": "router-a", + "key": "capabilities", + "kind": "attribute", + "source": "snmp-l2", + "value": "router", + "value_index": 0 + }, + { + "actor": "router-a", + "key": "chassis_id", + "kind": "match", + "source": "snmp-l2", + "value": "aa:bb:cc:00:00:02", + "value_index": 0 + }, + { + "actor": "router-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Router A" + }, + { + "actor": "router-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Router A" + }, + { + "actor": "router-a", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "198.51.100.1", + "value_index": 0 + }, + { + "actor": "router-a", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "203.0.113.1", + "value_index": 1 + }, + { + "actor": "router-a", + "key": "layer", + "kind": "identity", + "source": "snmp-l2", + "value": "network" + }, + { + "actor": "router-a", + "key": "management_ip", + "kind": "attribute", + "source": "snmp-l2", + "value": "198.51.100.1" + }, + { + "actor": "router-a", + "key": "model", + "kind": "attribute", + "source": "snmp-l2", + "value": "ASR 1001" + }, + { + "actor": "router-a", + "key": "ospf_router_id", + "kind": "attribute", + "source": "snmp-l2", + "value": "10.255.0.1" + }, + { + "actor": "router-a", + "key": "ports_total", + "kind": "attribute", + "source": "snmp-l2", + "value": "4" + }, + { + "actor": "router-a", + "key": "protocols_collected", + "kind": "attribute", + "source": "snmp-l2", + "value": "bgp_mib", + "value_index": 2 + }, + { + "actor": "router-a", + "key": "protocols_collected", + "kind": "attribute", + "source": "snmp-l2", + "value": "ip_mib", + "value_index": 0 + }, + { + "actor": "router-a", + "key": "protocols_collected", + "kind": "attribute", + "source": "snmp-l2", + "value": "ospf_mib", + "value_index": 1 + }, + { + "actor": "router-a", + "key": "role", + "kind": "label", + "source": "producer_label", + "value": "edge" + }, + { + "actor": "router-a", + "key": "source", + "kind": "identity", + "source": "snmp-l2", + "value": "snmp" + }, + { + "actor": "router-a", + "key": "sys_descr", + "kind": "attribute", + "source": "snmp-l2", + "value": "Synthetic router A" + }, + { + "actor": "router-a", + "key": "sys_name", + "kind": "match", + "source": "snmp-l2", + "value": "router-a" + }, + { + "actor": "router-a", + "key": "sys_object_id", + "kind": "match", + "source": "snmp-l2", + "value": "1.3.6.1.4.1.9.1.1745" + }, + { + "actor": "router-a", + "key": "vendor", + "kind": "attribute", + "source": "snmp-l2", + "value": "Cisco" + }, + { + "actor": "router-b", + "key": "actor_type", + "kind": "identity", + "source": "snmp-l2", + "value": "router" + }, + { + "actor": "router-b", + "key": "chassis_id", + "kind": "match", + "source": "snmp-l2", + "value": "aa:bb:cc:00:00:03", + "value_index": 0 + }, + { + "actor": "router-b", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Router B" + }, + { + "actor": "router-b", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Router B" + }, + { + "actor": "router-b", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "198.51.100.2", + "value_index": 0 + }, + { + "actor": "router-b", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "203.0.113.2", + "value_index": 1 + }, + { + "actor": "router-b", + "key": "layer", + "kind": "identity", + "source": "snmp-l2", + "value": "network" + }, + { + "actor": "router-b", + "key": "management_ip", + "kind": "attribute", + "source": "snmp-l2", + "value": "198.51.100.2" + }, + { + "actor": "router-b", + "key": "model", + "kind": "attribute", + "source": "snmp-l2", + "value": "MX204" + }, + { + "actor": "router-b", + "key": "ospf_router_id", + "kind": "attribute", + "source": "snmp-l2", + "value": "10.255.0.2" + }, + { + "actor": "router-b", + "key": "source", + "kind": "identity", + "source": "snmp-l2", + "value": "snmp" + }, + { + "actor": "router-b", + "key": "sys_descr", + "kind": "attribute", + "source": "snmp-l2", + "value": "Synthetic router B" + }, + { + "actor": "router-b", + "key": "sys_name", + "kind": "match", + "source": "snmp-l2", + "value": "router-b" + }, + { + "actor": "router-b", + "key": "sys_object_id", + "kind": "match", + "source": "snmp-l2", + "value": "1.3.6.1.4.1.2636.1.1.1.2.131" + }, + { + "actor": "router-b", + "key": "vendor", + "kind": "attribute", + "source": "snmp-l2", + "value": "Juniper" + }, + { + "actor": "segment-198-51-100-0-30", + "key": "actor_type", + "kind": "identity", + "source": "snmp-l2", + "value": "segment" + }, + { + "actor": "segment-198-51-100-0-30", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "198.51.100.0/30" + }, + { + "actor": "segment-198-51-100-0-30", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "198.51.100.0/30" + }, + { + "actor": "segment-198-51-100-0-30", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "198.51.100.0", + "value_index": 0 + }, + { + "actor": "segment-198-51-100-0-30", + "key": "layer", + "kind": "identity", + "source": "snmp-l2", + "value": "network" + }, + { + "actor": "segment-198-51-100-0-30", + "key": "source", + "kind": "identity", + "source": "snmp-l2", + "value": "ip_mib" + }, + { + "actor": "server-a", + "key": "actor_type", + "kind": "identity", + "source": "snmp-l2", + "value": "endpoint" + }, + { + "actor": "server-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Server A" + }, + { + "actor": "server-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Server A" + }, + { + "actor": "server-a", + "key": "hostname", + "kind": "match", + "source": "snmp-l2", + "value": "server-a", + "value_index": 0 + }, + { + "actor": "server-a", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "192.0.2.50", + "value_index": 0 + }, + { + "actor": "server-a", + "key": "layer", + "kind": "identity", + "source": "snmp-l2", + "value": "network" + }, + { + "actor": "server-a", + "key": "learned_sources", + "kind": "attribute", + "source": "snmp-l2", + "value": "arp", + "value_index": 1 + }, + { + "actor": "server-a", + "key": "learned_sources", + "kind": "attribute", + "source": "snmp-l2", + "value": "fdb", + "value_index": 0 + }, + { + "actor": "server-a", + "key": "mac_address", + "kind": "match", + "source": "snmp-l2", + "value": "00:11:22:33:44:55", + "value_index": 0 + }, + { + "actor": "server-a", + "key": "role", + "kind": "label", + "source": "producer_label", + "value": "application" + }, + { + "actor": "server-a", + "key": "source", + "kind": "identity", + "source": "snmp-l2", + "value": "fdb" + }, + { + "actor": "switch-a", + "key": "actor_type", + "kind": "identity", + "source": "snmp-l2", + "value": "switch" + }, + { + "actor": "switch-a", + "key": "capabilities", + "kind": "attribute", + "source": "snmp-l2", + "value": "bridge", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "capabilities", + "kind": "attribute", + "source": "snmp-l2", + "value": "router", + "value_index": 1 + }, + { + "actor": "switch-a", + "key": "cdp_neighbor_count", + "kind": "attribute", + "source": "snmp-l2", + "value": "1" + }, + { + "actor": "switch-a", + "key": "chart_context_prefix", + "kind": "attribute", + "source": "snmp-l2", + "value": "snmp.switch_a" + }, + { + "actor": "switch-a", + "key": "chart_id_prefix", + "kind": "attribute", + "source": "snmp-l2", + "value": "snmp_switch_a" + }, + { + "actor": "switch-a", + "key": "chassis_id", + "kind": "match", + "source": "snmp-l2", + "value": "aa:bb:cc:00:00:01", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Switch A" + }, + { + "actor": "switch-a", + "key": "display_name", + "kind": "attribute", + "source": "snmp-l2", + "value": "Switch A" + }, + { + "actor": "switch-a", + "key": "dns_name", + "kind": "match", + "source": "snmp-l2", + "value": "switch-a.example.test", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "endpoints_total", + "kind": "attribute", + "source": "snmp-l2", + "value": "12" + }, + { + "actor": "switch-a", + "key": "fdb_total_macs", + "kind": "attribute", + "source": "snmp-l2", + "value": "42" + }, + { + "actor": "switch-a", + "key": "hostname", + "kind": "match", + "source": "snmp-l2", + "value": "switch-a.example.test", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "ip_address", + "kind": "match", + "source": "snmp-l2", + "value": "192.0.2.10", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "layer", + "kind": "identity", + "source": "snmp-l2", + "value": "network" + }, + { + "actor": "switch-a", + "key": "lldp_neighbor_count", + "kind": "attribute", + "source": "snmp-l2", + "value": "1" + }, + { + "actor": "switch-a", + "key": "mac_address", + "kind": "match", + "source": "snmp-l2", + "value": "aa:bb:cc:00:00:01", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "management_ip", + "kind": "attribute", + "source": "snmp-l2", + "value": "192.0.2.10" + }, + { + "actor": "switch-a", + "key": "model", + "kind": "attribute", + "source": "snmp-l2", + "value": "Catalyst 9300" + }, + { + "actor": "switch-a", + "key": "netdata_host_id", + "kind": "attribute", + "source": "snmp-l2", + "value": "host-switch-a" + }, + { + "actor": "switch-a", + "key": "ospf_router_id", + "kind": "attribute", + "source": "snmp-l2", + "value": "10.255.0.1" + }, + { + "actor": "switch-a", + "key": "ports_total", + "kind": "attribute", + "source": "snmp-l2", + "value": "2" + }, + { + "actor": "switch-a", + "key": "protocols", + "kind": "attribute", + "source": "snmp-l2", + "value": "cdp", + "value_index": 1 + }, + { + "actor": "switch-a", + "key": "protocols", + "kind": "attribute", + "source": "snmp-l2", + "value": "lldp", + "value_index": 0 + }, + { + "actor": "switch-a", + "key": "protocols", + "kind": "attribute", + "source": "snmp-l2", + "value": "snmp", + "value_index": 2 + }, + { + "actor": "switch-a", + "key": "role", + "kind": "label", + "source": "producer_label", + "value": "distribution" + }, + { + "actor": "switch-a", + "key": "site", + "kind": "label", + "source": "producer_label", + "value": "lab" + }, + { + "actor": "switch-a", + "key": "source", + "kind": "identity", + "source": "snmp-l2", + "value": "snmp" + }, + { + "actor": "switch-a", + "key": "sys_contact", + "kind": "attribute", + "source": "snmp-l2", + "value": "noc@example.test" + }, + { + "actor": "switch-a", + "key": "sys_descr", + "kind": "attribute", + "source": "snmp-l2", + "value": "Synthetic switch fixture" + }, + { + "actor": "switch-a", + "key": "sys_location", + "kind": "attribute", + "source": "snmp-l2", + "value": "lab-rack-1" + }, + { + "actor": "switch-a", + "key": "sys_name", + "kind": "match", + "source": "snmp-l2", + "value": "switch-a" + }, + { + "actor": "switch-a", + "key": "sys_object_id", + "kind": "match", + "source": "snmp-l2", + "value": "1.3.6.1.4.1.9.1.1208" + }, + { + "actor": "switch-a", + "key": "vendor", + "kind": "attribute", + "source": "snmp-l2", + "value": "Cisco" + }, + { + "actor": "switch-a", + "key": "vlan_count", + "kind": "attribute", + "source": "snmp-l2", + "value": "3" + } + ] + }, + "type": "actor_labels" + }, + "actor_metadata": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "attributes:json:nullable", + "labels:json:nullable" + ], + "rows": [ + { + "actor": "router-a", + "attributes": { + "capabilities": [ + "router" + ], + "display_name": "Router A", + "management_ip": "198.51.100.1", + "model": "ASR 1001", + "ospf_router_id": "10.255.0.1", + "ports_total": 4, + "protocols_collected": [ + "ip_mib", + "ospf_mib", + "bgp_mib" + ], + "sys_descr": "Synthetic router A", + "vendor": "Cisco" + }, + "labels": { + "role": "edge" + } + }, + { + "actor": "router-b", + "attributes": { + "display_name": "Router B", + "management_ip": "198.51.100.2", + "model": "MX204", + "ospf_router_id": "10.255.0.2", + "sys_descr": "Synthetic router B", + "vendor": "Juniper" + } + }, + { + "actor": "segment-198-51-100-0-30", + "attributes": { + "display_name": "198.51.100.0/30" + } + }, + { + "actor": "server-a", + "attributes": { + "display_name": "Server A", + "learned_sources": [ + "fdb", + "arp" + ] + }, + "labels": { + "role": "application" + } + }, + { + "actor": "switch-a", + "attributes": { + "capabilities": [ + "bridge", + "router" + ], + "cdp_neighbor_count": 1, + "chart_context_prefix": "snmp.switch_a", + "chart_id_prefix": "snmp_switch_a", + "display_name": "Switch A", + "endpoints_total": 12, + "fdb_total_macs": 42, + "lldp_neighbor_count": 1, + "management_ip": "192.0.2.10", + "model": "Catalyst 9300", + "netdata_host_id": "host-switch-a", + "ospf_router_id": "10.255.0.1", + "ports_total": 2, + "protocols": [ + "lldp", + "cdp", + "snmp" + ], + "sys_contact": "noc@example.test", + "sys_descr": "Synthetic switch fixture", + "sys_location": "lab-rack-1", + "vendor": "Cisco", + "vlan_count": 3 + }, + "labels": { + "role": "distribution", + "site": "lab" + } + } + ] + }, + "type": "actor_metadata" + }, + "actor_ospf_neighbors": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "remote_actor:actor_ref:nullable:role=reference", + "local_router_id:string_ref:dict=strings:nullable", + "neighbor_router_id:string_ref:dict=strings:nullable", + "neighbor_ip:string_ref:dict=strings:nullable", + "state:string_ref:dict=strings:nullable", + "local_ip:string_ref:dict=strings:nullable", + "subnet:string_ref:dict=strings:nullable", + "addressless_index:string_ref:dict=strings:nullable", + "source:string_ref:dict=strings:nullable" + ], + "rows": [ + { + "actor": "router-a", + "addressless_index": "0", + "local_ip": "198.51.100.1", + "local_router_id": "10.255.0.1", + "neighbor_ip": "198.51.100.2", + "neighbor_router_id": "10.255.0.2", + "remote_actor": "router-b", + "source": "ospf_mib", + "state": "full", + "subnet": "198.51.100.0/30" + }, + { + "actor": "router-b", + "addressless_index": "0", + "local_ip": "198.51.100.2", + "local_router_id": "10.255.0.2", + "neighbor_ip": "198.51.100.1", + "neighbor_router_id": "10.255.0.1", + "remote_actor": "router-a", + "source": "ospf_mib", + "state": "full", + "subnet": "198.51.100.0/30" + } + ] + }, + "type": "actor_ospf_neighbors" + }, + "actor_port_links": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "link:link_ref:role=reference", + "remote_actor:actor_ref:role=reference", + "if_index:uint:nullable", + "port_id:string_ref:dict=strings:nullable", + "port_name:string_ref:dict=strings:nullable", + "remote_if_index:uint:nullable", + "remote_port_id:string_ref:dict=strings:nullable", + "remote_port_name:string_ref:dict=strings:nullable", + "type:string_ref:dict=strings:role=group_key", + "protocol:string_ref:dict=strings:role=group_key", + "state:string_ref:dict=strings:nullable", + "evidence_count:uint:aggregation=sum", + "confidence:string_ref:dict=strings:nullable", + "inference:string_ref:dict=strings:nullable", + "attachment_mode:string_ref:dict=strings:nullable", + "discovered_at:timestamp:nullable:role=timestamp", + "last_seen:timestamp:nullable:role=timestamp" + ], + "rows": [ + { + "actor": "router-a", + "attachment_mode": "direct", + "confidence": "high", + "discovered_at": "2026-01-02T02:04:05Z", + "evidence_count": 1, + "if_index": 201, + "inference": "lldp", + "last_seen": "2026-01-02T03:03:05Z", + "link": "switch-a -\u003e router-a | lldp | lldp | up | Gi1/0/1 | Gi0/0", + "port_id": "Gi0/0", + "port_name": "Gi0/0", + "protocol": "lldp", + "remote_actor": "switch-a", + "remote_if_index": 101, + "remote_port_id": "Gi1/0/1", + "remote_port_name": "Gi1/0/1", + "state": "up", + "type": "lldp" + }, + { + "actor": "server-a", + "attachment_mode": "probable_host", + "confidence": "low", + "evidence_count": 1, + "inference": "probable", + "link": "switch-a -\u003e server-a | probable | fdb | probable | Gi1/0/2 | \u003cnil\u003e", + "protocol": "fdb", + "remote_actor": "switch-a", + "remote_if_index": 102, + "remote_port_id": "Gi1/0/2", + "remote_port_name": "Gi1/0/2", + "state": "probable", + "type": "probable" + }, + { + "actor": "switch-a", + "attachment_mode": "direct", + "confidence": "high", + "discovered_at": "2026-01-02T02:04:05Z", + "evidence_count": 1, + "if_index": 101, + "inference": "lldp", + "last_seen": "2026-01-02T03:03:05Z", + "link": "switch-a -\u003e router-a | lldp | lldp | up | Gi1/0/1 | Gi0/0", + "port_id": "Gi1/0/1", + "port_name": "Gi1/0/1", + "protocol": "lldp", + "remote_actor": "router-a", + "remote_if_index": 201, + "remote_port_id": "Gi0/0", + "remote_port_name": "Gi0/0", + "state": "up", + "type": "lldp" + }, + { + "actor": "switch-a", + "attachment_mode": "probable_host", + "confidence": "low", + "evidence_count": 1, + "if_index": 102, + "inference": "probable", + "link": "switch-a -\u003e server-a | probable | fdb | probable | Gi1/0/2 | \u003cnil\u003e", + "port_id": "Gi1/0/2", + "port_name": "Gi1/0/2", + "protocol": "fdb", + "remote_actor": "server-a", + "state": "probable", + "type": "probable" + } + ] + }, + "type": "actor_port_links" + }, + "actor_ports": { + "table": { + "columns": [ + "actor:actor_ref:role=reference", + "if_index:uint:nullable", + "port_id:string_ref:dict=strings:nullable", + "name:string_ref:dict=strings:nullable", + "if_name:string_ref:dict=strings:nullable", + "if_descr:string_ref:dict=strings:nullable", + "if_alias:string_ref:dict=strings:nullable", + "mac:string_ref:dict=strings:nullable", + "speed:uint:nullable", + "topology_role:string_ref:dict=strings:nullable", + "oper_status:string_ref:dict=strings:nullable", + "admin_status:string_ref:dict=strings:nullable", + "port_type:string_ref:dict=strings:nullable", + "link_mode:string_ref:dict=strings:nullable", + "stp_state:string_ref:dict=strings:nullable", + "vlan_ids:array:nullable", + "fdb_mac_count:uint:nullable", + "link_count:uint:nullable", + "neighbor_count:uint:nullable", + "neighbor_actor:actor_ref:nullable:role=reference", + "neighbor_port_name:string_ref:dict=strings:nullable", + "neighbors:json:nullable", + "vlans:json:nullable", + "extra:json:nullable" + ], + "rows": [ + { + "actor": "router-a", + "extra": { + "vendor_context": "router uplink debug field" + }, + "if_alias": "to switch-a", + "if_index": 201, + "if_name": "Gi0/0", + "link_mode": "trunk", + "name": "Gi0/0", + "neighbor_actor": "switch-a", + "neighbor_port_name": "Gi1/0/1", + "oper_status": "up", + "port_id": "Gi0/0", + "topology_role": "uplink" + }, + { + "actor": "switch-a", + "admin_status": "up", + "extra": { + "duplex": "full", + "last_change": "2026-01-02T01:02:03Z", + "link_mode_confidence": "high", + "link_mode_sources": [ + "if_mib", + "lldp" + ], + "topology_role_confidence": "medium", + "topology_role_sources": [ + "stp", + "fdb" + ], + "vendor_note": "kept in actor_ports.extra until typed ports replace it" + }, + "fdb_mac_count": 12, + "if_alias": "to router-a", + "if_descr": "GigabitEthernet1/0/1", + "if_index": 101, + "if_name": "Gi1/0/1", + "link_count": 1, + "link_mode": "trunk", + "mac": "aa:bb:cc:00:01:01", + "name": "Gi1/0/1", + "neighbor_actor": "router-a", + "neighbor_count": 1, + "neighbor_port_name": "Gi0/0", + "neighbors": [ + { + "protocol": "lldp", + "remote_chassis_id": "aa:bb:cc:00:00:02", + "remote_port": "Gi0/0" + } + ], + "oper_status": "up", + "port_id": "Gi1/0/1", + "port_type": "ethernet", + "speed": 1000000000, + "stp_state": "forwarding", + "topology_role": "uplink", + "vlan_ids": [ + "10", + "20" + ], + "vlans": [ + { + "id": "10", + "name": "users" + }, + { + "id": "20", + "name": "servers" + } + ] + } + ] + }, + "type": "actor_ports" + } + } + }, + "types": { + "actor_types": { + "access_point": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "info", + "icon": "access_point", + "label": "Access Point", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "camera": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "neutral", + "icon": "camera", + "label": "Camera", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "custom": { + "aggregation_scopes": [ + "network" + ], + "identity": [ + "id" + ], + "layer": "custom", + "merge_identity": [ + "id" + ], + "presentation": { + "color_slot": "neutral", + "icon": "service", + "label": "Custom", + "label_policy": { + "array": "reject", + "columns": [ + "display_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "ip_address", + "label": "IP", + "max_values": 2 + }, + { + "key": "mac_address", + "label": "MAC", + "max_values": 2 + }, + { + "key": "hostname", + "label": "Hostname", + "max_values": 2 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "local_port", + "label": "Local Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "src_port_name", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "remote_port", + "label": "Remote Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "dst_port_name", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "direction", + "label": "Direction", + "projection": { + "column": "direction", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + } + ], + "id": "links", + "label": "Links", + "order": 1, + "owner_filter": { + "dst_actor_column": "dst_actor", + "mode": "incident_link", + "src_actor_column": "src_actor" + }, + "source": { + "kind": "links" + } + } + ] + }, + "role": "actor" + }, + "search": { + "columns": [ + "display_name" + ] + } + }, + "device": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "primary", + "icon": "server", + "label": "Device", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "endpoint": { + "aggregation_scopes": [ + "endpoint", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "mac_addresses", + "ip_addresses" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "derived", + "icon": "remote-endpoint", + "label": "Inferred endpoint", + "label_policy": { + "array": "reject", + "columns": [ + "display_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "weaker" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "ip_address", + "label": "IP", + "max_values": 2 + }, + { + "key": "mac_address", + "label": "MAC", + "max_values": 2 + }, + { + "key": "hostname", + "label": "Hostname", + "max_values": 2 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "local_port", + "label": "Local Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "src_port_name", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "remote_port", + "label": "Remote Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "dst_port_name", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "direction", + "label": "Direction", + "projection": { + "column": "direction", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + } + ], + "id": "links", + "label": "Links", + "order": 1, + "owner_filter": { + "dst_actor_column": "dst_actor", + "mode": "incident_link", + "src_actor_column": "src_actor" + }, + "source": { + "kind": "links" + } + } + ] + }, + "role": "endpoint", + "size": { + "mode": "fixed", + "scale": "compact" + } + }, + "search": { + "columns": [ + "display_name" + ] + } + }, + "firewall": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "warning", + "icon": "firewall", + "label": "Firewall", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "load_balancer": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "info", + "icon": "load_balancer", + "label": "Load Balancer", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "phone": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "neutral", + "icon": "phone", + "label": "Phone", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "printer": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "neutral", + "icon": "printer", + "label": "Printer", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "router": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "primary", + "icon": "router", + "label": "Router", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "segment": { + "aggregation_scopes": [ + "segment", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "id" + ], + "parent_identity": [ + "parent_devices" + ], + "presentation": { + "color_slot": "dim", + "icon": "segment", + "label": "Network segment", + "label_policy": { + "array": "reject", + "columns": [ + "display_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "weakest" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "ip_address", + "label": "IP", + "max_values": 2 + }, + { + "key": "mac_address", + "label": "MAC", + "max_values": 2 + }, + { + "key": "hostname", + "label": "Hostname", + "max_values": 2 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "local_port", + "label": "Local Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "src_port_name", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "remote_port", + "label": "Remote Port", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_port_column": "dst_port_name", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "direction", + "label": "Direction", + "projection": { + "column": "direction", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + } + ], + "id": "links", + "label": "Links", + "order": 1, + "owner_filter": { + "dst_actor_column": "dst_actor", + "mode": "incident_link", + "src_actor_column": "src_actor" + }, + "source": { + "kind": "links" + } + } + ] + }, + "role": "group", + "size": { + "mode": "fixed", + "scale": "compact" + } + }, + "search": { + "enabled": false + } + }, + "server": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "secondary", + "icon": "server", + "label": "Server", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "storage": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "secondary", + "icon": "storage", + "label": "Storage", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "switch": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "primary", + "icon": "switch", + "label": "Switch", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + }, + "ups": { + "aggregation_scopes": [ + "device", + "network" + ], + "identity": [ + "id" + ], + "layer": "network", + "merge_identity": [ + "chassis_ids", + "mac_addresses", + "ip_addresses", + "sys_name" + ], + "presentation": { + "border": { + "enabled": true + }, + "color_slot": "structural", + "icon": "ups", + "label": "UPS", + "label_policy": { + "array": "reject", + "columns": [ + "display_name", + "sys_name" + ], + "fallback": "type_label", + "max_length": 80 + }, + "layout": { + "repulsion": "stronger" + }, + "modal": { + "labels": { + "identification": { + "fields": [ + { + "key": "display_name", + "label": "Name", + "max_values": 1 + }, + { + "key": "management_ip", + "label": "Management IP", + "max_values": 1 + }, + { + "key": "vendor", + "label": "Vendor", + "max_values": 1 + }, + { + "key": "model", + "label": "Model", + "max_values": 1 + }, + { + "key": "ospf_router_id", + "label": "OSPF Router ID", + "max_values": 1 + }, + { + "key": "ports_total", + "label": "Ports", + "max_values": 1 + }, + { + "key": "lldp_neighbor_count", + "label": "LLDP", + "max_values": 1 + }, + { + "key": "cdp_neighbor_count", + "label": "CDP", + "max_values": 1 + } + ] + }, + "table": "actor_labels" + }, + "mini_topology": { + "depth": 1 + }, + "sections": [ + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "id": "ports", + "label": "Ports", + "order": 1, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ports" + } + }, + { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No port neighbors", + "id": "port_neighbors", + "label": "Port Neighbors", + "order": 2, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "if_index", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_port_links" + } + }, + { + "columns": [ + { + "cell": "actor_link", + "id": "remote", + "label": "Remote Actor", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "opposite_actor", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "local_endpoint", + "label": "Local Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "src_ip", + "local_port_column": "src_port_name", + "remote_ip_column": "dst_ip", + "remote_port_column": "dst_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "endpoint", + "id": "remote_endpoint", + "label": "Remote Endpoint", + "projection": { + "dst_actor_column": "dst_actor", + "kind": "selected_side_endpoint", + "local_ip_column": "dst_ip", + "local_port_column": "dst_port_name", + "remote_ip_column": "src_ip", + "remote_port_column": "src_port_name", + "src_actor_column": "src_actor" + } + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "prefix", + "label": "Prefix", + "projection": { + "column": "prefix", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "network", + "label": "Network", + "projection": { + "column": "network", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "netmask", + "label": "Netmask", + "projection": { + "column": "netmask", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No L3 adjacencies", + "id": "l3_adjacencies", + "label": "L3 Adjacencies", + "order": 3, + "owner_filter": { + "dst_actor_column": "dst_actor", + "link_column": "link", + "mode": "incident_evidence", + "src_actor_column": "src_actor" + }, + "sort": { + "column": "subnet", + "direction": "asc" + }, + "source": { + "evidence": "l3_subnet", + "kind": "evidence" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No OSPF neighbors", + "id": "ospf_neighbors", + "label": "OSPF Neighbors", + "order": 4, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_router_id", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_ospf_neighbors" + } + }, + { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "empty_label": "No BGP peers", + "id": "bgp_peers", + "label": "BGP Peers", + "order": 5, + "owner_filter": { + "actor_column": "actor", + "mode": "actor_column" + }, + "sort": { + "column": "neighbor_ip", + "direction": "asc" + }, + "source": { + "kind": "actor_table", + "table": "actor_bgp_peers" + } + } + ] + }, + "ports": { + "show_bullets": true, + "sources": [ + { + "actor_column": "actor", + "default_type": "topology", + "mode_column": "link_mode", + "name_column": "name", + "role_column": "topology_role", + "source": "actor_table", + "status_column": "oper_status", + "table": "actor_ports", + "type_column": "topology_role" + } + ] + }, + "role": "actor", + "size": { + "mode": "link_count", + "scale": "emphasized" + } + }, + "search": { + "columns": [ + "display_name", + "sys_name", + "management_ip", + "vendor", + "model" + ], + "label_keys": [ + "ospf_router_id" + ] + } + } + }, + "aggregation_scopes": { + "device": { + "columns": [ + "id" + ], + "evidence_policy": "preserve" + }, + "endpoint": { + "columns": [ + "id" + ], + "evidence_policy": "preserve" + }, + "network": { + "columns": [ + "type" + ], + "evidence_policy": "preserve" + }, + "segment": { + "columns": [ + "id" + ], + "evidence_policy": "preserve" + } + }, + "evidence_types": { + "arp": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "arp", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "bgp_adjacency": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "routing_instance", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_identifier", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "peer_identifier", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "neighbor_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_as", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "remote_as", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "bgp_adjacency", + "match_columns": [ + "src_actor", + "dst_actor", + "routing_instance" + ], + "role": "observation_evidence" + }, + "bridge": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "bridge", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "cdp": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "cdp", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "fdb": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "fdb", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "l2_observation": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "l2_observation", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "l3_subnet": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "subnet", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "network", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "netmask", + "nullable": true, + "type": "string_ref" + }, + { + "id": "prefix", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "l3_subnet", + "match_columns": [ + "src_actor", + "dst_actor", + "subnet", + "src_ip", + "dst_ip" + ], + "role": "observation_evidence" + }, + "lldp": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "lldp", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "ospf_adjacency": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_router_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_router_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "subnet", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "network", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "netmask", + "nullable": true, + "type": "string_ref" + }, + { + "id": "prefix", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "ospf_adjacency", + "match_columns": [ + "src_actor", + "dst_actor", + "src_router_id", + "dst_router_id", + "src_ip", + "dst_ip" + ], + "role": "observation_evidence" + }, + "probable": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "probable", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "snmp": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "snmp", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + }, + "stp": { + "columns": [ + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "src_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "dst_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "direction", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "src_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_if_index", + "nullable": true, + "type": "uint" + }, + { + "id": "dst_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "src_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "dst_management_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "src_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "dst_endpoint", + "nullable": true, + "type": "json" + }, + { + "id": "metrics", + "nullable": true, + "type": "json" + } + ], + "link_type": "stp", + "match_columns": [ + "src_actor", + "dst_actor", + "protocol", + "src_endpoint", + "dst_endpoint" + ], + "role": "observation_evidence" + } + }, + "link_types": { + "arp": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "arp" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "muted", + "curve": "straight", + "label": "ARP", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "bgp_adjacency": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "bgp_adjacency" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "accent", + "curve": "straight", + "label": "BGP adjacency", + "line_style": "dashed", + "width": "normal" + }, + "semantic_role": "control" + }, + "bridge": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "bridge" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "neutral", + "curve": "straight", + "label": "Bridge", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "cdp": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "cdp" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "accent", + "curve": "straight", + "label": "CDP", + "line_style": "solid", + "width": "thick" + }, + "semantic_role": "discovery" + }, + "fdb": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "fdb" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "neutral", + "curve": "straight", + "label": "FDB", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "l2_observation": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "l2_observation" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "neutral", + "curve": "straight", + "label": "L2 observation", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "l3_subnet": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "l3_subnet" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "info", + "curve": "straight", + "label": "L3 subnet", + "line_style": "dashed", + "width": "normal" + }, + "semantic_role": "normal" + }, + "lldp": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "lldp" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "accent", + "curve": "straight", + "label": "LLDP", + "line_style": "solid", + "width": "thick" + }, + "semantic_role": "discovery" + }, + "ospf_adjacency": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "ospf_adjacency" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "purple", + "curve": "straight", + "label": "OSPF adjacency", + "line_style": "dashed", + "width": "normal" + }, + "semantic_role": "control" + }, + "probable": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "probable" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "dim", + "curve": "straight", + "label": "Probable", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "snmp": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "snmp" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "primary", + "curve": "straight", + "label": "SNMP", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + }, + "stp": { + "aggregation": { + "direction": "canonicalize_unordered", + "evidence": "append", + "metrics": { + "evidence_count": "sum" + } + }, + "direction_role": "observation", + "evidence_types": [ + "stp" + ], + "orientation": "observed_bidirectional", + "presentation": { + "arrow": "none", + "color_slot": "muted", + "curve": "straight", + "label": "STP", + "line_style": "solid", + "width": "normal" + }, + "semantic_role": "normal" + } + }, + "port_types": { + "access": { + "presentation": { + "color_slot": "derived", + "label": "access", + "opacity": "normal" + } + }, + "host_candidate": { + "presentation": { + "color_slot": "info", + "label": "host-candidate", + "opacity": "normal" + } + }, + "host_facing": { + "presentation": { + "color_slot": "secondary", + "label": "host-facing", + "opacity": "normal" + } + }, + "idle": { + "presentation": { + "color_slot": "muted", + "label": "idle", + "opacity": "muted" + } + }, + "lldp": { + "presentation": { + "color_slot": "accent", + "label": "lldp/cdp", + "opacity": "normal" + } + }, + "switch_facing": { + "presentation": { + "color_slot": "primary", + "label": "switch-facing", + "opacity": "normal" + } + }, + "topology": { + "presentation": { + "color_slot": "neutral", + "label": "unclassified", + "opacity": "normal" + } + }, + "trunk": { + "presentation": { + "color_slot": "warning", + "label": "trunk", + "opacity": "normal" + } + }, + "unknown": { + "presentation": { + "color_slot": "dim", + "label": "unknown", + "opacity": "muted" + } + } + }, + "table_types": { + "actor_bgp_peers": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "remote_actor", + "nullable": true, + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "routing_instance", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "neighbor_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "remote_as", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "admin_status", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_as", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_identifier", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "peer_identifier", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "peer_type", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "bgp_version", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "description", + "nullable": true, + "type": "string_ref" + }, + { + "id": "established_uptime", + "nullable": true, + "type": "uint" + }, + { + "id": "last_received_update_age", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "type": "string_ref" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "remote_as", + "label": "Remote AS", + "projection": { + "column": "remote_as", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "routing_instance", + "label": "Routing Instance", + "projection": { + "column": "routing_instance", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_as", + "label": "Local AS", + "projection": { + "column": "local_as", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_identifier", + "label": "Local Identifier", + "projection": { + "column": "local_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "peer_identifier", + "label": "Peer Identifier", + "projection": { + "column": "peer_identifier", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "peer_type", + "label": "Peer Type", + "projection": { + "column": "peer_type", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "bgp_version", + "label": "BGP Version", + "projection": { + "column": "bgp_version", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "established_uptime", + "label": "Established Uptime", + "projection": { + "column": "established_uptime", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "duration", + "id": "last_received_update_age", + "label": "Last Update Age", + "projection": { + "column": "last_received_update_age", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "description", + "label": "Description", + "projection": { + "column": "description", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "label": "BGP Peers", + "order": 5 + }, + "role": "actor_detail" + }, + "actor_inventory": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "enabled", + "nullable": true, + "type": "bool" + }, + { + "id": "sensor_labels", + "nullable": true, + "type": "array" + }, + { + "dictionary": "strings", + "id": "slot", + "nullable": true, + "type": "string_ref" + }, + { + "id": "temperature_c", + "nullable": true, + "type": "float" + } + ], + "owner": "actor", + "role": "actor_detail" + }, + "actor_labels": { + "aggregation": "set", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "key", + "role": "attribute", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "value", + "role": "attribute", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "role": "attribute", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "kind", + "nullable": true, + "role": "attribute", + "type": "string_ref" + }, + { + "id": "value_index", + "nullable": true, + "role": "attribute", + "type": "uint" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "text", + "id": "key", + "label": "Label", + "projection": { + "column": "key", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "value", + "label": "Value", + "projection": { + "column": "value", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "kind", + "label": "Kind", + "projection": { + "column": "kind", + "kind": "direct" + } + } + ], + "label": "Labels" + }, + "role": "actor_inventory" + }, + "actor_metadata": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "attributes", + "nullable": true, + "type": "json" + }, + { + "id": "labels", + "nullable": true, + "type": "json" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "debug_json", + "id": "attributes", + "label": "Attributes", + "projection": { + "column": "attributes", + "kind": "direct" + } + }, + { + "cell": "debug_json", + "id": "labels", + "label": "Labels", + "projection": { + "column": "labels", + "kind": "direct" + } + } + ], + "default_visibility": "debug", + "label": "Debug metadata" + }, + "role": "actor_detail" + }, + "actor_ospf_neighbors": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "remote_actor", + "nullable": true, + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "local_router_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "neighbor_router_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "neighbor_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "local_ip", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "subnet", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "addressless_index", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "source", + "nullable": true, + "type": "string_ref" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "text", + "id": "neighbor_router_id", + "label": "Neighbor Router ID", + "projection": { + "column": "neighbor_router_id", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "neighbor_ip", + "label": "Neighbor IP", + "projection": { + "column": "neighbor_ip", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "local_router_id", + "label": "Local Router ID", + "projection": { + "column": "local_router_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "local_ip", + "label": "Local IP", + "projection": { + "column": "local_ip", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "subnet", + "label": "Subnet", + "projection": { + "column": "subnet", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "addressless_index", + "label": "Addressless Index", + "projection": { + "column": "addressless_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "source", + "label": "Source", + "projection": { + "column": "source", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "label": "OSPF Neighbors", + "order": 4 + }, + "role": "actor_detail" + }, + "actor_port_links": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "link", + "role": "reference", + "type": "link_ref" + }, + { + "id": "remote_actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "port_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "remote_if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "remote_port_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "remote_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "type", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "protocol", + "role": "group_key", + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "state", + "nullable": true, + "type": "string_ref" + }, + { + "aggregation": "sum", + "id": "evidence_count", + "type": "uint" + }, + { + "dictionary": "strings", + "id": "confidence", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "inference", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "attachment_mode", + "nullable": true, + "type": "string_ref" + }, + { + "id": "discovered_at", + "nullable": true, + "role": "timestamp", + "type": "timestamp" + }, + { + "id": "last_seen", + "nullable": true, + "role": "timestamp", + "type": "timestamp" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "port_name", + "label": "Port", + "projection": { + "column": "port_name", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "remote_actor", + "label": "Remote Actor", + "projection": { + "actor_column": "remote_actor", + "kind": "actor_ref_label" + } + }, + { + "cell": "text", + "id": "remote_port_name", + "label": "Remote Port", + "projection": { + "column": "remote_port_name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "type", + "label": "Type", + "projection": { + "column": "type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "state", + "label": "State", + "projection": { + "column": "state", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "evidence_count", + "label": "Evidence", + "projection": { + "column": "evidence_count", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "protocol", + "label": "Protocol", + "projection": { + "column": "protocol", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "remote_if_index", + "label": "Remote Port ID", + "projection": { + "column": "remote_if_index", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "remote_port_id", + "label": "Remote Source Port ID", + "projection": { + "column": "remote_port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "confidence", + "label": "Confidence", + "projection": { + "column": "confidence", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "inference", + "label": "Inference", + "projection": { + "column": "inference", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "attachment_mode", + "label": "Attachment", + "projection": { + "column": "attachment_mode", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "discovered_at", + "label": "Discovered", + "projection": { + "column": "discovered_at", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "timestamp", + "id": "last_seen", + "label": "Last Seen", + "projection": { + "column": "last_seen", + "kind": "direct" + }, + "visibility": "expanded" + } + ], + "label": "Port Neighbors", + "order": 2 + }, + "role": "actor_detail" + }, + "actor_ports": { + "aggregation": "append", + "columns": [ + { + "id": "actor", + "role": "reference", + "type": "actor_ref" + }, + { + "id": "if_index", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "port_id", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "if_name", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "if_descr", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "if_alias", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "mac", + "nullable": true, + "type": "string_ref" + }, + { + "id": "speed", + "nullable": true, + "type": "uint" + }, + { + "dictionary": "strings", + "id": "topology_role", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "oper_status", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "admin_status", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "port_type", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "link_mode", + "nullable": true, + "type": "string_ref" + }, + { + "dictionary": "strings", + "id": "stp_state", + "nullable": true, + "type": "string_ref" + }, + { + "id": "vlan_ids", + "nullable": true, + "type": "array" + }, + { + "id": "fdb_mac_count", + "nullable": true, + "type": "uint" + }, + { + "id": "link_count", + "nullable": true, + "type": "uint" + }, + { + "id": "neighbor_count", + "nullable": true, + "type": "uint" + }, + { + "id": "neighbor_actor", + "nullable": true, + "role": "reference", + "type": "actor_ref" + }, + { + "dictionary": "strings", + "id": "neighbor_port_name", + "nullable": true, + "type": "string_ref" + }, + { + "id": "neighbors", + "nullable": true, + "type": "json" + }, + { + "id": "vlans", + "nullable": true, + "type": "json" + }, + { + "id": "extra", + "nullable": true, + "type": "json" + } + ], + "owner": "actor", + "presentation": { + "columns": [ + { + "cell": "number", + "id": "if_index", + "label": "Port ID", + "projection": { + "column": "if_index", + "kind": "direct" + } + }, + { + "cell": "text", + "id": "name", + "label": "Port", + "projection": { + "column": "name", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "oper_status", + "label": "Status", + "projection": { + "column": "oper_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "admin_status", + "label": "Admin", + "projection": { + "column": "admin_status", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "port_type", + "label": "Type", + "projection": { + "column": "port_type", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "link_mode", + "label": "Mode", + "projection": { + "column": "link_mode", + "kind": "direct" + } + }, + { + "cell": "badge", + "id": "topology_role", + "label": "Role", + "projection": { + "column": "topology_role", + "kind": "direct" + } + }, + { + "cell": "array_count", + "id": "vlan_ids", + "label": "VLANs", + "projection": { + "column": "vlan_ids", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "fdb_mac_count", + "label": "FDB", + "projection": { + "column": "fdb_mac_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "link_count", + "label": "Links", + "projection": { + "column": "link_count", + "kind": "direct" + } + }, + { + "cell": "number", + "id": "neighbor_count", + "label": "Neighbors", + "projection": { + "column": "neighbor_count", + "kind": "direct" + } + }, + { + "cell": "actor_link", + "id": "neighbor_actor", + "label": "Neighbor", + "projection": { + "actor_column": "neighbor_actor", + "kind": "actor_ref_label" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "neighbor_port_name", + "label": "Neighbor Port", + "projection": { + "column": "neighbor_port_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_name", + "label": "ifName", + "projection": { + "column": "if_name", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_descr", + "label": "ifDescr", + "projection": { + "column": "if_descr", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "if_alias", + "label": "Alias", + "projection": { + "column": "if_alias", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "port_id", + "label": "Source Port ID", + "projection": { + "column": "port_id", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "text", + "id": "mac", + "label": "MAC", + "projection": { + "column": "mac", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "number", + "id": "speed", + "label": "Speed", + "projection": { + "column": "speed", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "badge", + "id": "stp_state", + "label": "STP", + "projection": { + "column": "stp_state", + "kind": "direct" + }, + "visibility": "expanded" + }, + { + "cell": "debug_json", + "id": "neighbors", + "label": "Neighbor Data", + "projection": { + "column": "neighbors", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "vlans", + "label": "VLAN Data", + "projection": { + "column": "vlans", + "kind": "direct" + }, + "visibility": "debug" + }, + { + "cell": "debug_json", + "id": "extra", + "label": "Extra", + "projection": { + "column": "extra", + "kind": "direct" + }, + "visibility": "debug" + } + ], + "label": "Ports", + "order": 1 + }, + "role": "actor_detail" + } + } + }, + "view": { + "id": "summary", + "mode": "detailed", + "scope": "network" + } +} diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links.go b/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links.go index 65aed9c45511cd..036a2167f2c2ab 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links.go @@ -284,15 +284,6 @@ func recordTopologyBGPEnrichmentStats(data *topologyData, stats topologyBGPEnric if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["bgp_peer_rows"] = stats.observedRows - data.Stats["bgp_peer_detail_rows"] = stats.attachedPeerRows - data.Stats["bgp_adjacency_emitted_links"] = stats.emittedLinks - data.Stats["bgp_adjacency_suppressed_non_established_state"] = stats.suppressedNonEstablished - data.Stats["bgp_adjacency_suppressed_unresolved_local"] = stats.suppressedUnresolvedLocal - data.Stats["bgp_adjacency_suppressed_unresolved_neighbor"] = stats.suppressedUnresolvedNeighbor - data.Stats["bgp_adjacency_suppressed_self_actor"] = stats.suppressedSelfActor - data.Stats["bgp_adjacency_suppressed_duplicate_link"] = stats.suppressedDuplicateLink + data.Stats.BGP = stats + data.Stats.HasBGP = true } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links_test.go index c9b60ee014b031..ad378444a9fa3c 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_bgp_links_test.go @@ -30,10 +30,10 @@ func TestApplyTopologyBGPAdjacencyEnrichmentEmitsEstablishedManagedLink(t *testi require.Equal(t, "established", link.State) require.Equal(t, "65001", link.Src.Attributes["as"]) require.Equal(t, "65002", link.Dst.Attributes["as"]) - require.Equal(t, 1, data.Stats["bgp_peer_rows"]) - require.Equal(t, 1, data.Stats["bgp_peer_detail_rows"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_emitted_links"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_peer_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_peer_detail_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) require.Len(t, data.Actors[0].Tables["bgp_peers"], 1) require.Equal(t, "router-b", data.Actors[0].Tables["bgp_peers"][0]["remote_actor_id"]) } @@ -137,7 +137,7 @@ func TestApplyTopologyBGPAdjacencyEnrichmentKeepsSuppressedPeersAsDetailOnly(t * require.NotContains(t, row, "remote_actor_id") } if tc.wantSuppressedStatsCounter != "" { - require.Equal(t, 1, tc.data.Stats[tc.wantSuppressedStatsCounter]) + require.Equal(t, 1, topologyStatsToV1(tc.data.Stats)[tc.wantSuppressedStatsCounter]) } }) } @@ -170,7 +170,7 @@ func TestApplyTopologyBGPAdjacencyEnrichmentBuildsManagedLinks(t *testing.T) { require.Len(t, data.Links, 1) require.Len(t, data.Actors[0].Tables["bgp_peers"], 1) require.Len(t, data.Actors[1].Tables["bgp_peers"], 1) - require.Equal(t, 1, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) }, }, "deduplicates-asymmetric-local-ip-observations": { @@ -194,7 +194,7 @@ func TestApplyTopologyBGPAdjacencyEnrichmentBuildsManagedLinks(t *testing.T) { require.Len(t, data.Links, 1) require.Len(t, data.Actors[0].Tables["bgp_peers"], 1) require.Len(t, data.Actors[1].Tables["bgp_peers"], 1) - require.Equal(t, 1, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) }, }, "keeps-routing-instances-separate": { @@ -215,7 +215,7 @@ func TestApplyTopologyBGPAdjacencyEnrichmentBuildsManagedLinks(t *testing.T) { require.Equal(t, 2, stats.emittedLinks) require.Len(t, data.Links, 2) - require.Equal(t, 2, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 2, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) require.ElementsMatch(t, []any{"blue", "red"}, []any{data.Links[0].Metrics["routing_instance"], data.Links[1].Metrics["routing_instance"]}) }, }, @@ -239,7 +239,7 @@ func TestApplyTopologyBGPAdjacencyEnrichmentBuildsManagedLinks(t *testing.T) { require.Equal(t, 1, stats.suppressedDuplicateLink) require.Len(t, data.Links, 1) require.Len(t, data.Actors[0].Tables["bgp_peers"], 2) - require.Equal(t, 1, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) }, }, } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_cache_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_cache_test.go index c6f58c96f95e76..6e7f7f4cb36439 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_cache_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_cache_test.go @@ -425,8 +425,8 @@ func TestTopologyCache_SnapshotBidirectionalPairMetadata(t *testing.T) { require.Equal(t, "lldp", link.Protocol) require.Equal(t, "bidirectional", link.Direction) require.Equal(t, true, link.Metrics["pair_consistent"]) - require.Equal(t, 1, data.Stats["links_bidirectional"]) - require.Equal(t, 0, data.Stats["links_unidirectional"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["links_bidirectional"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["links_unidirectional"]) } func TestTopologyCache_SnapshotMergesRemoteIdentityAcrossProtocols(t *testing.T) { diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links.go b/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links.go index c237def165932c..f4b217a2f605da 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links.go @@ -157,19 +157,6 @@ func recordTopologyL3EnrichmentStats(data *topologyData, stats topologyL3Enrichm if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["l3_subnet_candidate_subnets"] = stats.subnetStats.candidateSubnets - data.Stats["l3_subnet_candidate_links"] = stats.subnetStats.candidateLinks - data.Stats["l3_subnet_emitted_links"] = stats.emittedLinks - data.Stats["l3_subnet_suppressed_invalid"] = stats.subnetStats.suppressedInvalid - data.Stats["l3_subnet_suppressed_unsupported_prefix"] = stats.subnetStats.suppressedUnsupportedPrefix - data.Stats["l3_subnet_suppressed_duplicate_ip"] = stats.subnetStats.suppressedDuplicateIP - data.Stats["l3_subnet_suppressed_self_link"] = stats.subnetStats.suppressedSelfLink - data.Stats["l3_subnet_suppressed_unmatched"] = stats.subnetStats.suppressedUnmatched - data.Stats["l3_subnet_suppressed_multi_access"] = stats.subnetStats.suppressedMultiAccess - data.Stats["l3_subnet_suppressed_unresolved_actor"] = stats.suppressedUnresolvedActor - data.Stats["l3_subnet_suppressed_self_actor"] = stats.suppressedSelfActor - data.Stats["l3_subnet_suppressed_duplicate_link"] = stats.suppressedDuplicateLink + data.Stats.L3 = stats + data.Stats.HasL3 = true } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links_test.go index 53d370d6034b6d..89e4ac8f86b759 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_l3_links_test.go @@ -99,10 +99,10 @@ func TestApplyTopologyL3SubnetEnrichmentSuppressions(t *testing.T) { require.Equal(t, tc.wantUnresolvedActor, stats.suppressedUnresolvedActor) require.Equal(t, tc.wantSelfActor, stats.suppressedSelfActor) require.Equal(t, tc.wantDuplicateLink, stats.suppressedDuplicateLink) - require.Equal(t, 1, tc.data.Stats["l3_subnet_candidate_links"]) - require.Equal(t, 0, tc.data.Stats["l3_subnet_emitted_links"]) - require.Equal(t, tc.wantVisibleLinks, tc.data.Stats["l3_subnet_visible_links"]) - require.Equal(t, 1, tc.data.Stats[tc.wantSuppressedMetricName]) + require.Equal(t, 1, topologyStatsToV1(tc.data.Stats)["l3_subnet_candidate_links"]) + require.Equal(t, 0, topologyStatsToV1(tc.data.Stats)["l3_subnet_emitted_links"]) + require.Equal(t, tc.wantVisibleLinks, topologyStatsToV1(tc.data.Stats)["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(tc.data.Stats)[tc.wantSuppressedMetricName]) }) } } @@ -143,8 +143,9 @@ func TestTopologyL3SubnetLinkKeySeparatesDelimitedFields(t *testing.T) { func TestApplyTopologyDepthFocusFilterKeepsIncidentL3SubnetLink(t *testing.T) { data := topologyData{ - Stats: map[string]any{ - "l3_subnet_emitted_links": 1, + Stats: topologyStats{ + L3: topologyL3EnrichmentStats{emittedLinks: 1}, + HasL3: true, }, Actors: []topologyActor{ topologyL3ManagedActorForTest("router-a", nil, "198.51.100.1"), @@ -175,8 +176,8 @@ func TestApplyTopologyDepthFocusFilterKeepsIncidentL3SubnetLink(t *testing.T) { require.Len(t, data.Actors, 2) require.Len(t, data.Links, 1) require.Equal(t, topologyL3SubnetLinkType, data.Links[0].LinkType) - require.Equal(t, 1, data.Stats["l3_subnet_emitted_links"]) - require.Equal(t, 1, data.Stats["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_visible_links"]) } func topologyL3ManagedActorForTest(actorID string, attrs map[string]any, ips ...string) topologyActor { diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links.go b/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links.go index 5767b5223b5e79..5dd22726c6f6d2 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links.go @@ -220,15 +220,6 @@ func recordTopologyOSPFEnrichmentStats(data *topologyData, stats topologyOSPFEnr if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["ospf_neighbor_rows"] = stats.observedRows - data.Stats["ospf_neighbor_detail_rows"] = stats.attachedNeighborRows - data.Stats["ospf_adjacency_emitted_links"] = stats.emittedLinks - data.Stats["ospf_adjacency_suppressed_non_full_state"] = stats.suppressedNonFullState - data.Stats["ospf_adjacency_suppressed_unresolved_local"] = stats.suppressedUnresolvedLocal - data.Stats["ospf_adjacency_suppressed_unresolved_neighbor"] = stats.suppressedUnresolvedNeighbor - data.Stats["ospf_adjacency_suppressed_self_actor"] = stats.suppressedSelfActor - data.Stats["ospf_adjacency_suppressed_duplicate_link"] = stats.suppressedDuplicateLink + data.Stats.OSPF = stats + data.Stats.HasOSPF = true } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links_test.go index 580ebf27f59686..a06ca3fd37362c 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_ospf_links_test.go @@ -30,10 +30,10 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentEmitsFullManagedLink(t *testing.T) require.Equal(t, "full", link.State) require.Equal(t, "1.1.1.1", link.Src.Attributes["router_id"]) require.Equal(t, "2.2.2.2", link.Dst.Attributes["router_id"]) - require.Equal(t, 1, data.Stats["ospf_neighbor_rows"]) - require.Equal(t, 1, data.Stats["ospf_neighbor_detail_rows"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_emitted_links"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_neighbor_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_neighbor_detail_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_visible_links"]) require.Len(t, data.Actors[0].Tables["ospf_neighbors"], 1) require.Equal(t, "router-b", data.Actors[0].Tables["ospf_neighbors"][0]["remote_actor_id"]) } @@ -133,7 +133,7 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsSuppressedNeighborsAsDetailOnl require.NotContains(t, row, "remote_actor_id") } if tc.wantSuppressedStatsCounter != "" { - require.Equal(t, 1, tc.data.Stats[tc.wantSuppressedStatsCounter]) + require.Equal(t, 1, topologyStatsToV1(tc.data.Stats)[tc.wantSuppressedStatsCounter]) } }) } @@ -180,8 +180,9 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsMatchingL3SubnetEdge(t *testin }, } data := topologyData{ - Stats: map[string]any{ - "l3_subnet_emitted_links": 1, + Stats: topologyStats{ + L3: topologyL3EnrichmentStats{emittedLinks: 1}, + HasL3: true, }, Actors: []topologyActor{ topologyOSPFManagedActorForTest("router-a", "device-a", "1.1.1.1", "198.51.100.1"), @@ -201,8 +202,8 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsMatchingL3SubnetEdge(t *testin require.Len(t, data.Links, 2) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyL3SubnetLinkType)) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyOSPFAdjacencyLinkType)) - require.Equal(t, 1, data.Stats["l3_subnet_visible_links"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_visible_links"]) } func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsUnrelatedL3SubnetEdge(t *testing.T) { @@ -223,8 +224,9 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsUnrelatedL3SubnetEdge(t *testi }, } data := topologyData{ - Stats: map[string]any{ - "l3_subnet_emitted_links": 1, + Stats: topologyStats{ + L3: topologyL3EnrichmentStats{emittedLinks: 1}, + HasL3: true, }, Actors: []topologyActor{ topologyOSPFManagedActorForTest("router-a", "device-a", "1.1.1.1", "198.51.100.1"), @@ -247,8 +249,8 @@ func TestApplyTopologyOSPFAdjacencyEnrichmentKeepsUnrelatedL3SubnetEdge(t *testi require.Len(t, data.Links, 2) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyL3SubnetLinkType)) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyOSPFAdjacencyLinkType)) - require.Equal(t, 1, data.Stats["l3_subnet_visible_links"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_visible_links"]) } func TestApplyTopologyOSPFAdjacencyEnrichmentResolvesUnnumberedNeighborByRouterID(t *testing.T) { diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_registry_build.go b/src/go/plugin/go.d/collector/snmp_topology/topology_registry_build.go index 17e04e58833acf..c4fc601edd11b4 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_registry_build.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_registry_build.go @@ -106,7 +106,7 @@ func buildSNMPL2TopologyData( return topologyData{}, false } - data := topologyengine.ToGraph(result, topologyengine.GraphOptions{ + graphData, stats := topologyengine.ToGraph(result, topologyengine.GraphOptions{ SchemaVersion: topologySchemaVersion, Source: "snmp", Layer: "2", @@ -120,5 +120,19 @@ func buildSNMPL2TopologyData( ProbabilisticConnectivity: isTopologyMapTypeProbable(options.MapType), InferenceStrategy: options.InferenceStrategy, }) + data := topologyData{ + SchemaVersion: graphData.SchemaVersion, + Source: graphData.Source, + Layer: graphData.Layer, + AgentID: graphData.AgentID, + CollectedAt: graphData.CollectedAt, + View: graphData.View, + Actors: graphData.Actors, + Links: graphData.Links, + Stats: topologyStats{ + L2: stats, + HasL2: true, + }, + } return data, true } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_registry_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_registry_test.go index 46d4680ac5dc4e..447e2eb51cb3c9 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_registry_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_registry_test.go @@ -76,9 +76,9 @@ func TestTopologyRegistry_SnapshotAggregatesAcrossCaches(t *testing.T) { require.Equal(t, "snmp", data.Source) require.Equal(t, "summary", data.View) - require.GreaterOrEqual(t, data.Stats["devices_total"].(int), 2) - require.GreaterOrEqual(t, data.Stats["links_total"].(int), 1) - require.GreaterOrEqual(t, data.Stats["links_lldp"].(int), 1) + require.GreaterOrEqual(t, topologyStatsToV1(data.Stats)["devices_total"].(int), 2) + require.GreaterOrEqual(t, topologyStatsToV1(data.Stats)["links_total"].(int), 1) + require.GreaterOrEqual(t, topologyStatsToV1(data.Stats)["links_lldp"].(int), 1) } func TestTopologyRegistry_SnapshotSingleCacheKeepsLLDPUnidirectional(t *testing.T) { @@ -119,8 +119,8 @@ func TestTopologyRegistry_SnapshotSingleCacheKeepsLLDPUnidirectional(t *testing. require.Equal(t, "unidirectional", data.Links[0].Direction) _, hasPairConsistency := data.Links[0].Metrics["pair_consistent"] require.False(t, hasPairConsistency) - require.Equal(t, 1, data.Stats["links_unidirectional"].(int)) - require.Equal(t, 0, data.Stats["links_bidirectional"].(int)) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["links_unidirectional"].(int)) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["links_bidirectional"].(int)) } func TestTopologyRegistry_DefaultMapEmitsL3SubnetForManagedRoutersWithoutLLDP(t *testing.T) { @@ -184,9 +184,9 @@ func TestTopologyRegistry_DefaultMapEmitsL3SubnetForManagedRoutersWithoutLLDP(t require.Equal(t, "logical_l3_subnet", link.Metrics["attachment_mode"]) require.Equal(t, "198.51.100.1", link.Src.Attributes["ip"]) require.Equal(t, "198.51.100.2", link.Dst.Attributes["ip"]) - require.Equal(t, 1, data.Stats["l3_subnet_emitted_links"]) - require.Equal(t, 1, data.Stats["l3_subnet_visible_links"]) - require.Equal(t, 1, data.Stats["links_total"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["links_total"]) } func TestTopologyRegistry_OSPFSnapshotEnrichesSubnetAfterNeighborIngest(t *testing.T) { @@ -252,10 +252,10 @@ func TestTopologyRegistry_OSPFSnapshotEnrichesSubnetAfterNeighborIngest(t *testi require.Len(t, data.Links, 2) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyL3SubnetLinkType)) require.Equal(t, 1, countTopologyLinksByType(data.Links, topologyOSPFAdjacencyLinkType)) - require.Equal(t, 1, data.Stats["l3_subnet_emitted_links"]) - require.Equal(t, 1, data.Stats["l3_subnet_visible_links"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_emitted_links"]) - require.Equal(t, 1, data.Stats["ospf_adjacency_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["l3_subnet_visible_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["ospf_adjacency_visible_links"]) } func TestTopologyRegistry_BGPAdjacencyEmitsEstablishedManagedPeerLinkAndDetailRows(t *testing.T) { @@ -321,11 +321,11 @@ func TestTopologyRegistry_BGPAdjacencyEmitsEstablishedManagedPeerLinkAndDetailRo require.Equal(t, "default", link.Metrics["routing_instance"]) require.Equal(t, "65001", link.Src.Attributes["as"]) require.Equal(t, "65002", link.Dst.Attributes["as"]) - require.Equal(t, 2, data.Stats["bgp_peer_rows"]) - require.Equal(t, 2, data.Stats["bgp_peer_detail_rows"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_emitted_links"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_suppressed_duplicate_link"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 2, topologyStatsToV1(data.Stats)["bgp_peer_rows"]) + require.Equal(t, 2, topologyStatsToV1(data.Stats)["bgp_peer_detail_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_emitted_links"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_suppressed_duplicate_link"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) routerA := findDeviceActorBySysName(data, "router-a") require.NotNil(t, routerA) @@ -376,11 +376,11 @@ func TestTopologyRegistry_BGPAdjacencyKeepsUnresolvedAndNonEstablishedPeersAsDet require.True(t, ok) require.Empty(t, data.Links) - require.Equal(t, 2, data.Stats["bgp_peer_rows"]) - require.Equal(t, 2, data.Stats["bgp_peer_detail_rows"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_suppressed_unresolved_neighbor"]) - require.Equal(t, 1, data.Stats["bgp_adjacency_suppressed_non_established_state"]) - require.Equal(t, 0, data.Stats["bgp_adjacency_visible_links"]) + require.Equal(t, 2, topologyStatsToV1(data.Stats)["bgp_peer_rows"]) + require.Equal(t, 2, topologyStatsToV1(data.Stats)["bgp_peer_detail_rows"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_suppressed_unresolved_neighbor"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["bgp_adjacency_suppressed_non_established_state"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["bgp_adjacency_visible_links"]) routerA := findDeviceActorBySysName(data, "router-a") require.NotNil(t, routerA) @@ -432,8 +432,8 @@ func TestTopologyRegistry_SnapshotWithOptions_LLDPManagedKeepsRequestedMapType(t Depth: topologyDepthAllInternal, }) require.True(t, ok) - require.Equal(t, topologyMapTypeLLDPCDPManaged, data.Stats["map_type"]) - require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, data.Stats["inference_strategy"]) + require.Equal(t, topologyMapTypeLLDPCDPManaged, topologyStatsToV1(data.Stats)["map_type"]) + require.Equal(t, topologyInferenceStrategyFDBMinimumKnowledge, topologyStatsToV1(data.Stats)["inference_strategy"]) } func TestTopologyRegistry_SnapshotWithOptions_CollapseByIPPreservesEngineManagedOverlapPruning(t *testing.T) { @@ -496,7 +496,7 @@ func TestTopologyRegistry_SnapshotWithOptions_CollapseByIPPreservesEngineManaged require.True(t, ok) require.NotNil(t, findActorByMAC(withCollapse, "9c:6b:00:7b:98:c6")) require.Nil(t, findActorByMAC(withCollapse, "9c:6b:00:7b:98:c7")) - require.Equal(t, 1, withCollapse.Stats["actors_unlinked_suppressed"]) + require.Equal(t, 1, topologyStatsToV1(withCollapse.Stats)["actors_unlinked_suppressed"]) } func TestTopologyRegistry_ManagedDeviceFocusTargets_ReturnsPerDeviceIPTargets(t *testing.T) { @@ -752,7 +752,7 @@ func TestTopologyRegistry_SnapshotDeduplicatesDuplicateDeviceObservations(t *tes require.True(t, ok) require.Len(t, data.Links, 1) - require.Equal(t, 1, data.Stats["links_total"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["links_total"]) require.Equal(t, 2, countActorsByType(data, "device")) } @@ -794,7 +794,6 @@ func TestApplySNMPTopologyShapePolicies_CollapsesActorsByIP(t *testing.T) { Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applySNMPTopologyShapePolicies(&data, topologyQueryOptions{ @@ -804,7 +803,7 @@ func TestApplySNMPTopologyShapePolicies_CollapsesActorsByIP(t *testing.T) { require.Len(t, data.Actors, 1) require.Len(t, data.Links, 0) - require.Equal(t, 1, data.Stats["actors_collapsed_by_ip"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["actors_collapsed_by_ip"]) } func TestApplySNMPTopologyShapePolicies_EliminatesNonIPInferredActorsAndSparseSegments(t *testing.T) { @@ -833,7 +832,6 @@ func TestApplySNMPTopologyShapePolicies_EliminatesNonIPInferredActorsAndSparseSe Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applySNMPTopologyShapePolicies(&data, topologyQueryOptions{ @@ -843,8 +841,8 @@ func TestApplySNMPTopologyShapePolicies_EliminatesNonIPInferredActorsAndSparseSe require.Len(t, data.Actors, 0) require.Len(t, data.Links, 0) - require.Equal(t, 1, data.Stats["actors_non_ip_inferred_suppressed"]) - require.Equal(t, 1, data.Stats["segments_sparse_suppressed"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["actors_non_ip_inferred_suppressed"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["segments_sparse_suppressed"]) } func TestApplySNMPTopologyShapePolicies_HighConfidenceSuppressesUnlinkedInferredEndpoints(t *testing.T) { @@ -877,7 +875,6 @@ func TestApplySNMPTopologyShapePolicies_HighConfidenceSuppressesUnlinkedInferred Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applySNMPTopologyShapePolicies(&data, topologyQueryOptions{ @@ -885,7 +882,7 @@ func TestApplySNMPTopologyShapePolicies_HighConfidenceSuppressesUnlinkedInferred }) require.Len(t, data.Actors, 2) - require.Equal(t, 1, data.Stats["actors_map_type_suppressed"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["actors_map_type_suppressed"]) for _, actor := range data.Actors { require.NotEqual(t, "endpoint:unlinked", actor.ActorID) } @@ -927,7 +924,6 @@ func TestApplySNMPTopologyShapePolicies_LLDPManagedMapKeepsOnlyLLDPCDPAndManaged Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applySNMPTopologyShapePolicies(&data, topologyQueryOptions{ @@ -937,7 +933,7 @@ func TestApplySNMPTopologyShapePolicies_LLDPManagedMapKeepsOnlyLLDPCDPAndManaged require.Len(t, data.Actors, 2) require.Len(t, data.Links, 1) require.Equal(t, "lldp", data.Links[0].Protocol) - require.Equal(t, 1, data.Stats["actors_map_type_suppressed"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["actors_map_type_suppressed"]) } func TestMarkProbableDeltaLinks_MarksAllAddedLinksAsProbable(t *testing.T) { @@ -950,7 +946,6 @@ func TestMarkProbableDeltaLinks_MarksAllAddedLinksAsProbable(t *testing.T) { Direction: "bidirectional", }, }, - Stats: map[string]any{}, } probableData := topologyData{ Links: []topologyLink{ @@ -970,7 +965,6 @@ func TestMarkProbableDeltaLinks_MarksAllAddedLinksAsProbable(t *testing.T) { }, }, }, - Stats: map[string]any{}, } markProbableDeltaLinks(&strictData, &probableData) @@ -1030,7 +1024,6 @@ func TestApplyTopologyDepthFocusFilter_ManagedFocusDepthZero(t *testing.T) { Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applyTopologyDepthFocusFilter(&data, topologyQueryOptions{ @@ -1041,8 +1034,8 @@ func TestApplyTopologyDepthFocusFilter_ManagedFocusDepthZero(t *testing.T) { require.Len(t, data.Actors, 1) require.Len(t, data.Links, 0) - require.Equal(t, "ip:10.0.0.1", data.Stats["managed_snmp_device_focus"]) - require.Equal(t, 0, data.Stats["depth"]) + require.Equal(t, "ip:10.0.0.1", topologyStatsToV1(data.Stats)["managed_snmp_device_focus"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["depth"]) } func TestApplyTopologyDepthFocusFilter_ManagedFocusDepthOneIncludesDirectNeighbors(t *testing.T) { @@ -1093,7 +1086,6 @@ func TestApplyTopologyDepthFocusFilter_ManagedFocusDepthOneIncludesDirectNeighbo Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applyTopologyDepthFocusFilter(&data, topologyQueryOptions{ @@ -1104,8 +1096,8 @@ func TestApplyTopologyDepthFocusFilter_ManagedFocusDepthOneIncludesDirectNeighbo require.Len(t, data.Actors, 4) require.Len(t, data.Links, 3) - require.Equal(t, "ip:10.0.0.1", data.Stats["managed_snmp_device_focus"]) - require.Equal(t, 1, data.Stats["depth"]) + require.Equal(t, "ip:10.0.0.1", topologyStatsToV1(data.Stats)["managed_snmp_device_focus"]) + require.Equal(t, 1, topologyStatsToV1(data.Stats)["depth"]) } func TestApplyTopologyDepthFocusFilter_MultiFocusDepthZeroIncludesAllShortestPaths(t *testing.T) { @@ -1162,7 +1154,6 @@ func TestApplyTopologyDepthFocusFilter_MultiFocusDepthZeroIncludesAllShortestPat Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applyTopologyDepthFocusFilter(&data, topologyQueryOptions{ @@ -1181,8 +1172,8 @@ func TestApplyTopologyDepthFocusFilter_MultiFocusDepthZeroIncludesAllShortestPat actorIDs, ) require.Len(t, data.Links, 4) - require.Equal(t, "ip:10.0.0.1,ip:10.0.0.3", data.Stats["managed_snmp_device_focus"]) - require.Equal(t, 0, data.Stats["depth"]) + require.Equal(t, "ip:10.0.0.1,ip:10.0.0.3", topologyStatsToV1(data.Stats)["managed_snmp_device_focus"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["depth"]) } func TestApplyTopologyDepthFocusFilter_DepthExpandsFromSelectedRootsOnly(t *testing.T) { @@ -1233,7 +1224,6 @@ func TestApplyTopologyDepthFocusFilter_DepthExpandsFromSelectedRootsOnly(t *test Direction: "bidirectional", }, }, - Stats: map[string]any{}, } applyTopologyDepthFocusFilter(&data, topologyQueryOptions{ diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_graph_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_graph_test.go index 95fed80fa7322e..893d99fdf65e64 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_graph_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_graph_test.go @@ -173,11 +173,11 @@ func TestRecordTopologyFocusStatsNormalizesDepthAndFilteredCounts(t *testing.T) Depth: topologyDepthAllInternal, }, 5, 4) - require.Equal(t, "ip:10.0.0.1", data.Stats["managed_snmp_device_focus"]) - require.Equal(t, topologyDepthAll, data.Stats["depth"]) - require.Equal(t, 3, data.Stats["actors_focus_depth_filtered"]) - require.Equal(t, 3, data.Stats["links_focus_depth_filtered"]) - require.Equal(t, len(data.Links), intStatValue(data.Stats["links_total"])) + require.Equal(t, "ip:10.0.0.1", topologyStatsToV1(data.Stats)["managed_snmp_device_focus"]) + require.Equal(t, topologyDepthAll, topologyStatsToV1(data.Stats)["depth"]) + require.Equal(t, 3, topologyStatsToV1(data.Stats)["actors_focus_depth_filtered"]) + require.Equal(t, 3, topologyStatsToV1(data.Stats)["links_focus_depth_filtered"]) + require.Equal(t, len(data.Links), intStatValue(topologyStatsToV1(data.Stats)["links_total"])) } func TestRecordTopologyFocusAllDevicesStatsKeepsAllDepth(t *testing.T) { @@ -191,9 +191,9 @@ func TestRecordTopologyFocusAllDevicesStatsKeepsAllDepth(t *testing.T) { ManagedDeviceFocus: topologyManagedFocusAllDevices, }) - require.Equal(t, topologyManagedFocusAllDevices, data.Stats["managed_snmp_device_focus"]) - require.Equal(t, topologyDepthAll, data.Stats["depth"]) - require.Equal(t, 0, data.Stats["actors_focus_depth_filtered"]) - require.Equal(t, 0, data.Stats["links_focus_depth_filtered"]) - require.Equal(t, len(data.Links), intStatValue(data.Stats["links_total"])) + require.Equal(t, topologyManagedFocusAllDevices, topologyStatsToV1(data.Stats)["managed_snmp_device_focus"]) + require.Equal(t, topologyDepthAll, topologyStatsToV1(data.Stats)["depth"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["actors_focus_depth_filtered"]) + require.Equal(t, 0, topologyStatsToV1(data.Stats)["links_focus_depth_filtered"]) + require.Equal(t, len(data.Links), intStatValue(topologyStatsToV1(data.Stats)["links_total"])) } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_stats.go b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_stats.go index 70a0fa81788cf9..3aec74e727f26e 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_stats.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_focus_stats.go @@ -6,13 +6,11 @@ func recordTopologyFocusAllDevicesStats(data *topologyData, options topologyQuer if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["managed_snmp_device_focus"] = options.ManagedDeviceFocus - data.Stats["depth"] = topologyDepthAll - data.Stats["actors_focus_depth_filtered"] = 0 - data.Stats["links_focus_depth_filtered"] = 0 + data.Stats.Focus.ManagedSNMPDeviceFocus = options.ManagedDeviceFocus + data.Stats.Focus.Depth = topologyFocusDepth{All: true} + data.Stats.Focus.ActorsDepthFiltered = 0 + data.Stats.Focus.LinksDepthFiltered = 0 + data.Stats.HasFocus = true recomputeTopologyLinkStats(data) } @@ -20,16 +18,14 @@ func recordTopologyFocusStats(data *topologyData, options topologyQueryOptions, if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["managed_snmp_device_focus"] = options.ManagedDeviceFocus + data.Stats.Focus.ManagedSNMPDeviceFocus = options.ManagedDeviceFocus if options.Depth == topologyDepthAllInternal { - data.Stats["depth"] = topologyDepthAll + data.Stats.Focus.Depth = topologyFocusDepth{All: true} } else { - data.Stats["depth"] = options.Depth + data.Stats.Focus.Depth = topologyFocusDepth{Value: options.Depth} } - data.Stats["actors_focus_depth_filtered"] = beforeActors - len(data.Actors) - data.Stats["links_focus_depth_filtered"] = beforeLinks - len(data.Links) + data.Stats.Focus.ActorsDepthFiltered = beforeActors - len(data.Actors) + data.Stats.Focus.LinksDepthFiltered = beforeLinks - len(data.Links) + data.Stats.HasFocus = true recomputeTopologyLinkStats(data) } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_policies.go b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_policies.go index 9cc6f0b3898604..7e01b0014770cb 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_policies.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_policies.go @@ -41,19 +41,14 @@ func applySNMPTopologyShapePolicies(data *topologyData, options topologyQueryOpt return topologyLinkSortKey(data.Links[i]) < topologyLinkSortKey(data.Links[j]) }) - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["actors_collapsed_by_ip"] = collapsed - data.Stats["actors_non_ip_inferred_suppressed"] = removedNonIP - data.Stats["actors_map_type_suppressed"] = removedByMapType - data.Stats["segments_sparse_suppressed"] = removedSparseSegments - data.Stats["map_type"] = options.MapType + data.Stats.Shape.ActorsCollapsedByIP = collapsed + data.Stats.Shape.ActorsNonIPInferredSuppressed = removedNonIP + data.Stats.Shape.ActorsMapTypeSuppressed = removedByMapType + data.Stats.Shape.SegmentsSparseSuppressed = removedSparseSegments + data.Stats.Shape.MapType = options.MapType if strategy := normalizeTopologyInferenceStrategy(options.InferenceStrategy); strategy != "" { - data.Stats["inference_strategy"] = strategy - } - if removedSparseSegments > 0 { - data.Stats["segments_suppressed"] = intStatValue(data.Stats["segments_suppressed"]) + removedSparseSegments + data.Stats.Shape.InferenceStrategy = strategy } + data.Stats.HasShape = true recomputeTopologyLinkStats(data) } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats.go b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats.go index 5075c0fbd36e24..75bfb9ee5447c9 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats.go @@ -10,11 +10,8 @@ func recomputeTopologyLinkStats(data *topologyData) { if data == nil { return } - if data.Stats == nil { - data.Stats = make(map[string]any) - } - data.Stats["actors_total"] = len(data.Actors) - data.Stats["links_total"] = len(data.Links) + data.Stats.Recomputed.ActorsTotal = len(data.Actors) + data.Stats.Recomputed.LinksTotal = len(data.Links) probable := 0 for _, link := range data.Links { @@ -25,17 +22,15 @@ func recomputeTopologyLinkStats(data *topologyData) { probable++ } } - data.Stats["links_probable"] = probable + data.Stats.Recomputed.LinksProbable = probable + data.Stats.HasComputed = true recomputeTopologyL3VisibleLinkStats(data) recomputeTopologyOSPFVisibleLinkStats(data) recomputeTopologyBGPVisibleLinkStats(data) } func recomputeTopologyL3VisibleLinkStats(data *topologyData) { - if data == nil || data.Stats == nil { - return - } - if _, ok := data.Stats["l3_subnet_emitted_links"]; !ok { + if data == nil || !data.Stats.HasL3 { return } count := 0 @@ -44,14 +39,11 @@ func recomputeTopologyL3VisibleLinkStats(data *topologyData) { count++ } } - data.Stats["l3_subnet_visible_links"] = count + data.Stats.Recomputed.L3SubnetVisibleLinks = count } func recomputeTopologyOSPFVisibleLinkStats(data *topologyData) { - if data == nil || data.Stats == nil { - return - } - if _, ok := data.Stats["ospf_adjacency_emitted_links"]; !ok { + if data == nil || !data.Stats.HasOSPF { return } count := 0 @@ -60,14 +52,11 @@ func recomputeTopologyOSPFVisibleLinkStats(data *topologyData) { count++ } } - data.Stats["ospf_adjacency_visible_links"] = count + data.Stats.Recomputed.OSPFAdjacencyVisibleLinks = count } func recomputeTopologyBGPVisibleLinkStats(data *topologyData) { - if data == nil || data.Stats == nil { - return - } - if _, ok := data.Stats["bgp_adjacency_emitted_links"]; !ok { + if data == nil || !data.Stats.HasBGP { return } count := 0 @@ -76,5 +65,5 @@ func recomputeTopologyBGPVisibleLinkStats(data *topologyData) { count++ } } - data.Stats["bgp_adjacency_visible_links"] = count + data.Stats.Recomputed.BGPAdjacencyVisibleLinks = count } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats_test.go b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats_test.go index a2e9416658e4de..07b257ae10b610 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats_test.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_shape_stats_test.go @@ -27,8 +27,9 @@ func TestApplySNMPTopologyShapePolicies_EmitsStatsContractKeys(t *testing.T) { MapType: topologyMapTypeHighConfidenceInferred, }) - keys := make([]string, 0, len(data.Stats)) - for key := range data.Stats { + stats := topologyStatsToV1(data.Stats) + keys := make([]string, 0, len(stats)) + for key := range stats { keys = append(keys, key) } require.ElementsMatch(t, []string{ @@ -58,20 +59,20 @@ func TestApplySNMPTopologyShapePolicies_EmitsStatsContractKeys(t *testing.T) { } for name, tc := range expectedStats { t.Run("stat/"+name, func(t *testing.T) { - require.Equal(t, tc.want, data.Stats[tc.key]) + require.Equal(t, tc.want, topologyStatsToV1(data.Stats)[tc.key]) }) } } func TestRecomputeTopologyLinkStatsRefreshesExistingLogicalL3VisibleCounts(t *testing.T) { data := &topologyData{ - Stats: map[string]any{ - "l3_subnet_emitted_links": 2, - "ospf_adjacency_emitted_links": 1, - "bgp_adjacency_emitted_links": 1, - "l3_subnet_visible_links": 2, - "ospf_adjacency_visible_links": 1, - "bgp_adjacency_visible_links": 1, + Stats: topologyStats{ + L3: topologyL3EnrichmentStats{emittedLinks: 2}, + HasL3: true, + OSPF: topologyOSPFEnrichmentStats{emittedLinks: 1}, + HasOSPF: true, + BGP: topologyBGPEnrichmentStats{emittedLinks: 1}, + HasBGP: true, }, Links: []topologyLink{ {Protocol: topologyL3SubnetLinkType, LinkType: topologyL3SubnetLinkType}, @@ -96,7 +97,7 @@ func TestRecomputeTopologyLinkStatsRefreshesExistingLogicalL3VisibleCounts(t *te } for name, tc := range expectedStats { t.Run("stat/"+name, func(t *testing.T) { - require.Equal(t, tc.want, data.Stats[tc.key]) + require.Equal(t, tc.want, topologyStatsToV1(data.Stats)[tc.key]) }) } } diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_stats.go b/src/go/plugin/go.d/collector/snmp_topology/topology_stats.go new file mode 100644 index 00000000000000..8e09720e87d2f7 --- /dev/null +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_stats.go @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package snmptopology + +func topologyStatsToV1(stats topologyStats) map[string]any { + if !stats.HasL2 && !stats.HasShape && !stats.HasFocus && !stats.HasL3 && !stats.HasOSPF && !stats.HasBGP && !stats.HasComputed { + return nil + } + + out := make(map[string]any) + if stats.HasL2 { + addTopologyL2Stats(out, stats) + } + if stats.HasShape { + addTopologyShapeStats(out, stats) + } + if stats.HasFocus { + addTopologyFocusStats(out, stats.Focus) + } + if stats.HasL3 { + addTopologyL3Stats(out, stats) + } + if stats.HasOSPF { + addTopologyOSPFStats(out, stats) + } + if stats.HasBGP { + addTopologyBGPStats(out, stats) + } + if stats.HasComputed && !stats.HasL2 { + out["actors_total"] = stats.Recomputed.ActorsTotal + out["links_total"] = stats.Recomputed.LinksTotal + out["links_probable"] = stats.Recomputed.LinksProbable + } + + if len(out) == 0 { + return nil + } + return out +} + +func addTopologyL2Stats(out map[string]any, stats topologyStats) { + l2 := stats.L2 + + out["devices_total"] = l2.DevicesTotal + out["devices_discovered"] = l2.DevicesDiscovered + out["links_total"] = l2.LinksTotal + out["links_lldp"] = l2.LinksLLDP + out["links_cdp"] = l2.LinksCDP + out["links_stp"] = l2.LinksSTP + out["links_bidirectional"] = l2.LinksBidirectional + out["links_unidirectional"] = l2.LinksUnidirectional + out["links_fdb"] = l2.LinksFDB + out["links_fdb_endpoint_candidates"] = l2.LinksFDBEndpointCandidates + out["links_fdb_endpoint_emitted"] = l2.LinksFDBEndpointEmitted + out["links_fdb_endpoint_suppressed"] = l2.LinksFDBEndpointSuppressed + out["endpoints_ambiguous_segments"] = l2.EndpointsAmbiguousSegments + out["links_arp"] = l2.LinksARP + out["links_probable"] = l2.LinksProbable + out["segments_suppressed"] = l2.SegmentsSuppressed + out["actors_total"] = l2.ActorsTotal + out["actors_unlinked_suppressed"] = l2.ActorsUnlinkedSuppressed + out["endpoints_total"] = l2.EndpointsTotal + out["inference_strategy"] = l2.InferenceStrategy + + out["attachments_total"] = l2.AttachmentsTotal + out["attachments_fdb"] = l2.AttachmentsFDB + out["enrichments_total"] = l2.EnrichmentsTotal + out["enrichments_arp_nd"] = l2.EnrichmentsARPND + out["bridge_domains_total"] = l2.BridgeDomainsTotal + out["identity_alias_endpoints_mapped"] = l2.IdentityAliasEndpointsMapped + out["identity_alias_endpoints_ambiguous_mac"] = l2.IdentityAliasEndpointsAmbiguousMAC + out["identity_alias_ips_merged"] = l2.IdentityAliasIPsMerged + out["identity_alias_ips_conflict_skipped"] = l2.IdentityAliasIPsConflictSkipped + + if stats.HasComputed { + out["actors_total"] = stats.Recomputed.ActorsTotal + out["links_total"] = stats.Recomputed.LinksTotal + out["links_probable"] = stats.Recomputed.LinksProbable + } + if stats.HasShape { + out["segments_suppressed"] = l2.SegmentsSuppressed + stats.Shape.SegmentsSparseSuppressed + if stats.Shape.InferenceStrategy != "" { + out["inference_strategy"] = stats.Shape.InferenceStrategy + } + } +} + +func addTopologyShapeStats(out map[string]any, stats topologyStats) { + shape := stats.Shape + + out["actors_collapsed_by_ip"] = shape.ActorsCollapsedByIP + out["actors_non_ip_inferred_suppressed"] = shape.ActorsNonIPInferredSuppressed + out["actors_map_type_suppressed"] = shape.ActorsMapTypeSuppressed + out["segments_sparse_suppressed"] = shape.SegmentsSparseSuppressed + out["map_type"] = shape.MapType + if shape.InferenceStrategy != "" { + out["inference_strategy"] = shape.InferenceStrategy + } + if _, ok := out["segments_suppressed"]; !ok { + out["segments_suppressed"] = shape.SegmentsSparseSuppressed + } +} + +func addTopologyFocusStats(out map[string]any, stats topologyFocusStats) { + out["managed_snmp_device_focus"] = stats.ManagedSNMPDeviceFocus + if stats.Depth.All { + out["depth"] = topologyDepthAll + } else { + out["depth"] = stats.Depth.Value + } + out["actors_focus_depth_filtered"] = stats.ActorsDepthFiltered + out["links_focus_depth_filtered"] = stats.LinksDepthFiltered +} + +func addTopologyL3Stats(out map[string]any, stats topologyStats) { + l3 := stats.L3 + + out["l3_subnet_candidate_subnets"] = l3.subnetStats.candidateSubnets + out["l3_subnet_candidate_links"] = l3.subnetStats.candidateLinks + out["l3_subnet_emitted_links"] = l3.emittedLinks + out["l3_subnet_suppressed_invalid"] = l3.subnetStats.suppressedInvalid + out["l3_subnet_suppressed_unsupported_prefix"] = l3.subnetStats.suppressedUnsupportedPrefix + out["l3_subnet_suppressed_duplicate_ip"] = l3.subnetStats.suppressedDuplicateIP + out["l3_subnet_suppressed_self_link"] = l3.subnetStats.suppressedSelfLink + out["l3_subnet_suppressed_unmatched"] = l3.subnetStats.suppressedUnmatched + out["l3_subnet_suppressed_multi_access"] = l3.subnetStats.suppressedMultiAccess + out["l3_subnet_suppressed_unresolved_actor"] = l3.suppressedUnresolvedActor + out["l3_subnet_suppressed_self_actor"] = l3.suppressedSelfActor + out["l3_subnet_suppressed_duplicate_link"] = l3.suppressedDuplicateLink + out["l3_subnet_visible_links"] = l3.emittedLinks + if stats.HasComputed { + out["l3_subnet_visible_links"] = stats.Recomputed.L3SubnetVisibleLinks + } +} + +func addTopologyOSPFStats(out map[string]any, stats topologyStats) { + ospf := stats.OSPF + + out["ospf_neighbor_rows"] = ospf.observedRows + out["ospf_neighbor_detail_rows"] = ospf.attachedNeighborRows + out["ospf_adjacency_emitted_links"] = ospf.emittedLinks + out["ospf_adjacency_suppressed_non_full_state"] = ospf.suppressedNonFullState + out["ospf_adjacency_suppressed_unresolved_local"] = ospf.suppressedUnresolvedLocal + out["ospf_adjacency_suppressed_unresolved_neighbor"] = ospf.suppressedUnresolvedNeighbor + out["ospf_adjacency_suppressed_self_actor"] = ospf.suppressedSelfActor + out["ospf_adjacency_suppressed_duplicate_link"] = ospf.suppressedDuplicateLink + out["ospf_adjacency_visible_links"] = ospf.emittedLinks + if stats.HasComputed { + out["ospf_adjacency_visible_links"] = stats.Recomputed.OSPFAdjacencyVisibleLinks + } +} + +func addTopologyBGPStats(out map[string]any, stats topologyStats) { + bgp := stats.BGP + + out["bgp_peer_rows"] = bgp.observedRows + out["bgp_peer_detail_rows"] = bgp.attachedPeerRows + out["bgp_adjacency_emitted_links"] = bgp.emittedLinks + out["bgp_adjacency_suppressed_non_established_state"] = bgp.suppressedNonEstablished + out["bgp_adjacency_suppressed_unresolved_local"] = bgp.suppressedUnresolvedLocal + out["bgp_adjacency_suppressed_unresolved_neighbor"] = bgp.suppressedUnresolvedNeighbor + out["bgp_adjacency_suppressed_self_actor"] = bgp.suppressedSelfActor + out["bgp_adjacency_suppressed_duplicate_link"] = bgp.suppressedDuplicateLink + out["bgp_adjacency_visible_links"] = bgp.emittedLinks + if stats.HasComputed { + out["bgp_adjacency_visible_links"] = stats.Recomputed.BGPAdjacencyVisibleLinks + } +} diff --git a/src/go/plugin/go.d/collector/snmp_topology/topology_types.go b/src/go/plugin/go.d/collector/snmp_topology/topology_types.go index b82c63a6dd4fdb..e4c3b4be18042a 100644 --- a/src/go/plugin/go.d/collector/snmp_topology/topology_types.go +++ b/src/go/plugin/go.d/collector/snmp_topology/topology_types.go @@ -2,17 +2,78 @@ package snmptopology -import "github.com/netdata/netdata/go/plugins/pkg/topology/graph" +import ( + "time" + + topologyengine "github.com/netdata/netdata/go/plugins/pkg/l2topology" + "github.com/netdata/netdata/go/plugins/pkg/topology/graph" +) const topologySchemaVersion = "2.0" -type topologyData = graph.Graph type topologyActor = graph.Actor type topologyMatch = graph.Match type topologyLink = graph.Link type topologyLinkEndpoint = graph.LinkEndpoint -type topologyFlow = graph.Flow -type topologyIPPolicy = graph.IPPolicy + +type topologyData struct { + SchemaVersion string + Source string + Layer string + AgentID string + CollectedAt time.Time + View string + Actors []topologyActor + Links []topologyLink + Stats topologyStats +} + +type topologyStats struct { + L2 topologyengine.ProjectionStats + HasL2 bool + Shape topologyShapeStats + HasShape bool + Focus topologyFocusStats + HasFocus bool + L3 topologyL3EnrichmentStats + HasL3 bool + OSPF topologyOSPFEnrichmentStats + HasOSPF bool + BGP topologyBGPEnrichmentStats + HasBGP bool + Recomputed topologyRecomputedStats + HasComputed bool +} + +type topologyShapeStats struct { + ActorsCollapsedByIP int + ActorsNonIPInferredSuppressed int + ActorsMapTypeSuppressed int + SegmentsSparseSuppressed int + MapType string + InferenceStrategy string +} + +type topologyFocusStats struct { + ManagedSNMPDeviceFocus string + Depth topologyFocusDepth + ActorsDepthFiltered int + LinksDepthFiltered int +} + +type topologyFocusDepth struct { + All bool + Value int +} + +type topologyRecomputedStats struct { + ActorsTotal int + LinksTotal int + LinksProbable int + L3SubnetVisibleLinks int + OSPFAdjacencyVisibleLinks int + BGPAdjacencyVisibleLinks int +} type topologyManagementAddress struct { Address string `json:"address"` diff --git a/src/libnetdata/socket/listen-sockets.c b/src/libnetdata/socket/listen-sockets.c index 91f7a833fedcd2..40a7cb170f6215 100644 --- a/src/libnetdata/socket/listen-sockets.c +++ b/src/libnetdata/socket/listen-sockets.c @@ -33,6 +33,7 @@ static HTTP_ACL read_acl(char *st) { if (!strcmp(st,"management")) ret |= HTTP_ACL_MANAGEMENT; if (!strcmp(st,"streaming")) ret |= HTTP_ACL_STREAMING; if (!strcmp(st,"netdata.conf")) ret |= HTTP_ACL_NETDATACONF; + if (!strcmp(st,"mcp")) ret |= HTTP_ACL_MCP; return ret; } @@ -374,7 +375,8 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, sockets->failed++; } else { acl_flags = HTTP_ACL_API_UNIX | HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | - HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; + HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_MCP | + HTTP_ACL_SSL_DEFAULT; listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, path, 0, acl_flags); added++; } @@ -424,7 +426,8 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition, } acl_flags |= read_acl(portconfig); } else { - acl_flags |= HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT; + acl_flags |= HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | HTTP_ACL_MANAGEMENT | + HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_MCP | HTTP_ACL_SSL_DEFAULT; } //Case the user does not set the option SSL in the "bind to", but he has diff --git a/src/libnetdata/socket/listen-sockets.h b/src/libnetdata/socket/listen-sockets.h index ebeb55994d717e..c8ddfd70b6132f 100644 --- a/src/libnetdata/socket/listen-sockets.h +++ b/src/libnetdata/socket/listen-sockets.h @@ -22,7 +22,7 @@ typedef struct listen_sockets { char *fds_names[MAX_LISTEN_FDS]; // descriptions for the open sockets int fds_types[MAX_LISTEN_FDS]; // the socktype for the open sockets (SOCK_STREAM, SOCK_DGRAM) int fds_families[MAX_LISTEN_FDS]; // the family of the open sockets (AF_UNIX, AF_INET, AF_INET6) - HTTP_ACL fds_acl_flags[MAX_LISTEN_FDS]; // the acl to apply to the open sockets (dashboard, badges, streaming, netdata.conf, management) + HTTP_ACL fds_acl_flags[MAX_LISTEN_FDS]; // the acl to apply to the open sockets (dashboard, badges, streaming, netdata.conf, management, mcp) } LISTEN_SOCKETS; int listen_sockets_setup(LISTEN_SOCKETS *sockets); diff --git a/src/libnetdata/user-auth/http-access.h b/src/libnetdata/user-auth/http-access.h index dd23e776966044..c84f75fd2aca06 100644 --- a/src/libnetdata/user-auth/http-access.h +++ b/src/libnetdata/user-auth/http-access.h @@ -103,6 +103,7 @@ typedef enum __attribute__((packed)) { HTTP_ACL_MANAGEMENT = (1 << 17), HTTP_ACL_STREAMING = (1 << 18), HTTP_ACL_NETDATACONF = (1 << 19), + HTTP_ACL_MCP = (1 << 20), // SSL related HTTP_ACL_SSL_OPTIONAL = (1 << 28), @@ -142,6 +143,7 @@ typedef enum __attribute__((packed)) { | HTTP_ACL_MANAGEMENT \ | HTTP_ACL_STREAMING \ | HTTP_ACL_NETDATACONF \ + | HTTP_ACL_MCP \ ) #define HTTP_ACL_ACLK_LICENSE_MANAGER (HTTP_ACL)( \ @@ -160,6 +162,7 @@ typedef enum __attribute__((packed)) { #define http_can_access_mgmt(w) (((w)->acl & HTTP_ACL_MANAGEMENT) == HTTP_ACL_MANAGEMENT) #define http_can_access_stream(w) (((w)->acl & HTTP_ACL_STREAMING) == HTTP_ACL_STREAMING) #define http_can_access_netdataconf(w) (((w)->acl & HTTP_ACL_NETDATACONF) == HTTP_ACL_NETDATACONF) +#define http_can_access_mcp(w) (((w)->acl & HTTP_ACL_MCP) == HTTP_ACL_MCP) #define http_is_using_ssl_optional(w) (((w)->port_acl & HTTP_ACL_SSL_OPTIONAL) == HTTP_ACL_SSL_OPTIONAL) #define http_is_using_ssl_force(w) (((w)->port_acl & HTTP_ACL_SSL_FORCE) == HTTP_ACL_SSL_FORCE) #define http_is_using_ssl_default(w) (((w)->port_acl & HTTP_ACL_SSL_DEFAULT) == HTTP_ACL_SSL_DEFAULT) diff --git a/src/web/mcp/adapters/mcp-http.c b/src/web/mcp/adapters/mcp-http.c index f565e7e7f5afae..22c90ccbd6cf51 100644 --- a/src/web/mcp/adapters/mcp-http.c +++ b/src/web/mcp/adapters/mcp-http.c @@ -9,6 +9,7 @@ #include "mcp-http-common.h" #include "web/api/mcp_auth.h" +#include "web/api/http_auth.h" #include "libnetdata/libnetdata.h" #include "libnetdata/http/http_defs.h" @@ -66,17 +67,20 @@ static bool mcp_http_accepts_sse(struct web_client *w) { } #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY -static void mcp_http_apply_api_key(struct web_client *w) { +static bool mcp_http_apply_api_key(struct web_client *w) { if (web_client_has_mcp_preview_key(w)) { web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD); - return; + return true; } char api_key_buffer[MCP_DEV_PREVIEW_API_KEY_LENGTH + 1]; if (mcp_http_extract_api_key(w, api_key_buffer, sizeof(api_key_buffer)) && mcp_api_key_verify(api_key_buffer, false)) { // silent=false for MCP requests web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD); + return true; } + + return false; } #endif @@ -111,9 +115,17 @@ int mcp_http_handle_request(struct rrdhost *host __maybe_unused, struct web_clie return w->response.code; } + bool mcp_api_key_verified = false; #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY - mcp_http_apply_api_key(w); + mcp_api_key_verified = mcp_http_apply_api_key(w); #endif + if (netdata_is_protected_by_bearer && !mcp_api_key_verified) { + BUFFER *payload = mcp_jsonrpc_build_error_payload( + NULL, -32600, + "MCP API key is required when bearer token protection is enabled", + NULL, 0); + return mcp_http_prepare_error_response(w, payload, HTTP_RESP_PRECOND_FAIL); + } size_t body_len = 0; const char *body = mcp_http_body(w, &body_len); diff --git a/src/web/mcp/adapters/mcp-sse.c b/src/web/mcp/adapters/mcp-sse.c index 09752d0ddd8bf8..6806e2ba9db339 100644 --- a/src/web/mcp/adapters/mcp-sse.c +++ b/src/web/mcp/adapters/mcp-sse.c @@ -8,6 +8,7 @@ #include "mcp-http-common.h" #include "web/api/mcp_auth.h" +#include "web/api/http_auth.h" #include "libnetdata/libnetdata.h" #include "libnetdata/http/http_defs.h" @@ -25,17 +26,20 @@ static void mcp_sse_add_common_headers(struct web_client *w) { } #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY -static void mcp_sse_apply_api_key(struct web_client *w) { +static bool mcp_sse_apply_api_key(struct web_client *w) { if (web_client_has_mcp_preview_key(w)) { web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD); - return; + return true; } char api_key_buffer[MCP_DEV_PREVIEW_API_KEY_LENGTH + 1]; if (mcp_http_extract_api_key(w, api_key_buffer, sizeof(api_key_buffer)) && mcp_api_key_verify(api_key_buffer, false)) { // silent=false for MCP requests web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD); + return true; } + + return false; } #endif @@ -148,9 +152,20 @@ int mcp_sse_handle_request(struct rrdhost *host __maybe_unused, struct web_clien return w->response.code; } + bool mcp_api_key_verified = false; #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY - mcp_sse_apply_api_key(w); + mcp_api_key_verified = mcp_sse_apply_api_key(w); #endif + if (netdata_is_protected_by_bearer && !mcp_api_key_verified) { + buffer_flush(w->response.data); + w->response.data->content_type = CT_TEXT_EVENT_STREAM; + mcp_http_disable_compression(w); + mcp_sse_add_common_headers(w); + mcp_sse_append_event(w->response.data, "error", + "MCP API key is required when bearer token protection is enabled"); + w->response.code = HTTP_RESP_PRECOND_FAIL; + return w->response.code; + } size_t body_len = 0; const char *body = NULL; diff --git a/src/web/server/README.md b/src/web/server/README.md index 46f61fa20464bd..ca226e507543c3 100644 --- a/src/web/server/README.md +++ b/src/web/server/README.md @@ -36,6 +36,8 @@ You can modify web server behavior by editing the `[web]` section in `netdata.co | `allow connections by dns` | `heuristic` | See the [access list section](#access-lists) for details on using `allow` settings | | `allow dashboard from` | `localhost *` | Controls dashboard and API access | | `allow dashboard by dns` | `heuristic` | DNS resolution setting for dashboard access | +| `allow mcp from` | (defaults to the value of `allow dashboard from`, or `localhost *` if that is unset) | Controls MCP access (`/mcp`, `/sse`, MCP WebSocket protocol). When not explicitly set, this option inherits the value of `allow dashboard from` (falling back to `localhost *` only if `allow dashboard from` is unset) | +| `allow mcp by dns` | `heuristic` | DNS resolution setting for MCP access | | `allow badges from` | `*` | Controls badge API access | | `allow badges by dns` | `heuristic` | DNS resolution setting for badge access | | `allow streaming from` | `*` | Controls metric streaming from child agents | @@ -72,6 +74,8 @@ Once a connection is allowed globally, these settings control access to specific - **`allow dashboard from`** - receives the request and examines if it is a static dashboard file or an API call the dashboards do. +- **`allow mcp from`** - checks if the client is allowed to use MCP endpoints (`/mcp`, `/sse`) and MCP WebSocket protocol. + - **`allow badges from`** - checks if the API request is for a badge. Badges aren't matched by `allow dashboard from`. - **`allow streaming from`** - checks if the child willing to stream metrics to this Netdata is allowed. This can be controlled per API KEY and MACHINE GUID in `stream.conf`. The setting in `netdata.conf` is checked before the ones in `stream.conf`. @@ -87,6 +91,7 @@ Once a connection is allowed globally, these settings control access to specific [web] allow connections from = localhost allow dashboard from = localhost + allow mcp from = localhost allow management from = localhost ``` @@ -95,6 +100,7 @@ Once a connection is allowed globally, these settings control access to specific [web] allow connections from = localhost 10.* 192.168.* allow dashboard from = localhost 10.* 192.168.* + allow mcp from = localhost 10.* 192.168.* allow badges from = * allow streaming from = * allow management from = localhost @@ -114,6 +120,7 @@ This process can be expensive on a machine that is serving many connections. Eac ```text allow connections by dns = heuristic allow dashboard by dns = heuristic + allow mcp by dns = heuristic allow badges by dns = heuristic allow streaming by dns = heuristic allow netdata.conf by dns = no @@ -213,11 +220,12 @@ The option `[web].default port` is used when entries in `[web].bind to` do not s As shown in the example above, these permissions are optional, with the default permitting all request types on the specified port. -The request types are strings identical to the `allow X from` directives of the access lists, i.e. `dashboard`, `streaming`, `registry`, `netdata.conf`, `badges` and `management`. The access lists themselves and the general setting `allow connections from` in the next section are applied regardless of the ports that are configured to provide these services. +The request types are strings identical to the `allow X from` directives of the access lists, i.e. `dashboard`, `mcp`, `streaming`, `registry`, `netdata.conf`, `badges` and `management`. The access lists themselves and the general setting `allow connections from` in the next section are applied regardless of the ports that are configured to provide these services. The API requests are serviced as follows: - `dashboard` gives access to the UI, the read API and badges API calls. +- `mcp` gives access to MCP endpoints (`/mcp`, `/sse`) and the MCP WebSocket protocol. - `badges` gives access only to the badge API calls. - `management` gives access only to the management API calls. @@ -379,4 +387,3 @@ ws://localhost:19999/echo?max_frame_size=32768 This is particularly useful for resource-constrained devices or network environments with specific limitations. - diff --git a/src/web/server/web_client.c b/src/web/server/web_client.c index afadc0fca5b3a1..c177abc200c38d 100644 --- a/src/web/server/web_client.c +++ b/src/web/server/web_client.c @@ -37,7 +37,6 @@ void web_client_reset_permissions(struct web_client *w) { w->user_auth.method = USER_AUTH_METHOD_NONE; w->user_auth.access = HTTP_ACCESS_NONE; w->user_auth.user_role = HTTP_USER_ROLE_NONE; - web_client_clear_mcp_preview_key(w); } void web_client_set_permissions(struct web_client *w, HTTP_ACCESS access, HTTP_USER_ROLE role, USER_AUTH_METHOD type) { @@ -194,6 +193,7 @@ static void web_client_reset_allocations(struct web_client *w, bool free_all) { memset(&w->user_auth, 0, sizeof(w->user_auth)); web_client_reset_permissions(w); + web_client_clear_mcp_preview_key(w); web_client_flag_clear(w, WEB_CLIENT_ENCODING_GZIP|WEB_CLIENT_ENCODING_DEFLATE); web_client_flag_clear(w, WEB_CLIENT_FLAG_ACCEPT_JSON | WEB_CLIENT_FLAG_ACCEPT_SSE | @@ -1200,12 +1200,12 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch return check_host_and_call(host, w, decoded_url_path, web_client_api_request); } else if(likely(hash == hash_mcp && strcmp(tok, "mcp") == 0)) { - if(unlikely(!http_can_access_dashboard(w))) + if(unlikely(!http_can_access_mcp(w))) return web_client_permission_denied_acl(w); return mcp_http_handle_request(host, w); } else if(likely(hash == hash_sse && strcmp(tok, "sse") == 0)) { - if(unlikely(!http_can_access_dashboard(w))) + if(unlikely(!http_can_access_mcp(w))) return web_client_permission_denied_acl(w); return mcp_sse_handle_request(host, w); } @@ -1392,27 +1392,35 @@ void web_client_process_request_from_web_server(struct web_client *w) { return; case HTTP_REQUEST_MODE_WEBSOCKET: - if(unlikely(!http_can_access_dashboard(w))) { + // Coarse gate: allow handshake only for clients that can access at least one WebSocket surface. + // websocket_handle_handshake() performs the protocol-specific ACL check (MCP vs dashboard). + if(unlikely(!http_can_access_dashboard(w) && !http_can_access_mcp(w))) { web_client_permission_denied_acl(w); return; } - - // Handle WebSocket handshake - this will take over the socket - // similar to how stream_receiver_accept_connection works + w->response.code = websocket_handle_handshake(w); - - // After this point the socket has been taken over - // No need to send a response as the WebSocket handler - // has already sent the handshake response - return; - case HTTP_REQUEST_MODE_OPTIONS: + if(w->response.code == HTTP_RESP_WEBSOCKET_HANDSHAKE) + // socket taken over successfully, handshake response already sent + return; + + // handshake failed - fall through to send the HTTP error response + break; + + case HTTP_REQUEST_MODE_OPTIONS: { + // Path-aware coarse pre-filter: + // MCP ACL is accepted only for MCP endpoints (/mcp, /sse), not as generic API access. + // Reuse the canonical classification set at URL-decode time (see + // WEB_CLIENT_FLAG_PATH_IS_MCP) so the prefilter cannot drift from the dispatcher. + bool mcp_route_requested = web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_MCP); if(unlikely( !http_can_access_dashboard(w) && !http_can_access_registry(w) && !http_can_access_badges(w) && !http_can_access_mgmt(w) && - !http_can_access_netdataconf(w) + !http_can_access_netdataconf(w) && + !(mcp_route_requested && http_can_access_mcp(w)) )) { web_client_permission_denied_acl(w); break; @@ -1423,17 +1431,24 @@ void web_client_process_request_from_web_server(struct web_client *w) { buffer_strcat(w->response.data, "OK"); w->response.code = HTTP_RESP_OK; break; + } case HTTP_REQUEST_MODE_POST: case HTTP_REQUEST_MODE_GET: case HTTP_REQUEST_MODE_PUT: - case HTTP_REQUEST_MODE_DELETE: + case HTTP_REQUEST_MODE_DELETE: { + // Path-aware coarse pre-filter: + // MCP ACL may open only MCP routes, while all other routes still require their own ACL surface. + // Reuse the canonical classification set at URL-decode time (see + // WEB_CLIENT_FLAG_PATH_IS_MCP) so the prefilter cannot drift from the dispatcher. + bool mcp_route_requested = web_client_flag_check(w, WEB_CLIENT_FLAG_PATH_IS_MCP); if(unlikely( !http_can_access_dashboard(w) && !http_can_access_registry(w) && !http_can_access_badges(w) && !http_can_access_mgmt(w) && - !http_can_access_netdataconf(w) + !http_can_access_netdataconf(w) && + !(mcp_route_requested && http_can_access_mcp(w)) )) { web_client_permission_denied_acl(w); break; @@ -1467,6 +1482,7 @@ void web_client_process_request_from_web_server(struct web_client *w) { w->response.code = (short)web_client_process_url(localhost, w, path); break; + } default: web_client_permission_denied_acl(w); diff --git a/src/web/server/web_client_cache.c b/src/web/server/web_client_cache.c index 7073c65149822b..1f3ce2dc158719 100644 --- a/src/web/server/web_client_cache.c +++ b/src/web/server/web_client_cache.c @@ -120,6 +120,7 @@ struct web_client *web_client_get_from_cache(void) { w->acl = HTTP_ACL_NONE; w->mode = HTTP_REQUEST_MODE_GET; web_client_reset_permissions(w); + web_client_clear_mcp_preview_key(w); memset(w->transaction, 0, sizeof(w->transaction)); memset(w->mcp_session_id, 0, sizeof(w->mcp_session_id)); memset(&w->auth, 0, sizeof(w->auth)); diff --git a/src/web/server/web_server.c b/src/web/server/web_server.c index 46801a2dbce6d2..90fbcde1d6c1ff 100644 --- a/src/web/server/web_server.c +++ b/src/web/server/web_server.c @@ -48,6 +48,7 @@ void debug_sockets() { buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_MANAGEMENT) ? "management " : ""); buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_STREAMING) ? "streaming " : ""); buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_NETDATACONF) ? "netdata.conf " : ""); + buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_MCP) ? "mcp " : ""); netdata_log_debug(D_WEB_CLIENT, "Socket fd %d name '%s' acl_flags: %s", i, api_sockets.fds_names[i], @@ -91,6 +92,8 @@ SIMPLE_PATTERN *web_allow_mgmt_from = NULL; int web_allow_mgmt_dns; SIMPLE_PATTERN *web_allow_streaming_from = NULL; int web_allow_streaming_dns; +SIMPLE_PATTERN *web_allow_mcp_from = NULL; +int web_allow_mcp_dns; SIMPLE_PATTERN *web_allow_netdataconf_from = NULL; int web_allow_netdataconf_dns; @@ -123,6 +126,11 @@ void web_client_update_acl_matches(struct web_client *w) { web_allow_streaming_from, "streaming", web_allow_streaming_dns)) w->acl |= HTTP_ACL_STREAMING; + if (!web_allow_mcp_from || + connection_allowed(w->fd, w->user_auth.client_ip, w->client_host, sizeof(w->client_host), + web_allow_mcp_from, "mcp", web_allow_mcp_dns)) + w->acl |= HTTP_ACL_MCP; + if (!web_allow_netdataconf_from || connection_allowed(w->fd, w->user_auth.client_ip, w->client_host, sizeof(w->client_host), web_allow_netdataconf_from, "netdata.conf", web_allow_netdataconf_dns)) diff --git a/src/web/server/web_server.h b/src/web/server/web_server.h index a3e984c3d4b75e..ac7b543f26ece0 100644 --- a/src/web/server/web_server.h +++ b/src/web/server/web_server.h @@ -28,6 +28,8 @@ extern SIMPLE_PATTERN *web_allow_badges_from; extern int web_allow_badges_dns; extern SIMPLE_PATTERN *web_allow_streaming_from; extern int web_allow_streaming_dns; +extern SIMPLE_PATTERN *web_allow_mcp_from; +extern int web_allow_mcp_dns; extern SIMPLE_PATTERN *web_allow_netdataconf_from; extern int web_allow_netdataconf_dns; extern SIMPLE_PATTERN *web_allow_mgmt_from; diff --git a/src/web/websocket/websocket-handshake.c b/src/web/websocket/websocket-handshake.c index 1bbf5b0f9031dd..efd47b1d79b640 100644 --- a/src/web/websocket/websocket-handshake.c +++ b/src/web/websocket/websocket-handshake.c @@ -6,9 +6,11 @@ #include "websocket-echo.h" #include "../mcp/adapters/mcp-websocket.h" #include "web/api/mcp_auth.h" +#include "web/api/http_auth.h" // Global array of WebSocket threads WEBSOCKET_THREAD websocket_threads[WEBSOCKET_MAX_THREADS]; +static _Atomic bool websocket_mcp_query_api_key_compat_notice_logged = false; // Initialize WebSocket thread system void websocket_threads_init(void) { @@ -307,6 +309,17 @@ short int websocket_handle_handshake(struct web_client *w) { // Copy user authentication and authorization information wsc->user_auth = w->user_auth; + bool mcp_api_key_verified = false; + +#ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY + if (web_client_has_mcp_preview_key(w)) { + wsc->user_auth.access = HTTP_ACCESS_ALL; + wsc->user_auth.method = USER_AUTH_METHOD_GOD; + wsc->user_auth.user_role = HTTP_USER_ROLE_ADMIN; + mcp_api_key_verified = true; + websocket_debug(wsc, "MCP developer preview API key verified via Authorization header - enabling full access"); + } +#endif // Check for max_frame_size parameter in the URL query string if (w->url_query_string_decoded && buffer_strlen(w->url_query_string_decoded) > 0) { @@ -334,15 +347,18 @@ short int websocket_handle_handshake(struct web_client *w) { } #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY - if (web_client_has_mcp_preview_key(w)) { - wsc->user_auth.access = HTTP_ACCESS_ALL; - wsc->user_auth.method = USER_AUTH_METHOD_GOD; - wsc->user_auth.user_role = HTTP_USER_ROLE_ADMIN; - websocket_debug(wsc, "MCP developer preview API key verified via Authorization header - enabling full access"); - } else { - // Check for api_key parameter for MCP developer preview + if (!mcp_api_key_verified) { + // Backward-compatibility fallback for older MCP websocket clients. + // Preferred path is Authorization: Bearer, but we intentionally keep + // ?api_key= support to avoid breaking existing client integrations. char *api_key_str = strstr(query, "api_key="); if (api_key_str) { + if (!websocket_mcp_query_api_key_compat_notice_logged) { + websocket_mcp_query_api_key_compat_notice_logged = true; + netdata_log_info("WEBSOCKET: MCP query-string API key is accepted for backward compatibility. " + "Prefer Authorization: Bearer for better credential hygiene."); + } + api_key_str += strlen("api_key="); // Extract the API key value (until & or end of string) @@ -360,6 +376,7 @@ short int websocket_handle_handshake(struct web_client *w) { wsc->user_auth.access = HTTP_ACCESS_ALL; wsc->user_auth.method = USER_AUTH_METHOD_GOD; wsc->user_auth.user_role = HTTP_USER_ROLE_ADMIN; + mcp_api_key_verified = true; websocket_debug(wsc, "MCP developer preview API key verified - enabling full access"); } else { websocket_debug(wsc, "Invalid MCP developer preview API key provided"); @@ -388,6 +405,31 @@ short int websocket_handle_handshake(struct web_client *w) { return HTTP_RESP_BAD_REQUEST; } + if (wsc->protocol == WS_PROTOCOL_MCP) { + if (unlikely(!http_can_access_mcp(w))) { + freez(accept_key); + websocket_client_free(wsc); + return web_client_permission_denied_acl(w); + } + + if (netdata_is_protected_by_bearer && !mcp_api_key_verified) { + w->response.data->content_type = CT_TEXT_PLAIN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, + "MCP API key is required when bearer token protection is enabled"); + w->response.code = HTTP_RESP_PRECOND_FAIL; + + freez(accept_key); + websocket_client_free(wsc); + return HTTP_RESP_PRECOND_FAIL; + } + } + else if (unlikely(!http_can_access_dashboard(w))) { + freez(accept_key); + websocket_client_free(wsc); + return web_client_permission_denied_acl(w); + } + // Take over the connection immediately websocket_takeover_web_connection(w, wsc); @@ -414,7 +456,10 @@ short int websocket_handle_handshake(struct web_client *w) { netdata_log_error("WEBSOCKET: Failed to send complete WebSocket handshake response"); // No client yet freez(accept_key); websocket_client_free(wsc); - return HTTP_RESP_INTERNAL_SERVER_ERROR; + // The socket was already taken over (and is now closed by websocket_client_free). + // Return the handshake code so the caller does NOT fall through to emitting an + // HTTP response on the dead descriptor. + return HTTP_RESP_WEBSOCKET_HANDSHAKE; } freez(accept_key); @@ -457,7 +502,9 @@ short int websocket_handle_handshake(struct web_client *w) { // No protocol handler available - this shouldn't happen as we check earlier netdata_log_error("WEBSOCKET: No handler available for protocol %d", wsc->protocol); websocket_client_free(wsc); - return HTTP_RESP_BAD_REQUEST; + // Socket already taken over above; return the handshake code so the caller + // does not emit an HTTP response on the dead descriptor. + return HTTP_RESP_WEBSOCKET_HANDSHAKE; } // Register the client in our registry diff --git a/tests/acls/acl.sh.in b/tests/acls/acl.sh.in index 9ac404c0756bb2..6c28543e4b70bf 100644 --- a/tests/acls/acl.sh.in +++ b/tests/acls/acl.sh.in @@ -9,6 +9,7 @@ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;43m' NOCOLOR='\033[0m' +MCP_REQUEST='{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}' #change the previous acl file and with a new #and store it on a new file @@ -21,14 +22,14 @@ NETDATAPID="" change_ssl_file(){ KEYROW="ssl key = $3/key.pem" CERTROW="ssl certificate = $3/cert.pem" - sed "s@ssl key =@$KEYROW@g" netdata.ssl.cfg > tmp - sed "s@ssl certificate =@$CERTROW@g" tmp > tmp2 - sed "s/$1/$2/g" tmp2 > "$4" + sed "s@ssl key =@$KEYROW@g" netdata.ssl.cfg > tmp_ssl_1 + sed "s@ssl certificate =@$CERTROW@g" tmp_ssl_1 > tmp_ssl_2 + sed "s/$1/$2/g" tmp_ssl_2 > "$4" } run_acl_tests() { #Give a time for netdata start properly - sleep 2 + sleep 3 curl -v -k --tls-max 1.2 --create-dirs -o index.html "$2" 2> log_index.txt curl -v -k --tls-max 1.2 --create-dirs -o netdata.txt "$2/netdata.conf" 2> log_nc.txt @@ -36,11 +37,12 @@ run_acl_tests() { curl -v -k --tls-max 1.2 --create-dirs -o info.txt "$2/api/v1/info" 2> log_info.txt curl -H "X-Auth-Token: $1" -v -k --tls-max 1.2 --create-dirs -o health.csv "$2/api/v1/manage/health?cmd=LIST" 2> log_health.txt - TOT=$(grep -c "HTTP/1.1 301" log_*.txt | cut -d: -f2| grep -c 1) + TOT=$(grep -c "HTTP/1.1 399" log_*.txt | cut -d: -f2| grep -c 1) if [ "$TOT" -ne "$4" ]; then echo -e "${RED}I got a wrong number of redirects($TOT) when SSL is activated, It was expected $4 ${NOCOLOR}" - rm log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp* key.pem cert.pem info.txt - kill $NETDATAPID + rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null + rm -rf "$SSL_DIR" 2>/dev/null + kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null exit 1 elif [ "$TOT" -eq "$4" ] && [ "$4" -ne "0" ]; then echo -e "${YELLOW}I got the correct number of redirects($4) when SSL is activated and I try to access with HTTP. ${NOCOLOR}" @@ -50,19 +52,129 @@ run_acl_tests() { TOT=$(grep -c "HTTP/1.1 200 OK" log_* | cut -d: -f2| grep -c 1) if [ "$TOT" -ne "$3" ]; then echo -e "${RED}I got a wrong number of \"200 OK\" from the queries, it was expected $3. ${NOCOLOR}" - kill $NETDATAPID - rm log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp* key.pem cert.pem info.txt + kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null + rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null + rm -rf "$SSL_DIR" 2>/dev/null exit 1 fi echo -e "${GREEN}ACLs were applied correctly ${NOCOLOR}" } +run_mcp_acl_tests() { + URL="$1" + EXPECTED_MCP_HTTP="$2" + EXPECTED_SSE="$3" + EXPECTED_WS="$4" + AUTH_HEADER="$5" + + #Give a time for netdata start properly + sleep 1 + + if [ -n "$AUTH_HEADER" ]; then + MCP_HTTP_CODE=$(curl -sS -k --tls-max 1.2 --create-dirs -o mcp_http.json \ + -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $AUTH_HEADER" \ + --data "$MCP_REQUEST" \ + "$URL/mcp" 2> log_mcp_http.txt || true) + MCP_SSE_CODE=$(curl -sS -k --tls-max 1.2 --create-dirs -o mcp_sse.txt \ + -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $AUTH_HEADER" \ + --data "$MCP_REQUEST" \ + "$URL/sse" 2> log_mcp_sse.txt || true) + MCP_WS_CODE=$(curl -sS -k --tls-max 1.2 --http1.1 --create-dirs --max-time 2 -o mcp_ws.txt \ + -w "%{http_code}" \ + -H "Connection: Upgrade" \ + -H "Upgrade: websocket" \ + -H "Sec-WebSocket-Version: 13" \ + -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ + -H "Sec-WebSocket-Protocol: mcp" \ + -H "Authorization: Bearer $AUTH_HEADER" \ + "$URL/mcp" 2> log_mcp_ws.txt || true) + else + MCP_HTTP_CODE=$(curl -sS -k --tls-max 1.2 --create-dirs -o mcp_http.json \ + -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + --data "$MCP_REQUEST" \ + "$URL/mcp" 2> log_mcp_http.txt || true) + MCP_SSE_CODE=$(curl -sS -k --tls-max 1.2 --create-dirs -o mcp_sse.txt \ + -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + --data "$MCP_REQUEST" \ + "$URL/sse" 2> log_mcp_sse.txt || true) + MCP_WS_CODE=$(curl -sS -k --tls-max 1.2 --http1.1 --create-dirs --max-time 2 -o mcp_ws.txt \ + -w "%{http_code}" \ + -H "Connection: Upgrade" \ + -H "Upgrade: websocket" \ + -H "Sec-WebSocket-Version: 13" \ + -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ + -H "Sec-WebSocket-Protocol: mcp" \ + "$URL/mcp" 2> log_mcp_ws.txt || true) + fi + + if [ "$MCP_HTTP_CODE" != "$EXPECTED_MCP_HTTP" ]; then + echo -e "${RED}Unexpected /mcp HTTP response ($MCP_HTTP_CODE), expected $EXPECTED_MCP_HTTP on $URL ${NOCOLOR}" + kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null + rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null + rm -rf "$SSL_DIR" 2>/dev/null + exit 1 + fi + + if [ "$MCP_SSE_CODE" != "$EXPECTED_SSE" ]; then + echo -e "${RED}Unexpected /sse HTTP response ($MCP_SSE_CODE), expected $EXPECTED_SSE on $URL ${NOCOLOR}" + kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null + rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null + rm -rf "$SSL_DIR" 2>/dev/null + exit 1 + fi + + if [ "$MCP_WS_CODE" != "$EXPECTED_WS" ]; then + echo -e "${RED}Unexpected MCP WebSocket handshake response ($MCP_WS_CODE), expected $EXPECTED_WS on $URL ${NOCOLOR}" + kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null + rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null + rm -rf "$SSL_DIR" 2>/dev/null + exit 1 + fi + + echo -e "${GREEN}MCP ACL checks passed for $URL (${EXPECTED_MCP_HTTP}/${EXPECTED_SSE}/${EXPECTED_WS}) ${NOCOLOR}" +} + +change_file_with_mcp_acl() { + sed "s/$1/$2/g" netdata.cfg > "$5" + { + echo "" + echo "[web]" + echo " allow mcp from = $3" + } >> "$5" +} + +change_file_with_bearer_protection() { + sed "s/$1/$2/g" netdata.cfg > "$4" + { + echo "" + echo "[web]" + echo " bearer token protection = yes" + } >> "$4" +} + CONF=$(grep "bind" netdata.cfg) MUSER=$(grep run netdata.cfg | cut -d= -f2|sed 's/^[ \t]*//') -openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -sha512 -subj "/C=US/ST=Denied/L=Somewhere/O=Dis/CN=www.example.com" -keyout key.pem -out cert.pem -chown "$MUSER" key.pem cert.pem +# SSL certs must be in a directory accessible by the netdata user +# (the test directory may be inside a home directory with restricted permissions). +# Use a unique directory so concurrent runs do not collide, and 755 so the +# netdata user can traverse it and read the certificates. +SSL_DIR=$(mktemp -d "/tmp/netdata-acl-test-ssl.XXXXXX") || { echo "Failed to create temporary SSL directory" >&2; exit 1; } +chmod 755 "$SSL_DIR" +trap 'rm -rf "$SSL_DIR"' EXIT +openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -sha512 -subj "/C=US/ST=Denied/L=Somewhere/O=Dis/CN=www.example.com" -keyout "$SSL_DIR/key.pem" -out "$SSL_DIR/cert.pem" +chown "$MUSER" "$SSL_DIR/key.pem" "$SSL_DIR/cert.pem" CWD=$(pwd) if [ -f "${NETDATA_VARLIB_DIR}/netdata.api.key" ] ;then @@ -74,53 +186,93 @@ fi change_file "$CONF" " bind to = *" "$CWD" "netdata.conf.test0" netdata -c "netdata.conf.test0" -D & NETDATAPID=$! -run_acl_tests $TOKEN "$BASICURL:19999" 5 0 -kill $NETDATAPID +run_acl_tests "$TOKEN" "$BASICURL:19999" 5 0 +run_mcp_acl_tests "$BASICURL:19999" 200 200 101 +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null -change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management *:20001=dashboard|registry|netdata.conf^SSL=optional *:20002=dashboard|registry" "$CWD" "netdata.conf.test1" +change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management *:20001=dashboard|registry|netdata.conf^SSL=optional *:20002=dashboard|registry" "$SSL_DIR" "netdata.conf.test1" netdata -c "netdata.conf.test1" -D & NETDATAPID=$! -run_acl_tests $TOKEN "$BASICURL:19999" 5 5 -run_acl_tests $TOKEN "$BASICURLS:19999" 5 0 +run_acl_tests "$TOKEN" "$BASICURL:19999" 5 5 +run_acl_tests "$TOKEN" "$BASICURLS:19999" 5 0 -run_acl_tests $TOKEN "$BASICURL:20000" 4 5 -run_acl_tests $TOKEN "$BASICURLS:20000" 4 0 +run_acl_tests "$TOKEN" "$BASICURL:20000" 4 5 +run_acl_tests "$TOKEN" "$BASICURLS:20000" 4 0 -run_acl_tests $TOKEN "$BASICURL:20001" 4 0 -run_acl_tests $TOKEN "$BASICURLS:20001" 4 0 +run_acl_tests "$TOKEN" "$BASICURL:20001" 3 0 +run_acl_tests "$TOKEN" "$BASICURLS:20001" 3 0 -run_acl_tests $TOKEN "$BASICURL:20002" 3 5 -run_acl_tests $TOKEN "$BASICURLS:20002" 3 0 -kill $NETDATAPID +run_acl_tests "$TOKEN" "$BASICURL:20002" 2 5 +run_acl_tests "$TOKEN" "$BASICURLS:20002" 2 0 +run_mcp_acl_tests "$BASICURLS:19999" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20000" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20001" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20002" 451 451 451 +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null -change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management *:20001=dashboard|registry|netdata.conf^SSL=force *:20002=dashboard|registry" "$CWD" "netdata.conf.test2" +change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management *:20001=dashboard|registry|netdata.conf^SSL=force *:20002=dashboard|registry" "$SSL_DIR" "netdata.conf.test2" netdata -c "netdata.conf.test2" -D & NETDATAPID=$! -run_acl_tests $TOKEN "$BASICURL:19999" 5 5 -run_acl_tests $TOKEN "$BASICURLS:19999" 5 0 +run_acl_tests "$TOKEN" "$BASICURL:19999" 5 5 +run_acl_tests "$TOKEN" "$BASICURLS:19999" 5 0 -run_acl_tests $TOKEN "$BASICURL:20000" 4 5 -run_acl_tests $TOKEN "$BASICURLS:20000" 4 0 +run_acl_tests "$TOKEN" "$BASICURL:20000" 4 5 +run_acl_tests "$TOKEN" "$BASICURLS:20000" 4 0 -run_acl_tests $TOKEN "$BASICURL:20001" 4 5 -run_acl_tests $TOKEN "$BASICURLS:20001" 4 0 +run_acl_tests "$TOKEN" "$BASICURL:20001" 3 5 +run_acl_tests "$TOKEN" "$BASICURLS:20001" 3 0 -run_acl_tests $TOKEN "$BASICURL:20002" 3 5 -run_acl_tests $TOKEN "$BASICURLS:20002" 3 0 -kill $NETDATAPID +run_acl_tests "$TOKEN" "$BASICURL:20002" 2 5 +run_acl_tests "$TOKEN" "$BASICURLS:20002" 2 0 +run_mcp_acl_tests "$BASICURLS:19999" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20000" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20001" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20002" 451 451 451 +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null -change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management^SSL=optional *:20001=dashboard|registry|netdata.conf^SSL=force" "$CWD" "netdata.conf.test3" +change_ssl_file "$CONF" " bind to = *=dashboard|registry|badges|management|netdata.conf *:20000=dashboard|registry|badges|management^SSL=optional *:20001=dashboard|registry|netdata.conf^SSL=force" "$SSL_DIR" "netdata.conf.test3" netdata -c "netdata.conf.test3" -D & NETDATAPID=$! -run_acl_tests $TOKEN "$BASICURL:19999" 5 5 -run_acl_tests $TOKEN "$BASICURLS:19999" 5 0 +run_acl_tests "$TOKEN" "$BASICURL:19999" 5 5 +run_acl_tests "$TOKEN" "$BASICURLS:19999" 5 0 + +run_acl_tests "$TOKEN" "$BASICURL:20000" 4 0 +run_acl_tests "$TOKEN" "$BASICURLS:20000" 4 0 -run_acl_tests $TOKEN "$BASICURL:20000" 4 0 -run_acl_tests $TOKEN "$BASICURLS:20000" 4 0 +run_acl_tests "$TOKEN" "$BASICURL:20001" 3 5 +run_acl_tests "$TOKEN" "$BASICURLS:20001" 3 0 +run_mcp_acl_tests "$BASICURLS:19999" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20000" 451 451 451 +run_mcp_acl_tests "$BASICURLS:20001" 451 451 451 +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null -run_acl_tests $TOKEN "$BASICURL:20001" 4 5 -run_acl_tests $TOKEN "$BASICURLS:20001" 4 0 -kill $NETDATAPID +change_file_with_mcp_acl "$CONF" " bind to = *" "10.*" "$CWD" "netdata.conf.test4" +netdata -c "netdata.conf.test4" -D & +NETDATAPID=$! +run_acl_tests "$TOKEN" "$BASICURL:19999" 5 0 +run_mcp_acl_tests "$BASICURL:19999" 451 451 451 +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null + +change_file_with_bearer_protection "$CONF" " bind to = *" "$CWD" "netdata.conf.test5" +netdata -c "netdata.conf.test5" -D & +NETDATAPID=$! +sleep 2 +if [ -f "${NETDATA_VARLIB_DIR}/mcp_dev_preview_api_key" ] ;then + read -r MCP_TOKEN < "${NETDATA_VARLIB_DIR}/mcp_dev_preview_api_key" +else + MCP_TOKEN="NULL" +fi +run_mcp_acl_tests "$BASICURL:19999" 412 412 412 +run_mcp_acl_tests "$BASICURL:19999" 412 412 412 "00000000-0000-0000-0000-000000000000" +if [ "$MCP_TOKEN" != "NULL" ] && [ -f "${NETDATA_VARLIB_DIR}/cloud.d/claimed_id" ] ; then + set +x + run_mcp_acl_tests "$BASICURL:19999" 200 200 101 "$MCP_TOKEN" + set -x +else + echo -e "${YELLOW}Skipping authenticated MCP bearer check (agent not claimed or key missing)${NOCOLOR}" +fi +kill $NETDATAPID 2>/dev/null; wait $NETDATAPID 2>/dev/null -rm log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp* key.pem cert.pem info.txt +rm -f log_* netdata.conf.test* netdata.txt health.csv index.html badge.csv tmp_ssl_* info.txt mcp_* 2>/dev/null +rm -rf "$SSL_DIR" 2>/dev/null echo "All the tests were successful ${NOCOLOR}"