SNMP, Custom descriptions and units (#20100)
* work so far, units and description * tgo mod tidy * use custom descriptions and units, customized profiledefinition module added * go mod tidy * replace log.fatal with proper chain error * remove snake_case and unnecessary prints * format file * expect custom ID on profiledefinition tests * move files, add license * update go mod * fix tests --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>
Fotis Voutsas committed
Apr 14, 2025 at 18:49 UTC
126e58eeb832d6bb163bbefecc21f266dc631904
59 files changed
+4117
-88
REDISTRIBUTED.md
+5
-4
@@ -37,7 +37,8 @@ We have decided to redistribute all these, instead of using them through a CDN,
37
38
**go.d.plugin**:
39
40
-| Name | Copyright | License |
41
-|--------------------------------------------------------------------------------------------------------------------------|-----------------------------------|-------------------------------------------------------------------------------------------------------------|
42
-| [lmsensors](https://github.com/mdlayher/lmsensors) | Copyright 2016, Matt Layher | [MIT](https://github.com/mdlayher/lmsensors/blob/master/LICENSE.md) |
43
-| [snmp profiles](https://github.com/DataDog/integrations-core/tree/master/snmp/datadog_checks/snmp/data/default_profiles) | Copyright (c) 2016, Datadog, Inc. | [BSD 3-Clause "New" or "Revised" License](https://github.com/DataDog/integrations-core/blob/master/LICENSE) |
40
+| Name | Copyright | License |
41
+|------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|-------------------------------------------------------------------------------------------------------------|
42
+| [lmsensors](https://github.com/mdlayher/lmsensors) | Copyright 2016, Matt Layher | [MIT](https://github.com/mdlayher/lmsensors/blob/master/LICENSE.md) |
43
+| [snmp profiles](https://github.com/DataDog/integrations-core/tree/master/snmp/datadog_checks/snmp/data/default_profiles) | Copyright (c) 2016, Datadog, Inc. | [BSD 3-Clause "New" or "Revised" License](https://github.com/DataDog/integrations-core/blob/master/LICENSE) |
44
+| [snmp networkdevice/profile/profiledefinition](https://github.com/DataDog/datadog-agent/tree/main/pkg/networkdevice/profile/profiledefinition) | Copyright 2016-present Datadog, Inc. | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
src/go/go.mod
+4
-1
@@ -29,6 +29,7 @@ require (
29
github.com/google/uuid v1.6.0
30
github.com/gorcon/rcon v1.4.0
31
github.com/gosnmp/gosnmp v1.39.0
32
+ github.com/invopop/jsonschema v0.12.0
33
github.com/jackc/pgx/v4 v4.18.3
34
github.com/jackc/pgx/v5 v5.7.4
35
github.com/jessevdk/go-flags v1.6.1
@@ -44,8 +45,10 @@ require (
45
github.com/prometheus/common v0.63.0
46
github.com/prometheus/prometheus v2.55.1+incompatible
47
github.com/redis/go-redis/v9 v9.7.3
48
+ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
49
github.com/sijms/go-ora/v2 v2.8.24
50
github.com/sourcegraph/conc v0.3.0
51
+ github.com/spf13/cobra v1.9.1
52
github.com/stretchr/testify v1.10.0
53
github.com/tidwall/gjson v1.18.0
54
github.com/valyala/fastjson v1.6.4
@@ -101,7 +104,7 @@ require (
104
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
105
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
106
github.com/huandu/xstrings v1.5.0 // indirect
104
- github.com/invopop/jsonschema v0.12.0 // indirect
107
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
108
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
109
github.com/jackc/pgconn v1.14.3 // indirect
110
github.com/jackc/pgio v1.0.0 // indirect
src/go/go.sum
+8
@@ -83,6 +83,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
83
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
84
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
85
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
86
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
87
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
88
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
89
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -198,6 +199,8 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
199
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
200
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
201
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
202
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
203
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
204
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
205
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
206
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@@ -404,6 +407,9 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
407
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
408
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
409
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
410
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
411
+github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
412
+github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
413
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
414
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
415
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
@@ -421,6 +427,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
427
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
428
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
429
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
430
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
431
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
432
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
433
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
434
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
src/go/plugin/go.d/collector/snmp/charts.go
+10
-2
@@ -199,8 +199,16 @@ func (c *Collector) addSNMPChart(processedMetric processedMetric) {
199
chart := snmpChartTemplate.Copy()
200
201
chart.ID = fmt.Sprintf(chart.ID, processedMetric.name)
202
- chart.Title = fmt.Sprintf(chart.Title, processedMetric.name)
203
- chart.Units = fmt.Sprintf(chart.Units, "TBD unit")
202
+ if processedMetric.description == "" {
203
+ chart.Title = fmt.Sprintf(chart.Title, "TBD Description")
204
+ } else {
205
+ chart.Title = fmt.Sprintf(chart.Title, processedMetric.description)
206
+ }
207
+ if processedMetric.unit == "" {
208
+ chart.Units = fmt.Sprintf(chart.Units, "TBD unit")
209
+ } else {
210
+ chart.Units = fmt.Sprintf(chart.Units, processedMetric.unit)
211
+ }
212
chart.Ctx = fmt.Sprintf(chart.Ctx, processedMetric.name)
213
chart.Fam = fmt.Sprintf(chart.Fam, processedMetric.name)
214
src/go/plugin/go.d/collector/snmp/collect.go
+3
-7
@@ -5,7 +5,6 @@ package snmp
5
import (
6
"errors"
7
"fmt"
8
- "log"
8
"slices"
9
"strings"
10
@@ -18,6 +17,9 @@ import (
17
)
18
19
func (c *Collector) collect() (map[string]int64, error) {
20
+
21
+ mx := make(map[string]int64)
22
+
23
if c.enableProfiles {
24
sysObjectID, err := c.getSysObjectID(snmpsd.OidSysObject)
25
if err != nil {
@@ -31,7 +33,6 @@ func (c *Collector) collect() (map[string]int64, error) {
33
return nil, err
34
}
35
seen := make(map[string]bool)
34
- mx := make(map[string]int64)
36
c.makeChartsFromMetricMap(mx, metricMap, seen)
37
}
38
@@ -49,8 +50,6 @@ func (c *Collector) collect() (map[string]int64, error) {
50
}
51
}
52
52
- mx := make(map[string]int64)
53
-
53
if err := c.collectSysUptime(mx); err != nil {
54
return nil, err
55
}
@@ -83,9 +82,6 @@ func (c *Collector) makeChartsFromMetricMap(mx map[string]int64, metricMap map[s
82
if metric.tableName == "" {
83
switch s := metric.value.(type) {
84
case int:
86
-
87
- log.Println(metric)
88
-
85
name := metric.name
86
if name == "" {
87
continue
src/go/plugin/go.d/collector/snmp/collect_sys_info.go
deleted
-3
@@ -1,3 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package snmp
src/go/plugin/go.d/collector/snmp/command_func.go
+3
-4
@@ -22,7 +22,6 @@ func (c *Collector) walkOIDTree(baseOID string) (map[string]processedMetric, err
22
pdu := result.Variables[0]
23
24
nextOID := strings.Replace(pdu.Name, ".", "", 1) //remove dot at the start of the OID
25
- // fmt.Println(nextOID, baseOID)
25
26
// If the next OID does not start with the base OID, we've reached the end of the subtree.
27
if !strings.HasPrefix(nextOID, baseOID) {
@@ -33,9 +32,9 @@ func (c *Collector) walkOIDTree(baseOID string) (map[string]processedMetric, err
32
value := fmt.Sprintf("%v", pdu.Value)
33
34
tableRows[nextOID] = processedMetric{
36
- oid: nextOID,
37
- value: value,
38
- metric_type: metricType,
35
+ oid: nextOID,
36
+ value: value,
37
+ metricType: metricType,
38
}
39
40
currentOID = nextOID
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/LICENSE.md
new
+200
@@ -0,0 +1,200 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+END OF TERMS AND CONDITIONS
177
+
178
+APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+Copyright 2016-present Datadog, Inc.
190
+Licensed under the Apache License, Version 2.0 (the "License");
191
+you may not use this file except in compliance with the License.
192
+You may obtain a copy of the License at
193
+
194
+ http://www.apache.org/licenses/LICENSE-2.0
195
+
196
+Unless required by applicable law or agreed to in writing, software
197
+distributed under the License is distributed on an "AS IS" BASIS,
198
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199
+See the License for the specific language governing permissions and
200
+limitations under the License.
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/README.md
new
+9
@@ -0,0 +1,9 @@
1
+
2
+# Generate Json Schema
3
+
4
+```
5
+cd pkg/networkdevice/profile/profiledefinition/schema_cmd
6
+go generate
7
+```
8
+
9
+The command above will generate this jsonschema file `profiledefinition/schema/profile_rc_schema.json`.
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/bundle.go
new
+17
@@ -0,0 +1,17 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+// ProfileBundleProfileItem represent a profile entry with metadata.
9
+type ProfileBundleProfileItem struct {
10
+ Profile ProfileDefinition `json:"profile"`
11
+}
12
+
13
+// ProfileBundle represent a list of profiles meant to be downloaded by user.
14
+type ProfileBundle struct {
15
+ CreatedTimestamp int64 `json:"created_timestamp"` // Millisecond
16
+ Profiles []ProfileBundleProfileItem `json:"profiles"`
17
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/clone.go
new
+38
@@ -0,0 +1,38 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2024-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+// Cloneable is a generic type for objects that can duplicate themselves.
9
+// It is exclusively used in the form [T Cloneable[T]], i.e. a type that
10
+// has a .Clone() that returns a new instance of itself.
11
+type Cloneable[T any] interface {
12
+ Clone() T
13
+}
14
+
15
+// CloneSlice clones all the objects in a slice into a new slice.
16
+func CloneSlice[Slice ~[]T, T Cloneable[T]](s Slice) Slice {
17
+ if s == nil {
18
+ return nil
19
+ }
20
+ result := make(Slice, 0, len(s))
21
+ for _, v := range s {
22
+ result = append(result, v.Clone())
23
+ }
24
+ return result
25
+}
26
+
27
+// CloneMap clones a map[K]T for any cloneable type T.
28
+// The map keys are shallow-copied; values are cloned.
29
+func CloneMap[Map ~map[K]T, K comparable, T Cloneable[T]](m Map) Map {
30
+ if m == nil {
31
+ return nil
32
+ }
33
+ result := make(Map, len(m))
34
+ for k, v := range m {
35
+ result[k] = v.Clone()
36
+ }
37
+ return result
38
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/clone_test.go
new
+102
@@ -0,0 +1,102 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2024-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "slices"
10
+ "testing"
11
+
12
+ "github.com/stretchr/testify/assert"
13
+)
14
+
15
+type cloneMe struct {
16
+ label *string
17
+ ps []int
18
+}
19
+
20
+func item(label string, ps ...int) *cloneMe {
21
+ return &cloneMe{
22
+ label: &label,
23
+ ps: ps,
24
+ }
25
+}
26
+
27
+func (c *cloneMe) Clone() *cloneMe {
28
+ c2 := &cloneMe{
29
+ ps: slices.Clone(c.ps),
30
+ }
31
+ if c.label != nil {
32
+ var tmp = *c.label
33
+ c2.label = &tmp
34
+ }
35
+ return c2
36
+}
37
+
38
+func TestCloneSlice(t *testing.T) {
39
+ items := []*cloneMe{
40
+ item("a", 1, 2, 3, 4),
41
+ item("b", 1, 2),
42
+ }
43
+ itemsCopy := CloneSlice(items)
44
+ *itemsCopy[0].label = "aaa"
45
+ itemsCopy[1] = item("bbb", 10, 20)
46
+ itemsCopy = append(itemsCopy, item("ccc", 100, 200))
47
+ // items is unchanged
48
+ assert.Equal(t, []*cloneMe{
49
+ item("a", 1, 2, 3, 4),
50
+ item("b", 1, 2),
51
+ }, items)
52
+ assert.Equal(t, []*cloneMe{
53
+ item("aaa", 1, 2, 3, 4),
54
+ item("bbb", 10, 20),
55
+ item("ccc", 100, 200),
56
+ }, itemsCopy)
57
+}
58
+
59
+func TestCloneMap(t *testing.T) {
60
+ m := map[string]*cloneMe{
61
+ "Item A": item("a", 1, 2, 3, 4),
62
+ "Item B": item("b", 1, 2),
63
+ }
64
+ mCopy := CloneMap(m)
65
+ mCopy["Item A"].ps[0] = 100
66
+ mCopy["Item B"] = item("bbb", 10, 20)
67
+ mCopy["Item C"] = item("ccc", 100, 200)
68
+ assert.Equal(t, map[string]*cloneMe{
69
+ "Item A": item("a", 1, 2, 3, 4),
70
+ "Item B": item("b", 1, 2),
71
+ }, m)
72
+ assert.Equal(t, map[string]*cloneMe{
73
+ "Item A": item("a", 100, 2, 3, 4),
74
+ "Item B": item("bbb", 10, 20),
75
+ "Item C": item("ccc", 100, 200),
76
+ }, mCopy)
77
+}
78
+
79
+func TestCustomSliceClone(t *testing.T) {
80
+ type customSlice []*cloneMe
81
+
82
+ items := customSlice{
83
+ item("a", 1, 2, 3, 4),
84
+ item("b", 1, 2),
85
+ }
86
+ itemsCopy := CloneSlice(items)
87
+
88
+ assert.IsType(t, customSlice{}, itemsCopy)
89
+ assert.Equal(t, items, itemsCopy)
90
+}
91
+
92
+func TestCustomMapClone(t *testing.T) {
93
+ type customMap map[string]*cloneMe
94
+
95
+ m := customMap{
96
+ "Item A": item("a", 1, 2, 3, 4),
97
+ "Item B": item("b", 1, 2),
98
+ }
99
+ mCopy := CloneMap(m)
100
+ assert.IsType(t, customMap{}, mCopy)
101
+ assert.Equal(t, m, mCopy)
102
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/doc.go
new
+7
@@ -0,0 +1,7 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+// Package profiledefinition contain the structure used to parse SNMP Profiles
7
+package ddprofiledefinition
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/listmap.go
new
+56
@@ -0,0 +1,56 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "encoding/json"
10
+
11
+ "github.com/invopop/jsonschema"
12
+)
13
+
14
+// ListMap is used to marshall a map into a list (map[string]T to []MapItem[T]) and vice versa.
15
+type ListMap[T any] map[string]T
16
+
17
+// MapItem is used for ListMap marshalling/unmarshalling
18
+type MapItem[T any] struct {
19
+ Key string `json:"key"`
20
+ Value T `json:"value"`
21
+}
22
+
23
+// MarshalJSON marshalls map to list
24
+func (lm ListMap[T]) MarshalJSON() ([]byte, error) {
25
+ var items []MapItem[T]
26
+ for key, value := range lm {
27
+ items = append(items, MapItem[T]{Key: key, Value: value})
28
+ }
29
+ return json.Marshal(items)
30
+}
31
+
32
+// UnmarshalJSON unmarshalls list to map
33
+func (lm *ListMap[T]) UnmarshalJSON(data []byte) error {
34
+ var items []MapItem[T]
35
+ if err := json.Unmarshal(data, &items); err != nil {
36
+ return err
37
+ }
38
+ result := make(ListMap[T])
39
+ for _, item := range items {
40
+ result[item.Key] = item.Value
41
+ }
42
+ *lm = result
43
+ return nil
44
+}
45
+
46
+// JSONSchema is needed to customize jsonschema to match []MapItem[T] used in json format
47
+func (lm ListMap[T]) JSONSchema() *jsonschema.Schema {
48
+ reflector := jsonschema.Reflector{
49
+ AllowAdditionalProperties: false,
50
+ DoNotReference: true,
51
+ }
52
+ schema := reflector.Reflect([]MapItem[T]{})
53
+ // don't need version because this is a child of a versioned schema.
54
+ schema.Version = ""
55
+ return schema
56
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/listmap_test.go
new
+103
@@ -0,0 +1,103 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "encoding/json"
10
+ "testing"
11
+
12
+ "github.com/invopop/jsonschema"
13
+ "github.com/stretchr/testify/assert"
14
+ "github.com/stretchr/testify/require"
15
+)
16
+
17
+type ExampleStruct struct {
18
+ Mapping ListMap[string] `json:"mapping"`
19
+}
20
+
21
+var example = ExampleStruct{
22
+ Mapping: map[string]string{
23
+ "1": "aaa",
24
+ "2": "bbb",
25
+ },
26
+}
27
+
28
+var exampleJSON = `{
29
+ "mapping": [
30
+ {"key": "2", "value": "bbb"},
31
+ {"key": "1", "value": "aaa"}
32
+ ]
33
+}`
34
+
35
+func TestListMap_MarshalJSON(t *testing.T) {
36
+ bytes, err := json.Marshal(example)
37
+ require.NoError(t, err)
38
+
39
+ var expectedExample ExampleStruct
40
+ err = json.Unmarshal(bytes, &expectedExample)
41
+ require.NoError(t, err)
42
+
43
+ assert.Equal(t, example.Mapping, expectedExample.Mapping)
44
+}
45
+
46
+func TestListMap_UnmarshalJSON(t *testing.T) {
47
+ var expectedExample ExampleStruct
48
+ err := json.Unmarshal([]byte(exampleJSON), &expectedExample)
49
+ require.NoError(t, err)
50
+
51
+ assert.Equal(t, example.Mapping, expectedExample.Mapping)
52
+}
53
+
54
+func TestListMap_JSONSchema(t *testing.T) {
55
+ reflector := jsonschema.Reflector{
56
+ AllowAdditionalProperties: false,
57
+ }
58
+ schema := reflector.Reflect(&ExampleStruct{})
59
+ schema.Version = "" // no version, to make the test more future-proof
60
+ schemaJSON, err := json.MarshalIndent(schema, "", " ")
61
+ require.NoError(t, err)
62
+
63
+ expectedSchema := `
64
+{
65
+ "$id": "https://github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/example-struct",
66
+ "$ref": "#/$defs/ExampleStruct",
67
+ "$defs": {
68
+ "ExampleStruct": {
69
+ "properties": {
70
+ "mapping": {
71
+ "$ref": "#/$defs/ListMap[string]"
72
+ }
73
+ },
74
+ "additionalProperties": false,
75
+ "type": "object",
76
+ "required": [
77
+ "mapping"
78
+ ]
79
+ },
80
+ "ListMap[string]": {
81
+ "items": {
82
+ "properties": {
83
+ "key": {
84
+ "type": "string"
85
+ },
86
+ "value": {
87
+ "type": "string"
88
+ }
89
+ },
90
+ "additionalProperties": false,
91
+ "type": "object",
92
+ "required": [
93
+ "key",
94
+ "value"
95
+ ]
96
+ },
97
+ "type": "array"
98
+ }
99
+ }
100
+}
101
+`
102
+ assert.JSONEq(t, expectedSchema, string(schemaJSON))
103
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metadata.go
new
+75
@@ -0,0 +1,75 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import "github.com/invopop/jsonschema"
9
+
10
+// MetadataDeviceResource is the device resource name
11
+const MetadataDeviceResource = "device"
12
+
13
+// MetadataConfig holds configs per resource type
14
+type MetadataConfig ListMap[MetadataResourceConfig]
15
+
16
+// JSONSchema defines the JSON schema for MetadataConfig
17
+func (mc MetadataConfig) JSONSchema() *jsonschema.Schema {
18
+ return ListMap[MetadataResourceConfig](mc).JSONSchema()
19
+}
20
+
21
+// MarshalJSON marshals the metadata config
22
+func (mc MetadataConfig) MarshalJSON() ([]byte, error) {
23
+ return ListMap[MetadataResourceConfig](mc).MarshalJSON()
24
+}
25
+
26
+// UnmarshalJSON unmarshals the metadata config
27
+func (mc *MetadataConfig) UnmarshalJSON(data []byte) error {
28
+ return (*ListMap[MetadataResourceConfig])(mc).UnmarshalJSON(data)
29
+}
30
+
31
+// Clone duplicates this MetadataConfig
32
+func (mc MetadataConfig) Clone() MetadataConfig {
33
+ return CloneMap(mc)
34
+}
35
+
36
+// MetadataResourceConfig holds configs for a metadata resource
37
+type MetadataResourceConfig struct {
38
+ Fields ListMap[MetadataField] `yaml:"fields" json:"fields"`
39
+ IDTags MetricTagConfigList `yaml:"id_tags,omitempty" json:"id_tags,omitempty"`
40
+}
41
+
42
+// Clone duplicates this MetadataResourceConfig
43
+func (c MetadataResourceConfig) Clone() MetadataResourceConfig {
44
+ return MetadataResourceConfig{
45
+ Fields: CloneMap(c.Fields),
46
+ IDTags: CloneSlice(c.IDTags),
47
+ }
48
+}
49
+
50
+// MetadataField holds configs for a metadata field
51
+type MetadataField struct {
52
+ Symbol SymbolConfig `yaml:"symbol,omitempty" json:"symbol,omitempty"`
53
+ Symbols []SymbolConfig `yaml:"symbols,omitempty" json:"symbols,omitempty"`
54
+ Value string `yaml:"value,omitempty" json:"value,omitempty"`
55
+}
56
+
57
+// Clone duplicates this MetadataField
58
+func (c MetadataField) Clone() MetadataField {
59
+ return MetadataField{
60
+ Symbol: c.Symbol.Clone(),
61
+ Symbols: CloneSlice(c.Symbols),
62
+ Value: c.Value,
63
+ }
64
+}
65
+
66
+// NewMetadataResourceConfig returns a new metadata resource config
67
+func NewMetadataResourceConfig() MetadataResourceConfig {
68
+ return MetadataResourceConfig{}
69
+}
70
+
71
+// IsMetadataResourceWithScalarOids returns true if the resource is based on scalar OIDs
72
+// at the moment, we only expect "device" resource to be based on scalar OIDs
73
+func IsMetadataResourceWithScalarOids(resource string) bool {
74
+ return resource == MetadataDeviceResource
75
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metadata_test.go
new
+91
@@ -0,0 +1,91 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2024-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "regexp"
10
+ "testing"
11
+
12
+ "github.com/stretchr/testify/assert"
13
+)
14
+
15
+func makeMetadata() MetadataConfig {
16
+ // This is not actually a valid config, since e.g. it has ExtractValue and
17
+ // MatchPattern both set; this is just to check that every field gets copied
18
+ // properly.
19
+ return MetadataConfig{
20
+ "device": MetadataResourceConfig{
21
+ Fields: map[string]MetadataField{
22
+ "name": {
23
+ Value: "hey",
24
+ Symbol: SymbolConfig{
25
+ OID: "1.2.3",
26
+ Name: "someSymbol",
27
+ ExtractValue: ".*",
28
+ ExtractValueCompiled: regexp.MustCompile(".*"),
29
+ MatchPattern: ".*",
30
+ MatchPatternCompiled: regexp.MustCompile(".*"),
31
+ MatchValue: "$1",
32
+ ScaleFactor: 100,
33
+ Format: "mac_address",
34
+ ConstantValueOne: true,
35
+ MetricType: "gauge",
36
+ },
37
+ },
38
+ },
39
+ IDTags: []MetricTagConfig{
40
+ {
41
+ Tag: "foo",
42
+ Index: 1,
43
+ Column: SymbolConfig{
44
+ Name: "bar",
45
+ OID: "1.2.3",
46
+ ExtractValue: ".*",
47
+ ExtractValueCompiled: regexp.MustCompile(".*"),
48
+ },
49
+ OID: "2.3.4",
50
+ Symbol: SymbolConfigCompat{
51
+ OID: "1.2.3",
52
+ Name: "someSymbol",
53
+ ExtractValue: ".*",
54
+ ExtractValueCompiled: regexp.MustCompile(".*"),
55
+ },
56
+ IndexTransform: []MetricIndexTransform{
57
+ {
58
+ Start: 1,
59
+ End: 5,
60
+ },
61
+ },
62
+ Mapping: map[string]string{
63
+ "1": "on",
64
+ "2": "off",
65
+ },
66
+ Match: ".*",
67
+ Pattern: regexp.MustCompile(".*"),
68
+ Tags: map[string]string{
69
+ "foo": "bar",
70
+ },
71
+ SymbolTag: "ok",
72
+ },
73
+ },
74
+ },
75
+ }
76
+}
77
+
78
+func TestCloneMetadata(t *testing.T) {
79
+ metadata := makeMetadata()
80
+ metaCopy := metadata.Clone()
81
+ assert.Equal(t, metadata, metaCopy)
82
+ // Modify the copy in place
83
+ metaCopy["interface"] = MetadataResourceConfig{}
84
+ metaCopy["device"].Fields["foo"] = MetadataField{
85
+ Value: "foo",
86
+ }
87
+ // Original has not changed
88
+ assert.Equal(t, makeMetadata(), metadata)
89
+ // New one is different
90
+ assert.NotEqual(t, metadata, metaCopy)
91
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metrics.go
new
+234
@@ -0,0 +1,234 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "maps"
10
+ "regexp"
11
+ "slices"
12
+)
13
+
14
+// ProfileMetricType metric type used to override default type of the metric
15
+// By default metric type is derived from the type of the SNMP value, for example Counter32/64 -> rate.
16
+type ProfileMetricType string
17
+
18
+const (
19
+ // ProfileMetricTypeGauge is used to create a gauge metric
20
+ ProfileMetricTypeGauge ProfileMetricType = "gauge"
21
+
22
+ // ProfileMetricTypeMonotonicCount is used to create a monotonic_count metric
23
+ ProfileMetricTypeMonotonicCount ProfileMetricType = "monotonic_count"
24
+
25
+ // ProfileMetricTypeMonotonicCountAndRate is used to create a monotonic_count and rate metric
26
+ ProfileMetricTypeMonotonicCountAndRate ProfileMetricType = "monotonic_count_and_rate"
27
+
28
+ // ProfileMetricTypeRate is used to create a rate metric
29
+ ProfileMetricTypeRate ProfileMetricType = "rate"
30
+
31
+ // ProfileMetricTypeFlagStream is used to create metric based on a value that represent flags
32
+ // See details in https://github.com/DataDog/integrations-core/pull/7072
33
+ ProfileMetricTypeFlagStream ProfileMetricType = "flag_stream"
34
+
35
+ // ProfileMetricTypeCounter is DEPRECATED
36
+ // `counter` is deprecated in favour of `rate`
37
+ ProfileMetricTypeCounter ProfileMetricType = "counter"
38
+
39
+ // ProfileMetricTypePercent is DEPRECATED
40
+ // `percent` is deprecated in favour of `scale_factor`
41
+ ProfileMetricTypePercent ProfileMetricType = "percent"
42
+)
43
+
44
+// SymbolConfigCompat is used to deserialize string field or SymbolConfig.
45
+// For OID/Name to Symbol harmonization:
46
+// When users declare metric tag like:
47
+//
48
+// metric_tags:
49
+// - OID: 1.2.3
50
+// symbol: aSymbol
51
+//
52
+// this will lead to OID stored as MetricTagConfig.OID and name stored as MetricTagConfig.Symbol.Name
53
+// When this happens, in ValidateEnrichMetricTags we harmonize by moving MetricTagConfig.OID to MetricTagConfig.Symbol.OID.
54
+type SymbolConfigCompat SymbolConfig
55
+
56
+// Clone creates a duplicate of this SymbolConfigCompat
57
+func (s SymbolConfigCompat) Clone() SymbolConfigCompat {
58
+ return SymbolConfigCompat(SymbolConfig(s).Clone())
59
+}
60
+
61
+// SymbolConfig holds info for a single symbol/oid
62
+type SymbolConfig struct {
63
+ OID string `yaml:"OID,omitempty" json:"OID,omitempty"`
64
+ Name string `yaml:"name,omitempty" json:"name,omitempty"`
65
+
66
+ ExtractValue string `yaml:"extract_value,omitempty" json:"extract_value,omitempty"`
67
+ ExtractValueCompiled *regexp.Regexp `yaml:"-" json:"-"`
68
+
69
+ MatchPattern string `yaml:"match_pattern,omitempty" json:"match_pattern,omitempty"`
70
+ MatchValue string `yaml:"match_value,omitempty" json:"match_value,omitempty"`
71
+ MatchPatternCompiled *regexp.Regexp `yaml:"-" json:"-"`
72
+
73
+ ScaleFactor float64 `yaml:"scale_factor,omitempty" json:"scale_factor,omitempty"`
74
+ Format string `yaml:"format,omitempty" json:"format,omitempty"`
75
+ ConstantValueOne bool `yaml:"constant_value_one,omitempty" json:"constant_value_one,omitempty"`
76
+
77
+ // `metric_type` is used for force the metric type
78
+ // When empty, by default, the metric type is derived from SNMP OID value type.
79
+ // Valid `metric_type` types: `gauge`, `rate`, `monotonic_count`, `monotonic_count_and_rate`
80
+ // Deprecated types: `counter` (use `rate` instead), percent (use `scale_factor` instead)
81
+ MetricType ProfileMetricType `yaml:"metric_type,omitempty" json:"metric_type,omitempty"`
82
+ Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
83
+ Description string `yaml:"description,omitempty" json:"description,omitempty"`
84
+}
85
+
86
+// Clone creates a duplicate of this SymbolConfig
87
+func (s SymbolConfig) Clone() SymbolConfig {
88
+ // SymbolConfig has no mutable members, so simple assignment copies it.
89
+ // (technically this is false - regexes in go are mutable SOLELY through the
90
+ // .Longest() method. But we never use that, so we ignore it here)
91
+ return s
92
+}
93
+
94
+// MetricTagConfig holds metric tag info
95
+type MetricTagConfig struct {
96
+ Tag string `yaml:"tag" json:"tag"`
97
+
98
+ // Table config
99
+ Index uint `yaml:"index,omitempty" json:"index,omitempty"`
100
+
101
+ // DEPRECATED: Use .Symbol instead
102
+ Column SymbolConfig `yaml:"column,omitempty" json:"-"`
103
+
104
+ // DEPRECATED: use .Symbol instead
105
+ OID string `yaml:"OID,omitempty" json:"-" jsonschema:"-"`
106
+ // Symbol records the OID to be parsed. Note that .Symbol.Name is ignored:
107
+ // set .Tag to specify the tag name. If a serialized Symbol is a string
108
+ // instead of an object, it will be treated like {name: <value>}; this use
109
+ // pattern is deprecated
110
+ Symbol SymbolConfigCompat `yaml:"symbol,omitempty" json:"symbol,omitempty"`
111
+
112
+ IndexTransform []MetricIndexTransform `yaml:"index_transform,omitempty" json:"index_transform,omitempty"`
113
+
114
+ Mapping ListMap[string] `yaml:"mapping,omitempty" json:"mapping,omitempty"`
115
+
116
+ // Regex
117
+ // Match/Tags are not exposed as json (UI) since ExtractValue can be used instead
118
+ Match string `yaml:"match,omitempty" json:"-"`
119
+ Tags map[string]string `yaml:"tags,omitempty" json:"-"`
120
+ Pattern *regexp.Regexp `yaml:"-" json:"-"`
121
+
122
+ SymbolTag string `yaml:"-" json:"-"`
123
+}
124
+
125
+// Clone duplicates this MetricTagConfig
126
+func (m MetricTagConfig) Clone() MetricTagConfig {
127
+ m2 := m // non-pointer assignment shallow-copies members
128
+ // deep copy symbols and structures
129
+ m2.Column = m.Column.Clone()
130
+ m2.Symbol = m.Symbol.Clone()
131
+ m2.IndexTransform = slices.Clone(m.IndexTransform)
132
+ m2.Mapping = maps.Clone(m.Mapping)
133
+ m2.Tags = maps.Clone(m.Tags)
134
+ return m2
135
+}
136
+
137
+// MetricTagConfigList holds configs for a list of metric tags
138
+type MetricTagConfigList []MetricTagConfig
139
+
140
+// MetricIndexTransform holds configs for metric index transform
141
+type MetricIndexTransform struct {
142
+ Start uint `yaml:"start" json:"start"`
143
+ End uint `yaml:"end" json:"end"`
144
+}
145
+
146
+// MetricsConfigOption holds config for metrics options
147
+type MetricsConfigOption struct {
148
+ Placement uint `yaml:"placement,omitempty" json:"placement,omitempty"`
149
+ MetricSuffix string `yaml:"metric_suffix,omitempty" json:"metric_suffix,omitempty"`
150
+}
151
+
152
+// MetricsConfig holds configs for a metric
153
+type MetricsConfig struct {
154
+ // MIB the MIB used for this metric
155
+ MIB string `yaml:"MIB,omitempty" json:"MIB,omitempty"`
156
+
157
+ // Table the table OID
158
+ Table SymbolConfig `yaml:"table,omitempty" json:"table,omitempty"`
159
+
160
+ // Symbol configs
161
+ Symbol SymbolConfig `yaml:"symbol,omitempty" json:"symbol,omitempty"`
162
+
163
+ // DEPRECATED: Use .Symbol instead
164
+ OID string `yaml:"OID,omitempty" json:"OID,omitempty" jsonschema:"-"`
165
+ // DEPRECATED: Use .Symbol instead
166
+ Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"-"`
167
+
168
+ // Table configs
169
+ Symbols []SymbolConfig `yaml:"symbols,omitempty" json:"symbols,omitempty"`
170
+
171
+ // `static_tags` is not exposed as json at the moment since we need to evaluate if we want to expose it via UI
172
+ StaticTags []string `yaml:"static_tags,omitempty" json:"-"`
173
+ MetricTags MetricTagConfigList `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
174
+
175
+ // DEPRECATED: use Symbol.MetricType instead.
176
+ ForcedType ProfileMetricType `yaml:"forced_type,omitempty" json:"forced_type,omitempty" jsonschema:"-"`
177
+ // DEPRECATED: use Symbol.MetricType instead.
178
+ MetricType ProfileMetricType `yaml:"metric_type,omitempty" json:"metric_type,omitempty" jsonschema:"-"`
179
+
180
+ Options MetricsConfigOption `yaml:"options,omitempty" json:"options,omitempty"`
181
+}
182
+
183
+// Clone duplicates this MetricsConfig
184
+func (m MetricsConfig) Clone() MetricsConfig {
185
+ return MetricsConfig{
186
+ MIB: m.MIB,
187
+ Table: m.Table.Clone(),
188
+ Symbol: m.Symbol.Clone(),
189
+ OID: m.OID,
190
+ Name: m.Name,
191
+ Symbols: CloneSlice(m.Symbols),
192
+ StaticTags: slices.Clone(m.StaticTags),
193
+ MetricTags: CloneSlice(m.MetricTags),
194
+ ForcedType: m.ForcedType,
195
+ MetricType: m.MetricType,
196
+ Options: m.Options,
197
+ }
198
+}
199
+
200
+// GetSymbolTags returns symbol tags
201
+func (m *MetricsConfig) GetSymbolTags() []string {
202
+ var symbolTags []string
203
+ for _, metricTag := range m.MetricTags {
204
+ symbolTags = append(symbolTags, metricTag.SymbolTag)
205
+ }
206
+ return symbolTags
207
+}
208
+
209
+// IsColumn returns true if the metrics config define columns metrics
210
+func (m *MetricsConfig) IsColumn() bool {
211
+ return len(m.Symbols) > 0
212
+}
213
+
214
+// IsScalar returns true if the metrics config define scalar metrics
215
+func (m *MetricsConfig) IsScalar() bool {
216
+ return m.Symbol.OID != "" && m.Symbol.Name != ""
217
+}
218
+
219
+// NormalizeMetrics converts legacy syntax to new syntax
220
+// 1/ converts old symbol syntax to new symbol syntax
221
+// metric.Name and metric.OID info are moved to metric.Symbol.Name and metric.Symbol.OID
222
+func NormalizeMetrics(metrics []MetricsConfig) {
223
+ for i := range metrics {
224
+ metric := &metrics[i]
225
+
226
+ // converts old symbol syntax to new symbol syntax
227
+ if metric.Symbol.Name == "" && metric.Symbol.OID == "" && metric.Name != "" && metric.OID != "" {
228
+ metric.Symbol.Name = metric.Name
229
+ metric.Symbol.OID = metric.OID
230
+ metric.Name = ""
231
+ metric.OID = ""
232
+ }
233
+ }
234
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/metrics_test.go
new
+180
@@ -0,0 +1,180 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2024-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "regexp"
10
+ "testing"
11
+
12
+ "github.com/stretchr/testify/assert"
13
+)
14
+
15
+func TestCloneSymbolConfig(t *testing.T) {
16
+ s := SymbolConfig{
17
+ OID: "1.2.3.4",
18
+ Name: "foo",
19
+ ExtractValue: ".*",
20
+ ExtractValueCompiled: regexp.MustCompile(".*"),
21
+ MatchPattern: ".*",
22
+ MatchPatternCompiled: regexp.MustCompile(".*"),
23
+ MatchValue: "$1",
24
+ ScaleFactor: 100,
25
+ Format: "mac_address",
26
+ ConstantValueOne: true,
27
+ MetricType: ProfileMetricTypeCounter,
28
+ }
29
+ s2 := s.Clone()
30
+ assert.Equal(t, s, s2)
31
+ // An issue with our previous deepcopy was that regexes were duplicated but
32
+ // didn't keep their private internals, causing them to fail when used, so
33
+ // double-check that the regex works fine.
34
+ assert.True(t, s2.ExtractValueCompiled.MatchString("foo"))
35
+}
36
+
37
+func TestCloneSymbolConfigCompat(t *testing.T) {
38
+ s := SymbolConfigCompat{
39
+ OID: "1.2.3.4",
40
+ Name: "foo",
41
+ ExtractValue: ".*",
42
+ ExtractValueCompiled: regexp.MustCompile(".*"),
43
+ MatchPattern: ".*",
44
+ MatchPatternCompiled: regexp.MustCompile(".*"),
45
+ MatchValue: "$1",
46
+ ScaleFactor: 100,
47
+ Format: "mac_address",
48
+ ConstantValueOne: true,
49
+ MetricType: ProfileMetricTypeCounter,
50
+ }
51
+ s2 := s.Clone()
52
+ assert.Equal(t, s, s2)
53
+}
54
+
55
+func TestCloneMetricTagConfig(t *testing.T) {
56
+ c := MetricTagConfig{
57
+ Tag: "foo",
58
+ Index: 10,
59
+ Column: SymbolConfig{
60
+ OID: "1.2.3.4",
61
+ ExtractValue: ".*",
62
+ ExtractValueCompiled: regexp.MustCompile(".*"),
63
+ },
64
+ OID: "2.4",
65
+ Symbol: SymbolConfigCompat{},
66
+ IndexTransform: []MetricIndexTransform{
67
+ {
68
+ Start: 0,
69
+ End: 1,
70
+ },
71
+ },
72
+ Mapping: map[string]string{
73
+ "1": "bar",
74
+ "2": "baz",
75
+ },
76
+ Match: ".*",
77
+ Pattern: regexp.MustCompile(".*"),
78
+ Tags: map[string]string{
79
+ "foo": "$1",
80
+ },
81
+ SymbolTag: "baz",
82
+ }
83
+ c2 := c.Clone()
84
+ assert.Equal(t, c, c2)
85
+ c2.Tags["bar"] = "$2"
86
+ c2.IndexTransform = append(c2.IndexTransform, MetricIndexTransform{1, 3})
87
+ c2.Mapping["3"] = "foo"
88
+ c2.Tag = "bar"
89
+ assert.NotEqual(t, c, c2)
90
+ // Validate that c has not changed
91
+ assert.Equal(t, c, MetricTagConfig{
92
+ Tag: "foo",
93
+ Index: 10,
94
+ Column: SymbolConfig{
95
+ OID: "1.2.3.4",
96
+ ExtractValue: ".*",
97
+ ExtractValueCompiled: regexp.MustCompile(".*"),
98
+ },
99
+ OID: "2.4",
100
+ Symbol: SymbolConfigCompat{},
101
+ IndexTransform: []MetricIndexTransform{
102
+ {
103
+ Start: 0,
104
+ End: 1,
105
+ },
106
+ },
107
+ Mapping: map[string]string{
108
+ "1": "bar",
109
+ "2": "baz",
110
+ },
111
+ Match: ".*",
112
+ Pattern: regexp.MustCompile(".*"),
113
+ Tags: map[string]string{
114
+ "foo": "$1",
115
+ },
116
+ SymbolTag: "baz",
117
+ })
118
+}
119
+
120
+func TestCloneMetricsConfig(t *testing.T) {
121
+ buildConf := func() MetricsConfig {
122
+ return MetricsConfig{
123
+ MIB: "FOO-MIB",
124
+ Table: SymbolConfig{
125
+ OID: "1.2.3.4",
126
+ ExtractValue: ".*",
127
+ ExtractValueCompiled: regexp.MustCompile(".*"),
128
+ },
129
+ Symbol: SymbolConfig{
130
+ OID: "1.2.3.4",
131
+ ExtractValue: ".*",
132
+ ExtractValueCompiled: regexp.MustCompile(".*"),
133
+ },
134
+ OID: "1.2.3.4",
135
+ Name: "foo",
136
+ Symbols: []SymbolConfig{
137
+ {
138
+ OID: "1.2.3.4",
139
+ ExtractValue: ".*",
140
+ ExtractValueCompiled: regexp.MustCompile(".*"),
141
+ },
142
+ },
143
+ StaticTags: []string{
144
+ "foo",
145
+ "bar",
146
+ },
147
+ MetricTags: []MetricTagConfig{
148
+ {
149
+ IndexTransform: make([]MetricIndexTransform, 0),
150
+ },
151
+ },
152
+ ForcedType: ProfileMetricTypeCounter,
153
+ MetricType: ProfileMetricTypeGauge,
154
+ Options: MetricsConfigOption{
155
+ Placement: 1,
156
+ MetricSuffix: ".foo",
157
+ },
158
+ }
159
+ }
160
+ conf := buildConf()
161
+ unchanged := buildConf()
162
+
163
+ conf2 := conf.Clone()
164
+ assert.Equal(t, conf, conf2)
165
+ conf2.StaticTags[0] = "baz"
166
+ conf2.MetricTags[0].IndexTransform = []MetricIndexTransform{{5, 7}}
167
+ conf2.Options.Placement = 2
168
+ conf2.Options.MetricSuffix = ".bar"
169
+ assert.Equal(t, unchanged, conf)
170
+ assert.NotEqual(t, conf, conf2)
171
+}
172
+
173
+func TestCloneEmpty(t *testing.T) {
174
+ mc := MetricsConfig{}
175
+ assert.Equal(t, mc, mc.Clone())
176
+ sym := SymbolConfig{}
177
+ assert.Equal(t, sym, sym.Clone())
178
+ tag := MetricTagConfig{}
179
+ assert.Equal(t, tag, tag.Clone())
180
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/normalize_cmd/cmd/root.go
new
+125
@@ -0,0 +1,125 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+// Package cmd implements a cobra command for validating and normalizing profiles.
7
+package cmd
8
+
9
+import (
10
+ "encoding/json"
11
+ "fmt"
12
+ "os"
13
+ "path/filepath"
14
+
15
+ "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
16
+ "github.com/spf13/cobra"
17
+ "gopkg.in/yaml.v2"
18
+)
19
+
20
+// rootCmd represents the base command when called without any subcommands
21
+var rootCmd = &cobra.Command{
22
+ Use: "normalize_cmd FILE [FILE...]",
23
+ Short: "Validate and normalize profiles.",
24
+ Long: `normalize_cmd is a tool for validating and normalizing profiles.
25
+
26
+ Each profile file passed in will be parsed, and any errors in them will be
27
+ reported. If an output directory is specified with -o, then the profiles will
28
+ also be normalized, migrating legacy and deprecated structures to their
29
+ modern counterparts, and written to the output directory.`,
30
+
31
+ Run: func(cmd *cobra.Command, args []string) {
32
+ outdir, err := cmd.Flags().GetString("outdir")
33
+ if err != nil {
34
+ fmt.Printf("parse failure %v, unable to generate output\n", err)
35
+ }
36
+ useJSON, err := cmd.Flags().GetBool("json")
37
+ if err != nil {
38
+ fmt.Printf("parse failure %v, unable to generate output\n", err)
39
+ }
40
+ for _, filePath := range args {
41
+ filename := filepath.Base(filePath)
42
+ name := filename[:len(filename)-len(filepath.Ext(filename))] // remove extension
43
+ def, errors := GetProfile(filePath)
44
+ if len(errors) > 0 {
45
+ fmt.Printf("*** %d error(s) in profile %q ***\n", len(errors), filePath)
46
+ for _, e := range errors {
47
+ fmt.Println(" ", e)
48
+ }
49
+ fmt.Println()
50
+ continue
51
+ }
52
+ if err := WriteProfile(def, name, outdir, useJSON); err != nil {
53
+ fmt.Println(err)
54
+ }
55
+ }
56
+ },
57
+}
58
+
59
+// GetProfile parses a profile from a file path and validates it.
60
+func GetProfile(filePath string) (*profiledefinition.ProfileDefinition, []string) {
61
+ buf, err := os.ReadFile(filePath)
62
+ if err != nil {
63
+ return nil, []string{fmt.Sprintf("unable to read file: %v", err)}
64
+ }
65
+ def := profiledefinition.NewProfileDefinition()
66
+ err = yaml.Unmarshal(buf, def)
67
+ if err != nil {
68
+ return nil, []string{fmt.Sprintf("unable to parse profile: %v", err)}
69
+ }
70
+ errors := profiledefinition.ValidateEnrichProfile(def)
71
+ if len(errors) > 0 {
72
+ return nil, errors
73
+ }
74
+ return def, nil
75
+}
76
+
77
+// WriteProfile writes a profile to disk.
78
+func WriteProfile(def *profiledefinition.ProfileDefinition, name string, outdir string, useJSON bool) error {
79
+ if outdir == "" {
80
+ return nil
81
+ }
82
+ var filename string
83
+ var data []byte
84
+ if useJSON {
85
+ var err error
86
+ filename = name + ".json"
87
+ data, err = json.Marshal(def)
88
+ if err != nil {
89
+ return fmt.Errorf("unable to marshal profile %s: %w", name, err)
90
+ }
91
+ } else {
92
+ var err error
93
+ data, err = yaml.Marshal(def)
94
+ filename = name + ".yaml"
95
+ if err != nil {
96
+ return fmt.Errorf("unable to marshal profile %s: %w", name, err)
97
+ }
98
+ }
99
+ outfile := filepath.Join(outdir, filename)
100
+ f, err := os.Create(outfile)
101
+ if err != nil {
102
+ return fmt.Errorf("unable to create file %s: %w", outfile, err)
103
+ }
104
+ defer func() {
105
+ _ = f.Close()
106
+ }()
107
+ _, err = f.Write(data)
108
+ if err != nil {
109
+ return fmt.Errorf("unable to write to file %s: %w", outfile, err)
110
+ }
111
+ return nil
112
+}
113
+
114
+// Execute runs the command.
115
+func Execute() {
116
+ err := rootCmd.Execute()
117
+ if err != nil {
118
+ os.Exit(1)
119
+ }
120
+}
121
+
122
+func init() {
123
+ rootCmd.Flags().StringP("outdir", "o", "", "Output path for normalized files. If blank, inputs will be validated but not output.")
124
+ rootCmd.Flags().BoolP("json", "j", false, "Output as JSON")
125
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/normalize_cmd/main.go
new
+11
@@ -0,0 +1,11 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+package main
6
+
7
+import "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/normalize_cmd/cmd"
8
+
9
+func main() {
10
+ cmd.Execute()
11
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/profile_definition.go
new
+79
@@ -0,0 +1,79 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import "slices"
9
+
10
+// DeviceMeta holds device related static metadata
11
+// DEPRECATED in favour of profile metadata syntax
12
+type DeviceMeta struct {
13
+ // deprecated in favour of new `ProfileDefinition.Metadata` syntax
14
+ Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty"`
15
+}
16
+
17
+// ProfileDefinition is the root profile structure. The ProfileDefinition is currently used in:
18
+// 1/ SNMP Integration: the profiles are in yaml profiles. Yaml profiles include default datadog profiles and user custom profiles.
19
+// The serialisation of yaml profiles are defined by the yaml annotation and few custom unmarshaller (see yaml_utils.go).
20
+// 2/ Datadog backend: the profiles are in json format, they are used to store profiles created via UI.
21
+// The serialisation of json profiles are defined by the json annotation.
22
+type ProfileDefinition struct {
23
+ Name string `yaml:"name,omitempty" json:"name,omitempty"`
24
+ Description string `yaml:"description,omitempty" json:"description,omitempty"`
25
+ SysObjectIDs StringArray `yaml:"sysobjectid,omitempty" json:"sysobjectid,omitempty"`
26
+ Extends []string `yaml:"extends,omitempty" json:"extends,omitempty"`
27
+ Metadata MetadataConfig `yaml:"metadata,omitempty" json:"metadata,omitempty"`
28
+ MetricTags []MetricTagConfig `yaml:"metric_tags,omitempty" json:"metric_tags,omitempty"`
29
+ StaticTags []string `yaml:"static_tags,omitempty" json:"static_tags,omitempty"`
30
+ Metrics []MetricsConfig `yaml:"metrics,omitempty" json:"metrics,omitempty"`
31
+
32
+ // DEPRECATED: Use metadata directly
33
+ Device DeviceMeta `yaml:"device,omitempty" json:"device,omitempty" jsonschema:"device,omitempty"`
34
+
35
+ // Version is the profile version.
36
+ // It is currently used only with downloaded/RC profiles.
37
+ Version uint64 `yaml:"version,omitempty" json:"version,omitempty"`
38
+}
39
+
40
+// DeviceProfileRcConfig represent the profile stored in remote config.
41
+type DeviceProfileRcConfig struct {
42
+ Profile ProfileDefinition `json:"profile_definition"`
43
+}
44
+
45
+// NewProfileDefinition creates a new ProfileDefinition
46
+func NewProfileDefinition() *ProfileDefinition {
47
+ p := &ProfileDefinition{}
48
+ p.Metadata = make(MetadataConfig)
49
+ return p
50
+}
51
+
52
+// SplitOIDs returns two slices (scalars, columns) of all scalar and column OIDs requested by this profile.
53
+func (p *ProfileDefinition) SplitOIDs(includeMetadata bool) ([]string, []string) {
54
+ if includeMetadata {
55
+ return splitOIDs(p.Metrics, p.MetricTags, p.Metadata)
56
+ }
57
+ return splitOIDs(p.Metrics, p.MetricTags, nil)
58
+}
59
+
60
+// Clone duplicates this ProfileDefinition
61
+func (p *ProfileDefinition) Clone() *ProfileDefinition {
62
+ if p == nil {
63
+ return nil
64
+ }
65
+ return &ProfileDefinition{
66
+ Name: p.Name,
67
+ Description: p.Description,
68
+ SysObjectIDs: slices.Clone(p.SysObjectIDs),
69
+ Extends: slices.Clone(p.Extends),
70
+ Metadata: CloneMap(p.Metadata),
71
+ MetricTags: CloneSlice(p.MetricTags),
72
+ StaticTags: slices.Clone(p.StaticTags),
73
+ Metrics: CloneSlice(p.Metrics),
74
+ Device: DeviceMeta{
75
+ Vendor: p.Device.Vendor,
76
+ },
77
+ Version: p.Version,
78
+ }
79
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/doc.go
new
+7
@@ -0,0 +1,7 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+// Package schema contain json schema related code
7
+package schema
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/generate.go
new
+26
@@ -0,0 +1,26 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package schema
7
+
8
+import (
9
+ "encoding/json"
10
+ "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
11
+ "github.com/invopop/jsonschema"
12
+)
13
+
14
+// GenerateJSONSchema generate jsonschema from profiledefinition.DeviceProfileRcConfig
15
+func GenerateJSONSchema() ([]byte, error) {
16
+ reflector := jsonschema.Reflector{
17
+ AllowAdditionalProperties: false,
18
+ }
19
+ schema := reflector.Reflect(&profiledefinition.DeviceProfileRcConfig{})
20
+ schemaJSON, err := json.MarshalIndent(schema, "", " ")
21
+ if err != nil {
22
+ return nil, err
23
+ }
24
+ schemaJSON = append(schemaJSON, byte('\n'))
25
+ return schemaJSON, nil
26
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/generate_test.go
new
+20
@@ -0,0 +1,20 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package schema
7
+
8
+import (
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+ "github.com/stretchr/testify/require"
13
+)
14
+
15
+func TestGenerateJSONSchemaIsInSync(t *testing.T) {
16
+ schemaJSON, err := GenerateJSONSchema()
17
+ require.NoError(t, err)
18
+
19
+ assert.JSONEq(t, string(GetDeviceProfileRcConfigJsonschema()), string(schemaJSON))
20
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/profile_rc_schema.json
new
+463
@@ -0,0 +1,463 @@
1
+{
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/device-profile-rc-config",
4
+ "$ref": "#/$defs/DeviceProfileRcConfig",
5
+ "$defs": {
6
+ "DeviceMeta": {
7
+ "properties": {
8
+ "vendor": {
9
+ "type": "string"
10
+ }
11
+ },
12
+ "additionalProperties": false,
13
+ "type": "object"
14
+ },
15
+ "DeviceProfileRcConfig": {
16
+ "properties": {
17
+ "profile_definition": {
18
+ "$ref": "#/$defs/ProfileDefinition"
19
+ }
20
+ },
21
+ "additionalProperties": false,
22
+ "type": "object",
23
+ "required": [
24
+ "profile_definition"
25
+ ]
26
+ },
27
+ "ListMap[string]": {
28
+ "items": {
29
+ "properties": {
30
+ "key": {
31
+ "type": "string"
32
+ },
33
+ "value": {
34
+ "type": "string"
35
+ }
36
+ },
37
+ "additionalProperties": false,
38
+ "type": "object",
39
+ "required": [
40
+ "key",
41
+ "value"
42
+ ]
43
+ },
44
+ "type": "array"
45
+ },
46
+ "MetadataConfig": {
47
+ "items": {
48
+ "properties": {
49
+ "key": {
50
+ "type": "string"
51
+ },
52
+ "value": {
53
+ "properties": {
54
+ "fields": {
55
+ "items": {
56
+ "properties": {
57
+ "key": {
58
+ "type": "string"
59
+ },
60
+ "value": {
61
+ "properties": {
62
+ "symbol": {
63
+ "properties": {
64
+ "OID": {
65
+ "type": "string"
66
+ },
67
+ "name": {
68
+ "type": "string"
69
+ },
70
+ "extract_value": {
71
+ "type": "string"
72
+ },
73
+ "match_pattern": {
74
+ "type": "string"
75
+ },
76
+ "match_value": {
77
+ "type": "string"
78
+ },
79
+ "scale_factor": {
80
+ "type": "number"
81
+ },
82
+ "format": {
83
+ "type": "string"
84
+ },
85
+ "constant_value_one": {
86
+ "type": "boolean"
87
+ },
88
+ "metric_type": {
89
+ "type": "string"
90
+ }
91
+ },
92
+ "additionalProperties": false,
93
+ "type": "object"
94
+ },
95
+ "symbols": {
96
+ "items": {
97
+ "properties": {
98
+ "OID": {
99
+ "type": "string"
100
+ },
101
+ "name": {
102
+ "type": "string"
103
+ },
104
+ "extract_value": {
105
+ "type": "string"
106
+ },
107
+ "match_pattern": {
108
+ "type": "string"
109
+ },
110
+ "match_value": {
111
+ "type": "string"
112
+ },
113
+ "scale_factor": {
114
+ "type": "number"
115
+ },
116
+ "format": {
117
+ "type": "string"
118
+ },
119
+ "constant_value_one": {
120
+ "type": "boolean"
121
+ },
122
+ "metric_type": {
123
+ "type": "string"
124
+ }
125
+ },
126
+ "additionalProperties": false,
127
+ "type": "object"
128
+ },
129
+ "type": "array"
130
+ },
131
+ "value": {
132
+ "type": "string"
133
+ }
134
+ },
135
+ "additionalProperties": false,
136
+ "type": "object"
137
+ }
138
+ },
139
+ "additionalProperties": false,
140
+ "type": "object",
141
+ "required": [
142
+ "key",
143
+ "value"
144
+ ]
145
+ },
146
+ "type": "array"
147
+ },
148
+ "id_tags": {
149
+ "items": {
150
+ "properties": {
151
+ "tag": {
152
+ "type": "string"
153
+ },
154
+ "index": {
155
+ "type": "integer"
156
+ },
157
+ "symbol": {
158
+ "properties": {
159
+ "OID": {
160
+ "type": "string"
161
+ },
162
+ "name": {
163
+ "type": "string"
164
+ },
165
+ "extract_value": {
166
+ "type": "string"
167
+ },
168
+ "match_pattern": {
169
+ "type": "string"
170
+ },
171
+ "match_value": {
172
+ "type": "string"
173
+ },
174
+ "scale_factor": {
175
+ "type": "number"
176
+ },
177
+ "format": {
178
+ "type": "string"
179
+ },
180
+ "constant_value_one": {
181
+ "type": "boolean"
182
+ },
183
+ "metric_type": {
184
+ "type": "string"
185
+ }
186
+ },
187
+ "additionalProperties": false,
188
+ "type": "object"
189
+ },
190
+ "index_transform": {
191
+ "items": {
192
+ "properties": {
193
+ "start": {
194
+ "type": "integer"
195
+ },
196
+ "end": {
197
+ "type": "integer"
198
+ }
199
+ },
200
+ "additionalProperties": false,
201
+ "type": "object",
202
+ "required": [
203
+ "start",
204
+ "end"
205
+ ]
206
+ },
207
+ "type": "array"
208
+ },
209
+ "mapping": {
210
+ "items": {
211
+ "properties": {
212
+ "key": {
213
+ "type": "string"
214
+ },
215
+ "value": {
216
+ "type": "string"
217
+ }
218
+ },
219
+ "additionalProperties": false,
220
+ "type": "object",
221
+ "required": [
222
+ "key",
223
+ "value"
224
+ ]
225
+ },
226
+ "type": "array"
227
+ }
228
+ },
229
+ "additionalProperties": false,
230
+ "type": "object",
231
+ "required": [
232
+ "tag"
233
+ ]
234
+ },
235
+ "type": "array"
236
+ }
237
+ },
238
+ "additionalProperties": false,
239
+ "type": "object",
240
+ "required": [
241
+ "fields"
242
+ ]
243
+ }
244
+ },
245
+ "additionalProperties": false,
246
+ "type": "object",
247
+ "required": [
248
+ "key",
249
+ "value"
250
+ ]
251
+ },
252
+ "type": "array"
253
+ },
254
+ "MetricIndexTransform": {
255
+ "properties": {
256
+ "start": {
257
+ "type": "integer"
258
+ },
259
+ "end": {
260
+ "type": "integer"
261
+ }
262
+ },
263
+ "additionalProperties": false,
264
+ "type": "object",
265
+ "required": [
266
+ "start",
267
+ "end"
268
+ ]
269
+ },
270
+ "MetricTagConfig": {
271
+ "properties": {
272
+ "tag": {
273
+ "type": "string"
274
+ },
275
+ "index": {
276
+ "type": "integer"
277
+ },
278
+ "symbol": {
279
+ "$ref": "#/$defs/SymbolConfigCompat"
280
+ },
281
+ "index_transform": {
282
+ "items": {
283
+ "$ref": "#/$defs/MetricIndexTransform"
284
+ },
285
+ "type": "array"
286
+ },
287
+ "mapping": {
288
+ "$ref": "#/$defs/ListMap[string]"
289
+ }
290
+ },
291
+ "additionalProperties": false,
292
+ "type": "object",
293
+ "required": [
294
+ "tag"
295
+ ]
296
+ },
297
+ "MetricTagConfigList": {
298
+ "items": {
299
+ "$ref": "#/$defs/MetricTagConfig"
300
+ },
301
+ "type": "array"
302
+ },
303
+ "MetricsConfig": {
304
+ "properties": {
305
+ "MIB": {
306
+ "type": "string"
307
+ },
308
+ "table": {
309
+ "$ref": "#/$defs/SymbolConfig"
310
+ },
311
+ "symbol": {
312
+ "$ref": "#/$defs/SymbolConfig"
313
+ },
314
+ "symbols": {
315
+ "items": {
316
+ "$ref": "#/$defs/SymbolConfig"
317
+ },
318
+ "type": "array"
319
+ },
320
+ "metric_tags": {
321
+ "$ref": "#/$defs/MetricTagConfigList"
322
+ },
323
+ "options": {
324
+ "$ref": "#/$defs/MetricsConfigOption"
325
+ }
326
+ },
327
+ "additionalProperties": false,
328
+ "type": "object"
329
+ },
330
+ "MetricsConfigOption": {
331
+ "properties": {
332
+ "placement": {
333
+ "type": "integer"
334
+ },
335
+ "metric_suffix": {
336
+ "type": "string"
337
+ }
338
+ },
339
+ "additionalProperties": false,
340
+ "type": "object"
341
+ },
342
+ "ProfileDefinition": {
343
+ "properties": {
344
+ "name": {
345
+ "type": "string"
346
+ },
347
+ "description": {
348
+ "type": "string"
349
+ },
350
+ "sysobjectid": {
351
+ "$ref": "#/$defs/StringArray"
352
+ },
353
+ "extends": {
354
+ "items": {
355
+ "type": "string"
356
+ },
357
+ "type": "array"
358
+ },
359
+ "metadata": {
360
+ "$ref": "#/$defs/MetadataConfig"
361
+ },
362
+ "metric_tags": {
363
+ "items": {
364
+ "$ref": "#/$defs/MetricTagConfig"
365
+ },
366
+ "type": "array"
367
+ },
368
+ "static_tags": {
369
+ "items": {
370
+ "type": "string"
371
+ },
372
+ "type": "array"
373
+ },
374
+ "metrics": {
375
+ "items": {
376
+ "$ref": "#/$defs/MetricsConfig"
377
+ },
378
+ "type": "array"
379
+ },
380
+ "device": {
381
+ "$ref": "#/$defs/DeviceMeta"
382
+ },
383
+ "version": {
384
+ "type": "integer"
385
+ }
386
+ },
387
+ "additionalProperties": false,
388
+ "type": "object"
389
+ },
390
+ "StringArray": {
391
+ "items": {
392
+ "type": "string"
393
+ },
394
+ "type": "array"
395
+ },
396
+ "SymbolConfig": {
397
+ "properties": {
398
+ "OID": {
399
+ "type": "string"
400
+ },
401
+ "name": {
402
+ "type": "string"
403
+ },
404
+ "extract_value": {
405
+ "type": "string"
406
+ },
407
+ "match_pattern": {
408
+ "type": "string"
409
+ },
410
+ "match_value": {
411
+ "type": "string"
412
+ },
413
+ "scale_factor": {
414
+ "type": "number"
415
+ },
416
+ "format": {
417
+ "type": "string"
418
+ },
419
+ "constant_value_one": {
420
+ "type": "boolean"
421
+ },
422
+ "metric_type": {
423
+ "type": "string"
424
+ }
425
+ },
426
+ "additionalProperties": false,
427
+ "type": "object"
428
+ },
429
+ "SymbolConfigCompat": {
430
+ "properties": {
431
+ "OID": {
432
+ "type": "string"
433
+ },
434
+ "name": {
435
+ "type": "string"
436
+ },
437
+ "extract_value": {
438
+ "type": "string"
439
+ },
440
+ "match_pattern": {
441
+ "type": "string"
442
+ },
443
+ "match_value": {
444
+ "type": "string"
445
+ },
446
+ "scale_factor": {
447
+ "type": "number"
448
+ },
449
+ "format": {
450
+ "type": "string"
451
+ },
452
+ "constant_value_one": {
453
+ "type": "boolean"
454
+ },
455
+ "metric_type": {
456
+ "type": "string"
457
+ }
458
+ },
459
+ "additionalProperties": false,
460
+ "type": "object"
461
+ }
462
+ }
463
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/profile_rc_schema_test.go
new
+100
@@ -0,0 +1,100 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package schema
7
+
8
+import (
9
+ "encoding/json"
10
+ "fmt"
11
+ "io/fs"
12
+ "os"
13
+ "path/filepath"
14
+ "strings"
15
+ "testing"
16
+
17
+ "github.com/santhosh-tekuri/jsonschema/v5"
18
+ "github.com/stretchr/testify/assert"
19
+ "github.com/stretchr/testify/require"
20
+)
21
+
22
+type testcaseExpected struct {
23
+ ErrorPatterns []string `json:"error_patterns"`
24
+}
25
+
26
+func Test_DeviceProfileRcConfigJsonSchema(t *testing.T) {
27
+ // language=json
28
+ instanceJSON := `{
29
+ "profile_definition": {
30
+ "name": "my-profile",
31
+ "version": 10
32
+ }
33
+}`
34
+
35
+ err := assertAgainstSchema(t, instanceJSON)
36
+
37
+ fmt.Printf("%#v\n", err) // using %#v prints errors hierarchy
38
+ require.NoError(t, err)
39
+}
40
+
41
+func Test_Schema_TextCases(t *testing.T) {
42
+ var testcases []string
43
+ err := filepath.WalkDir("./testcases", func(s string, d fs.DirEntry, e error) error {
44
+ if e != nil {
45
+ return e
46
+ }
47
+ if strings.HasSuffix(d.Name(), "_exp.json") {
48
+ return nil
49
+ }
50
+ if filepath.Ext(d.Name()) == ".json" {
51
+ testcases = append(testcases, s)
52
+ }
53
+ return nil
54
+ })
55
+ require.NoError(t, err)
56
+
57
+ for _, testcaseJSONPath := range testcases {
58
+ t.Run(testcaseJSONPath, func(t *testing.T) {
59
+ content, err := os.ReadFile(testcaseJSONPath)
60
+ require.NoError(t, err)
61
+
62
+ validationErr := assertAgainstSchema(t, string(content))
63
+ validationErrStr := fmt.Sprintf("%#v\n", validationErr) // using %#v prints errors hierarchy
64
+
65
+ if validationErr != nil {
66
+ // Print validation error to ease debugging tests
67
+ fmt.Print("--- ACTUAL VALIDATION ERRORS --------------------------------------------\n")
68
+ fmt.Print(validationErrStr)
69
+ fmt.Print("-------------------------------------------------------------------------\n")
70
+ }
71
+
72
+ testcaseExpectedErrPath := strings.ReplaceAll(testcaseJSONPath, ".json", "_exp.json")
73
+ testcaseExpectedErr, err := os.ReadFile(testcaseExpectedErrPath)
74
+ require.NoError(t, err)
75
+
76
+ var expected testcaseExpected
77
+ err = json.Unmarshal(testcaseExpectedErr, &expected)
78
+ require.NoError(t, err)
79
+
80
+ for _, expectedErrorPattern := range expected.ErrorPatterns {
81
+ assert.Regexp(t, expectedErrorPattern, validationErrStr)
82
+ }
83
+ if len(expected.ErrorPatterns) == 0 {
84
+ assert.NoError(t, validationErr)
85
+ }
86
+ })
87
+ }
88
+}
89
+
90
+func assertAgainstSchema(t *testing.T, instanceJSON string) error {
91
+ sch, err := jsonschema.CompileString("schema.json", string(GetDeviceProfileRcConfigJsonschema()))
92
+ require.NoError(t, err)
93
+
94
+ var v interface{}
95
+ err = json.Unmarshal([]byte(instanceJSON), &v)
96
+ require.NoError(t, err)
97
+
98
+ err = sch.Validate(v)
99
+ return err
100
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/schema.go
new
+18
@@ -0,0 +1,18 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package schema
7
+
8
+import _ "embed"
9
+
10
+var (
11
+ //go:embed profile_rc_schema.json
12
+ deviceProfileRcConfigJsonschema []byte
13
+)
14
+
15
+// GetDeviceProfileRcConfigJsonschema returns deviceProfileRcConfigJsonschema
16
+func GetDeviceProfileRcConfigJsonschema() []byte {
17
+ return deviceProfileRcConfigJsonschema
18
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_forced_t.json
new
+14
@@ -0,0 +1,14 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "some-profile",
4
+ "metrics": [
5
+ {
6
+ "symbol": {
7
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
8
+ "name": "upsAdvBatteryNumOfBattPacks"
9
+ },
10
+ "forced_type": "gauge"
11
+ }
12
+ ]
13
+ }
14
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_forced_t_exp.json
new
+5
@@ -0,0 +1,5 @@
1
+{
2
+ "error_patterns": [
3
+ "defs/MetricsConfig/additionalProperties] additionalProperties 'forced_type' not allowed"
4
+ ]
5
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_oid_name.json
new
+11
@@ -0,0 +1,11 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "some-profile",
4
+ "metrics": [
5
+ {
6
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
7
+ "name": "upsAdvBatteryNumOfBattPacks"
8
+ }
9
+ ]
10
+ }
11
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_deprtd_oid_name_exp.json
new
+6
@@ -0,0 +1,6 @@
1
+{
2
+ "error_patterns": [
3
+ "MetricsConfig/additionalProperties] additionalProperties .*'OID'.* not allowed",
4
+ "MetricsConfig/additionalProperties] additionalProperties .*'name'.* not allowed"
5
+ ]
6
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_empty_profile.json
new
+1
@@ -0,0 +1 @@
1
+{}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_empty_profile_exp.json
new
+5
@@ -0,0 +1,5 @@
1
+{
2
+ "error_patterns": [
3
+ "missing properties: 'profile_definition'"
4
+ ]
5
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_mult_errors.json
new
+6
@@ -0,0 +1,6 @@
1
+{
2
+ "profile_definition": {
3
+ "name": 123,
4
+ "unknown_field": "abc"
5
+ }
6
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_mult_errors_exp.json
new
+6
@@ -0,0 +1,6 @@
1
+{
2
+ "error_patterns": [
3
+ "ProfileDefinition/properties/name/type\\] expected string, but got number",
4
+ "ProfileDefinition/additionalProperties] additionalProperties 'unknown_field' not allowed"
5
+ ]
6
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_symbol_field.json
new
+49
@@ -0,0 +1,49 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "apc-ups",
4
+ "extends": [
5
+ "apc.yaml"
6
+ ],
7
+ "sysobjectid": [
8
+ "1.3.6.1.4.1.318.1.*"
9
+ ],
10
+ "device": {
11
+ "vendor": "apc"
12
+ },
13
+ "metadata": {
14
+ "device": {
15
+ "fields": {
16
+ "version": {
17
+ "symbol": {
18
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.2.1.0",
19
+ "name": "upsAdvIdentFirmwareRevision"
20
+ }
21
+ },
22
+ "product_name": {
23
+ "symbol": {
24
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
25
+ "name": "upsBasicIdentModel"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ },
31
+ "metrics": [
32
+ {
33
+ "MIB": "PowerNet-MIB",
34
+ "symbol": {
35
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
36
+ "name": "upsAdvBatteryNumOfBattPacks",
37
+ "unknown_field": "value"
38
+ }
39
+ }
40
+ ],
41
+ "metric_tags": [
42
+ {
43
+ "tag": "model",
44
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
45
+ "symbol": "upsBasicIdentModel"
46
+ }
47
+ ]
48
+ }
49
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_symbol_field_exp.json
new
+5
@@ -0,0 +1,5 @@
1
+{
2
+ "error_patterns": [
3
+ "defs/SymbolConfig/additionalProperties] additionalProperties 'unknown_field' not allowed"
4
+ ]
5
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_unknown_field.json
new
+6
@@ -0,0 +1,6 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "some-profile",
4
+ "unknown_field": "abc"
5
+ }
6
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/invalid_unknown_field_exp.json
new
+5
@@ -0,0 +1,5 @@
1
+{
2
+ "error_patterns": [
3
+ "additionalProperties 'unknown_field' not allowed"
4
+ ]
5
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_device_vendor.json
new
+9
@@ -0,0 +1,9 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "some-profile",
4
+ "version": 10,
5
+ "device": {
6
+ "vendor": "abc"
7
+ }
8
+ }
9
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_device_vendor_exp.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "error_patterns": [
3
+ ]
4
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_empty_profile.json
new
+6
@@ -0,0 +1,6 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "some-profile",
4
+ "version": 10
5
+ }
6
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_empty_profile_exp.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "error_patterns": [
3
+ ]
4
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_metric_tag_mapping.json
new
+45
@@ -0,0 +1,45 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "apc-ups",
4
+ "version": 10,
5
+ "extends": [
6
+ "apc.yaml"
7
+ ],
8
+ "sysobjectid": [
9
+ "1.3.6.1.4.1.318.1.*"
10
+ ],
11
+ "metrics": [
12
+ {
13
+ "MIB": "PowerNet-MIB",
14
+ "table": {
15
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2",
16
+ "name": "upsOutletGroupStatusTable"
17
+ },
18
+ "symbols": [
19
+ {
20
+ "name": "upsOutletGroupStatus",
21
+ "constant_value_one": true
22
+ },
23
+ {
24
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
25
+ "name": "upsOutletGroupStatusGroupState"
26
+ }
27
+ ],
28
+ "metric_tags": [
29
+ {
30
+ "tag": "ups_outlet_group_status_group_state",
31
+ "symbol": {
32
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
33
+ "name": "upsOutletGroupStatusGroupState"
34
+ },
35
+ "mapping": [
36
+ {"key": "1", "value": "ups_outlet_group_status_on"},
37
+ {"key": "2", "value": "ups_outlet_group_status_off"},
38
+ {"key": "3", "value": "ups_outlet_group_status_unknown"}
39
+ ]
40
+ }
41
+ ]
42
+ }
43
+ ]
44
+ }
45
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_metric_tag_mapping_exp.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "error_patterns": [
3
+ ]
4
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_profile_apc_ups.json
new
+246
@@ -0,0 +1,246 @@
1
+{
2
+ "profile_definition": {
3
+ "name": "apc-ups",
4
+ "version": 10,
5
+ "extends": [
6
+ "apc.yaml"
7
+ ],
8
+ "sysobjectid": [
9
+ "1.3.6.1.4.1.318.1.*"
10
+ ],
11
+ "device": {
12
+ "vendor": "abc"
13
+ },
14
+ "metrics": [
15
+ {
16
+ "MIB": "PowerNet-MIB",
17
+ "symbol": {
18
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.5.0",
19
+ "name": "upsAdvBatteryNumOfBattPacks"
20
+ }
21
+ },
22
+ {
23
+ "MIB": "POWERNET-MIB",
24
+ "symbol": {
25
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.6.0",
26
+ "name": "upsAdvBatteryNumOfBadBattPacks"
27
+ }
28
+ },
29
+ {
30
+ "MIB": "PowerNet-MIB",
31
+ "symbol": {
32
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.4.0",
33
+ "name": "upsAdvBatteryReplaceIndicator"
34
+ }
35
+ },
36
+ {
37
+ "MIB": "PowerNet-MIB",
38
+ "symbol": {
39
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.3.0",
40
+ "name": "upsAdvBatteryRunTimeRemaining"
41
+ }
42
+ },
43
+ {
44
+ "MIB": "PowerNet-MIB",
45
+ "symbol": {
46
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.2.0",
47
+ "name": "upsAdvBatteryTemperature"
48
+ }
49
+ },
50
+ {
51
+ "MIB": "PowerNet-MIB",
52
+ "symbol": {
53
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.2.1.0",
54
+ "name": "upsAdvBatteryCapacity"
55
+ }
56
+ },
57
+ {
58
+ "MIB": "PowerNet-MIB",
59
+ "symbol": {
60
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.3.2.0",
61
+ "name": "upsHighPrecExtdBatteryTemperature"
62
+ }
63
+ },
64
+ {
65
+ "MIB": "PowerNet-MIB",
66
+ "symbol": {
67
+ "OID": "1.3.6.1.4.1.318.1.1.1.3.2.1.0",
68
+ "name": "upsAdvInputLineVoltage"
69
+ }
70
+ },
71
+ {
72
+ "MIB": "PowerNet-MIB",
73
+ "symbol": {
74
+ "OID": "1.3.6.1.4.1.318.1.1.1.3.2.4.0",
75
+ "name": "upsAdvInputFrequency"
76
+ }
77
+ },
78
+ {
79
+ "MIB": "PowerNet-MIB",
80
+ "symbol": {
81
+ "OID": "1.3.6.1.4.1.318.1.1.1.3.3.4.0",
82
+ "name": "upsHighPrecInputFrequency"
83
+ }
84
+ },
85
+ {
86
+ "MIB": "PowerNet-MIB",
87
+ "symbol": {
88
+ "OID": "1.3.6.1.4.1.318.1.1.1.3.3.1.0",
89
+ "name": "upsHighPrecInputLineVoltage"
90
+ }
91
+ },
92
+ {
93
+ "MIB": "PowerNet-MIB",
94
+ "symbol": {
95
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.3.4.0",
96
+ "name": "upsHighPrecOutputCurrent"
97
+ }
98
+ },
99
+ {
100
+ "MIB": "PowerNet-MIB",
101
+ "symbol": {
102
+ "OID": "1.3.6.1.4.1.318.1.1.1.3.2.5.0",
103
+ "name": "upsAdvInputLineFailCause"
104
+ }
105
+ },
106
+ {
107
+ "MIB": "PowerNet-MIB",
108
+ "symbol": {
109
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.1.0",
110
+ "name": "upsAdvOutputVoltage"
111
+ }
112
+ },
113
+ {
114
+ "MIB": "PowerNet-MIB",
115
+ "symbol": {
116
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.2.0",
117
+ "name": "upsAdvOutputFrequency"
118
+ }
119
+ },
120
+ {
121
+ "MIB": "PowerNet-MIB",
122
+ "symbol": {
123
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.3.0",
124
+ "name": "upsAdvOutputLoad"
125
+ }
126
+ },
127
+ {
128
+ "MIB": "PowerNet-MIB",
129
+ "symbol": {
130
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.4.0",
131
+ "name": "upsAdvOutputCurrent"
132
+ }
133
+ },
134
+ {
135
+ "MIB": "PowerNet-MIB",
136
+ "symbol": {
137
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.8.0",
138
+ "name": "upsAdvOutputActivePower"
139
+ }
140
+ },
141
+ {
142
+ "MIB": "PowerNet-MIB",
143
+ "symbol": {
144
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.9.0",
145
+ "name": "upsAdvOutputApparentPower"
146
+ }
147
+ },
148
+ {
149
+ "MIB": "PowerNet-MIB",
150
+ "symbol": {
151
+ "OID": "1.3.6.1.4.1.318.1.1.1.4.2.12.0",
152
+ "name": "upsAdvOutputEnergyUsage"
153
+ }
154
+ },
155
+ {
156
+ "MIB": "PowerNet-MIB",
157
+ "symbol": {
158
+ "OID": "1.3.6.1.4.1.318.1.1.1.2.1.2.0",
159
+ "name": "upsBasicBatteryTimeOnBattery"
160
+ }
161
+ },
162
+ {
163
+ "MIB": "PowerNet-MIB",
164
+ "symbol": {
165
+ "OID": "1.3.6.1.4.1.318.1.1.1.7.2.3.0",
166
+ "name": "upsAdvTestDiagnosticsResults"
167
+ }
168
+ },
169
+ {
170
+ "MIB": "PowerNet-MIB",
171
+ "table": {
172
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2",
173
+ "name": "upsOutletGroupStatusTable"
174
+ },
175
+ "symbols": [
176
+ {
177
+ "name": "upsOutletGroupStatus",
178
+ "constant_value_one": true
179
+ },
180
+ {
181
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
182
+ "name": "upsOutletGroupStatusGroupState"
183
+ }
184
+ ],
185
+ "metric_tags": [
186
+ {
187
+ "tag": "outlet_group_name",
188
+ "symbol": {
189
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.2",
190
+ "name": "upsOutletGroupStatusName"
191
+ }
192
+ },
193
+ {
194
+ "tag": "ups_outlet_group_status_group_state",
195
+ "symbol": {
196
+ "OID": "1.3.6.1.4.1.318.1.1.1.12.1.2.1.3",
197
+ "name": "upsOutletGroupStatusGroupState"
198
+ },
199
+ "mapping": [
200
+ {"key": "1", "value": "ups_outlet_group_status_on"},
201
+ {"key": "2", "value": "ups_outlet_group_status_off"},
202
+ {"key": "3", "value": "ups_outlet_group_status_unknown"}
203
+ ]
204
+ }
205
+ ]
206
+ },
207
+ {
208
+ "MIB": "PowerNet-MIB",
209
+ "symbol": {
210
+ "OID": "1.3.6.1.4.1.318.1.1.25.1.2.1.6.0",
211
+ "name": "uioSensorStatusTemperatureDegC"
212
+ }
213
+ }
214
+ ],
215
+ "metric_tags": [
216
+ {
217
+ "tag": "model",
218
+ "symbol": {
219
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.1.1.0",
220
+ "name": "upsBasicIdentModel"
221
+ }
222
+ },
223
+ {
224
+ "tag": "serial_num",
225
+ "symbol": {
226
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.2.3.0",
227
+ "name": "upsAdvIdentSerialNumber"
228
+ }
229
+ },
230
+ {
231
+ "tag": "firmware_version",
232
+ "symbol": {
233
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.2.1.0",
234
+ "name": "upsAdvIdentFirmwareRevision"
235
+ }
236
+ },
237
+ {
238
+ "tag": "ups_name",
239
+ "symbol": {
240
+ "OID": "1.3.6.1.4.1.318.1.1.1.1.1.2.0",
241
+ "name": "upsBasicIdentName"
242
+ }
243
+ }
244
+ ]
245
+ }
246
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema/testcases/valid_profile_apc_ups_exp.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "error_patterns": [
3
+ ]
4
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/schema_cmd/main.go
new
+35
@@ -0,0 +1,35 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+// Package main holds main related files
7
+package main
8
+
9
+import (
10
+ "flag"
11
+ "fmt"
12
+ "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition/schema"
13
+ "os"
14
+)
15
+
16
+//go:generate go run ./main.go ../schema/profile_rc_schema.json
17
+
18
+func main() {
19
+ var output string
20
+
21
+ flag.StringVar(&output, "output", "../schema/profile_rc_schema.json", "Generate JSON schema generated file")
22
+ flag.Parse()
23
+
24
+ schemaJSON, err := schema.GenerateJSONSchema()
25
+ if err != nil {
26
+ panic(err)
27
+ }
28
+
29
+ err = os.WriteFile(output, schemaJSON, 0664)
30
+ if err != nil {
31
+ panic(err)
32
+ }
33
+
34
+ fmt.Printf("File generated at %s\n", output)
35
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/splitoids.go
new
+63
@@ -0,0 +1,63 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import "sort"
9
+
10
+// splitOIDs returns all scalar and column (i.e. table) OIDs from metrics, tags, and metadata
11
+func splitOIDs(metrics []MetricsConfig, globalTags []MetricTagConfig, metadata MetadataConfig) ([]string, []string) {
12
+ scalars := make(map[string]bool)
13
+ columns := make(map[string]bool)
14
+ // Singular metric values are scalars; metrics with .Symbols are tables,
15
+ // and their symbols and tags are both expected to be columns.
16
+ for _, metric := range metrics {
17
+ scalars[metric.Symbol.OID] = true
18
+ for _, symbolConfig := range metric.Symbols {
19
+ columns[symbolConfig.OID] = true
20
+ }
21
+ for _, metricTag := range metric.MetricTags {
22
+ columns[metricTag.Symbol.OID] = true
23
+ }
24
+ }
25
+ // Global tags are scalar by definition
26
+ for _, tag := range globalTags {
27
+ scalars[tag.Symbol.OID] = true
28
+ }
29
+ // Metadata fields are all columns except when IsMetadataResourceWithScalarOids is true
30
+ for resource, metadataConfig := range metadata {
31
+ target := columns
32
+ if IsMetadataResourceWithScalarOids(resource) {
33
+ target = scalars
34
+ }
35
+ for _, field := range metadataConfig.Fields {
36
+ target[field.Symbol.OID] = true
37
+ for _, symbol := range field.Symbols {
38
+ target[symbol.OID] = true
39
+ }
40
+ }
41
+ for _, tagConfig := range metadataConfig.IDTags {
42
+ target[tagConfig.Symbol.OID] = true
43
+ }
44
+ }
45
+ scalarValues := make([]string, 0, len(scalars))
46
+ for key := range scalars {
47
+ if key == "" {
48
+ continue
49
+ }
50
+ scalarValues = append(scalarValues, key)
51
+ }
52
+ columnValues := make([]string, 0, len(columns))
53
+ for key := range columns {
54
+ if key == "" {
55
+ continue
56
+ }
57
+ columnValues = append(columnValues, key)
58
+ }
59
+ // Sort them for deterministic testing
60
+ sort.Strings(scalarValues)
61
+ sort.Strings(columnValues)
62
+ return scalarValues, columnValues
63
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/splitoids_test.go
new
+228
@@ -0,0 +1,228 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2023-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "testing"
10
+
11
+ "github.com/stretchr/testify/assert"
12
+)
13
+
14
+func TestSplitOIDs(t *testing.T) {
15
+ type testCase struct {
16
+ name string
17
+ metrics []MetricsConfig
18
+ tags []MetricTagConfig
19
+ metadata MetadataConfig
20
+ expectedScalars []string
21
+ expectedColumns []string
22
+ }
23
+ testCases := []testCase{
24
+ {
25
+ name: "scalar metric",
26
+ metrics: []MetricsConfig{{
27
+ Symbol: SymbolConfig{
28
+ OID: "1.2.3.4",
29
+ },
30
+ }},
31
+ expectedScalars: []string{"1.2.3.4"},
32
+ }, {
33
+ name: "tabular metric",
34
+ metrics: []MetricsConfig{{
35
+ Symbols: []SymbolConfig{{OID: "1.2.3.4"}},
36
+ MetricTags: []MetricTagConfig{
37
+ {Symbol: SymbolConfigCompat{
38
+ OID: "2.3.4.5",
39
+ }},
40
+ },
41
+ }},
42
+ expectedColumns: []string{"1.2.3.4", "2.3.4.5"},
43
+ }, {
44
+ name: "tags",
45
+ tags: []MetricTagConfig{
46
+ {Symbol: SymbolConfigCompat{
47
+ OID: "2.3.4.5",
48
+ },
49
+ },
50
+ },
51
+ expectedScalars: []string{"2.3.4.5"},
52
+ }, {
53
+ name: "metadata",
54
+ metadata: map[string]MetadataResourceConfig{
55
+ "device": {
56
+ Fields: map[string]MetadataField{
57
+ "vendor": {Value: "static"},
58
+ "name": {Symbol: SymbolConfig{
59
+ OID: "1.1",
60
+ }},
61
+ "os_name": {Symbols: []SymbolConfig{
62
+ {
63
+ OID: "1.2",
64
+ }, {
65
+ OID: "1.3",
66
+ },
67
+ }},
68
+ },
69
+ IDTags: []MetricTagConfig{
70
+ {Symbol: SymbolConfigCompat{
71
+ OID: "1.4",
72
+ }},
73
+ },
74
+ },
75
+ "not_device": {
76
+ Fields: map[string]MetadataField{
77
+ "vendor": {Value: "static"},
78
+ "name": {Symbol: SymbolConfig{
79
+ OID: "2.1",
80
+ }},
81
+ "os_name": {Symbols: []SymbolConfig{
82
+ {
83
+ OID: "2.2",
84
+ }, {
85
+ OID: "2.3",
86
+ },
87
+ }},
88
+ },
89
+ IDTags: []MetricTagConfig{
90
+ {Symbol: SymbolConfigCompat{
91
+ OID: "2.4",
92
+ }},
93
+ },
94
+ },
95
+ },
96
+ expectedScalars: []string{"1.1", "1.2", "1.3", "1.4"},
97
+ expectedColumns: []string{"2.1", "2.2", "2.3", "2.4"},
98
+ }, {
99
+ name: "duplicates",
100
+ metrics: []MetricsConfig{
101
+ {
102
+ Symbol: SymbolConfig{OID: "1.1"},
103
+ }, {
104
+ Symbols: []SymbolConfig{
105
+ {OID: "1.1"},
106
+ },
107
+ MetricTags: []MetricTagConfig{
108
+ {Symbol: SymbolConfigCompat{OID: "1.1"}},
109
+ },
110
+ }},
111
+ metadata: map[string]MetadataResourceConfig{
112
+ "device": {
113
+ Fields: map[string]MetadataField{
114
+ "name": {Symbol: SymbolConfig{
115
+ OID: "1.1",
116
+ }},
117
+ },
118
+ },
119
+ },
120
+ expectedScalars: []string{"1.1"},
121
+ expectedColumns: []string{"1.1"},
122
+ }, {
123
+ name: "sorting",
124
+ metrics: []MetricsConfig{
125
+ {Symbol: SymbolConfig{OID: "1.2"}},
126
+ {Symbol: SymbolConfig{OID: "1.1"}},
127
+ {
128
+ Symbols: []SymbolConfig{
129
+ {OID: "2.4"},
130
+ {OID: "2.3"},
131
+ },
132
+ MetricTags: []MetricTagConfig{
133
+ {Symbol: SymbolConfigCompat{OID: "2.2"}},
134
+ {Symbol: SymbolConfigCompat{OID: "2.1"}},
135
+ },
136
+ }},
137
+ expectedScalars: []string{"1.1", "1.2"},
138
+ expectedColumns: []string{"2.1", "2.2", "2.3", "2.4"},
139
+ },
140
+ }
141
+ for _, tc := range testCases {
142
+ t.Run(tc.name, func(t *testing.T) {
143
+ scalars, columns := splitOIDs(tc.metrics, tc.tags, tc.metadata)
144
+ expectedScalars := tc.expectedScalars
145
+ if expectedScalars == nil {
146
+ expectedScalars = []string{}
147
+ }
148
+ assert.Equal(t, expectedScalars, scalars)
149
+ expectedColumns := tc.expectedColumns
150
+ if expectedColumns == nil {
151
+ expectedColumns = []string{}
152
+ }
153
+ assert.Equal(t, expectedColumns, columns)
154
+ })
155
+ }
156
+}
157
+
158
+func TestProfileSplitOIDs(t *testing.T) {
159
+ p := ProfileDefinition{
160
+ Metrics: []MetricsConfig{
161
+ {Symbol: SymbolConfig{OID: "1.2"}},
162
+ {Symbol: SymbolConfig{OID: "1.1"}},
163
+ {
164
+ Symbols: []SymbolConfig{
165
+ {OID: "2.4"},
166
+ {OID: "2.3"},
167
+ },
168
+ MetricTags: []MetricTagConfig{
169
+ {Symbol: SymbolConfigCompat{OID: "2.2"}},
170
+ {Symbol: SymbolConfigCompat{OID: "2.1"}},
171
+ },
172
+ },
173
+ },
174
+ MetricTags: []MetricTagConfig{
175
+ {Symbol: SymbolConfigCompat{OID: "1.4"}},
176
+ {Symbol: SymbolConfigCompat{OID: "1.3"}},
177
+ },
178
+ Metadata: map[string]MetadataResourceConfig{
179
+ "device": {
180
+ Fields: map[string]MetadataField{
181
+ "vendor": {Value: "static"},
182
+ "name": {Symbol: SymbolConfig{
183
+ OID: "3.4",
184
+ }},
185
+ "os_name": {Symbols: []SymbolConfig{
186
+ {
187
+ OID: "3.3",
188
+ }, {
189
+ OID: "3.2",
190
+ },
191
+ }},
192
+ },
193
+ IDTags: []MetricTagConfig{
194
+ {Symbol: SymbolConfigCompat{
195
+ OID: "3.1",
196
+ }},
197
+ },
198
+ },
199
+ "not_device": {
200
+ Fields: map[string]MetadataField{
201
+ "vendor": {Value: "static"},
202
+ "name": {Symbol: SymbolConfig{
203
+ OID: "4.4",
204
+ }},
205
+ "os_name": {Symbols: []SymbolConfig{
206
+ {
207
+ OID: "4.3",
208
+ }, {
209
+ OID: "4.2",
210
+ },
211
+ }},
212
+ },
213
+ IDTags: []MetricTagConfig{
214
+ {Symbol: SymbolConfigCompat{
215
+ OID: "4.1",
216
+ }},
217
+ },
218
+ },
219
+ },
220
+ }
221
+ scalars, columns := p.SplitOIDs(true)
222
+ assert.Equal(t, []string{"1.1", "1.2", "1.3", "1.4", "3.1", "3.2", "3.3", "3.4"}, scalars)
223
+ assert.Equal(t, []string{"2.1", "2.2", "2.3", "2.4", "4.1", "4.2", "4.3", "4.4"}, columns)
224
+
225
+ scalars, columns = p.SplitOIDs(false)
226
+ assert.Equal(t, []string{"1.1", "1.2", "1.3", "1.4"}, scalars)
227
+ assert.Equal(t, []string{"2.1", "2.2", "2.3", "2.4"}, columns)
228
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/validation.go
new
+250
@@ -0,0 +1,250 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "fmt"
10
+ "regexp"
11
+)
12
+
13
+var validMetadataResources = map[string]map[string]bool{
14
+ "device": {
15
+ "name": true,
16
+ "description": true,
17
+ "sys_object_id": true,
18
+ "location": true,
19
+ "serial_number": true,
20
+ "vendor": true,
21
+ "version": true,
22
+ "product_name": true,
23
+ "model": true,
24
+ "os_name": true,
25
+ "os_version": true,
26
+ "os_hostname": true,
27
+ "type": true,
28
+ },
29
+ "interface": {
30
+ "name": true,
31
+ "alias": true,
32
+ "description": true,
33
+ "mac_address": true,
34
+ "admin_status": true,
35
+ "oper_status": true,
36
+ },
37
+}
38
+
39
+// SymbolContext represent the context in which the symbol is used
40
+type SymbolContext int64
41
+
42
+// ScalarSymbol enums
43
+const (
44
+ ScalarSymbol SymbolContext = iota
45
+ ColumnSymbol
46
+ MetricTagSymbol
47
+ MetadataSymbol
48
+)
49
+
50
+// ValidateEnrichProfile validates a profile and normalizes it.
51
+func ValidateEnrichProfile(profile *ProfileDefinition) []string {
52
+ NormalizeMetrics(profile.Metrics)
53
+ // migrate legacy profile.Device.Vendor to metadata field.
54
+ // TODO this is commented out because it changes existing behavior; uncomment when we want to support this properly.
55
+ // if profile.Device.Vendor != "" {
56
+ // dev, ok := profile.Metadata["device"]
57
+ // if !ok {
58
+ // profile.Metadata["device"] = MetadataResourceConfig{
59
+ // Fields: make(map[string]MetadataField),
60
+ // }
61
+ // dev = profile.Metadata["device"]
62
+ // }
63
+ // _, ok = dev.Fields["vendor"]
64
+ // if !ok {
65
+ // dev.Fields["vendor"] = MetadataField{
66
+ // Value: profile.Device.Vendor,
67
+ // }
68
+ // }
69
+ // profile.Device.Vendor = ""
70
+ // }
71
+ errors := ValidateEnrichMetadata(profile.Metadata)
72
+ errors = append(errors, ValidateEnrichMetrics(profile.Metrics)...)
73
+ errors = append(errors, ValidateEnrichMetricTags(profile.MetricTags)...)
74
+ return errors
75
+}
76
+
77
+// ValidateEnrichMetricTags validates and normalizes metric tags
78
+func ValidateEnrichMetricTags(metricTags []MetricTagConfig) []string {
79
+ var errors []string
80
+ for i := range metricTags {
81
+ errors = append(errors, validateEnrichMetricTag(&metricTags[i])...)
82
+ }
83
+ return errors
84
+}
85
+
86
+// ValidateEnrichMetrics will validate MetricsConfig and enrich it.
87
+// Example of enrichment:
88
+// - storage of compiled regex pattern
89
+func ValidateEnrichMetrics(metrics []MetricsConfig) []string {
90
+ var errors []string
91
+ for i := range metrics {
92
+ metricConfig := &metrics[i]
93
+ if !metricConfig.IsScalar() && !metricConfig.IsColumn() {
94
+ errors = append(errors, fmt.Sprintf("either a table symbol or a scalar symbol must be provided: %#v", metricConfig))
95
+ }
96
+ if metricConfig.IsScalar() && metricConfig.IsColumn() {
97
+ errors = append(errors, fmt.Sprintf("table symbol and scalar symbol cannot be both provided: %#v", metricConfig))
98
+ }
99
+ if metricConfig.IsScalar() {
100
+ errors = append(errors, validateEnrichSymbol(&metricConfig.Symbol, ScalarSymbol)...)
101
+ }
102
+ if metricConfig.IsColumn() {
103
+ for j := range metricConfig.Symbols {
104
+ errors = append(errors, validateEnrichSymbol(&metricConfig.Symbols[j], ColumnSymbol)...)
105
+ }
106
+ if len(metricConfig.MetricTags) == 0 {
107
+ errors = append(errors, fmt.Sprintf("column symbols doesn't have a 'metric_tags' section (%+v), all its metrics will use the same tags; "+
108
+ "if the table has multiple rows, only one row will be submitted; "+
109
+ "please add at least one discriminating metric tag (such as a row index) "+
110
+ "to ensure metrics of all rows are submitted", metricConfig.Symbols))
111
+ }
112
+ for i := range metricConfig.MetricTags {
113
+ metricTag := &metricConfig.MetricTags[i]
114
+ errors = append(errors, validateEnrichMetricTag(metricTag)...)
115
+ }
116
+ }
117
+ // Setting forced_type value to metric_type value for backward compatibility
118
+ if metricConfig.MetricType == "" && metricConfig.ForcedType != "" {
119
+ metricConfig.MetricType = metricConfig.ForcedType
120
+ }
121
+ metricConfig.ForcedType = ""
122
+ }
123
+ return errors
124
+}
125
+
126
+// ValidateEnrichMetadata will validate MetadataConfig and enrich it.
127
+func ValidateEnrichMetadata(metadata MetadataConfig) []string {
128
+ var errors []string
129
+ for resName := range metadata {
130
+ _, isValidRes := validMetadataResources[resName]
131
+ if !isValidRes {
132
+ errors = append(errors, fmt.Sprintf("invalid resource: %s", resName))
133
+ } else {
134
+ res := metadata[resName]
135
+ for fieldName := range res.Fields {
136
+ _, isValidField := validMetadataResources[resName][fieldName]
137
+ if !isValidField {
138
+ errors = append(errors, fmt.Sprintf("invalid resource (%s) field: %s", resName, fieldName))
139
+ continue
140
+ }
141
+ field := res.Fields[fieldName]
142
+ for i := range field.Symbols {
143
+ errors = append(errors, validateEnrichSymbol(&field.Symbols[i], MetadataSymbol)...)
144
+ }
145
+ if field.Symbol.OID != "" {
146
+ errors = append(errors, validateEnrichSymbol(&field.Symbol, MetadataSymbol)...)
147
+ }
148
+ res.Fields[fieldName] = field
149
+ }
150
+ metadata[resName] = res
151
+ }
152
+ if resName == "device" && len(metadata[resName].IDTags) > 0 {
153
+ errors = append(errors, "device resource does not support custom id_tags")
154
+ }
155
+ for i := range metadata[resName].IDTags {
156
+ metricTag := &metadata[resName].IDTags[i]
157
+ errors = append(errors, validateEnrichMetricTag(metricTag)...)
158
+ }
159
+ }
160
+ return errors
161
+}
162
+
163
+func validateEnrichSymbol(symbol *SymbolConfig, symbolContext SymbolContext) []string {
164
+ var errors []string
165
+ if symbol.Name == "" {
166
+ errors = append(errors, fmt.Sprintf("symbol name missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
167
+ }
168
+ if symbol.OID == "" {
169
+ if symbolContext == ColumnSymbol && !symbol.ConstantValueOne {
170
+ errors = append(errors, fmt.Sprintf("symbol oid or send_as_one missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
171
+ } else if symbolContext != ColumnSymbol {
172
+ errors = append(errors, fmt.Sprintf("symbol oid missing: name=`%s` oid=`%s`", symbol.Name, symbol.OID))
173
+ }
174
+ }
175
+ if symbol.ExtractValue != "" {
176
+ pattern, err := regexp.Compile(symbol.ExtractValue)
177
+ if err != nil {
178
+ errors = append(errors, fmt.Sprintf("cannot compile `extract_value` (%s): %s", symbol.ExtractValue, err.Error()))
179
+ } else {
180
+ symbol.ExtractValueCompiled = pattern
181
+ }
182
+ }
183
+ if symbol.MatchPattern != "" {
184
+ pattern, err := regexp.Compile(symbol.MatchPattern)
185
+ if err != nil {
186
+ errors = append(errors, fmt.Sprintf("cannot compile `extract_value` (%s): %s", symbol.ExtractValue, err.Error()))
187
+ } else {
188
+ symbol.MatchPatternCompiled = pattern
189
+ }
190
+ }
191
+ if symbolContext != ColumnSymbol && symbol.ConstantValueOne {
192
+ errors = append(errors, "`constant_value_one` cannot be used outside of tables")
193
+ }
194
+ if (symbolContext != ColumnSymbol && symbolContext != ScalarSymbol) && symbol.MetricType != "" {
195
+ errors = append(errors, "`metric_type` cannot be used outside scalar/table metric symbols and metrics root")
196
+ }
197
+ return errors
198
+}
199
+func validateEnrichMetricTag(metricTag *MetricTagConfig) []string {
200
+ var errors []string
201
+ if (metricTag.Column.OID != "" || metricTag.Column.Name != "") && (metricTag.Symbol.OID != "" || metricTag.Symbol.Name != "") {
202
+ errors = append(errors, fmt.Sprintf("metric tag symbol and column cannot be both declared: symbol=%v, column=%v", metricTag.Symbol, metricTag.Column))
203
+ }
204
+
205
+ // Move deprecated metricTag.Column to metricTag.Symbol
206
+ if metricTag.Column.OID != "" || metricTag.Column.Name != "" {
207
+ metricTag.Symbol = SymbolConfigCompat(metricTag.Column)
208
+ metricTag.Column = SymbolConfig{}
209
+ }
210
+
211
+ // OID/Name to Symbol harmonization:
212
+ // When users declare metric tag like:
213
+ // metric_tags:
214
+ // - OID: 1.2.3
215
+ // symbol: aSymbol
216
+ // this will lead to OID stored as MetricTagConfig.OID and name stored as MetricTagConfig.Symbol.Name
217
+ // When this happens, we harmonize by moving MetricTagConfig.OID to MetricTagConfig.Symbol.OID.
218
+ if metricTag.OID != "" && metricTag.Symbol.OID != "" {
219
+ errors = append(errors, fmt.Sprintf("metric tag OID and symbol.OID cannot be both declared: OID=%s, symbol.OID=%s", metricTag.OID, metricTag.Symbol.OID))
220
+ }
221
+ if metricTag.OID != "" && metricTag.Symbol.OID == "" {
222
+ metricTag.Symbol.OID = metricTag.OID
223
+ metricTag.OID = ""
224
+ }
225
+ if metricTag.Symbol.OID != "" || metricTag.Symbol.Name != "" {
226
+ symbol := SymbolConfig(metricTag.Symbol)
227
+ errors = append(errors, validateEnrichSymbol(&symbol, MetricTagSymbol)...)
228
+ metricTag.Symbol = SymbolConfigCompat(symbol)
229
+ }
230
+ if metricTag.Match != "" {
231
+ pattern, err := regexp.Compile(metricTag.Match)
232
+ if err != nil {
233
+ errors = append(errors, fmt.Sprintf("cannot compile `match` (`%s`): %s", metricTag.Match, err.Error()))
234
+ } else {
235
+ metricTag.Pattern = pattern
236
+ }
237
+ if len(metricTag.Tags) == 0 {
238
+ errors = append(errors, fmt.Sprintf("`tags` mapping must be provided if `match` (`%s`) is defined", metricTag.Match))
239
+ }
240
+ }
241
+ if len(metricTag.Mapping) > 0 && metricTag.Tag == "" {
242
+ errors = append(errors, fmt.Sprintf("``tag` must be provided if `mapping` (`%s`) is defined", metricTag.Mapping))
243
+ }
244
+ for _, transform := range metricTag.IndexTransform {
245
+ if transform.Start > transform.End {
246
+ errors = append(errors, fmt.Sprintf("transform rule end should be greater than start. Invalid rule: %#v", transform))
247
+ }
248
+ }
249
+ return errors
250
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/validation_test.go
new
+871
@@ -0,0 +1,871 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "fmt"
10
+ "regexp"
11
+ "testing"
12
+
13
+ "github.com/stretchr/testify/assert"
14
+)
15
+
16
+func Test_ValidateEnrichMetrics(t *testing.T) {
17
+ tests := []struct {
18
+ name string
19
+ metrics []MetricsConfig
20
+ expectedErrors []string
21
+ expectedMetrics []MetricsConfig
22
+ }{
23
+ {
24
+ name: "either table symbol or scalar symbol must be provided",
25
+ metrics: []MetricsConfig{
26
+ {},
27
+ },
28
+ expectedErrors: []string{
29
+ "either a table symbol or a scalar symbol must be provided",
30
+ },
31
+ expectedMetrics: []MetricsConfig{
32
+ {},
33
+ },
34
+ },
35
+ {
36
+ name: "table column symbols and scalar symbol cannot be both provided",
37
+ metrics: []MetricsConfig{
38
+ {
39
+ Symbol: SymbolConfig{
40
+ OID: "1.2",
41
+ Name: "abc",
42
+ },
43
+ Symbols: []SymbolConfig{
44
+ {
45
+ OID: "1.2",
46
+ Name: "abc",
47
+ },
48
+ },
49
+ MetricTags: MetricTagConfigList{
50
+ MetricTagConfig{},
51
+ },
52
+ },
53
+ },
54
+ expectedErrors: []string{
55
+ "table symbol and scalar symbol cannot be both provided",
56
+ },
57
+ },
58
+ {
59
+ name: "multiple errors",
60
+ metrics: []MetricsConfig{
61
+ {},
62
+ {
63
+ Symbol: SymbolConfig{
64
+ OID: "1.2",
65
+ Name: "abc",
66
+ },
67
+ Symbols: []SymbolConfig{
68
+ {
69
+ OID: "1.2",
70
+ Name: "abc",
71
+ },
72
+ },
73
+ MetricTags: MetricTagConfigList{
74
+ MetricTagConfig{},
75
+ },
76
+ },
77
+ },
78
+ expectedErrors: []string{
79
+ "either a table symbol or a scalar symbol must be provided",
80
+ "table symbol and scalar symbol cannot be both provided",
81
+ },
82
+ },
83
+ {
84
+ name: "missing symbol name",
85
+ metrics: []MetricsConfig{
86
+ {
87
+ Symbol: SymbolConfig{
88
+ OID: "1.2.3",
89
+ },
90
+ },
91
+ },
92
+ expectedErrors: []string{
93
+ "either a table symbol or a scalar symbol must be provided",
94
+ },
95
+ },
96
+ {
97
+ name: "table column symbol name missing",
98
+ metrics: []MetricsConfig{
99
+ {
100
+ Symbols: []SymbolConfig{
101
+ {
102
+ OID: "1.2",
103
+ },
104
+ {
105
+ Name: "abc",
106
+ },
107
+ },
108
+ MetricTags: MetricTagConfigList{
109
+ MetricTagConfig{},
110
+ },
111
+ },
112
+ },
113
+ expectedErrors: []string{
114
+ "symbol name missing: name=`` oid=`1.2`",
115
+ "symbol oid or send_as_one missing: name=`abc` oid=``",
116
+ },
117
+ },
118
+ {
119
+ name: "table external metric column tag symbol error",
120
+ metrics: []MetricsConfig{
121
+ {
122
+ Symbols: []SymbolConfig{
123
+ {
124
+ OID: "1.2",
125
+ Name: "abc",
126
+ },
127
+ },
128
+ MetricTags: MetricTagConfigList{
129
+ MetricTagConfig{
130
+ Symbol: SymbolConfigCompat{
131
+ OID: "1.2.3",
132
+ },
133
+ },
134
+ MetricTagConfig{
135
+ Symbol: SymbolConfigCompat{
136
+ Name: "abc",
137
+ },
138
+ },
139
+ },
140
+ },
141
+ },
142
+ expectedErrors: []string{
143
+ "symbol name missing: name=`` oid=`1.2.3`",
144
+ "symbol oid missing: name=`abc` oid=``",
145
+ },
146
+ },
147
+ {
148
+ name: "missing MetricTags",
149
+ metrics: []MetricsConfig{
150
+ {
151
+ Symbols: []SymbolConfig{
152
+ {
153
+ OID: "1.2",
154
+ Name: "abc",
155
+ },
156
+ },
157
+ MetricTags: MetricTagConfigList{},
158
+ },
159
+ },
160
+ expectedErrors: []string{
161
+ "column symbols doesn't have a 'metric_tags' section",
162
+ },
163
+ },
164
+ {
165
+ name: "table external metric column tag MIB error",
166
+ metrics: []MetricsConfig{
167
+ {
168
+ Symbols: []SymbolConfig{
169
+ {
170
+ OID: "1.2",
171
+ Name: "abc",
172
+ },
173
+ },
174
+ MetricTags: MetricTagConfigList{
175
+ MetricTagConfig{
176
+ Symbol: SymbolConfigCompat{
177
+ OID: "1.2.3",
178
+ },
179
+ },
180
+ MetricTagConfig{
181
+ Symbol: SymbolConfigCompat{
182
+ Name: "abc",
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ expectedErrors: []string{
189
+ "symbol name missing: name=`` oid=`1.2.3`",
190
+ "symbol oid missing: name=`abc` oid=``",
191
+ },
192
+ },
193
+ {
194
+ name: "missing match tags",
195
+ metrics: []MetricsConfig{
196
+ {
197
+ Symbols: []SymbolConfig{
198
+ {
199
+ OID: "1.2",
200
+ Name: "abc",
201
+ },
202
+ },
203
+ MetricTags: MetricTagConfigList{
204
+ MetricTagConfig{
205
+ Symbol: SymbolConfigCompat{
206
+ OID: "1.2.3",
207
+ Name: "abc",
208
+ },
209
+ Match: "([a-z])",
210
+ },
211
+ },
212
+ },
213
+ },
214
+ expectedErrors: []string{
215
+ "`tags` mapping must be provided if `match` (`([a-z])`) is defined",
216
+ },
217
+ },
218
+ {
219
+ name: "match cannot compile regex",
220
+ metrics: []MetricsConfig{
221
+ {
222
+ Symbols: []SymbolConfig{
223
+ {
224
+ OID: "1.2",
225
+ Name: "abc",
226
+ },
227
+ },
228
+ MetricTags: MetricTagConfigList{
229
+ MetricTagConfig{
230
+ Symbol: SymbolConfigCompat{
231
+ OID: "1.2.3",
232
+ Name: "abc",
233
+ },
234
+ Match: "([a-z)",
235
+ Tags: map[string]string{
236
+ "foo": "bar",
237
+ },
238
+ },
239
+ },
240
+ },
241
+ },
242
+ expectedErrors: []string{
243
+ "cannot compile `match` (`([a-z)`)",
244
+ },
245
+ },
246
+ {
247
+ name: "match cannot compile regex",
248
+ metrics: []MetricsConfig{
249
+ {
250
+ Symbols: []SymbolConfig{
251
+ {
252
+ OID: "1.2",
253
+ Name: "abc",
254
+ },
255
+ },
256
+ MetricTags: MetricTagConfigList{
257
+ MetricTagConfig{
258
+ Symbol: SymbolConfigCompat{
259
+ OID: "1.2.3",
260
+ Name: "abc",
261
+ },
262
+ Tag: "hello",
263
+ IndexTransform: []MetricIndexTransform{
264
+ {
265
+ Start: 2,
266
+ End: 1,
267
+ },
268
+ },
269
+ },
270
+ },
271
+ },
272
+ },
273
+ expectedErrors: []string{
274
+ "transform rule end should be greater than start. Invalid rule",
275
+ },
276
+ },
277
+ {
278
+ name: "compiling extract_value",
279
+ metrics: []MetricsConfig{
280
+ {
281
+ Symbol: SymbolConfig{
282
+ OID: "1.2.3",
283
+ Name: "myMetric",
284
+ ExtractValue: `(\d+)C`,
285
+ },
286
+ },
287
+ {
288
+ Symbols: []SymbolConfig{
289
+ {
290
+ OID: "1.2",
291
+ Name: "hey",
292
+ ExtractValue: `(\d+)C`,
293
+ },
294
+ },
295
+ MetricTags: MetricTagConfigList{
296
+ MetricTagConfig{
297
+ Symbol: SymbolConfigCompat{
298
+ OID: "1.2.3",
299
+ Name: "abc",
300
+ ExtractValue: `(\d+)C`,
301
+ },
302
+ Tag: "hello",
303
+ },
304
+ },
305
+ },
306
+ },
307
+ expectedMetrics: []MetricsConfig{
308
+ {
309
+ Symbol: SymbolConfig{
310
+ OID: "1.2.3",
311
+ Name: "myMetric",
312
+ ExtractValue: `(\d+)C`,
313
+ ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
314
+ },
315
+ },
316
+ {
317
+ Symbols: []SymbolConfig{
318
+ {
319
+ OID: "1.2",
320
+ Name: "hey",
321
+ ExtractValue: `(\d+)C`,
322
+ ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
323
+ },
324
+ },
325
+ MetricTags: MetricTagConfigList{
326
+ MetricTagConfig{
327
+ Symbol: SymbolConfigCompat{
328
+ OID: "1.2.3",
329
+ Name: "abc",
330
+ ExtractValue: `(\d+)C`,
331
+ ExtractValueCompiled: regexp.MustCompile(`(\d+)C`),
332
+ },
333
+ Tag: "hello",
334
+ },
335
+ },
336
+ },
337
+ },
338
+ expectedErrors: []string{},
339
+ },
340
+ {
341
+ name: "error compiling extract_value",
342
+ metrics: []MetricsConfig{
343
+ {
344
+ Symbol: SymbolConfig{
345
+ OID: "1.2.3",
346
+ Name: "myMetric",
347
+ ExtractValue: "[{",
348
+ },
349
+ },
350
+ },
351
+ expectedErrors: []string{
352
+ "cannot compile `extract_value`",
353
+ },
354
+ },
355
+ {
356
+ name: "constant_value_one usage in column symbol",
357
+ metrics: []MetricsConfig{
358
+ {
359
+ Symbols: []SymbolConfig{
360
+ {
361
+ Name: "abc",
362
+ ConstantValueOne: true,
363
+ },
364
+ },
365
+ MetricTags: MetricTagConfigList{
366
+ MetricTagConfig{
367
+ Symbol: SymbolConfigCompat{
368
+ Name: "abc",
369
+ OID: "1.2.3",
370
+ },
371
+ Tag: "hello",
372
+ },
373
+ },
374
+ },
375
+ },
376
+ expectedErrors: []string{},
377
+ },
378
+ {
379
+ name: "constant_value_one usage in scalar symbol",
380
+ metrics: []MetricsConfig{
381
+ {
382
+ Symbol: SymbolConfig{
383
+ Name: "myMetric",
384
+ ConstantValueOne: true,
385
+ },
386
+ },
387
+ },
388
+ expectedErrors: []string{
389
+ "either a table symbol or a scalar symbol must be provided",
390
+ },
391
+ },
392
+ {
393
+ name: "constant_value_one usage in scalar symbol with OID",
394
+ metrics: []MetricsConfig{
395
+ {
396
+ Symbol: SymbolConfig{
397
+ OID: "1.2.3",
398
+ Name: "myMetric",
399
+ ConstantValueOne: true,
400
+ },
401
+ },
402
+ },
403
+ expectedErrors: []string{
404
+ "`constant_value_one` cannot be used outside of tables",
405
+ },
406
+ },
407
+ {
408
+ name: "constant_value_one usage in metric tags",
409
+ metrics: []MetricsConfig{
410
+ {
411
+ Symbols: []SymbolConfig{
412
+ {
413
+ OID: "1.2",
414
+ Name: "abc",
415
+ },
416
+ },
417
+ MetricTags: MetricTagConfigList{
418
+ MetricTagConfig{
419
+ Symbol: SymbolConfigCompat{
420
+ Name: "abc",
421
+ ConstantValueOne: true,
422
+ },
423
+ Tag: "hello",
424
+ },
425
+ },
426
+ },
427
+ },
428
+ expectedErrors: []string{
429
+ "symbol oid missing",
430
+ "`constant_value_one` cannot be used outside of tables",
431
+ },
432
+ },
433
+ {
434
+ name: "metric_type usage in column symbol",
435
+ metrics: []MetricsConfig{
436
+ {
437
+ Symbols: []SymbolConfig{
438
+ {
439
+ Name: "abc",
440
+ OID: "1.2.3",
441
+ MetricType: ProfileMetricTypeCounter,
442
+ },
443
+ },
444
+ MetricTags: MetricTagConfigList{
445
+ MetricTagConfig{
446
+ Symbol: SymbolConfigCompat{
447
+ Name: "abc",
448
+ OID: "1.2.3",
449
+ },
450
+ Tag: "hello",
451
+ },
452
+ },
453
+ },
454
+ },
455
+ expectedErrors: []string{},
456
+ },
457
+ {
458
+ name: "metric_type usage in scalar symbol",
459
+ metrics: []MetricsConfig{
460
+ {
461
+ Symbol: SymbolConfig{
462
+ Name: "abc",
463
+ OID: "1.2.3",
464
+ MetricType: ProfileMetricTypeCounter,
465
+ },
466
+ },
467
+ },
468
+ expectedErrors: []string{},
469
+ },
470
+ {
471
+ name: "ERROR metric_type usage in metric_tags",
472
+ metrics: []MetricsConfig{
473
+ {
474
+ Symbols: []SymbolConfig{
475
+ {
476
+ Name: "abc",
477
+ OID: "1.2.3",
478
+ },
479
+ },
480
+ MetricTags: MetricTagConfigList{
481
+ MetricTagConfig{
482
+ Symbol: SymbolConfigCompat{
483
+ Name: "abc",
484
+ OID: "1.2.3",
485
+ MetricType: ProfileMetricTypeCounter,
486
+ },
487
+ Tag: "hello",
488
+ },
489
+ },
490
+ },
491
+ },
492
+ expectedErrors: []string{
493
+ "`metric_type` cannot be used outside scalar/table metric symbols and metrics root",
494
+ },
495
+ },
496
+ {
497
+ name: "metric root forced_type converted to metric_type",
498
+ metrics: []MetricsConfig{
499
+ {
500
+ ForcedType: ProfileMetricTypeCounter,
501
+ Symbols: []SymbolConfig{
502
+ {
503
+ Name: "abc",
504
+ OID: "1.2.3",
505
+ },
506
+ },
507
+ MetricTags: MetricTagConfigList{
508
+ MetricTagConfig{
509
+ Symbol: SymbolConfigCompat{
510
+ Name: "abc",
511
+ OID: "1.2.3",
512
+ },
513
+ Tag: "hello",
514
+ },
515
+ },
516
+ },
517
+ },
518
+ expectedMetrics: []MetricsConfig{
519
+ {
520
+ MetricType: ProfileMetricTypeCounter,
521
+ Symbols: []SymbolConfig{
522
+ {
523
+ Name: "abc",
524
+ OID: "1.2.3",
525
+ },
526
+ },
527
+ MetricTags: MetricTagConfigList{
528
+ MetricTagConfig{
529
+ Symbol: SymbolConfigCompat{
530
+ Name: "abc",
531
+ OID: "1.2.3",
532
+ },
533
+ Tag: "hello",
534
+ },
535
+ },
536
+ },
537
+ },
538
+ },
539
+ {
540
+ name: "mapping used without tag",
541
+ metrics: []MetricsConfig{
542
+ {
543
+ Symbols: []SymbolConfig{
544
+ {
545
+ OID: "1.2",
546
+ Name: "abc",
547
+ },
548
+ },
549
+ MetricTags: MetricTagConfigList{
550
+ MetricTagConfig{
551
+ Symbol: SymbolConfigCompat{
552
+ OID: "1.2",
553
+ Name: "abc",
554
+ },
555
+ Mapping: map[string]string{
556
+ "1": "abc",
557
+ "2": "def",
558
+ },
559
+ },
560
+ },
561
+ },
562
+ },
563
+ expectedErrors: []string{"`tag` must be provided if `mapping` (`map[1:abc 2:def]`) is defined"},
564
+ },
565
+ }
566
+ for _, tt := range tests {
567
+ t.Run(tt.name, func(t *testing.T) {
568
+ errors := ValidateEnrichMetrics(tt.metrics)
569
+ assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
570
+ for i := range errors {
571
+ assert.Contains(t, errors[i], tt.expectedErrors[i])
572
+ }
573
+ if tt.expectedMetrics != nil {
574
+ assert.Equal(t, tt.expectedMetrics, tt.metrics)
575
+ }
576
+ })
577
+ }
578
+}
579
+
580
+func Test_ValidateEnrichMetricTags(t *testing.T) {
581
+ tests := []struct {
582
+ name string
583
+ metrics []MetricTagConfig
584
+ expectedErrors []string
585
+ expectedMetrics []MetricTagConfig
586
+ }{
587
+ {
588
+ name: "Move OID to Symbol",
589
+ metrics: []MetricTagConfig{
590
+ {
591
+ OID: "1.2.3.4",
592
+ Symbol: SymbolConfigCompat{
593
+ Name: "mySymbol",
594
+ },
595
+ },
596
+ },
597
+ expectedMetrics: []MetricTagConfig{
598
+ {
599
+ Symbol: SymbolConfigCompat{
600
+ OID: "1.2.3.4",
601
+ Name: "mySymbol",
602
+ },
603
+ },
604
+ },
605
+ },
606
+ {
607
+ name: "Metric tag OID and symbol.OID cannot be both declared",
608
+ metrics: []MetricTagConfig{
609
+ {
610
+ OID: "1.2.3.4",
611
+ Symbol: SymbolConfigCompat{
612
+ OID: "1.2.3.5",
613
+ Name: "mySymbol",
614
+ },
615
+ },
616
+ },
617
+ expectedErrors: []string{
618
+ "metric tag OID and symbol.OID cannot be both declared",
619
+ },
620
+ },
621
+ {
622
+ name: "metric tag symbol and column cannot be both declared",
623
+ metrics: []MetricTagConfig{
624
+ {
625
+ Symbol: SymbolConfigCompat{
626
+ OID: "1.2.3.5",
627
+ Name: "mySymbol",
628
+ },
629
+ Column: SymbolConfig{
630
+ OID: "1.2.3.5",
631
+ Name: "mySymbol",
632
+ },
633
+ },
634
+ },
635
+ expectedErrors: []string{
636
+ "metric tag symbol and column cannot be both declared",
637
+ },
638
+ },
639
+ {
640
+ name: "Missing OID",
641
+ metrics: []MetricTagConfig{
642
+ {
643
+ Symbol: SymbolConfigCompat{
644
+ Name: "mySymbol",
645
+ },
646
+ },
647
+ },
648
+ expectedErrors: []string{
649
+ "symbol oid missing",
650
+ },
651
+ },
652
+ }
653
+ for _, tt := range tests {
654
+ t.Run(tt.name, func(t *testing.T) {
655
+ errors := ValidateEnrichMetricTags(tt.metrics)
656
+ assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
657
+ for i := range errors {
658
+ assert.Contains(t, errors[i], tt.expectedErrors[i])
659
+ }
660
+ if tt.expectedMetrics != nil {
661
+ assert.Equal(t, tt.expectedMetrics, tt.metrics)
662
+ }
663
+ })
664
+ }
665
+}
666
+
667
+func Test_validateEnrichMetadata(t *testing.T) {
668
+ tests := []struct {
669
+ name string
670
+ metadata MetadataConfig
671
+ expectedErrors []string
672
+ expectedMetadata MetadataConfig
673
+ }{
674
+ {
675
+ name: "both field symbol and value can be provided",
676
+ metadata: MetadataConfig{
677
+ "device": MetadataResourceConfig{
678
+ Fields: map[string]MetadataField{
679
+ "name": {
680
+ Value: "hey",
681
+ Symbol: SymbolConfig{
682
+ OID: "1.2.3",
683
+ Name: "someSymbol",
684
+ },
685
+ },
686
+ },
687
+ },
688
+ },
689
+ expectedMetadata: MetadataConfig{
690
+ "device": MetadataResourceConfig{
691
+ Fields: map[string]MetadataField{
692
+ "name": {
693
+ Value: "hey",
694
+ Symbol: SymbolConfig{
695
+ OID: "1.2.3",
696
+ Name: "someSymbol",
697
+ },
698
+ },
699
+ },
700
+ },
701
+ },
702
+ },
703
+ {
704
+ name: "invalid regex pattern for symbol",
705
+ metadata: MetadataConfig{
706
+ "device": MetadataResourceConfig{
707
+ Fields: map[string]MetadataField{
708
+ "name": {
709
+ Symbol: SymbolConfig{
710
+ OID: "1.2.3",
711
+ Name: "someSymbol",
712
+ ExtractValue: "(\\w[)",
713
+ },
714
+ },
715
+ },
716
+ },
717
+ },
718
+ expectedErrors: []string{
719
+ "cannot compile `extract_value`",
720
+ },
721
+ },
722
+ {
723
+ name: "invalid regex pattern for multiple symbols",
724
+ metadata: MetadataConfig{
725
+ "device": MetadataResourceConfig{
726
+ Fields: map[string]MetadataField{
727
+ "name": {
728
+ Symbols: []SymbolConfig{
729
+ {
730
+ OID: "1.2.3",
731
+ Name: "someSymbol",
732
+ ExtractValue: "(\\w[)",
733
+ },
734
+ },
735
+ },
736
+ },
737
+ },
738
+ },
739
+ expectedErrors: []string{
740
+ "cannot compile `extract_value`",
741
+ },
742
+ },
743
+ {
744
+ name: "field regex pattern is compiled",
745
+ metadata: MetadataConfig{
746
+ "device": MetadataResourceConfig{
747
+ Fields: map[string]MetadataField{
748
+ "name": {
749
+ Symbol: SymbolConfig{
750
+ OID: "1.2.3",
751
+ Name: "someSymbol",
752
+ ExtractValue: "(\\w)",
753
+ },
754
+ },
755
+ },
756
+ },
757
+ },
758
+ expectedErrors: []string{},
759
+ expectedMetadata: MetadataConfig{
760
+ "device": MetadataResourceConfig{
761
+ Fields: map[string]MetadataField{
762
+ "name": {
763
+ Symbol: SymbolConfig{
764
+ OID: "1.2.3",
765
+ Name: "someSymbol",
766
+ ExtractValue: "(\\w)",
767
+ ExtractValueCompiled: regexp.MustCompile(`(\w)`),
768
+ },
769
+ },
770
+ },
771
+ },
772
+ },
773
+ },
774
+ {
775
+ name: "invalid resource",
776
+ metadata: MetadataConfig{
777
+ "invalid-res": MetadataResourceConfig{
778
+ Fields: map[string]MetadataField{
779
+ "name": {
780
+ Value: "hey",
781
+ },
782
+ },
783
+ },
784
+ },
785
+ expectedErrors: []string{
786
+ "invalid resource: invalid-res",
787
+ },
788
+ },
789
+ {
790
+ name: "invalid field",
791
+ metadata: MetadataConfig{
792
+ "device": MetadataResourceConfig{
793
+ Fields: map[string]MetadataField{
794
+ "invalid-field": {
795
+ Value: "hey",
796
+ },
797
+ },
798
+ },
799
+ },
800
+ expectedErrors: []string{
801
+ "invalid resource (device) field: invalid-field",
802
+ },
803
+ },
804
+ {
805
+ name: "invalid idtags",
806
+ metadata: MetadataConfig{
807
+ "interface": MetadataResourceConfig{
808
+ Fields: map[string]MetadataField{
809
+ "invalid-field": {
810
+ Value: "hey",
811
+ },
812
+ },
813
+ IDTags: MetricTagConfigList{
814
+ MetricTagConfig{
815
+ Symbol: SymbolConfigCompat{
816
+ OID: "1.2.3",
817
+ Name: "abc",
818
+ },
819
+ Match: "([a-z)",
820
+ Tags: map[string]string{
821
+ "foo": "bar",
822
+ },
823
+ },
824
+ },
825
+ },
826
+ },
827
+ expectedErrors: []string{
828
+ "invalid resource (interface) field: invalid-field",
829
+ "cannot compile `match` (`([a-z)`)",
830
+ },
831
+ },
832
+ {
833
+ name: "device resource does not support id_tags",
834
+ metadata: MetadataConfig{
835
+ "device": MetadataResourceConfig{
836
+ Fields: map[string]MetadataField{
837
+ "name": {
838
+ Value: "hey",
839
+ },
840
+ },
841
+ IDTags: MetricTagConfigList{
842
+ MetricTagConfig{
843
+ Symbol: SymbolConfigCompat{
844
+ OID: "1.2.3",
845
+ Name: "abc",
846
+ },
847
+ Tag: "abc",
848
+ },
849
+ },
850
+ },
851
+ },
852
+ expectedErrors: []string{
853
+ "device resource does not support custom id_tags",
854
+ },
855
+ },
856
+ }
857
+ for _, tt := range tests {
858
+ t.Run(tt.name, func(t *testing.T) {
859
+ errors := ValidateEnrichMetadata(tt.metadata)
860
+ assert.Equal(t, len(tt.expectedErrors), len(errors), fmt.Sprintf("ERRORS: %v", errors))
861
+ for i := range errors {
862
+ assert.Contains(t, errors[i], tt.expectedErrors[i])
863
+ }
864
+ if tt.expectedMetadata != nil {
865
+ assert.Equal(t, tt.expectedMetadata, tt.metadata)
866
+ }
867
+ })
868
+ }
869
+}
870
+
871
+// TODO: Add test for ValidateEnrichMetricTags
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/yaml_utils.go
new
+64
@@ -0,0 +1,64 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+// StringArray is list of string with a yaml un-marshaller that support both array and string.
9
+// See test file for example usage.
10
+// Credit: https://github.com/go-yaml/yaml/issues/100#issuecomment-324964723
11
+type StringArray []string
12
+
13
+// UnmarshalYAML unmarshalls StringArray
14
+func (a *StringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
15
+ var multi []string
16
+ err := unmarshal(&multi)
17
+ if err != nil {
18
+ var single string
19
+ err := unmarshal(&single)
20
+ if err != nil {
21
+ return err
22
+ }
23
+ *a = []string{single}
24
+ } else {
25
+ *a = multi
26
+ }
27
+ return nil
28
+}
29
+
30
+// UnmarshalYAML unmarshalls SymbolConfig
31
+func (a *SymbolConfigCompat) UnmarshalYAML(unmarshal func(interface{}) error) error {
32
+ var symbol SymbolConfig
33
+ err := unmarshal(&symbol)
34
+ if err != nil {
35
+ var str string
36
+ err := unmarshal(&str)
37
+ if err != nil {
38
+ return err
39
+ }
40
+ *a = SymbolConfigCompat(SymbolConfig{Name: str})
41
+ } else {
42
+ *a = SymbolConfigCompat(symbol)
43
+ }
44
+ return nil
45
+}
46
+
47
+// UnmarshalYAML unmarshalls MetricTagConfigList
48
+func (mtcl *MetricTagConfigList) UnmarshalYAML(unmarshal func(interface{}) error) error {
49
+ var multi []MetricTagConfig
50
+ err := unmarshal(&multi)
51
+ if err != nil {
52
+ var tags []string
53
+ err := unmarshal(&tags)
54
+ if err != nil {
55
+ return err
56
+ }
57
+ multi = []MetricTagConfig{}
58
+ for _, tag := range tags {
59
+ multi = append(multi, MetricTagConfig{SymbolTag: tag})
60
+ }
61
+ }
62
+ *mtcl = multi
63
+ return nil
64
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition/yaml_utils_test.go
new
+94
@@ -0,0 +1,94 @@
1
+// Unless explicitly stated otherwise all files in this repository are licensed
2
+// under the Apache License Version 2.0.
3
+// This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+// Copyright 2016-present Datadog, Inc.
5
+
6
+package ddprofiledefinition
7
+
8
+import (
9
+ "testing"
10
+
11
+ "gopkg.in/yaml.v2"
12
+
13
+ "github.com/stretchr/testify/assert"
14
+)
15
+
16
+type MyStringArray struct {
17
+ SomeIDs StringArray `yaml:"my_field"`
18
+}
19
+
20
+type MySymbolStruct struct {
21
+ SymbolField SymbolConfigCompat `yaml:"my_symbol_field"`
22
+}
23
+
24
+func Test_metricTagConfig_UnmarshalYAML(t *testing.T) {
25
+ myStruct := MetricsConfig{}
26
+ expected := MetricsConfig{MetricTags: []MetricTagConfig{{Index: 3}}}
27
+
28
+ yaml.Unmarshal([]byte(`
29
+metric_tags:
30
+- index: 3
31
+`), &myStruct)
32
+
33
+ assert.Equal(t, expected, myStruct)
34
+}
35
+
36
+func Test_metricTagConfig_onlyTags(t *testing.T) {
37
+ myStruct := MetricsConfig{}
38
+ expected := MetricsConfig{MetricTags: []MetricTagConfig{{SymbolTag: "aaa"}}}
39
+
40
+ yaml.Unmarshal([]byte(`
41
+metric_tags:
42
+- aaa
43
+`), &myStruct)
44
+
45
+ assert.Equal(t, expected, myStruct)
46
+}
47
+
48
+func TestStringArray_UnmarshalYAML_array(t *testing.T) {
49
+ myStruct := MyStringArray{}
50
+ expected := MyStringArray{SomeIDs: StringArray{"aaa", "bbb"}}
51
+
52
+ yaml.Unmarshal([]byte(`
53
+my_field:
54
+ - aaa
55
+ - bbb
56
+`), &myStruct)
57
+
58
+ assert.Equal(t, expected, myStruct)
59
+}
60
+
61
+func TestStringArray_UnmarshalYAML_string(t *testing.T) {
62
+ myStruct := MyStringArray{}
63
+ expected := MyStringArray{SomeIDs: StringArray{"aaa"}}
64
+
65
+ yaml.Unmarshal([]byte(`
66
+my_field: aaa
67
+`), &myStruct)
68
+
69
+ assert.Equal(t, expected, myStruct)
70
+}
71
+
72
+func TestSymbolConfig_UnmarshalYAML_symbolObject(t *testing.T) {
73
+ myStruct := MySymbolStruct{}
74
+ expected := MySymbolStruct{SymbolField: SymbolConfigCompat{OID: "1.2.3", Name: "aSymbol"}}
75
+
76
+ yaml.Unmarshal([]byte(`
77
+my_symbol_field:
78
+ name: aSymbol
79
+ OID: 1.2.3
80
+`), &myStruct)
81
+
82
+ assert.Equal(t, expected, myStruct)
83
+}
84
+
85
+func TestSymbolConfig_UnmarshalYAML_symbolString(t *testing.T) {
86
+ myStruct := MySymbolStruct{}
87
+ expected := MySymbolStruct{SymbolField: SymbolConfigCompat{Name: "aSymbol"}}
88
+
89
+ yaml.Unmarshal([]byte(`
90
+my_symbol_field: aSymbol
91
+`), &myStruct)
92
+
93
+ assert.Equal(t, expected, myStruct)
94
+}
src/go/plugin/go.d/collector/snmp/ddsnmp/profile.go
+10
-11
@@ -5,9 +5,8 @@ package ddsnmp
5
import (
6
"errors"
7
8
- "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
9
-
8
"github.com/netdata/netdata/go/plugins/pkg/matcher"
9
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
10
)
11
12
func Find(sysObjId string) []*Profile {
@@ -30,8 +29,8 @@ func Find(sysObjId string) []*Profile {
29
}
30
31
type Profile struct {
33
- SourceFile string `yaml:"-"`
34
- Definition *profiledefinition.ProfileDefinition `yaml:",inline"`
32
+ SourceFile string `yaml:"-"`
33
+ Definition *ddprofiledefinition.ProfileDefinition `yaml:",inline"`
34
}
35
36
func (p *Profile) clone() *Profile {
@@ -47,19 +46,19 @@ func (p *Profile) merge(base *Profile) {
46
p.Definition.StaticTags = append(p.Definition.StaticTags, base.Definition.StaticTags...)
47
48
if p.Definition.Metadata == nil {
50
- p.Definition.Metadata = make(profiledefinition.MetadataConfig)
49
+ p.Definition.Metadata = make(ddprofiledefinition.MetadataConfig)
50
}
51
52
for resName, baseRes := range base.Definition.Metadata {
53
targetRes, exists := p.Definition.Metadata[resName]
54
if !exists {
56
- targetRes = profiledefinition.NewMetadataResourceConfig()
55
+ targetRes = ddprofiledefinition.NewMetadataResourceConfig()
56
}
57
58
targetRes.IDTags = append(targetRes.IDTags, baseRes.IDTags...)
59
60
if targetRes.Fields == nil && len(baseRes.Fields) > 0 {
62
- targetRes.Fields = make(map[string]profiledefinition.MetadataField, len(baseRes.Fields))
61
+ targetRes.Fields = make(map[string]ddprofiledefinition.MetadataField, len(baseRes.Fields))
62
}
63
64
for field, symbol := range baseRes.Fields {
@@ -73,11 +72,11 @@ func (p *Profile) merge(base *Profile) {
72
}
73
74
func (p *Profile) validate() error {
76
- profiledefinition.NormalizeMetrics(p.Definition.Metrics)
75
+ ddprofiledefinition.NormalizeMetrics(p.Definition.Metrics)
76
78
- errs := profiledefinition.ValidateEnrichMetadata(p.Definition.Metadata)
79
- errs = append(errs, profiledefinition.ValidateEnrichMetrics(p.Definition.Metrics)...)
80
- errs = append(errs, profiledefinition.ValidateEnrichMetricTags(p.Definition.MetricTags)...)
77
+ errs := ddprofiledefinition.ValidateEnrichMetadata(p.Definition.Metadata)
78
+ errs = append(errs, ddprofiledefinition.ValidateEnrichMetrics(p.Definition.Metrics)...)
79
+ errs = append(errs, ddprofiledefinition.ValidateEnrichMetricTags(p.Definition.MetricTags)...)
80
if len(errs) > 0 {
81
errList := make([]error, 0, len(errs))
82
for _, s := range errs {
src/go/plugin/go.d/collector/snmp/parsing.go
+37
-31
@@ -5,17 +5,18 @@ import (
5
"fmt"
6
"regexp"
7
8
- "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
8
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
9
)
10
11
-func parseMetrics(metrics []profiledefinition.MetricsConfig) (parsedResult, error) {
12
- oids := []string{}
13
- next_oids := []string{}
14
- bulk_oids := []string{}
15
- parsed_metrics := []parsedMetric{}
16
- oids_to_resolve := []map[string]string{}
17
- indexes_to_resolve := []indexMapping{}
18
- bulk_threshold := 0
11
+func parseMetrics(metrics []ddprofiledefinition.MetricsConfig) (parsedResult, error) {
12
+ var (
13
+ OIDs, nextOIDs, bulkOIDs []string
14
+ OIDsToResolve []map[string]string
15
+ parsedMetrics []parsedMetric
16
+ indexesToResolve []indexMapping
17
+ )
18
+
19
+ bulkThreshold := 0
20
for _, metric := range metrics {
21
result, err := parseMetric(metric)
22
@@ -23,33 +24,36 @@ func parseMetrics(metrics []profiledefinition.MetricsConfig) (parsedResult, erro
24
return parsedResult{}, err
25
}
26
26
- oids = append(oids, result.oidsToFetch...)
27
+ OIDs = append(OIDs, result.oidsToFetch...)
28
29
for name, oid := range result.oidsToResolve {
30
// here in the python implementation a registration happens to their OIDResolver. I will not support this atm
30
- oids_to_resolve = append(oids_to_resolve, map[string]string{name: oid})
31
+ OIDsToResolve = append(OIDsToResolve, map[string]string{name: oid})
32
}
33
34
// here in the python implementation a registration happens to their OIDResolver. I will not support this atm
34
- indexes_to_resolve = append(indexes_to_resolve, result.indexMappings...)
35
+ indexesToResolve = append(indexesToResolve, result.indexMappings...)
36
37
for _, batch := range result.tableBatches {
37
- should_query_in_bulk := bulk_threshold > 0 && len(batch.oids) > bulk_threshold
38
+ should_query_in_bulk := bulkThreshold > 0 && len(batch.oids) > bulkThreshold
39
if should_query_in_bulk {
39
- bulk_oids = append(bulk_oids, batch.tableOID)
40
+ bulkOIDs = append(bulkOIDs, batch.tableOID)
41
} else {
41
- next_oids = append(next_oids, batch.oids...)
42
+ nextOIDs = append(nextOIDs, batch.oids...)
43
}
44
}
45
45
- parsed_metrics = append(parsed_metrics, result.parsedMetrics...)
46
+ parsedMetrics = append(parsedMetrics, result.parsedMetrics...)
47
48
}
48
- return parsedResult{oids: oids,
49
- next_oids: next_oids, bulk_oids: bulk_oids, parsed_metrics: parsed_metrics}, nil
49
+ return parsedResult{
50
+ OIDs: OIDs,
51
+ nextOIDs: nextOIDs,
52
+ bulkOIDs: bulkOIDs,
53
+ parsedMetrics: parsedMetrics}, nil
54
}
55
52
-func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, error) {
56
+func parseMetric(metric ddprofiledefinition.MetricsConfig) (metricParseResult, error) {
57
/*Can either be:
58
59
* An OID metric:
@@ -92,7 +96,7 @@ func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, err
96
97
} else if len(metric.MIB) == 0 {
98
return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
95
- } else if metric.Symbol != (profiledefinition.SymbolConfig{}) {
99
+ } else if metric.Symbol != (ddprofiledefinition.SymbolConfig{}) {
100
// Single Metric
101
return (parseSymbolMetric(metric.Symbol, metric.MIB)) // TODO metric tags might be needed here.
102
//Can't support tables at the moment
@@ -102,7 +106,7 @@ func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, err
106
}
107
108
// TODO error outs on functions
105
-func parseSymbolMetric(symbol profiledefinition.SymbolConfig, mib string) (metricParseResult, error) {
109
+func parseSymbolMetric(symbol ddprofiledefinition.SymbolConfig, mib string) (metricParseResult, error) {
110
/* Parse a symbol metric (= an OID in a MIB).
111
Example:
112
@@ -118,31 +122,33 @@ func parseSymbolMetric(symbol profiledefinition.SymbolConfig, mib string) (metri
122
symbol: tcpActiveOpens # require MIB syntax
123
```*/
124
121
- parsed_symbol, err := parseSymbol(mib, symbol)
125
+ parsedSymbol, err := parseSymbol(symbol)
126
if err != nil {
127
return metricParseResult{}, err
128
}
129
126
- parsed_symbol_metric := parsedSymbolMetric{
127
- name: parsed_symbol.name,
130
+ parsedSymbolMetric := parsedSymbolMetric{
131
+ name: parsedSymbol.name,
132
tags: nil,
133
forcedType: string(symbol.MetricType),
134
enforceScalar: false,
135
options: nil,
132
- extractValuePattern: parsed_symbol.extractValuePattern,
133
- baseoid: parsed_symbol.oid,
136
+ extractValuePattern: parsedSymbol.extractValuePattern,
137
+ baseoid: parsedSymbol.oid,
138
+ unit: symbol.Unit,
139
+ description: symbol.Description,
140
}
141
142
return metricParseResult{
137
- oidsToFetch: []string{parsed_symbol.oid},
138
- oidsToResolve: parsed_symbol.oidsToResolve,
139
- parsedMetrics: []parsedMetric{parsed_symbol_metric},
143
+ oidsToFetch: []string{parsedSymbol.oid},
144
+ oidsToResolve: parsedSymbol.oidsToResolve,
145
+ parsedMetrics: []parsedMetric{parsedSymbolMetric},
146
tableBatches: nil,
147
indexMappings: nil,
148
}, nil
149
}
150
145
-func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
151
+func parseSymbol(symbol interface{}) (parsedSymbol, error) {
152
/*
153
Parse an OID symbol.
154
@@ -162,7 +168,7 @@ func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
168
*/
169
170
switch s := symbol.(type) {
165
- case profiledefinition.SymbolConfig:
171
+ case ddprofiledefinition.SymbolConfig:
172
oid := s.OID
173
name := s.Name
174
if s.ExtractValue != "" {
src/go/plugin/go.d/collector/snmp/profile.go
+8
-7
@@ -2,7 +2,6 @@ package snmp
2
3
import (
4
"fmt"
5
- "log"
5
6
"github.com/gosnmp/gosnmp"
7
@@ -34,13 +33,13 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
33
return nil, err
34
}
35
37
- for _, oid := range results.oids {
36
+ for _, oid := range results.OIDs {
37
response, err := c.snmpClient.Get([]string{oid})
38
if err != nil {
39
return nil, err
40
}
41
if (response != &gosnmp.SnmpPacket{}) {
43
- for _, metric := range results.parsed_metrics {
42
+ for _, metric := range results.parsedMetrics {
43
switch s := metric.(type) {
44
case parsedSymbolMetric:
45
// find a matching metric
@@ -48,8 +47,10 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
47
metricName := s.name
48
metricType := response.Variables[0].Type
49
metricValue := response.Variables[0].Value
50
+ metricUnit := s.unit
51
+ metricDescription := s.description
52
52
- metricMap[oid] = processedMetric{oid: oid, name: metricName, value: metricValue, metric_type: metricType}
53
+ metricMap[oid] = processedMetric{oid: oid, name: metricName, value: metricValue, metricType: metricType, unit: metricUnit, description: metricDescription}
54
}
55
}
56
}
@@ -57,14 +58,14 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile)
58
}
59
}
60
60
- for _, oid := range results.next_oids {
61
+ for _, oid := range results.nextOIDs {
62
if len(oid) < 1 {
63
continue
64
}
65
if tableRows, err := c.walkOIDTree(oid); err != nil {
65
- log.Fatalf("Error walking OID tree: %v, oid %s", err, oid)
66
+ return nil, fmt.Errorf("error walking OID tree: %v, oid %s", err, oid)
67
} else {
67
- for _, metric := range results.parsed_metrics {
68
+ for _, metric := range results.parsedMetrics {
69
switch s := metric.(type) {
70
case parsedTableMetric:
71
// find a matching metric
src/go/plugin/go.d/collector/snmp/types.go
+22
-18
@@ -7,18 +7,18 @@ import (
7
)
8
9
type snmpPDU struct {
10
- value interface{}
11
- oid string
12
- metric_type gosnmp.Asn1BER
10
+ value interface{}
11
+ oid string
12
+ metricType gosnmp.Asn1BER
13
}
14
15
type SysObjectIDs []string
16
17
type parsedResult struct {
18
- oids []string
19
- next_oids []string
20
- bulk_oids []string
21
- parsed_metrics []parsedMetric
18
+ OIDs []string
19
+ nextOIDs []string
20
+ bulkOIDs []string
21
+ parsedMetrics []parsedMetric
22
}
23
24
type tableBatchKey struct {
@@ -83,6 +83,8 @@ type parsedSymbolMetric struct {
83
options map[string]string
84
extractValuePattern *regexp.Regexp
85
baseoid string //TODO consider changing this to OID, it will not have nested OIDs as it is a symbol
86
+ unit string
87
+ description string
88
}
89
90
type parsedTableMetric struct {
@@ -102,8 +104,8 @@ type parsedMetric any
104
105
// Not supported yet
106
/*type parsedSimpleMetricTag struct {
105
- name string
106
-}
107
+ name string
108
+ }
109
110
type parsedMatchMetricTag struct {
111
tags []string
@@ -111,15 +113,15 @@ symbol Symbol
113
pattern *regexp.Regexp
114
}
115
114
-type symbolTag struct {
115
- parsedMetricTag parsedMetricTag
116
- symbol string
117
-}
116
+ type symbolTag struct {
117
+ parsedMetricTag parsedMetricTag
118
+ symbol string
119
+ }
120
119
-type parsedSymbolTagsResult struct {
120
- oids []string
121
- parsedSymbolTags []symbolTag
122
-}
121
+ type parsedSymbolTagsResult struct {
122
+ oids []string
123
+ parsedSymbolTags []symbolTag
124
+ }
125
*/
126
type parsedMetricTag struct {
127
name string
@@ -146,6 +148,8 @@ type processedMetric struct {
148
oid string
149
name string
150
value interface{}
149
- metric_type gosnmp.Asn1BER
151
+ metricType gosnmp.Asn1BER
152
tableName string
153
+ unit string
154
+ description string
155
}