Add SNMP BGP monitoring (#22170)
Co-authored-by: ilyam8 <ilya@netdata.cloud>
Costa Tsaousis committed
May 9, 2026 at 19:35 UTC
f4c83343a115db1c65df92414f93b7f15501444d
125 files changed
+15739
-1040
.agents/skills/mirror-netdata-repos/SKILL.md
+2
-2
@@ -109,8 +109,8 @@ output, end-of-run summary). Run it on demand.
109
4. Required tools: `git` and `jq`. Install via your package
110
manager.
111
5. For Phase 2 (auto-discovery): install `gh` (the GitHub CLI)
112
- and run `gh auth login`. SSH access to `git@github.com:netdata/...`
113
- must work for clones.
112
+ and run `gh auth login`. SSH clone access to the Netdata GitHub
113
+ organization must work for clones.
114
115
### First sync
116
.agents/skills/project-snmp-profiles-authoring/SKILL.md
+25
@@ -40,6 +40,17 @@ Use this skill before editing files under:
40
license row, declare an explicit stable `id:`. For table licensing rows,
41
keep `from:` references inside the same table OID and derive
42
`not-accessible` INDEX values from the row index.
43
+12. Put SNMP BGP rows under top-level `bgp:`. Do not model BGP telemetry as
44
+ vendor-specific raw metrics, `virtual_metrics`, or underscore-prefixed tag
45
+ protocols when adding or migrating BGP coverage.
46
+13. BGP peer-state mappings must use the six RFC 4271 canonical states
47
+ (`idle`, `connect`, `active`, `opensent`, `openconfirm`, `established`).
48
+ Use `partial: true` plus `partial_states` only when the source MIB is
49
+ intentionally partial.
50
+14. For BGP table rows, derive `not-accessible` INDEX objects with exactly one
51
+ row-index selector: `index`, `index_from_end`, or `index_transform`. Use
52
+ `index_from_end` for trailing AFI/SAFI-like components after variable-length
53
+ indexes such as `InetAddress`.
54
55
## Index Rules
56
@@ -125,6 +136,20 @@ together:
136
Verify that licensing rows are delivered through `ProfileMetrics.LicenseRows`,
137
not through underscore-prefixed `HiddenMetrics`.
138
139
+When adding or migrating BGP profile coverage, update all related parts
140
+together:
141
+
142
+- profile YAML using `bgp:`;
143
+- closed BGP row kind, peer-state, AFI/SAFI, and typed field validation when
144
+ adding new policy names or value domains;
145
+- typed `ProfileMetrics.BGPRows` producer/consumer tests;
146
+- MIB evidence for every OID and every `not-accessible` index-derived field;
147
+- SNMP integration metadata and generated docs when public BGP capability
148
+ claims change.
149
+
150
+Verify that BGP rows are delivered through `ProfileMetrics.BGPRows`, not
151
+through `metrics:`, `virtual_metrics:`, or underscore-prefixed hidden metrics.
152
+
153
When adding or refactoring SNMP profile, parser, or topology tests, prefer
154
table-driven cases using `map[string]struct{}` keyed by test-case name when
155
the cases share setup and assertion shape. Use separate test functions only for
.agents/sow/done/SOW-0015-20260508-snmp-bgp-typed-projection.md
new
+764
@@ -0,0 +1,764 @@
1
+# SOW-0015 - SNMP BGP typed profile projection
2
+
3
+## Status
4
+
5
+Status: in-progress
6
+
7
+Sub-state: reopened for final review-feedback fixes before merge; user decisions 1-23 resolved; all BGP-bearing stock profiles migrated to typed `bgp:` rows; legacy `bgp_public*` runtime deleted; metadata and generated SNMP integration docs regenerated; local `mibs/` reference files removed.
8
+
9
+## Requirements
10
+
11
+### Purpose
12
+
13
+Replace the WIP SNMP BGP monitoring branch's metric-name/tag routing layer with a clean typed BGP profile section and projection. Preserve normal ddsnmp chart-per-row behavior, keep BGP per-peer/per-family health templates enabled by default, and remove unsupported public coverage claims until real fixture evidence exists.
14
+
15
+Scope expansion on 2026-05-09: normalize typed SNMP profile organization before SOW close by moving remaining inline `bgp:` and `licensing:` sections into dedicated underscore-prefixed typed-domain fragments where practical.
16
+
17
+### User Request
18
+
19
+User request summary:
20
+
21
+- Analyze the `pr-ktsaou-snmp-bgp` branch after merge/rebase work.
22
+- Find hacks, smells, correctness risks, missing tests, and clean-end-state refactor directions.
23
+- Prefer a clean end state over low churn or backward compatibility with this WIP branch.
24
+- Use multiple external review agents, reconcile their findings, and record decisions before implementation.
25
+- Use a typed `bgp:` projection rather than the current stringly typed BGP public-metric router.
26
+
27
+Detailed design source:
28
+
29
+- `src/go/plugin/go.d/TODO-snmp-bgp-monitoring-review.md` (local development TODO for this work).
30
+
31
+### Assistant Understanding
32
+
33
+Facts:
34
+
35
+- The branch implements BGP as ordinary SNMP metrics plus a rewrite layer in `collector/snmp/bgp_public*.go`.
36
+- The rewrite layer routes by metric name, infers peer vs peer-family scope from tags, and uses underscore-prefixed labels as an in-band protocol.
37
+- `ddsnmp` already has typed projection precedents: `topology:` and `licensing:` sections, `ProfileMetrics.TopologyMetrics`, and `ProfileMetrics.LicenseRows`.
38
+- The existing generic ddsnmp behavior creates one chart per table metric row. The user explicitly decided BGP must keep that behavior and must not add BGP-specific caps.
39
+- The existing BGP health templates should remain default-on; no alert test framework will be created in this SOW.
40
+- Cumulus and Alcatel BGP capability claims must be removed until real fixtures exist.
41
+- Downloaded BGP MIB files are local reference evidence under untracked `mibs/`; they must not be committed and must be removed before close-out.
42
+
43
+Inferences:
44
+
45
+- The root problem is architectural: BGP has typed rows, identities, state enums, AFI/SAFI enums, and fixed signal classes, but the branch models these as string-routed chart metrics.
46
+- Fixing individual switch cases would leave the same hidden-protocol class of technical debt that topology and licensing migrations removed.
47
+- The clean migration should be schema-first, then runtime projection, then vendor-by-vendor profile migration, then deletion of the old router.
48
+
49
+- Unknowns:
50
+
51
+- No open user decisions remain.
52
+- Real Cumulus and Alcatel fixtures are not available now; coverage claims will be removed until they exist.
53
+- Cisco profile attachment requires a profile audit before Cisco migration, but it does not block schema/API scaffolding.
54
+
55
+### Acceptance Criteria
56
+
57
+- SNMP BGP profile data is represented by a first-class typed `bgp:` profile section/projection, not the `bgp_public*` metric-name router.
58
+- `ProfileMetrics` exposes typed `BGPRows` or an equivalent typed BGP output shape.
59
+- BGP function/cache/charts read typed BGP rows, not underscore-prefixed labels or `bgp_public*`-normalized ordinary metrics.
60
+- `bgp_public.go`, `bgp_public_routes.go`, `bgp_public_specs.go`, and related old-routing paths are deleted by the final migration.
61
+- The underscore-prefix BGP tag protocol is deleted by the final migration.
62
+- BGP structural identity uses a canonical length-prefixed key derived from typed identity fields, not display labels or underscore-concatenated strings.
63
+- AFI/SAFI values use closed canonical enums with an explicit vendor-private allow-list.
64
+- BGP state mappings cover all six RFC 4271 peer states unless a row explicitly declares partial coverage.
65
+- Profile validation rejects readable-symbol use of MIB `MAX-ACCESS not-accessible` objects; such objects must be derived from row indexes.
66
+- BGP profile projection is profile-gated: `snmp:bgp-peers` is exposed only for BGP-capable jobs/devices.
67
+- Normal ddsnmp chart-per-row behavior is preserved; no BGP-specific peer/peer-family chart caps are introduced.
68
+- Per-peer/per-family BGP health templates remain default-on.
69
+- Stale cache semantics are explicit: function output distinguishes fresh, stale-within-TTL, stale-expired, no-data-yet, and valid filtered-empty states; chart-side collection freshness is exposed.
70
+- Cisco BGP is removed from `_cisco-base.yaml` and attached only to audited router/L3-switch Cisco profiles.
71
+- Cumulus and Alcatel BGP capability claims are removed until real fixtures exist.
72
+- Each migrated vendor has production-shaped full-profile tests through normal profile loading/resolution/finalization.
73
+- `.agents/sow/specs/snmp-profile-projection.md` documents the BGP projection contract.
74
+- Metadata/docs/generated integration artifacts are coherent after final migration.
75
+- Typed-domain SNMP profile sections are organized as dedicated fragments where practical; concrete device profiles extend those fragments instead of mixing typed-domain sections with regular metric sections.
76
+- Local raw MIB files under `mibs/` are not committed and are removed before SOW close-out.
77
+
78
+## Analysis
79
+
80
+Sources checked:
81
+
82
+- `src/go/plugin/go.d/TODO-snmp-bgp-monitoring-review.md`
83
+- `collector/snmp/bgp_public.go`
84
+- `collector/snmp/bgp_public_routes.go`
85
+- `collector/snmp/bgp_public_specs.go`
86
+- `collector/snmp/bgp_integration.go`
87
+- `collector/snmp/collect_snmp.go`
88
+- `collector/snmp/charts.go`
89
+- `collector/snmp/metric_ids.go`
90
+- `collector/snmp/func_bgp_peers.go`
91
+- `collector/snmp/func_bgp_peers_cache.go`
92
+- `collector/snmp/ddsnmp/ddprofiledefinition/profile_definition.go`
93
+- `collector/snmp/ddsnmp/metric.go`
94
+- `collector/snmp/metadata.yaml`
95
+- `collector/snmp/integrations/snmp_devices.md`
96
+- `src/health/health.d/snmp_bgp.conf`
97
+- `config/go.d/snmp.profiles/default/*bgp*.yaml`
98
+- `config/go.d/snmp.profiles/default/cisco*.yaml`
99
+- `config/go.d/snmp.profiles/default/_cisco*.yaml`
100
+- `.agents/sow/specs/snmp-profile-projection.md`
101
+- `.agents/skills/project-snmp-profiles-authoring/SKILL.md`
102
+- `.agents/skills/project-writing-collectors/SKILL.md`
103
+- `.agents/skills/integrations-lifecycle/SKILL.md`
104
+- Local untracked BGP MIB files under `mibs/`
105
+
106
+Current state:
107
+
108
+- `profile_definition.go` supports `metrics`, `topology`, and `licensing`, but no `bgp`.
109
+- `ProfileMetrics` supports regular `Metrics`, `TopologyMetrics`, and `LicenseRows`, but no typed BGP rows.
110
+- `bgp_public_routes.go` routes raw vendor metric names through a large switch and decides peer vs peer-family scope from row tags.
111
+- `bgp_public_specs.go` separately defines public BGP chart specs.
112
+- `func_bgp_peers_cache.go` keeps another switch-like mapping from public BGP leaf names into function cache fields.
113
+- `metric_ids.go` derives BGP chart IDs and contexts through `strings.HasPrefix(name, "bgp.")`.
114
+- `collect_snmp.go` and `charts.go` show the generic ddsnmp chart-per-row behavior that BGP must preserve.
115
+- `src/health/health.d/snmp_bgp.conf` defines 7 default-on BGP health templates.
116
+- `collector/snmp/metadata.yaml` lists 7 BGP alerts and includes Cumulus/Alcatel capability claims that lack real fixture backing.
117
+
118
+Risks:
119
+
120
+- Regular SNMP metrics can regress if BGP migration changes generic profile projection or charting paths.
121
+- BGP function discovery can remain noisy unless `funcRouter` registration becomes profile-aware.
122
+- BGP charts and function output can freeze on stale SNMP data unless freshness is modeled for both chart and function surfaces.
123
+- Vendor profile migration can break real device coverage if MIB index accessibility or Cisco profile attachment scope is guessed instead of audited.
124
+- Integration artifacts can drift unless metadata, generated docs, health alerts, config, and docs are closed together.
125
+- Raw local MIBs and fixture provenance must not leak into committed artifacts.
126
+
127
+## Pre-Implementation Gate
128
+
129
+Status: ready
130
+
131
+Problem / root-cause model:
132
+
133
+- BGP monitoring is currently implemented as a post-collection public-metric rewrite. The code takes ordinary SNMP metrics, routes by name, infers row kind from tag presence, carries hidden label fields through underscore-prefixed tags, and then builds chart/function surfaces from those derived strings. This repeats the hidden-protocol architecture already removed from SNMP topology and licensing. BGP needs the same typed projection treatment because its row kinds, identities, states, AFI/SAFI values, and signals are domain data, not chart naming accidents.
134
+
135
+Evidence reviewed:
136
+
137
+- `collector/snmp/bgp_public_routes.go` contains the metric-name routing layer and peer/peer-family inference.
138
+- `collector/snmp/bgp_public_specs.go` contains separate public chart specs that must stay in sync with routes.
139
+- `collector/snmp/func_bgp_peers_cache.go` contains a separate BGP leaf-to-function-field mapping.
140
+- `collector/snmp/bgp_public_routes.go` and `func_bgp_peers_cache.go` carry underscore-prefixed hidden label handling.
141
+- `collector/snmp/metric_ids.go` uses `strings.HasPrefix(name, "bgp.")` as a chart context contract.
142
+- `collector/snmp/ddsnmp/ddprofiledefinition/profile_definition.go` and `collector/snmp/ddsnmp/metric.go` show typed topology/licensing projections but no BGP projection.
143
+- `collect_snmp.go` and `charts.go` show existing ddsnmp chart-per-row behavior.
144
+- `src/health/health.d/snmp_bgp.conf` shows 7 default BGP alert templates.
145
+- Local untracked MIBs show `MAX-ACCESS not-accessible` peer index objects that must not be configured as readable symbols.
146
+
147
+Affected contracts and surfaces:
148
+
149
+- SNMP profile schema and validation.
150
+- ddsnmp catalog projection and profile consumers.
151
+- ddsnmpcollector typed output and stats.
152
+- SNMP collector BGP charts, functions, cache, stale semantics, and health alert inputs.
153
+- Default SNMP profile YAMLs for standard BGP4-MIB, Cisco, Juniper, Nokia, Huawei, Arista, Dell, Cumulus, Alcatel, and related mixins.
154
+- SNMP metadata and generated integration artifacts.
155
+- `src/health/health.d/snmp_bgp.conf`.
156
+- SNMP profile projection spec.
157
+- Runtime SNMP profile authoring skill if authoring rules change.
158
+
159
+Existing patterns to reuse:
160
+
161
+- Top-level typed `topology:` and `licensing:` profile sections.
162
+- `ResolvedProfileSet.Project(metrics, licensing)` variadic projection pattern.
163
+- Licensing row structural identity and inheritance override validation patterns.
164
+- Table/index `MAX-ACCESS not-accessible` discipline from `project-snmp-profiles-authoring`.
165
+- Existing ddsnmp scalar/table collection, table cache, stats, chart construction, and row reaping paths.
166
+- Table-driven Go tests using `map[string]struct{}` for shared setup/assertion shapes.
167
+
168
+Risk and blast radius:
169
+
170
+- The code touches a broad collector and many vendor SNMP profiles. Keep the first slice schema/API-only where possible.
171
+- Backward compatibility with this WIP BGP implementation is not required, but ordinary SNMP metrics must not regress.
172
+- Vendor profile migrations must be small enough to validate independently.
173
+- Function registration lifecycle changes may affect other SNMP functions if done at the shared router layer.
174
+- Health alerts remain default-on; chart context and dimension names must be statically reviewed until/unless a project alert test framework exists.
175
+- Removing Cumulus/Alcatel claims can reduce advertised coverage, but it removes unsupported public debt.
176
+
177
+Sensitive data handling plan:
178
+
179
+- Do not commit local raw MIB files under `mibs/`.
180
+- Do not commit SNMP communities, SNMPv3 credentials, bearer tokens, customer hostnames, customer sysName/sysDescr, customer IPs, customer names, personal data, or private endpoints.
181
+- Any fixture provenance must use public repository/commit/relative-path evidence or sanitized text, never workstation-local paths.
182
+- SOW/spec/docs/code comments must cite MIB facts by object name, table, and relative local evidence only while MIB files remain untracked; raw MIB text is not copied into durable artifacts.
183
+- Remove local `mibs/` files before SOW close-out.
184
+
185
+Implementation plan:
186
+
187
+1. Schema/API scaffold:
188
+ - Add typed BGP profile config structs, closed enums, validation helpers, identity helpers, and `ProfileMetrics.BGPRows`.
189
+ - Add `ProfileConsumerBGP` projection and projection tests.
190
+ - Update `.agents/sow/specs/snmp-profile-projection.md` with the BGP projection contract.
191
+2. Typed producer scaffold:
192
+ - Add BGP row collection/projection in ddsnmpcollector without changing vendor profiles yet.
193
+ - Add full-profile/synthetic tests for the standard BGP shape.
194
+3. Runtime consumer/function/charts:
195
+ - Feed typed rows into BGP charts/function cache.
196
+ - Preserve chart-per-row behavior.
197
+ - Implement stale cache and chart freshness semantics.
198
+ - Make BGP function registration profile-aware.
199
+4. Vendor migrations:
200
+ - Migrate one vendor/profile family at a time, with full-profile tests.
201
+ - Delete that vendor's old `bgp_public*` routes/spec/cache handling in the same slice.
202
+ - Audit Cisco profile attachment before Cisco migration.
203
+ - Remove Cumulus/Alcatel claims unless real fixtures are available.
204
+5. Final cleanup:
205
+ - Delete remaining `bgp_public*` router, underscore-prefix BGP protocol, and `bgp.` string-prefix context heuristics.
206
+ - Regenerate metadata/integration artifacts.
207
+ - Remove local untracked MIB files before close-out.
208
+
209
+Validation plan:
210
+
211
+- Schema validation tests for row kind, identity, state mappings, AFI/SAFI enums, MIB accessibility, and invalid signal/source combinations.
212
+- Projection tests for `Project(metrics)`, `Project(bgp)`, and mixed `Project(metrics, bgp)`.
213
+- Full-profile smoke tests per migrated vendor through normal profile loading/resolution/finalization.
214
+- Fixture-backed typed BGP row tests for each migrated vendor.
215
+- Function tests for no data, fresh rows, filtered-empty rows, stale-within-TTL rows, and stale-expired rows.
216
+- Chart tests for chart-per-row identity stability and stale/freshness signals.
217
+- Static/manual review of `src/health/health.d/snmp_bgp.conf` contexts and dimensions against typed chart output.
218
+- Same-failure searches for leftover `bgp_public`, underscore-prefixed BGP protocol, `strings.HasPrefix(name, "bgp.")`, Cumulus/Alcatel unsupported claims, workstation paths, and raw MIB files in git status.
219
+- Narrow Go test suites:
220
+ - `go test -count=1 ./collector/snmp/ddsnmp/...`
221
+ - `go test -count=1 ./collector/snmp/...`
222
+
223
+Artifact impact plan:
224
+
225
+- AGENTS.md: no expected update; existing SOW, collector, git, and SNMP profile authoring rules already apply.
226
+- Runtime project skills: update `.agents/skills/project-snmp-profiles-authoring/SKILL.md` if typed BGP authoring adds durable MIB/index guidance beyond the existing rule.
227
+- Specs: update `.agents/sow/specs/snmp-profile-projection.md` with BGP consumer/projection/delivery/validation rules.
228
+- End-user/operator docs: update `collector/snmp/metadata.yaml`, generated integration artifacts, and any BGP function documentation affected by output/config changes.
229
+- End-user/operator skills: no expected update unless public Netdata AI skills reference SNMP BGP schema or function behavior.
230
+- SOW lifecycle: move to `current/in-progress` before implementation; close only after validation, artifact gates, local MIB cleanup, and follow-up mapping are complete.
231
+
232
+Open-source reference evidence:
233
+
234
+- No local mirrored open-source repositories were checked for this gate. The actionable evidence is in current branch code, project specs/skills, local untracked vendor MIB files, and review outputs.
235
+
236
+Open decisions:
237
+
238
+- None. User decisions 1-23 are resolved in `## Implications And Decisions`.
239
+
240
+## Implications And Decisions
241
+
242
+1. **BGP architecture: A.**
243
+ - Use typed `bgp:` projection and typed `ProfileMetrics.BGPRows`.
244
+2. **BGP function and integration exposure: A.**
245
+ - Expose BGP only for profiles/jobs that declare typed BGP rows; restructure `funcRouter` lifecycle as needed.
246
+3. **Chart cardinality: A.**
247
+ - Keep existing ddsnmp chart-per-row behavior; no BGP-specific chart caps.
248
+4. **Stale cache semantics: A.**
249
+ - Implement stale-aware function behavior and chart-side freshness.
250
+5. **Per-peer alerts: A.**
251
+ - Keep per-peer/per-family BGP health templates default-on.
252
+6. **Cisco BGP attachment: A.**
253
+ - Move BGP out of `_cisco-base.yaml`; attach only to audited router/L3-switch profiles.
254
+7. **Unproven Cumulus/Alcatel coverage: C.**
255
+ - Remove claims until real fixtures exist.
256
+8. **Structural identity: A.**
257
+ - Use typed structural identity with canonical length-prefixed key.
258
+9. **AFI/SAFI policy: A.**
259
+ - Use closed canonical enums with explicit vendor-private allow-list.
260
+10. **MIB accessibility: A.**
261
+ - Enforce `MAX-ACCESS` accessibility; `not-accessible` fields come from indexes.
262
+11. **Function lifecycle: A.**
263
+ - Make function registration profile-aware.
264
+12. **Cap overflow: not applicable.**
265
+ - Decision 3A removes BGP-specific caps.
266
+13. **Production-shaped tests: A.**
267
+ - Require full-profile tests per migrated vendor.
268
+14. **Migration/artifact gate: A.**
269
+ - Use schema-first typed migration and delete old router rows/specs per vendor.
270
+ - Implementation constraint: standard BGP4 metric-name routes are shared by still-unmigrated vendor profiles. The standard profile migration removes its old `metrics:`/`virtual_metrics:` definitions to prevent duplicate output, but shared `bgp_public*` cases remain until the last dependent vendor is migrated.
271
+15. **State mapping completeness: A.**
272
+ - Require all six RFC 4271 states unless explicitly partial.
273
+16. **Underscore protocol: A.**
274
+ - Delete underscore-prefix BGP protocol with `bgp_public*`.
275
+17. **Function cache representation: A.**
276
+ - Make `BGPRow` the canonical snapshot/cache representation.
277
+18. **Health alert testing: B.**
278
+ - Do not build an alert test framework; validate by static/manual review.
279
+19. **Projection spec: A.**
280
+ - Update `.agents/sow/specs/snmp-profile-projection.md` with BGP.
281
+20. **BGP signal payload / row field model: B.**
282
+ - Runtime planning exposed that `Signals map[BGPSignalKind]BGPValue` is not a clean end-state row model.
283
+ - Claude review accepted Option B and flagged a current scaffold bug: categorical signals such as `last_down_reason`, `graceful_restart_state`, and `unavailability_reason` are routed through numeric conversion.
284
+ - Replace generic `Signals` with typed row/config fields/groups before runtime consumer/function/chart work.
285
+21. **Typed BGP cross-table value sources: A.**
286
+ - Cisco peer-family rows in `cbgpPeer2AddrFamilyPrefixTable` need `remote_as`, local address, local AS, identifiers, and BGP version from `cbgpPeer2Table`.
287
+ - Current raw Cisco YAML gets these with cross-table `metric_tags`; typed `BGPValueConfig` has no equivalent first-class source.
288
+ - Add first-class cross-table support to `BGPValueConfig`, mirroring `MetricTagConfig.table` + `index_transform`, so typed identity/descriptors can stay schema-owned.
289
+22. **Variable-length BGP index tail extraction: A.**
290
+ - Cisco peer-family AFI/SAFI fields follow variable-length `InetAddress` in the row index, so fixed absolute positions work only for IPv4 rows.
291
+ - Add reusable tail-based index extraction for typed BGP values and use it for Cisco AFI/SAFI.
292
+23. **Catalyst 9k BGP attachment scope: A.**
293
+ - `cisco-catalyst.yaml` is currently BGP-free but covers Catalyst families that can run BGP.
294
+ - Attach typed Cisco BGP broadly to `cisco-catalyst.yaml`.
295
+ - Accepted trade-off: unsupported BGP table walk cost on non-BGP Catalyst devices is temporary and expected to be mitigated by a generalized unsupported table-root cache follow-up.
296
+ - Follow-up scope: `.agents/sow/pending/SOW-0014-20260507-snmp-licensing-unsupported-table-cache.md` has been expanded from licensing-only to typed licensing + typed BGP unsupported table-root caching.
297
+
298
+## Plan
299
+
300
+1. Move this SOW to `current/in-progress` before code changes.
301
+2. Land schema/API scaffold and tests:
302
+ - BGP config structs and enums.
303
+ - Validation helpers.
304
+ - Projection plumbing.
305
+ - `ProfileMetrics.BGPRows`.
306
+ - Spec update.
307
+3. Land typed producer scaffold with a minimal standard BGP fixture path.
308
+4. Land typed runtime consumer/function/chart path:
309
+ - First replace generic `BGPRow.Signals` with strongly typed BGP row fields/groups if Decision 20 is resolved as 20B.
310
+ - Preserve chart-per-row behavior.
311
+ - Add stale/freshness semantics.
312
+ - Make BGP function profile-gated.
313
+5. Audit Cisco profile attachment and MIB accessibility before vendor migrations.
314
+6. Migrate standard BGP4-MIB.
315
+7. Migrate Cisco and remove BGP from `_cisco-base.yaml`.
316
+8. Migrate Juniper.
317
+9. Migrate Nokia.
318
+10. Migrate Huawei.
319
+11. Migrate Arista and Dell.
320
+12. Remove Cumulus/Alcatel public claims unless real fixtures are added.
321
+13. Delete remaining old BGP router/protocol code and string-prefix context gates.
322
+14. Regenerate metadata/integration artifacts and validate generated outputs.
323
+15. Run same-failure and sensitive-data scans.
324
+16. Remove local untracked `mibs/` files before close-out.
325
+17. Complete validation gate, move SOW to `done/`, and commit lifecycle change with implementation.
326
+18. Normalize remaining inline typed-domain profile sections into dedicated fragments before close-out.
327
+
328
+## Execution Log
329
+
330
+### 2026-05-08
331
+
332
+- Created pending SOW from review TODO after user resolved decisions 1-19.
333
+- Moved SOW to current/in-progress before code changes.
334
+- Landed schema/API/projection scaffold:
335
+ - `ddprofiledefinition.BGPConfig`, BGP row kinds, peer-state enums, AFI/SAFI enums, and typed BGP row field groups.
336
+ - `ProfileDefinition.BGP`, `ProfileMetrics.BGPRows`, and typed BGP row output structs.
337
+ - `ProfileConsumerBGP` projection support, mixed `Project(metrics, bgp)` behavior, inheritance merge, and cross-profile dedup scaffolding.
338
+ - BGP schema validation tests and projection tests.
339
+ - Initial BGP projection contract in `.agents/sow/specs/snmp-profile-projection.md`.
340
+- Landed typed producer scaffold:
341
+ - `ddsnmpcollector.collectBGPRows` supports scalar and table typed BGP rows.
342
+ - BGP rows are collected best-effort so BGP processing errors do not drop regular SNMP metrics.
343
+ - BGP scalar row collection honors the shared missing-OID cache.
344
+ - BGP table row collection uses the existing table cache and table dependency walk path.
345
+ - Main SNMP profile projection includes `ConsumerBGP` with metrics and licensing.
346
+ - Internal profile stats now include BGP timing, row count, and processing error dimensions.
347
+ - Added scalar, table, best-effort, and missing-OID cache tests for typed BGP rows.
348
+- Reworked the BGP scaffold per Decision 20B:
349
+ - Removed the generic `Signals map[BGPSignalKind]BGPValue` row model.
350
+ - Added typed BGP row/config groups for admin, state, previous state, connection, traffic, transitions, timers, last-error, last-notifications, reasons, graceful restart, routes, route limits, and device counts.
351
+ - Added categorical field coverage so text/mapped fields such as last-down reason are not routed through numeric conversion.
352
+ - Updated BGP validation and projection spec wording to describe typed fields rather than signal kinds.
353
+- Started typed runtime consumer wiring:
354
+ - Added typed BGP row to public chart-metric projection for existing BGP chart contexts.
355
+ - Updated the BGP function cache to accept typed `BGPRow` snapshots directly, preserving descriptor fields without underscore-prefixed tag routing.
356
+ - Added tests for typed BGP chart metrics and typed-row function-cache output.
357
+- Recorded the Decision 14 implementation constraint:
358
+ - `_std-bgp4-mib.yaml` is extended by multiple still-unmigrated vendor profiles, so shared `bgp_public*` standard BGP4 route/spec/cache cases cannot be deleted until the last dependent vendor profile is migrated.
359
+ - The old standard profile `metrics:`/`virtual_metrics:` definitions still need to be removed during the standard migration to avoid double-emission.
360
+- Migrated the standard BGP4-MIB profile to typed BGP rows:
361
+ - `_std-bgp4-mib.yaml` now declares `bgpPeerTable` as typed `bgp:` row `bgp4-peer`.
362
+ - Removed old standard `metrics:` and `virtual_metrics:` from `_std-bgp4-mib.yaml`.
363
+ - Removed Nokia fallback alternatives that pointed at the old standard BGP4 virtual metrics.
364
+ - Kept shared `bgp_public*` standard BGP4 route/spec/cache cases temporarily because unmigrated vendor profiles still depend on them.
365
+ - Set BGP `OriginProfileID` during profile load, matching licensing origin behavior.
366
+ - Updated standard BGP4 and Cumulus/FRR fixture coverage to assert typed `BGPRows`, while keeping the legacy alert-surface test scoped to still-unmigrated vendor raw paths.
367
+- Landed typed BGP runtime lifecycle and stale-cache handling:
368
+ - `Collector` no longer allocates/registers BGP integration by default.
369
+ - BGP integration is enabled only for resolved profiles that contain typed `bgp:` rows or temporary legacy BGP public-router inputs.
370
+ - BGP collection failures mark the function cache stale instead of leaving stale rows indistinguishable from fresh rows.
371
+ - `snmp:bgp-peers` returns stale rows during the bounded stale window, returns 503 after the stale window expires, and returns a 200 empty table for valid filtered-empty views.
372
+ - Function row building now snapshots the cache and releases the cache lock before sorting/building response rows.
373
+ - Framework boundary recorded: the static module method list still advertises `snmp:bgp-peers` globally. Fully hiding the method from global discovery for non-BGP jobs requires a broader `funcctl`/collectorapi capability change; the SNMP handler itself is profile-gated.
374
+- Landed typed cross-table BGP value sources and the Cisco typed migration slice:
375
+ - `BGPValueConfig` now supports first-class `table:` value sources with `index_transform`, mirroring the existing cross-table tag pattern without making typed identity depend on labels.
376
+ - `_cisco-bgp4-mib.yaml` now declares typed peer and peer-family BGP rows instead of raw `metrics:`/`virtual_metrics:`.
377
+ - Cisco BGP was removed from `_cisco-base.yaml`.
378
+ - Cisco BGP is attached only to audited router/L3/data-center profiles: `cisco-asr.yaml`, `cisco-csr1000v.yaml`, `cisco-isr.yaml`, `cisco-isr-4431.yaml`, `cisco-3850.yaml`, `cisco-nexus.yaml`, and the new narrow `cisco-ncs.yaml` profile for the NCS 540 fixture.
379
+ - Generic `cisco.yaml` remains BGP-free by design.
380
+ - Added `BGPValueConfig.index_from_end` and corrected Cisco peer-family AFI/SAFI extraction to read the final two `cbgpPeer2AddrFamilyPrefixTable` row-index components, which works for both IPv4 and IPv6 `InetAddress` indexes.
381
+- Resolved the Claude slice-review commit blockers after Decision 22A:
382
+ - Protected `funcRouter` runtime handler registration with a router `sync.RWMutex`.
383
+ - Surfaced per-profile typed BGP collection failures through `ProfileMetrics.BGPCollectError`.
384
+ - BGP integration now marks the function cache stale and skips peer-cache reset/finalize when a profile-level BGP failure occurs inside an otherwise successful SNMP collection cycle.
385
+ - Added focused regression tests for concurrent router registration, per-profile BGP failure stale-cache preservation, Cisco IPv6 peer-family AFI/SAFI extraction, and generic Cisco profile BGP non-inheritance.
386
+- Reconciled the Claude post-Decision 22A review:
387
+ - Accepted RT2-1: the first stale-cache fix was all-or-nothing across profiles. Updated BGP integration/cache handling so failed sources preserve stale rows while successful sources refresh normally in the same cycle.
388
+ - Added mixed-profile failure regression coverage, including the case where an expired failed source must not be kept alive by another source refreshing successfully.
389
+ - Accepted MIB2-2 and normalized empty typed BGP routing instances to `default` for chart/function tags.
390
+ - Recorded Catalyst BGP attachment as Decision 23 instead of silently attaching or silently dropping it.
391
+- Applied Decision 23A:
392
+ - `cisco-catalyst.yaml` now extends `_cisco-bgp4-mib.yaml`.
393
+ - Added a Catalyst 9300 profile-merge regression test proving typed Cisco BGP rows are inherited.
394
+ - Expanded SOW-0014 from licensing-only to typed licensing + typed BGP unsupported table-root caching, because broad Catalyst BGP has the same unsupported-table probe trade-off as broad Cisco licensing.
395
+- Reconciled the Claude final pre-commit review:
396
+ - Included `config/go.d/snmp.profiles/default/cisco-ncs.yaml` in the slice because this SOW claims the new NCS profile.
397
+ - Added repo-root `/mibs/` to `.gitignore` so local raw MIB audit files remain uncommitted.
398
+ - Accepted remaining schema/spec/runtime hygiene findings as SOW-close items, not commit blockers for this slice.
399
+- Migrated Juniper BGP to typed rows:
400
+ - `_juniper-bgp4-v2.yaml` now declares typed peer and peer-family BGP rows instead of raw `metrics:`/`virtual_metrics:`.
401
+ - Juniper base/router profiles no longer extend `_juniper-bgp-virtual.yaml`; the old virtual-metric path is no longer referenced.
402
+ - `BGPValueConfig.lookup_symbol` now supports typed BGP cross-table value lookups, needed because Juniper prefix counters are keyed by peer ID while peer identity lives in `jnxBgpM2PeerTable`.
403
+ - Empty successful BGP dependency walks are cached as empty walked tables, so optional augmented tables do not drop otherwise valid peer rows.
404
+ - Runtime BGP rows are skipped when required identity fields are missing at collection time.
405
+ - Added BGP projection coverage to `collector/snmp/ddsnmp/profile_test.go`.
406
+ - Reworked Juniper fixture tests to assert typed `BGPRows` from BGP-projected profiles and refactored similar test cases to `map[string]struct{}` tables.
407
+- Migrated Nokia/TiMOS BGP to typed rows:
408
+ - `_nokia-timetra-bgp.yaml` now declares typed peer and six typed peer-family BGP rows instead of raw `metrics:`/`virtual_metrics:`.
409
+ - Nokia no longer inherits `_std-bgp4-mib.yaml`; SR OS BGP rows come from TIMETRA-BGP-MIB only.
410
+ - Peer address is derived from the `tBgpPeerNgTable`/`tBgpPeerNgOperTable` row index because the MIB index objects are `MAX-ACCESS not-accessible`.
411
+ - Deleted unused `_juniper-bgp-virtual.yaml` after the Juniper migration removed all references.
412
+ - Updated BGP structural identity to include typed config ID so multiple logical typed rows over the same table/index do not collide.
413
+ - Updated Nokia profile, LibreNMS identity, and TiMOS fixture coverage to assert typed `BGPRows`.
414
+ - Added Nokia BGP projection coverage to `collector/snmp/ddsnmp/profile_test.go`.
415
+ - Reviewed new BGP tests for the branch's table-driven style rule and refactored same-shape Cisco prefix, Arista/Dell fixture, and TiMOS fixture tests to `map[string]struct{}` or equivalent map-keyed tables.
416
+- Reconciled the Claude Nokia/TiMOS commit review:
417
+ - Accepted the review verdict as ready with follow-ups.
418
+ - Added Nokia profile assertions for admin and six-state peer-state mappings.
419
+ - Added synthetic Nokia typed-collection coverage for admin-disabled, non-established states, and non-default VRF routing-instance extraction.
420
+ - Added broader Nokia profile assertions for descriptor, timer, traffic, notification, transition, and last-error fields.
421
+ - Updated `.agents/sow/specs/snmp-profile-projection.md` so BGP table structural identity includes typed config ID.
422
+ - Recorded the inherited TIMETRA high-numbered oper OID verification gap as a SOW follow-up.
423
+- Migrated Huawei BGP to typed rows:
424
+ - `huawei-routers.yaml` now declares typed BGP device, peer, and peer-family rows instead of raw BGP `metrics:`/`virtual_metrics:`.
425
+ - Removed `_huawei-bgp-statistics.yaml`; Huawei BGP session counts now flow through typed `device_counts`.
426
+ - Extended typed BGP device counts with `ibgp_peers` and `ebgp_peers` so Huawei preserves the public `configured`/`ibgp`/`ebgp` peer-count dimensions without the legacy public router.
427
+ - Removed Huawei-specific cases from the temporary `bgp_public*` metric-name router.
428
+ - Added Huawei BGP projection coverage to `collector/snmp/ddsnmp/profile_test.go`.
429
+ - Reworked Huawei LibreNMS fixture coverage to assert typed `BGPRows` directly, including IPv4/IPv6 peer-family rows, route totals, peer-level message/update counters, and device peer counts.
430
+ - Added synthetic Huawei typed-collection coverage for state/admin mapping and peer-statistics remote-AS lookup by peer address.
431
+ - Local raw MIB audit gap: `mibs/` contains `HUAWEI-MPLS-BGP-VPN-MIB.mib`, not the exact `HUAWEI-BGP-VPN-MIB`; profile OIDs are carried forward from the existing Huawei profile and the linked public Huawei BGP MIB references.
432
+
433
+### 2026-05-09
434
+
435
+- Migrated the remaining BGP-bearing vendor profiles and deleted the legacy BGP public router:
436
+ - Arista, Dell OS10, and Alcatel now declare typed `bgp:` rows.
437
+ - All stock BGP coverage is typed: standard `BGP4-MIB`, Cisco, Juniper, Nokia SR OS, Huawei, Arista, Dell OS10, and Alcatel.
438
+ - `bgp_public.go`, `bgp_public_routes.go`, and `bgp_public_test.go` were deleted.
439
+ - The retained chart-filter and chart-spec behavior now lives in `bgp_chart_filter.go` and `bgp_metric_specs.go`.
440
+ - The BGP function cache consumes typed `BGPRow` updates only.
441
+- Reconciled final migration review blockers:
442
+ - Corrected inherited Alcatel IPv6 BGP table OIDs from `.12` to `.14` according to `ALCATEL-IND1-BGP-MIB`.
443
+ - Restored end-to-end collection-level assertions for BGP metric IDs and chart context.
444
+ - Removed dead BGP alert-surface test helpers and moved the remaining shared profile helper.
445
+ - Reviewed and refactored applicable BGP tests to map-keyed table-driven cases.
446
+- Resolved AC#69:
447
+ - Updated `collector/snmp/metadata.yaml` to remove Cumulus and Alcatel from public BGP support claims until real fixture evidence exists.
448
+ - Regenerated integration artifacts with `integrations/gen_integrations.py`, `integrations/gen_docs_integrations.py -c go.d.plugin/snmp`, `integrations/gen_doc_collector_page.py`, and `integrations/gen_doc_secrets_page.py`.
449
+ - Committed generated output changed in `collector/snmp/integrations/snmp_devices.md`.
450
+ - Gitignored generated `integrations/integrations.js` and `integrations/integrations.json` were regenerated locally.
451
+ - `src/collectors/COLLECTORS.md` and `src/collectors/SECRETS.md` had no tracked diff after generation.
452
+- Updated durable authoring and behavior references:
453
+ - `collector/snmp/profile-format.md` now documents typed BGP `partial_states`, routing-instance default labeling, and `index_from_end` selector behavior.
454
+ - `.agents/sow/specs/snmp-profile-projection.md` now records typed BGP structural identity, default routing-instance normalization, per-source stale-cache semantics, and row-index selector validation behavior.
455
+ - `.agents/skills/project-snmp-profiles-authoring/SKILL.md` now records the BGP authoring checks for typed `bgp:` rows, RFC 4271 state mappings, and index-derived BGP fields.
456
+- Created `.agents/sow/pending/SOW-0016-20260509-snmp-bgp-hardening-followups.md` for non-blocking validation/runtime/fixture hardening that should not be hidden as vague follow-up debt.
457
+- Applied the user's typed-domain profile organization decision:
458
+ - Extracted inline Arista, Dell OS10, and Huawei `bgp:` sections into `_arista-bgp4-v2.yaml`, `_dell-os10-bgp4-v2.yaml`, and `_huawei-bgp-vpn.yaml`.
459
+ - Extracted inline Blue Coat, Check Point, Fortinet FortiGate, MikroTik RouterOS, and Sophos XGS `licensing:` sections into `_bluecoat-proxysg-licensing.yaml`, `_checkpoint-licensing.yaml`, `_fortinet-fortigate-licensing.yaml`, `_mikrotik-routeros-licensing.yaml`, and `_sophos-xgs-firewall-licensing.yaml`.
460
+ - Concrete device profiles now extend the typed fragments and no longer mix inline typed-domain sections with regular metric sections.
461
+ - Updated Arista/Dell typed BGP fixture assertions to expect the new fragment origin profile IDs.
462
+- Folded the pending SOW-0016 hardening scope back into SOW-0015 by user decision:
463
+ - Deleted the separate pending SOW-0016 file after migrating its remaining work into this SOW's follow-up mapping.
464
+ - Reclassified RT-4 as an intentional chart-identity invariant: public BGP chart keys follow the same logical SNMP table identity model as regular table metrics (`name` plus visible tag values), while the BGP function cache keeps structural row identity separately.
465
+ - Added a focused runtime test proving same logical BGP peers from different typed rows resolve to the same public chart identity and do not include source or structural IDs in the chart key.
466
+ - Kept remaining hardening work in this SOW instead of tracking it through a separate pending SOW.
467
+- Completed the folded SOW-0016 hardening items selected by the user:
468
+ - Added typed BGP validation for mutually-exclusive row-index selectors (`index`, `index_from_end`, `index_transform`).
469
+ - Added conservative typed BGP cross-table validation for declared referenced BGP tables, including source and lookup-symbol OID prefix checks.
470
+ - Added stale BGP function-cache entry reaping after the stale window expires.
471
+ - Added Dell OS10 IPv6 typed BGP synthetic-PDU coverage.
472
+ - Added `index_from_end` plus `format` propagation coverage.
473
+ - Extended the logical chart-identity test to verify descriptor labels remain visible labels while public chart keys omit source and structural identity.
474
+- Reconciled final external review findings:
475
+ - Final review verdict was `READY TO MERGE`; no P0/P1 blockers were reported.
476
+ - Added remaining row-index selector combination negative tests.
477
+ - Added BGP row kind vs field-group compatibility negative tests.
478
+ - Extended Dell OS10 IPv6 coverage with a dependency-table traffic counter assertion.
479
+ - Replaced local-only Nokia/TiMOS MIB closure evidence with reproducible source references.
480
+
481
+## Validation
482
+
483
+Acceptance criteria evidence:
484
+
485
+- Met for the implemented migration:
486
+ - Typed `bgp:` profile section exists in `ddprofiledefinition.ProfileDefinition`.
487
+ - Typed BGP output exists in `ddsnmp.ProfileMetrics.BGPRows`.
488
+ - `Project(bgp)` and `Project(metrics, bgp)` projection paths exist and are tested.
489
+ - Closed row kind, peer state, AFI/SAFI, and typed-field validation exists.
490
+ - `ddsnmpcollector` emits typed BGP rows for scalar and table `bgp:` configs.
491
+ - BGP collection is wired into the regular SNMP profile collection path and keeps regular metrics on BGP row errors.
492
+ - BGP profile stats are exposed alongside scalar/table/licensing/internal stats.
493
+ - Typed BGP rows project into BGP chart metrics and the BGP peers function cache.
494
+ - All BGP-bearing stock profile coverage is typed: standard `BGP4-MIB`, Cisco, Juniper, Nokia SR OS, Huawei, Arista, Dell OS10, and Alcatel.
495
+ - `bgp_public.go`, `bgp_public_routes.go`, and `bgp_public_test.go` are deleted.
496
+ - Production runtime scans found no remaining `bgp_public`, `BGPSignalKind`, `bgpScopeAuto`, `mergeBGPPeerEntryTags`, `bgpPeerMetricLeaf`, `normalizeCollectorMetrics`, or `routeBGPPublicMetric` references outside tests/docs.
497
+ - Inline typed-domain profile section scan now finds `bgp:` and `licensing:` only in underscore-prefixed typed fragments.
498
+ - Legacy BGP identity/router underscore protocol (`_routing_instance`, `_neighbor`, `_remote_as`, `_address_family`, `_subsequent_address_family`) has no production code or profile YAML use. Remaining hits are the generic virtual-metric example in `collector/snmp/profile-format.md`.
499
+ - Typed BGP descriptor labels still use the generic SNMP convention of underscore-prefixed chart labels (`_local_address`, `_local_as`, `_peer_identifier`, and related descriptors) in `bgp_typed_metrics.go`; this is not the deleted legacy BGP public-router protocol. `TestTypedBGPMetricsUseLogicalChartIdentity` verifies descriptor labels are visible chart labels and are not part of public chart-key identity.
500
+ - Public typed BGP chart keys intentionally use logical SNMP table metric identity, not typed structural row identity. `TestTypedBGPMetricsUseLogicalChartIdentity` covers the invariant that same logical peer tags from different typed rows resolve to the same public chart key.
501
+ - Typed BGP profile validation rejects values that combine multiple row-index selectors.
502
+ - Typed BGP profile validation now verifies `table:` references to declared BGP row tables when the table root is known at profile-load time. It validates source OIDs and `lookup_symbol` OIDs against the referenced table OID prefix. Runtime-inferred cross-table roots remain supported for existing fragments whose `table:` source is not a declared BGP row table.
503
+ - Cisco BGP was removed from `_cisco-base.yaml` and attached to audited router/L3/data-center Cisco profiles, including Catalyst by Decision 23A.
504
+ - Cumulus and Alcatel were removed from public BGP capability claims in `metadata.yaml` and regenerated `snmp_devices.md`.
505
+ - `.agents/sow/specs/snmp-profile-projection.md` documents the BGP projection contract, including the typed config ID in structural identity.
506
+ - Metadata and generated integration docs are coherent for the BGP support list and BGP alert/metric/function surface.
507
+- No open items remain before moving this SOW to `done/`.
508
+
509
+Tests or equivalent validation:
510
+
511
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08.
512
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08.
513
+- `go test -count=1 -run 'TestCollector_Collect_BGPRows|TestCollector_Collect_StatsSnapshot' ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08.
514
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08 after the standard BGP4-MIB typed-profile migration.
515
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the standard BGP4-MIB typed-profile migration.
516
+- `go test -count=1 -run 'TestFuncBGPPeers|TestBGPPeerCache|TestProfilesHaveBGP|TestCollectSNMP_HidesBGPDiagnosticsButKeepsFunctionCache|TestCollector_BGPFunctionHandlerIsRegisteredOnlyWhenEnabled|TestTypedBGPMetricsFromProfileMetrics' ./collector/snmp` passed on 2026-05-08 after typed BGP runtime lifecycle changes.
517
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after typed BGP runtime lifecycle changes.
518
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08 after Decision 21A and the Cisco typed-profile migration.
519
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after Decision 21A and the Cisco typed-profile migration.
520
+- `go test -count=1 ./collector/snmp/ddsnmp/ddprofiledefinition` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
521
+- `go test -count=1 ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
522
+- `go test -count=1 ./collector/snmp` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
523
+- `go test -count=1 ./collector/snmp/ddsnmp -run 'Test_Cisco'` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
524
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
525
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after Decision 22A and the Claude blocker fixes.
526
+- `go test -race -run TestFuncRouter_ConcurrentRegisterAndHandle ./collector/snmp` passed on 2026-05-08.
527
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08 after the RT2-1 mixed-profile failure fix.
528
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the RT2-1 mixed-profile failure fix.
529
+- `go test -race -run 'TestFuncRouter_ConcurrentRegisterAndHandle|TestBGPIntegration_MixedProfileBGPErrorRefreshesSuccessfulProfiles|TestBGPIntegration_ExpiredFailedProfileDoesNotKeepStaleRowsWithFreshProfiles' ./collector/snmp` passed on 2026-05-08 after the RT2-1 mixed-profile failure fix.
530
+- `go test -count=1 ./collector/snmp/ddsnmp -run 'Test_CiscoBGPProfileMergedIntoCiscoCatalyst|Test_CiscoGenericProfilesDoNotInheritBGP|Test_CiscoBGPProfileMergedIntoCiscoASR|Test_CiscoBgpPrefixProfileMergedIntoCiscoASR'` passed on 2026-05-08 after Decision 23A.
531
+- `go test -count=1 ./collector/snmp/ddsnmp/...` passed on 2026-05-08 after Decision 23A.
532
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after Decision 23A.
533
+- `go test -count=1 ./collector/snmp/ddsnmp ./collector/snmp/ddsnmp/ddprofiledefinition ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08 after the Juniper typed-profile migration and BGP projection test refactor.
534
+- `go test -count=1 ./collector/snmp` passed on 2026-05-08 after the Juniper typed-profile migration and BGP projection test refactor.
535
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the Juniper typed-profile migration and BGP projection test refactor.
536
+- `go test -race -count=1 ./collector/snmp` passed on 2026-05-08 after the Juniper typed-profile migration and BGP projection test refactor.
537
+- `go test -count=1 ./collector/snmp/ddsnmp ./collector/snmp/ddsnmp/ddprofiledefinition ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08 after the Nokia/TiMOS typed-profile migration and test refactor.
538
+- `go test -count=1 ./collector/snmp` passed on 2026-05-08 after the Nokia/TiMOS typed-profile migration and test refactor.
539
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the Nokia/TiMOS typed-profile migration and test refactor.
540
+- `go test -race -count=1 ./collector/snmp` passed on 2026-05-08 after the Nokia/TiMOS typed-profile migration and test refactor.
541
+- `go test -count=1 ./collector/snmp/ddsnmp ./collector/snmp/ddsnmp/ddprofiledefinition ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08 after the Claude Nokia/TiMOS review follow-ups.
542
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the Claude Nokia/TiMOS review follow-ups.
543
+- `go test -race -count=1 ./collector/snmp` passed on 2026-05-08 after the Claude Nokia/TiMOS review follow-ups.
544
+- `go test -count=1 ./collector/snmp/ddsnmp ./collector/snmp/ddsnmp/ddprofiledefinition ./collector/snmp/ddsnmp/ddsnmpcollector` passed on 2026-05-08 after the Huawei typed-profile migration.
545
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-08 after the Huawei typed-profile migration.
546
+- `go test -race -count=1 ./collector/snmp` passed on 2026-05-08 after the Huawei typed-profile migration.
547
+- `go test -count=1 ./collector/snmp -run 'Test(BGPLastErrorText|BGPPeerEntryKey|BGPPeerCache|FuncBGPPeers|CollectSNMP_HidesBGP)'` passed on 2026-05-09 after the BGP table-driven test refactor.
548
+- `go test -count=1 ./collector/snmp/ddsnmp -run 'Test_(LibreNMSBGPIdentityFixtures|CiscoBGPProfiles|CiscoGeneric|AlcatelBGPProfile|HuaweiBGP|JuniperBGP|NokiaBGP)'` passed on 2026-05-09 after the BGP table-driven test refactor.
549
+- `go test -count=1 ./collector/snmp/ddsnmp/ddsnmpcollector -run 'TestCollector_Collect_(GenericBGP4Rows|HuaweiBGP|AlcatelBGP|AristaAndDellBGP|CiscoBgpPeer2|CiscoBgpPeer3|JuniperBGP|TiMOSBGP)'` passed on 2026-05-09 after the BGP table-driven test refactor.
550
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after the BGP table-driven test refactor.
551
+- `git diff --check` passed on 2026-05-09 after the BGP table-driven test refactor.
552
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after metadata/docs/spec/skill close-out updates and local integration docs regeneration.
553
+- `git diff --check` passed on 2026-05-09 after metadata/docs/spec/skill close-out updates and local integration docs regeneration.
554
+- `go test -count=1 ./collector/snmp/ddsnmp -run 'Test_(AristaAndDellBGPProfilesUseTypedRows|HuaweiBGPProfileUsesTypedRows|ProfilesProjectConsumers|NokiaBGPProfile|CiscoBGPProfiles|JuniperBGPProfiles)|TestProfile_Merge'` passed on 2026-05-09 after typed-domain profile fragment extraction.
555
+- `go test -count=1 ./collector/snmp/ddsnmp/ddsnmpcollector -run 'TestCollector_Collect_(LicensingProfiles|LicensingProfileFixtures|SophosLicensingProfile|FortiGate|BlueCoat|HuaweiBGP|AlcatelBGP|AristaAndDellBGP|TiMOSBGP|GenericBGP4Rows)'` passed on 2026-05-09 after typed-domain profile fragment extraction.
556
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after typed-domain profile fragment extraction.
557
+- `git diff --check` passed on 2026-05-09 after typed-domain profile fragment extraction.
558
+- `go test -count=1 ./collector/snmp -run 'Test(BGPPeerCache_UpdateRow|BGPIntegration_(PreservesFunctionCacheOnProfileBGPError|RecoveryClearsStaleFunctionRows|MixedProfileBGPErrorRefreshesSuccessfulProfiles|ExpiredFailedProfileDoesNotKeepStaleRowsWithFreshProfiles))'` passed on 2026-05-09 after pre-merge BGP cache hardening tests.
559
+- `go test -count=1 ./collector/snmp/ddsnmp/ddprofiledefinition -run 'TestValidateEnrichProfile_BGP'` passed on 2026-05-09 after adding the `partial: true` empty-mapping rejection test.
560
+- `go test -count=1 ./collector/snmp/ddsnmp -run 'Test_(AristaAndDellBGPProfilesUseTypedRows|JuniperBGPProfilesUseOnlyJuniperPeerTables|AlcatelBGPProfileUsesTypedRows|HuaweiBGPProfileUsesTypedRows|NokiaBGPProfileMergedIntoNokiaSROS)'` passed on 2026-05-09 after adding vendor six-state mapping assertions.
561
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after pre-merge hardening tests.
562
+- `go test -race -count=1 ./collector/snmp` passed on 2026-05-09 after pre-merge hardening tests.
563
+- `go test -count=1 ./collector/snmp -run 'TestTypedBGPMetricsUseLogicalChartIdentity|TestTypedBGPMetricsFromProfileMetrics|TestBGPPeerCache_UpdateRow'` passed on 2026-05-09 after folding SOW-0016 back into SOW-0015 and adding the logical chart-identity invariant test.
564
+- `go test -count=1 ./collector/snmp/ddsnmp/ddprofiledefinition -run 'TestValidateEnrichProfile_BGP'` passed on 2026-05-09 after typed BGP row-index selector and declared cross-table validation hardening.
565
+- `go test -count=1 ./collector/snmp -run 'Test(BGPPeerCache_UpdateRow|BGPIntegration_ExpiredFailedProfileDoesNotKeepStaleRowsWithFreshProfiles|TypedBGPMetricsUseLogicalChartIdentity)'` passed on 2026-05-09 after stale-entry reaping and descriptor-label chart-key coverage.
566
+- `go test -count=1 ./collector/snmp/ddsnmp/ddsnmpcollector -run 'TestCollector_Collect_(AristaAndDellBGP_FromLibreNMSFixtures|DellOS10BGP_IPv6Rows|BGPRowsWithCrossTableBGPValues)'` passed on 2026-05-09 after Dell IPv6 and `index_from_end` plus `format` coverage.
567
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after completing the folded SOW-0016 hardening items selected by the user.
568
+- `go test -count=1 ./collector/snmp/ddsnmp/ddprofiledefinition -run 'TestValidateEnrichProfile_BGP'` passed on 2026-05-09 after final-review P2 validation-test polish.
569
+- `go test -count=1 ./collector/snmp/ddsnmp/ddsnmpcollector -run 'TestCollector_Collect_DellOS10BGP_IPv6Rows'` passed on 2026-05-09 after final-review P2 Dell IPv6 dependency-table coverage.
570
+- `go test -count=1 ./collector/snmp/...` passed on 2026-05-09 after final-review P2 polish.
571
+- `git diff --check` passed on 2026-05-09 after final-review P2 polish.
572
+
573
+Real-use evidence:
574
+
575
+- No live production device walk was run in this SOW.
576
+- Production-shaped profile fixture and synthetic-PDU evidence covers the migrated typed paths:
577
+ - Standard `BGP4-MIB` and Cumulus/FRR-style standard fixture paths now assert typed `BGPRows`.
578
+ - Cisco typed peer and peer-family rows cover IPv4 and IPv6 `InetAddress` row-index AFI/SAFI extraction.
579
+ - Juniper, Nokia/TiMOS, Huawei, Arista, Dell OS10, and Alcatel tests assert typed rows through normal profile loading/resolution/finalization or collector production paths.
580
+ - `snmp:bgp-peers` cache/function tests cover typed row updates, stale rows, mixed per-source failures, expiry, and recovery.
581
+- Integration docs were regenerated through the repository generator path, not hand-edited in the generated file.
582
+
583
+Reviewer findings:
584
+
585
+- External Claude review passes were run by the user during the SOW and reconciled before commits.
586
+- Final external review on 2026-05-09 returned `READY TO MERGE` with no P0/P1 blockers.
587
+- Commit blockers fixed during the SOW include:
588
+ - `funcRouter` handler map runtime race.
589
+ - Per-profile BGP failure wiping stale peer cache.
590
+ - Cisco IPv6 peer-family AFI/SAFI positional extraction.
591
+ - Mixed-profile BGP failure freezing successful profiles.
592
+ - Catalyst BGP attachment decision and test.
593
+ - Untracked `cisco-ncs.yaml` slice drift.
594
+ - Alcatel IPv6 BGP table OID `.12` vs authoritative `.14`.
595
+ - Lost end-to-end BGP metric ID/chart-context coverage.
596
+ - Dead BGP alert-surface test helper file.
597
+- Non-blocking findings were either implemented during close-out or explicitly tracked:
598
+ - SOW-0014 owns typed licensing + typed BGP unsupported table-root caching.
599
+ - This SOW completed typed BGP validation/runtime/fixture hardening after the user's decision to fold SOW-0016 back into SOW-0015.
600
+ - Final-review P2 test gaps SCH5-1, SCH5-3, and PROF5-2 were implemented in SOW-0015.
601
+
602
+Same-failure scan:
603
+
604
+- Legacy router scan:
605
+ - Command: `rg -n "bgp_public|BGPSignalKind|bgpScopeAuto|mergeBGPPeerEntryTags|bgpPeerMetricLeaf|normalizeCollectorMetrics|routeBGPPublicMetric" src/go/plugin/go.d/collector/snmp src/go/plugin/go.d/config/go.d/snmp.profiles/default --glob '!**/*_test.go'`
606
+ - Result: no hits.
607
+- Legacy BGP underscore identity protocol scan:
608
+ - Command: `rg -n '"_(routing_instance|neighbor|remote_as|address_family|subsequent_address_family)"|\b_(routing_instance|neighbor|remote_as|address_family|subsequent_address_family)\b' src/go/plugin/go.d/collector/snmp src/go/plugin/go.d/config/go.d/snmp.profiles/default --glob '!**/*_test.go'`
609
+ - Result: only the generic virtual-metric example in `collector/snmp/profile-format.md`; no production code or profile YAML uses the deleted BGP identity-router protocol.
610
+- Cumulus/Alcatel BGP claim scan:
611
+ - Command: `rg -n "Cumulus|Alcatel" src/go/plugin/go.d/collector/snmp/metadata.yaml src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md integrations/integrations.js src/collectors/COLLECTORS.md`
612
+ - Result: remaining `Alcatel-Lucent` hits are in the generic Wireless vendor table, not the BGP capability/support list; regenerated `integrations.js` contains the updated BGP support sentence without Cumulus or Alcatel.
613
+- Raw MIB tracking scan:
614
+ - Command: `git ls-files mibs`
615
+ - Result: no tracked MIB files.
616
+ - Command: `git status --short --ignored=matching mibs`
617
+ - Result: no output after cleanup.
618
+ - Command: `test -d mibs && echo exists || echo missing`
619
+ - Result: `missing`.
620
+- Generated artifact status:
621
+ - `integrations/integrations.js` and `integrations/integrations.json` were regenerated and remain gitignored.
622
+ - `collector/snmp/integrations/snmp_devices.md` is the committed generated SNMP integration page diff for this SOW.
623
+- Typed-domain profile organization scan:
624
+ - Command: `rg -n "^bgp:|^licensing:" config/go.d/snmp.profiles/default -g "*.yaml"`
625
+ - Result: all hits are in underscore-prefixed typed fragments; no concrete device profile has an inline `bgp:` or `licensing:` section.
626
+
627
+Sensitive data gate:
628
+
629
+- Sensitive string scan:
630
+ - Command: `rg -n "BEGIN (RSA|OPENSSH|PRIVATE)|Authorization:|Bearer |password|passphrase|community:|SNMP_V3|SNMP_V2C|customer|token|secret|private key" .agents/sow/current/SOW-0015-20260508-snmp-bgp-typed-projection.md .agents/sow/specs/snmp-profile-projection.md .agents/skills/project-snmp-profiles-authoring/SKILL.md src/go/plugin/go.d/collector/snmp/metadata.yaml src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md src/go/plugin/go.d/collector/snmp/profile-format.md`
631
+ - Result: hits are documented placeholder SNMP examples (`community: public`, `auth_protocol_passphrase`, `priv_protocol_passphrase`) and the SOW's own sensitive-data policy text. No real credentials or customer data found.
632
+- Workstation path scan:
633
+ - Command: ran `rg` for home-directory paths, private scratch paths, mirrored-repo workstation paths, and user/workstation markers across the SOW, spec, project skill, metadata, generated SNMP integration docs, and profile-format docs.
634
+ - Result: no hits.
635
+- Local raw MIB files under `mibs/` were removed before SOW close-out.
636
+
637
+Artifact maintenance gate:
638
+
639
+- AGENTS.md:
640
+ - No project-wide workflow change needed. The existing Go test style rule already captures the user's table-driven test preference.
641
+- Runtime project skills:
642
+ - Updated `.agents/skills/project-snmp-profiles-authoring/SKILL.md` with typed BGP profile authoring checks.
643
+ - No update needed to `project-writing-collectors`; this SOW refined SNMP profile-specific BGP rules rather than general collector authoring policy.
644
+ - `integrations-lifecycle` was used for generator workflow; no missing pipeline behavior was discovered that required a skill update.
645
+- Specs:
646
+ - Updated `.agents/sow/specs/snmp-profile-projection.md` with BGP typed projection, structural identity, routing-instance normalization, stale-cache semantics, `partial_states`, row-index selector mutual exclusivity, and declared cross-table reference validation.
647
+- End-user/operator docs:
648
+ - Updated `collector/snmp/metadata.yaml`.
649
+ - Regenerated `collector/snmp/integrations/snmp_devices.md`.
650
+ - Updated `collector/snmp/profile-format.md` for typed BGP authoring details.
651
+ - `src/collectors/COLLECTORS.md` and `src/collectors/SECRETS.md` had no tracked diff after generation.
652
+- End-user/operator skills:
653
+ - No public Netdata AI skill or operator workflow skill was affected by the BGP SNMP profile schema/docs changes.
654
+ - SOW audit flagged an email-address-pattern false positive in `.agents/skills/mirror-netdata-repos/SKILL.md` for an SSH clone URL example. The line was reworded to avoid durable false-positive sensitive-data output.
655
+- SOW lifecycle:
656
+ - SOW-0015 status is `completed` and the file is moved to `.agents/sow/done/`.
657
+ - Initially created pending SOW-0016 for post-migration BGP hardening, then folded it back into SOW-0015 by user decision because the work is directly related and the branch is not under merge time pressure.
658
+ - SOW-0014 remains the owner for typed licensing + typed BGP unsupported table-root caching.
659
+
660
+Specs update:
661
+
662
+- Updated `.agents/sow/specs/snmp-profile-projection.md` with BGP consumer, row shape, projection, inheritance, delivery, validation guarantees, structural identity, routing-instance normalization, stale-cache semantics, `partial_states`, and current row-index selector behavior.
663
+- Updated `.agents/sow/specs/snmp-profile-projection.md` again during folded hardening to record row-index selector mutual exclusivity and declared cross-table OID validation.
664
+
665
+Project skills update:
666
+
667
+- Updated `.agents/skills/project-snmp-profiles-authoring/SKILL.md`.
668
+
669
+End-user/operator docs update:
670
+
671
+- Updated `collector/snmp/metadata.yaml`.
672
+- Regenerated `collector/snmp/integrations/snmp_devices.md`.
673
+- Updated `collector/snmp/profile-format.md`.
674
+
675
+End-user/operator skills update:
676
+
677
+- No public/end-user AI skills were affected.
678
+
679
+Lessons:
680
+
681
+- Typed projection work should migrate source profiles and runtime consumers before deleting string-router code; doing the deletion only after the final vendor migration made the old-protocol leak scan decisive.
682
+- Variable-length SNMP indexes require tail-based extraction for fields such as AFI/SAFI; fixed positional index assumptions work for IPv4 and fail for IPv6.
683
+- BGP function cache freshness must be source-aware. A single profile failure must not freeze successful profiles in the same collection cycle.
684
+- Integration docs should be regenerated locally after metadata changes. Waiting for CI would have hidden AC#69 drift until after merge.
685
+
686
+Follow-up mapping:
687
+
688
+- Implemented in SOW-0015:
689
+ - Remove Cumulus and Alcatel BGP public capability claims until real fixtures exist.
690
+ - Regenerate SNMP integration artifacts after metadata changes.
691
+ - Document `partial_states`, empty routing-instance normalization, per-source stale-cache semantics, and `index_from_end` current behavior.
692
+ - Delete the legacy `bgp_public*` runtime and BGP public-router underscore protocol.
693
+ - Normalize remaining inline typed-domain profile sections into dedicated `bgp:` and `licensing:` fragments.
694
+- Tracked in SOW-0014:
695
+ - Typed licensing + typed BGP unsupported table-root caching for broad Cisco/Catalyst unsupported table walks.
696
+- Resolved during pre-merge review:
697
+ - RT-4 typed BGP chart-key identity. Public chart keys intentionally use the existing logical SNMP table metric model (`name` plus visible tag values), not BGP structural row identity. `TestTypedBGPMetricsUseLogicalChartIdentity` covers that same logical peer tags from different typed rows resolve to the same public chart key. Function-cache identity remains structural and is tested separately.
698
+ - NOK-1 Nokia/TiMOS `tBgpPeerNgOperTable` high-numbered OID verification.
699
+ Nokia documentation states release MIB files are packaged with releases and
700
+ available from the Nokia support portal or on-device MIB bundles
701
+ (`https://documentation.nokia.com/srlinux/25-3/books/system-mgmt/snmp.html`).
702
+ The local untracked `mibs/TIMETRA-BGP-MIB.mib` was refreshed from the Nokia
703
+ `TIMETRA-BGP-MIB` revision `LAST-UPDATED "202302150000Z"`. The public
704
+ Observium mirror of Nokia's `TIMETRA-BGP-MIB`
705
+ (`https://mibs.observium.org/mib/TIMETRA-BGP-MIB/`) shows the same
706
+ TiMetra branch and high-numbered `tBgpPeerNgOperEntry` objects, including
707
+ entries beyond `.160`. The refreshed MIB confirms `.177`, `.178`, and
708
+ `.181-.188` as `MAX-ACCESS read-only`.
709
+ - SCH-3 `partial: true` empty mapping/spec-validator consistency. The
710
+ validator now returns a specific error for `partial: true` state configs
711
+ without mapping items, and `TestValidateEnrichProfile_BGP` covers that
712
+ contract.
713
+ - RT3-1 stale BGP cache entries from permanent per-source failure. Expired
714
+ stale entries are reaped after the configured stale window, and
715
+ `TestBGPIntegration_ExpiredFailedProfileDoesNotKeepStaleRowsWithFreshProfiles`
716
+ asserts the expired source key is removed from the cache.
717
+ - SCH-2 typed BGP cross-table `table:` reference validation. Profile
718
+ validation now resolves declared BGP row table names and rejects source or
719
+ lookup-symbol OIDs outside the referenced table root. Existing runtime
720
+ inference for non-declared cross-table roots remains supported by design.
721
+ - SCH2-4 row-index selector mutual exclusivity. Profile validation rejects
722
+ typed BGP values that set more than one of `index`, `index_from_end`, and
723
+ `index_transform`.
724
+ - SCH2-6 `index_from_end` plus `format` propagation coverage. The
725
+ cross-table BGP row test now covers `IndexFromEnd` with `Format: "hex"`.
726
+ - Dell IPv6 typed BGP coverage. `TestCollector_Collect_DellOS10BGP_IPv6Rows`
727
+ covers IPv6 peer and peer-family rows with typed identity, descriptors,
728
+ connection fields, and route counts.
729
+ - Typed BGP descriptor underscore label behavior. The logical chart-identity
730
+ test verifies descriptor labels such as `_local_address` and
731
+ `_peer_description` remain visible labels and do not participate in public
732
+ chart-key identity.
733
+ - Final-review P2 validation coverage. `TestValidateEnrichProfile_BGP` now
734
+ covers the remaining row-index selector combinations and BGP row kind vs
735
+ field-group compatibility branches.
736
+ - Final-review P2 Dell IPv6 dependency-table coverage.
737
+ `TestCollector_Collect_DellOS10BGP_IPv6Rows` now asserts an IPv6
738
+ dependency-table traffic counter in addition to identity, descriptors,
739
+ state, and routes.
740
+ - TST5-2/TST5-4/TST5-9 pre-merge test gaps. Added function-cache coverage
741
+ for same-peer structural IDs from different profile rows, stale failure
742
+ recovery, and vendor six-state mapping assertions for Arista, Dell,
743
+ Juniper, and Alcatel.
744
+- Rejected as a SOW-0015 follow-up:
745
+ - Generated function-name capitalization (`Snmp:*`, `Docker:*`, `Mysql:*`, etc.) is a pre-existing global integration template behavior in `integrations/templates/functions.md`, not specific to BGP typed projection. It should be handled only as a separate integrations-docs decision if the project wants to normalize all generated Function names.
746
+- Nothing remains pending before SOW close.
747
+
748
+## Outcome
749
+
750
+Completed. SNMP BGP monitoring now uses typed `bgp:` projection end to end, all BGP-bearing stock profiles are migrated, the legacy `bgp_public*` runtime is deleted, metadata/docs/spec artifacts are reconciled, external review blockers and selected P2 polish are addressed, SOW-0016 is folded into this SOW, and local raw `mibs/` reference files are removed.
751
+
752
+## Lessons Extracted
753
+
754
+Recorded in the Validation gate.
755
+
756
+## Followup
757
+
758
+All valid follow-up items from this SOW are implemented, rejected with evidence, or represented by SOW-0014. SOW-0016 was intentionally deleted after migration into this SOW by user decision.
759
+
760
+## Regression Log
761
+
762
+None yet.
763
+
764
+Append regression entries here only after this SOW was completed or closed and later testing or use found broken behavior. Use a dated `## Regression - YYYY-MM-DD` heading at the end of the file. Never prepend regression content above the original SOW narrative.
.agents/sow/pending/SOW-0014-20260507-snmp-licensing-unsupported-table-cache.md
+21
-16
@@ -1,34 +1,37 @@
1
-# SOW-0014 - SNMP licensing unsupported table cache
1
+# SOW-0014 - SNMP typed unsupported table cache
2
3
## Status
4
5
Status: open
6
7
-Sub-state: follow-up created from SOW-0013 close-out review.
7
+Sub-state: follow-up created from SOW-0013 close-out review; scope expanded by user on 2026-05-08 to cover both typed licensing and typed BGP table roots.
8
9
## Requirements
10
11
### Purpose
12
13
-Avoid repeated SNMP licensing table-root walks on Cisco and other devices that explicitly report unsupported licensing tables, without reintroducing broad missing-OID poisoning for ordinary metric tables or empty-but-valid tables.
13
+Avoid repeated typed SNMP table-root walks on Cisco and other devices that explicitly report unsupported typed licensing or typed BGP tables, without reintroducing broad missing-OID poisoning for ordinary metric tables or empty-but-valid tables.
14
15
### User Request
16
17
User accepted broad Cisco licensing coverage through dedicated mixins and agreed that nonexistent licensing table OID caching should be tracked as the solution for unsupported devices.
18
19
+User later accepted broad Catalyst BGP attachment and clarified that the unsupported table-root cache should apply to both typed licensing and typed BGP table roots.
20
+
21
### Assistant Understanding
22
23
Facts:
24
25
- `cisco.yaml` extends `_cisco-licensing-traditional.yaml` and `_cisco-licensing-smart.yaml`.
24
-- Those mixins add two licensing table roots: `clmgmtLicenseInfoTable` and `ciscoSlaEntitlementInfoTable`.
26
+- Those licensing mixins add two table roots: `clmgmtLicenseInfoTable` and `ciscoSlaEntitlementInfoTable`.
27
+- `cisco-catalyst.yaml` extends `_cisco-bgp4-mib.yaml` in the BGP typed projection SOW, so Catalyst devices that do not expose Cisco BGP tables can hit unsupported typed BGP table roots.
28
- SOW-0013 intentionally removed the old empty-walk table missing cache write because empty tables and unsupported tables must not be conflated.
26
-- Explicit `NoSuchObject` / `NoSuchInstance` table-root responses are still a reasonable candidate for a narrowly scoped licensing unsupported-table cache.
29
+- Explicit `NoSuchObject` / `NoSuchInstance` table-root responses are still a reasonable candidate for a narrowly scoped typed-row unsupported-table cache.
30
31
Inferences:
32
30
-- The follow-up should distinguish unsupported table roots from empty tables with zero rows.
31
-- The cache should be scoped to typed licensing table roots and must not suppress ordinary metric table OIDs.
33
+- The follow-up should distinguish unsupported typed table roots from empty typed tables with zero rows.
34
+- The cache should be scoped to typed licensing and typed BGP table roots and must not suppress ordinary metric table OIDs.
35
36
Unknowns:
37
@@ -36,10 +39,10 @@ Unknowns:
39
40
### Acceptance Criteria
41
39
-- Typed licensing table walks that receive explicit no-such table-root responses are skipped on later collection cycles.
42
+- Typed licensing and typed BGP table walks that receive explicit no-such table-root responses are skipped on later collection cycles.
43
- Empty valid tables with zero rows are not cached as unsupported.
44
- Ordinary metrics and topology table collection are unaffected.
42
-- Tests cover unsupported table root, empty table, and ordinary metric table behavior.
45
+- Tests cover unsupported typed licensing table root, unsupported typed BGP table root, empty table, and ordinary metric table behavior.
46
47
## Analysis
48
@@ -56,7 +59,7 @@ Current state:
59
60
- Missing scalar OIDs are cached exactly.
61
- Licensing table roots with no returned PDUs are not cached, by design, because zero rows may mean an empty valid table.
59
-- Broad Cisco licensing can therefore continue walking unsupported licensing table roots each cycle until this follow-up is implemented.
62
+- Broad Cisco licensing and broad Catalyst BGP can therefore continue walking unsupported typed table roots each cycle until this follow-up is implemented.
63
64
Risks:
65
@@ -69,7 +72,7 @@ Status: blocked
72
73
Problem / root-cause model:
74
72
-- Broad Cisco licensing coverage intentionally trades initial probe breadth for profile simplicity. Unsupported table roots need a precise negative-cache path keyed to explicit no-such table-root responses, not to empty walk results.
75
+- Broad Cisco licensing and Catalyst BGP coverage intentionally trade initial probe breadth for profile simplicity. Unsupported table roots need a precise negative-cache path keyed to explicit no-such table-root responses, not to empty walk results.
76
77
Evidence reviewed:
78
@@ -79,9 +82,10 @@ Evidence reviewed:
82
83
Affected contracts and surfaces:
84
82
-- ddsnmpcollector licensing table collection.
85
+- ddsnmpcollector typed licensing table collection.
86
+- ddsnmpcollector typed BGP table collection.
87
- Collection stats for missing OIDs and table walks.
84
-- Cisco SNMP collection overhead on unsupported devices.
88
+- Cisco SNMP collection overhead on unsupported licensing and BGP devices.
89
90
Existing patterns to reuse:
91
@@ -102,7 +106,7 @@ Sensitive data handling plan:
106
Implementation plan:
107
108
1. Model explicit table-root no-such responses in tests.
105
-2. Add a licensing-scoped unsupported table-root cache or equivalent filter.
109
+2. Add a typed-row unsupported table-root cache or equivalent filter for licensing and BGP table roots.
110
3. Prove empty valid tables are still retried.
111
112
Validation plan:
@@ -131,11 +135,12 @@ Open decisions:
135
## Implications And Decisions
136
137
- Follow-up created to satisfy SOW-0013 follow-up discipline; no implementation is part of SOW-0013.
138
+- Scope expanded on 2026-05-08: the same unsupported table-root handling must apply to typed licensing and typed BGP table roots.
139
140
## Plan
141
137
-1. Add tests for unsupported licensing table-root no-such handling.
138
-2. Implement narrowly scoped cache/filter behavior.
142
+1. Add tests for unsupported typed licensing and typed BGP table-root no-such handling.
143
+2. Implement narrowly scoped typed-row cache/filter behavior.
144
3. Validate narrow and shared SNMP suites.
145
146
## Execution Log
.agents/sow/specs/snmp-profile-projection.md
+218
-6
@@ -3,9 +3,9 @@
3
## Purpose
4
5
SNMP profiles are one catalog with explicit projections for their consumers.
6
-Regular SNMP metric collection, SNMP topology, and SNMP licensing use the same
7
-profile loading, matching, inheritance, metadata, and tag machinery, but they
8
-consume different profile views.
6
+Regular SNMP metric collection, SNMP topology, SNMP licensing, and SNMP BGP
7
+monitoring use the same profile loading, matching, inheritance, metadata, and
8
+tag machinery, but they consume different profile views.
9
10
## Consumers
11
@@ -14,6 +14,7 @@ The supported profile consumers are:
14
- `metrics` - regular SNMP charted metrics and virtual metrics.
15
- `topology` - SNMP topology observations.
16
- `licensing` - typed SNMP network-device license rows.
17
+- `bgp` - typed SNMP BGP device, peer, and peer-family rows.
18
19
Profile metadata fields and top-level `metric_tags` default to all consumers.
20
They may narrow their visibility with:
@@ -22,6 +23,7 @@ They may narrow their visibility with:
23
consumers: [metrics]
24
consumers: [topology]
25
consumers: [licensing]
26
+consumers: [bgp]
27
```
28
29
Metadata resource `id_tags` do not carry `consumers` today. They inherit the
@@ -37,6 +39,9 @@ Topology rows live under top-level `topology:` and must declare a closed
39
Licensing rows live under top-level `licensing:` and emit typed license rows,
40
not chart metrics and not hidden underscore-prefixed metrics.
41
42
+BGP rows live under top-level `bgp:` and emit typed BGP rows, not chart metrics
43
+and not hidden underscore-prefixed metrics.
44
+
45
## Topology Rows
46
47
Topology rows reuse the regular `MetricsConfig` scalar/table shape:
@@ -143,6 +148,137 @@ Timer signals may declare exactly one of the shorthand timer value,
148
`timestamp`, or `remaining`. Sentinel policies are closed names and are
149
evaluated by the typed licensing producer before runtime consumers see the row.
150
151
+## BGP Rows
152
+
153
+BGP rows are row-centric because one BGP row can aggregate identity,
154
+descriptors, connection state, counters, timers, route counts, limits, and
155
+error fields:
156
+
157
+```yaml
158
+bgp:
159
+ - id: std-peer
160
+ MIB: BGP4-MIB
161
+ kind: peer
162
+ table:
163
+ OID: 1.3.6.1.2.1.15.3
164
+ name: bgpPeerTable
165
+ identity:
166
+ neighbor:
167
+ symbol:
168
+ OID: 1.3.6.1.2.1.15.3.1.7
169
+ name: bgpPeerRemoteAddr
170
+ format: ip_address
171
+ remote_as:
172
+ symbol:
173
+ OID: 1.3.6.1.2.1.15.3.1.9
174
+ name: bgpPeerRemoteAs
175
+ format: uint32
176
+ state:
177
+ symbol:
178
+ OID: 1.3.6.1.2.1.15.3.1.2
179
+ name: bgpPeerState
180
+ mapping:
181
+ items:
182
+ 1: idle
183
+ 2: connect
184
+ 3: active
185
+ 4: opensent
186
+ 5: openconfirm
187
+ 6: established
188
+ connection:
189
+ established_uptime:
190
+ symbol:
191
+ OID: 1.3.6.1.2.1.15.3.1.16
192
+ name: bgpPeerFsmEstablishedTime
193
+```
194
+
195
+BGP row kinds are closed:
196
+
197
+- `device`
198
+- `peer`
199
+- `peer_family`
200
+
201
+Device rows are identity-free device summaries. Supported `device_counts`
202
+fields are `peers`, `ibgp_peers`, `ebgp_peers`, and per-state fields under
203
+`states`. `ibgp_peers` and `ebgp_peers` map to the public
204
+`bgp.devices.peer_counts` dimensions `ibgp` and `ebgp`; `peers` maps to
205
+`configured`.
206
+
207
+Peer rows must declare stable `neighbor` and `remote_as` identity fields.
208
+Peer-family rows must additionally declare canonical `address_family` and
209
+`subsequent_address_family` identity fields.
210
+
211
+The BGP state mapping is a closed RFC 4271 state contract. Rows that declare
212
+state must map all six states by default:
213
+
214
+- `idle`
215
+- `connect`
216
+- `active`
217
+- `opensent`
218
+- `openconfirm`
219
+- `established`
220
+
221
+Partial state coverage is allowed only when the row explicitly declares
222
+`partial: true`. That escape hatch records that the profile author knowingly
223
+accepts a partial source MIB. `partial_states: [...]` records the canonical
224
+states that the source can represent.
225
+
226
+AFI/SAFI values are normalized to closed canonical strings at profile load.
227
+Known address families include `ipv4`, `ipv6`, and `l2vpn`. Known subsequent
228
+address families include `unicast`, `multicast`, `labeled_unicast`, `mvpn`,
229
+`vpls`, `evpn`, and `vpn`. Vendor-private values require an explicit
230
+`allow_private: true` on that identity value.
231
+
232
+BGP keeps normal ddsnmp table-row chart behavior. A typed BGP table row can
233
+still produce one chart per row; the typed projection controls domain identity,
234
+validation, function output, and old-protocol deletion, not a separate
235
+BGP-specific chart cap.
236
+
237
+BGP value fields can read from the current row, from row indexes, from literal
238
+values, or from a related table in the same resolved profile. Row-index value
239
+sources can use `index: N`, `index_transform:`, or `index_from_end: N`.
240
+`index_from_end` selects one OID index component from the tail and is for MIBs
241
+where the wanted trailing INDEX component follows a variable-length component
242
+such as `InetAddress`. Profiles should declare only one row-index selector per
243
+typed BGP value. Validation rejects configs that set more than one of
244
+`index`, `index_from_end`, and `index_transform` on the same typed BGP value.
245
+
246
+A cross-table typed value uses `table: <table_name>` with `index_transform:`
247
+to derive the referenced row index:
248
+
249
+```yaml
250
+identity:
251
+ remote_as:
252
+ table: vendorBgpPeerTable
253
+ index_transform:
254
+ - start: 0
255
+ drop_right: 2
256
+ symbol:
257
+ OID: 1.3.6.1.4.1.example.2.1
258
+ name: vendorBgpPeerRemoteAs
259
+```
260
+
261
+If the referenced table is keyed differently, a typed BGP value may also use
262
+`lookup_symbol:`. The collector first derives a lookup value from the current
263
+row index with `index_transform:`, scans the referenced table for a row whose
264
+`lookup_symbol` column has that value, and then reads the requested typed
265
+`symbol` from the matched row. This is required for MIBs such as Juniper
266
+BGP4-V2 where peer-family counters are indexed by peer ID, AFI, and SAFI while
267
+peer identity is stored in a peer table keyed by routing-instance/local/remote
268
+address components.
269
+
270
+Cross-table typed values are internal BGP row sources. They do not require
271
+synthetic metric labels and must not reintroduce underscore-prefixed side
272
+protocols. Scalar BGP rows cannot use `table:` sources.
273
+
274
+When a peer or peer-family row does not expose a routing instance, public chart
275
+and function labels normalize the empty value to `default`.
276
+
277
+BGP function cache freshness is tracked per typed source profile. A failed BGP
278
+source preserves its stale rows during the bounded stale window without
279
+blocking other BGP sources from refreshing. After the stale window expires, the
280
+function omits expired stale rows and returns 503 when no usable rows remain.
281
+
282
## Resolve And Projection
283
284
`ddsnmp.Catalog.Resolve()` resolves profiles by `sysObjectID`, `sysDescr`, and
@@ -171,18 +307,32 @@ semantics. The topology collector uses manual-profile augment semantics.
307
- drops `virtual_metrics`;
308
- filters metadata and top-level metric tags by `consumers`.
309
310
+`ResolvedProfileSet.Project(bgp)` returns the BGP view:
311
+
312
+- keeps `bgp`;
313
+- drops regular `metrics`;
314
+- drops `topology`;
315
+- drops `licensing`;
316
+- drops `virtual_metrics`;
317
+- filters metadata and top-level metric tags by `consumers`.
318
+
319
The regular SNMP collector uses `Project(metrics, licensing)` so one SNMP pass
320
can produce charted metrics and typed license rows. Single-consumer projections
321
remain pure.
322
323
+The regular SNMP collector can use `Project(metrics, licensing, bgp)` when BGP
324
+typed rows are produced alongside ordinary charted metrics and licensing rows.
325
+Single-consumer projections remain pure.
326
+
327
`ProjectedView.FilterByKind()` is a topology view filter. VLAN-context topology
328
uses it with the VLAN-scopable kind set instead of hardcoded topology mixin
329
filenames.
330
331
## Inheritance And Merge Rules
332
184
-Profile inheritance must merge `topology:` and `licensing:` rows in addition to
185
-`metrics:`, `virtual_metrics`, metadata, global metric tags, and static tags.
333
+Profile inheritance must merge `topology:`, `licensing:`, and `bgp:` rows in
334
+addition to `metrics:`, `virtual_metrics`, metadata, global metric tags, and
335
+static tags.
336
337
Topology row identity is:
338
@@ -227,6 +377,37 @@ Derived `licensing:` rows with the same merge identity replace inherited rows.
377
This keeps intentional `extends:` overrides valid while duplicate signal kinds
378
inside one resolved profile remain load errors.
379
380
+Runtime BGP row structural identity is:
381
+
382
+```text
383
+origin profile id + row kind + typed config id + table OID + row index
384
+origin profile id + row kind + scalar signal OID
385
+origin profile id + row kind + explicit scalar group id
386
+```
387
+
388
+The typed config id is part of table-row structural identity because a single
389
+SNMP table row can legitimately produce multiple typed BGP rows with different
390
+semantics, such as separate peer-family views over one operational table. The
391
+runtime key still length-prefixes every component, including an empty config id.
392
+
393
+Within that structural identity, display fields such as peer description,
394
+local address, and peer identifier never determine storage identity. During the
395
+legacy-to-typed BGP migration, typed structural identity owns typed row and
396
+function row identity; chart and alert identity remain bound to the existing
397
+public chart context and label contract until the legacy BGP path is removed.
398
+
399
+Profile inheritance merge identity is the pre-collection form of that identity:
400
+
401
+```text
402
+row kind + table OID + optional row id
403
+row kind + scalar signal OID
404
+row kind + explicit scalar group id
405
+```
406
+
407
+Derived `bgp:` rows with the same merge identity replace inherited rows. Repeated
408
+`(structural identity, signal kind)` entries are load errors unless a valid
409
+inheritance override handles them.
410
+
411
## Delivery
412
413
Regular metrics are emitted through `ProfileMetrics.Metrics`.
@@ -238,9 +419,17 @@ Licensing rows are emitted through `ProfileMetrics.LicenseRows` and carry
419
typed grouped fields for identity, descriptors, state, timers, usage, tags,
420
origin profile id, table OID, row key, and structural id.
421
422
+BGP rows are emitted through `ProfileMetrics.BGPRows` and carry typed grouped
423
+fields for row kind, identity, descriptors, admin/state, connection, traffic,
424
+transitions, timers, last-error, last-notification, reason, graceful-restart,
425
+route, route-limit, device-count, tags, origin profile id, table OID, row key,
426
+and structural id. BGP does not use a generic signal-kind map; each field has
427
+its own numeric, boolean, text, or state shape.
428
+
429
`ProfileMetrics.HiddenMetrics` remains a generic delivery container for
430
underscore-prefixed non-topology and non-licensing metrics. SNMP topology and
243
-SNMP licensing must not depend on hidden metrics.
431
+SNMP licensing must not depend on hidden metrics. SNMP BGP must also not depend
432
+on hidden metrics or underscore-prefixed tag protocols.
433
434
Licensing row counts are reported through `Stats.Metrics.Licensing`. Ordinary
435
`Stats.Metrics.Tables` and `Stats.Metrics.Rows` remain regular chart-metric
@@ -272,5 +461,28 @@ Profile validation rejects:
461
symbols;
462
- timer slots that set both timestamp-style and remaining-style values;
463
- unsupported licensing value formats;
464
+- unknown BGP row kinds;
465
+- BGP peer rows without neighbor or remote AS identity;
466
+- BGP peer-family rows without neighbor, remote AS, AFI, or SAFI identity;
467
+- BGP state mappings that do not cover all six RFC 4271 states unless explicit
468
+ partial coverage is declared;
469
+- unknown BGP peer-state, AFI, or SAFI values;
470
+- repeated BGP typed fields for the same structural identity;
471
+- device-count fields on non-device BGP rows;
472
+- route and route-limit fields on non-peer-family BGP rows;
473
+- BGP table `from` OIDs outside the row table;
474
+- BGP scalar values that use `table:` sources;
475
+- BGP scalar values that use row-index sources such as `index:`,
476
+ `index_from_end:`, or `index_transform:`;
477
+- BGP values that set more than one row-index selector (`index`,
478
+ `index_from_end`, or `index_transform`);
479
+- BGP cross-table value sources without a source OID;
480
+- BGP cross-table value sources whose source or lookup OID is outside the
481
+ referenced table when that referenced table is declared by another BGP row in
482
+ the resolved profile;
483
+- BGP `lookup_symbol` value sources without a referenced table;
484
+- underscore-prefixed BGP value names;
485
+- regular metric chart/export-only fields, transforms, scale factors, and
486
+ constant-value hacks on BGP row value symbols;
487
- invalid `consumers` values;
488
- virtual metrics whose sources resolve to topology rows.
.gitignore
+3
@@ -6,6 +6,9 @@ gcs-credentials.json
6
# AI agents that follow AGENTS.md treat this as the place to write run-time data.
7
/.local/
8
9
+# Local vendor MIB reference files used during SNMP profile audits.
10
+/mibs/
11
+
12
# Cross-tool AI agent private overrides (per-user, not for sharing)
13
**/AGENTS.local.md
14
docs/top-monitoring-netdata-functions.md
+1
@@ -21,6 +21,7 @@ Beyond their primary roles of collecting metrics, collectors can execute specifi
21
| Processes | Real-time information about the system's resource usage, including CPU utilization, memory consumption, and disk IO for every running process. | `top`, `htop` | yes | [apps](/src/collectors/apps.plugin/README.md) |
22
| Running-queries | Currently executing SQL statements. Supported databases: CockroachDB, Oracle DB, PostgreSQL, RethinkDB, YugabyteDB. | | yes | [go.d](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector#readme): cockroachdb, oracledb, postgres, rethinkdb, yugabytedb |
23
| SNMP-interfaces | Network interface traffic and status metrics from SNMP-enabled devices, including bandwidth, errors, discards, and operational status. | `snmpwalk` | no | [snmp](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector/snmp#readme) |
24
+| SNMP-bgp-peers | Current BGP peer and peer-family state from SNMP-enabled devices, including routing instance, neighbor identity, peer state, prefix gauges, and diagnostic details such as last error, previous state, graceful restart state, and unavailability reason. | `show bgp summary`, `snmpwalk` | no | [snmp](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector/snmp#readme) |
25
| Systemd-list-units | Information about all systemd units, including their active state, description, whether or not they are enabled, and more. | `systemctl list-units` | yes | [systemd-journal](https://github.com/netdata/netdata/tree/master/src/collectors/systemd-journal.plugin#readme) |
26
| Systemd-services | System resource utilization for all running systemd services: CPU, memory, and disk IO. | `systemd-cgtop` | no | [cgroups](https://github.com/netdata/netdata/tree/master/src/collectors/cgroups.plugin#readme) |
27
| Top-queries | Aggregated SQL query performance metrics. Supported databases: ClickHouse, CockroachDB, Couchbase, Elasticsearch, MongoDB, Microsoft SQL Server, MySQL, MariaDB, OpenSearch, Oracle DB, Percona MySQL, PostgreSQL, ProxySQL, Redis, YugabyteDB. | | yes | [go.d](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector#readme): clickhouse, cockroachdb, couchbase, elasticsearch, mongodb, mssql, mysql, oracledb, postgres, proxysql, redis, yugabytedb |
src/go/plugin/go.d/collector/snmp/bgp_chart_filter.go
new
+36
@@ -0,0 +1,36 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
6
+
7
+func filterChartMetrics(metrics []ddsnmp.Metric) []ddsnmp.Metric {
8
+ if len(metrics) == 0 {
9
+ return nil
10
+ }
11
+
12
+ filtered := make([]ddsnmp.Metric, 0, len(metrics))
13
+ for _, metric := range metrics {
14
+ if shouldHideBGPDiagnosticMetric(metric.Name) {
15
+ continue
16
+ }
17
+ filtered = append(filtered, metric)
18
+ }
19
+ return filtered
20
+}
21
+
22
+func shouldHideBGPDiagnosticMetric(name string) bool {
23
+ switch name {
24
+ case "bgp.peers.previous_connection_state",
25
+ "bgp.peers.last_error",
26
+ "bgp.peers.last_down_reason",
27
+ "bgp.peers.last_received_notification_reason",
28
+ "bgp.peers.last_sent_notification_reason",
29
+ "bgp.peer_families.last_error",
30
+ "bgp.peer_families.graceful_restart_state",
31
+ "bgp.peer_families.unavailability_reason":
32
+ return true
33
+ default:
34
+ return false
35
+ }
36
+}
src/go/plugin/go.d/collector/snmp/bgp_integration.go
new
+210
@@ -0,0 +1,210 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "errors"
7
+ "fmt"
8
+ "time"
9
+
10
+ "github.com/netdata/netdata/go/plugins/pkg/funcapi"
11
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
12
+)
13
+
14
+type bgpIntegration struct {
15
+ peerCache *bgpPeerCache
16
+ collectError error
17
+ collectFailedSources map[string]bool
18
+}
19
+
20
+func newBGPIntegration() *bgpIntegration {
21
+ return &bgpIntegration{
22
+ peerCache: newBGPPeerCache(),
23
+ }
24
+}
25
+
26
+func (c *Collector) enableBGPIntegration() {
27
+ if c.bgp == nil {
28
+ c.bgp = newBGPIntegration()
29
+ }
30
+ c.bgp.setStaleAfter(c.bgpStaleAfter())
31
+ if c.funcRouter != nil {
32
+ for _, h := range c.bgp.functionHandlers() {
33
+ c.funcRouter.registerHandler(h.methodID, h.handler)
34
+ }
35
+ }
36
+}
37
+
38
+func (c *Collector) markBGPCollectFailed(err error) {
39
+ if c.bgp == nil {
40
+ return
41
+ }
42
+ c.bgp.markCollectFailed(err)
43
+}
44
+
45
+func (b *bgpIntegration) setStaleAfter(d time.Duration) {
46
+ if b == nil || b.peerCache == nil {
47
+ return
48
+ }
49
+ b.peerCache.setStaleAfter(d)
50
+}
51
+
52
+func (b *bgpIntegration) markCollectFailed(err error) {
53
+ if b == nil || b.peerCache == nil {
54
+ return
55
+ }
56
+ b.peerCache.markCollectFailed(err)
57
+}
58
+
59
+func (c *Collector) prepareProfileMetrics(pms []*ddsnmp.ProfileMetrics) []ddsnmp.Metric {
60
+ if c.bgp == nil {
61
+ return flattenProfileMetrics(pms)
62
+ }
63
+ return c.bgp.prepareProfileMetrics(pms)
64
+}
65
+
66
+func (b *bgpIntegration) prepareProfileMetrics(pms []*ddsnmp.ProfileMetrics) []ddsnmp.Metric {
67
+ b.collectError, b.collectFailedSources = bgpCollectErrorsBySource(pms)
68
+
69
+ metrics := flattenProfileMetrics(pms)
70
+ b.peerCache.resetExceptSources(b.collectFailedSources)
71
+ for _, pm := range pms {
72
+ if pm == nil || pm.BGPCollectError != nil {
73
+ continue
74
+ }
75
+ for _, row := range pm.BGPRows {
76
+ b.peerCache.updateRow(pm.Source, row)
77
+ }
78
+ }
79
+
80
+ metrics = append(metrics, typedBGPMetricsFromProfileMetrics(successfulBGPProfileMetrics(pms))...)
81
+
82
+ return filterChartMetrics(metrics)
83
+}
84
+
85
+func (c *Collector) finalizeProfileMetrics() {
86
+ if c.bgp == nil {
87
+ return
88
+ }
89
+ c.bgp.finalizeProfileMetrics()
90
+}
91
+
92
+func (b *bgpIntegration) finalizeProfileMetrics() {
93
+ b.peerCache.finalize()
94
+ if b.collectError != nil {
95
+ b.peerCache.markSourcesCollectFailed(b.collectFailedSources, b.collectError)
96
+ }
97
+ b.collectError = nil
98
+ b.collectFailedSources = nil
99
+}
100
+
101
+func bgpCollectErrorsBySource(pms []*ddsnmp.ProfileMetrics) (error, map[string]bool) {
102
+ var errs []error
103
+ sources := make(map[string]bool)
104
+ for _, pm := range pms {
105
+ if pm == nil || pm.BGPCollectError == nil {
106
+ continue
107
+ }
108
+ if pm.Source == "" {
109
+ errs = append(errs, pm.BGPCollectError)
110
+ continue
111
+ }
112
+ sources[pm.Source] = true
113
+ errs = append(errs, fmt.Errorf("%s: %w", pm.Source, pm.BGPCollectError))
114
+ }
115
+ if len(sources) == 0 {
116
+ sources = nil
117
+ }
118
+ return errors.Join(errs...), sources
119
+}
120
+
121
+func successfulBGPProfileMetrics(pms []*ddsnmp.ProfileMetrics) []*ddsnmp.ProfileMetrics {
122
+ result := make([]*ddsnmp.ProfileMetrics, 0, len(pms))
123
+ for _, pm := range pms {
124
+ if pm == nil || pm.BGPCollectError != nil {
125
+ continue
126
+ }
127
+ result = append(result, pm)
128
+ }
129
+ return result
130
+}
131
+
132
+func flattenProfileMetrics(pms []*ddsnmp.ProfileMetrics) []ddsnmp.Metric {
133
+ var metrics []ddsnmp.Metric
134
+ for _, pm := range pms {
135
+ for _, metric := range pm.Metrics {
136
+ if metric.Profile == nil {
137
+ metric.Profile = pm
138
+ }
139
+ metrics = append(metrics, metric)
140
+ }
141
+ }
142
+ return metrics
143
+}
144
+
145
+func (c *Collector) additionalFuncHandlers() []registeredSNMPFunction {
146
+ if c.bgp == nil {
147
+ return nil
148
+ }
149
+ return c.bgp.functionHandlers()
150
+}
151
+
152
+func (b *bgpIntegration) functionHandlers() []registeredSNMPFunction {
153
+ if b.peerCache == nil {
154
+ return nil
155
+ }
156
+ return []registeredSNMPFunction{{
157
+ methodID: bgpPeersMethodID,
158
+ handler: newFuncBGPPeers(b.peerCache),
159
+ }}
160
+}
161
+
162
+func collectorSpecificFunctionHandlers() []registeredSNMPFunction {
163
+ return []registeredSNMPFunction{{
164
+ methodID: bgpPeersMethodID,
165
+ handler: newFuncBGPPeers(nil),
166
+ }}
167
+}
168
+
169
+func collectorSpecificMethodConfigs() []funcapi.MethodConfig {
170
+ return []funcapi.MethodConfig{
171
+ bgpPeersMethodConfig(),
172
+ }
173
+}
174
+
175
+func profilesHaveBGP(profiles []*ddsnmp.Profile) bool {
176
+ for _, prof := range profiles {
177
+ if profileHasBGP(prof) {
178
+ return true
179
+ }
180
+ }
181
+ return false
182
+}
183
+
184
+func profileHasBGP(prof *ddsnmp.Profile) bool {
185
+ if prof == nil || prof.Definition == nil {
186
+ return false
187
+ }
188
+ def := prof.Definition
189
+ if len(def.BGP) > 0 {
190
+ return true
191
+ }
192
+ return false
193
+}
194
+
195
+func profileMetricsHaveBGP(pms []*ddsnmp.ProfileMetrics) bool {
196
+ for _, pm := range pms {
197
+ if pm.BGPCollectError != nil || len(pm.BGPRows) > 0 {
198
+ return true
199
+ }
200
+ }
201
+ return false
202
+}
203
+
204
+func (c *Collector) bgpStaleAfter() time.Duration {
205
+ updateEvery := c.UpdateEvery
206
+ if updateEvery <= 0 {
207
+ updateEvery = 10
208
+ }
209
+ return time.Duration(updateEvery*3) * time.Second
210
+}
src/go/plugin/go.d/collector/snmp/bgp_metric_specs.go
new
+148
@@ -0,0 +1,148 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
6
+
7
+type bgpScope string
8
+
9
+const (
10
+ bgpScopePeers bgpScope = "peers"
11
+ bgpScopePeerFamilies bgpScope = "peer_families"
12
+ bgpScopeDevices bgpScope = "devices"
13
+)
14
+
15
+type bgpMetricSpecDef struct {
16
+ description string
17
+ family string
18
+ unit string
19
+ chartType string
20
+ metricType ddprofiledefinition.ProfileMetricType
21
+ isTable bool
22
+}
23
+
24
+func buildBGPMetricName(scope bgpScope, leaf string) string {
25
+ switch scope {
26
+ case bgpScopePeerFamilies:
27
+ return "bgp.peer_families." + leaf
28
+ case bgpScopeDevices:
29
+ return "bgp.devices." + leaf
30
+ default:
31
+ return "bgp.peers." + leaf
32
+ }
33
+}
34
+
35
+func bgpMetricSpec(name string) (bgpMetricSpecDef, bool) {
36
+ switch name {
37
+ case "bgp.devices.peer_counts":
38
+ return bgpMetricSpecDef{
39
+ description: "BGP device peer counts",
40
+ family: "BGP/Devices",
41
+ unit: "peers",
42
+ metricType: ddprofiledefinition.ProfileMetricTypeGauge,
43
+ }, true
44
+ case "bgp.devices.peer_states":
45
+ return bgpMetricSpecDef{
46
+ description: "BGP device peer states",
47
+ family: "BGP/Devices",
48
+ unit: "peers",
49
+ metricType: ddprofiledefinition.ProfileMetricTypeGauge,
50
+ }, true
51
+ case "bgp.peers.availability":
52
+ return tableSpec("BGP peer availability", "BGP/Peers", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
53
+ case "bgp.peers.connection_state":
54
+ return tableSpec("BGP peer connection state", "BGP/Peers", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
55
+ case "bgp.peers.previous_connection_state":
56
+ return tableSpec("BGP peer previous connection state", "BGP/Peers", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
57
+ case "bgp.peers.established_uptime":
58
+ return tableSpec("BGP peer established uptime", "BGP/Peers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
59
+ case "bgp.peers.update_traffic":
60
+ return tableSpec("BGP peer update traffic", "BGP/Peers/Traffic", "updates/s", ddprofiledefinition.ProfileMetricTypeRate), true
61
+ case "bgp.peers.message_traffic":
62
+ return tableSpec("BGP peer message traffic", "BGP/Peers/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
63
+ case "bgp.peers.notification_traffic":
64
+ return tableSpec("BGP peer notification traffic", "BGP/Peers/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
65
+ case "bgp.peers.route_refresh_traffic":
66
+ return tableSpec("BGP peer route refresh traffic", "BGP/Peers/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
67
+ case "bgp.peers.open_traffic":
68
+ return tableSpec("BGP peer open traffic", "BGP/Peers/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
69
+ case "bgp.peers.keepalive_traffic":
70
+ return tableSpec("BGP peer keepalive traffic", "BGP/Peers/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
71
+ case "bgp.peers.established_transitions":
72
+ return tableSpec("BGP peer established transitions", "BGP/Peers/Events", "transitions/s", ddprofiledefinition.ProfileMetricTypeRate), true
73
+ case "bgp.peers.down_transitions":
74
+ return tableSpec("BGP peer down transitions", "BGP/Peers/Events", "transitions", ddprofiledefinition.ProfileMetricTypeMonotonicCount), true
75
+ case "bgp.peers.up_transitions":
76
+ return tableSpec("BGP peer up transitions", "BGP/Peers/Events", "transitions", ddprofiledefinition.ProfileMetricTypeMonotonicCount), true
77
+ case "bgp.peers.flaps":
78
+ return tableSpec("BGP peer flaps", "BGP/Peers/Events", "flaps", ddprofiledefinition.ProfileMetricTypeMonotonicCount), true
79
+ case "bgp.peers.last_received_update_age":
80
+ return tableSpec("BGP peer time since last update", "BGP/Peers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
81
+ case "bgp.peers.last_error":
82
+ return tableSpec("BGP peer last error", "BGP/Peers/Errors", "code", ddprofiledefinition.ProfileMetricTypeGauge), true
83
+ case "bgp.peers.last_down_reason":
84
+ return tableSpec("BGP peer last down reason", "BGP/Peers/Errors", "reason", ddprofiledefinition.ProfileMetricTypeGauge), true
85
+ case "bgp.peers.last_received_notification_reason":
86
+ return tableSpec("BGP peer last received notification reason", "BGP/Peers/Errors", "reason", ddprofiledefinition.ProfileMetricTypeGauge), true
87
+ case "bgp.peers.last_sent_notification_reason":
88
+ return tableSpec("BGP peer last sent notification reason", "BGP/Peers/Errors", "reason", ddprofiledefinition.ProfileMetricTypeGauge), true
89
+ case "bgp.peers.negotiated_timers":
90
+ return tableSpec("BGP peer negotiated timers", "BGP/Peers/Timers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
91
+ case "bgp.peers.configured_timers":
92
+ return tableSpec("BGP peer configured timers", "BGP/Peers/Timers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
93
+ case "bgp.peer_families.availability":
94
+ return tableSpec("BGP peer-family availability", "BGP/Peer Families", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
95
+ case "bgp.peer_families.connection_state":
96
+ return tableSpec("BGP peer-family connection state", "BGP/Peer Families", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
97
+ case "bgp.peer_families.established_uptime":
98
+ return tableSpec("BGP peer-family established uptime", "BGP/Peer Families", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
99
+ case "bgp.peer_families.update_traffic":
100
+ return tableSpec("BGP peer-family update traffic", "BGP/Peer Families/Traffic", "updates/s", ddprofiledefinition.ProfileMetricTypeRate), true
101
+ case "bgp.peer_families.message_traffic":
102
+ return tableSpec("BGP peer-family message traffic", "BGP/Peer Families/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
103
+ case "bgp.peer_families.notification_traffic":
104
+ return tableSpec("BGP peer-family notification traffic", "BGP/Peer Families/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
105
+ case "bgp.peer_families.route_refresh_traffic":
106
+ return tableSpec("BGP peer-family route refresh traffic", "BGP/Peer Families/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
107
+ case "bgp.peer_families.open_traffic":
108
+ return tableSpec("BGP peer-family open traffic", "BGP/Peer Families/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
109
+ case "bgp.peer_families.keepalive_traffic":
110
+ return tableSpec("BGP peer-family keepalive traffic", "BGP/Peer Families/Traffic", "messages/s", ddprofiledefinition.ProfileMetricTypeRate), true
111
+ case "bgp.peer_families.established_transitions":
112
+ return tableSpec("BGP peer-family established transitions", "BGP/Peer Families/Events", "transitions/s", ddprofiledefinition.ProfileMetricTypeRate), true
113
+ case "bgp.peer_families.down_transitions":
114
+ return tableSpec("BGP peer-family down transitions", "BGP/Peer Families/Events", "transitions", ddprofiledefinition.ProfileMetricTypeMonotonicCount), true
115
+ case "bgp.peer_families.last_received_update_age":
116
+ return tableSpec("BGP peer-family time since last update", "BGP/Peer Families", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
117
+ case "bgp.peer_families.last_error":
118
+ return tableSpec("BGP peer-family last error", "BGP/Peer Families/Errors", "code", ddprofiledefinition.ProfileMetricTypeGauge), true
119
+ case "bgp.peer_families.graceful_restart_state":
120
+ return tableSpec("BGP peer-family graceful restart state", "BGP/Peer Families", "status", ddprofiledefinition.ProfileMetricTypeGauge), true
121
+ case "bgp.peer_families.unavailability_reason":
122
+ return tableSpec("BGP peer-family unavailability reason", "BGP/Peer Families/Errors", "reason", ddprofiledefinition.ProfileMetricTypeGauge), true
123
+ case "bgp.peer_families.negotiated_timers":
124
+ return tableSpec("BGP peer-family negotiated timers", "BGP/Peer Families/Timers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
125
+ case "bgp.peer_families.configured_timers":
126
+ return tableSpec("BGP peer-family configured timers", "BGP/Peer Families/Timers", "seconds", ddprofiledefinition.ProfileMetricTypeGauge), true
127
+ case "bgp.peer_families.route_counts.current":
128
+ return tableSpec("BGP peer-family current route counts", "BGP/Peer Families/Routes", "prefixes", ddprofiledefinition.ProfileMetricTypeGauge), true
129
+ case "bgp.peer_families.route_totals":
130
+ return tableSpec("BGP peer-family route totals", "BGP/Peer Families/Routes", "prefixes", ddprofiledefinition.ProfileMetricTypeMonotonicCount), true
131
+ case "bgp.peer_families.route_limits":
132
+ return tableSpec("BGP peer-family route limits", "BGP/Peer Families/Routes", "prefixes", ddprofiledefinition.ProfileMetricTypeGauge), true
133
+ case "bgp.peer_families.route_limit_thresholds":
134
+ return tableSpec("BGP peer-family route limit thresholds", "BGP/Peer Families/Routes", "%", ddprofiledefinition.ProfileMetricTypeGauge), true
135
+ default:
136
+ return bgpMetricSpecDef{}, false
137
+ }
138
+}
139
+
140
+func tableSpec(description, family, unit string, metricType ddprofiledefinition.ProfileMetricType) bgpMetricSpecDef {
141
+ return bgpMetricSpecDef{
142
+ description: description,
143
+ family: family,
144
+ unit: unit,
145
+ metricType: metricType,
146
+ isTable: true,
147
+ }
148
+}
src/go/plugin/go.d/collector/snmp/bgp_typed_metrics.go
new
+327
@@ -0,0 +1,327 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "maps"
7
+
8
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
9
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
10
+)
11
+
12
+type bgpTypedMetricBuilder struct {
13
+ metrics []ddsnmp.Metric
14
+ explicitDevicePeerCounts map[string]int64
15
+ autoDevicePeerCounts map[string]int64
16
+ devicePeerStates map[string]int64
17
+ deviceSample *ddsnmp.ProfileMetrics
18
+}
19
+
20
+func typedBGPMetricsFromProfileMetrics(pms []*ddsnmp.ProfileMetrics) []ddsnmp.Metric {
21
+ builder := &bgpTypedMetricBuilder{}
22
+ for _, pm := range pms {
23
+ for _, row := range pm.BGPRows {
24
+ builder.addRow(pm, row)
25
+ }
26
+ }
27
+ return builder.metricsWithDeviceSummaries()
28
+}
29
+
30
+func (b *bgpTypedMetricBuilder) addRow(pm *ddsnmp.ProfileMetrics, row ddsnmp.BGPRow) {
31
+ if b.deviceSample == nil {
32
+ b.deviceSample = pm
33
+ }
34
+
35
+ switch row.Kind {
36
+ case ddprofiledefinition.BGPRowKindDevice:
37
+ b.addDeviceRow(pm, row)
38
+ case ddprofiledefinition.BGPRowKindPeer:
39
+ b.addPeerLikeRow(pm, row, bgpScopePeers)
40
+ b.addPeerDeviceSummary(row)
41
+ case ddprofiledefinition.BGPRowKindPeerFamily:
42
+ b.addPeerLikeRow(pm, row, bgpScopePeerFamilies)
43
+ }
44
+}
45
+
46
+func (b *bgpTypedMetricBuilder) addDeviceRow(_ *ddsnmp.ProfileMetrics, row ddsnmp.BGPRow) {
47
+ if row.Device.Peers.Has {
48
+ b.addExplicitDevicePeerCount("configured", row.Device.Peers.Value)
49
+ }
50
+ if row.Device.InternalPeers.Has {
51
+ b.addExplicitDevicePeerCount("ibgp", row.Device.InternalPeers.Value)
52
+ }
53
+ if row.Device.ExternalPeers.Has {
54
+ b.addExplicitDevicePeerCount("ebgp", row.Device.ExternalPeers.Value)
55
+ }
56
+ if row.Device.ByStateHas {
57
+ for state, value := range row.Device.ByState {
58
+ b.addDevicePeerState(string(state), value)
59
+ }
60
+ }
61
+}
62
+
63
+func (b *bgpTypedMetricBuilder) addPeerDeviceSummary(row ddsnmp.BGPRow) {
64
+ b.addAutoDevicePeerCount("configured", 1)
65
+ if row.Admin.Enabled.Has && row.Admin.Enabled.Value {
66
+ b.addAutoDevicePeerCount("admin_enabled", 1)
67
+ }
68
+ if row.State.Has {
69
+ b.addDevicePeerState(string(row.State.State), 1)
70
+ if row.State.State == ddprofiledefinition.BGPPeerStateEstablished {
71
+ b.addAutoDevicePeerCount("established", 1)
72
+ }
73
+ }
74
+}
75
+
76
+func (b *bgpTypedMetricBuilder) addPeerLikeRow(pm *ddsnmp.ProfileMetrics, row ddsnmp.BGPRow, scope bgpScope) {
77
+ prefix := buildBGPMetricName(scope, "")
78
+ tags := bgpTypedMetricTags(row, scope)
79
+
80
+ availability := make(map[string]int64)
81
+ if row.Admin.Enabled.Has {
82
+ if row.Admin.Enabled.Value {
83
+ availability["admin_enabled"] = 1
84
+ } else {
85
+ availability["admin_enabled"] = 0
86
+ availability["admin_disabled"] = 1
87
+ }
88
+ }
89
+ if row.State.Has {
90
+ if row.State.State == ddprofiledefinition.BGPPeerStateEstablished {
91
+ availability["established"] = 1
92
+ } else {
93
+ availability["established"] = 0
94
+ }
95
+ }
96
+ b.addMetric(pm, prefix+"availability", availability, tags)
97
+ if row.State.Has {
98
+ b.addMetric(pm, prefix+"connection_state", map[string]int64{string(row.State.State): 1}, tags)
99
+ }
100
+ if row.Previous.Has {
101
+ b.addMetric(pm, prefix+"previous_connection_state", map[string]int64{string(row.Previous.State): 1}, tags)
102
+ }
103
+ b.addIntMetric(pm, prefix+"established_uptime", "uptime", row.Connection.EstablishedUptime, tags)
104
+ b.addDirectionalMetric(pm, prefix+"update_traffic", row.Traffic.Updates, tags)
105
+ b.addDirectionalMetric(pm, prefix+"message_traffic", row.Traffic.Messages, tags)
106
+ b.addDirectionalMetric(pm, prefix+"notification_traffic", row.Traffic.Notifications, tags)
107
+ b.addDirectionalMetric(pm, prefix+"route_refresh_traffic", row.Traffic.RouteRefreshes, tags)
108
+ b.addDirectionalMetric(pm, prefix+"open_traffic", row.Traffic.Opens, tags)
109
+ b.addDirectionalMetric(pm, prefix+"keepalive_traffic", row.Traffic.Keepalives, tags)
110
+ b.addIntMetric(pm, prefix+"established_transitions", "transitions", row.Transitions.Established, tags)
111
+ b.addIntMetric(pm, prefix+"down_transitions", "transitions", row.Transitions.Down, tags)
112
+ b.addIntMetric(pm, prefix+"up_transitions", "transitions", row.Transitions.Up, tags)
113
+ b.addIntMetric(pm, prefix+"flaps", "flaps", row.Transitions.Flaps, tags)
114
+ b.addIntMetric(pm, prefix+"last_received_update_age", "age", row.Connection.LastReceivedUpdateAge, tags)
115
+ b.addLastErrorMetric(pm, prefix+"last_error", row.LastError, tags)
116
+ b.addTextStateMetric(pm, prefix+"last_down_reason", row.Reasons.LastDown, tags)
117
+ b.addTextStateMetric(pm, prefix+"last_received_notification_reason", row.LastNotify.Received.Reason, tags)
118
+ b.addTextStateMetric(pm, prefix+"last_sent_notification_reason", row.LastNotify.Sent.Reason, tags)
119
+ b.addTimerPairMetric(pm, prefix+"negotiated_timers", row.Timers.Negotiated, tags)
120
+ b.addTimerPairMetric(pm, prefix+"configured_timers", row.Timers.Configured, tags)
121
+
122
+ if scope == bgpScopePeerFamilies {
123
+ b.addTextStateMetric(pm, prefix+"graceful_restart_state", row.Restart.State, tags)
124
+ b.addTextStateMetric(pm, prefix+"unavailability_reason", row.Reasons.Unavailability, tags)
125
+ b.addRouteCountersMetric(pm, prefix+"route_counts.current", row.Routes.Current, tags)
126
+ b.addRouteCountersMetric(pm, prefix+"route_totals", row.Routes.Total, tags)
127
+ b.addRouteLimitsMetric(pm, prefix+"route_limits", row.RouteLimits, tags)
128
+ b.addRouteLimitThresholdsMetric(pm, prefix+"route_limit_thresholds", row.RouteLimits, tags)
129
+ }
130
+}
131
+
132
+func (b *bgpTypedMetricBuilder) addMetric(pm *ddsnmp.ProfileMetrics, name string, mv map[string]int64, tags map[string]string) {
133
+ if len(mv) == 0 {
134
+ return
135
+ }
136
+ spec, ok := bgpMetricSpec(name)
137
+ if !ok {
138
+ return
139
+ }
140
+ metric := ddsnmp.Metric{
141
+ Profile: pm,
142
+ Name: name,
143
+ Description: spec.description,
144
+ Family: spec.family,
145
+ Unit: spec.unit,
146
+ ChartType: spec.chartType,
147
+ MetricType: spec.metricType,
148
+ IsTable: spec.isTable,
149
+ MultiValue: maps.Clone(mv),
150
+ }
151
+ if len(tags) > 0 {
152
+ metric.Tags = maps.Clone(tags)
153
+ }
154
+ b.metrics = append(b.metrics, metric)
155
+}
156
+
157
+func (b *bgpTypedMetricBuilder) addIntMetric(pm *ddsnmp.ProfileMetrics, name, dim string, value ddsnmp.BGPInt64, tags map[string]string) {
158
+ if value.Has {
159
+ b.addMetric(pm, name, map[string]int64{dim: value.Value}, tags)
160
+ }
161
+}
162
+
163
+func (b *bgpTypedMetricBuilder) addDirectionalMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPDirectional, tags map[string]string) {
164
+ mv := make(map[string]int64)
165
+ if value.Received.Has {
166
+ mv["received"] = value.Received.Value
167
+ }
168
+ if value.Sent.Has {
169
+ mv["sent"] = value.Sent.Value
170
+ }
171
+ b.addMetric(pm, name, mv, tags)
172
+}
173
+
174
+func (b *bgpTypedMetricBuilder) addLastErrorMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPLastError, tags map[string]string) {
175
+ mv := make(map[string]int64)
176
+ if value.Code.Has {
177
+ mv["code"] = value.Code.Value
178
+ }
179
+ if value.Subcode.Has {
180
+ mv["subcode"] = value.Subcode.Value
181
+ }
182
+ b.addMetric(pm, name, mv, tags)
183
+}
184
+
185
+func (b *bgpTypedMetricBuilder) addTextStateMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPText, tags map[string]string) {
186
+ if value.Has && value.Value != "" {
187
+ b.addMetric(pm, name, map[string]int64{value.Value: 1}, tags)
188
+ }
189
+}
190
+
191
+func (b *bgpTypedMetricBuilder) addTimerPairMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPTimerPair, tags map[string]string) {
192
+ mv := make(map[string]int64)
193
+ if value.ConnectRetry.Has {
194
+ mv["connect_retry"] = value.ConnectRetry.Value
195
+ }
196
+ if value.HoldTime.Has {
197
+ mv["hold_time"] = value.HoldTime.Value
198
+ }
199
+ if value.KeepaliveTime.Has {
200
+ mv["keepalive"] = value.KeepaliveTime.Value
201
+ }
202
+ if value.MinASOriginationInterval.Has {
203
+ mv["min_as_origination_interval"] = value.MinASOriginationInterval.Value
204
+ }
205
+ if value.MinRouteAdvertisementInterval.Has {
206
+ mv["min_route_advertisement_interval"] = value.MinRouteAdvertisementInterval.Value
207
+ }
208
+ b.addMetric(pm, name, mv, tags)
209
+}
210
+
211
+func (b *bgpTypedMetricBuilder) addRouteCountersMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPRouteCounters, tags map[string]string) {
212
+ mv := make(map[string]int64)
213
+ if value.Received.Has {
214
+ mv["received"] = value.Received.Value
215
+ }
216
+ if value.Accepted.Has {
217
+ mv["accepted"] = value.Accepted.Value
218
+ }
219
+ if value.Rejected.Has {
220
+ mv["rejected"] = value.Rejected.Value
221
+ }
222
+ if value.Active.Has {
223
+ mv["active"] = value.Active.Value
224
+ }
225
+ if value.Advertised.Has {
226
+ mv["advertised"] = value.Advertised.Value
227
+ }
228
+ if value.Suppressed.Has {
229
+ mv["suppressed"] = value.Suppressed.Value
230
+ }
231
+ if value.Withdrawn.Has {
232
+ mv["withdrawn"] = value.Withdrawn.Value
233
+ }
234
+ b.addMetric(pm, name, mv, tags)
235
+}
236
+
237
+func (b *bgpTypedMetricBuilder) addRouteLimitsMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPRouteLimits, tags map[string]string) {
238
+ mv := make(map[string]int64)
239
+ if value.Limit.Has {
240
+ mv["admin_limit"] = value.Limit.Value
241
+ }
242
+ b.addMetric(pm, name, mv, tags)
243
+}
244
+
245
+func (b *bgpTypedMetricBuilder) addRouteLimitThresholdsMetric(pm *ddsnmp.ProfileMetrics, name string, value ddsnmp.BGPRouteLimits, tags map[string]string) {
246
+ mv := make(map[string]int64)
247
+ if value.Threshold.Has {
248
+ mv["threshold"] = value.Threshold.Value
249
+ }
250
+ if value.ClearThreshold.Has {
251
+ mv["clear_threshold"] = value.ClearThreshold.Value
252
+ }
253
+ b.addMetric(pm, name, mv, tags)
254
+}
255
+
256
+func (b *bgpTypedMetricBuilder) addExplicitDevicePeerCount(dim string, value int64) {
257
+ if b.explicitDevicePeerCounts == nil {
258
+ b.explicitDevicePeerCounts = make(map[string]int64)
259
+ }
260
+ b.explicitDevicePeerCounts[dim] += value
261
+}
262
+
263
+func (b *bgpTypedMetricBuilder) addAutoDevicePeerCount(dim string, value int64) {
264
+ if b.autoDevicePeerCounts == nil {
265
+ b.autoDevicePeerCounts = make(map[string]int64)
266
+ }
267
+ b.autoDevicePeerCounts[dim] += value
268
+}
269
+
270
+func (b *bgpTypedMetricBuilder) addDevicePeerState(dim string, value int64) {
271
+ if b.devicePeerStates == nil {
272
+ b.devicePeerStates = make(map[string]int64)
273
+ }
274
+ b.devicePeerStates[dim] += value
275
+}
276
+
277
+func (b *bgpTypedMetricBuilder) metricsWithDeviceSummaries() []ddsnmp.Metric {
278
+ if b.deviceSample != nil {
279
+ peerCounts := maps.Clone(b.autoDevicePeerCounts)
280
+ if peerCounts == nil && len(b.explicitDevicePeerCounts) > 0 {
281
+ peerCounts = make(map[string]int64, len(b.explicitDevicePeerCounts))
282
+ }
283
+ for dim, value := range b.explicitDevicePeerCounts {
284
+ peerCounts[dim] = value
285
+ }
286
+ b.addMetric(b.deviceSample, "bgp.devices.peer_counts", peerCounts, nil)
287
+ b.addMetric(b.deviceSample, "bgp.devices.peer_states", b.devicePeerStates, nil)
288
+ }
289
+ return b.metrics
290
+}
291
+
292
+func bgpTypedMetricTags(row ddsnmp.BGPRow, scope bgpScope) map[string]string {
293
+ tags := make(map[string]string)
294
+ add := func(key, value string) {
295
+ if value != "" {
296
+ tags[key] = value
297
+ }
298
+ }
299
+ add("routing_instance", bgpRoutingInstance(row))
300
+ add("neighbor", firstNonEmpty(row.Identity.Neighbor, row.Tags["neighbor"]))
301
+ add("remote_as", firstNonEmpty(row.Identity.RemoteAS, row.Tags["remote_as"]))
302
+ if scope == bgpScopePeerFamilies {
303
+ add("address_family", string(row.Identity.AddressFamily))
304
+ add("subsequent_address_family", string(row.Identity.SubsequentAddressFamily))
305
+ }
306
+ add("_local_address", row.Descriptors.LocalAddress)
307
+ add("_local_as", row.Descriptors.LocalAS)
308
+ add("_local_identifier", row.Descriptors.LocalIdentifier)
309
+ add("_peer_identifier", row.Descriptors.PeerIdentifier)
310
+ add("_peer_type", row.Descriptors.PeerType)
311
+ add("_bgp_version", row.Descriptors.BGPVersion)
312
+ add("_peer_description", row.Descriptors.Description)
313
+ return tags
314
+}
315
+
316
+func bgpRoutingInstance(row ddsnmp.BGPRow) string {
317
+ return firstNonEmpty(row.Identity.RoutingInstance, row.Tags["routing_instance"], "default")
318
+}
319
+
320
+func firstNonEmpty(values ...string) string {
321
+ for _, value := range values {
322
+ if value != "" {
323
+ return value
324
+ }
325
+ }
326
+ return ""
327
+}
src/go/plugin/go.d/collector/snmp/bgp_typed_metrics_test.go
new
+644
@@ -0,0 +1,644 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "context"
7
+ "errors"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/netdata/netdata/go/plugins/pkg/funcapi"
12
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
15
+ "github.com/stretchr/testify/assert"
16
+ "github.com/stretchr/testify/require"
17
+)
18
+
19
+func TestTypedBGPMetricsFromProfileMetrics(t *testing.T) {
20
+ tests := map[string]struct {
21
+ pm *ddsnmp.ProfileMetrics
22
+ validate func(t *testing.T, metrics []ddsnmp.Metric)
23
+ }{
24
+ "peer and peer-family rows become BGP chart metrics": {
25
+ pm: &ddsnmp.ProfileMetrics{
26
+ BGPRows: []ddsnmp.BGPRow{
27
+ typedBGPPeerRow(),
28
+ typedBGPPeerFamilyRow(),
29
+ },
30
+ },
31
+ validate: func(t *testing.T, metrics []ddsnmp.Metric) {
32
+ availability := requireMetric(t, metrics, "bgp.peers.availability", map[string]string{
33
+ "routing_instance": "blue",
34
+ "neighbor": "192.0.2.1",
35
+ "remote_as": "65001",
36
+ })
37
+ assert.Equal(t, map[string]int64{"admin_enabled": 1, "established": 1}, availability.MultiValue)
38
+ assert.NotContains(t, availability.Tags, "local_address")
39
+ assert.NotContains(t, availability.Tags, "peer_description")
40
+ assert.Equal(t, "198.51.100.1", availability.Tags["_local_address"])
41
+ assert.Equal(t, "Transit Peer", availability.Tags["_peer_description"])
42
+
43
+ updates := requireMetric(t, metrics, "bgp.peers.update_traffic", map[string]string{
44
+ "routing_instance": "blue",
45
+ "neighbor": "192.0.2.1",
46
+ "remote_as": "65001",
47
+ })
48
+ assert.Equal(t, map[string]int64{"received": 10, "sent": 20}, updates.MultiValue)
49
+
50
+ routes := requireMetric(t, metrics, "bgp.peer_families.route_counts.current", map[string]string{
51
+ "routing_instance": "blue",
52
+ "neighbor": "192.0.2.1",
53
+ "remote_as": "65001",
54
+ "address_family": "ipv4",
55
+ "subsequent_address_family": "unicast",
56
+ })
57
+ assert.Equal(t, map[string]int64{"accepted": 540336, "advertised": 540339}, routes.MultiValue)
58
+
59
+ counts := requireMetric(t, metrics, "bgp.devices.peer_counts", nil)
60
+ assert.Equal(t, map[string]int64{"configured": 1, "admin_enabled": 1, "established": 1}, counts.MultiValue)
61
+
62
+ states := requireMetric(t, metrics, "bgp.devices.peer_states", nil)
63
+ assert.Equal(t, map[string]int64{"established": 1}, states.MultiValue)
64
+ },
65
+ },
66
+ "empty routing instance is normalized to default": {
67
+ pm: &ddsnmp.ProfileMetrics{
68
+ BGPRows: []ddsnmp.BGPRow{
69
+ typedBGPPeerRowWithIdentity("typed-peer-default-vrf", "192.0.2.3", "65003", "", ddprofiledefinition.BGPPeerStateEstablished),
70
+ },
71
+ },
72
+ validate: func(t *testing.T, metrics []ddsnmp.Metric) {
73
+ availability := requireMetric(t, metrics, "bgp.peers.availability", map[string]string{
74
+ "routing_instance": "default",
75
+ "neighbor": "192.0.2.3",
76
+ "remote_as": "65003",
77
+ })
78
+ assert.Equal(t, map[string]int64{"admin_enabled": 1, "established": 1}, availability.MultiValue)
79
+ },
80
+ },
81
+ "explicit device peer counts override auto peer-row count dimensions": {
82
+ pm: &ddsnmp.ProfileMetrics{
83
+ BGPRows: []ddsnmp.BGPRow{
84
+ typedBGPPeerRow(),
85
+ {
86
+ OriginProfileID: "_vendor-bgp.yaml",
87
+ Kind: ddprofiledefinition.BGPRowKindDevice,
88
+ StructuralID: "typed-device-key",
89
+ Device: ddsnmp.BGPDeviceCounts{
90
+ Peers: ddsnmp.BGPInt64{Has: true, Value: 12},
91
+ InternalPeers: ddsnmp.BGPInt64{Has: true, Value: 4},
92
+ ExternalPeers: ddsnmp.BGPInt64{Has: true, Value: 8},
93
+ },
94
+ },
95
+ },
96
+ },
97
+ validate: func(t *testing.T, metrics []ddsnmp.Metric) {
98
+ counts := requireMetric(t, metrics, "bgp.devices.peer_counts", nil)
99
+ assert.Equal(t, map[string]int64{
100
+ "configured": 12,
101
+ "ibgp": 4,
102
+ "ebgp": 8,
103
+ "admin_enabled": 1,
104
+ "established": 1,
105
+ }, counts.MultiValue)
106
+ },
107
+ },
108
+ }
109
+
110
+ for name, tc := range tests {
111
+ t.Run(name, func(t *testing.T) {
112
+ metrics := typedBGPMetricsFromProfileMetrics([]*ddsnmp.ProfileMetrics{tc.pm})
113
+ tc.validate(t, metrics)
114
+ })
115
+ }
116
+}
117
+
118
+func TestTypedBGPMetricsUseLogicalChartIdentity(t *testing.T) {
119
+ tests := map[string]struct {
120
+ profileMetrics []*ddsnmp.ProfileMetrics
121
+ wantKey string
122
+ }{
123
+ "same logical peer from different BGP rows keeps one public chart identity": {
124
+ profileMetrics: []*ddsnmp.ProfileMetrics{
125
+ {
126
+ Source: "profile-a.yaml",
127
+ BGPRows: []ddsnmp.BGPRow{
128
+ typedBGPPeerRowWithIdentity("profile-a-peer", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished),
129
+ },
130
+ },
131
+ {
132
+ Source: "profile-b.yaml",
133
+ BGPRows: []ddsnmp.BGPRow{
134
+ func() ddsnmp.BGPRow {
135
+ row := typedBGPPeerRowWithIdentity("profile-b-peer", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished)
136
+ row.Descriptors.LocalAddress = "203.0.113.9"
137
+ row.Descriptors.Description = "Backup Peer"
138
+ return row
139
+ }(),
140
+ },
141
+ },
142
+ },
143
+ wantKey: "bgp.peers.availability_192.0.2.1_65001_blue",
144
+ },
145
+ }
146
+
147
+ for name, tc := range tests {
148
+ t.Run(name, func(t *testing.T) {
149
+ metrics := typedBGPMetricsFromProfileMetrics(tc.profileMetrics)
150
+
151
+ keys := make(map[string]struct{})
152
+ var availabilityMetrics []ddsnmp.Metric
153
+ for _, metric := range metrics {
154
+ if metric.Name != "bgp.peers.availability" {
155
+ continue
156
+ }
157
+ keys[tableMetricKey(metric)] = struct{}{}
158
+ availabilityMetrics = append(availabilityMetrics, metric)
159
+ }
160
+
161
+ require.Len(t, availabilityMetrics, 2)
162
+ assert.Equal(t, map[string]struct{}{tc.wantKey: {}}, keys)
163
+ for _, metric := range availabilityMetrics {
164
+ key := tableMetricKey(metric)
165
+ assert.NotContains(t, key, "profile-")
166
+ assert.NotContains(t, key, metric.Tags["_local_address"])
167
+ assert.NotContains(t, key, metric.Tags["_peer_description"])
168
+ assert.Contains(t, metric.Tags, "_local_address")
169
+ assert.Contains(t, metric.Tags, "_peer_description")
170
+ }
171
+ })
172
+ }
173
+}
174
+
175
+func requireMetric(t *testing.T, metrics []ddsnmp.Metric, name string, subset map[string]string) ddsnmp.Metric {
176
+ t.Helper()
177
+ for _, metric := range metrics {
178
+ if metric.Name != name {
179
+ continue
180
+ }
181
+ if len(subset) == 0 {
182
+ return metric
183
+ }
184
+ matches := true
185
+ for key, want := range subset {
186
+ if metric.Tags[key] != want {
187
+ matches = false
188
+ break
189
+ }
190
+ }
191
+ if matches {
192
+ return metric
193
+ }
194
+ }
195
+ require.Failf(t, "metric not found", "name=%q tags=%v metrics=%v", name, subset, metrics)
196
+ return ddsnmp.Metric{}
197
+}
198
+
199
+func TestBGPPeerCache_UpdateRow(t *testing.T) {
200
+ tests := map[string]struct {
201
+ rows []ddsnmp.BGPRow
202
+ params funcapi.ResolvedParams
203
+ validate func(t *testing.T, resp *funcapi.FunctionResponse)
204
+ }{
205
+ "typed rows populate function cache without underscore tags": {
206
+ rows: []ddsnmp.BGPRow{
207
+ typedBGPPeerRow(),
208
+ typedBGPPeerFamilyRow(),
209
+ },
210
+ params: resolveBGPPeerParams(map[string][]string{bgpPeersParamView: {bgpPeersViewAll}}),
211
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
212
+ assert.Equal(t, 200, resp.Status)
213
+ rows := responseRows(t, resp)
214
+ require.Len(t, rows, 2)
215
+
216
+ assert.Equal(t, "peer", rows[0][findBGPPeerColIdx("Scope")])
217
+ assert.Equal(t, "192.0.2.1", rows[0][findBGPPeerColIdx("Neighbor")])
218
+ assert.Equal(t, "198.51.100.1", rows[0][findBGPPeerColIdx("Local Address")])
219
+ assert.Equal(t, "Transit Peer", rows[0][findBGPPeerColIdx("Peer Description")])
220
+ assert.Equal(t, "enabled", rows[0][findBGPPeerColIdx("Admin Status")])
221
+ assert.Equal(t, "established", rows[0][findBGPPeerColIdx("Connection State")])
222
+ assert.Equal(t, "Cease (subcode 0)", rows[0][findBGPPeerColIdx("Last Error")])
223
+
224
+ assert.Equal(t, "peer family", rows[1][findBGPPeerColIdx("Scope")])
225
+ assert.Equal(t, "ipv4 unicast", rows[1][findBGPPeerColIdx("Family")])
226
+ assert.Equal(t, "restart time wait", rows[1][findBGPPeerColIdx("GR State")])
227
+ assert.Equal(t, "peer not ready", rows[1][findBGPPeerColIdx("Unavailability Reason")])
228
+ },
229
+ },
230
+ "same peer identity from different profile rows keeps distinct cache rows": {
231
+ rows: []ddsnmp.BGPRow{
232
+ typedBGPPeerRowWithIdentity("profile-a-peer", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished),
233
+ typedBGPPeerRowWithIdentity("profile-b-peer", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateActive),
234
+ },
235
+ params: resolveBGPPeerParams(nil),
236
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
237
+ assert.Equal(t, 200, resp.Status)
238
+ rows := responseRows(t, resp)
239
+ require.Len(t, rows, 2)
240
+
241
+ stateByRowID := make(map[string]string, len(rows))
242
+ for _, row := range rows {
243
+ assert.Equal(t, "192.0.2.1", row[findBGPPeerColIdx("Neighbor")])
244
+ assert.Equal(t, "65001", row[findBGPPeerColIdx("Remote AS")])
245
+ rowID, ok := row[findBGPPeerColIdx("rowId")].(string)
246
+ require.True(t, ok)
247
+ state, ok := row[findBGPPeerColIdx("Connection State")].(string)
248
+ require.True(t, ok)
249
+ stateByRowID[rowID] = state
250
+ }
251
+
252
+ assert.Equal(t, map[string]string{
253
+ "profile-a-peer": "established",
254
+ "profile-b-peer": "active",
255
+ }, stateByRowID)
256
+ },
257
+ },
258
+ }
259
+
260
+ for name, tc := range tests {
261
+ t.Run(name, func(t *testing.T) {
262
+ cache := newBGPPeerCache()
263
+ cache.reset()
264
+ for _, row := range tc.rows {
265
+ cache.updateRow("_vendor-bgp.yaml", row)
266
+ }
267
+ cache.finalize()
268
+
269
+ resp := newTestFuncBGPPeers(cache).Handle(context.Background(), bgpPeersMethodID, tc.params)
270
+ tc.validate(t, resp)
271
+ })
272
+ }
273
+}
274
+
275
+func TestBGPIntegration_PreservesFunctionCacheOnProfileBGPError(t *testing.T) {
276
+ tests := map[string]struct {
277
+ initial []*ddsnmp.ProfileMetrics
278
+ failed []*ddsnmp.ProfileMetrics
279
+ }{
280
+ "profile BGP failure marks existing function rows stale instead of deleting them": {
281
+ initial: []*ddsnmp.ProfileMetrics{{
282
+ Source: "typed-bgp-profile.yaml",
283
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRow()},
284
+ }},
285
+ failed: []*ddsnmp.ProfileMetrics{{
286
+ Source: "typed-bgp-profile.yaml",
287
+ BGPCollectError: errors.New("BGP table walk failed"),
288
+ }},
289
+ },
290
+ }
291
+
292
+ for name, tc := range tests {
293
+ t.Run(name, func(t *testing.T) {
294
+ collr := New()
295
+ collr.sysInfo = &snmputils.SysInfo{}
296
+ collr.enableBGPIntegration()
297
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.initial}
298
+
299
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
300
+
301
+ resp := newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
302
+ assert.Equal(t, 200, resp.Status)
303
+ require.Len(t, responseRows(t, resp), 1)
304
+
305
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.failed}
306
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
307
+
308
+ resp = newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
309
+ assert.Equal(t, 200, resp.Status)
310
+ assert.Contains(t, resp.Help, "showing stale BGP rows")
311
+ rows := responseRows(t, resp)
312
+ require.Len(t, rows, 1)
313
+ assert.Equal(t, true, rows[0][findBGPPeerColIdx("Stale")])
314
+ })
315
+ }
316
+}
317
+
318
+func TestBGPIntegration_RecoveryClearsStaleFunctionRows(t *testing.T) {
319
+ tests := map[string]struct {
320
+ initial []*ddsnmp.ProfileMetrics
321
+ failed []*ddsnmp.ProfileMetrics
322
+ recovered []*ddsnmp.ProfileMetrics
323
+ }{
324
+ "success then failure then success clears stale flag and help banner": {
325
+ initial: []*ddsnmp.ProfileMetrics{{
326
+ Source: "typed-bgp-profile.yaml",
327
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-a", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished)},
328
+ }},
329
+ failed: []*ddsnmp.ProfileMetrics{{
330
+ Source: "typed-bgp-profile.yaml",
331
+ BGPCollectError: errors.New("BGP table walk failed"),
332
+ }},
333
+ recovered: []*ddsnmp.ProfileMetrics{{
334
+ Source: "typed-bgp-profile.yaml",
335
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-a", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateActive)},
336
+ }},
337
+ },
338
+ }
339
+
340
+ for name, tc := range tests {
341
+ t.Run(name, func(t *testing.T) {
342
+ collr := New()
343
+ collr.sysInfo = &snmputils.SysInfo{}
344
+ collr.enableBGPIntegration()
345
+
346
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.initial}
347
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
348
+
349
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.failed}
350
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
351
+
352
+ resp := newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
353
+ assert.Equal(t, 200, resp.Status)
354
+ assert.Contains(t, resp.Help, "showing stale BGP rows")
355
+ rows := responseRows(t, resp)
356
+ require.Len(t, rows, 1)
357
+ assert.Equal(t, true, rows[0][findBGPPeerColIdx("Stale")])
358
+
359
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.recovered}
360
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
361
+
362
+ resp = newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
363
+ assert.Equal(t, 200, resp.Status)
364
+ assert.NotContains(t, resp.Help, "stale BGP rows")
365
+ rows = responseRows(t, resp)
366
+ require.Len(t, rows, 1)
367
+ assert.Equal(t, false, rows[0][findBGPPeerColIdx("Stale")])
368
+ assert.Equal(t, "active", rows[0][findBGPPeerColIdx("Connection State")])
369
+ })
370
+ }
371
+}
372
+
373
+func TestBGPIntegration_MixedProfileBGPErrorRefreshesSuccessfulProfiles(t *testing.T) {
374
+ tests := map[string]struct {
375
+ initial []*ddsnmp.ProfileMetrics
376
+ next []*ddsnmp.ProfileMetrics
377
+ }{
378
+ "failed profile keeps stale rows while successful profile refreshes": {
379
+ initial: []*ddsnmp.ProfileMetrics{
380
+ {
381
+ Source: "profile-a.yaml",
382
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-a", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished)},
383
+ },
384
+ {
385
+ Source: "profile-b.yaml",
386
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-b", "192.0.2.2", "65002", "blue", ddprofiledefinition.BGPPeerStateEstablished)},
387
+ },
388
+ },
389
+ next: []*ddsnmp.ProfileMetrics{
390
+ {
391
+ Source: "profile-a.yaml",
392
+ BGPCollectError: errors.New("BGP table walk failed"),
393
+ },
394
+ {
395
+ Source: "profile-b.yaml",
396
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-b", "192.0.2.2", "65002", "blue", ddprofiledefinition.BGPPeerStateActive)},
397
+ },
398
+ },
399
+ },
400
+ }
401
+
402
+ for name, tc := range tests {
403
+ t.Run(name, func(t *testing.T) {
404
+ collr := New()
405
+ collr.sysInfo = &snmputils.SysInfo{}
406
+ collr.enableBGPIntegration()
407
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.initial}
408
+
409
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
410
+
411
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.next}
412
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
413
+
414
+ resp := newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
415
+ assert.Equal(t, 200, resp.Status)
416
+ assert.Contains(t, resp.Help, "showing stale BGP rows")
417
+
418
+ byNeighbor := bgpPeerRowsByNeighbor(t, responseRows(t, resp))
419
+ require.Len(t, byNeighbor, 2)
420
+
421
+ assert.Equal(t, true, byNeighbor["192.0.2.1"][findBGPPeerColIdx("Stale")])
422
+ assert.Equal(t, "established", byNeighbor["192.0.2.1"][findBGPPeerColIdx("Connection State")])
423
+ assert.Equal(t, false, byNeighbor["192.0.2.2"][findBGPPeerColIdx("Stale")])
424
+ assert.Equal(t, "active", byNeighbor["192.0.2.2"][findBGPPeerColIdx("Connection State")])
425
+ })
426
+ }
427
+}
428
+
429
+func TestBGPIntegration_ExpiredFailedProfileDoesNotKeepStaleRowsWithFreshProfiles(t *testing.T) {
430
+ tests := map[string]struct {
431
+ initial []*ddsnmp.ProfileMetrics
432
+ next []*ddsnmp.ProfileMetrics
433
+ }{
434
+ "expired failed profile row is omitted while successful profile refreshes": {
435
+ initial: []*ddsnmp.ProfileMetrics{
436
+ {
437
+ Source: "profile-a.yaml",
438
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-a", "192.0.2.1", "65001", "blue", ddprofiledefinition.BGPPeerStateEstablished)},
439
+ },
440
+ {
441
+ Source: "profile-b.yaml",
442
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-b", "192.0.2.2", "65002", "blue", ddprofiledefinition.BGPPeerStateEstablished)},
443
+ },
444
+ },
445
+ next: []*ddsnmp.ProfileMetrics{
446
+ {
447
+ Source: "profile-a.yaml",
448
+ BGPCollectError: errors.New("BGP table walk failed"),
449
+ },
450
+ {
451
+ Source: "profile-b.yaml",
452
+ BGPRows: []ddsnmp.BGPRow{typedBGPPeerRowWithIdentity("peer-b", "192.0.2.2", "65002", "blue", ddprofiledefinition.BGPPeerStateActive)},
453
+ },
454
+ },
455
+ },
456
+ }
457
+
458
+ for name, tc := range tests {
459
+ t.Run(name, func(t *testing.T) {
460
+ collr := New()
461
+ collr.sysInfo = &snmputils.SysInfo{}
462
+ collr.enableBGPIntegration()
463
+ collr.bgp.setStaleAfter(time.Minute)
464
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.initial}
465
+
466
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
467
+ markBGPCacheSourceLastUpdate(t, collr.bgp.peerCache, "profile-a.yaml", time.Now().Add(-2*time.Minute))
468
+
469
+ collr.ddSnmpColl = &mockDdSnmpCollector{pms: tc.next}
470
+ require.NoError(t, collr.collectSNMP(map[string]int64{}))
471
+
472
+ resp := newTestFuncBGPPeers(collr.bgp.peerCache).Handle(context.Background(), bgpPeersMethodID, nil)
473
+ assert.Equal(t, 200, resp.Status)
474
+
475
+ byNeighbor := bgpPeerRowsByNeighbor(t, responseRows(t, resp))
476
+ require.Len(t, byNeighbor, 1)
477
+ require.NotContains(t, byNeighbor, "192.0.2.1")
478
+ require.Contains(t, byNeighbor, "192.0.2.2")
479
+ assert.Equal(t, false, byNeighbor["192.0.2.2"][findBGPPeerColIdx("Stale")])
480
+ assert.Equal(t, "active", byNeighbor["192.0.2.2"][findBGPPeerColIdx("Connection State")])
481
+ requireBGPCacheKeyAbsent(t, collr.bgp.peerCache, "peer-a")
482
+ })
483
+ }
484
+}
485
+
486
+func TestProfilesHaveBGP(t *testing.T) {
487
+ tests := map[string]struct {
488
+ profiles []*ddsnmp.Profile
489
+ want bool
490
+ }{
491
+ "typed BGP row enables integration": {
492
+ profiles: []*ddsnmp.Profile{{
493
+ Definition: &ddprofiledefinition.ProfileDefinition{
494
+ BGP: []ddprofiledefinition.BGPConfig{{ID: "peer"}},
495
+ },
496
+ }},
497
+ want: true,
498
+ },
499
+ "legacy BGP virtual metric does not enable integration": {
500
+ profiles: []*ddsnmp.Profile{{
501
+ Definition: &ddprofiledefinition.ProfileDefinition{
502
+ VirtualMetrics: []ddprofiledefinition.VirtualMetricConfig{{Name: "bgpPeerAvailability"}},
503
+ },
504
+ }},
505
+ want: false,
506
+ },
507
+ "legacy BGP raw metric does not enable integration": {
508
+ profiles: []*ddsnmp.Profile{{
509
+ Definition: &ddprofiledefinition.ProfileDefinition{
510
+ Metrics: []ddprofiledefinition.MetricsConfig{{
511
+ Symbols: []ddprofiledefinition.SymbolConfig{{Name: "bgpPeerState"}},
512
+ }},
513
+ },
514
+ }},
515
+ want: false,
516
+ },
517
+ "ordinary metrics do not enable integration": {
518
+ profiles: []*ddsnmp.Profile{{
519
+ Definition: &ddprofiledefinition.ProfileDefinition{
520
+ Metrics: []ddprofiledefinition.MetricsConfig{{
521
+ Symbol: ddprofiledefinition.SymbolConfig{Name: "sysUpTime"},
522
+ }},
523
+ },
524
+ }},
525
+ want: false,
526
+ },
527
+ }
528
+
529
+ for name, tc := range tests {
530
+ t.Run(name, func(t *testing.T) {
531
+ assert.Equal(t, tc.want, profilesHaveBGP(tc.profiles))
532
+ })
533
+ }
534
+}
535
+
536
+func typedBGPPeerRow() ddsnmp.BGPRow {
537
+ return ddsnmp.BGPRow{
538
+ OriginProfileID: "_vendor-bgp.yaml",
539
+ Kind: ddprofiledefinition.BGPRowKindPeer,
540
+ StructuralID: "typed-peer-key",
541
+ Identity: ddsnmp.BGPIdentity{
542
+ RoutingInstance: "blue",
543
+ Neighbor: "192.0.2.1",
544
+ RemoteAS: "65001",
545
+ },
546
+ Descriptors: ddsnmp.BGPDescriptors{
547
+ LocalAddress: "198.51.100.1",
548
+ Description: "Transit Peer",
549
+ },
550
+ Admin: ddsnmp.BGPAdmin{
551
+ Enabled: ddsnmp.BGPBool{Has: true, Value: true},
552
+ },
553
+ State: ddsnmp.BGPState{
554
+ Has: true,
555
+ State: ddprofiledefinition.BGPPeerStateEstablished,
556
+ },
557
+ Previous: ddsnmp.BGPState{
558
+ Has: true,
559
+ State: ddprofiledefinition.BGPPeerStateIdle,
560
+ },
561
+ Connection: ddsnmp.BGPConnection{
562
+ EstablishedUptime: ddsnmp.BGPInt64{Has: true, Value: 1234},
563
+ },
564
+ Traffic: ddsnmp.BGPTraffic{
565
+ Updates: ddsnmp.BGPDirectional{
566
+ Received: ddsnmp.BGPInt64{Has: true, Value: 10},
567
+ Sent: ddsnmp.BGPInt64{Has: true, Value: 20},
568
+ },
569
+ },
570
+ LastError: ddsnmp.BGPLastError{
571
+ Code: ddsnmp.BGPInt64{Has: true, Value: 6},
572
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 0},
573
+ },
574
+ }
575
+}
576
+
577
+func typedBGPPeerRowWithIdentity(structuralID, neighbor, remoteAS, routingInstance string, state ddprofiledefinition.BGPPeerState) ddsnmp.BGPRow {
578
+ row := typedBGPPeerRow()
579
+ row.StructuralID = structuralID
580
+ row.Identity.Neighbor = neighbor
581
+ row.Identity.RemoteAS = remoteAS
582
+ row.Identity.RoutingInstance = routingInstance
583
+ row.State.State = state
584
+ row.Tags = nil
585
+ return row
586
+}
587
+
588
+func bgpPeerRowsByNeighbor(t *testing.T, rows [][]any) map[string][]any {
589
+ t.Helper()
590
+ result := make(map[string][]any, len(rows))
591
+ for _, row := range rows {
592
+ neighbor, ok := row[findBGPPeerColIdx("Neighbor")].(string)
593
+ require.True(t, ok)
594
+ result[neighbor] = row
595
+ }
596
+ return result
597
+}
598
+
599
+func markBGPCacheSourceLastUpdate(t *testing.T, cache *bgpPeerCache, source string, lastUpdate time.Time) {
600
+ t.Helper()
601
+ cache.mu.Lock()
602
+ defer cache.mu.Unlock()
603
+ for _, entry := range cache.entries {
604
+ if entry.source == source {
605
+ entry.lastUpdate = lastUpdate
606
+ return
607
+ }
608
+ }
609
+ t.Fatalf("BGP cache source %q not found", source)
610
+}
611
+
612
+func requireBGPCacheKeyAbsent(t *testing.T, cache *bgpPeerCache, key string) {
613
+ t.Helper()
614
+ cache.mu.RLock()
615
+ defer cache.mu.RUnlock()
616
+ require.NotContains(t, cache.entries, key)
617
+}
618
+
619
+func typedBGPPeerFamilyRow() ddsnmp.BGPRow {
620
+ return ddsnmp.BGPRow{
621
+ OriginProfileID: "_vendor-bgp.yaml",
622
+ Kind: ddprofiledefinition.BGPRowKindPeerFamily,
623
+ StructuralID: "typed-peer-family-key",
624
+ Identity: ddsnmp.BGPIdentity{
625
+ RoutingInstance: "blue",
626
+ Neighbor: "192.0.2.1",
627
+ RemoteAS: "65001",
628
+ AddressFamily: ddprofiledefinition.BGPAddressFamilyIPv4,
629
+ SubsequentAddressFamily: ddprofiledefinition.BGPSubsequentAddressFamilyUnicast,
630
+ },
631
+ Routes: ddsnmp.BGPRoutes{
632
+ Current: ddsnmp.BGPRouteCounters{
633
+ Accepted: ddsnmp.BGPInt64{Has: true, Value: 540336},
634
+ Advertised: ddsnmp.BGPInt64{Has: true, Value: 540339},
635
+ },
636
+ },
637
+ Restart: ddsnmp.BGPGracefulRestart{
638
+ State: ddsnmp.BGPText{Has: true, Value: "restart_time_wait"},
639
+ },
640
+ Reasons: ddsnmp.BGPReasons{
641
+ Unavailability: ddsnmp.BGPText{Has: true, Value: "peer_not_ready"},
642
+ },
643
+ }
644
+}
src/go/plugin/go.d/collector/snmp/charts.go
+12
-9
@@ -94,6 +94,7 @@ var (
94
{ID: "snmp_device_prof_%s_stats_timings_scalar", Name: "scalar"},
95
{ID: "snmp_device_prof_%s_stats_timings_table", Name: "table"},
96
{ID: "snmp_device_prof_%s_stats_timings_licensing", Name: "licensing"},
97
+ {ID: "snmp_device_prof_%s_stats_timings_bgp", Name: "bgp"},
98
{ID: "snmp_device_prof_%s_stats_timings_virtual", Name: "virtual"},
99
},
100
}
@@ -127,6 +128,7 @@ var (
128
{ID: "snmp_device_prof_%s_stats_metrics_table", Name: "table"},
129
{ID: "snmp_device_prof_%s_stats_metrics_virtual", Name: "virtual"},
130
{ID: "snmp_device_prof_%s_stats_metrics_licensing", Name: "licensing"},
131
+ {ID: "snmp_device_prof_%s_stats_metrics_bgp", Name: "bgp"},
132
{ID: "snmp_device_prof_%s_stats_metrics_tables", Name: "tables"},
133
{ID: "snmp_device_prof_%s_stats_metrics_rows", Name: "rows"},
134
},
@@ -157,6 +159,7 @@ var (
159
{ID: "snmp_device_prof_%s_stats_errors_processing_scalar", Name: "processing_scalar"},
160
{ID: "snmp_device_prof_%s_stats_errors_processing_table", Name: "processing_table"},
161
{ID: "snmp_device_prof_%s_stats_errors_processing_licensing", Name: "processing_licensing"},
162
+ {ID: "snmp_device_prof_%s_stats_errors_processing_bgp", Name: "processing_bgp"},
163
},
164
}
165
)
@@ -192,12 +195,12 @@ func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
195
}
196
197
chart := &collectorapi.Chart{
195
- ID: fmt.Sprintf("snmp_device_prof_%s", cleanMetricName.Replace(m.Name)),
198
+ ID: chartIDFromName(m.Name),
199
Title: m.Description,
200
Type: collectorapi.ChartType(m.ChartType),
201
Units: m.Unit,
202
Fam: m.Family,
200
- Ctx: fmt.Sprintf("snmp.device_prof_%s", cleanMetricName.Replace(m.Name)),
203
+ Ctx: chartContextID(m.Name),
204
Priority: prioProfileChart,
205
}
206
if chart.Title == "" {
@@ -226,12 +229,12 @@ func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
229
for k := range m.MultiValue {
230
if !seen[k] {
231
seen[k] = true
229
- id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, k)
232
+ id := metricIDFromName(m.Name, k)
233
chart.Dims = append(chart.Dims, &collectorapi.Dim{ID: id, Name: k, Algo: dimAlgoFromDdSnmpType(m)})
234
}
235
}
236
} else {
234
- id := fmt.Sprintf("snmp_device_prof_%s", m.Name)
237
+ id := metricIDFromName(m.Name)
238
chart.Dims = collectorapi.Dims{
239
{ID: id, Name: m.Name, Algo: dimAlgoFromDdSnmpType(m)},
240
}
@@ -250,11 +253,11 @@ func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
253
key := tableMetricKey(m)
254
255
chart := &collectorapi.Chart{
253
- ID: fmt.Sprintf("snmp_device_prof_%s", cleanMetricName.Replace(key)),
256
+ ID: chartIDFromKey(key),
257
Title: m.Description,
258
Units: m.Unit,
259
Fam: m.Family,
257
- Ctx: fmt.Sprintf("snmp.device_prof_%s", cleanMetricName.Replace(m.Name)),
260
+ Ctx: chartContextID(m.Name),
261
Priority: prioProfileChart,
262
}
263
if chart.Title == "" {
@@ -284,12 +287,12 @@ func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
287
for k := range m.MultiValue {
288
if !seen[k] {
289
seen[k] = true
287
- id := fmt.Sprintf("snmp_device_prof_%s_%s", key, k)
290
+ id := metricIDFromKey(key, k)
291
chart.Dims = append(chart.Dims, &collectorapi.Dim{ID: id, Name: k, Algo: dimAlgoFromDdSnmpType(m)})
292
}
293
}
294
} else {
292
- id := fmt.Sprintf("snmp_device_prof_%s", key)
295
+ id := metricIDFromKey(key)
296
chart.Dims = collectorapi.Dims{
297
{ID: id, Name: m.Name, Algo: dimAlgoFromDdSnmpType(m)},
298
}
@@ -301,7 +304,7 @@ func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
304
}
305
306
func (c *Collector) removeProfileTableMetricChart(key string) {
304
- id := fmt.Sprintf("snmp_device_prof_%s", cleanMetricName.Replace(key))
307
+ id := chartIDFromKey(key)
308
if chart := c.Charts().Get(id); chart != nil {
309
chart.MarkRemove()
310
chart.MarkNotCreated()
src/go/plugin/go.d/collector/snmp/collect.go
+3
-3
@@ -215,7 +215,7 @@ func (c *Collector) initAndConnectSNMPClient() (gosnmp.Handler, error) {
215
return snmpClient, nil
216
}
217
218
- if c.Config.Options.MaxRepetitions == 0 {
218
+ if c.Options.MaxRepetitions == 0 {
219
c.disableBulkWalk = true
220
return snmpClient, nil
221
}
@@ -247,8 +247,8 @@ func (c *Collector) adjustMaxRepetitions(snmpClient gosnmp.Handler) (bool, error
247
return false, nil
248
}
249
250
- orig := c.Config.Options.MaxRepetitions
251
- maxReps := c.Config.Options.MaxRepetitions
250
+ orig := c.Options.MaxRepetitions
251
+ maxReps := c.Options.MaxRepetitions
252
attempts := 0
253
const maxAttempts = 20 // Prevent infinite loops
254
src/go/plugin/go.d/collector/snmp/collect_snmp.go
+49
-47
@@ -18,80 +18,79 @@ func (c *Collector) collectSNMP(mx map[string]int64) error {
18
19
pms, err := c.ddSnmpColl.Collect()
20
if err != nil {
21
+ c.markBGPCollectFailed(err)
22
return err
23
}
24
+ if c.bgp == nil && profileMetricsHaveBGP(pms) {
25
+ c.enableBGPIntegration()
26
+ }
27
28
c.resetIfaceCache()
29
c.collectLicensing(mx, pms)
30
27
- c.collectProfileScalarMetrics(mx, pms)
28
- c.collectProfileTableMetrics(mx, pms)
31
+ metrics := c.prepareProfileMetrics(pms)
32
+ c.collectProfileScalarMetrics(mx, metrics)
33
+ c.collectProfileTableMetrics(mx, metrics)
34
c.collectProfileStats(mx, pms)
35
36
c.finalizeIfaceCache()
37
+ c.finalizeProfileMetrics()
38
39
return nil
40
}
41
36
-func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, pms []*ddsnmp.ProfileMetrics) {
37
- for _, pm := range pms {
38
- for _, m := range pm.Metrics {
39
- if m.IsTable || m.Name == "" {
40
- continue
41
- }
42
+func (c *Collector) collectProfileScalarMetrics(mx map[string]int64, metrics []ddsnmp.Metric) {
43
+ for _, m := range metrics {
44
+ if m.IsTable || m.Name == "" {
45
+ continue
46
+ }
47
43
- if !c.seenScalarMetrics[m.Name] {
44
- c.seenScalarMetrics[m.Name] = true
45
- c.addProfileScalarMetricChart(m)
46
- }
48
+ if !c.seenScalarMetrics[m.Name] {
49
+ c.seenScalarMetrics[m.Name] = true
50
+ c.addProfileScalarMetricChart(m)
51
+ }
52
48
- if len(m.MultiValue) == 0 {
49
- id := fmt.Sprintf("snmp_device_prof_%s", m.Name)
50
- mx[id] = m.Value
51
- } else {
52
- for k, v := range m.MultiValue {
53
- id := fmt.Sprintf("snmp_device_prof_%s_%s", m.Name, k)
54
- mx[id] = v
55
- }
56
- }
53
+ if len(m.MultiValue) == 0 {
54
+ mx[metricIDFromName(m.Name)] = m.Value
55
+ continue
56
+ }
57
+
58
+ for k, v := range m.MultiValue {
59
+ mx[metricIDFromName(m.Name, k)] = v
60
}
61
}
62
}
63
61
-func (c *Collector) collectProfileTableMetrics(mx map[string]int64, pms []*ddsnmp.ProfileMetrics) {
64
+func (c *Collector) collectProfileTableMetrics(mx map[string]int64, metrics []ddsnmp.Metric) {
65
seen := make(map[string]bool)
66
64
- for _, pm := range pms {
65
- for _, m := range pm.Metrics {
66
- if !m.IsTable || m.Name == "" || len(m.Tags) == 0 {
67
- continue
68
- }
67
+ for _, m := range metrics {
68
+ if !m.IsTable || m.Name == "" || len(m.Tags) == 0 {
69
+ continue
70
+ }
71
70
- key := tableMetricKey(m)
71
- if key == "" {
72
- continue
73
- }
72
+ key := tableMetricKey(m)
73
+ if key == "" {
74
+ continue
75
+ }
76
75
- seen[key] = true
77
+ seen[key] = true
78
77
- if !c.seenTableMetrics[key] {
78
- c.seenTableMetrics[key] = true
79
- c.addProfileTableMetricChart(m)
80
- }
79
+ if !c.seenTableMetrics[key] {
80
+ c.seenTableMetrics[key] = true
81
+ c.addProfileTableMetricChart(m)
82
+ }
83
82
- if len(m.MultiValue) == 0 {
83
- id := fmt.Sprintf("snmp_device_prof_%s", key)
84
- mx[id] += m.Value
85
- } else {
86
- for k, v := range m.MultiValue {
87
- id := fmt.Sprintf("snmp_device_prof_%s_%s", key, k)
88
- mx[id] = v
89
- }
84
+ if len(m.MultiValue) == 0 {
85
+ mx[metricIDFromKey(key)] += m.Value
86
+ } else {
87
+ for k, v := range m.MultiValue {
88
+ mx[metricIDFromKey(key, k)] = v
89
}
90
+ }
91
92
- if isIfaceMetric(m.Name) {
93
- c.updateIfaceCacheEntry(m)
94
- }
92
+ if isIfaceMetric(m.Name) {
93
+ c.updateIfaceCacheEntry(m)
94
}
95
}
96
@@ -116,6 +115,7 @@ func (c *Collector) collectProfileStats(mx map[string]int64, pms []*ddsnmp.Profi
115
mx[px+"timings_scalar"] = pm.Stats.Timing.Scalar.Milliseconds()
116
mx[px+"timings_table"] = pm.Stats.Timing.Table.Milliseconds()
117
mx[px+"timings_licensing"] = pm.Stats.Timing.Licensing.Milliseconds()
118
+ mx[px+"timings_bgp"] = pm.Stats.Timing.BGP.Milliseconds()
119
mx[px+"timings_virtual"] = pm.Stats.Timing.VirtualMetrics.Milliseconds()
120
mx[px+"snmp_get_requests"] = pm.Stats.SNMP.GetRequests
121
mx[px+"snmp_get_oids"] = pm.Stats.SNMP.GetOIDs
@@ -127,6 +127,7 @@ func (c *Collector) collectProfileStats(mx map[string]int64, pms []*ddsnmp.Profi
127
mx[px+"metrics_table"] = pm.Stats.Metrics.Table
128
mx[px+"metrics_virtual"] = pm.Stats.Metrics.Virtual
129
mx[px+"metrics_licensing"] = pm.Stats.Metrics.Licensing
130
+ mx[px+"metrics_bgp"] = pm.Stats.Metrics.BGP
131
mx[px+"metrics_tables"] = pm.Stats.Metrics.Tables
132
mx[px+"metrics_rows"] = pm.Stats.Metrics.Rows
133
mx[px+"table_cache_hits"] = pm.Stats.TableCache.Hits
@@ -135,6 +136,7 @@ func (c *Collector) collectProfileStats(mx map[string]int64, pms []*ddsnmp.Profi
136
mx[px+"errors_processing_scalar"] = pm.Stats.Errors.Processing.Scalar
137
mx[px+"errors_processing_table"] = pm.Stats.Errors.Processing.Table
138
mx[px+"errors_processing_licensing"] = pm.Stats.Errors.Processing.Licensing
139
+ mx[px+"errors_processing_bgp"] = pm.Stats.Errors.Processing.BGP
140
}
141
}
142
src/go/plugin/go.d/collector/snmp/collector.go
+2
-1
@@ -101,7 +101,8 @@ type (
101
102
ifaceCache *ifaceCache // interface metrics cache for functions
103
licensing *licensingIntegration
104
- funcRouter *funcRouter // function router for method handlers
104
+ bgp *bgpIntegration // BGP metric normalization and function state
105
+ funcRouter *funcRouter // function router for method handlers
106
107
pingClient pinger.Client
108
newPinger func(pinger.Config, *logger.Logger) (pinger.Client, error)
src/go/plugin/go.d/collector/snmp/collector_test.go
+11
-2
@@ -370,9 +370,11 @@ func TestCollector_Collect(t *testing.T) {
370
"snmp_device_prof_test_stats_errors_processing_scalar": 0,
371
"snmp_device_prof_test_stats_errors_processing_table": 0,
372
"snmp_device_prof_test_stats_errors_processing_licensing": 0,
373
+ "snmp_device_prof_test_stats_errors_processing_bgp": 0,
374
"snmp_device_prof_test_stats_errors_snmp": 0,
375
"snmp_device_prof_test_stats_metrics_rows": 0,
376
"snmp_device_prof_test_stats_metrics_licensing": 0,
377
+ "snmp_device_prof_test_stats_metrics_bgp": 0,
378
"snmp_device_prof_test_stats_metrics_scalar": 0,
379
"snmp_device_prof_test_stats_metrics_table": 0,
380
"snmp_device_prof_test_stats_metrics_tables": 0,
@@ -388,6 +390,7 @@ func TestCollector_Collect(t *testing.T) {
390
"snmp_device_prof_test_stats_timings_scalar": 0,
391
"snmp_device_prof_test_stats_timings_table": 0,
392
"snmp_device_prof_test_stats_timings_licensing": 0,
393
+ "snmp_device_prof_test_stats_timings_bgp": 0,
394
"snmp_device_prof_test_stats_timings_virtual": 0,
395
"snmp_device_prof_uptime": 123,
396
},
@@ -431,9 +434,11 @@ func TestCollector_Collect(t *testing.T) {
434
"snmp_device_prof_test_stats_errors_processing_scalar": 0,
435
"snmp_device_prof_test_stats_errors_processing_table": 0,
436
"snmp_device_prof_test_stats_errors_processing_licensing": 0,
437
+ "snmp_device_prof_test_stats_errors_processing_bgp": 0,
438
"snmp_device_prof_test_stats_errors_snmp": 0,
439
"snmp_device_prof_test_stats_metrics_rows": 0,
440
"snmp_device_prof_test_stats_metrics_licensing": 0,
441
+ "snmp_device_prof_test_stats_metrics_bgp": 0,
442
"snmp_device_prof_test_stats_metrics_scalar": 0,
443
"snmp_device_prof_test_stats_metrics_table": 0,
444
"snmp_device_prof_test_stats_metrics_tables": 0,
@@ -449,6 +454,7 @@ func TestCollector_Collect(t *testing.T) {
454
"snmp_device_prof_test_stats_timings_scalar": 0,
455
"snmp_device_prof_test_stats_timings_table": 0,
456
"snmp_device_prof_test_stats_timings_licensing": 0,
457
+ "snmp_device_prof_test_stats_timings_bgp": 0,
458
"snmp_device_prof_test_stats_timings_virtual": 0,
459
"snmp_device_prof_if_octets_eth0_in": 1,
460
"snmp_device_prof_if_octets_eth0_out": 2,
@@ -563,7 +569,7 @@ func TestCollector_CollectPingOnlyUsesTrackingProbing(t *testing.T) {
569
assert.Equal(t, "collect", calls[1].ctx.Value(collectKey{}))
570
}
571
566
-func TestCollector_CollectMixedModeAllowsNilContext(t *testing.T) {
572
+func TestCollector_CollectMixedModeCollectsSNMPAndPingMetrics(t *testing.T) {
573
ctrl := gomock.NewController(t)
574
defer ctrl.Finish()
575
@@ -603,15 +609,17 @@ func TestCollector_CollectMixedModeAllowsNilContext(t *testing.T) {
609
require.NoError(t, collr.Init(context.Background()))
610
require.NoError(t, collr.Check(context.Background()))
611
606
- got := collr.Collect(nil)
612
+ got := collr.Collect(context.Background())
613
614
assert.Equal(t, map[string]int64{
615
"snmp_device_prof_test_stats_errors_processing_scalar": 0,
616
"snmp_device_prof_test_stats_errors_processing_table": 0,
617
"snmp_device_prof_test_stats_errors_processing_licensing": 0,
618
+ "snmp_device_prof_test_stats_errors_processing_bgp": 0,
619
"snmp_device_prof_test_stats_errors_snmp": 0,
620
"snmp_device_prof_test_stats_metrics_rows": 0,
621
"snmp_device_prof_test_stats_metrics_licensing": 0,
622
+ "snmp_device_prof_test_stats_metrics_bgp": 0,
623
"snmp_device_prof_test_stats_metrics_scalar": 0,
624
"snmp_device_prof_test_stats_metrics_table": 0,
625
"snmp_device_prof_test_stats_metrics_tables": 0,
@@ -627,6 +635,7 @@ func TestCollector_CollectMixedModeAllowsNilContext(t *testing.T) {
635
"snmp_device_prof_test_stats_timings_scalar": 0,
636
"snmp_device_prof_test_stats_timings_table": 0,
637
"snmp_device_prof_test_stats_timings_licensing": 0,
638
+ "snmp_device_prof_test_stats_timings_bgp": 0,
639
"snmp_device_prof_test_stats_timings_virtual": 0,
640
"snmp_device_prof_uptime": 123,
641
"ping_rtt_min": (10 * time.Millisecond).Microseconds(),
src/go/plugin/go.d/collector/snmp/ddsnmp/arista_dell_bgp_profile_test.go
new
+70
@@ -0,0 +1,70 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "slices"
7
+ "strings"
8
+ "testing"
9
+
10
+ "github.com/stretchr/testify/require"
11
+)
12
+
13
+func Test_AristaAndDellBGPProfilesUseTypedRows(t *testing.T) {
14
+ tests := map[string]struct {
15
+ sysObjectID string
16
+ profileFile string
17
+ legacyTables []string
18
+ bgpTables map[string]string
19
+ }{
20
+ "Arista switch": {
21
+ sysObjectID: "1.3.6.1.4.1.30065.1.3011.7010.427.48",
22
+ profileFile: "arista-switch.yaml",
23
+ legacyTables: []string{"bgpPeerTable", "aristaBgp4V2PeerTable", "aristaBgp4V2PeerCountersTable", "aristaBgp4V2PrefixGaugesTable"},
24
+ bgpTables: map[string]string{
25
+ "arista-bgp-peer": "aristaBgp4V2PeerTable",
26
+ "arista-bgp-peer-family": "aristaBgp4V2PrefixGaugesTable",
27
+ },
28
+ },
29
+ "Dell OS10": {
30
+ sysObjectID: "1.3.6.1.4.1.674.11000.5000.100.2.1.1",
31
+ profileFile: "dell-os10.yaml",
32
+ legacyTables: []string{"dell.os10bgp4V2PeerTable", "dell.os10bgp4V2PeerCountersTable", "dell.os10bgp4V2PrefixGaugesTable"},
33
+ bgpTables: map[string]string{
34
+ "dell-os10-bgp-peer": "dell.os10bgp4V2PeerTable",
35
+ "dell-os10-bgp-peer-family": "dell.os10bgp4V2PrefixGaugesTable",
36
+ },
37
+ },
38
+ }
39
+
40
+ for name, tc := range tests {
41
+ t.Run(name, func(t *testing.T) {
42
+ matched := FindProfiles(tc.sysObjectID, "", nil)
43
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
44
+ return strings.HasSuffix(p.SourceFile, tc.profileFile)
45
+ })
46
+ require.NotEqual(t, -1, index, "expected %s profile to match", tc.profileFile)
47
+
48
+ profile := matched[index]
49
+ for _, table := range tc.legacyTables {
50
+ assertNoMetricTable(t, profile, table)
51
+ }
52
+ assertNoVirtualMetric(t, profile, "bgpPeerAvailability")
53
+ assertNoVirtualMetric(t, profile, "bgpPeerUpdates")
54
+ for rowID, table := range tc.bgpTables {
55
+ assertBGPTableForRowID(t, profile, rowID, table)
56
+ }
57
+
58
+ for rowID := range tc.bgpTables {
59
+ if strings.Contains(rowID, "peer-family") {
60
+ continue
61
+ }
62
+ peer := requireBGPRowByID(t, profile, rowID)
63
+ assertBGPSixStateMapping(t, peer.State)
64
+ if peer.Previous.IsSet() {
65
+ assertBGPSixStateMapping(t, peer.Previous)
66
+ }
67
+ }
68
+ })
69
+ }
70
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/bgp_external_fixture_test.go
new
+160
@@ -0,0 +1,160 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "encoding/json"
7
+ "os"
8
+ "path/filepath"
9
+ "slices"
10
+ "strings"
11
+ "testing"
12
+
13
+ "github.com/stretchr/testify/assert"
14
+ "github.com/stretchr/testify/require"
15
+
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
17
+)
18
+
19
+type externalDeviceFixture struct {
20
+ OS struct {
21
+ Discovery struct {
22
+ Devices []struct {
23
+ SysObjectID string `json:"sysObjectID"`
24
+ SysDescr string `json:"sysDescr"`
25
+ } `json:"devices"`
26
+ } `json:"discovery"`
27
+ } `json:"os"`
28
+}
29
+
30
+func Test_LibreNMSBGPIdentityFixtures_MatchTypedProfiles(t *testing.T) {
31
+ tests := map[string]struct {
32
+ fixtureFile string
33
+ profileFile string
34
+ validate func(t *testing.T, profile *Profile)
35
+ }{
36
+ "Cumulus identity matches standard BGP projection": {
37
+ fixtureFile: "cumulus_bgp_identity.json",
38
+ profileFile: "nvidia-cumulus-linux-switch.yaml",
39
+ validate: func(t *testing.T, profile *Profile) {
40
+ require.NotEmpty(t, profile.Definition.BGP)
41
+ assert.True(t, profile.HasExtension("_std-bgp4-mib.yaml"))
42
+
43
+ row := profile.Definition.BGP[0]
44
+ assert.Equal(t, "_std-bgp4-mib.yaml", row.OriginProfileID)
45
+ assert.Equal(t, "bgp4-peer", row.ID)
46
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
47
+ assert.Equal(t, "bgpPeerTable", row.Table.Name)
48
+ assert.Equal(t, "1.3.6.1.2.1.15.3", row.Table.OID)
49
+ },
50
+ },
51
+ "Juniper vMX identity matches Juniper MX BGP profile": {
52
+ fixtureFile: "junos_vmx_identity.json",
53
+ profileFile: "juniper-mx.yaml",
54
+ validate: func(t *testing.T, profile *Profile) {
55
+ require.Len(t, profile.Definition.BGP, 2)
56
+
57
+ peer := requireBGPRowByID(t, profile, "juniper-bgp-peer")
58
+ assert.Equal(t, "_juniper-bgp4-v2.yaml", peer.OriginProfileID)
59
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, peer.Kind)
60
+ assert.Equal(t, "jnxBgpM2PeerTable", peer.Table.Name)
61
+ assert.Equal(t, "jnxBgpM2PeerCountersTable", peer.Traffic.Updates.Received.Table)
62
+
63
+ family := requireBGPRowByID(t, profile, "juniper-bgp-peer-family")
64
+ assert.Equal(t, "_juniper-bgp4-v2.yaml", family.OriginProfileID)
65
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, family.Kind)
66
+ assert.Equal(t, "jnxBgpM2PrefixCountersTable", family.Table.Name)
67
+ assert.Equal(t, "jnxBgpM2PeerTable", family.Identity.RemoteAS.Table)
68
+ assert.Equal(t, "jnxBgpM2PeerIndex", family.Identity.RemoteAS.LookupSymbol.Name)
69
+ },
70
+ },
71
+ "TiMOS IXR-S identity matches Nokia SR OS BGP profile": {
72
+ fixtureFile: "timos_ixr_s_identity.json",
73
+ profileFile: "nokia-service-router-os.yaml",
74
+ validate: func(t *testing.T, profile *Profile) {
75
+ require.Len(t, profile.Definition.BGP, 7)
76
+ assert.True(t, profile.HasExtension("_nokia-timetra-bgp.yaml"))
77
+ assert.False(t, profile.HasExtension("_std-bgp4-mib.yaml"))
78
+
79
+ peer := requireBGPRowByID(t, profile, "nokia-timos-bgp-peer")
80
+ assert.Equal(t, "_nokia-timetra-bgp.yaml", peer.OriginProfileID)
81
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, peer.Kind)
82
+ assert.Equal(t, "tBgpPeerNgTable", peer.Table.Name)
83
+ assert.Equal(t, "tBgpPeerNgOperTable", peer.Connection.EstablishedUptime.Table)
84
+
85
+ family := requireBGPRowByID(t, profile, "nokia-timos-bgp-peer-family-ipv4-vpn")
86
+ assert.Equal(t, "_nokia-timetra-bgp.yaml", family.OriginProfileID)
87
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, family.Kind)
88
+ assert.Equal(t, "tBgpPeerNgOperTable", family.Table.Name)
89
+ assert.Equal(t, "tBgpPeerNgTable", family.Identity.RemoteAS.Table)
90
+ },
91
+ },
92
+ "Cisco IOS XR NCS 540 identity matches Cisco NCS BGP profile": {
93
+ fixtureFile: "iosxr_ncs540_identity.json",
94
+ profileFile: "cisco-ncs.yaml",
95
+ validate: func(t *testing.T, profile *Profile) {
96
+ require.Len(t, profile.Definition.BGP, 2)
97
+ assert.True(t, profile.HasExtension("_cisco-bgp4-mib.yaml"))
98
+
99
+ row := requireBGPRowByID(t, profile, "cisco-bgp-peer-family")
100
+ assert.Equal(t, "_cisco-bgp4-mib.yaml", row.OriginProfileID)
101
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, row.Kind)
102
+ assert.Equal(t, "cbgpPeer2AddrFamilyPrefixTable", row.Table.Name)
103
+ assert.Equal(t, "cbgpPeer2Table", row.Identity.RemoteAS.Table)
104
+ },
105
+ },
106
+ }
107
+
108
+ for name, tc := range tests {
109
+ t.Run(name, func(t *testing.T) {
110
+ profile := matchedProfileFromIdentityFixture(t, tc.fixtureFile, tc.profileFile)
111
+ tc.validate(t, profile)
112
+ })
113
+ }
114
+}
115
+
116
+func matchedProfileFromIdentityFixture(t *testing.T, fixtureFile, profileFile string) *Profile {
117
+ t.Helper()
118
+
119
+ path := filepath.Join("testdata", "librenms", fixtureFile)
120
+ data, err := os.ReadFile(path)
121
+ require.NoError(t, err)
122
+
123
+ var fixture externalDeviceFixture
124
+ require.NoError(t, json.Unmarshal(data, &fixture))
125
+ require.Len(t, fixture.OS.Discovery.Devices, 1)
126
+
127
+ device := fixture.OS.Discovery.Devices[0]
128
+ sysObjectID := strings.TrimPrefix(device.SysObjectID, ".")
129
+ matched := FindProfiles(sysObjectID, device.SysDescr, nil)
130
+
131
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
132
+ return strings.HasSuffix(p.SourceFile, profileFile)
133
+ })
134
+ require.NotEqual(t, -1, index, "expected %s profile to match", profileFile)
135
+
136
+ return matched[index]
137
+}
138
+
139
+func requireBGPRowByID(t *testing.T, profile *Profile, id string) ddprofiledefinition.BGPConfig {
140
+ t.Helper()
141
+
142
+ rowIndex := slices.IndexFunc(profile.Definition.BGP, func(row ddprofiledefinition.BGPConfig) bool {
143
+ return row.ID == id
144
+ })
145
+ require.NotEqual(t, -1, rowIndex, "expected typed BGP row %s", id)
146
+
147
+ return profile.Definition.BGP[rowIndex]
148
+}
149
+
150
+func assertBGPSixStateMapping(t *testing.T, state ddprofiledefinition.BGPStateConfig) {
151
+ t.Helper()
152
+ assert.Equal(t, map[string]string{
153
+ "1": "idle",
154
+ "2": "connect",
155
+ "3": "active",
156
+ "4": "opensent",
157
+ "5": "openconfirm",
158
+ "6": "established",
159
+ }, state.Symbol.Mapping.Items)
160
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/bgp_virtual_metric_profile_test.go
new
+42
@@ -0,0 +1,42 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/require"
12
+
13
+ "github.com/netdata/netdata/go/plugins/pkg/multipath"
14
+)
15
+
16
+func Test_AlcatelBGPProfileUsesTypedRows(t *testing.T) {
17
+ dir, _ := filepath.Abs("../../../config/go.d/snmp.profiles/default")
18
+
19
+ profiles, err := loadProfilesFromDir(dir, multipath.New(dir))
20
+ require.NoError(t, err)
21
+ require.NotEmpty(t, profiles)
22
+
23
+ matched := FindProfiles("1.3.6.1.4.1.6486.801.1.1.2.1.1", "", nil)
24
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
25
+ return strings.HasSuffix(p.SourceFile, "alcatel-lucent-ent.yaml")
26
+ })
27
+ require.NotEqual(t, -1, index, "expected alcatel-lucent-ent.yaml profile to match")
28
+
29
+ profile := matched[index]
30
+ for _, table := range []string{"bgpPeerTable", "alaBgpPeerTable", "alaBgpPeer6Table"} {
31
+ assertNoMetricTable(t, profile, table)
32
+ }
33
+ for _, name := range []string{"bgpPeerAvailability", "bgpPeerUpdates", "alcatel.bgpPeerAvailability", "alcatel.bgpPeerUpdates"} {
34
+ assertNoVirtualMetric(t, profile, name)
35
+ }
36
+ assertBGPTableForRowID(t, profile, "alcatel-bgp4-peer", "bgpPeerTable")
37
+ assertBGPTableForRowID(t, profile, "alcatel-bgp4-peer-family", "alaBgpPeerTable")
38
+ assertBGPTableForRowID(t, profile, "alcatel-bgp6-peer", "alaBgpPeer6Table")
39
+ assertBGPTableForRowID(t, profile, "alcatel-bgp6-peer-family", "alaBgpPeer6Table")
40
+ assertBGPSixStateMapping(t, requireBGPRowByID(t, profile, "alcatel-bgp4-peer").State)
41
+ assertBGPSixStateMapping(t, requireBGPRowByID(t, profile, "alcatel-bgp6-peer").State)
42
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/cisco_bgp_external_reference_test.go
new
+61
@@ -0,0 +1,61 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/pkg/multipath"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
16
+)
17
+
18
+func Test_CiscoBgpPeer3Profile_AlignedWithOfficialMIBAndCheckmk(t *testing.T) {
19
+ dir, err := filepath.Abs("../../../config/go.d/snmp.profiles/default")
20
+ require.NoError(t, err)
21
+
22
+ profiles, err := loadProfilesFromDir(dir, multipath.New(dir))
23
+ require.NoError(t, err)
24
+ require.NotEmpty(t, profiles)
25
+
26
+ matched := FindProfiles("1.3.6.1.4.1.9.1.923", "", nil) // ciscoASR1002
27
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
28
+ return strings.HasSuffix(p.SourceFile, "cisco-asr.yaml")
29
+ })
30
+ require.NotEqual(t, -1, index, "expected cisco-asr profile to match")
31
+
32
+ profile := matched[index]
33
+ rowIndex := slices.IndexFunc(profile.Definition.BGP, func(row ddprofiledefinition.BGPConfig) bool {
34
+ return row.Table.Name == "cbgpPeer3Table" && row.Table.OID == "1.3.6.1.4.1.9.9.187.1.2.9"
35
+ })
36
+ require.NotEqual(t, -1, rowIndex, "expected merged Cisco profile to include typed cbgpPeer3Table row")
37
+
38
+ // Official CISCO-BGP4-MIB defines cbgpPeer3Entry indexed by:
39
+ // cbgpPeer3VrfId, cbgpPeer3Type, cbgpPeer3RemoteAddr.
40
+ row := profile.Definition.BGP[rowIndex]
41
+ assert.Equal(t, "cbgpPeer3VrfName", row.Identity.RoutingInstance.Symbol.Name)
42
+ assert.Equal(t, "cbgpPeer3RemoteAddr", row.Identity.Neighbor.Symbol.Name)
43
+ assert.EqualValues(t, 2, row.Descriptors.PeerType.Index)
44
+
45
+ // Checkmk's cisco_bgp_peerv3 section independently uses cbgpPeer3Table for:
46
+ // localAddr, localIdentifier, remoteAs, remoteIdentifier, adminStatus,
47
+ // state, establishedTime, vrfName, and OID-end remoteAddr extraction.
48
+ assert.Equal(t, "cbgpPeer3LocalAddr", row.Descriptors.LocalAddress.Symbol.Name)
49
+ assert.Equal(t, "cbgpPeer3RemoteAs", row.Identity.RemoteAS.Symbol.Name)
50
+ assert.Equal(t, "cbgpPeer3LocalIdentifier", row.Descriptors.LocalIdentifier.Symbol.Name)
51
+ assert.Equal(t, "cbgpPeer3RemoteIdentifier", row.Descriptors.PeerIdentifier.Symbol.Name)
52
+ assert.Equal(t, "cbgpPeer3AdminStatus", row.Admin.Enabled.Symbol.Name)
53
+ assert.Equal(t, "cbgpPeer3State", row.State.Symbol.Name)
54
+ assert.Equal(t, "cbgpPeer3FsmEstablishedTime", row.Connection.EstablishedUptime.Symbol.Name)
55
+
56
+ // Netdata intentionally prefers numeric last-error code/subcode over the
57
+ // vendor-specific text field, so operators get stable alertable metrics.
58
+ assert.Equal(t, "cbgpPeer3LastErrorCode", row.LastError.Code.Symbol.Name)
59
+ assert.Equal(t, "cbgpPeer3LastErrorSubcode", row.LastError.Subcode.Symbol.Name)
60
+ assert.Equal(t, "cbgpPeer3PrevState", row.Previous.Symbol.Name)
61
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/cisco_bgp_profile_test.go
new
+152
@@ -0,0 +1,152 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/pkg/multipath"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
16
+)
17
+
18
+func Test_CiscoBGPProfilesIncludeTypedRows(t *testing.T) {
19
+ tests := map[string]struct {
20
+ sysObjectID string
21
+ profileFile string
22
+ tableName string
23
+ tableOID string
24
+ validate func(t *testing.T, profile *Profile, row ddprofiledefinition.BGPConfig)
25
+ }{
26
+ "ASR has Cisco peer row": {
27
+ sysObjectID: "1.3.6.1.4.1.9.1.923", // ciscoASR1002
28
+ profileFile: "cisco-asr.yaml",
29
+ tableName: "cbgpPeer3Table",
30
+ tableOID: "1.3.6.1.4.1.9.9.187.1.2.9",
31
+ validate: func(t *testing.T, _ *Profile, row ddprofiledefinition.BGPConfig) {
32
+ assert.Equal(t, "_cisco-bgp4-mib.yaml", row.OriginProfileID)
33
+ assert.Equal(t, "cisco-bgp-peer", row.ID)
34
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
35
+ assert.Equal(t, "cbgpPeer3RemoteAddr", row.Identity.Neighbor.Symbol.Name)
36
+ assert.Equal(t, "cbgpPeer3RemoteAs", row.Identity.RemoteAS.Symbol.Name)
37
+ assert.Equal(t, "cbgpPeer3AdminStatus", row.Admin.Enabled.Symbol.Name)
38
+ assert.Equal(t, "cbgpPeer3State", row.State.Symbol.Name)
39
+ assert.Equal(t, "cbgpPeer3InUpdates", row.Traffic.Updates.Received.Symbol.Name)
40
+ assert.Equal(t, "cbgpPeer3OutTotalMessages", row.Traffic.Messages.Sent.Symbol.Name)
41
+ assert.Equal(t, "cbgpPeer3LastErrorCode", row.LastError.Code.Symbol.Name)
42
+ assert.Equal(t, "cbgpPeer3LastErrorSubcode", row.LastError.Subcode.Symbol.Name)
43
+ assert.Equal(t, "cbgpPeer3FsmEstablishedTransitions", row.Transitions.Established.Symbol.Name)
44
+ assert.Equal(t, "cbgpPeer3FsmEstablishedTime", row.Connection.EstablishedUptime.Symbol.Name)
45
+ assert.Equal(t, "cbgpPeer3InUpdateElapsedTime", row.Connection.LastReceivedUpdateAge.Symbol.Name)
46
+ assert.Equal(t, "cbgpPeer3PrevState", row.Previous.Symbol.Name)
47
+ },
48
+ },
49
+ "ASR has Cisco peer-family row": {
50
+ sysObjectID: "1.3.6.1.4.1.9.1.923", // ciscoASR1002
51
+ profileFile: "cisco-asr.yaml",
52
+ tableName: "cbgpPeer2AddrFamilyPrefixTable",
53
+ tableOID: "1.3.6.1.4.1.9.9.187.1.2.8",
54
+ validate: func(t *testing.T, _ *Profile, row ddprofiledefinition.BGPConfig) {
55
+ assert.Equal(t, "_cisco-bgp4-mib.yaml", row.OriginProfileID)
56
+ assert.Equal(t, "cisco-bgp-peer-family", row.ID)
57
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, row.Kind)
58
+ assert.Equal(t, "cbgpPeer2RemoteAs", row.Identity.RemoteAS.Symbol.Name)
59
+ assert.Equal(t, "cbgpPeer2Table", row.Identity.RemoteAS.Table)
60
+ assert.Equal(t, "cbgpPeer2AcceptedPrefixes", row.Routes.Current.Accepted.Symbol.Name)
61
+ assert.Equal(t, "cbgpPeer2DeniedPrefixes", row.Routes.Current.Rejected.Symbol.Name)
62
+ assert.Equal(t, "cbgpPeer2PrefixAdminLimit", row.RouteLimits.Limit.Symbol.Name)
63
+ assert.Equal(t, "cbgpPeer2PrefixThreshold", row.RouteLimits.Threshold.Symbol.Name)
64
+ assert.Equal(t, "cbgpPeer2PrefixClearThreshold", row.RouteLimits.ClearThreshold.Symbol.Name)
65
+ assert.Equal(t, "cbgpPeer2AdvertisedPrefixes", row.Routes.Current.Advertised.Symbol.Name)
66
+ assert.Equal(t, "cbgpPeer2SuppressedPrefixes", row.Routes.Current.Suppressed.Symbol.Name)
67
+ assert.Equal(t, "cbgpPeer2WithdrawnPrefixes", row.Routes.Current.Withdrawn.Symbol.Name)
68
+ assert.Equal(t, uint(2), row.Identity.AddressFamily.IndexFromEnd)
69
+ assert.Equal(t, uint(1), row.Identity.SubsequentAddressFamily.IndexFromEnd)
70
+ },
71
+ },
72
+ "Catalyst has Cisco peer row": {
73
+ sysObjectID: "1.3.6.1.4.1.9.1.2435", // catC930024T
74
+ profileFile: "cisco-catalyst.yaml",
75
+ tableName: "cbgpPeer3Table",
76
+ tableOID: "1.3.6.1.4.1.9.9.187.1.2.9",
77
+ validate: func(t *testing.T, profile *Profile, row ddprofiledefinition.BGPConfig) {
78
+ assert.True(t, profile.HasExtension("_cisco-bgp4-mib.yaml"))
79
+ assert.Equal(t, "_cisco-bgp4-mib.yaml", row.OriginProfileID)
80
+ },
81
+ },
82
+ }
83
+
84
+ dir, err := filepath.Abs("../../../config/go.d/snmp.profiles/default")
85
+ require.NoError(t, err)
86
+
87
+ profiles, err := loadProfilesFromDir(dir, multipath.New(dir))
88
+ require.NoError(t, err)
89
+ require.NotEmpty(t, profiles)
90
+
91
+ for name, tc := range tests {
92
+ t.Run(name, func(t *testing.T) {
93
+ profile := matchedProfileByFileForTest(t, tc.sysObjectID, tc.profileFile)
94
+ row := requireBGPRowByTable(t, profile, tc.tableName, tc.tableOID)
95
+ tc.validate(t, profile, row)
96
+ })
97
+ }
98
+}
99
+
100
+func Test_CiscoGenericProfilesDoNotInheritBGP(t *testing.T) {
101
+ tests := map[string]struct {
102
+ sysObjectID string
103
+ profileFile string
104
+ }{
105
+ "generic Cisco profile has no BGP rows": {
106
+ sysObjectID: "1.3.6.1.4.1.9.1",
107
+ profileFile: "cisco.yaml",
108
+ },
109
+ "Cisco base mixin has no BGP rows": {
110
+ sysObjectID: "1.3.6.1.4.1.9.1",
111
+ profileFile: "_cisco-base.yaml",
112
+ },
113
+ }
114
+
115
+ dir, err := filepath.Abs("../../../config/go.d/snmp.profiles/default")
116
+ require.NoError(t, err)
117
+
118
+ _, err = loadProfilesFromDir(dir, multipath.New(dir))
119
+ require.NoError(t, err)
120
+
121
+ for name, tc := range tests {
122
+ t.Run(name, func(t *testing.T) {
123
+ profile, err := loadProfile(filepath.Join(dir, tc.profileFile), multipath.New(dir))
124
+ require.NoError(t, err)
125
+ require.True(t, strings.HasSuffix(profile.SourceFile, tc.profileFile))
126
+ assert.Empty(t, profile.Definition.BGP)
127
+ })
128
+ }
129
+}
130
+
131
+func matchedProfileByFileForTest(t *testing.T, sysObjectID, profileFile string) *Profile {
132
+ t.Helper()
133
+
134
+ matched := FindProfiles(sysObjectID, "", nil)
135
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
136
+ return strings.HasSuffix(p.SourceFile, profileFile)
137
+ })
138
+ require.NotEqual(t, -1, index, "expected %s profile to match", profileFile)
139
+
140
+ return matched[index]
141
+}
142
+
143
+func requireBGPRowByTable(t *testing.T, profile *Profile, tableName, tableOID string) ddprofiledefinition.BGPConfig {
144
+ t.Helper()
145
+
146
+ rowIndex := slices.IndexFunc(profile.Definition.BGP, func(row ddprofiledefinition.BGPConfig) bool {
147
+ return row.Table.Name == tableName && row.Table.OID == tableOID
148
+ })
149
+ require.NotEqual(t, -1, rowIndex, "expected merged profile to include typed %s row", tableName)
150
+
151
+ return profile.Definition.BGP[rowIndex]
152
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/bgp.go
new
+674
@@ -0,0 +1,674 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddprofiledefinition
4
+
5
+import (
6
+ "slices"
7
+ "strings"
8
+)
9
+
10
+type BGPRowKind string
11
+
12
+const (
13
+ BGPRowKindDevice BGPRowKind = "device"
14
+ BGPRowKindPeer BGPRowKind = "peer"
15
+ BGPRowKindPeerFamily BGPRowKind = "peer_family"
16
+)
17
+
18
+var validBGPRowKinds = map[BGPRowKind]struct{}{
19
+ BGPRowKindDevice: {},
20
+ BGPRowKindPeer: {},
21
+ BGPRowKindPeerFamily: {},
22
+}
23
+
24
+func IsValidBGPRowKind(kind BGPRowKind) bool {
25
+ _, ok := validBGPRowKinds[kind]
26
+ return ok
27
+}
28
+
29
+type BGPPeerState string
30
+
31
+const (
32
+ BGPPeerStateNone BGPPeerState = "none"
33
+ BGPPeerStateIdle BGPPeerState = "idle"
34
+ BGPPeerStateConnect BGPPeerState = "connect"
35
+ BGPPeerStateActive BGPPeerState = "active"
36
+ BGPPeerStateOpenSent BGPPeerState = "opensent"
37
+ BGPPeerStateOpenConfirm BGPPeerState = "openconfirm"
38
+ BGPPeerStateEstablished BGPPeerState = "established"
39
+)
40
+
41
+var validBGPPeerStates = map[BGPPeerState]struct{}{
42
+ BGPPeerStateNone: {},
43
+ BGPPeerStateIdle: {},
44
+ BGPPeerStateConnect: {},
45
+ BGPPeerStateActive: {},
46
+ BGPPeerStateOpenSent: {},
47
+ BGPPeerStateOpenConfirm: {},
48
+ BGPPeerStateEstablished: {},
49
+}
50
+
51
+var requiredBGPPeerStates = []BGPPeerState{
52
+ BGPPeerStateIdle,
53
+ BGPPeerStateConnect,
54
+ BGPPeerStateActive,
55
+ BGPPeerStateOpenSent,
56
+ BGPPeerStateOpenConfirm,
57
+ BGPPeerStateEstablished,
58
+}
59
+
60
+func IsValidBGPPeerState(state BGPPeerState) bool {
61
+ _, ok := validBGPPeerStates[state]
62
+ return ok
63
+}
64
+
65
+type BGPAddressFamily string
66
+
67
+const (
68
+ BGPAddressFamilyIPv4 BGPAddressFamily = "ipv4"
69
+ BGPAddressFamilyIPv6 BGPAddressFamily = "ipv6"
70
+ BGPAddressFamilyL2VPN BGPAddressFamily = "l2vpn"
71
+)
72
+
73
+var validBGPAddressFamilies = map[BGPAddressFamily]struct{}{
74
+ BGPAddressFamilyIPv4: {},
75
+ BGPAddressFamilyIPv6: {},
76
+ BGPAddressFamilyL2VPN: {},
77
+}
78
+
79
+func IsValidBGPAddressFamily(family BGPAddressFamily) bool {
80
+ _, ok := validBGPAddressFamilies[family]
81
+ return ok
82
+}
83
+
84
+type BGPSubsequentAddressFamily string
85
+
86
+const (
87
+ BGPSubsequentAddressFamilyUnicast BGPSubsequentAddressFamily = "unicast"
88
+ BGPSubsequentAddressFamilyMulticast BGPSubsequentAddressFamily = "multicast"
89
+ BGPSubsequentAddressFamilyLabeledUnicast BGPSubsequentAddressFamily = "labeled_unicast"
90
+ BGPSubsequentAddressFamilyMVPN BGPSubsequentAddressFamily = "mvpn"
91
+ BGPSubsequentAddressFamilyVPLS BGPSubsequentAddressFamily = "vpls"
92
+ BGPSubsequentAddressFamilyEVPN BGPSubsequentAddressFamily = "evpn"
93
+ BGPSubsequentAddressFamilyVPN BGPSubsequentAddressFamily = "vpn"
94
+ BGPSubsequentAddressFamilyRTFilter BGPSubsequentAddressFamily = "rtfilter"
95
+ BGPSubsequentAddressFamilyFlow BGPSubsequentAddressFamily = "flow"
96
+)
97
+
98
+var validBGPSubsequentAddressFamilies = map[BGPSubsequentAddressFamily]struct{}{
99
+ BGPSubsequentAddressFamilyUnicast: {},
100
+ BGPSubsequentAddressFamilyMulticast: {},
101
+ BGPSubsequentAddressFamilyLabeledUnicast: {},
102
+ BGPSubsequentAddressFamilyMVPN: {},
103
+ BGPSubsequentAddressFamilyVPLS: {},
104
+ BGPSubsequentAddressFamilyEVPN: {},
105
+ BGPSubsequentAddressFamilyVPN: {},
106
+ BGPSubsequentAddressFamilyRTFilter: {},
107
+ BGPSubsequentAddressFamilyFlow: {},
108
+}
109
+
110
+func IsValidBGPSubsequentAddressFamily(family BGPSubsequentAddressFamily) bool {
111
+ _, ok := validBGPSubsequentAddressFamilies[family]
112
+ return ok
113
+}
114
+
115
+type BGPConfig struct {
116
+ OriginProfileID string `yaml:"-" json:"-"`
117
+ ID string `yaml:"id,omitempty" json:"id,omitempty"`
118
+ MIB string `yaml:"MIB,omitempty" json:"MIB,omitempty"`
119
+ Kind BGPRowKind `yaml:"kind,omitempty" json:"kind,omitempty"`
120
+ Table SymbolConfig `yaml:"table,omitempty" json:"table"`
121
+
122
+ Identity BGPIdentityConfig `yaml:"identity,omitempty" json:"identity"`
123
+ Descriptors BGPDescriptorsConfig `yaml:"descriptors,omitempty" json:"descriptors"`
124
+ Admin BGPAdminConfig `yaml:"admin,omitempty" json:"admin"`
125
+ State BGPStateConfig `yaml:"state,omitempty" json:"state"`
126
+ Previous BGPStateConfig `yaml:"previous_state,omitempty" json:"previous_state"`
127
+ Connection BGPConnectionConfig `yaml:"connection,omitempty" json:"connection"`
128
+ Traffic BGPTrafficConfig `yaml:"traffic,omitempty" json:"traffic"`
129
+ Transitions BGPTransitionsConfig `yaml:"transitions,omitempty" json:"transitions"`
130
+ Timers BGPTimersConfig `yaml:"timers,omitempty" json:"timers"`
131
+ LastError BGPLastErrorConfig `yaml:"last_error,omitempty" json:"last_error"`
132
+ LastNotify BGPLastNotifyConfig `yaml:"last_notifications,omitempty" json:"last_notifications"`
133
+ Reasons BGPReasonsConfig `yaml:"reasons,omitempty" json:"reasons"`
134
+ Restart BGPGracefulRestartConfig `yaml:"graceful_restart,omitempty" json:"graceful_restart"`
135
+ Routes BGPRoutesConfig `yaml:"routes,omitempty" json:"routes"`
136
+ RouteLimits BGPRouteLimitsConfig `yaml:"route_limits,omitempty" json:"route_limits"`
137
+ Device BGPDeviceCountsConfig `yaml:"device_counts,omitempty" json:"device_counts"`
138
+
139
+ StaticTags []StaticMetricTagConfig `yaml:"static_tags,omitempty" json:"-"`
140
+ MetricTags MetricTagConfigList `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
141
+}
142
+
143
+func (c BGPConfig) Clone() BGPConfig {
144
+ return BGPConfig{
145
+ OriginProfileID: c.OriginProfileID,
146
+ ID: c.ID,
147
+ MIB: c.MIB,
148
+ Kind: c.Kind,
149
+ Table: c.Table.Clone(),
150
+ Identity: c.Identity.Clone(),
151
+ Descriptors: c.Descriptors.Clone(),
152
+ Admin: c.Admin.Clone(),
153
+ State: c.State.Clone(),
154
+ Previous: c.Previous.Clone(),
155
+ Connection: c.Connection.Clone(),
156
+ Traffic: c.Traffic.Clone(),
157
+ Transitions: c.Transitions.Clone(),
158
+ Timers: c.Timers.Clone(),
159
+ LastError: c.LastError.Clone(),
160
+ LastNotify: c.LastNotify.Clone(),
161
+ Reasons: c.Reasons.Clone(),
162
+ Restart: c.Restart.Clone(),
163
+ Routes: c.Routes.Clone(),
164
+ RouteLimits: c.RouteLimits.Clone(),
165
+ Device: c.Device.Clone(),
166
+ StaticTags: slices.Clone(c.StaticTags),
167
+ MetricTags: cloneSlice(c.MetricTags),
168
+ }
169
+}
170
+
171
+type BGPIdentityConfig struct {
172
+ RoutingInstance BGPValueConfig `yaml:"routing_instance,omitempty" json:"routing_instance"`
173
+ Neighbor BGPValueConfig `yaml:"neighbor,omitempty" json:"neighbor"`
174
+ RemoteAS BGPValueConfig `yaml:"remote_as,omitempty" json:"remote_as"`
175
+ AddressFamily BGPAddressFamilyValueConfig `yaml:"address_family,omitempty" json:"address_family"`
176
+ SubsequentAddressFamily BGPSubsequentAddressFamilyValueConfig `yaml:"subsequent_address_family,omitempty" json:"subsequent_address_family"`
177
+}
178
+
179
+func (c BGPIdentityConfig) Clone() BGPIdentityConfig {
180
+ return BGPIdentityConfig{
181
+ RoutingInstance: c.RoutingInstance.Clone(),
182
+ Neighbor: c.Neighbor.Clone(),
183
+ RemoteAS: c.RemoteAS.Clone(),
184
+ AddressFamily: c.AddressFamily.Clone(),
185
+ SubsequentAddressFamily: c.SubsequentAddressFamily.Clone(),
186
+ }
187
+}
188
+
189
+type BGPDescriptorsConfig struct {
190
+ LocalAddress BGPValueConfig `yaml:"local_address,omitempty" json:"local_address"`
191
+ LocalAS BGPValueConfig `yaml:"local_as,omitempty" json:"local_as"`
192
+ LocalIdentifier BGPValueConfig `yaml:"local_identifier,omitempty" json:"local_identifier"`
193
+ PeerIdentifier BGPValueConfig `yaml:"peer_identifier,omitempty" json:"peer_identifier"`
194
+ PeerType BGPValueConfig `yaml:"peer_type,omitempty" json:"peer_type"`
195
+ BGPVersion BGPValueConfig `yaml:"bgp_version,omitempty" json:"bgp_version"`
196
+ Description BGPValueConfig `yaml:"description,omitempty" json:"description"`
197
+}
198
+
199
+func (c BGPDescriptorsConfig) Clone() BGPDescriptorsConfig {
200
+ return BGPDescriptorsConfig{
201
+ LocalAddress: c.LocalAddress.Clone(),
202
+ LocalAS: c.LocalAS.Clone(),
203
+ LocalIdentifier: c.LocalIdentifier.Clone(),
204
+ PeerIdentifier: c.PeerIdentifier.Clone(),
205
+ PeerType: c.PeerType.Clone(),
206
+ BGPVersion: c.BGPVersion.Clone(),
207
+ Description: c.Description.Clone(),
208
+ }
209
+}
210
+
211
+type BGPStateConfig struct {
212
+ BGPValueConfig `yaml:",inline" json:",inline"`
213
+ Partial bool `yaml:"partial,omitempty" json:"partial,omitempty"`
214
+ PartialStates []BGPPeerState `yaml:"partial_states,omitempty" json:"partial_states,omitempty"`
215
+}
216
+
217
+func (c BGPStateConfig) Clone() BGPStateConfig {
218
+ return BGPStateConfig{
219
+ BGPValueConfig: c.BGPValueConfig.Clone(),
220
+ Partial: c.Partial,
221
+ PartialStates: slices.Clone(c.PartialStates),
222
+ }
223
+}
224
+
225
+type BGPAdminConfig struct {
226
+ Enabled BGPValueConfig `yaml:"enabled,omitempty" json:"enabled"`
227
+}
228
+
229
+func (c BGPAdminConfig) Clone() BGPAdminConfig {
230
+ return BGPAdminConfig{Enabled: c.Enabled.Clone()}
231
+}
232
+
233
+type BGPConnectionConfig struct {
234
+ EstablishedUptime BGPValueConfig `yaml:"established_uptime,omitempty" json:"established_uptime"`
235
+ LastReceivedUpdateAge BGPValueConfig `yaml:"last_received_update_age,omitempty" json:"last_received_update_age"`
236
+}
237
+
238
+func (c BGPConnectionConfig) Clone() BGPConnectionConfig {
239
+ return BGPConnectionConfig{
240
+ EstablishedUptime: c.EstablishedUptime.Clone(),
241
+ LastReceivedUpdateAge: c.LastReceivedUpdateAge.Clone(),
242
+ }
243
+}
244
+
245
+type BGPDirectionalConfig struct {
246
+ Received BGPValueConfig `yaml:"received,omitempty" json:"received"`
247
+ Sent BGPValueConfig `yaml:"sent,omitempty" json:"sent"`
248
+}
249
+
250
+func (c BGPDirectionalConfig) Clone() BGPDirectionalConfig {
251
+ return BGPDirectionalConfig{
252
+ Received: c.Received.Clone(),
253
+ Sent: c.Sent.Clone(),
254
+ }
255
+}
256
+
257
+type BGPTrafficConfig struct {
258
+ Messages BGPDirectionalConfig `yaml:"messages,omitempty" json:"messages"`
259
+ Updates BGPDirectionalConfig `yaml:"updates,omitempty" json:"updates"`
260
+ Notifications BGPDirectionalConfig `yaml:"notifications,omitempty" json:"notifications"`
261
+ RouteRefreshes BGPDirectionalConfig `yaml:"route_refreshes,omitempty" json:"route_refreshes"`
262
+ Opens BGPDirectionalConfig `yaml:"opens,omitempty" json:"opens"`
263
+ Keepalives BGPDirectionalConfig `yaml:"keepalives,omitempty" json:"keepalives"`
264
+}
265
+
266
+func (c BGPTrafficConfig) Clone() BGPTrafficConfig {
267
+ return BGPTrafficConfig{
268
+ Messages: c.Messages.Clone(),
269
+ Updates: c.Updates.Clone(),
270
+ Notifications: c.Notifications.Clone(),
271
+ RouteRefreshes: c.RouteRefreshes.Clone(),
272
+ Opens: c.Opens.Clone(),
273
+ Keepalives: c.Keepalives.Clone(),
274
+ }
275
+}
276
+
277
+type BGPTransitionsConfig struct {
278
+ Established BGPValueConfig `yaml:"established,omitempty" json:"established"`
279
+ Down BGPValueConfig `yaml:"down,omitempty" json:"down"`
280
+ Up BGPValueConfig `yaml:"up,omitempty" json:"up"`
281
+ Flaps BGPValueConfig `yaml:"flaps,omitempty" json:"flaps"`
282
+}
283
+
284
+func (c BGPTransitionsConfig) Clone() BGPTransitionsConfig {
285
+ return BGPTransitionsConfig{
286
+ Established: c.Established.Clone(),
287
+ Down: c.Down.Clone(),
288
+ Up: c.Up.Clone(),
289
+ Flaps: c.Flaps.Clone(),
290
+ }
291
+}
292
+
293
+type BGPTimersConfig struct {
294
+ Negotiated BGPTimerPairConfig `yaml:"negotiated,omitempty" json:"negotiated"`
295
+ Configured BGPTimerPairConfig `yaml:"configured,omitempty" json:"configured"`
296
+}
297
+
298
+func (c BGPTimersConfig) Clone() BGPTimersConfig {
299
+ return BGPTimersConfig{
300
+ Negotiated: c.Negotiated.Clone(),
301
+ Configured: c.Configured.Clone(),
302
+ }
303
+}
304
+
305
+type BGPTimerPairConfig struct {
306
+ ConnectRetry BGPValueConfig `yaml:"connect_retry,omitempty" json:"connect_retry"`
307
+ HoldTime BGPValueConfig `yaml:"hold_time,omitempty" json:"hold_time"`
308
+ KeepaliveTime BGPValueConfig `yaml:"keepalive_time,omitempty" json:"keepalive_time"`
309
+ MinASOriginationInterval BGPValueConfig `yaml:"min_as_origination_interval,omitempty" json:"min_as_origination_interval"`
310
+ MinRouteAdvertisementInterval BGPValueConfig `yaml:"min_route_advertisement_interval,omitempty" json:"min_route_advertisement_interval"`
311
+}
312
+
313
+func (c BGPTimerPairConfig) Clone() BGPTimerPairConfig {
314
+ return BGPTimerPairConfig{
315
+ ConnectRetry: c.ConnectRetry.Clone(),
316
+ HoldTime: c.HoldTime.Clone(),
317
+ KeepaliveTime: c.KeepaliveTime.Clone(),
318
+ MinASOriginationInterval: c.MinASOriginationInterval.Clone(),
319
+ MinRouteAdvertisementInterval: c.MinRouteAdvertisementInterval.Clone(),
320
+ }
321
+}
322
+
323
+type BGPLastErrorConfig struct {
324
+ Code BGPValueConfig `yaml:"code,omitempty" json:"code"`
325
+ Subcode BGPValueConfig `yaml:"subcode,omitempty" json:"subcode"`
326
+}
327
+
328
+func (c BGPLastErrorConfig) Clone() BGPLastErrorConfig {
329
+ return BGPLastErrorConfig{
330
+ Code: c.Code.Clone(),
331
+ Subcode: c.Subcode.Clone(),
332
+ }
333
+}
334
+
335
+type BGPLastNotificationConfig struct {
336
+ Code BGPValueConfig `yaml:"code,omitempty" json:"code"`
337
+ Subcode BGPValueConfig `yaml:"subcode,omitempty" json:"subcode"`
338
+ Reason BGPValueConfig `yaml:"reason,omitempty" json:"reason"`
339
+}
340
+
341
+func (c BGPLastNotificationConfig) Clone() BGPLastNotificationConfig {
342
+ return BGPLastNotificationConfig{
343
+ Code: c.Code.Clone(),
344
+ Subcode: c.Subcode.Clone(),
345
+ Reason: c.Reason.Clone(),
346
+ }
347
+}
348
+
349
+type BGPLastNotifyConfig struct {
350
+ Received BGPLastNotificationConfig `yaml:"received,omitempty" json:"received"`
351
+ Sent BGPLastNotificationConfig `yaml:"sent,omitempty" json:"sent"`
352
+}
353
+
354
+func (c BGPLastNotifyConfig) Clone() BGPLastNotifyConfig {
355
+ return BGPLastNotifyConfig{
356
+ Received: c.Received.Clone(),
357
+ Sent: c.Sent.Clone(),
358
+ }
359
+}
360
+
361
+type BGPReasonsConfig struct {
362
+ LastDown BGPValueConfig `yaml:"last_down,omitempty" json:"last_down"`
363
+ Unavailability BGPValueConfig `yaml:"unavailability,omitempty" json:"unavailability"`
364
+}
365
+
366
+func (c BGPReasonsConfig) Clone() BGPReasonsConfig {
367
+ return BGPReasonsConfig{
368
+ LastDown: c.LastDown.Clone(),
369
+ Unavailability: c.Unavailability.Clone(),
370
+ }
371
+}
372
+
373
+type BGPGracefulRestartConfig struct {
374
+ State BGPValueConfig `yaml:"state,omitempty" json:"state"`
375
+}
376
+
377
+func (c BGPGracefulRestartConfig) Clone() BGPGracefulRestartConfig {
378
+ return BGPGracefulRestartConfig{State: c.State.Clone()}
379
+}
380
+
381
+type BGPRoutesConfig struct {
382
+ Current BGPRouteCountersConfig `yaml:"current,omitempty" json:"current"`
383
+ Total BGPRouteCountersConfig `yaml:"total,omitempty" json:"total"`
384
+}
385
+
386
+func (c BGPRoutesConfig) Clone() BGPRoutesConfig {
387
+ return BGPRoutesConfig{
388
+ Current: c.Current.Clone(),
389
+ Total: c.Total.Clone(),
390
+ }
391
+}
392
+
393
+type BGPRouteCountersConfig struct {
394
+ Received BGPValueConfig `yaml:"received,omitempty" json:"received"`
395
+ Accepted BGPValueConfig `yaml:"accepted,omitempty" json:"accepted"`
396
+ Rejected BGPValueConfig `yaml:"rejected,omitempty" json:"rejected"`
397
+ Active BGPValueConfig `yaml:"active,omitempty" json:"active"`
398
+ Advertised BGPValueConfig `yaml:"advertised,omitempty" json:"advertised"`
399
+ Suppressed BGPValueConfig `yaml:"suppressed,omitempty" json:"suppressed"`
400
+ Withdrawn BGPValueConfig `yaml:"withdrawn,omitempty" json:"withdrawn"`
401
+}
402
+
403
+func (c BGPRouteCountersConfig) Clone() BGPRouteCountersConfig {
404
+ return BGPRouteCountersConfig{
405
+ Received: c.Received.Clone(),
406
+ Accepted: c.Accepted.Clone(),
407
+ Rejected: c.Rejected.Clone(),
408
+ Active: c.Active.Clone(),
409
+ Advertised: c.Advertised.Clone(),
410
+ Suppressed: c.Suppressed.Clone(),
411
+ Withdrawn: c.Withdrawn.Clone(),
412
+ }
413
+}
414
+
415
+type BGPRouteLimitsConfig struct {
416
+ Limit BGPValueConfig `yaml:"limit,omitempty" json:"limit"`
417
+ Threshold BGPValueConfig `yaml:"threshold,omitempty" json:"threshold"`
418
+ ClearThreshold BGPValueConfig `yaml:"clear_threshold,omitempty" json:"clear_threshold"`
419
+}
420
+
421
+func (c BGPRouteLimitsConfig) Clone() BGPRouteLimitsConfig {
422
+ return BGPRouteLimitsConfig{
423
+ Limit: c.Limit.Clone(),
424
+ Threshold: c.Threshold.Clone(),
425
+ ClearThreshold: c.ClearThreshold.Clone(),
426
+ }
427
+}
428
+
429
+type BGPDeviceCountsConfig struct {
430
+ Peers BGPValueConfig `yaml:"peers,omitempty" json:"peers"`
431
+ InternalPeers BGPValueConfig `yaml:"ibgp_peers,omitempty" json:"ibgp_peers"`
432
+ ExternalPeers BGPValueConfig `yaml:"ebgp_peers,omitempty" json:"ebgp_peers"`
433
+ States BGPPeerStatesConfig `yaml:"states,omitempty" json:"states"`
434
+}
435
+
436
+func (c BGPDeviceCountsConfig) Clone() BGPDeviceCountsConfig {
437
+ return BGPDeviceCountsConfig{
438
+ Peers: c.Peers.Clone(),
439
+ InternalPeers: c.InternalPeers.Clone(),
440
+ ExternalPeers: c.ExternalPeers.Clone(),
441
+ States: c.States.Clone(),
442
+ }
443
+}
444
+
445
+type BGPPeerStatesConfig struct {
446
+ Idle BGPValueConfig `yaml:"idle,omitempty" json:"idle"`
447
+ Connect BGPValueConfig `yaml:"connect,omitempty" json:"connect"`
448
+ Active BGPValueConfig `yaml:"active,omitempty" json:"active"`
449
+ OpenSent BGPValueConfig `yaml:"opensent,omitempty" json:"opensent"`
450
+ OpenConfirm BGPValueConfig `yaml:"openconfirm,omitempty" json:"openconfirm"`
451
+ Established BGPValueConfig `yaml:"established,omitempty" json:"established"`
452
+}
453
+
454
+func (c BGPPeerStatesConfig) Clone() BGPPeerStatesConfig {
455
+ return BGPPeerStatesConfig{
456
+ Idle: c.Idle.Clone(),
457
+ Connect: c.Connect.Clone(),
458
+ Active: c.Active.Clone(),
459
+ OpenSent: c.OpenSent.Clone(),
460
+ OpenConfirm: c.OpenConfirm.Clone(),
461
+ Established: c.Established.Clone(),
462
+ }
463
+}
464
+
465
+type BGPAddressFamilyValueConfig struct {
466
+ BGPValueConfig `yaml:",inline" json:",inline"`
467
+ AllowPrivate bool `yaml:"allow_private,omitempty" json:"allow_private,omitempty"`
468
+}
469
+
470
+func (c BGPAddressFamilyValueConfig) Clone() BGPAddressFamilyValueConfig {
471
+ return BGPAddressFamilyValueConfig{
472
+ BGPValueConfig: c.BGPValueConfig.Clone(),
473
+ AllowPrivate: c.AllowPrivate,
474
+ }
475
+}
476
+
477
+type BGPSubsequentAddressFamilyValueConfig struct {
478
+ BGPValueConfig `yaml:",inline" json:",inline"`
479
+ AllowPrivate bool `yaml:"allow_private,omitempty" json:"allow_private,omitempty"`
480
+}
481
+
482
+func (c BGPSubsequentAddressFamilyValueConfig) Clone() BGPSubsequentAddressFamilyValueConfig {
483
+ return BGPSubsequentAddressFamilyValueConfig{
484
+ BGPValueConfig: c.BGPValueConfig.Clone(),
485
+ AllowPrivate: c.AllowPrivate,
486
+ }
487
+}
488
+
489
+type BGPValueConfig struct {
490
+ Value string `yaml:"value,omitempty" json:"value,omitempty"`
491
+ From string `yaml:"from,omitempty" json:"from,omitempty"`
492
+ Table string `yaml:"table,omitempty" json:"table,omitempty"`
493
+
494
+ Index uint `yaml:"index,omitempty" json:"index,omitempty"`
495
+ IndexFromEnd uint `yaml:"index_from_end,omitempty" json:"index_from_end,omitempty"`
496
+ IndexTransform []MetricIndexTransform `yaml:"index_transform,omitempty" json:"index_transform,omitempty"`
497
+
498
+ Symbol SymbolConfig `yaml:"symbol,omitempty" json:"symbol"`
499
+ OID string `yaml:"OID,omitempty" json:"OID,omitempty" jsonschema:"-"`
500
+ Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"-"`
501
+
502
+ LookupSymbol SymbolConfigCompat `yaml:"lookup_symbol,omitempty" json:"lookup_symbol"`
503
+
504
+ Format string `yaml:"format,omitempty" json:"format,omitempty"`
505
+ Mapping MappingConfig `yaml:"mapping,omitempty" json:"mapping"`
506
+}
507
+
508
+func (c BGPValueConfig) IsSet() bool {
509
+ return c.Value != "" ||
510
+ c.From != "" ||
511
+ c.Table != "" ||
512
+ c.Index != 0 ||
513
+ c.IndexFromEnd != 0 ||
514
+ len(c.IndexTransform) > 0 ||
515
+ c.Symbol.OID != "" ||
516
+ c.Symbol.Name != "" ||
517
+ c.OID != "" ||
518
+ c.Name != "" ||
519
+ c.LookupSymbol.OID != "" ||
520
+ c.LookupSymbol.Name != "" ||
521
+ c.Format != "" ||
522
+ c.Mapping.HasItems() ||
523
+ c.Mapping.Mode != ""
524
+}
525
+
526
+func (c BGPValueConfig) Clone() BGPValueConfig {
527
+ return BGPValueConfig{
528
+ Value: c.Value,
529
+ From: c.From,
530
+ Table: c.Table,
531
+ Index: c.Index,
532
+ IndexFromEnd: c.IndexFromEnd,
533
+ IndexTransform: slices.Clone(c.IndexTransform),
534
+ Symbol: c.Symbol.Clone(),
535
+ OID: c.OID,
536
+ Name: c.Name,
537
+ LookupSymbol: c.LookupSymbol.Clone(),
538
+ Format: c.Format,
539
+ Mapping: c.Mapping.Clone(),
540
+ }
541
+}
542
+
543
+func ForEachBGPSignalValue(row BGPConfig, add func(path string, value BGPValueConfig)) {
544
+ addValue := func(path string, value BGPValueConfig) {
545
+ if value.IsSet() {
546
+ add(path, value)
547
+ }
548
+ }
549
+ addState := func(path string, value BGPStateConfig) {
550
+ if value.BGPValueConfig.IsSet() {
551
+ add(path, value.BGPValueConfig)
552
+ }
553
+ }
554
+ addDirectional := func(prefix string, value BGPDirectionalConfig) {
555
+ addValue(prefix+".received", value.Received)
556
+ addValue(prefix+".sent", value.Sent)
557
+ }
558
+ addTimerPair := func(prefix string, value BGPTimerPairConfig) {
559
+ addValue(prefix+".connect_retry", value.ConnectRetry)
560
+ addValue(prefix+".hold_time", value.HoldTime)
561
+ addValue(prefix+".keepalive_time", value.KeepaliveTime)
562
+ addValue(prefix+".min_as_origination_interval", value.MinASOriginationInterval)
563
+ addValue(prefix+".min_route_advertisement_interval", value.MinRouteAdvertisementInterval)
564
+ }
565
+ addNotification := func(prefix string, value BGPLastNotificationConfig) {
566
+ addValue(prefix+".code", value.Code)
567
+ addValue(prefix+".subcode", value.Subcode)
568
+ addValue(prefix+".reason", value.Reason)
569
+ }
570
+ addRoutes := func(prefix string, value BGPRouteCountersConfig) {
571
+ addValue(prefix+".received", value.Received)
572
+ addValue(prefix+".accepted", value.Accepted)
573
+ addValue(prefix+".rejected", value.Rejected)
574
+ addValue(prefix+".active", value.Active)
575
+ addValue(prefix+".advertised", value.Advertised)
576
+ addValue(prefix+".suppressed", value.Suppressed)
577
+ addValue(prefix+".withdrawn", value.Withdrawn)
578
+ }
579
+
580
+ addValue("admin.enabled", row.Admin.Enabled)
581
+ addState("state", row.State)
582
+ addState("previous_state", row.Previous)
583
+ addValue("connection.established_uptime", row.Connection.EstablishedUptime)
584
+ addValue("connection.last_received_update_age", row.Connection.LastReceivedUpdateAge)
585
+ addDirectional("traffic.messages", row.Traffic.Messages)
586
+ addDirectional("traffic.updates", row.Traffic.Updates)
587
+ addDirectional("traffic.notifications", row.Traffic.Notifications)
588
+ addDirectional("traffic.route_refreshes", row.Traffic.RouteRefreshes)
589
+ addDirectional("traffic.opens", row.Traffic.Opens)
590
+ addDirectional("traffic.keepalives", row.Traffic.Keepalives)
591
+ addValue("transitions.established", row.Transitions.Established)
592
+ addValue("transitions.down", row.Transitions.Down)
593
+ addValue("transitions.up", row.Transitions.Up)
594
+ addValue("transitions.flaps", row.Transitions.Flaps)
595
+ addTimerPair("timers.negotiated", row.Timers.Negotiated)
596
+ addTimerPair("timers.configured", row.Timers.Configured)
597
+ addValue("last_error.code", row.LastError.Code)
598
+ addValue("last_error.subcode", row.LastError.Subcode)
599
+ addNotification("last_notifications.received", row.LastNotify.Received)
600
+ addNotification("last_notifications.sent", row.LastNotify.Sent)
601
+ addValue("reasons.last_down", row.Reasons.LastDown)
602
+ addValue("reasons.unavailability", row.Reasons.Unavailability)
603
+ addValue("graceful_restart.state", row.Restart.State)
604
+ addRoutes("routes.current", row.Routes.Current)
605
+ addRoutes("routes.total", row.Routes.Total)
606
+ addValue("route_limits.limit", row.RouteLimits.Limit)
607
+ addValue("route_limits.threshold", row.RouteLimits.Threshold)
608
+ addValue("route_limits.clear_threshold", row.RouteLimits.ClearThreshold)
609
+ addValue("device_counts.peers", row.Device.Peers)
610
+ addValue("device_counts.ibgp_peers", row.Device.InternalPeers)
611
+ addValue("device_counts.ebgp_peers", row.Device.ExternalPeers)
612
+ addValue("device_counts.states.idle", row.Device.States.Idle)
613
+ addValue("device_counts.states.connect", row.Device.States.Connect)
614
+ addValue("device_counts.states.active", row.Device.States.Active)
615
+ addValue("device_counts.states.opensent", row.Device.States.OpenSent)
616
+ addValue("device_counts.states.openconfirm", row.Device.States.OpenConfirm)
617
+ addValue("device_counts.states.established", row.Device.States.Established)
618
+}
619
+
620
+func BGPStructuralIdentity(row BGPConfig) string {
621
+ origin := row.OriginProfileID
622
+ if origin == "" {
623
+ origin = "<profile>"
624
+ }
625
+ return strings.Join([]string{origin, BGPMergeIdentity(row)}, "|")
626
+}
627
+
628
+func BGPMergeIdentity(row BGPConfig) string {
629
+ if row.Table.OID != "" {
630
+ parts := []string{"table", string(row.Kind), TrimBGPOID(row.Table.OID)}
631
+ if row.ID != "" {
632
+ parts = append(parts, row.ID)
633
+ }
634
+ return strings.Join(parts, "|")
635
+ }
636
+ if row.ID != "" {
637
+ return strings.Join([]string{"scalar-group", string(row.Kind), row.ID}, "|")
638
+ }
639
+ if oid := firstBGPSignalSourceOID(row); oid != "" {
640
+ return strings.Join([]string{"scalar", string(row.Kind), TrimBGPOID(oid)}, "|")
641
+ }
642
+ return strings.Join([]string{"scalar", string(row.Kind), "<missing-source>"}, "|")
643
+}
644
+
645
+func firstBGPSignalSourceOID(row BGPConfig) string {
646
+ var first string
647
+ ForEachBGPSignalValue(row, func(_ string, value BGPValueConfig) {
648
+ if first != "" {
649
+ return
650
+ }
651
+ if oid := BGPValueSourceOID(value); oid != "" {
652
+ first = oid
653
+ return
654
+ }
655
+ })
656
+ return first
657
+}
658
+
659
+func BGPValueSourceOID(value BGPValueConfig) string {
660
+ switch {
661
+ case value.From != "":
662
+ return value.From
663
+ case value.Symbol.OID != "":
664
+ return value.Symbol.OID
665
+ case value.OID != "":
666
+ return value.OID
667
+ default:
668
+ return ""
669
+ }
670
+}
671
+
672
+func TrimBGPOID(oid string) string {
673
+ return strings.TrimPrefix(strings.TrimSpace(oid), ".")
674
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/bgp_test.go
new
+741
@@ -0,0 +1,741 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddprofiledefinition
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/stretchr/testify/assert"
9
+ "github.com/stretchr/testify/require"
10
+ "gopkg.in/yaml.v2"
11
+)
12
+
13
+func TestProfileDefinition_UnmarshalBGP(t *testing.T) {
14
+ var profile ProfileDefinition
15
+
16
+ err := yaml.Unmarshal([]byte(`
17
+metric_tags:
18
+ - tag: vendor
19
+ consumers: [bgp]
20
+ symbol:
21
+ OID: 1.3.6.1.2.1.1.1.0
22
+ name: sysDescr
23
+bgp:
24
+ - id: std-peer
25
+ MIB: BGP4-MIB
26
+ kind: peer
27
+ table:
28
+ OID: 1.3.6.1.2.1.15.3
29
+ name: bgpPeerTable
30
+ identity:
31
+ neighbor:
32
+ symbol: { OID: 1.3.6.1.2.1.15.3.1.7, name: bgpPeerRemoteAddr, format: ip_address }
33
+ remote_as:
34
+ symbol: { OID: 1.3.6.1.2.1.15.3.1.9, name: bgpPeerRemoteAs, format: uint32 }
35
+ state:
36
+ symbol:
37
+ OID: 1.3.6.1.2.1.15.3.1.2
38
+ name: bgpPeerState
39
+ mapping:
40
+ items: { 1: idle, 2: connect, 3: active, 4: opensent, 5: openconfirm, 6: established }
41
+ connection:
42
+ established_uptime:
43
+ table: bgpPeerTimesTable
44
+ lookup_symbol: { OID: 1.3.6.1.2.1.15.3.1.8, name: bgpPeerIndex }
45
+ symbol: { OID: 1.3.6.1.2.1.15.3.1.16, name: bgpPeerFsmEstablishedTime }
46
+`), &profile)
47
+
48
+ require.NoError(t, err)
49
+ require.Len(t, profile.BGP, 1)
50
+ assert.Equal(t, "std-peer", profile.BGP[0].ID)
51
+ assert.Equal(t, "BGP4-MIB", profile.BGP[0].MIB)
52
+ assert.Equal(t, BGPRowKindPeer, profile.BGP[0].Kind)
53
+ assert.Equal(t, "bgpPeerRemoteAddr", profile.BGP[0].Identity.Neighbor.Symbol.Name)
54
+ assert.Equal(t, "bgpPeerFsmEstablishedTime", profile.BGP[0].Connection.EstablishedUptime.Symbol.Name)
55
+ assert.Equal(t, "bgpPeerIndex", SymbolConfig(profile.BGP[0].Connection.EstablishedUptime.LookupSymbol).Name)
56
+ require.Len(t, profile.MetricTags, 1)
57
+ assert.Equal(t, ConsumerSet{ConsumerBGP}, profile.MetricTags[0].Consumers)
58
+}
59
+
60
+func TestProfileDefinition_CloneBGP(t *testing.T) {
61
+ profile := &ProfileDefinition{
62
+ BGP: []BGPConfig{
63
+ {
64
+ OriginProfileID: "_vendor-bgp.yaml",
65
+ ID: "peer",
66
+ Kind: BGPRowKindPeerFamily,
67
+ Identity: BGPIdentityConfig{
68
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
69
+ RemoteAS: BGPValueConfig{Value: "65001"},
70
+ AddressFamily: BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{IndexFromEnd: 2, Mapping: NewExactMapping(map[string]string{"1": "ipv4"})}},
71
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
72
+ BGPValueConfig: BGPValueConfig{Value: "unicast"},
73
+ },
74
+ },
75
+ State: BGPStateConfig{
76
+ BGPValueConfig: BGPValueConfig{
77
+ Symbol: SymbolConfig{
78
+ OID: "1.2.3.1",
79
+ Name: "bgpPeerState",
80
+ Mapping: NewExactMapping(map[string]string{
81
+ "1": "idle",
82
+ "2": "connect",
83
+ "3": "active",
84
+ "4": "opensent",
85
+ "5": "openconfirm",
86
+ "6": "established",
87
+ }),
88
+ },
89
+ },
90
+ },
91
+ Routes: BGPRoutesConfig{
92
+ Current: BGPRouteCountersConfig{
93
+ Accepted: BGPValueConfig{
94
+ Table: "peerTable",
95
+ LookupSymbol: SymbolConfigCompat(SymbolConfig{OID: "1.2.3.14", Name: "peerIndex"}),
96
+ Symbol: SymbolConfig{OID: "1.2.4.1", Name: "acceptedPrefixes"},
97
+ },
98
+ },
99
+ },
100
+ MetricTags: MetricTagConfigList{
101
+ {Tag: "routing_instance", IndexTransform: []MetricIndexTransform{{Start: 1}}},
102
+ },
103
+ },
104
+ },
105
+ }
106
+
107
+ cloned := profile.Clone()
108
+ require.Equal(t, profile, cloned)
109
+
110
+ cloned.BGP[0].State.Symbol.Mapping.Items["1"] = "broken"
111
+ cloned.BGP[0].Routes.Current.Accepted.Symbol.Name = "brokenPrefixes"
112
+ cloned.BGP[0].Routes.Current.Accepted.LookupSymbol.Name = "brokenPeerIndex"
113
+ cloned.BGP[0].MetricTags[0].IndexTransform[0].Start = 2
114
+ cloned.BGP[0].Identity.AddressFamily.IndexFromEnd = 3
115
+
116
+ assert.Equal(t, "idle", profile.BGP[0].State.Symbol.Mapping.Items["1"])
117
+ assert.Equal(t, "acceptedPrefixes", profile.BGP[0].Routes.Current.Accepted.Symbol.Name)
118
+ assert.Equal(t, "peerIndex", profile.BGP[0].Routes.Current.Accepted.LookupSymbol.Name)
119
+ assert.Equal(t, uint(1), profile.BGP[0].MetricTags[0].IndexTransform[0].Start)
120
+ assert.Equal(t, uint(2), profile.BGP[0].Identity.AddressFamily.IndexFromEnd)
121
+}
122
+
123
+func TestValidateEnrichProfile_BGP(t *testing.T) {
124
+ tests := map[string]struct {
125
+ profile ProfileDefinition
126
+ wantErrContains []string
127
+ }{
128
+ "valid peer family": {
129
+ profile: ProfileDefinition{
130
+ BGP: []BGPConfig{validBGPPeerFamilyRow()},
131
+ },
132
+ },
133
+ "valid peer family with cross-table value source": {
134
+ profile: ProfileDefinition{
135
+ BGP: []BGPConfig{validBGPPeerFamilyRowWithCrossTableSource()},
136
+ },
137
+ },
138
+ "valid peer family with declared cross-table value source": {
139
+ profile: ProfileDefinition{
140
+ BGP: []BGPConfig{
141
+ validBGPPeerFamilyRowWithCrossTableSource(),
142
+ validBGPPeerRowForTable("bgpPeerTable", "1.2.4"),
143
+ },
144
+ },
145
+ },
146
+ "invalid kind": {
147
+ profile: ProfileDefinition{
148
+ BGP: []BGPConfig{
149
+ {Kind: BGPRowKind("session")},
150
+ },
151
+ },
152
+ wantErrContains: []string{`bgp[0].kind: invalid kind "session"`},
153
+ },
154
+ "peer requires neighbor and remote as": {
155
+ profile: ProfileDefinition{
156
+ BGP: []BGPConfig{
157
+ {
158
+ Kind: BGPRowKindPeer,
159
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{
160
+ Symbol: bgpStateSymbol(),
161
+ }},
162
+ },
163
+ },
164
+ },
165
+ wantErrContains: []string{
166
+ "bgp[0].identity.neighbor: peer rows require neighbor identity",
167
+ "bgp[0].identity.remote_as: peer rows require remote_as identity",
168
+ },
169
+ },
170
+ "peer family requires afi and safi": {
171
+ profile: ProfileDefinition{
172
+ BGP: []BGPConfig{
173
+ {
174
+ Kind: BGPRowKindPeerFamily,
175
+ Identity: BGPIdentityConfig{
176
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
177
+ RemoteAS: BGPValueConfig{Value: "65001"},
178
+ },
179
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{
180
+ Symbol: bgpStateSymbol(),
181
+ }},
182
+ },
183
+ },
184
+ },
185
+ wantErrContains: []string{
186
+ "bgp[0].identity.address_family: peer_family rows require address_family identity",
187
+ "bgp[0].identity.subsequent_address_family: peer_family rows require subsequent_address_family identity",
188
+ },
189
+ },
190
+ "forbids cross-table value source on scalar row": {
191
+ profile: ProfileDefinition{
192
+ BGP: []BGPConfig{
193
+ {
194
+ ID: "peer",
195
+ Kind: BGPRowKindPeer,
196
+ Identity: BGPIdentityConfig{
197
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
198
+ RemoteAS: BGPValueConfig{
199
+ Table: "peerTable",
200
+ Symbol: SymbolConfig{
201
+ OID: "1.2.3.1",
202
+ Name: "bgpPeerRemoteAs",
203
+ },
204
+ },
205
+ },
206
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{
207
+ Symbol: bgpStateSymbol(),
208
+ }},
209
+ },
210
+ },
211
+ },
212
+ wantErrContains: []string{"bgp[0].identity.remote_as.table: scalar BGP values do not support `table` lookups"},
213
+ },
214
+ "forbids table value source without symbol oid": {
215
+ profile: ProfileDefinition{
216
+ BGP: []BGPConfig{
217
+ {
218
+ ID: "peer-family",
219
+ Kind: BGPRowKindPeerFamily,
220
+ Table: SymbolConfig{
221
+ OID: "1.2.3",
222
+ Name: "peerFamilyTable",
223
+ },
224
+ Identity: BGPIdentityConfig{
225
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
226
+ RemoteAS: BGPValueConfig{
227
+ Table: "peerTable",
228
+ IndexTransform: []MetricIndexTransform{{Start: 0, DropRight: 2}},
229
+ },
230
+ AddressFamily: BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{Value: "ipv4"}},
231
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
232
+ BGPValueConfig: BGPValueConfig{Value: "unicast"},
233
+ },
234
+ },
235
+ Routes: BGPRoutesConfig{
236
+ Current: BGPRouteCountersConfig{
237
+ Accepted: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1", Name: "acceptedPrefixes"}},
238
+ },
239
+ },
240
+ },
241
+ },
242
+ },
243
+ wantErrContains: []string{"bgp[0].identity.remote_as.table: table lookups require symbol.OID, OID, or from"},
244
+ },
245
+ "forbids lookup symbol without table": {
246
+ profile: ProfileDefinition{
247
+ BGP: []BGPConfig{
248
+ {
249
+ ID: "peer-family",
250
+ Kind: BGPRowKindPeerFamily,
251
+ Table: SymbolConfig{OID: "1.2.3", Name: "peerFamilyTable"},
252
+ Identity: BGPIdentityConfig{
253
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
254
+ RemoteAS: BGPValueConfig{
255
+ Symbol: SymbolConfig{OID: "1.2.3.1", Name: "remoteAS"},
256
+ LookupSymbol: SymbolConfigCompat(SymbolConfig{OID: "1.2.4.1", Name: "peerIndex"}),
257
+ },
258
+ AddressFamily: BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{Value: "ipv4"}},
259
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
260
+ BGPValueConfig: BGPValueConfig{Value: "unicast"},
261
+ },
262
+ },
263
+ Routes: BGPRoutesConfig{
264
+ Current: BGPRouteCountersConfig{
265
+ Accepted: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.2", Name: "acceptedPrefixes"}},
266
+ },
267
+ },
268
+ },
269
+ },
270
+ },
271
+ wantErrContains: []string{"bgp[0].identity.remote_as.lookup_symbol: lookup_symbol requires table"},
272
+ },
273
+ "forbids cross-table source outside declared referenced table": {
274
+ profile: ProfileDefinition{
275
+ BGP: []BGPConfig{
276
+ func() BGPConfig {
277
+ row := validBGPPeerFamilyRowWithCrossTableSource()
278
+ row.Identity.RemoteAS.Symbol.OID = "1.9.9.1"
279
+ return row
280
+ }(),
281
+ validBGPPeerRowForTable("bgpPeerTable", "1.2.4"),
282
+ },
283
+ },
284
+ wantErrContains: []string{`bgp[0].identity.remote_as.table: referenced table "bgpPeerTable" uses OID "1.2.4"; source OID "1.9.9.1" is outside referenced table`},
285
+ },
286
+ "forbids lookup symbol outside declared referenced table": {
287
+ profile: ProfileDefinition{
288
+ BGP: []BGPConfig{
289
+ func() BGPConfig {
290
+ row := validBGPPeerFamilyRowWithCrossTableSource()
291
+ row.Identity.RemoteAS.LookupSymbol.OID = "1.9.9.2"
292
+ return row
293
+ }(),
294
+ validBGPPeerRowForTable("bgpPeerTable", "1.2.4"),
295
+ },
296
+ },
297
+ wantErrContains: []string{`bgp[0].identity.remote_as.lookup_symbol: referenced table "bgpPeerTable" uses OID "1.2.4"; lookup OID "1.9.9.2" is outside referenced table`},
298
+ },
299
+ "forbids incomplete state mapping unless partial": {
300
+ profile: ProfileDefinition{
301
+ BGP: []BGPConfig{
302
+ {
303
+ ID: "peer",
304
+ Kind: BGPRowKindPeer,
305
+ Identity: BGPIdentityConfig{
306
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
307
+ RemoteAS: BGPValueConfig{Value: "65001"},
308
+ },
309
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{
310
+ Symbol: SymbolConfig{
311
+ OID: "1.2.3.1",
312
+ Name: "bgpPeerState",
313
+ Mapping: NewExactMapping(map[string]string{"6": "established"}),
314
+ },
315
+ }},
316
+ },
317
+ },
318
+ },
319
+ wantErrContains: []string{`bgp[0].state.mapping: missing RFC 4271 state "idle"`},
320
+ },
321
+ "allows explicit partial state mapping": {
322
+ profile: ProfileDefinition{
323
+ BGP: []BGPConfig{
324
+ {
325
+ ID: "peer",
326
+ Kind: BGPRowKindPeer,
327
+ Identity: BGPIdentityConfig{
328
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
329
+ RemoteAS: BGPValueConfig{Value: "65001"},
330
+ },
331
+ State: BGPStateConfig{
332
+ BGPValueConfig: BGPValueConfig{
333
+ Symbol: SymbolConfig{
334
+ OID: "1.2.3.1",
335
+ Name: "bgpPeerState",
336
+ Mapping: NewExactMapping(map[string]string{"6": "established"}),
337
+ },
338
+ },
339
+ Partial: true,
340
+ PartialStates: []BGPPeerState{BGPPeerStateEstablished},
341
+ },
342
+ },
343
+ },
344
+ },
345
+ },
346
+ "forbids partial state mapping without mapping items": {
347
+ profile: ProfileDefinition{
348
+ BGP: []BGPConfig{
349
+ {
350
+ ID: "peer",
351
+ Kind: BGPRowKindPeer,
352
+ Identity: BGPIdentityConfig{
353
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
354
+ RemoteAS: BGPValueConfig{Value: "65001"},
355
+ },
356
+ State: BGPStateConfig{
357
+ BGPValueConfig: BGPValueConfig{
358
+ Symbol: SymbolConfig{OID: "1.2.3.1", Name: "bgpPeerState"},
359
+ },
360
+ Partial: true,
361
+ },
362
+ },
363
+ },
364
+ },
365
+ wantErrContains: []string{"bgp[0].state.mapping: partial state mapping requires at least one RFC 4271 state"},
366
+ },
367
+ "forbids invalid state mapping value": {
368
+ profile: ProfileDefinition{
369
+ BGP: []BGPConfig{
370
+ {
371
+ ID: "peer",
372
+ Kind: BGPRowKindPeer,
373
+ Identity: BGPIdentityConfig{
374
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
375
+ RemoteAS: BGPValueConfig{Value: "65001"},
376
+ },
377
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{
378
+ Symbol: SymbolConfig{
379
+ OID: "1.2.3.1",
380
+ Name: "bgpPeerState",
381
+ Mapping: NewExactMapping(map[string]string{"7": "almost_up"}),
382
+ },
383
+ }},
384
+ },
385
+ },
386
+ },
387
+ wantErrContains: []string{`bgp[0].state.mapping.items[7]: invalid BGP peer state "almost_up"`},
388
+ },
389
+ "forbids typed field without source": {
390
+ profile: ProfileDefinition{
391
+ BGP: []BGPConfig{
392
+ {
393
+ ID: "peer",
394
+ Kind: BGPRowKindPeer,
395
+ Identity: BGPIdentityConfig{
396
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
397
+ RemoteAS: BGPValueConfig{Value: "65001"},
398
+ },
399
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbol()}},
400
+ Connection: BGPConnectionConfig{
401
+ EstablishedUptime: BGPValueConfig{
402
+ Mapping: NewExactMapping(map[string]string{"1": "1"}),
403
+ },
404
+ },
405
+ },
406
+ },
407
+ },
408
+ wantErrContains: []string{"bgp[0].connection.established_uptime: must define value, from, symbol.OID, OID, index, index_from_end, or index_transform"},
409
+ },
410
+ "forbids invalid address family": {
411
+ profile: ProfileDefinition{
412
+ BGP: []BGPConfig{
413
+ {
414
+ ID: "peer-family",
415
+ Kind: BGPRowKindPeerFamily,
416
+ Identity: BGPIdentityConfig{
417
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
418
+ RemoteAS: BGPValueConfig{Value: "65001"},
419
+ AddressFamily: BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{Value: "vpls"}},
420
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
421
+ BGPValueConfig: BGPValueConfig{Value: "unicast"},
422
+ },
423
+ },
424
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbol()}},
425
+ },
426
+ },
427
+ },
428
+ wantErrContains: []string{`bgp[0].identity.address_family.value: invalid BGP address_family "vpls"`},
429
+ },
430
+ "allows explicit private family values": {
431
+ profile: ProfileDefinition{
432
+ BGP: []BGPConfig{
433
+ {
434
+ ID: "peer-family",
435
+ Kind: BGPRowKindPeerFamily,
436
+ Identity: BGPIdentityConfig{
437
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
438
+ RemoteAS: BGPValueConfig{Value: "65001"},
439
+ AddressFamily: BGPAddressFamilyValueConfig{
440
+ BGPValueConfig: BGPValueConfig{Value: "vendor_private"},
441
+ AllowPrivate: true,
442
+ },
443
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
444
+ BGPValueConfig: BGPValueConfig{Value: "vendor_private"},
445
+ AllowPrivate: true,
446
+ },
447
+ },
448
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbol()}},
449
+ },
450
+ },
451
+ },
452
+ },
453
+ "forbids table from outside row table": {
454
+ profile: ProfileDefinition{
455
+ BGP: []BGPConfig{
456
+ {
457
+ ID: "peer",
458
+ Kind: BGPRowKindPeer,
459
+ Table: SymbolConfig{OID: "1.2.3", Name: "bgpPeerTable"},
460
+ Identity: BGPIdentityConfig{
461
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
462
+ RemoteAS: BGPValueConfig{Value: "65001"},
463
+ },
464
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{From: "1.2.4.1"}},
465
+ },
466
+ },
467
+ },
468
+ wantErrContains: []string{`bgp[0].state.from: OID "1.2.4.1" is outside table "1.2.3"`},
469
+ },
470
+ "forbids duplicate typed fields for same identity": {
471
+ profile: ProfileDefinition{
472
+ BGP: []BGPConfig{
473
+ {
474
+ OriginProfileID: "_vendor-bgp.yaml",
475
+ ID: "peer",
476
+ Kind: BGPRowKindPeer,
477
+ Identity: BGPIdentityConfig{
478
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
479
+ RemoteAS: BGPValueConfig{Value: "65001"},
480
+ },
481
+ Connection: BGPConnectionConfig{
482
+ EstablishedUptime: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1", Name: "uptime"}},
483
+ },
484
+ },
485
+ {
486
+ OriginProfileID: "_vendor-bgp.yaml",
487
+ ID: "peer",
488
+ Kind: BGPRowKindPeer,
489
+ Identity: BGPIdentityConfig{
490
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
491
+ RemoteAS: BGPValueConfig{Value: "65001"},
492
+ },
493
+ Connection: BGPConnectionConfig{
494
+ EstablishedUptime: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.2", Name: "uptime2"}},
495
+ },
496
+ },
497
+ },
498
+ },
499
+ wantErrContains: []string{`duplicate BGP field for structural identity "_vendor-bgp.yaml|scalar-group|peer|peer"`},
500
+ },
501
+ "forbids scalar index lookups": {
502
+ profile: ProfileDefinition{
503
+ BGP: []BGPConfig{
504
+ {
505
+ ID: "peer",
506
+ Kind: BGPRowKindPeer,
507
+ Identity: BGPIdentityConfig{
508
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
509
+ RemoteAS: BGPValueConfig{Value: "65001"},
510
+ },
511
+ Connection: BGPConnectionConfig{
512
+ EstablishedUptime: BGPValueConfig{Index: 1},
513
+ },
514
+ },
515
+ },
516
+ },
517
+ wantErrContains: []string{"bgp[0].connection.established_uptime.index: scalar BGP values do not support `index` lookups"},
518
+ },
519
+ "forbids scalar index_from_end lookups": {
520
+ profile: ProfileDefinition{
521
+ BGP: []BGPConfig{
522
+ {
523
+ ID: "peer",
524
+ Kind: BGPRowKindPeer,
525
+ Identity: BGPIdentityConfig{
526
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
527
+ RemoteAS: BGPValueConfig{Value: "65001"},
528
+ },
529
+ Connection: BGPConnectionConfig{
530
+ EstablishedUptime: BGPValueConfig{IndexFromEnd: 1},
531
+ },
532
+ },
533
+ },
534
+ },
535
+ wantErrContains: []string{"bgp[0].connection.established_uptime.index_from_end: scalar BGP values do not support `index_from_end` lookups"},
536
+ },
537
+ "forbids multiple BGP row index selectors": {
538
+ profile: ProfileDefinition{
539
+ BGP: []BGPConfig{
540
+ func() BGPConfig {
541
+ row := validBGPPeerFamilyRow()
542
+ row.Identity.Neighbor = BGPValueConfig{
543
+ Index: 1,
544
+ IndexFromEnd: 2,
545
+ }
546
+ return row
547
+ }(),
548
+ },
549
+ },
550
+ wantErrContains: []string{"bgp[0].identity.neighbor: index, index_from_end, and index_transform are mutually exclusive (set: index, index_from_end)"},
551
+ },
552
+ "forbids index and index_transform together": {
553
+ profile: ProfileDefinition{
554
+ BGP: []BGPConfig{
555
+ func() BGPConfig {
556
+ row := validBGPPeerFamilyRow()
557
+ row.Identity.Neighbor = BGPValueConfig{
558
+ Index: 1,
559
+ IndexTransform: []MetricIndexTransform{{Start: 1}},
560
+ }
561
+ return row
562
+ }(),
563
+ },
564
+ },
565
+ wantErrContains: []string{"bgp[0].identity.neighbor: index, index_from_end, and index_transform are mutually exclusive (set: index, index_transform)"},
566
+ },
567
+ "forbids index_from_end and index_transform together": {
568
+ profile: ProfileDefinition{
569
+ BGP: []BGPConfig{
570
+ func() BGPConfig {
571
+ row := validBGPPeerFamilyRow()
572
+ row.Identity.AddressFamily = BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{
573
+ IndexFromEnd: 2,
574
+ IndexTransform: []MetricIndexTransform{{Start: 1}},
575
+ Mapping: NewExactMapping(map[string]string{"1": "ipv4"}),
576
+ }}
577
+ return row
578
+ }(),
579
+ },
580
+ },
581
+ wantErrContains: []string{"bgp[0].identity.address_family: index, index_from_end, and index_transform are mutually exclusive (set: index_from_end, index_transform)"},
582
+ },
583
+ "forbids all row index selectors together": {
584
+ profile: ProfileDefinition{
585
+ BGP: []BGPConfig{
586
+ func() BGPConfig {
587
+ row := validBGPPeerFamilyRow()
588
+ row.Identity.Neighbor = BGPValueConfig{
589
+ Index: 1,
590
+ IndexFromEnd: 2,
591
+ IndexTransform: []MetricIndexTransform{{Start: 1}},
592
+ }
593
+ return row
594
+ }(),
595
+ },
596
+ },
597
+ wantErrContains: []string{"bgp[0].identity.neighbor: index, index_from_end, and index_transform are mutually exclusive (set: index, index_from_end, index_transform)"},
598
+ },
599
+ "forbids non-device fields on device rows": {
600
+ profile: ProfileDefinition{
601
+ BGP: []BGPConfig{
602
+ {
603
+ Kind: BGPRowKindDevice,
604
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbol()}},
605
+ },
606
+ },
607
+ },
608
+ wantErrContains: []string{"bgp[0].state: device rows only support device_counts fields"},
609
+ },
610
+ "forbids device counts on peer rows": {
611
+ profile: ProfileDefinition{
612
+ BGP: []BGPConfig{
613
+ {
614
+ Kind: BGPRowKindPeer,
615
+ Identity: BGPIdentityConfig{
616
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
617
+ RemoteAS: BGPValueConfig{Value: "65001"},
618
+ },
619
+ Device: BGPDeviceCountsConfig{
620
+ Peers: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1", Name: "peerCount"}},
621
+ },
622
+ },
623
+ },
624
+ },
625
+ wantErrContains: []string{"bgp[0].device_counts.peers: device_counts fields require kind=device"},
626
+ },
627
+ "forbids route fields on peer rows": {
628
+ profile: ProfileDefinition{
629
+ BGP: []BGPConfig{
630
+ {
631
+ Kind: BGPRowKindPeer,
632
+ Identity: BGPIdentityConfig{
633
+ Neighbor: BGPValueConfig{Value: "192.0.2.1"},
634
+ RemoteAS: BGPValueConfig{Value: "65001"},
635
+ },
636
+ Routes: BGPRoutesConfig{
637
+ Current: BGPRouteCountersConfig{
638
+ Received: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1", Name: "receivedRoutes"}},
639
+ },
640
+ },
641
+ },
642
+ },
643
+ },
644
+ wantErrContains: []string{"bgp[0].routes.current.received: route fields require kind=peer_family"},
645
+ },
646
+ "forbids unknown consumers": {
647
+ profile: ProfileDefinition{
648
+ MetricTags: []GlobalMetricTagConfig{
649
+ {
650
+ MetricTagConfig: MetricTagConfig{Tag: "vendor"},
651
+ Consumers: ConsumerSet{ConsumerBGP, ProfileConsumer("bgp_hidden")},
652
+ },
653
+ },
654
+ },
655
+ wantErrContains: []string{`metric_tags[0].consumers[1]: invalid consumer "bgp_hidden"`},
656
+ },
657
+ }
658
+
659
+ for name, tc := range tests {
660
+ t.Run(name, func(t *testing.T) {
661
+ err := ValidateEnrichProfile(&tc.profile)
662
+ if len(tc.wantErrContains) == 0 {
663
+ require.NoError(t, err)
664
+ return
665
+ }
666
+
667
+ require.Error(t, err)
668
+ for _, want := range tc.wantErrContains {
669
+ assert.Contains(t, err.Error(), want)
670
+ }
671
+ })
672
+ }
673
+}
674
+
675
+func validBGPPeerFamilyRow() BGPConfig {
676
+ return BGPConfig{
677
+ ID: "peer-family",
678
+ Kind: BGPRowKindPeerFamily,
679
+ Table: SymbolConfig{OID: "1.2.3", Name: "bgpPeerFamilyTable"},
680
+ Identity: BGPIdentityConfig{
681
+ Neighbor: BGPValueConfig{Index: 1},
682
+ RemoteAS: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1.2", Name: "remoteAS"}},
683
+ AddressFamily: BGPAddressFamilyValueConfig{BGPValueConfig: BGPValueConfig{Value: "ipv4"}},
684
+ SubsequentAddressFamily: BGPSubsequentAddressFamilyValueConfig{
685
+ BGPValueConfig: BGPValueConfig{Value: "unicast"},
686
+ },
687
+ },
688
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbol()}},
689
+ Routes: BGPRoutesConfig{
690
+ Current: BGPRouteCountersConfig{
691
+ Accepted: BGPValueConfig{Symbol: SymbolConfig{OID: "1.2.3.1.3", Name: "acceptedRoutes"}},
692
+ },
693
+ },
694
+ }
695
+}
696
+
697
+func validBGPPeerFamilyRowWithCrossTableSource() BGPConfig {
698
+ row := validBGPPeerFamilyRow()
699
+ row.Identity.RemoteAS = BGPValueConfig{
700
+ Table: "bgpPeerTable",
701
+ IndexTransform: []MetricIndexTransform{{Start: 0, DropRight: 2}},
702
+ LookupSymbol: SymbolConfigCompat(SymbolConfig{OID: "1.2.4.1.1", Name: "peerIndex"}),
703
+ Symbol: SymbolConfig{
704
+ OID: "1.2.4.1.2",
705
+ Name: "remoteAS",
706
+ },
707
+ }
708
+ return row
709
+}
710
+
711
+func validBGPPeerRowForTable(name, oid string) BGPConfig {
712
+ return BGPConfig{
713
+ ID: name + "-peer",
714
+ Kind: BGPRowKindPeer,
715
+ Table: SymbolConfig{Name: name, OID: oid},
716
+ Identity: BGPIdentityConfig{
717
+ Neighbor: BGPValueConfig{Symbol: SymbolConfig{OID: oid + ".1.1", Name: name + "RemoteAddr", Format: "ip_address"}},
718
+ RemoteAS: BGPValueConfig{Symbol: SymbolConfig{OID: oid + ".1.2", Name: name + "RemoteAs", Format: "uint32"}},
719
+ },
720
+ State: BGPStateConfig{BGPValueConfig: BGPValueConfig{Symbol: bgpStateSymbolWithOID(oid + ".1.3")}},
721
+ }
722
+}
723
+
724
+func bgpStateSymbol() SymbolConfig {
725
+ return bgpStateSymbolWithOID("1.2.3.1.1")
726
+}
727
+
728
+func bgpStateSymbolWithOID(oid string) SymbolConfig {
729
+ return SymbolConfig{
730
+ OID: oid,
731
+ Name: "bgpPeerState",
732
+ Mapping: NewExactMapping(map[string]string{
733
+ "1": "idle",
734
+ "2": "connect",
735
+ "3": "active",
736
+ "4": "opensent",
737
+ "5": "openconfirm",
738
+ "6": "established",
739
+ }),
740
+ }
741
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/consumers.go
+1
@@ -12,6 +12,7 @@ const (
12
ConsumerMetrics ProfileConsumer = "metrics"
13
ConsumerTopology ProfileConsumer = "topology"
14
ConsumerLicensing ProfileConsumer = "licensing"
15
+ ConsumerBGP ProfileConsumer = "bgp"
16
)
17
18
type ConsumerSet []ProfileConsumer
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/profile_definition.go
+2
@@ -16,6 +16,7 @@ type ProfileDefinition struct {
16
Metrics []MetricsConfig `yaml:"metrics,omitempty" json:"metrics,omitempty"`
17
Topology []TopologyConfig `yaml:"topology,omitempty" json:"topology,omitempty"`
18
Licensing []LicensingConfig `yaml:"licensing,omitempty" json:"licensing,omitempty"`
19
+ BGP []BGPConfig `yaml:"bgp,omitempty" json:"bgp,omitempty"`
20
MetricTags []GlobalMetricTagConfig `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
21
StaticTags []StaticMetricTagConfig `yaml:"static_tags,omitempty" json:"static_tags,omitempty"`
22
@@ -41,6 +42,7 @@ func (p *ProfileDefinition) Clone() *ProfileDefinition {
42
Metrics: cloneSlice(p.Metrics),
43
Topology: cloneSlice(p.Topology),
44
Licensing: cloneSlice(p.Licensing),
45
+ BGP: cloneSlice(p.BGP),
46
VirtualMetrics: cloneSlice(p.VirtualMetrics),
47
}
48
}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/validation.go
+572
-1
@@ -71,6 +71,7 @@ func ValidateEnrichProfile(p *ProfileDefinition) error {
71
normalizeMetrics(p.Metrics)
72
normalizeTopology(p.Topology)
73
normalizeLicensing(p.Licensing)
74
+ normalizeBGP(p.BGP)
75
76
errs := []error{
77
validateEnrichLegacySelector(p),
@@ -79,6 +80,7 @@ func ValidateEnrichProfile(p *ProfileDefinition) error {
80
validateEnrichMetrics(p.Metrics),
81
validateEnrichTopology(p.Topology),
82
validateEnrichLicensing(p.Licensing),
83
+ validateEnrichBGP(p.BGP),
84
validateEnrichGlobalMetricTags(p.MetricTags),
85
validateEnrichVirtualMetrics(p.Metrics, p.Topology, p.VirtualMetrics),
86
}
@@ -116,6 +118,39 @@ func normalizeLicensing(licensing []LicensingConfig) {
118
}
119
}
120
121
+func normalizeBGP(rows []BGPConfig) {
122
+ for i := range rows {
123
+ normalizeBGPValue(&rows[i].Identity.RoutingInstance)
124
+ normalizeBGPValue(&rows[i].Identity.Neighbor)
125
+ normalizeBGPValue(&rows[i].Identity.RemoteAS)
126
+ normalizeBGPValue(&rows[i].Identity.AddressFamily.BGPValueConfig)
127
+ normalizeBGPValue(&rows[i].Identity.SubsequentAddressFamily.BGPValueConfig)
128
+ normalizeBGPValue(&rows[i].Descriptors.LocalAddress)
129
+ normalizeBGPValue(&rows[i].Descriptors.LocalAS)
130
+ normalizeBGPValue(&rows[i].Descriptors.LocalIdentifier)
131
+ normalizeBGPValue(&rows[i].Descriptors.PeerIdentifier)
132
+ normalizeBGPValue(&rows[i].Descriptors.PeerType)
133
+ normalizeBGPValue(&rows[i].Descriptors.BGPVersion)
134
+ normalizeBGPValue(&rows[i].Descriptors.Description)
135
+ forEachBGPSignalValueConfig(&rows[i], func(_ string, value *BGPValueConfig) {
136
+ normalizeBGPValue(value)
137
+ })
138
+ }
139
+}
140
+
141
+func normalizeBGPValue(value *BGPValueConfig) {
142
+ if value.Symbol.Name == "" && value.Symbol.OID == "" && value.Name != "" && value.OID != "" {
143
+ value.Symbol.Name = value.Name
144
+ value.Symbol.OID = value.OID
145
+ value.Name = ""
146
+ value.OID = ""
147
+ }
148
+ if value.Symbol.Format == "" {
149
+ value.Symbol.Format = value.Format
150
+ value.Format = ""
151
+ }
152
+}
153
+
154
func normalizeLicenseSignals(signals *LicenseSignalsConfig) {
155
normalizeLicenseTimerSignals(&signals.Expiry)
156
normalizeLicenseTimerSignals(&signals.Authorization)
@@ -762,6 +797,542 @@ func oidHasPrefix(oid, prefix string) bool {
797
return oid == prefix || strings.HasPrefix(oid, prefix+".")
798
}
799
800
+func validateEnrichBGP(rows []BGPConfig) error {
801
+ var errs []error
802
+ seenSignals := make(map[bgpSignalValidationKey]string)
803
+
804
+ for i := range rows {
805
+ row := &rows[i]
806
+ isTable := row.Table.OID != ""
807
+
808
+ errs = append(errs, validateEnrichBGPRowShape(i, row))
809
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].identity.routing_instance", i), &row.Identity.RoutingInstance, isTable))
810
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].identity.neighbor", i), &row.Identity.Neighbor, isTable))
811
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].identity.remote_as", i), &row.Identity.RemoteAS, isTable))
812
+ errs = append(errs, validateEnrichBGPAddressFamilyValue(fmt.Sprintf("bgp[%d].identity.address_family", i), &row.Identity.AddressFamily, isTable))
813
+ errs = append(errs, validateEnrichBGPSubsequentAddressFamilyValue(fmt.Sprintf("bgp[%d].identity.subsequent_address_family", i), &row.Identity.SubsequentAddressFamily, isTable))
814
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.local_address", i), &row.Descriptors.LocalAddress, isTable))
815
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.local_as", i), &row.Descriptors.LocalAS, isTable))
816
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.local_identifier", i), &row.Descriptors.LocalIdentifier, isTable))
817
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.peer_identifier", i), &row.Descriptors.PeerIdentifier, isTable))
818
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.peer_type", i), &row.Descriptors.PeerType, isTable))
819
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.bgp_version", i), &row.Descriptors.BGPVersion, isTable))
820
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].descriptors.description", i), &row.Descriptors.Description, isTable))
821
+ errs = append(errs, validateEnrichBGPSignalValues(i, row, isTable))
822
+ errs = append(errs, validateBGPGroupKindCompatibility(i, row))
823
+ errs = append(errs, validateBGPFromReferences(i, row))
824
+ errs = append(errs, validateBGPSignalDuplicates(i, row, seenSignals))
825
+
826
+ for j := range row.MetricTags {
827
+ errs = append(errs, validateEnrichMetricTag(&row.MetricTags[j]))
828
+ if !isTable {
829
+ metricTag := &row.MetricTags[j]
830
+ if metricTag.Table != "" {
831
+ errs = append(errs, fmt.Errorf("bgp[%d].metric_tags[%d]: scalar metric_tags do not support `table` lookups (tag=%q, table=%q)", i, j, metricTag.Tag, metricTag.Table))
832
+ }
833
+ if metricTag.Index != 0 {
834
+ errs = append(errs, fmt.Errorf("bgp[%d].metric_tags[%d]: scalar metric_tags do not support `index` lookups (tag=%q, index=%d)", i, j, metricTag.Tag, metricTag.Index))
835
+ }
836
+ if len(metricTag.IndexTransform) > 0 {
837
+ errs = append(errs, fmt.Errorf("bgp[%d].metric_tags[%d]: scalar metric_tags do not support `index_transform` (tag=%q)", i, j, metricTag.Tag))
838
+ }
839
+ if metricTag.Symbol.OID == "" {
840
+ errs = append(errs, fmt.Errorf("bgp[%d].metric_tags[%d]: scalar metric_tags require `symbol.OID` (tag=%q)", i, j, metricTag.Tag))
841
+ }
842
+ }
843
+ }
844
+ }
845
+
846
+ errs = append(errs, validateBGPTableReferences(rows))
847
+
848
+ return errors.Join(errs...)
849
+}
850
+
851
+func forEachBGPSignalValueConfig(row *BGPConfig, add func(path string, value *BGPValueConfig)) {
852
+ addState := func(path string, value *BGPStateConfig) {
853
+ add(path, &value.BGPValueConfig)
854
+ }
855
+ addDirectional := func(prefix string, value *BGPDirectionalConfig) {
856
+ add(prefix+".received", &value.Received)
857
+ add(prefix+".sent", &value.Sent)
858
+ }
859
+ addTimerPair := func(prefix string, value *BGPTimerPairConfig) {
860
+ add(prefix+".connect_retry", &value.ConnectRetry)
861
+ add(prefix+".hold_time", &value.HoldTime)
862
+ add(prefix+".keepalive_time", &value.KeepaliveTime)
863
+ add(prefix+".min_as_origination_interval", &value.MinASOriginationInterval)
864
+ add(prefix+".min_route_advertisement_interval", &value.MinRouteAdvertisementInterval)
865
+ }
866
+ addNotification := func(prefix string, value *BGPLastNotificationConfig) {
867
+ add(prefix+".code", &value.Code)
868
+ add(prefix+".subcode", &value.Subcode)
869
+ add(prefix+".reason", &value.Reason)
870
+ }
871
+ addRoutes := func(prefix string, value *BGPRouteCountersConfig) {
872
+ add(prefix+".received", &value.Received)
873
+ add(prefix+".accepted", &value.Accepted)
874
+ add(prefix+".rejected", &value.Rejected)
875
+ add(prefix+".active", &value.Active)
876
+ add(prefix+".advertised", &value.Advertised)
877
+ add(prefix+".suppressed", &value.Suppressed)
878
+ add(prefix+".withdrawn", &value.Withdrawn)
879
+ }
880
+
881
+ add("admin.enabled", &row.Admin.Enabled)
882
+ addState("state", &row.State)
883
+ addState("previous_state", &row.Previous)
884
+ add("connection.established_uptime", &row.Connection.EstablishedUptime)
885
+ add("connection.last_received_update_age", &row.Connection.LastReceivedUpdateAge)
886
+ addDirectional("traffic.messages", &row.Traffic.Messages)
887
+ addDirectional("traffic.updates", &row.Traffic.Updates)
888
+ addDirectional("traffic.notifications", &row.Traffic.Notifications)
889
+ addDirectional("traffic.route_refreshes", &row.Traffic.RouteRefreshes)
890
+ addDirectional("traffic.opens", &row.Traffic.Opens)
891
+ addDirectional("traffic.keepalives", &row.Traffic.Keepalives)
892
+ add("transitions.established", &row.Transitions.Established)
893
+ add("transitions.down", &row.Transitions.Down)
894
+ add("transitions.up", &row.Transitions.Up)
895
+ add("transitions.flaps", &row.Transitions.Flaps)
896
+ addTimerPair("timers.negotiated", &row.Timers.Negotiated)
897
+ addTimerPair("timers.configured", &row.Timers.Configured)
898
+ add("last_error.code", &row.LastError.Code)
899
+ add("last_error.subcode", &row.LastError.Subcode)
900
+ addNotification("last_notifications.received", &row.LastNotify.Received)
901
+ addNotification("last_notifications.sent", &row.LastNotify.Sent)
902
+ add("reasons.last_down", &row.Reasons.LastDown)
903
+ add("reasons.unavailability", &row.Reasons.Unavailability)
904
+ add("graceful_restart.state", &row.Restart.State)
905
+ addRoutes("routes.current", &row.Routes.Current)
906
+ addRoutes("routes.total", &row.Routes.Total)
907
+ add("route_limits.limit", &row.RouteLimits.Limit)
908
+ add("route_limits.threshold", &row.RouteLimits.Threshold)
909
+ add("route_limits.clear_threshold", &row.RouteLimits.ClearThreshold)
910
+ add("device_counts.peers", &row.Device.Peers)
911
+ add("device_counts.ibgp_peers", &row.Device.InternalPeers)
912
+ add("device_counts.ebgp_peers", &row.Device.ExternalPeers)
913
+ add("device_counts.states.idle", &row.Device.States.Idle)
914
+ add("device_counts.states.connect", &row.Device.States.Connect)
915
+ add("device_counts.states.active", &row.Device.States.Active)
916
+ add("device_counts.states.opensent", &row.Device.States.OpenSent)
917
+ add("device_counts.states.openconfirm", &row.Device.States.OpenConfirm)
918
+ add("device_counts.states.established", &row.Device.States.Established)
919
+}
920
+
921
+func validateEnrichBGPRowShape(rowIdx int, row *BGPConfig) error {
922
+ var errs []error
923
+
924
+ if row.Kind == "" {
925
+ errs = append(errs, fmt.Errorf("bgp[%d].kind: missing kind", rowIdx))
926
+ } else if !IsValidBGPRowKind(row.Kind) {
927
+ errs = append(errs, fmt.Errorf("bgp[%d].kind: invalid kind %q", rowIdx, row.Kind))
928
+ }
929
+ if row.Table.Name != "" && row.Table.OID == "" {
930
+ errs = append(errs, fmt.Errorf("bgp[%d].table: table name %q requires table OID", rowIdx, row.Table.Name))
931
+ }
932
+ if row.Table.OID != "" && row.Table.Name == "" {
933
+ errs = append(errs, fmt.Errorf("bgp[%d].table: table OID %q requires table name", rowIdx, row.Table.OID))
934
+ }
935
+ if !bgpRowHasSignalConfigs(*row) {
936
+ errs = append(errs, fmt.Errorf("bgp[%d]: must define at least one typed BGP field", rowIdx))
937
+ }
938
+
939
+ switch row.Kind {
940
+ case BGPRowKindPeer:
941
+ if !row.Identity.Neighbor.IsSet() {
942
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.neighbor: peer rows require neighbor identity", rowIdx))
943
+ }
944
+ if !row.Identity.RemoteAS.IsSet() {
945
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.remote_as: peer rows require remote_as identity", rowIdx))
946
+ }
947
+ case BGPRowKindPeerFamily:
948
+ if !row.Identity.Neighbor.IsSet() {
949
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.neighbor: peer_family rows require neighbor identity", rowIdx))
950
+ }
951
+ if !row.Identity.RemoteAS.IsSet() {
952
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.remote_as: peer_family rows require remote_as identity", rowIdx))
953
+ }
954
+ if !row.Identity.AddressFamily.IsSet() {
955
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.address_family: peer_family rows require address_family identity", rowIdx))
956
+ }
957
+ if !row.Identity.SubsequentAddressFamily.IsSet() {
958
+ errs = append(errs, fmt.Errorf("bgp[%d].identity.subsequent_address_family: peer_family rows require subsequent_address_family identity", rowIdx))
959
+ }
960
+ }
961
+
962
+ if row.Table.OID == "" && row.ID == "" {
963
+ sourceOIDs := collectBGPSignalSourceOIDs(*row)
964
+ switch {
965
+ case len(sourceOIDs) == 0:
966
+ errs = append(errs, fmt.Errorf("bgp[%d]: scalar rows without a typed-field source OID require explicit id", rowIdx))
967
+ case len(sourceOIDs) > 1:
968
+ errs = append(errs, fmt.Errorf("bgp[%d]: scalar rows with multiple typed-field source OIDs require explicit id", rowIdx))
969
+ }
970
+ }
971
+
972
+ return errors.Join(errs...)
973
+}
974
+
975
+func validateBGPGroupKindCompatibility(rowIdx int, row *BGPConfig) error {
976
+ var errs []error
977
+ ForEachBGPSignalValue(*row, func(path string, _ BGPValueConfig) {
978
+ switch {
979
+ case row.Kind == BGPRowKindDevice && !strings.HasPrefix(path, "device_counts."):
980
+ errs = append(errs, fmt.Errorf("bgp[%d].%s: device rows only support device_counts fields", rowIdx, path))
981
+ case strings.HasPrefix(path, "device_counts.") && row.Kind != BGPRowKindDevice:
982
+ errs = append(errs, fmt.Errorf("bgp[%d].%s: device_counts fields require kind=device", rowIdx, path))
983
+ case (strings.HasPrefix(path, "routes.") || strings.HasPrefix(path, "route_limits.")) && row.Kind != BGPRowKindPeerFamily:
984
+ errs = append(errs, fmt.Errorf("bgp[%d].%s: route fields require kind=peer_family", rowIdx, path))
985
+ }
986
+ })
987
+ return errors.Join(errs...)
988
+}
989
+
990
+func validateEnrichBGPState(path string, state *BGPStateConfig, isTable bool) error {
991
+ var errs []error
992
+ if !state.BGPValueConfig.IsSet() {
993
+ if state.Partial {
994
+ errs = append(errs, fmt.Errorf("%s.partial: partial requires state value source", path))
995
+ }
996
+ return errors.Join(errs...)
997
+ }
998
+
999
+ errs = append(errs, validateEnrichBGPValue(path, &state.BGPValueConfig, isTable))
1000
+ errs = append(errs, validateBGPPeerStateMapping(path, state))
1001
+ return errors.Join(errs...)
1002
+}
1003
+
1004
+func validateEnrichBGPSignalValues(rowIdx int, row *BGPConfig, isTable bool) error {
1005
+ var errs []error
1006
+ errs = append(errs, validateEnrichBGPState(fmt.Sprintf("bgp[%d].state", rowIdx), &row.State, isTable))
1007
+ errs = append(errs, validateEnrichBGPState(fmt.Sprintf("bgp[%d].previous_state", rowIdx), &row.Previous, isTable))
1008
+ forEachBGPSignalValueConfig(row, func(path string, value *BGPValueConfig) {
1009
+ if path == "state" || path == "previous_state" {
1010
+ return
1011
+ }
1012
+ errs = append(errs, validateEnrichBGPValue(fmt.Sprintf("bgp[%d].%s", rowIdx, path), value, isTable))
1013
+ })
1014
+ return errors.Join(errs...)
1015
+}
1016
+
1017
+func validateEnrichBGPAddressFamilyValue(path string, value *BGPAddressFamilyValueConfig, isTable bool) error {
1018
+ var errs []error
1019
+ errs = append(errs, validateEnrichBGPValue(path, &value.BGPValueConfig, isTable))
1020
+ if !value.AllowPrivate {
1021
+ errs = append(errs, validateBGPAddressFamilyValues(path, value.BGPValueConfig))
1022
+ }
1023
+ return errors.Join(errs...)
1024
+}
1025
+
1026
+func validateEnrichBGPSubsequentAddressFamilyValue(path string, value *BGPSubsequentAddressFamilyValueConfig, isTable bool) error {
1027
+ var errs []error
1028
+ errs = append(errs, validateEnrichBGPValue(path, &value.BGPValueConfig, isTable))
1029
+ if !value.AllowPrivate {
1030
+ errs = append(errs, validateBGPSubsequentAddressFamilyValues(path, value.BGPValueConfig))
1031
+ }
1032
+ return errors.Join(errs...)
1033
+}
1034
+
1035
+func validateEnrichBGPValue(path string, value *BGPValueConfig, isTable bool) error {
1036
+ var errs []error
1037
+ if !value.IsSet() {
1038
+ return nil
1039
+ }
1040
+
1041
+ if !bgpValueHasSource(*value) {
1042
+ errs = append(errs, fmt.Errorf("%s: must define value, from, symbol.OID, OID, index, index_from_end, or index_transform", path))
1043
+ }
1044
+ errs = append(errs, validateMapping(value.Mapping, MetadataSymbol))
1045
+ errs = append(errs, validateBGPRowIndexSelector(path, value))
1046
+
1047
+ if strings.HasPrefix(value.Name, "_") {
1048
+ errs = append(errs, fmt.Errorf("%s.name: name %q cannot be underscore-prefixed", path, value.Name))
1049
+ }
1050
+ if !isTable {
1051
+ if value.Table != "" {
1052
+ errs = append(errs, fmt.Errorf("%s.table: scalar BGP values do not support `table` lookups", path))
1053
+ }
1054
+ if value.Index != 0 {
1055
+ errs = append(errs, fmt.Errorf("%s.index: scalar BGP values do not support `index` lookups", path))
1056
+ }
1057
+ if value.IndexFromEnd != 0 {
1058
+ errs = append(errs, fmt.Errorf("%s.index_from_end: scalar BGP values do not support `index_from_end` lookups", path))
1059
+ }
1060
+ if len(value.IndexTransform) > 0 {
1061
+ errs = append(errs, fmt.Errorf("%s.index_transform: scalar BGP values do not support `index_transform`", path))
1062
+ }
1063
+ }
1064
+ if value.Table != "" && bgpValueSymbolForValidation(*value).OID == "" {
1065
+ errs = append(errs, fmt.Errorf("%s.table: table lookups require symbol.OID, OID, or from", path))
1066
+ }
1067
+ if value.LookupSymbol.OID != "" || value.LookupSymbol.Name != "" {
1068
+ if value.Table == "" {
1069
+ errs = append(errs, fmt.Errorf("%s.lookup_symbol: lookup_symbol requires table", path))
1070
+ }
1071
+ lookupSymbol := SymbolConfig(value.LookupSymbol)
1072
+ errs = append(errs, validateEnrichBGPSymbol(path+".lookup_symbol", &lookupSymbol))
1073
+ value.LookupSymbol = SymbolConfigCompat(lookupSymbol)
1074
+ }
1075
+ if value.Symbol.OID != "" || value.Symbol.Name != "" {
1076
+ errs = append(errs, validateEnrichBGPSymbol(path, &value.Symbol))
1077
+ }
1078
+
1079
+ return errors.Join(errs...)
1080
+}
1081
+
1082
+func validateBGPRowIndexSelector(path string, value *BGPValueConfig) error {
1083
+ var selectors []string
1084
+ if value.Index != 0 {
1085
+ selectors = append(selectors, "index")
1086
+ }
1087
+ if value.IndexFromEnd != 0 {
1088
+ selectors = append(selectors, "index_from_end")
1089
+ }
1090
+ if len(value.IndexTransform) > 0 {
1091
+ selectors = append(selectors, "index_transform")
1092
+ }
1093
+ if len(selectors) <= 1 {
1094
+ return nil
1095
+ }
1096
+ return fmt.Errorf("%s: index, index_from_end, and index_transform are mutually exclusive (set: %s)", path, strings.Join(selectors, ", "))
1097
+}
1098
+
1099
+func validateEnrichBGPSymbol(path string, symbol *SymbolConfig) error {
1100
+ var errs []error
1101
+
1102
+ errs = append(errs, validateEnrichSymbol(symbol, MetadataSymbol))
1103
+ if strings.HasPrefix(symbol.Name, "_") {
1104
+ errs = append(errs, fmt.Errorf("%s.symbol: name %q cannot be underscore-prefixed", path, symbol.Name))
1105
+ }
1106
+ if symbol.ChartMeta != (ChartMeta{}) {
1107
+ errs = append(errs, fmt.Errorf("%s.symbol: chart_meta cannot be used in BGP rows", path))
1108
+ }
1109
+ if symbol.MetricType != "" {
1110
+ errs = append(errs, fmt.Errorf("%s.symbol: metric_type cannot be used in BGP rows", path))
1111
+ }
1112
+ if symbol.Transform != "" {
1113
+ errs = append(errs, fmt.Errorf("%s.symbol: transform cannot be used in BGP rows", path))
1114
+ }
1115
+ if symbol.ScaleFactor != 0 {
1116
+ errs = append(errs, fmt.Errorf("%s.symbol: scale_factor cannot be used in BGP rows", path))
1117
+ }
1118
+ if symbol.ConstantValueOne {
1119
+ errs = append(errs, fmt.Errorf("%s.symbol: constant_value_one cannot be used in BGP rows", path))
1120
+ }
1121
+
1122
+ return errors.Join(errs...)
1123
+}
1124
+
1125
+func validateBGPPeerStateMapping(path string, state *BGPStateConfig) error {
1126
+ mapping := effectiveBGPValueMapping(state.BGPValueConfig)
1127
+ if !mapping.HasItems() {
1128
+ if state.Partial {
1129
+ return fmt.Errorf("%s.mapping: partial state mapping requires at least one RFC 4271 state", path)
1130
+ }
1131
+ return fmt.Errorf("%s.mapping: state mapping must cover all six RFC 4271 states", path)
1132
+ }
1133
+
1134
+ var errs []error
1135
+ covered := make(map[BGPPeerState]bool)
1136
+ for raw, mapped := range mapping.Items {
1137
+ peerState := BGPPeerState(mapped)
1138
+ if !IsValidBGPPeerState(peerState) {
1139
+ errs = append(errs, fmt.Errorf("%s.mapping.items[%s]: invalid BGP peer state %q", path, raw, mapped))
1140
+ continue
1141
+ }
1142
+ covered[peerState] = true
1143
+ }
1144
+ for i, partial := range state.PartialStates {
1145
+ if !IsValidBGPPeerState(partial) {
1146
+ errs = append(errs, fmt.Errorf("%s.partial_states[%d]: invalid BGP peer state %q", path, i, partial))
1147
+ }
1148
+ }
1149
+ if state.Partial {
1150
+ return errors.Join(errs...)
1151
+ }
1152
+ for _, required := range requiredBGPPeerStates {
1153
+ if !covered[required] {
1154
+ errs = append(errs, fmt.Errorf("%s.mapping: missing RFC 4271 state %q", path, required))
1155
+ }
1156
+ }
1157
+ return errors.Join(errs...)
1158
+}
1159
+
1160
+func validateBGPAddressFamilyValues(path string, value BGPValueConfig) error {
1161
+ return validateBGPEnumValues(path, value, func(v string) bool {
1162
+ return IsValidBGPAddressFamily(BGPAddressFamily(v))
1163
+ }, "address_family")
1164
+}
1165
+
1166
+func validateBGPSubsequentAddressFamilyValues(path string, value BGPValueConfig) error {
1167
+ return validateBGPEnumValues(path, value, func(v string) bool {
1168
+ return IsValidBGPSubsequentAddressFamily(BGPSubsequentAddressFamily(v))
1169
+ }, "subsequent_address_family")
1170
+}
1171
+
1172
+func validateBGPEnumValues(path string, value BGPValueConfig, valid func(string) bool, label string) error {
1173
+ var errs []error
1174
+ if value.Value != "" && !valid(value.Value) {
1175
+ errs = append(errs, fmt.Errorf("%s.value: invalid BGP %s %q", path, label, value.Value))
1176
+ }
1177
+ for raw, mapped := range effectiveBGPValueMapping(value).Items {
1178
+ if !valid(mapped) {
1179
+ errs = append(errs, fmt.Errorf("%s.mapping.items[%s]: invalid BGP %s %q", path, raw, label, mapped))
1180
+ }
1181
+ }
1182
+ return errors.Join(errs...)
1183
+}
1184
+
1185
+func effectiveBGPValueMapping(value BGPValueConfig) MappingConfig {
1186
+ if value.Mapping.HasItems() || value.Mapping.Mode != "" {
1187
+ return value.Mapping
1188
+ }
1189
+ return value.Symbol.Mapping
1190
+}
1191
+
1192
+type bgpSignalValidationKey struct {
1193
+ identity string
1194
+ path string
1195
+}
1196
+
1197
+func validateBGPSignalDuplicates(rowIdx int, row *BGPConfig, seen map[bgpSignalValidationKey]string) error {
1198
+ var errs []error
1199
+ identity := BGPStructuralIdentity(*row)
1200
+ ForEachBGPSignalValue(*row, func(path string, _ BGPValueConfig) {
1201
+ fullPath := fmt.Sprintf("bgp[%d].%s", rowIdx, path)
1202
+ key := bgpSignalValidationKey{identity: identity, path: path}
1203
+ if prev, ok := seen[key]; ok {
1204
+ errs = append(errs, fmt.Errorf("%s: duplicate BGP field for structural identity %q (first seen at %s)", fullPath, identity, prev))
1205
+ return
1206
+ }
1207
+ seen[key] = fullPath
1208
+ })
1209
+ return errors.Join(errs...)
1210
+}
1211
+
1212
+func validateBGPFromReferences(rowIdx int, row *BGPConfig) error {
1213
+ if row.Table.OID == "" {
1214
+ return nil
1215
+ }
1216
+
1217
+ var errs []error
1218
+ tableOID := TrimBGPOID(row.Table.OID)
1219
+ for _, ref := range collectBGPValueReferences(*row) {
1220
+ if ref.value.From == "" {
1221
+ continue
1222
+ }
1223
+ if ref.value.Table != "" {
1224
+ continue
1225
+ }
1226
+ fromOID := TrimBGPOID(ref.value.From)
1227
+ if !oidHasPrefix(fromOID, tableOID) {
1228
+ errs = append(errs, fmt.Errorf("bgp[%d].%s.from: OID %q is outside table %q", rowIdx, ref.path, ref.value.From, row.Table.OID))
1229
+ }
1230
+ }
1231
+ return errors.Join(errs...)
1232
+}
1233
+
1234
+func validateBGPTableReferences(rows []BGPConfig) error {
1235
+ tableNameToOID := make(map[string]string)
1236
+ for i := range rows {
1237
+ row := &rows[i]
1238
+ if row.Table.Name != "" && row.Table.OID != "" {
1239
+ tableNameToOID[row.Table.Name] = TrimBGPOID(row.Table.OID)
1240
+ }
1241
+ }
1242
+
1243
+ var errs []error
1244
+ for rowIdx := range rows {
1245
+ row := &rows[rowIdx]
1246
+ for _, ref := range collectBGPValueReferences(*row) {
1247
+ if ref.value.Table == "" {
1248
+ continue
1249
+ }
1250
+ refTableOID, ok := tableNameToOID[ref.value.Table]
1251
+ if !ok {
1252
+ continue
1253
+ }
1254
+ sourceOID := TrimBGPOID(BGPValueSourceOID(ref.value))
1255
+ if sourceOID != "" && !oidHasPrefix(sourceOID, refTableOID) {
1256
+ errs = append(errs, fmt.Errorf("bgp[%d].%s.table: referenced table %q uses OID %q; source OID %q is outside referenced table", rowIdx, ref.path, ref.value.Table, refTableOID, sourceOID))
1257
+ }
1258
+ lookupOID := TrimBGPOID(SymbolConfig(ref.value.LookupSymbol).OID)
1259
+ if lookupOID != "" && !oidHasPrefix(lookupOID, refTableOID) {
1260
+ errs = append(errs, fmt.Errorf("bgp[%d].%s.lookup_symbol: referenced table %q uses OID %q; lookup OID %q is outside referenced table", rowIdx, ref.path, ref.value.Table, refTableOID, lookupOID))
1261
+ }
1262
+ }
1263
+ }
1264
+ return errors.Join(errs...)
1265
+}
1266
+
1267
+type bgpValueValidationRef struct {
1268
+ path string
1269
+ value BGPValueConfig
1270
+}
1271
+
1272
+func collectBGPValueReferences(row BGPConfig) []bgpValueValidationRef {
1273
+ var values []bgpValueValidationRef
1274
+ add := func(path string, value BGPValueConfig) {
1275
+ if value.IsSet() {
1276
+ values = append(values, bgpValueValidationRef{path: path, value: value})
1277
+ }
1278
+ }
1279
+ add("identity.routing_instance", row.Identity.RoutingInstance)
1280
+ add("identity.neighbor", row.Identity.Neighbor)
1281
+ add("identity.remote_as", row.Identity.RemoteAS)
1282
+ add("identity.address_family", row.Identity.AddressFamily.BGPValueConfig)
1283
+ add("identity.subsequent_address_family", row.Identity.SubsequentAddressFamily.BGPValueConfig)
1284
+ add("descriptors.local_address", row.Descriptors.LocalAddress)
1285
+ add("descriptors.local_as", row.Descriptors.LocalAS)
1286
+ add("descriptors.local_identifier", row.Descriptors.LocalIdentifier)
1287
+ add("descriptors.peer_identifier", row.Descriptors.PeerIdentifier)
1288
+ add("descriptors.peer_type", row.Descriptors.PeerType)
1289
+ add("descriptors.bgp_version", row.Descriptors.BGPVersion)
1290
+ add("descriptors.description", row.Descriptors.Description)
1291
+ ForEachBGPSignalValue(row, add)
1292
+ return values
1293
+}
1294
+
1295
+func bgpRowHasSignalConfigs(row BGPConfig) bool {
1296
+ hasSignals := false
1297
+ ForEachBGPSignalValue(row, func(_ string, _ BGPValueConfig) {
1298
+ hasSignals = true
1299
+ })
1300
+ return hasSignals
1301
+}
1302
+
1303
+func collectBGPSignalSourceOIDs(row BGPConfig) map[string]struct{} {
1304
+ oids := make(map[string]struct{})
1305
+ ForEachBGPSignalValue(row, func(_ string, value BGPValueConfig) {
1306
+ if oid := BGPValueSourceOID(value); oid != "" {
1307
+ oids[TrimBGPOID(oid)] = struct{}{}
1308
+ }
1309
+ })
1310
+ return oids
1311
+}
1312
+
1313
+func bgpValueHasSource(value BGPValueConfig) bool {
1314
+ return value.Value != "" ||
1315
+ value.From != "" ||
1316
+ value.Symbol.OID != "" ||
1317
+ value.OID != "" ||
1318
+ value.Index != 0 ||
1319
+ value.IndexFromEnd != 0 ||
1320
+ len(value.IndexTransform) > 0
1321
+}
1322
+
1323
+func bgpValueSymbolForValidation(value BGPValueConfig) SymbolConfig {
1324
+ sym := value.Symbol
1325
+ if sym.OID == "" {
1326
+ switch {
1327
+ case value.From != "":
1328
+ sym.OID = value.From
1329
+ case value.OID != "":
1330
+ sym.OID = value.OID
1331
+ }
1332
+ }
1333
+ return sym
1334
+}
1335
+
1336
func validateEnrichTopologySymbol(topologyIdx int, symbol *SymbolConfig, symbolContext SymbolContext) error {
1337
var errs []error
1338
@@ -808,7 +1379,7 @@ func validateConsumers(path string, consumers ConsumerSet) error {
1379
seen := make(map[ProfileConsumer]int)
1380
for i, consumer := range consumers {
1381
switch consumer {
811
- case ConsumerMetrics, ConsumerTopology, ConsumerLicensing:
1382
+ case ConsumerMetrics, ConsumerTopology, ConsumerLicensing, ConsumerBGP:
1383
default:
1384
errs = append(errs, fmt.Errorf("%s[%d]: invalid consumer %q", path, i, consumer))
1385
continue
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/yaml_utils_test.go
+13
-12
@@ -11,6 +11,7 @@ import (
11
"gopkg.in/yaml.v2"
12
13
"github.com/stretchr/testify/assert"
14
+ "github.com/stretchr/testify/require"
15
)
16
17
type MyStringArray struct {
@@ -29,10 +30,10 @@ func Test_metricTagConfig_UnmarshalYAML(t *testing.T) {
30
myStruct := MetricsConfig{}
31
expected := MetricsConfig{MetricTags: []MetricTagConfig{{Index: 3}}}
32
32
- yaml.Unmarshal([]byte(`
33
+ require.NoError(t, yaml.Unmarshal([]byte(`
34
metric_tags:
35
- index: 3
35
-`), &myStruct)
36
+`), &myStruct))
37
38
assert.Equal(t, expected, myStruct)
39
}
@@ -41,10 +42,10 @@ func Test_metricTagConfig_onlyTags(t *testing.T) {
42
myStruct := MetricsConfig{}
43
expected := MetricsConfig{MetricTags: []MetricTagConfig{{SymbolTag: "aaa"}}}
44
44
- yaml.Unmarshal([]byte(`
45
+ require.NoError(t, yaml.Unmarshal([]byte(`
46
metric_tags:
47
- aaa
47
-`), &myStruct)
48
+`), &myStruct))
49
50
assert.Equal(t, expected, myStruct)
51
}
@@ -53,11 +54,11 @@ func TestStringArray_UnmarshalYAML_array(t *testing.T) {
54
myStruct := MyStringArray{}
55
expected := MyStringArray{SomeIDs: StringArray{"aaa", "bbb"}}
56
56
- yaml.Unmarshal([]byte(`
57
+ require.NoError(t, yaml.Unmarshal([]byte(`
58
my_field:
59
- aaa
60
- bbb
60
-`), &myStruct)
61
+`), &myStruct))
62
63
assert.Equal(t, expected, myStruct)
64
}
@@ -66,9 +67,9 @@ func TestStringArray_UnmarshalYAML_string(t *testing.T) {
67
myStruct := MyStringArray{}
68
expected := MyStringArray{SomeIDs: StringArray{"aaa"}}
69
69
- yaml.Unmarshal([]byte(`
70
+ require.NoError(t, yaml.Unmarshal([]byte(`
71
my_field: aaa
71
-`), &myStruct)
72
+`), &myStruct))
73
74
assert.Equal(t, expected, myStruct)
75
}
@@ -77,11 +78,11 @@ func TestSymbolConfig_UnmarshalYAML_symbolObject(t *testing.T) {
78
myStruct := MySymbolStruct{}
79
expected := MySymbolStruct{SymbolField: SymbolConfigCompat{OID: "1.2.3", Name: "aSymbol"}}
80
80
- yaml.Unmarshal([]byte(`
81
+ require.NoError(t, yaml.Unmarshal([]byte(`
82
my_symbol_field:
83
name: aSymbol
84
OID: 1.2.3
84
-`), &myStruct)
85
+`), &myStruct))
86
87
assert.Equal(t, expected, myStruct)
88
}
@@ -90,9 +91,9 @@ func TestSymbolConfig_UnmarshalYAML_symbolString(t *testing.T) {
91
myStruct := MySymbolStruct{}
92
expected := MySymbolStruct{SymbolField: SymbolConfigCompat{Name: "aSymbol"}}
93
93
- yaml.Unmarshal([]byte(`
94
+ require.NoError(t, yaml.Unmarshal([]byte(`
95
my_symbol_field: aSymbol
95
-`), &myStruct)
96
+`), &myStruct))
97
98
assert.Equal(t, expected, myStruct)
99
}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/alcatel_bgp_profile_test.go
new
+193
@@ -0,0 +1,193 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_AlcatelBGP_TypedRows(t *testing.T) {
18
+ sysObjectID := "1.3.6.1.4.1.6486.801.1.1.2.1.1"
19
+ profile := matchedBGPProjectionByFile(t, sysObjectID, "alcatel-lucent-ent.yaml")
20
+
21
+ ipv4Idx := "192.0.2.10"
22
+ ipv6Idx := "32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1"
23
+ ipv6Neighbor := []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
24
+ ipv6Local := []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
25
+ pdus := []gosnmp.SnmpPDU{
26
+ createPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.1", ipv4Idx), gosnmp.IPAddress, "198.51.100.1"),
27
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.2", ipv4Idx), 6),
28
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.3", ipv4Idx), 2),
29
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.4", ipv4Idx), 4),
30
+ createPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.5", ipv4Idx), gosnmp.IPAddress, "192.0.2.1"),
31
+ createPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.7", ipv4Idx), gosnmp.IPAddress, "192.0.2.10"),
32
+ createGauge32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.9", ipv4Idx), 65001),
33
+ createCounter32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.10", ipv4Idx), 12),
34
+ createCounter32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.11", ipv4Idx), 13),
35
+ createCounter32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.12", ipv4Idx), 100),
36
+ createCounter32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.13", ipv4Idx), 110),
37
+ createPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.14", ipv4Idx), gosnmp.OctetString, []byte{0x02, 0x03}),
38
+ createCounter32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.15", ipv4Idx), 5),
39
+ createGauge32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.16", ipv4Idx), 3600),
40
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.17", ipv4Idx), 30),
41
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.18", ipv4Idx), 90),
42
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.19", ipv4Idx), 30),
43
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.20", ipv4Idx), 180),
44
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.21", ipv4Idx), 60),
45
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.22", ipv4Idx), 15),
46
+ createIntegerPDU(oidWithIndex("1.3.6.1.2.1.15.3.1.23", ipv4Idx), 30),
47
+ createGauge32PDU(oidWithIndex("1.3.6.1.2.1.15.3.1.24", ipv4Idx), 7),
48
+
49
+ createPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.1", ipv4Idx), gosnmp.IPAddress, "192.0.2.10"),
50
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.2", ipv4Idx), 65001),
51
+ createStringPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.4", ipv4Idx), "alcatel-ipv4"),
52
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.13", ipv4Idx), 3),
53
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.14", ipv4Idx), 4),
54
+ createPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.17", ipv4Idx), gosnmp.IPAddress, "192.0.2.1"),
55
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.18", ipv4Idx), 7),
56
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.21", ipv4Idx), 1),
57
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.22", ipv4Idx), 2),
58
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.23", ipv4Idx), 25),
59
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.24", ipv4Idx), 22),
60
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.25", ipv4Idx), 42),
61
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.26", ipv4Idx), 2),
62
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.27", ipv4Idx), 2),
63
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.47", ipv4Idx), 6),
64
+
65
+ createPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.1", ipv6Idx), gosnmp.OctetString, ipv6Neighbor),
66
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.2", ipv6Idx), 65002),
67
+ createStringPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.4", ipv6Idx), "alcatel-ipv6"),
68
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.13", ipv6Idx), 5),
69
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.14", ipv6Idx), 6),
70
+ createPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.17", ipv6Idx), gosnmp.OctetString, ipv6Local),
71
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.18", ipv6Idx), 9),
72
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.21", ipv6Idx), 7),
73
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.22", ipv6Idx), 8),
74
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.23", ipv6Idx), 32),
75
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.24", ipv6Idx), 23),
76
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.25", ipv6Idx), 84),
77
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.26", ipv6Idx), 1),
78
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.27", ipv6Idx), 1),
79
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.47", ipv6Idx), 8),
80
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.49", ipv6Idx), 200),
81
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.50", ipv6Idx), 210),
82
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.51", ipv6Idx), 22),
83
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.52", ipv6Idx), 23),
84
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.61", ipv6Idx), 90),
85
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.62", ipv6Idx), 30),
86
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.63", ipv6Idx), 30),
87
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.64", ipv6Idx), 180),
88
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.65", ipv6Idx), 60),
89
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.68", ipv6Idx), 2),
90
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.69", ipv6Idx), 6),
91
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.73", ipv6Idx), 30),
92
+ }
93
+
94
+ ctrl, mockHandler := setupMockHandler(t)
95
+ defer ctrl.Finish()
96
+
97
+ expectSystemMetadataGets(mockHandler, sysObjectID, "alcatel")
98
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
99
+
100
+ collector := New(Config{
101
+ SnmpClient: mockHandler,
102
+ Profiles: []*ddsnmp.Profile{profile},
103
+ Log: logger.New(),
104
+ SysObjectID: sysObjectID,
105
+ })
106
+
107
+ results, err := collector.Collect()
108
+ require.NoError(t, err)
109
+ require.Len(t, results, 1)
110
+ require.Empty(t, results[0].Metrics)
111
+ require.Empty(t, results[0].TopologyMetrics)
112
+ require.Empty(t, results[0].LicenseRows)
113
+ require.Len(t, results[0].BGPRows, 4)
114
+
115
+ tests := map[string]struct {
116
+ match func(ddsnmp.BGPRow) bool
117
+ validate func(t *testing.T, row ddsnmp.BGPRow)
118
+ }{
119
+ "IPv4 peer row": {
120
+ match: func(row ddsnmp.BGPRow) bool {
121
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "192.0.2.10"
122
+ },
123
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
124
+ assert.Equal(t, "default", row.Identity.RoutingInstance)
125
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
126
+ assert.Equal(t, "192.0.2.1", row.Descriptors.LocalAddress)
127
+ assert.Equal(t, "alcatel-ipv4", row.Descriptors.Description)
128
+ assert.Equal(t, "external", row.Descriptors.PeerType)
129
+ require.True(t, row.Admin.Enabled.Has)
130
+ assert.True(t, row.Admin.Enabled.Value)
131
+ require.True(t, row.State.Has)
132
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
133
+ assert.EqualValues(t, 12, row.Traffic.Updates.Received.Value)
134
+ assert.EqualValues(t, 13, row.Traffic.Updates.Sent.Value)
135
+ assert.EqualValues(t, 3, row.Traffic.RouteRefreshes.Received.Value)
136
+ assert.EqualValues(t, 4, row.Traffic.RouteRefreshes.Sent.Value)
137
+ assert.EqualValues(t, 2, row.LastError.Code.Value)
138
+ assert.EqualValues(t, 3, row.LastError.Subcode.Value)
139
+ assert.Equal(t, "peer_notify", row.Reasons.LastDown.Value)
140
+ assert.Equal(t, "hold_timeout", row.LastNotify.Received.Reason.Value)
141
+ assert.Equal(t, "cease_admin_shutdown", row.LastNotify.Sent.Reason.Value)
142
+ },
143
+ },
144
+ "IPv4 peer-family row": {
145
+ match: func(row ddsnmp.BGPRow) bool {
146
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily && row.Identity.Neighbor == "192.0.2.10"
147
+ },
148
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
149
+ assert.Equal(t, ddprofiledefinition.BGPAddressFamilyIPv4, row.Identity.AddressFamily)
150
+ assert.Equal(t, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast, row.Identity.SubsequentAddressFamily)
151
+ assert.EqualValues(t, 42, row.Routes.Current.Received.Value)
152
+ },
153
+ },
154
+ "IPv6 peer row": {
155
+ match: func(row ddsnmp.BGPRow) bool {
156
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "2001:db8::1"
157
+ },
158
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
159
+ assert.Equal(t, "65002", row.Identity.RemoteAS)
160
+ assert.Equal(t, "2001:db8::2", row.Descriptors.LocalAddress)
161
+ assert.Equal(t, "internal", row.Descriptors.PeerType)
162
+ require.True(t, row.Admin.Enabled.Has)
163
+ assert.True(t, row.Admin.Enabled.Value)
164
+ require.True(t, row.State.Has)
165
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
166
+ assert.EqualValues(t, 22, row.Traffic.Updates.Received.Value)
167
+ assert.EqualValues(t, 23, row.Traffic.Updates.Sent.Value)
168
+ assert.EqualValues(t, 5, row.Traffic.RouteRefreshes.Received.Value)
169
+ assert.EqualValues(t, 6, row.Traffic.RouteRefreshes.Sent.Value)
170
+ assert.Equal(t, "none", row.Reasons.LastDown.Value)
171
+ assert.Equal(t, "fsm_error", row.LastNotify.Received.Reason.Value)
172
+ assert.Equal(t, "none", row.LastNotify.Sent.Reason.Value)
173
+ },
174
+ },
175
+ "IPv6 peer-family row": {
176
+ match: func(row ddsnmp.BGPRow) bool {
177
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily && row.Identity.Neighbor == "2001:db8::1"
178
+ },
179
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
180
+ assert.Equal(t, ddprofiledefinition.BGPAddressFamilyIPv6, row.Identity.AddressFamily)
181
+ assert.Equal(t, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast, row.Identity.SubsequentAddressFamily)
182
+ assert.EqualValues(t, 84, row.Routes.Current.Received.Value)
183
+ },
184
+ },
185
+ }
186
+
187
+ for name, tc := range tests {
188
+ t.Run(name, func(t *testing.T) {
189
+ row := requireBGPRowMatching(t, results[0].BGPRows, tc.match)
190
+ tc.validate(t, row)
191
+ })
192
+ }
193
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/arista_dell_bgp_external_fixture_test.go
new
+219
@@ -0,0 +1,219 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_AristaAndDellBGP_FromLibreNMSFixtures(t *testing.T) {
18
+ tests := map[string]struct {
19
+ sysObjectID string
20
+ profileFile string
21
+ originProfileID string
22
+ fixture string
23
+ validate func(t *testing.T, rows []ddsnmp.BGPRow, originProfileID string)
24
+ }{
25
+ "Arista EOS emits typed peer and peer-family rows": {
26
+ sysObjectID: "1.3.6.1.4.1.30065.1.3011.7050.1958.128",
27
+ profileFile: "arista-switch.yaml",
28
+ originProfileID: "_arista-bgp4-v2.yaml",
29
+ fixture: "librenms/arista_eos_bgp_peer_table.snmprec",
30
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow, originProfileID string) {
31
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
32
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "192.168.0.2"
33
+ })
34
+ assert.Equal(t, originProfileID, peer.OriginProfileID)
35
+ assert.Equal(t, "aristaBgp4V2PeerTable", peer.Table)
36
+ assert.Equal(t, "1", peer.Identity.RoutingInstance)
37
+ assert.Equal(t, "65000", peer.Identity.RemoteAS)
38
+ assert.Equal(t, "192.168.0.1", peer.Descriptors.LocalAddress)
39
+ assert.Equal(t, "ipv4", peer.Descriptors.PeerType)
40
+ assert.Equal(t, "peer.example.com EBGP", peer.Descriptors.Description)
41
+ require.True(t, peer.Admin.Enabled.Has)
42
+ assert.True(t, peer.Admin.Enabled.Value)
43
+ require.True(t, peer.State.Has)
44
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
45
+ assert.EqualValues(t, 13090321, peer.Connection.EstablishedUptime.Value)
46
+ assert.EqualValues(t, 13362513, peer.Traffic.Updates.Received.Value)
47
+ assert.EqualValues(t, 13579799, peer.Traffic.Updates.Sent.Value)
48
+ assert.EqualValues(t, 13579799, peer.Traffic.Messages.Received.Value)
49
+ assert.EqualValues(t, 17644316, peer.Traffic.Messages.Sent.Value)
50
+ assert.EqualValues(t, 0, peer.LastError.Code.Value)
51
+ assert.EqualValues(t, 0, peer.LastError.Subcode.Value)
52
+
53
+ family := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
54
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
55
+ row.Identity.Neighbor == "192.168.0.2" &&
56
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv4 &&
57
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
58
+ })
59
+ assert.Equal(t, "aristaBgp4V2PrefixGaugesTable", family.Table)
60
+ assert.Equal(t, "1", family.Identity.RoutingInstance)
61
+ assert.Equal(t, "65000", family.Identity.RemoteAS)
62
+ assert.Equal(t, "192.168.0.1", family.Descriptors.LocalAddress)
63
+ assert.Equal(t, "peer.example.com EBGP", family.Descriptors.Description)
64
+ assert.EqualValues(t, 540336, family.Routes.Current.Received.Value)
65
+ assert.EqualValues(t, 540339, family.Routes.Current.Accepted.Value)
66
+ },
67
+ },
68
+ "Dell OS10 emits typed peer and peer-family rows": {
69
+ sysObjectID: "1.3.6.1.4.1.674.11000.5000.100.2.1.7",
70
+ profileFile: "dell-os10.yaml",
71
+ originProfileID: "_dell-os10-bgp4-v2.yaml",
72
+ fixture: "librenms/dell_os10_bgp_peer_table.snmprec",
73
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow, originProfileID string) {
74
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
75
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "169.254.247.1"
76
+ })
77
+ assert.Equal(t, originProfileID, peer.OriginProfileID)
78
+ assert.Equal(t, "dell.os10bgp4V2PeerTable", peer.Table)
79
+ assert.Equal(t, "1", peer.Identity.RoutingInstance)
80
+ assert.Equal(t, "64513", peer.Identity.RemoteAS)
81
+ assert.Equal(t, "169.254.247.2", peer.Descriptors.LocalAddress)
82
+ assert.Equal(t, "64514", peer.Descriptors.LocalAS)
83
+ assert.Equal(t, "192.168.255.10", peer.Descriptors.LocalIdentifier)
84
+ assert.Equal(t, "54.240.205.233", peer.Descriptors.PeerIdentifier)
85
+ assert.Equal(t, "DIRECT CONNECT", peer.Descriptors.Description)
86
+ require.True(t, peer.Admin.Enabled.Has)
87
+ assert.False(t, peer.Admin.Enabled.Value)
88
+ require.True(t, peer.State.Has)
89
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
90
+ assert.EqualValues(t, 28063000, peer.Connection.EstablishedUptime.Value)
91
+ assert.EqualValues(t, 27997600, peer.Connection.LastReceivedUpdateAge.Value)
92
+ assert.EqualValues(t, 17, peer.Traffic.Updates.Received.Value)
93
+ assert.EqualValues(t, 4830, peer.Traffic.Updates.Sent.Value)
94
+ assert.EqualValues(t, 430177, peer.Traffic.Messages.Received.Value)
95
+ assert.EqualValues(t, 489554, peer.Traffic.Messages.Sent.Value)
96
+ assert.EqualValues(t, 7, peer.Transitions.Established.Value)
97
+ assert.EqualValues(t, 0, peer.LastError.Code.Value)
98
+ assert.EqualValues(t, 0, peer.LastError.Subcode.Value)
99
+
100
+ family := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
101
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
102
+ row.Identity.Neighbor == "169.254.247.1" &&
103
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv4 &&
104
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
105
+ })
106
+ assert.Equal(t, "dell.os10bgp4V2PrefixGaugesTable", family.Table)
107
+ assert.Equal(t, "1", family.Identity.RoutingInstance)
108
+ assert.Equal(t, "64513", family.Identity.RemoteAS)
109
+ assert.Equal(t, "169.254.247.2", family.Descriptors.LocalAddress)
110
+ assert.Equal(t, "DIRECT CONNECT", family.Descriptors.Description)
111
+ assert.EqualValues(t, 27, family.Routes.Current.Received.Value)
112
+ assert.EqualValues(t, 27, family.Routes.Current.Accepted.Value)
113
+ assert.EqualValues(t, 69, family.Routes.Current.Advertised.Value)
114
+ },
115
+ },
116
+ }
117
+
118
+ for name, tc := range tests {
119
+ t.Run(name, func(t *testing.T) {
120
+ profile := matchedBGPProjectionByFile(t, tc.sysObjectID, tc.profileFile)
121
+ pdus := loadSnmprecPDUs(t, tc.fixture)
122
+
123
+ ctrl, mockHandler := setupMockHandler(t)
124
+ defer ctrl.Finish()
125
+
126
+ expectSystemMetadataGets(mockHandler, tc.sysObjectID, "bgp-device")
127
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
128
+
129
+ collector := New(Config{
130
+ SnmpClient: mockHandler,
131
+ Profiles: []*ddsnmp.Profile{profile},
132
+ Log: logger.New(),
133
+ SysObjectID: tc.sysObjectID,
134
+ })
135
+
136
+ results, err := collector.Collect()
137
+ require.NoError(t, err)
138
+ require.Len(t, results, 1)
139
+ require.Empty(t, results[0].Metrics)
140
+ require.Empty(t, results[0].TopologyMetrics)
141
+ require.Empty(t, results[0].LicenseRows)
142
+ require.NotEmpty(t, results[0].BGPRows)
143
+
144
+ tc.validate(t, results[0].BGPRows, tc.originProfileID)
145
+ })
146
+ }
147
+}
148
+
149
+func TestCollector_Collect_DellOS10BGP_IPv6Rows(t *testing.T) {
150
+ sysObjectID := "1.3.6.1.4.1.674.11000.5000.100.2.1.7"
151
+ profile := matchedBGPProjectionByFile(t, sysObjectID, "dell-os10.yaml")
152
+
153
+ peerIndex := "1.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1"
154
+ familyIndex := peerIndex + ".2.1"
155
+ peerOID := func(column string) string { return column + "." + peerIndex }
156
+ familyOID := func(column string) string { return column + "." + familyIndex }
157
+ pdus := []gosnmp.SnmpPDU{
158
+ createPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.3"), gosnmp.OctetString, []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}),
159
+ createGauge32PDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.7"), 64514),
160
+ createStringPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.8"), "192.0.2.10"),
161
+ createGauge32PDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.10"), 64513),
162
+ createStringPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.11"), "192.0.2.20"),
163
+ createIntegerPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.12"), 2),
164
+ createIntegerPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.13"), 6),
165
+ createStringPDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.14"), "IPv6 DIRECT CONNECT"),
166
+ createGauge32PDU(peerOID("1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.3"), 4096),
167
+ createGauge32PDU(familyOID("1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.3"), 127),
168
+ createGauge32PDU(familyOID("1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.4"), 126),
169
+ createGauge32PDU(familyOID("1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.5"), 269),
170
+ }
171
+
172
+ ctrl, mockHandler := setupMockHandler(t)
173
+ defer ctrl.Finish()
174
+
175
+ expectSystemMetadataGets(mockHandler, sysObjectID, "dell-ipv6")
176
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
177
+
178
+ collector := New(Config{
179
+ SnmpClient: mockHandler,
180
+ Profiles: []*ddsnmp.Profile{profile},
181
+ Log: logger.New(),
182
+ SysObjectID: sysObjectID,
183
+ })
184
+
185
+ results, err := collector.Collect()
186
+ require.NoError(t, err)
187
+ require.Len(t, results, 1)
188
+
189
+ rows := results[0].BGPRows
190
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
191
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer &&
192
+ row.Identity.Neighbor == "2001:db8::1"
193
+ })
194
+ assert.Equal(t, "_dell-os10-bgp4-v2.yaml", peer.OriginProfileID)
195
+ assert.Equal(t, "1", peer.Identity.RoutingInstance)
196
+ assert.Equal(t, "64513", peer.Identity.RemoteAS)
197
+ assert.Equal(t, "2001:db8::2", peer.Descriptors.LocalAddress)
198
+ assert.Equal(t, "64514", peer.Descriptors.LocalAS)
199
+ assert.Equal(t, "ipv6", peer.Descriptors.PeerType)
200
+ assert.Equal(t, "IPv6 DIRECT CONNECT", peer.Descriptors.Description)
201
+ require.True(t, peer.State.Has)
202
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
203
+ require.True(t, peer.Traffic.Messages.Received.Has)
204
+ assert.EqualValues(t, 4096, peer.Traffic.Messages.Received.Value)
205
+
206
+ family := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
207
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
208
+ row.Identity.Neighbor == "2001:db8::1" &&
209
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv6 &&
210
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
211
+ })
212
+ assert.Equal(t, "64513", family.Identity.RemoteAS)
213
+ assert.Equal(t, "2001:db8::2", family.Descriptors.LocalAddress)
214
+ assert.Equal(t, "ipv6", family.Descriptors.PeerType)
215
+ assert.Equal(t, "IPv6 DIRECT CONNECT", family.Descriptors.Description)
216
+ assert.EqualValues(t, 127, family.Routes.Current.Received.Value)
217
+ assert.EqualValues(t, 126, family.Routes.Current.Accepted.Value)
218
+ assert.EqualValues(t, 269, family.Routes.Current.Advertised.Value)
219
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/bgp_external_fixture_test.go
new
+93
@@ -0,0 +1,93 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_GenericBGP4Rows_WithMatchedStandardProfile(t *testing.T) {
18
+ profile := matchedProfileByFile(t, "1.3.6.1.4.1.40310", "nvidia-cumulus-linux-switch.yaml")
19
+ profile.Definition.MetricTags = nil
20
+ profile.Definition.StaticTags = nil
21
+ profile.Definition.Metadata = nil
22
+ profile.Definition.SysobjectIDMetadata = nil
23
+ profile.Definition.Topology = nil
24
+ profile.Definition.Licensing = nil
25
+ profile.Definition.Metrics = nil
26
+ profile.Definition.VirtualMetrics = nil
27
+
28
+ // LibreNMS does not currently ship a Cumulus peer-table capture with BGP rows.
29
+ // Use a real generic BGP4-MIB fixture to verify the standard BGP mixin after
30
+ // production profile matching.
31
+ pdus := loadSnmprecPDUs(t, "librenms/pfsense_frr_bgp_peer_table.snmprec")
32
+ pdus = append(pdus,
33
+ createPDU("1.3.6.1.2.1.15.3.1.7.169.254.1.1", gosnmp.IPAddress, "169.254.1.1"),
34
+ createPDU("1.3.6.1.2.1.15.3.1.7.169.254.1.9", gosnmp.IPAddress, "169.254.1.9"),
35
+ )
36
+
37
+ ctrl, mockHandler := setupMockHandler(t)
38
+ defer ctrl.Finish()
39
+
40
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.2.1.15.3", pdus)
41
+
42
+ collector := New(Config{
43
+ SnmpClient: mockHandler,
44
+ Profiles: []*ddsnmp.Profile{profile},
45
+ Log: logger.New(),
46
+ SysObjectID: "1.3.6.1.4.1.40310",
47
+ })
48
+
49
+ results, err := collector.Collect()
50
+ require.NoError(t, err)
51
+ require.Len(t, results, 1)
52
+
53
+ pm := results[0]
54
+ require.Empty(t, pm.Metrics)
55
+ require.Len(t, pm.BGPRows, 2)
56
+
57
+ rows := bgpRowsByNeighbor(pm.BGPRows)
58
+ tests := map[string]struct {
59
+ neighbor string
60
+ validate func(t *testing.T, row ddsnmp.BGPRow)
61
+ }{
62
+ "established FRR peer": {
63
+ neighbor: "169.254.1.1",
64
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
65
+ assert.Equal(t, "_std-bgp4-mib.yaml", row.OriginProfileID)
66
+ assert.Equal(t, "4200000000", row.Identity.RemoteAS)
67
+ assert.True(t, row.Admin.Enabled.Value)
68
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
69
+ assert.EqualValues(t, 6, row.Traffic.Updates.Received.Value)
70
+ assert.EqualValues(t, 14, row.Traffic.Updates.Sent.Value)
71
+ assert.EqualValues(t, 4, row.LastError.Code.Value)
72
+ assert.EqualValues(t, 0, row.LastError.Subcode.Value)
73
+ assert.EqualValues(t, 96951, row.Connection.EstablishedUptime.Value)
74
+ },
75
+ },
76
+ "second FRR peer": {
77
+ neighbor: "169.254.1.9",
78
+ validate: func(t *testing.T, row ddsnmp.BGPRow) {
79
+ assert.Equal(t, "4200000004", row.Identity.RemoteAS)
80
+ assert.EqualValues(t, 2, row.LastError.Code.Value)
81
+ assert.EqualValues(t, 2, row.LastError.Subcode.Value)
82
+ },
83
+ },
84
+ }
85
+
86
+ for name, tc := range tests {
87
+ t.Run(name, func(t *testing.T) {
88
+ row := rows[tc.neighbor]
89
+ require.NotZero(t, row)
90
+ tc.validate(t, row)
91
+ })
92
+ }
93
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/bgp_zone_index_surface_test.go
new
+152
@@ -0,0 +1,152 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_AristaBGP_IPv6ZoneIndexTags(t *testing.T) {
18
+ profile := matchedBGPProjectionByFile(t, "1.3.6.1.4.1.30065.1.3011.7010.427.48", "arista-switch.yaml")
19
+
20
+ idx := "7.4.20.254.128.1.2.0.0.0.0.194.213.130.253.254.123.34.167.0.0.14.132"
21
+ prefixIdx := idx + ".2.1"
22
+ localAddr := []byte{
23
+ 254, 128, 1, 2, 0, 0, 0, 0, 194, 213, 130, 253, 254, 123, 34, 168,
24
+ 0, 0, 14, 133,
25
+ }
26
+
27
+ ctrl, mockHandler := setupMockHandler(t)
28
+ defer ctrl.Finish()
29
+
30
+ expectSystemMetadataGets(mockHandler, "1.3.6.1.4.1.30065.1.3011.7010.427.48", "arista")
31
+ pdus := []gosnmp.SnmpPDU{
32
+ createPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.3", idx), gosnmp.OctetString, localAddr),
33
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.7", idx), 65000),
34
+ createPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.8", idx), gosnmp.IPAddress, "198.51.100.12"),
35
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.10", idx), 65001),
36
+ createPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.11", idx), gosnmp.IPAddress, "203.0.113.12"),
37
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.12", idx), 2),
38
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.13", idx), 6),
39
+ createStringPDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.2.1.14", idx), "link-local-upstream"),
40
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.7.1.1", idx), 16),
41
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.7.1.2", idx), 17),
42
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.7.1.5", idx), 2),
43
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.30065.4.1.1.8.1.4", prefixIdx), 1301),
44
+ }
45
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
46
+
47
+ collector := New(Config{
48
+ SnmpClient: mockHandler,
49
+ Profiles: []*ddsnmp.Profile{profile},
50
+ Log: logger.New(),
51
+ SysObjectID: "1.3.6.1.4.1.30065.1.3011.7010.427.48",
52
+ })
53
+
54
+ results, err := collector.Collect()
55
+ require.NoError(t, err)
56
+ require.Len(t, results, 1)
57
+ require.Empty(t, results[0].Metrics)
58
+ require.Len(t, results[0].BGPRows, 2)
59
+
60
+ peer := requireBGPRowMatching(t, results[0].BGPRows, func(row ddsnmp.BGPRow) bool {
61
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer &&
62
+ row.Identity.Neighbor == "fe80:102::c2d5:82fd:fe7b:22a7%0.0.14.132"
63
+ })
64
+ assert.Equal(t, "7", peer.Identity.RoutingInstance)
65
+ assert.Equal(t, "65001", peer.Identity.RemoteAS)
66
+ assert.Equal(t, "ipv6z", peer.Descriptors.PeerType)
67
+ assert.Equal(t, "fe80:102::c2d5:82fd:fe7b:22a8%0.0.14.133", peer.Descriptors.LocalAddress)
68
+ require.True(t, peer.Admin.Enabled.Has)
69
+ assert.True(t, peer.Admin.Enabled.Value)
70
+ require.True(t, peer.State.Has)
71
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
72
+ assert.EqualValues(t, 16, peer.Traffic.Updates.Received.Value)
73
+ assert.EqualValues(t, 17, peer.Traffic.Updates.Sent.Value)
74
+
75
+ family := requireBGPRowMatching(t, results[0].BGPRows, func(row ddsnmp.BGPRow) bool {
76
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
77
+ row.Identity.Neighbor == "fe80:102::c2d5:82fd:fe7b:22a7%0.0.14.132" &&
78
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv6 &&
79
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
80
+ })
81
+ assert.Equal(t, "7", family.Identity.RoutingInstance)
82
+ assert.Equal(t, "65001", family.Identity.RemoteAS)
83
+ assert.Equal(t, "ipv6z", family.Descriptors.PeerType)
84
+ assert.Equal(t, "fe80:102::c2d5:82fd:fe7b:22a8%0.0.14.133", family.Descriptors.LocalAddress)
85
+ assert.EqualValues(t, 1301, family.Routes.Current.Accepted.Value)
86
+}
87
+
88
+func TestCollector_Collect_CiscoBgpPeer3_IPv6ZoneIndexTags(t *testing.T) {
89
+ profile := matchedProfileByFile(t, "1.3.6.1.4.1.9.1.923", "cisco-asr.yaml")
90
+ filterProfileForTypedBGPByID(t, profile, "cisco-bgp-peer")
91
+
92
+ idx := "7.4.20.254.128.1.2.0.0.0.0.194.213.130.253.254.123.34.167.0.0.14.132"
93
+ remoteAddr := []byte{
94
+ 254, 128, 1, 2, 0, 0, 0, 0, 194, 213, 130, 253, 254, 123, 34, 167,
95
+ 0, 0, 14, 132,
96
+ }
97
+ localAddr := []byte{
98
+ 254, 128, 1, 2, 0, 0, 0, 0, 194, 213, 130, 253, 254, 123, 34, 168,
99
+ 0, 0, 14, 133,
100
+ }
101
+
102
+ ctrl, mockHandler := setupMockHandler(t)
103
+ defer ctrl.Finish()
104
+
105
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.9", []gosnmp.SnmpPDU{
106
+ createStringPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.4", idx), "blue"),
107
+ createPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.3", idx), gosnmp.OctetString, remoteAddr),
108
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.5", idx), 6),
109
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.6", idx), 2),
110
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.7", idx), 4),
111
+ createPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.8", idx), gosnmp.OctetString, localAddr),
112
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.10", idx), 65000),
113
+ createPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.11", idx), gosnmp.IPAddress, "198.51.100.10"),
114
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.13", idx), 65001),
115
+ createPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.14", idx), gosnmp.IPAddress, "203.0.113.10"),
116
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.15", idx), 21),
117
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.16", idx), 22),
118
+ createPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.19", idx), gosnmp.OctetString, []byte{0x02, 0x03}),
119
+ createCounter32PDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.20", idx), 5),
120
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.9.9.187.1.2.9.1.31", idx), 5),
121
+ })
122
+
123
+ collector := New(Config{
124
+ SnmpClient: mockHandler,
125
+ Profiles: []*ddsnmp.Profile{profile},
126
+ Log: logger.New(),
127
+ SysObjectID: "1.3.6.1.4.1.9.1.923",
128
+ })
129
+
130
+ results, err := collector.Collect()
131
+ require.NoError(t, err)
132
+ require.Len(t, results, 1)
133
+ require.Empty(t, results[0].Metrics)
134
+ require.Len(t, results[0].BGPRows, 1)
135
+
136
+ row := results[0].BGPRows[0]
137
+ assert.Equal(t, "blue", row.Identity.RoutingInstance)
138
+ assert.Equal(t, "ipv6z", row.Descriptors.PeerType)
139
+ assert.Equal(t, "fe80:102::c2d5:82fd:fe7b:22a7%0.0.14.132", row.Identity.Neighbor)
140
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
141
+ assert.Equal(t, "fe80:102::c2d5:82fd:fe7b:22a8%0.0.14.133", row.Descriptors.LocalAddress)
142
+ require.True(t, row.Admin.Enabled.Has)
143
+ assert.True(t, row.Admin.Enabled.Value)
144
+ require.True(t, row.State.Has)
145
+ assert.Equal(t, "established", string(row.State.State))
146
+ assert.EqualValues(t, 21, row.Traffic.Updates.Received.Value)
147
+ assert.EqualValues(t, 22, row.Traffic.Updates.Sent.Value)
148
+ assert.EqualValues(t, 5, row.Transitions.Established.Value)
149
+ assert.Equal(t, "openconfirm", string(row.Previous.State))
150
+ assert.EqualValues(t, 2, row.LastError.Code.Value)
151
+ assert.EqualValues(t, 3, row.LastError.Subcode.Value)
152
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/cisco_bgp_prefix_external_fixture_test.go
new
+112
@@ -0,0 +1,112 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+)
15
+
16
+func TestCollector_Collect_CiscoBgpPeer2Prefixes_FromLibreNMSFixtures(t *testing.T) {
17
+ tests := map[string]struct {
18
+ sysObjectID string
19
+ profileFile string
20
+ fixture string
21
+ validate func(t *testing.T, rows []ddsnmp.BGPRow)
22
+ }{
23
+ "ASR9001 emits VPNv4 prefix rows": {
24
+ sysObjectID: "1.3.6.1.4.1.9.1.1639",
25
+ profileFile: "cisco-asr.yaml",
26
+ fixture: "librenms/cisco_iosxr_asr9001_bgp_peer2_prefix_table.snmprec",
27
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
28
+ row := requireCiscoPeerFamilyRow(t, rows, "10.246.0.3", "ipv4", "vpn")
29
+ assert.Equal(t, "10.246.0.1", row.Descriptors.LocalAddress)
30
+ assert.Equal(t, "65056", row.Identity.RemoteAS)
31
+ assert.EqualValues(t, 5, row.Routes.Current.Accepted.Value)
32
+ assert.EqualValues(t, 2097152, row.RouteLimits.Limit.Value)
33
+ assert.EqualValues(t, 75, row.RouteLimits.Threshold.Value)
34
+ assert.EqualValues(t, 25, row.Routes.Current.Advertised.Value)
35
+ assert.EqualValues(t, 7, row.Routes.Current.Withdrawn.Value)
36
+
37
+ idleRow := requireCiscoPeerFamilyRow(t, rows, "10.246.0.4", "ipv4", "vpn")
38
+ assert.Equal(t, "0.0.0.0", idleRow.Descriptors.LocalAddress)
39
+ assert.Equal(t, "65056", idleRow.Identity.RemoteAS)
40
+ assert.EqualValues(t, 0, idleRow.Routes.Current.Accepted.Value)
41
+ },
42
+ },
43
+ "NCS540 emits IPv4 unicast prefix rows": {
44
+ sysObjectID: "1.3.6.1.4.1.9.1.2984",
45
+ profileFile: "cisco-ncs.yaml",
46
+ fixture: "librenms/cisco_iosxr_ncs540_bgp_peer2_prefix_table.snmprec",
47
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
48
+ row := requireCiscoPeerFamilyRow(t, rows, "10.255.9.1", "ipv4", "unicast")
49
+ assert.Equal(t, "10.255.9.31", row.Descriptors.LocalAddress)
50
+ assert.Equal(t, "65011", row.Identity.RemoteAS)
51
+ assert.EqualValues(t, 30, row.Routes.Current.Accepted.Value)
52
+ assert.EqualValues(t, 4294967295, row.RouteLimits.Limit.Value)
53
+ assert.EqualValues(t, 75, row.RouteLimits.Threshold.Value)
54
+ assert.EqualValues(t, 75, row.RouteLimits.ClearThreshold.Value)
55
+ assert.EqualValues(t, 8, row.Routes.Current.Advertised.Value)
56
+ assert.EqualValues(t, 0, row.Routes.Current.Withdrawn.Value)
57
+ },
58
+ },
59
+ }
60
+
61
+ for name, tc := range tests {
62
+ t.Run(name, func(t *testing.T) {
63
+ rows := collectCiscoPeer2PrefixRowsFromFixture(t, tc.sysObjectID, tc.profileFile, tc.fixture)
64
+ tc.validate(t, rows)
65
+ })
66
+ }
67
+}
68
+
69
+func collectCiscoPeer2PrefixRowsFromFixture(t *testing.T, sysObjectID, profileFile, fixturePath string) []ddsnmp.BGPRow {
70
+ t.Helper()
71
+
72
+ profile := matchedProfileByFile(t, sysObjectID, profileFile)
73
+ filterProfileForTypedBGPByID(t, profile, "cisco-bgp-peer-family")
74
+
75
+ pdus := loadSnmprecPDUs(t, fixturePath)
76
+
77
+ ctrl, mockHandler := setupMockHandler(t)
78
+ defer ctrl.Finish()
79
+
80
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.8", snmprecPDUsWithPrefix(pdus, "1.3.6.1.4.1.9.9.187.1.2.8."))
81
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.5.1", snmprecPDUsWithPrefix(pdus, "1.3.6.1.4.1.9.9.187.1.2.5.1."))
82
+
83
+ collector := New(Config{
84
+ SnmpClient: mockHandler,
85
+ Profiles: []*ddsnmp.Profile{profile},
86
+ Log: logger.New(),
87
+ SysObjectID: sysObjectID,
88
+ })
89
+
90
+ results, err := collector.Collect()
91
+ require.NoError(t, err)
92
+ require.Len(t, results, 1)
93
+ require.Empty(t, results[0].Metrics)
94
+ require.NotEmpty(t, results[0].BGPRows)
95
+
96
+ return results[0].BGPRows
97
+}
98
+
99
+func requireCiscoPeerFamilyRow(t *testing.T, rows []ddsnmp.BGPRow, neighbor, afi, safi string) ddsnmp.BGPRow {
100
+ t.Helper()
101
+
102
+ for _, row := range rows {
103
+ if row.Identity.Neighbor == neighbor &&
104
+ string(row.Identity.AddressFamily) == afi &&
105
+ string(row.Identity.SubsequentAddressFamily) == safi {
106
+ return row
107
+ }
108
+ }
109
+
110
+ require.FailNowf(t, "missing BGP row", "missing Cisco BGP peer-family row for %s %s/%s", neighbor, afi, safi)
111
+ return ddsnmp.BGPRow{}
112
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/cisco_bgp_prefix_profile_test.go
new
+406
@@ -0,0 +1,406 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_CiscoBgpPeer2PrefixProfile(t *testing.T) {
18
+ ctrl, mockHandler := setupMockHandler(t)
19
+ defer ctrl.Finish()
20
+
21
+ prefixIndex := "1.4.192.0.2.1.1.128"
22
+ peerIndex := "1.4.192.0.2.1"
23
+
24
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.8", []gosnmp.SnmpPDU{
25
+ createCounter32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.1."+prefixIndex, 1200),
26
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.2."+prefixIndex, 7),
27
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.3."+prefixIndex, 2000),
28
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.4."+prefixIndex, 80),
29
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.5."+prefixIndex, 70),
30
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.6."+prefixIndex, 1100),
31
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.7."+prefixIndex, 3),
32
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.8.1.8."+prefixIndex, 25),
33
+ })
34
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.5.1", []gosnmp.SnmpPDU{
35
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.5.1.6."+peerIndex, gosnmp.OctetString, []byte{198, 51, 100, 1}),
36
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.5.1.8."+peerIndex, 65000),
37
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.5.1.9."+peerIndex, gosnmp.IPAddress, "198.51.100.10"),
38
+ createIntegerPDU("1.3.6.1.4.1.9.9.187.1.2.5.1.5."+peerIndex, 4),
39
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.5.1.11."+peerIndex, 65001),
40
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.5.1.12."+peerIndex, gosnmp.IPAddress, "203.0.113.10"),
41
+ })
42
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.7.1.3", []gosnmp.SnmpPDU{
43
+ createStringPDU("1.3.6.1.4.1.9.9.187.1.2.7.1.3."+prefixIndex, "vpnv4 unicast"),
44
+ })
45
+
46
+ profile := &ddsnmp.Profile{
47
+ SourceFile: "cisco-bgp4-mib.yaml",
48
+ Definition: &ddprofiledefinition.ProfileDefinition{
49
+ Metrics: []ddprofiledefinition.MetricsConfig{
50
+ {
51
+ Table: ddprofiledefinition.SymbolConfig{
52
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8",
53
+ Name: "cbgpPeer2AddrFamilyPrefixTable",
54
+ },
55
+ Symbols: []ddprofiledefinition.SymbolConfig{
56
+ {
57
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.1",
58
+ Name: "bgpPeerPrefixesAccepted",
59
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
60
+ ChartMeta: ddprofiledefinition.ChartMeta{
61
+ Description: "The number of accepted route prefixes on this connection for the address family",
62
+ Family: "Network/Routing/BGP/Peer/Prefix/Accepted",
63
+ Unit: "{prefix}",
64
+ },
65
+ },
66
+ {
67
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.2",
68
+ Name: "bgpPeerPrefixesRejected",
69
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
70
+ ChartMeta: ddprofiledefinition.ChartMeta{
71
+ Description: "The number of route prefixes denied on this connection for the address family",
72
+ Family: "Network/Routing/BGP/Peer/Prefix/Rejected",
73
+ Unit: "{prefix}",
74
+ },
75
+ },
76
+ {
77
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.3",
78
+ Name: "bgpPeerPrefixAdminLimit",
79
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
80
+ ChartMeta: ddprofiledefinition.ChartMeta{
81
+ Description: "The administrative maximum number of accepted route prefixes for the address family",
82
+ Family: "Network/Routing/BGP/Peer/Prefix/AdminLimit",
83
+ Unit: "{prefix}",
84
+ },
85
+ },
86
+ {
87
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.4",
88
+ Name: "bgpPeerPrefixThreshold",
89
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
90
+ ChartMeta: ddprofiledefinition.ChartMeta{
91
+ Description: "The warning threshold percentage for accepted route prefixes for the address family",
92
+ Family: "Network/Routing/BGP/Peer/Prefix/Threshold",
93
+ Unit: "%",
94
+ },
95
+ },
96
+ {
97
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.5",
98
+ Name: "bgpPeerPrefixClearThreshold",
99
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
100
+ ChartMeta: ddprofiledefinition.ChartMeta{
101
+ Description: "The clear threshold percentage for accepted route prefixes for the address family",
102
+ Family: "Network/Routing/BGP/Peer/Prefix/ClearThreshold",
103
+ Unit: "%",
104
+ },
105
+ },
106
+ {
107
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.6",
108
+ Name: "bgpPeerPrefixesAdvertised",
109
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
110
+ ChartMeta: ddprofiledefinition.ChartMeta{
111
+ Description: "The number of route prefixes advertised on this connection for the address family",
112
+ Family: "Network/Routing/BGP/Peer/Prefix/Advertised",
113
+ Unit: "{prefix}",
114
+ },
115
+ },
116
+ {
117
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.7",
118
+ Name: "bgpPeerPrefixesSuppressed",
119
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
120
+ ChartMeta: ddprofiledefinition.ChartMeta{
121
+ Description: "The number of route prefixes suppressed from being sent on this connection for the address family",
122
+ Family: "Network/Routing/BGP/Peer/Prefix/Suppressed",
123
+ Unit: "{prefix}",
124
+ },
125
+ },
126
+ {
127
+ OID: "1.3.6.1.4.1.9.9.187.1.2.8.1.8",
128
+ Name: "bgpPeerPrefixesWithdrawn",
129
+ MetricType: ddprofiledefinition.ProfileMetricTypeGauge,
130
+ ChartMeta: ddprofiledefinition.ChartMeta{
131
+ Description: "The number of route prefixes withdrawn on this connection for the address family",
132
+ Family: "Network/Routing/BGP/Peer/Prefix/Withdrawn",
133
+ Unit: "{prefix}",
134
+ },
135
+ },
136
+ },
137
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
138
+ {
139
+ Tag: "neighbor_address_type",
140
+ Index: 1,
141
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
142
+ "0": "unknown",
143
+ "1": "ipv4",
144
+ "2": "ipv6",
145
+ "3": "ipv4z",
146
+ "4": "ipv6z",
147
+ "16": "dns",
148
+ }),
149
+ },
150
+ {
151
+ Tag: "neighbor",
152
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
153
+ Name: "cbgpPeer2RemoteAddrIndex",
154
+ Format: "ip_address",
155
+ },
156
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
157
+ {
158
+ Start: 2,
159
+ DropRight: 2,
160
+ },
161
+ },
162
+ },
163
+ {
164
+ Tag: "address_family",
165
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
166
+ Name: "cbgpPeer2AddrFamilyAfiIndex",
167
+ ExtractValueCompiled: mustCompileRegex(`^(?:\d+\.)+(\d+)\.\d+$`),
168
+ },
169
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
170
+ "1": "ipv4",
171
+ }),
172
+ },
173
+ {
174
+ Tag: "subsequent_address_family",
175
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
176
+ Name: "cbgpPeer2AddrFamilySafiIndex",
177
+ ExtractValueCompiled: mustCompileRegex(`^(?:\d+\.)+\d+\.(\d+)$`),
178
+ },
179
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
180
+ "128": "vpn",
181
+ }),
182
+ },
183
+ {
184
+ Tag: "address_family_name",
185
+ Table: "cbgpPeer2AddrFamilyTable",
186
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
187
+ OID: "1.3.6.1.4.1.9.9.187.1.2.7.1.3",
188
+ Name: "cbgpPeer2AddrFamilyName",
189
+ },
190
+ },
191
+ {
192
+ Tag: "local_address",
193
+ Table: "cbgpPeer2Table",
194
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
195
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.6",
196
+ Name: "cbgpPeer2LocalAddr",
197
+ Format: "ip_address",
198
+ },
199
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
200
+ {
201
+ Start: 0,
202
+ DropRight: 2,
203
+ },
204
+ },
205
+ },
206
+ {
207
+ Tag: "local_as",
208
+ Table: "cbgpPeer2Table",
209
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
210
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.8",
211
+ Name: "cbgpPeer2LocalAs",
212
+ },
213
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
214
+ {
215
+ Start: 0,
216
+ DropRight: 2,
217
+ },
218
+ },
219
+ },
220
+ {
221
+ Tag: "remote_as",
222
+ Table: "cbgpPeer2Table",
223
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
224
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.11",
225
+ Name: "cbgpPeer2RemoteAs",
226
+ },
227
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
228
+ {
229
+ Start: 0,
230
+ DropRight: 2,
231
+ },
232
+ },
233
+ },
234
+ {
235
+ Tag: "local_identifier",
236
+ Table: "cbgpPeer2Table",
237
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
238
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.9",
239
+ Name: "cbgpPeer2LocalIdentifier",
240
+ Format: "ip_address",
241
+ },
242
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
243
+ {
244
+ Start: 0,
245
+ DropRight: 2,
246
+ },
247
+ },
248
+ },
249
+ {
250
+ Tag: "peer_identifier",
251
+ Table: "cbgpPeer2Table",
252
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
253
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.12",
254
+ Name: "cbgpPeer2RemoteIdentifier",
255
+ Format: "ip_address",
256
+ },
257
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
258
+ {
259
+ Start: 0,
260
+ DropRight: 2,
261
+ },
262
+ },
263
+ },
264
+ {
265
+ Tag: "bgp_version",
266
+ Table: "cbgpPeer2Table",
267
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
268
+ OID: "1.3.6.1.4.1.9.9.187.1.2.5.1.5",
269
+ Name: "cbgpPeer2NegotiatedVersion",
270
+ },
271
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{
272
+ {
273
+ Start: 0,
274
+ DropRight: 2,
275
+ },
276
+ },
277
+ },
278
+ },
279
+ },
280
+ },
281
+ },
282
+ }
283
+
284
+ ddsnmp.HandleCrossTableTagsWithoutMetrics(profile)
285
+ require.NoError(t, ddsnmp.CompileTransforms(profile))
286
+
287
+ collector := New(Config{
288
+ SnmpClient: mockHandler,
289
+ Profiles: []*ddsnmp.Profile{profile},
290
+ Log: logger.New(),
291
+ SysObjectID: "",
292
+ })
293
+
294
+ results, err := collector.Collect()
295
+ require.NoError(t, err)
296
+ require.Len(t, results, 1)
297
+
298
+ for i := range results[0].Metrics {
299
+ results[0].Metrics[i].Profile = nil
300
+ }
301
+
302
+ expectedTags := map[string]string{
303
+ "neighbor_address_type": "ipv4",
304
+ "neighbor": "192.0.2.1",
305
+ "address_family": "ipv4",
306
+ "subsequent_address_family": "vpn",
307
+ "address_family_name": "vpnv4 unicast",
308
+ "local_address": "198.51.100.1",
309
+ "local_as": "65000",
310
+ "remote_as": "65001",
311
+ "local_identifier": "198.51.100.10",
312
+ "peer_identifier": "203.0.113.10",
313
+ "bgp_version": "4",
314
+ }
315
+
316
+ assert.ElementsMatch(t, []ddsnmp.Metric{
317
+ {
318
+ Name: "bgpPeerPrefixesAccepted",
319
+ Description: "The number of accepted route prefixes on this connection for the address family",
320
+ Family: "Network/Routing/BGP/Peer/Prefix/Accepted",
321
+ Unit: "{prefix}",
322
+ Value: 1200,
323
+ Tags: expectedTags,
324
+ MetricType: "gauge",
325
+ IsTable: true,
326
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
327
+ },
328
+ {
329
+ Name: "bgpPeerPrefixesRejected",
330
+ Description: "The number of route prefixes denied on this connection for the address family",
331
+ Family: "Network/Routing/BGP/Peer/Prefix/Rejected",
332
+ Unit: "{prefix}",
333
+ Value: 7,
334
+ Tags: expectedTags,
335
+ MetricType: "gauge",
336
+ IsTable: true,
337
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
338
+ },
339
+ {
340
+ Name: "bgpPeerPrefixAdminLimit",
341
+ Description: "The administrative maximum number of accepted route prefixes for the address family",
342
+ Family: "Network/Routing/BGP/Peer/Prefix/AdminLimit",
343
+ Unit: "{prefix}",
344
+ Value: 2000,
345
+ Tags: expectedTags,
346
+ MetricType: "gauge",
347
+ IsTable: true,
348
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
349
+ },
350
+ {
351
+ Name: "bgpPeerPrefixThreshold",
352
+ Description: "The warning threshold percentage for accepted route prefixes for the address family",
353
+ Family: "Network/Routing/BGP/Peer/Prefix/Threshold",
354
+ Unit: "%",
355
+ Value: 80,
356
+ Tags: expectedTags,
357
+ MetricType: "gauge",
358
+ IsTable: true,
359
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
360
+ },
361
+ {
362
+ Name: "bgpPeerPrefixClearThreshold",
363
+ Description: "The clear threshold percentage for accepted route prefixes for the address family",
364
+ Family: "Network/Routing/BGP/Peer/Prefix/ClearThreshold",
365
+ Unit: "%",
366
+ Value: 70,
367
+ Tags: expectedTags,
368
+ MetricType: "gauge",
369
+ IsTable: true,
370
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
371
+ },
372
+ {
373
+ Name: "bgpPeerPrefixesAdvertised",
374
+ Description: "The number of route prefixes advertised on this connection for the address family",
375
+ Family: "Network/Routing/BGP/Peer/Prefix/Advertised",
376
+ Unit: "{prefix}",
377
+ Value: 1100,
378
+ Tags: expectedTags,
379
+ MetricType: "gauge",
380
+ IsTable: true,
381
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
382
+ },
383
+ {
384
+ Name: "bgpPeerPrefixesSuppressed",
385
+ Description: "The number of route prefixes suppressed from being sent on this connection for the address family",
386
+ Family: "Network/Routing/BGP/Peer/Prefix/Suppressed",
387
+ Unit: "{prefix}",
388
+ Value: 3,
389
+ Tags: expectedTags,
390
+ MetricType: "gauge",
391
+ IsTable: true,
392
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
393
+ },
394
+ {
395
+ Name: "bgpPeerPrefixesWithdrawn",
396
+ Description: "The number of route prefixes withdrawn on this connection for the address family",
397
+ Family: "Network/Routing/BGP/Peer/Prefix/Withdrawn",
398
+ Unit: "{prefix}",
399
+ Value: 25,
400
+ Tags: expectedTags,
401
+ MetricType: "gauge",
402
+ IsTable: true,
403
+ Table: "cbgpPeer2AddrFamilyPrefixTable",
404
+ },
405
+ }, results[0].Metrics)
406
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/cisco_bgp_profile_test.go
new
+377
@@ -0,0 +1,377 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_CiscoBgpPeer3Profile(t *testing.T) {
18
+ ctrl, mockHandler := setupMockHandler(t)
19
+ defer ctrl.Finish()
20
+
21
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.9.9.187.1.2.9", []gosnmp.SnmpPDU{
22
+ createIntegerPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.6.7.1.4.192.0.2.1", 2),
23
+ createIntegerPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.5.7.1.4.192.0.2.1", 6),
24
+ createCounter32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.15.7.1.4.192.0.2.1", 42),
25
+ createCounter32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.18.7.1.4.192.0.2.1", 88),
26
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.19.7.1.4.192.0.2.1", gosnmp.OctetString, []byte{0x02, 0x03}),
27
+ createCounter32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.20.7.1.4.192.0.2.1", 5),
28
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.21.7.1.4.192.0.2.1", 3600),
29
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.29.7.1.4.192.0.2.1", 30),
30
+ createIntegerPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.31.7.1.4.192.0.2.1", 5),
31
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.1.7.1.4.192.0.2.1", 7),
32
+ createStringPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.4.7.1.4.192.0.2.1", "blue"),
33
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.3.7.1.4.192.0.2.1", gosnmp.OctetString, []byte{192, 0, 2, 1}),
34
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.8.7.1.4.192.0.2.1", gosnmp.OctetString, []byte{198, 51, 100, 1}),
35
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.10.7.1.4.192.0.2.1", 65000),
36
+ createGauge32PDU("1.3.6.1.4.1.9.9.187.1.2.9.1.13.7.1.4.192.0.2.1", 65001),
37
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.11.7.1.4.192.0.2.1", gosnmp.IPAddress, "198.51.100.10"),
38
+ createPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.14.7.1.4.192.0.2.1", gosnmp.IPAddress, "203.0.113.10"),
39
+ createIntegerPDU("1.3.6.1.4.1.9.9.187.1.2.9.1.7.7.1.4.192.0.2.1", 4),
40
+ })
41
+
42
+ profile := &ddsnmp.Profile{
43
+ SourceFile: "cisco-bgp4-mib.yaml",
44
+ Definition: &ddprofiledefinition.ProfileDefinition{
45
+ Metrics: []ddprofiledefinition.MetricsConfig{
46
+ {
47
+ Table: ddprofiledefinition.SymbolConfig{
48
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9",
49
+ Name: "cbgpPeer3Table",
50
+ },
51
+ Symbols: []ddprofiledefinition.SymbolConfig{
52
+ {
53
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.6",
54
+ Name: "bgpPeerAdminStatus",
55
+ ChartMeta: ddprofiledefinition.ChartMeta{
56
+ Description: "The desired state of the BGP connection",
57
+ Family: "Network/Routing/BGP/Peer/Admin/Status",
58
+ Unit: "{status}",
59
+ },
60
+ },
61
+ {
62
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.5",
63
+ Name: "bgpPeerState",
64
+ ChartMeta: ddprofiledefinition.ChartMeta{
65
+ Description: "The BGP peer connection state",
66
+ Family: "Network/Routing/BGP/Peer/Connection/Status",
67
+ Unit: "{status}",
68
+ },
69
+ },
70
+ {
71
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.15",
72
+ Name: "bgpPeerInUpdates",
73
+ ChartMeta: ddprofiledefinition.ChartMeta{
74
+ Description: "The number of BGP UPDATE messages received on this connection",
75
+ Family: "Network/Routing/BGP/Peer/Message/Update/In",
76
+ Unit: "{message}",
77
+ },
78
+ },
79
+ {
80
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.18",
81
+ Name: "bgpPeerOutTotalMessages",
82
+ ChartMeta: ddprofiledefinition.ChartMeta{
83
+ Description: "The total number of messages transmitted to the remote peer on this connection",
84
+ Family: "Network/Routing/BGP/Peer/Message/Total/Out",
85
+ Unit: "{message}",
86
+ },
87
+ },
88
+ {
89
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.19",
90
+ Name: "bgpPeerLastErrorCode",
91
+ Format: "hex",
92
+ ExtractValueCompiled: mustCompileRegex("^([0-9a-f]{2})"),
93
+ ChartMeta: ddprofiledefinition.ChartMeta{
94
+ Description: "The last BGP NOTIFICATION error code seen on this connection",
95
+ Family: "Network/Routing/BGP/Peer/Error/Last/Code",
96
+ Unit: "{code}",
97
+ },
98
+ },
99
+ {
100
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.19",
101
+ Name: "bgpPeerLastErrorSubcode",
102
+ Format: "hex",
103
+ ExtractValueCompiled: mustCompileRegex("^[0-9a-f]{2}([0-9a-f]{2})"),
104
+ ChartMeta: ddprofiledefinition.ChartMeta{
105
+ Description: "The last BGP NOTIFICATION error subcode seen on this connection",
106
+ Family: "Network/Routing/BGP/Peer/Error/Last/Subcode",
107
+ Unit: "{subcode}",
108
+ },
109
+ },
110
+ {
111
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.20",
112
+ Name: "bgpPeerFsmEstablishedTransitions",
113
+ ChartMeta: ddprofiledefinition.ChartMeta{
114
+ Description: "The total number of times the BGP FSM transitioned into the established state",
115
+ Family: "Network/Routing/BGP/Peer/FSM/Transition/Established/Count",
116
+ Unit: "{transition}",
117
+ },
118
+ },
119
+ {
120
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.21",
121
+ Name: "bgpPeerFsmEstablishedTime",
122
+ ChartMeta: ddprofiledefinition.ChartMeta{
123
+ Description: "Time in seconds this peer has been in the Established state or since last in Established state",
124
+ Family: "Network/Routing/BGP/Peer/FSM/Duration/Established",
125
+ Unit: "s",
126
+ },
127
+ },
128
+ {
129
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.29",
130
+ Name: "bgpPeerInUpdateElapsedTime",
131
+ ChartMeta: ddprofiledefinition.ChartMeta{
132
+ Description: "Time since the last BGP UPDATE message was received from the peer",
133
+ Family: "Network/Routing/BGP/Peer/Message/Update/In/Elapsed",
134
+ Unit: "s",
135
+ },
136
+ },
137
+ {
138
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.31",
139
+ Name: "bgpPeerPreviousState",
140
+ ChartMeta: ddprofiledefinition.ChartMeta{
141
+ Description: "The previous BGP peer connection state before the current state",
142
+ Family: "Network/Routing/BGP/Peer/Connection/Previous/Status",
143
+ Unit: "{status}",
144
+ },
145
+ },
146
+ },
147
+ MetricTags: []ddprofiledefinition.MetricTagConfig{
148
+ {
149
+ Tag: "routing_instance",
150
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
151
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.4",
152
+ Name: "cbgpPeer3VrfName",
153
+ },
154
+ },
155
+ {
156
+ Tag: "routing_instance_id",
157
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
158
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.1",
159
+ Name: "cbgpPeer3VrfId",
160
+ Format: "uint32",
161
+ },
162
+ },
163
+ {
164
+ Tag: "neighbor_address_type",
165
+ Index: 2,
166
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
167
+ "0": "unknown",
168
+ "1": "ipv4",
169
+ "2": "ipv6",
170
+ "3": "ipv4z",
171
+ "4": "ipv6z",
172
+ "16": "dns",
173
+ }),
174
+ },
175
+ {
176
+ Tag: "neighbor",
177
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
178
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.3",
179
+ Name: "cbgpPeer3RemoteAddr",
180
+ Format: "ip_address",
181
+ },
182
+ },
183
+ {
184
+ Tag: "local_address",
185
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
186
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.8",
187
+ Name: "cbgpPeer3LocalAddr",
188
+ Format: "ip_address",
189
+ },
190
+ },
191
+ {
192
+ Tag: "local_as",
193
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
194
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.10",
195
+ Name: "cbgpPeer3LocalAs",
196
+ },
197
+ },
198
+ {
199
+ Tag: "remote_as",
200
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
201
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.13",
202
+ Name: "cbgpPeer3RemoteAs",
203
+ },
204
+ },
205
+ {
206
+ Tag: "local_identifier",
207
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
208
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.11",
209
+ Name: "cbgpPeer3LocalIdentifier",
210
+ Format: "ip_address",
211
+ },
212
+ },
213
+ {
214
+ Tag: "peer_identifier",
215
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
216
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.14",
217
+ Name: "cbgpPeer3RemoteIdentifier",
218
+ Format: "ip_address",
219
+ },
220
+ },
221
+ {
222
+ Tag: "bgp_version",
223
+ Symbol: ddprofiledefinition.SymbolConfigCompat{
224
+ OID: "1.3.6.1.4.1.9.9.187.1.2.9.1.7",
225
+ Name: "cbgpPeer3NegotiatedVersion",
226
+ },
227
+ },
228
+ },
229
+ },
230
+ },
231
+ },
232
+ }
233
+
234
+ ddsnmp.HandleCrossTableTagsWithoutMetrics(profile)
235
+ require.NoError(t, ddsnmp.CompileTransforms(profile))
236
+
237
+ collector := New(Config{
238
+ SnmpClient: mockHandler,
239
+ Profiles: []*ddsnmp.Profile{profile},
240
+ Log: logger.New(),
241
+ SysObjectID: "",
242
+ })
243
+
244
+ results, err := collector.Collect()
245
+ require.NoError(t, err)
246
+ require.Len(t, results, 1)
247
+
248
+ for i := range results[0].Metrics {
249
+ results[0].Metrics[i].Profile = nil
250
+ }
251
+
252
+ expectedTags := map[string]string{
253
+ "routing_instance": "blue",
254
+ "routing_instance_id": "7",
255
+ "neighbor_address_type": "ipv4",
256
+ "neighbor": "192.0.2.1",
257
+ "local_address": "198.51.100.1",
258
+ "local_as": "65000",
259
+ "remote_as": "65001",
260
+ "local_identifier": "198.51.100.10",
261
+ "peer_identifier": "203.0.113.10",
262
+ "bgp_version": "4",
263
+ }
264
+
265
+ assert.ElementsMatch(t, []ddsnmp.Metric{
266
+ {
267
+ Name: "bgpPeerAdminStatus",
268
+ Description: "The desired state of the BGP connection",
269
+ Family: "Network/Routing/BGP/Peer/Admin/Status",
270
+ Unit: "{status}",
271
+ Value: 2,
272
+ Tags: expectedTags,
273
+ MetricType: "gauge",
274
+ IsTable: true,
275
+ Table: "cbgpPeer3Table",
276
+ },
277
+ {
278
+ Name: "bgpPeerState",
279
+ Description: "The BGP peer connection state",
280
+ Family: "Network/Routing/BGP/Peer/Connection/Status",
281
+ Unit: "{status}",
282
+ Value: 6,
283
+ Tags: expectedTags,
284
+ MetricType: "gauge",
285
+ IsTable: true,
286
+ Table: "cbgpPeer3Table",
287
+ },
288
+ {
289
+ Name: "bgpPeerInUpdates",
290
+ Description: "The number of BGP UPDATE messages received on this connection",
291
+ Family: "Network/Routing/BGP/Peer/Message/Update/In",
292
+ Unit: "{message}",
293
+ Value: 42,
294
+ Tags: expectedTags,
295
+ MetricType: "rate",
296
+ IsTable: true,
297
+ Table: "cbgpPeer3Table",
298
+ },
299
+ {
300
+ Name: "bgpPeerOutTotalMessages",
301
+ Description: "The total number of messages transmitted to the remote peer on this connection",
302
+ Family: "Network/Routing/BGP/Peer/Message/Total/Out",
303
+ Unit: "{message}",
304
+ Value: 88,
305
+ Tags: expectedTags,
306
+ MetricType: "rate",
307
+ IsTable: true,
308
+ Table: "cbgpPeer3Table",
309
+ },
310
+ {
311
+ Name: "bgpPeerLastErrorCode",
312
+ Description: "The last BGP NOTIFICATION error code seen on this connection",
313
+ Family: "Network/Routing/BGP/Peer/Error/Last/Code",
314
+ Unit: "{code}",
315
+ Value: 2,
316
+ Tags: expectedTags,
317
+ MetricType: "gauge",
318
+ IsTable: true,
319
+ Table: "cbgpPeer3Table",
320
+ },
321
+ {
322
+ Name: "bgpPeerLastErrorSubcode",
323
+ Description: "The last BGP NOTIFICATION error subcode seen on this connection",
324
+ Family: "Network/Routing/BGP/Peer/Error/Last/Subcode",
325
+ Unit: "{subcode}",
326
+ Value: 3,
327
+ Tags: expectedTags,
328
+ MetricType: "gauge",
329
+ IsTable: true,
330
+ Table: "cbgpPeer3Table",
331
+ },
332
+ {
333
+ Name: "bgpPeerFsmEstablishedTransitions",
334
+ Description: "The total number of times the BGP FSM transitioned into the established state",
335
+ Family: "Network/Routing/BGP/Peer/FSM/Transition/Established/Count",
336
+ Unit: "{transition}",
337
+ Value: 5,
338
+ Tags: expectedTags,
339
+ MetricType: "rate",
340
+ IsTable: true,
341
+ Table: "cbgpPeer3Table",
342
+ },
343
+ {
344
+ Name: "bgpPeerFsmEstablishedTime",
345
+ Description: "Time in seconds this peer has been in the Established state or since last in Established state",
346
+ Family: "Network/Routing/BGP/Peer/FSM/Duration/Established",
347
+ Unit: "s",
348
+ Value: 3600,
349
+ Tags: expectedTags,
350
+ MetricType: "gauge",
351
+ IsTable: true,
352
+ Table: "cbgpPeer3Table",
353
+ },
354
+ {
355
+ Name: "bgpPeerInUpdateElapsedTime",
356
+ Description: "Time since the last BGP UPDATE message was received from the peer",
357
+ Family: "Network/Routing/BGP/Peer/Message/Update/In/Elapsed",
358
+ Unit: "s",
359
+ Value: 30,
360
+ Tags: expectedTags,
361
+ MetricType: "gauge",
362
+ IsTable: true,
363
+ Table: "cbgpPeer3Table",
364
+ },
365
+ {
366
+ Name: "bgpPeerPreviousState",
367
+ Description: "The previous BGP peer connection state before the current state",
368
+ Family: "Network/Routing/BGP/Peer/Connection/Previous/Status",
369
+ Unit: "{status}",
370
+ Value: 5,
371
+ Tags: expectedTags,
372
+ MetricType: "gauge",
373
+ IsTable: true,
374
+ Table: "cbgpPeer3Table",
375
+ },
376
+ }, results[0].Metrics)
377
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector.go
+113
@@ -214,6 +214,17 @@ func (c *Collector) collectProfile(ps *profileState) (*ddsnmp.ProfileMetrics, er
214
pm.Stats.Metrics.Licensing += int64(len(licenseRows))
215
pm.Stats.Timing.Licensing = time.Since(now)
216
217
+ now = time.Now()
218
+ bgpRows, err := c.collectBGPRows(ps.profile, &pm.Stats)
219
+ if err != nil {
220
+ pm.BGPCollectError = err
221
+ c.log.Limit(bgpRowsFailedLogKey+ps.profile.SourceFile, 1, bgpRowsErrorLogEvery).
222
+ Warningf("failed to collect BGP rows for profile %q: %v", ps.profile.SourceFile, err)
223
+ }
224
+ pm.BGPRows = append(pm.BGPRows, bgpRows...)
225
+ pm.Stats.Metrics.BGP += int64(len(bgpRows))
226
+ pm.Stats.Timing.BGP = time.Since(now)
227
+
228
for i := range pm.Metrics {
229
pm.Metrics[i].Profile = pm
230
}
@@ -234,6 +245,9 @@ func (c *Collector) updateProfileMetrics(pm *ddsnmp.ProfileMetrics) {
245
for i := range pm.LicenseRows {
246
sanitizeLicenseRow(&pm.LicenseRows[i])
247
}
248
+ for i := range pm.BGPRows {
249
+ sanitizeBGPRow(&pm.BGPRows[i])
250
+ }
251
}
252
253
func sanitizeMetricMetadata(m *ddsnmp.Metric) {
@@ -268,6 +282,105 @@ func sanitizeLicenseRow(row *ddsnmp.LicenseRow) {
282
}
283
}
284
285
+func sanitizeBGPRow(row *ddsnmp.BGPRow) {
286
+ row.RowKey = metricMetaReplacer.Replace(row.RowKey)
287
+ row.Identity.RoutingInstance = metricMetaReplacer.Replace(row.Identity.RoutingInstance)
288
+ row.Identity.Neighbor = metricMetaReplacer.Replace(row.Identity.Neighbor)
289
+ row.Identity.RemoteAS = metricMetaReplacer.Replace(row.Identity.RemoteAS)
290
+ row.Descriptors.LocalAddress = metricMetaReplacer.Replace(row.Descriptors.LocalAddress)
291
+ row.Descriptors.LocalAS = metricMetaReplacer.Replace(row.Descriptors.LocalAS)
292
+ row.Descriptors.LocalIdentifier = metricMetaReplacer.Replace(row.Descriptors.LocalIdentifier)
293
+ row.Descriptors.PeerIdentifier = metricMetaReplacer.Replace(row.Descriptors.PeerIdentifier)
294
+ row.Descriptors.PeerType = metricMetaReplacer.Replace(row.Descriptors.PeerType)
295
+ row.Descriptors.BGPVersion = metricMetaReplacer.Replace(row.Descriptors.BGPVersion)
296
+ row.Descriptors.Description = metricMetaReplacer.Replace(row.Descriptors.Description)
297
+ sanitizeBGPState(&row.State)
298
+ sanitizeBGPState(&row.Previous)
299
+ sanitizeBGPBool(&row.Admin.Enabled)
300
+ sanitizeBGPInt64(&row.Connection.EstablishedUptime)
301
+ sanitizeBGPInt64(&row.Connection.LastReceivedUpdateAge)
302
+ sanitizeBGPDirectional(&row.Traffic.Messages)
303
+ sanitizeBGPDirectional(&row.Traffic.Updates)
304
+ sanitizeBGPDirectional(&row.Traffic.Notifications)
305
+ sanitizeBGPDirectional(&row.Traffic.RouteRefreshes)
306
+ sanitizeBGPDirectional(&row.Traffic.Opens)
307
+ sanitizeBGPDirectional(&row.Traffic.Keepalives)
308
+ sanitizeBGPInt64(&row.Transitions.Established)
309
+ sanitizeBGPInt64(&row.Transitions.Down)
310
+ sanitizeBGPInt64(&row.Transitions.Up)
311
+ sanitizeBGPInt64(&row.Transitions.Flaps)
312
+ sanitizeBGPTimerPair(&row.Timers.Negotiated)
313
+ sanitizeBGPTimerPair(&row.Timers.Configured)
314
+ sanitizeBGPInt64(&row.LastError.Code)
315
+ sanitizeBGPInt64(&row.LastError.Subcode)
316
+ sanitizeBGPLastNotification(&row.LastNotify.Received)
317
+ sanitizeBGPLastNotification(&row.LastNotify.Sent)
318
+ sanitizeBGPText(&row.Reasons.LastDown)
319
+ sanitizeBGPText(&row.Reasons.Unavailability)
320
+ sanitizeBGPText(&row.Restart.State)
321
+ sanitizeBGPRouteCounters(&row.Routes.Current)
322
+ sanitizeBGPRouteCounters(&row.Routes.Total)
323
+ sanitizeBGPInt64(&row.RouteLimits.Limit)
324
+ sanitizeBGPInt64(&row.RouteLimits.Threshold)
325
+ sanitizeBGPInt64(&row.RouteLimits.ClearThreshold)
326
+ sanitizeBGPInt64(&row.Device.Peers)
327
+ sanitizeBGPInt64(&row.Device.InternalPeers)
328
+ sanitizeBGPInt64(&row.Device.ExternalPeers)
329
+ for k, v := range row.Tags {
330
+ if strings.HasPrefix(k, "rm:") {
331
+ delete(row.Tags, k)
332
+ continue
333
+ }
334
+ row.Tags[k] = metricMetaReplacer.Replace(v)
335
+ }
336
+}
337
+
338
+func sanitizeBGPState(value *ddsnmp.BGPState) {
339
+ value.Raw = metricMetaReplacer.Replace(value.Raw)
340
+}
341
+
342
+func sanitizeBGPInt64(value *ddsnmp.BGPInt64) {
343
+ value.Raw = metricMetaReplacer.Replace(value.Raw)
344
+}
345
+
346
+func sanitizeBGPText(value *ddsnmp.BGPText) {
347
+ value.Raw = metricMetaReplacer.Replace(value.Raw)
348
+ value.Value = metricMetaReplacer.Replace(value.Value)
349
+}
350
+
351
+func sanitizeBGPBool(value *ddsnmp.BGPBool) {
352
+ value.Raw = metricMetaReplacer.Replace(value.Raw)
353
+}
354
+
355
+func sanitizeBGPDirectional(value *ddsnmp.BGPDirectional) {
356
+ sanitizeBGPInt64(&value.Received)
357
+ sanitizeBGPInt64(&value.Sent)
358
+}
359
+
360
+func sanitizeBGPTimerPair(value *ddsnmp.BGPTimerPair) {
361
+ sanitizeBGPInt64(&value.ConnectRetry)
362
+ sanitizeBGPInt64(&value.HoldTime)
363
+ sanitizeBGPInt64(&value.KeepaliveTime)
364
+ sanitizeBGPInt64(&value.MinASOriginationInterval)
365
+ sanitizeBGPInt64(&value.MinRouteAdvertisementInterval)
366
+}
367
+
368
+func sanitizeBGPLastNotification(value *ddsnmp.BGPLastNotification) {
369
+ sanitizeBGPInt64(&value.Code)
370
+ sanitizeBGPInt64(&value.Subcode)
371
+ sanitizeBGPText(&value.Reason)
372
+}
373
+
374
+func sanitizeBGPRouteCounters(value *ddsnmp.BGPRouteCounters) {
375
+ sanitizeBGPInt64(&value.Received)
376
+ sanitizeBGPInt64(&value.Accepted)
377
+ sanitizeBGPInt64(&value.Rejected)
378
+ sanitizeBGPInt64(&value.Active)
379
+ sanitizeBGPInt64(&value.Advertised)
380
+ sanitizeBGPInt64(&value.Suppressed)
381
+ sanitizeBGPInt64(&value.Withdrawn)
382
+}
383
+
384
var metricMetaReplacer = strings.NewReplacer(
385
"'", "",
386
"\n", " ",
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_bgp.go
new
+1338
@@ -0,0 +1,1338 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "errors"
7
+ "fmt"
8
+ "maps"
9
+ "slices"
10
+ "strconv"
11
+ "strings"
12
+ "time"
13
+
14
+ "github.com/gosnmp/gosnmp"
15
+
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
17
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
18
+)
19
+
20
+const (
21
+ bgpRowsPartialErrorLogKey = "snmp-bgp-rows-partial-error:"
22
+ bgpRowsFailedLogKey = "snmp-bgp-rows-failed:"
23
+ bgpRowsErrorLogEvery = time.Hour
24
+)
25
+
26
+type bgpValueContext struct {
27
+ pdus map[string]gosnmp.SnmpPDU
28
+ rowIndex string
29
+ rowPDUs map[string]gosnmp.SnmpPDU
30
+ tableName string
31
+ crossTableCtx *crossTableContext
32
+ oidCache map[string]string
33
+}
34
+
35
+func (ctx bgpValueContext) lookupPDU(oid string) (gosnmp.SnmpPDU, bool) {
36
+ oid = trimOID(oid)
37
+ if ctx.rowPDUs != nil {
38
+ pdu, ok := ctx.rowPDUs[oid]
39
+ return pdu, ok
40
+ }
41
+ pdu, ok := ctx.pdus[oid]
42
+ return pdu, ok
43
+}
44
+
45
+func (c *Collector) collectBGPRows(prof *ddsnmp.Profile, stats *ddsnmp.CollectionStats) ([]ddsnmp.BGPRow, error) {
46
+ if prof.Definition == nil || len(prof.Definition.BGP) == 0 {
47
+ return nil, nil
48
+ }
49
+
50
+ var rows []ddsnmp.BGPRow
51
+ var errs []error
52
+
53
+ scalarRows, err := c.collectScalarBGPRows(prof.Definition.BGP, stats)
54
+ if err != nil {
55
+ errs = append(errs, err)
56
+ }
57
+ rows = append(rows, scalarRows...)
58
+
59
+ tableRows, err := c.collectTableBGPRows(prof.Definition.BGP, stats)
60
+ if err != nil {
61
+ errs = append(errs, err)
62
+ }
63
+ rows = append(rows, tableRows...)
64
+
65
+ if len(rows) == 0 && len(errs) > 0 {
66
+ return nil, errors.Join(errs...)
67
+ }
68
+ if len(errs) > 0 {
69
+ c.log.Limit(bgpRowsPartialErrorLogKey+prof.SourceFile, 1, bgpRowsErrorLogEvery).
70
+ Warningf("collecting BGP rows for profile %q partially failed: %v", prof.SourceFile, errors.Join(errs...))
71
+ }
72
+
73
+ return rows, nil
74
+}
75
+
76
+func (c *Collector) collectScalarBGPRows(configs []ddprofiledefinition.BGPConfig, stats *ddsnmp.CollectionStats) ([]ddsnmp.BGPRow, error) {
77
+ var rows []ddsnmp.BGPRow
78
+ var errs []error
79
+
80
+ for _, cfg := range configs {
81
+ if cfg.Table.OID != "" {
82
+ continue
83
+ }
84
+
85
+ oids, missingOIDs := c.bgpScalarOIDs(cfg)
86
+ if len(missingOIDs) > 0 {
87
+ c.log.Debugf("BGP scalar row %q missing OIDs: %v", bgpConfigDisplayName(cfg), missingOIDs)
88
+ stats.Errors.MissingOIDs += int64(len(missingOIDs))
89
+ }
90
+
91
+ var pdus map[string]gosnmp.SnmpPDU
92
+ var err error
93
+ if len(oids) > 0 {
94
+ pdus, err = c.scalarCollector.getScalarValues(oids, stats)
95
+ if err != nil {
96
+ stats.Errors.SNMP++
97
+ errs = append(errs, fmt.Errorf("BGP scalar row %q: %w", bgpConfigDisplayName(cfg), err))
98
+ continue
99
+ }
100
+ }
101
+
102
+ row, ok, err := c.buildScalarBGPRow(cfg, pdus)
103
+ if err != nil {
104
+ stats.Errors.Processing.BGP++
105
+ errs = append(errs, fmt.Errorf("BGP scalar row %q: %w", bgpConfigDisplayName(cfg), err))
106
+ continue
107
+ }
108
+ if ok {
109
+ rows = append(rows, row)
110
+ }
111
+ }
112
+
113
+ if len(rows) == 0 && len(errs) > 0 {
114
+ return nil, errors.Join(errs...)
115
+ }
116
+ return rows, nil
117
+}
118
+
119
+func (c *Collector) collectTableBGPRows(configs []ddprofiledefinition.BGPConfig, stats *ddsnmp.CollectionStats) ([]ddsnmp.BGPRow, error) {
120
+ var rows []ddsnmp.BGPRow
121
+ var errs []error
122
+ walkedData := make(map[string]map[string]gosnmp.SnmpPDU)
123
+ tableNameToOID := bgpTableNameToOID(configs)
124
+
125
+ for _, cfg := range configs {
126
+ if cfg.Table.OID == "" {
127
+ continue
128
+ }
129
+ metricsCfg := bgpConfigAsMetricsConfig(cfg)
130
+ cachedRows, ok, err := c.collectTableBGPRowsFromCache(cfg, metricsCfg, stats)
131
+ if err != nil {
132
+ c.log.Debugf("Cached BGP collection failed for table %s: %v", cfg.Table.Name, err)
133
+ }
134
+ if ok {
135
+ stats.TableCache.Hits++
136
+ stats.SNMP.TablesCached++
137
+ rows = append(rows, cachedRows...)
138
+ continue
139
+ }
140
+ stats.TableCache.Misses++
141
+
142
+ tableOID := trimOID(cfg.Table.OID)
143
+ pdus := walkedData[tableOID]
144
+ if pdus == nil {
145
+ var err error
146
+ pdus, err = c.tableCollector.snmpWalk(cfg.Table.OID, stats)
147
+ if err != nil {
148
+ stats.Errors.SNMP++
149
+ errs = append(errs, fmt.Errorf("BGP table %q: %w", bgpConfigDisplayName(cfg), err))
150
+ continue
151
+ }
152
+ if len(pdus) > 0 {
153
+ stats.SNMP.TablesWalked++
154
+ walkedData[tableOID] = pdus
155
+ }
156
+ }
157
+ if len(pdus) == 0 {
158
+ continue
159
+ }
160
+
161
+ if err := c.walkBGPTableDependencies(cfg, metricsCfg, tableNameToOID, walkedData, stats); err != nil {
162
+ errs = append(errs, fmt.Errorf("BGP table %q dependencies: %w", bgpConfigDisplayName(cfg), err))
163
+ continue
164
+ }
165
+
166
+ ctx := &tableProcessingContext{
167
+ config: metricsCfg,
168
+ pdus: pdus,
169
+ walkedData: walkedData,
170
+ tableNameToOID: tableNameToOID,
171
+ }
172
+ ctx.columnOIDs = buildColumnOIDs(metricsCfg)
173
+ ctx.orderedTags = buildOrderedTags(metricsCfg)
174
+ ctx.rows, ctx.oidCache, ctx.tagCache = c.tableCollector.organizePDUsByRow(ctx)
175
+
176
+ for rowIndex, rowPDUs := range ctx.rows {
177
+ row, ok, err := c.buildTableBGPRow(cfg, rowIndex, rowPDUs, ctx)
178
+ if err != nil {
179
+ stats.Errors.Processing.BGP++
180
+ errs = append(errs, fmt.Errorf("BGP table %q row %q: %w", bgpConfigDisplayName(cfg), rowIndex, err))
181
+ continue
182
+ }
183
+ if ok {
184
+ rows = append(rows, row)
185
+ }
186
+ }
187
+
188
+ deps := bgpTableDependencies(cfg, metricsCfg, ctx.tableNameToOID)
189
+ c.tableCollector.tableCache.cacheData(metricsCfg, ctx.oidCache, ctx.tagCache, deps)
190
+ }
191
+
192
+ if len(rows) == 0 && len(errs) > 0 {
193
+ return nil, errors.Join(errs...)
194
+ }
195
+ return rows, nil
196
+}
197
+
198
+func (c *Collector) walkBGPTableDependencies(
199
+ cfg ddprofiledefinition.BGPConfig,
200
+ metricsCfg ddprofiledefinition.MetricsConfig,
201
+ tableNameToOID map[string]string,
202
+ walkedData map[string]map[string]gosnmp.SnmpPDU,
203
+ stats *ddsnmp.CollectionStats,
204
+) error {
205
+ var errs []error
206
+ for _, depOID := range bgpTableDependencies(cfg, metricsCfg, tableNameToOID) {
207
+ depOID = trimOID(depOID)
208
+ if walkedData[depOID] != nil {
209
+ continue
210
+ }
211
+ pdus, err := c.tableCollector.snmpWalk(depOID, stats)
212
+ if err != nil {
213
+ stats.Errors.SNMP++
214
+ errs = append(errs, fmt.Errorf("table OID %q: %w", depOID, err))
215
+ continue
216
+ }
217
+ walkedData[depOID] = pdus
218
+ if len(pdus) > 0 {
219
+ stats.SNMP.TablesWalked++
220
+ }
221
+ }
222
+ return errors.Join(errs...)
223
+}
224
+
225
+func (c *Collector) collectTableBGPRowsFromCache(cfg ddprofiledefinition.BGPConfig, metricsCfg ddprofiledefinition.MetricsConfig, stats *ddsnmp.CollectionStats) ([]ddsnmp.BGPRow, bool, error) {
226
+ cachedOIDs, cachedTags, ok := c.tableCollector.tableCache.getCachedData(metricsCfg)
227
+ if !ok {
228
+ return nil, false, nil
229
+ }
230
+
231
+ columnOIDs := buildColumnOIDs(metricsCfg)
232
+ var oidsToGet []string
233
+ for _, columns := range cachedOIDs {
234
+ for columnOID, fullOID := range columns {
235
+ if _, ok := columnOIDs[columnOID]; ok {
236
+ oidsToGet = append(oidsToGet, fullOID)
237
+ }
238
+ }
239
+ }
240
+ if len(oidsToGet) == 0 {
241
+ return nil, true, nil
242
+ }
243
+
244
+ pdus, err := c.tableCollector.snmpGet(oidsToGet, stats)
245
+ if err != nil {
246
+ return nil, false, err
247
+ }
248
+ if len(pdus) < len(oidsToGet)/2 {
249
+ return nil, false, fmt.Errorf("table structure may have changed, got %d/%d PDUs", len(pdus), len(oidsToGet))
250
+ }
251
+
252
+ var rows []ddsnmp.BGPRow
253
+ for rowIndex, columns := range cachedOIDs {
254
+ rowPDUs := make(map[string]gosnmp.SnmpPDU)
255
+ for columnOID, fullOID := range columns {
256
+ pdu, ok := pdus[trimOID(fullOID)]
257
+ if ok {
258
+ rowPDUs[columnOID] = pdu
259
+ }
260
+ }
261
+ rowTags := maps.Clone(cachedTags[rowIndex])
262
+ row, ok, err := c.buildTableBGPRowWithTags(cfg, rowIndex, rowPDUs, rowTags, bgpValueContext{
263
+ rowIndex: rowIndex,
264
+ rowPDUs: rowPDUs,
265
+ tableName: cfg.Table.Name,
266
+ })
267
+ if err != nil {
268
+ return nil, false, fmt.Errorf("row %q: %w", rowIndex, err)
269
+ }
270
+ if ok {
271
+ rows = append(rows, row)
272
+ }
273
+ }
274
+ return rows, true, nil
275
+}
276
+
277
+func (c *Collector) buildScalarBGPRow(cfg ddprofiledefinition.BGPConfig, pdus map[string]gosnmp.SnmpPDU) (ddsnmp.BGPRow, bool, error) {
278
+ rowKey := scalarBGPRowKey(cfg)
279
+ row := ddsnmp.BGPRow{
280
+ OriginProfileID: cfg.OriginProfileID,
281
+ Kind: cfg.Kind,
282
+ RowKey: rowKey,
283
+ StructuralID: bgpScalarStructuralID(cfg.OriginProfileID, cfg.Kind, rowKey),
284
+ Tags: parseStaticTags(cfg.StaticTags),
285
+ }
286
+
287
+ bgpCtx := bgpValueContext{pdus: pdus}
288
+ if err := c.populateBGPRow(&row, cfg, bgpCtx); err != nil {
289
+ return ddsnmp.BGPRow{}, false, err
290
+ }
291
+
292
+ if len(cfg.MetricTags) > 0 {
293
+ tags := make(map[string]string)
294
+ ta := tagAdder{tags: tags}
295
+ for _, tagCfg := range cfg.MetricTags {
296
+ if tagCfg.Symbol.OID == "" {
297
+ continue
298
+ }
299
+ if err := c.scalarCollector.tagProc.processTag(tagCfg, pdus, ta); err != nil {
300
+ c.log.Debugf("Error processing scalar BGP tag %s: %v", metricTagDisplayName(tagCfg), err)
301
+ }
302
+ }
303
+ mergeStringMaps(row.Tags, tags)
304
+ }
305
+
306
+ if !bgpRowHasSignals(row) {
307
+ return ddsnmp.BGPRow{}, false, nil
308
+ }
309
+ if !bgpRowIdentityComplete(row) {
310
+ return ddsnmp.BGPRow{}, false, nil
311
+ }
312
+
313
+ return row, true, nil
314
+}
315
+
316
+func (c *Collector) buildTableBGPRow(cfg ddprofiledefinition.BGPConfig, rowIndex string, rowPDUs map[string]gosnmp.SnmpPDU, ctx *tableProcessingContext) (ddsnmp.BGPRow, bool, error) {
317
+ rowTags := make(map[string]string)
318
+ rowData := &tableRowData{
319
+ index: rowIndex,
320
+ pdus: rowPDUs,
321
+ tags: rowTags,
322
+ staticTags: parseStaticTags(cfg.StaticTags),
323
+ tableName: cfg.Table.Name,
324
+ }
325
+ crossTableCtx := &crossTableContext{
326
+ walkedData: ctx.walkedData,
327
+ tableNameToOID: ctx.tableNameToOID,
328
+ lookupIndexCache: make(map[crossTableLookupKey]string),
329
+ rowTags: rowData.tags,
330
+ }
331
+ rowCtx := &tableRowProcessingContext{
332
+ config: ctx.config,
333
+ columnOIDs: ctx.columnOIDs,
334
+ crossTableCtx: crossTableCtx,
335
+ orderedTags: ctx.orderedTags,
336
+ }
337
+ if err := c.tableCollector.rowProcessor.processRowTags(rowData, rowCtx); err != nil {
338
+ c.log.Debugf("Error processing BGP row tags for %s: %v", rowIndex, err)
339
+ }
340
+ maps.Copy(ctx.tagCache[rowIndex], rowData.tags)
341
+
342
+ return c.buildTableBGPRowWithTags(cfg, rowIndex, rowPDUs, rowData.tags, bgpValueContext{
343
+ rowIndex: rowIndex,
344
+ rowPDUs: rowPDUs,
345
+ tableName: cfg.Table.Name,
346
+ crossTableCtx: crossTableCtx,
347
+ oidCache: ctx.oidCache[rowIndex],
348
+ })
349
+}
350
+
351
+func (c *Collector) buildTableBGPRowWithTags(cfg ddprofiledefinition.BGPConfig, rowIndex string, rowPDUs map[string]gosnmp.SnmpPDU, rowTags map[string]string, bgpCtx bgpValueContext) (ddsnmp.BGPRow, bool, error) {
352
+ row := ddsnmp.BGPRow{
353
+ OriginProfileID: cfg.OriginProfileID,
354
+ Kind: cfg.Kind,
355
+ TableOID: trimOID(cfg.Table.OID),
356
+ Table: cfg.Table.Name,
357
+ RowKey: rowIndex,
358
+ StructuralID: bgpTableStructuralID(cfg.OriginProfileID, cfg.Kind, cfg.ID, trimOID(cfg.Table.OID), rowIndex),
359
+ Tags: parseStaticTags(cfg.StaticTags),
360
+ }
361
+ mergeStringMaps(row.Tags, rowTags)
362
+
363
+ if bgpCtx.rowIndex == "" {
364
+ bgpCtx.rowIndex = rowIndex
365
+ }
366
+ if bgpCtx.rowPDUs == nil {
367
+ bgpCtx.rowPDUs = rowPDUs
368
+ }
369
+ if bgpCtx.tableName == "" {
370
+ bgpCtx.tableName = cfg.Table.Name
371
+ }
372
+ if err := c.populateBGPRow(&row, cfg, bgpCtx); err != nil {
373
+ return ddsnmp.BGPRow{}, false, err
374
+ }
375
+
376
+ if !bgpRowHasSignals(row) {
377
+ return ddsnmp.BGPRow{}, false, nil
378
+ }
379
+ if !bgpRowIdentityComplete(row) {
380
+ return ddsnmp.BGPRow{}, false, nil
381
+ }
382
+
383
+ return row, true, nil
384
+}
385
+
386
+func (c *Collector) populateBGPRow(row *ddsnmp.BGPRow, cfg ddprofiledefinition.BGPConfig, ctx bgpValueContext) error {
387
+ var err error
388
+
389
+ if row.Identity.RoutingInstance, err = c.bgpTextValue(cfg.Identity.RoutingInstance, ctx); err != nil {
390
+ return fmt.Errorf("identity.routing_instance: %w", err)
391
+ }
392
+ if row.Identity.Neighbor, err = c.bgpTextValue(cfg.Identity.Neighbor, ctx); err != nil {
393
+ return fmt.Errorf("identity.neighbor: %w", err)
394
+ }
395
+ if row.Identity.RemoteAS, err = c.bgpTextValue(cfg.Identity.RemoteAS, ctx); err != nil {
396
+ return fmt.Errorf("identity.remote_as: %w", err)
397
+ }
398
+ if row.Identity.AddressFamily, err = c.bgpAddressFamilyValue(cfg.Identity.AddressFamily.BGPValueConfig, ctx); err != nil {
399
+ return fmt.Errorf("identity.address_family: %w", err)
400
+ }
401
+ if row.Identity.SubsequentAddressFamily, err = c.bgpSubsequentAddressFamilyValue(cfg.Identity.SubsequentAddressFamily.BGPValueConfig, ctx); err != nil {
402
+ return fmt.Errorf("identity.subsequent_address_family: %w", err)
403
+ }
404
+ if row.Descriptors.LocalAddress, err = c.bgpTextValue(cfg.Descriptors.LocalAddress, ctx); err != nil {
405
+ return fmt.Errorf("descriptors.local_address: %w", err)
406
+ }
407
+ if row.Descriptors.LocalAS, err = c.bgpTextValue(cfg.Descriptors.LocalAS, ctx); err != nil {
408
+ return fmt.Errorf("descriptors.local_as: %w", err)
409
+ }
410
+ if row.Descriptors.LocalIdentifier, err = c.bgpTextValue(cfg.Descriptors.LocalIdentifier, ctx); err != nil {
411
+ return fmt.Errorf("descriptors.local_identifier: %w", err)
412
+ }
413
+ if row.Descriptors.PeerIdentifier, err = c.bgpTextValue(cfg.Descriptors.PeerIdentifier, ctx); err != nil {
414
+ return fmt.Errorf("descriptors.peer_identifier: %w", err)
415
+ }
416
+ if row.Descriptors.PeerType, err = c.bgpTextValue(cfg.Descriptors.PeerType, ctx); err != nil {
417
+ return fmt.Errorf("descriptors.peer_type: %w", err)
418
+ }
419
+ if row.Descriptors.BGPVersion, err = c.bgpTextValue(cfg.Descriptors.BGPVersion, ctx); err != nil {
420
+ return fmt.Errorf("descriptors.bgp_version: %w", err)
421
+ }
422
+ if row.Descriptors.Description, err = c.bgpTextValue(cfg.Descriptors.Description, ctx); err != nil {
423
+ return fmt.Errorf("descriptors.description: %w", err)
424
+ }
425
+
426
+ if err := c.populateBGPStateValue(&row.State, cfg.State, ctx); err != nil {
427
+ return fmt.Errorf("state: %w", err)
428
+ }
429
+ if err := c.populateBGPStateValue(&row.Previous, cfg.Previous, ctx); err != nil {
430
+ return fmt.Errorf("previous_state: %w", err)
431
+ }
432
+ if err := c.populateBGPBool(&row.Admin.Enabled, cfg.Admin.Enabled, ctx); err != nil {
433
+ return fmt.Errorf("admin.enabled: %w", err)
434
+ }
435
+ if err := c.populateBGPConnection(&row.Connection, cfg.Connection, ctx); err != nil {
436
+ return fmt.Errorf("connection: %w", err)
437
+ }
438
+ if err := c.populateBGPTraffic(&row.Traffic, cfg.Traffic, ctx); err != nil {
439
+ return fmt.Errorf("traffic: %w", err)
440
+ }
441
+ if err := c.populateBGPTransitions(&row.Transitions, cfg.Transitions, ctx); err != nil {
442
+ return fmt.Errorf("transitions: %w", err)
443
+ }
444
+ if err := c.populateBGPTimers(&row.Timers, cfg.Timers, ctx); err != nil {
445
+ return fmt.Errorf("timers: %w", err)
446
+ }
447
+ if err := c.populateBGPLastError(&row.LastError, cfg.LastError, ctx); err != nil {
448
+ return fmt.Errorf("last_error: %w", err)
449
+ }
450
+ if err := c.populateBGPLastNotifications(&row.LastNotify, cfg.LastNotify, ctx); err != nil {
451
+ return fmt.Errorf("last_notifications: %w", err)
452
+ }
453
+ if err := c.populateBGPReasons(&row.Reasons, cfg.Reasons, ctx); err != nil {
454
+ return fmt.Errorf("reasons: %w", err)
455
+ }
456
+ if err := c.populateBGPText(&row.Restart.State, cfg.Restart.State, ctx); err != nil {
457
+ return fmt.Errorf("graceful_restart.state: %w", err)
458
+ }
459
+ if err := c.populateBGPRoutes(&row.Routes, cfg.Routes, ctx); err != nil {
460
+ return fmt.Errorf("routes: %w", err)
461
+ }
462
+ if err := c.populateBGPRouteLimits(&row.RouteLimits, cfg.RouteLimits, ctx); err != nil {
463
+ return fmt.Errorf("route_limits: %w", err)
464
+ }
465
+ if err := c.populateBGPDeviceCounts(&row.Device, cfg.Device, ctx); err != nil {
466
+ return fmt.Errorf("device_counts: %w", err)
467
+ }
468
+
469
+ return nil
470
+}
471
+
472
+func (c *Collector) populateBGPStateValue(dst *ddsnmp.BGPState, cfg ddprofiledefinition.BGPStateConfig, ctx bgpValueContext) error {
473
+ if !cfg.IsSet() {
474
+ return nil
475
+ }
476
+
477
+ raw, sourceOID, ok, err := c.bgpRawTextValue(cfg.BGPValueConfig, ctx)
478
+ if err != nil {
479
+ return err
480
+ }
481
+ if !ok {
482
+ return nil
483
+ }
484
+ state := raw
485
+ if mapped, ok := bgpValueSymbol(cfg.BGPValueConfig).Mapping.Lookup(raw); ok {
486
+ state = mapped
487
+ }
488
+
489
+ *dst = ddsnmp.BGPState{
490
+ Has: true,
491
+ State: ddprofiledefinition.BGPPeerState(state),
492
+ Raw: raw,
493
+ SourceOID: sourceOID,
494
+ }
495
+ return nil
496
+}
497
+
498
+func (c *Collector) populateBGPInt64(dst *ddsnmp.BGPInt64, cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) error {
499
+ if !cfg.IsSet() {
500
+ return nil
501
+ }
502
+ value, sourceOID, ok, err := c.bgpNumericValue(cfg, ctx)
503
+ if err != nil {
504
+ return err
505
+ }
506
+ if !ok {
507
+ return nil
508
+ }
509
+ raw, _, _, _ := c.bgpRawTextValue(cfg, ctx)
510
+ *dst = ddsnmp.BGPInt64{
511
+ Has: true,
512
+ Value: value,
513
+ Raw: raw,
514
+ SourceOID: sourceOID,
515
+ }
516
+ return nil
517
+}
518
+
519
+func (c *Collector) populateBGPText(dst *ddsnmp.BGPText, cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) error {
520
+ if !cfg.IsSet() {
521
+ return nil
522
+ }
523
+ raw, sourceOID, ok, err := c.bgpRawTextValue(cfg, ctx)
524
+ if err != nil {
525
+ return err
526
+ }
527
+ if !ok {
528
+ return nil
529
+ }
530
+ value := raw
531
+ if mapped, ok := bgpValueSymbol(cfg).Mapping.Lookup(raw); ok {
532
+ value = mapped
533
+ }
534
+ *dst = ddsnmp.BGPText{
535
+ Has: true,
536
+ Value: value,
537
+ Raw: raw,
538
+ SourceOID: sourceOID,
539
+ }
540
+ return nil
541
+}
542
+
543
+func (c *Collector) populateBGPBool(dst *ddsnmp.BGPBool, cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) error {
544
+ if !cfg.IsSet() {
545
+ return nil
546
+ }
547
+ raw, sourceOID, ok, err := c.bgpRawTextValue(cfg, ctx)
548
+ if err != nil {
549
+ return err
550
+ }
551
+ if !ok {
552
+ return nil
553
+ }
554
+ text := strings.ToLower(strings.TrimSpace(raw))
555
+ if mapped, ok := bgpValueSymbol(cfg).Mapping.Lookup(raw); ok {
556
+ text = strings.ToLower(strings.TrimSpace(mapped))
557
+ }
558
+ value, err := bgpParseBool(text)
559
+ if err != nil {
560
+ return err
561
+ }
562
+ *dst = ddsnmp.BGPBool{
563
+ Has: true,
564
+ Value: value,
565
+ Raw: raw,
566
+ SourceOID: sourceOID,
567
+ }
568
+ return nil
569
+}
570
+
571
+func (c *Collector) populateBGPConnection(dst *ddsnmp.BGPConnection, cfg ddprofiledefinition.BGPConnectionConfig, ctx bgpValueContext) error {
572
+ if err := c.populateBGPInt64(&dst.EstablishedUptime, cfg.EstablishedUptime, ctx); err != nil {
573
+ return fmt.Errorf("established_uptime: %w", err)
574
+ }
575
+ if err := c.populateBGPInt64(&dst.LastReceivedUpdateAge, cfg.LastReceivedUpdateAge, ctx); err != nil {
576
+ return fmt.Errorf("last_received_update_age: %w", err)
577
+ }
578
+ return nil
579
+}
580
+
581
+func (c *Collector) populateBGPDirectional(dst *ddsnmp.BGPDirectional, cfg ddprofiledefinition.BGPDirectionalConfig, ctx bgpValueContext) error {
582
+ if err := c.populateBGPInt64(&dst.Received, cfg.Received, ctx); err != nil {
583
+ return fmt.Errorf("received: %w", err)
584
+ }
585
+ if err := c.populateBGPInt64(&dst.Sent, cfg.Sent, ctx); err != nil {
586
+ return fmt.Errorf("sent: %w", err)
587
+ }
588
+ return nil
589
+}
590
+
591
+func (c *Collector) populateBGPTraffic(dst *ddsnmp.BGPTraffic, cfg ddprofiledefinition.BGPTrafficConfig, ctx bgpValueContext) error {
592
+ if err := c.populateBGPDirectional(&dst.Messages, cfg.Messages, ctx); err != nil {
593
+ return fmt.Errorf("messages: %w", err)
594
+ }
595
+ if err := c.populateBGPDirectional(&dst.Updates, cfg.Updates, ctx); err != nil {
596
+ return fmt.Errorf("updates: %w", err)
597
+ }
598
+ if err := c.populateBGPDirectional(&dst.Notifications, cfg.Notifications, ctx); err != nil {
599
+ return fmt.Errorf("notifications: %w", err)
600
+ }
601
+ if err := c.populateBGPDirectional(&dst.RouteRefreshes, cfg.RouteRefreshes, ctx); err != nil {
602
+ return fmt.Errorf("route_refreshes: %w", err)
603
+ }
604
+ if err := c.populateBGPDirectional(&dst.Opens, cfg.Opens, ctx); err != nil {
605
+ return fmt.Errorf("opens: %w", err)
606
+ }
607
+ if err := c.populateBGPDirectional(&dst.Keepalives, cfg.Keepalives, ctx); err != nil {
608
+ return fmt.Errorf("keepalives: %w", err)
609
+ }
610
+ return nil
611
+}
612
+
613
+func (c *Collector) populateBGPTransitions(dst *ddsnmp.BGPTransitions, cfg ddprofiledefinition.BGPTransitionsConfig, ctx bgpValueContext) error {
614
+ if err := c.populateBGPInt64(&dst.Established, cfg.Established, ctx); err != nil {
615
+ return fmt.Errorf("established: %w", err)
616
+ }
617
+ if err := c.populateBGPInt64(&dst.Down, cfg.Down, ctx); err != nil {
618
+ return fmt.Errorf("down: %w", err)
619
+ }
620
+ if err := c.populateBGPInt64(&dst.Up, cfg.Up, ctx); err != nil {
621
+ return fmt.Errorf("up: %w", err)
622
+ }
623
+ if err := c.populateBGPInt64(&dst.Flaps, cfg.Flaps, ctx); err != nil {
624
+ return fmt.Errorf("flaps: %w", err)
625
+ }
626
+ return nil
627
+}
628
+
629
+func (c *Collector) populateBGPTimers(dst *ddsnmp.BGPTimers, cfg ddprofiledefinition.BGPTimersConfig, ctx bgpValueContext) error {
630
+ if err := c.populateBGPTimerPair(&dst.Negotiated, cfg.Negotiated, ctx); err != nil {
631
+ return fmt.Errorf("negotiated: %w", err)
632
+ }
633
+ if err := c.populateBGPTimerPair(&dst.Configured, cfg.Configured, ctx); err != nil {
634
+ return fmt.Errorf("configured: %w", err)
635
+ }
636
+ return nil
637
+}
638
+
639
+func (c *Collector) populateBGPTimerPair(dst *ddsnmp.BGPTimerPair, cfg ddprofiledefinition.BGPTimerPairConfig, ctx bgpValueContext) error {
640
+ if err := c.populateBGPInt64(&dst.ConnectRetry, cfg.ConnectRetry, ctx); err != nil {
641
+ return fmt.Errorf("connect_retry: %w", err)
642
+ }
643
+ if err := c.populateBGPInt64(&dst.HoldTime, cfg.HoldTime, ctx); err != nil {
644
+ return fmt.Errorf("hold_time: %w", err)
645
+ }
646
+ if err := c.populateBGPInt64(&dst.KeepaliveTime, cfg.KeepaliveTime, ctx); err != nil {
647
+ return fmt.Errorf("keepalive_time: %w", err)
648
+ }
649
+ if err := c.populateBGPInt64(&dst.MinASOriginationInterval, cfg.MinASOriginationInterval, ctx); err != nil {
650
+ return fmt.Errorf("min_as_origination_interval: %w", err)
651
+ }
652
+ if err := c.populateBGPInt64(&dst.MinRouteAdvertisementInterval, cfg.MinRouteAdvertisementInterval, ctx); err != nil {
653
+ return fmt.Errorf("min_route_advertisement_interval: %w", err)
654
+ }
655
+ return nil
656
+}
657
+
658
+func (c *Collector) populateBGPLastError(dst *ddsnmp.BGPLastError, cfg ddprofiledefinition.BGPLastErrorConfig, ctx bgpValueContext) error {
659
+ if err := c.populateBGPInt64(&dst.Code, cfg.Code, ctx); err != nil {
660
+ return fmt.Errorf("code: %w", err)
661
+ }
662
+ if err := c.populateBGPInt64(&dst.Subcode, cfg.Subcode, ctx); err != nil {
663
+ return fmt.Errorf("subcode: %w", err)
664
+ }
665
+ return nil
666
+}
667
+
668
+func (c *Collector) populateBGPLastNotifications(dst *ddsnmp.BGPLastNotifications, cfg ddprofiledefinition.BGPLastNotifyConfig, ctx bgpValueContext) error {
669
+ if err := c.populateBGPLastNotification(&dst.Received, cfg.Received, ctx); err != nil {
670
+ return fmt.Errorf("received: %w", err)
671
+ }
672
+ if err := c.populateBGPLastNotification(&dst.Sent, cfg.Sent, ctx); err != nil {
673
+ return fmt.Errorf("sent: %w", err)
674
+ }
675
+ return nil
676
+}
677
+
678
+func (c *Collector) populateBGPLastNotification(dst *ddsnmp.BGPLastNotification, cfg ddprofiledefinition.BGPLastNotificationConfig, ctx bgpValueContext) error {
679
+ if err := c.populateBGPInt64(&dst.Code, cfg.Code, ctx); err != nil {
680
+ return fmt.Errorf("code: %w", err)
681
+ }
682
+ if err := c.populateBGPInt64(&dst.Subcode, cfg.Subcode, ctx); err != nil {
683
+ return fmt.Errorf("subcode: %w", err)
684
+ }
685
+ if err := c.populateBGPText(&dst.Reason, cfg.Reason, ctx); err != nil {
686
+ return fmt.Errorf("reason: %w", err)
687
+ }
688
+ return nil
689
+}
690
+
691
+func (c *Collector) populateBGPReasons(dst *ddsnmp.BGPReasons, cfg ddprofiledefinition.BGPReasonsConfig, ctx bgpValueContext) error {
692
+ if err := c.populateBGPText(&dst.LastDown, cfg.LastDown, ctx); err != nil {
693
+ return fmt.Errorf("last_down: %w", err)
694
+ }
695
+ if err := c.populateBGPText(&dst.Unavailability, cfg.Unavailability, ctx); err != nil {
696
+ return fmt.Errorf("unavailability: %w", err)
697
+ }
698
+ return nil
699
+}
700
+
701
+func (c *Collector) populateBGPRoutes(dst *ddsnmp.BGPRoutes, cfg ddprofiledefinition.BGPRoutesConfig, ctx bgpValueContext) error {
702
+ if err := c.populateBGPRouteCounters(&dst.Current, cfg.Current, ctx); err != nil {
703
+ return fmt.Errorf("current: %w", err)
704
+ }
705
+ if err := c.populateBGPRouteCounters(&dst.Total, cfg.Total, ctx); err != nil {
706
+ return fmt.Errorf("total: %w", err)
707
+ }
708
+ return nil
709
+}
710
+
711
+func (c *Collector) populateBGPRouteCounters(dst *ddsnmp.BGPRouteCounters, cfg ddprofiledefinition.BGPRouteCountersConfig, ctx bgpValueContext) error {
712
+ if err := c.populateBGPInt64(&dst.Received, cfg.Received, ctx); err != nil {
713
+ return fmt.Errorf("received: %w", err)
714
+ }
715
+ if err := c.populateBGPInt64(&dst.Accepted, cfg.Accepted, ctx); err != nil {
716
+ return fmt.Errorf("accepted: %w", err)
717
+ }
718
+ if err := c.populateBGPInt64(&dst.Rejected, cfg.Rejected, ctx); err != nil {
719
+ return fmt.Errorf("rejected: %w", err)
720
+ }
721
+ if err := c.populateBGPInt64(&dst.Active, cfg.Active, ctx); err != nil {
722
+ return fmt.Errorf("active: %w", err)
723
+ }
724
+ if err := c.populateBGPInt64(&dst.Advertised, cfg.Advertised, ctx); err != nil {
725
+ return fmt.Errorf("advertised: %w", err)
726
+ }
727
+ if err := c.populateBGPInt64(&dst.Suppressed, cfg.Suppressed, ctx); err != nil {
728
+ return fmt.Errorf("suppressed: %w", err)
729
+ }
730
+ if err := c.populateBGPInt64(&dst.Withdrawn, cfg.Withdrawn, ctx); err != nil {
731
+ return fmt.Errorf("withdrawn: %w", err)
732
+ }
733
+ return nil
734
+}
735
+
736
+func (c *Collector) populateBGPRouteLimits(dst *ddsnmp.BGPRouteLimits, cfg ddprofiledefinition.BGPRouteLimitsConfig, ctx bgpValueContext) error {
737
+ if err := c.populateBGPInt64(&dst.Limit, cfg.Limit, ctx); err != nil {
738
+ return fmt.Errorf("limit: %w", err)
739
+ }
740
+ if err := c.populateBGPInt64(&dst.Threshold, cfg.Threshold, ctx); err != nil {
741
+ return fmt.Errorf("threshold: %w", err)
742
+ }
743
+ if err := c.populateBGPInt64(&dst.ClearThreshold, cfg.ClearThreshold, ctx); err != nil {
744
+ return fmt.Errorf("clear_threshold: %w", err)
745
+ }
746
+ return nil
747
+}
748
+
749
+func (c *Collector) populateBGPDeviceCounts(dst *ddsnmp.BGPDeviceCounts, cfg ddprofiledefinition.BGPDeviceCountsConfig, ctx bgpValueContext) error {
750
+ if err := c.populateBGPInt64(&dst.Peers, cfg.Peers, ctx); err != nil {
751
+ return fmt.Errorf("peers: %w", err)
752
+ }
753
+ if err := c.populateBGPInt64(&dst.InternalPeers, cfg.InternalPeers, ctx); err != nil {
754
+ return fmt.Errorf("ibgp_peers: %w", err)
755
+ }
756
+ if err := c.populateBGPInt64(&dst.ExternalPeers, cfg.ExternalPeers, ctx); err != nil {
757
+ return fmt.Errorf("ebgp_peers: %w", err)
758
+ }
759
+ counts, err := c.bgpPeerStateCounts(cfg.States, ctx)
760
+ if err != nil {
761
+ return fmt.Errorf("states: %w", err)
762
+ }
763
+ if len(counts) > 0 {
764
+ dst.ByStateHas = true
765
+ dst.ByState = counts
766
+ }
767
+ return nil
768
+}
769
+
770
+func (c *Collector) bgpPeerStateCounts(cfg ddprofiledefinition.BGPPeerStatesConfig, ctx bgpValueContext) (map[ddprofiledefinition.BGPPeerState]int64, error) {
771
+ counts := make(map[ddprofiledefinition.BGPPeerState]int64)
772
+ add := func(state ddprofiledefinition.BGPPeerState, valueCfg ddprofiledefinition.BGPValueConfig) error {
773
+ var value ddsnmp.BGPInt64
774
+ if err := c.populateBGPInt64(&value, valueCfg, ctx); err != nil {
775
+ return err
776
+ }
777
+ if value.Has {
778
+ counts[state] = value.Value
779
+ }
780
+ return nil
781
+ }
782
+ if err := add(ddprofiledefinition.BGPPeerStateIdle, cfg.Idle); err != nil {
783
+ return nil, fmt.Errorf("idle: %w", err)
784
+ }
785
+ if err := add(ddprofiledefinition.BGPPeerStateConnect, cfg.Connect); err != nil {
786
+ return nil, fmt.Errorf("connect: %w", err)
787
+ }
788
+ if err := add(ddprofiledefinition.BGPPeerStateActive, cfg.Active); err != nil {
789
+ return nil, fmt.Errorf("active: %w", err)
790
+ }
791
+ if err := add(ddprofiledefinition.BGPPeerStateOpenSent, cfg.OpenSent); err != nil {
792
+ return nil, fmt.Errorf("opensent: %w", err)
793
+ }
794
+ if err := add(ddprofiledefinition.BGPPeerStateOpenConfirm, cfg.OpenConfirm); err != nil {
795
+ return nil, fmt.Errorf("openconfirm: %w", err)
796
+ }
797
+ if err := add(ddprofiledefinition.BGPPeerStateEstablished, cfg.Established); err != nil {
798
+ return nil, fmt.Errorf("established: %w", err)
799
+ }
800
+ return counts, nil
801
+}
802
+
803
+func bgpParseBool(text string) (bool, error) {
804
+ switch text {
805
+ case "1", "true", "yes", "enabled", "enable", "start", "running", "up":
806
+ return true, nil
807
+ case "0", "false", "no", "disabled", "disable", "stop", "halted", "down":
808
+ return false, nil
809
+ default:
810
+ value, err := strconv.ParseInt(text, 10, 64)
811
+ if err != nil {
812
+ return false, fmt.Errorf("cannot parse %q as boolean", text)
813
+ }
814
+ return value != 0, nil
815
+ }
816
+}
817
+
818
+func (c *Collector) bgpTextValue(cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) (string, error) {
819
+ value, _, ok, err := c.bgpRawTextValue(cfg, ctx)
820
+ if err != nil || !ok {
821
+ return "", err
822
+ }
823
+ sym := bgpValueSymbol(cfg)
824
+ if mapped, ok := sym.Mapping.Lookup(value); ok {
825
+ value = mapped
826
+ }
827
+ return value, nil
828
+}
829
+
830
+func (c *Collector) bgpAddressFamilyValue(cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) (ddprofiledefinition.BGPAddressFamily, error) {
831
+ value, err := c.bgpTextValue(cfg, ctx)
832
+ if err != nil || value == "" {
833
+ return "", err
834
+ }
835
+ return ddprofiledefinition.BGPAddressFamily(value), nil
836
+}
837
+
838
+func (c *Collector) bgpSubsequentAddressFamilyValue(cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) (ddprofiledefinition.BGPSubsequentAddressFamily, error) {
839
+ value, err := c.bgpTextValue(cfg, ctx)
840
+ if err != nil || value == "" {
841
+ return "", err
842
+ }
843
+ return ddprofiledefinition.BGPSubsequentAddressFamily(value), nil
844
+}
845
+
846
+func (c *Collector) bgpRawTextValue(cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) (value string, sourceOID string, ok bool, err error) {
847
+ if !cfg.IsSet() {
848
+ return "", "", false, nil
849
+ }
850
+ if cfg.Value != "" {
851
+ return cfg.Value, "", true, nil
852
+ }
853
+ sym := bgpValueSymbol(cfg)
854
+ if sym.OID == "" && (cfg.Index != 0 || cfg.IndexFromEnd != 0 || len(cfg.IndexTransform) > 0) {
855
+ value, err := c.bgpIndexValue(cfg, ctx.rowIndex)
856
+ return value, "", err == nil, err
857
+ }
858
+
859
+ if sym.OID == "" {
860
+ return "", "", false, nil
861
+ }
862
+ pdu, sourceOID, ok, err := c.lookupBGPValuePDU(cfg, sym, ctx)
863
+ if err != nil || !ok {
864
+ return "", "", false, err
865
+ }
866
+
867
+ value, err = convPduToStringf(pdu, sym.Format)
868
+ if err != nil {
869
+ if errors.Is(err, errNoTextDateValue) {
870
+ return "", "", false, nil
871
+ }
872
+ return "", "", false, err
873
+ }
874
+
875
+ if sym.ExtractValueCompiled != nil {
876
+ sm := sym.ExtractValueCompiled.FindStringSubmatch(value)
877
+ if len(sm) < 2 {
878
+ return "", "", false, fmt.Errorf("extract_value did not match value %q", value)
879
+ }
880
+ value = sm[1]
881
+ }
882
+ if sym.MatchPatternCompiled != nil {
883
+ sm := sym.MatchPatternCompiled.FindStringSubmatch(value)
884
+ if len(sm) == 0 {
885
+ return "", "", false, fmt.Errorf("match_pattern %q did not match value %q", sym.MatchPattern, value)
886
+ }
887
+ value = replaceSubmatches(sym.MatchValue, sm)
888
+ }
889
+
890
+ return value, sourceOID, true, nil
891
+}
892
+
893
+func (c *Collector) bgpNumericValue(cfg ddprofiledefinition.BGPValueConfig, ctx bgpValueContext) (value int64, sourceOID string, ok bool, err error) {
894
+ if !cfg.IsSet() {
895
+ return 0, "", false, nil
896
+ }
897
+ sym := bgpValueSymbol(cfg)
898
+ if cfg.Value != "" || (sym.OID == "" && (cfg.Index != 0 || cfg.IndexFromEnd != 0 || len(cfg.IndexTransform) > 0)) {
899
+ text, _, ok, err := c.bgpRawTextValue(cfg, ctx)
900
+ if err != nil || !ok || text == "" {
901
+ return 0, "", false, err
902
+ }
903
+ value, err := strconv.ParseInt(strings.TrimSpace(text), 10, 64)
904
+ if err != nil {
905
+ return 0, "", false, err
906
+ }
907
+ return value, "", true, nil
908
+ }
909
+
910
+ if sym.OID == "" {
911
+ return 0, "", false, nil
912
+ }
913
+ pdu, sourceOID, ok, err := c.lookupBGPValuePDU(cfg, sym, ctx)
914
+ if err != nil || !ok {
915
+ return 0, "", false, err
916
+ }
917
+
918
+ value, err = c.scalarCollector.valProc.processValue(sym, pdu)
919
+ if err != nil {
920
+ if errors.Is(err, errNoTextDateValue) {
921
+ return 0, "", false, nil
922
+ }
923
+ return 0, "", false, err
924
+ }
925
+
926
+ return value, sourceOID, true, nil
927
+}
928
+
929
+func (c *Collector) lookupBGPValuePDU(cfg ddprofiledefinition.BGPValueConfig, sym ddprofiledefinition.SymbolConfig, ctx bgpValueContext) (gosnmp.SnmpPDU, string, bool, error) {
930
+ sourceOID := trimOID(sym.OID)
931
+ if cfg.Table == "" || cfg.Table == ctx.tableName {
932
+ pdu, ok := ctx.lookupPDU(sym.OID)
933
+ return pdu, sourceOID, ok, nil
934
+ }
935
+ if ctx.crossTableCtx == nil {
936
+ pdu, ok := ctx.lookupPDU(sym.OID)
937
+ return pdu, sourceOID, ok, nil
938
+ }
939
+
940
+ refTableOID, err := c.tableCollector.rowProcessor.crossTableResolver.findReferencedTableOID(cfg.Table, ctx.crossTableCtx.tableNameToOID)
941
+ if err != nil {
942
+ return gosnmp.SnmpPDU{}, "", false, err
943
+ }
944
+ refTablePDUs, err := c.tableCollector.rowProcessor.crossTableResolver.getReferencedTableData(cfg.Table, refTableOID, ctx.crossTableCtx.walkedData)
945
+ if err != nil {
946
+ return gosnmp.SnmpPDU{}, "", false, err
947
+ }
948
+ lookupIndex, err := c.tableCollector.rowProcessor.crossTableResolver.transformIndex(ctx.rowIndex, cfg.IndexTransform)
949
+ if err != nil {
950
+ return gosnmp.SnmpPDU{}, "", false, err
951
+ }
952
+ tagCfg := ddprofiledefinition.MetricTagConfig{
953
+ Table: cfg.Table,
954
+ Symbol: ddprofiledefinition.SymbolConfigCompat(sym),
955
+ LookupSymbol: cfg.LookupSymbol,
956
+ }
957
+ if c.tableCollector.rowProcessor.crossTableResolver.requiresLookupByValue(tagCfg) {
958
+ lookupIndex, err = c.tableCollector.rowProcessor.crossTableResolver.resolveLookupIndexByValue(tagCfg, lookupIndex, refTableOID, refTablePDUs, ctx.crossTableCtx)
959
+ if err != nil {
960
+ return gosnmp.SnmpPDU{}, "", false, err
961
+ }
962
+ }
963
+
964
+ fullOID := sourceOID + "." + lookupIndex
965
+ pdu, ok := refTablePDUs[fullOID]
966
+ if !ok {
967
+ return gosnmp.SnmpPDU{}, sourceOID, false, nil
968
+ }
969
+ if ctx.oidCache != nil {
970
+ ctx.oidCache[sourceOID] = fullOID
971
+ }
972
+ return pdu, sourceOID, true, nil
973
+}
974
+
975
+func (c *Collector) bgpIndexValue(cfg ddprofiledefinition.BGPValueConfig, rowIndex string) (string, error) {
976
+ if rowIndex == "" {
977
+ return "", nil
978
+ }
979
+ sym := bgpValueSymbol(cfg)
980
+ if cfg.IndexFromEnd != 0 {
981
+ rawValue, ok := c.tableCollector.rowProcessor.extractIndexPositionFromEnd(rowIndex, cfg.IndexFromEnd)
982
+ if !ok {
983
+ return "", fmt.Errorf("position from end %d not found", cfg.IndexFromEnd)
984
+ }
985
+ tagCfg := ddprofiledefinition.MetricTagConfig{
986
+ Symbol: ddprofiledefinition.SymbolConfigCompat(sym),
987
+ Mapping: sym.Mapping,
988
+ }
989
+ return processRawIndexTagValue(tagCfg, rawValue)
990
+ }
991
+ tagCfg := ddprofiledefinition.MetricTagConfig{
992
+ Index: cfg.Index,
993
+ IndexTransform: cfg.IndexTransform,
994
+ Symbol: ddprofiledefinition.SymbolConfigCompat(sym),
995
+ Mapping: sym.Mapping,
996
+ }
997
+ _, value, err := c.tableCollector.rowProcessor.processIndexTag(tagCfg, rowIndex)
998
+ return value, err
999
+}
1000
+
1001
+func bgpConfigAsMetricsConfig(cfg ddprofiledefinition.BGPConfig) ddprofiledefinition.MetricsConfig {
1002
+ columns := make(map[string]ddprofiledefinition.SymbolConfig)
1003
+ addColumn := func(valueCfg ddprofiledefinition.BGPValueConfig) {
1004
+ sym := bgpValueSymbol(valueCfg)
1005
+ if sym.OID != "" {
1006
+ columns[trimOID(sym.OID)] = sym
1007
+ }
1008
+ }
1009
+
1010
+ forEachBGPValue(cfg, addColumn)
1011
+ for _, tagCfg := range cfg.MetricTags {
1012
+ if tagCfg.Symbol.OID != "" {
1013
+ columns[trimOID(tagCfg.Symbol.OID)] = ddprofiledefinition.SymbolConfig(tagCfg.Symbol)
1014
+ }
1015
+ }
1016
+
1017
+ symbols := make([]ddprofiledefinition.SymbolConfig, 0, len(columns))
1018
+ for _, sym := range columns {
1019
+ symbols = append(symbols, sym)
1020
+ }
1021
+
1022
+ return ddprofiledefinition.MetricsConfig{
1023
+ MIB: bgpMetricsConfigMIB(cfg.MIB),
1024
+ Table: cfg.Table,
1025
+ Symbols: symbols,
1026
+ StaticTags: cfg.StaticTags,
1027
+ MetricTags: cfg.MetricTags,
1028
+ }
1029
+}
1030
+
1031
+func bgpMetricsConfigMIB(mib string) string {
1032
+ if mib == "" {
1033
+ return "bgp"
1034
+ }
1035
+ return "bgp:" + mib
1036
+}
1037
+
1038
+func bgpTableNameToOID(configs []ddprofiledefinition.BGPConfig) map[string]string {
1039
+ tableNameToOID := make(map[string]string)
1040
+ crossTableOIDs := make(map[string][]string)
1041
+
1042
+ for _, cfg := range configs {
1043
+ if cfg.Table.Name != "" && cfg.Table.OID != "" {
1044
+ tableNameToOID[cfg.Table.Name] = trimOID(cfg.Table.OID)
1045
+ }
1046
+ }
1047
+
1048
+ for _, cfg := range configs {
1049
+ if cfg.Table.OID == "" {
1050
+ continue
1051
+ }
1052
+ metricsCfg := bgpConfigAsMetricsConfig(cfg)
1053
+ for _, tagCfg := range metricsCfg.MetricTags {
1054
+ if tagCfg.Table == "" || tagCfg.Table == cfg.Table.Name {
1055
+ continue
1056
+ }
1057
+ if _, ok := tableNameToOID[tagCfg.Table]; ok {
1058
+ continue
1059
+ }
1060
+ if tagCfg.Symbol.OID != "" {
1061
+ crossTableOIDs[tagCfg.Table] = append(crossTableOIDs[tagCfg.Table], tagCfg.Symbol.OID)
1062
+ }
1063
+ if tagCfg.LookupSymbol.OID != "" {
1064
+ crossTableOIDs[tagCfg.Table] = append(crossTableOIDs[tagCfg.Table], tagCfg.LookupSymbol.OID)
1065
+ }
1066
+ }
1067
+ forEachBGPValue(cfg, func(valueCfg ddprofiledefinition.BGPValueConfig) {
1068
+ if valueCfg.Table == "" || valueCfg.Table == cfg.Table.Name {
1069
+ return
1070
+ }
1071
+ if _, ok := tableNameToOID[valueCfg.Table]; ok {
1072
+ return
1073
+ }
1074
+ if oid := bgpValueSymbol(valueCfg).OID; oid != "" {
1075
+ crossTableOIDs[valueCfg.Table] = append(crossTableOIDs[valueCfg.Table], oid)
1076
+ }
1077
+ if oid := ddprofiledefinition.SymbolConfig(valueCfg.LookupSymbol).OID; oid != "" {
1078
+ crossTableOIDs[valueCfg.Table] = append(crossTableOIDs[valueCfg.Table], oid)
1079
+ }
1080
+ })
1081
+ }
1082
+
1083
+ for tableName, oids := range crossTableOIDs {
1084
+ if oid := longestCommonOIDPrefix(oids); oid != "" {
1085
+ tableNameToOID[tableName] = oid
1086
+ }
1087
+ }
1088
+
1089
+ return tableNameToOID
1090
+}
1091
+
1092
+func bgpTableDependencies(cfg ddprofiledefinition.BGPConfig, metricsCfg ddprofiledefinition.MetricsConfig, tableNameToOID map[string]string) []string {
1093
+ deps := make(map[string]bool)
1094
+ for _, dep := range extractTableDependencies(metricsCfg, tableNameToOID) {
1095
+ deps[trimOID(dep)] = true
1096
+ }
1097
+ forEachBGPValue(cfg, func(valueCfg ddprofiledefinition.BGPValueConfig) {
1098
+ if valueCfg.Table == "" || valueCfg.Table == cfg.Table.Name {
1099
+ return
1100
+ }
1101
+ if tableOID, ok := tableNameToOID[valueCfg.Table]; ok {
1102
+ deps[trimOID(tableOID)] = true
1103
+ }
1104
+ })
1105
+
1106
+ result := make([]string, 0, len(deps))
1107
+ for oid := range deps {
1108
+ result = append(result, oid)
1109
+ }
1110
+ slices.Sort(result)
1111
+ return result
1112
+}
1113
+
1114
+func (c *Collector) bgpScalarOIDs(cfg ddprofiledefinition.BGPConfig) ([]string, []string) {
1115
+ oids := make(map[string]struct{})
1116
+ var missingOIDs []string
1117
+ addOID := func(valueCfg ddprofiledefinition.BGPValueConfig) {
1118
+ sym := bgpValueSymbol(valueCfg)
1119
+ if sym.OID != "" {
1120
+ oid := trimOID(sym.OID)
1121
+ if c.missingOIDs[oid] {
1122
+ missingOIDs = append(missingOIDs, sym.OID)
1123
+ return
1124
+ }
1125
+ oids[oid] = struct{}{}
1126
+ }
1127
+ }
1128
+
1129
+ forEachBGPValue(cfg, addOID)
1130
+ for _, tagCfg := range cfg.MetricTags {
1131
+ if tagCfg.Symbol.OID != "" {
1132
+ oid := trimOID(tagCfg.Symbol.OID)
1133
+ if c.missingOIDs[oid] {
1134
+ missingOIDs = append(missingOIDs, tagCfg.Symbol.OID)
1135
+ continue
1136
+ }
1137
+ oids[oid] = struct{}{}
1138
+ }
1139
+ }
1140
+
1141
+ result := make([]string, 0, len(oids))
1142
+ for oid := range oids {
1143
+ result = append(result, oid)
1144
+ }
1145
+ slices.Sort(result)
1146
+ slices.Sort(missingOIDs)
1147
+ missingOIDs = slices.Compact(missingOIDs)
1148
+ return result, missingOIDs
1149
+}
1150
+
1151
+func forEachBGPValue(cfg ddprofiledefinition.BGPConfig, fn func(ddprofiledefinition.BGPValueConfig)) {
1152
+ fn(cfg.Identity.RoutingInstance)
1153
+ fn(cfg.Identity.Neighbor)
1154
+ fn(cfg.Identity.RemoteAS)
1155
+ fn(cfg.Identity.AddressFamily.BGPValueConfig)
1156
+ fn(cfg.Identity.SubsequentAddressFamily.BGPValueConfig)
1157
+ fn(cfg.Descriptors.LocalAddress)
1158
+ fn(cfg.Descriptors.LocalAS)
1159
+ fn(cfg.Descriptors.LocalIdentifier)
1160
+ fn(cfg.Descriptors.PeerIdentifier)
1161
+ fn(cfg.Descriptors.PeerType)
1162
+ fn(cfg.Descriptors.BGPVersion)
1163
+ fn(cfg.Descriptors.Description)
1164
+ ddprofiledefinition.ForEachBGPSignalValue(cfg, func(_ string, value ddprofiledefinition.BGPValueConfig) {
1165
+ fn(value)
1166
+ })
1167
+}
1168
+
1169
+func bgpValueSymbol(cfg ddprofiledefinition.BGPValueConfig) ddprofiledefinition.SymbolConfig {
1170
+ sym := cfg.Symbol
1171
+ if sym.OID == "" {
1172
+ switch {
1173
+ case cfg.From != "":
1174
+ sym.OID = cfg.From
1175
+ case cfg.OID != "":
1176
+ sym.OID = cfg.OID
1177
+ }
1178
+ }
1179
+ if sym.Name == "" {
1180
+ sym.Name = cfg.Name
1181
+ }
1182
+ if sym.Name == "" && sym.OID != "" {
1183
+ sym.Name = "bgp:" + trimOID(sym.OID)
1184
+ }
1185
+ if sym.Format == "" {
1186
+ sym.Format = cfg.Format
1187
+ }
1188
+ if !sym.Mapping.HasItems() && cfg.Mapping.HasItems() {
1189
+ sym.Mapping = cfg.Mapping
1190
+ }
1191
+ return sym
1192
+}
1193
+
1194
+func scalarBGPRowKey(cfg ddprofiledefinition.BGPConfig) string {
1195
+ if cfg.ID != "" {
1196
+ return cfg.ID
1197
+ }
1198
+ rowKey := ""
1199
+ forEachBGPValue(cfg, func(valueCfg ddprofiledefinition.BGPValueConfig) {
1200
+ if rowKey != "" {
1201
+ return
1202
+ }
1203
+ if oid := bgpValueSymbol(valueCfg).OID; oid != "" {
1204
+ rowKey = trimOID(oid)
1205
+ }
1206
+ })
1207
+ if rowKey != "" {
1208
+ return rowKey
1209
+ }
1210
+ return cfg.OriginProfileID
1211
+}
1212
+
1213
+func bgpConfigDisplayName(cfg ddprofiledefinition.BGPConfig) string {
1214
+ if cfg.ID != "" {
1215
+ return cfg.ID
1216
+ }
1217
+ if cfg.Table.Name != "" {
1218
+ return cfg.Table.Name
1219
+ }
1220
+ if cfg.Table.OID != "" {
1221
+ return cfg.Table.OID
1222
+ }
1223
+ return cfg.OriginProfileID
1224
+}
1225
+
1226
+func bgpRowHasSignals(row ddsnmp.BGPRow) bool {
1227
+ return row.State.Has ||
1228
+ row.Previous.Has ||
1229
+ row.Admin.Enabled.Has ||
1230
+ bgpConnectionHasSignals(row.Connection) ||
1231
+ bgpTrafficHasSignals(row.Traffic) ||
1232
+ bgpTransitionsHasSignals(row.Transitions) ||
1233
+ bgpTimersHasSignals(row.Timers) ||
1234
+ row.LastError.Code.Has ||
1235
+ row.LastError.Subcode.Has ||
1236
+ bgpLastNotificationsHasSignals(row.LastNotify) ||
1237
+ row.Reasons.LastDown.Has ||
1238
+ row.Reasons.Unavailability.Has ||
1239
+ row.Restart.State.Has ||
1240
+ bgpRoutesHasSignals(row.Routes) ||
1241
+ row.RouteLimits.Limit.Has ||
1242
+ row.RouteLimits.Threshold.Has ||
1243
+ row.RouteLimits.ClearThreshold.Has ||
1244
+ row.Device.Peers.Has ||
1245
+ row.Device.InternalPeers.Has ||
1246
+ row.Device.ExternalPeers.Has ||
1247
+ row.Device.ByStateHas
1248
+}
1249
+
1250
+func bgpRowIdentityComplete(row ddsnmp.BGPRow) bool {
1251
+ switch row.Kind {
1252
+ case ddprofiledefinition.BGPRowKindPeer:
1253
+ return row.Identity.Neighbor != "" && row.Identity.RemoteAS != ""
1254
+ case ddprofiledefinition.BGPRowKindPeerFamily:
1255
+ return row.Identity.Neighbor != "" &&
1256
+ row.Identity.RemoteAS != "" &&
1257
+ row.Identity.AddressFamily != "" &&
1258
+ row.Identity.SubsequentAddressFamily != ""
1259
+ default:
1260
+ return true
1261
+ }
1262
+}
1263
+
1264
+func bgpConnectionHasSignals(v ddsnmp.BGPConnection) bool {
1265
+ return v.EstablishedUptime.Has || v.LastReceivedUpdateAge.Has
1266
+}
1267
+
1268
+func bgpDirectionalHasSignals(v ddsnmp.BGPDirectional) bool {
1269
+ return v.Received.Has || v.Sent.Has
1270
+}
1271
+
1272
+func bgpTrafficHasSignals(v ddsnmp.BGPTraffic) bool {
1273
+ return bgpDirectionalHasSignals(v.Messages) ||
1274
+ bgpDirectionalHasSignals(v.Updates) ||
1275
+ bgpDirectionalHasSignals(v.Notifications) ||
1276
+ bgpDirectionalHasSignals(v.RouteRefreshes) ||
1277
+ bgpDirectionalHasSignals(v.Opens) ||
1278
+ bgpDirectionalHasSignals(v.Keepalives)
1279
+}
1280
+
1281
+func bgpTransitionsHasSignals(v ddsnmp.BGPTransitions) bool {
1282
+ return v.Established.Has || v.Down.Has || v.Up.Has || v.Flaps.Has
1283
+}
1284
+
1285
+func bgpTimerPairHasSignals(v ddsnmp.BGPTimerPair) bool {
1286
+ return v.ConnectRetry.Has ||
1287
+ v.HoldTime.Has ||
1288
+ v.KeepaliveTime.Has ||
1289
+ v.MinASOriginationInterval.Has ||
1290
+ v.MinRouteAdvertisementInterval.Has
1291
+}
1292
+
1293
+func bgpTimersHasSignals(v ddsnmp.BGPTimers) bool {
1294
+ return bgpTimerPairHasSignals(v.Negotiated) || bgpTimerPairHasSignals(v.Configured)
1295
+}
1296
+
1297
+func bgpLastNotificationHasSignals(v ddsnmp.BGPLastNotification) bool {
1298
+ return v.Code.Has || v.Subcode.Has || v.Reason.Has
1299
+}
1300
+
1301
+func bgpLastNotificationsHasSignals(v ddsnmp.BGPLastNotifications) bool {
1302
+ return bgpLastNotificationHasSignals(v.Received) || bgpLastNotificationHasSignals(v.Sent)
1303
+}
1304
+
1305
+func bgpRouteCountersHasSignals(v ddsnmp.BGPRouteCounters) bool {
1306
+ return v.Received.Has ||
1307
+ v.Accepted.Has ||
1308
+ v.Rejected.Has ||
1309
+ v.Active.Has ||
1310
+ v.Advertised.Has ||
1311
+ v.Suppressed.Has ||
1312
+ v.Withdrawn.Has
1313
+}
1314
+
1315
+func bgpRoutesHasSignals(v ddsnmp.BGPRoutes) bool {
1316
+ return bgpRouteCountersHasSignals(v.Current) || bgpRouteCountersHasSignals(v.Total)
1317
+}
1318
+
1319
+func bgpScalarStructuralID(originProfileID string, kind ddprofiledefinition.BGPRowKind, rowKey string) string {
1320
+ return lengthPrefixedKey(originProfileID, string(kind), "scalar", rowKey)
1321
+}
1322
+
1323
+func bgpTableStructuralID(originProfileID string, kind ddprofiledefinition.BGPRowKind, configID, tableOID, rowKey string) string {
1324
+ return lengthPrefixedKey(originProfileID, string(kind), "table", configID, tableOID, rowKey)
1325
+}
1326
+
1327
+func lengthPrefixedKey(parts ...string) string {
1328
+ var sb strings.Builder
1329
+ for _, part := range parts {
1330
+ if sb.Len() > 0 {
1331
+ sb.WriteByte('|')
1332
+ }
1333
+ sb.WriteString(strconv.Itoa(len(part)))
1334
+ sb.WriteByte(':')
1335
+ sb.WriteString(part)
1336
+ }
1337
+ return sb.String()
1338
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_bgp_profile_test.go
new
+135
@@ -0,0 +1,135 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "testing"
9
+
10
+ "github.com/gosnmp/gosnmp"
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/logger"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
17
+)
18
+
19
+func TestCollector_Collect_StdBGP4Profile(t *testing.T) {
20
+ tests := map[string]struct {
21
+ fixture string
22
+ }{
23
+ "generic BGP4-MIB peer table": {
24
+ fixture: "librenms/pfsense_frr_bgp_peer_table.snmprec",
25
+ },
26
+ }
27
+
28
+ for name, tc := range tests {
29
+ t.Run(name, func(t *testing.T) {
30
+ profile := mustLoadTypedBGPProfile(t, "_std-bgp4-mib", func(row ddprofiledefinition.BGPConfig) bool {
31
+ return row.ID == "bgp4-peer"
32
+ })
33
+
34
+ pdus := loadSnmprecPDUs(t, tc.fixture)
35
+ pdus = append(pdus,
36
+ createPDU("1.3.6.1.2.1.15.3.1.7.169.254.1.1", gosnmp.IPAddress, "169.254.1.1"),
37
+ createPDU("1.3.6.1.2.1.15.3.1.7.169.254.1.9", gosnmp.IPAddress, "169.254.1.9"),
38
+ )
39
+
40
+ ctrl, mockHandler := setupMockHandler(t)
41
+ defer ctrl.Finish()
42
+
43
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.2.1.15.3", pdus)
44
+
45
+ collector := New(Config{
46
+ SnmpClient: mockHandler,
47
+ Profiles: []*ddsnmp.Profile{profile},
48
+ Log: logger.New(),
49
+ })
50
+
51
+ results, err := collector.Collect()
52
+ require.NoError(t, err)
53
+ require.Len(t, results, 1)
54
+
55
+ pm := results[0]
56
+ require.Empty(t, pm.HiddenMetrics)
57
+ require.Empty(t, pm.Metrics)
58
+ require.Empty(t, pm.TopologyMetrics)
59
+ require.Empty(t, pm.LicenseRows)
60
+ require.Len(t, pm.BGPRows, 2)
61
+ assert.EqualValues(t, 2, pm.Stats.Metrics.BGP)
62
+
63
+ rows := bgpRowsByNeighbor(pm.BGPRows)
64
+ row := rows["169.254.1.1"]
65
+ require.NotZero(t, row)
66
+ assert.Equal(t, "_std-bgp4-mib.yaml", row.OriginProfileID)
67
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
68
+ assert.Equal(t, "1.3.6.1.2.1.15.3", row.TableOID)
69
+ assert.Equal(t, "bgpPeerTable", row.Table)
70
+ assert.Equal(t, "169.254.1.1", row.RowKey)
71
+ assert.Equal(t, "default", row.Identity.RoutingInstance)
72
+ assert.Equal(t, "169.254.1.1", row.Identity.Neighbor)
73
+ assert.Equal(t, "4200000000", row.Identity.RemoteAS)
74
+ assert.Equal(t, "169.254.1.2", row.Descriptors.LocalAddress)
75
+ assert.True(t, row.Admin.Enabled.Has)
76
+ assert.True(t, row.Admin.Enabled.Value)
77
+ require.True(t, row.State.Has)
78
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
79
+ assert.EqualValues(t, 96951, row.Connection.EstablishedUptime.Value)
80
+ assert.EqualValues(t, 96950, row.Connection.LastReceivedUpdateAge.Value)
81
+ assert.EqualValues(t, 8330, row.Traffic.Messages.Received.Value)
82
+ assert.EqualValues(t, 8323, row.Traffic.Messages.Sent.Value)
83
+ assert.EqualValues(t, 6, row.Traffic.Updates.Received.Value)
84
+ assert.EqualValues(t, 14, row.Traffic.Updates.Sent.Value)
85
+ assert.EqualValues(t, 4, row.LastError.Code.Value)
86
+ assert.EqualValues(t, 0, row.LastError.Subcode.Value)
87
+ })
88
+ }
89
+}
90
+
91
+func mustLoadTypedBGPProfile(t *testing.T, profileName string, keep func(row ddprofiledefinition.BGPConfig) bool) *ddsnmp.Profile {
92
+ t.Helper()
93
+
94
+ profile, err := ddsnmp.LoadProfileByName(profileName)
95
+ require.NoError(t, err)
96
+
97
+ filterProfileForTypedBGP(t, profile, keep)
98
+ assert.Equal(t, profileName+".yaml", filepath.Base(profile.SourceFile))
99
+
100
+ return profile
101
+}
102
+
103
+func filterProfileForTypedBGPByID(t *testing.T, profile *ddsnmp.Profile, id string) {
104
+ t.Helper()
105
+
106
+ filterProfileForTypedBGP(t, profile, func(row ddprofiledefinition.BGPConfig) bool {
107
+ return row.ID == id
108
+ })
109
+}
110
+
111
+func filterProfileForTypedBGP(t *testing.T, profile *ddsnmp.Profile, keep func(row ddprofiledefinition.BGPConfig) bool) {
112
+ t.Helper()
113
+
114
+ profile.Definition.Metadata = nil
115
+ profile.Definition.SysobjectIDMetadata = nil
116
+ profile.Definition.MetricTags = nil
117
+ profile.Definition.StaticTags = nil
118
+ profile.Definition.VirtualMetrics = nil
119
+ profile.Definition.Topology = nil
120
+ profile.Definition.Metrics = nil
121
+ profile.Definition.Licensing = nil
122
+ profile.Definition.BGP = slices.DeleteFunc(profile.Definition.BGP, func(row ddprofiledefinition.BGPConfig) bool {
123
+ return !keep(row)
124
+ })
125
+
126
+ require.NotEmpty(t, profile.Definition.BGP)
127
+}
128
+
129
+func bgpRowsByNeighbor(rows []ddsnmp.BGPRow) map[string]ddsnmp.BGPRow {
130
+ out := make(map[string]ddsnmp.BGPRow, len(rows))
131
+ for _, row := range rows {
132
+ out[row.Identity.Neighbor] = row
133
+ }
134
+ return out
135
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_bgp_test.go
new
+612
@@ -0,0 +1,612 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_BGPRowsFromScalarBGPConfig(t *testing.T) {
18
+ tests := map[string]struct {
19
+ statePDU gosnmp.SnmpPDU
20
+ uptimePDU gosnmp.SnmpPDU
21
+ }{
22
+ "scalar peer row with state and signal": {
23
+ statePDU: createIntegerPDU("1.3.6.1.4.1.99999.20.1.0", 6),
24
+ uptimePDU: createGauge32PDU("1.3.6.1.4.1.99999.20.2.0", 3600),
25
+ },
26
+ }
27
+
28
+ for name, tc := range tests {
29
+ t.Run(name, func(t *testing.T) {
30
+ ctrl, mockHandler := setupMockHandler(t)
31
+ defer ctrl.Finish()
32
+
33
+ expectSNMPGet(mockHandler,
34
+ []string{
35
+ "1.3.6.1.4.1.99999.20.1.0",
36
+ "1.3.6.1.4.1.99999.20.2.0",
37
+ },
38
+ []gosnmp.SnmpPDU{tc.statePDU, tc.uptimePDU},
39
+ )
40
+
41
+ collector := New(Config{
42
+ SnmpClient: mockHandler,
43
+ Profiles: []*ddsnmp.Profile{
44
+ {
45
+ SourceFile: "vendor-device.yaml",
46
+ Definition: &ddprofiledefinition.ProfileDefinition{
47
+ BGP: []ddprofiledefinition.BGPConfig{
48
+ scalarBGPTestConfig(),
49
+ },
50
+ },
51
+ },
52
+ },
53
+ Log: logger.New(),
54
+ })
55
+
56
+ results, err := collector.Collect()
57
+ require.NoError(t, err)
58
+ require.Len(t, results, 1)
59
+
60
+ pm := results[0]
61
+ require.Empty(t, pm.HiddenMetrics)
62
+ require.Empty(t, pm.Metrics)
63
+ require.Empty(t, pm.TopologyMetrics)
64
+ require.Empty(t, pm.LicenseRows)
65
+ require.Len(t, pm.BGPRows, 1)
66
+
67
+ row := pm.BGPRows[0]
68
+ assert.Equal(t, "_vendor-bgp.yaml", row.OriginProfileID)
69
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
70
+ assert.Equal(t, "scalar-peer", row.RowKey)
71
+ assert.Equal(t, lengthPrefixedKey("_vendor-bgp.yaml", "peer", "scalar", "scalar-peer"), row.StructuralID)
72
+ assert.Equal(t, "default", row.Identity.RoutingInstance)
73
+ assert.Equal(t, "192.0.2.1", row.Identity.Neighbor)
74
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
75
+ assert.True(t, row.State.Has)
76
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
77
+ assert.Equal(t, "6", row.State.Raw)
78
+ assert.Equal(t, "1.3.6.1.4.1.99999.20.1.0", row.State.SourceOID)
79
+ require.True(t, row.Connection.EstablishedUptime.Has)
80
+ assert.EqualValues(t, 3600, row.Connection.EstablishedUptime.Value)
81
+ assert.Equal(t, "1.3.6.1.4.1.99999.20.2.0", row.Connection.EstablishedUptime.SourceOID)
82
+ assert.Equal(t, map[string]string{"routing_protocol": "bgp"}, row.Tags)
83
+ assert.EqualValues(t, 1, pm.Stats.Metrics.BGP)
84
+ })
85
+ }
86
+}
87
+
88
+func TestCollector_Collect_BGPRowsFromTableBGPConfig(t *testing.T) {
89
+ tests := map[string]struct {
90
+ pdus []gosnmp.SnmpPDU
91
+ }{
92
+ "table peer row with index identity": {
93
+ pdus: []gosnmp.SnmpPDU{
94
+ createIntegerPDU("1.3.6.1.4.1.99999.30.1.2.42", 6),
95
+ createGauge32PDU("1.3.6.1.4.1.99999.30.1.3.42", 65001),
96
+ createGauge32PDU("1.3.6.1.4.1.99999.30.1.4.42", 7200),
97
+ },
98
+ },
99
+ }
100
+
101
+ for name, tc := range tests {
102
+ t.Run(name, func(t *testing.T) {
103
+ ctrl, mockHandler := setupMockHandler(t)
104
+ defer ctrl.Finish()
105
+
106
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.99999.30.1", tc.pdus)
107
+
108
+ collector := New(Config{
109
+ SnmpClient: mockHandler,
110
+ Profiles: []*ddsnmp.Profile{
111
+ {
112
+ SourceFile: "vendor-device.yaml",
113
+ Definition: &ddprofiledefinition.ProfileDefinition{
114
+ BGP: []ddprofiledefinition.BGPConfig{
115
+ tableBGPTestConfig(),
116
+ },
117
+ },
118
+ },
119
+ },
120
+ Log: logger.New(),
121
+ })
122
+
123
+ results, err := collector.Collect()
124
+ require.NoError(t, err)
125
+ require.Len(t, results, 1)
126
+
127
+ pm := results[0]
128
+ require.Len(t, pm.BGPRows, 1)
129
+
130
+ row := pm.BGPRows[0]
131
+ assert.Equal(t, "_vendor-bgp.yaml", row.OriginProfileID)
132
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
133
+ assert.Equal(t, "1.3.6.1.4.1.99999.30.1", row.TableOID)
134
+ assert.Equal(t, "vendorBgpPeerTable", row.Table)
135
+ assert.Equal(t, "42", row.RowKey)
136
+ assert.Equal(t, lengthPrefixedKey("_vendor-bgp.yaml", "peer", "table", "table-peer", "1.3.6.1.4.1.99999.30.1", "42"), row.StructuralID)
137
+ assert.Equal(t, "42", row.Identity.Neighbor)
138
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
139
+ assert.Equal(t, "blue", row.Tags["routing_instance"])
140
+ assert.True(t, row.State.Has)
141
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
142
+ require.True(t, row.Connection.EstablishedUptime.Has)
143
+ assert.EqualValues(t, 7200, row.Connection.EstablishedUptime.Value)
144
+ assert.EqualValues(t, 1, pm.Stats.Metrics.BGP)
145
+ assert.EqualValues(t, 1, pm.Stats.TableCache.Misses)
146
+ assert.EqualValues(t, 1, pm.Stats.SNMP.TablesWalked)
147
+ })
148
+ }
149
+}
150
+
151
+func TestCollector_Collect_BGPRowsWithCrossTableBGPValues(t *testing.T) {
152
+ tests := map[string]struct {
153
+ mainPDUs []gosnmp.SnmpPDU
154
+ peerPDUs []gosnmp.SnmpPDU
155
+ expectedRowKey string
156
+ expectedNeighbor string
157
+ expectedAFI ddprofiledefinition.BGPAddressFamily
158
+ expectedSAFI ddprofiledefinition.BGPSubsequentAddressFamily
159
+ expectedPeerType string
160
+ }{
161
+ "ipv4 peer family row resolves identity from peer table": {
162
+ mainPDUs: []gosnmp.SnmpPDU{
163
+ createGauge32PDU("1.3.6.1.4.1.99999.70.1.1.4.192.0.2.1.1.1", 42),
164
+ },
165
+ peerPDUs: []gosnmp.SnmpPDU{
166
+ createGauge32PDU("1.3.6.1.4.1.99999.70.2.1.4.192.0.2.1", 65001),
167
+ createStringPDU("1.3.6.1.4.1.99999.70.2.2.4.192.0.2.1", "blue"),
168
+ },
169
+ expectedRowKey: "4.192.0.2.1.1.1",
170
+ expectedNeighbor: "192.0.2.1",
171
+ expectedAFI: ddprofiledefinition.BGPAddressFamilyIPv4,
172
+ expectedSAFI: ddprofiledefinition.BGPSubsequentAddressFamilyUnicast,
173
+ expectedPeerType: "01",
174
+ },
175
+ "ipv6 peer family row resolves AFI and SAFI from the tail of a variable-length row index": {
176
+ mainPDUs: []gosnmp.SnmpPDU{
177
+ createGauge32PDU("1.3.6.1.4.1.99999.70.1.1.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.2.1", 42),
178
+ },
179
+ peerPDUs: []gosnmp.SnmpPDU{
180
+ createGauge32PDU("1.3.6.1.4.1.99999.70.2.1.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1", 65001),
181
+ createStringPDU("1.3.6.1.4.1.99999.70.2.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1", "blue"),
182
+ },
183
+ expectedRowKey: "16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.2.1",
184
+ expectedNeighbor: "2001:db8::1",
185
+ expectedAFI: ddprofiledefinition.BGPAddressFamilyIPv6,
186
+ expectedSAFI: ddprofiledefinition.BGPSubsequentAddressFamilyUnicast,
187
+ expectedPeerType: "02",
188
+ },
189
+ }
190
+
191
+ for name, tc := range tests {
192
+ t.Run(name, func(t *testing.T) {
193
+ ctrl, mockHandler := setupMockHandler(t)
194
+ defer ctrl.Finish()
195
+
196
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.99999.70.1", tc.mainPDUs)
197
+ expectSNMPWalk(mockHandler, gosnmp.Version2c, "1.3.6.1.4.1.99999.70.2", tc.peerPDUs)
198
+
199
+ collector := New(Config{
200
+ SnmpClient: mockHandler,
201
+ Profiles: []*ddsnmp.Profile{
202
+ {
203
+ SourceFile: "vendor-device.yaml",
204
+ Definition: &ddprofiledefinition.ProfileDefinition{
205
+ BGP: []ddprofiledefinition.BGPConfig{
206
+ crossTableBGPTestConfig(),
207
+ },
208
+ },
209
+ },
210
+ },
211
+ Log: logger.New(),
212
+ })
213
+
214
+ results, err := collector.Collect()
215
+ require.NoError(t, err)
216
+ require.Len(t, results, 1)
217
+ require.Len(t, results[0].BGPRows, 1)
218
+
219
+ row := results[0].BGPRows[0]
220
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, row.Kind)
221
+ assert.Equal(t, tc.expectedRowKey, row.RowKey)
222
+ assert.Equal(t, tc.expectedNeighbor, row.Identity.Neighbor)
223
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
224
+ assert.Equal(t, tc.expectedAFI, row.Identity.AddressFamily)
225
+ assert.Equal(t, tc.expectedSAFI, row.Identity.SubsequentAddressFamily)
226
+ assert.Equal(t, "blue", row.Identity.RoutingInstance)
227
+ assert.Equal(t, tc.expectedPeerType, row.Descriptors.PeerType)
228
+ require.True(t, row.Routes.Current.Accepted.Has)
229
+ assert.EqualValues(t, 42, row.Routes.Current.Accepted.Value)
230
+ assert.Equal(t, "1.3.6.1.4.1.99999.70.1.1", row.Routes.Current.Accepted.SourceOID)
231
+ assert.EqualValues(t, 1, results[0].Stats.Metrics.BGP)
232
+ assert.EqualValues(t, 2, results[0].Stats.SNMP.TablesWalked)
233
+ })
234
+ }
235
+}
236
+
237
+func TestCollector_Collect_BGPRowsFromCategoricalBGPFields(t *testing.T) {
238
+ tests := map[string]struct {
239
+ reasonPDU gosnmp.SnmpPDU
240
+ }{
241
+ "mapped last down reason is emitted as text": {
242
+ reasonPDU: createIntegerPDU("1.3.6.1.4.1.99999.60.1.0", 2),
243
+ },
244
+ }
245
+
246
+ for name, tc := range tests {
247
+ t.Run(name, func(t *testing.T) {
248
+ ctrl, mockHandler := setupMockHandler(t)
249
+ defer ctrl.Finish()
250
+
251
+ expectSNMPGet(mockHandler,
252
+ []string{"1.3.6.1.4.1.99999.60.1.0"},
253
+ []gosnmp.SnmpPDU{tc.reasonPDU},
254
+ )
255
+
256
+ collector := New(Config{
257
+ SnmpClient: mockHandler,
258
+ Profiles: []*ddsnmp.Profile{
259
+ {
260
+ SourceFile: "vendor-device.yaml",
261
+ Definition: &ddprofiledefinition.ProfileDefinition{
262
+ BGP: []ddprofiledefinition.BGPConfig{
263
+ {
264
+ OriginProfileID: "_vendor-bgp.yaml",
265
+ ID: "categorical-peer",
266
+ Kind: ddprofiledefinition.BGPRowKindPeer,
267
+ Identity: ddprofiledefinition.BGPIdentityConfig{
268
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
269
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
270
+ },
271
+ Reasons: ddprofiledefinition.BGPReasonsConfig{
272
+ LastDown: ddprofiledefinition.BGPValueConfig{
273
+ Symbol: ddprofiledefinition.SymbolConfig{
274
+ OID: "1.3.6.1.4.1.99999.60.1.0",
275
+ Name: "bgpPeerLastDownReason",
276
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
277
+ "2": "hold_timer_expired",
278
+ }),
279
+ },
280
+ },
281
+ },
282
+ },
283
+ },
284
+ },
285
+ },
286
+ },
287
+ Log: logger.New(),
288
+ })
289
+
290
+ results, err := collector.Collect()
291
+ require.NoError(t, err)
292
+ require.Len(t, results, 1)
293
+ require.Len(t, results[0].BGPRows, 1)
294
+
295
+ row := results[0].BGPRows[0]
296
+ require.True(t, row.Reasons.LastDown.Has)
297
+ assert.Equal(t, "hold_timer_expired", row.Reasons.LastDown.Value)
298
+ assert.Equal(t, "2", row.Reasons.LastDown.Raw)
299
+ assert.Equal(t, "1.3.6.1.4.1.99999.60.1.0", row.Reasons.LastDown.SourceOID)
300
+ })
301
+ }
302
+}
303
+
304
+func TestCollector_Collect_BGPRowsBestEffortForRegularMetrics(t *testing.T) {
305
+ tests := map[string]struct {
306
+ bgpPDU gosnmp.SnmpPDU
307
+ }{
308
+ "invalid BGP signal does not drop scalar metric": {
309
+ bgpPDU: createStringPDU("1.3.6.1.4.1.99999.40.1.0", "not-a-number"),
310
+ },
311
+ }
312
+
313
+ for name, tc := range tests {
314
+ t.Run(name, func(t *testing.T) {
315
+ ctrl, mockHandler := setupMockHandler(t)
316
+ defer ctrl.Finish()
317
+
318
+ expectSNMPGet(mockHandler,
319
+ []string{"1.3.6.1.2.1.1.3.0"},
320
+ []gosnmp.SnmpPDU{createTimeTicksPDU("1.3.6.1.2.1.1.3.0", 123456)},
321
+ )
322
+ expectSNMPGet(mockHandler,
323
+ []string{"1.3.6.1.4.1.99999.40.1.0"},
324
+ []gosnmp.SnmpPDU{tc.bgpPDU},
325
+ )
326
+
327
+ collector := New(Config{
328
+ SnmpClient: mockHandler,
329
+ Profiles: []*ddsnmp.Profile{
330
+ {
331
+ SourceFile: "vendor-device.yaml",
332
+ Definition: &ddprofiledefinition.ProfileDefinition{
333
+ Metrics: []ddprofiledefinition.MetricsConfig{
334
+ {
335
+ Symbol: ddprofiledefinition.SymbolConfig{
336
+ OID: "1.3.6.1.2.1.1.3.0",
337
+ Name: "sysUpTime",
338
+ },
339
+ },
340
+ },
341
+ BGP: []ddprofiledefinition.BGPConfig{
342
+ {
343
+ OriginProfileID: "_vendor-bgp.yaml",
344
+ ID: "bad-bgp-row",
345
+ Kind: ddprofiledefinition.BGPRowKindPeer,
346
+ Identity: ddprofiledefinition.BGPIdentityConfig{
347
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
348
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
349
+ },
350
+ Connection: ddprofiledefinition.BGPConnectionConfig{
351
+ EstablishedUptime: ddprofiledefinition.BGPValueConfig{
352
+ Symbol: ddprofiledefinition.SymbolConfig{
353
+ OID: "1.3.6.1.4.1.99999.40.1.0",
354
+ Name: "bgpPeerEstablishedTime",
355
+ },
356
+ },
357
+ },
358
+ },
359
+ },
360
+ },
361
+ },
362
+ },
363
+ Log: logger.New(),
364
+ })
365
+
366
+ results, err := collector.Collect()
367
+ require.NoError(t, err)
368
+ require.Len(t, results, 1)
369
+ require.Len(t, results[0].Metrics, 1)
370
+ assert.Equal(t, "sysUpTime", results[0].Metrics[0].Name)
371
+ assert.Empty(t, results[0].BGPRows)
372
+ assert.EqualValues(t, 1, results[0].Stats.Errors.Processing.BGP)
373
+ })
374
+ }
375
+}
376
+
377
+func TestCollector_Collect_BGPRowsSkipsKnownMissingScalarOIDs(t *testing.T) {
378
+ tests := map[string]struct {
379
+ missingPDU gosnmp.SnmpPDU
380
+ }{
381
+ "no such object cached after first BGP poll": {
382
+ missingPDU: createNoSuchObjectPDU("1.3.6.1.4.1.99999.50.1.0"),
383
+ },
384
+ }
385
+
386
+ for name, tc := range tests {
387
+ t.Run(name, func(t *testing.T) {
388
+ ctrl, mockHandler := setupMockHandler(t)
389
+ defer ctrl.Finish()
390
+
391
+ expectSNMPGet(mockHandler,
392
+ []string{"1.3.6.1.2.1.1.3.0"},
393
+ []gosnmp.SnmpPDU{createTimeTicksPDU("1.3.6.1.2.1.1.3.0", 123456)},
394
+ )
395
+ expectSNMPGet(mockHandler,
396
+ []string{"1.3.6.1.4.1.99999.50.1.0"},
397
+ []gosnmp.SnmpPDU{tc.missingPDU},
398
+ )
399
+ expectSNMPGet(mockHandler,
400
+ []string{"1.3.6.1.2.1.1.3.0"},
401
+ []gosnmp.SnmpPDU{createTimeTicksPDU("1.3.6.1.2.1.1.3.0", 223456)},
402
+ )
403
+
404
+ collector := New(Config{
405
+ SnmpClient: mockHandler,
406
+ Profiles: []*ddsnmp.Profile{
407
+ {
408
+ SourceFile: "vendor-device.yaml",
409
+ Definition: &ddprofiledefinition.ProfileDefinition{
410
+ Metrics: []ddprofiledefinition.MetricsConfig{
411
+ {
412
+ Symbol: ddprofiledefinition.SymbolConfig{
413
+ OID: "1.3.6.1.2.1.1.3.0",
414
+ Name: "sysUpTime",
415
+ },
416
+ },
417
+ },
418
+ BGP: []ddprofiledefinition.BGPConfig{
419
+ {
420
+ OriginProfileID: "_vendor-bgp.yaml",
421
+ ID: "missing-bgp-row",
422
+ Kind: ddprofiledefinition.BGPRowKindPeer,
423
+ Identity: ddprofiledefinition.BGPIdentityConfig{
424
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
425
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
426
+ },
427
+ Connection: ddprofiledefinition.BGPConnectionConfig{
428
+ EstablishedUptime: ddprofiledefinition.BGPValueConfig{
429
+ Symbol: ddprofiledefinition.SymbolConfig{
430
+ OID: "1.3.6.1.4.1.99999.50.1.0",
431
+ Name: "bgpPeerEstablishedTime",
432
+ },
433
+ },
434
+ },
435
+ },
436
+ },
437
+ },
438
+ },
439
+ },
440
+ Log: logger.New(),
441
+ })
442
+
443
+ first, err := collector.Collect()
444
+ require.NoError(t, err)
445
+ require.Len(t, first, 1)
446
+ assert.Empty(t, first[0].BGPRows)
447
+ assert.EqualValues(t, 1, first[0].Stats.Errors.MissingOIDs)
448
+
449
+ second, err := collector.Collect()
450
+ require.NoError(t, err)
451
+ require.Len(t, second, 1)
452
+ assert.Empty(t, second[0].BGPRows)
453
+ assert.EqualValues(t, 1, second[0].Stats.Errors.MissingOIDs)
454
+ assert.EqualValues(t, 1, second[0].Stats.SNMP.GetRequests)
455
+ assert.EqualValues(t, 1, second[0].Stats.SNMP.GetOIDs)
456
+ })
457
+ }
458
+}
459
+
460
+func scalarBGPTestConfig() ddprofiledefinition.BGPConfig {
461
+ return ddprofiledefinition.BGPConfig{
462
+ OriginProfileID: "_vendor-bgp.yaml",
463
+ ID: "scalar-peer",
464
+ Kind: ddprofiledefinition.BGPRowKindPeer,
465
+ Identity: ddprofiledefinition.BGPIdentityConfig{
466
+ RoutingInstance: ddprofiledefinition.BGPValueConfig{Value: "default"},
467
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
468
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
469
+ },
470
+ State: ddprofiledefinition.BGPStateConfig{
471
+ BGPValueConfig: ddprofiledefinition.BGPValueConfig{
472
+ Symbol: ddprofiledefinition.SymbolConfig{
473
+ OID: "1.3.6.1.4.1.99999.20.1.0",
474
+ Name: "bgpPeerState",
475
+ Mapping: bgpPeerStateMapping(),
476
+ },
477
+ },
478
+ },
479
+ Connection: ddprofiledefinition.BGPConnectionConfig{
480
+ EstablishedUptime: ddprofiledefinition.BGPValueConfig{
481
+ Symbol: ddprofiledefinition.SymbolConfig{
482
+ OID: "1.3.6.1.4.1.99999.20.2.0",
483
+ Name: "bgpPeerEstablishedTime",
484
+ },
485
+ },
486
+ },
487
+ StaticTags: []ddprofiledefinition.StaticMetricTagConfig{
488
+ {Tag: "routing_protocol", Value: "bgp"},
489
+ },
490
+ }
491
+}
492
+
493
+func tableBGPTestConfig() ddprofiledefinition.BGPConfig {
494
+ return ddprofiledefinition.BGPConfig{
495
+ OriginProfileID: "_vendor-bgp.yaml",
496
+ ID: "table-peer",
497
+ Kind: ddprofiledefinition.BGPRowKindPeer,
498
+ Table: ddprofiledefinition.SymbolConfig{
499
+ OID: "1.3.6.1.4.1.99999.30.1",
500
+ Name: "vendorBgpPeerTable",
501
+ },
502
+ Identity: ddprofiledefinition.BGPIdentityConfig{
503
+ Neighbor: ddprofiledefinition.BGPValueConfig{Index: 1},
504
+ RemoteAS: ddprofiledefinition.BGPValueConfig{
505
+ Symbol: ddprofiledefinition.SymbolConfig{
506
+ OID: "1.3.6.1.4.1.99999.30.1.3",
507
+ Name: "vendorBgpPeerRemoteAs",
508
+ },
509
+ },
510
+ },
511
+ State: ddprofiledefinition.BGPStateConfig{
512
+ BGPValueConfig: ddprofiledefinition.BGPValueConfig{
513
+ Symbol: ddprofiledefinition.SymbolConfig{
514
+ OID: "1.3.6.1.4.1.99999.30.1.2",
515
+ Name: "vendorBgpPeerState",
516
+ Mapping: bgpPeerStateMapping(),
517
+ },
518
+ },
519
+ },
520
+ Connection: ddprofiledefinition.BGPConnectionConfig{
521
+ EstablishedUptime: ddprofiledefinition.BGPValueConfig{
522
+ Symbol: ddprofiledefinition.SymbolConfig{
523
+ OID: "1.3.6.1.4.1.99999.30.1.4",
524
+ Name: "vendorBgpPeerEstablishedTime",
525
+ },
526
+ },
527
+ },
528
+ StaticTags: []ddprofiledefinition.StaticMetricTagConfig{
529
+ {Tag: "routing_instance", Value: "blue"},
530
+ },
531
+ }
532
+}
533
+
534
+func crossTableBGPTestConfig() ddprofiledefinition.BGPConfig {
535
+ peerIndex := []ddprofiledefinition.MetricIndexTransform{{Start: 0, DropRight: 2}}
536
+
537
+ return ddprofiledefinition.BGPConfig{
538
+ OriginProfileID: "_vendor-bgp.yaml",
539
+ ID: "table-peer-family",
540
+ Kind: ddprofiledefinition.BGPRowKindPeerFamily,
541
+ Table: ddprofiledefinition.SymbolConfig{
542
+ OID: "1.3.6.1.4.1.99999.70.1",
543
+ Name: "vendorBgpPeerFamilyTable",
544
+ },
545
+ Identity: ddprofiledefinition.BGPIdentityConfig{
546
+ RoutingInstance: ddprofiledefinition.BGPValueConfig{
547
+ Table: "vendorBgpPeerTable",
548
+ IndexTransform: peerIndex,
549
+ Symbol: ddprofiledefinition.SymbolConfig{
550
+ OID: "1.3.6.1.4.1.99999.70.2.2",
551
+ Name: "vendorBgpPeerRoutingInstance",
552
+ },
553
+ },
554
+ Neighbor: ddprofiledefinition.BGPValueConfig{
555
+ IndexTransform: []ddprofiledefinition.MetricIndexTransform{{Start: 1, DropRight: 2}},
556
+ Format: "ip_address",
557
+ },
558
+ RemoteAS: ddprofiledefinition.BGPValueConfig{
559
+ Table: "vendorBgpPeerTable",
560
+ IndexTransform: peerIndex,
561
+ Symbol: ddprofiledefinition.SymbolConfig{
562
+ OID: "1.3.6.1.4.1.99999.70.2.1",
563
+ Name: "vendorBgpPeerRemoteAs",
564
+ },
565
+ },
566
+ AddressFamily: ddprofiledefinition.BGPAddressFamilyValueConfig{
567
+ BGPValueConfig: ddprofiledefinition.BGPValueConfig{
568
+ IndexFromEnd: 2,
569
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
570
+ "1": "ipv4",
571
+ "2": "ipv6",
572
+ }),
573
+ },
574
+ },
575
+ SubsequentAddressFamily: ddprofiledefinition.BGPSubsequentAddressFamilyValueConfig{
576
+ BGPValueConfig: ddprofiledefinition.BGPValueConfig{
577
+ IndexFromEnd: 1,
578
+ Mapping: ddprofiledefinition.NewExactMapping(map[string]string{
579
+ "1": "unicast",
580
+ }),
581
+ },
582
+ },
583
+ },
584
+ Descriptors: ddprofiledefinition.BGPDescriptorsConfig{
585
+ PeerType: ddprofiledefinition.BGPValueConfig{
586
+ IndexFromEnd: 2,
587
+ Format: "hex",
588
+ },
589
+ },
590
+ Routes: ddprofiledefinition.BGPRoutesConfig{
591
+ Current: ddprofiledefinition.BGPRouteCountersConfig{
592
+ Accepted: ddprofiledefinition.BGPValueConfig{
593
+ Symbol: ddprofiledefinition.SymbolConfig{
594
+ OID: "1.3.6.1.4.1.99999.70.1.1",
595
+ Name: "vendorBgpPrefixesAccepted",
596
+ },
597
+ },
598
+ },
599
+ },
600
+ }
601
+}
602
+
603
+func bgpPeerStateMapping() ddprofiledefinition.MappingConfig {
604
+ return ddprofiledefinition.NewExactMapping(map[string]string{
605
+ "1": "idle",
606
+ "2": "connect",
607
+ "3": "active",
608
+ "4": "opensent",
609
+ "5": "openconfirm",
610
+ "6": "established",
611
+ })
612
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/common_test.go
+34
@@ -4,6 +4,7 @@ package ddsnmpcollector
4
5
import (
6
"regexp"
7
+ "strings"
8
"testing"
9
"time"
10
@@ -33,6 +34,24 @@ func setupMockHandler(t *testing.T) (*gomock.Controller, *snmpmock.MockHandler)
34
return ctrl, mockHandler
35
}
36
37
+func matchedProfileByFile(t *testing.T, sysObjectID, profileFile string) *ddsnmp.Profile {
38
+ t.Helper()
39
+
40
+ matched := ddsnmp.FindProfiles(sysObjectID, "", nil)
41
+ for _, prof := range matched {
42
+ if strings.HasSuffix(prof.SourceFile, profileFile) {
43
+ return prof
44
+ }
45
+ }
46
+
47
+ require.FailNowf(t, "missing profile", "expected %s for %s", profileFile, sysObjectID)
48
+ return nil
49
+}
50
+
51
+func oidWithIndex(baseOID, index string) string {
52
+ return strings.Trim(baseOID, ".") + "." + strings.Trim(index, ".")
53
+}
54
+
55
func createTestProfile(sourceFile string, metrics []ddprofiledefinition.MetricsConfig) *ddsnmp.Profile {
56
return &ddsnmp.Profile{
57
SourceFile: sourceFile,
@@ -61,6 +80,21 @@ func expectSNMPGetError(mockHandler *snmpmock.MockHandler, oids []string, err er
80
mockHandler.EXPECT().Get(gomock.InAnyOrder(oids)).Return(nil, err)
81
}
82
83
+func expectSystemMetadataGets(mockHandler *snmpmock.MockHandler, sysObjectID, sysName string) {
84
+ mockHandler.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{Variables: []gosnmp.SnmpPDU{
85
+ createStringPDU("1.0.8802.1.1.2.1.3.1.0", sysName),
86
+ createStringPDU("1.0.8802.1.1.2.1.3.2.0", sysName),
87
+ createStringPDU("1.0.8802.1.1.2.1.3.3.0", sysName),
88
+ createStringPDU("1.0.8802.1.1.2.1.3.4.0", sysName),
89
+ createStringPDU("1.0.8802.1.1.2.1.3.5.0", sysName),
90
+ createStringPDU("1.0.8802.1.1.2.1.3.6.0", sysName),
91
+ createStringPDU("1.3.6.1.2.1.1.1.0", "SNMP device"),
92
+ createPDU("1.3.6.1.2.1.1.2.0", gosnmp.ObjectIdentifier, sysObjectID),
93
+ createStringPDU("1.3.6.1.2.1.1.5.0", sysName),
94
+ createStringPDU("1.3.6.1.2.1.1.6.0", "lab"),
95
+ }}, nil).AnyTimes()
96
+}
97
+
98
func expectSNMPWalk(mockHandler *snmpmock.MockHandler, version gosnmp.SnmpVersion, oid string, pdus []gosnmp.SnmpPDU) {
99
mockHandler.EXPECT().Version().Return(version)
100
if version == gosnmp.Version1 {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/huawei_bgp_external_fixture_test.go
new
+234
@@ -0,0 +1,234 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_HuaweiBGP_FromLibreNMSFixture(t *testing.T) {
18
+ profile := matchedBGPProjectionByFile(t, "1.3.6.1.4.1.2011.2.224.279", "huawei-routers.yaml")
19
+ pdus := loadSnmprecPDUs(t, "librenms/huawei_vrp_ne8000_bgp_peer_table.snmprec")
20
+
21
+ ctrl, mockHandler := setupMockHandler(t)
22
+ defer ctrl.Finish()
23
+
24
+ expectSNMPGet(mockHandler, []string{
25
+ "1.3.6.1.2.1.1.1.0",
26
+ "1.3.6.1.2.1.1.2.0",
27
+ "1.3.6.1.2.1.1.5.0",
28
+ "1.3.6.1.2.1.1.6.0",
29
+ }, []gosnmp.SnmpPDU{
30
+ createStringPDU("1.3.6.1.2.1.1.1.0", "Huawei VRP"),
31
+ createPDU("1.3.6.1.2.1.1.2.0", gosnmp.ObjectIdentifier, "1.3.6.1.4.1.2011.2.224.279"),
32
+ createStringPDU("1.3.6.1.2.1.1.5.0", "huawei"),
33
+ createStringPDU("1.3.6.1.2.1.1.6.0", "lab"),
34
+ })
35
+ expectSNMPGet(mockHandler, []string{
36
+ "1.3.6.1.2.1.1.5.0",
37
+ "1.3.6.1.4.1.2011.5.25.31.6.5.0",
38
+ }, []gosnmp.SnmpPDU{
39
+ createStringPDU("1.3.6.1.2.1.1.5.0", "huawei"),
40
+ createStringPDU("1.3.6.1.4.1.2011.5.25.31.6.5.0", "NetEngine"),
41
+ })
42
+ expectSNMPGet(mockHandler, []string{
43
+ "1.3.6.1.4.1.2011.5.25.177.1.4.1",
44
+ "1.3.6.1.4.1.2011.5.25.177.1.4.2",
45
+ "1.3.6.1.4.1.2011.5.25.177.1.4.3",
46
+ }, []gosnmp.SnmpPDU{
47
+ createGauge32PDU("1.3.6.1.4.1.2011.5.25.177.1.4.1", 12),
48
+ createGauge32PDU("1.3.6.1.4.1.2011.5.25.177.1.4.2", 4),
49
+ createGauge32PDU("1.3.6.1.4.1.2011.5.25.177.1.4.3", 8),
50
+ })
51
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
52
+
53
+ collector := New(Config{
54
+ SnmpClient: mockHandler,
55
+ Profiles: []*ddsnmp.Profile{profile},
56
+ Log: logger.New(),
57
+ SysObjectID: "1.3.6.1.4.1.2011.2.224.279",
58
+ })
59
+
60
+ results, err := collector.Collect()
61
+ require.NoError(t, err)
62
+ require.Len(t, results, 1)
63
+ require.Empty(t, results[0].Metrics)
64
+ require.Empty(t, results[0].TopologyMetrics)
65
+ require.Empty(t, results[0].LicenseRows)
66
+ require.NotEmpty(t, results[0].BGPRows)
67
+
68
+ device := requireBGPRowMatching(t, results[0].BGPRows, func(row ddsnmp.BGPRow) bool {
69
+ return row.Kind == ddprofiledefinition.BGPRowKindDevice
70
+ })
71
+ require.True(t, device.Device.Peers.Has)
72
+ assert.EqualValues(t, 12, device.Device.Peers.Value)
73
+ assert.EqualValues(t, 4, device.Device.InternalPeers.Value)
74
+ assert.EqualValues(t, 8, device.Device.ExternalPeers.Value)
75
+
76
+ families := map[string]struct {
77
+ neighbor string
78
+ af ddprofiledefinition.BGPAddressFamily
79
+ routingInstance string
80
+ remoteAS string
81
+ establishedTrans int64
82
+ lastErrorCode int64
83
+ lastErrorSubcode int64
84
+ wantAdmin bool
85
+ wantAdminEnabled bool
86
+ wantLastError bool
87
+ }{
88
+ "IPv4 family": {
89
+ neighbor: "10.45.2.2",
90
+ af: ddprofiledefinition.BGPAddressFamilyIPv4,
91
+ routingInstance: "Public",
92
+ remoteAS: "26479",
93
+ establishedTrans: 2,
94
+ lastErrorCode: 5,
95
+ lastErrorSubcode: 0,
96
+ wantAdmin: true,
97
+ wantAdminEnabled: true,
98
+ wantLastError: true,
99
+ },
100
+ "IPv6 family": {
101
+ neighbor: "2001:12f8::223:253",
102
+ af: ddprofiledefinition.BGPAddressFamilyIPv6,
103
+ routingInstance: "PublicV6",
104
+ remoteAS: "26162",
105
+ establishedTrans: 7,
106
+ },
107
+ }
108
+
109
+ for name, tc := range families {
110
+ t.Run(name, func(t *testing.T) {
111
+ row := requireHuaweiPeerFamilyRow(t, results[0].BGPRows, tc.neighbor, tc.af)
112
+ assert.Equal(t, tc.routingInstance, row.Identity.RoutingInstance)
113
+ assert.Equal(t, tc.remoteAS, row.Identity.RemoteAS)
114
+ assert.Equal(t, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast, row.Identity.SubsequentAddressFamily)
115
+ if tc.wantAdmin {
116
+ require.True(t, row.Admin.Enabled.Has)
117
+ assert.Equal(t, tc.wantAdminEnabled, row.Admin.Enabled.Value)
118
+ }
119
+ require.True(t, row.State.Has)
120
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, row.State.State)
121
+ assert.EqualValues(t, tc.establishedTrans, row.Transitions.Established.Value)
122
+ if tc.wantLastError {
123
+ assert.EqualValues(t, tc.lastErrorCode, row.LastError.Code.Value)
124
+ assert.EqualValues(t, tc.lastErrorSubcode, row.LastError.Subcode.Value)
125
+ }
126
+ })
127
+ }
128
+
129
+ routeRows := map[string]struct {
130
+ neighbor string
131
+ af ddprofiledefinition.BGPAddressFamily
132
+ received int64
133
+ active int64
134
+ advertised int64
135
+ }{
136
+ "IPv4 routes": {
137
+ neighbor: "10.45.2.2",
138
+ af: ddprofiledefinition.BGPAddressFamilyIPv4,
139
+ received: 13970,
140
+ active: 1007,
141
+ advertised: 8,
142
+ },
143
+ "IPv6 routes": {
144
+ neighbor: "2001:12f8::223:253",
145
+ af: ddprofiledefinition.BGPAddressFamilyIPv6,
146
+ },
147
+ }
148
+
149
+ for name, tc := range routeRows {
150
+ t.Run(name, func(t *testing.T) {
151
+ row := requireHuaweiPeerFamilyRowByTable(t, results[0].BGPRows, "hwBgpPeerRouteTable", tc.neighbor, tc.af)
152
+ assert.EqualValues(t, tc.received, row.Routes.Total.Received.Value)
153
+ assert.EqualValues(t, tc.active, row.Routes.Total.Active.Value)
154
+ assert.EqualValues(t, tc.advertised, row.Routes.Total.Advertised.Value)
155
+ })
156
+ }
157
+
158
+ peers := map[string]struct {
159
+ neighbor string
160
+ remoteAS string
161
+ peerType string
162
+ establishedTrans int64
163
+ downTrans int64
164
+ wantTransitions bool
165
+ updatesReceived int64
166
+ updatesSent int64
167
+ messagesReceived int64
168
+ messagesSent int64
169
+ }{
170
+ "IPv4 peer": {
171
+ neighbor: "10.45.2.2",
172
+ remoteAS: "26479",
173
+ peerType: "ipv4",
174
+ establishedTrans: 2,
175
+ downTrans: 1,
176
+ wantTransitions: true,
177
+ updatesReceived: 70063,
178
+ updatesSent: 971,
179
+ messagesReceived: 99928,
180
+ messagesSent: 31448,
181
+ },
182
+ "IPv6 peer": {
183
+ neighbor: "2001:12f8::223:253",
184
+ remoteAS: "26162",
185
+ peerType: "ipv6",
186
+ updatesReceived: 3318661,
187
+ updatesSent: 6,
188
+ messagesReceived: 3337254,
189
+ messagesSent: 19035,
190
+ },
191
+ }
192
+
193
+ for name, tc := range peers {
194
+ t.Run(name, func(t *testing.T) {
195
+ row := requireHuaweiPeerRow(t, results[0].BGPRows, tc.neighbor)
196
+ assert.Equal(t, "0", row.Identity.RoutingInstance)
197
+ assert.Equal(t, tc.remoteAS, row.Identity.RemoteAS)
198
+ assert.Equal(t, tc.peerType, row.Descriptors.PeerType)
199
+ if tc.wantTransitions {
200
+ assert.EqualValues(t, tc.establishedTrans, row.Transitions.Established.Value)
201
+ assert.EqualValues(t, tc.downTrans, row.Transitions.Down.Value)
202
+ }
203
+ assert.EqualValues(t, tc.updatesReceived, row.Traffic.Updates.Received.Value)
204
+ assert.EqualValues(t, tc.updatesSent, row.Traffic.Updates.Sent.Value)
205
+ assert.EqualValues(t, tc.messagesReceived, row.Traffic.Messages.Received.Value)
206
+ assert.EqualValues(t, tc.messagesSent, row.Traffic.Messages.Sent.Value)
207
+ })
208
+ }
209
+}
210
+
211
+func requireHuaweiPeerFamilyRow(t *testing.T, rows []ddsnmp.BGPRow, neighbor string, af ddprofiledefinition.BGPAddressFamily) ddsnmp.BGPRow {
212
+ t.Helper()
213
+
214
+ return requireHuaweiPeerFamilyRowByTable(t, rows, "hwBgpPeerTable", neighbor, af)
215
+}
216
+
217
+func requireHuaweiPeerFamilyRowByTable(t *testing.T, rows []ddsnmp.BGPRow, table, neighbor string, af ddprofiledefinition.BGPAddressFamily) ddsnmp.BGPRow {
218
+ t.Helper()
219
+
220
+ return requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
221
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
222
+ row.Table == table &&
223
+ row.Identity.Neighbor == neighbor &&
224
+ row.Identity.AddressFamily == af
225
+ })
226
+}
227
+
228
+func requireHuaweiPeerRow(t *testing.T, rows []ddsnmp.BGPRow, neighbor string) ddsnmp.BGPRow {
229
+ t.Helper()
230
+
231
+ return requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
232
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == neighbor
233
+ })
234
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/huawei_bgp_statistics_test.go
new
+101
@@ -0,0 +1,101 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/logger"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+func TestCollector_Collect_HuaweiTypedBGPRows(t *testing.T) {
18
+ tests := map[string]struct {
19
+ rowID string
20
+ pdus []gosnmp.SnmpPDU
21
+ validate func(t *testing.T, rows []ddsnmp.BGPRow)
22
+ }{
23
+ "peer family maps state admin and routing instance": {
24
+ rowID: "huawei-bgp-peer-family",
25
+ pdus: []gosnmp.SnmpPDU{
26
+ createStringPDU("1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6.0.1.1.1.4.192.0.2.21", "blue"),
27
+ createGauge32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2.0.1.1.1.4.192.0.2.21", 65001),
28
+ createPDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4.0.1.1.1.4.192.0.2.21", gosnmp.OctetString, []byte{192, 0, 2, 21}),
29
+ createIntegerPDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.5.0.1.1.1.4.192.0.2.21", 3),
30
+ createIntegerPDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.11.0.1.1.1.4.192.0.2.21", 1),
31
+ createCounter32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.6.0.1.1.1.4.192.0.2.21", 4),
32
+ },
33
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
34
+ require.Len(t, rows, 1)
35
+ row := rows[0]
36
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, row.Kind)
37
+ assert.Equal(t, "blue", row.Identity.RoutingInstance)
38
+ assert.Equal(t, "192.0.2.21", row.Identity.Neighbor)
39
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
40
+ assert.Equal(t, ddprofiledefinition.BGPAddressFamilyIPv4, row.Identity.AddressFamily)
41
+ assert.Equal(t, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast, row.Identity.SubsequentAddressFamily)
42
+ require.True(t, row.Admin.Enabled.Has)
43
+ assert.False(t, row.Admin.Enabled.Value)
44
+ require.True(t, row.State.Has)
45
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateActive, row.State.State)
46
+ assert.EqualValues(t, 4, row.Transitions.Established.Value)
47
+ },
48
+ },
49
+ "peer statistics resolves non-default VRF index and remote AS by peer address": {
50
+ rowID: "huawei-bgp-peer-statistics",
51
+ pdus: []gosnmp.SnmpPDU{
52
+ createPDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4.42.1.1.1.4.192.0.2.22", gosnmp.OctetString, []byte{192, 0, 2, 22}),
53
+ createGauge32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2.42.1.1.1.4.192.0.2.22", 65002),
54
+ createCounter32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.7.1.4.0.42.4.192.0.2.22", 5),
55
+ createCounter32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.7.1.5.0.42.4.192.0.2.22", 2),
56
+ createCounter32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.7.1.6.0.42.4.192.0.2.22", 101),
57
+ createCounter32PDU("1.3.6.1.4.1.2011.5.25.177.1.1.7.1.7.0.42.4.192.0.2.22", 202),
58
+ },
59
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
60
+ require.Len(t, rows, 1)
61
+ row := rows[0]
62
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, row.Kind)
63
+ assert.Equal(t, "42", row.Identity.RoutingInstance)
64
+ assert.Equal(t, "192.0.2.22", row.Identity.Neighbor)
65
+ assert.Equal(t, "65002", row.Identity.RemoteAS)
66
+ assert.Equal(t, "ipv4", row.Descriptors.PeerType)
67
+ assert.EqualValues(t, 5, row.Transitions.Established.Value)
68
+ assert.EqualValues(t, 2, row.Transitions.Down.Value)
69
+ assert.EqualValues(t, 101, row.Traffic.Updates.Received.Value)
70
+ assert.EqualValues(t, 202, row.Traffic.Updates.Sent.Value)
71
+ },
72
+ },
73
+ }
74
+
75
+ for name, tc := range tests {
76
+ t.Run(name, func(t *testing.T) {
77
+ profile := matchedBGPProjectionByFile(t, "1.3.6.1.4.1.2011.2.224.279", "huawei-routers.yaml")
78
+ filterProfileForTypedBGPByID(t, profile, tc.rowID)
79
+
80
+ ctrl, mockHandler := setupMockHandler(t)
81
+ defer ctrl.Finish()
82
+
83
+ expectBGPTableWalksFromFixture(mockHandler, profile, tc.pdus)
84
+
85
+ collector := New(Config{
86
+ SnmpClient: mockHandler,
87
+ Profiles: []*ddsnmp.Profile{profile},
88
+ Log: logger.New(),
89
+ SysObjectID: "1.3.6.1.4.1.2011.2.224.279",
90
+ })
91
+
92
+ results, err := collector.Collect()
93
+ require.NoError(t, err)
94
+ require.Len(t, results, 1)
95
+ require.Empty(t, results[0].Metrics)
96
+ require.Len(t, results[0].BGPRows, 1)
97
+
98
+ tc.validate(t, results[0].BGPRows)
99
+ })
100
+ }
101
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/juniper_bgp_external_fixture_test.go
new
+225
@@ -0,0 +1,225 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "strings"
7
+ "testing"
8
+
9
+ "github.com/golang/mock/gomock"
10
+ "github.com/gosnmp/gosnmp"
11
+ snmpmock "github.com/gosnmp/gosnmp/mocks"
12
+ "github.com/stretchr/testify/assert"
13
+ "github.com/stretchr/testify/require"
14
+
15
+ "github.com/netdata/netdata/go/plugins/logger"
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
17
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
18
+)
19
+
20
+func TestCollector_Collect_JuniperBGP_FromLibreNMSFixtures(t *testing.T) {
21
+ tests := map[string]struct {
22
+ sysObjectID string
23
+ profileFile string
24
+ fixture string
25
+ validate func(t *testing.T, rows []ddsnmp.BGPRow)
26
+ }{
27
+ "Juniper MX emits typed peer and peer-family rows": {
28
+ sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.57",
29
+ profileFile: "juniper-mx.yaml",
30
+ fixture: "librenms/juniper_mx_bgp_peer_table.snmprec",
31
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
32
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
33
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "192.168.99.25"
34
+ })
35
+ assert.Equal(t, "_juniper-bgp4-v2.yaml", peer.OriginProfileID)
36
+ assert.Equal(t, "jnxBgpM2PeerTable", peer.Table)
37
+ assert.Equal(t, "0", peer.Identity.RoutingInstance)
38
+ assert.Equal(t, "64513", peer.Identity.RemoteAS)
39
+ assert.Equal(t, "192.168.99.24", peer.Descriptors.LocalAddress)
40
+ assert.Equal(t, "64513", peer.Descriptors.LocalAS)
41
+ assert.Equal(t, "192.168.99.25", peer.Descriptors.PeerIdentifier)
42
+ require.True(t, peer.Admin.Enabled.Has)
43
+ assert.True(t, peer.Admin.Enabled.Value)
44
+ require.True(t, peer.State.Has)
45
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
46
+ assert.EqualValues(t, 361781, peer.Connection.EstablishedUptime.Value)
47
+ assert.EqualValues(t, 49798, peer.Connection.LastReceivedUpdateAge.Value)
48
+ assert.EqualValues(t, 56, peer.Traffic.Updates.Received.Value)
49
+ assert.EqualValues(t, 1, peer.Traffic.Updates.Sent.Value)
50
+ assert.EqualValues(t, 13199, peer.Traffic.Messages.Received.Value)
51
+ assert.EqualValues(t, 13351, peer.Traffic.Messages.Sent.Value)
52
+
53
+ unicast := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
54
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
55
+ row.Identity.Neighbor == "192.168.99.25" &&
56
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv4 &&
57
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
58
+ })
59
+ assert.Equal(t, "jnxBgpM2PrefixCountersTable", unicast.Table)
60
+ assert.Equal(t, "0", unicast.Identity.RoutingInstance)
61
+ assert.Equal(t, "64513", unicast.Identity.RemoteAS)
62
+ assert.EqualValues(t, 20, unicast.Routes.Current.Received.Value)
63
+ assert.EqualValues(t, 20, unicast.Routes.Current.Accepted.Value)
64
+ assert.EqualValues(t, 0, unicast.Routes.Current.Advertised.Value)
65
+ assert.EqualValues(t, 6, unicast.Routes.Current.Active.Value)
66
+
67
+ vpls := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
68
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
69
+ row.Identity.Neighbor == "192.168.99.25" &&
70
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyL2VPN &&
71
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyVPLS
72
+ })
73
+ assert.EqualValues(t, 0, vpls.Routes.Current.Received.Value)
74
+ },
75
+ },
76
+ "Juniper vMX emits typed peer and peer-family rows": {
77
+ sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.108",
78
+ profileFile: "juniper-mx.yaml",
79
+ fixture: "librenms/juniper_vmx_bgp_peer_table.snmprec",
80
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
81
+ firstPeer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
82
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "10.1.1.1"
83
+ })
84
+ assert.Equal(t, "9", firstPeer.Identity.RoutingInstance)
85
+ assert.Equal(t, "62371", firstPeer.Identity.RemoteAS)
86
+ assert.Equal(t, "10.1.1.2", firstPeer.Descriptors.LocalAddress)
87
+ assert.Equal(t, "172.16.0.10", firstPeer.Descriptors.PeerIdentifier)
88
+ require.True(t, firstPeer.State.Has)
89
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, firstPeer.State.State)
90
+ assert.EqualValues(t, 2, firstPeer.Traffic.Updates.Received.Value)
91
+ assert.EqualValues(t, 2, firstPeer.Traffic.Updates.Sent.Value)
92
+ assert.EqualValues(t, 468, firstPeer.Traffic.Messages.Received.Value)
93
+ assert.EqualValues(t, 12859, firstPeer.Connection.EstablishedUptime.Value)
94
+
95
+ secondPeer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
96
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "10.1.1.5"
97
+ })
98
+ assert.Equal(t, "10.1.1.6", secondPeer.Descriptors.LocalAddress)
99
+ assert.Equal(t, "172.16.0.11", secondPeer.Descriptors.PeerIdentifier)
100
+ assert.EqualValues(t, 477, secondPeer.Traffic.Messages.Sent.Value)
101
+ assert.EqualValues(t, 12757, secondPeer.Connection.LastReceivedUpdateAge.Value)
102
+
103
+ firstFamily := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
104
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
105
+ row.Identity.Neighbor == "10.1.1.1" &&
106
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv4 &&
107
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
108
+ })
109
+ assert.EqualValues(t, 1, firstFamily.Routes.Current.Accepted.Value)
110
+ assert.EqualValues(t, 2, firstFamily.Routes.Current.Advertised.Value)
111
+ assert.EqualValues(t, 1, firstFamily.Routes.Current.Active.Value)
112
+
113
+ secondFamily := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
114
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
115
+ row.Identity.Neighbor == "10.1.1.5" &&
116
+ row.Identity.AddressFamily == ddprofiledefinition.BGPAddressFamilyIPv4 &&
117
+ row.Identity.SubsequentAddressFamily == ddprofiledefinition.BGPSubsequentAddressFamilyUnicast
118
+ })
119
+ assert.EqualValues(t, 0, secondFamily.Routes.Current.Rejected.Value)
120
+ assert.EqualValues(t, 1, secondFamily.Routes.Current.Active.Value)
121
+ },
122
+ },
123
+ }
124
+
125
+ for name, tc := range tests {
126
+ t.Run(name, func(t *testing.T) {
127
+ profile := matchedBGPProjectionByFile(t, tc.sysObjectID, tc.profileFile)
128
+ pdus := loadSnmprecPDUs(t, tc.fixture)
129
+
130
+ ctrl, mockHandler := setupMockHandler(t)
131
+ defer ctrl.Finish()
132
+
133
+ expectJuniperMetadataGets(mockHandler)
134
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
135
+
136
+ collector := New(Config{
137
+ SnmpClient: mockHandler,
138
+ Profiles: []*ddsnmp.Profile{profile},
139
+ Log: logger.New(),
140
+ SysObjectID: tc.sysObjectID,
141
+ })
142
+
143
+ results, err := collector.Collect()
144
+ require.NoError(t, err)
145
+ require.Len(t, results, 1)
146
+ require.Empty(t, results[0].Metrics)
147
+ require.Empty(t, results[0].TopologyMetrics)
148
+ require.Empty(t, results[0].LicenseRows)
149
+ require.NotEmpty(t, results[0].BGPRows)
150
+
151
+ tc.validate(t, results[0].BGPRows)
152
+ })
153
+ }
154
+}
155
+
156
+func matchedBGPProjectionByFile(t *testing.T, sysObjectID, profileFile string) *ddsnmp.Profile {
157
+ t.Helper()
158
+
159
+ profiles := ddsnmp.DefaultCatalog().Resolve(ddsnmp.ResolveRequest{
160
+ SysObjectID: sysObjectID,
161
+ }).Project(ddsnmp.ConsumerBGP).Profiles()
162
+
163
+ for _, prof := range profiles {
164
+ if strings.HasSuffix(prof.SourceFile, profileFile) {
165
+ return prof
166
+ }
167
+ }
168
+
169
+ require.FailNowf(t, "missing profile", "expected %s BGP projection for %s", profileFile, sysObjectID)
170
+ return nil
171
+}
172
+
173
+func expectJuniperMetadataGets(mockHandler *snmpmock.MockHandler) {
174
+ mockHandler.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{Variables: []gosnmp.SnmpPDU{
175
+ createStringPDU("1.0.8802.1.1.2.1.3.1.0", "juniper"),
176
+ createStringPDU("1.0.8802.1.1.2.1.3.2.0", "juniper"),
177
+ createStringPDU("1.0.8802.1.1.2.1.3.3.0", "juniper"),
178
+ createStringPDU("1.0.8802.1.1.2.1.3.4.0", "juniper"),
179
+ createStringPDU("1.0.8802.1.1.2.1.3.5.0", "juniper"),
180
+ createStringPDU("1.0.8802.1.1.2.1.3.6.0", "juniper"),
181
+ createStringPDU("1.3.6.1.2.1.1.1.0", "Juniper Networks"),
182
+ createPDU("1.3.6.1.2.1.1.2.0", gosnmp.ObjectIdentifier, "1.3.6.1.4.1.2636.1.1.1.2.57"),
183
+ createStringPDU("1.3.6.1.2.1.1.5.0", "juniper"),
184
+ createStringPDU("1.3.6.1.2.1.1.6.0", "lab"),
185
+ }}, nil).AnyTimes()
186
+}
187
+
188
+func expectBGPTableWalksFromFixture(mockHandler *snmpmock.MockHandler, profile *ddsnmp.Profile, pdus []gosnmp.SnmpPDU) {
189
+ mockHandler.EXPECT().Version().Return(gosnmp.Version2c).AnyTimes()
190
+
191
+ tableNameToOID := bgpTableNameToOID(profile.Definition.BGP)
192
+ walked := make(map[string]bool)
193
+ for _, cfg := range profile.Definition.BGP {
194
+ if cfg.Table.OID == "" {
195
+ continue
196
+ }
197
+ tableOID := trimOID(cfg.Table.OID)
198
+ if !walked[tableOID] {
199
+ mockHandler.EXPECT().BulkWalkAll(tableOID).Return(snmprecPDUsWithPrefix(pdus, tableOID+"."), nil)
200
+ walked[tableOID] = true
201
+ }
202
+ metricsCfg := bgpConfigAsMetricsConfig(cfg)
203
+ for _, depOID := range bgpTableDependencies(cfg, metricsCfg, tableNameToOID) {
204
+ depOID = trimOID(depOID)
205
+ if walked[depOID] {
206
+ continue
207
+ }
208
+ mockHandler.EXPECT().BulkWalkAll(depOID).Return(snmprecPDUsWithPrefix(pdus, depOID+"."), nil)
209
+ walked[depOID] = true
210
+ }
211
+ }
212
+}
213
+
214
+func requireBGPRowMatching(t *testing.T, rows []ddsnmp.BGPRow, match func(ddsnmp.BGPRow) bool) ddsnmp.BGPRow {
215
+ t.Helper()
216
+
217
+ for _, row := range rows {
218
+ if match(row) {
219
+ return row
220
+ }
221
+ }
222
+
223
+ require.FailNowf(t, "missing BGP row", "available rows: %v", rows)
224
+ return ddsnmp.BGPRow{}
225
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/snmprec_fixture_helpers_test.go
new
+19
@@ -0,0 +1,19 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "strings"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+)
10
+
11
+func snmprecPDUsWithPrefix(pdus []gosnmp.SnmpPDU, prefix string) []gosnmp.SnmpPDU {
12
+ out := make([]gosnmp.SnmpPDU, 0, len(pdus))
13
+ for _, pdu := range pdus {
14
+ if strings.HasPrefix(strings.Trim(pdu.Name, "."), strings.Trim(prefix, ".")) {
15
+ out = append(out, pdu)
16
+ }
17
+ }
18
+ return out
19
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/snmprec_fixture_parser_test.go
new
+41
@@ -0,0 +1,41 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/gosnmp/gosnmp"
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+)
12
+
13
+func TestSNMPRecPDU_SupportsAdditionalTypes(t *testing.T) {
14
+ t.Run("object identifier", func(t *testing.T) {
15
+ pdu, err := snmprecPDU("1.2.3.0", "6", "1.3.6.1.4.1.6527.1.3.17")
16
+ require.NoError(t, err)
17
+ assert.Equal(t, gosnmp.ObjectIdentifier, pdu.Type)
18
+ assert.Equal(t, "1.3.6.1.4.1.6527.1.3.17", pdu.Value)
19
+ })
20
+
21
+ t.Run("timeticks", func(t *testing.T) {
22
+ pdu, err := snmprecPDU("1.2.3.0", "67", "2158388928")
23
+ require.NoError(t, err)
24
+ assert.Equal(t, gosnmp.TimeTicks, pdu.Type)
25
+ assert.EqualValues(t, uint32(2158388928), pdu.Value)
26
+ })
27
+
28
+ t.Run("counter64", func(t *testing.T) {
29
+ pdu, err := snmprecPDU("1.2.3.0", "70", "151712621952719")
30
+ require.NoError(t, err)
31
+ assert.Equal(t, gosnmp.Counter64, pdu.Type)
32
+ assert.EqualValues(t, uint64(151712621952719), pdu.Value)
33
+ })
34
+
35
+ t.Run("hex ipaddress", func(t *testing.T) {
36
+ pdu, err := snmprecPDU("1.2.3.0", "64x", "C0A80001")
37
+ require.NoError(t, err)
38
+ assert.Equal(t, gosnmp.IPAddress, pdu.Type)
39
+ assert.Equal(t, []byte{192, 168, 0, 1}, pdu.Value)
40
+ })
41
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/snmprec_fixture_test.go
new
+103
@@ -0,0 +1,103 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "encoding/hex"
7
+ "fmt"
8
+ "os"
9
+ "path/filepath"
10
+ "strconv"
11
+ "strings"
12
+ "testing"
13
+
14
+ "github.com/gosnmp/gosnmp"
15
+ "github.com/stretchr/testify/require"
16
+)
17
+
18
+func loadSnmprecPDUs(t *testing.T, relativePath string) []gosnmp.SnmpPDU {
19
+ t.Helper()
20
+
21
+ path := filepath.Join("testdata", relativePath)
22
+ data, err := os.ReadFile(path)
23
+ require.NoError(t, err)
24
+
25
+ var pdus []gosnmp.SnmpPDU
26
+ for lineNum, raw := range strings.Split(string(data), "\n") {
27
+ line := strings.TrimSpace(raw)
28
+ if line == "" {
29
+ continue
30
+ }
31
+
32
+ parts := strings.SplitN(line, "|", 3)
33
+ require.Lenf(t, parts, 3, "invalid snmprec line %d", lineNum+1)
34
+
35
+ name := parts[0]
36
+ typeCode := parts[1]
37
+ value := parts[2]
38
+
39
+ pdu, err := snmprecPDU(name, typeCode, value)
40
+ require.NoErrorf(t, err, "invalid snmprec line %d", lineNum+1)
41
+ pdus = append(pdus, pdu)
42
+ }
43
+
44
+ return pdus
45
+}
46
+
47
+func snmprecPDU(name, typeCode, raw string) (gosnmp.SnmpPDU, error) {
48
+ switch typeCode {
49
+ case "2":
50
+ n, err := strconv.Atoi(raw)
51
+ if err != nil {
52
+ return gosnmp.SnmpPDU{}, err
53
+ }
54
+ return createIntegerPDU(name, n), nil
55
+ case "4":
56
+ return createStringPDU(name, raw), nil
57
+ case "4x":
58
+ decoded, err := hex.DecodeString(raw)
59
+ if err != nil {
60
+ return gosnmp.SnmpPDU{}, err
61
+ }
62
+ return createPDU(name, gosnmp.OctetString, decoded), nil
63
+ case "6":
64
+ return createPDU(name, gosnmp.ObjectIdentifier, strings.TrimPrefix(raw, ".")), nil
65
+ case "64":
66
+ return createPDU(name, gosnmp.IPAddress, raw), nil
67
+ case "64x":
68
+ decoded, err := hex.DecodeString(raw)
69
+ if err != nil {
70
+ return gosnmp.SnmpPDU{}, err
71
+ }
72
+ if len(decoded) != 4 && len(decoded) != 16 {
73
+ return gosnmp.SnmpPDU{}, fmt.Errorf("unsupported 64x length %d", len(decoded))
74
+ }
75
+ return createPDU(name, gosnmp.IPAddress, decoded), nil
76
+ case "65":
77
+ n, err := strconv.ParseUint(raw, 10, 32)
78
+ if err != nil {
79
+ return gosnmp.SnmpPDU{}, err
80
+ }
81
+ return createCounter32PDU(name, uint(n)), nil
82
+ case "66":
83
+ n, err := strconv.ParseUint(raw, 10, 32)
84
+ if err != nil {
85
+ return gosnmp.SnmpPDU{}, err
86
+ }
87
+ return createGauge32PDU(name, uint(n)), nil
88
+ case "67":
89
+ n, err := strconv.ParseUint(raw, 10, 32)
90
+ if err != nil {
91
+ return gosnmp.SnmpPDU{}, err
92
+ }
93
+ return createTimeTicksPDU(name, uint32(n)), nil
94
+ case "70":
95
+ n, err := strconv.ParseUint(raw, 10, 64)
96
+ if err != nil {
97
+ return gosnmp.SnmpPDU{}, err
98
+ }
99
+ return createCounter64PDU(name, n), nil
100
+ default:
101
+ return gosnmp.SnmpPDU{}, fmt.Errorf("unsupported snmprec type %q", typeCode)
102
+ }
103
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/table_row_processor.go
+21
-4
@@ -168,6 +168,27 @@ func (p *tableRowProcessor) extractIndexPosition(index string, position uint) (s
168
return index, n == position && index != ""
169
}
170
171
+func (p *tableRowProcessor) extractIndexPositionFromEnd(index string, position uint) (string, bool) {
172
+ if position == 0 || index == "" {
173
+ return "", false
174
+ }
175
+
176
+ end := len(index)
177
+ for n := uint(1); ; n++ {
178
+ start := strings.LastIndexByte(index[:end], '.')
179
+ if n == position {
180
+ if start == -1 {
181
+ return index[:end], end > 0
182
+ }
183
+ return index[start+1 : end], start+1 < end
184
+ }
185
+ if start == -1 {
186
+ return "", false
187
+ }
188
+ end = start
189
+ }
190
+}
191
+
192
func (p *tableRowProcessor) processRowMetrics(row *tableRowData, ctx *tableRowProcessingContext) ([]ddsnmp.Metric, error) {
193
symbolCount := 0
194
for _, syms := range ctx.columnOIDs {
@@ -355,7 +376,3 @@ func (r *crossTableResolver) applyIndexTransform(index string, transforms []ddpr
376
377
return strings.Join(result, ".")
378
}
358
-
359
-func (r *crossTableResolver) isCrossTable(tagCfg ddprofiledefinition.MetricTagConfig, currentTableName string) bool {
360
- return tagCfg.Table != "" && tagCfg.Table != currentTableName && tagCfg.Index == 0
361
-}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/table_row_processor_test.go
+53
@@ -41,3 +41,56 @@ func TestTableRowProcessor_ProcessRowMetrics_SkipsTextDateNoValue(t *testing.T)
41
require.NoError(t, err)
42
assert.Empty(t, metrics)
43
}
44
+
45
+func TestTableRowProcessor_ExtractIndexPositionFromEnd(t *testing.T) {
46
+ tests := map[string]struct {
47
+ index string
48
+ position uint
49
+ want string
50
+ wantOK bool
51
+ }{
52
+ "last component": {
53
+ index: "1.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.2.1",
54
+ position: 1,
55
+ want: "1",
56
+ wantOK: true,
57
+ },
58
+ "second from end": {
59
+ index: "1.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.2.1",
60
+ position: 2,
61
+ want: "2",
62
+ wantOK: true,
63
+ },
64
+ "first component from full length": {
65
+ index: "7.8.9",
66
+ position: 3,
67
+ want: "7",
68
+ wantOK: true,
69
+ },
70
+ "single component": {
71
+ index: "7",
72
+ position: 1,
73
+ want: "7",
74
+ wantOK: true,
75
+ },
76
+ "position too large": {
77
+ index: "7.8.9",
78
+ position: 4,
79
+ },
80
+ "zero position": {
81
+ index: "7.8.9",
82
+ },
83
+ "empty index": {
84
+ position: 1,
85
+ },
86
+ }
87
+
88
+ p := newTableRowProcessor(logger.New())
89
+ for name, tc := range tests {
90
+ t.Run(name, func(t *testing.T) {
91
+ got, ok := p.extractIndexPositionFromEnd(tc.index, tc.position)
92
+ assert.Equal(t, tc.wantOK, ok)
93
+ assert.Equal(t, tc.want, got)
94
+ })
95
+ }
96
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/arista_eos_bgp_peer_table.snmprec
new
+14
@@ -0,0 +1,14 @@
1
+1.3.6.1.4.1.30065.4.1.1.2.1.3.1.1.4.192.168.0.2|4x|C0A80001
2
+1.3.6.1.4.1.30065.4.1.1.2.1.10.1.1.4.192.168.0.2|66|65000
3
+1.3.6.1.4.1.30065.4.1.1.2.1.12.1.1.4.192.168.0.2|2|2
4
+1.3.6.1.4.1.30065.4.1.1.2.1.13.1.1.4.192.168.0.2|2|6
5
+1.3.6.1.4.1.30065.4.1.1.2.1.14.1.1.4.192.168.0.2|4|peer.example.com EBGP
6
+1.3.6.1.4.1.30065.4.1.1.3.1.1.1.1.4.192.168.0.2|66|0
7
+1.3.6.1.4.1.30065.4.1.1.3.1.2.1.1.4.192.168.0.2|66|0
8
+1.3.6.1.4.1.30065.4.1.1.4.1.1.1.1.4.192.168.0.2|66|13090321
9
+1.3.6.1.4.1.30065.4.1.1.7.1.1.1.1.4.192.168.0.2|65|13362513
10
+1.3.6.1.4.1.30065.4.1.1.7.1.2.1.1.4.192.168.0.2|65|13579799
11
+1.3.6.1.4.1.30065.4.1.1.7.1.3.1.1.4.192.168.0.2|65|13579799
12
+1.3.6.1.4.1.30065.4.1.1.7.1.4.1.1.4.192.168.0.2|65|17644316
13
+1.3.6.1.4.1.30065.4.1.1.8.1.3.1.1.4.192.168.0.2.1.1|66|540336
14
+1.3.6.1.4.1.30065.4.1.1.8.1.4.1.1.4.192.168.0.2.1.1|66|540339
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/cisco_iosxr_asr9001_bgp_peer2_prefix_table.snmprec
new
+16
@@ -0,0 +1,16 @@
1
+1.3.6.1.4.1.9.9.187.1.2.5.1.6.1.4.10.246.0.3|4x|0AF60001
2
+1.3.6.1.4.1.9.9.187.1.2.5.1.6.1.4.10.246.0.4|4x|00000000
3
+1.3.6.1.4.1.9.9.187.1.2.5.1.11.1.4.10.246.0.3|66|65056
4
+1.3.6.1.4.1.9.9.187.1.2.5.1.11.1.4.10.246.0.4|66|65056
5
+1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.10.246.0.3.1.128|4|VPNv4 Unicast
6
+1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.10.246.0.4.1.128|4|VPNv4 Unicast
7
+1.3.6.1.4.1.9.9.187.1.2.8.1.1.1.4.10.246.0.3.1.128|65|5
8
+1.3.6.1.4.1.9.9.187.1.2.8.1.1.1.4.10.246.0.4.1.128|65|0
9
+1.3.6.1.4.1.9.9.187.1.2.8.1.3.1.4.10.246.0.3.1.128|66|2097152
10
+1.3.6.1.4.1.9.9.187.1.2.8.1.3.1.4.10.246.0.4.1.128|66|2097152
11
+1.3.6.1.4.1.9.9.187.1.2.8.1.4.1.4.10.246.0.3.1.128|66|75
12
+1.3.6.1.4.1.9.9.187.1.2.8.1.4.1.4.10.246.0.4.1.128|66|75
13
+1.3.6.1.4.1.9.9.187.1.2.8.1.6.1.4.10.246.0.3.1.128|66|25
14
+1.3.6.1.4.1.9.9.187.1.2.8.1.6.1.4.10.246.0.4.1.128|66|0
15
+1.3.6.1.4.1.9.9.187.1.2.8.1.8.1.4.10.246.0.3.1.128|66|7
16
+1.3.6.1.4.1.9.9.187.1.2.8.1.8.1.4.10.246.0.4.1.128|66|0
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/cisco_iosxr_ncs540_bgp_peer2_prefix_table.snmprec
new
+18
@@ -0,0 +1,18 @@
1
+1.3.6.1.4.1.9.9.187.1.2.5.1.6.1.4.10.255.9.1|4x|0AFF091F
2
+1.3.6.1.4.1.9.9.187.1.2.5.1.6.1.4.10.255.9.2|4x|0AFF091F
3
+1.3.6.1.4.1.9.9.187.1.2.5.1.11.1.4.10.255.9.1|66|65011
4
+1.3.6.1.4.1.9.9.187.1.2.5.1.11.1.4.10.255.9.2|66|65011
5
+1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.10.255.9.1.1.1|4|IPv4 Unicast
6
+1.3.6.1.4.1.9.9.187.1.2.7.1.3.1.4.10.255.9.2.1.1|4|IPv4 Unicast
7
+1.3.6.1.4.1.9.9.187.1.2.8.1.1.1.4.10.255.9.1.1.1|65|30
8
+1.3.6.1.4.1.9.9.187.1.2.8.1.1.1.4.10.255.9.2.1.1|65|30
9
+1.3.6.1.4.1.9.9.187.1.2.8.1.3.1.4.10.255.9.1.1.1|66|4294967295
10
+1.3.6.1.4.1.9.9.187.1.2.8.1.3.1.4.10.255.9.2.1.1|66|4294967295
11
+1.3.6.1.4.1.9.9.187.1.2.8.1.4.1.4.10.255.9.1.1.1|66|75
12
+1.3.6.1.4.1.9.9.187.1.2.8.1.4.1.4.10.255.9.2.1.1|66|75
13
+1.3.6.1.4.1.9.9.187.1.2.8.1.5.1.4.10.255.9.1.1.1|66|75
14
+1.3.6.1.4.1.9.9.187.1.2.8.1.5.1.4.10.255.9.2.1.1|66|75
15
+1.3.6.1.4.1.9.9.187.1.2.8.1.6.1.4.10.255.9.1.1.1|66|8
16
+1.3.6.1.4.1.9.9.187.1.2.8.1.6.1.4.10.255.9.2.1.1|66|8
17
+1.3.6.1.4.1.9.9.187.1.2.8.1.8.1.4.10.255.9.1.1.1|66|0
18
+1.3.6.1.4.1.9.9.187.1.2.8.1.8.1.4.10.255.9.2.1.1|66|0
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/dell_os10_bgp_peer_table.snmprec
new
+21
@@ -0,0 +1,21 @@
1
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.3.1.1.4.169.254.247.1|4|169.254.247.2
2
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.7.1.1.4.169.254.247.1|66|64514
3
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.8.1.1.4.169.254.247.1|4|49.57.50.46.49.54.56.46.50.53.53.46.49.48
4
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.10.1.1.4.169.254.247.1|66|64513
5
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.11.1.1.4.169.254.247.1|4|53.52.46.50.52.48.46.50.48.53.46.50.51.51
6
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.12.1.1.4.169.254.247.1|2|1
7
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.13.1.1.4.169.254.247.1|2|6
8
+1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.14.1.1.4.169.254.247.1|4|DIRECT CONNECT
9
+1.3.6.1.4.1.674.11000.5000.200.1.1.3.1.1.1.1.4.169.254.247.1|66|0
10
+1.3.6.1.4.1.674.11000.5000.200.1.1.3.1.2.1.1.4.169.254.247.1|66|0
11
+1.3.6.1.4.1.674.11000.5000.200.1.1.3.1.3.1.1.4.169.254.247.1|67|0
12
+1.3.6.1.4.1.674.11000.5000.200.1.1.4.1.1.1.1.4.169.254.247.1|66|28063000
13
+1.3.6.1.4.1.674.11000.5000.200.1.1.4.1.2.1.1.4.169.254.247.1|66|27997600
14
+1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.1.1.1.4.169.254.247.1|65|17
15
+1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.2.1.1.4.169.254.247.1|65|4830
16
+1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.3.1.1.4.169.254.247.1|65|430177
17
+1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.4.1.1.4.169.254.247.1|65|489554
18
+1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.5.1.1.4.169.254.247.1|65|7
19
+1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.3.1.1.4.169.254.247.1.1.1|66|27
20
+1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.4.1.1.4.169.254.247.1.1.1|66|27
21
+1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.5.1.1.4.169.254.247.1.1.1|66|69
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/huawei_vrp_ne8000_bgp_peer_table.snmprec
new
+36
@@ -0,0 +1,36 @@
1
+1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6.0.1.1.1.4.10.45.2.2|4|Public
2
+1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|4|PublicV6
3
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1.0.1.1.1.4.10.45.2.2|66|4
4
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|66|4
5
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2.0.1.1.1.4.10.45.2.2|66|26479
6
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|66|26162
7
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4.0.1.1.1.4.10.45.2.2|4|10.45.2.2
8
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|4|2001:12F8::223:253
9
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.5.0.1.1.1.4.10.45.2.2|2|6
10
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.5.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|2|6
11
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.6.0.1.1.1.4.10.45.2.2|66|2
12
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.6.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|66|7
13
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.9.0.1.1.1.4.10.45.2.2|4x|0500
14
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.9.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|4x|0500
15
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.10.0.1.1.1.4.10.45.2.2|66|0
16
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.10.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|66|0
17
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.11.0.1.1.1.4.10.45.2.2|2|2
18
+1.3.6.1.4.1.2011.5.25.177.1.1.2.1.11.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|2|2
19
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.1.0.1.1.1.4.10.45.2.2|65|13970
20
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.1.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|0
21
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.2.0.1.1.1.4.10.45.2.2|65|1007
22
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.2.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|0
23
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.3.0.1.1.1.4.10.45.2.2|65|8
24
+1.3.6.1.4.1.2011.5.25.177.1.1.3.1.3.0.2.1.2.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|0
25
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.4.0.0.4.10.45.2.2|65|2
26
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.4.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|7
27
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.5.0.0.4.10.45.2.2|65|1
28
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.5.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|6
29
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.6.0.0.4.10.45.2.2|65|70063
30
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.6.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|3318661
31
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.7.0.0.4.10.45.2.2|65|971
32
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.7.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|6
33
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.8.0.0.4.10.45.2.2|65|99928
34
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.8.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|3337254
35
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.9.0.0.4.10.45.2.2|65|31448
36
+1.3.6.1.4.1.2011.5.25.177.1.1.7.1.9.0.0.16.32.1.18.248.0.0.0.0.0.0.0.0.2.35.2.83|65|19035
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/juniper_mx_bgp_peer_table.snmprec
new
+25
@@ -0,0 +1,25 @@
1
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.1.0.1.192.168.99.24.1.192.168.99.25|4x|C0A86319
2
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2.0.1.192.168.99.24.1.192.168.99.25|2|6
3
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.3.0.1.192.168.99.24.1.192.168.99.25|2|2
4
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.5.0.1.192.168.99.24.1.192.168.99.25|66|0
5
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.7.0.1.192.168.99.24.1.192.168.99.25|4x|C0A86318
6
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.9.0.1.192.168.99.24.1.192.168.99.25|66|64513
7
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.10.0.1.192.168.99.24.1.192.168.99.25|2|1
8
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.11.0.1.192.168.99.24.1.192.168.99.25|4x|C0A86319
9
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13.0.1.192.168.99.24.1.192.168.99.25|66|64513
10
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14.0.1.192.168.99.24.1.192.168.99.25|66|1
11
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.15.0.1.192.168.99.24.1.192.168.99.25|66|0
12
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.1.0.1.192.168.99.24.1.192.168.99.25|66|361781
13
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.2.0.1.192.168.99.24.1.192.168.99.25|66|49798
14
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.1.0.1.192.168.99.24.1.192.168.99.25|65|56
15
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.2.0.1.192.168.99.24.1.192.168.99.25|65|1
16
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.3.0.1.192.168.99.24.1.192.168.99.25|65|13199
17
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.4.0.1.192.168.99.24.1.192.168.99.25|65|13351
18
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7.1.1.1|66|20
19
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7.1.1.2|66|4
20
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7.1.25.65|66|0
21
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8.1.1.1|66|20
22
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8.1.1.2|66|4
23
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8.1.25.65|66|0
24
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.10.1.1.1|66|0
25
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.11.1.1.1|66|6
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/juniper_vmx_bgp_peer_table.snmprec
new
+48
@@ -0,0 +1,48 @@
1
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.1.9.1.10.1.1.2.1.10.1.1.1|4x|AC10000A
2
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.1.9.1.10.1.1.6.1.10.1.1.5|4x|AC10000B
3
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2.9.1.10.1.1.2.1.10.1.1.1|2|6
4
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2.9.1.10.1.1.6.1.10.1.1.5|2|6
5
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.3.9.1.10.1.1.2.1.10.1.1.1|2|2
6
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.3.9.1.10.1.1.6.1.10.1.1.5|2|2
7
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.5.9.1.10.1.1.2.1.10.1.1.1|66|0
8
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.5.9.1.10.1.1.6.1.10.1.1.5|66|0
9
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.7.9.1.10.1.1.2.1.10.1.1.1|4x|0A010102
10
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.7.9.1.10.1.1.6.1.10.1.1.5|4x|0A010106
11
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.9.9.1.10.1.1.2.1.10.1.1.1|66|64999
12
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.9.9.1.10.1.1.6.1.10.1.1.5|66|64999
13
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.10.9.1.10.1.1.2.1.10.1.1.1|2|1
14
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.10.9.1.10.1.1.6.1.10.1.1.5|2|1
15
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.11.9.1.10.1.1.2.1.10.1.1.1|4x|0A010101
16
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.11.9.1.10.1.1.6.1.10.1.1.5|4x|0A010105
17
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13.9.1.10.1.1.2.1.10.1.1.1|66|62371
18
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13.9.1.10.1.1.6.1.10.1.1.5|66|62371
19
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14.9.1.10.1.1.2.1.10.1.1.1|66|0
20
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14.9.1.10.1.1.6.1.10.1.1.5|66|1
21
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.15.9.1.10.1.1.2.1.10.1.1.1|66|9
22
+1.3.6.1.4.1.2636.5.1.1.2.1.1.1.15.9.1.10.1.1.6.1.10.1.1.5|66|9
23
+1.3.6.1.4.1.2636.5.1.1.2.2.1.1.1.9.1.10.1.1.2.1.10.1.1.1|4x|0000
24
+1.3.6.1.4.1.2636.5.1.1.2.2.1.1.1.9.1.10.1.1.6.1.10.1.1.5|4x|0000
25
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.1.9.1.10.1.1.2.1.10.1.1.1|66|12859
26
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.1.9.1.10.1.1.6.1.10.1.1.5|66|12855
27
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.2.9.1.10.1.1.2.1.10.1.1.1|66|12759
28
+1.3.6.1.4.1.2636.5.1.1.2.4.1.1.2.9.1.10.1.1.6.1.10.1.1.5|66|12757
29
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.1.9.1.10.1.1.2.1.10.1.1.1|65|2
30
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.1.9.1.10.1.1.6.1.10.1.1.5|65|2
31
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.2.9.1.10.1.1.2.1.10.1.1.1|65|2
32
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.2.9.1.10.1.1.6.1.10.1.1.5|65|2
33
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.3.9.1.10.1.1.2.1.10.1.1.1|65|468
34
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.3.9.1.10.1.1.6.1.10.1.1.5|65|470
35
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.4.9.1.10.1.1.2.1.10.1.1.1|65|478
36
+1.3.6.1.4.1.2636.5.1.1.2.6.1.1.4.9.1.10.1.1.6.1.10.1.1.5|65|477
37
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.1.0.1.1|2|1
38
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.1.1.1.1|2|1
39
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7.0.1.1|66|1
40
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7.1.1.1|66|1
41
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8.0.1.1|66|1
42
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8.1.1.1|66|1
43
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.9.0.1.1|66|0
44
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.9.1.1.1|66|0
45
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.10.0.1.1|66|2
46
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.10.1.1.1|66|2
47
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.11.0.1.1|66|1
48
+1.3.6.1.4.1.2636.5.1.1.2.6.2.1.11.1.1.1|66|1
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/pfsense_frr_bgp_peer_table.snmprec
new
+22
@@ -0,0 +1,22 @@
1
+1.3.6.1.2.1.15.3.1.2.169.254.1.1|2|6
2
+1.3.6.1.2.1.15.3.1.2.169.254.1.9|2|6
3
+1.3.6.1.2.1.15.3.1.3.169.254.1.1|2|2
4
+1.3.6.1.2.1.15.3.1.3.169.254.1.9|2|2
5
+1.3.6.1.2.1.15.3.1.5.169.254.1.1|64|169.254.1.2
6
+1.3.6.1.2.1.15.3.1.5.169.254.1.9|64|169.254.1.10
7
+1.3.6.1.2.1.15.3.1.9.169.254.1.1|2|-94967296
8
+1.3.6.1.2.1.15.3.1.9.169.254.1.9|2|-94967292
9
+1.3.6.1.2.1.15.3.1.10.169.254.1.1|65|6
10
+1.3.6.1.2.1.15.3.1.10.169.254.1.9|65|6
11
+1.3.6.1.2.1.15.3.1.11.169.254.1.1|65|14
12
+1.3.6.1.2.1.15.3.1.11.169.254.1.9|65|15
13
+1.3.6.1.2.1.15.3.1.12.169.254.1.1|65|8330
14
+1.3.6.1.2.1.15.3.1.12.169.254.1.9|65|8544
15
+1.3.6.1.2.1.15.3.1.13.169.254.1.1|65|8323
16
+1.3.6.1.2.1.15.3.1.13.169.254.1.9|65|8518
17
+1.3.6.1.2.1.15.3.1.14.169.254.1.1|4x|0400
18
+1.3.6.1.2.1.15.3.1.14.169.254.1.9|4x|0202
19
+1.3.6.1.2.1.15.3.1.16.169.254.1.1|66|96951
20
+1.3.6.1.2.1.15.3.1.16.169.254.1.9|66|97193
21
+1.3.6.1.2.1.15.3.1.24.169.254.1.1|66|96950
22
+1.3.6.1.2.1.15.3.1.24.169.254.1.9|66|97191
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/timos_7750_bgp_peer_table.snmprec
new
+30
@@ -0,0 +1,30 @@
1
+1.3.6.1.4.1.6527.3.1.2.3.1.1.4.1|4|Base
2
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.6.1.1.4.192.168.119.2|2|2
3
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.7.1.1.4.192.168.119.2|4|router.name
4
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.13.1.1.4.192.168.119.2|4x|3E287706
5
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.14.1.1.4.192.168.119.2|2|1
6
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.59.1.1.4.192.168.119.2|2|6
7
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.65.1.1.4.192.168.119.2|66|12345
8
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66.1.1.4.192.168.119.2|66|12345
9
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.5.1.1.4.192.168.119.2|65|13
10
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.6.1.1.4.192.168.119.2|65|660
11
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.7.1.1.4.192.168.119.2|66|6
12
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.13.1.1.4.192.168.119.2|65|43
13
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.14.1.1.4.192.168.119.2|65|175
14
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.15.1.1.4.192.168.119.2|66|25
15
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.40.1.1.4.192.168.119.2|65|18
16
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.41.1.1.4.192.168.119.2|65|44
17
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.42.1.1.4.192.168.119.2|66|13
18
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.49.1.1.4.192.168.119.2|65|2
19
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.50.1.1.4.192.168.119.2|65|0
20
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.51.1.1.4.192.168.119.2|65|0
21
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.88.1.1.4.192.168.119.2|65|0
22
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.89.1.1.4.192.168.119.2|65|0
23
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.90.1.1.4.192.168.119.2|65|0
24
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.98.1.1.4.192.168.119.2|65|0
25
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.99.1.1.4.192.168.119.2|65|0
26
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.100.1.1.4.192.168.119.2|65|0
27
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.101.1.1.4.192.168.119.2|65|0
28
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.103.1.1.4.192.168.119.2|65|0
29
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.112.1.1.4.192.168.119.2|65|0
30
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.177.1.1.4.192.168.119.2|66|4656225
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/testdata/librenms/timos_ixr_s_bgp_peer_table.snmprec
new
+25
@@ -0,0 +1,25 @@
1
+1.3.6.1.4.1.6527.3.1.2.3.1.1.4.1|4|Base
2
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.6.1.1.4.172.30.0.17|2|2
3
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.6.1.1.4.172.30.0.18|2|2
4
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.59.1.1.4.172.30.0.17|2|6
5
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.59.1.1.4.172.30.0.18|2|6
6
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.65.1.1.4.172.30.0.17|66|393227
7
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.65.1.1.4.172.30.0.18|66|393227
8
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66.1.1.4.172.30.0.17|66|393227
9
+1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66.1.1.4.172.30.0.18|66|393227
10
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.5.1.1.4.172.30.0.17|65|0
11
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.5.1.1.4.172.30.0.18|65|0
12
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.6.1.1.4.172.30.0.17|65|0
13
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.6.1.1.4.172.30.0.18|65|0
14
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.7.1.1.4.172.30.0.17|66|0
15
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.7.1.1.4.172.30.0.18|66|0
16
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.13.1.1.4.172.30.0.17|65|184
17
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.13.1.1.4.172.30.0.18|65|184
18
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.14.1.1.4.172.30.0.17|65|1
19
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.14.1.1.4.172.30.0.18|65|1
20
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.15.1.1.4.172.30.0.17|66|174
21
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.15.1.1.4.172.30.0.18|66|9
22
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.112.1.1.4.172.30.0.17|65|0
23
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.112.1.1.4.172.30.0.18|65|0
24
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.177.1.1.4.172.30.0.17|66|1800
25
+1.3.6.1.4.1.6527.3.1.2.14.4.8.1.177.1.1.4.172.30.0.18|66|1800
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/timos_bgp_external_fixture_test.go
new
+224
@@ -0,0 +1,224 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmpcollector
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/golang/mock/gomock"
9
+ "github.com/gosnmp/gosnmp"
10
+ snmpmock "github.com/gosnmp/gosnmp/mocks"
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/logger"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
17
+)
18
+
19
+func TestCollector_Collect_TiMOSBGP_FromLibreNMSFixtures(t *testing.T) {
20
+ tests := map[string]struct {
21
+ sysObjectID string
22
+ fixture string
23
+ validate func(t *testing.T, rows []ddsnmp.BGPRow)
24
+ }{
25
+ "7750 emits typed peer and peer-family rows": {
26
+ sysObjectID: "1.3.6.1.4.1.6527.1.3.17",
27
+ fixture: "librenms/timos_7750_bgp_peer_table.snmprec",
28
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
29
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
30
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "192.168.119.2"
31
+ })
32
+ assert.Equal(t, "_nokia-timetra-bgp.yaml", peer.OriginProfileID)
33
+ assert.Equal(t, "tBgpPeerNgTable", peer.Table)
34
+ assert.Equal(t, "Base", peer.Identity.RoutingInstance)
35
+ assert.Equal(t, "12345", peer.Identity.RemoteAS)
36
+ assert.Equal(t, "62.40.119.6", peer.Descriptors.LocalAddress)
37
+ assert.Equal(t, "12345", peer.Descriptors.LocalAS)
38
+ assert.Equal(t, "router.name", peer.Descriptors.Description)
39
+ require.True(t, peer.Admin.Enabled.Has)
40
+ assert.True(t, peer.Admin.Enabled.Value)
41
+ require.True(t, peer.State.Has)
42
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
43
+ assert.EqualValues(t, 4656225, peer.Connection.EstablishedUptime.Value)
44
+
45
+ unicast := requireTiMOSFamilyRow(t, rows, "192.168.119.2", ddprofiledefinition.BGPAddressFamilyIPv4, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast)
46
+ assert.Equal(t, "ipv4 unicast", unicast.Tags["address_family_name"])
47
+ assert.EqualValues(t, 13, unicast.Routes.Total.Received.Value)
48
+ assert.EqualValues(t, 660, unicast.Routes.Total.Advertised.Value)
49
+ assert.EqualValues(t, 6, unicast.Routes.Current.Active.Value)
50
+ assert.Equal(t, "router.name", unicast.Descriptors.Description)
51
+
52
+ vpnv4 := requireTiMOSFamilyRow(t, rows, "192.168.119.2", ddprofiledefinition.BGPAddressFamilyIPv4, ddprofiledefinition.BGPSubsequentAddressFamilyVPN)
53
+ assert.EqualValues(t, 43, vpnv4.Routes.Total.Received.Value)
54
+ assert.EqualValues(t, 25, vpnv4.Routes.Current.Active.Value)
55
+
56
+ vpnv6 := requireTiMOSFamilyRow(t, rows, "192.168.119.2", ddprofiledefinition.BGPAddressFamilyIPv6, ddprofiledefinition.BGPSubsequentAddressFamilyVPN)
57
+ assert.EqualValues(t, 18, vpnv6.Routes.Total.Received.Value)
58
+
59
+ vpls := requireTiMOSFamilyRow(t, rows, "192.168.119.2", ddprofiledefinition.BGPAddressFamilyL2VPN, ddprofiledefinition.BGPSubsequentAddressFamilyVPLS)
60
+ assert.EqualValues(t, 2, vpls.Routes.Total.Received.Value)
61
+
62
+ evpn := requireTiMOSFamilyRow(t, rows, "192.168.119.2", ddprofiledefinition.BGPAddressFamilyL2VPN, ddprofiledefinition.BGPSubsequentAddressFamilyEVPN)
63
+ assert.EqualValues(t, 0, evpn.Routes.Total.Rejected.Value)
64
+ },
65
+ },
66
+ "IXR-S emits typed peer and peer-family rows": {
67
+ sysObjectID: "1.3.6.1.4.1.6527.1.3.23",
68
+ fixture: "librenms/timos_ixr_s_bgp_peer_table.snmprec",
69
+ validate: func(t *testing.T, rows []ddsnmp.BGPRow) {
70
+ peer := requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
71
+ return row.Kind == ddprofiledefinition.BGPRowKindPeer && row.Identity.Neighbor == "172.30.0.17"
72
+ })
73
+ assert.Equal(t, "Base", peer.Identity.RoutingInstance)
74
+ assert.Equal(t, "393227", peer.Identity.RemoteAS)
75
+ require.True(t, peer.Admin.Enabled.Has)
76
+ assert.True(t, peer.Admin.Enabled.Value)
77
+ require.True(t, peer.State.Has)
78
+ assert.Equal(t, ddprofiledefinition.BGPPeerStateEstablished, peer.State.State)
79
+ assert.EqualValues(t, 1800, peer.Connection.EstablishedUptime.Value)
80
+
81
+ unicast := requireTiMOSFamilyRow(t, rows, "172.30.0.17", ddprofiledefinition.BGPAddressFamilyIPv4, ddprofiledefinition.BGPSubsequentAddressFamilyUnicast)
82
+ assert.Equal(t, "ipv4 unicast", unicast.Tags["address_family_name"])
83
+ assert.EqualValues(t, 0, unicast.Routes.Total.Received.Value)
84
+
85
+ vpnv4 := requireTiMOSFamilyRow(t, rows, "172.30.0.17", ddprofiledefinition.BGPAddressFamilyIPv4, ddprofiledefinition.BGPSubsequentAddressFamilyVPN)
86
+ assert.EqualValues(t, 184, vpnv4.Routes.Total.Received.Value)
87
+ assert.EqualValues(t, 1, vpnv4.Routes.Total.Advertised.Value)
88
+ assert.EqualValues(t, 174, vpnv4.Routes.Current.Active.Value)
89
+
90
+ secondPeerVPNv4 := requireTiMOSFamilyRow(t, rows, "172.30.0.18", ddprofiledefinition.BGPAddressFamilyIPv4, ddprofiledefinition.BGPSubsequentAddressFamilyVPN)
91
+ assert.EqualValues(t, 9, secondPeerVPNv4.Routes.Current.Active.Value)
92
+
93
+ evpn := requireTiMOSFamilyRow(t, rows, "172.30.0.17", ddprofiledefinition.BGPAddressFamilyL2VPN, ddprofiledefinition.BGPSubsequentAddressFamilyEVPN)
94
+ assert.EqualValues(t, 0, evpn.Routes.Total.Rejected.Value)
95
+ },
96
+ },
97
+ }
98
+
99
+ for name, tc := range tests {
100
+ t.Run(name, func(t *testing.T) {
101
+ profile := matchedBGPProjectionByFile(t, tc.sysObjectID, "nokia-service-router-os.yaml")
102
+ pdus := loadSnmprecPDUs(t, tc.fixture)
103
+
104
+ ctrl, mockHandler := setupMockHandler(t)
105
+ defer ctrl.Finish()
106
+
107
+ expectTiMOSMetadataGets(mockHandler, tc.sysObjectID)
108
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
109
+
110
+ collector := New(Config{
111
+ SnmpClient: mockHandler,
112
+ Profiles: []*ddsnmp.Profile{profile},
113
+ Log: logger.New(),
114
+ SysObjectID: tc.sysObjectID,
115
+ })
116
+
117
+ results, err := collector.Collect()
118
+ require.NoError(t, err)
119
+ require.Len(t, results, 1)
120
+ require.Empty(t, results[0].Metrics)
121
+ require.Empty(t, results[0].TopologyMetrics)
122
+ require.Empty(t, results[0].LicenseRows)
123
+ require.NotEmpty(t, results[0].BGPRows)
124
+
125
+ tc.validate(t, results[0].BGPRows)
126
+ })
127
+ }
128
+}
129
+
130
+func TestCollector_Collect_TiMOSBGP_StateAdminAndRoutingInstance(t *testing.T) {
131
+ tests := map[string]struct {
132
+ rowIndex string
133
+ instanceID string
134
+ routingInstance string
135
+ neighbor string
136
+ shutdown int
137
+ wantAdmin bool
138
+ state int
139
+ wantState ddprofiledefinition.BGPPeerState
140
+ }{
141
+ "admin disabled active peer in non-default VRF": {
142
+ rowIndex: "2.1.4.192.0.2.21",
143
+ instanceID: "2",
144
+ routingInstance: "vprn111",
145
+ neighbor: "192.0.2.21",
146
+ shutdown: 1,
147
+ wantAdmin: false,
148
+ state: 3,
149
+ wantState: ddprofiledefinition.BGPPeerStateActive,
150
+ },
151
+ "idle peer in second non-default VRF": {
152
+ rowIndex: "3.1.4.192.0.2.22",
153
+ instanceID: "3",
154
+ routingInstance: "vprn222",
155
+ neighbor: "192.0.2.22",
156
+ shutdown: 2,
157
+ wantAdmin: true,
158
+ state: 1,
159
+ wantState: ddprofiledefinition.BGPPeerStateIdle,
160
+ },
161
+ }
162
+
163
+ for name, tc := range tests {
164
+ t.Run(name, func(t *testing.T) {
165
+ profile := matchedBGPProjectionByFile(t, "1.3.6.1.4.1.6527.1.3.17", "nokia-service-router-os.yaml")
166
+ filterProfileForTypedBGPByID(t, profile, "nokia-timos-bgp-peer")
167
+
168
+ pdus := []gosnmp.SnmpPDU{
169
+ createStringPDU(oidWithIndex("1.3.6.1.4.1.6527.3.1.2.3.1.1.4", tc.instanceID), tc.routingInstance),
170
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6527.3.1.2.14.4.7.1.6", tc.rowIndex), tc.shutdown),
171
+ createIntegerPDU(oidWithIndex("1.3.6.1.4.1.6527.3.1.2.14.4.7.1.59", tc.rowIndex), tc.state),
172
+ createGauge32PDU(oidWithIndex("1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66", tc.rowIndex), 65001),
173
+ }
174
+
175
+ ctrl, mockHandler := setupMockHandler(t)
176
+ defer ctrl.Finish()
177
+
178
+ expectTiMOSMetadataGets(mockHandler, "1.3.6.1.4.1.6527.1.3.17")
179
+ expectBGPTableWalksFromFixture(mockHandler, profile, pdus)
180
+
181
+ collector := New(Config{
182
+ SnmpClient: mockHandler,
183
+ Profiles: []*ddsnmp.Profile{profile},
184
+ Log: logger.New(),
185
+ SysObjectID: "1.3.6.1.4.1.6527.1.3.17",
186
+ })
187
+
188
+ results, err := collector.Collect()
189
+ require.NoError(t, err)
190
+ require.Len(t, results, 1)
191
+ require.Empty(t, results[0].Metrics)
192
+ require.Len(t, results[0].BGPRows, 1)
193
+
194
+ row := requireBGPRowMatching(t, results[0].BGPRows, func(row ddsnmp.BGPRow) bool {
195
+ return row.Identity.Neighbor == tc.neighbor
196
+ })
197
+ assert.Equal(t, tc.routingInstance, row.Identity.RoutingInstance)
198
+ assert.Equal(t, "65001", row.Identity.RemoteAS)
199
+ require.True(t, row.Admin.Enabled.Has)
200
+ assert.Equal(t, tc.wantAdmin, row.Admin.Enabled.Value)
201
+ require.True(t, row.State.Has)
202
+ assert.Equal(t, tc.wantState, row.State.State)
203
+ })
204
+ }
205
+}
206
+
207
+func expectTiMOSMetadataGets(mockHandler *snmpmock.MockHandler, sysObjectID string) {
208
+ mockHandler.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{Variables: []gosnmp.SnmpPDU{
209
+ createStringPDU("1.3.6.1.2.1.1.1.0", "Nokia SR OS"),
210
+ createPDU("1.3.6.1.2.1.1.2.0", gosnmp.ObjectIdentifier, sysObjectID),
211
+ createStringPDU("1.3.6.1.2.1.1.5.0", "timos"),
212
+ }}, nil).AnyTimes()
213
+}
214
+
215
+func requireTiMOSFamilyRow(t *testing.T, rows []ddsnmp.BGPRow, neighbor string, af ddprofiledefinition.BGPAddressFamily, safi ddprofiledefinition.BGPSubsequentAddressFamily) ddsnmp.BGPRow {
216
+ t.Helper()
217
+
218
+ return requireBGPRowMatching(t, rows, func(row ddsnmp.BGPRow) bool {
219
+ return row.Kind == ddprofiledefinition.BGPRowKindPeerFamily &&
220
+ row.Identity.Neighbor == neighbor &&
221
+ row.Identity.AddressFamily == af &&
222
+ row.Identity.SubsequentAddressFamily == safi
223
+ })
224
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/huawei_bgp_profile_test.go
new
+85
@@ -0,0 +1,85 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "strings"
7
+ "testing"
8
+
9
+ "github.com/stretchr/testify/assert"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
13
+)
14
+
15
+func Test_HuaweiBGPProfileUsesTypedRows(t *testing.T) {
16
+ matched := FindProfiles("1.3.6.1.4.1.2011.2.224.279", "", nil)
17
+ var profile *Profile
18
+ for _, candidate := range matched {
19
+ if strings.HasSuffix(candidate.SourceFile, "huawei-routers.yaml") {
20
+ profile = candidate
21
+ break
22
+ }
23
+ }
24
+ require.NotNil(t, profile, "expected huawei-routers.yaml profile to match")
25
+
26
+ require.Len(t, profile.Definition.BGP, 5)
27
+ assertNoMetricTable(t, profile, "hwBgpPeerTable")
28
+ assertNoMetricTable(t, profile, "hwBgpPeerRouteTable")
29
+ assertNoMetricTable(t, profile, "hwBgpPeerMessageTable")
30
+ assertNoMetricTable(t, profile, "hwBgpPeerStatisticTable")
31
+ assertNoVirtualMetric(t, profile, "bgpPeerAvailability")
32
+ assertNoVirtualMetric(t, profile, "bgpPeerUpdates")
33
+ assertNoVirtualMetric(t, profile, "bgpPeerFsmEstablishedTransitions")
34
+
35
+ device := requireBGPRowByID(t, profile, "huawei-bgp-device-counts")
36
+ assert.Equal(t, ddprofiledefinition.BGPRowKindDevice, device.Kind)
37
+ assert.Equal(t, "huawei.hwBgpPeerSessionNum", device.Device.Peers.Symbol.Name)
38
+ assert.Equal(t, "huawei.hwIBgpPeerSessionNum", device.Device.InternalPeers.Symbol.Name)
39
+ assert.Equal(t, "huawei.hwEBgpPeerSessionNum", device.Device.ExternalPeers.Symbol.Name)
40
+
41
+ peerFamily := requireBGPRowByID(t, profile, "huawei-bgp-peer-family")
42
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, peerFamily.Kind)
43
+ assert.Equal(t, "hwBgpPeerTable", peerFamily.Table.Name)
44
+ assert.Equal(t, "hwBgpPeerAddrFamilyTable", peerFamily.Identity.RoutingInstance.Table)
45
+ assert.Equal(t, "huawei.hwBgpPeerRemoteAddr", peerFamily.Identity.Neighbor.Symbol.Name)
46
+ assert.Equal(t, map[string]string{"1": "stop", "2": "start"}, peerFamily.Admin.Enabled.Symbol.Mapping.Items)
47
+ assert.Equal(t, map[string]string{
48
+ "1": "idle",
49
+ "2": "connect",
50
+ "3": "active",
51
+ "4": "opensent",
52
+ "5": "openconfirm",
53
+ "6": "established",
54
+ }, peerFamily.State.Symbol.Mapping.Items)
55
+
56
+ rows := map[string]struct {
57
+ id string
58
+ kind ddprofiledefinition.BGPRowKind
59
+ table string
60
+ }{
61
+ "routes": {
62
+ id: "huawei-bgp-peer-family-routes",
63
+ kind: ddprofiledefinition.BGPRowKindPeerFamily,
64
+ table: "hwBgpPeerRouteTable",
65
+ },
66
+ "messages": {
67
+ id: "huawei-bgp-peer-family-messages",
68
+ kind: ddprofiledefinition.BGPRowKindPeerFamily,
69
+ table: "hwBgpPeerMessageTable",
70
+ },
71
+ "statistics": {
72
+ id: "huawei-bgp-peer-statistics",
73
+ kind: ddprofiledefinition.BGPRowKindPeer,
74
+ table: "hwBgpPeerStatisticTable",
75
+ },
76
+ }
77
+
78
+ for name, tc := range rows {
79
+ t.Run(name, func(t *testing.T) {
80
+ row := requireBGPRowByID(t, profile, tc.id)
81
+ assert.Equal(t, tc.kind, row.Kind)
82
+ assert.Equal(t, tc.table, row.Table.Name)
83
+ })
84
+ }
85
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/juniper_bgp_profile_test.go
new
+84
@@ -0,0 +1,84 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/pkg/multipath"
15
+)
16
+
17
+func Test_JuniperBGPProfilesUseOnlyJuniperPeerTables(t *testing.T) {
18
+ dir, err := filepath.Abs("../../../config/go.d/snmp.profiles/default")
19
+ require.NoError(t, err)
20
+
21
+ profiles, err := loadProfilesFromDir(dir, multipath.New(dir))
22
+ require.NoError(t, err)
23
+ require.NotEmpty(t, profiles)
24
+
25
+ tests := map[string]struct {
26
+ sysObjectID string
27
+ profileFile string
28
+ }{
29
+ "generic Juniper": {sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.9999", profileFile: "juniper.yaml"},
30
+ "Juniper MX": {sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.21", profileFile: "juniper-mx.yaml"},
31
+ "Juniper EX": {sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.30", profileFile: "juniper-ex.yaml"},
32
+ "Juniper QFX": {sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.82", profileFile: "juniper-qfx.yaml"},
33
+ "Juniper SRX": {sysObjectID: "1.3.6.1.4.1.2636.1.1.1.2.26", profileFile: "juniper-srx.yaml"},
34
+ }
35
+
36
+ for name, tc := range tests {
37
+ t.Run(name, func(t *testing.T) {
38
+ matched := FindProfiles(tc.sysObjectID, "", nil)
39
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
40
+ return strings.HasSuffix(p.SourceFile, tc.profileFile)
41
+ })
42
+ require.NotEqual(t, -1, index, "expected %s profile to match", tc.profileFile)
43
+
44
+ profile := matched[index]
45
+ assertNoMetricTable(t, profile, "bgpPeerTable")
46
+ assertNoMetricTable(t, profile, "jnxBgpM2PeerTable")
47
+ assertNoMetricTable(t, profile, "jnxBgpM2PeerCountersTable")
48
+ assertNoVirtualMetric(t, profile, "bgpPeerAvailability")
49
+ assertNoVirtualMetric(t, profile, "bgpPeerUpdates")
50
+ assertBGPTableForRowID(t, profile, "juniper-bgp-peer", "jnxBgpM2PeerTable")
51
+ assertBGPTableForRowID(t, profile, "juniper-bgp-peer-family", "jnxBgpM2PrefixCountersTable")
52
+ assertBGPSixStateMapping(t, requireBGPRowByID(t, profile, "juniper-bgp-peer").State)
53
+ })
54
+ }
55
+}
56
+
57
+func assertNoMetricTable(t *testing.T, profile *Profile, tableName string) {
58
+ t.Helper()
59
+
60
+ for _, metric := range profile.Definition.Metrics {
61
+ assert.NotEqual(t, tableName, metric.Table.Name, "did not expect %s in %s", tableName, profile.SourceFile)
62
+ }
63
+}
64
+
65
+func assertNoVirtualMetric(t *testing.T, profile *Profile, name string) {
66
+ t.Helper()
67
+
68
+ for _, metric := range profile.Definition.VirtualMetrics {
69
+ assert.NotEqual(t, name, metric.Name, "did not expect virtual metric %s in %s", name, profile.SourceFile)
70
+ }
71
+}
72
+
73
+func assertBGPTableForRowID(t *testing.T, profile *Profile, rowID, wantTable string) {
74
+ t.Helper()
75
+
76
+ var tables []string
77
+ for _, row := range profile.Definition.BGP {
78
+ if row.ID == rowID {
79
+ tables = append(tables, row.Table.Name)
80
+ }
81
+ }
82
+
83
+ require.Equal(t, []string{wantTable}, tables, "unexpected BGP tables for %s in %s", rowID, profile.SourceFile)
84
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/load.go
+15
-2
@@ -117,7 +117,7 @@ func loadProfilesFromDir(dirpath string, extendsPaths multipath.MultiPath) ([]*P
117
if err != nil {
118
return err
119
}
120
- if !(strings.HasSuffix(d.Name(), ".yaml") || strings.HasSuffix(d.Name(), ".yml")) {
120
+ if !strings.HasSuffix(d.Name(), ".yaml") && !strings.HasSuffix(d.Name(), ".yml") {
121
return nil
122
}
123
// Skip abstract profiles
@@ -163,7 +163,9 @@ func loadProfileWithExtendsMap(filename string, extendsPaths multipath.MultiPath
163
if prof.SourceFile == "" {
164
prof.SourceFile, _ = filepath.Abs(filename)
165
}
166
- setLicensingOriginProfileID(&prof, profileOriginID(filename, extendsPaths))
166
+ originID := profileOriginID(filename, extendsPaths)
167
+ setLicensingOriginProfileID(&prof, originID)
168
+ setBGPOriginProfileID(&prof, originID)
169
170
// Handle empty profiles - these are profiles where content has been deliberately removed,
171
// but the file itself is preserved. This ensures that when users update, their existing
@@ -222,6 +224,17 @@ func setLicensingOriginProfileID(prof *Profile, originID string) {
224
}
225
}
226
227
+func setBGPOriginProfileID(prof *Profile, originID string) {
228
+ if prof == nil || prof.Definition == nil {
229
+ return
230
+ }
231
+ for i := range prof.Definition.BGP {
232
+ if prof.Definition.BGP[i].OriginProfileID == "" {
233
+ prof.Definition.BGP[i].OriginProfileID = originID
234
+ }
235
+ }
236
+}
237
+
238
func profileOriginID(filename string, paths multipath.MultiPath) string {
239
absFile, err := filepath.Abs(filename)
240
if err != nil {
src/go/plugin/go.d/collector/snmp/ddsnmp/metric.go
+181
-3
@@ -13,6 +13,8 @@ type ProfileMetrics struct {
13
Metrics []Metric
14
TopologyMetrics []Metric
15
LicenseRows []LicenseRow
16
+ BGPRows []BGPRow
17
+ BGPCollectError error
18
HiddenMetrics []Metric
19
Stats CollectionStats
20
}
@@ -93,6 +95,177 @@ type LicenseUsage struct {
95
Percent int64
96
}
97
98
+type BGPRow struct {
99
+ OriginProfileID string
100
+ Kind ddprofiledefinition.BGPRowKind
101
+ TableOID string
102
+ Table string
103
+ RowKey string
104
+ StructuralID string
105
+
106
+ Identity BGPIdentity
107
+ Descriptors BGPDescriptors
108
+ Admin BGPAdmin
109
+ State BGPState
110
+ Previous BGPState
111
+ Connection BGPConnection
112
+ Traffic BGPTraffic
113
+ Transitions BGPTransitions
114
+ Timers BGPTimers
115
+ LastError BGPLastError
116
+ LastNotify BGPLastNotifications
117
+ Reasons BGPReasons
118
+ Restart BGPGracefulRestart
119
+ Routes BGPRoutes
120
+ RouteLimits BGPRouteLimits
121
+ Device BGPDeviceCounts
122
+ Tags map[string]string
123
+}
124
+
125
+type BGPIdentity struct {
126
+ RoutingInstance string
127
+ Neighbor string
128
+ RemoteAS string
129
+ AddressFamily ddprofiledefinition.BGPAddressFamily
130
+ SubsequentAddressFamily ddprofiledefinition.BGPSubsequentAddressFamily
131
+}
132
+
133
+type BGPDescriptors struct {
134
+ LocalAddress string
135
+ LocalAS string
136
+ LocalIdentifier string
137
+ PeerIdentifier string
138
+ PeerType string
139
+ BGPVersion string
140
+ Description string
141
+}
142
+
143
+type BGPState struct {
144
+ Has bool
145
+ State ddprofiledefinition.BGPPeerState
146
+ Raw string
147
+ SourceOID string
148
+}
149
+
150
+type BGPAdmin struct {
151
+ Enabled BGPBool
152
+}
153
+
154
+type BGPInt64 struct {
155
+ Has bool
156
+ Value int64
157
+ Raw string
158
+ SourceOID string
159
+}
160
+
161
+type BGPText struct {
162
+ Has bool
163
+ Value string
164
+ Raw string
165
+ SourceOID string
166
+}
167
+
168
+type BGPBool struct {
169
+ Has bool
170
+ Value bool
171
+ Raw string
172
+ SourceOID string
173
+}
174
+
175
+type BGPConnection struct {
176
+ EstablishedUptime BGPInt64
177
+ LastReceivedUpdateAge BGPInt64
178
+}
179
+
180
+type BGPDirectional struct {
181
+ Received BGPInt64
182
+ Sent BGPInt64
183
+}
184
+
185
+type BGPTraffic struct {
186
+ Messages BGPDirectional
187
+ Updates BGPDirectional
188
+ Notifications BGPDirectional
189
+ RouteRefreshes BGPDirectional
190
+ Opens BGPDirectional
191
+ Keepalives BGPDirectional
192
+}
193
+
194
+type BGPTransitions struct {
195
+ Established BGPInt64
196
+ Down BGPInt64
197
+ Up BGPInt64
198
+ Flaps BGPInt64
199
+}
200
+
201
+type BGPTimers struct {
202
+ Negotiated BGPTimerPair
203
+ Configured BGPTimerPair
204
+}
205
+
206
+type BGPTimerPair struct {
207
+ ConnectRetry BGPInt64
208
+ HoldTime BGPInt64
209
+ KeepaliveTime BGPInt64
210
+ MinASOriginationInterval BGPInt64
211
+ MinRouteAdvertisementInterval BGPInt64
212
+}
213
+
214
+type BGPLastError struct {
215
+ Code BGPInt64
216
+ Subcode BGPInt64
217
+ Text string
218
+}
219
+
220
+type BGPLastNotification struct {
221
+ Code BGPInt64
222
+ Subcode BGPInt64
223
+ Reason BGPText
224
+}
225
+
226
+type BGPLastNotifications struct {
227
+ Received BGPLastNotification
228
+ Sent BGPLastNotification
229
+}
230
+
231
+type BGPReasons struct {
232
+ LastDown BGPText
233
+ Unavailability BGPText
234
+}
235
+
236
+type BGPGracefulRestart struct {
237
+ State BGPText
238
+}
239
+
240
+type BGPRoutes struct {
241
+ Current BGPRouteCounters
242
+ Total BGPRouteCounters
243
+}
244
+
245
+type BGPRouteCounters struct {
246
+ Received BGPInt64
247
+ Accepted BGPInt64
248
+ Rejected BGPInt64
249
+ Active BGPInt64
250
+ Advertised BGPInt64
251
+ Suppressed BGPInt64
252
+ Withdrawn BGPInt64
253
+}
254
+
255
+type BGPRouteLimits struct {
256
+ Limit BGPInt64
257
+ Threshold BGPInt64
258
+ ClearThreshold BGPInt64
259
+}
260
+
261
+type BGPDeviceCounts struct {
262
+ Peers BGPInt64
263
+ InternalPeers BGPInt64
264
+ ExternalPeers BGPInt64
265
+ ByState map[ddprofiledefinition.BGPPeerState]int64
266
+ ByStateHas bool
267
+}
268
+
269
// CollectionStats contains statistics for a single profile collection cycle.
270
type CollectionStats struct {
271
Timing TimingStats
@@ -110,12 +283,14 @@ type TimingStats struct {
283
Table time.Duration
284
// Licensing is time spent collecting typed licensing rows.
285
Licensing time.Duration
286
+ // BGP is time spent collecting typed BGP rows.
287
+ BGP time.Duration
288
// VirtualMetrics is time spent computing derived/aggregated metrics.
289
VirtualMetrics time.Duration
290
}
291
292
func (s TimingStats) Total() time.Duration {
118
- return s.Scalar + s.Table + s.Licensing + s.VirtualMetrics
293
+ return s.Scalar + s.Table + s.Licensing + s.BGP + s.VirtualMetrics
294
}
295
296
// SNMPOperationStats captures SNMP protocol-level operations.
@@ -144,11 +319,13 @@ type MetricCountStats struct {
319
Virtual int64
320
// Licensing is the count of typed licensing rows produced.
321
Licensing int64
322
+ // BGP is the count of typed BGP rows produced.
323
+ BGP int64
324
// Tables is the count of unique regular metric tables. Typed licensing
148
- // rows are counted separately in Licensing.
325
+ // and BGP rows are counted separately.
326
Tables int64
327
// Rows is the total number of regular metric table rows. Typed licensing
151
- // rows are counted separately in Licensing.
328
+ // and BGP rows are counted separately.
329
Rows int64
330
}
331
@@ -169,6 +346,7 @@ type ErrorStats struct {
346
Scalar int64
347
Table int64
348
Licensing int64
349
+ BGP int64
350
}
351
// MissingOIDs is the count of NoSuchObject/NoSuchName responses.
352
MissingOIDs int64
src/go/plugin/go.d/collector/snmp/ddsnmp/nokia_bgp_profile_test.go
new
+223
@@ -0,0 +1,223 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package ddsnmp
4
+
5
+import (
6
+ "path/filepath"
7
+ "slices"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+
14
+ "github.com/netdata/netdata/go/plugins/pkg/multipath"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
16
+)
17
+
18
+func Test_NokiaBGPProfileMergedIntoNokiaSROS(t *testing.T) {
19
+ profile := mustLoadNokiaSROSBGPProfile(t)
20
+
21
+ require.Len(t, profile.Definition.BGP, 7)
22
+ assert.True(t, profile.HasExtension("_nokia-timetra-bgp.yaml"))
23
+ assert.False(t, profile.HasExtension("_std-bgp4-mib.yaml"))
24
+
25
+ peer := requireBGPRowByID(t, profile, "nokia-timos-bgp-peer")
26
+ assert.Equal(t, "_nokia-timetra-bgp.yaml", peer.OriginProfileID)
27
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeer, peer.Kind)
28
+ assert.Equal(t, "tBgpPeerNgTable", peer.Table.Name)
29
+ assert.Equal(t, "1.3.6.1.4.1.6527.3.1.2.14.4.7", peer.Table.OID)
30
+ assert.Equal(t, "vRtrConfTable", peer.Identity.RoutingInstance.Table)
31
+ assert.Equal(t, "tBgpPeerNgAddress", peer.Identity.Neighbor.Name)
32
+ assert.Equal(t, "tBgpPeerNgPeerAS4Byte", peer.Identity.RemoteAS.Symbol.Name)
33
+ assert.Equal(t, "tBgpPeerNgOperTable", peer.Connection.EstablishedUptime.Table)
34
+ assert.Equal(t, "tBgpPeerNgOperTable", peer.Traffic.Updates.Received.Table)
35
+ assert.Equal(t, "tBgpPeerNgOperTable", peer.LastError.Code.Table)
36
+ assert.Equal(t, map[string]string{"1": "stop", "2": "start"}, peer.Admin.Enabled.Symbol.Mapping.Items)
37
+ assert.Equal(t, map[string]string{
38
+ "1": "idle",
39
+ "2": "connect",
40
+ "3": "active",
41
+ "4": "opensent",
42
+ "5": "openconfirm",
43
+ "6": "established",
44
+ }, peer.State.Symbol.Mapping.Items)
45
+
46
+ values := map[string]struct {
47
+ value ddprofiledefinition.BGPValueConfig
48
+ table string
49
+ symbol string
50
+ }{
51
+ "peer_identifier": {
52
+ value: peer.Descriptors.PeerIdentifier,
53
+ table: "tBgpPeerNgOperTable",
54
+ symbol: "bgpPeerIdentifier",
55
+ },
56
+ "last_received_update_age": {
57
+ value: peer.Connection.LastReceivedUpdateAge,
58
+ table: "tBgpPeerNgOperTable",
59
+ symbol: "bgpPeerInUpdateElapsedTime",
60
+ },
61
+ "messages.received": {
62
+ value: peer.Traffic.Messages.Received,
63
+ table: "tBgpPeerNgOperTable",
64
+ symbol: "bgpPeerInTotalMessages",
65
+ },
66
+ "messages.sent": {
67
+ value: peer.Traffic.Messages.Sent,
68
+ table: "tBgpPeerNgOperTable",
69
+ symbol: "bgpPeerOutTotalMessages",
70
+ },
71
+ "notifications.received": {
72
+ value: peer.Traffic.Notifications.Received,
73
+ table: "tBgpPeerNgOperTable",
74
+ symbol: "bgpPeerInNotifications",
75
+ },
76
+ "notifications.sent": {
77
+ value: peer.Traffic.Notifications.Sent,
78
+ table: "tBgpPeerNgOperTable",
79
+ symbol: "bgpPeerOutNotifications",
80
+ },
81
+ "transitions.flaps": {
82
+ value: peer.Transitions.Flaps,
83
+ table: "tBgpPeerNgOperTable",
84
+ symbol: "tBgpPeerNgOperFlaps",
85
+ },
86
+ "timers.configured.connect_retry": {
87
+ value: peer.Timers.Configured.ConnectRetry,
88
+ symbol: "tBgpPeerNgConnectRetry",
89
+ },
90
+ "timers.configured.hold_time": {
91
+ value: peer.Timers.Configured.HoldTime,
92
+ symbol: "tBgpPeerNgHoldTime",
93
+ },
94
+ "timers.configured.keepalive_time": {
95
+ value: peer.Timers.Configured.KeepaliveTime,
96
+ symbol: "tBgpPeerNgKeepAlive",
97
+ },
98
+ "timers.configured.min_route_advertisement_interval": {
99
+ value: peer.Timers.Configured.MinRouteAdvertisementInterval,
100
+ symbol: "tBgpPeerNgMinRouteAdvertisement",
101
+ },
102
+ "last_error.subcode": {
103
+ value: peer.LastError.Subcode,
104
+ table: "tBgpPeerNgOperTable",
105
+ symbol: "bgpPeerLastErrorSubcode",
106
+ },
107
+ }
108
+
109
+ for name, tc := range values {
110
+ t.Run(name, func(t *testing.T) {
111
+ assert.Equal(t, tc.table, tc.value.Table)
112
+ assert.Equal(t, tc.symbol, tc.value.Symbol.Name)
113
+ })
114
+ }
115
+
116
+ tests := map[string]struct {
117
+ id string
118
+ af ddprofiledefinition.BGPAddressFamily
119
+ safi ddprofiledefinition.BGPSubsequentAddressFamily
120
+ label string
121
+ recv string
122
+ active string
123
+ reject string
124
+ }{
125
+ "ipv4 unicast": {
126
+ id: "nokia-timos-bgp-peer-family-ipv4-unicast",
127
+ af: ddprofiledefinition.BGPAddressFamilyIPv4,
128
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyUnicast,
129
+ label: "ipv4 unicast",
130
+ recv: "tBgpPeerNgOperReceivedPrefixes",
131
+ active: "tBgpPeerNgOperActivePrefixes",
132
+ reject: "tBgpPeerNgOperIpv4RejPfxs",
133
+ },
134
+ "ipv4 vpn": {
135
+ id: "nokia-timos-bgp-peer-family-ipv4-vpn",
136
+ af: ddprofiledefinition.BGPAddressFamilyIPv4,
137
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyVPN,
138
+ label: "vpnv4 unicast",
139
+ recv: "tBgpPeerNgOperVpnRecvPrefixes",
140
+ active: "tBgpPeerNgOperVpnActivePrefixes",
141
+ reject: "tBgpPeerNgOperVpnIpv4RejPfxs",
142
+ },
143
+ "ipv6 unicast": {
144
+ id: "nokia-timos-bgp-peer-family-ipv6-unicast",
145
+ af: ddprofiledefinition.BGPAddressFamilyIPv6,
146
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyUnicast,
147
+ label: "ipv6 unicast",
148
+ recv: "tBgpPeerNgOperV6ReceivedPrefixes",
149
+ active: "tBgpPeerNgOperV6ActivePrefixes",
150
+ reject: "tBgpPeerNgOperIpv6RejPfxs",
151
+ },
152
+ "ipv6 vpn": {
153
+ id: "nokia-timos-bgp-peer-family-ipv6-vpn",
154
+ af: ddprofiledefinition.BGPAddressFamilyIPv6,
155
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyVPN,
156
+ label: "vpnv6 unicast",
157
+ recv: "tBgpPeerNgOperVpnIpv6RecvPfxs",
158
+ active: "tBgpPeerNgOperVpnIpv6ActivePfxs",
159
+ reject: "tBgpPeerNgOperVpnIpv6RejPfxs",
160
+ },
161
+ "l2vpn vpls": {
162
+ id: "nokia-timos-bgp-peer-family-l2vpn-vpls",
163
+ af: ddprofiledefinition.BGPAddressFamilyL2VPN,
164
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyVPLS,
165
+ label: "l2vpn vpls",
166
+ recv: "tBgpPeerNgOperl2VpnRecvPfxs",
167
+ active: "tBgpPeerNgOperl2VpnActivePfxs",
168
+ reject: "tBgpPeerNgOperl2VpnRejPfxs",
169
+ },
170
+ "l2vpn evpn": {
171
+ id: "nokia-timos-bgp-peer-family-l2vpn-evpn",
172
+ af: ddprofiledefinition.BGPAddressFamilyL2VPN,
173
+ safi: ddprofiledefinition.BGPSubsequentAddressFamilyEVPN,
174
+ label: "l2vpn evpn",
175
+ recv: "tBgpPeerNgOperEvpnRecvPfxs",
176
+ active: "tBgpPeerNgOperEvpnActivePfxs",
177
+ reject: "tBgpPeerNgOperEvpnRejPfxs",
178
+ },
179
+ }
180
+
181
+ for name, tc := range tests {
182
+ t.Run(name, func(t *testing.T) {
183
+ row := requireBGPRowByID(t, profile, tc.id)
184
+ assert.Equal(t, "_nokia-timetra-bgp.yaml", row.OriginProfileID)
185
+ assert.Equal(t, ddprofiledefinition.BGPRowKindPeerFamily, row.Kind)
186
+ assert.Equal(t, "tBgpPeerNgOperTable", row.Table.Name)
187
+ assert.Equal(t, "1.3.6.1.4.1.6527.3.1.2.14.4.8", row.Table.OID)
188
+ assert.Equal(t, "vRtrConfTable", row.Identity.RoutingInstance.Table)
189
+ assert.Equal(t, "tBgpPeerNgAddress", row.Identity.Neighbor.Name)
190
+ assert.Equal(t, "tBgpPeerNgTable", row.Identity.RemoteAS.Table)
191
+ assert.Equal(t, tc.af, ddprofiledefinition.BGPAddressFamily(row.Identity.AddressFamily.Value))
192
+ assert.Equal(t, tc.safi, ddprofiledefinition.BGPSubsequentAddressFamily(row.Identity.SubsequentAddressFamily.Value))
193
+ assert.True(t, hasStaticTag(row.StaticTags, "address_family_name", tc.label))
194
+ assert.Equal(t, tc.recv, row.Routes.Total.Received.Symbol.Name)
195
+ assert.Equal(t, tc.active, row.Routes.Current.Active.Symbol.Name)
196
+ assert.Equal(t, tc.reject, row.Routes.Total.Rejected.Symbol.Name)
197
+ })
198
+ }
199
+}
200
+
201
+func mustLoadNokiaSROSBGPProfile(t *testing.T) *Profile {
202
+ t.Helper()
203
+
204
+ dir, _ := filepath.Abs("../../../config/go.d/snmp.profiles/default")
205
+
206
+ profiles, err := loadProfilesFromDir(dir, multipath.New(dir))
207
+ require.NoError(t, err)
208
+ require.NotEmpty(t, profiles)
209
+
210
+ matched := FindProfiles("1.3.6.1.4.1.6527.1.3.17", "", nil)
211
+ index := slices.IndexFunc(matched, func(p *Profile) bool {
212
+ return strings.HasSuffix(p.SourceFile, "nokia-service-router-os.yaml")
213
+ })
214
+ require.NotEqual(t, -1, index, "expected nokia-service-router-os profile to match")
215
+
216
+ return matched[index]
217
+}
218
+
219
+func hasStaticTag(tags []ddprofiledefinition.StaticMetricTagConfig, name, value string) bool {
220
+ return slices.ContainsFunc(tags, func(tag ddprofiledefinition.StaticMetricTagConfig) bool {
221
+ return tag.Tag == name && tag.Value == value
222
+ })
223
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go
+60
@@ -178,6 +178,7 @@ func (p *Profile) merge(base *Profile) error {
178
return err
179
}
180
p.mergeLicensing(base)
181
+ p.mergeBGP(base)
182
// Append other fields as before (these likely don't need deduplication)
183
p.Definition.MetricTags = append(p.Definition.MetricTags, base.Definition.MetricTags...)
184
p.Definition.StaticTags = append(slices.Clone(base.Definition.StaticTags), p.Definition.StaticTags...)
@@ -330,6 +331,20 @@ func (p *Profile) mergeLicensing(base *Profile) {
331
}
332
}
333
334
+func (p *Profile) mergeBGP(base *Profile) {
335
+ overridden := make(map[string]bool, len(p.Definition.BGP))
336
+ for _, row := range p.Definition.BGP {
337
+ overridden[ddprofiledefinition.BGPMergeIdentity(row)] = true
338
+ }
339
+
340
+ for _, row := range base.Definition.BGP {
341
+ if overridden[ddprofiledefinition.BGPMergeIdentity(row)] {
342
+ continue
343
+ }
344
+ p.Definition.BGP = append(p.Definition.BGP, row)
345
+ }
346
+}
347
+
348
func indexTopologyMergeConflicts(
349
topo ddprofiledefinition.TopologyConfig,
350
scalarKinds map[topologyScalarConflictKey]ddprofiledefinition.TopologyKind,
@@ -493,6 +508,9 @@ func enrichProfile(prof *Profile) {
508
for i := range prof.Definition.Topology {
509
enrichMetricTagMappingRefs(prof.Definition.Topology[i].MetricTags)
510
}
511
+ for i := range prof.Definition.BGP {
512
+ enrichMetricTagMappingRefs(prof.Definition.BGP[i].MetricTags)
513
+ }
514
}
515
516
func enrichMetricTagMappingRefs(tags ddprofiledefinition.MetricTagConfigList) {
@@ -522,6 +540,7 @@ func deduplicateMetricsAcrossProfiles(profiles []*Profile) {
540
seenMetrics := make(map[string]bool)
541
seenVmetrics := make(map[string]bool)
542
seenLicenseSignals := make(map[string]bool)
543
+ seenBGPSignals := make(map[string]bool)
544
545
for _, prof := range profiles {
546
if prof.Definition == nil {
@@ -553,6 +572,7 @@ func deduplicateMetricsAcrossProfiles(profiles []*Profile) {
572
573
deduplicateTopologyInProfile(prof, seenMetrics)
574
deduplicateLicensingInProfile(prof, seenLicenseSignals)
575
+ deduplicateBGPInProfile(prof, seenBGPSignals)
576
}
577
}
578
@@ -645,6 +665,46 @@ func generateLicenseSignalKeys(row ddprofiledefinition.LicensingConfig) []string
665
return keys
666
}
667
668
+func deduplicateBGPInProfile(prof *Profile, seenSignals map[string]bool) {
669
+ filtered := prof.Definition.BGP[:0]
670
+ for _, row := range prof.Definition.BGP {
671
+ keys := generateBGPSignalKeys(row)
672
+ if len(keys) == 0 {
673
+ filtered = append(filtered, row)
674
+ continue
675
+ }
676
+
677
+ duplicate := false
678
+ for _, key := range keys {
679
+ if seenSignals[key] {
680
+ duplicate = true
681
+ break
682
+ }
683
+ }
684
+ if duplicate {
685
+ continue
686
+ }
687
+ for _, key := range keys {
688
+ seenSignals[key] = true
689
+ }
690
+ filtered = append(filtered, row)
691
+ }
692
+ if len(filtered) == 0 {
693
+ prof.Definition.BGP = nil
694
+ return
695
+ }
696
+ prof.Definition.BGP = filtered
697
+}
698
+
699
+func generateBGPSignalKeys(row ddprofiledefinition.BGPConfig) []string {
700
+ identity := ddprofiledefinition.BGPStructuralIdentity(row)
701
+ var keys []string
702
+ ddprofiledefinition.ForEachBGPSignalValue(row, func(path string, _ ddprofiledefinition.BGPValueConfig) {
703
+ keys = append(keys, strings.Join([]string{identity, path}, "|"))
704
+ })
705
+ return keys
706
+}
707
+
708
func addLicenseTimerSignalKeys(cfg ddprofiledefinition.LicenseTimerSignalsConfig, add func(ddprofiledefinition.LicenseValueConfig)) {
709
add(cfg.LicenseValueConfig)
710
add(cfg.Timestamp)
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_catalog.go
+20
-2
@@ -14,6 +14,7 @@ const (
14
ConsumerMetrics = ddprofiledefinition.ConsumerMetrics
15
ConsumerTopology = ddprofiledefinition.ConsumerTopology
16
ConsumerLicensing = ddprofiledefinition.ConsumerLicensing
17
+ ConsumerBGP = ddprofiledefinition.ConsumerBGP
18
)
19
20
type ManualProfilePolicy int
@@ -197,18 +198,27 @@ func projectProfile(prof *Profile, consumer ProfileConsumer) {
198
case ConsumerMetrics:
199
def.Topology = nil
200
def.Licensing = nil
201
+ def.BGP = nil
202
case ConsumerTopology:
203
def.Metrics = nil
204
def.Licensing = nil
205
+ def.BGP = nil
206
def.VirtualMetrics = nil
207
case ConsumerLicensing:
208
def.Metrics = nil
209
def.Topology = nil
210
+ def.BGP = nil
211
+ def.VirtualMetrics = nil
212
+ case ConsumerBGP:
213
+ def.Metrics = nil
214
+ def.Topology = nil
215
+ def.Licensing = nil
216
def.VirtualMetrics = nil
217
default:
218
def.Metrics = nil
219
def.Topology = nil
220
def.Licensing = nil
221
+ def.BGP = nil
222
def.VirtualMetrics = nil
223
def.Metadata = nil
224
def.SysobjectIDMetadata = nil
@@ -236,6 +246,9 @@ func projectProfileForConsumers(prof *Profile, consumers []ProfileConsumer) {
246
if !profileConsumersInclude(consumers, ConsumerLicensing) {
247
def.Licensing = nil
248
}
249
+ if !profileConsumersInclude(consumers, ConsumerBGP) {
250
+ def.BGP = nil
251
+ }
252
}
253
254
func projectMetadata(meta ddprofiledefinition.MetadataConfig, consumer ProfileConsumer) ddprofiledefinition.MetadataConfig {
@@ -346,7 +359,7 @@ func projectSysobjectIDMetadataForConsumers(entries []ddprofiledefinition.Sysobj
359
360
func projectMetricTagList(tags []ddprofiledefinition.MetricTagConfig, consumer ProfileConsumer) []ddprofiledefinition.MetricTagConfig {
361
// Metadata id_tags do not carry Consumers today. They inherit metadata defaults.
349
- if consumer == ConsumerMetrics || consumer == ConsumerTopology {
362
+ if consumer == ConsumerMetrics || consumer == ConsumerTopology || consumer == ConsumerBGP {
363
return tags
364
}
365
return nil
@@ -354,7 +367,7 @@ func projectMetricTagList(tags []ddprofiledefinition.MetricTagConfig, consumer P
367
368
func projectMetricTagListForConsumers(tags []ddprofiledefinition.MetricTagConfig, consumers []ProfileConsumer) []ddprofiledefinition.MetricTagConfig {
369
// Metadata id_tags do not carry Consumers today. They inherit metadata defaults.
357
- if profileConsumersInclude(consumers, ConsumerMetrics) || profileConsumersInclude(consumers, ConsumerTopology) {
370
+ if profileConsumersInclude(consumers, ConsumerMetrics) || profileConsumersInclude(consumers, ConsumerTopology) || profileConsumersInclude(consumers, ConsumerBGP) {
371
return tags
372
}
373
return nil
@@ -422,6 +435,11 @@ func profileHasProjectedData(def *ddprofiledefinition.ProfileDefinition, consume
435
len(def.MetricTags) > 0 ||
436
len(def.Metadata) > 0 ||
437
len(def.SysobjectIDMetadata) > 0
438
+ case ConsumerBGP:
439
+ return len(def.BGP) > 0 ||
440
+ len(def.MetricTags) > 0 ||
441
+ len(def.Metadata) > 0 ||
442
+ len(def.SysobjectIDMetadata) > 0
443
default:
444
return false
445
}
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_catalog_test.go
+87
@@ -65,6 +65,7 @@ func TestResolvedProfileSetProject_SeparatesMetricsAndTopology(t *testing.T) {
65
metrics int
66
topology int
67
licensing int
68
+ bgp int
69
virtual int
70
metadataField string
71
metricTag string
@@ -94,6 +95,13 @@ func TestResolvedProfileSetProject_SeparatesMetricsAndTopology(t *testing.T) {
95
metricTag: "license_model",
96
sysobjectID: "sysobjectid_license_vendor",
97
},
98
+ "bgp_projection": {
99
+ consumer: ConsumerBGP,
100
+ bgp: 1,
101
+ metadataField: "bgp_vendor",
102
+ metricTag: "bgp_model",
103
+ sysobjectID: "sysobjectid_bgp_vendor",
104
+ },
105
}
106
107
for name, tc := range tests {
@@ -107,6 +115,7 @@ func TestResolvedProfileSetProject_SeparatesMetricsAndTopology(t *testing.T) {
115
require.Len(t, def.Metrics, tc.metrics)
116
require.Len(t, def.Topology, tc.topology)
117
require.Len(t, def.Licensing, tc.licensing)
118
+ require.Len(t, def.BGP, tc.bgp)
119
require.Len(t, def.VirtualMetrics, tc.virtual)
120
require.Len(t, def.Metadata["device"].Fields, 1)
121
assert.Contains(t, def.Metadata["device"].Fields, tc.metadataField)
@@ -144,6 +153,30 @@ func TestResolvedProfileSetProject_MetricsAndLicensing(t *testing.T) {
153
assert.NotContains(t, def.SysobjectIDMetadata[0].Metadata, "sysobjectid_topology_vendor")
154
}
155
156
+func TestResolvedProfileSetProject_MetricsAndBGP(t *testing.T) {
157
+ resolved := &ResolvedProfileSet{profiles: []*Profile{projectionTestProfile()}}
158
+
159
+ profiles := resolved.Project(ConsumerMetrics, ConsumerBGP).Profiles()
160
+
161
+ require.Len(t, profiles, 1)
162
+ def := profiles[0].Definition
163
+ require.Len(t, def.Metrics, 2)
164
+ require.Len(t, def.BGP, 1)
165
+ require.Len(t, def.VirtualMetrics, 1)
166
+ require.Empty(t, def.Topology)
167
+ require.Empty(t, def.Licensing)
168
+ assert.Contains(t, def.Metadata["device"].Fields, "vendor")
169
+ assert.Contains(t, def.Metadata["device"].Fields, "bgp_vendor")
170
+ assert.NotContains(t, def.Metadata["device"].Fields, "lldp_loc_sys_name")
171
+ require.Len(t, def.MetricTags, 2)
172
+ assert.Equal(t, "model", def.MetricTags[0].Tag)
173
+ assert.Equal(t, "bgp_model", def.MetricTags[1].Tag)
174
+ require.Len(t, def.SysobjectIDMetadata, 1)
175
+ assert.Contains(t, def.SysobjectIDMetadata[0].Metadata, "sysobjectid_vendor")
176
+ assert.Contains(t, def.SysobjectIDMetadata[0].Metadata, "sysobjectid_bgp_vendor")
177
+ assert.NotContains(t, def.SysobjectIDMetadata[0].Metadata, "sysobjectid_topology_vendor")
178
+}
179
+
180
func TestResolvedProfileSetProject_UnscopedMetricTagsPropagateToLicensing(t *testing.T) {
181
resolved := &ResolvedProfileSet{profiles: []*Profile{
182
{
@@ -172,6 +205,38 @@ func TestResolvedProfileSetProject_UnscopedMetricTagsPropagateToLicensing(t *tes
205
assert.Equal(t, "device_model", profiles[0].Definition.MetricTags[0].Tag)
206
}
207
208
+func TestResolvedProfileSetProject_UnscopedMetricTagsPropagateToBGP(t *testing.T) {
209
+ resolved := &ResolvedProfileSet{profiles: []*Profile{
210
+ {
211
+ SourceFile: "bgp.yaml",
212
+ Definition: &ddprofiledefinition.ProfileDefinition{
213
+ MetricTags: []ddprofiledefinition.GlobalMetricTagConfig{
214
+ {MetricTagConfig: ddprofiledefinition.MetricTagConfig{Tag: "device_model"}},
215
+ },
216
+ BGP: []ddprofiledefinition.BGPConfig{
217
+ {
218
+ ID: "peer",
219
+ Kind: ddprofiledefinition.BGPRowKindPeer,
220
+ Identity: ddprofiledefinition.BGPIdentityConfig{
221
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
222
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
223
+ },
224
+ State: ddprofiledefinition.BGPStateConfig{
225
+ BGPValueConfig: ddprofiledefinition.BGPValueConfig{Value: "established"},
226
+ },
227
+ },
228
+ },
229
+ },
230
+ },
231
+ }}
232
+
233
+ profiles := resolved.Project(ConsumerBGP).Profiles()
234
+
235
+ require.Len(t, profiles, 1)
236
+ require.Len(t, profiles[0].Definition.MetricTags, 1)
237
+ assert.Equal(t, "device_model", profiles[0].Definition.MetricTags[0].Tag)
238
+}
239
+
240
func TestResolvedProfileSetProject_DoesNotShareMutableProjectionState(t *testing.T) {
241
resolved := &ResolvedProfileSet{profiles: []*Profile{projectionTestProfile()}}
242
@@ -228,6 +293,10 @@ func projectionTestProfile() *Profile {
293
Value: "Cisco licensing",
294
Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerLicensing},
295
},
296
+ "bgp_vendor": {
297
+ Value: "Cisco BGP",
298
+ Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerBGP},
299
+ },
300
},
301
},
302
},
@@ -247,6 +316,10 @@ func projectionTestProfile() *Profile {
316
Value: "Cisco licensing",
317
Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerLicensing},
318
},
319
+ "sysobjectid_bgp_vendor": {
320
+ Value: "Cisco BGP",
321
+ Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerBGP},
322
+ },
323
},
324
},
325
},
@@ -263,6 +336,10 @@ func projectionTestProfile() *Profile {
336
MetricTagConfig: ddprofiledefinition.MetricTagConfig{Tag: "license_model"},
337
Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerLicensing},
338
},
339
+ {
340
+ MetricTagConfig: ddprofiledefinition.MetricTagConfig{Tag: "bgp_model"},
341
+ Consumers: ddprofiledefinition.ConsumerSet{ddprofiledefinition.ConsumerBGP},
342
+ },
343
},
344
Metrics: []ddprofiledefinition.MetricsConfig{
345
{
@@ -307,6 +384,16 @@ func projectionTestProfile() *Profile {
384
},
385
},
386
},
387
+ BGP: []ddprofiledefinition.BGPConfig{
388
+ {
389
+ ID: "peer",
390
+ Kind: ddprofiledefinition.BGPRowKindPeer,
391
+ Identity: ddprofiledefinition.BGPIdentityConfig{
392
+ Neighbor: ddprofiledefinition.BGPValueConfig{Value: "192.0.2.1"},
393
+ RemoteAS: ddprofiledefinition.BGPValueConfig{Value: "65001"},
394
+ },
395
+ },
396
+ },
397
VirtualMetrics: []ddprofiledefinition.VirtualMetricConfig{
398
{Name: "sysNameTotal", Sources: []ddprofiledefinition.VirtualMetricSourceConfig{{Metric: "sysName"}}},
399
},
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_prepare.go
+7
@@ -10,6 +10,13 @@ import (
10
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
11
)
12
13
+// HandleCrossTableTagsWithoutMetrics is the exported entry point for callers outside
14
+// this package (notably tests in ddsnmpcollector) that need to prepare a profile in
15
+// isolation, without the additional enrichment performed by FinalizeProfiles.
16
+func HandleCrossTableTagsWithoutMetrics(prof *Profile) {
17
+ handleCrossTableTagsWithoutMetrics(prof)
18
+}
19
+
20
// handleCrossTableTagsWithoutMetrics ensures tables referenced only by cross-table tags
21
// are still walked during collection. Without this, if a table like ifXTable is used
22
// only for cross-table tags (e.g., getting interface names) but has no metrics defined,
src/go/plugin/go.d/collector/snmp/ddsnmp/profile_test.go
+98
-4
@@ -174,25 +174,31 @@ func Test_FindProfiles(t *testing.T) {
174
}
175
}
176
177
-func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *testing.T) {
177
+func TestDefaultCatalogResolveProject_LoadedProfilesSeparateConsumers(t *testing.T) {
178
tests := map[string]struct {
179
+ sysObjectID string
180
consumer ProfileConsumer
181
consumers []ProfileConsumer
182
wantMetrics []string
183
wantTopologyKinds []ddprofiledefinition.TopologyKind
184
wantLicensingIDs []string
185
+ wantBGPIDs []string
186
wantNoMetrics bool
187
wantNoTopology bool
188
wantNoLicensing bool
189
+ wantNoBGP bool
190
}{
191
"metrics_projection": {
192
+ sysObjectID: "1.3.6.1.4.1.9.1.1",
193
consumer: ConsumerMetrics,
194
wantMetrics: []string{"systemUptime", "tcpCurrEstab", "cpmCPUTotal5minRev"},
195
wantNoTopology: true,
196
wantNoLicensing: true,
197
+ wantNoBGP: true,
198
},
199
"topology_projection": {
195
- consumer: ConsumerTopology,
200
+ sysObjectID: "1.3.6.1.4.1.9.1.1",
201
+ consumer: ConsumerTopology,
202
wantTopologyKinds: []ddprofiledefinition.TopologyKind{
203
ddprofiledefinition.KindLldpRem,
204
ddprofiledefinition.KindCdpCache,
@@ -203,9 +209,11 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
209
},
210
wantNoMetrics: true,
211
wantNoLicensing: true,
212
+ wantNoBGP: true,
213
},
214
"licensing_projection": {
208
- consumer: ConsumerLicensing,
215
+ sysObjectID: "1.3.6.1.4.1.9.1.1",
216
+ consumer: ConsumerLicensing,
217
wantLicensingIDs: []string{
218
"cisco_traditional_licenses",
219
"smart_registration",
@@ -216,8 +224,83 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
224
},
225
wantNoMetrics: true,
226
wantNoTopology: true,
227
+ wantNoBGP: true,
228
+ },
229
+ "bgp_projection": {
230
+ sysObjectID: "1.3.6.1.4.1.9.1.923",
231
+ consumer: ConsumerBGP,
232
+ wantBGPIDs: []string{"cisco-bgp-peer", "cisco-bgp-peer-family"},
233
+ wantNoMetrics: true,
234
+ wantNoTopology: true,
235
+ wantNoLicensing: true,
236
+ },
237
+ "bgp_projection_nokia": {
238
+ sysObjectID: "1.3.6.1.4.1.6527.1.3.17",
239
+ consumer: ConsumerBGP,
240
+ wantBGPIDs: []string{
241
+ "nokia-timos-bgp-peer",
242
+ "nokia-timos-bgp-peer-family-ipv4-unicast",
243
+ "nokia-timos-bgp-peer-family-ipv4-vpn",
244
+ "nokia-timos-bgp-peer-family-ipv6-unicast",
245
+ "nokia-timos-bgp-peer-family-ipv6-vpn",
246
+ "nokia-timos-bgp-peer-family-l2vpn-vpls",
247
+ "nokia-timos-bgp-peer-family-l2vpn-evpn",
248
+ },
249
+ wantNoMetrics: true,
250
+ wantNoTopology: true,
251
+ wantNoLicensing: true,
252
+ },
253
+ "bgp_projection_huawei": {
254
+ sysObjectID: "1.3.6.1.4.1.2011.2.224.279",
255
+ consumer: ConsumerBGP,
256
+ wantBGPIDs: []string{
257
+ "huawei-bgp-device-counts",
258
+ "huawei-bgp-peer-family",
259
+ "huawei-bgp-peer-family-routes",
260
+ "huawei-bgp-peer-family-messages",
261
+ "huawei-bgp-peer-statistics",
262
+ },
263
+ wantNoMetrics: true,
264
+ wantNoTopology: true,
265
+ wantNoLicensing: true,
266
+ },
267
+ "bgp_projection_arista": {
268
+ sysObjectID: "1.3.6.1.4.1.30065.1.3011.7010.427.48",
269
+ consumer: ConsumerBGP,
270
+ wantBGPIDs: []string{
271
+ "arista-bgp-peer",
272
+ "arista-bgp-peer-family",
273
+ },
274
+ wantNoMetrics: true,
275
+ wantNoTopology: true,
276
+ wantNoLicensing: true,
277
+ },
278
+ "bgp_projection_dell_os10": {
279
+ sysObjectID: "1.3.6.1.4.1.674.11000.5000.100.2.1.1",
280
+ consumer: ConsumerBGP,
281
+ wantBGPIDs: []string{
282
+ "dell-os10-bgp-peer",
283
+ "dell-os10-bgp-peer-family",
284
+ },
285
+ wantNoMetrics: true,
286
+ wantNoTopology: true,
287
+ wantNoLicensing: true,
288
+ },
289
+ "bgp_projection_alcatel": {
290
+ sysObjectID: "1.3.6.1.4.1.6486.801.1.1.2.1.1",
291
+ consumer: ConsumerBGP,
292
+ wantBGPIDs: []string{
293
+ "alcatel-bgp4-peer",
294
+ "alcatel-bgp4-peer-family",
295
+ "alcatel-bgp6-peer",
296
+ "alcatel-bgp6-peer-family",
297
+ },
298
+ wantNoMetrics: true,
299
+ wantNoTopology: true,
300
+ wantNoLicensing: true,
301
},
302
"metrics_and_licensing_projection": {
303
+ sysObjectID: "1.3.6.1.4.1.9.1.1",
304
consumer: ConsumerMetrics,
305
consumers: []ProfileConsumer{ConsumerLicensing},
306
wantMetrics: []string{"systemUptime", "tcpCurrEstab", "cpmCPUTotal5minRev"},
@@ -230,13 +313,14 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
313
"smart_entitlements",
314
},
315
wantNoTopology: true,
316
+ wantNoBGP: true,
317
},
318
}
319
320
for name, tc := range tests {
321
t.Run(name, func(t *testing.T) {
322
profiles := DefaultCatalog().Resolve(ResolveRequest{
239
- SysObjectID: "1.3.6.1.4.1.9.1.1",
323
+ SysObjectID: tc.sysObjectID,
324
ManualPolicy: ManualProfileFallback,
325
}).Project(tc.consumer, tc.consumers...).Profiles()
326
require.NotEmpty(t, profiles)
@@ -244,6 +328,7 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
328
metricNames := make(map[string]bool)
329
topologyKinds := make(map[ddprofiledefinition.TopologyKind]bool)
330
licensingIDs := make(map[string]bool)
331
+ bgpIDs := make(map[string]bool)
332
333
for _, prof := range profiles {
334
require.NotNil(t, prof.Definition)
@@ -257,6 +342,9 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
342
if tc.wantNoLicensing {
343
assert.Empty(t, prof.Definition.Licensing, prof.SourceFile)
344
}
345
+ if tc.wantNoBGP {
346
+ assert.Empty(t, prof.Definition.BGP, prof.SourceFile)
347
+ }
348
349
for _, metric := range prof.Definition.Metrics {
350
if metric.Symbol.Name != "" {
@@ -272,6 +360,9 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
360
for _, row := range prof.Definition.Licensing {
361
licensingIDs[row.ID] = true
362
}
363
+ for _, row := range prof.Definition.BGP {
364
+ bgpIDs[row.ID] = true
365
+ }
366
}
367
368
for _, metricName := range tc.wantMetrics {
@@ -283,6 +374,9 @@ func TestDefaultCatalogResolveProject_LoadedCiscoProfileSeparatesConsumers(t *te
374
for _, id := range tc.wantLicensingIDs {
375
assert.True(t, licensingIDs[id], "missing licensing row %q", id)
376
}
377
+ for _, id := range tc.wantBGPIDs {
378
+ assert.True(t, bgpIDs[id], "missing BGP row %q", id)
379
+ }
380
})
381
}
382
}
src/go/plugin/go.d/collector/snmp/ddsnmp/testdata/librenms/cumulus_bgp_identity.json
new
+14
@@ -0,0 +1,14 @@
1
+{
2
+ "os": {
3
+ "discovery": {
4
+ "devices": [
5
+ {
6
+ "sysName": "cumulus-bgp",
7
+ "sysObjectID": ".1.3.6.1.4.1.40310",
8
+ "sysDescr": "Cumulus-linux 5.9.2 (Linux Kernel 6.1.94-1+cl5.9.2u6)",
9
+ "os": "cumulus"
10
+ }
11
+ ]
12
+ }
13
+ }
14
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/testdata/librenms/iosxr_ncs540_identity.json
new
+12
@@ -0,0 +1,12 @@
1
+{
2
+ "os": {
3
+ "discovery": {
4
+ "devices": [
5
+ {
6
+ "sysObjectID": ".1.3.6.1.4.1.9.1.2984",
7
+ "sysDescr": "Cisco IOS XR Software (NCS540L), Version 7.11.2 Copyright (c) 2013-2024 by Cisco Systems, Inc."
8
+ }
9
+ ]
10
+ }
11
+ }
12
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/testdata/librenms/junos_vmx_identity.json
new
+12
@@ -0,0 +1,12 @@
1
+{
2
+ "os": {
3
+ "discovery": {
4
+ "devices": [
5
+ {
6
+ "sysObjectID": ".1.3.6.1.4.1.2636.1.1.1.2.108",
7
+ "sysDescr": "Juniper Networks, Inc. vmx internet router, kernel JUNOS 18.2R1.9, Build date: 2018-06-28 04:23:52 UTC Copyright (c) 1996-2018 Juniper Networks, Inc."
8
+ }
9
+ ]
10
+ }
11
+ }
12
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/testdata/librenms/timos_ixr_s_identity.json
new
+12
@@ -0,0 +1,12 @@
1
+{
2
+ "os": {
3
+ "discovery": {
4
+ "devices": [
5
+ {
6
+ "sysObjectID": ".1.3.6.1.4.1.6527.1.3.23",
7
+ "sysDescr": "TiMOS-C-21.10.R2 cpm/x86hops64 Nokia 7250 IXR Copyright (c) 2000-2021 Nokia.\nAll rights reserved. All use subject to applicable license agreements.\r\nBuilt on Wed Dec 15 14:15:11 PST 2021 by builder in /builds/c/2110B/R2/panos/main/sros"
8
+ }
9
+ ]
10
+ }
11
+ }
12
+}
src/go/plugin/go.d/collector/snmp/func_bgp_error_text.go
new
+105
@@ -0,0 +1,105 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import "fmt"
6
+
7
+var bgpErrorCodeText = map[int64]string{
8
+ 1: "Message Header Error",
9
+ 2: "OPEN Message Error",
10
+ 3: "UPDATE Message Error",
11
+ 4: "Hold Timer Expired",
12
+ 5: "Finite State Machine Error",
13
+ 6: "Cease",
14
+ 7: "ROUTE-REFRESH Message Error",
15
+ 8: "Send Hold Timer Expired",
16
+ 9: "Loss of LSDB Synchronization",
17
+}
18
+
19
+var bgpErrorSubcodeText = map[int64]map[int64]string{
20
+ 1: {
21
+ 0: "Unspecific",
22
+ 1: "Connection Not Synchronized",
23
+ 2: "Bad Message Length",
24
+ 3: "Bad Message Type",
25
+ },
26
+ 2: {
27
+ 0: "Unspecific",
28
+ 1: "Unsupported Version Number",
29
+ 2: "Bad Peer AS",
30
+ 3: "Bad BGP Identifier",
31
+ 4: "Unsupported Optional Parameter",
32
+ 5: "Deprecated",
33
+ 6: "Unacceptable Hold Time",
34
+ 7: "Unsupported Capability",
35
+ 8: "Deprecated",
36
+ 9: "Deprecated",
37
+ 10: "Deprecated",
38
+ 11: "Role Mismatch",
39
+ },
40
+ 3: {
41
+ 0: "Unspecific",
42
+ 1: "Malformed Attribute List",
43
+ 2: "Unrecognized Well-known Attribute",
44
+ 3: "Missing Well-known Attribute",
45
+ 4: "Attribute Flags Error",
46
+ 5: "Attribute Length Error",
47
+ 6: "Invalid ORIGIN Attribute",
48
+ 7: "Deprecated",
49
+ 8: "Invalid NEXT_HOP Attribute",
50
+ 9: "Optional Attribute Error",
51
+ 10: "Invalid Network Field",
52
+ 11: "Malformed AS_PATH",
53
+ },
54
+ 5: {
55
+ 0: "Unspecified Error",
56
+ 1: "Receive Unexpected Message in OpenSent State",
57
+ 2: "Receive Unexpected Message in OpenConfirm State",
58
+ 3: "Receive Unexpected Message in Established State",
59
+ },
60
+ 6: {
61
+ 1: "Maximum Number of Prefixes Reached",
62
+ 2: "Administrative Shutdown",
63
+ 3: "Peer De-configured",
64
+ 4: "Administrative Reset",
65
+ 5: "Connection Rejected",
66
+ 6: "Other Configuration Change",
67
+ 7: "Connection Collision Resolution",
68
+ 8: "Out of Resources",
69
+ 9: "Hard Reset",
70
+ 10: "BFD Down",
71
+ },
72
+ 7: {
73
+ 0: "Reserved",
74
+ 1: "Invalid Message Length",
75
+ },
76
+}
77
+
78
+func bgpLastErrorText(code, subcode *int64) string {
79
+ if code == nil {
80
+ return ""
81
+ }
82
+ if *code == 0 && (subcode == nil || *subcode == 0) {
83
+ return ""
84
+ }
85
+
86
+ codeText, ok := bgpErrorCodeText[*code]
87
+ if !ok {
88
+ if subcode == nil {
89
+ return fmt.Sprintf("Unknown error code %d", *code)
90
+ }
91
+ return fmt.Sprintf("Unknown error code %d / subcode %d", *code, *subcode)
92
+ }
93
+
94
+ if subcode == nil {
95
+ return codeText
96
+ }
97
+
98
+ if subcodes, ok := bgpErrorSubcodeText[*code]; ok {
99
+ if subcodeText, ok := subcodes[*subcode]; ok {
100
+ return fmt.Sprintf("%s - %s", codeText, subcodeText)
101
+ }
102
+ }
103
+
104
+ return fmt.Sprintf("%s (subcode %d)", codeText, *subcode)
105
+}
src/go/plugin/go.d/collector/snmp/func_bgp_peers.go
new
+274
@@ -0,0 +1,274 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "context"
7
+ "sort"
8
+ "strconv"
9
+ "time"
10
+
11
+ "github.com/netdata/netdata/go/plugins/pkg/funcapi"
12
+)
13
+
14
+const (
15
+ bgpPeersMethodID = "bgp-peers"
16
+ bgpPeersParamView = "view"
17
+ bgpPeersViewPeers = "peers"
18
+ bgpPeersViewFamilies = "peer_families"
19
+ bgpPeersViewAll = "all"
20
+ bgpPeersDefaultView = bgpPeersViewPeers
21
+)
22
+
23
+type bgpPeerColumn struct {
24
+ funcapi.ColumnMeta
25
+ Value func(*bgpPeerEntry) any
26
+}
27
+
28
+type funcBGPPeers struct {
29
+ cache *bgpPeerCache
30
+}
31
+
32
+func newFuncBGPPeers(cache *bgpPeerCache) *funcBGPPeers {
33
+ return &funcBGPPeers{cache: cache}
34
+}
35
+
36
+func bgpPeerColumnSet(cols []bgpPeerColumn) funcapi.ColumnSet[bgpPeerColumn] {
37
+ return funcapi.Columns(cols, func(c bgpPeerColumn) funcapi.ColumnMeta { return c.ColumnMeta })
38
+}
39
+
40
+var _ funcapi.MethodHandler = (*funcBGPPeers)(nil)
41
+
42
+func bgpPeersMethodConfig() funcapi.MethodConfig {
43
+ return funcapi.MethodConfig{
44
+ ID: bgpPeersMethodID,
45
+ Name: "BGP Peers",
46
+ UpdateEvery: 10,
47
+ Help: "Detailed current BGP peer and peer-family state from cached SNMP data",
48
+ RequiredParams: []funcapi.ParamConfig{{
49
+ ID: bgpPeersParamView,
50
+ Name: "View",
51
+ Help: "Choose whether to show peer rows, peer-family rows, or both",
52
+ Selection: funcapi.ParamSelect,
53
+ Options: []funcapi.ParamOption{
54
+ {ID: bgpPeersViewPeers, Name: "Peers", Default: true},
55
+ {ID: bgpPeersViewFamilies, Name: "Peer Families"},
56
+ {ID: bgpPeersViewAll, Name: "All"},
57
+ },
58
+ }},
59
+ }
60
+}
61
+
62
+func (f *funcBGPPeers) MethodParams(_ context.Context, method string) ([]funcapi.ParamConfig, error) {
63
+ if method != bgpPeersMethodID {
64
+ return nil, nil
65
+ }
66
+ return bgpPeersMethodConfig().RequiredParams, nil
67
+}
68
+
69
+func (f *funcBGPPeers) Cleanup(_ context.Context) {}
70
+
71
+func (f *funcBGPPeers) Handle(_ context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
72
+ if method != bgpPeersMethodID {
73
+ return funcapi.NotFoundResponse(method)
74
+ }
75
+ if f.cache == nil {
76
+ return funcapi.UnavailableResponse("BGP peer data is not available for this device")
77
+ }
78
+
79
+ view := params.GetOne(bgpPeersParamView)
80
+ if view == "" {
81
+ view = bgpPeersDefaultView
82
+ }
83
+
84
+ snapshot := f.cache.snapshot(time.Now())
85
+ if snapshot.expired || len(snapshot.entries) == 0 {
86
+ if snapshot.message == "" {
87
+ snapshot.message = "no BGP peer data is available for this device"
88
+ }
89
+ return funcapi.UnavailableResponse(snapshot.message)
90
+ }
91
+
92
+ rows := make([][]any, 0, len(snapshot.entries))
93
+ for _, entry := range snapshot.entries {
94
+ if !matchesBGPPeerView(entry.scope, view) {
95
+ continue
96
+ }
97
+ rows = append(rows, buildBGPPeerRow(entry))
98
+ }
99
+
100
+ sortBGPPeerRows(rows)
101
+
102
+ cs := bgpPeerColumnSet(bgpPeerColumns)
103
+ help := "Detailed current BGP peer and peer-family state from cached SNMP data"
104
+ if snapshot.stale && snapshot.message != "" {
105
+ help += ". " + snapshot.message
106
+ }
107
+ return &funcapi.FunctionResponse{
108
+ Status: 200,
109
+ Help: help,
110
+ Columns: cs.BuildColumns(),
111
+ Data: rows,
112
+ DefaultSortColumn: "Neighbor",
113
+ }
114
+}
115
+
116
+func matchesBGPPeerView(scope, view string) bool {
117
+ switch view {
118
+ case bgpPeersViewAll:
119
+ return true
120
+ case bgpPeersViewFamilies:
121
+ return scope == bgpPeersViewFamilies
122
+ default:
123
+ return scope == bgpPeersViewPeers
124
+ }
125
+}
126
+
127
+func buildBGPPeerRow(entry *bgpPeerEntry) []any {
128
+ row := make([]any, len(bgpPeerColumns))
129
+ for i, col := range bgpPeerColumns {
130
+ row[i] = col.Value(entry)
131
+ }
132
+ return row
133
+}
134
+
135
+func sortBGPPeerRows(rows [][]any) {
136
+ if len(rows) == 0 {
137
+ return
138
+ }
139
+
140
+ colIdx := map[string]int{}
141
+ for i, col := range bgpPeerColumns {
142
+ colIdx[col.Name] = i
143
+ }
144
+
145
+ sort.Slice(rows, func(i, j int) bool {
146
+ a := rows[i]
147
+ b := rows[j]
148
+
149
+ for _, name := range []string{"Scope", "Routing Instance", "Neighbor", "Family"} {
150
+ av, _ := a[colIdx[name]].(string)
151
+ bv, _ := b[colIdx[name]].(string)
152
+ if av == bv {
153
+ continue
154
+ }
155
+ return av < bv
156
+ }
157
+ return false
158
+ })
159
+}
160
+
161
+func bgpScopeLabel(scope string) string {
162
+ switch scope {
163
+ case bgpPeersViewFamilies:
164
+ return "peer family"
165
+ default:
166
+ return "peer"
167
+ }
168
+}
169
+
170
+func bgpFamilyLabel(tags map[string]string) string {
171
+ if name := tags["address_family_name"]; name != "" && name != "all all" {
172
+ return name
173
+ }
174
+ af := tags["address_family"]
175
+ safi := tags["subsequent_address_family"]
176
+ switch {
177
+ case af == "" && safi == "":
178
+ return ""
179
+ case af == "all" && safi == "all":
180
+ return "all"
181
+ case safi == "":
182
+ return af
183
+ default:
184
+ return af + " " + safi
185
+ }
186
+}
187
+
188
+func bgpLastErrorDisplay(entry *bgpPeerEntry) string {
189
+ if entry.lastErrorText != "" {
190
+ return entry.lastErrorText
191
+ }
192
+ if entry.lastErrorCode == nil {
193
+ return ""
194
+ }
195
+ if entry.lastErrorSubcode == nil {
196
+ return "code " + int64ToString(entry.lastErrorCode)
197
+ }
198
+ return "code " + int64ToString(entry.lastErrorCode) + " / subcode " + int64ToString(entry.lastErrorSubcode)
199
+}
200
+
201
+func int64ToString(v *int64) string {
202
+ if v == nil {
203
+ return ""
204
+ }
205
+ return strconv.FormatInt(*v, 10)
206
+}
207
+
208
+func int64Value(v *int64) any {
209
+ if v == nil {
210
+ return nil
211
+ }
212
+ return *v
213
+}
214
+
215
+var bgpPeerColumns = []bgpPeerColumn{
216
+ {ColumnMeta: funcapi.ColumnMeta{Name: "rowId", Tooltip: "Row ID", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, UniqueKey: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.key }},
217
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Stale", Tooltip: "Whether this row is from a previous successful collection after a newer BGP collection failure", Type: funcapi.FieldTypeBoolean, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.stale }},
218
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Scope", Tooltip: "Scope", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return bgpScopeLabel(e.scope) }},
219
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Routing Instance", Tooltip: "Routing Instance", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["routing_instance"] }},
220
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Neighbor", Tooltip: "Neighbor", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Sticky: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["neighbor"] }},
221
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Local Address", Tooltip: "Local Address", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["local_address"] }},
222
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Remote AS", Tooltip: "Remote AS", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["remote_as"] }},
223
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Peer Description", Tooltip: "Peer Description", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["peer_description"] }},
224
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Family", Tooltip: "Family", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return bgpFamilyLabel(e.tags) }},
225
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Admin Status", Tooltip: "Admin Status", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Visualization: funcapi.FieldVisualPill, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.adminStatus }},
226
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Connection State", Tooltip: "Connection State", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Visualization: funcapi.FieldVisualPill, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return humanizeBGPLabel(e.state) }},
227
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Previous State", Tooltip: "Previous State", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Visualization: funcapi.FieldVisualPill, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return humanizeBGPLabel(e.previousState) }},
228
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Established Uptime", Tooltip: "Established Uptime", Type: funcapi.FieldTypeInteger, Units: "seconds", Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(e.establishedUptime) }},
229
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Update Age", Tooltip: "Last Update Age", Type: funcapi.FieldTypeInteger, Units: "seconds", Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(e.lastReceivedUpdate) }},
230
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Updates Received", Tooltip: "Updates Received", Type: funcapi.FieldTypeInteger, Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.updateCounts, "received")) }},
231
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Updates Sent", Tooltip: "Updates Sent", Type: funcapi.FieldTypeInteger, Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.updateCounts, "sent")) }},
232
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Accepted", Tooltip: "Prefixes Accepted", Type: funcapi.FieldTypeInteger, Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "accepted")) }},
233
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Advertised", Tooltip: "Prefixes Advertised", Type: funcapi.FieldTypeInteger, Visible: true, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "advertised")) }},
234
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Error", Tooltip: "Last Error", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Wrap: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return bgpLastErrorDisplay(e) }},
235
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Down Reason", Tooltip: "Down Reason", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.lastDownReason }},
236
+ {ColumnMeta: funcapi.ColumnMeta{Name: "GR State", Tooltip: "GR State", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.gracefulRestart }},
237
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Unavailability Reason", Tooltip: "Unavailability Reason", Type: funcapi.FieldTypeString, Visible: true, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.unavailabilityReason }},
238
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Neighbor Address Type", Tooltip: "Neighbor Address Type", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["neighbor_address_type"] }},
239
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Local AS", Tooltip: "Local AS", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["local_as"] }},
240
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Local Identifier", Tooltip: "Local Identifier", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["local_identifier"] }},
241
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Peer Identifier", Tooltip: "Peer Identifier", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["peer_identifier"] }},
242
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Peer Type", Tooltip: "Peer Type", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["peer_type"] }},
243
+ {ColumnMeta: funcapi.ColumnMeta{Name: "BGP Version", Tooltip: "BGP Version", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.tags["bgp_version"] }},
244
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Messages Received", Tooltip: "Messages Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.messageCounts, "received")) }},
245
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Messages Sent", Tooltip: "Messages Sent", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.messageCounts, "sent")) }},
246
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Notifications Received", Tooltip: "Notifications Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.notificationCounts, "received")) }},
247
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Notifications Sent", Tooltip: "Notifications Sent", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.notificationCounts, "sent")) }},
248
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Received Notification Reason", Tooltip: "Last Received Notification Reason", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.lastRecvNotify }},
249
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Sent Notification Reason", Tooltip: "Last Sent Notification Reason", Type: funcapi.FieldTypeString, Visible: false, Sortable: true, Filter: funcapi.FieldFilterMultiselect, FullWidth: true, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return e.lastSentNotify }},
250
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Open Received", Tooltip: "Open Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.openCounts, "received")) }},
251
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Open Sent", Tooltip: "Open Sent", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.openCounts, "sent")) }},
252
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Keepalive Received", Tooltip: "Keepalive Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.keepaliveCounts, "received")) }},
253
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Keepalive Sent", Tooltip: "Keepalive Sent", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.keepaliveCounts, "sent")) }},
254
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Route Refresh Received", Tooltip: "Route Refresh Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeRefreshCounts, "received")) }},
255
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Route Refresh Sent", Tooltip: "Route Refresh Sent", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeRefreshCounts, "sent")) }},
256
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Established Transitions", Tooltip: "Established Transitions", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(e.establishedCount) }},
257
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Down Transitions", Tooltip: "Down Transitions", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(e.downTransitions) }},
258
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Up Transitions", Tooltip: "Up Transitions", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(e.upTransitions) }},
259
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Flaps", Tooltip: "Flaps", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummarySum}, Value: func(e *bgpPeerEntry) any { return int64Value(e.flaps) }},
260
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Received", Tooltip: "Prefixes Received", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "received")) }},
261
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Active", Tooltip: "Prefixes Active", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "active")) }},
262
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Rejected", Tooltip: "Prefixes Rejected", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "rejected")) }},
263
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Suppressed", Tooltip: "Prefixes Suppressed", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "suppressed")) }},
264
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefixes Withdrawn", Tooltip: "Prefixes Withdrawn", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeCounts, "withdrawn")) }},
265
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Routes Received Total", Tooltip: "Routes Received Total", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeTotals, "received")) }},
266
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Routes Advertised Total", Tooltip: "Routes Advertised Total", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeTotals, "advertised")) }},
267
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Routes Rejected Total", Tooltip: "Routes Rejected Total", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeTotals, "rejected")) }},
268
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Routes Active Total", Tooltip: "Routes Active Total", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeTotals, "active")) }},
269
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefix Limit", Tooltip: "Prefix Limit", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeLimits, "admin_limit")) }},
270
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefix Threshold", Tooltip: "Prefix Threshold", Type: funcapi.FieldTypeInteger, Units: "%", Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeLimitThresholds, "threshold")) }},
271
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Prefix Clear Threshold", Tooltip: "Prefix Clear Threshold", Type: funcapi.FieldTypeInteger, Units: "%", Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryMax}, Value: func(e *bgpPeerEntry) any { return int64Value(mapValuePtr(e.routeLimitThresholds, "clear_threshold")) }},
272
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Error Code", Tooltip: "Last Error Code", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return int64Value(e.lastErrorCode) }},
273
+ {ColumnMeta: funcapi.ColumnMeta{Name: "Last Error Subcode", Tooltip: "Last Error Subcode", Type: funcapi.FieldTypeInteger, Visible: false, Sortable: true, Filter: funcapi.FieldFilterRange, Transform: funcapi.FieldTransformNumber, Summary: funcapi.FieldSummaryCount}, Value: func(e *bgpPeerEntry) any { return int64Value(e.lastErrorSubcode) }},
274
+}
src/go/plugin/go.d/collector/snmp/func_bgp_peers_cache.go
new
+581
@@ -0,0 +1,581 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "fmt"
7
+ "maps"
8
+ "strconv"
9
+ "strings"
10
+ "sync"
11
+ "time"
12
+
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
15
+)
16
+
17
+type bgpPeerCache struct {
18
+ mu sync.RWMutex
19
+ lastUpdate time.Time
20
+ lastFailure time.Time
21
+ lastError string
22
+ staleAfter time.Duration
23
+ updateTime time.Time
24
+ entries map[string]*bgpPeerEntry
25
+}
26
+
27
+type bgpPeerCacheSnapshot struct {
28
+ entries []*bgpPeerEntry
29
+ stale bool
30
+ expired bool
31
+ message string
32
+}
33
+
34
+type bgpPeerEntry struct {
35
+ key string
36
+ scope string
37
+ source string
38
+ tags map[string]string
39
+ updated bool
40
+ stale bool
41
+
42
+ lastUpdate time.Time
43
+ lastFailure time.Time
44
+ lastError string
45
+
46
+ adminStatus string
47
+ state string
48
+ previousState string
49
+
50
+ establishedUptime *int64
51
+ lastReceivedUpdate *int64
52
+ establishedCount *int64
53
+ downTransitions *int64
54
+ upTransitions *int64
55
+ flaps *int64
56
+ lastErrorCode *int64
57
+ lastErrorSubcode *int64
58
+ lastErrorText string
59
+ lastDownReason string
60
+ lastRecvNotify string
61
+ lastSentNotify string
62
+ gracefulRestart string
63
+ unavailabilityReason string
64
+
65
+ updateCounts map[string]int64
66
+ messageCounts map[string]int64
67
+ notificationCounts map[string]int64
68
+ routeRefreshCounts map[string]int64
69
+ openCounts map[string]int64
70
+ keepaliveCounts map[string]int64
71
+ routeCounts map[string]int64
72
+ routeTotals map[string]int64
73
+ routeLimits map[string]int64
74
+ routeLimitThresholds map[string]int64
75
+}
76
+
77
+var (
78
+ bgpPeerIdentityTags = []string{"routing_instance", "neighbor", "remote_as"}
79
+ bgpPeerRequiredIdentityTags = []string{"neighbor", "remote_as"}
80
+ bgpPeerFamilyIdentityTags = []string{"routing_instance", "neighbor", "remote_as", "address_family", "subsequent_address_family"}
81
+ bgpPeerFamilyRequiredIdentityTags = []string{"neighbor", "remote_as", "address_family", "subsequent_address_family"}
82
+)
83
+
84
+func newBGPPeerCache() *bgpPeerCache {
85
+ return &bgpPeerCache{
86
+ staleAfter: 30 * time.Second,
87
+ entries: make(map[string]*bgpPeerEntry),
88
+ }
89
+}
90
+
91
+func (c *bgpPeerCache) setStaleAfter(d time.Duration) {
92
+ if c == nil || d <= 0 {
93
+ return
94
+ }
95
+ c.mu.Lock()
96
+ defer c.mu.Unlock()
97
+ c.staleAfter = d
98
+}
99
+
100
+func (entry *bgpPeerEntry) reset() {
101
+ key := entry.key
102
+ scope := entry.scope
103
+ source := entry.source
104
+ tags := entry.tags
105
+ if tags == nil {
106
+ tags = make(map[string]string)
107
+ } else {
108
+ clear(tags)
109
+ }
110
+
111
+ *entry = bgpPeerEntry{
112
+ key: key,
113
+ scope: scope,
114
+ source: source,
115
+ tags: tags,
116
+ }
117
+}
118
+
119
+func (c *bgpPeerCache) reset() {
120
+ c.resetExceptSources(nil)
121
+}
122
+
123
+func (c *bgpPeerCache) resetExceptSources(excludedSources map[string]bool) {
124
+ if c == nil {
125
+ return
126
+ }
127
+
128
+ c.mu.Lock()
129
+ defer c.mu.Unlock()
130
+
131
+ c.updateTime = time.Now()
132
+ for _, entry := range c.entries {
133
+ if len(excludedSources) > 0 && excludedSources[entry.source] {
134
+ entry.stale = true
135
+ continue
136
+ }
137
+ entry.reset()
138
+ }
139
+}
140
+
141
+func (c *bgpPeerCache) markCollectFailed(err error) {
142
+ if c == nil {
143
+ return
144
+ }
145
+
146
+ c.mu.Lock()
147
+ defer c.mu.Unlock()
148
+
149
+ c.lastFailure = time.Now()
150
+ if err == nil {
151
+ c.lastError = ""
152
+ return
153
+ }
154
+ c.lastError = err.Error()
155
+}
156
+
157
+func (c *bgpPeerCache) markSourcesCollectFailed(sources map[string]bool, err error) {
158
+ if c == nil {
159
+ return
160
+ }
161
+
162
+ c.mu.Lock()
163
+ defer c.mu.Unlock()
164
+
165
+ marked := 0
166
+ now := time.Now()
167
+ lastError := ""
168
+ if err != nil {
169
+ lastError = err.Error()
170
+ }
171
+ for _, entry := range c.entries {
172
+ if len(sources) == 0 || sources[entry.source] {
173
+ entry.stale = true
174
+ entry.lastFailure = now
175
+ entry.lastError = lastError
176
+ marked++
177
+ }
178
+ }
179
+ if len(sources) > 0 && marked == 0 {
180
+ return
181
+ }
182
+
183
+ if len(sources) == 0 {
184
+ c.lastFailure = now
185
+ c.lastError = lastError
186
+ }
187
+}
188
+
189
+func (c *bgpPeerCache) updateRow(source string, row ddsnmp.BGPRow) {
190
+ if c == nil {
191
+ return
192
+ }
193
+
194
+ scope := bgpPeerEntryScopeFromRow(row)
195
+ if scope == "" {
196
+ return
197
+ }
198
+
199
+ key := row.StructuralID
200
+ if key == "" {
201
+ key = bgpPeerEntryKey(scope, bgpPeerEntryTagsFromRow(row))
202
+ }
203
+ if key == "" {
204
+ return
205
+ }
206
+
207
+ c.mu.Lock()
208
+ defer c.mu.Unlock()
209
+
210
+ entry := c.entries[key]
211
+ if entry == nil {
212
+ entry = &bgpPeerEntry{
213
+ key: key,
214
+ scope: scope,
215
+ source: source,
216
+ tags: make(map[string]string),
217
+ }
218
+ c.entries[key] = entry
219
+ }
220
+ entry.scope = scope
221
+ entry.source = firstNonEmpty(source, entry.source)
222
+ maps.Copy(entry.tags, bgpPeerEntryTagsFromRow(row))
223
+
224
+ if row.Admin.Enabled.Has {
225
+ if row.Admin.Enabled.Value {
226
+ entry.adminStatus = "enabled"
227
+ } else {
228
+ entry.adminStatus = "disabled"
229
+ }
230
+ }
231
+ if row.State.Has {
232
+ entry.state = string(row.State.State)
233
+ }
234
+ if row.Previous.Has {
235
+ entry.previousState = string(row.Previous.State)
236
+ }
237
+ entry.establishedUptime = bgpValuePtr(row.Connection.EstablishedUptime)
238
+ entry.lastReceivedUpdate = bgpValuePtr(row.Connection.LastReceivedUpdateAge)
239
+ entry.establishedCount = bgpValuePtr(row.Transitions.Established)
240
+ entry.downTransitions = bgpValuePtr(row.Transitions.Down)
241
+ entry.upTransitions = bgpValuePtr(row.Transitions.Up)
242
+ entry.flaps = bgpValuePtr(row.Transitions.Flaps)
243
+ entry.updateCounts = bgpDirectionalMap(row.Traffic.Updates)
244
+ entry.messageCounts = bgpDirectionalMap(row.Traffic.Messages)
245
+ entry.notificationCounts = bgpDirectionalMap(row.Traffic.Notifications)
246
+ entry.routeRefreshCounts = bgpDirectionalMap(row.Traffic.RouteRefreshes)
247
+ entry.openCounts = bgpDirectionalMap(row.Traffic.Opens)
248
+ entry.keepaliveCounts = bgpDirectionalMap(row.Traffic.Keepalives)
249
+ setBGPPeerLastErrorFromRow(entry, row.LastError)
250
+ entry.lastDownReason = humanizeBGPLabel(row.Reasons.LastDown.Value)
251
+ entry.lastRecvNotify = humanizeBGPLabel(row.LastNotify.Received.Reason.Value)
252
+ entry.lastSentNotify = humanizeBGPLabel(row.LastNotify.Sent.Reason.Value)
253
+ entry.gracefulRestart = humanizeBGPLabel(row.Restart.State.Value)
254
+ entry.unavailabilityReason = humanizeBGPLabel(row.Reasons.Unavailability.Value)
255
+ entry.routeCounts = bgpRouteCountersMap(row.Routes.Current)
256
+ entry.routeTotals = bgpRouteCountersMap(row.Routes.Total)
257
+ entry.routeLimits = bgpRouteLimitsMap(row.RouteLimits)
258
+ entry.routeLimitThresholds = bgpRouteLimitThresholdsMap(row.RouteLimits)
259
+ entry.updated = true
260
+ entry.stale = false
261
+}
262
+
263
+func bgpPeerEntryScopeFromRow(row ddsnmp.BGPRow) string {
264
+ switch row.Kind {
265
+ case ddprofiledefinition.BGPRowKindPeer:
266
+ return bgpPeersViewPeers
267
+ case ddprofiledefinition.BGPRowKindPeerFamily:
268
+ return bgpPeersViewFamilies
269
+ default:
270
+ return ""
271
+ }
272
+}
273
+
274
+func bgpPeerEntryTagsFromRow(row ddsnmp.BGPRow) map[string]string {
275
+ tags := maps.Clone(row.Tags)
276
+ if tags == nil {
277
+ tags = make(map[string]string)
278
+ }
279
+ add := func(key, value string) {
280
+ if value != "" {
281
+ tags[key] = value
282
+ }
283
+ }
284
+ add("routing_instance", bgpRoutingInstance(row))
285
+ add("neighbor", row.Identity.Neighbor)
286
+ add("remote_as", row.Identity.RemoteAS)
287
+ add("address_family", string(row.Identity.AddressFamily))
288
+ add("subsequent_address_family", string(row.Identity.SubsequentAddressFamily))
289
+ add("local_address", row.Descriptors.LocalAddress)
290
+ add("local_as", row.Descriptors.LocalAS)
291
+ add("local_identifier", row.Descriptors.LocalIdentifier)
292
+ add("peer_identifier", row.Descriptors.PeerIdentifier)
293
+ add("peer_type", row.Descriptors.PeerType)
294
+ add("bgp_version", row.Descriptors.BGPVersion)
295
+ add("peer_description", row.Descriptors.Description)
296
+ return tags
297
+}
298
+
299
+func bgpValuePtr(value ddsnmp.BGPInt64) *int64 {
300
+ if !value.Has {
301
+ return nil
302
+ }
303
+ return int64Ptr(value.Value)
304
+}
305
+
306
+func bgpDirectionalMap(value ddsnmp.BGPDirectional) map[string]int64 {
307
+ result := make(map[string]int64)
308
+ if value.Received.Has {
309
+ result["received"] = value.Received.Value
310
+ }
311
+ if value.Sent.Has {
312
+ result["sent"] = value.Sent.Value
313
+ }
314
+ if len(result) == 0 {
315
+ return nil
316
+ }
317
+ return result
318
+}
319
+
320
+func setBGPPeerLastErrorFromRow(entry *bgpPeerEntry, value ddsnmp.BGPLastError) {
321
+ code := bgpValuePtr(value.Code)
322
+ subcode := bgpValuePtr(value.Subcode)
323
+ if isNoBGPLastError(code, subcode) {
324
+ entry.lastErrorCode = nil
325
+ entry.lastErrorSubcode = nil
326
+ entry.lastErrorText = ""
327
+ return
328
+ }
329
+ entry.lastErrorCode = code
330
+ entry.lastErrorSubcode = subcode
331
+ entry.lastErrorText = bgpLastErrorText(code, subcode)
332
+}
333
+
334
+func bgpRouteCountersMap(value ddsnmp.BGPRouteCounters) map[string]int64 {
335
+ result := make(map[string]int64)
336
+ add := func(key string, value ddsnmp.BGPInt64) {
337
+ if value.Has {
338
+ result[key] = value.Value
339
+ }
340
+ }
341
+ add("received", value.Received)
342
+ add("accepted", value.Accepted)
343
+ add("rejected", value.Rejected)
344
+ add("active", value.Active)
345
+ add("advertised", value.Advertised)
346
+ add("suppressed", value.Suppressed)
347
+ add("withdrawn", value.Withdrawn)
348
+ if len(result) == 0 {
349
+ return nil
350
+ }
351
+ return result
352
+}
353
+
354
+func bgpRouteLimitsMap(value ddsnmp.BGPRouteLimits) map[string]int64 {
355
+ if !value.Limit.Has {
356
+ return nil
357
+ }
358
+ return map[string]int64{"admin_limit": value.Limit.Value}
359
+}
360
+
361
+func bgpRouteLimitThresholdsMap(value ddsnmp.BGPRouteLimits) map[string]int64 {
362
+ result := make(map[string]int64)
363
+ if value.Threshold.Has {
364
+ result["threshold"] = value.Threshold.Value
365
+ }
366
+ if value.ClearThreshold.Has {
367
+ result["clear_threshold"] = value.ClearThreshold.Value
368
+ }
369
+ if len(result) == 0 {
370
+ return nil
371
+ }
372
+ return result
373
+}
374
+
375
+func (c *bgpPeerCache) finalize() {
376
+ if c == nil {
377
+ return
378
+ }
379
+
380
+ c.mu.Lock()
381
+ defer c.mu.Unlock()
382
+
383
+ now := c.updateTime
384
+ if now.IsZero() {
385
+ now = time.Now()
386
+ }
387
+
388
+ hadUpdatedEntries := false
389
+ for key, entry := range c.entries {
390
+ if !entry.updated && !entry.stale {
391
+ delete(c.entries, key)
392
+ continue
393
+ }
394
+ if !entry.updated && entry.stale && !entry.lastUpdate.IsZero() && c.staleAfter > 0 && now.Sub(entry.lastUpdate) >= c.staleAfter {
395
+ delete(c.entries, key)
396
+ continue
397
+ }
398
+ if entry.updated {
399
+ hadUpdatedEntries = true
400
+ entry.lastUpdate = now
401
+ entry.lastFailure = time.Time{}
402
+ entry.lastError = ""
403
+ }
404
+ entry.updated = false
405
+ }
406
+
407
+ if hadUpdatedEntries || len(c.entries) == 0 {
408
+ c.lastUpdate = now
409
+ }
410
+ c.lastFailure = time.Time{}
411
+ c.lastError = ""
412
+}
413
+
414
+func (c *bgpPeerCache) snapshot(now time.Time) bgpPeerCacheSnapshot {
415
+ if c == nil {
416
+ return bgpPeerCacheSnapshot{expired: true, message: "BGP peer data not available yet, please retry after data collection"}
417
+ }
418
+
419
+ c.mu.RLock()
420
+ defer c.mu.RUnlock()
421
+
422
+ state := c.snapshotState(now)
423
+ entries := make([]*bgpPeerEntry, 0, len(c.entries))
424
+ hasEntryStale := false
425
+ nonExpiredEntries := 0
426
+ entryStaleMessage := ""
427
+ entryExpired := make(map[string]bool, len(c.entries))
428
+ for _, entry := range c.entries {
429
+ if entry.stale {
430
+ hasEntryStale = true
431
+ expired, message := entry.staleSnapshot(now, c.staleAfter)
432
+ entryExpired[entry.key] = expired
433
+ if message != "" && entryStaleMessage == "" {
434
+ entryStaleMessage = message
435
+ }
436
+ if !expired {
437
+ nonExpiredEntries++
438
+ }
439
+ } else {
440
+ nonExpiredEntries++
441
+ }
442
+ }
443
+ for _, entry := range c.entries {
444
+ if hasEntryStale && entryExpired[entry.key] && nonExpiredEntries > 0 {
445
+ continue
446
+ }
447
+ clone := entry.clone()
448
+ clone.stale = entry.stale || (!hasEntryStale && state.stale)
449
+ entries = append(entries, clone)
450
+ }
451
+ if hasEntryStale {
452
+ state.stale = nonExpiredEntries > 0
453
+ state.expired = nonExpiredEntries == 0
454
+ state.message = entryStaleMessage
455
+ }
456
+ state.entries = entries
457
+ return state
458
+}
459
+
460
+func (c *bgpPeerCache) snapshotState(now time.Time) bgpPeerCacheSnapshot {
461
+ if len(c.entries) == 0 {
462
+ return bgpPeerCacheSnapshot{message: "no BGP peer data is available for this device"}
463
+ }
464
+ if c.lastFailure.IsZero() || (!c.lastUpdate.IsZero() && !c.lastFailure.After(c.lastUpdate)) {
465
+ return bgpPeerCacheSnapshot{}
466
+ }
467
+ if c.lastUpdate.IsZero() {
468
+ return bgpPeerCacheSnapshot{expired: true, message: bgpStaleMessage("no successful BGP collection yet", c.lastError)}
469
+ }
470
+
471
+ age := now.Sub(c.lastUpdate)
472
+ if c.staleAfter > 0 && age >= c.staleAfter {
473
+ return bgpPeerCacheSnapshot{expired: true, message: bgpStaleMessage(fmt.Sprintf("last successful BGP collection was %s ago", age.Round(time.Second)), c.lastError)}
474
+ }
475
+ return bgpPeerCacheSnapshot{stale: true, message: bgpStaleMessage(fmt.Sprintf("showing stale BGP rows from %s ago", age.Round(time.Second)), c.lastError)}
476
+}
477
+
478
+func bgpStaleMessage(prefix, err string) string {
479
+ if err == "" {
480
+ return prefix
481
+ }
482
+ return prefix + ": " + err
483
+}
484
+
485
+func (entry *bgpPeerEntry) staleSnapshot(now time.Time, staleAfter time.Duration) (bool, string) {
486
+ if entry == nil || !entry.stale {
487
+ return false, ""
488
+ }
489
+ if entry.lastUpdate.IsZero() {
490
+ return true, bgpStaleMessage("no successful BGP collection yet", entry.lastError)
491
+ }
492
+ age := now.Sub(entry.lastUpdate)
493
+ if staleAfter > 0 && age >= staleAfter {
494
+ return true, bgpStaleMessage(fmt.Sprintf("last successful BGP collection was %s ago", age.Round(time.Second)), entry.lastError)
495
+ }
496
+ return false, bgpStaleMessage(fmt.Sprintf("showing stale BGP rows from %s ago", age.Round(time.Second)), entry.lastError)
497
+}
498
+
499
+func (entry *bgpPeerEntry) clone() *bgpPeerEntry {
500
+ if entry == nil {
501
+ return nil
502
+ }
503
+ clone := *entry
504
+ clone.tags = maps.Clone(entry.tags)
505
+ clone.updateCounts = maps.Clone(entry.updateCounts)
506
+ clone.messageCounts = maps.Clone(entry.messageCounts)
507
+ clone.notificationCounts = maps.Clone(entry.notificationCounts)
508
+ clone.routeRefreshCounts = maps.Clone(entry.routeRefreshCounts)
509
+ clone.openCounts = maps.Clone(entry.openCounts)
510
+ clone.keepaliveCounts = maps.Clone(entry.keepaliveCounts)
511
+ clone.routeCounts = maps.Clone(entry.routeCounts)
512
+ clone.routeTotals = maps.Clone(entry.routeTotals)
513
+ clone.routeLimits = maps.Clone(entry.routeLimits)
514
+ clone.routeLimitThresholds = maps.Clone(entry.routeLimitThresholds)
515
+ return &clone
516
+}
517
+
518
+func bgpPeerEntryKey(scope string, tags map[string]string) string {
519
+ identityTags, requiredTags := bgpPeerIdentityKeyTags(scope)
520
+ if len(identityTags) == 0 {
521
+ return ""
522
+ }
523
+
524
+ for _, key := range requiredTags {
525
+ if tags[key] == "" {
526
+ return ""
527
+ }
528
+ }
529
+
530
+ var sb strings.Builder
531
+ bgpWritePeerKeyPart(&sb, scope)
532
+ for _, key := range identityTags {
533
+ bgpWritePeerKeyPart(&sb, key)
534
+ bgpWritePeerKeyPart(&sb, tags[key])
535
+ }
536
+ return sb.String()
537
+}
538
+
539
+func bgpPeerIdentityKeyTags(scope string) ([]string, []string) {
540
+ switch scope {
541
+ case "peers":
542
+ return bgpPeerIdentityTags, bgpPeerRequiredIdentityTags
543
+ case "peer_families":
544
+ return bgpPeerFamilyIdentityTags, bgpPeerFamilyRequiredIdentityTags
545
+ default:
546
+ return nil, nil
547
+ }
548
+}
549
+
550
+func isNoBGPLastError(code, subcode *int64) bool {
551
+ return (code == nil || *code == 0) && (subcode == nil || *subcode == 0)
552
+}
553
+
554
+func int64Ptr(v int64) *int64 {
555
+ value := v
556
+ return &value
557
+}
558
+
559
+func mapValuePtr(m map[string]int64, key string) *int64 {
560
+ if len(m) == 0 {
561
+ return nil
562
+ }
563
+ v, ok := m[key]
564
+ if !ok {
565
+ return nil
566
+ }
567
+ return int64Ptr(v)
568
+}
569
+
570
+func humanizeBGPLabel(value string) string {
571
+ if value == "" {
572
+ return ""
573
+ }
574
+ return strings.ReplaceAll(value, "_", " ")
575
+}
576
+
577
+func bgpWritePeerKeyPart(sb *strings.Builder, value string) {
578
+ sb.WriteString(strconv.Itoa(len(value)))
579
+ sb.WriteByte(':')
580
+ sb.WriteString(value)
581
+}
src/go/plugin/go.d/collector/snmp/func_bgp_peers_test.go
new
+537
@@ -0,0 +1,537 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "context"
7
+ "errors"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/golang/mock/gomock"
12
+ "github.com/gosnmp/gosnmp"
13
+ snmpmock "github.com/gosnmp/gosnmp/mocks"
14
+ "github.com/netdata/netdata/go/plugins/pkg/funcapi"
15
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
17
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector"
18
+ "github.com/stretchr/testify/assert"
19
+ "github.com/stretchr/testify/require"
20
+)
21
+
22
+func newTestFuncBGPPeers(cache *bgpPeerCache) *funcBGPPeers {
23
+ return newFuncBGPPeers(cache)
24
+}
25
+
26
+func TestBGPLastErrorText(t *testing.T) {
27
+ tests := map[string]struct {
28
+ code *int64
29
+ subcode *int64
30
+ expected string
31
+ }{
32
+ "nil code": {
33
+ expected: "",
34
+ },
35
+ "open bad bgp identifier": {
36
+ code: int64Ptr(2),
37
+ subcode: int64Ptr(3),
38
+ expected: "OPEN Message Error - Bad BGP Identifier",
39
+ },
40
+ "unknown subcode": {
41
+ code: int64Ptr(7),
42
+ subcode: int64Ptr(99),
43
+ expected: "ROUTE-REFRESH Message Error (subcode 99)",
44
+ },
45
+ "unknown code": {
46
+ code: int64Ptr(42),
47
+ subcode: int64Ptr(7),
48
+ expected: "Unknown error code 42 / subcode 7",
49
+ },
50
+ }
51
+
52
+ for name, tc := range tests {
53
+ t.Run(name, func(t *testing.T) {
54
+ assert.Equal(t, tc.expected, bgpLastErrorText(tc.code, tc.subcode))
55
+ })
56
+ }
57
+}
58
+
59
+func TestBGPPeerEntryKey(t *testing.T) {
60
+ tests := map[string]struct {
61
+ scope string
62
+ tags map[string]string
63
+ otherScope string
64
+ otherTags map[string]string
65
+ wantEqual bool
66
+ wantEmpty bool
67
+ }{
68
+ "escapes delimiter-like values": {
69
+ scope: "peers",
70
+ tags: map[string]string{
71
+ "neighbor": "198.51.100.1|remote_as=65001",
72
+ "remote_as": "65002",
73
+ },
74
+ otherScope: "peers",
75
+ otherTags: map[string]string{
76
+ "neighbor": "198.51.100.1",
77
+ "remote_as": "65001|remote_as=65002",
78
+ },
79
+ wantEqual: false,
80
+ },
81
+ "ignores peer descriptor tags": {
82
+ scope: "peers",
83
+ tags: map[string]string{
84
+ "routing_instance": "blue",
85
+ "neighbor": "198.51.100.1",
86
+ "remote_as": "65001",
87
+ "local_address": "192.0.2.1",
88
+ "peer_description": "Transit A",
89
+ },
90
+ otherScope: "peers",
91
+ otherTags: map[string]string{
92
+ "routing_instance": "blue",
93
+ "neighbor": "198.51.100.1",
94
+ "remote_as": "65001",
95
+ "local_address": "192.0.2.2",
96
+ "peer_description": "Transit B",
97
+ },
98
+ wantEqual: true,
99
+ },
100
+ "scope participates in identity": {
101
+ scope: "peers",
102
+ tags: map[string]string{
103
+ "routing_instance": "blue",
104
+ "neighbor": "198.51.100.1",
105
+ "remote_as": "65001",
106
+ },
107
+ otherScope: "peer_families",
108
+ otherTags: map[string]string{
109
+ "routing_instance": "blue",
110
+ "neighbor": "198.51.100.1",
111
+ "remote_as": "65001",
112
+ "address_family": "ipv4",
113
+ "subsequent_address_family": "unicast",
114
+ },
115
+ wantEqual: false,
116
+ },
117
+ "family address family participates in identity": {
118
+ scope: "peer_families",
119
+ tags: map[string]string{
120
+ "routing_instance": "blue",
121
+ "neighbor": "198.51.100.1",
122
+ "remote_as": "65001",
123
+ "address_family": "ipv4",
124
+ "subsequent_address_family": "unicast",
125
+ },
126
+ otherScope: "peer_families",
127
+ otherTags: map[string]string{
128
+ "routing_instance": "blue",
129
+ "neighbor": "198.51.100.1",
130
+ "remote_as": "65001",
131
+ "address_family": "ipv6",
132
+ "subsequent_address_family": "unicast",
133
+ },
134
+ wantEqual: false,
135
+ },
136
+ "empty routing instance is same as missing": {
137
+ scope: "peers",
138
+ tags: map[string]string{
139
+ "neighbor": "198.51.100.1",
140
+ "remote_as": "65001",
141
+ },
142
+ otherScope: "peers",
143
+ otherTags: map[string]string{
144
+ "routing_instance": "",
145
+ "neighbor": "198.51.100.1",
146
+ "remote_as": "65001",
147
+ },
148
+ wantEqual: true,
149
+ },
150
+ "routing instance partitions peers": {
151
+ scope: "peers",
152
+ tags: map[string]string{
153
+ "routing_instance": "blue",
154
+ "neighbor": "198.51.100.1",
155
+ "remote_as": "65001",
156
+ },
157
+ otherScope: "peers",
158
+ otherTags: map[string]string{
159
+ "neighbor": "198.51.100.1",
160
+ "remote_as": "65001",
161
+ },
162
+ wantEqual: false,
163
+ },
164
+ "missing peer neighbor is empty": {
165
+ scope: "peers",
166
+ tags: map[string]string{"remote_as": "65001"},
167
+ wantEmpty: true,
168
+ },
169
+ "missing peer remote AS is empty": {
170
+ scope: "peers",
171
+ tags: map[string]string{"neighbor": "198.51.100.1"},
172
+ wantEmpty: true,
173
+ },
174
+ "missing peer-family SAFI is empty": {
175
+ scope: "peer_families",
176
+ tags: map[string]string{
177
+ "neighbor": "198.51.100.1",
178
+ "remote_as": "65001",
179
+ "address_family": "ipv4",
180
+ "subsequent_address_family": "",
181
+ },
182
+ wantEmpty: true,
183
+ },
184
+ }
185
+
186
+ for name, tc := range tests {
187
+ t.Run(name, func(t *testing.T) {
188
+ key := bgpPeerEntryKey(tc.scope, tc.tags)
189
+ if tc.wantEmpty {
190
+ assert.Empty(t, key)
191
+ return
192
+ }
193
+
194
+ require.NotEmpty(t, key)
195
+ if tc.otherTags == nil {
196
+ return
197
+ }
198
+
199
+ otherKey := bgpPeerEntryKey(tc.otherScope, tc.otherTags)
200
+ require.NotEmpty(t, otherKey)
201
+ if tc.wantEqual {
202
+ assert.Equal(t, key, otherKey)
203
+ } else {
204
+ assert.NotEqual(t, key, otherKey)
205
+ }
206
+ })
207
+ }
208
+}
209
+
210
+func TestBGPPeerCacheClearsZeroLastError(t *testing.T) {
211
+ cache := newBGPPeerCache()
212
+
213
+ row := minimalBGPPeerRow("peer-a", "198.51.100.1", "65001")
214
+ row.LastError = ddsnmp.BGPLastError{
215
+ Code: ddsnmp.BGPInt64{Has: true, Value: 2},
216
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 3},
217
+ }
218
+
219
+ cache.reset()
220
+ cache.updateRow("test-profile.yaml", row)
221
+ require.Len(t, cache.entries, 1)
222
+
223
+ for _, entry := range cache.entries {
224
+ require.NotNil(t, entry.lastErrorCode)
225
+ require.NotNil(t, entry.lastErrorSubcode)
226
+ assert.Equal(t, "OPEN Message Error - Bad BGP Identifier", bgpLastErrorDisplay(entry))
227
+ }
228
+
229
+ row.LastError = ddsnmp.BGPLastError{
230
+ Code: ddsnmp.BGPInt64{Has: true, Value: 0},
231
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 0},
232
+ }
233
+ cache.reset()
234
+ cache.updateRow("test-profile.yaml", row)
235
+ require.Len(t, cache.entries, 1)
236
+
237
+ for _, entry := range cache.entries {
238
+ assert.Nil(t, entry.lastErrorCode)
239
+ assert.Nil(t, entry.lastErrorSubcode)
240
+ assert.Empty(t, entry.lastErrorText)
241
+ assert.Empty(t, bgpLastErrorDisplay(entry))
242
+ }
243
+}
244
+
245
+func TestBGPPeerCacheResetClearsStaleFieldsBetweenPolls(t *testing.T) {
246
+ cache := newBGPPeerCache()
247
+
248
+ row := minimalBGPPeerRow("peer-a", "198.51.100.1", "65001")
249
+ row.Descriptors.LocalAddress = "192.0.2.1"
250
+ row.Admin.Enabled = ddsnmp.BGPBool{Has: true, Value: true}
251
+ row.LastError = ddsnmp.BGPLastError{
252
+ Code: ddsnmp.BGPInt64{Has: true, Value: 2},
253
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 3},
254
+ }
255
+ row.Traffic.Updates = ddsnmp.BGPDirectional{
256
+ Received: ddsnmp.BGPInt64{Has: true, Value: 10},
257
+ Sent: ddsnmp.BGPInt64{Has: true, Value: 20},
258
+ }
259
+ cache.reset()
260
+ cache.updateRow("test-profile.yaml", row)
261
+ cache.finalize()
262
+ require.Len(t, cache.entries, 1)
263
+
264
+ next := minimalBGPPeerRow("peer-a", "198.51.100.1", "65001")
265
+ next.State = ddsnmp.BGPState{Has: true, State: ddprofiledefinition.BGPPeerStateEstablished}
266
+ cache.reset()
267
+ cache.updateRow("test-profile.yaml", next)
268
+ cache.finalize()
269
+ require.Len(t, cache.entries, 1)
270
+
271
+ for _, entry := range cache.entries {
272
+ assert.Equal(t, "established", entry.state)
273
+ assert.Empty(t, entry.adminStatus)
274
+ assert.Nil(t, entry.lastErrorCode)
275
+ assert.Nil(t, entry.lastErrorSubcode)
276
+ assert.Empty(t, entry.lastErrorText)
277
+ assert.Nil(t, entry.updateCounts)
278
+ assert.Equal(t, "198.51.100.1", entry.tags["neighbor"])
279
+ assert.Equal(t, "65001", entry.tags["remote_as"])
280
+ assert.NotContains(t, entry.tags, "local_address")
281
+ }
282
+}
283
+
284
+func TestFuncBGPPeersHandle(t *testing.T) {
285
+ cache := newBGPPeerCache()
286
+ cache.reset()
287
+
288
+ peer := typedBGPPeerRow()
289
+ peer.LastError = ddsnmp.BGPLastError{
290
+ Code: ddsnmp.BGPInt64{Has: true, Value: 2},
291
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 3},
292
+ }
293
+ peer.Connection.LastReceivedUpdateAge = ddsnmp.BGPInt64{Has: true, Value: 45}
294
+ family := typedBGPPeerFamilyRow()
295
+ cache.updateRow("test-profile.yaml", peer)
296
+ cache.updateRow("test-profile.yaml", family)
297
+ cache.finalize()
298
+
299
+ tests := map[string]struct {
300
+ params funcapi.ResolvedParams
301
+ validate func(t *testing.T, resp *funcapi.FunctionResponse)
302
+ }{
303
+ "peers view returns peer row with human-readable last error": {
304
+ params: resolveBGPPeerParams(nil),
305
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
306
+ assert.Equal(t, 200, resp.Status)
307
+ assert.Equal(t, "Neighbor", resp.DefaultSortColumn)
308
+
309
+ rowIDCol, ok := resp.Columns["rowId"].(map[string]any)
310
+ require.True(t, ok)
311
+ assert.Equal(t, false, rowIDCol["visible"])
312
+ assert.Equal(t, true, rowIDCol["unique_key"])
313
+
314
+ rows := responseRows(t, resp)
315
+ require.Len(t, rows, 1)
316
+
317
+ assert.Equal(t, "peer", rows[0][findBGPPeerColIdx("Scope")])
318
+ assert.Equal(t, "192.0.2.1", rows[0][findBGPPeerColIdx("Neighbor")])
319
+ assert.Equal(t, "198.51.100.1", rows[0][findBGPPeerColIdx("Local Address")])
320
+ assert.Equal(t, "Transit Peer", rows[0][findBGPPeerColIdx("Peer Description")])
321
+ assert.Equal(t, "enabled", rows[0][findBGPPeerColIdx("Admin Status")])
322
+ assert.Equal(t, "OPEN Message Error - Bad BGP Identifier", rows[0][findBGPPeerColIdx("Last Error")])
323
+ assert.Equal(t, "idle", rows[0][findBGPPeerColIdx("Previous State")])
324
+ },
325
+ },
326
+ "all view returns peer and peer-family rows": {
327
+ params: resolveBGPPeerParams(map[string][]string{bgpPeersParamView: {bgpPeersViewAll}}),
328
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
329
+ assert.Equal(t, 200, resp.Status)
330
+ rows := responseRows(t, resp)
331
+ require.Len(t, rows, 2)
332
+ assert.NotEqual(t, rows[0][findBGPPeerColIdx("rowId")], rows[1][findBGPPeerColIdx("rowId")])
333
+ assert.Equal(t, "ipv4 unicast", rows[1][findBGPPeerColIdx("Family")])
334
+ assert.Equal(t, "restart time wait", rows[1][findBGPPeerColIdx("GR State")])
335
+ assert.Equal(t, "peer not ready", rows[1][findBGPPeerColIdx("Unavailability Reason")])
336
+ },
337
+ },
338
+ }
339
+
340
+ for name, tc := range tests {
341
+ t.Run(name, func(t *testing.T) {
342
+ resp := newTestFuncBGPPeers(cache).Handle(context.Background(), bgpPeersMethodID, tc.params)
343
+ tc.validate(t, resp)
344
+ })
345
+ }
346
+}
347
+
348
+func TestFuncBGPPeers_HandleStaleAndFilteredRows(t *testing.T) {
349
+ tests := map[string]struct {
350
+ prepare func(cache *bgpPeerCache)
351
+ params funcapi.ResolvedParams
352
+ validate func(t *testing.T, resp *funcapi.FunctionResponse)
353
+ }{
354
+ "recent failure keeps stale rows visible": {
355
+ prepare: func(cache *bgpPeerCache) {
356
+ cache.setStaleAfter(time.Hour)
357
+ cache.updateRow("test-profile.yaml", typedBGPPeerRow())
358
+ cache.finalize()
359
+ cache.markCollectFailed(errors.New("walk failed"))
360
+ },
361
+ params: resolveBGPPeerParams(nil),
362
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
363
+ assert.Equal(t, 200, resp.Status)
364
+ assert.Contains(t, resp.Help, "showing stale BGP rows")
365
+ rows := responseRows(t, resp)
366
+ require.Len(t, rows, 1)
367
+ assert.Equal(t, true, rows[0][findBGPPeerColIdx("Stale")])
368
+ },
369
+ },
370
+ "expired failure returns unavailable": {
371
+ prepare: func(cache *bgpPeerCache) {
372
+ cache.setStaleAfter(time.Minute)
373
+ cache.updateRow("test-profile.yaml", typedBGPPeerRow())
374
+ cache.finalize()
375
+ cache.mu.Lock()
376
+ cache.lastUpdate = time.Now().Add(-2 * time.Minute)
377
+ cache.lastFailure = time.Now()
378
+ cache.lastError = "walk failed"
379
+ cache.mu.Unlock()
380
+ },
381
+ params: resolveBGPPeerParams(nil),
382
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
383
+ assert.Equal(t, 503, resp.Status)
384
+ assert.Contains(t, resp.Message, "last successful BGP collection")
385
+ },
386
+ },
387
+ "filtered empty view returns an empty table": {
388
+ prepare: func(cache *bgpPeerCache) {
389
+ cache.updateRow("test-profile.yaml", typedBGPPeerRow())
390
+ cache.finalize()
391
+ },
392
+ params: resolveBGPPeerParams(map[string][]string{bgpPeersParamView: {bgpPeersViewFamilies}}),
393
+ validate: func(t *testing.T, resp *funcapi.FunctionResponse) {
394
+ assert.Equal(t, 200, resp.Status)
395
+ assert.Empty(t, responseRows(t, resp))
396
+ },
397
+ },
398
+ }
399
+
400
+ for name, tc := range tests {
401
+ t.Run(name, func(t *testing.T) {
402
+ cache := newBGPPeerCache()
403
+ tc.prepare(cache)
404
+
405
+ resp := newTestFuncBGPPeers(cache).Handle(context.Background(), bgpPeersMethodID, tc.params)
406
+ tc.validate(t, resp)
407
+ })
408
+ }
409
+}
410
+
411
+func TestCollector_BGPFunctionHandlerIsAlwaysRegistered(t *testing.T) {
412
+ tests := map[string]struct {
413
+ prepare func(c *Collector)
414
+ wantStatus int
415
+ wantBGP bool
416
+ }{
417
+ "new collector has unavailable BGP handler": {
418
+ wantStatus: 503,
419
+ },
420
+ "enabled BGP integration registers handler": {
421
+ prepare: func(c *Collector) {
422
+ c.enableBGPIntegration()
423
+ },
424
+ wantStatus: 503,
425
+ wantBGP: true,
426
+ },
427
+ }
428
+
429
+ for name, tc := range tests {
430
+ t.Run(name, func(t *testing.T) {
431
+ collr := New()
432
+ if tc.prepare != nil {
433
+ tc.prepare(collr)
434
+ }
435
+
436
+ assert.Equal(t, tc.wantBGP, collr.bgp != nil)
437
+
438
+ params, err := collr.funcRouter.MethodParams(context.Background(), bgpPeersMethodID)
439
+ require.NoError(t, err)
440
+ require.NotEmpty(t, params)
441
+
442
+ resp := collr.funcRouter.Handle(context.Background(), bgpPeersMethodID, nil)
443
+ assert.Equal(t, tc.wantStatus, resp.Status)
444
+ })
445
+ }
446
+}
447
+
448
+func TestCollectSNMP_HidesBGPDiagnosticsButKeepsFunctionCache(t *testing.T) {
449
+ ctrl := gomock.NewController(t)
450
+ defer ctrl.Finish()
451
+
452
+ mockSNMP := snmpmock.NewMockHandler(ctrl)
453
+ setMockClientInitExpect(mockSNMP)
454
+ setMockClientSysInfoExpect(mockSNMP)
455
+
456
+ collr := New()
457
+ collr.Config = prepareV2Config()
458
+ collr.CreateVnode = false
459
+ collr.Ping.Enabled = false
460
+ collr.snmpProfiles = []*ddsnmp.Profile{{}}
461
+ collr.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
462
+ collr.newDdSnmpColl = func(ddsnmpcollector.Config) ddCollector {
463
+ row := typedBGPPeerRow()
464
+ row.LastError = ddsnmp.BGPLastError{
465
+ Code: ddsnmp.BGPInt64{Has: true, Value: 2},
466
+ Subcode: ddsnmp.BGPInt64{Has: true, Value: 3},
467
+ }
468
+ return &mockDdSnmpCollector{pms: []*ddsnmp.ProfileMetrics{{
469
+ Source: "test",
470
+ BGPRows: []ddsnmp.BGPRow{row},
471
+ }}}
472
+ }
473
+
474
+ require.NoError(t, collr.Init(context.Background()))
475
+ _ = collr.Check(context.Background())
476
+
477
+ mx := collr.Collect(context.Background())
478
+ require.NotNil(t, mx)
479
+
480
+ assert.Contains(t, mx, "snmp_bgp_peers_availability_192_0_2_1_65001_blue_established")
481
+ assert.Contains(t, mx, "snmp_bgp_peers_availability_192_0_2_1_65001_blue_admin_enabled")
482
+
483
+ chart := collr.Charts().Get("snmp_bgp_peers_availability_192_0_2_1_65001_blue")
484
+ require.NotNil(t, chart)
485
+ assert.Equal(t, "snmp.bgp.peers.availability", chart.Ctx)
486
+ assert.Equal(t, "198.51.100.1", chartLabels(chart)["local_address"])
487
+ assert.Equal(t, "Transit Peer", chartLabels(chart)["peer_description"])
488
+
489
+ for key := range mx {
490
+ assert.NotContains(t, key, "last_error")
491
+ }
492
+
493
+ require.NotNil(t, collr.bgp)
494
+ require.NotNil(t, collr.bgp.peerCache)
495
+ require.Len(t, collr.bgp.peerCache.entries, 1)
496
+ for _, entry := range collr.bgp.peerCache.entries {
497
+ assert.Equal(t, "OPEN Message Error - Bad BGP Identifier", entry.lastErrorText)
498
+ }
499
+}
500
+
501
+func minimalBGPPeerRow(structuralID, neighbor, remoteAS string) ddsnmp.BGPRow {
502
+ return ddsnmp.BGPRow{
503
+ OriginProfileID: "test-profile.yaml",
504
+ Kind: ddprofiledefinition.BGPRowKindPeer,
505
+ StructuralID: structuralID,
506
+ Identity: ddsnmp.BGPIdentity{
507
+ RoutingInstance: "default",
508
+ Neighbor: neighbor,
509
+ RemoteAS: remoteAS,
510
+ },
511
+ }
512
+}
513
+
514
+func findBGPPeerColIdx(name string) int {
515
+ for i, col := range bgpPeerColumns {
516
+ if col.Name == name {
517
+ return i
518
+ }
519
+ }
520
+ return -1
521
+}
522
+
523
+func resolveBGPPeerParams(values map[string][]string) funcapi.ResolvedParams {
524
+ f := newTestFuncBGPPeers(newBGPPeerCache())
525
+ params, err := f.MethodParams(context.Background(), bgpPeersMethodID)
526
+ if err != nil {
527
+ return nil
528
+ }
529
+ return funcapi.ResolveParams(params, values)
530
+}
531
+
532
+func responseRows(t *testing.T, resp *funcapi.FunctionResponse) [][]any {
533
+ t.Helper()
534
+ rows, ok := resp.Data.([][]any)
535
+ require.True(t, ok)
536
+ return rows
537
+}
src/go/plugin/go.d/collector/snmp/func_interfaces_test.go
+39
-9
@@ -20,18 +20,36 @@ func newTestFuncInterfaces(cache *ifaceCache) *funcInterfaces {
20
func TestSnmpMethods(t *testing.T) {
21
methods := snmpMethods()
22
23
- require.Len(t, methods, 2)
24
- assert.Equal(t, "interfaces", methods[0].ID)
25
- assert.Equal(t, "Network Interfaces", methods[0].Name)
26
- require.NotEmpty(t, methods[0].RequiredParams)
27
- assert.Equal(t, "licenses", methods[1].ID)
28
- assert.Equal(t, "Licenses", methods[1].Name)
23
+ var ifacesMethod *funcapi.MethodConfig
24
+ var licensesMethod *funcapi.MethodConfig
25
+ var bgpMethod *funcapi.MethodConfig
26
+ for i := range methods {
27
+ switch methods[i].ID {
28
+ case "interfaces":
29
+ ifacesMethod = &methods[i]
30
+ case "licenses":
31
+ licensesMethod = &methods[i]
32
+ case "bgp-peers":
33
+ bgpMethod = &methods[i]
34
+ }
35
+ }
36
+
37
+ require.NotNil(t, ifacesMethod)
38
+ assert.Equal(t, "Network Interfaces", ifacesMethod.Name)
39
+ require.NotEmpty(t, ifacesMethod.RequiredParams)
40
+
41
+ require.NotNil(t, licensesMethod)
42
+ assert.Equal(t, "Licenses", licensesMethod.Name)
43
+
44
+ require.NotNil(t, bgpMethod)
45
+ assert.Equal(t, "BGP Peers", bgpMethod.Name)
46
+ require.NotEmpty(t, bgpMethod.RequiredParams)
47
48
// Verify type group param exists
49
var typeGroupParam *funcapi.ParamConfig
32
- for i := range methods[0].RequiredParams {
33
- if methods[0].RequiredParams[i].ID == "if_type_group" {
34
- typeGroupParam = &methods[0].RequiredParams[i]
50
+ for i := range ifacesMethod.RequiredParams {
51
+ if ifacesMethod.RequiredParams[i].ID == "if_type_group" {
52
+ typeGroupParam = &ifacesMethod.RequiredParams[i]
53
break
54
}
55
}
@@ -48,6 +66,18 @@ func TestSnmpMethods(t *testing.T) {
66
}
67
}
68
assert.True(t, hasDefault, "should have a default type group option")
69
+
70
+ var bgpViewParam *funcapi.ParamConfig
71
+ for i := range bgpMethod.RequiredParams {
72
+ if bgpMethod.RequiredParams[i].ID == "view" {
73
+ bgpViewParam = &bgpMethod.RequiredParams[i]
74
+ break
75
+ }
76
+ }
77
+ require.NotNil(t, bgpViewParam, "expected BGP view required param")
78
+ require.Len(t, bgpViewParam.Options, 3)
79
+ assert.Equal(t, "peers", bgpViewParam.Options[0].ID)
80
+ assert.True(t, bgpViewParam.Options[0].Default)
81
}
82
83
func TestFuncIfacesColumns(t *testing.T) {
src/go/plugin/go.d/collector/snmp/func_router.go
+39
-3
@@ -5,6 +5,7 @@ package snmp
5
import (
6
"context"
7
"fmt"
8
+ "sync"
9
10
"github.com/netdata/netdata/go/plugins/pkg/funcapi"
11
"github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
@@ -14,15 +15,31 @@ import (
15
type funcRouter struct {
16
ifaceCache *ifaceCache
17
18
+ mu sync.RWMutex
19
handlers map[string]funcapi.MethodHandler
20
}
21
20
-func newFuncRouter(ifaceCache *ifaceCache) *funcRouter {
22
+type registeredSNMPFunction struct {
23
+ methodID string
24
+ handler funcapi.MethodHandler
25
+}
26
+
27
+func newFuncRouter(ifaceCache *ifaceCache, extraHandlers ...registeredSNMPFunction) *funcRouter {
28
r := &funcRouter{
29
ifaceCache: ifaceCache,
30
handlers: make(map[string]funcapi.MethodHandler),
31
}
32
r.registerHandler(ifacesMethodID, newFuncInterfaces(r))
33
+ for _, h := range collectorSpecificFunctionHandlers() {
34
+ if h.methodID != "" {
35
+ r.registerHandler(h.methodID, h.handler)
36
+ }
37
+ }
38
+ for _, h := range extraHandlers {
39
+ if h.methodID != "" {
40
+ r.registerHandler(h.methodID, h.handler)
41
+ }
42
+ }
43
addTopologyFunctionHandler(r.handlers)
44
return r
45
}
@@ -31,6 +48,9 @@ func (r *funcRouter) registerHandler(method string, handler funcapi.MethodHandle
48
if r == nil || handler == nil {
49
return
50
}
51
+ r.mu.Lock()
52
+ defer r.mu.Unlock()
53
+
54
if r.handlers == nil {
55
r.handlers = make(map[string]funcapi.MethodHandler)
56
}
@@ -41,21 +61,36 @@ func (r *funcRouter) registerHandler(method string, handler funcapi.MethodHandle
61
var _ funcapi.MethodHandler = (*funcRouter)(nil)
62
63
func (r *funcRouter) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
44
- if h, ok := r.handlers[method]; ok {
64
+ r.mu.RLock()
65
+ h, ok := r.handlers[method]
66
+ r.mu.RUnlock()
67
+
68
+ if ok {
69
return h.MethodParams(ctx, method)
70
}
71
return nil, fmt.Errorf("unknown method: %s", method)
72
}
73
74
func (r *funcRouter) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
51
- if h, ok := r.handlers[method]; ok {
75
+ r.mu.RLock()
76
+ h, ok := r.handlers[method]
77
+ r.mu.RUnlock()
78
+
79
+ if ok {
80
return h.Handle(ctx, method, params)
81
}
82
return funcapi.NotFoundResponse(method)
83
}
84
85
func (r *funcRouter) Cleanup(ctx context.Context) {
86
+ r.mu.RLock()
87
+ handlers := make([]funcapi.MethodHandler, 0, len(r.handlers))
88
for _, h := range r.handlers {
89
+ handlers = append(handlers, h)
90
+ }
91
+ r.mu.RUnlock()
92
+
93
+ for _, h := range handlers {
94
h.Cleanup(ctx)
95
}
96
}
@@ -64,6 +99,7 @@ func snmpBaseMethods() []funcapi.MethodConfig {
99
methods := []funcapi.MethodConfig{
100
ifacesMethodConfig(),
101
}
102
+ methods = append(methods, collectorSpecificMethodConfigs()...)
103
return appendTopologyMethodConfig(methods)
104
}
105
src/go/plugin/go.d/collector/snmp/func_router_test.go
new
+63
@@ -0,0 +1,63 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "context"
7
+ "sync"
8
+ "testing"
9
+
10
+ "github.com/netdata/netdata/go/plugins/pkg/funcapi"
11
+ "github.com/stretchr/testify/assert"
12
+)
13
+
14
+type testMethodHandler struct{}
15
+
16
+func (testMethodHandler) MethodParams(context.Context, string) ([]funcapi.ParamConfig, error) {
17
+ return nil, nil
18
+}
19
+
20
+func (testMethodHandler) Handle(context.Context, string, funcapi.ResolvedParams) *funcapi.FunctionResponse {
21
+ return &funcapi.FunctionResponse{Status: 200}
22
+}
23
+
24
+func (testMethodHandler) Cleanup(context.Context) {}
25
+
26
+func TestFuncRouter_ConcurrentRegisterAndHandle(t *testing.T) {
27
+ tests := map[string]struct {
28
+ iterations int
29
+ }{
30
+ "registering a late handler does not race with function calls": {
31
+ iterations: 200,
32
+ },
33
+ }
34
+
35
+ for name, tc := range tests {
36
+ t.Run(name, func(t *testing.T) {
37
+ router := newFuncRouter(newIfaceCache())
38
+ handler := testMethodHandler{}
39
+ ctx := context.Background()
40
+
41
+ var wg sync.WaitGroup
42
+ wg.Add(2)
43
+
44
+ go func() {
45
+ defer wg.Done()
46
+ for i := 0; i < tc.iterations; i++ {
47
+ router.registerHandler("dynamic", handler)
48
+ }
49
+ }()
50
+
51
+ go func() {
52
+ defer wg.Done()
53
+ for i := 0; i < tc.iterations; i++ {
54
+ _, _ = router.MethodParams(ctx, "dynamic")
55
+ resp := router.Handle(ctx, "dynamic", nil)
56
+ assert.NotNil(t, resp)
57
+ }
58
+ }()
59
+
60
+ wg.Wait()
61
+ })
62
+ }
63
+}
src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md
+143
-3
@@ -44,6 +44,34 @@ This collector discovers and monitors any SNMP-enabled network device.
44
45
> This table highlights common vendors—the **full library includes many more**.
46
47
+**SNMP BGP monitoring**
48
+
49
+Netdata ships BGP monitoring profiles for generic `BGP4-MIB` devices and vendor MIBs including Cisco, Juniper, Nokia SR OS, Huawei, Arista, and Dell.
50
+
51
+The operator-facing BGP charts are normalized under:
52
+
53
+- `snmp.bgp.peers.*`
54
+- `snmp.bgp.peer_families.*`
55
+- `snmp.bgp.devices.peer_counts`
56
+- `snmp.bgp.devices.peer_states`
57
+
58
+Rich per-peer diagnostics such as previous state, last error, graceful-restart state, and vendor unavailability reasons are exposed through the **Live** function `snmp:bgp-peers` instead of being charted as regular time-series.
59
+
60
+This SNMP BGP surface is designed for:
61
+
62
+- peer/session availability and FSM state
63
+- established uptime
64
+- BGP UPDATE and message traffic
65
+- route-count monitoring where the vendor MIB exposes truthful counts
66
+- stock alerts for peer down, update churn, transition anomalies, and accepted-prefix drift
67
+
68
+**Important limits**
69
+
70
+- Standard `BGP4-MIB` gives peer health and message counters, but **not** full route-count coverage.
71
+- Some route counters are **current gauges**, while others are **cumulative totals**. Netdata keeps them separate instead of flattening unlike semantics into one fake chart.
72
+- Huawei contributes to device-level **peer/session counts**, but not device-level **peer state counts** in this SNMP batch.
73
+- SNMP does **not** provide live per-route inventory. If you need “all routes to and from a peer in real time”, that belongs to BMP, not this integration page.
74
+
75
76
:::info
77
@@ -359,6 +387,29 @@ jobs:
387
```
388
</details>
389
390
+###### BGP router with forced profile
391
+
392
+Use `manual_profiles` when auto-detection cannot safely distinguish the device, or when you want to force a specific vendor BGP profile during testing.
393
+
394
+This example targets a Cisco ASR router and keeps the optional ICMP latency charts enabled.
395
+
396
+
397
+<details open><summary>Config</summary>
398
+
399
+```yaml
400
+jobs:
401
+ - name: edge-router
402
+ update_every: 10
403
+ hostname: 192.0.2.10
404
+ community: public
405
+ manual_profiles:
406
+ - cisco-asr
407
+ options:
408
+ version: 2
409
+
410
+```
411
+</details>
412
+
413
414
415
## Alerts
@@ -375,6 +426,13 @@ The following alerts are available:
426
| [ snmp_license_state_warning ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp.conf) | snmp.license.state | One or more monitored licenses on this device are degraded, in grace, or otherwise in warning state. |
427
| [ snmp_license_state_critical ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp.conf) | snmp.license.state | One or more monitored licenses on this device are expired, invalid, unauthorized, or otherwise in critical state. |
428
| [ snmp_license_usage_high ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp.conf) | snmp.license.usage_percent | The most constrained monitored license pool on this device is nearing exhaustion. |
429
+| [ snmp_bgp_peer_down ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peers.availability | BGP peer is administratively enabled but remains out of Established |
430
+| [ snmp_bgp_peer_family_down ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peer_families.availability | BGP peer-family is administratively enabled but remains out of Established |
431
+| [ snmp_bgp_peer_transitions_anomaly ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peers.established_transitions | ML anomaly detection on per-peer established transition activity |
432
+| [ snmp_bgp_peer_family_transitions_anomaly ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peer_families.established_transitions | ML anomaly detection on per-peer-family established transition activity |
433
+| [ snmp_bgp_peer_updates_anomaly ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peers.update_traffic | ML anomaly detection on per-peer BGP UPDATE traffic |
434
+| [ snmp_bgp_peer_family_updates_anomaly ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peer_families.update_traffic | ML anomaly detection on per-peer-family BGP UPDATE traffic |
435
+| [ snmp_bgp_peer_family_prefixes_accepted_anomaly ](https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf) | snmp.bgp.peer_families.route_counts.current | ML anomaly detection on accepted-prefix gauges where the vendor MIB exposes them |
436
437
438
## Metrics
@@ -418,6 +476,32 @@ To understand the structure of these profiles (metrics, tags, virtual metrics, e
476
477
If `ping.enabled` is true, ICMP latency/packet-loss charts are also provided (or exclusively, when `ping_only: true`).
478
479
+**For BGP-capable profiles, the public chart contract is:**
480
+
481
+- `snmp.bgp.peers.*` for one BGP peer/session per chart instance
482
+- `snmp.bgp.peer_families.*` for one peer plus AFI/SAFI per chart instance
483
+- `snmp.bgp.devices.peer_counts` for device-level peer/session counts
484
+- `snmp.bgp.devices.peer_states` for device-level peer-state summaries where the source MIB exposes canonical peer rows
485
+- Rich peer diagnostics live in the Live function `snmp:bgp-peers`, not in charted time-series
486
+
487
+**BGP capability notes**
488
+
489
+| Vendor / MIB surface | Peer charts | Peer-family charts | Device peer counts | Device peer states | Route counts |
490
+|----------------------|-------------|--------------------|--------------------|--------------------|--------------|
491
+| Standard `BGP4-MIB` | Yes | No | Yes | Yes | No |
492
+| Cisco ASR | Yes | Yes | Yes | Yes | Yes |
493
+| Juniper MX | Yes | Yes | Yes | Yes | Yes |
494
+| Nokia SR OS | Yes | Yes | Yes | Yes | Yes |
495
+| Arista | Yes | Yes | Yes | Yes | Yes |
496
+| Dell OS10 | Yes | Yes | Yes | Yes | Yes |
497
+| Huawei | Partial | Yes | Yes | No | Totals only |
498
+
499
+**Interpretation guidance**
500
+
501
+- `route_counts.current` contains current gauges such as received, accepted, advertised, active, suppressed, or withdrawn prefixes when the vendor MIB exposes them.
502
+- `route_totals` contains cumulative counters where the vendor MIB only exposes totals.
503
+- When the source model is peer-family scoped, alerts and chart labels include AFI/SAFI so operators can distinguish otherwise similar peers.
504
+
505
506
### Per device licensing
507
@@ -505,6 +589,65 @@ Network interface metrics from cached SNMP data, including traffic rates, packet
589
| Multicast In | float | packets/s | hidden | Rate of multicast packets (destined for a group) received per second. Common in video streaming, multicast applications, and routing protocols. |
590
| Multicast Out | float | packets/s | hidden | Rate of multicast packets transmitted per second. |
591
592
+### BGP Peers
593
+
594
+Provides detailed current BGP peer and peer-family state from cached SNMP data.
595
+
596
+This function uses the normalized BGP surface produced during regular SNMP polling and presents it as a sortable, filterable troubleshooting table. It is designed for details that are useful operationally but should not be charted as regular time-series, such as previous state, last error, last down reason, graceful restart state, and vendor-specific unavailability reasons.
597
+
598
+Use cases:
599
+- Identify exactly which peer or peer-family is unhealthy right now
600
+- See the most recent BGP NOTIFICATION error as human-readable text
601
+- Inspect peer identity, AFI/SAFI scope, prefix gauges, and current troubleshooting context in one view
602
+
603
+Data is sourced from the last successful SNMP collection cycle. No additional SNMP requests are triggered when calling this function.
604
+
605
+
606
+| Aspect | Description |
607
+|:-------|:------------|
608
+| Name | `Snmp:bgp-peers` |
609
+| Require Cloud | no |
610
+| Performance | Uses cached normalized SNMP data only, no additional SNMP requests are triggered:<br/>• Responses are instantaneous from memory cache<br/>• Large devices with many peers or peer-families may return many rows |
611
+| Security | Exposes current BGP control-plane state and identifiers only:<br/>• No authentication credentials are exposed<br/>• No device configuration changes are triggered<br/>• No packet payloads or full route inventory are exposed |
612
+| Availability | Available when:<br/>• The collector has completed at least one successful BGP-capable SNMP collection cycle<br/>• BGP peer data exists for the matched profile(s)<br/>• Returns HTTP 503 if no BGP rows are available yet |
613
+
614
+#### Prerequisites
615
+
616
+No additional configuration is required.
617
+
618
+#### Parameters
619
+
620
+| Parameter | Type | Description | Required | Default | Options |
621
+|:---------|:-----|:------------|:--------:|:--------|:--------|
622
+| View | select | Choose whether to show peer rows, peer-family rows, or both. | yes | peers | Peers (default), Peer Families, All |
623
+
624
+#### Returns
625
+
626
+Current BGP peer and peer-family details from cached normalized SNMP data. Each row represents either one peer or one peer plus AFI/SAFI, depending on the selected view. Additional hidden columns provide raw codes, message totals, and threshold fields for deeper inspection in the UI.
627
+
628
+| Column | Type | Unit | Visibility | Description |
629
+|:-------|:-----|:-----|:-----------|:------------|
630
+| Scope | string | | | Whether the row represents a peer or a peer-family. |
631
+| Routing Instance | string | | | Routing-instance / VRF identifier when exposed by the source MIB. |
632
+| Neighbor | string | | | Remote peer address. |
633
+| Local Address | string | | | Local address used for the BGP session when exposed by the source MIB. |
634
+| Remote AS | string | | | Remote Autonomous System number. |
635
+| Peer Description | string | | | Peer description or label when exposed by the source MIB. |
636
+| Family | string | | | Address-family / SAFI scope for peer-family rows. |
637
+| Admin Status | string | | | Whether the peer is administratively enabled. |
638
+| Connection State | string | | | Current BGP FSM state. |
639
+| Previous State | string | | | Previous FSM state when the source MIB exposes it. |
640
+| Established Uptime | integer | seconds | | Time spent in the Established state. |
641
+| Last Update Age | integer | seconds | | Time since the last received UPDATE. |
642
+| Updates Received | integer | updates | | Current received UPDATE counter from the latest poll. |
643
+| Updates Sent | integer | updates | | Current sent UPDATE counter from the latest poll. |
644
+| Prefixes Accepted | integer | prefixes | | Current accepted-prefix gauge where the source MIB exposes it. |
645
+| Prefixes Advertised | integer | prefixes | | Current advertised-prefix gauge where the source MIB exposes it. |
646
+| Last Error | string | | | Human-readable BGP last-error text derived from the code/subcode pair when available. |
647
+| Down Reason | string | | | Last peer-down reason when the source MIB exposes it. |
648
+| GR State | string | | | Graceful-restart state for peer-family scoped rows when exposed by the source MIB. |
649
+| Unavailability Reason | string | | | Vendor-specific unavailability reason for peer-family scoped rows when exposed by the source MIB. |
650
+
651
### Network Topology
652
653
Provides the agent-wide SNMP topology view built from all currently running topology-enabled SNMP jobs.
@@ -723,6 +866,3 @@ Table metrics are usually the slowest and often determine the total collection t
866
1. Do logs show “skipping data collection”?
867
2. Does *Internal → Stats* show collection time > `update_every`?
868
3. Increase `update_every` until skips disappear.
726
-
727
-
728
-
src/go/plugin/go.d/collector/snmp/metadata.yaml
+215
@@ -193,6 +193,34 @@ modules:
193
| Infrastructure | APC UPS/PDU, Dell servers, plus standard MIBs (BGP, OSPF, TCP/UDP) |
194
195
> This table highlights common vendors—the **full library includes many more**.
196
+
197
+ **SNMP BGP monitoring**
198
+
199
+ Netdata ships BGP monitoring profiles for generic `BGP4-MIB` devices and vendor MIBs including Cisco, Juniper, Nokia SR OS, Huawei, Arista, and Dell.
200
+
201
+ The operator-facing BGP charts are normalized under:
202
+
203
+ - `snmp.bgp.peers.*`
204
+ - `snmp.bgp.peer_families.*`
205
+ - `snmp.bgp.devices.peer_counts`
206
+ - `snmp.bgp.devices.peer_states`
207
+
208
+ Rich per-peer diagnostics such as previous state, last error, graceful-restart state, and vendor unavailability reasons are exposed through the **Live** function `snmp:bgp-peers` instead of being charted as regular time-series.
209
+
210
+ This SNMP BGP surface is designed for:
211
+
212
+ - peer/session availability and FSM state
213
+ - established uptime
214
+ - BGP UPDATE and message traffic
215
+ - route-count monitoring where the vendor MIB exposes truthful counts
216
+ - stock alerts for peer down, update churn, transition anomalies, and accepted-prefix drift
217
+
218
+ **Important limits**
219
+
220
+ - Standard `BGP4-MIB` gives peer health and message counters, but **not** full route-count coverage.
221
+ - Some route counters are **current gauges**, while others are **cumulative totals**. Netdata keeps them separate instead of flattening unlike semantics into one fake chart.
222
+ - Huawei contributes to device-level **peer/session counts**, but not device-level **peer state counts** in this SNMP batch.
223
+ - SNMP does **not** provide live per-route inventory. If you need “all routes to and from a peer in real time”, that belongs to BMP, not this integration page.
224
method_description: |
225
:::info
226
@@ -524,6 +552,21 @@ modules:
552
- <<: *snmp_v3_job
553
name: switch3
554
hostname: 192.0.2.3
555
+ - name: BGP router with forced profile
556
+ description: |
557
+ Use `manual_profiles` when auto-detection cannot safely distinguish the device, or when you want to force a specific vendor BGP profile during testing.
558
+
559
+ This example targets a Cisco ASR router and keeps the optional ICMP latency charts enabled.
560
+ config: |
561
+ jobs:
562
+ - name: edge-router
563
+ update_every: 10
564
+ hostname: 192.0.2.10
565
+ community: public
566
+ manual_profiles:
567
+ - cisco-asr
568
+ options:
569
+ version: 2
570
alerts:
571
- name: snmp_license_expiring
572
metric: snmp.license.remaining_time
@@ -553,6 +596,34 @@ modules:
596
metric: snmp.license.usage_percent
597
info: "The most constrained monitored license pool on this device is nearing exhaustion."
598
link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp.conf
599
+ - name: snmp_bgp_peer_down
600
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
601
+ metric: snmp.bgp.peers.availability
602
+ info: BGP peer is administratively enabled but remains out of Established
603
+ - name: snmp_bgp_peer_family_down
604
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
605
+ metric: snmp.bgp.peer_families.availability
606
+ info: BGP peer-family is administratively enabled but remains out of Established
607
+ - name: snmp_bgp_peer_transitions_anomaly
608
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
609
+ metric: snmp.bgp.peers.established_transitions
610
+ info: ML anomaly detection on per-peer established transition activity
611
+ - name: snmp_bgp_peer_family_transitions_anomaly
612
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
613
+ metric: snmp.bgp.peer_families.established_transitions
614
+ info: ML anomaly detection on per-peer-family established transition activity
615
+ - name: snmp_bgp_peer_updates_anomaly
616
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
617
+ metric: snmp.bgp.peers.update_traffic
618
+ info: ML anomaly detection on per-peer BGP UPDATE traffic
619
+ - name: snmp_bgp_peer_family_updates_anomaly
620
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
621
+ metric: snmp.bgp.peer_families.update_traffic
622
+ info: ML anomaly detection on per-peer-family BGP UPDATE traffic
623
+ - name: snmp_bgp_peer_family_prefixes_accepted_anomaly
624
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/snmp_bgp.conf
625
+ metric: snmp.bgp.peer_families.route_counts.current
626
+ info: ML anomaly detection on accepted-prefix gauges where the vendor MIB exposes them
627
functions:
628
description: |
629
This collector exposes real-time functions for interactive troubleshooting in the Live tab.
@@ -681,6 +752,124 @@ modules:
752
availability: |
753
Available when:<br/>• The collector has completed at least one data collection cycle<br/>• Interface data is cached from the last successful SNMP collection<br/>• Returns HTTP 503 if cache is not ready yet
754
755
+ - id: bgp-peers
756
+ name: BGP Peers
757
+ description: |
758
+ Provides detailed current BGP peer and peer-family state from cached SNMP data.
759
+
760
+ This function uses the normalized BGP surface produced during regular SNMP polling and presents it as a sortable, filterable troubleshooting table. It is designed for details that are useful operationally but should not be charted as regular time-series, such as previous state, last error, last down reason, graceful restart state, and vendor-specific unavailability reasons.
761
+
762
+ Use cases:
763
+ - Identify exactly which peer or peer-family is unhealthy right now
764
+ - See the most recent BGP NOTIFICATION error as human-readable text
765
+ - Inspect peer identity, AFI/SAFI scope, prefix gauges, and current troubleshooting context in one view
766
+
767
+ Data is sourced from the last successful SNMP collection cycle. No additional SNMP requests are triggered when calling this function.
768
+ parameters:
769
+ - id: view
770
+ name: View
771
+ description: Choose whether to show peer rows, peer-family rows, or both.
772
+ type: select
773
+ required: true
774
+ default: peers
775
+ options:
776
+ - id: peers
777
+ name: Peers
778
+ default: true
779
+ - id: peer_families
780
+ name: Peer Families
781
+ - id: all
782
+ name: All
783
+ returns:
784
+ description: Current BGP peer and peer-family details from cached normalized SNMP data. Each row represents either one peer or one peer plus AFI/SAFI, depending on the selected view. Additional hidden columns provide raw codes, message totals, and threshold fields for deeper inspection in the UI.
785
+ columns:
786
+ - name: Scope
787
+ type: string
788
+ unit: ""
789
+ description: Whether the row represents a peer or a peer-family.
790
+ - name: Routing Instance
791
+ type: string
792
+ unit: ""
793
+ description: Routing-instance / VRF identifier when exposed by the source MIB.
794
+ - name: Neighbor
795
+ type: string
796
+ unit: ""
797
+ description: Remote peer address.
798
+ - name: Local Address
799
+ type: string
800
+ unit: ""
801
+ description: Local address used for the BGP session when exposed by the source MIB.
802
+ - name: Remote AS
803
+ type: string
804
+ unit: ""
805
+ description: Remote Autonomous System number.
806
+ - name: Peer Description
807
+ type: string
808
+ unit: ""
809
+ description: Peer description or label when exposed by the source MIB.
810
+ - name: Family
811
+ type: string
812
+ unit: ""
813
+ description: Address-family / SAFI scope for peer-family rows.
814
+ - name: Admin Status
815
+ type: string
816
+ unit: ""
817
+ description: Whether the peer is administratively enabled.
818
+ - name: Connection State
819
+ type: string
820
+ unit: ""
821
+ description: Current BGP FSM state.
822
+ - name: Previous State
823
+ type: string
824
+ unit: ""
825
+ description: Previous FSM state when the source MIB exposes it.
826
+ - name: Established Uptime
827
+ type: integer
828
+ unit: "seconds"
829
+ description: Time spent in the Established state.
830
+ - name: Last Update Age
831
+ type: integer
832
+ unit: "seconds"
833
+ description: Time since the last received UPDATE.
834
+ - name: Updates Received
835
+ type: integer
836
+ unit: "updates"
837
+ description: Current received UPDATE counter from the latest poll.
838
+ - name: Updates Sent
839
+ type: integer
840
+ unit: "updates"
841
+ description: Current sent UPDATE counter from the latest poll.
842
+ - name: Prefixes Accepted
843
+ type: integer
844
+ unit: "prefixes"
845
+ description: Current accepted-prefix gauge where the source MIB exposes it.
846
+ - name: Prefixes Advertised
847
+ type: integer
848
+ unit: "prefixes"
849
+ description: Current advertised-prefix gauge where the source MIB exposes it.
850
+ - name: Last Error
851
+ type: string
852
+ unit: ""
853
+ description: Human-readable BGP last-error text derived from the code/subcode pair when available.
854
+ - name: Down Reason
855
+ type: string
856
+ unit: ""
857
+ description: Last peer-down reason when the source MIB exposes it.
858
+ - name: GR State
859
+ type: string
860
+ unit: ""
861
+ description: Graceful-restart state for peer-family scoped rows when exposed by the source MIB.
862
+ - name: Unavailability Reason
863
+ type: string
864
+ unit: ""
865
+ description: Vendor-specific unavailability reason for peer-family scoped rows when exposed by the source MIB.
866
+ performance: |
867
+ Uses cached normalized SNMP data only, no additional SNMP requests are triggered:<br/>• Responses are instantaneous from memory cache<br/>• Large devices with many peers or peer-families may return many rows
868
+ security: |
869
+ Exposes current BGP control-plane state and identifiers only:<br/>• No authentication credentials are exposed<br/>• No device configuration changes are triggered<br/>• No packet payloads or full route inventory are exposed
870
+ availability: |
871
+ Available when:<br/>• The collector has completed at least one successful BGP-capable SNMP collection cycle<br/>• BGP peer data exists for the matched profile(s)<br/>• Returns HTTP 503 if no BGP rows are available yet
872
+
873
- id: topology
874
name: Network Topology
875
description: |
@@ -929,6 +1118,32 @@ modules:
1118
:::
1119
1120
If `ping.enabled` is true, ICMP latency/packet-loss charts are also provided (or exclusively, when `ping_only: true`).
1121
+
1122
+ **For BGP-capable profiles, the public chart contract is:**
1123
+
1124
+ - `snmp.bgp.peers.*` for one BGP peer/session per chart instance
1125
+ - `snmp.bgp.peer_families.*` for one peer plus AFI/SAFI per chart instance
1126
+ - `snmp.bgp.devices.peer_counts` for device-level peer/session counts
1127
+ - `snmp.bgp.devices.peer_states` for device-level peer-state summaries where the source MIB exposes canonical peer rows
1128
+ - Rich peer diagnostics live in the Live function `snmp:bgp-peers`, not in charted time-series
1129
+
1130
+ **BGP capability notes**
1131
+
1132
+ | Vendor / MIB surface | Peer charts | Peer-family charts | Device peer counts | Device peer states | Route counts |
1133
+ |----------------------|-------------|--------------------|--------------------|--------------------|--------------|
1134
+ | Standard `BGP4-MIB` | Yes | No | Yes | Yes | No |
1135
+ | Cisco ASR | Yes | Yes | Yes | Yes | Yes |
1136
+ | Juniper MX | Yes | Yes | Yes | Yes | Yes |
1137
+ | Nokia SR OS | Yes | Yes | Yes | Yes | Yes |
1138
+ | Arista | Yes | Yes | Yes | Yes | Yes |
1139
+ | Dell OS10 | Yes | Yes | Yes | Yes | Yes |
1140
+ | Huawei | Partial | Yes | Yes | No | Totals only |
1141
+
1142
+ **Interpretation guidance**
1143
+
1144
+ - `route_counts.current` contains current gauges such as received, accepted, advertised, active, suppressed, or withdrawn prefixes when the vendor MIB exposes them.
1145
+ - `route_totals` contains cumulative counters where the vendor MIB only exposes totals.
1146
+ - When the source model is peer-family scoped, alerts and chart labels include AFI/SAFI so operators can distinguish otherwise similar peers.
1147
availability: []
1148
scopes:
1149
- name: device licensing
src/go/plugin/go.d/collector/snmp/metric_ids.go
new
+64
@@ -0,0 +1,64 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import "strings"
6
+
7
+func chartIDFromName(name string) string {
8
+ if isBGPChartMetricName(name) {
9
+ return "snmp_" + cleanMetricName.Replace(name)
10
+ }
11
+ return "snmp_device_prof_" + cleanMetricName.Replace(name)
12
+}
13
+
14
+func chartIDFromKey(key string) string {
15
+ if isBGPChartMetricKey(key) {
16
+ return "snmp_" + cleanMetricName.Replace(key)
17
+ }
18
+ return "snmp_device_prof_" + cleanMetricName.Replace(key)
19
+}
20
+
21
+func metricIDFromName(name string, subkeys ...string) string {
22
+ if isBGPChartMetricName(name) {
23
+ return cleanedMetricID("snmp_", name, subkeys...)
24
+ }
25
+ return rawMetricID("snmp_device_prof_", name, subkeys...)
26
+}
27
+
28
+func metricIDFromKey(key string, subkeys ...string) string {
29
+ if isBGPChartMetricKey(key) {
30
+ return cleanedMetricID("snmp_", key, subkeys...)
31
+ }
32
+ return rawMetricID("snmp_device_prof_", key, subkeys...)
33
+}
34
+
35
+func rawMetricID(prefix, base string, subkeys ...string) string {
36
+ id := prefix + base
37
+ for _, subkey := range subkeys {
38
+ id += "_" + subkey
39
+ }
40
+ return id
41
+}
42
+
43
+func cleanedMetricID(prefix, base string, subkeys ...string) string {
44
+ id := prefix + cleanMetricName.Replace(base)
45
+ for _, subkey := range subkeys {
46
+ id += "_" + cleanMetricName.Replace(subkey)
47
+ }
48
+ return id
49
+}
50
+
51
+func chartContextID(name string) string {
52
+ if isBGPChartMetricName(name) {
53
+ return "snmp." + name
54
+ }
55
+ return "snmp.device_prof_" + cleanMetricName.Replace(name)
56
+}
57
+
58
+func isBGPChartMetricName(name string) bool {
59
+ return strings.HasPrefix(name, "bgp.")
60
+}
61
+
62
+func isBGPChartMetricKey(key string) bool {
63
+ return strings.HasPrefix(key, "bgp.")
64
+}
src/go/plugin/go.d/collector/snmp/metric_ids_test.go
new
+39
@@ -0,0 +1,39 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/stretchr/testify/assert"
9
+)
10
+
11
+func TestMetricIDs(t *testing.T) {
12
+ tests := map[string]struct {
13
+ call func() string
14
+ want string
15
+ }{
16
+ "generic profile metric name keeps legacy raw ID": {
17
+ call: func() string { return metricIDFromName("metric.with space", "sub.key with space") },
18
+ want: "snmp_device_prof_metric.with space_sub.key with space",
19
+ },
20
+ "generic profile metric key keeps legacy raw ID": {
21
+ call: func() string { return metricIDFromKey("metric_192.0.2.10 with space", "rx.bytes") },
22
+ want: "snmp_device_prof_metric_192.0.2.10 with space_rx.bytes",
23
+ },
24
+ "generic profile chart key stays cleaned": {
25
+ call: func() string { return chartIDFromKey("metric_192.0.2.10 with space") },
26
+ want: "snmp_device_prof_metric_192_0_2_10_with_space",
27
+ },
28
+ "BGP chart metric name uses cleaned ID": {
29
+ call: func() string { return metricIDFromName("bgp.peers.message_traffic", "received.total") },
30
+ want: "snmp_bgp_peers_message_traffic_received_total",
31
+ },
32
+ }
33
+
34
+ for name, tc := range tests {
35
+ t.Run(name, func(t *testing.T) {
36
+ assert.Equal(t, tc.want, tc.call())
37
+ })
38
+ }
39
+}
src/go/plugin/go.d/collector/snmp/profile-format.md
+120
-1
@@ -150,6 +150,8 @@ extends: <base profiles to include>
150
metadata: <device information>
151
metrics: <what to collect>
152
topology: <what to collect for topology>
153
+bgp: <what to collect for typed BGP monitoring>
154
+licensing: <what to collect for typed licensing>
155
metric_tags: <global tags>
156
static_tags: <static tags>
157
virtual_metrics: <calculated metrics>
@@ -162,6 +164,8 @@ virtual_metrics: <calculated metrics>
164
| [**metadata**](#3-metadata) | Collects device-level information (host labels). |
165
| [**metrics**](#4-metrics) | Defines which OIDs to collect and how to chart them. |
166
| [**topology**](#41-topology) | Defines SNMP topology rows and their topology kind. |
167
+| [**bgp**](#bgp-rows) | Defines typed BGP device, peer, and peer-family rows. |
168
+| [**licensing**](#licensing-rows) | Defines typed license rows. |
169
| [**metric_tags**](#5-metric_tags) | Defines global dynamic tags collected once per device and attached to all metrics. |
170
| [**static_tags**](#6-static_tags) | Defines fixed tags applied to all metrics. |
171
| [**virtual_metrics**](#7-virtual_metrics) | Defines calculated or aggregated metrics based on others. |
@@ -1233,7 +1237,7 @@ metrics:
1237
1238
**Important behavior**:
1239
1236
-- `lookup_symbol` is used only for **cross-table tags**
1240
+- `lookup_symbol` is used for **cross-table tags** and typed BGP value fields
1241
- it works together with `index_transform`, not instead of it
1242
- if no row matches, the tag lookup fails for that row
1243
- if one row matches, the collector reads the requested tag from that row
@@ -1342,6 +1346,23 @@ The two index extraction mechanisms use different counting bases:
1346
So `index: 1` and `index_transform: [{start: 0, end: 0}]` both extract the
1347
first index component.
1348
1349
+Typed BGP value fields also support `index_from_end: N`, where `N` is
1350
+1-based from the right side of the row index. Use it only when the target
1351
+INDEX component is a trailing component after a variable-length field, such as
1352
+AFI/SAFI after an `InetAddress` peer address.
1353
+
1354
+Use exactly one row-index selector per typed BGP value: `index`,
1355
+`index_from_end`, or `index_transform`. Profile validation rejects typed BGP
1356
+values that set more than one of these selectors.
1357
+
1358
+Typed BGP cross-table value fields can also use `lookup_symbol` with
1359
+`table:` and `index_transform:`. This is needed when a BGP peer-family table is
1360
+indexed by a compact peer ID, but peer identity fields such as neighbor and
1361
+remote AS live in a peer table keyed by a different composite index. The
1362
+collector extracts the lookup value from the current row index, finds the row
1363
+in the referenced table whose `lookup_symbol` column has that value, and then
1364
+reads the requested typed value symbol from the matched row.
1365
+
1366
Examples:
1367
1368
- `Q-BRIDGE-MIB::dot1qTpFdbAddress` is `not-accessible` and is part of the
@@ -2185,6 +2206,104 @@ What this does
2206
- Each `as` becomes a **dimension** (`in_ucast`, `out_ucast`, `in_mcast`, …).
2207
- No `per_row`/`group_by` → totals aggregated across all interfaces.
2208
2209
+## BGP rows
2210
+
2211
+The SNMP collector ships a shared BGP pipeline that turns vendor-specific BGP
2212
+MIB rows into typed device, peer, and peer-family rows. Profiles describe this
2213
+telemetry in a top-level `bgp:` section. The collector emits typed BGP rows from
2214
+that section; underscore-prefixed helper tags and `virtual_metrics:` aliases
2215
+are legacy migration mechanisms, not the preferred BGP transport.
2216
+
2217
+### Authoring contract
2218
+
2219
+BGP row `kind` values are closed:
2220
+
2221
+- `device` — device-level BGP summaries with no peer identity.
2222
+- `peer` — peer-level rows identified by `neighbor` and `remote_as`.
2223
+- `peer_family` — address-family rows identified by `neighbor`, `remote_as`,
2224
+ `address_family`, and `subsequent_address_family`.
2225
+
2226
+Peer-state mappings must use the six RFC 4271 state names: `idle`, `connect`,
2227
+`active`, `opensent`, `openconfirm`, and `established`. A complete source must
2228
+map all six states. If a source MIB is intentionally partial, set
2229
+`partial: true` and use `partial_states: [...]` to record which canonical
2230
+states the source can represent.
2231
+
2232
+When a peer or peer-family row does not provide a routing instance, the public
2233
+chart/function label defaults to `default`. Profiles may still set
2234
+`identity.routing_instance` explicitly when a vendor MIB exposes VRF or routing
2235
+instance identity.
2236
+
2237
+Device rows support `device_counts.peers`, `device_counts.ibgp_peers`,
2238
+`device_counts.ebgp_peers`, and per-state counters under
2239
+`device_counts.states`. Peer and peer-family rows support typed groups such as
2240
+`admin`, `state`, `connection`, `traffic`, `transitions`, `timers`,
2241
+`last_error`, `last_notifications`, `reasons`, `graceful_restart`, `routes`,
2242
+and `route_limits`.
2243
+
2244
+For table-backed rows, readable columns use `symbol:`. `not-accessible` index
2245
+objects must be derived from the row index with `index`, `index_from_end`, or
2246
+`index_transform`. Values from related tables use the first-class `table:`
2247
+field on the BGP value, optionally with `lookup_symbol:` when the current row
2248
+must join to another table by value.
2249
+
2250
+Example device-level counts:
2251
+
2252
+```yaml
2253
+bgp:
2254
+ - id: vendor-bgp-device-counts
2255
+ MIB: VENDOR-BGP-MIB
2256
+ kind: device
2257
+ device_counts:
2258
+ peers:
2259
+ symbol: { OID: 1.3.6.1.4.1.99999.1, name: vendor.bgpPeerSessionNum }
2260
+ ibgp_peers:
2261
+ symbol: { OID: 1.3.6.1.4.1.99999.2, name: vendor.iBgpPeerSessionNum }
2262
+ ebgp_peers:
2263
+ symbol: { OID: 1.3.6.1.4.1.99999.3, name: vendor.eBgpPeerSessionNum }
2264
+```
2265
+
2266
+Example peer-family row:
2267
+
2268
+```yaml
2269
+bgp:
2270
+ - id: vendor-bgp-peer-family
2271
+ MIB: VENDOR-BGP-MIB
2272
+ kind: peer_family
2273
+ table:
2274
+ OID: 1.3.6.1.4.1.99999.10
2275
+ name: vendorBgpPeerTable
2276
+ identity:
2277
+ routing_instance: { value: default }
2278
+ neighbor:
2279
+ symbol: { OID: 1.3.6.1.4.1.99999.10.1.4, name: vendorBgpPeerRemoteAddr, format: ip_address }
2280
+ remote_as:
2281
+ symbol: { OID: 1.3.6.1.4.1.99999.10.1.5, name: vendorBgpPeerRemoteAs, format: uint32 }
2282
+ address_family:
2283
+ index: 1
2284
+ mapping: { 1: ipv4, 2: ipv6, 25: l2vpn }
2285
+ subsequent_address_family:
2286
+ index: 2
2287
+ mapping: { 1: unicast, 128: vpn }
2288
+ state:
2289
+ symbol:
2290
+ OID: 1.3.6.1.4.1.99999.10.1.6
2291
+ name: vendorBgpPeerState
2292
+ mapping:
2293
+ 1: idle
2294
+ 2: connect
2295
+ 3: active
2296
+ 4: opensent
2297
+ 5: openconfirm
2298
+ 6: established
2299
+ traffic:
2300
+ updates:
2301
+ received:
2302
+ symbol: { OID: 1.3.6.1.4.1.99999.10.1.7, name: vendorBgpPeerInUpdates }
2303
+ sent:
2304
+ symbol: { OID: 1.3.6.1.4.1.99999.10.1.8, name: vendorBgpPeerOutUpdates }
2305
+```
2306
+
2307
## Licensing rows
2308
2309
The SNMP collector ships a **shared device-level licensing pipeline** that
src/go/plugin/go.d/collector/snmp/profile_sets.go
+6
-1
@@ -23,7 +23,12 @@ func (c *Collector) setupProfiles(si *snmputils.SysInfo) []*ddsnmp.Profile {
23
matchedProfiles := resolved.Profiles()
24
c.logMatchedProfiles(matchedProfiles, si.SysObjectID)
25
26
- return resolved.Project(ddsnmp.ConsumerMetrics, ddsnmp.ConsumerLicensing).Profiles()
26
+ profiles := resolved.Project(ddsnmp.ConsumerMetrics, ddsnmp.ConsumerLicensing, ddsnmp.ConsumerBGP).Profiles()
27
+ if profilesHaveBGP(profiles) {
28
+ c.enableBGPIntegration()
29
+ }
30
+
31
+ return profiles
32
}
33
34
func (c *Collector) logMatchedProfiles(profiles []*ddsnmp.Profile, sysObjectID string) {
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_alcatel-bgp.yaml
new
+397
@@ -0,0 +1,397 @@
1
+# Alcatel-Lucent AOS7 BGP peer monitoring via BGP4-MIB and ALCATEL-IND1-BGP-MIB.
2
+# IPv4 peer state comes from the standard BGP4-MIB peer table; Alcatel-specific
3
+# peer fields are joined from alaBgpPeerTable by the same IPv4 peer address index.
4
+# IPv6 peers are available only in ALCATEL-IND1-BGP-MIB.
5
+
6
+bgp:
7
+ - id: alcatel-bgp4-peer
8
+ MIB: BGP4-MIB
9
+ kind: peer
10
+ table: &alcatel_bgp4_peer_table
11
+ name: bgpPeerTable
12
+ OID: 1.3.6.1.2.1.15.3
13
+ identity: &alcatel_bgp4_peer_identity
14
+ routing_instance: &alcatel_bgp_default_routing_instance
15
+ value: default
16
+ neighbor: &alcatel_bgp4_neighbor
17
+ symbol:
18
+ OID: 1.3.6.1.2.1.15.3.1.7
19
+ name: bgpPeerRemoteAddr
20
+ format: ip_address
21
+ remote_as: &alcatel_bgp4_remote_as
22
+ symbol:
23
+ OID: 1.3.6.1.2.1.15.3.1.9
24
+ name: bgpPeerRemoteAs
25
+ format: uint32
26
+ descriptors:
27
+ bgp_version:
28
+ symbol:
29
+ OID: 1.3.6.1.2.1.15.3.1.4
30
+ name: bgpPeerNegotiatedVersion
31
+ local_address: &alcatel_bgp4_local_address
32
+ symbol:
33
+ OID: 1.3.6.1.2.1.15.3.1.5
34
+ name: bgpPeerLocalAddr
35
+ format: ip_address
36
+ peer_identifier:
37
+ symbol:
38
+ OID: 1.3.6.1.2.1.15.3.1.1
39
+ name: bgpPeerIdentifier
40
+ format: ip_address
41
+ description:
42
+ table: alaBgpPeerTable
43
+ symbol:
44
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.4
45
+ name: alaBgpPeerName
46
+ peer_type: &alcatel_bgp4_peer_type
47
+ table: alaBgpPeerTable
48
+ symbol:
49
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.27
50
+ name: alaBgpPeerType
51
+ mapping: &alcatel_bgp_peer_type_mapping
52
+ 1: internal
53
+ 2: external
54
+ admin:
55
+ enabled:
56
+ symbol:
57
+ OID: 1.3.6.1.2.1.15.3.1.3
58
+ name: bgpPeerAdminStatus
59
+ mapping: &alcatel_bgp_admin_mapping
60
+ 1: stop
61
+ 2: start
62
+ state:
63
+ symbol:
64
+ OID: 1.3.6.1.2.1.15.3.1.2
65
+ name: bgpPeerState
66
+ mapping: &alcatel_bgp_state_mapping
67
+ 1: idle
68
+ 2: connect
69
+ 3: active
70
+ 4: opensent
71
+ 5: openconfirm
72
+ 6: established
73
+ connection:
74
+ established_uptime:
75
+ symbol:
76
+ OID: 1.3.6.1.2.1.15.3.1.16
77
+ name: bgpPeerFsmEstablishedTime
78
+ last_received_update_age:
79
+ symbol:
80
+ OID: 1.3.6.1.2.1.15.3.1.24
81
+ name: bgpPeerInUpdateElapsedTime
82
+ traffic:
83
+ messages:
84
+ received:
85
+ symbol:
86
+ OID: 1.3.6.1.2.1.15.3.1.12
87
+ name: bgpPeerInTotalMessages
88
+ sent:
89
+ symbol:
90
+ OID: 1.3.6.1.2.1.15.3.1.13
91
+ name: bgpPeerOutTotalMessages
92
+ updates:
93
+ received:
94
+ symbol:
95
+ OID: 1.3.6.1.2.1.15.3.1.10
96
+ name: bgpPeerInUpdates
97
+ sent:
98
+ symbol:
99
+ OID: 1.3.6.1.2.1.15.3.1.11
100
+ name: bgpPeerOutUpdates
101
+ notifications:
102
+ received:
103
+ table: alaBgpPeerTable
104
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.21, name: alaBgpPeerRcvdNotifyMsgs }
105
+ sent:
106
+ table: alaBgpPeerTable
107
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.22, name: alaBgpPeerSentNotifyMsgs }
108
+ route_refreshes:
109
+ received:
110
+ table: alaBgpPeerTable
111
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.13, name: alaBgpPeerRcvdRtRefreshMsgs }
112
+ sent:
113
+ table: alaBgpPeerTable
114
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.14, name: alaBgpPeerSentRtRefreshMsgs }
115
+ transitions:
116
+ established:
117
+ symbol:
118
+ OID: 1.3.6.1.2.1.15.3.1.15
119
+ name: bgpPeerFsmEstablishedTransitions
120
+ up:
121
+ table: alaBgpPeerTable
122
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.47, name: alaBgpPeerUpTransitions }
123
+ down:
124
+ table: alaBgpPeerTable
125
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.26, name: alaBgpPeerDownTransitions }
126
+ timers:
127
+ negotiated:
128
+ hold_time:
129
+ symbol:
130
+ OID: 1.3.6.1.2.1.15.3.1.18
131
+ name: bgpPeerHoldTime
132
+ keepalive_time:
133
+ symbol:
134
+ OID: 1.3.6.1.2.1.15.3.1.19
135
+ name: bgpPeerKeepAlive
136
+ configured:
137
+ connect_retry:
138
+ symbol:
139
+ OID: 1.3.6.1.2.1.15.3.1.17
140
+ name: bgpPeerConnectRetryInterval
141
+ hold_time:
142
+ symbol:
143
+ OID: 1.3.6.1.2.1.15.3.1.20
144
+ name: bgpPeerHoldTimeConfigured
145
+ keepalive_time:
146
+ symbol:
147
+ OID: 1.3.6.1.2.1.15.3.1.21
148
+ name: bgpPeerKeepAliveConfigured
149
+ min_as_origination_interval:
150
+ symbol:
151
+ OID: 1.3.6.1.2.1.15.3.1.22
152
+ name: bgpPeerMinASOriginationInterval
153
+ min_route_advertisement_interval:
154
+ symbol:
155
+ OID: 1.3.6.1.2.1.15.3.1.23
156
+ name: bgpPeerMinRouteAdvertisementInterval
157
+ last_error:
158
+ code:
159
+ symbol:
160
+ OID: 1.3.6.1.2.1.15.3.1.14
161
+ name: bgpPeerLastErrorCode
162
+ format: hex
163
+ extract_value: '^([0-9a-f]{2})'
164
+ subcode:
165
+ symbol:
166
+ OID: 1.3.6.1.2.1.15.3.1.14
167
+ name: bgpPeerLastErrorSubcode
168
+ format: hex
169
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
170
+ reasons:
171
+ last_down:
172
+ table: alaBgpPeerTable
173
+ symbol:
174
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.18
175
+ name: alaBgpPeerLastDownReason
176
+ mapping: &alcatel_bgp_last_down_reason_mapping
177
+ 1: user_request
178
+ 2: connection_timeout
179
+ 3: hold_timeout
180
+ 4: bad_message
181
+ 5: fsm_unexpected_event
182
+ 6: peer_closed
183
+ 7: peer_notify
184
+ 8: transport_error
185
+ 9: none
186
+ last_notifications:
187
+ received:
188
+ reason:
189
+ table: alaBgpPeerTable
190
+ symbol:
191
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.24
192
+ name: alaBgpPeerLastRecvNotifyReason
193
+ mapping: &alcatel_bgp_notify_reason_mapping
194
+ 1: msg_hdr_no_sync
195
+ 2: msg_hdr_bad_len
196
+ 3: msg_hdr_bad_type
197
+ 4: open_unsupp_version
198
+ 5: open_bad_as
199
+ 6: open_bad_id
200
+ 7: open_unsupp_option
201
+ 8: open_auth_fail
202
+ 9: open_bad_holdtime
203
+ 10: open_unsupp_capability
204
+ 11: update_malform_attr
205
+ 12: update_unsupp_wknwn_attr
206
+ 13: update_missing_wknwn_attr
207
+ 14: update_bad_attr_flags
208
+ 15: update_bad_attr_len
209
+ 16: update_bad_origin
210
+ 17: update_as_loop
211
+ 18: update_bad_nexthop
212
+ 19: update_bad_opt_attr
213
+ 20: update_bad_net
214
+ 21: update_bad_aspath
215
+ 22: hold_timeout
216
+ 23: fsm_error
217
+ 24: cease_max_prefix_reached
218
+ 25: cease_admin_shutdown
219
+ 26: cease_peer_deconfigured
220
+ 27: cease_admin_reset
221
+ 28: cease_conn_rejected
222
+ 29: cease_other_conf_change
223
+ 30: cease_conn_collision_resolution
224
+ 31: cease_out_of_resources
225
+ 32: none
226
+ sent:
227
+ reason:
228
+ table: alaBgpPeerTable
229
+ symbol:
230
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.23
231
+ name: alaBgpPeerLastSentNotifyReason
232
+ mapping: *alcatel_bgp_notify_reason_mapping
233
+
234
+ - id: alcatel-bgp4-peer-family
235
+ MIB: ALCATEL-IND1-BGP-MIB
236
+ kind: peer_family
237
+ table: &alcatel_bgp4_alcatel_peer_table
238
+ name: alaBgpPeerTable
239
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2
240
+ identity:
241
+ routing_instance: *alcatel_bgp_default_routing_instance
242
+ neighbor: &alcatel_bgp4_alcatel_neighbor
243
+ symbol:
244
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.1
245
+ name: alaBgpPeerAddr
246
+ format: ip_address
247
+ remote_as: &alcatel_bgp4_alcatel_remote_as
248
+ symbol:
249
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.2
250
+ name: alaBgpPeerAS
251
+ format: uint32
252
+ address_family:
253
+ value: ipv4
254
+ subsequent_address_family:
255
+ value: unicast
256
+ descriptors:
257
+ local_address:
258
+ symbol:
259
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.17
260
+ name: alaBgpPeerLocalAddr
261
+ format: ip_address
262
+ description:
263
+ symbol:
264
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.4
265
+ name: alaBgpPeerName
266
+ peer_type: *alcatel_bgp4_peer_type
267
+ routes:
268
+ current:
269
+ received:
270
+ symbol:
271
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.2.1.25
272
+ name: alaBgpPeerRcvdPrefixes
273
+
274
+ - id: alcatel-bgp6-peer
275
+ MIB: ALCATEL-IND1-BGP-MIB
276
+ kind: peer
277
+ table: &alcatel_bgp6_peer_table
278
+ name: alaBgpPeer6Table
279
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14
280
+ identity: &alcatel_bgp6_peer_identity
281
+ routing_instance: *alcatel_bgp_default_routing_instance
282
+ neighbor: &alcatel_bgp6_neighbor
283
+ symbol:
284
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.1
285
+ name: alaBgpPeer6Addr
286
+ format: ip_address
287
+ remote_as: &alcatel_bgp6_remote_as
288
+ symbol:
289
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.2
290
+ name: alaBgpPeer6AS
291
+ format: uint32
292
+ descriptors: &alcatel_bgp6_peer_descriptors
293
+ local_address:
294
+ symbol:
295
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.17
296
+ name: alaBgpPeer6LocalAddr
297
+ format: ip_address
298
+ description:
299
+ symbol:
300
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.4
301
+ name: alaBgpPeer6Name
302
+ peer_type: &alcatel_bgp6_peer_type
303
+ symbol:
304
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.27
305
+ name: alaBgpPeer6Type
306
+ mapping: *alcatel_bgp_peer_type_mapping
307
+ admin:
308
+ enabled:
309
+ symbol:
310
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.68
311
+ name: alaBgpPeer6AdminStatus
312
+ mapping: *alcatel_bgp_admin_mapping
313
+ state:
314
+ symbol:
315
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.69
316
+ name: alaBgpPeer6State
317
+ mapping: *alcatel_bgp_state_mapping
318
+ traffic:
319
+ messages:
320
+ received:
321
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.49, name: alaBgpPeer6RcvdMsgs }
322
+ sent:
323
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.50, name: alaBgpPeer6SentMsgs }
324
+ updates:
325
+ received:
326
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.51, name: alaBgpPeer6RcvdUpdMsgs }
327
+ sent:
328
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.52, name: alaBgpPeer6SentUpdMsgs }
329
+ notifications:
330
+ received:
331
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.21, name: alaBgpPeer6RcvdNotifyMsgs }
332
+ sent:
333
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.22, name: alaBgpPeer6SentNotifyMsgs }
334
+ route_refreshes:
335
+ received:
336
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.13, name: alaBgpPeer6RcvdRtRefreshMsgs }
337
+ sent:
338
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.14, name: alaBgpPeer6SentRtRefreshMsgs }
339
+ transitions:
340
+ up:
341
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.47, name: alaBgpPeer6UpTransitions }
342
+ down:
343
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.26, name: alaBgpPeer6DownTransitions }
344
+ timers:
345
+ negotiated:
346
+ hold_time:
347
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.61, name: alaBgpPeer6HoldTime }
348
+ keepalive_time:
349
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.62, name: alaBgpPeer6KeepAlive }
350
+ configured:
351
+ connect_retry:
352
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.63, name: alaBgpPeer6ConnRetryInterval }
353
+ hold_time:
354
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.64, name: alaBgpPeer6HoldTimeConfigured }
355
+ keepalive_time:
356
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.65, name: alaBgpPeer6KeepAliveConfigured }
357
+ min_route_advertisement_interval:
358
+ symbol: { OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.73, name: alaBgpPeer6MinRouteAdvertisementInterval }
359
+ reasons:
360
+ last_down:
361
+ symbol:
362
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.18
363
+ name: alaBgpPeer6LastDownReason
364
+ mapping: *alcatel_bgp_last_down_reason_mapping
365
+ last_notifications:
366
+ received:
367
+ reason:
368
+ symbol:
369
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.24
370
+ name: alaBgpPeer6LastRecvNotifyReason
371
+ mapping: *alcatel_bgp_notify_reason_mapping
372
+ sent:
373
+ reason:
374
+ symbol:
375
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.23
376
+ name: alaBgpPeer6LastSentNotifyReason
377
+ mapping: *alcatel_bgp_notify_reason_mapping
378
+
379
+ - id: alcatel-bgp6-peer-family
380
+ MIB: ALCATEL-IND1-BGP-MIB
381
+ kind: peer_family
382
+ table: *alcatel_bgp6_peer_table
383
+ identity:
384
+ routing_instance: *alcatel_bgp_default_routing_instance
385
+ neighbor: *alcatel_bgp6_neighbor
386
+ remote_as: *alcatel_bgp6_remote_as
387
+ address_family:
388
+ value: ipv6
389
+ subsequent_address_family:
390
+ value: unicast
391
+ descriptors: *alcatel_bgp6_peer_descriptors
392
+ routes:
393
+ current:
394
+ received:
395
+ symbol:
396
+ OID: 1.3.6.1.4.1.6486.801.1.2.1.10.5.1.1.14.1.25
397
+ name: alaBgpPeer6RcvdPrefixes
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_arista-bgp4-v2.yaml
new
+188
@@ -0,0 +1,188 @@
1
+bgp:
2
+ - id: arista-bgp-peer
3
+ MIB: ARISTA-BGP4V2-MIB
4
+ kind: peer
5
+ table: &arista_bgp_peer_table
6
+ name: aristaBgp4V2PeerTable
7
+ OID: 1.3.6.1.4.1.30065.4.1.1.2
8
+ identity: &arista_bgp_peer_identity
9
+ routing_instance: &arista_bgp_routing_instance
10
+ index: 1
11
+ neighbor: &arista_bgp_neighbor
12
+ index_transform:
13
+ - start: 3
14
+ name: aristaBgp4V2PeerRemoteAddrIndex
15
+ format: ip_address
16
+ remote_as: &arista_bgp_remote_as
17
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.10, name: aristaBgp4V2PeerRemoteAs, format: uint32 }
18
+ descriptors: &arista_bgp_peer_descriptors
19
+ local_address:
20
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.3, name: aristaBgp4V2PeerLocalAddr, format: ip_address }
21
+ local_as:
22
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.7, name: aristaBgp4V2PeerLocalAs, format: uint32 }
23
+ local_identifier:
24
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.8, name: aristaBgp4V2PeerLocalIdentifier, format: ip_address }
25
+ peer_identifier:
26
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.11, name: aristaBgp4V2PeerRemoteIdentifier, format: ip_address }
27
+ peer_type: &arista_bgp_peer_type
28
+ index: 2
29
+ mapping:
30
+ 0: unknown
31
+ 1: ipv4
32
+ 2: ipv6
33
+ 3: ipv4z
34
+ 4: ipv6z
35
+ 16: dns
36
+ description:
37
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.14, name: aristaBgp4V2PeerDescription }
38
+ admin:
39
+ enabled:
40
+ symbol:
41
+ OID: 1.3.6.1.4.1.30065.4.1.1.2.1.12
42
+ name: aristaBgp4V2PeerAdminStatus
43
+ mapping:
44
+ 1: halted
45
+ 2: running
46
+ state:
47
+ symbol:
48
+ OID: 1.3.6.1.4.1.30065.4.1.1.2.1.13
49
+ name: aristaBgp4V2PeerState
50
+ mapping: &arista_bgp_state_mapping
51
+ 1: idle
52
+ 2: connect
53
+ 3: active
54
+ 4: opensent
55
+ 5: openconfirm
56
+ 6: established
57
+ previous_state:
58
+ symbol:
59
+ OID: 1.3.6.1.4.1.30065.4.1.1.2.1.15
60
+ name: aristaBgp4V2PeerPrevState
61
+ mapping: *arista_bgp_state_mapping
62
+ connection:
63
+ established_uptime:
64
+ table: aristaBgp4V2PeerEventTimesTable
65
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.4.1.1, name: bgpPeerFsmEstablishedTime }
66
+ last_received_update_age:
67
+ table: aristaBgp4V2PeerEventTimesTable
68
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.4.1.2, name: bgpPeerInUpdateElapsedTime }
69
+ traffic:
70
+ messages:
71
+ received:
72
+ table: aristaBgp4V2PeerCountersTable
73
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.7.1.3, name: bgpPeerInTotalMessages }
74
+ sent:
75
+ table: aristaBgp4V2PeerCountersTable
76
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.7.1.4, name: bgpPeerOutTotalMessages }
77
+ updates:
78
+ received:
79
+ table: aristaBgp4V2PeerCountersTable
80
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.7.1.1, name: bgpPeerInUpdates }
81
+ sent:
82
+ table: aristaBgp4V2PeerCountersTable
83
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.7.1.2, name: bgpPeerOutUpdates }
84
+ transitions:
85
+ established:
86
+ table: aristaBgp4V2PeerCountersTable
87
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.7.1.5, name: bgpPeerFsmEstablishedTransitions }
88
+ timers:
89
+ negotiated:
90
+ hold_time:
91
+ table: aristaBgp4V2PeerNegotiatedTimersTable
92
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.6.1.1, name: bgpPeerHoldTime }
93
+ keepalive_time:
94
+ table: aristaBgp4V2PeerNegotiatedTimersTable
95
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.6.1.2, name: bgpPeerKeepAlive }
96
+ configured:
97
+ connect_retry:
98
+ table: aristaBgp4V2PeerConfiguredTimersTable
99
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.5.1.1, name: bgpPeerConnectRetryInterval }
100
+ hold_time:
101
+ table: aristaBgp4V2PeerConfiguredTimersTable
102
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.5.1.2, name: bgpPeerHoldTimeConfigured }
103
+ keepalive_time:
104
+ table: aristaBgp4V2PeerConfiguredTimersTable
105
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.5.1.3, name: bgpPeerKeepAliveConfigured }
106
+ min_as_origination_interval:
107
+ table: aristaBgp4V2PeerConfiguredTimersTable
108
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.5.1.4, name: bgpPeerMinASOriginationInterval }
109
+ min_route_advertisement_interval:
110
+ table: aristaBgp4V2PeerConfiguredTimersTable
111
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.5.1.5, name: bgpPeerMinRouteAdvertisementInterval }
112
+ last_error:
113
+ code:
114
+ table: aristaBgp4V2PeerErrorsTable
115
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.3.1.1, name: bgpPeerLastErrorCode }
116
+ subcode:
117
+ table: aristaBgp4V2PeerErrorsTable
118
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.3.1.2, name: bgpPeerLastErrorSubcode }
119
+
120
+ - id: arista-bgp-peer-family
121
+ MIB: ARISTA-BGP4V2-MIB
122
+ kind: peer_family
123
+ table:
124
+ name: aristaBgp4V2PrefixGaugesTable
125
+ OID: 1.3.6.1.4.1.30065.4.1.1.8
126
+ identity:
127
+ routing_instance: *arista_bgp_routing_instance
128
+ neighbor:
129
+ index_transform:
130
+ - start: 3
131
+ drop_right: 2
132
+ name: aristaBgp4V2PrefixPeerRemoteAddrIndex
133
+ format: ip_address
134
+ remote_as:
135
+ <<: *arista_bgp_remote_as
136
+ table: aristaBgp4V2PeerTable
137
+ index_transform: &arista_bgp_peer_index_from_family
138
+ - start: 0
139
+ drop_right: 2
140
+ address_family:
141
+ index_from_end: 2
142
+ mapping:
143
+ 1: ipv4
144
+ 2: ipv6
145
+ 25: l2vpn
146
+ subsequent_address_family:
147
+ index_from_end: 1
148
+ mapping:
149
+ 1: unicast
150
+ 2: multicast
151
+ 4: labeled_unicast
152
+ 5: mvpn
153
+ 65: vpls
154
+ 70: evpn
155
+ 128: vpn
156
+ 132: rtfilter
157
+ 133: flow
158
+ descriptors:
159
+ <<: *arista_bgp_peer_descriptors
160
+ local_address:
161
+ table: aristaBgp4V2PeerTable
162
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.3, name: aristaBgp4V2PeerLocalAddr, format: ip_address }
163
+ index_transform: *arista_bgp_peer_index_from_family
164
+ local_as:
165
+ table: aristaBgp4V2PeerTable
166
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.7, name: aristaBgp4V2PeerLocalAs, format: uint32 }
167
+ index_transform: *arista_bgp_peer_index_from_family
168
+ local_identifier:
169
+ table: aristaBgp4V2PeerTable
170
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.8, name: aristaBgp4V2PeerLocalIdentifier, format: ip_address }
171
+ index_transform: *arista_bgp_peer_index_from_family
172
+ peer_identifier:
173
+ table: aristaBgp4V2PeerTable
174
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.11, name: aristaBgp4V2PeerRemoteIdentifier, format: ip_address }
175
+ index_transform: *arista_bgp_peer_index_from_family
176
+ description:
177
+ table: aristaBgp4V2PeerTable
178
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.2.1.14, name: aristaBgp4V2PeerDescription }
179
+ index_transform: *arista_bgp_peer_index_from_family
180
+ peer_type: *arista_bgp_peer_type
181
+ routes:
182
+ current:
183
+ received:
184
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.8.1.3, name: bgpPeerPrefixesReceived }
185
+ accepted:
186
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.8.1.4, name: bgpPeerPrefixesAccepted }
187
+ advertised:
188
+ symbol: { OID: 1.3.6.1.4.1.30065.4.1.1.8.1.5, name: bgpPeerPrefixesAdvertised }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_bluecoat-proxysg-licensing.yaml
new
+47
@@ -0,0 +1,47 @@
1
+# References: LibreNMS SGOS OS detection and BLUECOAT-LICENSE-MIB.
2
+
3
+licensing:
4
+ - MIB: BLUECOAT-LICENSE-MIB
5
+ id: app_license_status
6
+ table:
7
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1
8
+ name: appLicenseStatusTable
9
+ identity:
10
+ id:
11
+ index: 1
12
+ name:
13
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.2
14
+ name: appLicenseStatusApplicationName
15
+ feature:
16
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.3
17
+ name: appLicenseStatusFeatureName
18
+ component:
19
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.4
20
+ name: appLicenseStatusComponentName
21
+ descriptors:
22
+ type:
23
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.5
24
+ name: appLicenseStatusExpireType
25
+ mapping:
26
+ 0: unknown
27
+ 1: perpetual
28
+ 2: subscription
29
+ 3: demo
30
+ perpetual:
31
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.5
32
+ name: appLicenseStatusExpireType
33
+ mapping:
34
+ 1: "true"
35
+ state:
36
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.7
37
+ name: appLicenseStatusLicenseState
38
+ mapping:
39
+ 0: "1"
40
+ 1: "0"
41
+ 2: "2"
42
+ signals:
43
+ expiry:
44
+ timestamp:
45
+ OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.6
46
+ name: appLicenseStatusExpireDate
47
+ format: snmp_dateandtime
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_checkpoint-licensing.yaml
new
+55
@@ -0,0 +1,55 @@
1
+licensing:
2
+ - id: licensing_blades
3
+ MIB: CHECKPOINT-MIB
4
+ table:
5
+ OID: 1.3.6.1.4.1.2620.1.6.18.1
6
+ name: licensingTable
7
+ identity:
8
+ id:
9
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.2
10
+ name: licensingID
11
+ name:
12
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.4
13
+ name: licensingBladeName
14
+ component:
15
+ value: blade
16
+ descriptors:
17
+ type:
18
+ value: subscription
19
+ impact:
20
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.7
21
+ name: licensingImpact
22
+ state:
23
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.5
24
+ name: licensingState
25
+ mapping:
26
+ # MIB-documented hyphenated tokens
27
+ valid: "0"
28
+ "up-to-date": "0"
29
+ "about-to-expire": "1"
30
+ unknown: "1"
31
+ expired: "2"
32
+ "not-associated": "2"
33
+ # Real on-wire TitleCase variants observed on production gateways
34
+ Valid: "0"
35
+ "Up To Date": "0"
36
+ "About To Expire": "1"
37
+ Unknown: "1"
38
+ Expired: "2"
39
+ "Not Associated": "2"
40
+ "Not Entitled": "2"
41
+ Evaluation: "1"
42
+ Trial: "1"
43
+ signals:
44
+ expiry:
45
+ timestamp:
46
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.6
47
+ name: licensingExpirationDate
48
+ sentinel: [timer_u32_max]
49
+ usage:
50
+ used:
51
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.10
52
+ name: licensingUsedQuota
53
+ capacity:
54
+ OID: 1.3.6.1.4.1.2620.1.6.18.1.1.9
55
+ name: licensingTotalQuota
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_cisco-base.yaml
+17
-18
@@ -12,11 +12,27 @@ extends:
12
- _std-tcp-mib.yaml
13
- _std-udp-mib.yaml
14
- _std-ospf-mib.yaml
15
- - _std-bgp4-mib.yaml
15
- _std-ip-mib.yaml
16
- _cisco-metadata.yaml
17
- _cisco-hardware.yaml
18
19
+virtual_metrics:
20
+ # CPU memory: prefer 64-bit HC counters, fallback to 32-bit
21
+ - name: cpmCPUMemory
22
+ per_row: true
23
+ group_by: ["cpu_index"]
24
+ alternatives:
25
+ - sources:
26
+ - {metric: _cpmCPUMemoryHCUsed, table: cpmCPUTotalTable, as: used}
27
+ - {metric: _cpmCPUMemoryHCFree, table: cpmCPUTotalTable, as: free}
28
+ - sources:
29
+ - {metric: _cpmCPUMemoryUsed, table: cpmCPUTotalTable, as: used}
30
+ - {metric: _cpmCPUMemoryFree, table: cpmCPUTotalTable, as: free}
31
+ chart_meta:
32
+ description: CPU wide system memory usage
33
+ family: 'System/CPU/Memory'
34
+ unit: "By"
35
+
36
metrics:
37
- MIB: CISCO-ENTITY-FRU-CONTROL-MIB
38
table:
@@ -638,20 +654,3 @@ metrics:
654
24: y1731_loss
655
25: mcast_jitter
656
26: fabric_path_echo
641
-
642
-virtual_metrics:
643
- # CPU memory: prefer 64-bit HC counters, fallback to 32-bit
644
- - name: cpmCPUMemory
645
- per_row: true
646
- group_by: ["cpu_index"]
647
- alternatives:
648
- - sources:
649
- - {metric: _cpmCPUMemoryHCUsed, table: cpmCPUTotalTable, as: used}
650
- - {metric: _cpmCPUMemoryHCFree, table: cpmCPUTotalTable, as: free}
651
- - sources:
652
- - {metric: _cpmCPUMemoryUsed, table: cpmCPUTotalTable, as: used}
653
- - {metric: _cpmCPUMemoryFree, table: cpmCPUTotalTable, as: free}
654
- chart_meta:
655
- description: CPU wide system memory usage
656
- family: 'System/CPU/Memory'
657
- unit: "By"
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_cisco-bgp4-mib.yaml
new
+218
@@ -0,0 +1,218 @@
1
+# Vendor-native Cisco BGP peer visibility from CISCO-BGP4-MIB.
2
+# The peer row uses the VRF-aware cbgpPeer3Table. The peer-family row uses
3
+# cbgpPeer2AddrFamilyPrefixTable and resolves peer descriptors from
4
+# cbgpPeer2Table because the prefix table's remote address, AFI, and SAFI are
5
+# not-accessible INDEX objects.
6
+
7
+bgp:
8
+ - id: cisco-bgp-peer
9
+ MIB: CISCO-BGP4-MIB
10
+ kind: peer
11
+ table:
12
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9
13
+ name: cbgpPeer3Table
14
+ identity:
15
+ routing_instance:
16
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.4, name: cbgpPeer3VrfName }
17
+ neighbor:
18
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.3, name: cbgpPeer3RemoteAddr, format: ip_address }
19
+ remote_as:
20
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.13, name: cbgpPeer3RemoteAs, format: uint32 }
21
+ descriptors:
22
+ local_address:
23
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.8, name: cbgpPeer3LocalAddr, format: ip_address }
24
+ local_as:
25
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.10, name: cbgpPeer3LocalAs, format: uint32 }
26
+ local_identifier:
27
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.11, name: cbgpPeer3LocalIdentifier, format: ip_address }
28
+ peer_identifier:
29
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.14, name: cbgpPeer3RemoteIdentifier, format: ip_address }
30
+ peer_type:
31
+ index: 2
32
+ mapping:
33
+ 0: unknown
34
+ 1: ipv4
35
+ 2: ipv6
36
+ 3: ipv4z
37
+ 4: ipv6z
38
+ 16: dns
39
+ bgp_version:
40
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.7, name: cbgpPeer3NegotiatedVersion }
41
+ admin:
42
+ enabled:
43
+ symbol:
44
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.6
45
+ name: cbgpPeer3AdminStatus
46
+ mapping:
47
+ 1: stop
48
+ 2: start
49
+ state:
50
+ symbol:
51
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.5
52
+ name: cbgpPeer3State
53
+ mapping:
54
+ 1: idle
55
+ 2: connect
56
+ 3: active
57
+ 4: opensent
58
+ 5: openconfirm
59
+ 6: established
60
+ previous_state:
61
+ partial: true
62
+ partial_states: [idle, connect, active, opensent, openconfirm, established]
63
+ symbol:
64
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.31
65
+ name: cbgpPeer3PrevState
66
+ mapping:
67
+ 0: none
68
+ 1: idle
69
+ 2: connect
70
+ 3: active
71
+ 4: opensent
72
+ 5: openconfirm
73
+ 6: established
74
+ connection:
75
+ established_uptime:
76
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.21, name: cbgpPeer3FsmEstablishedTime }
77
+ last_received_update_age:
78
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.29, name: cbgpPeer3InUpdateElapsedTime }
79
+ traffic:
80
+ messages:
81
+ received:
82
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.17, name: cbgpPeer3InTotalMessages }
83
+ sent:
84
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.18, name: cbgpPeer3OutTotalMessages }
85
+ updates:
86
+ received:
87
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.15, name: cbgpPeer3InUpdates }
88
+ sent:
89
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.16, name: cbgpPeer3OutUpdates }
90
+ transitions:
91
+ established:
92
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.20, name: cbgpPeer3FsmEstablishedTransitions }
93
+ timers:
94
+ negotiated:
95
+ hold_time:
96
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.23, name: cbgpPeer3HoldTime }
97
+ keepalive_time:
98
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.24, name: cbgpPeer3KeepAlive }
99
+ configured:
100
+ connect_retry:
101
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.22, name: cbgpPeer3ConnectRetryInterval }
102
+ hold_time:
103
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.25, name: cbgpPeer3HoldTimeConfigured }
104
+ keepalive_time:
105
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.26, name: cbgpPeer3KeepAliveConfigured }
106
+ min_as_origination_interval:
107
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.27, name: cbgpPeer3MinASOriginationInterval }
108
+ min_route_advertisement_interval:
109
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.28, name: cbgpPeer3MinRouteAdvertisementInterval }
110
+ last_error:
111
+ code:
112
+ symbol:
113
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.19
114
+ name: cbgpPeer3LastErrorCode
115
+ format: hex
116
+ extract_value: '^([0-9a-f]{2})'
117
+ subcode:
118
+ symbol:
119
+ OID: 1.3.6.1.4.1.9.9.187.1.2.9.1.19
120
+ name: cbgpPeer3LastErrorSubcode
121
+ format: hex
122
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
123
+
124
+ - id: cisco-bgp-peer-family
125
+ MIB: CISCO-BGP4-MIB
126
+ kind: peer_family
127
+ table:
128
+ OID: 1.3.6.1.4.1.9.9.187.1.2.8
129
+ name: cbgpPeer2AddrFamilyPrefixTable
130
+ identity:
131
+ neighbor:
132
+ index_transform:
133
+ - start: 2
134
+ drop_right: 2
135
+ format: ip_address
136
+ remote_as:
137
+ table: cbgpPeer2Table
138
+ index_transform:
139
+ - start: 0
140
+ drop_right: 2
141
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.11, name: cbgpPeer2RemoteAs, format: uint32 }
142
+ address_family:
143
+ index_from_end: 2
144
+ mapping:
145
+ 1: ipv4
146
+ 2: ipv6
147
+ 25: l2vpn
148
+ subsequent_address_family:
149
+ index_from_end: 1
150
+ mapping:
151
+ 1: unicast
152
+ 2: multicast
153
+ 4: labeled_unicast
154
+ 5: mvpn
155
+ 65: vpls
156
+ 70: evpn
157
+ 128: vpn
158
+ 132: rtfilter
159
+ 133: flow
160
+ descriptors:
161
+ local_address:
162
+ table: cbgpPeer2Table
163
+ index_transform:
164
+ - start: 0
165
+ drop_right: 2
166
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.6, name: cbgpPeer2LocalAddr, format: ip_address }
167
+ local_as:
168
+ table: cbgpPeer2Table
169
+ index_transform:
170
+ - start: 0
171
+ drop_right: 2
172
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.8, name: cbgpPeer2LocalAs, format: uint32 }
173
+ local_identifier:
174
+ table: cbgpPeer2Table
175
+ index_transform:
176
+ - start: 0
177
+ drop_right: 2
178
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.9, name: cbgpPeer2LocalIdentifier, format: ip_address }
179
+ peer_identifier:
180
+ table: cbgpPeer2Table
181
+ index_transform:
182
+ - start: 0
183
+ drop_right: 2
184
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.12, name: cbgpPeer2RemoteIdentifier, format: ip_address }
185
+ peer_type:
186
+ index: 1
187
+ mapping:
188
+ 0: unknown
189
+ 1: ipv4
190
+ 2: ipv6
191
+ 3: ipv4z
192
+ 4: ipv6z
193
+ 16: dns
194
+ bgp_version:
195
+ table: cbgpPeer2Table
196
+ index_transform:
197
+ - start: 0
198
+ drop_right: 2
199
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.5.1.5, name: cbgpPeer2NegotiatedVersion }
200
+ routes:
201
+ current:
202
+ accepted:
203
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.1, name: cbgpPeer2AcceptedPrefixes }
204
+ rejected:
205
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.2, name: cbgpPeer2DeniedPrefixes }
206
+ advertised:
207
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.6, name: cbgpPeer2AdvertisedPrefixes }
208
+ suppressed:
209
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.7, name: cbgpPeer2SuppressedPrefixes }
210
+ withdrawn:
211
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.8, name: cbgpPeer2WithdrawnPrefixes }
212
+ route_limits:
213
+ limit:
214
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.3, name: cbgpPeer2PrefixAdminLimit }
215
+ threshold:
216
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.4, name: cbgpPeer2PrefixThreshold }
217
+ clear_threshold:
218
+ symbol: { OID: 1.3.6.1.4.1.9.9.187.1.2.8.1.5, name: cbgpPeer2PrefixClearThreshold }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_dell-os10-bgp4-v2.yaml
new
+176
@@ -0,0 +1,176 @@
1
+bgp:
2
+ - id: dell-os10-bgp-peer
3
+ MIB: DELLEMC-OS10-BGP4V2-MIB
4
+ kind: peer
5
+ table: &dell_os10_bgp_peer_table
6
+ name: dell.os10bgp4V2PeerTable
7
+ OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2
8
+ identity: &dell_os10_bgp_peer_identity
9
+ routing_instance: &dell_os10_bgp_routing_instance
10
+ index: 1
11
+ neighbor: &dell_os10_bgp_neighbor
12
+ index_transform:
13
+ - start: 3
14
+ name: dell.os10bgp4V2PeerRemoteAddrIndex
15
+ format: ip_address
16
+ remote_as: &dell_os10_bgp_remote_as
17
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.10, name: dell.os10bgp4V2PeerRemoteAs, format: uint32 }
18
+ descriptors: &dell_os10_bgp_peer_descriptors
19
+ local_address:
20
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.3, name: dell.os10bgp4V2PeerLocalAddr, format: ip_address }
21
+ local_as:
22
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.7, name: dell.os10bgp4V2PeerLocalAs, format: uint32 }
23
+ local_identifier:
24
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.8, name: dell.os10bgp4V2PeerLocalIdentifier, format: ip_address }
25
+ peer_identifier:
26
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.11, name: dell.os10bgp4V2PeerRemoteIdentifier, format: ip_address }
27
+ peer_type: &dell_os10_bgp_peer_type
28
+ index: 2
29
+ mapping:
30
+ 0: unknown
31
+ 1: ipv4
32
+ 2: ipv6
33
+ 3: ipv4z
34
+ 4: ipv6z
35
+ 16: dns
36
+ description:
37
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.14, name: dell.os10bgp4V2PeerDescription }
38
+ admin:
39
+ enabled:
40
+ symbol:
41
+ OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.12
42
+ name: dell.os10bgp4V2PeerAdminStatus
43
+ mapping:
44
+ 1: halted
45
+ 2: running
46
+ state:
47
+ symbol:
48
+ OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.13
49
+ name: dell.os10bgp4V2PeerState
50
+ mapping:
51
+ 1: idle
52
+ 2: connect
53
+ 3: active
54
+ 4: opensent
55
+ 5: openconfirm
56
+ 6: established
57
+ connection:
58
+ established_uptime:
59
+ table: dell.os10bgp4V2PeerEventTimesTable
60
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.4.1.1, name: bgpPeerFsmEstablishedTime }
61
+ last_received_update_age:
62
+ table: dell.os10bgp4V2PeerEventTimesTable
63
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.4.1.2, name: bgpPeerInUpdateElapsedTime }
64
+ traffic:
65
+ messages:
66
+ received:
67
+ table: dell.os10bgp4V2PeerCountersTable
68
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.3, name: bgpPeerInTotalMessages }
69
+ sent:
70
+ table: dell.os10bgp4V2PeerCountersTable
71
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.4, name: bgpPeerOutTotalMessages }
72
+ updates:
73
+ received:
74
+ table: dell.os10bgp4V2PeerCountersTable
75
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.1, name: bgpPeerInUpdates }
76
+ sent:
77
+ table: dell.os10bgp4V2PeerCountersTable
78
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.2, name: bgpPeerOutUpdates }
79
+ transitions:
80
+ established:
81
+ table: dell.os10bgp4V2PeerCountersTable
82
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.7.1.5, name: bgpPeerFsmEstablishedTransitions }
83
+ timers:
84
+ negotiated:
85
+ hold_time:
86
+ table: dell.os10bgp4V2PeerNegotiatedTimersTable
87
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.6.1.1, name: bgpPeerHoldTime }
88
+ keepalive_time:
89
+ table: dell.os10bgp4V2PeerNegotiatedTimersTable
90
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.6.1.2, name: bgpPeerKeepAlive }
91
+ configured:
92
+ connect_retry:
93
+ table: dell.os10bgp4V2PeerConfiguredTimersTable
94
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.5.1.1, name: bgpPeerConnectRetryInterval }
95
+ hold_time:
96
+ table: dell.os10bgp4V2PeerConfiguredTimersTable
97
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.5.1.2, name: bgpPeerHoldTimeConfigured }
98
+ keepalive_time:
99
+ table: dell.os10bgp4V2PeerConfiguredTimersTable
100
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.5.1.3, name: bgpPeerKeepAliveConfigured }
101
+ min_as_origination_interval:
102
+ table: dell.os10bgp4V2PeerConfiguredTimersTable
103
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.5.1.4, name: bgpPeerMinASOriginationInterval }
104
+ min_route_advertisement_interval:
105
+ table: dell.os10bgp4V2PeerConfiguredTimersTable
106
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.5.1.5, name: bgpPeerMinRouteAdvertisementInterval }
107
+ last_error:
108
+ code:
109
+ table: dell.os10bgp4V2PeerErrorsTable
110
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.3.1.1, name: bgpPeerLastErrorCode }
111
+ subcode:
112
+ table: dell.os10bgp4V2PeerErrorsTable
113
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.3.1.2, name: bgpPeerLastErrorSubcode }
114
+
115
+ - id: dell-os10-bgp-peer-family
116
+ MIB: DELLEMC-OS10-BGP4V2-MIB
117
+ kind: peer_family
118
+ table:
119
+ name: dell.os10bgp4V2PrefixGaugesTable
120
+ OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.8
121
+ identity:
122
+ routing_instance: *dell_os10_bgp_routing_instance
123
+ neighbor:
124
+ index_transform:
125
+ - start: 3
126
+ drop_right: 2
127
+ name: dell.os10bgp4V2PrefixPeerRemoteAddrIndex
128
+ format: ip_address
129
+ remote_as:
130
+ <<: *dell_os10_bgp_remote_as
131
+ table: dell.os10bgp4V2PeerTable
132
+ index_transform: &dell_os10_bgp_peer_index_from_family
133
+ - start: 0
134
+ drop_right: 2
135
+ address_family:
136
+ index_from_end: 2
137
+ mapping:
138
+ 1: ipv4
139
+ 2: ipv6
140
+ subsequent_address_family:
141
+ index_from_end: 1
142
+ mapping:
143
+ 1: unicast
144
+ 2: multicast
145
+ 4: labeled_unicast
146
+ descriptors:
147
+ <<: *dell_os10_bgp_peer_descriptors
148
+ local_address:
149
+ table: dell.os10bgp4V2PeerTable
150
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.3, name: dell.os10bgp4V2PeerLocalAddr, format: ip_address }
151
+ index_transform: *dell_os10_bgp_peer_index_from_family
152
+ local_as:
153
+ table: dell.os10bgp4V2PeerTable
154
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.7, name: dell.os10bgp4V2PeerLocalAs, format: uint32 }
155
+ index_transform: *dell_os10_bgp_peer_index_from_family
156
+ local_identifier:
157
+ table: dell.os10bgp4V2PeerTable
158
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.8, name: dell.os10bgp4V2PeerLocalIdentifier, format: ip_address }
159
+ index_transform: *dell_os10_bgp_peer_index_from_family
160
+ peer_identifier:
161
+ table: dell.os10bgp4V2PeerTable
162
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.11, name: dell.os10bgp4V2PeerRemoteIdentifier, format: ip_address }
163
+ index_transform: *dell_os10_bgp_peer_index_from_family
164
+ description:
165
+ table: dell.os10bgp4V2PeerTable
166
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.14, name: dell.os10bgp4V2PeerDescription }
167
+ index_transform: *dell_os10_bgp_peer_index_from_family
168
+ peer_type: *dell_os10_bgp_peer_type
169
+ routes:
170
+ current:
171
+ received:
172
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.3, name: bgpPeerPrefixesReceived }
173
+ accepted:
174
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.4, name: bgpPeerPrefixesAccepted }
175
+ advertised:
176
+ symbol: { OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.8.1.5, name: bgpPeerPrefixesAdvertised }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_fortinet-fortigate-licensing.yaml
new
+80
@@ -0,0 +1,80 @@
1
+# References:
2
+# https://docs.fortinet.com/document/fortigate/8.0.0/fortigate-mib-information-overview/293724/fortigate-system-mibs
3
+# https://mibbrowser.online/mibdb_search.php?mib=FORTINET-FORTIGATE-MIB
4
+# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/fortinet/FORTINET-FORTIGATE-MIB
5
+
6
+licensing:
7
+ - MIB: FORTINET-FORTIGATE-MIB
8
+ id: fortigate_contracts
9
+ table:
10
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2
11
+ name: fgLicContractTable
12
+ identity:
13
+ id:
14
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.1
15
+ name: fgLicContractDesc
16
+ name:
17
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.1
18
+ name: fgLicContractDesc
19
+ component:
20
+ value: device
21
+ descriptors:
22
+ type:
23
+ value: contract
24
+ signals:
25
+ expiry:
26
+ timestamp:
27
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.2
28
+ name: fgLicContractExpiry
29
+ format: text_date
30
+
31
+ - MIB: FORTINET-FORTIGATE-MIB
32
+ id: fortiguard_services
33
+ table:
34
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2
35
+ name: fgLicVersionTable
36
+ identity:
37
+ id:
38
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.1
39
+ name: fgLicVersionDesc
40
+ name:
41
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.1
42
+ name: fgLicVersionDesc
43
+ feature:
44
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.3
45
+ name: fgLicVersionNumber
46
+ component:
47
+ value: fortiguard
48
+ descriptors:
49
+ type:
50
+ value: service
51
+ signals:
52
+ expiry:
53
+ timestamp:
54
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.2
55
+ name: fgLicVersionExpiry
56
+ format: text_date
57
+
58
+ - MIB: FORTINET-FORTIGATE-MIB
59
+ id: forticare_account_contracts
60
+ table:
61
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2
62
+ name: fgLicAlContractTable
63
+ identity:
64
+ id:
65
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.1
66
+ name: fgLicAlContractDesc
67
+ name:
68
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.1
69
+ name: fgLicAlContractDesc
70
+ component:
71
+ value: account
72
+ descriptors:
73
+ type:
74
+ value: account_contract
75
+ signals:
76
+ expiry:
77
+ timestamp:
78
+ OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.2
79
+ name: fgLicAlContractExpiry
80
+ format: text_date
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_huawei-bgp-vpn.yaml
new
+256
@@ -0,0 +1,256 @@
1
+# Vendor-native Huawei BGP visibility from HUAWEI-BGP-VPN-MIB.
2
+# hwBgpPeerAddrFamilyTable, hwBgpPeerTable, hwBgpPeerRouteTable, and
3
+# hwBgpPeerMessageTable share the same address-family row index:
4
+# instance ID, AFI, SAFI, peer address type, and peer address. The index
5
+# components are not-accessible in Huawei's MIB reference, so AFI/SAFI and
6
+# peer address descriptors are index-derived or resolved through typed
7
+# cross-table sources.
8
+#
9
+# https://mibbrowser.online/mibdb_search.php?mib=HUAWEI-BGP-VPN-MIB
10
+# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/huawei/HUAWEI-BGP-VPN-MIB
11
+
12
+bgp:
13
+ - id: huawei-bgp-device-counts
14
+ MIB: HUAWEI-BGP-VPN-MIB
15
+ kind: device
16
+ device_counts:
17
+ peers:
18
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.4.1, name: huawei.hwBgpPeerSessionNum }
19
+ ibgp_peers:
20
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.4.2, name: huawei.hwIBgpPeerSessionNum }
21
+ ebgp_peers:
22
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.4.3, name: huawei.hwEBgpPeerSessionNum }
23
+
24
+ - id: huawei-bgp-peer-family
25
+ MIB: HUAWEI-BGP-VPN-MIB
26
+ kind: peer_family
27
+ table: &huawei_bgp_peer_table
28
+ name: hwBgpPeerTable
29
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2
30
+ identity: &huawei_bgp_peer_family_identity
31
+ routing_instance: &huawei_bgp_routing_instance
32
+ table: hwBgpPeerAddrFamilyTable
33
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6, name: huawei.hwBgpPeerVrfName }
34
+ neighbor: &huawei_bgp_neighbor
35
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4, name: huawei.hwBgpPeerRemoteAddr, format: ip_address }
36
+ remote_as: &huawei_bgp_remote_as
37
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2, name: huawei.hwBgpPeerRemoteAs, format: uint32 }
38
+ address_family: &huawei_bgp_address_family
39
+ index: 2
40
+ mapping:
41
+ 1: ipv4
42
+ 2: ipv6
43
+ 25: l2vpn
44
+ 196: l2vpn
45
+ subsequent_address_family: &huawei_bgp_subsequent_address_family
46
+ index: 3
47
+ allow_private: true
48
+ mapping:
49
+ 1: unicast
50
+ 2: multicast
51
+ 4: labeled_unicast
52
+ 5: mvpn
53
+ 65: vpls
54
+ 66: mdt
55
+ 70: evpn
56
+ 74: sd-wan
57
+ 128: vpn
58
+ 132: rtfilter
59
+ descriptors: &huawei_bgp_peer_family_descriptors
60
+ peer_type: &huawei_bgp_peer_type
61
+ index: 4
62
+ mapping:
63
+ 0: unknown
64
+ 1: ipv4
65
+ 2: ipv6
66
+ 3: ipv4z
67
+ 4: ipv6z
68
+ 16: dns
69
+ bgp_version:
70
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1, name: huawei.hwBgpPeerNegotiatedVersion }
71
+ description:
72
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.12, name: huawei.hwBgpPeerDescription }
73
+ admin:
74
+ enabled:
75
+ symbol:
76
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.11
77
+ name: huawei.hwBgpPeerAdminStatus
78
+ mapping:
79
+ 1: stop
80
+ 2: start
81
+ state:
82
+ symbol:
83
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.5
84
+ name: huawei.hwBgpPeerState
85
+ mapping:
86
+ 1: idle
87
+ 2: connect
88
+ 3: active
89
+ 4: opensent
90
+ 5: openconfirm
91
+ 6: established
92
+ transitions:
93
+ established:
94
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.6, name: huawei.hwBgpPeerFsmEstablishedCounter }
95
+ last_error:
96
+ code:
97
+ symbol:
98
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.9
99
+ name: huawei.hwBgpPeerLastErrorCode
100
+ format: hex
101
+ extract_value: '^([0-9a-f]{2})'
102
+ subcode:
103
+ symbol:
104
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.9
105
+ name: huawei.hwBgpPeerLastErrorSubcode
106
+ format: hex
107
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
108
+ graceful_restart:
109
+ state:
110
+ symbol:
111
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.8
112
+ name: huawei.hwBgpPeerGRStatus
113
+ mapping:
114
+ 1: peer_not_being_helped
115
+ 2: peer_restarting
116
+ 3: peer_finish_restart
117
+ 4: peer_helping
118
+ reasons:
119
+ unavailability:
120
+ symbol:
121
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.10
122
+ name: huawei.hwBgpPeerUnAvaiReason
123
+ mapping:
124
+ 1: configuration_lead_peer_down
125
+ 2: receive_notification
126
+ 3: receive_error_packet
127
+ 4: hold_timer_expire
128
+ 5: remote_peer_not_reachable
129
+ 6: direct_connect_interface_down
130
+ 7: route_limit
131
+
132
+ - id: huawei-bgp-peer-family-routes
133
+ MIB: HUAWEI-BGP-VPN-MIB
134
+ kind: peer_family
135
+ table:
136
+ name: hwBgpPeerRouteTable
137
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3
138
+ identity: &huawei_bgp_cross_table_peer_family_identity
139
+ routing_instance: *huawei_bgp_routing_instance
140
+ neighbor:
141
+ table: hwBgpPeerTable
142
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4, name: huawei.hwBgpPeerRemoteAddr, format: ip_address }
143
+ remote_as:
144
+ table: hwBgpPeerTable
145
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2, name: huawei.hwBgpPeerRemoteAs, format: uint32 }
146
+ address_family: *huawei_bgp_address_family
147
+ subsequent_address_family: *huawei_bgp_subsequent_address_family
148
+ descriptors: &huawei_bgp_cross_table_peer_family_descriptors
149
+ peer_type: *huawei_bgp_peer_type
150
+ bgp_version:
151
+ table: hwBgpPeerTable
152
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1, name: huawei.hwBgpPeerNegotiatedVersion }
153
+ description:
154
+ table: hwBgpPeerTable
155
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.12, name: huawei.hwBgpPeerDescription }
156
+ routes:
157
+ total:
158
+ received:
159
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.1, name: huawei.hwBgpPeerPrefixRcvCounter }
160
+ active:
161
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.2, name: huawei.hwBgpPeerPrefixActiveCounter }
162
+ advertised:
163
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.3, name: huawei.hwBgpPeerPrefixAdvCounter }
164
+
165
+ - id: huawei-bgp-peer-family-messages
166
+ MIB: HUAWEI-BGP-VPN-MIB
167
+ kind: peer_family
168
+ table:
169
+ name: hwBgpPeerMessageTable
170
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4
171
+ identity: *huawei_bgp_cross_table_peer_family_identity
172
+ descriptors: *huawei_bgp_cross_table_peer_family_descriptors
173
+ traffic:
174
+ messages:
175
+ received:
176
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.1, name: huawei.hwBgpPeerInTotalMsgCounter }
177
+ sent:
178
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.2, name: huawei.hwBgpPeerOutTotalMsgCounter }
179
+ opens:
180
+ received:
181
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.3, name: huawei.hwBgpPeerInOpenMsgCounter }
182
+ sent:
183
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.8, name: huawei.hwBgpPeerOutOpenMsgCounter }
184
+ updates:
185
+ received:
186
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.4, name: huawei.hwBgpPeerInUpdateMsgCounter }
187
+ sent:
188
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.9, name: huawei.hwBgpPeerOutUpdateMsgCounter }
189
+ notifications:
190
+ received:
191
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.5, name: huawei.hwBgpPeerInNotificationMsgCounter }
192
+ sent:
193
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.10, name: huawei.hwBgpPeerOutNotificationMsgCounter }
194
+ keepalives:
195
+ received:
196
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.6, name: huawei.hwBgpPeerInKeepAliveMsgCounter }
197
+ sent:
198
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.11, name: huawei.hwBgpPeerOutKeepAliveMsgCounter }
199
+ route_refreshes:
200
+ received:
201
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.7, name: huawei.hwBgpPeerInRouteFreshMsgCounter }
202
+ sent:
203
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.12, name: huawei.hwBgpPeerOutRouteFreshMsgCounter }
204
+
205
+ - id: huawei-bgp-peer-statistics
206
+ MIB: HUAWEI-BGP-VPN-MIB
207
+ kind: peer
208
+ table:
209
+ name: hwBgpPeerStatisticTable
210
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7
211
+ identity:
212
+ routing_instance:
213
+ index: 2
214
+ neighbor:
215
+ index_transform:
216
+ - start: 3
217
+ name: huawei.hwBgpPeerStatisticRemoteAddr
218
+ format: ip_address
219
+ remote_as:
220
+ table: hwBgpPeerTable
221
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.2, name: huawei.hwBgpPeerRemoteAs, format: uint32 }
222
+ lookup_symbol: &huawei_bgp_peer_remote_addr_lookup
223
+ OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4
224
+ name: huawei.hwBgpPeerRemoteAddr
225
+ extract_value: '^(?:[^.]+\.){2}(?:4|16)\.(.*)$'
226
+ format: ip_address
227
+ descriptors:
228
+ peer_type:
229
+ index: 3
230
+ mapping:
231
+ 4: ipv4
232
+ 16: ipv6
233
+ bgp_version:
234
+ table: hwBgpPeerTable
235
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.1, name: huawei.hwBgpPeerNegotiatedVersion }
236
+ lookup_symbol: *huawei_bgp_peer_remote_addr_lookup
237
+ description:
238
+ table: hwBgpPeerTable
239
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.12, name: huawei.hwBgpPeerDescription }
240
+ lookup_symbol: *huawei_bgp_peer_remote_addr_lookup
241
+ transitions:
242
+ established:
243
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.4, name: huawei.hwBgpPeerFsmEstablishedTransitions }
244
+ down:
245
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.5, name: huawei.hwBgpPeerDownCounts }
246
+ traffic:
247
+ updates:
248
+ received:
249
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.6, name: huawei.hwBgpPeerInUpdateMsgs }
250
+ sent:
251
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.7, name: huawei.hwBgpPeerOutUpdateMsgs }
252
+ messages:
253
+ received:
254
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.8, name: huawei.hwBgpPeerInTotalMsgs }
255
+ sent:
256
+ symbol: { OID: 1.3.6.1.4.1.2011.5.25.177.1.1.7.1.9, name: huawei.hwBgpPeerOutTotalMsgs }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_juniper-bgp4-v2.yaml
new
+230
@@ -0,0 +1,230 @@
1
+# Vendor-native Juniper BGP visibility from BGP4-V2-MIB-JUNIPER.
2
+# The peer row uses jnxBgpM2PeerTable and reads augmented per-peer tables
3
+# through typed cross-table BGP sources. The peer-family row uses
4
+# jnxBgpM2PrefixCountersTable and resolves peer identity by matching its
5
+# jnxBgpM2PeerIndex index component against jnxBgpM2PeerTable.
6
+
7
+bgp:
8
+ - id: juniper-bgp-peer
9
+ MIB: BGP4-V2-MIB-JUNIPER
10
+ kind: peer
11
+ table:
12
+ name: jnxBgpM2PeerTable
13
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1
14
+ identity:
15
+ routing_instance:
16
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.15, name: jnxBgpM2PeerRoutingInstance }
17
+ neighbor:
18
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.11, name: jnxBgpM2PeerRemoteAddr, format: ip_address }
19
+ remote_as:
20
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13, name: jnxBgpM2PeerRemoteAs, format: uint32 }
21
+ descriptors:
22
+ local_address:
23
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.7, name: jnxBgpM2PeerLocalAddr, format: ip_address }
24
+ local_as:
25
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.9, name: jnxBgpM2PeerLocalAs, format: uint32 }
26
+ peer_identifier:
27
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.1, name: jnxBgpM2PeerIdentifier, format: ip_address }
28
+ peer_type:
29
+ symbol:
30
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.10
31
+ name: jnxBgpM2PeerRemoteAddrType
32
+ mapping:
33
+ 0: unknown
34
+ 1: ipv4
35
+ 2: ipv6
36
+ 3: ipv4z
37
+ 4: ipv6z
38
+ 16: dns
39
+ 25: l2vpn
40
+ bgp_version:
41
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.5, name: jnxBgpM2PeerNegotiatedVersion }
42
+ admin:
43
+ enabled:
44
+ symbol:
45
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.3
46
+ name: jnxBgpM2PeerStatus
47
+ mapping:
48
+ 1: halted
49
+ 2: running
50
+ state:
51
+ symbol:
52
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2
53
+ name: jnxBgpM2PeerState
54
+ mapping:
55
+ 1: idle
56
+ 2: connect
57
+ 3: active
58
+ 4: opensent
59
+ 5: openconfirm
60
+ 6: established
61
+ connection:
62
+ established_uptime:
63
+ table: jnxBgpM2PeerEventTimesTable
64
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.1.1.1, name: jnxBgpM2PeerFsmEstablishedTime }
65
+ last_received_update_age:
66
+ table: jnxBgpM2PeerEventTimesTable
67
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.1.1.2, name: jnxBgpM2PeerInUpdatesElapsedTime }
68
+ traffic:
69
+ messages:
70
+ received:
71
+ table: jnxBgpM2PeerCountersTable
72
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.1.1.3, name: jnxBgpM2PeerInTotalMessages }
73
+ sent:
74
+ table: jnxBgpM2PeerCountersTable
75
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.1.1.4, name: jnxBgpM2PeerOutTotalMessages }
76
+ updates:
77
+ received:
78
+ table: jnxBgpM2PeerCountersTable
79
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.1.1.1, name: jnxBgpM2PeerInUpdates }
80
+ sent:
81
+ table: jnxBgpM2PeerCountersTable
82
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.1.1.2, name: jnxBgpM2PeerOutUpdates }
83
+ transitions:
84
+ established:
85
+ table: jnxBgpM2PeerCountersTable
86
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.1.1.5, name: jnxBgpM2PeerFsmEstablishedTrans }
87
+ timers:
88
+ negotiated:
89
+ hold_time:
90
+ table: jnxBgpM2PeerNegotiatedTimersTable
91
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.3.1.1, name: jnxBgpM2PeerHoldTime }
92
+ keepalive_time:
93
+ table: jnxBgpM2PeerNegotiatedTimersTable
94
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.3.1.2, name: jnxBgpM2PeerKeepAlive }
95
+ configured:
96
+ connect_retry:
97
+ table: jnxBgpM2PeerConfiguredTimersTable
98
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.2.1.1, name: jnxBgpM2PeerConnectRetryInterval }
99
+ hold_time:
100
+ table: jnxBgpM2PeerConfiguredTimersTable
101
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.2.1.2, name: jnxBgpM2PeerHoldTimeConfigured }
102
+ keepalive_time:
103
+ table: jnxBgpM2PeerConfiguredTimersTable
104
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.2.1.3, name: jnxBgpM2PeerKeepAliveConfigured }
105
+ min_as_origination_interval:
106
+ table: jnxBgpM2PeerConfiguredTimersTable
107
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.2.1.4, name: jnxBgpM2PeerMinASOrigInterval }
108
+ min_route_advertisement_interval:
109
+ table: jnxBgpM2PeerConfiguredTimersTable
110
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.4.2.1.5, name: jnxBgpM2PeerMinRouteAdverInterval }
111
+ last_error:
112
+ code:
113
+ table: jnxBgpM2PeerErrorsTable
114
+ symbol:
115
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.2.1.1.1
116
+ name: jnxBgpM2PeerLastErrorReceivedCode
117
+ format: hex
118
+ extract_value: '^([0-9a-f]{2})'
119
+ subcode:
120
+ table: jnxBgpM2PeerErrorsTable
121
+ symbol:
122
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.2.1.1.1
123
+ name: jnxBgpM2PeerLastErrorReceivedSubcode
124
+ format: hex
125
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
126
+
127
+ - id: juniper-bgp-peer-family
128
+ MIB: BGP4-V2-MIB-JUNIPER
129
+ kind: peer_family
130
+ table:
131
+ name: jnxBgpM2PrefixCountersTable
132
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2
133
+ identity:
134
+ routing_instance:
135
+ table: jnxBgpM2PeerTable
136
+ index_transform:
137
+ - start: 0
138
+ end: 0
139
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
140
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.15, name: jnxBgpM2PeerRoutingInstance }
141
+ neighbor:
142
+ table: jnxBgpM2PeerTable
143
+ index_transform:
144
+ - start: 0
145
+ end: 0
146
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
147
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.11, name: jnxBgpM2PeerRemoteAddr, format: ip_address }
148
+ remote_as:
149
+ table: jnxBgpM2PeerTable
150
+ index_transform:
151
+ - start: 0
152
+ end: 0
153
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
154
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13, name: jnxBgpM2PeerRemoteAs, format: uint32 }
155
+ address_family:
156
+ index: 2
157
+ mapping:
158
+ 1: ipv4
159
+ 2: ipv6
160
+ 25: l2vpn
161
+ subsequent_address_family:
162
+ index: 3
163
+ mapping:
164
+ 1: unicast
165
+ 2: multicast
166
+ 4: labeled_unicast
167
+ 5: mvpn
168
+ 65: vpls
169
+ 70: evpn
170
+ 128: vpn
171
+ 132: rtfilter
172
+ 133: flow
173
+ descriptors:
174
+ local_address:
175
+ table: jnxBgpM2PeerTable
176
+ index_transform:
177
+ - start: 0
178
+ end: 0
179
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
180
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.7, name: jnxBgpM2PeerLocalAddr, format: ip_address }
181
+ local_as:
182
+ table: jnxBgpM2PeerTable
183
+ index_transform:
184
+ - start: 0
185
+ end: 0
186
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
187
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.9, name: jnxBgpM2PeerLocalAs, format: uint32 }
188
+ peer_identifier:
189
+ table: jnxBgpM2PeerTable
190
+ index_transform:
191
+ - start: 0
192
+ end: 0
193
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
194
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.1, name: jnxBgpM2PeerIdentifier, format: ip_address }
195
+ peer_type:
196
+ table: jnxBgpM2PeerTable
197
+ index_transform:
198
+ - start: 0
199
+ end: 0
200
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
201
+ symbol:
202
+ OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.10
203
+ name: jnxBgpM2PeerRemoteAddrType
204
+ mapping:
205
+ 0: unknown
206
+ 1: ipv4
207
+ 2: ipv6
208
+ 3: ipv4z
209
+ 4: ipv6z
210
+ 16: dns
211
+ 25: l2vpn
212
+ bgp_version:
213
+ table: jnxBgpM2PeerTable
214
+ index_transform:
215
+ - start: 0
216
+ end: 0
217
+ lookup_symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.14, name: jnxBgpM2PeerIndex }
218
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.1.1.1.5, name: jnxBgpM2PeerNegotiatedVersion }
219
+ routes:
220
+ current:
221
+ received:
222
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2.1.7, name: jnxBgpM2PrefixInPrefixes }
223
+ accepted:
224
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2.1.8, name: jnxBgpM2PrefixInPrefixesAccepted }
225
+ rejected:
226
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2.1.9, name: jnxBgpM2PrefixInPrefixesRejected }
227
+ advertised:
228
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2.1.10, name: jnxBgpM2PrefixOutPrefixes }
229
+ active:
230
+ symbol: { OID: 1.3.6.1.4.1.2636.5.1.1.2.6.2.1.11, name: jnxBgpM2PrefixInPrefixesActive }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_mikrotik-routeros-licensing.yaml
new
+22
@@ -0,0 +1,22 @@
1
+licensing:
2
+ - id: routeros_upgrade
3
+ MIB: MIKROTIK-MIB
4
+ identity:
5
+ id:
6
+ value: routeros_upgrade
7
+ name:
8
+ value: RouterOS upgrade entitlement
9
+ component:
10
+ value: routeros
11
+ descriptors:
12
+ type:
13
+ value: upgrade_entitlement
14
+ impact:
15
+ value: RouterOS software upgrade entitlement for this device
16
+ signals:
17
+ expiry:
18
+ timestamp:
19
+ OID: 1.3.6.1.4.1.14988.1.1.4.2.0
20
+ name: mtxrLicUpgrUntil
21
+ format: snmp_dateandtime
22
+ sentinel: [timer_pre_1971]
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_nokia-timetra-bgp.yaml
new
+293
@@ -0,0 +1,293 @@
1
+# Vendor-native Nokia SR OS BGP visibility from TIMETRA-BGP-MIB.
2
+# tBgpPeerNgOperTable augments tBgpPeerNgTable, so the same row index is used
3
+# for configured peer attributes and operational counters.
4
+
5
+bgp:
6
+ - id: nokia-timos-bgp-peer
7
+ MIB: TIMETRA-BGP-MIB
8
+ kind: peer
9
+ table:
10
+ name: tBgpPeerNgTable
11
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7
12
+ identity:
13
+ routing_instance:
14
+ table: vRtrConfTable
15
+ index_transform:
16
+ - start: 0
17
+ end: 0
18
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.3.1.1.4, name: vRtrName }
19
+ neighbor:
20
+ index_transform:
21
+ - start: 3
22
+ name: tBgpPeerNgAddress
23
+ format: ip_address
24
+ remote_as:
25
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66, name: tBgpPeerNgPeerAS4Byte, format: uint32 }
26
+ descriptors:
27
+ local_address:
28
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.13, name: tBgpPeerNgLocalAddress, format: ip_address }
29
+ local_as:
30
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.65, name: tBgpPeerNgLocalAS4Byte, format: uint32 }
31
+ peer_identifier:
32
+ table: tBgpPeerNgOperTable
33
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.181, name: bgpPeerIdentifier, format: ip_address }
34
+ peer_type:
35
+ index: 2
36
+ mapping:
37
+ 0: unknown
38
+ 1: ipv4
39
+ 2: ipv6
40
+ 3: ipv4z
41
+ 4: ipv6z
42
+ 16: dns
43
+ description:
44
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.7, name: tBgpPeerNgDescription }
45
+ admin:
46
+ enabled:
47
+ symbol:
48
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.6
49
+ name: tBgpPeerNgShutdown
50
+ mapping:
51
+ 1: stop
52
+ 2: start
53
+ state:
54
+ symbol:
55
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.59
56
+ name: tBgpPeerNgConnState
57
+ mapping:
58
+ 1: idle
59
+ 2: connect
60
+ 3: active
61
+ 4: opensent
62
+ 5: openconfirm
63
+ 6: established
64
+ connection:
65
+ established_uptime:
66
+ table: tBgpPeerNgOperTable
67
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.177, name: bgpPeerFsmEstablishedTime }
68
+ last_received_update_age:
69
+ table: tBgpPeerNgOperTable
70
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.188, name: bgpPeerInUpdateElapsedTime }
71
+ traffic:
72
+ messages:
73
+ received:
74
+ table: tBgpPeerNgOperTable
75
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.184, name: bgpPeerInTotalMessages }
76
+ sent:
77
+ table: tBgpPeerNgOperTable
78
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.185, name: bgpPeerOutTotalMessages }
79
+ updates:
80
+ received:
81
+ table: tBgpPeerNgOperTable
82
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.182, name: bgpPeerInUpdates }
83
+ sent:
84
+ table: tBgpPeerNgOperTable
85
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.183, name: bgpPeerOutUpdates }
86
+ notifications:
87
+ received:
88
+ table: tBgpPeerNgOperTable
89
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.186, name: bgpPeerInNotifications }
90
+ sent:
91
+ table: tBgpPeerNgOperTable
92
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.187, name: bgpPeerOutNotifications }
93
+ transitions:
94
+ flaps:
95
+ table: tBgpPeerNgOperTable
96
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.10, name: tBgpPeerNgOperFlaps }
97
+ timers:
98
+ configured:
99
+ connect_retry:
100
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.8, name: tBgpPeerNgConnectRetry }
101
+ hold_time:
102
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.9, name: tBgpPeerNgHoldTime }
103
+ keepalive_time:
104
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.10, name: tBgpPeerNgKeepAlive }
105
+ min_route_advertisement_interval:
106
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.18, name: tBgpPeerNgMinRouteAdvertisement }
107
+ last_error:
108
+ code:
109
+ table: tBgpPeerNgOperTable
110
+ symbol:
111
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.178
112
+ name: bgpPeerLastErrorCode
113
+ format: hex
114
+ extract_value: '^([0-9a-f]{2})'
115
+ subcode:
116
+ table: tBgpPeerNgOperTable
117
+ symbol:
118
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.178
119
+ name: bgpPeerLastErrorSubcode
120
+ format: hex
121
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
122
+
123
+ - id: nokia-timos-bgp-peer-family-ipv4-unicast
124
+ MIB: TIMETRA-BGP-MIB
125
+ kind: peer_family
126
+ table: &nokia_timos_peer_oper_table
127
+ name: tBgpPeerNgOperTable
128
+ OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8
129
+ static_tags:
130
+ - { tag: address_family_name, value: ipv4 unicast }
131
+ identity: &nokia_timos_peer_family_identity_ipv4_unicast
132
+ routing_instance: &nokia_timos_routing_instance
133
+ table: vRtrConfTable
134
+ index_transform:
135
+ - start: 0
136
+ end: 0
137
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.3.1.1.4, name: vRtrName }
138
+ neighbor: &nokia_timos_neighbor
139
+ index_transform:
140
+ - start: 3
141
+ name: tBgpPeerNgAddress
142
+ format: ip_address
143
+ remote_as: &nokia_timos_remote_as
144
+ table: tBgpPeerNgTable
145
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.66, name: tBgpPeerNgPeerAS4Byte, format: uint32 }
146
+ address_family: { value: ipv4 }
147
+ subsequent_address_family: { value: unicast }
148
+ descriptors: &nokia_timos_peer_family_descriptors
149
+ local_address:
150
+ table: tBgpPeerNgTable
151
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.13, name: tBgpPeerNgLocalAddress, format: ip_address }
152
+ local_as:
153
+ table: tBgpPeerNgTable
154
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.65, name: tBgpPeerNgLocalAS4Byte, format: uint32 }
155
+ peer_identifier:
156
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.181, name: bgpPeerIdentifier, format: ip_address }
157
+ peer_type:
158
+ index: 2
159
+ mapping:
160
+ 0: unknown
161
+ 1: ipv4
162
+ 2: ipv6
163
+ 3: ipv4z
164
+ 4: ipv6z
165
+ 16: dns
166
+ description:
167
+ table: tBgpPeerNgTable
168
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.7.1.7, name: tBgpPeerNgDescription }
169
+ routes:
170
+ total:
171
+ received:
172
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.5, name: tBgpPeerNgOperReceivedPrefixes }
173
+ advertised:
174
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.6, name: tBgpPeerNgOperSentPrefixes }
175
+ rejected:
176
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.98, name: tBgpPeerNgOperIpv4RejPfxs }
177
+ current:
178
+ active:
179
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.7, name: tBgpPeerNgOperActivePrefixes }
180
+
181
+ - id: nokia-timos-bgp-peer-family-ipv4-vpn
182
+ MIB: TIMETRA-BGP-MIB
183
+ kind: peer_family
184
+ table: *nokia_timos_peer_oper_table
185
+ static_tags:
186
+ - { tag: address_family_name, value: vpnv4 unicast }
187
+ identity:
188
+ <<: *nokia_timos_peer_family_identity_ipv4_unicast
189
+ subsequent_address_family: { value: vpn }
190
+ descriptors: *nokia_timos_peer_family_descriptors
191
+ routes:
192
+ total:
193
+ received:
194
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.13, name: tBgpPeerNgOperVpnRecvPrefixes }
195
+ advertised:
196
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.14, name: tBgpPeerNgOperVpnSentPrefixes }
197
+ rejected:
198
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.99, name: tBgpPeerNgOperVpnIpv4RejPfxs }
199
+ current:
200
+ active:
201
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.15, name: tBgpPeerNgOperVpnActivePrefixes }
202
+
203
+ - id: nokia-timos-bgp-peer-family-ipv6-unicast
204
+ MIB: TIMETRA-BGP-MIB
205
+ kind: peer_family
206
+ table: *nokia_timos_peer_oper_table
207
+ static_tags:
208
+ - { tag: address_family_name, value: ipv6 unicast }
209
+ identity:
210
+ <<: *nokia_timos_peer_family_identity_ipv4_unicast
211
+ address_family: { value: ipv6 }
212
+ subsequent_address_family: { value: unicast }
213
+ descriptors: *nokia_timos_peer_family_descriptors
214
+ routes:
215
+ total:
216
+ received:
217
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.27, name: tBgpPeerNgOperV6ReceivedPrefixes }
218
+ advertised:
219
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.28, name: tBgpPeerNgOperV6SentPrefixes }
220
+ rejected:
221
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.100, name: tBgpPeerNgOperIpv6RejPfxs }
222
+ current:
223
+ active:
224
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.29, name: tBgpPeerNgOperV6ActivePrefixes }
225
+
226
+ - id: nokia-timos-bgp-peer-family-ipv6-vpn
227
+ MIB: TIMETRA-BGP-MIB
228
+ kind: peer_family
229
+ table: *nokia_timos_peer_oper_table
230
+ static_tags:
231
+ - { tag: address_family_name, value: vpnv6 unicast }
232
+ identity:
233
+ <<: *nokia_timos_peer_family_identity_ipv4_unicast
234
+ address_family: { value: ipv6 }
235
+ subsequent_address_family: { value: vpn }
236
+ descriptors: *nokia_timos_peer_family_descriptors
237
+ routes:
238
+ total:
239
+ received:
240
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.40, name: tBgpPeerNgOperVpnIpv6RecvPfxs }
241
+ advertised:
242
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.41, name: tBgpPeerNgOperVpnIpv6SentPfxs }
243
+ rejected:
244
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.101, name: tBgpPeerNgOperVpnIpv6RejPfxs }
245
+ current:
246
+ active:
247
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.42, name: tBgpPeerNgOperVpnIpv6ActivePfxs }
248
+
249
+ - id: nokia-timos-bgp-peer-family-l2vpn-vpls
250
+ MIB: TIMETRA-BGP-MIB
251
+ kind: peer_family
252
+ table: *nokia_timos_peer_oper_table
253
+ static_tags:
254
+ - { tag: address_family_name, value: l2vpn vpls }
255
+ identity:
256
+ <<: *nokia_timos_peer_family_identity_ipv4_unicast
257
+ address_family: { value: l2vpn }
258
+ subsequent_address_family: { value: vpls }
259
+ descriptors: *nokia_timos_peer_family_descriptors
260
+ routes:
261
+ total:
262
+ received:
263
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.49, name: tBgpPeerNgOperl2VpnRecvPfxs }
264
+ advertised:
265
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.50, name: tBgpPeerNgOperl2VpnSentPfxs }
266
+ rejected:
267
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.103, name: tBgpPeerNgOperl2VpnRejPfxs }
268
+ current:
269
+ active:
270
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.51, name: tBgpPeerNgOperl2VpnActivePfxs }
271
+
272
+ - id: nokia-timos-bgp-peer-family-l2vpn-evpn
273
+ MIB: TIMETRA-BGP-MIB
274
+ kind: peer_family
275
+ table: *nokia_timos_peer_oper_table
276
+ static_tags:
277
+ - { tag: address_family_name, value: l2vpn evpn }
278
+ identity:
279
+ <<: *nokia_timos_peer_family_identity_ipv4_unicast
280
+ address_family: { value: l2vpn }
281
+ subsequent_address_family: { value: evpn }
282
+ descriptors: *nokia_timos_peer_family_descriptors
283
+ routes:
284
+ total:
285
+ received:
286
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.88, name: tBgpPeerNgOperEvpnRecvPfxs }
287
+ advertised:
288
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.89, name: tBgpPeerNgOperEvpnSentPfxs }
289
+ rejected:
290
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.112, name: tBgpPeerNgOperEvpnRejPfxs }
291
+ current:
292
+ active:
293
+ symbol: { OID: 1.3.6.1.4.1.6527.3.1.2.14.4.8.1.90, name: tBgpPeerNgOperEvpnActivePfxs }
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_sophos-xgs-firewall-licensing.yaml
new
+293
@@ -0,0 +1,293 @@
1
+# SFOS-FIREWALL-MIB
2
+# https://mibbrowser.online/mibdb_search.php?mib=SFOS-FIREWALL-MIB
3
+# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/sophos/SFOS-FIREWALL-MIB
4
+
5
+licensing:
6
+ - id: base_firewall
7
+ MIB: SFOS-FIREWALL-MIB
8
+ identity:
9
+ id:
10
+ value: base_firewall
11
+ name:
12
+ value: Base Firewall
13
+ component:
14
+ value: base_firewall
15
+ descriptors:
16
+ type:
17
+ value: subscription
18
+ impact:
19
+ value: Sophos Base Firewall subscription state and expiry on the device
20
+ state:
21
+ OID: 1.3.6.1.4.1.2604.5.1.5.1.1.0
22
+ name: sfosBaseFWLicRegStatus
23
+ policy: sophos
24
+ mapping:
25
+ "0": "0"
26
+ "1": "1"
27
+ "2": "0"
28
+ "3": "0"
29
+ "4": "2"
30
+ "5": "2"
31
+ signals:
32
+ expiry:
33
+ timestamp:
34
+ from: 1.3.6.1.4.1.2604.5.1.5.1.2.0
35
+ name: sfosBaseFWLicExpiryDate
36
+ format: text_date
37
+ sentinel: [timer_zero_or_negative]
38
+ - id: network_protection
39
+ MIB: SFOS-FIREWALL-MIB
40
+ identity:
41
+ id:
42
+ value: network_protection
43
+ name:
44
+ value: Network Protection
45
+ component:
46
+ value: network_protection
47
+ descriptors:
48
+ type:
49
+ value: subscription
50
+ impact:
51
+ value: Sophos Network Protection subscription state and expiry on the device
52
+ state:
53
+ OID: 1.3.6.1.4.1.2604.5.1.5.2.1.0
54
+ name: sfosNetProtectionLicRegStatus
55
+ policy: sophos
56
+ mapping:
57
+ "0": "0"
58
+ "1": "1"
59
+ "2": "0"
60
+ "3": "0"
61
+ "4": "2"
62
+ "5": "2"
63
+ signals:
64
+ expiry:
65
+ timestamp:
66
+ from: 1.3.6.1.4.1.2604.5.1.5.2.2.0
67
+ name: sfosNetProtectionLicExpiryDate
68
+ format: text_date
69
+ sentinel: [timer_zero_or_negative]
70
+ - id: web_protection
71
+ MIB: SFOS-FIREWALL-MIB
72
+ identity:
73
+ id:
74
+ value: web_protection
75
+ name:
76
+ value: Web Protection
77
+ component:
78
+ value: web_protection
79
+ descriptors:
80
+ type:
81
+ value: subscription
82
+ impact:
83
+ value: Sophos Web Protection subscription state and expiry on the device
84
+ state:
85
+ OID: 1.3.6.1.4.1.2604.5.1.5.3.1.0
86
+ name: sfosWebProtectionLicRegStatus
87
+ policy: sophos
88
+ mapping:
89
+ "0": "0"
90
+ "1": "1"
91
+ "2": "0"
92
+ "3": "0"
93
+ "4": "2"
94
+ "5": "2"
95
+ signals:
96
+ expiry:
97
+ timestamp:
98
+ from: 1.3.6.1.4.1.2604.5.1.5.3.2.0
99
+ name: sfosWebProtectionLicExpiryDate
100
+ format: text_date
101
+ sentinel: [timer_zero_or_negative]
102
+ - id: mail_protection
103
+ MIB: SFOS-FIREWALL-MIB
104
+ identity:
105
+ id:
106
+ value: mail_protection
107
+ name:
108
+ value: Mail Protection
109
+ component:
110
+ value: mail_protection
111
+ descriptors:
112
+ type:
113
+ value: subscription
114
+ impact:
115
+ value: Sophos Mail Protection subscription state and expiry on the device
116
+ state:
117
+ OID: 1.3.6.1.4.1.2604.5.1.5.4.1.0
118
+ name: sfosMailProtectionLicRegStatus
119
+ policy: sophos
120
+ mapping:
121
+ "0": "0"
122
+ "1": "1"
123
+ "2": "0"
124
+ "3": "0"
125
+ "4": "2"
126
+ "5": "2"
127
+ signals:
128
+ expiry:
129
+ timestamp:
130
+ from: 1.3.6.1.4.1.2604.5.1.5.4.2.0
131
+ name: sfosMailProtectionLicExpiryDate
132
+ format: text_date
133
+ sentinel: [timer_zero_or_negative]
134
+ - id: web_server_protection
135
+ MIB: SFOS-FIREWALL-MIB
136
+ identity:
137
+ id:
138
+ value: web_server_protection
139
+ name:
140
+ value: Web Server Protection
141
+ component:
142
+ value: web_server_protection
143
+ descriptors:
144
+ type:
145
+ value: subscription
146
+ impact:
147
+ value: Sophos Web Server Protection subscription state and expiry on the device
148
+ state:
149
+ OID: 1.3.6.1.4.1.2604.5.1.5.5.1.0
150
+ name: sfosWebServerProtectionLicRegStatus
151
+ policy: sophos
152
+ mapping:
153
+ "0": "0"
154
+ "1": "1"
155
+ "2": "0"
156
+ "3": "0"
157
+ "4": "2"
158
+ "5": "2"
159
+ signals:
160
+ expiry:
161
+ timestamp:
162
+ from: 1.3.6.1.4.1.2604.5.1.5.5.2.0
163
+ name: sfosWebServerProtectionLicExpiryDate
164
+ format: text_date
165
+ sentinel: [timer_zero_or_negative]
166
+ - id: sandstorm
167
+ MIB: SFOS-FIREWALL-MIB
168
+ identity:
169
+ id:
170
+ value: sandstorm
171
+ name:
172
+ value: Sandstorm
173
+ component:
174
+ value: sandstorm
175
+ descriptors:
176
+ type:
177
+ value: subscription
178
+ impact:
179
+ value: Sophos Sandstorm subscription state and expiry on the device
180
+ state:
181
+ OID: 1.3.6.1.4.1.2604.5.1.5.6.1.0
182
+ name: sfosSandstromLicRegStatus
183
+ policy: sophos
184
+ mapping:
185
+ "0": "0"
186
+ "1": "1"
187
+ "2": "0"
188
+ "3": "0"
189
+ "4": "2"
190
+ "5": "2"
191
+ signals:
192
+ expiry:
193
+ timestamp:
194
+ from: 1.3.6.1.4.1.2604.5.1.5.6.2.0
195
+ name: sfosSandstromLicExpiryDate
196
+ format: text_date
197
+ sentinel: [timer_zero_or_negative]
198
+ - id: enhanced_support
199
+ MIB: SFOS-FIREWALL-MIB
200
+ identity:
201
+ id:
202
+ value: enhanced_support
203
+ name:
204
+ value: Enhanced Support
205
+ component:
206
+ value: enhanced_support
207
+ descriptors:
208
+ type:
209
+ value: support
210
+ impact:
211
+ value: Sophos Enhanced Support state and expiry on the device
212
+ state:
213
+ OID: 1.3.6.1.4.1.2604.5.1.5.7.1.0
214
+ name: sfosEnhancedSupportLicRegStatus
215
+ policy: sophos
216
+ mapping:
217
+ "0": "0"
218
+ "1": "1"
219
+ "2": "0"
220
+ "3": "0"
221
+ "4": "2"
222
+ "5": "2"
223
+ signals:
224
+ expiry:
225
+ timestamp:
226
+ from: 1.3.6.1.4.1.2604.5.1.5.7.2.0
227
+ name: sfosEnhancedSupportLicExpiryDate
228
+ format: text_date
229
+ sentinel: [timer_zero_or_negative]
230
+ - id: enhanced_plus_support
231
+ MIB: SFOS-FIREWALL-MIB
232
+ identity:
233
+ id:
234
+ value: enhanced_plus_support
235
+ name:
236
+ value: Enhanced Plus Support
237
+ component:
238
+ value: enhanced_plus_support
239
+ descriptors:
240
+ type:
241
+ value: support
242
+ impact:
243
+ value: Sophos Enhanced Plus Support state and expiry on the device
244
+ state:
245
+ OID: 1.3.6.1.4.1.2604.5.1.5.8.1.0
246
+ name: sfosEnhancedPlusLicRegStatus
247
+ policy: sophos
248
+ mapping:
249
+ "0": "0"
250
+ "1": "1"
251
+ "2": "0"
252
+ "3": "0"
253
+ "4": "2"
254
+ "5": "2"
255
+ signals:
256
+ expiry:
257
+ timestamp:
258
+ from: 1.3.6.1.4.1.2604.5.1.5.8.2.0
259
+ name: sfosEnhancedPlusLicExpiryDate
260
+ format: text_date
261
+ sentinel: [timer_zero_or_negative]
262
+ - id: central_orchestration
263
+ MIB: SFOS-FIREWALL-MIB
264
+ identity:
265
+ id:
266
+ value: central_orchestration
267
+ name:
268
+ value: Central Orchestration
269
+ component:
270
+ value: central_orchestration
271
+ descriptors:
272
+ type:
273
+ value: subscription
274
+ impact:
275
+ value: Sophos Central Orchestration state and expiry on the device
276
+ state:
277
+ OID: 1.3.6.1.4.1.2604.5.1.5.9.1.0
278
+ name: sfosCentralOrchestrationLicRegStatus
279
+ policy: sophos
280
+ mapping:
281
+ "0": "0"
282
+ "1": "1"
283
+ "2": "0"
284
+ "3": "0"
285
+ "4": "2"
286
+ "5": "2"
287
+ signals:
288
+ expiry:
289
+ timestamp:
290
+ from: 1.3.6.1.4.1.2604.5.1.5.9.2.0
291
+ name: sfosCentralOrchestrationLicExpiryDate
292
+ format: text_date
293
+ sentinel: [timer_zero_or_negative]
src/go/plugin/go.d/config/go.d/snmp.profiles/default/_std-bgp4-mib.yaml
+117
-63
@@ -1,26 +1,52 @@
1
# Peer
2
# This table contains one entry per neighbor (BGP peer) and information about the connections with BGP peers.
3
-metrics:
4
- - MIB: BGP4-MIB
3
+bgp:
4
+ - id: bgp4-peer
5
+ MIB: BGP4-MIB
6
+ kind: peer
7
table:
8
name: bgpPeerTable
9
OID: 1.3.6.1.2.1.15.3
8
- symbols:
9
- - OID: 1.3.6.1.2.1.15.3.1.3
10
- name: bgpPeerAdminStatus
11
- chart_meta:
12
- description: The desired state of the BGP connection
13
- family: 'Network/Routing/BGP/Peer/Admin/Status'
14
- unit: "{status}"
15
- mapping:
16
- 1: stop
17
- 2: start
18
- - OID: 1.3.6.1.2.1.15.3.1.2
10
+ identity:
11
+ routing_instance:
12
+ value: default
13
+ neighbor:
14
+ symbol:
15
+ OID: 1.3.6.1.2.1.15.3.1.7
16
+ name: bgpPeerRemoteAddr
17
+ format: ip_address
18
+ remote_as:
19
+ symbol:
20
+ OID: 1.3.6.1.2.1.15.3.1.9
21
+ name: bgpPeerRemoteAs
22
+ format: uint32
23
+ descriptors:
24
+ bgp_version:
25
+ symbol:
26
+ OID: 1.3.6.1.2.1.15.3.1.4
27
+ name: bgpPeerNegotiatedVersion
28
+ local_address:
29
+ symbol:
30
+ OID: 1.3.6.1.2.1.15.3.1.5
31
+ name: bgpPeerLocalAddr
32
+ format: ip_address
33
+ peer_identifier:
34
+ symbol:
35
+ OID: 1.3.6.1.2.1.15.3.1.1
36
+ name: bgpPeerIdentifier
37
+ format: ip_address
38
+ admin:
39
+ enabled:
40
+ symbol:
41
+ OID: 1.3.6.1.2.1.15.3.1.3
42
+ name: bgpPeerAdminStatus
43
+ mapping:
44
+ 1: stop
45
+ 2: start
46
+ state:
47
+ symbol:
48
+ OID: 1.3.6.1.2.1.15.3.1.2
49
name: bgpPeerState
20
- chart_meta:
21
- description: The BGP peer connection state
22
- family: 'Network/Routing/BGP/Peer/Connection/Status'
23
- unit: "{status}"
50
mapping:
51
1: idle
52
2: connect
@@ -28,52 +54,80 @@ metrics:
54
4: opensent
55
5: openconfirm
56
6: established
31
- - OID: 1.3.6.1.2.1.15.3.1.12
32
- name: bgpPeerInTotalMessages
33
- chart_meta:
34
- description: The total number of messages received from the remote peer on this connection
35
- family: 'Network/Routing/BGP/Peer/Message/Total/In'
36
- unit: "{message}"
37
- - OID: 1.3.6.1.2.1.15.3.1.13
38
- name: bgpPeerOutTotalMessages
39
- chart_meta:
40
- description: The total number of messages transmitted to the remote peer on this connection
41
- family: 'Network/Routing/BGP/Peer/Message/Total/Out'
42
- unit: "{message}"
43
- - OID: 1.3.6.1.2.1.15.3.1.10
44
- name: bgpPeerInUpdates
45
- chart_meta:
46
- description: The number of BGP UPDATE messages received on this connection
47
- family: 'Network/Routing/BGP/Peer/Message/Update/In'
48
- unit: "{message}"
49
- - OID: 1.3.6.1.2.1.15.3.1.11
50
- name: bgpPeerOutUpdates
51
- chart_meta:
52
- description: The number of BGP UPDATE messages transmitted on this connection
53
- family: 'Network/Routing/BGP/Peer/Message/Update/Out'
54
- unit: "{message}"
55
- - OID: 1.3.6.1.2.1.15.3.1.15
56
- name: bgpPeerFsmEstablishedTransitions
57
- chart_meta:
58
- description: The total number of times the BGP FSM transitioned into the established state
59
- family: 'Network/Routing/BGP/Peer/FSM/Transition/Established/Count'
60
- unit: "{transition}"
61
- - OID: 1.3.6.1.2.1.15.3.1.16
62
- name: bgpPeerFsmEstablishedTime
63
- chart_meta:
64
- description: Time in seconds this peer has been in the Established state or since last in Established state
65
- family: 'Network/Routing/BGP/Peer/FSM/Duration/Established'
66
- unit: "s"
67
- metric_tags:
68
- - tag: remote_as
57
+ connection:
58
+ established_uptime:
59
symbol:
70
- OID: 1.3.6.1.2.1.15.3.1.9
71
- name: bgpPeerRemoteAs
72
- - tag: neighbor
60
+ OID: 1.3.6.1.2.1.15.3.1.16
61
+ name: bgpPeerFsmEstablishedTime
62
+ last_received_update_age:
63
symbol:
74
- OID: 1.3.6.1.2.1.15.3.1.7
75
- name: bgpPeerRemoteAddr
76
- - tag: bgp_version
64
+ OID: 1.3.6.1.2.1.15.3.1.24
65
+ name: bgpPeerInUpdateElapsedTime
66
+ traffic:
67
+ messages:
68
+ received:
69
+ symbol:
70
+ OID: 1.3.6.1.2.1.15.3.1.12
71
+ name: bgpPeerInTotalMessages
72
+ sent:
73
+ symbol:
74
+ OID: 1.3.6.1.2.1.15.3.1.13
75
+ name: bgpPeerOutTotalMessages
76
+ updates:
77
+ received:
78
+ symbol:
79
+ OID: 1.3.6.1.2.1.15.3.1.10
80
+ name: bgpPeerInUpdates
81
+ sent:
82
+ symbol:
83
+ OID: 1.3.6.1.2.1.15.3.1.11
84
+ name: bgpPeerOutUpdates
85
+ transitions:
86
+ established:
87
symbol:
78
- OID: 1.3.6.1.2.1.15.3.1.4
79
- name: bgpPeerNegotiatedVersion
88
+ OID: 1.3.6.1.2.1.15.3.1.15
89
+ name: bgpPeerFsmEstablishedTransitions
90
+ timers:
91
+ negotiated:
92
+ hold_time:
93
+ symbol:
94
+ OID: 1.3.6.1.2.1.15.3.1.18
95
+ name: bgpPeerHoldTime
96
+ keepalive_time:
97
+ symbol:
98
+ OID: 1.3.6.1.2.1.15.3.1.19
99
+ name: bgpPeerKeepAlive
100
+ configured:
101
+ connect_retry:
102
+ symbol:
103
+ OID: 1.3.6.1.2.1.15.3.1.17
104
+ name: bgpPeerConnectRetryInterval
105
+ hold_time:
106
+ symbol:
107
+ OID: 1.3.6.1.2.1.15.3.1.20
108
+ name: bgpPeerHoldTimeConfigured
109
+ keepalive_time:
110
+ symbol:
111
+ OID: 1.3.6.1.2.1.15.3.1.21
112
+ name: bgpPeerKeepAliveConfigured
113
+ min_as_origination_interval:
114
+ symbol:
115
+ OID: 1.3.6.1.2.1.15.3.1.22
116
+ name: bgpPeerMinASOriginationInterval
117
+ min_route_advertisement_interval:
118
+ symbol:
119
+ OID: 1.3.6.1.2.1.15.3.1.23
120
+ name: bgpPeerMinRouteAdvertisementInterval
121
+ last_error:
122
+ code:
123
+ symbol:
124
+ OID: 1.3.6.1.2.1.15.3.1.14
125
+ name: bgpPeerLastErrorCode
126
+ format: hex
127
+ extract_value: '^([0-9a-f]{2})'
128
+ subcode:
129
+ symbol:
130
+ OID: 1.3.6.1.2.1.15.3.1.14
131
+ name: bgpPeerLastErrorSubcode
132
+ format: hex
133
+ extract_value: '^[0-9a-f]{2}([0-9a-f]{2})'
src/go/plugin/go.d/config/go.d/snmp.profiles/default/alcatel-lucent-ent.yaml
+1
@@ -1,5 +1,6 @@
1
extends:
2
- alcatel-lucent.yaml
3
+ - _alcatel-bgp.yaml
4
5
selector:
6
- sysobjectid:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/arista-switch.yaml
+2
-70
@@ -1,8 +1,8 @@
1
extends:
2
- _arista.yaml
3
+ - _arista-bgp4-v2.yaml
4
- _arista-queue.yaml
5
- _system-base.yaml
5
- - _std-bgp4-mib.yaml
6
- _std-entity-sensor-mib.yaml
7
- _std-if-mib.yaml
8
- _std-ospf-mib.yaml
@@ -165,75 +165,7 @@ selector:
165
- 1.3.6.1.4.1.30065.1.3011.7280.877.48
166
- 1.3.6.1.4.1.30065.1.3011.7280.877.48.972
167
168
-metrics:
169
- - MIB: ARISTA-BGP4V2-MIB
170
- table:
171
- name: aristaBgp4V2PeerTable
172
- OID: 1.3.6.1.4.1.30065.4.1.1.2
173
- symbols:
174
- - OID: 1.3.6.1.4.1.30065.4.1.1.2.1.12
175
- name: aristaBgp4V2PeerAdminStatus
176
- chart_meta:
177
- description: BGP FSM Status
178
- family: 'Network/Routing/BGP/Peer/Admin/Status'
179
- unit: "{status}"
180
- mapping:
181
- 1: halted
182
- 2: running
183
- - OID: 1.3.6.1.4.1.30065.4.1.1.2.1.13
184
- name: aristaBgp4V2PeerState
185
- chart_meta:
186
- description: BGP peer connection state
187
- family: 'Network/Routing/BGP/Peer/Status'
188
- unit: "{status}"
189
- mapping:
190
- 1: idle
191
- 2: connect
192
- 3: active
193
- 4: opensent
194
- 5: openconfirm
195
- 6: established
196
- metric_tags:
197
- - tag: arista_bgp4_v2_peer_local_as
198
- symbol:
199
- name: aristaBgp4V2PeerLocalAs
200
- OID: 1.3.6.1.4.1.30065.4.1.1.2.1.7
201
- - tag: arista_bgp4_v2_peer_remote_as
202
- symbol:
203
- name: aristaBgp4V2PeerRemoteAs
204
- OID: 1.3.6.1.4.1.30065.4.1.1.2.1.10
205
- - tag: arista_bgp4_v2_peer_local_identifier
206
- symbol:
207
- name: aristaBgp4V2PeerLocalIdentifier
208
- OID: 1.3.6.1.4.1.30065.4.1.1.2.1.8
209
- format: ip_address
210
- - tag: arista_bgp4_v2_peer_remote_identifier
211
- symbol:
212
- name: aristaBgp4V2PeerRemoteIdentifier
213
- OID: 1.3.6.1.4.1.30065.4.1.1.2.1.11
214
- format: ip_address
215
- - tag: arista_bgp4_v2_peer_local_addr_type
216
- symbol:
217
- OID: 1.3.6.1.4.1.30065.4.1.1.2.1.2
218
- name: aristaBgp4V2PeerLocalAddrType
219
- mapping:
220
- 0: unknown
221
- 1: ipv4
222
- 2: ipv6
223
- 3: ipv4z
224
- 4: ipv6z
225
- 16: dns
226
- # - tag: _arista_bgp4_v2_peer_description
227
- # symbol:
228
- # name: aristaBgp4V2PeerDescription
229
- # OID: 1.3.6.1.4.1.30065.4.1.1.2.1.14
230
- # - tag: _arista_bgp4_v2_peer_local_addr
231
- # symbol:
232
- # name: aristaBgp4V2PeerLocalAddr
233
- # OID: 1.3.6.1.4.1.30065.4.1.1.2.1.3
234
- # format: ip_address
235
-
236
- # - MIB: ARISTA-IF-MIB
168
+# - MIB: ARISTA-IF-MIB
169
# table:
170
# name: aristaIfTable
171
# OID: 1.3.6.1.4.1.30065.3.15.1.1
src/go/plugin/go.d/config/go.d/snmp.profiles/default/bluecoat-proxysg.yaml
+1
-48
@@ -7,6 +7,7 @@
7
extends:
8
- _system-base.yaml
9
- _std-if-mib.yaml
10
+ - _bluecoat-proxysg-licensing.yaml
11
12
selector:
13
- sysobjectid:
@@ -42,51 +43,3 @@ metadata:
43
name: sgProxyVersion
44
type:
45
value: "Proxy"
45
-
46
-# References: LibreNMS SGOS OS detection and BLUECOAT-LICENSE-MIB.
47
-
48
-licensing:
49
- - MIB: BLUECOAT-LICENSE-MIB
50
- id: app_license_status
51
- table:
52
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1
53
- name: appLicenseStatusTable
54
- identity:
55
- id:
56
- index: 1
57
- name:
58
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.2
59
- name: appLicenseStatusApplicationName
60
- feature:
61
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.3
62
- name: appLicenseStatusFeatureName
63
- component:
64
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.4
65
- name: appLicenseStatusComponentName
66
- descriptors:
67
- type:
68
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.5
69
- name: appLicenseStatusExpireType
70
- mapping:
71
- 0: unknown
72
- 1: perpetual
73
- 2: subscription
74
- 3: demo
75
- perpetual:
76
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.5
77
- name: appLicenseStatusExpireType
78
- mapping:
79
- 1: "true"
80
- state:
81
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.7
82
- name: appLicenseStatusLicenseState
83
- mapping:
84
- 0: "1"
85
- 1: "0"
86
- 2: "2"
87
- signals:
88
- expiry:
89
- timestamp:
90
- OID: 1.3.6.1.4.1.3417.2.16.1.1.1.1.6
91
- name: appLicenseStatusExpireDate
92
- format: snmp_dateandtime
src/go/plugin/go.d/config/go.d/snmp.profiles/default/checkpoint.yaml
+1
-56
@@ -16,6 +16,7 @@ extends:
16
- _checkpoint-hardware.yaml
17
- _checkpoint-cluster.yaml
18
- _checkpoint-vsx.yaml
19
+ - _checkpoint-licensing.yaml
20
21
selector:
22
- sysobjectid:
@@ -67,62 +68,6 @@ metadata:
68
type:
69
value: "Firewall"
70
70
-licensing:
71
- - id: licensing_blades
72
- MIB: CHECKPOINT-MIB
73
- table:
74
- OID: 1.3.6.1.4.1.2620.1.6.18.1
75
- name: licensingTable
76
- identity:
77
- id:
78
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.2
79
- name: licensingID
80
- name:
81
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.4
82
- name: licensingBladeName
83
- component:
84
- value: blade
85
- descriptors:
86
- type:
87
- value: subscription
88
- impact:
89
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.7
90
- name: licensingImpact
91
- state:
92
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.5
93
- name: licensingState
94
- mapping:
95
- # MIB-documented hyphenated tokens
96
- valid: "0"
97
- "up-to-date": "0"
98
- "about-to-expire": "1"
99
- unknown: "1"
100
- expired: "2"
101
- "not-associated": "2"
102
- # Real on-wire TitleCase variants observed on production gateways
103
- Valid: "0"
104
- "Up To Date": "0"
105
- "About To Expire": "1"
106
- Unknown: "1"
107
- Expired: "2"
108
- "Not Associated": "2"
109
- "Not Entitled": "2"
110
- Evaluation: "1"
111
- Trial: "1"
112
- signals:
113
- expiry:
114
- timestamp:
115
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.6
116
- name: licensingExpirationDate
117
- sentinel: [timer_u32_max]
118
- usage:
119
- used:
120
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.10
121
- name: licensingUsedQuota
122
- capacity:
123
- OID: 1.3.6.1.4.1.2620.1.6.18.1.1.9
124
- name: licensingTotalQuota
125
-
71
metrics:
72
# CPU
73
- MIB: CHECKPOINT-MIB
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-3850.yaml
+1
@@ -5,6 +5,7 @@ extends:
5
- _system-base.yaml
6
- _cisco-base.yaml
7
- _cisco-catalyst.yaml
8
+ - _cisco-bgp4-mib.yaml
9
10
selector:
11
- sysobjectid:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-asr.yaml
+1
@@ -3,6 +3,7 @@
3
extends:
4
- _system-base.yaml
5
- _cisco-base.yaml
6
+ - _cisco-bgp4-mib.yaml
7
8
metadata:
9
device:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-catalyst.yaml
+1
@@ -3,6 +3,7 @@
3
extends:
4
- _system-base.yaml
5
- _cisco-base.yaml
6
+ - _cisco-bgp4-mib.yaml
7
- _cisco-catalyst.yaml
8
- _cisco-catalyst-poe.yaml
9
- _std-lag-mib.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-csr1000v.yaml
+1
@@ -1,6 +1,7 @@
1
extends:
2
- _system-base.yaml
3
- _cisco-base.yaml
4
+ - _cisco-bgp4-mib.yaml
5
6
selector:
7
- sysobjectid:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-isr-4431.yaml
+1
@@ -1,6 +1,7 @@
1
extends:
2
- _system-base.yaml
3
- _cisco-base.yaml
4
+ - _cisco-bgp4-mib.yaml
5
- _cisco-voice.yaml
6
7
selector:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-isr.yaml
+1
@@ -3,6 +3,7 @@
3
extends:
4
- _system-base.yaml
5
- _cisco-base.yaml
6
+ - _cisco-bgp4-mib.yaml
7
8
9
metadata:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-ncs.yaml
new
+19
@@ -0,0 +1,19 @@
1
+# Profile for Cisco NCS routers
2
+
3
+extends:
4
+ - _system-base.yaml
5
+ - _cisco-base.yaml
6
+ - _cisco-bgp4-mib.yaml
7
+
8
+metadata:
9
+ device:
10
+ fields:
11
+ type:
12
+ value: Router
13
+ vendor:
14
+ value: Cisco
15
+
16
+selector:
17
+ - sysobjectid:
18
+ include:
19
+ - 1.3.6.1.4.1.9.1.2984 # ciscoNCS540L
src/go/plugin/go.d/config/go.d/snmp.profiles/default/cisco-nexus.yaml
+1
@@ -3,6 +3,7 @@
3
extends:
4
- _system-base.yaml
5
- _cisco-base.yaml
6
+ - _cisco-bgp4-mib.yaml
7
- _cisco-nexus-vpc.yaml
8
- _cisco-nexus-system-ext.yaml
9
- _cisco-nexus-feature.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/dell-os10.yaml
+1
-73
@@ -1,5 +1,6 @@
1
extends:
2
- _dell.yaml
3
+ - _dell-os10-bgp4-v2.yaml
4
- _std-host-resources-mib-feat-base.yaml
5
- _std-ucd-mib.yaml
6
# - if32-mib.yaml # TODO: later (NDM-1933)
@@ -151,76 +152,3 @@ metrics:
152
symbol:
153
OID: 1.3.6.1.4.1.674.11000.5000.100.4.1.2.3.1.1
154
name: dell.os10FanIndex
154
-
155
- - MIB: DELLEMC-OS10-BGP4V2-MIB
156
- table:
157
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2
158
- name: dell.os10bgp4V2PeerTable
159
- symbols:
160
- - OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.12
161
- name: dell.os10bgp4V2PeerAdminStatus
162
- chart_meta:
163
- family: 'Network/Routing/BGP/Peer/Status/Admin'
164
- description: "Administrative status of the BGP peer"
165
- unit: "{status}"
166
- mapping:
167
- 1: halted
168
- 2: running
169
- - OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.13
170
- name: dell.os10bgp4V2PeerState
171
- chart_meta:
172
- family: 'Network/Routing/BGP/Peer/State'
173
- description: "Current state of the BGP peer"
174
- unit: "{status}"
175
- mapping:
176
- 1: idle
177
- 2: connect
178
- 3: active
179
- 4: opensent
180
- 5: openconfirm
181
- 6: established
182
- metric_tags:
183
- - symbol:
184
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.2
185
- name: dell.os10bgp4V2PeerLocalAddrType
186
- tag: dell_os10bgp4_v2_peer_local_addr_type
187
- mapping:
188
- 0: unknown
189
- 1: ipv4
190
- 2: ipv6
191
- 3: ipv4z
192
- 4: ipv6z
193
- 16: dns
194
- 25: l2vpn
195
- - symbol:
196
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.4
197
- name: dell.os10bgp4V2PeerRemoteAddrType
198
- tag: dell_os10bgp4_v2_peer_remote_addr_type
199
- mapping:
200
- 0: unknown
201
- 1: ipv4
202
- 2: ipv6
203
- 3: ipv4z
204
- 4: ipv6z
205
- 16: dns
206
- 25: l2vpn
207
- - symbol:
208
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.7
209
- name: dell.os10bgp4V2PeerLocalAs
210
- tag: dell_os10bgp4_v2_peer_local_as
211
- - symbol:
212
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.8
213
- name: dell.os10bgp4V2PeerLocalIdentifier
214
- tag: dell_os10bgp4_v2_peer_local_identifier
215
- - symbol:
216
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.10
217
- name: dell.os10bgp4V2PeerRemoteAs
218
- tag: dell_os10bgp4_v2_peer_remote_as
219
- - symbol:
220
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.11
221
- name: dell.os10bgp4V2PeerRemoteIdentifier
222
- tag: dell_os10bgp4_v2_peer_remote_identifier
223
- - symbol:
224
- OID: 1.3.6.1.4.1.674.11000.5000.200.1.1.2.1.14
225
- name: dell.os10bgp4V2PeerDescription
226
- tag: dell_os10bgp4_v2_peer_description
src/go/plugin/go.d/config/go.d/snmp.profiles/default/fortinet-fortigate.yaml
+1
-81
@@ -11,6 +11,7 @@ extends:
11
- _fortinet-fortigate-sdwan.yaml
12
- _fortinet-fortigate-security.yaml
13
- _fortinet-fortigate-wireless.yaml
14
+ - _fortinet-fortigate-licensing.yaml
15
16
# All fortinet devices have sysObjectID starting with `1.3.6.1.4.1.12356.101.1` (fgModel)
17
# We only target Fortinet Fortigate devices (1.3.6.1.4.1.12356.101/fnFortiGateMib)
@@ -69,87 +70,6 @@ metadata:
70
match_value: "$1"
71
# Examples: `5.6.4` from `v5.6.4,build1575,180425 (GA)`
72
72
-# References:
73
-# https://docs.fortinet.com/document/fortigate/8.0.0/fortigate-mib-information-overview/293724/fortigate-system-mibs
74
-# https://mibbrowser.online/mibdb_search.php?mib=FORTINET-FORTIGATE-MIB
75
-# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/fortinet/FORTINET-FORTIGATE-MIB
76
-
77
-licensing:
78
- - MIB: FORTINET-FORTIGATE-MIB
79
- id: fortigate_contracts
80
- table:
81
- OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2
82
- name: fgLicContractTable
83
- identity:
84
- id:
85
- OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.1
86
- name: fgLicContractDesc
87
- name:
88
- OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.1
89
- name: fgLicContractDesc
90
- component:
91
- value: device
92
- descriptors:
93
- type:
94
- value: contract
95
- signals:
96
- expiry:
97
- timestamp:
98
- OID: 1.3.6.1.4.1.12356.101.4.6.3.1.2.1.2
99
- name: fgLicContractExpiry
100
- format: text_date
101
-
102
- - MIB: FORTINET-FORTIGATE-MIB
103
- id: fortiguard_services
104
- table:
105
- OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2
106
- name: fgLicVersionTable
107
- identity:
108
- id:
109
- OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.1
110
- name: fgLicVersionDesc
111
- name:
112
- OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.1
113
- name: fgLicVersionDesc
114
- feature:
115
- OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.3
116
- name: fgLicVersionNumber
117
- component:
118
- value: fortiguard
119
- descriptors:
120
- type:
121
- value: service
122
- signals:
123
- expiry:
124
- timestamp:
125
- OID: 1.3.6.1.4.1.12356.101.4.6.3.2.2.1.2
126
- name: fgLicVersionExpiry
127
- format: text_date
128
-
129
- - MIB: FORTINET-FORTIGATE-MIB
130
- id: forticare_account_contracts
131
- table:
132
- OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2
133
- name: fgLicAlContractTable
134
- identity:
135
- id:
136
- OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.1
137
- name: fgLicAlContractDesc
138
- name:
139
- OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.1
140
- name: fgLicAlContractDesc
141
- component:
142
- value: account
143
- descriptors:
144
- type:
145
- value: account_contract
146
- signals:
147
- expiry:
148
- timestamp:
149
- OID: 1.3.6.1.4.1.12356.101.4.6.3.3.2.1.2
150
- name: fgLicAlContractExpiry
151
- format: text_date
152
-
73
metrics:
74
75
### VPN
src/go/plugin/go.d/config/go.d/snmp.profiles/default/huawei-routers.yaml
+1
-178
@@ -1,5 +1,6 @@
1
extends:
2
- huawei.yaml
3
+ - _huawei-bgp-vpn.yaml
4
5
selector:
6
- sysobjectid:
@@ -11,181 +12,3 @@ metadata:
12
fields:
13
type:
14
value: "Router"
14
-
15
-# https://mibbrowser.online/mibdb_search.php?mib=HUAWEI-BGP-VPN-MIB
16
-# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/huawei/HUAWEI-BGP-VPN-MIB
17
-
18
-metrics:
19
- - MIB: HUAWEI-BGP-VPN-MIB
20
- table:
21
- name: hwBgpPeerTable
22
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2
23
- symbols:
24
- - name: huawei.hwBgpPeerFsmEstablishedCounter
25
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.6
26
- chart_meta:
27
- description: The counter that records the times the remote BGP peer is correctly connected
28
- family: 'Network/Routing/BGP/Peer/Connection/Established/Count'
29
- unit: "{connection}/s"
30
- - OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.5
31
- name: huawei.hwBgpPeerState
32
- chart_meta:
33
- description: Current state of the BGP peer connection
34
- family: 'Network/Routing/BGP/Peer/Connection/State'
35
- unit: "{status}"
36
- mapping:
37
- 1: idle
38
- 2: connect
39
- 3: active
40
- 4: opensent
41
- 5: openconfirm
42
- 6: established
43
- - OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.10
44
- name: huawei.hwBgpPeerUnAvaiReason
45
- chart_meta:
46
- description: Reason why the BGP peer is unavailable
47
- family: 'Network/Routing/BGP/Peer/Unavailable/Reason'
48
- unit: "{status}"
49
- mapping:
50
- 1: configuration_lead_peer_down
51
- 2: receive_notification
52
- 3: receive_error_packet
53
- 4: hold_timer_expire
54
- 5: remote_peer_not_reachable
55
- 6: direct_connect_interface_down
56
- 7: route_limit
57
- metric_tags:
58
- - tag: huawei_hw_bgp_peer_vrf_name
59
- symbol:
60
- name: huawei.hwBgpPeerVrfName
61
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6
62
- - tag: huawei_hw_bgp_peer_remote_addr
63
- symbol:
64
- name: huawei.hwBgpPeerRemoteAddr
65
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4
66
- format: ip_address
67
-
68
- - MIB: HUAWEI-BGP-VPN-MIB
69
- table:
70
- name: hwBgpPeerRouteTable
71
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3
72
- symbols:
73
- - name: huawei.hwBgpPeerPrefixRcvCounter
74
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.1
75
- metric_type: monotonic_count
76
- chart_meta:
77
- description: The number of prefixes received from the remote BGP peer
78
- family: 'Network/Routing/BGP/Peer/Prefix/Received/Total'
79
- unit: "{prefix}"
80
- - name: huawei.hwBgpPeerPrefixActiveCounter
81
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.2
82
- metric_type: monotonic_count
83
- chart_meta:
84
- description: The number of active prefixes received from the remote BGP peer
85
- family: 'Network/Routing/BGP/Peer/Prefix/Active/Total'
86
- unit: "{prefix}"
87
- - name: huawei.hwBgpPeerPrefixAdvCounter
88
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.3.1.3
89
- metric_type: monotonic_count
90
- chart_meta:
91
- description: The number of prefixes sent to the remote BGP peer
92
- family: 'Network/Routing/BGP/Peer/Prefix/Advertised/Total'
93
- unit: "{prefix}"
94
- metric_tags:
95
- - tag: huawei_hw_bgp_peer_vrf_name
96
- symbol:
97
- name: huawei.hwBgpPeerVrfName
98
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6
99
- - tag: huawei_hw_bgp_peer_remote_addr
100
- symbol:
101
- name: huawei.hwBgpPeerRemoteAddr
102
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4
103
- format: ip_address
104
-
105
- - MIB: HUAWEI-BGP-VPN-MIB
106
- table:
107
- name: hwBgpPeerMessageTable
108
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4
109
- symbols:
110
- - name: huawei.hwBgpPeerInTotalMsgCounter
111
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.1
112
- chart_meta:
113
- description: The total number of protocol packets received from the remote BGP peer
114
- family: 'Network/Routing/BGP/Peer/Message/Total/In'
115
- unit: "{packet}/s"
116
- - name: huawei.hwBgpPeerOutTotalMsgCounter
117
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.2
118
- chart_meta:
119
- description: The total number of protocol packets sent to the remote BGP peer
120
- family: 'Network/Routing/BGP/Peer/Message/Total/Out'
121
- unit: "{packet}/s"
122
- - name: huawei.hwBgpPeerInOpenMsgCounter
123
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.3
124
- chart_meta:
125
- description: The total number of Open packets received from the remote BGP peer
126
- family: 'Network/Routing/BGP/Peer/Message/Open/In'
127
- unit: "{packet}/s"
128
- - name: huawei.hwBgpPeerInUpdateMsgCounter
129
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.4
130
- chart_meta:
131
- description: The total number of Update packets received from the remote BGP peer
132
- family: 'Network/Routing/BGP/Peer/Message/Update/In'
133
- unit: "{packet}/s"
134
- - name: huawei.hwBgpPeerInNotificationMsgCounter
135
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.5
136
- chart_meta:
137
- description: The total number of Notification packets received from the remote BGP peer
138
- family: 'Network/Routing/BGP/Peer/Message/Notification/In'
139
- unit: "{packet}/s"
140
- - name: huawei.hwBgpPeerInKeepAliveMsgCounter
141
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.6
142
- chart_meta:
143
- description: The total number of Keepalive packets received from the remote BGP peer
144
- family: 'Network/Routing/BGP/Peer/Message/KeepAlive/In'
145
- unit: "{packet}/s"
146
- - name: huawei.hwBgpPeerInRouteFreshMsgCounter
147
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.7
148
- chart_meta:
149
- description: The total number of Route-Refresh packets received from the remote BGP peer
150
- family: 'Network/Routing/BGP/Peer/Message/RouteRefresh/In'
151
- unit: "{packet}/s"
152
- - name: huawei.hwBgpPeerOutOpenMsgCounter
153
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.8
154
- chart_meta:
155
- description: The total number of Open packets sent to the remote BGP peer
156
- family: 'Network/Routing/BGP/Peer/Message/Open/Out'
157
- unit: "{packet}/s"
158
- - name: huawei.hwBgpPeerOutUpdateMsgCounter
159
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.9
160
- chart_meta:
161
- description: The total number of Update packets sent to the remote BGP peer
162
- family: 'Network/Routing/BGP/Peer/Message/Update/Out'
163
- unit: "{packet}/s"
164
- - name: huawei.hwBgpPeerOutNotificationMsgCounter
165
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.10
166
- chart_meta:
167
- description: The total number of Notification packets sent to the remote BGP peer
168
- family: 'Network/Routing/BGP/Peer/Message/Notification/Out'
169
- unit: "{packet}/s"
170
- - name: huawei.hwBgpPeerOutKeepAliveMsgCounter
171
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.11
172
- chart_meta:
173
- description: The total number of Keepalive packets sent to the remote BGP peer
174
- family: 'Network/Routing/BGP/Peer/Message/KeepAlive/Out'
175
- unit: "{packet}/s"
176
- - name: huawei.hwBgpPeerOutRouteFreshMsgCounter
177
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.4.1.12
178
- chart_meta:
179
- description: The total number of Route-Refresh packets sent to the remote BGP peer
180
- family: 'Network/Routing/BGP/Peer/Message/RouteRefresh/Out'
181
- unit: "{packet}/s"
182
- metric_tags:
183
- - tag: huawei_hw_bgp_peer_vrf_name
184
- symbol:
185
- name: huawei.hwBgpPeerVrfName
186
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.1.1.6
187
- - tag: huawei_hw_bgp_peer_remote_addr
188
- symbol:
189
- name: huawei.hwBgpPeerRemoteAddr
190
- OID: 1.3.6.1.4.1.2011.5.25.177.1.1.2.1.4
191
- format: ip_address
src/go/plugin/go.d/config/go.d/snmp.profiles/default/juniper-ex.yaml
+1
-1
@@ -8,7 +8,7 @@ extends:
8
- _std-ip-mib.yaml
9
- _std-tcp-mib.yaml
10
- _std-udp-mib.yaml
11
- - _std-bgp4-mib.yaml
11
+ - _juniper-bgp4-v2.yaml
12
- _std-ospf-mib.yaml
13
- _std-host-resources-mib.yaml
14
- _juniper-virtualchassis.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/juniper-mx.yaml
+1
-1
@@ -8,7 +8,7 @@ extends:
8
- _std-ip-mib.yaml
9
- _std-tcp-mib.yaml
10
- _std-udp-mib.yaml
11
- - _std-bgp4-mib.yaml
11
+ - _juniper-bgp4-v2.yaml
12
- _std-ospf-mib.yaml
13
- _std-host-resources-mib.yaml
14
- _juniper-virtualchassis.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/juniper-qfx.yaml
+1
-1
@@ -1,5 +1,5 @@
1
extends:
2
- - _std-bgp4-mib.yaml
2
+ - _juniper-bgp4-v2.yaml
3
- _std-host-resources-mib.yaml
4
- _std-if-mib.yaml
5
- _std-ospf-mib.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/juniper-srx.yaml
+1
-1
@@ -10,7 +10,7 @@ extends:
10
- _std-tcp-mib.yaml
11
- _std-udp-mib.yaml
12
- _std-ospf-mib.yaml
13
- - _std-bgp4-mib.yaml
13
+ - _juniper-bgp4-v2.yaml
14
- _std-host-resources-mib.yaml
15
- _juniper-userfirewall.yaml
16
- _juniper-scu.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/juniper.yaml
+1
-1
@@ -5,7 +5,7 @@ extends:
5
- _std-ip-mib.yaml
6
- _std-tcp-mib.yaml
7
- _std-udp-mib.yaml
8
- - _std-bgp4-mib.yaml
8
+ - _juniper-bgp4-v2.yaml
9
- _std-ospf-mib.yaml
10
- _std-host-resources-mib.yaml
11
- _juniper-junos-generic.yaml
src/go/plugin/go.d/config/go.d/snmp.profiles/default/mikrotik-router.yaml
+1
-23
@@ -6,6 +6,7 @@ extends:
6
- _std-topology-q-bridge-mib.yaml
7
- _std-topology-stp-mib.yaml
8
- _mikrotik-ipsec.yaml
9
+ - _mikrotik-routeros-licensing.yaml
10
11
selector:
12
- sysobjectid:
@@ -84,29 +85,6 @@ metric_tags:
85
symbol: mtxrLicVersion
86
tag: license_version
87
87
-licensing:
88
- - id: routeros_upgrade
89
- MIB: MIKROTIK-MIB
90
- identity:
91
- id:
92
- value: routeros_upgrade
93
- name:
94
- value: RouterOS upgrade entitlement
95
- component:
96
- value: routeros
97
- descriptors:
98
- type:
99
- value: upgrade_entitlement
100
- impact:
101
- value: RouterOS software upgrade entitlement for this device
102
- signals:
103
- expiry:
104
- timestamp:
105
- OID: 1.3.6.1.4.1.14988.1.1.4.2.0
106
- name: mtxrLicUpgrUntil
107
- format: snmp_dateandtime
108
- sentinel: [timer_pre_1971]
109
-
88
metrics:
89
- MIB: MIKROTIK-MIB
90
symbol:
src/go/plugin/go.d/config/go.d/snmp.profiles/default/nokia-service-router-os.yaml
new
+17
@@ -0,0 +1,17 @@
1
+extends:
2
+ - _system-base.yaml
3
+ - _std-if-mib.yaml
4
+ - _nokia-timetra-bgp.yaml
5
+
6
+selector:
7
+ - sysobjectid:
8
+ include:
9
+ - 1.3.6.1.4.1.6527.*
10
+
11
+metadata:
12
+ device:
13
+ fields:
14
+ vendor:
15
+ value: "Nokia"
16
+ type:
17
+ value: "Router"
src/go/plugin/go.d/config/go.d/snmp.profiles/default/nvidia-cumulus-linux-switch.yaml
+1
@@ -1,5 +1,6 @@
1
extends:
2
- _std-host-resources-mib-feat-base.yaml
3
+ - _std-bgp4-mib.yaml
4
- _std-ucd-mib.yaml
5
- nvidia.yaml
6
src/go/plugin/go.d/config/go.d/snmp.profiles/default/sophos-xgs-firewall.yaml
+1
-294
@@ -2,6 +2,7 @@ extends:
2
- _system-base.yaml
3
- _std-host-resources-mib-feat-base.yaml
4
- _std-if-mib.yaml
5
+ - _sophos-xgs-firewall-licensing.yaml
6
7
selector:
8
- sysobjectid:
@@ -27,300 +28,6 @@ metric_tags:
28
OID: 1.3.6.1.4.1.2604.5.1.1.6.0
29
symbol: sfosIPSVersion
30
30
-# SFOS-FIREWALL-MIB
31
-# https://mibbrowser.online/mibdb_search.php?mib=SFOS-FIREWALL-MIB
32
-# https://github.com/DanielleHuisman/observium-community-edition/blob/main/mibs/sophos/SFOS-FIREWALL-MIB
33
-
34
-licensing:
35
- - id: base_firewall
36
- MIB: SFOS-FIREWALL-MIB
37
- identity:
38
- id:
39
- value: base_firewall
40
- name:
41
- value: Base Firewall
42
- component:
43
- value: base_firewall
44
- descriptors:
45
- type:
46
- value: subscription
47
- impact:
48
- value: Sophos Base Firewall subscription state and expiry on the device
49
- state:
50
- OID: 1.3.6.1.4.1.2604.5.1.5.1.1.0
51
- name: sfosBaseFWLicRegStatus
52
- policy: sophos
53
- mapping:
54
- "0": "0"
55
- "1": "1"
56
- "2": "0"
57
- "3": "0"
58
- "4": "2"
59
- "5": "2"
60
- signals:
61
- expiry:
62
- timestamp:
63
- from: 1.3.6.1.4.1.2604.5.1.5.1.2.0
64
- name: sfosBaseFWLicExpiryDate
65
- format: text_date
66
- sentinel: [timer_zero_or_negative]
67
- - id: network_protection
68
- MIB: SFOS-FIREWALL-MIB
69
- identity:
70
- id:
71
- value: network_protection
72
- name:
73
- value: Network Protection
74
- component:
75
- value: network_protection
76
- descriptors:
77
- type:
78
- value: subscription
79
- impact:
80
- value: Sophos Network Protection subscription state and expiry on the device
81
- state:
82
- OID: 1.3.6.1.4.1.2604.5.1.5.2.1.0
83
- name: sfosNetProtectionLicRegStatus
84
- policy: sophos
85
- mapping:
86
- "0": "0"
87
- "1": "1"
88
- "2": "0"
89
- "3": "0"
90
- "4": "2"
91
- "5": "2"
92
- signals:
93
- expiry:
94
- timestamp:
95
- from: 1.3.6.1.4.1.2604.5.1.5.2.2.0
96
- name: sfosNetProtectionLicExpiryDate
97
- format: text_date
98
- sentinel: [timer_zero_or_negative]
99
- - id: web_protection
100
- MIB: SFOS-FIREWALL-MIB
101
- identity:
102
- id:
103
- value: web_protection
104
- name:
105
- value: Web Protection
106
- component:
107
- value: web_protection
108
- descriptors:
109
- type:
110
- value: subscription
111
- impact:
112
- value: Sophos Web Protection subscription state and expiry on the device
113
- state:
114
- OID: 1.3.6.1.4.1.2604.5.1.5.3.1.0
115
- name: sfosWebProtectionLicRegStatus
116
- policy: sophos
117
- mapping:
118
- "0": "0"
119
- "1": "1"
120
- "2": "0"
121
- "3": "0"
122
- "4": "2"
123
- "5": "2"
124
- signals:
125
- expiry:
126
- timestamp:
127
- from: 1.3.6.1.4.1.2604.5.1.5.3.2.0
128
- name: sfosWebProtectionLicExpiryDate
129
- format: text_date
130
- sentinel: [timer_zero_or_negative]
131
- - id: mail_protection
132
- MIB: SFOS-FIREWALL-MIB
133
- identity:
134
- id:
135
- value: mail_protection
136
- name:
137
- value: Mail Protection
138
- component:
139
- value: mail_protection
140
- descriptors:
141
- type:
142
- value: subscription
143
- impact:
144
- value: Sophos Mail Protection subscription state and expiry on the device
145
- state:
146
- OID: 1.3.6.1.4.1.2604.5.1.5.4.1.0
147
- name: sfosMailProtectionLicRegStatus
148
- policy: sophos
149
- mapping:
150
- "0": "0"
151
- "1": "1"
152
- "2": "0"
153
- "3": "0"
154
- "4": "2"
155
- "5": "2"
156
- signals:
157
- expiry:
158
- timestamp:
159
- from: 1.3.6.1.4.1.2604.5.1.5.4.2.0
160
- name: sfosMailProtectionLicExpiryDate
161
- format: text_date
162
- sentinel: [timer_zero_or_negative]
163
- - id: web_server_protection
164
- MIB: SFOS-FIREWALL-MIB
165
- identity:
166
- id:
167
- value: web_server_protection
168
- name:
169
- value: Web Server Protection
170
- component:
171
- value: web_server_protection
172
- descriptors:
173
- type:
174
- value: subscription
175
- impact:
176
- value: Sophos Web Server Protection subscription state and expiry on the device
177
- state:
178
- OID: 1.3.6.1.4.1.2604.5.1.5.5.1.0
179
- name: sfosWebServerProtectionLicRegStatus
180
- policy: sophos
181
- mapping:
182
- "0": "0"
183
- "1": "1"
184
- "2": "0"
185
- "3": "0"
186
- "4": "2"
187
- "5": "2"
188
- signals:
189
- expiry:
190
- timestamp:
191
- from: 1.3.6.1.4.1.2604.5.1.5.5.2.0
192
- name: sfosWebServerProtectionLicExpiryDate
193
- format: text_date
194
- sentinel: [timer_zero_or_negative]
195
- - id: sandstorm
196
- MIB: SFOS-FIREWALL-MIB
197
- identity:
198
- id:
199
- value: sandstorm
200
- name:
201
- value: Sandstorm
202
- component:
203
- value: sandstorm
204
- descriptors:
205
- type:
206
- value: subscription
207
- impact:
208
- value: Sophos Sandstorm subscription state and expiry on the device
209
- state:
210
- OID: 1.3.6.1.4.1.2604.5.1.5.6.1.0
211
- name: sfosSandstromLicRegStatus
212
- policy: sophos
213
- mapping:
214
- "0": "0"
215
- "1": "1"
216
- "2": "0"
217
- "3": "0"
218
- "4": "2"
219
- "5": "2"
220
- signals:
221
- expiry:
222
- timestamp:
223
- from: 1.3.6.1.4.1.2604.5.1.5.6.2.0
224
- name: sfosSandstromLicExpiryDate
225
- format: text_date
226
- sentinel: [timer_zero_or_negative]
227
- - id: enhanced_support
228
- MIB: SFOS-FIREWALL-MIB
229
- identity:
230
- id:
231
- value: enhanced_support
232
- name:
233
- value: Enhanced Support
234
- component:
235
- value: enhanced_support
236
- descriptors:
237
- type:
238
- value: support
239
- impact:
240
- value: Sophos Enhanced Support state and expiry on the device
241
- state:
242
- OID: 1.3.6.1.4.1.2604.5.1.5.7.1.0
243
- name: sfosEnhancedSupportLicRegStatus
244
- policy: sophos
245
- mapping:
246
- "0": "0"
247
- "1": "1"
248
- "2": "0"
249
- "3": "0"
250
- "4": "2"
251
- "5": "2"
252
- signals:
253
- expiry:
254
- timestamp:
255
- from: 1.3.6.1.4.1.2604.5.1.5.7.2.0
256
- name: sfosEnhancedSupportLicExpiryDate
257
- format: text_date
258
- sentinel: [timer_zero_or_negative]
259
- - id: enhanced_plus_support
260
- MIB: SFOS-FIREWALL-MIB
261
- identity:
262
- id:
263
- value: enhanced_plus_support
264
- name:
265
- value: Enhanced Plus Support
266
- component:
267
- value: enhanced_plus_support
268
- descriptors:
269
- type:
270
- value: support
271
- impact:
272
- value: Sophos Enhanced Plus Support state and expiry on the device
273
- state:
274
- OID: 1.3.6.1.4.1.2604.5.1.5.8.1.0
275
- name: sfosEnhancedPlusLicRegStatus
276
- policy: sophos
277
- mapping:
278
- "0": "0"
279
- "1": "1"
280
- "2": "0"
281
- "3": "0"
282
- "4": "2"
283
- "5": "2"
284
- signals:
285
- expiry:
286
- timestamp:
287
- from: 1.3.6.1.4.1.2604.5.1.5.8.2.0
288
- name: sfosEnhancedPlusLicExpiryDate
289
- format: text_date
290
- sentinel: [timer_zero_or_negative]
291
- - id: central_orchestration
292
- MIB: SFOS-FIREWALL-MIB
293
- identity:
294
- id:
295
- value: central_orchestration
296
- name:
297
- value: Central Orchestration
298
- component:
299
- value: central_orchestration
300
- descriptors:
301
- type:
302
- value: subscription
303
- impact:
304
- value: Sophos Central Orchestration state and expiry on the device
305
- state:
306
- OID: 1.3.6.1.4.1.2604.5.1.5.9.1.0
307
- name: sfosCentralOrchestrationLicRegStatus
308
- policy: sophos
309
- mapping:
310
- "0": "0"
311
- "1": "1"
312
- "2": "0"
313
- "3": "0"
314
- "4": "2"
315
- "5": "2"
316
- signals:
317
- expiry:
318
- timestamp:
319
- from: 1.3.6.1.4.1.2604.5.1.5.9.2.0
320
- name: sfosCentralOrchestrationLicExpiryDate
321
- format: text_date
322
- sentinel: [timer_zero_or_negative]
323
-
31
metrics:
32
- MIB: SFOS-FIREWALL-MIB
33
symbol:
src/health/health.d/snmp_bgp.conf
new
+132
@@ -0,0 +1,132 @@
1
+# you can disable an alarm notification by setting the 'to' line to: silent
2
+
3
+# --- Availability: Peer Down (SNMP) ---
4
+
5
+ template: snmp_bgp_peer_down
6
+ on: snmp.bgp.peers.availability
7
+chart labels: neighbor=*
8
+ class: Availability
9
+ type: Network
10
+component: BGP
11
+ lookup: average -2m unaligned of admin_enabled,established
12
+ calc: ($admin_enabled == 1 AND $established == 0) ? (1) : (0)
13
+ units: status
14
+ every: 30s
15
+ crit: $this == 1
16
+ delay: down 2m multiplier 1.5 max 30m
17
+ summary: SNMP BGP peer ${label:neighbor} down
18
+ info: SNMP BGP peer ${label:neighbor} (AS${label:remote_as}) is administratively enabled but has remained out of Established for the full 2-minute evaluation window
19
+ to: sysadmin
20
+
21
+# --- Availability: Peer-Family Down (SNMP) ---
22
+
23
+ template: snmp_bgp_peer_family_down
24
+ on: snmp.bgp.peer_families.availability
25
+chart labels: neighbor=* address_family=* subsequent_address_family=*
26
+ class: Availability
27
+ type: Network
28
+component: BGP
29
+ lookup: average -2m unaligned of admin_enabled,established
30
+ calc: ($admin_enabled == 1 AND $established == 0) ? (1) : (0)
31
+ units: status
32
+ every: 30s
33
+ crit: $this == 1
34
+ delay: down 2m multiplier 1.5 max 30m
35
+ summary: SNMP BGP peer-family ${label:neighbor} ${label:address_family}/${label:subsequent_address_family} down
36
+ info: SNMP BGP peer ${label:neighbor} (AS${label:remote_as}) for ${label:address_family}/${label:subsequent_address_family} has remained out of Established for the full 2-minute evaluation window
37
+ to: sysadmin
38
+
39
+# --- Errors: Established Transition Anomaly (SNMP) ---
40
+
41
+ template: snmp_bgp_peer_transitions_anomaly
42
+ on: snmp.bgp.peers.established_transitions
43
+chart labels: neighbor=*
44
+ class: Errors
45
+ type: Network
46
+component: BGP
47
+ lookup: average -10m anomaly-bit of transitions
48
+ calc: $this
49
+ units: %
50
+ every: 30s
51
+ warn: $this > (($status >= $WARNING) ? (5) : (20))
52
+ crit: $this > (($status == $CRITICAL) ? (20) : (40))
53
+ delay: down 10m multiplier 1.5 max 1h
54
+ summary: SNMP BGP transition anomaly on peer ${label:neighbor}
55
+ info: Rolling ML anomaly rate for established-transition counts on SNMP BGP peer ${label:neighbor} (AS${label:remote_as}); sustained anomalies usually indicate flaps or unstable reachability
56
+ to: sysadmin
57
+
58
+# --- Errors: Peer-Family Established Transition Anomaly (SNMP) ---
59
+
60
+ template: snmp_bgp_peer_family_transitions_anomaly
61
+ on: snmp.bgp.peer_families.established_transitions
62
+chart labels: neighbor=* address_family=* subsequent_address_family=*
63
+ class: Errors
64
+ type: Network
65
+component: BGP
66
+ lookup: average -10m anomaly-bit of transitions
67
+ calc: $this
68
+ units: %
69
+ every: 30s
70
+ warn: $this > (($status >= $WARNING) ? (5) : (20))
71
+ crit: $this > (($status == $CRITICAL) ? (20) : (40))
72
+ delay: down 10m multiplier 1.5 max 1h
73
+ summary: SNMP BGP transition anomaly on peer-family ${label:neighbor} ${label:address_family}/${label:subsequent_address_family}
74
+ info: Rolling ML anomaly rate for established-transition counts on SNMP BGP peer ${label:neighbor} (AS${label:remote_as}) for ${label:address_family}/${label:subsequent_address_family}; sustained anomalies usually indicate flaps or unstable reachability
75
+ to: sysadmin
76
+
77
+# --- Errors: Update Churn Anomaly (SNMP) ---
78
+
79
+ template: snmp_bgp_peer_updates_anomaly
80
+ on: snmp.bgp.peers.update_traffic
81
+chart labels: neighbor=*
82
+ class: Errors
83
+ type: Network
84
+component: BGP
85
+ lookup: average -10m anomaly-bit of received,sent
86
+ calc: $this
87
+ units: %
88
+ every: 30s
89
+ warn: $this > (($status >= $WARNING) ? (5) : (20))
90
+ crit: $this > (($status == $CRITICAL) ? (20) : (40))
91
+ delay: down 10m multiplier 1.5 max 1h
92
+ summary: SNMP BGP update anomaly on peer ${label:neighbor}
93
+ info: Rolling ML anomaly rate for BGP UPDATE traffic on SNMP BGP peer ${label:neighbor} (AS${label:remote_as}); sustained anomalies usually indicate route churn or unstable adjacencies
94
+ to: sysadmin
95
+
96
+# --- Errors: Peer-Family Update Churn Anomaly (SNMP) ---
97
+
98
+ template: snmp_bgp_peer_family_updates_anomaly
99
+ on: snmp.bgp.peer_families.update_traffic
100
+chart labels: neighbor=* address_family=* subsequent_address_family=*
101
+ class: Errors
102
+ type: Network
103
+component: BGP
104
+ lookup: average -10m anomaly-bit of received,sent
105
+ calc: $this
106
+ units: %
107
+ every: 30s
108
+ warn: $this > (($status >= $WARNING) ? (5) : (20))
109
+ crit: $this > (($status == $CRITICAL) ? (20) : (40))
110
+ delay: down 10m multiplier 1.5 max 1h
111
+ summary: SNMP BGP update anomaly on peer-family ${label:neighbor} ${label:address_family}/${label:subsequent_address_family}
112
+ info: Rolling ML anomaly rate for BGP UPDATE traffic on SNMP BGP peer ${label:neighbor} (AS${label:remote_as}) for ${label:address_family}/${label:subsequent_address_family}; sustained anomalies usually indicate route churn or unstable adjacencies
113
+ to: sysadmin
114
+
115
+# --- Errors: Accepted Prefix Drift (SNMP) ---
116
+
117
+ template: snmp_bgp_peer_family_prefixes_accepted_anomaly
118
+ on: snmp.bgp.peer_families.route_counts.current
119
+chart labels: neighbor=* address_family=* subsequent_address_family=*
120
+ class: Errors
121
+ type: Network
122
+component: BGP
123
+ lookup: average -15m anomaly-bit of accepted
124
+ calc: $this
125
+ units: %
126
+ every: 30s
127
+ warn: $this > (($status >= $WARNING) ? (5) : (15))
128
+ crit: $this > (($status == $CRITICAL) ? (15) : (30))
129
+ delay: down 10m multiplier 1.5 max 1h
130
+ summary: SNMP BGP accepted-prefix drift on peer-family ${label:neighbor} ${label:address_family}/${label:subsequent_address_family}
131
+ info: Rolling ML anomaly rate for accepted prefix counts on SNMP BGP peer ${label:neighbor} (AS${label:remote_as}) for ${label:address_family}/${label:subsequent_address_family}; sustained anomalies usually mean unexpected route loss or route growth after policy
132
+ to: sysadmin