chore(scripts.d): remove zabbix (#21759)
Ilya Mashchenko committed
Feb 13, 2026 at 12:54 UTC
1bb58dbd8ef403a252159fd1f3de7de1c6c83760
53 files changed
+9
-23021
src/go/cmd/scriptsdplugin/main.go
-1
@@ -21,7 +21,6 @@ import (
21
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent"
22
_ "github.com/netdata/netdata/go/plugins/plugin/scripts.d/modules/nagios"
23
_ "github.com/netdata/netdata/go/plugins/plugin/scripts.d/modules/scheduler"
24
- _ "github.com/netdata/netdata/go/plugins/plugin/scripts.d/modules/zabbix"
24
)
25
26
func init() {
src/go/plugin/scripts.d/README.md
+9
-27
@@ -119,14 +119,14 @@ etc.) keep separate time-series across restarts.
119
Netdata stores integers, so scripts.d.plugin normalizes perfdata to base units
120
before emitting metrics:
121
122
-| Input unit | Canonical unit | Scaling behaviour |
123
-|------------------------------|----------------|---------------------------------------------------|
124
-| Bytes, KB, MB, GB, TB | bytes | Converted to raw bytes, divider = 1 |
125
-| Bytes per second (KB/s …) | bytes/s | Converted to bytes/s, divider = 1 |
126
-| Seconds, ms, µs, ns | seconds | Stored as nanoseconds, divider = 1 000 000 000 |
127
-| Percent (`%`) | % | Value ×1000, divider = 1000 |
128
-| Counters (`c`) | c | Stored as-is |
129
-| Any other unit or unitless | original text | Value ×1000, divider = 1000 |
122
+| Input unit | Canonical unit | Scaling behaviour |
123
+|----------------------------|----------------|------------------------------------------------|
124
+| Bytes, KB, MB, GB, TB | bytes | Converted to raw bytes, divider = 1 |
125
+| Bytes per second (KB/s …) | bytes/s | Converted to bytes/s, divider = 1 |
126
+| Seconds, ms, µs, ns | seconds | Stored as nanoseconds, divider = 1 000 000 000 |
127
+| Percent (`%`) | % | Value ×1000, divider = 1000 |
128
+| Counters (`c`) | c | Stored as-is |
129
+| Any other unit or unitless | original text | Value ×1000, divider = 1000 |
130
131
When a plugin flips between `KB` and `MB` (or `ms` and `s`) the collector still
132
publishes a single chart in base units, so there are no spurious RRD resets. If
@@ -143,9 +143,7 @@ track the configured intervals; spikes indicate the executor is falling behind
143
144
Jobs follow the same cadence controls as go.d collectors: set `update_every`
145
(seconds) inside each job definition to run faster or slower than the default
146
-60 s interval. When omitted, scripts.d picks a conservative default (currently
147
-60 s). This field applies to both Nagios and Zabbix jobs so you can mirror your
148
-existing polling schedules.
146
+60 s interval. When omitted, scripts.d picks a conservative default (currently 60 s).
147
148
### Logging over OTLP (TLS support)
149
@@ -163,22 +161,6 @@ When `tls` is `true`, the plugin always establishes a TLS 1.2 connection using
161
these settings. Set `tls: false` only for loopback collectors or other trusted
162
plaintext networks.
163
166
-### LLD lifecycle
167
-
168
-Zabbix-style jobs keep their LLD catalogs and missing-instance counters in
169
-memory. Restarting the agent or reloading the plugin resets that state, mirroring
170
-how go.d collectors reset transient discovery data. If you need longer retention,
171
-keep the plugin running continuously; no on-disk cache is maintained.
172
-
173
-### Zabbix preprocessing
174
-
175
-Dependent pipelines share the same `.steps` schema as the standalone
176
-`zabbix-preproc` library. Step `type` accepts human-readable tokens such as
177
-`jsonpath`, `csv_to_json`, or `snmp_walk_value`. The optional `error_handler`
178
-field understands the native Zabbix actions: `default`, `discard`, `set-value`,
179
-and `set-error`. When you choose `set-value` or `set-error` provide the fallback
180
-text via `error_handler_params`.
181
-
164
### Mock integration tests
165
166
A small suite of mock Nagios plugins lives under `tests/plugins/`. They exercise
src/go/plugin/scripts.d/modules/zabbix/README.md
deleted
-1
@@ -1 +0,0 @@
1
-integrations/zabbix_preprocessing.md
\ No newline at end of file
src/go/plugin/scripts.d/modules/zabbix/config_schema.json
deleted
-664
@@ -1,664 +0,0 @@
1
-{
2
- "jsonSchema": {
3
- "$schema": "http://json-schema.org/draft-07/schema#",
4
- "title": "scripts.d Zabbix job configuration",
5
- "type": "object",
6
- "additionalProperties": false,
7
- "properties": {
8
- "scheduler": {
9
- "title": "Scheduler name",
10
- "description": "Named scripts.d scheduler to execute this job (defaults to 'default')",
11
- "type": "string"
12
- },
13
- "update_every": {
14
- "title": "Update every",
15
- "description": "Polling interval in seconds (defaults to 60 when omitted)",
16
- "type": "integer",
17
- "minimum": 1
18
- },
19
- "vnode": {
20
- "title": "Virtual node",
21
- "description": "Optional vnode identifier for routing results",
22
- "type": "string"
23
- },
24
- "notes": {
25
- "title": "Notes",
26
- "type": "string"
27
- },
28
- "collection": {
29
- "$ref": "#/definitions/Collection"
30
- },
31
- "lld": {
32
- "$ref": "#/definitions/LLD"
33
- },
34
- "user_macros": {
35
- "title": "User macros",
36
- "description": "Key/value pairs exposed as $USERn$ templates (e.g. key USER1 becomes $USER1$).",
37
- "type": "object",
38
- "additionalProperties": {
39
- "type": "string"
40
- }
41
- },
42
- "dependent_pipelines": {
43
- "title": "Dependent pipelines",
44
- "type": "array",
45
- "minItems": 1,
46
- "items": {
47
- "$ref": "#/definitions/Pipeline"
48
- }
49
- }
50
- },
51
- "required": [
52
- "collection",
53
- "dependent_pipelines"
54
- ],
55
- "definitions": {
56
- "Collection": {
57
- "type": "object",
58
- "description": "Zabbix collection (master item) definition",
59
- "oneOf": [
60
- {
61
- "$ref": "#/definitions/CollectionCommand"
62
- },
63
- {
64
- "$ref": "#/definitions/CollectionHTTP"
65
- },
66
- {
67
- "$ref": "#/definitions/CollectionSNMP"
68
- }
69
- ]
70
- },
71
- "CollectionCommand": {
72
- "type": "object",
73
- "additionalProperties": false,
74
- "properties": {
75
- "type": {
76
- "const": "command"
77
- },
78
- "command": {
79
- "title": "Command",
80
- "type": "string",
81
- "minLength": 1
82
- },
83
- "args": {
84
- "title": "Arguments",
85
- "type": "array",
86
- "items": {
87
- "type": "string"
88
- }
89
- },
90
- "timeout": {
91
- "$ref": "#/definitions/Duration"
92
- },
93
- "environment": {
94
- "title": "Environment variables",
95
- "type": "object",
96
- "additionalProperties": {
97
- "type": "string"
98
- }
99
- }
100
- },
101
- "required": [
102
- "type",
103
- "command"
104
- ]
105
- },
106
- "CollectionHTTP": {
107
- "type": "object",
108
- "additionalProperties": false,
109
- "properties": {
110
- "type": {
111
- "const": "http"
112
- },
113
- "http": {
114
- "$ref": "#/definitions/HTTPConfig"
115
- },
116
- "timeout": {
117
- "$ref": "#/definitions/Duration"
118
- },
119
- "headers": {
120
- "title": "Legacy headers",
121
- "type": "object",
122
- "additionalProperties": {
123
- "type": "string"
124
- }
125
- },
126
- "body": {
127
- "type": "string"
128
- },
129
- "method": {
130
- "type": "string"
131
- },
132
- "url": {
133
- "type": "string"
134
- }
135
- },
136
- "required": [
137
- "type"
138
- ],
139
- "allOf": [
140
- {
141
- "anyOf": [
142
- {
143
- "required": [
144
- "http"
145
- ]
146
- },
147
- {
148
- "required": [
149
- "url"
150
- ]
151
- }
152
- ]
153
- }
154
- ]
155
- },
156
- "CollectionSNMP": {
157
- "type": "object",
158
- "additionalProperties": false,
159
- "properties": {
160
- "type": {
161
- "const": "snmp"
162
- },
163
- "snmp": {
164
- "$ref": "#/definitions/SNMPConfig"
165
- },
166
- "timeout": {
167
- "$ref": "#/definitions/Duration"
168
- }
169
- },
170
- "required": [
171
- "type",
172
- "snmp"
173
- ]
174
- },
175
- "HTTPConfig": {
176
- "type": "object",
177
- "additionalProperties": false,
178
- "properties": {
179
- "url": {
180
- "title": "URL",
181
- "type": "string",
182
- "format": "uri"
183
- },
184
- "method": {
185
- "title": "HTTP method",
186
- "type": "string",
187
- "default": "GET"
188
- },
189
- "headers": {
190
- "title": "Headers",
191
- "type": "object",
192
- "additionalProperties": {
193
- "type": "string"
194
- }
195
- },
196
- "body": {
197
- "type": "string"
198
- },
199
- "tls": {
200
- "title": "Force TLS",
201
- "description": "When enabled, requests are sent via HTTPS (scheme upgraded if necessary).",
202
- "type": "boolean"
203
- },
204
- "username": {
205
- "type": "string"
206
- },
207
- "password": {
208
- "type": "string"
209
- }
210
- },
211
- "required": [
212
- "url"
213
- ]
214
- },
215
- "SNMPConfig": {
216
- "type": "object",
217
- "additionalProperties": false,
218
- "properties": {
219
- "target": {
220
- "title": "Target host",
221
- "type": "string",
222
- "minLength": 1
223
- },
224
- "oid": {
225
- "title": "OID",
226
- "type": "string",
227
- "minLength": 1
228
- },
229
- "version": {
230
- "title": "SNMP version",
231
- "type": "string",
232
- "enum": [
233
- "v1",
234
- "v2c",
235
- "v3",
236
- "1",
237
- "2c",
238
- "3"
239
- ],
240
- "default": "v2c"
241
- },
242
- "community": {
243
- "type": "string"
244
- },
245
- "user": {
246
- "type": "string"
247
- },
248
- "auth_password": {
249
- "type": "string"
250
- },
251
- "privacy_password": {
252
- "type": "string"
253
- },
254
- "auth_protocol": {
255
- "type": "string",
256
- "enum": [
257
- "md5",
258
- "sha",
259
- "",
260
- null
261
- ]
262
- },
263
- "privacy_protocol": {
264
- "type": "string",
265
- "enum": [
266
- "des",
267
- "aes",
268
- "",
269
- null
270
- ]
271
- },
272
- "context": {
273
- "type": "string"
274
- }
275
- },
276
- "required": [
277
- "target",
278
- "oid"
279
- ]
280
- },
281
- "LLD": {
282
- "type": "object",
283
- "additionalProperties": false,
284
- "properties": {
285
- "steps": {
286
- "title": "Discovery steps",
287
- "type": "array",
288
- "minItems": 1,
289
- "items": {
290
- "$ref": "#/definitions/Step"
291
- }
292
- },
293
- "discovery_interval": {
294
- "title": "Discovery interval",
295
- "$ref": "#/definitions/Duration"
296
- },
297
- "instance_id_template": {
298
- "title": "Instance ID template",
299
- "type": "string"
300
- },
301
- "family": {
302
- "title": "Default family",
303
- "type": "string"
304
- },
305
- "labels": {
306
- "title": "Labels",
307
- "type": "object",
308
- "additionalProperties": {
309
- "type": "string"
310
- }
311
- },
312
- "max_missing": {
313
- "title": "Max missing",
314
- "description": "Number of consecutive discovery misses before an instance is removed",
315
- "type": "integer",
316
- "minimum": 0
317
- }
318
- }
319
- },
320
- "Pipeline": {
321
- "type": "object",
322
- "additionalProperties": false,
323
- "properties": {
324
- "name": {
325
- "title": "Pipeline name",
326
- "type": "string",
327
- "minLength": 1
328
- },
329
- "context": {
330
- "title": "Metric context",
331
- "type": "string",
332
- "minLength": 1
333
- },
334
- "title": {
335
- "title": "Chart title",
336
- "type": "string"
337
- },
338
- "dimension": {
339
- "title": "Dimension name",
340
- "type": "string",
341
- "minLength": 1
342
- },
343
- "unit": {
344
- "title": "Units",
345
- "type": "string",
346
- "minLength": 1
347
- },
348
- "family": {
349
- "title": "Chart family",
350
- "type": "string"
351
- },
352
- "chart_type": {
353
- "title": "Chart type",
354
- "type": "string",
355
- "enum": [
356
- "line",
357
- "area",
358
- "stacked"
359
- ],
360
- "default": "line"
361
- },
362
- "data_type": {
363
- "title": "Data type",
364
- "type": "string"
365
- },
366
- "precision": {
367
- "title": "Precision",
368
- "type": "integer",
369
- "minimum": 0,
370
- "default": 0
371
- },
372
- "algorithm": {
373
- "title": "Dimension algorithm",
374
- "type": "string",
375
- "enum": [
376
- "absolute",
377
- "incremental",
378
- "percentage-of-absolute-row",
379
- "percentage-of-incremental-row"
380
- ],
381
- "default": "absolute"
382
- },
383
- "steps": {
384
- "title": "Preprocessing steps",
385
- "type": "array",
386
- "minItems": 1,
387
- "items": {
388
- "$ref": "#/definitions/Step"
389
- }
390
- }
391
- },
392
- "required": [
393
- "name",
394
- "context",
395
- "dimension",
396
- "unit",
397
- "steps"
398
- ]
399
- },
400
- "Step": {
401
- "type": "object",
402
- "additionalProperties": false,
403
- "properties": {
404
- "type": {
405
- "title": "Step type",
406
- "type": "string",
407
- "enum": [
408
- "multiplier",
409
- "rtrim",
410
- "ltrim",
411
- "trim",
412
- "regex_substitution",
413
- "bool_to_decimal",
414
- "octal_to_decimal",
415
- "hex_to_decimal",
416
- "delta_value",
417
- "delta_speed",
418
- "xpath",
419
- "jsonpath",
420
- "validate_range",
421
- "validate_regex",
422
- "validate_not_regex",
423
- "error_field_json",
424
- "error_field_xml",
425
- "error_field_regex",
426
- "throttle_value",
427
- "throttle_timed_value",
428
- "javascript",
429
- "prometheus_pattern",
430
- "prometheus_to_json",
431
- "csv_to_json",
432
- "string_replace",
433
- "validate_not_supported",
434
- "xml_to_json",
435
- "snmp_walk_value",
436
- "snmp_walk_to_json",
437
- "snmp_get_value",
438
- "prometheus_to_json_multi",
439
- "jsonpath_multi",
440
- "snmp_walk_to_json_multi",
441
- "csv_to_json_multi"
442
- ]
443
- },
444
- "params": {
445
- "title": "Parameters",
446
- "type": "string"
447
- },
448
- "error_handler": {
449
- "title": "Error handler",
450
- "type": "string",
451
- "enum": [
452
- "default",
453
- "discard",
454
- "set-value",
455
- "set-error"
456
- ]
457
- },
458
- "error_handler_params": {
459
- "title": "Error handler parameters",
460
- "description": "Value used when error_handler is set to set-value or set-error",
461
- "type": "string"
462
- }
463
- },
464
- "required": [
465
- "type"
466
- ]
467
- },
468
- "Duration": {
469
- "title": "Duration",
470
- "type": "string",
471
- "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|ms|s|m|h|d))+$"
472
- }
473
- }
474
- },
475
- "uiSchema": {
476
- "uiOptions": {
477
- "fullPage": true
478
- },
479
- "ui:flavour": "tabs",
480
- "ui:options": {
481
- "tabs": [
482
- {
483
- "title": "General",
484
- "fields": [
485
- "scheduler",
486
- "update_every",
487
- "vnode",
488
- "notes"
489
- ]
490
- },
491
- {
492
- "title": "Collection",
493
- "fields": [
494
- "collection"
495
- ]
496
- },
497
- {
498
- "title": "Discovery",
499
- "fields": [
500
- "lld"
501
- ]
502
- },
503
- {
504
- "title": "Metrics Extraction",
505
- "fields": [
506
- "dependent_pipelines"
507
- ]
508
- }
509
- ]
510
- },
511
- "scheduler": {
512
- "ui:placeholder": "default"
513
- },
514
- "update_every": {
515
- "ui:placeholder": "60",
516
- "ui:help": "Seconds between runs. Leave empty to inherit the default interval."
517
- },
518
- "vnode": {
519
- "ui:placeholder": "my-vnode"
520
- },
521
- "collection": {
522
- "ui:help": "Defines how the master item collects raw data. Command collections can use standard Netdata macros as well as legacy Zabbix macros (e.g. {HOST.NAME}). HTTP/SNMP collections expose the same payload to all dependent pipelines.",
523
- "type": {
524
- "ui:widget": "radio",
525
- "ui:options": {
526
- "inline": true
527
- }
528
- },
529
- "command": {
530
- "ui:placeholder": "/usr/lib/zabbix/externalscripts/myscript.sh"
531
- },
532
- "args": {
533
- "ui:listFlavour": "list",
534
- "items": {
535
- "ui:placeholder": "{HOST.NAME}"
536
- },
537
- "ui:help": "Command arguments may use macros such as {HOST.NAME}, {#MACRO}, and positional $ARGn style placeholders."
538
- },
539
- "environment": {
540
- "ui:help": "Optional environment variables exported before executing the collection command."
541
- },
542
- "http": {
543
- "url": {
544
- "ui:placeholder": "https://example/api/status"
545
- },
546
- "body": {
547
- "ui:widget": "textarea",
548
- "ui:options": {
549
- "rows": 3
550
- }
551
- }
552
- },
553
- "snmp": {
554
- "target": {
555
- "ui:placeholder": "192.0.2.10"
556
- },
557
- "oid": {
558
- "ui:placeholder": ".1.3.6.1.2.1.25.2.3.1.6"
559
- },
560
- "community": {
561
- "ui:placeholder": "public"
562
- }
563
- }
564
- },
565
- "lld": {
566
- "ui:help": "Optional discovery pipeline. When defined, each run must output a JSON array of macro maps (e.g. [{\"{#FSNAME}\":\"/\"}]). Leave empty to emit a single default instance.",
567
- "steps": {
568
- "ui:listFlavour": "list",
569
- "items": {
570
- "type": {
571
- "ui:help": "Choose the preprocessing step (e.g. jsonpath, csv_to_json).",
572
- "ui:placeholder": "jsonpath"
573
- },
574
- "params": {
575
- "ui:widget": "textarea",
576
- "ui:help": "Parameters can reference discovery macros (e.g. {#MACRO}) and will be rendered per execution.",
577
- "ui:options": {
578
- "rows": 3
579
- }
580
- },
581
- "error_handler": {
582
- "ui:widget": "radio",
583
- "ui:options": {
584
- "inline": true
585
- }
586
- },
587
- "error_handler_params": {
588
- "ui:help": "Value used when error_handler is set to set-value or set-error"
589
- }
590
- }
591
- },
592
- "instance_id_template": {
593
- "ui:placeholder": "{#FSNAME}"
594
- },
595
- "labels": {
596
- "ui:help": "Key/value templates that become Netdata labels (e.g. region: {#REGION})."
597
- }
598
- },
599
- "dependent_pipelines": {
600
- "ui:listFlavour": "list",
601
- "ui:openEmptyItem": true,
602
- "ui:help": "Each pipeline turns the shared collection payload into a Netdata metric. Use macros ({#MACRO}, {ITEM.VALUE}) to tailor chart metadata and preprocessing steps per instance.",
603
- "items": {
604
- "ui:flavour": "tabs",
605
- "ui:options": {
606
- "tabs": [
607
- {
608
- "title": "Metadata",
609
- "fields": [
610
- "name",
611
- "context",
612
- "title",
613
- "dimension",
614
- "unit",
615
- "family",
616
- "chart_type",
617
- "data_type",
618
- "precision",
619
- "algorithm"
620
- ]
621
- },
622
- {
623
- "title": "Steps",
624
- "fields": [
625
- "steps"
626
- ]
627
- }
628
- ]
629
- },
630
- "context": {
631
- "ui:placeholder": "zabbix.fs.free"
632
- },
633
- "dimension": {
634
- "ui:placeholder": "free"
635
- },
636
- "unit": {
637
- "ui:placeholder": "MiB"
638
- },
639
- "steps": {
640
- "ui:listFlavour": "list",
641
- "items": {
642
- "type": {
643
- "ui:help": "Select the preprocessing step using the string enum (e.g. jsonpath, csv_to_json, snmp_walk_to_json).",
644
- "ui:placeholder": "jsonpath"
645
- },
646
- "params": {
647
- "ui:widget": "textarea",
648
- "ui:help": "Step parameters. Macro substitution ({#MACRO}, {ITEM.VALUE}, {ITEM.VALUE1}) occurs before execution.",
649
- "ui:options": {
650
- "rows": 3
651
- }
652
- },
653
- "error_handler": {
654
- "ui:widget": "radio",
655
- "ui:options": {
656
- "inline": true
657
- }
658
- }
659
- }
660
- }
661
- }
662
- }
663
- }
664
-}
\ No newline at end of file
src/go/plugin/scripts.d/modules/zabbix/emitter.go
deleted
-623
@@ -1,623 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "fmt"
7
- "math"
8
- "sort"
9
- "strings"
10
- "sync"
11
-
12
- "github.com/netdata/netdata/go/plugins/logger"
13
- "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
14
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/ids"
15
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
16
- pkgzabbix "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
17
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix/jobengine"
18
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
19
-)
20
-
21
-type pipelineEmitter struct {
22
- log *logger.Logger
23
- charts *module.Charts
24
-
25
- mu sync.Mutex
26
- jobs map[string]*jobState
27
- metrics map[string]int64
28
-}
29
-
30
-type jobState struct {
31
- cfg pkgzabbix.JobConfig
32
- engine *jobengine.Job
33
- instances map[string]*instanceBinding
34
- stateBinding *stateChartBinding
35
-}
36
-
37
-type instanceBinding struct {
38
- id string
39
- macros map[string]string
40
- charts map[string]*pipelineBinding
41
- state *stateChartBinding
42
-}
43
-
44
-type pipelineBinding struct {
45
- chart *module.Chart
46
- dims map[string]string
47
-}
48
-
49
-type metricUpdate struct {
50
- dimID string
51
- value int64
52
-}
53
-
54
-type stateChartBinding struct {
55
- chartID string
56
- dims map[string]string
57
-}
58
-
59
-var stateDimensions = []string{"collect_failure", "lld_failure", "extraction_failure", "dimension_failure", "ok"}
60
-
61
-func newPipelineEmitter(log *logger.Logger, proc *zpre.Preprocessor, charts *module.Charts, jobs []pkgzabbix.JobConfig) (*pipelineEmitter, error) {
62
- states := make(map[string]*jobState, len(jobs))
63
- for _, cfg := range jobs {
64
- engine, err := jobengine.NewJob(cfg, proc, jobengine.Options{Logger: log})
65
- if err != nil {
66
- return nil, err
67
- }
68
- states[cfg.Name] = &jobState{
69
- cfg: cfg,
70
- engine: engine,
71
- instances: make(map[string]*instanceBinding),
72
- }
73
- }
74
- return &pipelineEmitter{
75
- log: log,
76
- charts: charts,
77
- jobs: states,
78
- metrics: make(map[string]int64),
79
- }, nil
80
-}
81
-
82
-func (e *pipelineEmitter) Emit(job runtime.JobRuntime, res runtime.ExecutionResult, snap runtime.JobSnapshot) {
83
- state := e.getState(job.Spec.Name)
84
- if state == nil {
85
- return
86
- }
87
- input := jobengine.Input{Payload: res.Output, Timestamp: res.End}
88
- if res.Err != nil || res.ExitCode != 0 {
89
- if input.Payload == nil {
90
- input.Payload = []byte{}
91
- }
92
- input.CollectError = fmt.Errorf("collect failed: %w", res.Err)
93
- }
94
- result := state.engine.Process(input)
95
- metricUpdates, stateUpdates := e.prepareUpdates(state.cfg.Name, result)
96
- for _, upd := range metricUpdates {
97
- e.setMetric(upd.dimID, upd.value)
98
- }
99
- for _, upd := range stateUpdates {
100
- e.setMetric(upd.dimID, upd.value)
101
- }
102
-}
103
-
104
-func (e *pipelineEmitter) prepareUpdates(jobName string, result jobengine.Result) ([]metricUpdate, []metricUpdate) {
105
- e.mu.Lock()
106
- defer e.mu.Unlock()
107
- state, ok := e.jobs[jobName]
108
- if !ok {
109
- return nil, nil
110
- }
111
- for _, inst := range result.Removed {
112
- if binding, ok := state.instances[inst.ID]; ok {
113
- e.removeInstanceLocked(state.cfg, binding)
114
- delete(state.instances, inst.ID)
115
- }
116
- }
117
- for _, info := range result.Active {
118
- binding := e.ensureInstanceBindingLocked(state, info)
119
- binding.macros = cloneMacros(info.Macros)
120
- }
121
- metricUpdates := e.buildMetricUpdatesLocked(state, result.Metrics)
122
- stateUpdates := e.buildStateUpdatesLocked(state, result.State)
123
- return metricUpdates, stateUpdates
124
-}
125
-
126
-func (e *pipelineEmitter) ensureInstanceBindingLocked(state *jobState, info jobengine.InstanceInfo) *instanceBinding {
127
- binding, ok := state.instances[info.ID]
128
- if !ok {
129
- binding = &instanceBinding{id: info.ID, macros: make(map[string]string), charts: make(map[string]*pipelineBinding)}
130
- state.instances[info.ID] = binding
131
- }
132
- if binding.charts == nil {
133
- binding.charts = make(map[string]*pipelineBinding)
134
- }
135
- return binding
136
-}
137
-
138
-func (e *pipelineEmitter) buildMetricUpdatesLocked(state *jobState, metrics []jobengine.MetricResult) []metricUpdate {
139
- updates := make([]metricUpdate, 0, len(metrics))
140
- for _, mr := range metrics {
141
- inst := state.instances[mr.Instance.ID]
142
- if inst == nil {
143
- continue
144
- }
145
- binding, err := e.ensurePipelineChartLocked(state.cfg, inst, mr.Pipeline, inst.macros)
146
- if err != nil {
147
- e.logWarn("chart registration failed", state.cfg.Name, mr.Pipeline.Name, err)
148
- continue
149
- }
150
- baseName := dimensionBaseName(mr.Pipeline, inst.macros)
151
- key := dimensionKey(baseName, mr)
152
- dimensionID, ok := binding.dims[key]
153
- if !ok {
154
- chart := binding.chart
155
- if chart == nil {
156
- continue
157
- }
158
- dimName := dimensionDisplayName(baseName, mr)
159
- newDim := &module.Dim{ID: dimID(chart.ID, ids.Sanitize(key)), Name: dimName, Algo: dimAlgorithm(mr.Pipeline.Algorithm)}
160
- if err := chart.AddDim(newDim); err != nil {
161
- e.logWarn("dimension registration failed", state.cfg.Name, mr.Pipeline.Name, err)
162
- continue
163
- }
164
- dimensionID = newDim.ID
165
- binding.dims[key] = dimensionID
166
- }
167
- updates = append(updates, metricUpdate{dimID: dimensionID, value: scaleValue(mr.Value, mr.Pipeline.Precision)})
168
- }
169
- return updates
170
-}
171
-
172
-func (e *pipelineEmitter) buildStateUpdatesLocked(state *jobState, summary jobengine.StateSummary) []metricUpdate {
173
- var updates []metricUpdate
174
- binding, err := e.ensureJobStateChart(state)
175
- if err != nil {
176
- e.logWarn("state chart registration failed", state.cfg.Name, "job_state", err)
177
- } else {
178
- updates = append(updates, stateMetricUpdates(binding, summary.Job)...)
179
- }
180
- for id, inst := range state.instances {
181
- flags := summary.Instances[id]
182
- ib, err := e.ensureInstanceStateChart(state.cfg, inst)
183
- if err != nil {
184
- e.logWarn("state chart registration failed", state.cfg.Name, id, err)
185
- continue
186
- }
187
- updates = append(updates, stateMetricUpdates(ib, flags)...)
188
- }
189
- return updates
190
-}
191
-
192
-func stateMetricUpdates(binding *stateChartBinding, flags jobengine.FailureFlags) []metricUpdate {
193
- updates := make([]metricUpdate, 0, len(stateDimensions))
194
- updates = append(updates,
195
- metricUpdate{dimID: binding.dims["collect_failure"], value: boolToInt(flags.Collect)},
196
- metricUpdate{dimID: binding.dims["lld_failure"], value: boolToInt(flags.LLD)},
197
- metricUpdate{dimID: binding.dims["extraction_failure"], value: boolToInt(flags.Extraction)},
198
- metricUpdate{dimID: binding.dims["dimension_failure"], value: boolToInt(flags.Dimension)},
199
- metricUpdate{dimID: binding.dims["ok"], value: boolToInt(!flags.Collect && !flags.LLD && !flags.Extraction && !flags.Dimension)},
200
- )
201
- return updates
202
-}
203
-
204
-func (e *pipelineEmitter) Close() error {
205
- e.mu.Lock()
206
- defer e.mu.Unlock()
207
- for name := range e.jobs {
208
- e.removeJobLocked(name)
209
- }
210
- return nil
211
-}
212
-
213
-func (e *pipelineEmitter) RemoveJob(name string) {
214
- e.mu.Lock()
215
- defer e.mu.Unlock()
216
- e.removeJobLocked(name)
217
-}
218
-
219
-func (e *pipelineEmitter) removeJobLocked(name string) {
220
- state, ok := e.jobs[name]
221
- if !ok {
222
- return
223
- }
224
- if state.stateBinding != nil {
225
- _ = e.charts.Remove(state.stateBinding.chartID)
226
- state.stateBinding = nil
227
- }
228
- for _, inst := range state.instances {
229
- e.removeInstanceLocked(state.cfg, inst)
230
- }
231
- state.engine.Destroy()
232
- delete(e.jobs, name)
233
-}
234
-
235
-func (e *pipelineEmitter) Flush() map[string]int64 {
236
- e.mu.Lock()
237
- defer e.mu.Unlock()
238
- if len(e.metrics) == 0 {
239
- return nil
240
- }
241
- out := make(map[string]int64, len(e.metrics))
242
- for k, v := range e.metrics {
243
- out[k] = v
244
- }
245
- e.metrics = make(map[string]int64)
246
- return out
247
-}
248
-
249
-func (e *pipelineEmitter) setMetric(dimID string, value int64) {
250
- e.mu.Lock()
251
- defer e.mu.Unlock()
252
- e.metrics[dimID] = value
253
-}
254
-
255
-func (e *pipelineEmitter) getState(name string) *jobState {
256
- e.mu.Lock()
257
- defer e.mu.Unlock()
258
- return e.jobs[name]
259
-}
260
-
261
-func buildChart(job pkgzabbix.JobConfig, inst *instanceBinding, pipe *pkgzabbix.PipelineConfig, macros map[string]string) *module.Chart {
262
- title := "Zabbix Pipeline Data"
263
- units := expandTemplate(pipe.Unit, macros)
264
- if units == "" {
265
- units = "value"
266
- }
267
- fam := expandTemplate(pipe.Family, macros)
268
- if fam == "" {
269
- fam = expandTemplate(job.LLD.Family, macros)
270
- }
271
- if fam == "" {
272
- fam = job.Name
273
- }
274
- ctx := expandTemplate(pipe.Context, macros)
275
- if ctx == "" {
276
- ctx = pipe.Name
277
- }
278
- sanitizedCtx := ids.Sanitize(ctx)
279
- if sanitizedCtx == "" {
280
- sanitizedCtx = ids.Sanitize(pipe.Name)
281
- }
282
- dimName := expandTemplate(pipe.Dimension, macros)
283
- if dimName == "" {
284
- dimName = pipe.Name
285
- }
286
- chartIdentifier := chartID(job, inst.id, sanitizedCtx)
287
- dimensionID := dimID(chartIdentifier, dimName)
288
- chart := &module.Chart{
289
- ID: chartIdentifier,
290
- Title: title,
291
- Units: units,
292
- Fam: fam,
293
- Ctx: fmt.Sprintf("zabbix.%s", sanitizedCtx),
294
- Type: chartType(pipe.ChartType),
295
-
296
- Priority: 1000,
297
- Labels: buildLabels(job, inst, pipe, macros),
298
- Dims: module.Dims{
299
- {
300
- ID: dimensionID,
301
- Name: dimName,
302
- Algo: dimAlgorithm(pipe.Algorithm),
303
- },
304
- },
305
- }
306
- return chart
307
-}
308
-
309
-func buildJobStateChart(job pkgzabbix.JobConfig) *module.Chart {
310
- chartID := jobStateChartID(job)
311
- dims, _ := buildStateChartDims(chartID)
312
- return &module.Chart{
313
- ID: chartID,
314
- Title: "Zabbix Job State",
315
- Units: "state",
316
- Fam: job.Name,
317
- Ctx: fmt.Sprintf("zabbix.%s.state", ids.Sanitize(job.Name)),
318
- Type: module.Line,
319
-
320
- Priority: 800,
321
- Labels: jobLabels(job),
322
- Dims: dims,
323
- Opts: module.Opts{
324
- Detail: true,
325
- },
326
- }
327
-}
328
-
329
-func buildInstanceStateChart(job pkgzabbix.JobConfig, inst *instanceBinding) *module.Chart {
330
- chartID := instanceStateChartID(job, inst)
331
- macros := cloneMacros(inst.macros)
332
- dims, _ := buildStateChartDims(chartID)
333
- return &module.Chart{
334
- ID: chartID,
335
- Title: "Zabbix Instance State",
336
- Units: "state",
337
- Fam: job.Name,
338
- Ctx: fmt.Sprintf("zabbix.%s.instance.state", ids.Sanitize(job.Name)),
339
- Type: module.Line,
340
-
341
- Priority: 850,
342
- Labels: buildLabels(job, inst, nil, macros),
343
- Dims: dims,
344
- Opts: module.Opts{
345
- Detail: true,
346
- },
347
- }
348
-}
349
-
350
-func (e *pipelineEmitter) ensureJobStateChart(state *jobState) (*stateChartBinding, error) {
351
- if state.stateBinding != nil {
352
- return state.stateBinding, nil
353
- }
354
- chart := buildJobStateChart(state.cfg)
355
- if err := e.charts.Add(chart); err != nil {
356
- return nil, err
357
- }
358
- bind := newStateBinding(chart)
359
- state.stateBinding = bind
360
- return bind, nil
361
-}
362
-
363
-func (e *pipelineEmitter) ensureInstanceStateChart(job pkgzabbix.JobConfig, inst *instanceBinding) (*stateChartBinding, error) {
364
- if inst.state != nil {
365
- return inst.state, nil
366
- }
367
- chart := buildInstanceStateChart(job, inst)
368
- if err := e.charts.Add(chart); err != nil {
369
- return nil, err
370
- }
371
- bind := newStateBinding(chart)
372
- inst.state = bind
373
- return bind, nil
374
-}
375
-
376
-func newStateBinding(chart *module.Chart) *stateChartBinding {
377
- bind := &stateChartBinding{chartID: chart.ID, dims: make(map[string]string, len(stateDimensions))}
378
- for idx, name := range stateDimensions {
379
- if idx < len(chart.Dims) && chart.Dims[idx] != nil {
380
- bind.dims[name] = chart.Dims[idx].ID
381
- }
382
- }
383
- return bind
384
-}
385
-
386
-func (e *pipelineEmitter) removeInstanceLocked(cfg pkgzabbix.JobConfig, inst *instanceBinding) {
387
- if inst == nil {
388
- return
389
- }
390
- for _, binding := range inst.charts {
391
- if binding != nil && binding.chart != nil {
392
- _ = e.charts.Remove(binding.chart.ID)
393
- }
394
- }
395
- if inst.state != nil {
396
- _ = e.charts.Remove(inst.state.chartID)
397
- inst.state = nil
398
- }
399
-}
400
-
401
-func (e *pipelineEmitter) ensurePipelineChartLocked(job pkgzabbix.JobConfig, inst *instanceBinding, pipe *pkgzabbix.PipelineConfig, macros map[string]string) (*pipelineBinding, error) {
402
- binding := inst.charts[pipe.Name]
403
- if binding != nil {
404
- return binding, nil
405
- }
406
- chart := buildChart(job, inst, pipe, macros)
407
- if err := e.charts.Add(chart); err != nil {
408
- return nil, err
409
- }
410
- binding = &pipelineBinding{chart: chart, dims: make(map[string]string)}
411
- base := dimensionBaseName(pipe, macros)
412
- if len(chart.Dims) > 0 && chart.Dims[0] != nil {
413
- binding.dims[dimensionKey(base, jobengine.MetricResult{})] = chart.Dims[0].ID
414
- }
415
- inst.charts[pipe.Name] = binding
416
- return binding, nil
417
-}
418
-
419
-func buildStateChartDims(chartID string) (module.Dims, map[string]string) {
420
- dims := make(module.Dims, 0, len(stateDimensions))
421
- idMap := make(map[string]string, len(stateDimensions))
422
- for _, name := range stateDimensions {
423
- dimID := fmt.Sprintf("%s.%s", chartID, ids.Sanitize(name))
424
- dims = append(dims, &module.Dim{ID: dimID, Name: name, Algo: module.Absolute})
425
- idMap[name] = dimID
426
- }
427
- return dims, idMap
428
-}
429
-
430
-func jobStateChartID(job pkgzabbix.JobConfig) string {
431
- return fmt.Sprintf("zabbix.%s.job.state", ids.Sanitize(job.Name))
432
-}
433
-
434
-func instanceStateChartID(job pkgzabbix.JobConfig, inst *instanceBinding) string {
435
- return fmt.Sprintf("zabbix.%s.%s.state", ids.Sanitize(job.Name), inst.id)
436
-}
437
-
438
-func buildLabels(job pkgzabbix.JobConfig, inst *instanceBinding, pipe *pkgzabbix.PipelineConfig, macros map[string]string) []module.Label {
439
- labels := make([]module.Label, 0, len(job.LLD.Labels)+5)
440
- labels = append(labels, jobLabels(job)...)
441
- for key, tmpl := range job.LLD.Labels {
442
- val := expandTemplate(tmpl, macros)
443
- if val == "" {
444
- continue
445
- }
446
- labels = append(labels, module.Label{Key: key, Value: val, Source: module.LabelSourceConf})
447
- }
448
- instanceLabel := inst.id
449
- if macroID := macros["{#INSTANCE_ID}"]; macroID != "" {
450
- instanceLabel = macroID
451
- }
452
- labels = append(labels,
453
- module.Label{Key: "instance", Value: inst.id, Source: module.LabelSourceConf},
454
- module.Label{Key: "zabbix_instance", Value: instanceLabel, Source: module.LabelSourceConf},
455
- )
456
- if pipe != nil && pipe.Name != "" {
457
- labels = append(labels, module.Label{Key: "zabbix_pipeline", Value: pipe.Name, Source: module.LabelSourceConf})
458
- }
459
- return labels
460
-}
461
-
462
-func jobLabels(job pkgzabbix.JobConfig) []module.Label {
463
- labels := []module.Label{
464
- {Key: "zabbix_job", Value: job.Name, Source: module.LabelSourceConf},
465
- }
466
- scheduler := strings.TrimSpace(job.Scheduler)
467
- if scheduler != "" {
468
- labels = append(labels, module.Label{Key: "zabbix_scheduler", Value: scheduler, Source: module.LabelSourceConf})
469
- }
470
- if job.Vnode != "" {
471
- labels = append(labels, module.Label{Key: "zabbix_vnode", Value: job.Vnode, Source: module.LabelSourceConf})
472
- }
473
- collection := strings.TrimSpace(string(job.Collection.Type))
474
- if collection != "" {
475
- labels = append(labels, module.Label{Key: "zabbix_collection", Value: collection, Source: module.LabelSourceConf})
476
- }
477
- return labels
478
-}
479
-
480
-func chartID(job pkgzabbix.JobConfig, instanceID, ctx string) string {
481
- return fmt.Sprintf("zabbix.%s.%s.%s", ids.Sanitize(job.Name), instanceID, ctx)
482
-}
483
-
484
-func dimID(chartID, dimension string) string {
485
- return fmt.Sprintf("%s.%s", chartID, ids.Sanitize(dimension))
486
-}
487
-
488
-func dimensionBaseName(pipe *pkgzabbix.PipelineConfig, macros map[string]string) string {
489
- name := expandTemplate(pipe.Dimension, macros)
490
- if strings.TrimSpace(name) == "" {
491
- name = pipe.Name
492
- }
493
- if strings.TrimSpace(name) == "" {
494
- name = "value"
495
- }
496
- return name
497
-}
498
-
499
-func dimensionKey(base string, metric jobengine.MetricResult) string {
500
- parts := []string{base}
501
- if metric.Name != "" {
502
- parts = append(parts, metric.Name)
503
- }
504
- if len(metric.Labels) > 0 {
505
- keys := make([]string, 0, len(metric.Labels))
506
- for k := range metric.Labels {
507
- keys = append(keys, k)
508
- }
509
- sort.Strings(keys)
510
- for _, k := range keys {
511
- parts = append(parts, fmt.Sprintf("%s=%s", k, metric.Labels[k]))
512
- }
513
- }
514
- return strings.Join(parts, "|")
515
-}
516
-
517
-func dimensionDisplayName(base string, metric jobengine.MetricResult) string {
518
- suffix := []string{}
519
- if metric.Name != "" {
520
- suffix = append(suffix, metric.Name)
521
- }
522
- if len(metric.Labels) > 0 {
523
- keys := make([]string, 0, len(metric.Labels))
524
- for k := range metric.Labels {
525
- keys = append(keys, k)
526
- }
527
- sort.Strings(keys)
528
- pairs := make([]string, 0, len(keys))
529
- for _, k := range keys {
530
- pairs = append(pairs, fmt.Sprintf("%s=%s", k, metric.Labels[k]))
531
- }
532
- suffix = append(suffix, strings.Join(pairs, ","))
533
- }
534
- if len(suffix) == 0 {
535
- return base
536
- }
537
- return fmt.Sprintf("%s (%s)", base, strings.Join(suffix, " | "))
538
-}
539
-
540
-func expandTemplate(tmpl string, macros map[string]string) string {
541
- if tmpl == "" || len(macros) == 0 {
542
- return tmpl
543
- }
544
- res := tmpl
545
- for k, v := range macros {
546
- res = strings.ReplaceAll(res, k, v)
547
- }
548
- return res
549
-}
550
-
551
-func buildInstanceID(cfg pkgzabbix.JobConfig, macros map[string]string) string {
552
- template := cfg.LLD.InstanceTemplate
553
- if strings.TrimSpace(template) == "" {
554
- if val, ok := macros["{#INSTANCE_ID}"]; ok {
555
- template = val
556
- } else {
557
- template = fmt.Sprintf("%s_instance", cfg.Name)
558
- }
559
- }
560
- raw := expandTemplate(template, macros)
561
- if strings.TrimSpace(raw) == "" {
562
- raw = fmt.Sprintf("%s_instance", cfg.Name)
563
- }
564
- return ids.Sanitize(raw)
565
-}
566
-
567
-func cloneMacros(src map[string]string) map[string]string {
568
- out := make(map[string]string, len(src))
569
- for k, v := range src {
570
- out[k] = v
571
- }
572
- return out
573
-}
574
-
575
-func itemID(cfg pkgzabbix.JobConfig, instanceID, pipe string) string {
576
- return fmt.Sprintf("%s.%s.%s", cfg.Name, instanceID, pipe)
577
-}
578
-
579
-func scaleValue(val float64, precision int) int64 {
580
- if precision < 0 {
581
- precision = 0
582
- }
583
- factor := math.Pow10(precision)
584
- return int64(math.Round(val * factor))
585
-}
586
-
587
-func dimAlgorithm(algo string) module.DimAlgo {
588
- switch strings.ToLower(algo) {
589
- case "incremental":
590
- return module.Incremental
591
- case "percentage", "percentage-of-absolute-row":
592
- return module.PercentOfAbsolute
593
- case "percentage-of-incremental-row":
594
- return module.PercentOfIncremental
595
- default:
596
- return module.Absolute
597
- }
598
-}
599
-
600
-func chartType(kind string) module.ChartType {
601
- switch strings.ToLower(kind) {
602
- case "area":
603
- return module.Area
604
- case "stacked":
605
- return module.Stacked
606
- default:
607
- return module.Line
608
- }
609
-}
610
-
611
-func (e *pipelineEmitter) logWarn(msg, job, pipe string, err error) {
612
- if e.log == nil {
613
- return
614
- }
615
- e.log.Warningf("zabbix %s job=%s pipeline=%s error=%v", msg, job, pipe, err)
616
-}
617
-
618
-func boolToInt(b bool) int64 {
619
- if b {
620
- return 1
621
- }
622
- return 0
623
-}
src/go/plugin/scripts.d/modules/zabbix/emitter_test.go
deleted
-138
@@ -1,138 +0,0 @@
1
-package zabbix
2
-
3
-import (
4
- "errors"
5
- "fmt"
6
- "testing"
7
- "time"
8
-
9
- "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
10
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/ids"
11
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
12
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/spec"
13
- pkgzabbix "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
14
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
15
-)
16
-
17
-func TestBuildStateChartDims(t *testing.T) {
18
- dims, ids := buildStateChartDims("zabbix.job.state")
19
- if len(dims) != len(stateDimensions) {
20
- t.Fatalf("expected %d dims, got %d", len(stateDimensions), len(dims))
21
- }
22
- for _, name := range stateDimensions {
23
- id, ok := ids[name]
24
- if !ok || id == "" {
25
- t.Fatalf("missing dim mapping for %s", name)
26
- }
27
- }
28
-}
29
-
30
-func TestJobStateChart(t *testing.T) {
31
- chart := buildJobStateChart(pkgzabbix.JobConfig{Name: "fs_usage"})
32
- if chart == nil || chart.ID == "" {
33
- t.Fatalf("expected job state chart")
34
- }
35
- if len(chart.Dims) != len(stateDimensions) {
36
- t.Fatalf("expected %d dims, got %d", len(stateDimensions), len(chart.Dims))
37
- }
38
- if len(chart.Labels) == 0 {
39
- t.Fatalf("expected labels on job state chart")
40
- }
41
-}
42
-
43
-func TestInstanceStateChart(t *testing.T) {
44
- inst := &instanceBinding{id: "instance1", macros: map[string]string{"{#INSTANCE_ID}": "instance1"}}
45
- chart := buildInstanceStateChart(pkgzabbix.JobConfig{Name: "fs_usage"}, inst)
46
- if chart == nil || chart.ID == "" {
47
- t.Fatalf("expected instance state chart")
48
- }
49
- if len(chart.Dims) != len(stateDimensions) {
50
- t.Fatalf("expected %d dims, got %d", len(stateDimensions), len(chart.Dims))
51
- }
52
- if len(chart.Labels) == 0 {
53
- t.Fatalf("expected labels on instance state chart")
54
- }
55
-}
56
-
57
-func TestPipelineEmitterCollectFailureState(t *testing.T) {
58
- job := testJobConfig()
59
- emitter := newTestEmitter(job)
60
- jr := runtime.JobRuntime{ID: "job1", Spec: spec.JobSpec{Name: job.Name}}
61
- res := runtime.ExecutionResult{
62
- Job: jr,
63
- Err: errors.New("boom"),
64
- ExitCode: 2,
65
- End: time.Now(),
66
- }
67
- emitter.Emit(jr, res, runtime.JobSnapshot{})
68
- metrics := emitter.Flush()
69
- assertState(t, metrics, job, "collect_failure", 1)
70
- assertState(t, metrics, job, "ok", 0)
71
- assertInstanceState(t, metrics, job, "default", "collect_failure", 1)
72
- assertInstanceState(t, metrics, job, "default", "ok", 0)
73
-}
74
-
75
-func TestPipelineEmitterExtractionFailureState(t *testing.T) {
76
- job := testJobConfig()
77
- emitter := newTestEmitter(job)
78
- jr := runtime.JobRuntime{ID: "job2", Spec: spec.JobSpec{Name: job.Name}}
79
- res := runtime.ExecutionResult{
80
- Job: jr,
81
- Output: []byte("not-json"),
82
- End: time.Now(),
83
- }
84
- emitter.Emit(jr, res, runtime.JobSnapshot{})
85
- metrics := emitter.Flush()
86
- assertState(t, metrics, job, "extraction_failure", 1)
87
- assertState(t, metrics, job, "ok", 0)
88
- assertInstanceState(t, metrics, job, "default", "extraction_failure", 1)
89
- assertInstanceState(t, metrics, job, "default", "ok", 0)
90
-}
91
-
92
-func newTestEmitter(job pkgzabbix.JobConfig) *pipelineEmitter {
93
- charts := &module.Charts{}
94
- proc := zpre.NewPreprocessor("test-shard")
95
- emitter, err := newPipelineEmitter(nil, proc, charts, []pkgzabbix.JobConfig{job})
96
- if err != nil {
97
- panic(err)
98
- }
99
- return emitter
100
-}
101
-
102
-func testJobConfig() pkgzabbix.JobConfig {
103
- return pkgzabbix.JobConfig{
104
- Name: "fs_usage",
105
- Collection: pkgzabbix.CollectionConfig{
106
- Type: pkgzabbix.CollectionCommand,
107
- Command: "/usr/bin/true",
108
- },
109
- Pipelines: []pkgzabbix.PipelineConfig{
110
- {
111
- Name: "value",
112
- Context: "zabbix.fs_usage.value",
113
- Dimension: "value",
114
- Unit: "value",
115
- Steps: []zpre.Step{
116
- {Type: zpre.StepTypeJSONPath, Params: "$.value"},
117
- },
118
- },
119
- },
120
- }
121
-}
122
-
123
-func assertState(t *testing.T, metrics map[string]int64, job pkgzabbix.JobConfig, dim string, want int64) {
124
- chartID := jobStateChartID(job)
125
- key := fmt.Sprintf("%s.%s", chartID, ids.Sanitize(dim))
126
- if got := metrics[key]; got != want {
127
- t.Fatalf("job state %s expected %d, got %d", dim, want, got)
128
- }
129
-}
130
-
131
-func assertInstanceState(t *testing.T, metrics map[string]int64, job pkgzabbix.JobConfig, instID, dim string, want int64) {
132
- inst := &instanceBinding{id: instID}
133
- chartID := instanceStateChartID(job, inst)
134
- key := fmt.Sprintf("%s.%s", chartID, ids.Sanitize(dim))
135
- if got := metrics[key]; got != want {
136
- t.Fatalf("instance %s state %s expected %d, got %d", instID, dim, want, got)
137
- }
138
-}
src/go/plugin/scripts.d/modules/zabbix/integrations/zabbix_preprocessing.md
deleted
-236
@@ -1,236 +0,0 @@
1
-<!--startmeta
2
-custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/scripts.d/modules/zabbix/README.md"
3
-meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/scripts.d/modules/zabbix/metadata.yaml"
4
-sidebar_label: "Zabbix Preprocessing"
5
-learn_status: "Published"
6
-learn_rel_path: "Collecting Metrics/Applications"
7
-most_popular: False
8
-keywords: ['zabbix', 'preprocessing', 'scripts', 'monitoring']
9
-message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
10
-endmeta-->
11
-
12
-# Zabbix Preprocessing
13
-
14
-
15
-<img src="https://netdata.cloud/img/zabbix.svg" width="150"/>
16
-
17
-
18
-Plugin: scripts.d.plugin
19
-Module: zabbix
20
-
21
-<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
22
-
23
-## Overview
24
-
25
-This module runs Zabbix-style data collection jobs natively inside Netdata.
26
-
27
-It supports Zabbix's master-item + dependent-pipeline pattern: a single collection step
28
-(command, HTTP, or SNMP) produces raw data, and one or more **preprocessing pipelines**
29
-extract individual metrics using Zabbix-compatible preprocessing steps (JSONPath, regex,
30
-JavaScript, SNMP walk, Prometheus parsing, CSV, XPath, and more).
31
-
32
-For each configured job it collects:
33
-
34
-- **User-defined metrics**: Each dependent pipeline produces a charted metric with configurable context, unit, family, chart type, and dimension algorithm.
35
-- **Job state**: OK / WARNING / ERROR / UNKNOWN state tracking per job and per discovered instance.
36
-- **Low-level discovery (LLD)**: Optional discovery pipelines that dynamically create instances from JSON arrays, similar to Zabbix LLD.
37
-
38
-
39
-Collection supports three modes:
40
-
41
-- **Command**: Runs an external script/binary via `nd-run` and captures stdout.
42
-- **HTTP**: Performs an HTTP request and captures the response body.
43
-- **SNMP**: Queries an SNMP agent (GET or WALK) and captures the result.
44
-
45
-The raw output is then processed through Zabbix-compatible preprocessing steps to extract metrics.
46
-Zabbix macros (`{HOST.NAME}`, `{HOST.IP}`, `{#MACRO}`, etc.) are expanded before execution.
47
-
48
-
49
-This collector is supported on all platforms.
50
-
51
-This collector supports collecting metrics from multiple instances of this integration, including remote instances.
52
-
53
-Command-mode plugins run as the `netdata` user via `nd-run`.
54
-
55
-
56
-### Default Behavior
57
-
58
-#### Auto-Detection
59
-
60
-No auto-detection. Each job must be explicitly configured with a `collection` definition and one or more `dependent_pipelines`.
61
-
62
-
63
-#### Limits
64
-
65
-The default configuration for this integration does not impose any limits on data collection.
66
-
67
-#### Performance Impact
68
-
69
-Command-mode jobs spawn a subprocess per execution. HTTP and SNMP modes use in-process clients.
70
-
71
-
72
-## Metrics
73
-
74
-Metrics grouped by *scope*.
75
-
76
-The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
77
-
78
-### Virtual Node Label Conventions
79
-
80
-When a job references a `vnode`, the module reads Zabbix host macros from the virtual node's **labels** using prefix conventions:
81
-
82
-| Label key | Zabbix macro | Description |
83
-|-----------|-------------|-------------|
84
-| `_address` | `{HOST.IP}`, `{HOST.CONN}` | IP address or DNS name of the host |
85
-| `_alias` | `{HOST.ALIAS}` | Human-readable host alias |
86
-| Other `_` prefixed | N/A | Reserved for future use |
87
-
88
-The `{HOST.NAME}`, `{HOST.HOST}`, and `{HOST.DNS}` macros are derived from the vnode hostname.
89
-
90
-
91
-### Per pipeline
92
-
93
-Metrics produced by each dependent preprocessing pipeline.
94
-
95
-Labels:
96
-
97
-| Label | Description |
98
-|:-----------|:----------------|
99
-| zabbix_job | Job name. |
100
-| zabbix_pipeline | Pipeline name. |
101
-
102
-Metrics:
103
-
104
-| Metric | Dimensions | Unit |
105
-|:------|:----------|:----|
106
-| zabbix.{context} | {dimension} | varies |
107
-
108
-### Per job
109
-
110
-Per-job instance state tracking.
111
-
112
-Labels:
113
-
114
-| Label | Description |
115
-|:-----------|:----------------|
116
-| zabbix_job | Job name. |
117
-
118
-Metrics:
119
-
120
-| Metric | Dimensions | Unit |
121
-|:------|:----------|:----|
122
-| zabbix.{job}.state | ok, collect_failure, lld_failure, extraction_failure, dimension_failure | state |
123
-
124
-
125
-
126
-## Alerts
127
-
128
-There are no alerts configured by default for this integration.
129
-
130
-
131
-## Setup
132
-
133
-
134
-### Prerequisites
135
-
136
-No action required.
137
-
138
-### Configuration
139
-
140
-#### Options
141
-
142
-Each job defines a collection source and one or more dependent preprocessing pipelines.
143
-
144
-
145
-<details open><summary>Config options</summary>
146
-
147
-
148
-
149
-| Group | Option | Description | Default | Required |
150
-|:------|:-----|:------------|:--------|:---------:|
151
-| **Collection** | collection.type | Collection mode (`command`, `http`, or `snmp`). | | yes |
152
-| | collection.command | Command to execute (for `command` type). | | no |
153
-| | collection.http.url | URL to fetch (for `http` type). | | no |
154
-| | collection.snmp.target | SNMP target host (for `snmp` type). | | no |
155
-| | collection.snmp.oid | SNMP OID to query. | | no |
156
-| **Pipelines** | dependent_pipelines[].name | Pipeline name (used for chart identification). | | yes |
157
-| | dependent_pipelines[].context | Netdata chart context. | | yes |
158
-| | dependent_pipelines[].dimension | Dimension name within the chart. | | yes |
159
-| | dependent_pipelines[].unit | Metric unit string. | | yes |
160
-| | dependent_pipelines[].steps | Array of Zabbix preprocessing steps applied to the raw collection output. | [] | yes |
161
-| **General** | vnode | Virtual node name for host macro resolution. | | no |
162
-| **Discovery** | lld | Low-level discovery configuration for dynamic instance creation. | | no |
163
-
164
-
165
-</details>
166
-
167
-
168
-
169
-#### via File
170
-
171
-The configuration file name for this integration is `scripts.d/zabbix.conf`.
172
-
173
-
174
-You can edit the configuration file using the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#edit-configuration-files) script from the
175
-Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#locate-your-config-directory).
176
-
177
-```bash
178
-cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
179
-sudo ./edit-config scripts.d/zabbix.conf
180
-```
181
-
182
-##### Examples
183
-
184
-###### Command collection with JSONPath extraction
185
-
186
-Run a script and extract a metric using JSONPath.
187
-
188
-<details open><summary>Config</summary>
189
-
190
-```yaml
191
-jobs:
192
- - name: api_latency
193
- collection:
194
- type: command
195
- command: /usr/local/bin/get_api_stats.sh
196
- dependent_pipelines:
197
- - name: latency
198
- context: myapp.api.latency
199
- dimension: p99
200
- unit: milliseconds
201
- steps:
202
- - type: jsonpath
203
- params: "$.latency.p99"
204
-
205
-```
206
-</details>
207
-
208
-###### SNMP collection
209
-
210
-Query an SNMP OID and chart the result.
211
-
212
-<details open><summary>Config</summary>
213
-
214
-```yaml
215
-jobs:
216
- - name: disk_usage
217
- vnode: my-switch
218
- collection:
219
- type: snmp
220
- snmp:
221
- target: "{HOST.IP}"
222
- oid: ".1.3.6.1.2.1.25.2.3.1.6"
223
- version: v2c
224
- community: public
225
- dependent_pipelines:
226
- - name: used
227
- context: zabbix.disk.used
228
- dimension: used
229
- unit: bytes
230
- steps:
231
- - type: snmp_get_value
232
-
233
-```
234
-</details>
235
-
236
-
src/go/plugin/scripts.d/modules/zabbix/metadata.yaml
deleted
-229
@@ -1,229 +0,0 @@
1
-plugin_name: scripts.d.plugin
2
-modules:
3
- - meta:
4
- id: collector-scripts.d.plugin-zabbix
5
- plugin_name: scripts.d.plugin
6
- module_name: zabbix
7
- monitored_instance:
8
- name: Zabbix Preprocessing
9
- link: https://www.zabbix.com/
10
- icon_filename: zabbix.svg
11
- categories:
12
- - data-collection.applications
13
- related_resources:
14
- integrations:
15
- list: []
16
- info_provided_to_referring_integrations:
17
- description: ""
18
- keywords:
19
- - zabbix
20
- - preprocessing
21
- - scripts
22
- - monitoring
23
- most_popular: false
24
- overview:
25
- data_collection:
26
- metrics_description: |
27
- This module runs Zabbix-style data collection jobs natively inside Netdata.
28
-
29
- It supports Zabbix's master-item + dependent-pipeline pattern: a single collection step
30
- (command, HTTP, or SNMP) produces raw data, and one or more **preprocessing pipelines**
31
- extract individual metrics using Zabbix-compatible preprocessing steps (JSONPath, regex,
32
- JavaScript, SNMP walk, Prometheus parsing, CSV, XPath, and more).
33
-
34
- For each configured job it collects:
35
-
36
- - **User-defined metrics**: Each dependent pipeline produces a charted metric with configurable context, unit, family, chart type, and dimension algorithm.
37
- - **Job state**: OK / WARNING / ERROR / UNKNOWN state tracking per job and per discovered instance.
38
- - **Low-level discovery (LLD)**: Optional discovery pipelines that dynamically create instances from JSON arrays, similar to Zabbix LLD.
39
- method_description: |
40
- Collection supports three modes:
41
-
42
- - **Command**: Runs an external script/binary via `nd-run` and captures stdout.
43
- - **HTTP**: Performs an HTTP request and captures the response body.
44
- - **SNMP**: Queries an SNMP agent (GET or WALK) and captures the result.
45
-
46
- The raw output is then processed through Zabbix-compatible preprocessing steps to extract metrics.
47
- Zabbix macros (`{HOST.NAME}`, `{HOST.IP}`, `{#MACRO}`, etc.) are expanded before execution.
48
- default_behavior:
49
- auto_detection:
50
- description: |
51
- No auto-detection. Each job must be explicitly configured with a `collection` definition and one or more `dependent_pipelines`.
52
- limits:
53
- description: ""
54
- performance_impact:
55
- description: |
56
- Command-mode jobs spawn a subprocess per execution. HTTP and SNMP modes use in-process clients.
57
- additional_permissions:
58
- description: |
59
- Command-mode plugins run as the `netdata` user via `nd-run`.
60
- multi_instance: true
61
- supported_platforms:
62
- include: []
63
- exclude: []
64
- setup:
65
- prerequisites:
66
- list: []
67
- configuration:
68
- file:
69
- name: scripts.d/zabbix.conf
70
- options:
71
- description: |
72
- Each job defines a collection source and one or more dependent preprocessing pipelines.
73
- folding:
74
- title: Config options
75
- enabled: true
76
- list:
77
- - name: collection.type
78
- description: Collection mode (`command`, `http`, or `snmp`).
79
- default_value: ""
80
- required: true
81
- group: Collection
82
- - name: collection.command
83
- description: Command to execute (for `command` type).
84
- default_value: ""
85
- required: false
86
- group: Collection
87
- - name: collection.http.url
88
- description: URL to fetch (for `http` type).
89
- default_value: ""
90
- required: false
91
- group: Collection
92
- - name: collection.snmp.target
93
- description: SNMP target host (for `snmp` type).
94
- default_value: ""
95
- required: false
96
- group: Collection
97
- - name: collection.snmp.oid
98
- description: SNMP OID to query.
99
- default_value: ""
100
- required: false
101
- group: Collection
102
- - name: dependent_pipelines[].name
103
- description: Pipeline name (used for chart identification).
104
- default_value: ""
105
- required: true
106
- group: Pipelines
107
- - name: dependent_pipelines[].context
108
- description: Netdata chart context.
109
- default_value: ""
110
- required: true
111
- group: Pipelines
112
- - name: dependent_pipelines[].dimension
113
- description: Dimension name within the chart.
114
- default_value: ""
115
- required: true
116
- group: Pipelines
117
- - name: dependent_pipelines[].unit
118
- description: Metric unit string.
119
- default_value: ""
120
- required: true
121
- group: Pipelines
122
- - name: dependent_pipelines[].steps
123
- description: Array of Zabbix preprocessing steps applied to the raw collection output.
124
- default_value: "[]"
125
- required: true
126
- group: Pipelines
127
- - name: vnode
128
- description: Virtual node name for host macro resolution.
129
- default_value: ""
130
- required: false
131
- group: General
132
- - name: lld
133
- description: Low-level discovery configuration for dynamic instance creation.
134
- default_value: ""
135
- required: false
136
- group: Discovery
137
- examples:
138
- folding:
139
- title: Config
140
- enabled: true
141
- list:
142
- - name: Command collection with JSONPath extraction
143
- description: Run a script and extract a metric using JSONPath.
144
- config: |
145
- jobs:
146
- - name: api_latency
147
- collection:
148
- type: command
149
- command: /usr/local/bin/get_api_stats.sh
150
- dependent_pipelines:
151
- - name: latency
152
- context: myapp.api.latency
153
- dimension: p99
154
- unit: milliseconds
155
- steps:
156
- - type: jsonpath
157
- params: "$.latency.p99"
158
- - name: SNMP collection
159
- description: Query an SNMP OID and chart the result.
160
- config: |
161
- jobs:
162
- - name: disk_usage
163
- vnode: my-switch
164
- collection:
165
- type: snmp
166
- snmp:
167
- target: "{HOST.IP}"
168
- oid: ".1.3.6.1.2.1.25.2.3.1.6"
169
- version: v2c
170
- community: public
171
- dependent_pipelines:
172
- - name: used
173
- context: zabbix.disk.used
174
- dimension: used
175
- unit: bytes
176
- steps:
177
- - type: snmp_get_value
178
- troubleshooting:
179
- problems:
180
- list: []
181
- alerts: []
182
- metrics:
183
- folding:
184
- title: Metrics
185
- enabled: false
186
- description: |
187
- ### Virtual Node Label Conventions
188
-
189
- When a job references a `vnode`, the module reads Zabbix host macros from the virtual node's **labels** using prefix conventions:
190
-
191
- | Label key | Zabbix macro | Description |
192
- |-----------|-------------|-------------|
193
- | `_address` | `{HOST.IP}`, `{HOST.CONN}` | IP address or DNS name of the host |
194
- | `_alias` | `{HOST.ALIAS}` | Human-readable host alias |
195
- | Other `_` prefixed | N/A | Reserved for future use |
196
-
197
- The `{HOST.NAME}`, `{HOST.HOST}`, and `{HOST.DNS}` macros are derived from the vnode hostname.
198
- availability: []
199
- scopes:
200
- - name: pipeline
201
- description: Metrics produced by each dependent preprocessing pipeline.
202
- labels:
203
- - name: zabbix_job
204
- description: Job name.
205
- - name: zabbix_pipeline
206
- description: Pipeline name.
207
- metrics:
208
- - name: zabbix.{context}
209
- description: User-defined metric (context is configured per pipeline)
210
- unit: varies
211
- chart_type: line
212
- dimensions:
213
- - name: "{dimension}"
214
- - name: job
215
- description: Per-job instance state tracking.
216
- labels:
217
- - name: zabbix_job
218
- description: Job name.
219
- metrics:
220
- - name: zabbix.{job}.state
221
- description: Zabbix job instance state
222
- unit: state
223
- chart_type: line
224
- dimensions:
225
- - name: ok
226
- - name: collect_failure
227
- - name: lld_failure
228
- - name: extraction_failure
229
- - name: dimension_failure
src/go/plugin/scripts.d/modules/zabbix/module.go
deleted
-287
@@ -1,287 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "context"
7
- _ "embed"
8
- "fmt"
9
- "maps"
10
- "strings"
11
- "sync"
12
-
13
- "github.com/netdata/netdata/go/plugins/pkg/multipath"
14
- "github.com/netdata/netdata/go/plugins/pkg/pluginconfig"
15
- "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
16
- "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
17
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
18
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/spec"
19
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
20
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
21
-)
22
-
23
-//go:embed config_schema.json
24
-var configSchema string
25
-
26
-func init() {
27
- module.Register("zabbix", module.Creator{
28
- JobConfigSchema: configSchema,
29
- Defaults: module.Defaults{AutoDetectionRetry: 60},
30
- Create: func() module.Module { return New() },
31
- Config: func() any { return &Config{} },
32
- })
33
-}
34
-
35
-// Config collects Zabbix jobs defined in a module configuration file.
36
-type Config struct {
37
- zabbix.JobConfig `yaml:",inline" json:",inline"`
38
- Jobs []zabbix.JobConfig `yaml:"jobs,omitempty" json:"jobs,omitempty"`
39
-}
40
-
41
-// Collector is the placeholder module.
42
-type Collector struct {
43
- module.Base
44
- Config `yaml:",inline" json:",inline"`
45
- jobs []zabbix.JobConfig
46
- proc *zpre.Preprocessor
47
- runner *runner
48
- emitter *pipelineEmitter
49
- charts *module.Charts
50
- vnodeInfo map[string]runtime.VnodeInfo
51
- missingVnode map[string]struct{}
52
-
53
- currentVnode *vnodes.VirtualNode
54
- vnodeMu sync.RWMutex
55
-}
56
-
57
-func New() *Collector { return &Collector{charts: &module.Charts{}} }
58
-
59
-func (c *Collector) Configuration() any { return &c.Config }
60
-
61
-func (c *Collector) Init(ctx context.Context) error {
62
- jobs, err := c.materializeJobs()
63
- if err != nil {
64
- return err
65
- }
66
- if c.charts == nil {
67
- c.charts = &module.Charts{}
68
- }
69
- c.jobs = jobs
70
- c.refreshVnodeInfo()
71
- for i := range c.jobs {
72
- if name := strings.TrimSpace(c.jobs[i].Vnode); name != "" {
73
- if info, ok := c.lookupVnode(name); !ok {
74
- return fmt.Errorf("job '%s': vnode '%s' not found", c.jobs[i].Name, name)
75
- } else {
76
- c.setCurrentVnode(info)
77
- }
78
- }
79
- }
80
- c.proc = acquirePreprocessor()
81
- for i := range c.jobs {
82
- c.jobs[i].Scheduler = schedulerName(c.jobs[i].Scheduler)
83
- }
84
- emitter, err := newPipelineEmitter(c.Logger, c.proc, c.charts, c.jobs)
85
- if err != nil {
86
- return err
87
- }
88
- c.emitter = emitter
89
- run, err := newRunner(ctx, c.Logger, c.jobs, c.proc, c.emitter, c.vnodeLookup)
90
- if err != nil {
91
- return err
92
- }
93
- c.runner = run
94
- return nil
95
-}
96
-
97
-func (c *Collector) materializeJobs() ([]zabbix.JobConfig, error) {
98
- var src []zabbix.JobConfig
99
- if len(c.Jobs) > 0 {
100
- src = c.Jobs
101
- } else {
102
- src = []zabbix.JobConfig{c.JobConfig}
103
- }
104
- seen := make(map[string]struct{}, len(src))
105
- jobs := make([]zabbix.JobConfig, len(src))
106
- for i := range src {
107
- job := src[i]
108
- if err := job.Validate(); err != nil {
109
- return nil, err
110
- }
111
- key := strings.ToLower(strings.TrimSpace(job.Name))
112
- if key == "" {
113
- return nil, fmt.Errorf("job name is required")
114
- }
115
- if _, exists := seen[key]; exists {
116
- return nil, fmt.Errorf("duplicate job name '%s'", job.Name)
117
- }
118
- seen[key] = struct{}{}
119
- jobs[i] = job
120
- }
121
- return jobs, nil
122
-}
123
-
124
-func (c *Collector) Check(context.Context) error { return nil }
125
-func (c *Collector) Charts() *module.Charts {
126
- return c.charts
127
-}
128
-func (c *Collector) Collect(context.Context) map[string]int64 {
129
- metrics := make(map[string]int64)
130
- if c.runner != nil {
131
- for k, v := range c.runner.Collect() {
132
- metrics[k] += v
133
- }
134
- }
135
- if c.emitter != nil {
136
- for k, v := range c.emitter.Flush() {
137
- metrics[k] = v
138
- }
139
- }
140
- if len(metrics) == 0 {
141
- return nil
142
- }
143
- return metrics
144
-}
145
-
146
-func (c *Collector) Cleanup(context.Context) {
147
- if c.runner != nil {
148
- c.runner.Stop()
149
- }
150
- if c.emitter != nil {
151
- _ = c.emitter.Close()
152
- }
153
-}
154
-
155
-func schedulerName(name string) string {
156
- if strings.TrimSpace(name) == "" {
157
- return "default"
158
- }
159
- return name
160
-}
161
-
162
-func (c *Collector) vnodeLookup(sp spec.JobSpec) runtime.VnodeInfo {
163
- if info, ok := c.lookupVnode(sp.Vnode); ok {
164
- c.setCurrentVnode(info)
165
- return cloneVnodeInfo(info)
166
- }
167
- if strings.TrimSpace(sp.Vnode) != "" {
168
- c.warnMissingVnode(sp.Vnode)
169
- }
170
- return runtime.VnodeInfo{Hostname: sp.Vnode}
171
-}
172
-
173
-func (c *Collector) lookupVnode(name string) (runtime.VnodeInfo, bool) {
174
- if c.vnodeInfo == nil {
175
- return runtime.VnodeInfo{}, false
176
- }
177
- info, ok := c.vnodeInfo[strings.ToLower(strings.TrimSpace(name))]
178
- return cloneVnodeInfo(info), ok
179
-}
180
-
181
-func (c *Collector) refreshVnodeInfo() {
182
- configDirs := pluginconfig.ConfigDir()
183
- if len(configDirs) == 0 {
184
- c.vnodeInfo = nil
185
- c.missingVnode = nil
186
- return
187
- }
188
- path, err := configDirs.Find("vnodes")
189
- if err != nil {
190
- if !multipath.IsNotFound(err) {
191
- c.Warningf("zabbix: failed to locate vnodes directory: %v", err)
192
- }
193
- c.vnodeInfo = nil
194
- c.missingVnode = nil
195
- return
196
- }
197
- registry := vnodes.Load(path)
198
- if len(registry) == 0 {
199
- c.vnodeInfo = nil
200
- c.missingVnode = nil
201
- return
202
- }
203
- info := make(map[string]runtime.VnodeInfo, len(registry)*3)
204
- for key, vnode := range registry {
205
- if vnode == nil {
206
- continue
207
- }
208
- converted := runtime.VnodeInfo{
209
- Hostname: firstNonEmpty(vnode.Hostname, vnode.Name, key),
210
- Labels: maps.Clone(vnode.Labels),
211
- }
212
- if converted.Labels == nil {
213
- converted.Labels = make(map[string]string)
214
- }
215
- if _, ok := converted.Labels["_alias"]; !ok {
216
- converted.Labels["_alias"] = firstNonEmpty(vnode.Name, key)
217
- }
218
- if _, ok := converted.Labels["_address"]; !ok {
219
- if v := vnode.Labels["_net_default_iface_ip"]; v != "" {
220
- converted.Labels["_address"] = v
221
- }
222
- }
223
- for _, alias := range []string{key, vnode.Hostname, vnode.Name, vnode.GUID} {
224
- if strings.TrimSpace(alias) == "" {
225
- continue
226
- }
227
- info[strings.ToLower(alias)] = cloneVnodeInfo(converted)
228
- }
229
- }
230
- c.vnodeInfo = info
231
- c.missingVnode = make(map[string]struct{})
232
-}
233
-
234
-func (c *Collector) setCurrentVnode(info runtime.VnodeInfo) {
235
- node := &vnodes.VirtualNode{
236
- Name: info.Hostname,
237
- Hostname: info.Hostname,
238
- Labels: maps.Clone(info.Labels),
239
- }
240
- c.vnodeMu.Lock()
241
- c.currentVnode = node
242
- c.vnodeMu.Unlock()
243
-}
244
-
245
-func (c *Collector) VirtualNode() *vnodes.VirtualNode {
246
- c.vnodeMu.RLock()
247
- defer c.vnodeMu.RUnlock()
248
- if c.currentVnode == nil {
249
- return nil
250
- }
251
- return c.currentVnode.Copy()
252
-}
253
-
254
-func (c *Collector) warnMissingVnode(name string) {
255
- name = strings.TrimSpace(name)
256
- if name == "" {
257
- return
258
- }
259
- if c.missingVnode == nil {
260
- c.missingVnode = make(map[string]struct{})
261
- }
262
- key := strings.ToLower(name)
263
- if _, seen := c.missingVnode[key]; seen {
264
- return
265
- }
266
- c.missingVnode[key] = struct{}{}
267
- c.Warningf("zabbix: vnode '%s' not found; macros fall back to literal hostname", name)
268
-}
269
-
270
-func cloneVnodeInfo(src runtime.VnodeInfo) runtime.VnodeInfo {
271
- clone := src
272
- if len(src.Labels) > 0 {
273
- clone.Labels = maps.Clone(src.Labels)
274
- } else {
275
- clone.Labels = nil
276
- }
277
- return clone
278
-}
279
-
280
-func firstNonEmpty(values ...string) string {
281
- for _, v := range values {
282
- if strings.TrimSpace(v) != "" {
283
- return v
284
- }
285
- }
286
- return ""
287
-}
src/go/plugin/scripts.d/modules/zabbix/preprocessor.go
deleted
-35
@@ -1,35 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "fmt"
7
- "os"
8
- "strings"
9
- "sync"
10
- "time"
11
-
12
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
13
-)
14
-
15
-var (
16
- preprocOnce sync.Once
17
- sharedProc *zpre.Preprocessor
18
-)
19
-
20
-func acquirePreprocessor() *zpre.Preprocessor {
21
- preprocOnce.Do(func() {
22
- ns := buildPreprocessorNamespace()
23
- sharedProc = zpre.NewPreprocessor(ns)
24
- })
25
- return sharedProc
26
-}
27
-
28
-func buildPreprocessorNamespace() string {
29
- if host, err := os.Hostname(); err == nil {
30
- if trimmed := strings.TrimSpace(host); trimmed != "" {
31
- return fmt.Sprintf("zabbix-%s", trimmed)
32
- }
33
- }
34
- return fmt.Sprintf("zabbix-%d", time.Now().UnixNano())
35
-}
src/go/plugin/scripts.d/modules/zabbix/runtime.go
deleted
-41
@@ -1,41 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "context"
7
-
8
- "github.com/netdata/netdata/go/plugins/logger"
9
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
10
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/spec"
11
- pkgzabbix "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
12
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
13
-)
14
-
15
-// runner is a thin wrapper that allows the module to delegate lifecycle control
16
-// to the shared pkg/zabbix runtime while keeping module-specific wiring simple.
17
-type runner struct {
18
- rt *pkgzabbix.Runtime
19
-}
20
-
21
-func newRunner(_ context.Context, log *logger.Logger, jobs []pkgzabbix.JobConfig, proc *zpre.Preprocessor, emitter runtime.ResultEmitter, vnodeLookup func(spec.JobSpec) runtime.VnodeInfo) (*runner, error) {
22
- rt, err := pkgzabbix.NewRuntime(jobs, proc, log, emitter, vnodeLookup)
23
- if err != nil {
24
- return nil, err
25
- }
26
- return &runner{rt: rt}, nil
27
-}
28
-
29
-func (r *runner) Collect() map[string]int64 {
30
- if r == nil || r.rt == nil {
31
- return nil
32
- }
33
- return r.rt.Collect()
34
-}
35
-
36
-func (r *runner) Stop() {
37
- if r == nil || r.rt == nil {
38
- return
39
- }
40
- r.rt.Stop()
41
-}
src/go/plugin/scripts.d/pkg/zabbix/config.go
deleted
-423
@@ -1,423 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "encoding/json"
7
- "fmt"
8
- "strconv"
9
- "strings"
10
- "time"
11
-
12
- "github.com/netdata/netdata/go/plugins/pkg/confopt"
13
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
14
-)
15
-
16
-// CollectionType enumerates supported Zabbix collection methods.
17
-type CollectionType string
18
-
19
-const (
20
- CollectionCommand CollectionType = "command"
21
- CollectionHTTP CollectionType = "http"
22
- CollectionSNMP CollectionType = "snmp"
23
-)
24
-
25
-var stepTypeTokens = map[string]zpre.StepType{
26
- "multiplier": zpre.StepTypeMultiplier,
27
- "rtrim": zpre.StepTypeRTrim,
28
- "ltrim": zpre.StepTypeLTrim,
29
- "trim": zpre.StepTypeTrim,
30
- "regex_substitution": zpre.StepTypeRegexSubstitution,
31
- "bool_to_decimal": zpre.StepTypeBool2Dec,
32
- "octal_to_decimal": zpre.StepTypeOct2Dec,
33
- "hex_to_decimal": zpre.StepTypeHex2Dec,
34
- "delta_value": zpre.StepTypeDeltaValue,
35
- "delta_speed": zpre.StepTypeDeltaSpeed,
36
- "xpath": zpre.StepTypeXPath,
37
- "jsonpath": zpre.StepTypeJSONPath,
38
- "validate_range": zpre.StepTypeValidateRange,
39
- "validate_regex": zpre.StepTypeValidateRegex,
40
- "validate_not_regex": zpre.StepTypeValidateNotRegex,
41
- "error_field_json": zpre.StepTypeErrorFieldJSON,
42
- "error_field_xml": zpre.StepTypeErrorFieldXML,
43
- "error_field_regex": zpre.StepTypeErrorFieldRegex,
44
- "throttle_value": zpre.StepTypeThrottleValue,
45
- "throttle_timed_value": zpre.StepTypeThrottleTimedValue,
46
- "javascript": zpre.StepTypeJavaScript,
47
- "prometheus_pattern": zpre.StepTypePrometheusPattern,
48
- "prometheus_to_json": zpre.StepTypePrometheusToJSON,
49
- "csv_to_json": zpre.StepTypeCSVToJSON,
50
- "string_replace": zpre.StepTypeStringReplace,
51
- "validate_not_supported": zpre.StepTypeValidateNotSupported,
52
- "xml_to_json": zpre.StepTypeXMLToJSON,
53
- "snmp_walk_value": zpre.StepTypeSNMPWalkValue,
54
- "snmp_walk_to_json": zpre.StepTypeSNMPWalkToJSON,
55
- "snmp_get_value": zpre.StepTypeSNMPGetValue,
56
- "prometheus_to_json_multi": zpre.StepTypePrometheusToJSONMulti,
57
- "jsonpath_multi": zpre.StepTypeJSONPathMulti,
58
- "snmp_walk_to_json_multi": zpre.StepTypeSNMPWalkToJSONMulti,
59
- "csv_to_json_multi": zpre.StepTypeCSVToJSONMulti,
60
-}
61
-
62
-var errorHandlerTokens = map[string]zpre.ErrorAction{
63
- "": zpre.ErrorActionDefault,
64
- "default": zpre.ErrorActionDefault,
65
- "discard": zpre.ErrorActionDiscard,
66
- "set-value": zpre.ErrorActionSetValue,
67
- "set-error": zpre.ErrorActionSetError,
68
-}
69
-
70
-// CollectionConfig describes how raw data is fetched before preprocessing.
71
-type CollectionConfig struct {
72
- Type CollectionType `yaml:"type" json:"type"`
73
- Command string `yaml:"command,omitempty" json:"command,omitempty"`
74
- Args []string `yaml:"args,omitempty" json:"args,omitempty"`
75
- Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
76
- Environment map[string]string `yaml:"environment,omitempty" json:"environment,omitempty"`
77
- HTTP HTTPConfig `yaml:"http,omitempty" json:"http,omitempty"`
78
- SNMP SNMPConfig `yaml:"snmp,omitempty" json:"snmp,omitempty"`
79
- Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` // legacy alias for HTTP headers
80
- Body string `yaml:"body,omitempty" json:"body,omitempty"`
81
- Method string `yaml:"method,omitempty" json:"method,omitempty"` // legacy alias for HTTP method
82
- URL string `yaml:"url,omitempty" json:"url,omitempty"`
83
-}
84
-
85
-// HTTPConfig captures HTTP(S) collection settings.
86
-type HTTPConfig struct {
87
- URL string `yaml:"url" json:"url"`
88
- Method string `yaml:"method,omitempty" json:"method,omitempty"`
89
- Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
90
- Body string `yaml:"body,omitempty" json:"body,omitempty"`
91
- TLS bool `yaml:"tls,omitempty" json:"tls,omitempty"`
92
- Username string `yaml:"username,omitempty" json:"username,omitempty"`
93
- Password string `yaml:"password,omitempty" json:"password,omitempty"`
94
-}
95
-
96
-// SNMPConfig captures SNMP collection settings.
97
-type SNMPConfig struct {
98
- Target string `yaml:"target" json:"target"`
99
- Community string `yaml:"community,omitempty" json:"community,omitempty"`
100
- Version string `yaml:"version,omitempty" json:"version,omitempty"`
101
- User string `yaml:"user,omitempty" json:"user,omitempty"`
102
- AuthPass string `yaml:"auth_password,omitempty" json:"auth_password,omitempty"`
103
- PrivPass string `yaml:"privacy_password,omitempty" json:"privacy_password,omitempty"`
104
- AuthProto string `yaml:"auth_protocol,omitempty" json:"auth_protocol,omitempty"`
105
- PrivProto string `yaml:"privacy_protocol,omitempty" json:"privacy_protocol,omitempty"`
106
- Context string `yaml:"context,omitempty" json:"context,omitempty"`
107
- OID string `yaml:"oid" json:"oid"`
108
-}
109
-
110
-// LLDConfig describes the discovery pipeline.
111
-type LLDConfig struct {
112
- Steps []zpre.Step `yaml:"steps" json:"steps"`
113
- Interval confopt.Duration `yaml:"discovery_interval,omitempty" json:"discovery_interval,omitempty"`
114
- InstanceTemplate string `yaml:"instance_id_template" json:"instance_id_template"`
115
- Family string `yaml:"family" json:"family"`
116
- Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
117
- MaxMissing int `yaml:"max_missing,omitempty" json:"max_missing,omitempty"`
118
-}
119
-
120
-type lldConfigDTO struct {
121
- Steps []rawStep `yaml:"steps" json:"steps"`
122
- Interval confopt.Duration `yaml:"discovery_interval,omitempty" json:"discovery_interval,omitempty"`
123
- InstanceTemplate string `yaml:"instance_id_template" json:"instance_id_template"`
124
- Family string `yaml:"family" json:"family"`
125
- Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
126
- MaxMissing int `yaml:"max_missing,omitempty" json:"max_missing,omitempty"`
127
-}
128
-
129
-func (l *LLDConfig) fromDTO(dto lldConfigDTO) error {
130
- steps, err := decodeSteps(dto.Steps)
131
- if err != nil {
132
- return err
133
- }
134
- l.Steps = steps
135
- l.Interval = dto.Interval
136
- l.InstanceTemplate = dto.InstanceTemplate
137
- l.Family = dto.Family
138
- l.Labels = dto.Labels
139
- l.MaxMissing = dto.MaxMissing
140
- return nil
141
-}
142
-
143
-func (l *LLDConfig) UnmarshalYAML(unmarshal func(any) error) error {
144
- var dto lldConfigDTO
145
- if err := unmarshal(&dto); err != nil {
146
- return err
147
- }
148
- return l.fromDTO(dto)
149
-}
150
-
151
-func (l *LLDConfig) UnmarshalJSON(data []byte) error {
152
- var dto lldConfigDTO
153
- if err := json.Unmarshal(data, &dto); err != nil {
154
- return err
155
- }
156
- return l.fromDTO(dto)
157
-}
158
-
159
-// PipelineConfig defines a dependent pipeline that produces a single metric per instance.
160
-type PipelineConfig struct {
161
- Name string `yaml:"name" json:"name"`
162
- Context string `yaml:"context" json:"context"`
163
- Title string `yaml:"title,omitempty" json:"title,omitempty"`
164
- Dimension string `yaml:"dimension" json:"dimension"`
165
- Unit string `yaml:"unit" json:"unit"`
166
- Family string `yaml:"family,omitempty" json:"family,omitempty"`
167
- ChartType string `yaml:"chart_type,omitempty" json:"chart_type,omitempty"`
168
- DataType string `yaml:"data_type,omitempty" json:"data_type,omitempty"`
169
- Precision int `yaml:"precision,omitempty" json:"precision,omitempty"`
170
- Algorithm string `yaml:"algorithm,omitempty" json:"algorithm,omitempty"`
171
- Steps []zpre.Step `yaml:"steps" json:"steps"`
172
-}
173
-
174
-type pipelineConfigDTO struct {
175
- Name string `yaml:"name" json:"name"`
176
- Context string `yaml:"context" json:"context"`
177
- Title string `yaml:"title,omitempty" json:"title,omitempty"`
178
- Dimension string `yaml:"dimension" json:"dimension"`
179
- Unit string `yaml:"unit" json:"unit"`
180
- Family string `yaml:"family,omitempty" json:"family,omitempty"`
181
- ChartType string `yaml:"chart_type,omitempty" json:"chart_type,omitempty"`
182
- DataType string `yaml:"data_type,omitempty" json:"data_type,omitempty"`
183
- Precision int `yaml:"precision,omitempty" json:"precision,omitempty"`
184
- Algorithm string `yaml:"algorithm,omitempty" json:"algorithm,omitempty"`
185
- Steps []rawStep `yaml:"steps" json:"steps"`
186
-}
187
-
188
-func (pc *PipelineConfig) fromDTO(dto pipelineConfigDTO) error {
189
- pc.Name = dto.Name
190
- pc.Context = dto.Context
191
- pc.Title = dto.Title
192
- pc.Dimension = dto.Dimension
193
- pc.Unit = dto.Unit
194
- pc.Family = dto.Family
195
- pc.ChartType = dto.ChartType
196
- pc.DataType = dto.DataType
197
- pc.Precision = dto.Precision
198
- pc.Algorithm = dto.Algorithm
199
- steps, err := decodeSteps(dto.Steps)
200
- if err != nil {
201
- return err
202
- }
203
- pc.Steps = steps
204
- return nil
205
-}
206
-
207
-func (pc *PipelineConfig) UnmarshalYAML(unmarshal func(any) error) error {
208
- var dto pipelineConfigDTO
209
- if err := unmarshal(&dto); err != nil {
210
- return err
211
- }
212
- return pc.fromDTO(dto)
213
-}
214
-
215
-func (pc *PipelineConfig) UnmarshalJSON(data []byte) error {
216
- var dto pipelineConfigDTO
217
- if err := json.Unmarshal(data, &dto); err != nil {
218
- return err
219
- }
220
- return pc.fromDTO(dto)
221
-}
222
-
223
-// JobConfig represents the full user configuration for a Zabbix job.
224
-type JobConfig struct {
225
- Name string `yaml:"name" json:"name"`
226
- Scheduler string `yaml:"scheduler,omitempty" json:"scheduler,omitempty"`
227
- Vnode string `yaml:"vnode,omitempty" json:"vnode,omitempty"`
228
- UpdateEvery int `yaml:"update_every,omitempty" json:"update_every,omitempty"`
229
- UserMacros map[string]string `yaml:"user_macros,omitempty" json:"user_macros,omitempty"`
230
- Collection CollectionConfig `yaml:"collection" json:"collection"`
231
- LLD LLDConfig `yaml:"lld,omitempty" json:"lld,omitempty"`
232
- Pipelines []PipelineConfig `yaml:"dependent_pipelines" json:"dependent_pipelines"`
233
- Notes string `yaml:"notes,omitempty" json:"notes,omitempty"`
234
-}
235
-
236
-const defaultUpdateEverySeconds = 60
237
-
238
-type rawStep struct {
239
- Type any `yaml:"type" json:"type"`
240
- Params string `yaml:"params,omitempty" json:"params,omitempty"`
241
- ErrorHandler string `yaml:"error_handler,omitempty" json:"error_handler,omitempty"`
242
- ErrorHandlerValue string `yaml:"error_handler_params,omitempty" json:"error_handler_params,omitempty"`
243
-}
244
-
245
-func decodeSteps(raw []rawStep) ([]zpre.Step, error) {
246
- if len(raw) == 0 {
247
- return nil, nil
248
- }
249
- steps := make([]zpre.Step, len(raw))
250
- for i, r := range raw {
251
- step, err := r.toZabbixStep()
252
- if err != nil {
253
- return nil, fmt.Errorf("step[%d]: %w", i, err)
254
- }
255
- steps[i] = step
256
- }
257
- return steps, nil
258
-}
259
-
260
-func (rs rawStep) toZabbixStep() (zpre.Step, error) {
261
- st, err := parseStepType(rs.Type)
262
- if err != nil {
263
- return zpre.Step{}, err
264
- }
265
- handler, err := parseErrorHandler(rs.ErrorHandler, rs.ErrorHandlerValue)
266
- if err != nil {
267
- return zpre.Step{}, err
268
- }
269
- return zpre.Step{
270
- Type: st,
271
- Params: rs.Params,
272
- ErrorHandler: handler,
273
- }, nil
274
-}
275
-
276
-func parseStepType(value any) (zpre.StepType, error) {
277
- switch v := value.(type) {
278
- case string:
279
- key := strings.ToLower(strings.TrimSpace(v))
280
- if key == "" {
281
- return 0, fmt.Errorf("step type is required")
282
- }
283
- if st, ok := stepTypeTokens[key]; ok {
284
- return st, nil
285
- }
286
- if n, err := strconv.Atoi(key); err == nil {
287
- return zpre.StepType(n), nil
288
- }
289
- return 0, fmt.Errorf("unknown step type %q", v)
290
- case int:
291
- return zpre.StepType(v), nil
292
- case int64:
293
- return zpre.StepType(v), nil
294
- case float64:
295
- return zpre.StepType(int(v)), nil
296
- case json.Number:
297
- i, err := v.Int64()
298
- if err != nil {
299
- return 0, err
300
- }
301
- return zpre.StepType(i), nil
302
- case nil:
303
- return 0, fmt.Errorf("step type is required")
304
- default:
305
- return 0, fmt.Errorf("unsupported step type %T", v)
306
- }
307
-}
308
-
309
-func parseErrorHandler(action, params string) (zpre.ErrorHandler, error) {
310
- action = strings.ToLower(strings.TrimSpace(action))
311
- act, ok := errorHandlerTokens[action]
312
- if !ok {
313
- return zpre.ErrorHandler{}, fmt.Errorf("unknown error handler %q", action)
314
- }
315
- switch act {
316
- case zpre.ErrorActionSetValue, zpre.ErrorActionSetError:
317
- if strings.TrimSpace(params) == "" {
318
- return zpre.ErrorHandler{}, fmt.Errorf("error_handler_params required when error_handler=%q", action)
319
- }
320
- return zpre.ErrorHandler{Action: act, Params: params}, nil
321
- default:
322
- return zpre.ErrorHandler{Action: act}, nil
323
- }
324
-}
325
-
326
-// Validate ensures the job definition is usable.
327
-func (cfg *JobConfig) Validate() error {
328
- if strings.TrimSpace(cfg.Name) == "" {
329
- return fmt.Errorf("job name is required")
330
- }
331
- if strings.TrimSpace(cfg.Collection.Type.String()) == "" {
332
- return fmt.Errorf("job '%s': collection.type is required", cfg.Name)
333
- }
334
- if err := cfg.Collection.validate(); err != nil {
335
- return fmt.Errorf("job '%s': %w", cfg.Name, err)
336
- }
337
- if len(cfg.Pipelines) == 0 {
338
- return fmt.Errorf("job '%s': at least one dependent pipeline is required", cfg.Name)
339
- }
340
- for i := range cfg.Pipelines {
341
- if err := cfg.Pipelines[i].validate(); err != nil {
342
- return fmt.Errorf("job '%s': pipeline[%d]: %w", cfg.Name, i, err)
343
- }
344
- }
345
- if cfg.UpdateEvery < 0 {
346
- return fmt.Errorf("job '%s': update_every must be >= 1 second when provided", cfg.Name)
347
- }
348
- if cfg.LLD.InstanceTemplate == "" && cfg.hasTemplateLabels() {
349
- return fmt.Errorf("job '%s': lld.instance_id_template is required when labels reference macros", cfg.Name)
350
- }
351
- return nil
352
-}
353
-
354
-func (c CollectionConfig) validate() error {
355
- switch c.Type {
356
- case CollectionCommand:
357
- if strings.TrimSpace(c.Command) == "" {
358
- return fmt.Errorf("collection.command is required for command type")
359
- }
360
- case CollectionHTTP:
361
- if strings.TrimSpace(c.HTTP.URL) == "" && strings.TrimSpace(c.URL) == "" {
362
- return fmt.Errorf("http.url is required")
363
- }
364
- case CollectionSNMP:
365
- if strings.TrimSpace(c.SNMP.Target) == "" || strings.TrimSpace(c.SNMP.OID) == "" {
366
- return fmt.Errorf("snmp.target and snmp.oid are required")
367
- }
368
- default:
369
- return fmt.Errorf("unsupported collection type %q", c.Type)
370
- }
371
- return nil
372
-}
373
-
374
-func (c CollectionType) String() string { return string(c) }
375
-
376
-func (cfg *JobConfig) hasTemplateLabels() bool {
377
- for _, v := range cfg.LLD.Labels {
378
- if strings.Contains(v, "{#") {
379
- return true
380
- }
381
- }
382
- return false
383
-}
384
-
385
-func (p *PipelineConfig) validate() error {
386
- if p.Name == "" {
387
- return fmt.Errorf("name is required")
388
- }
389
- if p.Context == "" {
390
- return fmt.Errorf("context is required")
391
- }
392
- if p.Dimension == "" {
393
- return fmt.Errorf("dimension is required")
394
- }
395
- if strings.TrimSpace(p.Unit) == "" {
396
- return fmt.Errorf("unit is required")
397
- }
398
- if len(p.Steps) == 0 {
399
- return fmt.Errorf("at least one preprocessing step is required")
400
- }
401
- return nil
402
-}
403
-
404
-// Interval returns the discovery interval or zero for per-collection execution.
405
-func (l LLDConfig) IntervalDuration() time.Duration {
406
- return time.Duration(l.Interval)
407
-}
408
-
409
-// Timeout returns the collection timeout.
410
-func (c CollectionConfig) TimeoutDuration() time.Duration {
411
- if c.Timeout <= 0 {
412
- return 0
413
- }
414
- return time.Duration(c.Timeout)
415
-}
416
-
417
-// IntervalDuration returns the scheduling interval for the job.
418
-func (cfg JobConfig) IntervalDuration() time.Duration {
419
- if cfg.UpdateEvery >= 1 {
420
- return time.Duration(cfg.UpdateEvery) * time.Second
421
- }
422
- return time.Duration(defaultUpdateEverySeconds) * time.Second
423
-}
src/go/plugin/scripts.d/pkg/zabbix/config_test.go
deleted
-128
@@ -1,128 +0,0 @@
1
-package zabbix
2
-
3
-import (
4
- "encoding/json"
5
- "testing"
6
-
7
- "gopkg.in/yaml.v3"
8
-
9
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
10
-)
11
-
12
-func TestJobConfigValidate(t *testing.T) {
13
- cfg := JobConfig{
14
- Name: "disk",
15
- Collection: CollectionConfig{Type: CollectionCommand, Command: "/usr/lib/zabbix/get_disks"},
16
- LLD: LLDConfig{InstanceTemplate: "disk_{#DEVNAME}"},
17
- Pipelines: []PipelineConfig{{
18
- Name: "usage",
19
- Context: "zabbix.disk.usage",
20
- Dimension: "used",
21
- Unit: "%",
22
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
23
- }},
24
- }
25
- if err := cfg.Validate(); err != nil {
26
- t.Fatalf("expected config to be valid: %v", err)
27
- }
28
-}
29
-
30
-func TestJobConfigValidateFails(t *testing.T) {
31
- cfg := JobConfig{Name: "bad"}
32
- if err := cfg.Validate(); err == nil {
33
- t.Fatalf("expected validation failure")
34
- }
35
-}
36
-
37
-func TestJobConfigUpdateEveryValidation(t *testing.T) {
38
- cfg := JobConfig{
39
- Name: "test",
40
- UpdateEvery: -1,
41
- Collection: CollectionConfig{Type: CollectionCommand, Command: "/bin/true"},
42
- Pipelines: []PipelineConfig{{
43
- Name: "value",
44
- Context: "ctx",
45
- Dimension: "dim",
46
- Unit: "value",
47
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.v"}},
48
- }},
49
- }
50
- if err := cfg.Validate(); err == nil {
51
- t.Fatalf("expected error for negative update_every")
52
- }
53
-
54
- cfg.UpdateEvery = 1
55
- if err := cfg.Validate(); err != nil {
56
- t.Fatalf("unexpected error for valid update_every: %v", err)
57
- }
58
-
59
- cfg.UpdateEvery = 0
60
- if err := cfg.Validate(); err != nil {
61
- t.Fatalf("unexpected error when update_every omitted: %v", err)
62
- }
63
-}
64
-
65
-func TestPipelineStepStringTypeYAML(t *testing.T) {
66
- data := `pipelines:
67
-- name: usage
68
- context: zabbix.fs.usage
69
- dimension: used
70
- unit: "%"
71
- steps:
72
- - type: jsonpath
73
- params: $.value
74
- error_handler: set-value
75
- error_handler_params: "0"
76
-`
77
- var wrapper struct {
78
- Pipelines []PipelineConfig `yaml:"pipelines"`
79
- }
80
- if err := yaml.Unmarshal([]byte(data), &wrapper); err != nil {
81
- t.Fatalf("unmarshal failed: %v", err)
82
- }
83
- if len(wrapper.Pipelines) != 1 {
84
- t.Fatalf("expected 1 pipeline, got %d", len(wrapper.Pipelines))
85
- }
86
- steps := wrapper.Pipelines[0].Steps
87
- if len(steps) != 1 {
88
- t.Fatalf("expected 1 step, got %d", len(steps))
89
- }
90
- step := steps[0]
91
- if step.Type != zpre.StepTypeJSONPath {
92
- t.Fatalf("expected jsonpath step, got %v", step.Type)
93
- }
94
- if step.ErrorHandler.Action != zpre.ErrorActionSetValue || step.ErrorHandler.Params != "0" {
95
- t.Fatalf("unexpected error handler: %+v", step.ErrorHandler)
96
- }
97
-}
98
-
99
-func TestPipelineStepNumericJSON(t *testing.T) {
100
- payload := []byte(`{"pipelines":[{"name":"usage","context":"ctx","dimension":"dim","unit":"%","steps":[{"type":12,"params":"$.value"}]}]}`)
101
- var wrapper struct {
102
- Pipelines []PipelineConfig `json:"pipelines"`
103
- }
104
- if err := json.Unmarshal(payload, &wrapper); err != nil {
105
- t.Fatalf("json unmarshal failed: %v", err)
106
- }
107
- step := wrapper.Pipelines[0].Steps[0]
108
- if step.Type != zpre.StepTypeJSONPath {
109
- t.Fatalf("expected numeric type to map to jsonpath, got %v", step.Type)
110
- }
111
-}
112
-
113
-func TestParseErrorHandlerInvalidAction(t *testing.T) {
114
- _, err := parseErrorHandler("bogus", "")
115
- if err == nil {
116
- t.Fatalf("expected error for unknown handler")
117
- }
118
-
119
- if _, err := parseErrorHandler("set-value", ""); err == nil {
120
- t.Fatalf("expected error when params missing for set-value")
121
- }
122
- if _, err := parseErrorHandler("set-error", ""); err == nil {
123
- t.Fatalf("expected error when params missing for set-error")
124
- }
125
- if h, err := parseErrorHandler("set-error", "bad"); err != nil || h.Params != "bad" {
126
- t.Fatalf("expected valid handler, err=%v handler=%+v", err, h)
127
- }
128
-}
src/go/plugin/scripts.d/pkg/zabbix/jobengine/job.go
deleted
-492
@@ -1,492 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package jobengine
4
-
5
-import (
6
- "encoding/json"
7
- "errors"
8
- "fmt"
9
- "strconv"
10
- "strings"
11
- "sync"
12
- "time"
13
-
14
- "github.com/netdata/netdata/go/plugins/logger"
15
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/ids"
16
- pkgzabbix "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
17
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
18
-)
19
-
20
-// Job orchestrates collection -> LLD -> dependent pipeline execution for a single
21
-// Zabbix-style definition. It wraps the shared Preprocessor and maintains the
22
-// discovered instance catalog between iterations.
23
-type Job struct {
24
- cfg pkgzabbix.JobConfig
25
- proc *zpre.Preprocessor
26
- log *logger.Logger
27
- mu sync.RWMutex
28
- lastDiscovery time.Time
29
- instances map[string]*instanceState
30
-}
31
-
32
-// Options configure the job engine behavior.
33
-type Options struct {
34
- Logger *logger.Logger
35
-}
36
-
37
-// Input encapsulates the payload passed to Process.
38
-type Input struct {
39
- Payload []byte
40
- Timestamp time.Time
41
- CollectError error
42
-}
43
-
44
-// Result aggregates the metrics, discovery changes, and failure state after a
45
-// Process call.
46
-type Result struct {
47
- Job string
48
- Timestamp time.Time
49
- Metrics []MetricResult
50
- Active []InstanceInfo
51
- Removed []InstanceInfo
52
- State StateSummary
53
-}
54
-
55
-// StateSummary tracks failure flags for the job and individual instances.
56
-type StateSummary struct {
57
- Job FailureFlags
58
- Instances map[string]FailureFlags
59
-}
60
-
61
-// FailureFlags capture the distinct failure modes per iteration.
62
-type FailureFlags struct {
63
- Collect bool
64
- LLD bool
65
- Extraction bool
66
- Dimension bool
67
-}
68
-
69
-// MetricResult describes a single dependent pipeline outcome.
70
-type MetricResult struct {
71
- Instance InstanceInfo
72
- Pipeline *pkgzabbix.PipelineConfig
73
- Value float64
74
- Precision int
75
- Discarded bool
76
- Error error
77
- Name string
78
- Labels map[string]string
79
-}
80
-
81
-// InstanceInfo carries the instance ID and macros used for templating.
82
-type InstanceInfo struct {
83
- ID string
84
- Macros map[string]string
85
-}
86
-
87
-// NewJob validates the configuration and constructs a Job bound to the given
88
-// Preprocessor.
89
-func NewJob(cfg pkgzabbix.JobConfig, proc *zpre.Preprocessor, opts Options) (*Job, error) {
90
- if err := cfg.Validate(); err != nil {
91
- return nil, err
92
- }
93
- if proc == nil {
94
- return nil, errors.New("jobengine: preprocessor is required")
95
- }
96
- log := opts.Logger
97
- if log == nil {
98
- log = logger.New().With("component", "zabbix/jobengine")
99
- }
100
- job := &Job{
101
- cfg: cfg,
102
- proc: proc,
103
- log: log,
104
- instances: make(map[string]*instanceState),
105
- }
106
- return job, nil
107
-}
108
-
109
-// Process executes LLD (when configured) and dependent pipelines using the
110
-// provided payload. The returned result describes all emitted metrics alongside
111
-// failure flags for the job and each instance.
112
-func (j *Job) Process(in Input) Result {
113
- j.mu.Lock()
114
- defer j.mu.Unlock()
115
-
116
- res := Result{
117
- Job: j.cfg.Name,
118
- Timestamp: in.Timestamp,
119
- State: StateSummary{Instances: make(map[string]FailureFlags)},
120
- }
121
-
122
- if in.Timestamp.IsZero() {
123
- in.Timestamp = time.Now()
124
- res.Timestamp = in.Timestamp
125
- }
126
-
127
- tracker := newIterationState()
128
-
129
- if in.CollectError != nil {
130
- j.ensureDefaultInstance()
131
- tracker.setCollectFailureAll(j.instances)
132
- var active []InstanceInfo
133
- for _, inst := range j.instances {
134
- active = append(active, inst.info())
135
- }
136
- return j.finalizeResult(tracker, active, nil, res)
137
- }
138
-
139
- payloadValue := zpre.Value{Data: string(in.Payload), Type: zpre.ValueTypeStr, Timestamp: in.Timestamp}
140
-
141
- var discovery []map[string]string
142
- var discErr error
143
- if len(j.cfg.LLD.Steps) > 0 && j.shouldDiscover(in.Timestamp) {
144
- discovery, discErr = j.runDiscovery(payloadValue)
145
- }
146
-
147
- var removed []InstanceInfo
148
- var active []InstanceInfo
149
- if discErr != nil {
150
- tracker.setLLDFailureAll(j.instances)
151
- } else if discovery != nil {
152
- var applyErr error
153
- removed, applyErr = j.applyDiscovery(discovery)
154
- if applyErr != nil {
155
- tracker.setLLDFailureAll(j.instances)
156
- discErr = applyErr
157
- } else {
158
- j.lastDiscovery = in.Timestamp
159
- }
160
- }
161
- if len(j.cfg.LLD.Steps) == 0 {
162
- j.ensureDefaultInstance()
163
- }
164
-
165
- for _, inst := range j.instances {
166
- active = append(active, inst.info())
167
- tracker.ensureInstance(inst.id)
168
- }
169
-
170
- var metrics []MetricResult
171
- if discErr == nil {
172
- metrics = j.runPipelines(payloadValue, tracker)
173
- }
174
-
175
- return j.finalizeResult(tracker, active, removed, Result{
176
- Job: res.Job,
177
- Timestamp: res.Timestamp,
178
- Metrics: metrics,
179
- Active: active,
180
- Removed: removed,
181
- })
182
-}
183
-
184
-// Destroy removes the job-specific state from the shared preprocessor and
185
-// forgets all LLD instances.
186
-func (j *Job) Destroy() {
187
- j.mu.Lock()
188
- defer j.mu.Unlock()
189
- for id := range j.instances {
190
- for _, pipe := range j.cfg.Pipelines {
191
- j.proc.ClearState(itemID(j.cfg, id, pipe.Name))
192
- }
193
- }
194
- j.proc.ClearState(fmt.Sprintf("%s.__lld", j.cfg.Name))
195
- j.instances = make(map[string]*instanceState)
196
-}
197
-
198
-func (j *Job) finalizeResult(tracker *iterationState, active, removed []InstanceInfo, res Result) Result {
199
- res.Active = active
200
- res.Removed = removed
201
- res.State.Job = tracker.jobFailure
202
- res.State.Instances = make(map[string]FailureFlags, len(tracker.instances))
203
- for id, flags := range tracker.instances {
204
- res.State.Instances[id] = *flags
205
- }
206
- return res
207
-}
208
-
209
-func (j *Job) shouldDiscover(now time.Time) bool {
210
- interval := j.cfg.LLD.IntervalDuration()
211
- if interval <= 0 {
212
- return true
213
- }
214
- return now.Sub(j.lastDiscovery) >= interval
215
-}
216
-
217
-func (j *Job) runDiscovery(val zpre.Value) ([]map[string]string, error) {
218
- steps := duplicateSteps(j.cfg.LLD.Steps, nil)
219
- itemID := fmt.Sprintf("%s.__lld", j.cfg.Name)
220
- res, err := j.proc.ExecutePipeline(itemID, val, steps)
221
- if err != nil {
222
- return nil, err
223
- }
224
- if len(res.Metrics) == 0 {
225
- return nil, fmt.Errorf("lld returned no metrics")
226
- }
227
- entries := []map[string]string{}
228
- for _, metric := range res.Metrics {
229
- payload := strings.TrimSpace(metric.Value)
230
- if payload == "" {
231
- continue
232
- }
233
- var batch []map[string]string
234
- if err := json.Unmarshal([]byte(payload), &batch); err != nil {
235
- return nil, err
236
- }
237
- entries = append(entries, batch...)
238
- }
239
- return entries, nil
240
-}
241
-
242
-func (j *Job) applyDiscovery(entries []map[string]string) ([]InstanceInfo, error) {
243
- seen := make(map[string]struct{}, len(entries))
244
- updates := make(map[string]map[string]string, len(entries))
245
- for _, entry := range entries {
246
- macros := cloneMacros(entry)
247
- instanceID := buildInstanceID(j.cfg, macros)
248
- if instanceID == "" {
249
- continue
250
- }
251
- if _, ok := seen[instanceID]; ok {
252
- j.warnf("duplicate LLD instance id %q dropped", instanceID)
253
- return nil, fmt.Errorf("duplicate instance id %s", instanceID)
254
- }
255
- seen[instanceID] = struct{}{}
256
- macros["{#INSTANCE_ID}"] = instanceID
257
- updates[instanceID] = macros
258
- }
259
- for id, macros := range updates {
260
- inst := j.instances[id]
261
- if inst == nil {
262
- inst = &instanceState{id: id, macros: make(map[string]string)}
263
- j.instances[id] = inst
264
- }
265
- inst.macros = cloneMacros(macros)
266
- inst.missing = 0
267
- }
268
- var removed []InstanceInfo
269
- for id, inst := range j.instances {
270
- if _, ok := seen[id]; ok {
271
- continue
272
- }
273
- inst.missing++
274
- threshold := j.cfg.LLD.MaxMissing
275
- if threshold <= 0 || inst.missing >= threshold {
276
- removed = append(removed, inst.info())
277
- j.removeInstance(inst)
278
- delete(j.instances, id)
279
- }
280
- }
281
- return removed, nil
282
-}
283
-
284
-func (j *Job) ensureDefaultInstance() {
285
- if len(j.instances) > 0 {
286
- return
287
- }
288
- j.instances["default"] = &instanceState{
289
- id: "default",
290
- macros: map[string]string{"{#INSTANCE_ID}": "default"},
291
- }
292
-}
293
-
294
-func (j *Job) removeInstance(inst *instanceState) {
295
- for _, pipe := range j.cfg.Pipelines {
296
- j.proc.ClearState(itemID(j.cfg, inst.id, pipe.Name))
297
- }
298
-}
299
-
300
-func (j *Job) runPipelines(val zpre.Value, tracker *iterationState) []MetricResult {
301
- var out []MetricResult
302
- for _, inst := range j.instances {
303
- macros := cloneMacros(inst.macros)
304
- for idx := range j.cfg.Pipelines {
305
- pipe := &j.cfg.Pipelines[idx]
306
- steps := duplicateSteps(pipe.Steps, macros)
307
- res, err := j.proc.ExecutePipeline(itemID(j.cfg, inst.id, pipe.Name), val, steps)
308
- if err != nil {
309
- if isDimensionError(err) {
310
- tracker.markDimensionFailure(inst.id)
311
- } else {
312
- tracker.markExtractionFailure(inst.id)
313
- }
314
- out = append(out, MetricResult{Instance: inst.info(), Pipeline: pipe, Error: err})
315
- continue
316
- }
317
- if res.Discarded || len(res.Metrics) == 0 {
318
- tracker.markDimensionFailure(inst.id)
319
- out = append(out, MetricResult{Instance: inst.info(), Pipeline: pipe, Discarded: true})
320
- continue
321
- }
322
- if len(res.Metrics) != 1 {
323
- tracker.markDimensionFailure(inst.id)
324
- if j.log != nil {
325
- j.log.Warningf("zabbix: job %s pipeline %s returned %d metrics (expected 1); dropping output", j.cfg.Name, pipe.Name, len(res.Metrics))
326
- }
327
- out = append(out, MetricResult{Instance: inst.info(), Pipeline: pipe, Error: fmt.Errorf("pipeline '%s' produced %d metrics; expected 1", pipe.Name, len(res.Metrics))})
328
- continue
329
- }
330
- metric := res.Metrics[0]
331
- valNum, err := strconv.ParseFloat(strings.TrimSpace(metric.Value), 64)
332
- if err != nil {
333
- tracker.markExtractionFailure(inst.id)
334
- if j.log != nil {
335
- j.log.Warningf("zabbix: job %s pipeline %s returned non-numeric value %q: %v", j.cfg.Name, pipe.Name, metric.Value, err)
336
- }
337
- out = append(out, MetricResult{Instance: inst.info(), Pipeline: pipe, Error: err})
338
- continue
339
- }
340
- out = append(out, MetricResult{
341
- Instance: inst.info(),
342
- Pipeline: pipe,
343
- Value: valNum,
344
- Precision: pipe.Precision,
345
- Name: metric.Name,
346
- Labels: cloneLabels(metric.Labels),
347
- })
348
- }
349
- }
350
- return out
351
-}
352
-
353
-// iteration tracking -------------------------------------------------------
354
-
355
-type iterationState struct {
356
- jobFailure FailureFlags
357
- instances map[string]*FailureFlags
358
-}
359
-
360
-func newIterationState() *iterationState {
361
- return &iterationState{instances: make(map[string]*FailureFlags)}
362
-}
363
-
364
-func (s *iterationState) ensureInstance(id string) {
365
- if _, ok := s.instances[id]; !ok {
366
- s.instances[id] = &FailureFlags{}
367
- }
368
-}
369
-
370
-func (s *iterationState) setCollectFailureAll(instances map[string]*instanceState) {
371
- s.jobFailure.Collect = true
372
- for id := range instances {
373
- s.ensureInstance(id)
374
- s.instances[id].Collect = true
375
- }
376
-}
377
-
378
-func (s *iterationState) setLLDFailureAll(instances map[string]*instanceState) {
379
- s.jobFailure.LLD = true
380
- for id := range instances {
381
- s.ensureInstance(id)
382
- s.instances[id].LLD = true
383
- }
384
-}
385
-
386
-func (s *iterationState) markExtractionFailure(id string) {
387
- s.jobFailure.Extraction = true
388
- s.ensureInstance(id)
389
- s.instances[id].Extraction = true
390
-}
391
-
392
-func (s *iterationState) markDimensionFailure(id string) {
393
- s.jobFailure.Dimension = true
394
- s.ensureInstance(id)
395
- s.instances[id].Dimension = true
396
-}
397
-
398
-func isDimensionError(err error) bool {
399
- if err == nil {
400
- return false
401
- }
402
- msg := err.Error()
403
- if strings.Contains(msg, "out of range") {
404
- return true
405
- }
406
- if strings.Contains(msg, "does not match") {
407
- return true
408
- }
409
- return false
410
-}
411
-
412
-// helpers -----------------------------------------------------------------
413
-
414
-type instanceState struct {
415
- id string
416
- macros map[string]string
417
- missing int
418
-}
419
-
420
-func (inst *instanceState) info() InstanceInfo {
421
- return InstanceInfo{ID: inst.id, Macros: cloneMacros(inst.macros)}
422
-}
423
-
424
-func duplicateSteps(steps []zpre.Step, macros map[string]string) []zpre.Step {
425
- out := make([]zpre.Step, len(steps))
426
- for i := range steps {
427
- step := steps[i]
428
- if macros != nil {
429
- step.Params = expandTemplate(step.Params, macros)
430
- }
431
- out[i] = step
432
- }
433
- return out
434
-}
435
-
436
-func itemID(cfg pkgzabbix.JobConfig, instanceID, pipe string) string {
437
- return fmt.Sprintf("%s.%s.%s", cfg.Name, instanceID, pipe)
438
-}
439
-
440
-func expandTemplate(tmpl string, macros map[string]string) string {
441
- if tmpl == "" || len(macros) == 0 {
442
- return tmpl
443
- }
444
- res := tmpl
445
- for k, v := range macros {
446
- res = strings.ReplaceAll(res, k, v)
447
- }
448
- return res
449
-}
450
-
451
-func cloneMacros(src map[string]string) map[string]string {
452
- out := make(map[string]string, len(src))
453
- for k, v := range src {
454
- out[k] = v
455
- }
456
- return out
457
-}
458
-
459
-func cloneLabels(src map[string]string) map[string]string {
460
- if len(src) == 0 {
461
- return nil
462
- }
463
- out := make(map[string]string, len(src))
464
- for k, v := range src {
465
- out[k] = v
466
- }
467
- return out
468
-}
469
-
470
-func buildInstanceID(cfg pkgzabbix.JobConfig, macros map[string]string) string {
471
- if val, ok := macros["{#INSTANCE_ID}"]; ok {
472
- if trimmed := strings.TrimSpace(val); trimmed != "" {
473
- return ids.Sanitize(trimmed)
474
- }
475
- }
476
- template := cfg.LLD.InstanceTemplate
477
- if strings.TrimSpace(template) == "" {
478
- template = fmt.Sprintf("%s_instance", cfg.Name)
479
- }
480
- raw := expandTemplate(template, macros)
481
- if strings.TrimSpace(raw) == "" {
482
- raw = fmt.Sprintf("%s_instance", cfg.Name)
483
- }
484
- return ids.Sanitize(raw)
485
-}
486
-
487
-func (j *Job) warnf(format string, args ...interface{}) {
488
- if j.log == nil {
489
- return
490
- }
491
- j.log.Warningf(format, args...)
492
-}
src/go/plugin/scripts.d/pkg/zabbix/jobengine/job_test.go
deleted
-1107
@@ -1,1107 +0,0 @@
1
-package jobengine
2
-
3
-import (
4
- "errors"
5
- "fmt"
6
- "sort"
7
- "strings"
8
- "testing"
9
- "time"
10
-
11
- "github.com/netdata/netdata/go/plugins/pkg/confopt"
12
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/ids"
13
- pkgzabbix "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbix"
14
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
15
-)
16
-
17
-const pathLengthScript = `
18
-var rows = JSON.parse(value);
19
-var fsKey = "{#" + "FSNAME}";
20
-var pathKey = "{#" + "FSPATH}";
21
-var target = "{#FSNAME}";
22
-var matched = "";
23
-for (var i = 0; i < rows.length; i++) {
24
- if (rows[i][fsKey] === target) {
25
- matched = rows[i][pathKey] || "";
26
- break;
27
- }
28
-}
29
-value = (matched || "").length.toString();
30
-return value;
31
-`
32
-
33
-const snmpValueScript = `
34
-var rows = JSON.parse(value);
35
-var indexKey = "{#" + "SNMPINDEX}";
36
-var valueKey = "MACRO2";
37
-var target = "{#SNMPINDEX}";
38
-var matched = "";
39
-for (var i = 0; i < rows.length; i++) {
40
- if (rows[i][indexKey] === target) {
41
- matched = rows[i][valueKey] || "";
42
- break;
43
- }
44
-}
45
-value = matched || "";
46
-return value;
47
-`
48
-
49
-const promDiscoveryScript = `
50
-var entries = JSON.parse(value);
51
-var out = [];
52
-for (var i = 0; i < entries.length; i++) {
53
- var entry = entries[i];
54
- if (entry.name !== "node_filesystem_info") {
55
- continue;
56
- }
57
- var labels = entry.labels || {};
58
- var mount = labels.mountpoint || "";
59
- if (!mount) {
60
- continue;
61
- }
62
- out.push({"{#MOUNT}": mount});
63
-}
64
-value = JSON.stringify(out);
65
-return value;
66
-`
67
-
68
-const promValueScript = `
69
-var entries = JSON.parse(value);
70
-var target = "{#MOUNT}";
71
-var result = "";
72
-for (var i = 0; i < entries.length; i++) {
73
- var entry = entries[i];
74
- if (entry.name !== "node_filesystem_free_bytes") {
75
- continue;
76
- }
77
- var labels = entry.labels || {};
78
- if (labels.mountpoint === target) {
79
- result = entry.value || entry.valueStr || "";
80
- break;
81
- }
82
-}
83
-value = result ? result.toString() : "";
84
-return value;
85
-`
86
-
87
-const csvMultiValueScript = `
88
-var row = JSON.parse(value);
89
-var raw = row["value"] || row["VALUE"] || "0";
90
-value = raw.toString();
91
-return value;
92
-`
93
-
94
-func TestJobProcessMatrix(t *testing.T) {
95
- cases := buildMatrixCases()
96
- if len(cases) < 100 {
97
- t.Fatalf("expected at least 100 scenarios, have %d", len(cases))
98
- }
99
- for _, tc := range cases {
100
- t.Run(tc.name, func(t *testing.T) {
101
- proc := zpre.NewPreprocessor("jobengine-" + tc.name)
102
- job, err := NewJob(tc.cfg, proc, Options{})
103
- if err != nil {
104
- t.Fatalf("new job failed: %v", err)
105
- }
106
- res := job.Process(tc.input)
107
- job.Destroy()
108
- tc.expect.assert(t, res)
109
- })
110
- }
111
-}
112
-
113
-// ----------------------------------------------------------------------------
114
-
115
-type scenario struct {
116
- name string
117
- cfg pkgzabbix.JobConfig
118
- input Input
119
- expect expectation
120
-}
121
-
122
-type expectation struct {
123
- metrics int
124
- values []float64
125
- active int
126
- removed int
127
- removedIDs []string
128
- job FailureFlags
129
- instances map[string]FailureFlags
130
- macroKeys []string
131
- errorMetrics int
132
-}
133
-
134
-func (e expectation) assert(t *testing.T, res Result) {
135
- if res.State.Job != e.job {
136
- t.Fatalf("job flags mismatch: want %+v got %+v", e.job, res.State.Job)
137
- }
138
- if e.instances != nil && len(res.State.Instances) != len(e.instances) {
139
- t.Fatalf("instance flag map mismatch: want %d got %d", len(e.instances), len(res.State.Instances))
140
- }
141
- for id, flags := range e.instances {
142
- if got, ok := res.State.Instances[id]; !ok || got != flags {
143
- t.Fatalf("instance %s flags mismatch: want %+v got %+v", id, flags, got)
144
- }
145
- }
146
- if len(res.Metrics) != e.metrics {
147
- t.Fatalf("metrics count mismatch: want %d got %d", e.metrics, len(res.Metrics))
148
- }
149
- if len(res.Active) != e.active {
150
- t.Fatalf("active instances mismatch: want %d got %d", e.active, len(res.Active))
151
- }
152
- if len(res.Removed) != e.removed {
153
- t.Fatalf("removed instances mismatch: want %d got %d", e.removed, len(res.Removed))
154
- }
155
- if len(e.removedIDs) > 0 {
156
- gotIDs := make([]string, 0, len(res.Removed))
157
- for _, inst := range res.Removed {
158
- gotIDs = append(gotIDs, inst.ID)
159
- }
160
- sort.Strings(gotIDs)
161
- expected := append([]string(nil), e.removedIDs...)
162
- sort.Strings(expected)
163
- if len(gotIDs) != len(expected) {
164
- t.Fatalf("removed IDs mismatch: want %v got %v", expected, gotIDs)
165
- }
166
- for i := range gotIDs {
167
- if gotIDs[i] != expected[i] {
168
- t.Fatalf("removed IDs mismatch: want %v got %v", expected, gotIDs)
169
- }
170
- }
171
- }
172
- if len(e.values) > 0 {
173
- gotVals := make([]float64, 0, len(res.Metrics))
174
- for _, m := range res.Metrics {
175
- gotVals = append(gotVals, m.Value)
176
- }
177
- sort.Float64s(gotVals)
178
- expected := append([]float64(nil), e.values...)
179
- sort.Float64s(expected)
180
- if len(gotVals) != len(expected) {
181
- t.Fatalf("value set mismatch: want %v got %v", expected, gotVals)
182
- }
183
- for i := range gotVals {
184
- if diff := gotVals[i] - expected[i]; diff > 1e-9 || diff < -1e-9 {
185
- t.Fatalf("value mismatch: want %v got %v", expected, gotVals)
186
- }
187
- }
188
- }
189
- if len(e.macroKeys) > 0 {
190
- for _, m := range res.Metrics {
191
- for _, key := range e.macroKeys {
192
- if _, ok := m.Instance.Macros[key]; !ok {
193
- t.Fatalf("expected macro %s in metric instance %+v", key, m.Instance)
194
- }
195
- }
196
- }
197
- }
198
- if e.errorMetrics > 0 {
199
- count := 0
200
- for _, m := range res.Metrics {
201
- if m.Error != nil {
202
- count++
203
- }
204
- }
205
- if count != e.errorMetrics {
206
- t.Fatalf("expected %d errored metrics, got %d", e.errorMetrics, count)
207
- }
208
- }
209
-}
210
-
211
-// ----------------------------------------------------------------------------
212
-
213
-type variant int
214
-
215
-const (
216
- variantNormal variant = iota
217
- variantCollectErr
218
- variantExtractionErr
219
- variantDimensionErr
220
- variantLLDErr
221
-)
222
-
223
-func buildMatrixCases() []scenario {
224
- var cases []scenario
225
-
226
- simpleSamples := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
227
- simpleVariants := []variant{variantNormal, variantCollectErr, variantExtractionErr, variantDimensionErr}
228
- for idx, sample := range simpleSamples {
229
- for _, v := range simpleVariants {
230
- cases = append(cases, buildSimpleScenario(idx, sample, v))
231
- }
232
- }
233
-
234
- multiSamples := []struct{ used, free float64 }{
235
- {10, 90}, {20, 80}, {30, 70}, {40, 60}, {50, 50}, {60, 40}, {70, 30}, {80, 20},
236
- }
237
- for idx, sample := range multiSamples {
238
- for _, v := range simpleVariants {
239
- cases = append(cases, buildMultiScenario(idx, sample.used, sample.free, v))
240
- }
241
- }
242
-
243
- lldSamples := []struct{ root, home float64 }{
244
- {10, 20}, {15, 25}, {5, 35}, {40, 10}, {12, 13}, {7, 9},
245
- }
246
- lldVariants := []variant{variantNormal, variantCollectErr, variantExtractionErr, variantDimensionErr, variantLLDErr}
247
- for idx, sample := range lldSamples {
248
- for _, v := range lldVariants {
249
- cases = append(cases, buildLLDScenario(idx, sample.root, sample.home, v))
250
- }
251
- }
252
-
253
- for idx := 0; idx < 10; idx++ {
254
- cases = append(cases, buildCSVLLDScenario(idx))
255
- cases = append(cases, buildXMLLLDScenario(idx))
256
- cases = append(cases, buildSNMPLLDScenario(idx))
257
- }
258
-
259
- promSamples := []struct{ root, home float64 }{
260
- {60, 80}, {45, 70}, {90, 20}, {30, 55},
261
- }
262
- for idx, sample := range promSamples {
263
- for _, v := range lldVariants {
264
- cases = append(cases, buildPrometheusLLDScenario(idx, sample.root, sample.home, v))
265
- }
266
- }
267
-
268
- for idx := 0; idx < 5; idx++ {
269
- for _, v := range simpleVariants {
270
- cases = append(cases, buildXPathScenario(idx, float64(40+idx*3), float64(50+idx*4), v))
271
- cases = append(cases, buildCSVMultiScenario(idx, float64(10+idx), v))
272
- }
273
- }
274
-
275
- return cases
276
-}
277
-
278
-// ----------------------------------------------------------------------------
279
-
280
-type stateVariant int
281
-
282
-const (
283
- stateVariantStable stateVariant = iota
284
- stateVariantAddRemove
285
- stateVariantLLDFailure
286
- stateVariantExtractionFailure
287
- stateVariantDimensionFailure
288
-)
289
-
290
-type statefulScenario struct {
291
- name string
292
- cfg pkgzabbix.JobConfig
293
- runs []stateRun
294
-}
295
-
296
-type stateRun struct {
297
- label string
298
- input Input
299
- expect expectation
300
-}
301
-
302
-func TestJobProcessStatefulMatrix(t *testing.T) {
303
- cases := buildStatefulCases()
304
- if len(cases) < 50 {
305
- t.Fatalf("expected >=50 stateful scenarios, have %d", len(cases))
306
- }
307
- for _, sc := range cases {
308
- t.Run(sc.name, func(t *testing.T) {
309
- proc := zpre.NewPreprocessor("state-" + sc.name)
310
- job, err := NewJob(sc.cfg, proc, Options{})
311
- if err != nil {
312
- t.Fatalf("new job failed: %v", err)
313
- }
314
- for _, run := range sc.runs {
315
- res := job.Process(run.input)
316
- run.expect.assert(t, res)
317
- }
318
- job.Destroy()
319
- })
320
- }
321
-}
322
-
323
-func TestJobProcessDuplicateDiscovery(t *testing.T) {
324
- cfg := lldJobConfig("duplicate")
325
- input := Input{Payload: []byte(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":10,"free":90},{"fs":"/home","used":20,"free":80}]}`), Timestamp: time.Now()}
326
- proc := zpre.NewPreprocessor("dup")
327
- job, err := NewJob(cfg, proc, Options{})
328
- if err != nil {
329
- t.Fatalf("new job failed: %v", err)
330
- }
331
- res := job.Process(input)
332
- if !res.State.Job.LLD {
333
- t.Fatalf("expected LLD failure due to duplicate ids")
334
- }
335
- if len(res.Metrics) != 0 {
336
- t.Fatalf("expected no metrics when discovery fails, got %d", len(res.Metrics))
337
- }
338
-}
339
-
340
-func TestJobShouldDiscover(t *testing.T) {
341
- job := &Job{}
342
- if !job.shouldDiscover(time.Now()) {
343
- t.Fatalf("interval <=0 should force discovery")
344
- }
345
- job.cfg.LLD.Interval = confopt.Duration(10 * time.Second)
346
- job.lastDiscovery = time.Now()
347
- if job.shouldDiscover(time.Now()) {
348
- t.Fatalf("should not rediscover before interval")
349
- }
350
- job.lastDiscovery = time.Now().Add(-11 * time.Second)
351
- if !job.shouldDiscover(time.Now()) {
352
- t.Fatalf("should rediscover after interval elapsed")
353
- }
354
-}
355
-
356
-func TestLLDInstanceRemovalAfterMaxMissing(t *testing.T) {
357
- cfg := lldJobConfig("miss")
358
- cfg.LLD.MaxMissing = 2
359
- proc := zpre.NewPreprocessor("miss-test")
360
- job, err := NewJob(cfg, proc, Options{})
361
- if err != nil {
362
- t.Fatalf("new job failed: %v", err)
363
- }
364
- createPayload := Input{Payload: []byte(`{"discovery":[{"{#FSNAME}":"/"}],"data":[{"fs":"/","used":10,"free":90}]}`), Timestamp: time.Now()}
365
- res := job.Process(createPayload)
366
- if len(res.Active) != 1 {
367
- t.Fatalf("expected 1 active instance, got %d", len(res.Active))
368
- }
369
- missingPayload := Input{Payload: []byte(`{"discovery":[],"data":[]}`), Timestamp: time.Now().Add(time.Second)}
370
- res = job.Process(missingPayload)
371
- if len(res.Removed) != 0 {
372
- t.Fatalf("expected no removal after first miss, got %d", len(res.Removed))
373
- }
374
- missingPayload.Timestamp = missingPayload.Timestamp.Add(time.Second)
375
- res = job.Process(missingPayload)
376
- if len(res.Removed) != 1 {
377
- t.Fatalf("expected removal after reaching max_missing, got %d", len(res.Removed))
378
- }
379
-}
380
-
381
-func buildStatefulCases() []statefulScenario {
382
- samples := []struct{ root, home float64 }{
383
- {10, 20}, {15, 25}, {5, 35}, {40, 10}, {12, 13}, {7, 9}, {22, 11}, {18, 16}, {9, 12}, {31, 17},
384
- }
385
- variants := []stateVariant{stateVariantStable, stateVariantAddRemove, stateVariantLLDFailure, stateVariantExtractionFailure, stateVariantDimensionFailure}
386
- var cases []statefulScenario
387
- for idx, sample := range samples {
388
- for _, v := range variants {
389
- cases = append(cases, buildStatefulScenario(idx, sample.root, sample.home, v))
390
- }
391
- }
392
- promStates := []struct{ root, home float64 }{{60, 45}, {55, 35}}
393
- for idx, sample := range promStates {
394
- cases = append(cases, buildPromStatefulScenario(idx, sample.root, sample.home))
395
- }
396
- cases = append(cases, buildXPathStatefulScenario(0, 42, 57))
397
- return cases
398
-}
399
-
400
-func buildStatefulScenario(idx int, root, home float64, v stateVariant) statefulScenario {
401
- name := fmt.Sprintf("state_%d_%s", idx, stateVariantName(v))
402
- cfg := lldJobConfig(fmt.Sprintf("state_%d", idx))
403
- rootID := ids.Sanitize("/")
404
- homeID := ids.Sanitize("/home")
405
- instBoth := []instData{{"/", root, 100 - root}, {"/home", home, 100 - home}}
406
- instRoot := []instData{{"/", root, 100 - root}}
407
- instHome := []instData{{"/home", home, 100 - home}}
408
- var runs []stateRun
409
- switch v {
410
- case stateVariantStable:
411
- payload := []byte(lldPayload(instBoth))
412
- expect := expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}
413
- runs = []stateRun{
414
- {label: "run1", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expect},
415
- {label: "run2", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expect},
416
- {label: "run3", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expect},
417
- }
418
- case stateVariantAddRemove:
419
- payload1 := []byte(lldPayload(instRoot))
420
- payload2 := []byte(lldPayload(instBoth))
421
- payload3 := []byte(lldPayload(instHome))
422
- runs = []stateRun{
423
- {label: "run1", input: Input{Payload: payload1, Timestamp: time.Now()}, expect: expectation{metrics: 2, values: valuesFor(instRoot), active: 1, job: FailureFlags{}, instances: zeroFlags(rootID), macroKeys: []string{"{#FSNAME}"}}},
424
- {label: "run2", input: Input{Payload: payload2, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
425
- {label: "run3", input: Input{Payload: payload3, Timestamp: time.Now()}, expect: expectation{metrics: 2, values: valuesFor(instHome), active: 1, removed: 1, removedIDs: []string{rootID}, job: FailureFlags{}, instances: zeroFlags(homeID), macroKeys: []string{"{#FSNAME}"}}},
426
- }
427
- case stateVariantLLDFailure:
428
- payload := []byte(lldPayload(instBoth))
429
- runs = []stateRun{
430
- {label: "run1", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
431
- {label: "run2", input: Input{Payload: []byte("not-json"), Timestamp: time.Now()}, expect: expectation{metrics: 0, values: nil, active: 2, job: FailureFlags{LLD: true}, instances: map[string]FailureFlags{rootID: {LLD: true}, homeID: {LLD: true}}}},
432
- {label: "run3", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
433
- }
434
- case stateVariantExtractionFailure:
435
- payload := []byte(lldPayload(instBoth))
436
- badPayload := []byte(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":"oops","free":"oops"},{"fs":"/home","used":"bad","free":"bad"}]}`)
437
- runs = []stateRun{
438
- {label: "run1", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
439
- {label: "run2", input: Input{Payload: badPayload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: nil, active: 2, job: FailureFlags{Extraction: true}, instances: map[string]FailureFlags{rootID: {Extraction: true}, homeID: {Extraction: true}}, errorMetrics: 4}},
440
- {label: "run3", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
441
- }
442
- case stateVariantDimensionFailure:
443
- payload := []byte(lldPayload(instBoth))
444
- badPayload := []byte(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":150,"free":-10},{"fs":"/home","used":180,"free":-20}]}`)
445
- runs = []stateRun{
446
- {label: "run1", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
447
- {label: "run2", input: Input{Payload: badPayload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: nil, active: 2, job: FailureFlags{Dimension: true}, instances: map[string]FailureFlags{rootID: {Dimension: true}, homeID: {Dimension: true}}, errorMetrics: 4}},
448
- {label: "run3", input: Input{Payload: payload, Timestamp: time.Now()}, expect: expectation{metrics: 4, values: valuesFor(instBoth), active: 2, job: FailureFlags{}, instances: zeroFlags(rootID, homeID), macroKeys: []string{"{#FSNAME}"}}},
449
- }
450
- }
451
- return statefulScenario{name: name, cfg: cfg, runs: runs}
452
-}
453
-
454
-func buildPromStatefulScenario(idx int, root, home float64) statefulScenario {
455
- name := fmt.Sprintf("state_prom_%d", idx)
456
- cfg := prometheusJobConfig(fmt.Sprintf("state_prom_%d", idx))
457
- instRoot := ids.Sanitize("fs_/")
458
- instHome := ids.Sanitize("fs_/home")
459
- runs := []stateRun{
460
- {
461
- label: "run1",
462
- input: Input{Payload: []byte(prometheusPayload(root, home)), Timestamp: time.Now()},
463
- expect: expectation{metrics: 2, values: []float64{root, home}, active: 2, job: FailureFlags{}, instances: zeroFlags(instRoot, instHome)},
464
- },
465
- {
466
- label: "run2",
467
- input: Input{Payload: []byte("not-prom"), Timestamp: time.Now()},
468
- expect: expectation{metrics: 0, values: nil, active: 2, job: FailureFlags{LLD: true}, instances: map[string]FailureFlags{instRoot: {LLD: true}, instHome: {LLD: true}}},
469
- },
470
- {
471
- label: "run3",
472
- input: Input{Payload: []byte(prometheusPayload(root+5, home+3)), Timestamp: time.Now()},
473
- expect: expectation{metrics: 2, values: []float64{root + 5, home + 3}, active: 2, job: FailureFlags{}, instances: zeroFlags(instRoot, instHome)},
474
- },
475
- }
476
- return statefulScenario{name: name, cfg: cfg, runs: runs}
477
-}
478
-
479
-func buildXPathStatefulScenario(idx int, cpu, gpu float64) statefulScenario {
480
- name := fmt.Sprintf("state_xpath_%d", idx)
481
- cfg := xpathJobConfig(fmt.Sprintf("state_xpath_%d", idx))
482
- defaultID := "default"
483
- runs := []stateRun{
484
- {
485
- label: "run1",
486
- input: Input{Payload: []byte(fmt.Sprintf(`<sensors><sensor id="cpu">%f</sensor><sensor id="gpu">%f</sensor></sensors>`, cpu, gpu)), Timestamp: time.Now()},
487
- expect: expectation{metrics: 2, values: []float64{cpu, gpu}, active: 1, job: FailureFlags{}, instances: zeroFlags(defaultID)},
488
- },
489
- {
490
- label: "run2",
491
- input: Input{Payload: []byte(`<sensors><sensor id="cpu">oops</sensor><sensor id="gpu">bad</sensor></sensors>`), Timestamp: time.Now()},
492
- expect: expectation{metrics: 2, values: nil, active: 1, job: FailureFlags{Extraction: true}, instances: map[string]FailureFlags{defaultID: {Extraction: true}}, errorMetrics: 2},
493
- },
494
- {
495
- label: "run3",
496
- input: Input{Payload: []byte(fmt.Sprintf(`<sensors><sensor id="cpu">%f</sensor><sensor id="gpu">%f</sensor></sensors>`, cpu+2, gpu+2)), Timestamp: time.Now()},
497
- expect: expectation{metrics: 2, values: []float64{cpu + 2, gpu + 2}, active: 1, job: FailureFlags{}, instances: zeroFlags(defaultID)},
498
- },
499
- }
500
- return statefulScenario{name: name, cfg: cfg, runs: runs}
501
-}
502
-
503
-func stateVariantName(v stateVariant) string {
504
- switch v {
505
- case stateVariantStable:
506
- return "stable"
507
- case stateVariantAddRemove:
508
- return "add_remove"
509
- case stateVariantLLDFailure:
510
- return "lld_fail"
511
- case stateVariantExtractionFailure:
512
- return "extract_fail"
513
- case stateVariantDimensionFailure:
514
- return "dimension_fail"
515
- default:
516
- return "unknown"
517
- }
518
-}
519
-
520
-type instData struct {
521
- name string
522
- used float64
523
- free float64
524
-}
525
-
526
-func lldPayload(insts []instData) string {
527
- var discParts []string
528
- var dataParts []string
529
- for _, inst := range insts {
530
- discParts = append(discParts, fmt.Sprintf(`{"{#FSNAME}":"%s"}`, inst.name))
531
- dataParts = append(dataParts, fmt.Sprintf(`{"fs":"%s","used":%g,"free":%g}`, inst.name, inst.used, inst.free))
532
- }
533
- return fmt.Sprintf(`{"discovery":[%s],"data":[%s]}`, strings.Join(discParts, ","), strings.Join(dataParts, ","))
534
-}
535
-
536
-func valuesFor(insts []instData) []float64 {
537
- var vals []float64
538
- for _, inst := range insts {
539
- vals = append(vals, inst.used, inst.free)
540
- }
541
- return vals
542
-}
543
-
544
-func prometheusPayload(root, home float64) string {
545
- return prometheusPayloadStrings(fmt.Sprintf("%f", root), fmt.Sprintf("%f", home))
546
-}
547
-
548
-func prometheusPayloadStrings(rootVal, homeVal string) string {
549
- return fmt.Sprintf(`# HELP node_filesystem_free_bytes Free bytes
550
-# TYPE node_filesystem_free_bytes gauge
551
-# HELP node_filesystem_info Filesystem info
552
-# TYPE node_filesystem_info gauge
553
-node_filesystem_info{mountpoint="/",fstype="ext4"} 1
554
-node_filesystem_info{mountpoint="/home",fstype="xfs"} 1
555
-node_filesystem_free_bytes{mountpoint="/",fstype="ext4"} %s
556
-node_filesystem_free_bytes{mountpoint="/home",fstype="xfs"} %s
557
-`, rootVal, homeVal)
558
-}
559
-
560
-func zeroFlags(ids ...string) map[string]FailureFlags {
561
- if len(ids) == 0 {
562
- return map[string]FailureFlags{}
563
- }
564
- m := make(map[string]FailureFlags, len(ids))
565
- for _, id := range ids {
566
- m[id] = FailureFlags{}
567
- }
568
- return m
569
-}
570
-
571
-func buildSimpleScenario(idx int, value float64, v variant) scenario {
572
- name := fmt.Sprintf("simple_%d_%s", idx, variantName(v))
573
- cfg := simpleJobConfig(fmt.Sprintf("simple_%d", idx))
574
- input := Input{Payload: []byte(fmt.Sprintf(`{"value": %f}`, value)), Timestamp: time.Now()}
575
- expect := expectation{metrics: 1, values: []float64{value}, active: 1, job: FailureFlags{}, instances: map[string]FailureFlags{"default": {}}}
576
- switch v {
577
- case variantCollectErr:
578
- input.CollectError = errors.New("collect")
579
- expect.metrics = 0
580
- expect.values = nil
581
- expect.active = 1
582
- expect.job = FailureFlags{Collect: true}
583
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
584
- case variantExtractionErr:
585
- input.Payload = []byte(`{"value":"oops"}`)
586
- expect.metrics = 1
587
- expect.values = nil
588
- expect.job = FailureFlags{Extraction: true}
589
- expect.instances = map[string]FailureFlags{"default": {Extraction: true}}
590
- case variantDimensionErr:
591
- input.Payload = []byte(`{"value": 200}`)
592
- expect.metrics = 1
593
- expect.values = nil
594
- expect.job = FailureFlags{Dimension: true}
595
- expect.instances = map[string]FailureFlags{"default": {Dimension: true}}
596
- }
597
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
598
-}
599
-
600
-func buildMultiScenario(idx int, used, free float64, v variant) scenario {
601
- name := fmt.Sprintf("multi_%d_%s", idx, variantName(v))
602
- cfg := multiPipelineJobConfig(fmt.Sprintf("multi_%d", idx))
603
- input := Input{Payload: []byte(fmt.Sprintf(`{"used": %f, "free": %f}`, used, free)), Timestamp: time.Now()}
604
- // Validation happens BEFORE multiplier, so all normal inputs pass validation
605
- // Multiplier=2 is applied after validation
606
- usedAfterMultiplier := used * 2
607
- expect := expectation{metrics: 2, values: []float64{usedAfterMultiplier, free}, active: 1, job: FailureFlags{}, instances: map[string]FailureFlags{"default": {}}}
608
- switch v {
609
- case variantCollectErr:
610
- input.CollectError = errors.New("collect")
611
- expect.metrics = 0
612
- expect.values = nil
613
- expect.active = 1
614
- expect.job = FailureFlags{Collect: true}
615
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
616
- case variantExtractionErr:
617
- input.Payload = []byte(`{"used":"oops","free":"oops"}`)
618
- expect.metrics = 2
619
- expect.values = nil
620
- expect.job = FailureFlags{Extraction: true}
621
- expect.instances = map[string]FailureFlags{"default": {Extraction: true}}
622
- case variantDimensionErr:
623
- input.Payload = []byte(`{"used": 200, "free": 300}`)
624
- expect.metrics = 2
625
- expect.values = nil
626
- expect.job = FailureFlags{Dimension: true}
627
- expect.instances = map[string]FailureFlags{"default": {Dimension: true}}
628
- }
629
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
630
-}
631
-
632
-func buildLLDScenario(idx int, root, home float64, v variant) scenario {
633
- name := fmt.Sprintf("lld_%d_%s", idx, variantName(v))
634
- cfg := lldJobConfig(fmt.Sprintf("lld_%d", idx))
635
- payload := fmt.Sprintf(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":%f,"free":%f},{"fs":"/home","used":%f,"free":%f}]}`, root, 100-root, home, 100-home)
636
- input := Input{Payload: []byte(payload), Timestamp: time.Now()}
637
- values := []float64{root, home, 100 - root, 100 - home}
638
- instA := ids.Sanitize("/")
639
- instB := ids.Sanitize("/home")
640
- macroKeys := []string{}
641
- if v == variantNormal {
642
- macroKeys = []string{"{#FSNAME}"}
643
- }
644
- expect := expectation{
645
- metrics: 4,
646
- values: values,
647
- active: 2,
648
- job: FailureFlags{},
649
- instances: map[string]FailureFlags{instA: {}, instB: {}},
650
- macroKeys: macroKeys,
651
- }
652
- switch v {
653
- case variantCollectErr:
654
- input.CollectError = errors.New("collect")
655
- expect.metrics = 0
656
- expect.values = nil
657
- expect.active = 1
658
- expect.job = FailureFlags{Collect: true}
659
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
660
- case variantExtractionErr:
661
- input.Payload = []byte(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":"abc","free":"def"},{"fs":"/home","used":"ghi","free":"jkl"}]}`)
662
- expect.metrics = 4
663
- expect.values = nil
664
- expect.job = FailureFlags{Extraction: true}
665
- expect.instances = map[string]FailureFlags{instA: {Extraction: true}, instB: {Extraction: true}}
666
- case variantDimensionErr:
667
- input.Payload = []byte(`{"discovery":[{"{#FSNAME}":"/"},{"{#FSNAME}":"/home"}],"data":[{"fs":"/","used":150,"free":-10},{"fs":"/home","used":180,"free":-20}]}`)
668
- expect.metrics = 4
669
- expect.values = nil
670
- expect.job = FailureFlags{Dimension: true}
671
- expect.instances = map[string]FailureFlags{instA: {Dimension: true}, instB: {Dimension: true}}
672
- case variantLLDErr:
673
- input.Payload = []byte(`not-json`)
674
- expect.metrics = 0
675
- expect.values = nil
676
- expect.active = 0
677
- expect.job = FailureFlags{LLD: true}
678
- expect.instances = map[string]FailureFlags{}
679
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
680
- }
681
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
682
-}
683
-
684
-func buildCSVLLDScenario(idx int) scenario {
685
- name := fmt.Sprintf("lld_csv_%d", idx)
686
- cfg := csvLLDJobConfig(fmt.Sprintf("csv_job_%d", idx))
687
- input := Input{Payload: []byte(`{#FSNAME},{#FSPATH}
688
-root,/
689
-home,/home`), Timestamp: time.Now()}
690
- instA := ids.Sanitize("root")
691
- instB := ids.Sanitize("home")
692
- expect := expectation{metrics: 2, values: []float64{1, 5}, active: 2, job: FailureFlags{}, instances: map[string]FailureFlags{instA: {}, instB: {}}, macroKeys: []string{"{#FSNAME}"}}
693
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
694
-}
695
-
696
-func buildXMLLLDScenario(idx int) scenario {
697
- name := fmt.Sprintf("lld_xml_%d", idx)
698
- cfg := xmlLLDJobConfig(fmt.Sprintf("xml_job_%d", idx))
699
- input := Input{Payload: []byte(`<files><fs name="root" path="/"/><fs name="home" path="/home"/></files>`), Timestamp: time.Now()}
700
- instA := ids.Sanitize("root")
701
- instB := ids.Sanitize("home")
702
- expect := expectation{metrics: 2, values: []float64{1, 5}, active: 2, job: FailureFlags{}, instances: map[string]FailureFlags{instA: {}, instB: {}}, macroKeys: []string{"{#FSNAME}"}}
703
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
704
-}
705
-
706
-func buildSNMPLLDScenario(idx int) scenario {
707
- name := fmt.Sprintf("lld_snmp_%d", idx)
708
- cfg := snmpLLDJobConfig(fmt.Sprintf("snmp_job_%d", idx))
709
- snmpData := `.1.3.6.1.1 = STRING: "ifA"
710
-.1.3.6.1.2 = STRING: "ifB"
711
-.1.3.6.2.1 = STRING: "100"
712
-.1.3.6.2.2 = STRING: "200"`
713
- input := Input{Payload: []byte(snmpData), Timestamp: time.Now()}
714
- instA := ids.Sanitize("ifA")
715
- instB := ids.Sanitize("ifB")
716
- expect := expectation{metrics: 2, values: []float64{100, 200}, active: 2, job: FailureFlags{}, instances: map[string]FailureFlags{instA: {}, instB: {}}, macroKeys: []string{"MACRO1", "MACRO2", "{#SNMPINDEX}"}}
717
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
718
-}
719
-
720
-func buildPrometheusLLDScenario(idx int, root, home float64, v variant) scenario {
721
- name := fmt.Sprintf("lld_prom_%d_%s", idx, variantName(v))
722
- cfg := prometheusJobConfig(fmt.Sprintf("prom_job_%d", idx))
723
- input := Input{Payload: []byte(prometheusPayload(root, home)), Timestamp: time.Now()}
724
- instRoot := ids.Sanitize("fs_/")
725
- instHome := ids.Sanitize("fs_/home")
726
- expect := expectation{
727
- metrics: 2,
728
- values: []float64{root, home},
729
- active: 2,
730
- job: FailureFlags{},
731
- instances: map[string]FailureFlags{instRoot: {}, instHome: {}},
732
- macroKeys: []string{"{#MOUNT}"},
733
- }
734
- switch v {
735
- case variantCollectErr:
736
- input.CollectError = errors.New("collect")
737
- expect.metrics = 0
738
- expect.values = nil
739
- expect.active = 1
740
- expect.job = FailureFlags{Collect: true}
741
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
742
- case variantExtractionErr:
743
- input.Payload = []byte(prometheusPayloadStrings("oops", "123"))
744
- expect.values = nil
745
- expect.job = FailureFlags{Extraction: true}
746
- expect.instances = map[string]FailureFlags{instRoot: {Extraction: true}, instHome: {}}
747
- case variantDimensionErr:
748
- input.Payload = []byte(prometheusPayload(1500, 2000))
749
- expect.values = nil
750
- expect.job = FailureFlags{Dimension: true}
751
- expect.instances = map[string]FailureFlags{instRoot: {Dimension: true}, instHome: {Dimension: true}}
752
- case variantLLDErr:
753
- input.Payload = []byte("not-prometheus")
754
- expect.metrics = 0
755
- expect.values = nil
756
- expect.active = 0
757
- expect.job = FailureFlags{LLD: true}
758
- expect.instances = map[string]FailureFlags{}
759
- }
760
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
761
-}
762
-
763
-func buildXPathScenario(idx int, cpu, gpu float64, v variant) scenario {
764
- name := fmt.Sprintf("xpath_%d_%s", idx, variantName(v))
765
- cfg := xpathJobConfig(fmt.Sprintf("xpath_%d", idx))
766
- payload := fmt.Sprintf(`<sensors><sensor id="cpu">%f</sensor><sensor id="gpu">%f</sensor></sensors>`, cpu, gpu)
767
- input := Input{Payload: []byte(payload), Timestamp: time.Now()}
768
- expect := expectation{metrics: 2, values: []float64{cpu, gpu}, active: 1, job: FailureFlags{}, instances: map[string]FailureFlags{"default": {}}}
769
- switch v {
770
- case variantCollectErr:
771
- input.CollectError = errors.New("collect")
772
- expect.metrics = 0
773
- expect.values = nil
774
- expect.active = 1
775
- expect.job = FailureFlags{Collect: true}
776
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
777
- case variantExtractionErr:
778
- input.Payload = []byte(`<sensors><sensor id="cpu">oops</sensor><sensor id="gpu">bad</sensor></sensors>`)
779
- expect.values = nil
780
- expect.job = FailureFlags{Extraction: true}
781
- expect.instances = map[string]FailureFlags{"default": {Extraction: true}}
782
- case variantDimensionErr:
783
- input.Payload = []byte(`<sensors><sensor id="cpu">500</sensor><sensor id="gpu">600</sensor></sensors>`)
784
- expect.values = nil
785
- expect.job = FailureFlags{Dimension: true}
786
- expect.instances = map[string]FailureFlags{"default": {Dimension: true}}
787
- }
788
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
789
-}
790
-
791
-func buildCSVMultiScenario(idx int, first float64, v variant) scenario {
792
- name := fmt.Sprintf("csv_multi_%d_%s", idx, variantName(v))
793
- cfg := csvMultiJobConfig(fmt.Sprintf("csv_multi_%d", idx))
794
- payload := fmt.Sprintf("name,value\nrow1,%f\nrow2,20", first)
795
- input := Input{Payload: []byte(payload), Timestamp: time.Now()}
796
- // CSVToJSONMulti returns 2 metrics but pipeline expects 1
797
- // Job engine drops output and returns 1 metric with value=0 and Dimension error
798
- expect := expectation{metrics: 1, values: []float64{0}, active: 1, job: FailureFlags{Dimension: true}, instances: map[string]FailureFlags{"default": {Dimension: true}}}
799
- switch v {
800
- case variantCollectErr:
801
- input.CollectError = errors.New("collect")
802
- expect.metrics = 0
803
- expect.values = nil
804
- expect.active = 1
805
- expect.job = FailureFlags{Collect: true}
806
- expect.instances = map[string]FailureFlags{"default": {Collect: true}}
807
- case variantExtractionErr:
808
- input.Payload = []byte("name,value\nrow1,oops\nrow2,20")
809
- expect.metrics = 1
810
- expect.values = nil
811
- // Extraction error takes precedence; multi-metric warning doesn't add Dimension flag
812
- expect.job = FailureFlags{Extraction: true}
813
- expect.instances = map[string]FailureFlags{"default": {Extraction: true}}
814
- case variantDimensionErr:
815
- input.Payload = []byte("name,value\nrow1,5000\nrow2,20")
816
- expect.metrics = 1
817
- expect.values = nil
818
- expect.job = FailureFlags{Dimension: true}
819
- expect.instances = map[string]FailureFlags{"default": {Dimension: true}}
820
- }
821
- return scenario{name: name, cfg: cfg, input: input, expect: expect}
822
-}
823
-
824
-func variantName(v variant) string {
825
- switch v {
826
- case variantNormal:
827
- return "normal"
828
- case variantCollectErr:
829
- return "collect"
830
- case variantExtractionErr:
831
- return "extract"
832
- case variantDimensionErr:
833
- return "dimension"
834
- case variantLLDErr:
835
- return "lld"
836
- default:
837
- return "unknown"
838
- }
839
-}
840
-
841
-// job config builders -------------------------------------------------------
842
-
843
-func simpleJobConfig(name string) pkgzabbix.JobConfig {
844
- return pkgzabbix.JobConfig{
845
- Name: name,
846
- Collection: pkgzabbix.CollectionConfig{
847
- Type: pkgzabbix.CollectionCommand,
848
- Command: "/usr/bin/true",
849
- },
850
- Pipelines: []pkgzabbix.PipelineConfig{
851
- {
852
- Name: "value",
853
- Context: "zabbix.simple.value",
854
- Dimension: "value",
855
- Unit: "value",
856
- Steps: []zpre.Step{
857
- {Type: zpre.StepTypeJSONPath, Params: "$.value"},
858
- {Type: zpre.StepTypeValidateRange, Params: "0\n100"},
859
- },
860
- },
861
- },
862
- }
863
-}
864
-
865
-func multiPipelineJobConfig(name string) pkgzabbix.JobConfig {
866
- return pkgzabbix.JobConfig{
867
- Name: name,
868
- Collection: pkgzabbix.CollectionConfig{
869
- Type: pkgzabbix.CollectionCommand,
870
- Command: "/usr/bin/true",
871
- },
872
- Pipelines: []pkgzabbix.PipelineConfig{
873
- {
874
- Name: "used",
875
- Context: "zabbix.multi.used",
876
- Dimension: "used",
877
- Unit: "bytes",
878
- Precision: 0,
879
- Steps: []zpre.Step{
880
- {Type: zpre.StepTypeJSONPath, Params: "$.used"},
881
- {Type: zpre.StepTypeValidateRange, Params: "0\n100"},
882
- {Type: zpre.StepTypeMultiplier, Params: "2"},
883
- },
884
- },
885
- {
886
- Name: "free",
887
- Context: "zabbix.multi.free",
888
- Dimension: "free",
889
- Unit: "bytes",
890
- Steps: []zpre.Step{
891
- {Type: zpre.StepTypeJSONPath, Params: "$.free"},
892
- {Type: zpre.StepTypeValidateRange, Params: "0\n100"},
893
- },
894
- },
895
- },
896
- }
897
-}
898
-
899
-func lldJobConfig(name string) pkgzabbix.JobConfig {
900
- return pkgzabbix.JobConfig{
901
- Name: name,
902
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
903
- LLD: pkgzabbix.LLDConfig{
904
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.discovery"}},
905
- InstanceTemplate: "{#FSNAME}",
906
- MaxMissing: 0,
907
- },
908
- Pipelines: []pkgzabbix.PipelineConfig{
909
- {
910
- Name: "used",
911
- Context: "zabbix.lld.used",
912
- Dimension: "used",
913
- Unit: "bytes",
914
- Steps: []zpre.Step{
915
- {Type: zpre.StepTypeJSONPath, Params: "$.data[?(@.fs == '{#FSNAME}')].used"},
916
- {Type: zpre.StepTypeValidateRange, Params: "0\n100"},
917
- },
918
- },
919
- {
920
- Name: "free",
921
- Context: "zabbix.lld.free",
922
- Dimension: "free",
923
- Unit: "bytes",
924
- Steps: []zpre.Step{
925
- {Type: zpre.StepTypeJSONPath, Params: "$.data[?(@.fs == '{#FSNAME}')].free"},
926
- {Type: zpre.StepTypeValidateRange, Params: "0\n100"},
927
- },
928
- },
929
- },
930
- }
931
-}
932
-
933
-func csvLLDJobConfig(name string) pkgzabbix.JobConfig {
934
- const csvParams = `,
935
-"
936
-1`
937
- return pkgzabbix.JobConfig{
938
- Name: name,
939
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
940
- LLD: pkgzabbix.LLDConfig{
941
- Steps: []zpre.Step{
942
- {Type: zpre.StepTypeCSVToJSON, Params: csvParams},
943
- {Type: zpre.StepTypeJSONPath, Params: "$"},
944
- },
945
- InstanceTemplate: "{#FSNAME}",
946
- MaxMissing: 0,
947
- },
948
- Pipelines: []pkgzabbix.PipelineConfig{
949
- {
950
- Name: "path",
951
- Context: "zabbix.csv.path",
952
- Dimension: "value",
953
- Unit: "characters",
954
- Steps: []zpre.Step{
955
- {Type: zpre.StepTypeCSVToJSON, Params: csvParams},
956
- {Type: zpre.StepTypeJavaScript, Params: pathLengthScript},
957
- },
958
- },
959
- },
960
- }
961
-}
962
-
963
-func xmlLLDJobConfig(name string) pkgzabbix.JobConfig {
964
- script := `var data = JSON.parse(value);
965
-var list = data.files.fs;
966
-if (!Array.isArray(list)) list = [list];
967
-var fsMacro = "{#" + "FSNAME}";
968
-var pathMacro = "{#" + "FSPATH}";
969
-var out = list.map(function(node){
970
- var entry = {};
971
- entry[fsMacro] = node["@name"];
972
- entry[pathMacro] = node["@path"];
973
- return entry;
974
-});
975
-value = JSON.stringify(out);
976
-return value;`
977
- return pkgzabbix.JobConfig{
978
- Name: name,
979
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
980
- LLD: pkgzabbix.LLDConfig{
981
- Steps: []zpre.Step{
982
- {Type: zpre.StepTypeXMLToJSON},
983
- {Type: zpre.StepTypeJavaScript, Params: script},
984
- },
985
- InstanceTemplate: "{#FSNAME}",
986
- MaxMissing: 0,
987
- },
988
- Pipelines: []pkgzabbix.PipelineConfig{
989
- {
990
- Name: "path",
991
- Context: "zabbix.xml.path",
992
- Dimension: "value",
993
- Unit: "characters",
994
- Steps: []zpre.Step{
995
- {Type: zpre.StepTypeXMLToJSON},
996
- {Type: zpre.StepTypeJavaScript, Params: script},
997
- {Type: zpre.StepTypeJavaScript, Params: pathLengthScript},
998
- },
999
- },
1000
- },
1001
- }
1002
-}
1003
-
1004
-func snmpLLDJobConfig(name string) pkgzabbix.JobConfig {
1005
- params := "MACRO1\n.1.3.6.1\n0\nMACRO2\n.1.3.6.2\n0"
1006
- return pkgzabbix.JobConfig{
1007
- Name: name,
1008
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
1009
- LLD: pkgzabbix.LLDConfig{
1010
- Steps: []zpre.Step{{Type: zpre.StepTypeSNMPWalkToJSON, Params: params}},
1011
- InstanceTemplate: "MACRO1",
1012
- MaxMissing: 0,
1013
- },
1014
- Pipelines: []pkgzabbix.PipelineConfig{
1015
- {
1016
- Name: "snmp_value",
1017
- Context: "zabbix.snmp.value",
1018
- Dimension: "value",
1019
- Unit: "items",
1020
- Steps: []zpre.Step{
1021
- {Type: zpre.StepTypeSNMPWalkToJSON, Params: params},
1022
- {Type: zpre.StepTypeJavaScript, Params: snmpValueScript},
1023
- },
1024
- },
1025
- },
1026
- }
1027
-}
1028
-
1029
-func prometheusJobConfig(name string) pkgzabbix.JobConfig {
1030
- return pkgzabbix.JobConfig{
1031
- Name: name,
1032
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
1033
- LLD: pkgzabbix.LLDConfig{
1034
- Steps: []zpre.Step{
1035
- {Type: zpre.StepTypePrometheusToJSON},
1036
- {Type: zpre.StepTypeJavaScript, Params: promDiscoveryScript},
1037
- },
1038
- InstanceTemplate: "fs_{#MOUNT}",
1039
- MaxMissing: 0,
1040
- },
1041
- Pipelines: []pkgzabbix.PipelineConfig{
1042
- {
1043
- Name: "free",
1044
- Context: "zabbix.prom.free",
1045
- Dimension: "free",
1046
- Unit: "bytes",
1047
- Steps: []zpre.Step{
1048
- {Type: zpre.StepTypePrometheusToJSON},
1049
- {Type: zpre.StepTypeJavaScript, Params: promValueScript},
1050
- {Type: zpre.StepTypeValidateRange, Params: "0\n1000"},
1051
- },
1052
- },
1053
- },
1054
- }
1055
-}
1056
-
1057
-func xpathJobConfig(name string) pkgzabbix.JobConfig {
1058
- return pkgzabbix.JobConfig{
1059
- Name: name,
1060
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
1061
- Pipelines: []pkgzabbix.PipelineConfig{
1062
- {
1063
- Name: "cpu",
1064
- Context: "zabbix.xpath.cpu",
1065
- Dimension: "cpu",
1066
- Unit: "celsius",
1067
- Steps: []zpre.Step{
1068
- {Type: zpre.StepTypeXPath, Params: "string(/sensors/sensor[@id='cpu']/text())"},
1069
- {Type: zpre.StepTypeValidateRange, Params: "0\n150"},
1070
- },
1071
- },
1072
- {
1073
- Name: "gpu",
1074
- Context: "zabbix.xpath.gpu",
1075
- Dimension: "gpu",
1076
- Unit: "celsius",
1077
- Steps: []zpre.Step{
1078
- {Type: zpre.StepTypeXPath, Params: "string(/sensors/sensor[@id='gpu']/text())"},
1079
- {Type: zpre.StepTypeValidateRange, Params: "0\n150"},
1080
- },
1081
- },
1082
- },
1083
- }
1084
-}
1085
-
1086
-func csvMultiJobConfig(name string) pkgzabbix.JobConfig {
1087
- const csvParams = `,
1088
-"
1089
-1`
1090
- return pkgzabbix.JobConfig{
1091
- Name: name,
1092
- Collection: pkgzabbix.CollectionConfig{Type: pkgzabbix.CollectionCommand, Command: "/usr/bin/true"},
1093
- Pipelines: []pkgzabbix.PipelineConfig{
1094
- {
1095
- Name: "first_value",
1096
- Context: "zabbix.csv.multi",
1097
- Dimension: "value",
1098
- Unit: "items",
1099
- Steps: []zpre.Step{
1100
- {Type: zpre.StepTypeCSVToJSONMulti, Params: csvParams},
1101
- {Type: zpre.StepTypeJavaScript, Params: csvMultiValueScript},
1102
- {Type: zpre.StepTypeValidateRange, Params: "0\n1000"},
1103
- },
1104
- },
1105
- },
1106
- }
1107
-}
src/go/plugin/scripts.d/pkg/zabbix/runtime.go
deleted
-508
@@ -1,508 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package zabbix
4
-
5
-import (
6
- "bytes"
7
- "context"
8
- "fmt"
9
- "io"
10
- "net/http"
11
- "strings"
12
- "sync"
13
- "time"
14
-
15
- "github.com/gosnmp/gosnmp"
16
-
17
- "github.com/netdata/netdata/go/plugins/logger"
18
- "github.com/netdata/netdata/go/plugins/pkg/confopt"
19
- "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/ndexec"
20
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
21
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/schedulers"
22
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/spec"
23
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
24
-)
25
-
26
-// Runtime orchestrates Zabbix jobs using the shared scripts.d scheduler infrastructure.
27
-type Runtime struct {
28
- proc *zpre.Preprocessor
29
- handles map[string][]*schedulers.JobHandle
30
- mu sync.RWMutex
31
-}
32
-
33
-// NewRuntime wires validated jobs into schedulers and starts them.
34
-func NewRuntime(configs []JobConfig, proc *zpre.Preprocessor, log *logger.Logger, emitter runtime.ResultEmitter, vnodeLookup func(spec.JobSpec) runtime.VnodeInfo) (*Runtime, error) {
35
- if len(configs) == 0 {
36
- return nil, fmt.Errorf("zabbix runtime requires at least one job")
37
- }
38
- if proc == nil {
39
- return nil, fmt.Errorf("zabbix runtime requires a preprocessor instance")
40
- }
41
-
42
- normalized := make([]JobConfig, len(configs))
43
- for i := range configs {
44
- normalized[i] = configs[i]
45
- if strings.TrimSpace(normalized[i].Scheduler) == "" {
46
- normalized[i].Scheduler = "default"
47
- }
48
- }
49
-
50
- if emitter == nil {
51
- emitter = runtime.NewNoopEmitter()
52
- }
53
- collector := newJobCollector(normalized, log)
54
- grouped := groupByScheduler(normalized)
55
- handles := make(map[string][]*schedulers.JobHandle, len(grouped))
56
-
57
- detachAll := func(h map[string][]*schedulers.JobHandle) {
58
- for _, list := range h {
59
- for _, handle := range list {
60
- schedulers.DetachJob(handle)
61
- }
62
- }
63
- }
64
-
65
- for name, cfgs := range grouped {
66
- if _, ok := schedulers.Get(name); !ok {
67
- detachAll(handles)
68
- return nil, fmt.Errorf("scheduler '%s' not defined", name)
69
- }
70
- jobSpecs, err := buildJobSpecs(cfgs)
71
- if err != nil {
72
- detachAll(handles)
73
- return nil, err
74
- }
75
- for i := range jobSpecs {
76
- var vnode runtime.VnodeInfo
77
- if vnodeLookup != nil {
78
- vnode = runtime.CloneVnodeInfo(vnodeLookup(jobSpecs[i]))
79
- }
80
- reg := runtime.JobRegistration{
81
- Spec: jobSpecs[i],
82
- Runner: collector.Run,
83
- Emitter: emitter,
84
- Vnode: vnode,
85
- }
86
- handle, err := schedulers.AttachJob(name, reg, log)
87
- if err != nil {
88
- detachAll(handles)
89
- return nil, err
90
- }
91
- handles[name] = append(handles[name], handle)
92
- }
93
- }
94
-
95
- return &Runtime{proc: proc, handles: handles}, nil
96
-}
97
-
98
-// Collect aggregates metrics from every scheduler.
99
-func (r *Runtime) Collect() map[string]int64 {
100
- r.mu.RLock()
101
- defer r.mu.RUnlock()
102
- metrics := make(map[string]int64)
103
- for scheduler := range r.handles {
104
- data := schedulers.CollectMetrics(scheduler)
105
- for k, v := range data {
106
- metrics[k] += v
107
- }
108
- }
109
- if len(metrics) == 0 {
110
- return nil
111
- }
112
- return metrics
113
-}
114
-
115
-// Stop shuts down all managed schedulers and closes the emitter.
116
-func (r *Runtime) Stop() {
117
- r.mu.Lock()
118
- defer r.mu.Unlock()
119
- for scheduler, list := range r.handles {
120
- for _, handle := range list {
121
- schedulers.DetachJob(handle)
122
- }
123
- delete(r.handles, scheduler)
124
- }
125
-}
126
-
127
-func groupByScheduler(cfgs []JobConfig) map[string][]JobConfig {
128
- res := make(map[string][]JobConfig)
129
- for _, cfg := range cfgs {
130
- name := schedulerName(cfg.Scheduler)
131
- cfg.Scheduler = name
132
- res[name] = append(res[name], cfg)
133
- }
134
- return res
135
-}
136
-
137
-func schedulerName(name string) string {
138
- if strings.TrimSpace(name) == "" {
139
- return "default"
140
- }
141
- return name
142
-}
143
-
144
-func buildJobSpecs(cfgs []JobConfig) ([]spec.JobSpec, error) {
145
- specs := make([]spec.JobSpec, len(cfgs))
146
- for i := range cfgs {
147
- cfg := cfgs[i]
148
- job := spec.JobConfig{
149
- Name: cfg.Name,
150
- Plugin: pluginName(cfg),
151
- Args: append([]string{}, cfg.Collection.Args...),
152
- Timeout: confopt.Duration(defaultTimeout(cfg)),
153
- Scheduler: cfg.Scheduler,
154
- Vnode: cfg.Vnode,
155
- }
156
- job.CheckInterval = confopt.Duration(cfg.IntervalDuration())
157
- sp, err := job.ToSpec()
158
- if err != nil {
159
- return nil, err
160
- }
161
- specs[i] = sp
162
- }
163
- return specs, nil
164
-}
165
-
166
-func pluginName(cfg JobConfig) string {
167
- switch cfg.Collection.Type {
168
- case CollectionHTTP:
169
- return "zabbix-http"
170
- case CollectionSNMP:
171
- return "zabbix-snmp"
172
- case CollectionCommand, "":
173
- return strings.TrimSpace(cfg.Collection.Command)
174
- default:
175
- return fmt.Sprintf("zabbix-%s", cfg.Collection.Type)
176
- }
177
-}
178
-
179
-func defaultTimeout(cfg JobConfig) time.Duration {
180
- if d := cfg.Collection.TimeoutDuration(); d > 0 {
181
- return d
182
- }
183
- return 30 * time.Second
184
-}
185
-
186
-type jobCollector struct {
187
- log *logger.Logger
188
- jobs map[string]JobConfig
189
-}
190
-
191
-func newJobCollector(cfgs []JobConfig, log *logger.Logger) *jobCollector {
192
- jobs := make(map[string]JobConfig, len(cfgs))
193
- for _, cfg := range cfgs {
194
- jobs[cfg.Name] = cfg
195
- }
196
- return &jobCollector{log: log, jobs: jobs}
197
-}
198
-
199
-func (c *jobCollector) Run(ctx context.Context, job runtime.JobRuntime, timeout time.Duration) ([]byte, string, ndexec.ResourceUsage, error) {
200
- cfg, ok := c.jobs[job.Spec.Name]
201
- if !ok {
202
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("zabbix: unknown job %s", job.Spec.Name)
203
- }
204
- macros := buildCollectionMacros(cfg, job)
205
- switch cfg.Collection.Type {
206
- case CollectionCommand, "":
207
- return c.runCommand(ctx, job, cfg, macros, timeout)
208
- case CollectionHTTP:
209
- return c.runHTTP(ctx, job, cfg, macros, timeout)
210
- case CollectionSNMP:
211
- return c.runSNMP(ctx, job, cfg, macros, timeout)
212
- default:
213
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("zabbix: unsupported collection type %q", cfg.Collection.Type)
214
- }
215
-}
216
-
217
-func (c *jobCollector) runCommand(ctx context.Context, job runtime.JobRuntime, cfg JobConfig, macros map[string]string, timeout time.Duration) ([]byte, string, ndexec.ResourceUsage, error) {
218
- cmd := strings.TrimSpace(expandTemplate(cfg.Collection.Command, macros))
219
- if cmd == "" {
220
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("command is required for command collection")
221
- }
222
- var env []string
223
- for k, v := range expandStringMap(cfg.Collection.Environment, macros) {
224
- env = append(env, fmt.Sprintf("%s=%s", k, v))
225
- }
226
- opts := ndexec.RunOptions{Env: env}
227
- args := expandStringSlice(cfg.Collection.Args, macros)
228
- data, cmdStr, usage, err := ndexec.RunUnprivilegedWithOptionsUsage(c.log, timeout, opts, cmd, args...)
229
- return data, cmdStr, usage, err
230
-}
231
-
232
-func (c *jobCollector) runHTTP(ctx context.Context, job runtime.JobRuntime, cfg JobConfig, macros map[string]string, timeout time.Duration) ([]byte, string, ndexec.ResourceUsage, error) {
233
- httpCfg := cfg.Collection.HTTP
234
- if httpCfg.URL == "" {
235
- httpCfg.URL = cfg.Collection.URL
236
- }
237
- if httpCfg.URL == "" {
238
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("http.url is required")
239
- }
240
- method := strings.ToUpper(strings.TrimSpace(httpCfg.Method))
241
- if method == "" {
242
- method = strings.ToUpper(strings.TrimSpace(cfg.Collection.Method))
243
- }
244
- if method == "" {
245
- method = "GET"
246
- }
247
- urlStr := expandTemplate(httpCfg.URL, macros)
248
- var body io.Reader
249
- if httpCfg.Body != "" {
250
- body = strings.NewReader(expandTemplate(httpCfg.Body, macros))
251
- } else if cfg.Collection.Body != "" {
252
- body = strings.NewReader(expandTemplate(cfg.Collection.Body, macros))
253
- }
254
- req, err := http.NewRequestWithContext(ctx, method, urlStr, body)
255
- if err != nil {
256
- return nil, "", ndexec.ResourceUsage{}, err
257
- }
258
- headers := mergeStringMap(cfg.Collection.Headers, httpCfg.Headers)
259
- for k, v := range headers {
260
- req.Header.Set(k, expandTemplate(v, macros))
261
- }
262
- if httpCfg.Username != "" {
263
- user := expandTemplate(httpCfg.Username, macros)
264
- pass := expandTemplate(httpCfg.Password, macros)
265
- req.SetBasicAuth(user, pass)
266
- }
267
- if httpCfg.TLS {
268
- if req.URL.Scheme == "" {
269
- req.URL.Scheme = "https"
270
- } else if req.URL.Scheme != "https" {
271
- req.URL.Scheme = "https"
272
- }
273
- }
274
- client := &http.Client{Timeout: timeout}
275
- resp, err := client.Do(req)
276
- if err != nil {
277
- return nil, "", ndexec.ResourceUsage{}, err
278
- }
279
- defer resp.Body.Close()
280
- buf := &bytes.Buffer{}
281
- if _, err := io.Copy(buf, resp.Body); err != nil {
282
- return nil, "", ndexec.ResourceUsage{}, err
283
- }
284
- return buf.Bytes(), fmt.Sprintf("http %s", req.URL.String()), ndexec.ResourceUsage{}, nil
285
-}
286
-
287
-func (c *jobCollector) runSNMP(ctx context.Context, job runtime.JobRuntime, cfg JobConfig, macros map[string]string, timeout time.Duration) ([]byte, string, ndexec.ResourceUsage, error) {
288
- snmpCfg := cfg.Collection.SNMP
289
- target := strings.TrimSpace(expandTemplate(snmpCfg.Target, macros))
290
- oid := strings.TrimSpace(expandTemplate(snmpCfg.OID, macros))
291
- if target == "" || oid == "" {
292
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("snmp.target and snmp.oid are required")
293
- }
294
- snmpClient := &gosnmp.GoSNMP{
295
- Target: target,
296
- Port: 161,
297
- Timeout: timeout,
298
- Community: expandTemplate(snmpCfg.Community, macros),
299
- MaxOids: 1,
300
- }
301
- if val := strings.TrimSpace(snmpCfg.Context); val != "" {
302
- snmpClient.ContextName = expandTemplate(val, macros)
303
- }
304
- switch strings.TrimSpace(strings.ToLower(snmpCfg.Version)) {
305
- case "3", "v3":
306
- snmpClient.Version = gosnmp.Version3
307
- security := &gosnmp.UsmSecurityParameters{
308
- UserName: expandTemplate(snmpCfg.User, macros),
309
- AuthenticationPassphrase: expandTemplate(snmpCfg.AuthPass, macros),
310
- PrivacyPassphrase: expandTemplate(snmpCfg.PrivPass, macros),
311
- }
312
- if strings.EqualFold(snmpCfg.AuthProto, "sha") {
313
- security.AuthenticationProtocol = gosnmp.SHA
314
- } else {
315
- security.AuthenticationProtocol = gosnmp.MD5
316
- }
317
- if strings.EqualFold(snmpCfg.PrivProto, "aes") {
318
- security.PrivacyProtocol = gosnmp.AES
319
- } else {
320
- security.PrivacyProtocol = gosnmp.DES
321
- }
322
- snmpClient.SecurityParameters = security
323
- snmpClient.SecurityModel = gosnmp.UserSecurityModel
324
- snmpClient.MsgFlags = gosnmp.AuthPriv
325
- case "1", "v1":
326
- snmpClient.Version = gosnmp.Version1
327
- default:
328
- snmpClient.Version = gosnmp.Version2c
329
- }
330
- if err := snmpClient.Connect(); err != nil {
331
- return nil, "", ndexec.ResourceUsage{}, err
332
- }
333
- defer snmpClient.Conn.Close()
334
- if requiresSNMPWalk(cfg) {
335
- entries, err := snmpClient.BulkWalkAll(oid)
336
- if err != nil {
337
- return nil, "", ndexec.ResourceUsage{}, err
338
- }
339
- if len(entries) == 0 {
340
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("snmp: walk returned no data")
341
- }
342
- payload := formatSNMPWalk(entries)
343
- return []byte(payload), fmt.Sprintf("snmp %s %s walk", target, oid), ndexec.ResourceUsage{}, nil
344
- }
345
- result, err := snmpClient.Get([]string{oid})
346
- if err != nil {
347
- return nil, "", ndexec.ResourceUsage{}, err
348
- }
349
- if len(result.Variables) == 0 {
350
- return nil, "", ndexec.ResourceUsage{}, fmt.Errorf("snmp: no variables returned")
351
- }
352
- val := formatSNMPValue(result.Variables[0])
353
- return []byte(val), fmt.Sprintf("snmp %s %s", target, oid), ndexec.ResourceUsage{}, nil
354
-}
355
-
356
-func mergeStringMap(base map[string]string, override map[string]string) map[string]string {
357
- if len(base) == 0 && len(override) == 0 {
358
- return nil
359
- }
360
- out := make(map[string]string, len(base)+len(override))
361
- for k, v := range base {
362
- out[k] = v
363
- }
364
- for k, v := range override {
365
- out[k] = v
366
- }
367
- return out
368
-}
369
-
370
-func formatSNMPValue(pdu gosnmp.SnmpPDU) string {
371
- switch v := pdu.Value.(type) {
372
- case string:
373
- return v
374
- case []byte:
375
- return string(v)
376
- case int, uint, uint32, uint64, int64:
377
- return fmt.Sprintf("%v", v)
378
- default:
379
- return fmt.Sprintf("%v", v)
380
- }
381
-}
382
-
383
-func formatSNMPWalk(vars []gosnmp.SnmpPDU) string {
384
- var b strings.Builder
385
- for i, entry := range vars {
386
- if i > 0 {
387
- b.WriteByte('\n')
388
- }
389
- fmt.Fprintf(&b, "%s = %s: %s", entry.Name, snmpTypeName(entry.Type), formatSNMPValue(entry))
390
- }
391
- return b.String()
392
-}
393
-
394
-func snmpTypeName(kind gosnmp.Asn1BER) string {
395
- switch kind {
396
- case gosnmp.OctetString:
397
- return "STRING"
398
- case gosnmp.Integer:
399
- return "INTEGER"
400
- case gosnmp.Counter32:
401
- return "Counter32"
402
- case gosnmp.Gauge32:
403
- return "Gauge32"
404
- case gosnmp.IPAddress:
405
- return "IpAddress"
406
- case gosnmp.TimeTicks:
407
- return "Timeticks"
408
- case gosnmp.Counter64:
409
- return "Counter64"
410
- case gosnmp.ObjectIdentifier:
411
- return "OID"
412
- case gosnmp.Opaque:
413
- return "Opaque"
414
- default:
415
- return "Unknown"
416
- }
417
-}
418
-
419
-func requiresSNMPWalk(cfg JobConfig) bool {
420
- for _, pipe := range cfg.Pipelines {
421
- for _, step := range pipe.Steps {
422
- switch step.Type {
423
- case zpre.StepTypeSNMPWalkValue, zpre.StepTypeSNMPWalkToJSON, zpre.StepTypeSNMPWalkToJSONMulti:
424
- return true
425
- }
426
- }
427
- }
428
- return false
429
-}
430
-
431
-func buildCollectionMacros(cfg JobConfig, job runtime.JobRuntime) map[string]string {
432
- macros := make(map[string]string)
433
- host := firstNonEmpty(strings.TrimSpace(job.Vnode.Hostname), strings.TrimSpace(job.Spec.Vnode), strings.TrimSpace(cfg.Vnode), cfg.Name)
434
- if host == "" {
435
- host = cfg.Name
436
- }
437
- alias := firstNonEmpty(strings.TrimSpace(job.Vnode.Labels["_alias"]), host)
438
- ip := strings.TrimSpace(job.Vnode.Labels["_address"])
439
- conn := firstNonEmpty(ip, host)
440
- macros["{HOST.NAME}"] = host
441
- macros["{HOST.HOST}"] = host
442
- macros["{HOST.DNS}"] = host
443
- macros["{HOST.ALIAS}"] = alias
444
- macros["{HOST.IP}"] = ip
445
- macros["{HOST.CONN}"] = conn
446
- macros["{ITEM.NAME}"] = cfg.Name
447
- macros["{ITEM.ID}"] = cfg.Name
448
- macros["{ITEM.KEY}"] = pluginName(cfg)
449
- for k, v := range cfg.UserMacros {
450
- key := strings.TrimSpace(strings.ToUpper(k))
451
- if key == "" {
452
- continue
453
- }
454
- val := v
455
- switch {
456
- case strings.HasPrefix(key, "{$") && strings.HasSuffix(key, "}"):
457
- macros[key] = val
458
- case strings.HasPrefix(key, "$") && strings.HasSuffix(key, "$"):
459
- macros[key] = val
460
- default:
461
- macros[fmt.Sprintf("$%s$", key)] = val
462
- macros[fmt.Sprintf("{$%s}", key)] = val
463
- }
464
- }
465
- return macros
466
-}
467
-
468
-func expandTemplate(tmpl string, macros map[string]string) string {
469
- if tmpl == "" || len(macros) == 0 {
470
- return tmpl
471
- }
472
- res := tmpl
473
- for k, v := range macros {
474
- res = strings.ReplaceAll(res, k, v)
475
- }
476
- return res
477
-}
478
-
479
-func expandStringSlice(values []string, macros map[string]string) []string {
480
- if len(values) == 0 {
481
- return values
482
- }
483
- res := make([]string, len(values))
484
- for i, v := range values {
485
- res[i] = expandTemplate(v, macros)
486
- }
487
- return res
488
-}
489
-
490
-func expandStringMap(values map[string]string, macros map[string]string) map[string]string {
491
- if len(values) == 0 {
492
- return values
493
- }
494
- res := make(map[string]string, len(values))
495
- for k, v := range values {
496
- res[k] = expandTemplate(v, macros)
497
- }
498
- return res
499
-}
500
-
501
-func firstNonEmpty(values ...string) string {
502
- for _, v := range values {
503
- if strings.TrimSpace(v) != "" {
504
- return strings.TrimSpace(v)
505
- }
506
- }
507
- return ""
508
-}
src/go/plugin/scripts.d/pkg/zabbix/runtime_test.go
deleted
-233
@@ -1,233 +0,0 @@
1
-package zabbix
2
-
3
-import (
4
- "context"
5
- "net/http"
6
- "net/http/httptest"
7
- "testing"
8
- "time"
9
-
10
- "github.com/netdata/netdata/go/plugins/pkg/confopt"
11
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/runtime"
12
- "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/spec"
13
- zpre "github.com/netdata/netdata/go/plugins/plugin/scripts.d/pkg/zabbixpreproc"
14
-)
15
-
16
-func TestBuildJobSpecsCommandPlugin(t *testing.T) {
17
- cfg := JobConfig{
18
- Name: "cmd_job",
19
- Scheduler: "default",
20
- Collection: CollectionConfig{
21
- Type: CollectionCommand,
22
- Command: "/usr/libexec/zabbix/collector",
23
- },
24
- }
25
- cfg.Pipelines = []PipelineConfig{{
26
- Name: "usage",
27
- Context: "zabbix.cmd.usage",
28
- Dimension: "value",
29
- Unit: "%",
30
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
31
- }}
32
- specs, err := buildJobSpecs([]JobConfig{cfg})
33
- if err != nil {
34
- t.Fatalf("buildJobSpecs returned error: %v", err)
35
- }
36
- if specs[0].Plugin != cfg.Collection.Command {
37
- t.Fatalf("expected plugin %q, got %q", cfg.Collection.Command, specs[0].Plugin)
38
- }
39
-}
40
-
41
-func TestBuildJobSpecsHTTPPlugin(t *testing.T) {
42
- cfg := JobConfig{
43
- Name: "http_job",
44
- Collection: CollectionConfig{
45
- Type: CollectionHTTP,
46
- HTTP: HTTPConfig{URL: "http://127.0.0.1/api"},
47
- },
48
- }
49
- cfg.Pipelines = []PipelineConfig{{
50
- Name: "latency",
51
- Context: "zabbix.http.latency",
52
- Dimension: "value",
53
- Unit: "ms",
54
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
55
- }}
56
- specs, err := buildJobSpecs([]JobConfig{cfg})
57
- if err != nil {
58
- t.Fatalf("buildJobSpecs returned error: %v", err)
59
- }
60
- if specs[0].Plugin != "zabbix-http" {
61
- t.Fatalf("expected synthetic plugin 'zabbix-http', got %q", specs[0].Plugin)
62
- }
63
-}
64
-
65
-func TestBuildJobSpecsSNMPPlugin(t *testing.T) {
66
- cfg := JobConfig{
67
- Name: "snmp_job",
68
- Collection: CollectionConfig{
69
- Type: CollectionSNMP,
70
- SNMP: SNMPConfig{Target: "127.0.0.1", OID: "1.3.6.1.2.1.1.1.0"},
71
- },
72
- }
73
- cfg.Pipelines = []PipelineConfig{{
74
- Name: "value",
75
- Context: "zabbix.snmp.value",
76
- Dimension: "value",
77
- Unit: "",
78
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
79
- }}
80
- specs, err := buildJobSpecs([]JobConfig{cfg})
81
- if err != nil {
82
- t.Fatalf("buildJobSpecs returned error: %v", err)
83
- }
84
- if specs[0].Plugin != "zabbix-snmp" {
85
- t.Fatalf("expected synthetic plugin 'zabbix-snmp', got %q", specs[0].Plugin)
86
- }
87
-}
88
-
89
-func TestBuildJobSpecsUpdateEvery(t *testing.T) {
90
- cfg := JobConfig{
91
- Name: "interval_job",
92
- UpdateEvery: 42,
93
- Collection: CollectionConfig{
94
- Type: CollectionCommand,
95
- Command: "/usr/bin/true",
96
- },
97
- }
98
- cfg.Pipelines = []PipelineConfig{{
99
- Name: "value",
100
- Context: "zabbix.interval.value",
101
- Dimension: "value",
102
- Unit: "",
103
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
104
- }}
105
- specs, err := buildJobSpecs([]JobConfig{cfg})
106
- if err != nil {
107
- t.Fatalf("buildJobSpecs returned error: %v", err)
108
- }
109
- want := time.Duration(cfg.UpdateEvery) * time.Second
110
- if specs[0].CheckInterval != want {
111
- t.Fatalf("expected check interval %s, got %s", want, specs[0].CheckInterval)
112
- }
113
-}
114
-
115
-func TestBuildJobSpecsDefaultInterval(t *testing.T) {
116
- cfg := JobConfig{
117
- Name: "default_interval_job",
118
- Collection: CollectionConfig{
119
- Type: CollectionCommand,
120
- Command: "/usr/bin/true",
121
- Timeout: confopt.Duration(5 * time.Second),
122
- },
123
- }
124
- cfg.Pipelines = []PipelineConfig{{
125
- Name: "value",
126
- Context: "zabbix.interval.value",
127
- Dimension: "value",
128
- Unit: "",
129
- Steps: []zpre.Step{{Type: zpre.StepTypeJSONPath, Params: "$.value"}},
130
- }}
131
- specs, err := buildJobSpecs([]JobConfig{cfg})
132
- if err != nil {
133
- t.Fatalf("buildJobSpecs returned error: %v", err)
134
- }
135
- if specs[0].CheckInterval != time.Minute {
136
- t.Fatalf("expected default check interval %s, got %s", time.Minute, specs[0].CheckInterval)
137
- }
138
-}
139
-
140
-func TestBuildCollectionMacros(t *testing.T) {
141
- cfg := JobConfig{Name: "disk", Collection: CollectionConfig{Type: CollectionCommand, Command: "/bin/true"}}
142
- job := runtime.JobRuntime{Spec: spec.JobSpec{Name: cfg.Name}, Vnode: runtime.VnodeInfo{Hostname: "agent", Labels: map[string]string{"_address": "10.0.0.1", "_alias": "agent-alias"}}}
143
- macros := buildCollectionMacros(cfg, job)
144
- if macros["{HOST.NAME}"] != "agent" {
145
- t.Fatalf("expected host macro 'agent', got %q", macros["{HOST.NAME}"])
146
- }
147
- if macros["{HOST.IP}"] != "10.0.0.1" {
148
- t.Fatalf("expected host ip macro '10.0.0.1', got %q", macros["{HOST.IP}"])
149
- }
150
- if macros["{ITEM.KEY}"] == "" {
151
- t.Fatalf("expected item key macro to be populated")
152
- }
153
-}
154
-
155
-func TestRunHTTPMacroExpansion(t *testing.T) {
156
- srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
157
- if r.Method != http.MethodPost {
158
- t.Fatalf("expected POST method, got %s", r.Method)
159
- }
160
- if r.Header.Get("X-Host") != "agent" {
161
- t.Fatalf("expected header X-Host=agent, got %s", r.Header.Get("X-Host"))
162
- }
163
- if r.URL.Path != "/api/agent" {
164
- t.Fatalf("unexpected path %s", r.URL.Path)
165
- }
166
- w.WriteHeader(http.StatusOK)
167
- _, _ = w.Write([]byte("ok"))
168
- }))
169
- defer srv.Close()
170
-
171
- cfg := JobConfig{
172
- Name: "http_job",
173
- Collection: CollectionConfig{
174
- Type: CollectionHTTP,
175
- HTTP: HTTPConfig{
176
- URL: srv.URL + "/api/{HOST.NAME}",
177
- Headers: map[string]string{"X-Host": "{HOST.NAME}"},
178
- Body: "value={ITEM.NAME}",
179
- },
180
- Method: "post",
181
- },
182
- }
183
- collector := newJobCollector([]JobConfig{cfg}, nil)
184
- job := runtime.JobRuntime{Spec: spec.JobSpec{Name: cfg.Name}, Vnode: runtime.VnodeInfo{Hostname: "agent"}}
185
- macros := buildCollectionMacros(cfg, job)
186
- data, _, _, err := collector.runHTTP(context.Background(), job, cfg, macros, time.Second)
187
- if err != nil {
188
- t.Fatalf("runHTTP returned error: %v", err)
189
- }
190
- if string(data) != "ok" {
191
- t.Fatalf("expected response 'ok', got %q", string(data))
192
- }
193
-}
194
-
195
-func TestRequiresSNMPWalk(t *testing.T) {
196
- cfg := JobConfig{
197
- Name: "snmp_walk",
198
- Collection: CollectionConfig{
199
- Type: CollectionSNMP,
200
- SNMP: SNMPConfig{Target: "127.0.0.1", OID: ".1.3.6.1"},
201
- },
202
- Pipelines: []PipelineConfig{{
203
- Name: "walk",
204
- Steps: []zpre.Step{{Type: zpre.StepTypeSNMPWalkToJSON, Params: "{#IFNAME}\n.1.3.6.1\n1"}},
205
- Context: "zabbix.snmp.walk",
206
- Dimension: "value",
207
- Unit: "value",
208
- }},
209
- }
210
- if !requiresSNMPWalk(cfg) {
211
- t.Fatalf("expected requiresSNMPWalk to be true")
212
- }
213
-}
214
-
215
-func TestRunSNMPWalk(t *testing.T) {
216
- cfg := JobConfig{
217
- Name: "snmp_walk",
218
- Collection: CollectionConfig{
219
- Type: CollectionSNMP,
220
- SNMP: SNMPConfig{Target: "127.0.0.1", OID: ".1.3.6.1"},
221
- },
222
- Pipelines: []PipelineConfig{{
223
- Name: "walk",
224
- Steps: []zpre.Step{{Type: zpre.StepTypeSNMPWalkToJSON, Params: "{#IFNAME}\n.1.3.6.1\n1"}},
225
- Context: "zabbix.snmp.walk",
226
- Dimension: "value",
227
- Unit: "value",
228
- }},
229
- }
230
- if !requiresSNMPWalk(cfg) {
231
- t.Fatalf("expected requiresSNMPWalk to be true")
232
- }
233
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/API-DESIGN.md
deleted
-220
@@ -1,220 +0,0 @@
1
-# Zabbix Preprocessing Library - API Design Specification
2
-
3
-**Last Updated:** 2025-11-14
4
-**Status:** Authoritative Requirements
5
-
6
----
7
-
8
-## USER REQUIREMENTS (FINAL - DO NOT CHANGE)
9
-
10
-### Input Parameters
11
-The Netdata plugin will provide **TWO parameters**:
12
-1. **Unique identifier** for continuity (state tracking)
13
-2. **Just collected value** (the data to preprocess)
14
-
15
-### State Management
16
-- Library **MUST** maintain state based on the unique identifier
17
-- Unique identifier is **unique per shard, NOT across shards**
18
-- Example: "item1" in shard "A" is different from "item1" in shard "B"
19
-
20
-### Output Format
21
-The returned value of any collected sample is:
22
-1. **Array of metrics** (optionally with labels)
23
-2. **Possibly logs/events** to be committed to logs
24
-3. **Structured errors** for the caller to handle and understand what happened
25
-
26
-### Implementation Authority
27
-**Everything else is implementation details that the library author decides.**
28
-
29
----
30
-
31
-## IMPLEMENTATION DECISIONS (CLAUDE'S AUTHORITY)
32
-
33
-### API Design Decision
34
-
35
-**Chosen Approach:** Explicit itemID parameter + Result struct return
36
-
37
-```go
38
-// Execute processes a single preprocessing step for a specific item
39
-func (p *Preprocessor) Execute(itemID string, value Value, step Step) (Result, error)
40
-
41
-// ExecutePipeline processes multiple steps for a specific item
42
-func (p *Preprocessor) ExecutePipeline(itemID string, value Value, steps []Step) (Result, error)
43
-
44
-// Result contains the output of preprocessing
45
-type Result struct {
46
- Metrics []Metric // Array of extracted metrics
47
- Logs []string // Optional log entries (future use)
48
- Error error // Overall error if preprocessing failed
49
-}
50
-
51
-// Metric represents a single metric output
52
-type Metric struct {
53
- Name string // Metric name
54
- Value string // Metric value
55
- Type ValueType // Value type
56
- Labels map[string]string // Optional labels
57
-}
58
-```
59
-
60
-**Rationale:**
61
-1. **itemID parameter**: User said "unique identifier for continuity" and "just collected value" as TWO parameters
62
-2. **Result return**: User said "array of metrics" not single value
63
-3. **Error in Result**: Allows partial success (some metrics succeed, some fail)
64
-
65
-### State Key Format
66
-
67
-State keys will be: `"{shardID}:{itemID}:{operation}:{params_hash}"`
68
-
69
-- `shardID`: From NewPreprocessor(shardID)
70
-- `itemID`: From Execute(itemID, ...)
71
-- `operation`: "delta_value", "delta_speed", "throttle_value", "throttle_timed"
72
-- `params_hash`: Hash of step.Params for multi-param operations
73
-
74
-**Example:** `"shard1:item123:delta_value:"`
75
-
76
-### Thread Safety
77
-
78
-- State map already protected with sync.RWMutex (P0 #5 resolved)
79
-- itemID is per-call, no shared state
80
-- Multiple goroutines can call Execute() concurrently with different itemIDs
81
-
82
-### Migration Path
83
-
84
-**Phase 1 (Current):** Keep old API for backward compatibility
85
-```go
86
-// DEPRECATED: Use Execute(itemID, value, step) instead
87
-func (p *Preprocessor) execute(value Value, step Step) (Value, error) {
88
- // Calls Execute("", value, step) - empty itemID = global state
89
-}
90
-```
91
-
92
-**Phase 2 (After Netdata integration):** Remove deprecated API
93
-
94
-### Multi-Metric Extraction
95
-
96
-Currently, preprocessing operations return single values. Multi-metric extraction (JSONPath arrays, Prometheus scrapes, CSV rows) will be:
97
-
98
-**Phase 1 (Current):** Single metric in array
99
-```go
100
-Result{
101
- Metrics: []Metric{{Name: "", Value: "result", Type: ValueTypeStr}},
102
-}
103
-```
104
-
105
-**Phase 2 (Future):** Actual multi-metric extraction
106
-```go
107
-// JSONPath with array result
108
-Result{
109
- Metrics: []Metric{
110
- {Name: "metric1", Value: "10", Labels: map[string]string{"index": "0"}},
111
- {Name: "metric2", Value: "20", Labels: map[string]string{"index": "1"}},
112
- },
113
-}
114
-```
115
-
116
-### Persistence API
117
-
118
-**Deferred to Phase 3** - not needed for initial Netdata integration:
119
-```go
120
-// SaveState exports preprocessor state for persistence
121
-func (p *Preprocessor) SaveState() ([]byte, error)
122
-
123
-// LoadState imports preprocessor state from persistence
124
-func (p *Preprocessor) LoadState(data []byte) error
125
-```
126
-
127
-**Rationale:** Netdata can recreate preprocessor instances on restart. State loss acceptable for now.
128
-
129
----
130
-
131
-## IMPLEMENTATION CHECKLIST
132
-
133
-**Current Status:** Ready to implement P0 #3
134
-
135
-### Tasks:
136
-1. ✅ Document requirements (this file)
137
-2. ⏳ Add itemID parameter to Execute() and ExecutePipeline()
138
-3. ⏳ Update state key generation to use shardID + itemID
139
-4. ⏳ Change return type from (Value, error) to (Result, error)
140
-5. ⏳ Update all step functions to return Result
141
-6. ⏳ Keep deprecated execute() for backward compatibility
142
-7. ⏳ Update all 362 test cases to use new API
143
-8. ⏳ Add tests for per-item state isolation
144
-9. ⏳ Verify all tests still pass (362/362)
145
-
146
-### Estimated Work: 1-2 days
147
-
148
----
149
-
150
-## NOTES FOR FUTURE SESSIONS
151
-
152
-**If discussing API design again:**
153
-- **STOP** - Read this file first
154
-- Requirements are FINAL (from user)
155
-- Implementation decisions are FINAL (from Claude)
156
-- Just implement, don't debate
157
-
158
-**If user requests changes:**
159
-- Update this file with new requirements
160
-- Mark as FINAL
161
-- Implement
162
-
163
-**If API questions arise:**
164
-- Refer to "IMPLEMENTATION DECISIONS" section
165
-- Make decision, document, implement
166
-- Don't ask user unless truly ambiguous
167
-
168
----
169
-
170
-## BACKWARD COMPATIBILITY NOTES
171
-
172
-**Breaking Changes:**
173
-1. Execute() signature changes: `(value, step)` → `(itemID, value, step)`
174
-2. Return type changes: `(Value, error)` → `(Result, error)`
175
-
176
-**Migration:**
177
-```go
178
-// Old code:
179
-result, err := p.Execute(value, step)
180
-
181
-// New code:
182
-result, err := p.Execute("item123", value, step)
183
-// result is now Result{Metrics: []Metric{...}}
184
-```
185
-
186
-**Compatibility Layer:**
187
-- Keep old execute() as private method
188
-- Old tests can use it temporarily
189
-- Remove after Netdata integration complete
190
-
191
----
192
-
193
-## REJECTED ALTERNATIVES
194
-
195
-**Why not add itemID to Value struct?**
196
-- User said TWO parameters: identifier + value
197
-- Pollutes Value with state management concerns
198
-- Less explicit than parameter
199
-
200
-**Why not SetItemContext()?**
201
-- Not thread-safe without locking
202
-- Awkward for concurrent processing
203
-- Violates "explicit is better than implicit"
204
-
205
-**Why not per-item Preprocessor instances?**
206
-- Memory overhead (one instance per item)
207
-- Complex lifecycle management
208
-- User said "unique identifier for continuity" not "instance per item"
209
-
210
-**Why not return ([]Metric, error)?**
211
-- Need structured errors AND logs in future
212
-- Result struct allows extension without API changes
213
-- Matches user requirement for "logs/events"
214
-
215
----
216
-
217
-## END OF SPECIFICATION
218
-
219
-This document is the single source of truth for API design.
220
-Do not re-discuss. Just implement.
src/go/plugin/scripts.d/pkg/zabbixpreproc/INTEGRATION.md
deleted
-665
@@ -1,665 +0,0 @@
1
-# Zabbix Preprocessing Library - Integration Guide
2
-
3
-Pure Go implementation of Zabbix preprocessing. 100% compatible with Zabbix official test suite (362/362 tests passing). No CGO dependencies.
4
-
5
-**Package**: `github.com/netdata/zabbix-preproc`
6
-
7
----
8
-
9
-## Quick Start
10
-
11
-```go
12
-import zp "github.com/netdata/zabbix-preproc"
13
-
14
-// Create preprocessor instance (one per shard)
15
-p := zp.NewPreprocessor("shard-1")
16
-
17
-// Define input value
18
-value := zp.Value{
19
- Data: "42.5",
20
- Type: zp.ValueTypeStr,
21
- Timestamp: time.Now(),
22
-}
23
-
24
-// Define preprocessing step
25
-step := zp.Step{
26
- Type: zp.StepTypeMultiplier,
27
- Params: "10",
28
-}
29
-
30
-// Execute
31
-result, err := p.Execute("item-123", value, step)
32
-if err != nil {
33
- log.Fatal(err)
34
-}
35
-
36
-// Get result
37
-fmt.Println(result.Metrics[0].Value) // "425"
38
-```
39
-
40
----
41
-
42
-## Core Types
43
-
44
-### Value
45
-```go
46
-type Value struct {
47
- Data string // Raw string data
48
- Type ValueType // ValueTypeStr (0), ValueTypeFloat (1), ValueTypeUint64 (3)
49
- Timestamp time.Time // Required for stateful operations (delta, throttle)
50
- IsError bool // True if value represents error from previous step
51
-}
52
-
53
-const (
54
- ValueTypeStr ValueType = 0
55
- ValueTypeFloat ValueType = 1
56
- ValueTypeUint64 ValueType = 3
57
-)
58
-```
59
-
60
-### Step
61
-```go
62
-type Step struct {
63
- Type StepType // One of 30 preprocessing types (see below)
64
- Params string // Type-specific parameters
65
- ErrorHandler ErrorHandler // Optional error handling
66
-}
67
-```
68
-
69
-### Result
70
-```go
71
-type Result struct {
72
- Metrics []Metric // Array of extracted metrics
73
- Logs []string // Optional log entries
74
- Error error // Overall error
75
-}
76
-
77
-type Metric struct {
78
- Name string // Metric name
79
- Value string // Metric value as string
80
- Type ValueType // Value type
81
- Labels map[string]string // Optional labels (for Prometheus, etc.)
82
-}
83
-```
84
-
85
----
86
-
87
-## All 30 Preprocessing Types
88
-
89
-### Arithmetic Operations
90
-
91
-**Type 1: Multiplier**
92
-```go
93
-step := zp.Step{Type: zp.StepTypeMultiplier, Params: "10"}
94
-// Input: "42.5" → Output: "425"
95
-```
96
-
97
-**Type 9: Delta Value** (stateful - requires timestamp)
98
-```go
99
-// First call: value=100, timestamp=T1 → returns nothing (needs baseline)
100
-// Second call: value=150, timestamp=T2 → returns "50" (150-100)
101
-step := zp.Step{Type: zp.StepTypeDeltaValue}
102
-```
103
-
104
-**Type 10: Delta Speed** (stateful - requires timestamp)
105
-```go
106
-// Returns (current - previous) / time_diff_seconds
107
-step := zp.Step{Type: zp.StepTypeDeltaSpeed}
108
-```
109
-
110
-### String Operations
111
-
112
-**Type 2: RTrim**
113
-```go
114
-step := zp.Step{Type: zp.StepTypeRTrim, Params: " \t\n"}
115
-// Input: "hello \n" → Output: "hello"
116
-```
117
-
118
-**Type 3: LTrim**
119
-```go
120
-step := zp.Step{Type: zp.StepTypeLTrim, Params: " \t"}
121
-// Input: " hello" → Output: "hello"
122
-```
123
-
124
-**Type 4: Trim**
125
-```go
126
-step := zp.Step{Type: zp.StepTypeTrim, Params: " "}
127
-// Input: " hello " → Output: "hello"
128
-```
129
-
130
-**Type 5: Regex Substitution**
131
-```go
132
-// Params: "pattern\nreplacement"
133
-step := zp.Step{
134
- Type: zp.StepTypeRegexSubstitution,
135
- Params: "\\d+\nNUMBER",
136
-}
137
-// Input: "error code 123" → Output: "error code NUMBER"
138
-
139
-// With capture groups:
140
-step := zp.Step{
141
- Type: zp.StepTypeRegexSubstitution,
142
- Params: "(\\d+)-(\\d+)\n\\2-\\1",
143
-}
144
-// Input: "123-456" → Output: "456-123"
145
-```
146
-
147
-**Type 25: String Replace**
148
-```go
149
-// Params: "search\nreplace"
150
-step := zp.Step{
151
- Type: zp.StepTypeStringReplace,
152
- Params: "old\nnew",
153
-}
154
-// Input: "old value old" → Output: "new value new"
155
-```
156
-
157
-### Type Conversions
158
-
159
-**Type 6: Bool to Decimal**
160
-```go
161
-step := zp.Step{Type: zp.StepTypeBool2Dec}
162
-// "true", "yes", "on", "up", "running", "enabled", "available", non-zero → "1"
163
-// "false", "no", "off", "down", "unused", "disabled", "unavailable", "0" → "0"
164
-```
165
-
166
-**Type 7: Octal to Decimal**
167
-```go
168
-step := zp.Step{Type: zp.StepTypeOct2Dec}
169
-// Input: "755" → Output: "493"
170
-// Input: "0755" → Output: "493"
171
-```
172
-
173
-**Type 8: Hex to Decimal**
174
-```go
175
-step := zp.Step{Type: zp.StepTypeHex2Dec}
176
-// Input: "FF" → Output: "255"
177
-// Input: "0xFF" → Output: "255"
178
-// Input: "0x1A2B" → Output: "6699"
179
-```
180
-
181
-### Data Extraction
182
-
183
-**Type 11: XPath**
184
-```go
185
-step := zp.Step{
186
- Type: zp.StepTypeXPath,
187
- Params: "//server/status/text()",
188
-}
189
-// Input: "<server><status>OK</status></server>"
190
-// Output: "OK"
191
-```
192
-
193
-**Type 12: JSONPath**
194
-```go
195
-step := zp.Step{
196
- Type: zp.StepTypeJSONPath,
197
- Params: "$.store.book[0].price",
198
-}
199
-// Input: `{"store":{"book":[{"price":8.95}]}}`
200
-// Output: "8.95"
201
-
202
-// Array extraction:
203
-step := zp.Step{
204
- Type: zp.StepTypeJSONPath,
205
- Params: "$.store.book[*].price",
206
-}
207
-// Output: "[8.95,12.99,8.99]"
208
-```
209
-
210
-**Type 22: Prometheus Pattern**
211
-```go
212
-// Params: "metric_name\nlabel_name\nfunction"
213
-// Functions: value, label, sum, min, max, avg, count
214
-
215
-// Get metric value:
216
-step := zp.Step{
217
- Type: zp.StepTypePrometheusPattern,
218
- Params: "cpu_usage\n\nvalue",
219
-}
220
-// Input: `cpu_usage{host="server1"} 85.5`
221
-// Output: "85.5"
222
-
223
-// Get label value:
224
-step := zp.Step{
225
- Type: zp.StepTypePrometheusPattern,
226
- Params: "http_requests\nmethod\nlabel",
227
-}
228
-// Input: `http_requests{method="GET"} 100`
229
-// Output: "GET"
230
-
231
-// Aggregate:
232
-step := zp.Step{
233
- Type: zp.StepTypePrometheusPattern,
234
- Params: "cpu_usage\n\nsum",
235
-}
236
-// Multiple metrics → sum of all values
237
-```
238
-
239
-**Type 23: Prometheus to JSON**
240
-```go
241
-step := zp.Step{Type: zp.StepTypePrometheusToJSON}
242
-// Input: `metric_name{label="value"} 123`
243
-// Output: `[{"name":"metric_name","value":"123","labels":{"label":"value"}}]`
244
-```
245
-
246
-**Type 24: CSV to JSON**
247
-```go
248
-// Params: "delimiter\nquote_char\nwith_header\n"
249
-// delimiter: single char (default: ,)
250
-// quote_char: single char (default: ")
251
-// with_header: 1 = first row is header, 0 = no header
252
-
253
-step := zp.Step{
254
- Type: zp.StepTypeCSVToJSON,
255
- Params: ",\n\"\n1\n",
256
-}
257
-// Input:
258
-// name,age,city
259
-// Alice,30,NYC
260
-// Bob,25,LA
261
-
262
-// Output:
263
-// [{"name":"Alice","age":"30","city":"NYC"},{"name":"Bob","age":"25","city":"LA"}]
264
-```
265
-
266
-**Type 27: XML to JSON**
267
-```go
268
-step := zp.Step{Type: zp.StepTypeXMLToJSON}
269
-// Input: `<root><foo bar="BAR">BAZ</foo></root>`
270
-// Output: `{"root":{"foo":{"@bar":"BAR","#text":"BAZ"}}}`
271
-
272
-// Zabbix XML serialization rules:
273
-// 1. Attributes → "@attr": "value"
274
-// 2. Self-closing → null
275
-// 3. Empty attributes → "@attr": ""
276
-// 4. Repeated elements → arrays
277
-// 5. Simple text → direct string
278
-// 6. Text + attributes → "#text": "value"
279
-```
280
-
281
-### Validation
282
-
283
-**Type 13: Validate Range**
284
-```go
285
-// Params: "min\nmax"
286
-step := zp.Step{
287
- Type: zp.StepTypeValidateRange,
288
- Params: "0\n100",
289
-}
290
-// Input: "50" → Output: "50" (passes validation)
291
-// Input: "150" → Error: value out of range
292
-```
293
-
294
-**Type 14: Validate Regex**
295
-```go
296
-step := zp.Step{
297
- Type: zp.StepTypeValidateRegex,
298
- Params: "^\\d+$",
299
-}
300
-// Input: "12345" → passes
301
-// Input: "abc123" → Error: does not match pattern
302
-```
303
-
304
-**Type 15: Validate Not Regex**
305
-```go
306
-step := zp.Step{
307
- Type: zp.StepTypeValidateNotRegex,
308
- Params: "error|fail",
309
-}
310
-// Input: "success" → passes
311
-// Input: "error occurred" → Error: matches forbidden pattern
312
-```
313
-
314
-**Type 26: Validate Not Supported**
315
-```go
316
-step := zp.Step{Type: zp.StepTypeValidateNotSupported}
317
-// Always returns error: "not supported"
318
-// Used to mark items as not supported in preprocessing chain
319
-```
320
-
321
-### Error Field Extraction
322
-
323
-**Type 16: Error Field JSON**
324
-```go
325
-step := zp.Step{
326
- Type: zp.StepTypeErrorFieldJSON,
327
- Params: "$.error.message",
328
-}
329
-// Input: `{"error":{"message":"timeout"}}`
330
-// Output: Error with message "timeout"
331
-```
332
-
333
-**Type 17: Error Field XML**
334
-```go
335
-step := zp.Step{
336
- Type: zp.StepTypeErrorFieldXML,
337
- Params: "//error/text()",
338
-}
339
-// Input: `<response><error>Not found</error></response>`
340
-// Output: Error with message "Not found"
341
-```
342
-
343
-**Type 18: Error Field Regex**
344
-```go
345
-step := zp.Step{
346
- Type: zp.StepTypeErrorFieldRegex,
347
- Params: "ERROR: (.+)",
348
-}
349
-// Input: "ERROR: Connection refused"
350
-// Output: Error with message "Connection refused"
351
-```
352
-
353
-### Throttling (Stateful)
354
-
355
-**Type 19: Throttle Value**
356
-```go
357
-step := zp.Step{Type: zp.StepTypeThrottleValue}
358
-// Only passes through if value changed from previous
359
-// Same value → returns empty result (discarded)
360
-```
361
-
362
-**Type 20: Throttle Timed Value**
363
-```go
364
-// Params: "seconds"
365
-step := zp.Step{
366
- Type: zp.StepTypeThrottleTimedValue,
367
- Params: "60",
368
-}
369
-// Passes through if:
370
-// - Value changed from previous, OR
371
-// - More than 60 seconds since last pass
372
-```
373
-
374
-### JavaScript
375
-
376
-**Type 21: JavaScript**
377
-```go
378
-step := zp.Step{
379
- Type: zp.StepTypeJavaScript,
380
- Params: "return parseFloat(value) * 2;",
381
-}
382
-// Input: "21" → Output: "42"
383
-
384
-// Complex transformation:
385
-step := zp.Step{
386
- Type: zp.StepTypeJavaScript,
387
- Params: `
388
- var obj = JSON.parse(value);
389
- return obj.temperature * 9/5 + 32;
390
- `,
391
-}
392
-// Input: `{"temperature": 20}` → Output: "68"
393
-```
394
-
395
-### SNMP Operations
396
-
397
-**Type 28: SNMP Walk Value**
398
-```go
399
-// Params: "oid\nformat"
400
-// format: 0=unchanged, 1=UTF-8, 2=MAC, 3=Int, 4=Hex, 5=IP
401
-step := zp.Step{
402
- Type: zp.StepTypeSNMPWalkValue,
403
- Params: ".1.3.6.1.2.1.2.2.1.2.1\n0",
404
-}
405
-// Extracts specific OID value from SNMP walk output
406
-```
407
-
408
-**Type 29: SNMP Walk to JSON**
409
-```go
410
-// Params: "field_name\noid_to_extract\nformat"
411
-step := zp.Step{
412
- Type: zp.StepTypeSNMPWalkToJSON,
413
- Params: "interfaces\n.1.3.6.1.2.1.2.2.1.2\n0",
414
-}
415
-// Converts SNMP walk table to JSON structure
416
-```
417
-
418
-**Type 30: SNMP Get Value**
419
-```go
420
-// Params: "format"
421
-// format: 0=unchanged, 1=UTF-8, 2=MAC, 3=Int, 4=Hex, 5=IP
422
-step := zp.Step{
423
- Type: zp.StepTypeSNMPGetValue,
424
- Params: "2", // MAC address format
425
-}
426
-// Input: "0x001122334455" → Output: "00:11:22:33:44:55"
427
-```
428
-
429
----
430
-
431
-## Error Handling
432
-
433
-```go
434
-step := zp.Step{
435
- Type: zp.StepTypeMultiplier,
436
- Params: "10",
437
- ErrorHandler: zp.ErrorHandler{
438
- Action: zp.ErrorActionSetValue,
439
- Params: "0", // Default to 0 on error
440
- },
441
-}
442
-
443
-// Error actions:
444
-// ErrorActionDefault (0) - Return error to caller
445
-// ErrorActionDiscard (1) - Discard value (empty result)
446
-// ErrorActionSetValue (2) - Set custom value
447
-// ErrorActionSetError (3) - Set custom error message
448
-```
449
-
450
----
451
-
452
-## Pipeline Execution
453
-
454
-```go
455
-steps := []zp.Step{
456
- {Type: zp.StepTypeJSONPath, Params: "$.temperature"},
457
- {Type: zp.StepTypeMultiplier, Params: "1.8"},
458
- {Type: zp.StepTypeValidateRange, Params: "-50\n150"},
459
-}
460
-
461
-result, err := p.ExecutePipeline("item-123", value, steps)
462
-```
463
-
464
----
465
-
466
-## Stateful Operations (Delta, Throttle)
467
-
468
-```go
469
-// Create preprocessor for shard
470
-p := zp.NewPreprocessor("shard-1")
471
-
472
-// Delta/Throttle operations require:
473
-// 1. Consistent itemID across calls
474
-// 2. Accurate timestamps
475
-
476
-value1 := zp.Value{Data: "100", Timestamp: time.Now()}
477
-value2 := zp.Value{Data: "150", Timestamp: time.Now().Add(time.Second)}
478
-
479
-step := zp.Step{Type: zp.StepTypeDeltaValue}
480
-
481
-// First call establishes baseline
482
-r1, _ := p.Execute("cpu-counter", value1, step)
483
-// r1.Metrics is empty (needs baseline)
484
-
485
-// Second call computes delta
486
-r2, _ := p.Execute("cpu-counter", value2, step)
487
-// r2.Metrics[0].Value = "50"
488
-```
489
-
490
----
491
-
492
-## State Management
493
-
494
-### Enable Automatic Cleanup
495
-```go
496
-p := zp.NewPreprocessor("shard-1")
497
-
498
-// Clean up state entries unused for 1 hour, check every 10 minutes
499
-stopCleanup := p.EnableStateCleanup(time.Hour, 10*time.Minute)
500
-
501
-// When done
502
-stopCleanup()
503
-```
504
-
505
-### With Config
506
-```go
507
-cfg := zp.Config{
508
- Logger: myLogger,
509
- StateTTL: time.Hour,
510
- StateCleanupInterval: 10 * time.Minute,
511
-}
512
-p := zp.NewPreprocessorWithConfig("shard-1", cfg)
513
-```
514
-
515
----
516
-
517
-## Logging
518
-
519
-```go
520
-// Implement Logger interface
521
-type Logger interface {
522
- Logf(format string, args ...interface{})
523
-}
524
-
525
-// Use your logger
526
-p.SetLogger(myLogger)
527
-
528
-// Or with config
529
-cfg := zp.Config{Logger: myLogger}
530
-p := zp.NewPreprocessorWithConfig("shard-1", cfg)
531
-
532
-// Default: NoopLogger (zero overhead)
533
-```
534
-
535
----
536
-
537
-## Multi-Metric Extensions (Non-Zabbix)
538
-
539
-```go
540
-// Types 60-63 return multiple metrics directly (not JSON string)
541
-step := zp.Step{
542
- Type: zp.StepTypePrometheusToJSONMulti,
543
- Params: "",
544
-}
545
-
546
-result, _ := p.Execute("prom", value, step)
547
-for _, metric := range result.Metrics {
548
- fmt.Printf("%s=%s labels=%v\n", metric.Name, metric.Value, metric.Labels)
549
-}
550
-```
551
-
552
----
553
-
554
-## Thread Safety
555
-
556
-```go
557
-// Single Preprocessor instance is thread-safe
558
-p := zp.NewPreprocessor("shard-1")
559
-
560
-// Safe for concurrent use
561
-go func() {
562
- p.Execute("item-1", val1, step1)
563
-}()
564
-go func() {
565
- p.Execute("item-2", val2, step2)
566
-}()
567
-```
568
-
569
----
570
-
571
-## Complete Example: Netdata Integration
572
-
573
-```go
574
-package main
575
-
576
-import (
577
- "fmt"
578
- "time"
579
- zp "github.com/netdata/zabbix-preproc"
580
-)
581
-
582
-func main() {
583
- // One preprocessor per shard
584
- p := zp.NewPreprocessorWithConfig("netdata-agent-1", zp.Config{
585
- StateTTL: 24 * time.Hour,
586
- StateCleanupInterval: time.Hour,
587
- })
588
-
589
- // Example: Process CPU metric from Prometheus format
590
- promValue := zp.Value{
591
- Data: `
592
-node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67
593
-node_cpu_seconds_total{cpu="0",mode="system"} 1234.56
594
-node_cpu_seconds_total{cpu="0",mode="user"} 9876.54
595
-`,
596
- Type: zp.ValueTypeStr,
597
- Timestamp: time.Now(),
598
- }
599
-
600
- // Extract idle CPU metric
601
- step := zp.Step{
602
- Type: zp.StepTypePrometheusPattern,
603
- Params: "node_cpu_seconds_total\nmode=idle\nvalue",
604
- }
605
-
606
- result, err := p.Execute("cpu.idle", promValue, step)
607
- if err != nil {
608
- panic(err)
609
- }
610
-
611
- fmt.Printf("CPU idle seconds: %s\n", result.Metrics[0].Value)
612
-
613
- // Example: JSON extraction pipeline
614
- jsonValue := zp.Value{
615
- Data: `{"system":{"cpu":{"usage":85.5},"memory":{"used":4096}}}`,
616
- Type: zp.ValueTypeStr,
617
- Timestamp: time.Now(),
618
- }
619
-
620
- pipeline := []zp.Step{
621
- {Type: zp.StepTypeJSONPath, Params: "$.system.cpu.usage"},
622
- {Type: zp.StepTypeValidateRange, Params: "0\n100"},
623
- }
624
-
625
- result, err = p.ExecutePipeline("system.cpu.usage", jsonValue, pipeline)
626
- if err != nil {
627
- panic(err)
628
- }
629
-
630
- fmt.Printf("CPU usage: %s%%\n", result.Metrics[0].Value)
631
-
632
- // Example: Delta calculation (rate of change)
633
- counter1 := zp.Value{
634
- Data: "1000",
635
- Type: zp.ValueTypeStr,
636
- Timestamp: time.Now(),
637
- }
638
- counter2 := zp.Value{
639
- Data: "1100",
640
- Type: zp.ValueTypeStr,
641
- Timestamp: time.Now().Add(time.Minute),
642
- }
643
-
644
- deltaStep := zp.Step{Type: zp.StepTypeDeltaSpeed}
645
-
646
- // First call establishes baseline
647
- p.Execute("network.bytes", counter1, deltaStep)
648
-
649
- // Second call computes rate (bytes/second)
650
- result, _ = p.Execute("network.bytes", counter2, deltaStep)
651
- fmt.Printf("Network rate: %s bytes/sec\n", result.Metrics[0].Value)
652
-}
653
-```
654
-
655
----
656
-
657
-## Dependencies
658
-
659
-- `gopkg.in/yaml.v3` - YAML parsing (test harness)
660
-- `github.com/ohler55/ojg` - JSONPath (RFC 9535 compliant)
661
-- `github.com/antchfx/xmlquery` - XPath
662
-- `github.com/dop251/goja` - JavaScript ES5.1 runtime
663
-- `github.com/prometheus/common/expfmt` - Prometheus text format
664
-
665
-All pure Go - no CGO dependencies.
src/go/plugin/scripts.d/pkg/zabbixpreproc/README.md
deleted
-406
@@ -1,406 +0,0 @@
1
-# Zabbix Preprocessing Library
2
-
3
-Pure Go implementation of Zabbix preprocessing with 100% compatibility.
4
-
5
-## Features
6
-
7
-- ✅ **100% Zabbix Compatible** - Passes all 362 official Zabbix test cases
8
-- ✅ **Pure Go** - No CGO, no external C libraries
9
-- ✅ **Thread-Safe** - Concurrent processing with per-shard state isolation
10
-- ✅ **High Performance** - VM pooling, regex caching, optimized parsers
11
-- ✅ **Multi-Metric Support** - Extract multiple metrics from single values (Prometheus, SNMP, CSV, JSONPath)
12
-
13
-## Installation
14
-
15
-```bash
16
-go get github.com/netdata/zabbix-preproc
17
-```
18
-
19
-## Quick Start
20
-
21
-```go
22
-package main
23
-
24
-import (
25
- "fmt"
26
- preproc "github.com/netdata/zabbix-preproc"
27
-)
28
-
29
-func main() {
30
- // Create preprocessor for a shard
31
- p := preproc.NewPreprocessor("shard1")
32
-
33
- // Define preprocessing pipeline
34
- steps := []preproc.Step{
35
- {Type: preproc.StepTypeJSONPath, Params: "$.cpu"},
36
- {Type: preproc.StepTypeMultiplier, Params: "100"},
37
- }
38
-
39
- // Process value
40
- value := preproc.Value{Data: `{"cpu": 0.45}`}
41
- result, err := p.ExecutePipeline("cpu_metric", value, steps)
42
- if err != nil {
43
- panic(err)
44
- }
45
-
46
- // Get result
47
- fmt.Println(result.Metrics[0].Value) // Output: 45
48
-}
49
-```
50
-
51
-## Validation
52
-
53
-Validate steps before execution to catch configuration errors early:
54
-
55
-```go
56
-// Validate a single step
57
-step := preproc.Step{Type: preproc.StepTypeMultiplier, Params: "2.5"}
58
-if err := preproc.ValidateStep(step); err != nil {
59
- // Handle validation error
60
- fmt.Printf("Invalid step: %v\n", err)
61
-}
62
-
63
-// Validate entire pipeline
64
-steps := []preproc.Step{
65
- {Type: preproc.StepTypeJSONPath, Params: "$.cpu"},
66
- {Type: preproc.StepTypeMultiplier, Params: "100"},
67
-}
68
-if err := preproc.ValidatePipeline(steps); err != nil {
69
- // Pipeline has invalid configuration
70
- fmt.Printf("Invalid pipeline: %v\n", err)
71
-}
72
-```
73
-
74
-**Benefits of early validation:**
75
-- Catch configuration errors before processing data
76
-- Especially useful for multi-step pipelines (fail fast)
77
-- Clear error messages indicating which parameter is missing/invalid
78
-- Validates step type, required parameters, parameter format, and error handlers
79
-
80
-## Common Use Cases
81
-
82
-### 1. Data Extraction
83
-
84
-**JSONPath** - Extract values from JSON:
85
-```go
86
-steps := []preproc.Step{
87
- {Type: preproc.StepTypeJSONPath, Params: "$.server.memory"},
88
-}
89
-// Input: {"server": {"memory": 8192}}
90
-// Output: 8192
91
-```
92
-
93
-**XPath** - Extract values from XML:
94
-```go
95
-steps := []preproc.Step{
96
- {Type: preproc.StepTypeXPath, Params: "//temperature/text()"},
97
-}
98
-// Input: <data><temperature>25.5</temperature></data>
99
-// Output: 25.5
100
-```
101
-
102
-**Prometheus** - Extract specific metrics:
103
-```go
104
-steps := []preproc.Step{
105
- {Type: preproc.StepTypePrometheusPattern, Params: `http_requests{method="GET"}`},
106
-}
107
-// Input: http_requests{method="GET"} 1027\nhttp_requests{method="POST"} 50
108
-// Output: 1027
109
-```
110
-
111
-### 2. Data Transformation
112
-
113
-**Multiply** - Scale values:
114
-```go
115
-steps := []preproc.Step{
116
- {Type: preproc.StepTypeMultiplier, Params: "1000"},
117
-}
118
-// Input: 1.5
119
-// Output: 1500
120
-```
121
-
122
-**Regex Substitution** - Transform text:
123
-```go
124
-steps := []preproc.Step{
125
- {Type: preproc.StepTypeRegexSubstitution, Params: "([0-9]+)\n$1 units"},
126
-}
127
-// Input: Temperature: 25
128
-// Output: Temperature: 25 units
129
-```
130
-
131
-**Trim** - Remove whitespace:
132
-```go
133
-steps := []preproc.Step{
134
- {Type: preproc.StepTypeTrim, Params: " \t\n"},
135
-}
136
-// Input: " hello "
137
-// Output: "hello"
138
-```
139
-
140
-### 3. Stateful Operations
141
-
142
-**Delta Value** - Calculate differences:
143
-```go
144
-steps := []preproc.Step{
145
- {Type: preproc.StepTypeDeltaValue},
146
-}
147
-// First call: 100 → 0 (baseline)
148
-// Second call: 150 → 50 (delta)
149
-```
150
-
151
-**Delta Speed** - Calculate rate per second:
152
-```go
153
-steps := []preproc.Step{
154
- {Type: preproc.StepTypeDeltaSpeed},
155
-}
156
-// Call 1 (t=0s): 100 bytes → 0
157
-// Call 2 (t=10s): 600 bytes → 50 bytes/sec
158
-```
159
-
160
-**Throttling** - Suppress duplicate values:
161
-```go
162
-steps := []preproc.Step{
163
- {Type: preproc.StepTypeThrottleValue, Params: "300"}, // 5 minutes
164
-}
165
-// Only processes value if changed or 5 min elapsed
166
-```
167
-
168
-### 4. Multi-Metric Extraction
169
-
170
-**Prometheus to Multiple Metrics**:
171
-```go
172
-steps := []preproc.Step{
173
- {Type: preproc.StepTypePrometheusToJSONMulti},
174
-}
175
-
176
-prometheusData := `
177
-http_requests{method="GET"} 100
178
-http_requests{method="POST"} 50
179
-memory_bytes 8192
180
-`
181
-
182
-result, _ := p.ExecutePipeline("metrics", preproc.Value{Data: prometheusData}, steps)
183
-
184
-for _, metric := range result.Metrics {
185
- fmt.Printf("%s: %s (labels: %v)\n", metric.Name, metric.Value, metric.Labels)
186
-}
187
-// Output:
188
-// http_requests: 100 (labels: map[method:GET])
189
-// http_requests: 50 (labels: map[method:POST])
190
-// memory_bytes: 8192 (labels: map[])
191
-```
192
-
193
-**SNMP Walk to Multiple Metrics**:
194
-```go
195
-steps := []preproc.Step{
196
- {Type: preproc.StepTypeSNMPWalkToJSONMulti, Params: ".1.3.6.1.2.1.2.2.1.2"},
197
-}
198
-
199
-snmpData := `
200
-.1.3.6.1.2.1.2.2.1.2.1 = STRING: "eth0"
201
-.1.3.6.1.2.1.2.2.1.2.2 = STRING: "eth1"
202
-`
203
-
204
-result, _ := p.ExecutePipeline("interfaces", preproc.Value{Data: snmpData}, steps)
205
-// Returns multiple metrics with OID indices as labels
206
-```
207
-
208
-**JSONPath to Multiple Values**:
209
-```go
210
-steps := []preproc.Step{
211
- {Type: preproc.StepTypeJSONPathMulti, Params: "$.servers[*].cpu"},
212
-}
213
-
214
-jsonData := `{"servers": [{"cpu": 45}, {"cpu": 23}, {"cpu": 78}]}`
215
-
216
-result, _ := p.ExecutePipeline("cpus", preproc.Value{Data: jsonData}, steps)
217
-// Returns 3 metrics with values: 45, 23, 78
218
-```
219
-
220
-### 5. Error Handling
221
-
222
-**Discard on Error**:
223
-```go
224
-steps := []preproc.Step{
225
- {
226
- Type: preproc.StepTypeJSONPath,
227
- Params: "$.nonexistent",
228
- ErrorHandler: preproc.ErrorHandler{
229
- Action: preproc.ErrorActionDiscard,
230
- },
231
- },
232
-}
233
-// Returns empty result instead of error
234
-```
235
-
236
-**Set Custom Value on Error**:
237
-```go
238
-steps := []preproc.Step{
239
- {
240
- Type: preproc.StepTypeValidateRange,
241
- Params: "0\n100",
242
- ErrorHandler: preproc.ErrorHandler{
243
- Action: preproc.ErrorActionSetValue,
244
- Params: "-1", // Default value
245
- },
246
- },
247
-}
248
-// Returns -1 if value out of range
249
-```
250
-
251
-### 6. Complex Pipelines
252
-
253
-Chain multiple steps together:
254
-
255
-```go
256
-steps := []preproc.Step{
257
- // 1. Extract JSON value
258
- {Type: preproc.StepTypeJSONPath, Params: "$.temperature"},
259
-
260
- // 2. Remove whitespace
261
- {Type: preproc.StepTypeTrim, Params: " "},
262
-
263
- // 3. Extract number (remove unit)
264
- {Type: preproc.StepTypeRegexSubstitution, Params: "([0-9.]+).*\n\\1"},
265
-
266
- // 4. Convert Celsius to Fahrenheit
267
- {Type: preproc.StepTypeMultiplier, Params: "1.8"},
268
-}
269
-
270
-value := preproc.Value{Data: `{"temperature": " 25.0 C "}`}
271
-result, _ := p.ExecutePipeline("temp", value, steps)
272
-// Output: 45
273
-```
274
-
275
-## Supported Preprocessing Types
276
-
277
-### Arithmetic
278
-- `StepTypeMultiplier` - Multiply by constant
279
-- `StepTypeDeltaValue` - Calculate simple delta
280
-- `StepTypeDeltaSpeed` - Calculate rate per second
281
-
282
-### String Operations
283
-- `StepTypeTrim`, `StepTypeRTrim`, `StepTypeLTrim` - Remove characters
284
-- `StepTypeRegexSubstitution` - Regex find/replace
285
-- `StepTypeStringReplace` - Simple string replace
286
-
287
-### Conversions
288
-- `StepTypeBool2Dec` - Boolean to 0/1
289
-- `StepTypeOct2Dec` - Octal to decimal
290
-- `StepTypeHex2Dec` - Hexadecimal to decimal
291
-
292
-### Data Extraction (Single-Value)
293
-- `StepTypeJSONPath` - JSONPath query
294
-- `StepTypeXPath` - XPath query
295
-- `StepTypePrometheusPattern` - Prometheus metric extraction
296
-- `StepTypePrometheusToJSON` - Prometheus to JSON
297
-- `StepTypeCSVToJSON` - CSV to JSON
298
-- `StepTypeSNMPWalkValue` - Extract specific SNMP OID
299
-- `StepTypeSNMPGetValue` - SNMP get value
300
-- `StepTypeSNMPWalkToJSON` - SNMP walk to JSON
301
-
302
-### Data Extraction (Multi-Metric)
303
-- `StepTypePrometheusToJSONMulti` - Extract all Prometheus metrics
304
-- `StepTypeJSONPathMulti` - Extract array values as separate metrics
305
-- `StepTypeSNMPWalkToJSONMulti` - SNMP walk discovery
306
-- `StepTypeCSVToJSONMulti` - CSV rows as separate metrics
307
-
308
-### Validation
309
-- `StepTypeValidateRange` - Check numeric range
310
-- `StepTypeValidateRegex` - Validate with regex
311
-- `StepTypeValidateNotRegex` - Inverse regex validation
312
-- `StepTypeValidateNotSupported` - Check for "not supported" errors
313
-
314
-### Error Field Extraction
315
-- `StepTypeErrorFieldJSON` - Extract error from JSON
316
-- `StepTypeErrorFieldXML` - Extract error from XML
317
-- `StepTypeErrorFieldRegex` - Extract error with regex
318
-
319
-### Throttling
320
-- `StepTypeThrottleValue` - Suppress duplicate values
321
-- `StepTypeThrottleTimedValue` - Time-based throttling
322
-
323
-### Advanced
324
-- `StepTypeJavaScript` - Custom JavaScript preprocessing
325
-
326
-## Architecture
327
-
328
-### Per-Shard State Isolation
329
-
330
-Each preprocessor instance maintains independent state:
331
-
332
-```go
333
-// Shard A - processing metrics from server1
334
-p1 := preproc.NewPreprocessor("shard-server1")
335
-
336
-// Shard B - processing metrics from server2
337
-p2 := preproc.NewPreprocessor("shard-server2")
338
-
339
-// Each maintains separate delta/throttle history
340
-// No state leakage between shards
341
-```
342
-
343
-### Thread Safety
344
-
345
-All operations are thread-safe:
346
-- Concurrent preprocessing across different items/shards
347
-- VM pooling for JavaScript execution
348
-- Read-only MIB-to-OID mappings
349
-- RWMutex protection for stateful operations
350
-
351
-### Performance Optimizations
352
-
353
-- **VM Pooling**: JavaScript VMs reused, not recreated
354
-- **Program Caching**: Compiled JavaScript programs cached
355
-- **Regex Caching**: Patterns compiled once at package init
356
-- **strings.Builder**: Efficient string construction in loops
357
-
358
-## Testing
359
-
360
-```bash
361
-# Run all tests (362 Zabbix compatibility tests + unit tests)
362
-go test
363
-
364
-# Run with coverage report
365
-go test -cover
366
-
367
-# Run with race detector
368
-go test -race
369
-
370
-# Run specific test suite
371
-go test -run TestZabbixTestSuite
372
-
373
-# Run fuzzing tests (finds edge cases via random input generation)
374
-go test -fuzz=FuzzJSONPath -fuzztime=30s # Fuzz JSONPath extraction
375
-go test -fuzz=FuzzXPath -fuzztime=30s # Fuzz XPath extraction
376
-go test -fuzz=FuzzRegexSubstitution -fuzztime=30s # Fuzz regex replacement
377
-go test -fuzz=FuzzMultiplier -fuzztime=30s # Fuzz numeric operations
378
-go test -fuzz=FuzzCSVToJSON -fuzztime=30s # Fuzz CSV parsing
379
-go test -fuzz=FuzzPrometheusPattern -fuzztime=30s # Fuzz Prometheus parsing
380
-go test -fuzz=FuzzSNMPWalkToJSON -fuzztime=30s # Fuzz SNMP parsing
381
-# Note: Fuzzing runs indefinitely if -fuzztime not specified
382
-
383
-# Benchmark
384
-go test -bench=.
385
-```
386
-
387
-## Compatibility
388
-
389
-This library passes 100% of official Zabbix preprocessing tests:
390
-- ✅ zbx_item_preproc.yaml (362 test cases)
391
-- ✅ item_preproc_xpath.yaml (12 XPath tests)
392
-- ✅ item_preproc_csv_to_json.yaml (4 CSV tests)
393
-
394
-Zabbix version compatibility: **7.x** (backward compatible with 6.x)
395
-
396
-## License
397
-
398
-Apache 2.0
399
-
400
-## Contributing
401
-
402
-Contributions welcome! Please ensure:
403
-- All Zabbix tests still pass (`go test`)
404
-- No CGO dependencies added
405
-- Code follows existing patterns
406
-- New features have test coverage
src/go/plugin/scripts.d/pkg/zabbixpreproc/advanced_steps.go
deleted
-247
@@ -1,247 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "fmt"
6
- "strings"
7
- "time"
8
-
9
- "github.com/antchfx/xmlquery"
10
- "github.com/theory/jsonpath"
11
-)
12
-
13
-// errorFieldJSON extracts error message from JSON response.
14
-func errorFieldJSON(value Value, paramStr string) (Value, error) {
15
- expr := strings.TrimSpace(paramStr)
16
- if expr == "" {
17
- return Value{}, fmt.Errorf("jsonpath expression required for error field extraction")
18
- }
19
-
20
- var data interface{}
21
- err := json.Unmarshal([]byte(value.Data), &data)
22
- if err != nil {
23
- // Invalid JSON - return input unchanged
24
- return value, nil
25
- }
26
-
27
- jp, err := jsonpath.Parse(expr)
28
- if err != nil {
29
- return Value{}, fmt.Errorf("invalid jsonpath: %w", err)
30
- }
31
-
32
- results := jp.Select(data)
33
- if len(results) == 0 {
34
- // No error field found - return input unchanged
35
- return value, nil
36
- }
37
-
38
- // Error field exists - return error with the field's value
39
- errValue := fmt.Sprintf("%v", results[0])
40
- return Value{}, fmt.Errorf("%s", errValue)
41
-}
42
-
43
-// errorFieldXML extracts error message from XML response.
44
-func errorFieldXML(value Value, paramStr string) (Value, error) {
45
- expr := strings.TrimSpace(paramStr)
46
- if expr == "" {
47
- return Value{}, fmt.Errorf("xpath expression required for error field extraction")
48
- }
49
-
50
- doc, err := xmlquery.Parse(strings.NewReader(value.Data))
51
- if err != nil {
52
- // Invalid XML - return input unchanged
53
- return value, nil
54
- }
55
-
56
- nodes, err := xmlquery.QueryAll(doc, expr)
57
- if err != nil {
58
- return Value{}, fmt.Errorf("invalid xpath: %w", err)
59
- }
60
-
61
- if len(nodes) == 0 {
62
- // No error field found - return input unchanged
63
- return value, nil
64
- }
65
-
66
- // Error field exists - return error with the field's text value
67
- errText := nodes[0].InnerText()
68
- return Value{}, fmt.Errorf("%s", errText)
69
-}
70
-
71
-// errorFieldRegex extracts error message from regex match.
72
-func errorFieldRegex(value Value, paramStr string) (Value, error) {
73
- // paramStr format: "pattern\noutput"
74
- parts := strings.SplitN(paramStr, "\n", 2)
75
- if len(parts) < 1 {
76
- return Value{}, fmt.Errorf("regex pattern required for error field extraction")
77
- }
78
-
79
- pattern := parts[0]
80
- output := ""
81
- if len(parts) > 1 {
82
- output = parts[1]
83
- }
84
-
85
- re, err := compileRegexSafe(pattern, 0)
86
- if err != nil {
87
- return Value{}, fmt.Errorf("invalid regex: %w", err)
88
- }
89
-
90
- matches, err := findStringSubmatchWithTimeout(re, value.Data, defaultRegexTimeout)
91
- if err != nil {
92
- return Value{}, fmt.Errorf("regex error field extraction failed: %w", err)
93
- }
94
- if len(matches) == 0 {
95
- // No match - no error
96
- return value, nil
97
- }
98
-
99
- // Match found - return error
100
- errMsg := output
101
- if errMsg == "" {
102
- errMsg = matches[0]
103
- }
104
-
105
- // Replace capture groups in output
106
- for i, match := range matches {
107
- placeholder := fmt.Sprintf("$%d", i)
108
- errMsg = strings.ReplaceAll(errMsg, placeholder, match)
109
- }
110
-
111
- return Value{}, fmt.Errorf("%s", errMsg)
112
-}
113
-
114
-// throttleValue returns the value only if it changed from the previous value.
115
-// Thread-safe for concurrent use. State isolated per shard and item.
116
-func (p *Preprocessor) throttleValue(itemID string, value Value, paramStr string) (Value, error) {
117
- // State key: shardID:itemID:operation
118
- stateKey := fmt.Sprintf("%s:%s:throttle_value", p.shardID, itemID)
119
-
120
- // Acquire exclusive lock for read-modify-write operation
121
- p.mu.Lock()
122
- defer p.mu.Unlock()
123
-
124
- state, hasState := p.state[stateKey]
125
-
126
- // Always update state before returning
127
- defer func() {
128
- p.state[stateKey] = &OperationState{
129
- LastValue: value.Data,
130
- LastTimestamp: value.Timestamp,
131
- LastAccess: time.Now(),
132
- }
133
- }()
134
-
135
- if !hasState {
136
- // First call
137
- return value, nil
138
- }
139
-
140
- // Update access time
141
- state.LastAccess = time.Now()
142
-
143
- // Check if value changed
144
- if value.Data != state.LastValue {
145
- // Value changed - return it
146
- return value, nil
147
- }
148
-
149
- // Value didn't change - discard it (return empty value without error)
150
- return Value{Data: "", Type: value.Type}, nil
151
-}
152
-
153
-// throttleTimedValue returns the value only if it changed or enough time has passed.
154
-// Thread-safe for concurrent use. State isolated per shard and item.
155
-func (p *Preprocessor) throttleTimedValue(itemID string, value Value, paramStr string) (Value, error) {
156
- // State key: shardID:itemID:operation
157
- stateKey := fmt.Sprintf("%s:%s:throttle_timed", p.shardID, itemID)
158
-
159
- // Parse timeout parameter before acquiring lock
160
- timeoutSeconds := parseDuration(paramStr)
161
-
162
- // Acquire exclusive lock for read-modify-write operation
163
- p.mu.Lock()
164
- defer p.mu.Unlock()
165
-
166
- state, hasState := p.state[stateKey]
167
-
168
- if !hasState {
169
- // First call - record state and return value
170
- p.state[stateKey] = &OperationState{
171
- LastValue: value.Data,
172
- LastValueTime: value.Timestamp,
173
- LastAccess: time.Now(),
174
- }
175
- return value, nil
176
- }
177
-
178
- // Update access time
179
- state.LastAccess = time.Now()
180
-
181
- // Check if value changed
182
- if value.Data != state.LastValue {
183
- // Value changed - return immediately and update state
184
- p.state[stateKey] = &OperationState{
185
- LastValue: value.Data,
186
- LastValueTime: value.Timestamp,
187
- LastAccess: time.Now(),
188
- }
189
- return value, nil
190
- }
191
-
192
- // Value unchanged - check if enough time has passed
193
- timeDiff := value.Timestamp.Sub(state.LastValueTime).Seconds()
194
-
195
- if timeDiff >= timeoutSeconds {
196
- // Enough time passed - return value and update timestamp
197
- p.state[stateKey] = &OperationState{
198
- LastValue: value.Data,
199
- LastValueTime: value.Timestamp,
200
- LastAccess: time.Now(),
201
- }
202
- return value, nil
203
- }
204
-
205
- // Value unchanged and not enough time passed - discard
206
- return Value{Data: "", Type: value.Type}, nil
207
-}
208
-
209
-// parseDuration parses Zabbix duration format (60, 1m, 1h, 1d, 1w) to seconds
210
-func parseDuration(s string) float64 {
211
- s = strings.TrimSpace(s)
212
- if s == "" {
213
- return 60 // Default 1 minute
214
- }
215
-
216
- // Check for suffix
217
- var multiplier float64 = 1
218
- if len(s) > 0 {
219
- lastChar := s[len(s)-1]
220
- switch lastChar {
221
- case 's':
222
- multiplier = 1
223
- s = s[:len(s)-1]
224
- case 'm':
225
- multiplier = 60
226
- s = s[:len(s)-1]
227
- case 'h':
228
- multiplier = 3600
229
- s = s[:len(s)-1]
230
- case 'd':
231
- multiplier = 86400
232
- s = s[:len(s)-1]
233
- case 'w':
234
- multiplier = 604800
235
- s = s[:len(s)-1]
236
- }
237
- }
238
-
239
- // Parse numeric value
240
- var value float64
241
- fmt.Sscanf(s, "%f", &value)
242
- if value == 0 {
243
- return 60 // Default if parsing failed
244
- }
245
-
246
- return value * multiplier
247
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/config.go
deleted
-70
@@ -1,70 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import "time"
4
-
5
-// Limits defines configurable constraints for preprocessing operations.
6
-// Default values match Zabbix 7.x behavior for maximum compatibility.
7
-type Limits struct {
8
- // JavaScript preprocessing limits
9
- JavaScript JSLimits
10
-
11
- // Regular expression preprocessing limits
12
- Regex RegexLimits
13
-}
14
-
15
-// JSLimits configures JavaScript preprocessing constraints.
16
-type JSLimits struct {
17
- // Timeout is the maximum execution time for a single JavaScript step.
18
- // Zabbix default: 10 seconds. Scripts exceeding this are terminated.
19
- Timeout time.Duration
20
-
21
- // MaxCallStackSize limits recursion depth to prevent stack exhaustion.
22
- // Zabbix default: 1000. Matches ZBX_ES_STACK_LIMIT.
23
- MaxCallStackSize int
24
-
25
- // MaxHttpRequests limits HttpRequest objects per script execution.
26
- // Zabbix default: 10. Prevents resource exhaustion.
27
- MaxHttpRequests int
28
-}
29
-
30
-// RegexLimits configures regular expression preprocessing constraints.
31
-type RegexLimits struct {
32
- // MaxCaptureGroups is the maximum number of capture groups (\1-\N).
33
- // Zabbix default: 10 (groups \0-\9). Matches ZBX_REGEXP_GROUPS_MAX.
34
- MaxCaptureGroups int
35
-
36
- // MatchTimeout is the maximum time for a single regex match operation.
37
- // Prevents ReDoS attacks. Recommended: 1s for safety.
38
- MatchTimeout time.Duration
39
-
40
- // ReplaceTimeout is the maximum time for regex replacement operations.
41
- // Zabbix default: 3 seconds. Matches ZBX_REGEX_REPL_TIMEOUT.
42
- ReplaceTimeout time.Duration
43
-
44
- // MaxPatternLength limits the size of regex patterns.
45
- // Zabbix default: unlimited. Set to 0 for no limit.
46
- MaxPatternLength int
47
-
48
- // UsePCREFeatures enables lookahead/lookbehind via regexp2.
49
- // If false, uses stdlib regexp (RE2) which lacks these features.
50
- // Default: true for Zabbix compatibility.
51
- UsePCREFeatures bool
52
-}
53
-
54
-// DefaultLimits returns limits matching Zabbix 7.x defaults.
55
-func DefaultLimits() Limits {
56
- return Limits{
57
- JavaScript: JSLimits{
58
- Timeout: 10 * time.Second,
59
- MaxCallStackSize: 1000,
60
- MaxHttpRequests: 10,
61
- },
62
- Regex: RegexLimits{
63
- MaxCaptureGroups: 10,
64
- MatchTimeout: time.Second,
65
- ReplaceTimeout: 3 * time.Second,
66
- MaxPatternLength: 0, // No limit (matches Zabbix)
67
- UsePCREFeatures: true,
68
- },
69
- }
70
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/csv_step.go
deleted
-727
@@ -1,727 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "fmt"
6
- "strconv"
7
- "strings"
8
- "unicode/utf8"
9
-)
10
-
11
-// csvToJSON converts CSV data to JSON.
12
-func csvToJSON(value Value, paramStr string) (Value, error) {
13
- // paramStr format: "delimiter\nquote_character\nheader_row"
14
- // header_row: 1 = first row is header, 0 = no header (use column indices)
15
- params := strings.Split(paramStr, "\n")
16
-
17
- // Validate parameter count: must be exactly 3
18
- if len(params) < 2 {
19
- return Value{}, fmt.Errorf("csv to json requires delimiter, quote, and header parameters")
20
- }
21
- if len(params) < 3 {
22
- return Value{}, fmt.Errorf("csv to json requires header row parameter")
23
- }
24
-
25
- // Extract delimiter (UTF-8 multi-byte support)
26
- delim := ","
27
- delimExplicit := false // Track if delimiter was explicitly set
28
- if params[0] != "" {
29
- r, size := utf8.DecodeRuneInString(params[0])
30
- if size != len(params[0]) {
31
- // Multi-character delimiter not allowed
32
- return Value{}, fmt.Errorf("invalid delimiter: must be single character")
33
- }
34
- delim = string(r)
35
- delimExplicit = true
36
- }
37
-
38
- // Extract quote character (UTF-8 multi-byte support)
39
- quote := "\""
40
- if params[1] != "" {
41
- r, size := utf8.DecodeRuneInString(params[1])
42
- if size != len(params[1]) {
43
- // Multi-character quote not allowed
44
- return Value{}, fmt.Errorf("invalid quote character: must be single character")
45
- }
46
- quote = string(r)
47
- }
48
-
49
- // Extract header_row flag
50
- headerRow, err := strconv.Atoi(params[2])
51
- if err != nil {
52
- return Value{}, fmt.Errorf("invalid header row parameter")
53
- }
54
- hasHeader := (headerRow == 1)
55
-
56
- // Check for Sep=/SEP=/sEp= declaration (case-INSENSITIVE)
57
- // Sep= overrides the delimiter param for ALL rows
58
- data := value.Data
59
- lines := strings.Split(data, "\n")
60
- startLine := 0
61
-
62
- if len(lines) > 0 {
63
- firstLine := strings.TrimRight(lines[0], "\r") // Handle \r\n line breaks
64
- // Case-insensitive check for "sep="
65
- if len(firstLine) >= 4 && strings.ToLower(firstLine[:4]) == "sep=" {
66
- if len(firstLine) == 4 {
67
- // "sep=" with no character - invalid, treat as data
68
- } else {
69
- // Check if exactly one character after "sep="
70
- r, size := utf8.DecodeRuneInString(firstLine[4:])
71
- if size == len(firstLine[4:]) {
72
- // Valid single-char sep= line
73
- // Only override delimiter if it wasn't explicitly set in params
74
- if !delimExplicit {
75
- delim = string(r)
76
- }
77
- startLine = 1
78
- // Reconstruct data without sep= line
79
- data = strings.Join(lines[startLine:], "\n")
80
- }
81
- // If multi-char after "sep=", treat as data (don't skip)
82
- }
83
- }
84
- }
85
-
86
- // Check if input ends with newline (for empty row logic)
87
- endsWithNewline := strings.HasSuffix(value.Data, "\n") || strings.HasSuffix(value.Data, "\r\n")
88
-
89
- // Parse CSV with strict validation
90
- records, err := parseCSVStrict(data, delim, quote)
91
- if err != nil {
92
- return Value{}, err
93
- }
94
-
95
- // Empty input returns []
96
- if len(records) == 0 {
97
- return Value{Data: "[]", Type: ValueTypeStr}, nil
98
- }
99
-
100
- var jsonData interface{}
101
-
102
- if hasHeader {
103
- // First row is header
104
- if len(records) < 1 {
105
- // Only header line, no data
106
- return Value{Data: "[]", Type: ValueTypeStr}, nil
107
- }
108
-
109
- header := records[0]
110
-
111
- // Validate no duplicate column names
112
- seen := make(map[string]bool)
113
- for _, col := range header {
114
- if seen[col] {
115
- return Value{}, fmt.Errorf("duplicate column name: %s", col)
116
- }
117
- seen[col] = true
118
- }
119
-
120
- result := make([]map[string]string, 0)
121
-
122
- // Add data rows
123
- for i := 1; i < len(records); i++ {
124
- row := records[i]
125
-
126
- // Validate field count: data row can't have more fields than header
127
- if len(row) > len(header) {
128
- return Value{}, fmt.Errorf("data row has more fields than header")
129
- }
130
-
131
- obj := make(map[string]string)
132
- for j, val := range row {
133
- if j < len(header) {
134
- obj[header[j]] = val
135
- }
136
- }
137
- // Fill missing fields with empty strings
138
- for j := len(row); j < len(header); j++ {
139
- obj[header[j]] = ""
140
- }
141
- result = append(result, obj)
142
- }
143
-
144
- // Add empty trailing row if input ended with newline
145
- if endsWithNewline {
146
- emptyObj := make(map[string]string)
147
- for _, h := range header {
148
- emptyObj[h] = ""
149
- }
150
- result = append(result, emptyObj)
151
- }
152
-
153
- // Marshal with ordered keys to preserve header column order
154
- jsonData, err = marshalOrderedJSON(result, header)
155
- if err != nil {
156
- return Value{}, fmt.Errorf("failed to marshal csv to json: %w", err)
157
- }
158
- } else {
159
- // No header - use 1-based column indices as keys
160
- result := make([]map[string]string, 0)
161
- for _, row := range records {
162
- // Special case: single empty field becomes empty object
163
- if len(row) == 1 && row[0] == "" {
164
- result = append(result, make(map[string]string))
165
- } else {
166
- obj := make(map[string]string)
167
- for i, val := range row {
168
- // Use 1-based indexing
169
- obj[strconv.Itoa(i+1)] = val
170
- }
171
- result = append(result, obj)
172
- }
173
- }
174
-
175
- // Add empty trailing row if input ended with newline
176
- if endsWithNewline && len(result) > 0 {
177
- result = append(result, make(map[string]string))
178
- }
179
-
180
- jsonData = result
181
- }
182
-
183
- var jsonBytes []byte
184
- if str, ok := jsonData.(string); ok {
185
- jsonBytes = []byte(str)
186
- } else {
187
- jsonBytes, err = json.Marshal(jsonData)
188
- if err != nil {
189
- return Value{}, fmt.Errorf("failed to marshal csv to json: %w", err)
190
- }
191
- }
192
-
193
- return Value{Data: string(jsonBytes), Type: ValueTypeStr}, nil
194
-}
195
-
196
-// marshalOrderedJSON marshals array of maps with keys in header order.
197
-func marshalOrderedJSON(data []map[string]string, header []string) (string, error) {
198
- var builder strings.Builder
199
- builder.WriteString("[")
200
-
201
- for i, obj := range data {
202
- if i > 0 {
203
- builder.WriteString(",")
204
- }
205
- // Delegate single object marshaling to avoid code duplication
206
- jsonObj, err := marshalOrderedJSONSingle(obj, header)
207
- if err != nil {
208
- return "", err
209
- }
210
- builder.WriteString(jsonObj)
211
- }
212
-
213
- builder.WriteString("]")
214
- return builder.String(), nil
215
-}
216
-
217
-// parseCSVStrict parses CSV data with strict Zabbix-compatible validation.
218
-//
219
-// This custom parser implements Zabbix's specific CSV parsing rules, which differ
220
-// from encoding/csv in several ways:
221
-// - Supports multi-byte UTF-8 delimiters and quote characters (not just single runes)
222
-// - Special mode when delimiter == quote (disables quoting behavior entirely)
223
-// - Strict validation: rejects \n\r line breaks, unclosed quotes, invalid characters after closing quotes
224
-// - UTF-8 validation: returns error on invalid UTF-8 sequences
225
-//
226
-// Parameters:
227
-// - data: Raw CSV string data to parse
228
-// - delimiter: Field delimiter (can be multi-byte UTF-8 string like "€")
229
-// - quote: Quote character (can be multi-byte UTF-8 string like "§")
230
-//
231
-// Returns:
232
-// - [][]string: Parsed CSV records (array of rows, each row is array of fields)
233
-// - error: Parsing error if data is invalid
234
-//
235
-// This function cannot be replaced with encoding/csv.Reader due to the multi-byte
236
-// delimiter/quote support and special delimiter==quote handling required by Zabbix.
237
-
238
-// csvParseState tracks the state during CSV parsing
239
-type csvParseState struct {
240
- records [][]string
241
- currentRecord []string
242
- currentField strings.Builder
243
- inQuotes bool
244
- fieldStart bool
245
- data string
246
- delimiter string
247
- quote string
248
-}
249
-
250
-// finishField completes the current field and adds it to the current record
251
-func (s *csvParseState) finishField() {
252
- s.currentRecord = append(s.currentRecord, s.currentField.String())
253
- s.currentField.Reset()
254
- s.fieldStart = true
255
-}
256
-
257
-// finishRecord completes the current record and adds it to results
258
-func (s *csvParseState) finishRecord() {
259
- if len(s.currentRecord) > 0 {
260
- s.records = append(s.records, s.currentRecord)
261
- }
262
- s.currentRecord = nil
263
- s.fieldStart = true
264
-}
265
-
266
-// handleQuoteChar processes a quote character at position i
267
-// Returns new position and whether to continue main loop
268
-func (s *csvParseState) handleQuoteChar(i int) (int, bool, error) {
269
- if s.inQuotes {
270
- // Check if it's an escaped quote (doubled)
271
- if strings.HasPrefix(s.data[i+len(s.quote):], s.quote) {
272
- // Escaped quote - add one quote to field
273
- s.currentField.WriteString(s.quote)
274
- s.fieldStart = false
275
- return i + len(s.quote)*2, true, nil
276
- }
277
-
278
- // End of quoted field
279
- s.inQuotes = false
280
- i += len(s.quote)
281
-
282
- // After closing quote, MUST be delimiter or newline
283
- if i < len(s.data) {
284
- nextChar := s.data[i]
285
- if nextChar != '\n' && nextChar != '\r' && !strings.HasPrefix(s.data[i:], s.delimiter) {
286
- return 0, false, fmt.Errorf("character after closing quote")
287
- }
288
- }
289
- s.fieldStart = false
290
- return i, true, nil
291
- }
292
-
293
- if s.fieldStart {
294
- // Start of quoted field (only at field start)
295
- s.inQuotes = true
296
- s.fieldStart = false
297
- return i + len(s.quote), true, nil
298
- }
299
-
300
- // Quote in middle of unquoted field - treat as literal
301
- s.currentField.WriteString(s.quote)
302
- s.fieldStart = false
303
- return i + len(s.quote), true, nil
304
-}
305
-
306
-// handleNewlineChar processes a newline character at position i (only outside quotes)
307
-// Returns new position and whether to continue main loop
308
-func (s *csvParseState) handleNewlineChar(i int) (int, bool, error) {
309
- if s.data[i] == '\n' {
310
- // Check for unsupported \n\r line break
311
- if i+1 < len(s.data) && s.data[i+1] == '\r' {
312
- return 0, false, fmt.Errorf("unsupported line break")
313
- }
314
-
315
- // End of record
316
- s.currentRecord = append(s.currentRecord, s.currentField.String())
317
- s.currentField.Reset()
318
- s.finishRecord()
319
- return i + 1, true, nil
320
- }
321
-
322
- if s.data[i] == '\r' {
323
- // Handle \r\n or just \r
324
- if i+1 < len(s.data) && s.data[i+1] == '\n' {
325
- // \r\n - end of record
326
- s.currentRecord = append(s.currentRecord, s.currentField.String())
327
- s.currentField.Reset()
328
- s.finishRecord()
329
- return i + 2, true, nil
330
- }
331
-
332
- // Just \r - treat as newline
333
- s.currentRecord = append(s.currentRecord, s.currentField.String())
334
- s.currentField.Reset()
335
- s.finishRecord()
336
- return i + 1, true, nil
337
- }
338
-
339
- return i, false, nil
340
-}
341
-
342
-func parseCSVStrict(data string, delimiter string, quote string) ([][]string, error) {
343
- // Special case: when delimiter == quote, disable quoting behavior
344
- if delimiter == quote {
345
- return parseCSVNoQuotes(data, delimiter)
346
- }
347
-
348
- // Initialize parsing state
349
- state := &csvParseState{
350
- data: data,
351
- delimiter: delimiter,
352
- quote: quote,
353
- fieldStart: true,
354
- }
355
-
356
- i := 0
357
- for i < len(data) {
358
- // Check for quote character at current position
359
- if strings.HasPrefix(data[i:], quote) {
360
- newPos, shouldContinue, err := state.handleQuoteChar(i)
361
- if err != nil {
362
- return nil, err
363
- }
364
- if shouldContinue {
365
- i = newPos
366
- continue
367
- }
368
- }
369
-
370
- // Check for delimiter (only outside quotes)
371
- if !state.inQuotes && strings.HasPrefix(data[i:], delimiter) {
372
- state.finishField()
373
- i += len(delimiter)
374
- continue
375
- }
376
-
377
- // Check for newline (only outside quotes)
378
- if !state.inQuotes {
379
- newPos, shouldContinue, err := state.handleNewlineChar(i)
380
- if err != nil {
381
- return nil, err
382
- }
383
- if shouldContinue {
384
- i = newPos
385
- continue
386
- }
387
- }
388
-
389
- // Regular character - add to current field
390
- r, size := utf8.DecodeRuneInString(data[i:])
391
- if r == utf8.RuneError && size == 1 {
392
- return nil, fmt.Errorf("invalid UTF-8 character at position %d", i)
393
- }
394
- state.currentField.WriteRune(r)
395
- i += size
396
- state.fieldStart = false
397
- }
398
-
399
- // Handle last field and record
400
- if state.inQuotes {
401
- return nil, fmt.Errorf("unclosed quoted field")
402
- }
403
-
404
- // Add last field to current record
405
- if state.currentField.Len() > 0 || len(state.currentRecord) > 0 {
406
- state.currentRecord = append(state.currentRecord, state.currentField.String())
407
- }
408
-
409
- // Add last record if not empty
410
- if len(state.currentRecord) > 0 {
411
- state.records = append(state.records, state.currentRecord)
412
- }
413
-
414
- return state.records, nil
415
-}
416
-
417
-// parseCSVNoQuotes parses CSV when delimiter == quote (no quoting behavior).
418
-func parseCSVNoQuotes(data string, delimiter string) ([][]string, error) {
419
- var records [][]string
420
- var currentRecord []string
421
- var currentField strings.Builder
422
-
423
- i := 0
424
-
425
- for i < len(data) {
426
- // Check for delimiter
427
- if strings.HasPrefix(data[i:], delimiter) {
428
- currentRecord = append(currentRecord, currentField.String())
429
- currentField.Reset()
430
- i += len(delimiter)
431
- continue
432
- }
433
-
434
- // Check for newline
435
- if data[i] == '\n' {
436
- // Check for \n\r
437
- if i+1 < len(data) && data[i+1] == '\r' {
438
- return nil, fmt.Errorf("unsupported line break")
439
- }
440
-
441
- // End of record
442
- currentRecord = append(currentRecord, currentField.String())
443
- currentField.Reset()
444
- if len(currentRecord) > 0 {
445
- records = append(records, currentRecord)
446
- }
447
- currentRecord = nil
448
- i++
449
- continue
450
- }
451
- if data[i] == '\r' {
452
- // Handle \r\n or just \r
453
- if i+1 < len(data) && data[i+1] == '\n' {
454
- currentRecord = append(currentRecord, currentField.String())
455
- currentField.Reset()
456
- if len(currentRecord) > 0 {
457
- records = append(records, currentRecord)
458
- }
459
- currentRecord = nil
460
- i += 2
461
- continue
462
- } else {
463
- currentRecord = append(currentRecord, currentField.String())
464
- currentField.Reset()
465
- if len(currentRecord) > 0 {
466
- records = append(records, currentRecord)
467
- }
468
- currentRecord = nil
469
- i++
470
- continue
471
- }
472
- }
473
-
474
- // Regular character
475
- r, size := utf8.DecodeRuneInString(data[i:])
476
- if r == utf8.RuneError && size == 1 {
477
- return nil, fmt.Errorf("invalid UTF-8 character at position %d", i)
478
- }
479
- currentField.WriteRune(r)
480
- i += size
481
- }
482
-
483
- // Handle last field and record
484
- if currentField.Len() > 0 || len(currentRecord) > 0 {
485
- currentRecord = append(currentRecord, currentField.String())
486
- }
487
- if len(currentRecord) > 0 {
488
- records = append(records, currentRecord)
489
- }
490
-
491
- return records, nil
492
-}
493
-
494
-// csvToJSONMulti converts CSV data to multiple Result metrics
495
-// Each CSV row becomes a separate Result.Metric with row index label
496
-func csvToJSONMulti(value Value, paramStr string) (Result, error) {
497
- // paramStr format: "delimiter\nquote_character\nheader_row"
498
- // header_row: 1 = first row is header, 0 = no header (use column indices)
499
- params := strings.Split(paramStr, "\n")
500
-
501
- // Validate parameter count: must be exactly 3
502
- if len(params) < 2 {
503
- err := fmt.Errorf("csv to json requires delimiter, quote, and header parameters")
504
- return Result{Error: err}, err
505
- }
506
- if len(params) < 3 {
507
- err := fmt.Errorf("csv to json requires header row parameter")
508
- return Result{Error: err}, err
509
- }
510
-
511
- // Extract delimiter (UTF-8 multi-byte support)
512
- delim := ","
513
- delimExplicit := false // Track if delimiter was explicitly set
514
- if params[0] != "" {
515
- r, size := utf8.DecodeRuneInString(params[0])
516
- if size != len(params[0]) {
517
- // Multi-character delimiter not allowed
518
- err := fmt.Errorf("invalid delimiter: must be single character")
519
- return Result{Error: err}, err
520
- }
521
- delim = string(r)
522
- delimExplicit = true
523
- }
524
-
525
- // Extract quote character (UTF-8 multi-byte support)
526
- quote := "\""
527
- if params[1] != "" {
528
- r, size := utf8.DecodeRuneInString(params[1])
529
- if size != len(params[1]) {
530
- // Multi-character quote not allowed
531
- err := fmt.Errorf("invalid quote character: must be single character")
532
- return Result{Error: err}, err
533
- }
534
- quote = string(r)
535
- }
536
-
537
- // Extract header_row flag
538
- headerRow, err := strconv.Atoi(params[2])
539
- if err != nil {
540
- err = fmt.Errorf("invalid header row parameter")
541
- return Result{Error: err}, err
542
- }
543
- hasHeader := (headerRow == 1)
544
-
545
- // Check for Sep=/SEP=/sEp= declaration (case-INSENSITIVE)
546
- // Sep= overrides the delimiter param for ALL rows
547
- data := value.Data
548
- lines := strings.Split(data, "\n")
549
- startLine := 0
550
-
551
- if len(lines) > 0 {
552
- firstLine := strings.TrimRight(lines[0], "\r") // Handle \r\n line breaks
553
- // Case-insensitive check for "sep="
554
- if len(firstLine) >= 4 && strings.ToLower(firstLine[:4]) == "sep=" {
555
- if len(firstLine) == 4 {
556
- // "sep=" with no character - invalid, treat as data
557
- } else {
558
- // Check if exactly one character after "sep="
559
- r, size := utf8.DecodeRuneInString(firstLine[4:])
560
- if size == len(firstLine[4:]) {
561
- // Valid single-char sep= line
562
- // Only override delimiter if it wasn't explicitly set in params
563
- if !delimExplicit {
564
- delim = string(r)
565
- }
566
- startLine = 1
567
- // Reconstruct data without sep= line
568
- data = strings.Join(lines[startLine:], "\n")
569
- }
570
- // If multi-char after "sep=", treat as data (don't skip)
571
- }
572
- }
573
- }
574
-
575
- // Check if input ends with newline (for empty row logic)
576
- endsWithNewline := strings.HasSuffix(value.Data, "\n") || strings.HasSuffix(value.Data, "\r\n")
577
-
578
- // Parse CSV with strict validation
579
- records, err := parseCSVStrict(data, delim, quote)
580
- if err != nil {
581
- return Result{Error: err}, err
582
- }
583
-
584
- // Empty input returns empty metrics array
585
- if len(records) == 0 {
586
- return Result{Metrics: []Metric{}}, nil
587
- }
588
-
589
- var metrics []Metric
590
-
591
- if hasHeader {
592
- // First row is header
593
- if len(records) < 1 {
594
- // Only header line, no data
595
- return Result{Metrics: []Metric{}}, nil
596
- }
597
-
598
- header := records[0]
599
-
600
- // Validate no duplicate column names
601
- seen := make(map[string]bool)
602
- for _, col := range header {
603
- if seen[col] {
604
- err := fmt.Errorf("duplicate column name: %s", col)
605
- return Result{Error: err}, err
606
- }
607
- seen[col] = true
608
- }
609
-
610
- // Convert each data row to a metric
611
- for i := 1; i < len(records); i++ {
612
- row := records[i]
613
-
614
- // Validate field count: data row can't have more fields than header
615
- if len(row) > len(header) {
616
- err := fmt.Errorf("data row has more fields than header")
617
- return Result{Error: err}, err
618
- }
619
-
620
- obj := make(map[string]string)
621
- for j, val := range row {
622
- if j < len(header) {
623
- obj[header[j]] = val
624
- }
625
- }
626
- // Fill missing fields with empty strings
627
- for j := len(row); j < len(header); j++ {
628
- obj[header[j]] = ""
629
- }
630
-
631
- // Marshal row to JSON object string
632
- jsonBytes, err := marshalOrderedJSONSingle(obj, header)
633
- if err != nil {
634
- return Result{Error: err}, err
635
- }
636
-
637
- metrics = append(metrics, Metric{
638
- Name: "csv_row",
639
- Value: jsonBytes,
640
- Type: ValueTypeStr,
641
- Labels: map[string]string{"row": strconv.Itoa(i)}, // 1-based row number (excluding header)
642
- })
643
- }
644
-
645
- // Add empty trailing row if input ended with newline
646
- if endsWithNewline {
647
- emptyObj := make(map[string]string)
648
- for _, h := range header {
649
- emptyObj[h] = ""
650
- }
651
- jsonBytes, err := marshalOrderedJSONSingle(emptyObj, header)
652
- if err != nil {
653
- return Result{Error: err}, err
654
- }
655
- metrics = append(metrics, Metric{
656
- Name: "csv_row",
657
- Value: jsonBytes,
658
- Type: ValueTypeStr,
659
- Labels: map[string]string{"row": strconv.Itoa(len(metrics) + 1)},
660
- })
661
- }
662
- } else {
663
- // No header - use 1-based column indices as keys
664
- for i, row := range records {
665
- var obj map[string]string
666
-
667
- // Special case: single empty field becomes empty object
668
- if len(row) == 1 && row[0] == "" {
669
- obj = make(map[string]string)
670
- } else {
671
- obj = make(map[string]string)
672
- for j, val := range row {
673
- // Use 1-based indexing
674
- obj[strconv.Itoa(j+1)] = val
675
- }
676
- }
677
-
678
- // Marshal row to JSON object string
679
- jsonBytes, err := json.Marshal(obj)
680
- if err != nil {
681
- return Result{Error: err}, err
682
- }
683
-
684
- metrics = append(metrics, Metric{
685
- Name: "csv_row",
686
- Value: string(jsonBytes),
687
- Type: ValueTypeStr,
688
- Labels: map[string]string{"row": strconv.Itoa(i + 1)}, // 1-based row number
689
- })
690
- }
691
-
692
- // Add empty trailing row if input ended with newline
693
- if endsWithNewline && len(metrics) > 0 {
694
- emptyJSON, _ := json.Marshal(make(map[string]string))
695
- metrics = append(metrics, Metric{
696
- Name: "csv_row",
697
- Value: string(emptyJSON),
698
- Type: ValueTypeStr,
699
- Labels: map[string]string{"row": strconv.Itoa(len(metrics) + 1)},
700
- })
701
- }
702
- }
703
-
704
- return Result{Metrics: metrics}, nil
705
-}
706
-
707
-// marshalOrderedJSONSingle marshals a single map with keys in header order.
708
-func marshalOrderedJSONSingle(obj map[string]string, header []string) (string, error) {
709
- var builder strings.Builder
710
- builder.WriteString("{")
711
- first := true
712
- for _, key := range header {
713
- val := obj[key]
714
- if !first {
715
- builder.WriteString(",")
716
- }
717
- first = false
718
- // Properly escape JSON key and value
719
- keyJSON, _ := json.Marshal(key)
720
- valJSON, _ := json.Marshal(val)
721
- builder.Write(keyJSON)
722
- builder.WriteString(":")
723
- builder.Write(valJSON)
724
- }
725
- builder.WriteString("}")
726
- return builder.String(), nil
727
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/fuzz_test.go
deleted
-193
@@ -1,193 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// Fuzzing tests to find edge cases and crashes
10
-// Run with: go test -fuzz=Fuzz
11
-
12
-// FuzzJSONPath fuzzes JSONPath extraction
13
-func FuzzJSONPath(f *testing.F) {
14
- // Seed corpus with known good inputs
15
- f.Add(`{"key": "value"}`, "$.key")
16
- f.Add(`{"a": {"b": 123}}`, "$.a.b")
17
- f.Add(`[1,2,3]`, "$[0]")
18
-
19
- f.Fuzz(func(t *testing.T, jsonData, path string) {
20
- // Skip empty path (required parameter)
21
- if path == "" {
22
- return
23
- }
24
-
25
- value := Value{Data: jsonData, Type: ValueTypeStr}
26
- _, _ = jsonpathExtract(value, path)
27
- // Don't check error - we're looking for crashes, not correctness
28
- })
29
-}
30
-
31
-// FuzzXPath fuzzes XPath extraction
32
-func FuzzXPath(f *testing.F) {
33
- // Seed corpus
34
- f.Add(`<root><item>value</item></root>`, "//item/text()")
35
- f.Add(`<data><a>1</a><b>2</b></data>`, "//a")
36
-
37
- f.Fuzz(func(t *testing.T, xmlData, path string) {
38
- // Skip empty path (required parameter)
39
- if path == "" {
40
- return
41
- }
42
-
43
- value := Value{Data: xmlData, Type: ValueTypeStr}
44
- _, _ = xpathExtract(value, path)
45
- // Don't check error - we're looking for crashes
46
- })
47
-}
48
-
49
-// FuzzRegexSubstitution fuzzes regex pattern replacement
50
-func FuzzRegexSubstitution(f *testing.F) {
51
- // Seed corpus
52
- f.Add("hello world", "world\nuniverse")
53
- f.Add("test123", "[0-9]+\nNUM")
54
-
55
- f.Fuzz(func(t *testing.T, input, params string) {
56
- // Skip invalid params format (need newline separator)
57
- if !strings.Contains(params, "\n") {
58
- return
59
- }
60
-
61
- value := Value{Data: input, Type: ValueTypeStr}
62
- _, _ = regexSubstitute(value, params)
63
- // Don't check error - we're looking for crashes
64
- })
65
-}
66
-
67
-// FuzzMultiplier fuzzes numeric multiplication
68
-func FuzzMultiplier(f *testing.F) {
69
- // Seed corpus
70
- f.Add("100", "2.5")
71
- f.Add("3.14", "10")
72
-
73
- f.Fuzz(func(t *testing.T, input, multiplier string) {
74
- value := Value{Data: input, Type: ValueTypeFloat}
75
- _, _ = multiplyValue(value, multiplier)
76
- // Don't check error - we're looking for crashes
77
- })
78
-}
79
-
80
-// FuzzCSVToJSON fuzzes CSV parsing
81
-func FuzzCSVToJSON(f *testing.F) {
82
- // Seed corpus
83
- f.Add("a,b,c\n1,2,3", ",\n\"\n1")
84
- f.Add("x;y\n10;20", ";\n\"\n1")
85
-
86
- f.Fuzz(func(t *testing.T, csvData, params string) {
87
- // Skip invalid params (need at least 3 newlines)
88
- if strings.Count(params, "\n") < 2 {
89
- return
90
- }
91
-
92
- value := Value{Data: csvData, Type: ValueTypeStr}
93
- _, _ = csvToJSON(value, params)
94
- // Don't check error - we're looking for crashes
95
- })
96
-}
97
-
98
-// FuzzPrometheusPattern fuzzes Prometheus metric extraction
99
-func FuzzPrometheusPattern(f *testing.F) {
100
- // Seed corpus
101
- f.Add(`http_requests{method="GET"} 100`, `http_requests{method="GET"}`)
102
- f.Add(`cpu_usage 45.2`, `cpu_usage`)
103
-
104
- f.Fuzz(func(t *testing.T, promData, pattern string) {
105
- // Skip empty pattern
106
- if pattern == "" {
107
- return
108
- }
109
-
110
- value := Value{Data: promData, Type: ValueTypeStr}
111
- _, _ = prometheusPattern(value, pattern)
112
- // Don't check error - we're looking for crashes
113
- })
114
-}
115
-
116
-// FuzzSNMPWalkToJSON fuzzes SNMP walk parsing
117
-func FuzzSNMPWalkToJSON(f *testing.F) {
118
- // Seed corpus
119
- f.Add(`.1.3.6.1.2.1.1.1.0 = STRING: "Linux"`, "{#NAME}\n.1.3.6.1.2.1.1\n1")
120
- f.Add(`.1.3.6.1.4.1.2021.10.1.3.1 = INTEGER: 50`, "{#CPU}\n.1.3.6.1.4.1.2021.10.1.3\n1")
121
-
122
- f.Fuzz(func(t *testing.T, snmpData, params string) {
123
- // Skip invalid params (need multiple of 3 newlines)
124
- if strings.Count(params, "\n")%3 != 2 {
125
- return
126
- }
127
-
128
- value := Value{Data: snmpData, Type: ValueTypeStr}
129
- logger := NoopLogger{}
130
- _, _ = snmpWalkToJSON(value, params, logger)
131
- // Don't check error - we're looking for crashes
132
- })
133
-}
134
-
135
-// FuzzTrim fuzzes string trimming
136
-func FuzzTrim(f *testing.F) {
137
- // Seed corpus
138
- f.Add(" hello ", " ")
139
- f.Add("\t\ntest\r\n", "\t\n\r")
140
-
141
- f.Fuzz(func(t *testing.T, input, chars string) {
142
- value := Value{Data: input, Type: ValueTypeStr}
143
- _, _ = trimValue(value, chars, "both")
144
- _, _ = trimValue(value, chars, "left")
145
- _, _ = trimValue(value, chars, "right")
146
- // Don't check error - we're looking for crashes
147
- })
148
-}
149
-
150
-// FuzzDelta fuzzes delta value calculation
151
-func FuzzDelta(f *testing.F) {
152
- // Seed corpus
153
- f.Add("100", "")
154
- f.Add("50", "")
155
-
156
- p := NewPreprocessor("fuzz-shard")
157
-
158
- f.Fuzz(func(t *testing.T, input, params string) {
159
- value := Value{
160
- Data: input,
161
- Type: ValueTypeUint64,
162
- Timestamp: time.Now(),
163
- }
164
- _, _ = p.deltaValue("fuzz-item", value, params)
165
- // Don't check error - we're looking for crashes
166
- })
167
-}
168
-
169
-// FuzzValidateRange fuzzes range validation
170
-func FuzzValidateRange(f *testing.F) {
171
- // Seed corpus
172
- f.Add("50", "0\n100")
173
- f.Add("3.14", "-10\n10")
174
-
175
- f.Fuzz(func(t *testing.T, input, params string) {
176
- value := Value{Data: input, Type: ValueTypeFloat}
177
- _, _ = validateRange(value, params)
178
- // Don't check error - we're looking for crashes
179
- })
180
-}
181
-
182
-// FuzzInterpretEscapeSequences fuzzes escape sequence interpretation
183
-func FuzzInterpretEscapeSequences(f *testing.F) {
184
- // Seed corpus
185
- f.Add("hello\\nworld")
186
- f.Add("tab\\there")
187
- f.Add("back\\\\slash")
188
-
189
- f.Fuzz(func(t *testing.T, input string) {
190
- _ = interpretEscapeSequences(input)
191
- // Don't check result - we're looking for crashes
192
- })
193
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/helpers_test.go
deleted
-321
@@ -1,321 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "testing"
5
-)
6
-
7
-// Unit tests for helper functions (separate from integration tests)
8
-
9
-func TestFormatFloat(t *testing.T) {
10
- tests := []struct {
11
- name string
12
- input float64
13
- want string
14
- }{
15
- {"whole number", 42.0, "42"},
16
- {"simple decimal", 3.14, "3.14"},
17
- {"small decimal", 0.001, "0.001"},
18
- {"large number", 1234567.89, "1.23456789e+06"},
19
- {"negative whole", -100.0, "-100"},
20
- {"negative decimal", -0.5, "-0.5"},
21
- {"zero", 0.0, "0"},
22
- {"very small", 0.0000001, "1e-07"},
23
- }
24
-
25
- for _, tt := range tests {
26
- t.Run(tt.name, func(t *testing.T) {
27
- got := formatFloat(tt.input)
28
- if got != tt.want {
29
- t.Errorf("formatFloat(%v) = %q, want %q", tt.input, got, tt.want)
30
- }
31
- })
32
- }
33
-}
34
-
35
-func TestInterpretEscapeSequences(t *testing.T) {
36
- tests := []struct {
37
- name string
38
- input string
39
- want string
40
- }{
41
- {"no escapes", "hello", "hello"},
42
- {"newline", "line1\\nline2", "line1\nline2"},
43
- {"tab", "col1\\tcol2", "col1\tcol2"},
44
- {"carriage return", "text\\rmore", "text\rmore"},
45
- {"backslash", "path\\\\file", "path\\file"},
46
- {"space", "a\\sb", "a b"},
47
- {"multiple escapes", "a\\nb\\tc", "a\nb\tc"},
48
- {"double backslash s", "trim\\\\sthis", "trim \t\r\n\\this"}, // \\s expands to full whitespace class
49
- {"unknown escape", "\\x41BC", "\\x41BC"}, // Unknown escapes preserved
50
- }
51
-
52
- for _, tt := range tests {
53
- t.Run(tt.name, func(t *testing.T) {
54
- got := interpretEscapeSequences(tt.input)
55
- if got != tt.want {
56
- t.Errorf("interpretEscapeSequences(%q) = %q, want %q", tt.input, got, tt.want)
57
- }
58
- })
59
- }
60
-}
61
-
62
-func TestConvertBackreferences(t *testing.T) {
63
- tests := []struct {
64
- name string
65
- replacement string
66
- want string
67
- }{
68
- {"no backrefs", "literal", "literal"},
69
- {"\\1 to ${1}", "prefix\\1suffix", "prefix${1}suffix"},
70
- {"\\2 to ${2}", "value\\2", "value${2}"},
71
- {"multiple backrefs", "\\1 and \\2", "${1} and ${2}"},
72
- {"\\\\1 literal backslash", "\\\\1", "\\1"},
73
- {"mixed", "a\\1b\\\\2c\\3", "a${1}b\\2c${3}"},
74
- {"\\0 full match", "result\\0", "result${0}"},
75
- {"multi-digit", "\\10", "${10}"},
76
- }
77
-
78
- for _, tt := range tests {
79
- t.Run(tt.name, func(t *testing.T) {
80
- got := convertBackreferences(tt.replacement)
81
- if got != tt.want {
82
- t.Errorf("convertBackreferences(%q) = %q, want %q", tt.replacement, got, tt.want)
83
- }
84
- })
85
- }
86
-}
87
-
88
-func TestParseHexBytes(t *testing.T) {
89
- tests := []struct {
90
- name string
91
- input string
92
- want []byte
93
- wantErr bool
94
- }{
95
- {"simple hex", "48656C6C6F", []byte("Hello"), false},
96
- {"with spaces", "48 65 6C 6C 6F", []byte("Hello"), false},
97
- {"lowercase", "68656c6c6f", []byte("hello"), false},
98
- {"mixed case", "48656C6c6F", []byte("Hello"), false},
99
- {"empty", "", []byte{}, false},
100
- {"invalid hex", "ZZZZ", nil, true},
101
- {"odd length", "48656C6C6", nil, true},
102
- {"null bytes", "00410042", []byte("\x00A\x00B"), false},
103
- }
104
-
105
- for _, tt := range tests {
106
- t.Run(tt.name, func(t *testing.T) {
107
- got, err := parseHexBytes(tt.input)
108
- if tt.wantErr {
109
- if err == nil {
110
- t.Errorf("parseHexBytes(%q) expected error, got nil", tt.input)
111
- }
112
- } else {
113
- if err != nil {
114
- t.Errorf("parseHexBytes(%q) unexpected error: %v", tt.input, err)
115
- }
116
- if string(got) != string(tt.want) {
117
- t.Errorf("parseHexBytes(%q) = %q, want %q", tt.input, got, tt.want)
118
- }
119
- }
120
- })
121
- }
122
-}
123
-
124
-func TestConvertBITSToInteger(t *testing.T) {
125
- tests := []struct {
126
- name string
127
- input string
128
- want string
129
- wantErr bool
130
- }{
131
- {"single byte", "05", "5", false},
132
- {"two bytes hex", "01 02", "513", false}, // Little-endian: reversed to 02 01 = 0x0201 = 513
133
- {"four bytes hex", "01 02 03 04", "67305985", false}, // Little-endian: reversed to 04 03 02 01 = 0x04030201 = 67305985
134
- {"all zeros", "00 00", "0", false},
135
- {"all ones", "FF FF", "65535", false},
136
- {"invalid hex", "ZZ", "", true},
137
- }
138
-
139
- for _, tt := range tests {
140
- t.Run(tt.name, func(t *testing.T) {
141
- got, err := convertBITSToInteger(tt.input)
142
- if tt.wantErr {
143
- if err == nil {
144
- t.Errorf("convertBITSToInteger(%q) expected error, got nil", tt.input)
145
- }
146
- } else {
147
- if err != nil {
148
- t.Errorf("convertBITSToInteger(%q) unexpected error: %v", tt.input, err)
149
- }
150
- if got != tt.want {
151
- t.Errorf("convertBITSToInteger(%q) = %q, want %q", tt.input, got, tt.want)
152
- }
153
- }
154
- })
155
- }
156
-}
157
-
158
-func TestFormatSTRING(t *testing.T) {
159
- tests := []struct {
160
- name string
161
- input string
162
- formatMode int
163
- want string
164
- wantErr bool
165
- }{
166
- {"plain text - value only", "hello", 1, "hello", false},
167
- {"with quotes - value only", `"quoted"`, 1, `quoted`, false}, // formatSTRING strips outer quotes
168
- {"empty - value only", "", 1, "", false},
169
- {"plain text - oid and value", "hello", 0, "hello", false},
170
- {"with quotes - oid and value", `"quoted"`, 0, `quoted`, false}, // formatSTRING strips outer quotes
171
- }
172
-
173
- for _, tt := range tests {
174
- t.Run(tt.name, func(t *testing.T) {
175
- got, err := formatSTRING(tt.input, tt.formatMode)
176
- if tt.wantErr {
177
- if err == nil {
178
- t.Errorf("formatSTRING(%q, %d) expected error, got nil", tt.input, tt.formatMode)
179
- }
180
- } else {
181
- if err != nil {
182
- t.Errorf("formatSTRING(%q, %d) unexpected error: %v", tt.input, tt.formatMode, err)
183
- }
184
- if got != tt.want {
185
- t.Errorf("formatSTRING(%q, %d) = %q, want %q", tt.input, tt.formatMode, got, tt.want)
186
- }
187
- }
188
- })
189
- }
190
-}
191
-
192
-func TestValueTypeToString(t *testing.T) {
193
- tests := []struct {
194
- name string
195
- input ValueType
196
- want string
197
- }{
198
- {"string type", ValueTypeStr, "ITEM_VALUE_TYPE_STR"},
199
- {"uint64 type", ValueTypeUint64, "ITEM_VALUE_TYPE_UINT64"},
200
- {"float type", ValueTypeFloat, "ITEM_VALUE_TYPE_FLOAT"},
201
- {"invalid type", ValueType(99), "UNKNOWN"},
202
- }
203
-
204
- for _, tt := range tests {
205
- t.Run(tt.name, func(t *testing.T) {
206
- got := valueTypeToString(tt.input)
207
- if got != tt.want {
208
- t.Errorf("valueTypeToString(%v) = %q, want %q", tt.input, got, tt.want)
209
- }
210
- })
211
- }
212
-}
213
-
214
-func TestParseValueType(t *testing.T) {
215
- tests := []struct {
216
- name string
217
- input string
218
- want ValueType
219
- wantErr bool
220
- }{
221
- {"string type", "ITEM_VALUE_TYPE_STR", ValueTypeStr, false},
222
- {"uint64 type", "ITEM_VALUE_TYPE_UINT64", ValueTypeUint64, false},
223
- {"float type", "ITEM_VALUE_TYPE_FLOAT", ValueTypeFloat, false},
224
- {"invalid type", "INVALID", ValueTypeStr, true},
225
- {"empty", "", ValueTypeStr, true},
226
- }
227
-
228
- for _, tt := range tests {
229
- t.Run(tt.name, func(t *testing.T) {
230
- got, err := parseValueType(tt.input)
231
- if tt.wantErr {
232
- if err == nil {
233
- t.Errorf("parseValueType(%q) expected error, got nil", tt.input)
234
- }
235
- } else {
236
- if err != nil {
237
- t.Errorf("parseValueType(%q) unexpected error: %v", tt.input, err)
238
- }
239
- if got != tt.want {
240
- t.Errorf("parseValueType(%q) = %v, want %v", tt.input, got, tt.want)
241
- }
242
- }
243
- })
244
- }
245
-}
246
-
247
-func TestNoopLogger(t *testing.T) {
248
- // Ensure NoopLogger doesn't panic and accepts all methods
249
- logger := NoopLogger{}
250
- logger.Debug("test", "key", "value")
251
- logger.Info("test", "key", "value")
252
- logger.Warn("test", "key", "value")
253
- logger.Error("test", "key", "value")
254
-
255
- // If we get here without panic, test passes
256
-}
257
-
258
-func TestErrorHandlerValidation(t *testing.T) {
259
- p := NewPreprocessor("test")
260
-
261
- tests := []struct {
262
- name string
263
- handler ErrorHandler
264
- origErr error
265
- wantErr bool
266
- wantVal string
267
- }{
268
- {
269
- name: "default - returns original error",
270
- handler: ErrorHandler{Action: ErrorActionDefault},
271
- origErr: errTestError,
272
- wantErr: true,
273
- },
274
- {
275
- name: "discard - returns empty value",
276
- handler: ErrorHandler{Action: ErrorActionDiscard},
277
- origErr: errTestError,
278
- wantErr: false,
279
- wantVal: "",
280
- },
281
- {
282
- name: "set value",
283
- handler: ErrorHandler{Action: ErrorActionSetValue, Params: "fallback"},
284
- origErr: errTestError,
285
- wantErr: false,
286
- wantVal: "fallback",
287
- },
288
- {
289
- name: "set error - custom message",
290
- handler: ErrorHandler{Action: ErrorActionSetError, Params: "Custom error"},
291
- origErr: errTestError,
292
- wantErr: true,
293
- },
294
- }
295
-
296
- for _, tt := range tests {
297
- t.Run(tt.name, func(t *testing.T) {
298
- val, err := p.handleError(tt.origErr, tt.handler)
299
- if tt.wantErr && err == nil {
300
- t.Error("expected error, got nil")
301
- }
302
- if !tt.wantErr && err != nil {
303
- t.Errorf("unexpected error: %v", err)
304
- }
305
- if !tt.wantErr && val.Data != tt.wantVal {
306
- t.Errorf("got value %q, want %q", val.Data, tt.wantVal)
307
- }
308
- })
309
- }
310
-}
311
-
312
-// Helper error for tests
313
-var errTestError = &testError{msg: "test error"}
314
-
315
-type testError struct {
316
- msg string
317
-}
318
-
319
-func (e *testError) Error() string {
320
- return e.msg
321
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/javascript_sandbox_test.go
deleted
-289
@@ -1,289 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// TestJavaScriptExecutionTimeout tests that JavaScript execution times out after 5 seconds
10
-func TestJavaScriptExecutionTimeout(t *testing.T) {
11
- value := Value{Data: "test", Type: ValueTypeStr}
12
-
13
- // Infinite loop should timeout
14
- script := `
15
- while(true) {
16
- // Infinite loop
17
- }
18
- return value;
19
- `
20
-
21
- start := time.Now()
22
- _, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
23
- duration := time.Since(start)
24
-
25
- if err == nil {
26
- t.Fatal("Expected timeout error, got nil")
27
- }
28
-
29
- if !strings.Contains(err.Error(), "timeout") && !strings.Contains(err.Error(), "Interrupt") {
30
- t.Errorf("Expected timeout error, got: %v", err)
31
- }
32
-
33
- // Should timeout around 10 seconds (Zabbix default), allow some overhead
34
- if duration < 9*time.Second || duration > 12*time.Second {
35
- t.Errorf("Expected timeout around 10s, got %v", duration)
36
- }
37
-}
38
-
39
-// TestJavaScriptCallStackLimit tests that deep recursion is limited
40
-func TestJavaScriptCallStackLimit(t *testing.T) {
41
- value := Value{Data: "test", Type: ValueTypeStr}
42
-
43
- // Deep recursion should hit stack limit
44
- script := `
45
- function recurse(n) {
46
- if (n > 0) {
47
- return recurse(n - 1);
48
- }
49
- return n;
50
- }
51
- return recurse(10000);
52
- `
53
-
54
- _, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
55
-
56
- if err == nil {
57
- t.Fatal("Expected stack overflow error, got nil")
58
- }
59
-
60
- // Goja may report stack issues in various ways - just verify it errors on deep recursion
61
- if err == nil {
62
- t.Fatalf("Expected deep recursion to fail, got nil error")
63
- }
64
- // The error should mention the recursive function or contain depth info
65
- errStr := err.Error()
66
- if !strings.Contains(errStr, "recurse") && !strings.Contains(errStr, "stack") && !strings.Contains(errStr, "RangeError") {
67
- t.Logf("Deep recursion error: %v", err)
68
- }
69
-}
70
-
71
-// TestJavaScriptRSAKeyValidation tests that RSA key validation is in place
72
-// Note: Minimum key size is 1024 bits to match Zabbix behavior (though 2048+ is recommended)
73
-func TestJavaScriptRSAKeyValidation(t *testing.T) {
74
- // The official Zabbix tests verify that 1024-bit keys work correctly.
75
- // Keys smaller than 1024 bits would be rejected by the validation in javascriptExecute.
76
- // This test verifies the validation code exists and works with valid keys.
77
-
78
- // This is already tested by the official Zabbix test suite with 1024-bit keys,
79
- // so we just verify that the validation constant is set correctly.
80
- if jsMinRSAKeyBits != 1024 {
81
- t.Errorf("Expected minimum RSA key size to be 1024 bits (Zabbix compatibility), got %d", jsMinRSAKeyBits)
82
- }
83
-
84
- t.Log("RSA key validation is in place with minimum", jsMinRSAKeyBits, "bits")
85
-}
86
-
87
-// TestJavaScriptNormalExecution tests that valid JavaScript still executes correctly
88
-func TestJavaScriptNormalExecution(t *testing.T) {
89
- value := Value{Data: "hello", Type: ValueTypeStr}
90
-
91
- tests := []struct {
92
- name string
93
- script string
94
- expected string
95
- }{
96
- {
97
- name: "simple return",
98
- script: `return value;`,
99
- expected: "hello",
100
- },
101
- {
102
- name: "string concatenation",
103
- script: `return value + " world";`,
104
- expected: "hello world",
105
- },
106
- {
107
- name: "btoa encoding",
108
- script: `return btoa(value);`,
109
- expected: "aGVsbG8=",
110
- },
111
- {
112
- name: "atob decoding",
113
- script: `return atob("aGVsbG8=");`,
114
- expected: "hello",
115
- },
116
- {
117
- name: "hmac",
118
- script: `return hmac("sha256", "secret", value);`,
119
- expected: "88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b",
120
- },
121
- }
122
-
123
- for _, tt := range tests {
124
- t.Run(tt.name, func(t *testing.T) {
125
- result, err := javascriptExecute(value, tt.script, DefaultLimits().JavaScript)
126
- if err != nil {
127
- t.Fatalf("Unexpected error: %v", err)
128
- }
129
-
130
- if result.Data != tt.expected {
131
- t.Errorf("Expected '%s', got '%s'", tt.expected, result.Data)
132
- }
133
- })
134
- }
135
-}
136
-
137
-// TestJavaScriptMemoryIntensiveOperation tests that memory-intensive operations complete within limits
138
-func TestJavaScriptMemoryIntensiveOperation(t *testing.T) {
139
- value := Value{Data: "test", Type: ValueTypeStr}
140
-
141
- // Create a large array (but not infinite)
142
- script := `
143
- var arr = [];
144
- for (var i = 0; i < 10000; i++) {
145
- arr.push(i);
146
- }
147
- return arr.length.toString();
148
- `
149
-
150
- result, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
151
- if err != nil {
152
- t.Fatalf("Unexpected error: %v", err)
153
- }
154
-
155
- if result.Data != "10000" {
156
- t.Errorf("Expected '10000', got '%s'", result.Data)
157
- }
158
-}
159
-
160
-// TestJavaScriptCPUIntensiveOperation tests that CPU-intensive operations complete within timeout
161
-func TestJavaScriptCPUIntensiveOperation(t *testing.T) {
162
- value := Value{Data: "test", Type: ValueTypeStr}
163
-
164
- // Fibonacci calculation (should complete quickly)
165
- script := `
166
- function fib(n) {
167
- if (n <= 1) return n;
168
- return fib(n - 1) + fib(n - 2);
169
- }
170
- return fib(20).toString();
171
- `
172
-
173
- result, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
174
- if err != nil {
175
- t.Fatalf("Unexpected error: %v", err)
176
- }
177
-
178
- if result.Data != "6765" {
179
- t.Errorf("Expected '6765', got '%s'", result.Data)
180
- }
181
-}
182
-
183
-// TestJavaScriptPanicRecovery tests that panics are caught and converted to errors
184
-func TestJavaScriptPanicRecovery(t *testing.T) {
185
- value := Value{Data: "test", Type: ValueTypeStr}
186
-
187
- // Trigger a JavaScript error
188
- script := `
189
- throw new Error("test error");
190
- return value;
191
- `
192
-
193
- _, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
194
-
195
- if err == nil {
196
- t.Fatal("Expected error from thrown exception, got nil")
197
- }
198
-
199
- if !strings.Contains(err.Error(), "test error") && !strings.Contains(err.Error(), "Error") {
200
- t.Errorf("Expected error message, got: %v", err)
201
- }
202
-}
203
-
204
-// TestJavaScriptHMACWithInvalidAlgorithm tests HMAC error handling
205
-func TestJavaScriptHMACWithInvalidAlgorithm(t *testing.T) {
206
- value := Value{Data: "test", Type: ValueTypeStr}
207
-
208
- script := `
209
- return hmac("invalid", "key", value);
210
- `
211
-
212
- _, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
213
-
214
- if err == nil {
215
- t.Fatal("Expected error for invalid HMAC algorithm, got nil")
216
- }
217
-
218
- if !strings.Contains(err.Error(), "unsupported") && !strings.Contains(err.Error(), "algorithm") {
219
- t.Errorf("Expected algorithm error, got: %v", err)
220
- }
221
-}
222
-
223
-// TestJavaScriptSandboxingDoesNotAffectValidCode tests that sandboxing doesn't break valid operations
224
-func TestJavaScriptSandboxingDoesNotAffectValidCode(t *testing.T) {
225
- value := Value{Data: "100", Type: ValueTypeStr}
226
-
227
- // Test various operations that should work fine
228
- tests := []struct {
229
- name string
230
- script string
231
- }{
232
- {
233
- name: "arithmetic",
234
- script: `return (parseInt(value) * 2).toString();`,
235
- },
236
- {
237
- name: "string operations",
238
- script: `return value.toUpperCase();`,
239
- },
240
- {
241
- name: "array operations",
242
- script: `return [1, 2, 3].map(x => x * 2).join(",");`,
243
- },
244
- {
245
- name: "object operations",
246
- script: `var obj = {a: 1}; return obj.a.toString();`,
247
- },
248
- {
249
- name: "json operations",
250
- script: `return JSON.stringify({value: value});`,
251
- },
252
- }
253
-
254
- for _, tt := range tests {
255
- t.Run(tt.name, func(t *testing.T) {
256
- _, err := javascriptExecute(value, tt.script, DefaultLimits().JavaScript)
257
- if err != nil {
258
- t.Errorf("Valid code failed after sandboxing: %v", err)
259
- }
260
- })
261
- }
262
-}
263
-
264
-// TestJavaScriptTimeoutPreventsHang tests that timeout prevents indefinite hangs
265
-func TestJavaScriptTimeoutPreventsHang(t *testing.T) {
266
- value := Value{Data: "test", Type: ValueTypeStr}
267
-
268
- // Slow operation that would hang without timeout
269
- script := `
270
- var sum = 0;
271
- for (var i = 0; i < 999999999999; i++) {
272
- sum += i;
273
- }
274
- return sum.toString();
275
- `
276
-
277
- start := time.Now()
278
- _, err := javascriptExecute(value, script, DefaultLimits().JavaScript)
279
- duration := time.Since(start)
280
-
281
- if err == nil {
282
- t.Fatal("Expected timeout error, got nil")
283
- }
284
-
285
- // Should timeout around 10s (Zabbix default), not run for minutes
286
- if duration > 12*time.Second {
287
- t.Errorf("Execution took too long (%v), timeout not working", duration)
288
- }
289
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/javascript_step.go
deleted
-467
@@ -1,467 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "crypto"
5
- "crypto/hmac"
6
- "crypto/md5"
7
- "crypto/rand"
8
- "crypto/rsa"
9
- "crypto/sha256"
10
- "crypto/x509"
11
- "encoding/base64"
12
- "encoding/hex"
13
- "encoding/pem"
14
- "fmt"
15
- "hash"
16
- "strings"
17
- "sync"
18
- "time"
19
-
20
- "github.com/dop251/goja"
21
-)
22
-
23
-// JavaScript execution safety constants
24
-const (
25
- jsExecutionTimeout = 5 * time.Second // Maximum execution time for JavaScript
26
- jsMaxCallStackSize = 100 // Maximum call stack depth
27
- jsMinRSAKeyBits = 1024 // Minimum RSA key size (bits) - matches Zabbix behavior
28
- jsProgramCacheSize = 1000 // Maximum number of cached compiled programs
29
- // NOTE: 1024-bit RSA keys are deprecated and insecure (NIST recommends 2048+ bits).
30
- // This limit matches Zabbix's behavior for compatibility. Production deployments
31
- // should enforce 2048+ bit keys at the configuration/policy level.
32
-)
33
-
34
-// PEM key normalization constants
35
-const (
36
- pemBeginMarker = "-----BEGIN"
37
- pemEndMarker = "-----END "
38
- pemDelimiter = "-----"
39
- pemBeginMarkerLen = 10 // len("-----BEGIN")
40
- pemEndMarkerLen = 9 // len("-----END ")
41
- pemDelimiterLen = 5 // len("-----")
42
- pemLineWidth = 64 // Standard PEM line width (RFC 7468)
43
- pemMinHeaderLen = 30 // Minimum length for PEM header check
44
- pemNewlineCheckPos = 27 // Position to check for newline after BEGIN marker
45
-)
46
-
47
-// boundedProgramCache implements a size-limited LRU cache for compiled JavaScript programs.
48
-type boundedProgramCache struct {
49
- mu sync.RWMutex
50
- cache map[string]*goja.Program
51
- order []string // LRU tracking (oldest first)
52
-}
53
-
54
-// newBoundedProgramCache creates a bounded program cache.
55
-func newBoundedProgramCache(maxSize int) *boundedProgramCache {
56
- return &boundedProgramCache{
57
- cache: make(map[string]*goja.Program),
58
- order: make([]string, 0, maxSize),
59
- }
60
-}
61
-
62
-// Get retrieves a compiled program from the cache.
63
-func (c *boundedProgramCache) Get(key string) (*goja.Program, bool) {
64
- c.mu.RLock()
65
- prog, found := c.cache[key]
66
- c.mu.RUnlock()
67
-
68
- if found {
69
- // Move to end of LRU order (most recently used)
70
- c.mu.Lock()
71
- for i, k := range c.order {
72
- if k == key {
73
- c.order = append(c.order[:i], c.order[i+1:]...)
74
- c.order = append(c.order, key)
75
- break
76
- }
77
- }
78
- c.mu.Unlock()
79
- }
80
-
81
- return prog, found
82
-}
83
-
84
-// Put stores a compiled program in the cache, evicting oldest if at capacity.
85
-func (c *boundedProgramCache) Put(key string, prog *goja.Program) {
86
- c.mu.Lock()
87
- defer c.mu.Unlock()
88
-
89
- // Check if already cached
90
- if _, exists := c.cache[key]; exists {
91
- // Update LRU order
92
- for i, k := range c.order {
93
- if k == key {
94
- c.order = append(c.order[:i], c.order[i+1:]...)
95
- c.order = append(c.order, key)
96
- break
97
- }
98
- }
99
- return
100
- }
101
-
102
- // Evict oldest if at capacity
103
- if len(c.cache) >= jsProgramCacheSize {
104
- oldest := c.order[0]
105
- delete(c.cache, oldest)
106
- c.order = c.order[1:]
107
- }
108
-
109
- // Add new entry
110
- c.cache[key] = prog
111
- c.order = append(c.order, key)
112
-}
113
-
114
-// JavaScript program caching for performance optimization
115
-var (
116
- // programCache caches compiled JavaScript programs (keyed by script code)
117
- // Bounded to jsProgramCacheSize to prevent unbounded memory growth
118
- programCache = newBoundedProgramCache(jsProgramCacheSize)
119
-)
120
-
121
-// createConfiguredVM creates a new goja VM with all built-in functions configured.
122
-// This function is called by vmPool when creating new VM instances.
123
-func createConfiguredVM() *goja.Runtime {
124
- vm := goja.New()
125
- vm.SetMaxCallStackSize(jsMaxCallStackSize)
126
-
127
- // Configure all built-in functions
128
- setupBuiltinFunctions(vm)
129
-
130
- return vm
131
-}
132
-
133
-// setupBuiltinFunctions configures all Zabbix built-in JavaScript functions on a VM.
134
-// This is called once per VM instance when it's created.
135
-func setupBuiltinFunctions(vm *goja.Runtime) {
136
- // btoa - Base64 encode (supports string and Uint8Array)
137
- vm.Set("btoa", func(call goja.FunctionCall) goja.Value {
138
- if len(call.Arguments) == 0 {
139
- return goja.Null()
140
- }
141
-
142
- var data []byte
143
- arg := call.Arguments[0]
144
-
145
- // Check if it's a Uint8Array
146
- if obj := arg.ToObject(vm); obj != nil {
147
- if arr := obj.Get("constructor"); arr != nil && arr.String() == "function Uint8Array() { [native code] }" {
148
- // It's a typed array, extract bytes
149
- if length := obj.Get("length"); length != nil {
150
- n := int(length.ToInteger())
151
- data = make([]byte, n)
152
- for i := 0; i < n; i++ {
153
- data[i] = byte(obj.Get(fmt.Sprintf("%d", i)).ToInteger())
154
- }
155
- }
156
- }
157
- }
158
-
159
- // If not Uint8Array, treat as string
160
- if data == nil {
161
- data = []byte(arg.String())
162
- }
163
-
164
- encoded := base64.StdEncoding.EncodeToString(data)
165
- return vm.ToValue(encoded)
166
- })
167
-
168
- // atob - Base64 decode
169
- vm.Set("atob", func(call goja.FunctionCall) goja.Value {
170
- if len(call.Arguments) == 0 {
171
- return goja.Null()
172
- }
173
-
174
- encoded := call.Arguments[0].String()
175
- decoded, err := base64.StdEncoding.DecodeString(encoded)
176
- if err != nil {
177
- // Invalid base64 - return empty string
178
- return vm.ToValue("")
179
- }
180
-
181
- return vm.ToValue(string(decoded))
182
- })
183
-
184
- // hmac - HMAC hash function
185
- vm.Set("hmac", func(call goja.FunctionCall) goja.Value {
186
- if len(call.Arguments) < 3 {
187
- panic(vm.NewGoError(fmt.Errorf("hmac requires 3 arguments: algorithm, key, data")))
188
- }
189
-
190
- // Check for null/undefined arguments
191
- if goja.IsNull(call.Arguments[1]) || goja.IsUndefined(call.Arguments[1]) {
192
- panic(vm.NewGoError(fmt.Errorf("invalid key parameter")))
193
- }
194
- if goja.IsNull(call.Arguments[2]) || goja.IsUndefined(call.Arguments[2]) {
195
- panic(vm.NewGoError(fmt.Errorf("invalid data parameter")))
196
- }
197
-
198
- algorithm := call.Arguments[0].String()
199
- key := call.Arguments[1].String()
200
- data := call.Arguments[2].String()
201
-
202
- var mac hash.Hash
203
-
204
- switch algorithm {
205
- case "md5":
206
- mac = hmac.New(md5.New, []byte(key))
207
- case "sha256":
208
- mac = hmac.New(sha256.New, []byte(key))
209
- default:
210
- panic(vm.NewGoError(fmt.Errorf("unsupported hmac algorithm: %s", algorithm)))
211
- }
212
-
213
- mac.Write([]byte(data))
214
- result := hex.EncodeToString(mac.Sum(nil))
215
- return vm.ToValue(result)
216
- })
217
-
218
- // sign - RSA signature function
219
- vm.Set("sign", func(call goja.FunctionCall) goja.Value {
220
- if len(call.Arguments) < 3 {
221
- panic(vm.NewGoError(fmt.Errorf("sign requires 3 arguments: algorithm, privateKey, data")))
222
- }
223
-
224
- algorithm := call.Arguments[0].String()
225
- pemKey := call.Arguments[1].String()
226
- dataArg := call.Arguments[2]
227
-
228
- // Extract data (support string and Uint8Array)
229
- var data []byte
230
- if obj := dataArg.ToObject(vm); obj != nil {
231
- if arr := obj.Get("constructor"); arr != nil && arr.String() == "function Uint8Array() { [native code] }" {
232
- // It's a typed array, extract bytes
233
- if length := obj.Get("length"); length != nil {
234
- n := int(length.ToInteger())
235
- data = make([]byte, n)
236
- for i := 0; i < n; i++ {
237
- data[i] = byte(obj.Get(fmt.Sprintf("%d", i)).ToInteger())
238
- }
239
- }
240
- }
241
- }
242
- if data == nil {
243
- data = []byte(dataArg.String())
244
- }
245
-
246
- if algorithm != "sha256" {
247
- panic(vm.NewGoError(fmt.Errorf("unsupported signature algorithm: %s", algorithm)))
248
- }
249
-
250
- // Normalize PEM key (handle single-line keys)
251
- pemKey = normalizePEMKey(pemKey)
252
-
253
- // Parse PEM key
254
- block, _ := pem.Decode([]byte(pemKey))
255
- if block == nil {
256
- panic(vm.NewGoError(fmt.Errorf("failed to parse PEM block")))
257
- }
258
-
259
- var privateKey *rsa.PrivateKey
260
- var err error
261
-
262
- // Try PKCS#1 format first
263
- privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
264
- if err != nil {
265
- // Try PKCS#8 format
266
- key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
267
- if err2 != nil {
268
- panic(vm.NewGoError(fmt.Errorf("failed to parse private key: %v", err)))
269
- }
270
- var ok bool
271
- privateKey, ok = key.(*rsa.PrivateKey)
272
- if !ok {
273
- panic(vm.NewGoError(fmt.Errorf("not an RSA private key")))
274
- }
275
- }
276
-
277
- // Validate RSA key size (minimum 2048 bits for security)
278
- keyBits := privateKey.N.BitLen()
279
- if keyBits < jsMinRSAKeyBits {
280
- panic(vm.NewGoError(fmt.Errorf("RSA key too small: %d bits (minimum %d bits required)", keyBits, jsMinRSAKeyBits)))
281
- }
282
-
283
- // Hash the data
284
- hashed := sha256.Sum256(data)
285
-
286
- // Sign the hash
287
- signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
288
- if err != nil {
289
- panic(vm.NewGoError(fmt.Errorf("signing failed: %v", err)))
290
- }
291
-
292
- // Return hex-encoded signature
293
- result := hex.EncodeToString(signature)
294
- return vm.ToValue(result)
295
- })
296
-
297
- // HttpRequest - Minimal stub for Zabbix HTTP request object
298
- // Only needed for test compatibility - actual HTTP functionality not implemented
299
- vm.RunString(`
300
- function HttpRequest() {
301
- // Minimal constructor - allows property assignment
302
- // Actual HTTP methods would be implemented here in full Zabbix
303
- }
304
- `)
305
-}
306
-
307
-// normalizePEMKey normalizes a PEM key by adding proper newlines
308
-func normalizePEMKey(pemStr string) string {
309
- // If already has newlines after BEGIN, return as is
310
- if len(pemStr) > pemMinHeaderLen && pemStr[pemNewlineCheckPos] == '\n' {
311
- return pemStr
312
- }
313
-
314
- // Find BEGIN and END markers
315
- beginIdx := 0
316
- endIdx := len(pemStr)
317
-
318
- for i := 0; i < len(pemStr)-pemBeginMarkerLen; i++ {
319
- if pemStr[i:i+pemBeginMarkerLen] == pemBeginMarker {
320
- // Find end of BEGIN line
321
- for j := i; j < len(pemStr)-pemDelimiterLen; j++ {
322
- if pemStr[j:j+pemDelimiterLen] == pemDelimiter && j > i+pemBeginMarkerLen {
323
- beginIdx = j + pemDelimiterLen
324
- break
325
- }
326
- }
327
- }
328
- if pemStr[i:i+pemEndMarkerLen] == pemEndMarker {
329
- endIdx = i
330
- break
331
- }
332
- }
333
-
334
- if beginIdx == 0 || endIdx == len(pemStr) {
335
- return pemStr // No markers found, return as is
336
- }
337
-
338
- // Extract header, body, footer
339
- header := pemStr[:beginIdx]
340
- body := pemStr[beginIdx:endIdx]
341
- footer := pemStr[endIdx:]
342
-
343
- // Remove any existing whitespace from body
344
- var bodyClean strings.Builder
345
- bodyClean.Grow(len(body))
346
- for _, ch := range body {
347
- if ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t' {
348
- bodyClean.WriteRune(ch)
349
- }
350
- }
351
-
352
- // Split body into pemLineWidth-char lines (RFC 7468 standard)
353
- cleanStr := bodyClean.String()
354
- var lines []string
355
- for i := 0; i < len(cleanStr); i += pemLineWidth {
356
- end := i + pemLineWidth
357
- if end > len(cleanStr) {
358
- end = len(cleanStr)
359
- }
360
- lines = append(lines, cleanStr[i:end])
361
- }
362
-
363
- // Reconstruct PEM
364
- var result strings.Builder
365
- result.Grow(len(header) + len(cleanStr) + len(footer) + len(lines)*2)
366
- result.WriteString(header)
367
- result.WriteByte('\n')
368
- for _, line := range lines {
369
- result.WriteString(line)
370
- result.WriteByte('\n')
371
- }
372
- result.WriteString(footer)
373
-
374
- return result.String()
375
-}
376
-
377
-// javascriptExecute executes JavaScript preprocessing step with program caching.
378
-//
379
-// Performance optimizations:
380
-// - Caches compiled programs in programCache (avoids recompilation)
381
-// - Built-in functions configured once per VM (not per execution)
382
-//
383
-// Security note: VMs are NOT pooled to prevent prototype pollution attacks.
384
-// Each execution gets a fresh VM to ensure complete isolation.
385
-func javascriptExecute(value Value, paramStr string, limits JSLimits) (Value, error) {
386
- if paramStr == "" {
387
- return Value{}, fmt.Errorf("javascript code required")
388
- }
389
-
390
- // Create fresh VM for each execution (prevents prototype pollution)
391
- // VM pooling was removed for security - prototype modifications persist across reuse
392
- vm := createConfiguredVM()
393
-
394
- // No defer to return VM to pool - VM is discarded after use (GC will collect it)
395
- // This ensures no state leakage between executions
396
-
397
- // Set up execution timeout using a goroutine that will interrupt the VM
398
- // The interrupt will cause the current execution to fail; since we create a fresh
399
- // VM per execution (not pooled), the interrupted VM is simply discarded afterward
400
- timeout := limits.Timeout
401
- if timeout == 0 {
402
- timeout = 10 * time.Second // Default to Zabbix's 10s timeout
403
- }
404
- done := make(chan struct{})
405
- go func() {
406
- select {
407
- case <-time.After(timeout):
408
- vm.Interrupt("JavaScript execution timeout")
409
- case <-done:
410
- return
411
- }
412
- }()
413
- defer close(done)
414
-
415
- // Panic recovery for JavaScript errors
416
- defer func() {
417
- if r := recover(); r != nil {
418
- // Panics from goja are already handled, but catch any other panics
419
- }
420
- }()
421
-
422
- // Create the main function code
423
- code := `
424
-(function(value) {
425
- ` + paramStr + `
426
-})
427
-`
428
-
429
- // Try to get compiled program from cache
430
- var program *goja.Program
431
- if cached, ok := programCache.Get(code); ok {
432
- program = cached
433
- } else {
434
- // Compile the program (first time for this script)
435
- var err error
436
- program, err = goja.Compile("", code, false)
437
- if err != nil {
438
- return Value{}, fmt.Errorf("javascript compilation error: %w", err)
439
- }
440
- // Cache the compiled program (bounded LRU, max 1000 entries)
441
- programCache.Put(code, program)
442
- }
443
-
444
- // Run the compiled program
445
- result, err := vm.RunProgram(program)
446
- if err != nil {
447
- return Value{}, fmt.Errorf("javascript execution error: %w", err)
448
- }
449
-
450
- // Call the function with value as parameter
451
- fn := result
452
- var fnVal goja.Callable
453
- ok := false
454
- if fnVal, ok = goja.AssertFunction(fn); !ok {
455
- return Value{}, fmt.Errorf("javascript code must return a function")
456
- }
457
-
458
- // Execute the function
459
- res, err := fnVal(goja.Undefined(), vm.ToValue(value.Data))
460
- if err != nil {
461
- return Value{}, fmt.Errorf("javascript function error: %w", err)
462
- }
463
-
464
- // Get the result as string
465
- resultStr := res.String()
466
- return Value{Data: resultStr, Type: ValueTypeStr}, nil
467
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/jsonpath_step.go
deleted
-203
@@ -1,203 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "fmt"
6
- "strconv"
7
- "strings"
8
-
9
- "github.com/theory/jsonpath"
10
-)
11
-
12
-// JSON Unicode escape sequence constants
13
-const (
14
- unicodeEscapeLen = 6 // Length of \uXXXX sequence
15
- surrogatePairLen = 12 // Length of surrogate pair \uXXXX\uYYYY
16
- unicodeHexDigits = 4 // Number of hex digits in \uXXXX
17
- unicodeHexBase = 16 // Base for parsing hex codes
18
- unicodeHexBitSize = 16 // Bit size for ParseUint
19
- unicodeSurrogateStart = 0xD800 // Start of surrogate range
20
- unicodeSurrogateEnd = 0xDFFF // End of surrogate range
21
- unicodeHighSurroStart = 0xD800 // Start of high surrogate range
22
- unicodeHighSurroEnd = 0xDBFF // End of high surrogate range
23
- unicodeLowSurroStart = 0xDC00 // Start of low surrogate range
24
- unicodeLowSurroEnd = 0xDFFF // End of low surrogate range
25
-)
26
-
27
-// validateSurrogatePairs checks for unpaired UTF-16 surrogates in JSON string.
28
-// Returns error if any unpaired surrogates are found.
29
-func validateSurrogatePairs(jsonStr string) error {
30
- i := 0
31
- for i < len(jsonStr) {
32
- // Find next \u escape sequence
33
- if jsonStr[i] == '\\' && i+unicodeEscapeLen-1 < len(jsonStr) && jsonStr[i+1] == 'u' {
34
- // Parse the 4-digit hex code
35
- hexCode := jsonStr[i+2 : i+2+unicodeHexDigits]
36
- code, err := strconv.ParseUint(hexCode, unicodeHexBase, unicodeHexBitSize)
37
- if err != nil {
38
- // Invalid hex code, but let json.Unmarshal handle it
39
- i++
40
- continue
41
- }
42
-
43
- // Check if this is a surrogate
44
- if code >= unicodeSurrogateStart && code <= unicodeSurrogateEnd {
45
- // This is a surrogate
46
- if code >= unicodeHighSurroStart && code <= unicodeHighSurroEnd {
47
- // High surrogate - must be followed by low surrogate
48
- if i+surrogatePairLen < len(jsonStr) && jsonStr[i+6:i+8] == "\\u" {
49
- nextHex := jsonStr[i+8 : i+8+unicodeHexDigits]
50
- nextCode, err := strconv.ParseUint(nextHex, unicodeHexBase, unicodeHexBitSize)
51
- if err == nil && nextCode >= unicodeLowSurroStart && nextCode <= unicodeLowSurroEnd {
52
- // Valid surrogate pair, skip both
53
- i += surrogatePairLen
54
- continue
55
- }
56
- }
57
- // High surrogate without low surrogate = invalid
58
- return fmt.Errorf("invalid JSON: unpaired high surrogate")
59
- } else {
60
- // Low surrogate without preceding high surrogate = invalid
61
- return fmt.Errorf("invalid JSON: unpaired low surrogate")
62
- }
63
- }
64
-
65
- i += unicodeEscapeLen // Skip this \uXXXX sequence
66
- } else {
67
- i++
68
- }
69
- }
70
- return nil
71
-}
72
-
73
-// jsonpathExtract extracts values from JSON using JSONPath expression.
74
-// jsonPathMulti extracts values using JSONPath and returns multiple metrics for arrays
75
-func jsonPathMulti(value Value, paramStr string) (Result, error) {
76
- expr := strings.TrimSpace(paramStr)
77
- if expr == "" {
78
- err := fmt.Errorf("jsonpath expression is required")
79
- return Result{Error: err}, err
80
- }
81
-
82
- // Validate surrogate pairs before parsing
83
- if err := validateSurrogatePairs(value.Data); err != nil {
84
- return Result{Error: err}, err
85
- }
86
-
87
- // Parse JSON
88
- var data interface{}
89
- err := json.Unmarshal([]byte(value.Data), &data)
90
- if err != nil {
91
- err = fmt.Errorf("invalid JSON: %w", err)
92
- return Result{Error: err}, err
93
- }
94
-
95
- // Compile and execute JSONPath expression
96
- jp, err := jsonpath.Parse(expr)
97
- if err != nil {
98
- err = fmt.Errorf("invalid jsonpath expression: %w", err)
99
- return Result{Error: err}, err
100
- }
101
-
102
- results := jp.Select(data)
103
- if len(results) == 0 {
104
- err = fmt.Errorf("jsonpath expression did not match any values")
105
- return Result{Error: err}, err
106
- }
107
-
108
- // If single result and not an array, return single metric
109
- if len(results) == 1 {
110
- // Check if the single result is an array
111
- if arr, ok := results[0].([]interface{}); ok {
112
- // It's an array - return each element as separate metric
113
- metrics := make([]Metric, 0, len(arr))
114
- for i, item := range arr {
115
- metrics = append(metrics, Metric{
116
- Name: "item",
117
- Value: marshalJSONValue(item),
118
- Type: ValueTypeStr,
119
- Labels: map[string]string{"index": fmt.Sprintf("%d", i)},
120
- })
121
- }
122
- return Result{Metrics: metrics}, nil
123
- }
124
- // Single non-array result - return as single metric
125
- return Result{
126
- Metrics: []Metric{{
127
- Name: "",
128
- Value: marshalJSONValue(results[0]),
129
- Type: ValueTypeStr,
130
- }},
131
- }, nil
132
- }
133
-
134
- // Multiple results from JSONPath - return each as separate metric
135
- metrics := make([]Metric, 0, len(results))
136
- for i, result := range results {
137
- metrics = append(metrics, Metric{
138
- Name: "item",
139
- Value: marshalJSONValue(result),
140
- Type: ValueTypeStr,
141
- Labels: map[string]string{"index": fmt.Sprintf("%d", i)},
142
- })
143
- }
144
-
145
- return Result{Metrics: metrics}, nil
146
-}
147
-
148
-// marshalJSONValue converts a value to JSON string, handling strings specially
149
-func marshalJSONValue(v interface{}) string {
150
- // If it's already a string, return it directly without JSON encoding
151
- if s, ok := v.(string); ok {
152
- return s
153
- }
154
- // For non-string values, encode as JSON
155
- b, _ := json.Marshal(v)
156
- return string(b)
157
-}
158
-
159
-func jsonpathExtract(value Value, paramStr string) (Value, error) {
160
- expr := strings.TrimSpace(paramStr)
161
- if expr == "" {
162
- return Value{}, fmt.Errorf("jsonpath expression is required")
163
- }
164
-
165
- // Validate surrogate pairs before parsing
166
- if err := validateSurrogatePairs(value.Data); err != nil {
167
- return Value{}, err
168
- }
169
-
170
- // Parse JSON
171
- var data interface{}
172
- err := json.Unmarshal([]byte(value.Data), &data)
173
- if err != nil {
174
- return Value{}, fmt.Errorf("invalid JSON: %w", err)
175
- }
176
-
177
- // Compile and execute JSONPath expression
178
- jp, err := jsonpath.Parse(expr)
179
- if err != nil {
180
- return Value{}, fmt.Errorf("invalid jsonpath expression: %w", err)
181
- }
182
-
183
- results := jp.Select(data)
184
- if len(results) == 0 {
185
- return Value{}, fmt.Errorf("jsonpath expression did not match any values")
186
- }
187
-
188
- // Return the first result
189
- result := results[0]
190
-
191
- // If result is a string, return it directly without JSON encoding
192
- if s, ok := result.(string); ok {
193
- return Value{Data: s, Type: ValueTypeStr}, nil
194
- }
195
-
196
- // For non-string results, encode as JSON
197
- resultBytes, err := json.Marshal(result)
198
- if err != nil {
199
- return Value{}, fmt.Errorf("failed to marshal result: %w", err)
200
- }
201
-
202
- return Value{Data: string(resultBytes), Type: ValueTypeStr}, nil
203
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/logger.go
deleted
-49
@@ -1,49 +0,0 @@
1
-package zabbixpreproc
2
-
3
-// Logger is a minimal logging interface for preprocessing operations.
4
-// This allows users to plug in any logging library (slog, logrus, zap, etc.)
5
-// without forcing dependencies on the library.
6
-//
7
-// By default, the library uses NoopLogger (zero overhead).
8
-// Users can provide their own logger implementation via SetLogger().
9
-//
10
-// Example with slog:
11
-//
12
-// logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
13
-// preprocessor.SetLogger(NewSlogAdapter(logger))
14
-type Logger interface {
15
- // Debug logs debug-level messages with optional structured key-value pairs.
16
- // Used for: skipped SNMP lines, empty values, parsing decisions
17
- Debug(msg string, keysAndValues ...interface{})
18
-
19
- // Info logs informational messages with optional structured key-value pairs.
20
- // Used for: preprocessing statistics, cache hits
21
- Info(msg string, keysAndValues ...interface{})
22
-
23
- // Warn logs warning messages with optional structured key-value pairs.
24
- // Used for: deprecated features, performance concerns
25
- Warn(msg string, keysAndValues ...interface{})
26
-
27
- // Error logs error messages with optional structured key-value pairs.
28
- // Note: This is for logging only. Errors are still returned to caller.
29
- Error(msg string, keysAndValues ...interface{})
30
-}
31
-
32
-// NoopLogger is a no-operation logger that discards all log messages.
33
-// This is the default logger (zero performance overhead).
34
-type NoopLogger struct{}
35
-
36
-// Debug implements Logger.Debug (no-op)
37
-func (NoopLogger) Debug(msg string, keysAndValues ...interface{}) {}
38
-
39
-// Info implements Logger.Info (no-op)
40
-func (NoopLogger) Info(msg string, keysAndValues ...interface{}) {}
41
-
42
-// Warn implements Logger.Warn (no-op)
43
-func (NoopLogger) Warn(msg string, keysAndValues ...interface{}) {}
44
-
45
-// Error implements Logger.Error (no-op)
46
-func (NoopLogger) Error(msg string, keysAndValues ...interface{}) {}
47
-
48
-// Ensure NoopLogger implements Logger
49
-var _ Logger = (*NoopLogger)(nil)
src/go/plugin/scripts.d/pkg/zabbixpreproc/multi_metric_test.go
deleted
-858
@@ -1,858 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// TestPrometheusMultiMetric tests that prometheusToJSONMulti() returns multiple metrics
10
-func TestPrometheusMultiMetric(t *testing.T) {
11
- input := `# HELP http_requests_total Total HTTP requests
12
-# TYPE http_requests_total counter
13
-http_requests_total{method="GET"} 100
14
-http_requests_total{method="POST"} 50
15
-memory_usage_bytes 1024
16
-`
17
-
18
- result, err := prometheusToJSONMulti(Value{Data: input, Type: ValueTypeStr}, "")
19
- if err != nil {
20
- t.Fatalf("prometheusToJSONMulti() error: %v", err)
21
- }
22
-
23
- // Should return 3 metrics
24
- if len(result.Metrics) != 3 {
25
- t.Fatalf("Expected 3 metrics, got %d", len(result.Metrics))
26
- }
27
-
28
- // Verify first metric
29
- m0 := result.Metrics[0]
30
- if m0.Name != "http_requests_total" {
31
- t.Errorf("Metric 0: expected name 'http_requests_total', got %q", m0.Name)
32
- }
33
- if m0.Value != "100" {
34
- t.Errorf("Metric 0: expected value '100', got %q", m0.Value)
35
- }
36
- if m0.Type != ValueTypeStr {
37
- t.Errorf("Metric 0: expected type ValueTypeStr, got %v", m0.Type)
38
- }
39
- if m0.Labels == nil {
40
- t.Fatal("Metric 0: labels should not be nil")
41
- }
42
- if m0.Labels["method"] != "GET" {
43
- t.Errorf("Metric 0: expected label method=GET, got %q", m0.Labels["method"])
44
- }
45
-
46
- // Verify second metric
47
- m1 := result.Metrics[1]
48
- if m1.Name != "http_requests_total" {
49
- t.Errorf("Metric 1: expected name 'http_requests_total', got %q", m1.Name)
50
- }
51
- if m1.Value != "50" {
52
- t.Errorf("Metric 1: expected value '50', got %q", m1.Value)
53
- }
54
- if m1.Labels["method"] != "POST" {
55
- t.Errorf("Metric 1: expected label method=POST, got %q", m1.Labels["method"])
56
- }
57
-
58
- // Verify third metric (no labels)
59
- m2 := result.Metrics[2]
60
- if m2.Name != "memory_usage_bytes" {
61
- t.Errorf("Metric 2: expected name 'memory_usage_bytes', got %q", m2.Name)
62
- }
63
- if m2.Value != "1024" {
64
- t.Errorf("Metric 2: expected value '1024', got %q", m2.Value)
65
- }
66
- if len(m2.Labels) != 0 {
67
- t.Errorf("Metric 2: expected no labels, got %v", m2.Labels)
68
- }
69
-}
70
-
71
-// TestPrometheusMultiMetric_EmptyInput tests empty input handling
72
-func TestPrometheusMultiMetric_EmptyInput(t *testing.T) {
73
- input := ""
74
-
75
- result, err := prometheusToJSONMulti(Value{Data: input, Type: ValueTypeStr}, "")
76
- if err != nil {
77
- t.Fatalf("prometheusToJSONMulti() error on empty input: %v", err)
78
- }
79
-
80
- // Empty input should return zero metrics
81
- if len(result.Metrics) != 0 {
82
- t.Errorf("Expected 0 metrics for empty input, got %d", len(result.Metrics))
83
- }
84
-}
85
-
86
-// TestPrometheusMultiMetric_InvalidInput tests error handling
87
-func TestPrometheusMultiMetric_InvalidInput(t *testing.T) {
88
- input := "invalid prometheus data here"
89
-
90
- _, err := prometheusToJSONMulti(Value{Data: input, Type: ValueTypeStr}, "")
91
- if err == nil {
92
- t.Fatal("Expected error for invalid Prometheus data, got nil")
93
- }
94
-}
95
-
96
-// TestPrometheusMultiMetric_SingleMetric tests single metric returns single-element array
97
-func TestPrometheusMultiMetric_SingleMetric(t *testing.T) {
98
- input := "cpu_usage 42.5"
99
-
100
- result, err := prometheusToJSONMulti(Value{Data: input, Type: ValueTypeStr}, "")
101
- if err != nil {
102
- t.Fatalf("prometheusToJSONMulti() error: %v", err)
103
- }
104
-
105
- if len(result.Metrics) != 1 {
106
- t.Fatalf("Expected 1 metric, got %d", len(result.Metrics))
107
- }
108
-
109
- m := result.Metrics[0]
110
- if m.Name != "cpu_usage" {
111
- t.Errorf("Expected name 'cpu_usage', got %q", m.Name)
112
- }
113
- if m.Value != "42.5" {
114
- t.Errorf("Expected value '42.5', got %q", m.Value)
115
- }
116
-}
117
-
118
-// TestPrometheusMultiMetric_ViaPreprocessor tests multi-metric through Preprocessor.Execute()
119
-func TestPrometheusMultiMetric_ViaPreprocessor(t *testing.T) {
120
- input := `http_requests_total{method="GET"} 100
121
-http_requests_total{method="POST"} 50
122
-memory_usage_bytes 1024
123
-`
124
-
125
- p := NewPreprocessor("test-shard")
126
- result, err := p.Execute("item1",
127
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
128
- Step{Type: StepTypePrometheusToJSONMulti, Params: ""})
129
-
130
- if err != nil {
131
- t.Fatalf("Execute() error: %v", err)
132
- }
133
-
134
- // Should return 3 metrics
135
- if len(result.Metrics) != 3 {
136
- t.Fatalf("Expected 3 metrics, got %d", len(result.Metrics))
137
- }
138
-
139
- // Verify metric names and values
140
- expected := []struct {
141
- name string
142
- value string
143
- }{
144
- {"http_requests_total", "100"},
145
- {"http_requests_total", "50"},
146
- {"memory_usage_bytes", "1024"},
147
- }
148
-
149
- for i, exp := range expected {
150
- if result.Metrics[i].Name != exp.name {
151
- t.Errorf("Metric %d: expected name %q, got %q", i, exp.name, result.Metrics[i].Name)
152
- }
153
- if result.Metrics[i].Value != exp.value {
154
- t.Errorf("Metric %d: expected value %q, got %q", i, exp.value, result.Metrics[i].Value)
155
- }
156
- }
157
-}
158
-
159
-// TestZabbixCompatibility_PrometheusToJSON tests that old step type still works
160
-func TestZabbixCompatibility_PrometheusToJSON(t *testing.T) {
161
- input := `http_requests_total{method="GET"} 100
162
-http_requests_total{method="POST"} 50
163
-`
164
-
165
- p := NewPreprocessor("test-shard")
166
- result, err := p.Execute("item1",
167
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
168
- Step{Type: StepTypePrometheusToJSON, Params: ""}) // OLD step type
169
-
170
- if err != nil {
171
- t.Fatalf("Execute() error: %v", err)
172
- }
173
-
174
- // OLD behavior: Should return SINGLE metric with JSON array string
175
- if len(result.Metrics) != 1 {
176
- t.Fatalf("Expected 1 metric (Zabbix compatibility), got %d", len(result.Metrics))
177
- }
178
-
179
- // Value should be a JSON array string
180
- value := result.Metrics[0].Value
181
- if value[0] != '[' || value[len(value)-1] != ']' {
182
- t.Errorf("Expected JSON array string, got: %s", value)
183
- }
184
-
185
- // Verify it contains both metrics in the JSON array
186
- if !contains(value, `"name":"http_requests_total"`) {
187
- t.Errorf("JSON array missing http_requests_total metric")
188
- }
189
- if !contains(value, `"method":"GET"`) || !contains(value, `"method":"POST"`) {
190
- t.Errorf("JSON array missing expected labels")
191
- }
192
-}
193
-
194
-func contains(s, substr string) bool {
195
- return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsHelper(s, substr))
196
-}
197
-
198
-func containsHelper(s, substr string) bool {
199
- for i := 0; i <= len(s)-len(substr); i++ {
200
- if s[i:i+len(substr)] == substr {
201
- return true
202
- }
203
- }
204
- return false
205
-}
206
-
207
-// ============================================================================
208
-// JSONPath Multi-Metric Tests
209
-// ============================================================================
210
-
211
-// TestJSONPathMulti_Array tests JSONPath with array result
212
-func TestJSONPathMulti_Array(t *testing.T) {
213
- input := `{"items": [{"name": "a", "value": 10}, {"name": "b", "value": 20}]}`
214
- jsonpath := "$.items[*]"
215
-
216
- result, err := jsonPathMulti(Value{Data: input, Type: ValueTypeStr}, jsonpath)
217
- if err != nil {
218
- t.Fatalf("jsonPathMulti() error: %v", err)
219
- }
220
-
221
- // Should return 2 metrics (one per array element)
222
- if len(result.Metrics) != 2 {
223
- t.Fatalf("Expected 2 metrics, got %d", len(result.Metrics))
224
- }
225
-
226
- // Verify first metric
227
- m0 := result.Metrics[0]
228
- if m0.Name != "item" {
229
- t.Errorf("Metric 0: expected name 'item', got %q", m0.Name)
230
- }
231
- if m0.Labels["index"] != "0" {
232
- t.Errorf("Metric 0: expected index label '0', got %q", m0.Labels["index"])
233
- }
234
- if !contains(m0.Value, `"name":"a"`) {
235
- t.Errorf("Metric 0: expected to contain name:a, got %q", m0.Value)
236
- }
237
-
238
- // Verify second metric
239
- m1 := result.Metrics[1]
240
- if m1.Labels["index"] != "1" {
241
- t.Errorf("Metric 1: expected index label '1', got %q", m1.Labels["index"])
242
- }
243
-}
244
-
245
-// TestJSONPathMulti_SingleValue tests JSONPath with single value result
246
-func TestJSONPathMulti_SingleValue(t *testing.T) {
247
- input := `{"name": "test", "value": 42}`
248
- jsonpath := "$.value"
249
-
250
- result, err := jsonPathMulti(Value{Data: input, Type: ValueTypeStr}, jsonpath)
251
- if err != nil {
252
- t.Fatalf("jsonPathMulti() error: %v", err)
253
- }
254
-
255
- // Should return 1 metric
256
- if len(result.Metrics) != 1 {
257
- t.Fatalf("Expected 1 metric, got %d", len(result.Metrics))
258
- }
259
-
260
- m := result.Metrics[0]
261
- if m.Value != "42" {
262
- t.Errorf("Expected value '42', got %q", m.Value)
263
- }
264
- if len(m.Labels) != 0 {
265
- t.Errorf("Single value should have no labels, got %v", m.Labels)
266
- }
267
-}
268
-
269
-// TestJSONPathMulti_StringValue tests JSONPath extracting string
270
-func TestJSONPathMulti_StringValue(t *testing.T) {
271
- input := `{"message": "hello world"}`
272
- jsonpath := "$.message"
273
-
274
- result, err := jsonPathMulti(Value{Data: input, Type: ValueTypeStr}, jsonpath)
275
- if err != nil {
276
- t.Fatalf("jsonPathMulti() error: %v", err)
277
- }
278
-
279
- if len(result.Metrics) != 1 {
280
- t.Fatalf("Expected 1 metric, got %d", len(result.Metrics))
281
- }
282
-
283
- // String should be returned directly without JSON encoding
284
- m := result.Metrics[0]
285
- if m.Value != "hello world" {
286
- t.Errorf("Expected value 'hello world', got %q", m.Value)
287
- }
288
-}
289
-
290
-// TestJSONPathMulti_ArrayOfStrings tests array of primitive values
291
-func TestJSONPathMulti_ArrayOfStrings(t *testing.T) {
292
- input := `{"tags": ["red", "green", "blue"]}`
293
- jsonpath := "$.tags[*]"
294
-
295
- result, err := jsonPathMulti(Value{Data: input, Type: ValueTypeStr}, jsonpath)
296
- if err != nil {
297
- t.Fatalf("jsonPathMulti() error: %v", err)
298
- }
299
-
300
- // Should return 3 metrics
301
- if len(result.Metrics) != 3 {
302
- t.Fatalf("Expected 3 metrics, got %d", len(result.Metrics))
303
- }
304
-
305
- // Verify values
306
- expected := []string{"red", "green", "blue"}
307
- for i, exp := range expected {
308
- if result.Metrics[i].Value != exp {
309
- t.Errorf("Metric %d: expected value %q, got %q", i, exp, result.Metrics[i].Value)
310
- }
311
- if result.Metrics[i].Labels["index"] != fmt.Sprintf("%d", i) {
312
- t.Errorf("Metric %d: expected index %d, got %q", i, i, result.Metrics[i].Labels["index"])
313
- }
314
- }
315
-}
316
-
317
-// TestJSONPathMulti_ViaPreprocessor tests full integration
318
-func TestJSONPathMulti_ViaPreprocessor(t *testing.T) {
319
- input := `{"servers": [{"name": "web1", "cpu": 45}, {"name": "web2", "cpu": 60}]}`
320
- jsonpath := "$.servers[*]"
321
-
322
- p := NewPreprocessor("test-shard")
323
- result, err := p.Execute("item1",
324
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
325
- Step{Type: StepTypeJSONPathMulti, Params: jsonpath})
326
-
327
- if err != nil {
328
- t.Fatalf("Execute() error: %v", err)
329
- }
330
-
331
- if len(result.Metrics) != 2 {
332
- t.Fatalf("Expected 2 metrics, got %d", len(result.Metrics))
333
- }
334
-
335
- // Verify both metrics have index labels
336
- for i := 0; i < 2; i++ {
337
- if result.Metrics[i].Labels["index"] != fmt.Sprintf("%d", i) {
338
- t.Errorf("Metric %d missing correct index label", i)
339
- }
340
- }
341
-}
342
-
343
-// TestJSONPathMulti_EmptyResult tests error on no matches
344
-func TestJSONPathMulti_EmptyResult(t *testing.T) {
345
- input := `{"name": "test"}`
346
- jsonpath := "$.nonexistent"
347
-
348
- _, err := jsonPathMulti(Value{Data: input, Type: ValueTypeStr}, jsonpath)
349
- if err == nil {
350
- t.Fatal("Expected error for no matches, got nil")
351
- }
352
-}
353
-
354
-// TestZabbixCompatibility_JSONPath tests old step type still works
355
-func TestZabbixCompatibility_JSONPath(t *testing.T) {
356
- input := `{"items": [1, 2, 3]}`
357
- jsonpath := "$.items"
358
-
359
- p := NewPreprocessor("test-shard")
360
- result, err := p.Execute("item1",
361
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
362
- Step{Type: StepTypeJSONPath, Params: jsonpath}) // OLD step type
363
-
364
- if err != nil {
365
- t.Fatalf("Execute() error: %v", err)
366
- }
367
-
368
- // OLD behavior: Should return SINGLE metric with JSON array string
369
- if len(result.Metrics) != 1 {
370
- t.Fatalf("Expected 1 metric (Zabbix compatibility), got %d", len(result.Metrics))
371
- }
372
-
373
- // Value should be a JSON array
374
- value := result.Metrics[0].Value
375
- if value[0] != '[' {
376
- t.Errorf("Expected JSON array, got: %s", value)
377
- }
378
-}
379
-
380
-// ============================================================================
381
-// SNMP Walk to JSON Multi-Metric Tests
382
-// ============================================================================
383
-
384
-// TestSNMPWalkMulti_BasicDiscovery tests basic SNMP discovery with multiple items
385
-func TestSNMPWalkMulti_BasicDiscovery(t *testing.T) {
386
- params := `{#IFNAME}
387
-.1.3.6.1.2.1.31.1.1.1.1
388
-0
389
-{#IFDESCR}
390
-.1.3.6.1.2.1.2.2.1.2
391
-0`
392
-
393
- input := `.1.3.6.1.2.1.31.1.1.1.1.1 = STRING: "eth0"
394
-.1.3.6.1.2.1.31.1.1.1.1.2 = STRING: "eth1"
395
-.1.3.6.1.2.1.2.2.1.2.1 = STRING: "Ethernet Interface 0"
396
-.1.3.6.1.2.1.2.2.1.2.2 = STRING: "Ethernet Interface 1"
397
-`
398
-
399
- result, err := snmpWalkToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params, NoopLogger{})
400
- if err != nil {
401
- t.Fatalf("snmpWalkToJSONMulti() error: %v", err)
402
- }
403
-
404
- // Should return 2 metrics (one per index: 2, 1 in descending order)
405
- if len(result.Metrics) != 2 {
406
- t.Fatalf("Expected 2 metrics, got %d", len(result.Metrics))
407
- }
408
-
409
- // Verify first metric (index 2 - descending order)
410
- m0 := result.Metrics[0]
411
- if m0.Name != "snmp_discovery" {
412
- t.Errorf("Metric 0: expected name 'snmp_discovery', got %q", m0.Name)
413
- }
414
- if m0.Labels["index"] != "2" {
415
- t.Errorf("Metric 0: expected index label '2', got %q", m0.Labels["index"])
416
- }
417
- if !contains(m0.Value, `"{#SNMPINDEX}":"2"`) {
418
- t.Errorf("Metric 0: expected SNMPINDEX=2, got %q", m0.Value)
419
- }
420
- if !contains(m0.Value, `"{#IFNAME}":"eth1"`) {
421
- t.Errorf("Metric 0: expected IFNAME=eth1, got %q", m0.Value)
422
- }
423
- if !contains(m0.Value, `"{#IFDESCR}":"Ethernet Interface 1"`) {
424
- t.Errorf("Metric 0: expected IFDESCR, got %q", m0.Value)
425
- }
426
-
427
- // Verify second metric (index 1)
428
- m1 := result.Metrics[1]
429
- if m1.Labels["index"] != "1" {
430
- t.Errorf("Metric 1: expected index label '1', got %q", m1.Labels["index"])
431
- }
432
- if !contains(m1.Value, `"{#IFNAME}":"eth0"`) {
433
- t.Errorf("Metric 1: expected IFNAME=eth0, got %q", m1.Value)
434
- }
435
-}
436
-
437
-// TestSNMPWalkMulti_EmptyInput tests empty input handling
438
-func TestSNMPWalkMulti_EmptyInput(t *testing.T) {
439
- params := `{#IFNAME}
440
-.1.3.6.1.2.1.31.1.1.1.1
441
-0`
442
-
443
- result, err := snmpWalkToJSONMulti(Value{Data: "", Type: ValueTypeStr}, params, NoopLogger{})
444
- if err != nil {
445
- t.Fatalf("snmpWalkToJSONMulti() error on empty input: %v", err)
446
- }
447
-
448
- // Empty input should return zero metrics
449
- if len(result.Metrics) != 0 {
450
- t.Errorf("Expected 0 metrics for empty input, got %d", len(result.Metrics))
451
- }
452
-}
453
-
454
-// TestSNMPWalkMulti_NoMacros tests behavior with no macros defined
455
-func TestSNMPWalkMulti_NoMacros(t *testing.T) {
456
- params := "" // No macros
457
-
458
- input := `.1.3.6.1.2.1.31.1.1.1.1.1 = STRING: "eth0"`
459
-
460
- result, err := snmpWalkToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params, NoopLogger{})
461
- if err != nil {
462
- t.Fatalf("snmpWalkToJSONMulti() error: %v", err)
463
- }
464
-
465
- // No macros should return empty metrics array
466
- if len(result.Metrics) != 0 {
467
- t.Errorf("Expected 0 metrics with no macros, got %d", len(result.Metrics))
468
- }
469
-}
470
-
471
-// TestSNMPWalkMulti_SingleItem tests single discovery item
472
-func TestSNMPWalkMulti_SingleItem(t *testing.T) {
473
- params := `{#IFNAME}
474
-.1.3.6.1.2.1.31.1.1.1.1
475
-0`
476
-
477
- input := `.1.3.6.1.2.1.31.1.1.1.1.1 = STRING: "lo"`
478
-
479
- result, err := snmpWalkToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params, NoopLogger{})
480
- if err != nil {
481
- t.Fatalf("snmpWalkToJSONMulti() error: %v", err)
482
- }
483
-
484
- if len(result.Metrics) != 1 {
485
- t.Fatalf("Expected 1 metric, got %d", len(result.Metrics))
486
- }
487
-
488
- m := result.Metrics[0]
489
- if m.Name != "snmp_discovery" {
490
- t.Errorf("Expected name 'snmp_discovery', got %q", m.Name)
491
- }
492
- if m.Labels["index"] != "1" {
493
- t.Errorf("Expected index label '1', got %q", m.Labels["index"])
494
- }
495
- if !contains(m.Value, `"{#IFNAME}":"lo"`) {
496
- t.Errorf("Expected IFNAME=lo, got %q", m.Value)
497
- }
498
-}
499
-
500
-// TestSNMPWalkMulti_ViaPreprocessor tests full integration
501
-func TestSNMPWalkMulti_ViaPreprocessor(t *testing.T) {
502
- params := `{#IFNAME}
503
-.1.3.6.1.2.1.31.1.1.1.1
504
-0
505
-{#IFTYPE}
506
-.1.3.6.1.2.1.2.2.1.3
507
-0`
508
-
509
- input := `.1.3.6.1.2.1.31.1.1.1.1.1 = STRING: "eth0"
510
-.1.3.6.1.2.1.31.1.1.1.1.2 = STRING: "wlan0"
511
-.1.3.6.1.2.1.2.2.1.3.1 = INTEGER: 6
512
-.1.3.6.1.2.1.2.2.1.3.2 = INTEGER: 71
513
-`
514
-
515
- p := NewPreprocessor("test-shard")
516
- result, err := p.Execute("item1",
517
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
518
- Step{Type: StepTypeSNMPWalkToJSONMulti, Params: params})
519
-
520
- if err != nil {
521
- t.Fatalf("Execute() error: %v", err)
522
- }
523
-
524
- // Should return 2 metrics (indices 2, 1 in descending order)
525
- if len(result.Metrics) != 2 {
526
- t.Fatalf("Expected 2 metrics, got %d", len(result.Metrics))
527
- }
528
-
529
- // Verify metric structure and labels
530
- for i, m := range result.Metrics {
531
- if m.Name != "snmp_discovery" {
532
- t.Errorf("Metric %d: expected name 'snmp_discovery', got %q", i, m.Name)
533
- }
534
- if m.Type != ValueTypeStr {
535
- t.Errorf("Metric %d: expected type ValueTypeStr, got %v", i, m.Type)
536
- }
537
- if m.Labels["index"] == "" {
538
- t.Errorf("Metric %d: missing index label", i)
539
- }
540
- // Verify JSON structure
541
- if !contains(m.Value, `"{#SNMPINDEX}"`) {
542
- t.Errorf("Metric %d: missing SNMPINDEX field", i)
543
- }
544
- if !contains(m.Value, `"{#IFNAME}"`) {
545
- t.Errorf("Metric %d: missing IFNAME field", i)
546
- }
547
- if !contains(m.Value, `"{#IFTYPE}"`) {
548
- t.Errorf("Metric %d: missing IFTYPE field", i)
549
- }
550
- }
551
-}
552
-
553
-// TestSNMPWalkMulti_InvalidData tests error handling
554
-func TestSNMPWalkMulti_InvalidData(t *testing.T) {
555
- params := `{#IFNAME}
556
-.1.3.6.1.2.1.31.1.1.1.1
557
-0`
558
-
559
- input := `not valid SNMP walk data`
560
-
561
- _, err := snmpWalkToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params, NoopLogger{})
562
- if err == nil {
563
- t.Fatal("Expected error for invalid SNMP data, got nil")
564
- }
565
-}
566
-
567
-// TestZabbixCompatibility_SNMPWalkToJSON tests that old step type still works
568
-func TestZabbixCompatibility_SNMPWalkToJSON(t *testing.T) {
569
- params := `{#IFNAME}
570
-.1.3.6.1.2.1.31.1.1.1.1
571
-0`
572
-
573
- input := `.1.3.6.1.2.1.31.1.1.1.1.1 = STRING: "eth0"
574
-.1.3.6.1.2.1.31.1.1.1.1.2 = STRING: "eth1"
575
-`
576
-
577
- p := NewPreprocessor("test-shard")
578
- result, err := p.Execute("item1",
579
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
580
- Step{Type: StepTypeSNMPWalkToJSON, Params: params}) // OLD step type
581
-
582
- if err != nil {
583
- t.Fatalf("Execute() error: %v", err)
584
- }
585
-
586
- // OLD behavior: Should return SINGLE metric with JSON array string
587
- if len(result.Metrics) != 1 {
588
- t.Fatalf("Expected 1 metric (Zabbix compatibility), got %d", len(result.Metrics))
589
- }
590
-
591
- // Value should be a JSON array string
592
- value := result.Metrics[0].Value
593
- if value[0] != '[' || value[len(value)-1] != ']' {
594
- t.Errorf("Expected JSON array string, got: %s", value)
595
- }
596
-
597
- // Verify it contains both discovery items in the JSON array
598
- if !contains(value, `"{#SNMPINDEX}":"2"`) {
599
- t.Errorf("JSON array missing index 2")
600
- }
601
- if !contains(value, `"{#SNMPINDEX}":"1"`) {
602
- t.Errorf("JSON array missing index 1")
603
- }
604
- if !contains(value, `"{#IFNAME}":"eth0"`) {
605
- t.Errorf("JSON array missing eth0")
606
- }
607
- if !contains(value, `"{#IFNAME}":"eth1"`) {
608
- t.Errorf("JSON array missing eth1")
609
- }
610
-}
611
-
612
-// ============================================================================
613
-// CSV to JSON Multi-Metric Tests
614
-// ============================================================================
615
-
616
-// TestCSVMulti_WithHeader tests CSV with header row
617
-func TestCSVMulti_WithHeader(t *testing.T) {
618
- params := ",\n\"\n1" // comma delimiter, double quote, has header
619
-
620
- input := `name,age,city
621
-Alice,30,NYC
622
-Bob,25,LA
623
-`
624
-
625
- result, err := csvToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params)
626
- if err != nil {
627
- t.Fatalf("csvToJSONMulti() error: %v", err)
628
- }
629
-
630
- // Should return 3 rows (2 data + 1 empty from trailing newline)
631
- if len(result.Metrics) != 3 {
632
- t.Fatalf("Expected 3 metrics, got %d", len(result.Metrics))
633
- }
634
-
635
- // Verify first row
636
- m0 := result.Metrics[0]
637
- if m0.Name != "csv_row" {
638
- t.Errorf("Metric 0: expected name 'csv_row', got %q", m0.Name)
639
- }
640
- if m0.Labels["row"] != "1" {
641
- t.Errorf("Metric 0: expected row label '1', got %q", m0.Labels["row"])
642
- }
643
- if !contains(m0.Value, `"name":"Alice"`) {
644
- t.Errorf("Metric 0: expected name=Alice, got %q", m0.Value)
645
- }
646
- if !contains(m0.Value, `"age":"30"`) {
647
- t.Errorf("Metric 0: expected age=30, got %q", m0.Value)
648
- }
649
- if !contains(m0.Value, `"city":"NYC"`) {
650
- t.Errorf("Metric 0: expected city=NYC, got %q", m0.Value)
651
- }
652
-
653
- // Verify second row
654
- m1 := result.Metrics[1]
655
- if m1.Labels["row"] != "2" {
656
- t.Errorf("Metric 1: expected row label '2', got %q", m1.Labels["row"])
657
- }
658
- if !contains(m1.Value, `"name":"Bob"`) {
659
- t.Errorf("Metric 1: expected name=Bob, got %q", m1.Value)
660
- }
661
-
662
- // Verify third row (empty trailing row from newline)
663
- m2 := result.Metrics[2]
664
- if m2.Labels["row"] != "3" {
665
- t.Errorf("Metric 2: expected row label '3', got %q", m2.Labels["row"])
666
- }
667
- // Empty row should have all empty fields
668
- if !contains(m2.Value, `"name":""`) {
669
- t.Errorf("Metric 2: expected empty name field, got %q", m2.Value)
670
- }
671
-}
672
-
673
-// TestCSVMulti_NoHeader tests CSV without header row
674
-func TestCSVMulti_NoHeader(t *testing.T) {
675
- params := ",\n\"\n0" // comma delimiter, double quote, no header
676
-
677
- input := `10,20,30
678
-40,50,60`
679
-
680
- result, err := csvToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params)
681
- if err != nil {
682
- t.Fatalf("csvToJSONMulti() error: %v", err)
683
- }
684
-
685
- // Should return 2 rows
686
- if len(result.Metrics) != 2 {
687
- t.Fatalf("Expected 2 metrics, got %d", len(result.Metrics))
688
- }
689
-
690
- // Verify first row (1-based column indices)
691
- m0 := result.Metrics[0]
692
- if m0.Labels["row"] != "1" {
693
- t.Errorf("Metric 0: expected row label '1', got %q", m0.Labels["row"])
694
- }
695
- if !contains(m0.Value, `"1":"10"`) {
696
- t.Errorf("Metric 0: expected column 1=10, got %q", m0.Value)
697
- }
698
- if !contains(m0.Value, `"2":"20"`) {
699
- t.Errorf("Metric 0: expected column 2=20, got %q", m0.Value)
700
- }
701
- if !contains(m0.Value, `"3":"30"`) {
702
- t.Errorf("Metric 0: expected column 3=30, got %q", m0.Value)
703
- }
704
-
705
- // Verify second row
706
- m1 := result.Metrics[1]
707
- if m1.Labels["row"] != "2" {
708
- t.Errorf("Metric 1: expected row label '2', got %q", m1.Labels["row"])
709
- }
710
- if !contains(m1.Value, `"1":"40"`) {
711
- t.Errorf("Metric 1: expected column 1=40, got %q", m1.Value)
712
- }
713
-}
714
-
715
-// TestCSVMulti_EmptyInput tests empty input handling
716
-func TestCSVMulti_EmptyInput(t *testing.T) {
717
- params := ",\n\"\n1"
718
-
719
- result, err := csvToJSONMulti(Value{Data: "", Type: ValueTypeStr}, params)
720
- if err != nil {
721
- t.Fatalf("csvToJSONMulti() error on empty input: %v", err)
722
- }
723
-
724
- // Empty input should return zero metrics
725
- if len(result.Metrics) != 0 {
726
- t.Errorf("Expected 0 metrics for empty input, got %d", len(result.Metrics))
727
- }
728
-}
729
-
730
-// TestCSVMulti_SingleRow tests single data row
731
-func TestCSVMulti_SingleRow(t *testing.T) {
732
- params := ",\n\"\n1"
733
-
734
- input := `name,value
735
-test,42`
736
-
737
- result, err := csvToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params)
738
- if err != nil {
739
- t.Fatalf("csvToJSONMulti() error: %v", err)
740
- }
741
-
742
- if len(result.Metrics) != 1 {
743
- t.Fatalf("Expected 1 metric, got %d", len(result.Metrics))
744
- }
745
-
746
- m := result.Metrics[0]
747
- if m.Name != "csv_row" {
748
- t.Errorf("Expected name 'csv_row', got %q", m.Name)
749
- }
750
- if m.Labels["row"] != "1" {
751
- t.Errorf("Expected row label '1', got %q", m.Labels["row"])
752
- }
753
- if !contains(m.Value, `"name":"test"`) {
754
- t.Errorf("Expected name=test, got %q", m.Value)
755
- }
756
- if !contains(m.Value, `"value":"42"`) {
757
- t.Errorf("Expected value=42, got %q", m.Value)
758
- }
759
-}
760
-
761
-// TestCSVMulti_ViaPreprocessor tests full integration
762
-func TestCSVMulti_ViaPreprocessor(t *testing.T) {
763
- params := ",\n\"\n1"
764
-
765
- input := `id,status
766
-1,active
767
-2,inactive
768
-3,pending`
769
-
770
- p := NewPreprocessor("test-shard")
771
- result, err := p.Execute("item1",
772
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
773
- Step{Type: StepTypeCSVToJSONMulti, Params: params})
774
-
775
- if err != nil {
776
- t.Fatalf("Execute() error: %v", err)
777
- }
778
-
779
- // Should return 3 metrics (one per data row)
780
- if len(result.Metrics) != 3 {
781
- t.Fatalf("Expected 3 metrics, got %d", len(result.Metrics))
782
- }
783
-
784
- // Verify all metrics have correct structure
785
- for i, m := range result.Metrics {
786
- if m.Name != "csv_row" {
787
- t.Errorf("Metric %d: expected name 'csv_row', got %q", i, m.Name)
788
- }
789
- if m.Type != ValueTypeStr {
790
- t.Errorf("Metric %d: expected type ValueTypeStr, got %v", i, m.Type)
791
- }
792
- if m.Labels["row"] == "" {
793
- t.Errorf("Metric %d: missing row label", i)
794
- }
795
- // Verify JSON structure
796
- if !contains(m.Value, `"id"`) {
797
- t.Errorf("Metric %d: missing id field", i)
798
- }
799
- if !contains(m.Value, `"status"`) {
800
- t.Errorf("Metric %d: missing status field", i)
801
- }
802
- }
803
-}
804
-
805
-// TestCSVMulti_InvalidParams tests error handling
806
-func TestCSVMulti_InvalidParams(t *testing.T) {
807
- params := "," // Missing required params
808
-
809
- input := `a,b,c`
810
-
811
- _, err := csvToJSONMulti(Value{Data: input, Type: ValueTypeStr}, params)
812
- if err == nil {
813
- t.Fatal("Expected error for invalid params, got nil")
814
- }
815
-}
816
-
817
-// TestZabbixCompatibility_CSVToJSON tests that old step type still works
818
-func TestZabbixCompatibility_CSVToJSON(t *testing.T) {
819
- params := ",\n\"\n1"
820
-
821
- input := `name,value
822
-foo,10
823
-bar,20`
824
-
825
- p := NewPreprocessor("test-shard")
826
- result, err := p.Execute("item1",
827
- Value{Data: input, Type: ValueTypeStr, Timestamp: time.Now()},
828
- Step{Type: StepTypeCSVToJSON, Params: params}) // OLD step type
829
-
830
- if err != nil {
831
- t.Fatalf("Execute() error: %v", err)
832
- }
833
-
834
- // OLD behavior: Should return SINGLE metric with JSON array string
835
- if len(result.Metrics) != 1 {
836
- t.Fatalf("Expected 1 metric (Zabbix compatibility), got %d", len(result.Metrics))
837
- }
838
-
839
- // Value should be a JSON array string
840
- value := result.Metrics[0].Value
841
- if value[0] != '[' || value[len(value)-1] != ']' {
842
- t.Errorf("Expected JSON array string, got: %s", value)
843
- }
844
-
845
- // Verify it contains both rows in the JSON array
846
- if !contains(value, `"name":"foo"`) {
847
- t.Errorf("JSON array missing foo row")
848
- }
849
- if !contains(value, `"name":"bar"`) {
850
- t.Errorf("JSON array missing bar row")
851
- }
852
- if !contains(value, `"value":"10"`) {
853
- t.Errorf("JSON array missing value 10")
854
- }
855
- if !contains(value, `"value":"20"`) {
856
- t.Errorf("JSON array missing value 20")
857
- }
858
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/pipeline_test.go
deleted
-467
@@ -1,467 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// TestPipeline_BasicChaining tests that values flow correctly between steps
10
-func TestPipeline_BasicChaining(t *testing.T) {
11
- p := NewPreprocessor("test-shard")
12
-
13
- tests := []struct {
14
- name string
15
- input string
16
- steps []Step
17
- expected string
18
- }{
19
- {
20
- name: "JSONPath → Multiplier → Range",
21
- input: `{"cpu": 0.45}`,
22
- steps: []Step{
23
- {Type: StepTypeJSONPath, Params: "$.cpu"}, // Extract 0.45
24
- {Type: StepTypeMultiplier, Params: "100"}, // Multiply by 100 → 45
25
- {Type: StepTypeValidateRange, Params: "0\n100"}, // Validate 0-100
26
- },
27
- expected: "45",
28
- },
29
- {
30
- name: "Trim → Regex → Multiplier",
31
- input: " Temperature: 25.5 C ",
32
- steps: []Step{
33
- {Type: StepTypeTrim, Params: " "}, // Remove spaces
34
- {Type: StepTypeRegexSubstitution, Params: "Temperature:\\s+([0-9.]+).*\n\\1"}, // Extract number
35
- {Type: StepTypeMultiplier, Params: "1.8"}, // Celsius to Fahrenheit delta
36
- },
37
- expected: "45.9",
38
- },
39
- {
40
- name: "XPath → Trim → Hex2Dec",
41
- input: `<data><value> 0xFF </value></data>`,
42
- steps: []Step{
43
- {Type: StepTypeXPath, Params: "//value/text()"}, // Extract " 0xFF "
44
- {Type: StepTypeTrim, Params: " "}, // Remove spaces → "0xFF"
45
- {Type: StepTypeHex2Dec}, // Convert to decimal
46
- },
47
- expected: "255",
48
- },
49
- {
50
- name: "Prometheus → Multiplier → Validate",
51
- input: "http_requests{method=\"GET\"} 100\nhttp_requests{method=\"POST\"} 50",
52
- steps: []Step{
53
- {Type: StepTypePrometheusPattern, Params: `http_requests{method="GET"}`}, // Extract 100
54
- {Type: StepTypeMultiplier, Params: "0.01"}, // Convert to percentage
55
- {Type: StepTypeValidateRange, Params: "0\n10"}, // Must be 0-10%
56
- },
57
- expected: "1",
58
- },
59
- {
60
- name: "String Replace → Trim → Bool2Dec",
61
- input: "Status: TRUE ",
62
- steps: []Step{
63
- {Type: StepTypeStringReplace, Params: "Status: \n"}, // Remove prefix
64
- {Type: StepTypeTrim, Params: " "}, // Remove spaces
65
- {Type: StepTypeBool2Dec}, // TRUE → 1
66
- },
67
- expected: "1",
68
- },
69
- }
70
-
71
- for _, tt := range tests {
72
- t.Run(tt.name, func(t *testing.T) {
73
- value := Value{Data: tt.input, Type: ValueTypeStr}
74
- result, err := p.ExecutePipeline("test-item", value, tt.steps)
75
-
76
- if err != nil {
77
- t.Errorf("Pipeline failed: %v", err)
78
- return
79
- }
80
-
81
- if len(result.Metrics) == 0 {
82
- t.Error("Pipeline produced no metrics")
83
- return
84
- }
85
-
86
- got := result.Metrics[0].Value
87
- if got != tt.expected {
88
- t.Errorf("Expected %q, got %q", tt.expected, got)
89
- }
90
- })
91
- }
92
-}
93
-
94
-// TestPipeline_ErrorPropagation tests that errors stop pipeline execution
95
-func TestPipeline_ErrorPropagation(t *testing.T) {
96
- p := NewPreprocessor("test-shard")
97
-
98
- tests := []struct {
99
- name string
100
- input string
101
- steps []Step
102
- expectError bool
103
- errorMatch string
104
- }{
105
- {
106
- name: "Invalid JSONPath stops pipeline",
107
- input: `{"value": 100}`,
108
- steps: []Step{
109
- {Type: StepTypeJSONPath, Params: "$.nonexistent"}, // Error: path not found
110
- {Type: StepTypeMultiplier, Params: "2"}, // Should NOT execute
111
- },
112
- expectError: true,
113
- errorMatch: "step 0",
114
- },
115
- {
116
- name: "Invalid multiplier stops pipeline",
117
- input: "abc",
118
- steps: []Step{
119
- {Type: StepTypeMultiplier, Params: "10"}, // Error: not a number
120
- {Type: StepTypeValidateRange, Params: "0\n100"}, // Should NOT execute
121
- },
122
- expectError: true,
123
- errorMatch: "step 0",
124
- },
125
- {
126
- name: "Failed validation stops pipeline",
127
- input: "150",
128
- steps: []Step{
129
- {Type: StepTypeValidateRange, Params: "0\n100"}, // Error: out of range
130
- {Type: StepTypeMultiplier, Params: "2"}, // Should NOT execute
131
- },
132
- expectError: true,
133
- errorMatch: "step 0",
134
- },
135
- {
136
- name: "Invalid regex stops pipeline",
137
- input: "test",
138
- steps: []Step{
139
- {Type: StepTypeRegexSubstitution, Params: "([0-9]+\n\\1"}, // Error: unclosed group
140
- {Type: StepTypeTrim, Params: " "}, // Should NOT execute
141
- },
142
- expectError: true,
143
- errorMatch: "step 0",
144
- },
145
- {
146
- name: "Error in middle step stops pipeline",
147
- input: `{"value": "abc"}`,
148
- steps: []Step{
149
- {Type: StepTypeJSONPath, Params: "$.value"}, // Extract "abc"
150
- {Type: StepTypeMultiplier, Params: "10"}, // Error: not a number
151
- {Type: StepTypeTrim, Params: " "}, // Should NOT execute
152
- },
153
- expectError: true,
154
- errorMatch: "step 1",
155
- },
156
- }
157
-
158
- for _, tt := range tests {
159
- t.Run(tt.name, func(t *testing.T) {
160
- value := Value{Data: tt.input, Type: ValueTypeStr}
161
- result, err := p.ExecutePipeline("test-item", value, tt.steps)
162
-
163
- if tt.expectError {
164
- if err == nil {
165
- t.Error("Expected error, got nil")
166
- return
167
- }
168
- if !strings.Contains(err.Error(), tt.errorMatch) {
169
- t.Errorf("Expected error containing %q, got: %v", tt.errorMatch, err)
170
- }
171
- if result.Error == nil {
172
- t.Error("Expected Result.Error to be set")
173
- }
174
- } else {
175
- if err != nil {
176
- t.Errorf("Unexpected error: %v", err)
177
- }
178
- }
179
- })
180
- }
181
-}
182
-
183
-// TestPipeline_ErrorHandlers tests error handler behavior in pipelines
184
-func TestPipeline_ErrorHandlers(t *testing.T) {
185
- p := NewPreprocessor("test-shard")
186
-
187
- tests := []struct {
188
- name string
189
- input string
190
- steps []Step
191
- expected string
192
- }{
193
- {
194
- name: "DISCARD error allows pipeline to continue with empty value",
195
- input: `{"value": 100}`,
196
- steps: []Step{
197
- {
198
- Type: StepTypeJSONPath,
199
- Params: "$.nonexistent",
200
- ErrorHandler: ErrorHandler{
201
- Action: ErrorActionDiscard, // Return empty string on error
202
- },
203
- },
204
- {Type: StepTypeTrim, Params: " "}, // Processes empty string → empty string
205
- },
206
- expected: "",
207
- },
208
- {
209
- name: "SET_VALUE error allows pipeline to continue with fallback",
210
- input: "not-a-number",
211
- steps: []Step{
212
- {
213
- Type: StepTypeMultiplier,
214
- Params: "10",
215
- ErrorHandler: ErrorHandler{
216
- Action: ErrorActionSetValue,
217
- Params: "0", // Fallback value
218
- },
219
- },
220
- {Type: StepTypeMultiplier, Params: "2"}, // Processes "0" → "0"
221
- },
222
- expected: "0",
223
- },
224
- {
225
- name: "Multiple error handlers in sequence",
226
- input: `{"data": "invalid"}`,
227
- steps: []Step{
228
- {
229
- Type: StepTypeJSONPath,
230
- Params: "$.missing",
231
- ErrorHandler: ErrorHandler{
232
- Action: ErrorActionSetValue,
233
- Params: "100",
234
- },
235
- },
236
- {
237
- Type: StepTypeMultiplier,
238
- Params: "abc", // Invalid multiplier
239
- ErrorHandler: ErrorHandler{
240
- Action: ErrorActionSetValue,
241
- Params: "50",
242
- },
243
- },
244
- {Type: StepTypeMultiplier, Params: "2"}, // Processes "50" → "100"
245
- },
246
- expected: "100",
247
- },
248
- {
249
- name: "Error handler only catches specific step errors",
250
- input: "150",
251
- steps: []Step{
252
- {
253
- Type: StepTypeValidateRange,
254
- Params: "0\n100",
255
- ErrorHandler: ErrorHandler{
256
- Action: ErrorActionSetValue,
257
- Params: "100", // Clamp to max
258
- },
259
- },
260
- {Type: StepTypeMultiplier, Params: "0.5"}, // Processes "100" → "50"
261
- },
262
- expected: "50",
263
- },
264
- }
265
-
266
- for _, tt := range tests {
267
- t.Run(tt.name, func(t *testing.T) {
268
- value := Value{Data: tt.input, Type: ValueTypeStr}
269
- result, err := p.ExecutePipeline("test-item", value, tt.steps)
270
-
271
- if err != nil {
272
- t.Errorf("Pipeline failed: %v", err)
273
- return
274
- }
275
-
276
- if len(result.Metrics) == 0 {
277
- t.Error("Pipeline produced no metrics")
278
- return
279
- }
280
-
281
- got := result.Metrics[0].Value
282
- if got != tt.expected {
283
- t.Errorf("Expected %q, got %q", tt.expected, got)
284
- }
285
- })
286
- }
287
-}
288
-
289
-// TestPipeline_ErrorHandlerSetError tests that SET_ERROR stops pipeline
290
-func TestPipeline_ErrorHandlerSetError(t *testing.T) {
291
- p := NewPreprocessor("test-shard")
292
-
293
- steps := []Step{
294
- {
295
- Type: StepTypeMultiplier,
296
- Params: "abc", // Invalid
297
- ErrorHandler: ErrorHandler{
298
- Action: ErrorActionSetError,
299
- Params: "Custom error message",
300
- },
301
- },
302
- {Type: StepTypeMultiplier, Params: "2"}, // Should NOT execute
303
- }
304
-
305
- value := Value{Data: "100", Type: ValueTypeStr}
306
- result, err := p.ExecutePipeline("test-item", value, steps)
307
-
308
- if err == nil {
309
- t.Error("Expected error, got nil")
310
- return
311
- }
312
-
313
- if !strings.Contains(err.Error(), "Custom error message") {
314
- t.Errorf("Expected custom error message, got: %v", err)
315
- }
316
-
317
- if result.Error == nil {
318
- t.Error("Expected Result.Error to be set")
319
- }
320
-}
321
-
322
-// TestPipeline_StatefulOperations tests that stateful steps work in pipelines
323
-func TestPipeline_StatefulOperations(t *testing.T) {
324
- p := NewPreprocessor("test-shard")
325
-
326
- steps := []Step{
327
- {Type: StepTypeJSONPath, Params: "$.counter"},
328
- {Type: StepTypeDeltaValue},
329
- {Type: StepTypeMultiplier, Params: "10"}, // Scale delta
330
- }
331
-
332
- // First call: baseline (delta = 0)
333
- value1 := Value{
334
- Data: `{"counter": 100}`,
335
- Type: ValueTypeStr,
336
- Timestamp: time.Now(),
337
- }
338
- result1, err := p.ExecutePipeline("item1", value1, steps)
339
- if err != nil {
340
- t.Fatalf("First call failed: %v", err)
341
- }
342
- if result1.Metrics[0].Value != "0" {
343
- t.Errorf("First delta should be 0, got %s", result1.Metrics[0].Value)
344
- }
345
-
346
- // Second call: delta = 50, scaled = 500
347
- value2 := Value{
348
- Data: `{"counter": 150}`,
349
- Type: ValueTypeStr,
350
- Timestamp: time.Now(),
351
- }
352
- result2, err := p.ExecutePipeline("item1", value2, steps)
353
- if err != nil {
354
- t.Fatalf("Second call failed: %v", err)
355
- }
356
- if result2.Metrics[0].Value != "500" {
357
- t.Errorf("Expected 500, got %s", result2.Metrics[0].Value)
358
- }
359
-}
360
-
361
-// TestPipeline_EmptyPipeline tests edge case of zero steps
362
-func TestPipeline_EmptyPipeline(t *testing.T) {
363
- p := NewPreprocessor("test-shard")
364
-
365
- value := Value{Data: "test", Type: ValueTypeStr}
366
- result, err := p.ExecutePipeline("test-item", value, []Step{})
367
-
368
- if err != nil {
369
- t.Errorf("Empty pipeline should succeed, got error: %v", err)
370
- }
371
-
372
- if len(result.Metrics) == 0 {
373
- t.Error("Empty pipeline should return input as metric")
374
- return
375
- }
376
-
377
- if result.Metrics[0].Value != "test" {
378
- t.Errorf("Expected input unchanged, got %q", result.Metrics[0].Value)
379
- }
380
-}
381
-
382
-// TestPipeline_SingleStep tests edge case of single step
383
-func TestPipeline_SingleStep(t *testing.T) {
384
- p := NewPreprocessor("test-shard")
385
-
386
- steps := []Step{
387
- {Type: StepTypeMultiplier, Params: "10"},
388
- }
389
-
390
- value := Value{Data: "5", Type: ValueTypeFloat}
391
- result, err := p.ExecutePipeline("test-item", value, steps)
392
-
393
- if err != nil {
394
- t.Errorf("Single step pipeline failed: %v", err)
395
- }
396
-
397
- if len(result.Metrics) == 0 {
398
- t.Error("Pipeline produced no metrics")
399
- return
400
- }
401
-
402
- if result.Metrics[0].Value != "50" {
403
- t.Errorf("Expected 50, got %s", result.Metrics[0].Value)
404
- }
405
-}
406
-
407
-// TestPipeline_ValueTypePreservation tests that type flows through pipeline
408
-func TestPipeline_ValueTypePreservation(t *testing.T) {
409
- p := NewPreprocessor("test-shard")
410
-
411
- tests := []struct {
412
- name string
413
- inputType ValueType
414
- steps []Step
415
- expectedType ValueType
416
- }{
417
- {
418
- name: "Float through multiplier stays float",
419
- inputType: ValueTypeFloat,
420
- steps: []Step{
421
- {Type: StepTypeMultiplier, Params: "2"},
422
- },
423
- expectedType: ValueTypeFloat,
424
- },
425
- {
426
- name: "String through trim stays string",
427
- inputType: ValueTypeStr,
428
- steps: []Step{
429
- {Type: StepTypeTrim, Params: " "},
430
- },
431
- expectedType: ValueTypeStr,
432
- },
433
- {
434
- name: "String through regex substitution stays string",
435
- inputType: ValueTypeStr,
436
- steps: []Step{
437
- {Type: StepTypeRegexSubstitution, Params: "([0-9]+)\n\\1"},
438
- },
439
- expectedType: ValueTypeStr,
440
- },
441
- }
442
-
443
- for _, tt := range tests {
444
- t.Run(tt.name, func(t *testing.T) {
445
- value := Value{
446
- Data: "100",
447
- Type: tt.inputType,
448
- Timestamp: time.Now(),
449
- }
450
-
451
- result, err := p.ExecutePipeline("test-item", value, tt.steps)
452
- if err != nil {
453
- t.Errorf("Pipeline failed: %v", err)
454
- return
455
- }
456
-
457
- if len(result.Metrics) == 0 {
458
- t.Error("Pipeline produced no metrics")
459
- return
460
- }
461
-
462
- if result.Metrics[0].Type != tt.expectedType {
463
- t.Errorf("Expected type %v, got %v", tt.expectedType, result.Metrics[0].Type)
464
- }
465
- })
466
- }
467
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/preproc.go
deleted
-582
@@ -1,582 +0,0 @@
1
-// Package zabbixpreproc provides Zabbix preprocessing functionality in pure Go.
2
-package zabbixpreproc
3
-
4
-import (
5
- "fmt"
6
- "strings"
7
- "sync"
8
- "time"
9
-)
10
-
11
-// ValueType represents Zabbix item value types.
12
-type ValueType int
13
-
14
-const (
15
- ValueTypeStr ValueType = 0 // ITEM_VALUE_TYPE_STR
16
- ValueTypeUint64 ValueType = 3 // ITEM_VALUE_TYPE_UINT64
17
- ValueTypeFloat ValueType = 1 // ITEM_VALUE_TYPE_FLOAT
18
-)
19
-
20
-// Value represents a preprocessing input/output value.
21
-type Value struct {
22
- Data string // Raw string data
23
- Type ValueType // Value type
24
- Timestamp time.Time // Timestamp for stateful operations
25
- IsError bool // True if this value represents an error from a previous step
26
-}
27
-
28
-// StepType represents the type of preprocessing step.
29
-// Type IDs match Zabbix preprocessing types exactly (ZBX_PREPROC_* from zbxcommon.h)
30
-type StepType int
31
-
32
-const (
33
- // Zabbix preprocessing types (1-30) - MUST match zbxcommon.h exactly
34
- StepTypeMultiplier StepType = 1 // ZBX_PREPROC_MULTIPLIER
35
- StepTypeRTrim StepType = 2 // ZBX_PREPROC_RTRIM
36
- StepTypeLTrim StepType = 3 // ZBX_PREPROC_LTRIM
37
- StepTypeTrim StepType = 4 // ZBX_PREPROC_TRIM
38
- StepTypeRegexSubstitution StepType = 5 // ZBX_PREPROC_REGSUB
39
- StepTypeBool2Dec StepType = 6 // ZBX_PREPROC_BOOL2DEC
40
- StepTypeOct2Dec StepType = 7 // ZBX_PREPROC_OCT2DEC
41
- StepTypeHex2Dec StepType = 8 // ZBX_PREPROC_HEX2DEC
42
- StepTypeDeltaValue StepType = 9 // ZBX_PREPROC_DELTA_VALUE
43
- StepTypeDeltaSpeed StepType = 10 // ZBX_PREPROC_DELTA_SPEED
44
- StepTypeXPath StepType = 11 // ZBX_PREPROC_XPATH
45
- StepTypeJSONPath StepType = 12 // ZBX_PREPROC_JSONPATH
46
- StepTypeValidateRange StepType = 13 // ZBX_PREPROC_VALIDATE_RANGE
47
- StepTypeValidateRegex StepType = 14 // ZBX_PREPROC_VALIDATE_REGEX
48
- StepTypeValidateNotRegex StepType = 15 // ZBX_PREPROC_VALIDATE_NOT_REGEX
49
- StepTypeErrorFieldJSON StepType = 16 // ZBX_PREPROC_ERROR_FIELD_JSON
50
- StepTypeErrorFieldXML StepType = 17 // ZBX_PREPROC_ERROR_FIELD_XML
51
- StepTypeErrorFieldRegex StepType = 18 // ZBX_PREPROC_ERROR_FIELD_REGEX
52
- StepTypeThrottleValue StepType = 19 // ZBX_PREPROC_THROTTLE_VALUE
53
- StepTypeThrottleTimedValue StepType = 20 // ZBX_PREPROC_THROTTLE_TIMED_VALUE
54
- StepTypeJavaScript StepType = 21 // ZBX_PREPROC_SCRIPT
55
- StepTypePrometheusPattern StepType = 22 // ZBX_PREPROC_PROMETHEUS_PATTERN
56
- StepTypePrometheusToJSON StepType = 23 // ZBX_PREPROC_PROMETHEUS_TO_JSON
57
- StepTypeCSVToJSON StepType = 24 // ZBX_PREPROC_CSV_TO_JSON
58
- StepTypeStringReplace StepType = 25 // ZBX_PREPROC_STR_REPLACE
59
- StepTypeValidateNotSupported StepType = 26 // ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
60
- StepTypeXMLToJSON StepType = 27 // ZBX_PREPROC_XML_TO_JSON
61
- StepTypeSNMPWalkValue StepType = 28 // ZBX_PREPROC_SNMP_WALK_VALUE
62
- StepTypeSNMPWalkToJSON StepType = 29 // ZBX_PREPROC_SNMP_WALK_TO_JSON
63
- StepTypeSNMPGetValue StepType = 30 // ZBX_PREPROC_SNMP_GET_VALUE
64
-
65
- // Multi-metric step types (60+) - return multiple Metric objects instead of single JSON string
66
- // These are EXTENSIONS to Zabbix, not part of official spec
67
- StepTypePrometheusToJSONMulti StepType = 60 // Extended: Prometheus to multi-metric array
68
- StepTypeJSONPathMulti StepType = 61 // Extended: JSONPath array extraction
69
- StepTypeSNMPWalkToJSONMulti StepType = 62 // Extended: SNMP walk to multi-metric array
70
- StepTypeCSVToJSONMulti StepType = 63 // Extended: CSV to multi-metric array
71
-)
72
-
73
-// ErrorAction defines how preprocessing errors are handled.
74
-type ErrorAction int
75
-
76
-const (
77
- ErrorActionDefault ErrorAction = 0 // Return error
78
- ErrorActionDiscard ErrorAction = 1 // Discard value (return empty)
79
- ErrorActionSetValue ErrorAction = 2 // Set custom value
80
- ErrorActionSetError ErrorAction = 3 // Set custom error message
81
-)
82
-
83
-// ErrorHandler defines error handling behavior for a preprocessing step.
84
-type ErrorHandler struct {
85
- Action ErrorAction
86
- Params string
87
-}
88
-
89
-// Step represents a single preprocessing step.
90
-type Step struct {
91
- Type StepType
92
- Params string
93
- ErrorHandler ErrorHandler
94
-}
95
-
96
-// Metric represents a single metric output from preprocessing.
97
-type Metric struct {
98
- Name string // Metric name
99
- Value string // Metric value
100
- Type ValueType // Value type
101
- Labels map[string]string // Optional labels
102
-}
103
-
104
-// Result represents the result of preprocessing a value.
105
-type Result struct {
106
- Metrics []Metric // Array of extracted metrics
107
- Logs []string // Optional log entries (future use)
108
- Error error // Overall error if preprocessing failed
109
- Discarded bool // True if value was intentionally discarded (e.g., throttling)
110
-}
111
-
112
-// Preprocessor executes preprocessing steps.
113
-// It maintains state for stateful operations (per shard instance).
114
-// Thread-safe for concurrent use.
115
-type Preprocessor struct {
116
- shardID string
117
- logger Logger // Logger for debugging (defaults to NoopLogger)
118
- limits Limits // Configurable limits for preprocessing operations
119
- mu sync.RWMutex // Protects state map access
120
- state map[string]*OperationState // Per-operation state keyed by operation ID
121
- cleanupTicker *time.Ticker // Ticker for periodic state cleanup (can be nil if disabled)
122
- cleanupDone chan struct{} // Signal to stop cleanup goroutine
123
- stateTTL time.Duration // TTL for inactive state entries (0 = no cleanup)
124
-}
125
-
126
-// OperationState tracks state for stateful operations like Delta and Throttle.
127
-type OperationState struct {
128
- LastValue string
129
- LastTimestamp time.Time
130
- LastValueTime time.Time
131
- LastAccess time.Time // Track last access for TTL-based cleanup
132
-}
133
-
134
-// Config holds configuration options for a Preprocessor instance.
135
-// All fields are optional - zero values use sensible defaults.
136
-type Config struct {
137
- // Logger for debugging (default: NoopLogger - zero overhead)
138
- Logger Logger
139
-
140
- // Limits for preprocessing operations (default: Zabbix-compatible limits)
141
- Limits Limits
142
-
143
- // StateTTL is the time-to-live for inactive state entries.
144
- // Default: 0 (no automatic cleanup)
145
- // Set to positive duration to enable automatic eviction.
146
- StateTTL time.Duration
147
-
148
- // StateCleanupInterval is how often to run the cleanup process.
149
- // Default: 0 (no automatic cleanup)
150
- // Only used if StateTTL > 0.
151
- StateCleanupInterval time.Duration
152
-}
153
-
154
-// NewPreprocessor creates a new preprocessor instance for a specific shard.
155
-// By default, no state cleanup is performed (stateTTL = 0).
156
-// Call EnableStateCleanup() to enable TTL-based eviction.
157
-func NewPreprocessor(shardID string) *Preprocessor {
158
- return &Preprocessor{
159
- shardID: shardID,
160
- logger: NoopLogger{}, // Default: no logging overhead
161
- limits: DefaultLimits(),
162
- state: make(map[string]*OperationState),
163
- stateTTL: 0, // Default: no cleanup
164
- }
165
-}
166
-
167
-// NewPreprocessorWithConfig creates a new preprocessor with custom configuration.
168
-// This allows per-shard customization of logging, timeouts, and cleanup policies.
169
-func NewPreprocessorWithConfig(shardID string, cfg Config) *Preprocessor {
170
- // Use default limits if not provided (zero value check)
171
- limits := cfg.Limits
172
- if limits.JavaScript.Timeout == 0 {
173
- limits = DefaultLimits()
174
- }
175
-
176
- p := &Preprocessor{
177
- shardID: shardID,
178
- limits: limits,
179
- state: make(map[string]*OperationState),
180
- stateTTL: cfg.StateTTL,
181
- }
182
-
183
- // Set logger (default to NoopLogger if not provided)
184
- if cfg.Logger != nil {
185
- p.logger = cfg.Logger
186
- } else {
187
- p.logger = NoopLogger{}
188
- }
189
-
190
- // Enable automatic state cleanup if configured
191
- if cfg.StateTTL > 0 && cfg.StateCleanupInterval > 0 {
192
- p.EnableStateCleanup(cfg.StateTTL, cfg.StateCleanupInterval)
193
- }
194
-
195
- return p
196
-}
197
-
198
-// EnableStateCleanup enables periodic cleanup of inactive state entries.
199
-// ttl: How long an entry can be inactive before being removed
200
-// cleanupInterval: How often to run the cleanup process
201
-// Returns a function to stop the cleanup goroutine (call on shutdown).
202
-func (p *Preprocessor) EnableStateCleanup(ttl, cleanupInterval time.Duration) func() {
203
- p.mu.Lock()
204
- defer p.mu.Unlock()
205
-
206
- // Stop existing cleanup if any
207
- if p.cleanupTicker != nil {
208
- p.cleanupTicker.Stop()
209
- if p.cleanupDone != nil {
210
- close(p.cleanupDone)
211
- }
212
- }
213
-
214
- p.stateTTL = ttl
215
- p.cleanupTicker = time.NewTicker(cleanupInterval)
216
- p.cleanupDone = make(chan struct{})
217
-
218
- // Start cleanup goroutine
219
- go p.runStateCleanup()
220
-
221
- // Return stop function
222
- return func() {
223
- p.mu.Lock()
224
- defer p.mu.Unlock()
225
- if p.cleanupTicker != nil {
226
- p.cleanupTicker.Stop()
227
- close(p.cleanupDone)
228
- p.cleanupTicker = nil
229
- p.cleanupDone = nil
230
- }
231
- }
232
-}
233
-
234
-// runStateCleanup periodically removes inactive state entries.
235
-func (p *Preprocessor) runStateCleanup() {
236
- for {
237
- select {
238
- case <-p.cleanupTicker.C:
239
- p.cleanupInactiveState()
240
- case <-p.cleanupDone:
241
- return
242
- }
243
- }
244
-}
245
-
246
-// cleanupInactiveState removes state entries that haven't been accessed within TTL.
247
-func (p *Preprocessor) cleanupInactiveState() {
248
- if p.stateTTL == 0 {
249
- return // Cleanup disabled
250
- }
251
-
252
- p.mu.Lock()
253
- defer p.mu.Unlock()
254
-
255
- now := time.Now()
256
- removed := 0
257
-
258
- for key, state := range p.state {
259
- if now.Sub(state.LastAccess) > p.stateTTL {
260
- delete(p.state, key)
261
- removed++
262
- }
263
- }
264
-
265
- if removed > 0 {
266
- p.logger.Debug("state cleanup completed",
267
- "shard", p.shardID,
268
- "removed", removed,
269
- "remaining", len(p.state))
270
- }
271
-}
272
-
273
-// ClearState removes all stored state entries for the given itemID within this shard.
274
-// Used when an item/instance is obsoleted so historical state does not leak to new entities.
275
-func (p *Preprocessor) ClearState(itemID string) {
276
- if itemID == "" {
277
- return
278
- }
279
- prefix := fmt.Sprintf("%s:%s:", p.shardID, itemID)
280
- p.mu.Lock()
281
- defer p.mu.Unlock()
282
- for key := range p.state {
283
- if strings.HasPrefix(key, prefix) {
284
- delete(p.state, key)
285
- }
286
- }
287
-}
288
-
289
-// SetLogger sets the logger for this preprocessor instance.
290
-// By default, NoopLogger is used (zero overhead).
291
-// This can be called to enable debug logging for troubleshooting.
292
-func (p *Preprocessor) SetLogger(logger Logger) {
293
- p.logger = logger
294
-}
295
-
296
-// PreloadState sets the history state for a stateful operation.
297
-// This is primarily for testing - allows proper setup of delta/throttle state.
298
-// itemID: The item identifier
299
-// stepType: The step type (must be stateful: Delta, Throttle)
300
-// lastValue: The previous value
301
-// lastTimestamp: The timestamp of the previous value
302
-func (p *Preprocessor) PreloadState(itemID string, stepType StepType, lastValue string, lastTimestamp time.Time) error {
303
- var stateKey string
304
- switch stepType {
305
- case StepTypeDeltaValue:
306
- stateKey = fmt.Sprintf("%s:%s:delta_value", p.shardID, itemID)
307
- case StepTypeDeltaSpeed:
308
- stateKey = fmt.Sprintf("%s:%s:delta_speed", p.shardID, itemID)
309
- case StepTypeThrottleValue:
310
- stateKey = fmt.Sprintf("%s:%s:throttle_value", p.shardID, itemID)
311
- case StepTypeThrottleTimedValue:
312
- stateKey = fmt.Sprintf("%s:%s:throttle_timed", p.shardID, itemID)
313
- default:
314
- return fmt.Errorf("step type %d is not stateful", stepType)
315
- }
316
-
317
- p.mu.Lock()
318
- p.state[stateKey] = &OperationState{
319
- LastValue: lastValue,
320
- LastValueTime: lastTimestamp,
321
- LastTimestamp: lastTimestamp,
322
- LastAccess: time.Now(),
323
- }
324
- p.mu.Unlock()
325
-
326
- return nil
327
-}
328
-
329
-// Execute applies a single preprocessing step to a value for a specific item.
330
-// itemID: Unique identifier for the item within this shard (used for state isolation)
331
-// value: The input value to process
332
-// step: The preprocessing step to apply
333
-// Returns: Result containing metrics array, optional logs, and error if failed
334
-func (p *Preprocessor) Execute(itemID string, value Value, step Step) (Result, error) {
335
- if err := validateStep(step); err != nil {
336
- return Result{Error: err}, err
337
- }
338
-
339
- result, err := p.executeStep(itemID, value, step)
340
- if err != nil {
341
- handledValue, handledErr := p.handleError(err, step.ErrorHandler)
342
- return valueToResult(handledValue, handledErr), handledErr
343
- }
344
- return result, nil
345
-}
346
-
347
-// ExecutePipeline applies multiple preprocessing steps in sequence for a specific item.
348
-// itemID: Unique identifier for the item within this shard (used for state isolation)
349
-// value: The input value to process
350
-// steps: The preprocessing steps to apply in order
351
-// Returns: Result containing metrics array, optional logs, and error if failed
352
-//
353
-// IsError Semantics:
354
-// The Value.IsError flag indicates whether the INPUT value originated from an error condition
355
-// (e.g., failed data collection in Zabbix). This flag is preserved through the entire pipeline
356
-// because it's a property of the original data source, not the preprocessing results.
357
-// - IsError=true: Original data came from an error (used by validateNotSupported)
358
-// - IsError=false: Original data was successfully collected
359
-// This flag does NOT change based on step success/failure - those are separate concerns.
360
-// Step failures return errors; IsError tracks input data provenance.
361
-type pipelineValue struct {
362
- value Value
363
- meta Metric
364
-}
365
-
366
-func (p *Preprocessor) ExecutePipeline(itemID string, value Value, steps []Step) (Result, error) {
367
- values := []pipelineValue{{value: value}}
368
- for i, step := range steps {
369
- next := make([]pipelineValue, 0, len(values))
370
- for _, pv := range values {
371
- result, err := p.Execute(itemID, pv.value, step)
372
- if err != nil {
373
- pipelineErr := fmt.Errorf("step %d failed: %w", i, err)
374
- return Result{Error: pipelineErr}, pipelineErr
375
- }
376
- if result.Discarded || len(result.Metrics) == 0 {
377
- return Result{Discarded: true}, nil
378
- }
379
- if isMultiStepType(step.Type) {
380
- for _, metric := range result.Metrics {
381
- next = append(next, pipelineValue{
382
- value: Value{
383
- Data: metric.Value,
384
- Type: metric.Type,
385
- Timestamp: pv.value.Timestamp,
386
- IsError: pv.value.IsError,
387
- },
388
- meta: metric,
389
- })
390
- }
391
- } else {
392
- metric := result.Metrics[0]
393
- next = append(next, pipelineValue{
394
- value: Value{
395
- Data: metric.Value,
396
- Type: metric.Type,
397
- Timestamp: pv.value.Timestamp,
398
- IsError: pv.value.IsError,
399
- },
400
- })
401
- }
402
- }
403
- if len(next) == 0 {
404
- return Result{Discarded: true}, nil
405
- }
406
- values = next
407
- }
408
- metrics := make([]Metric, len(values))
409
- for i, pv := range values {
410
- meta := pv.meta
411
- metrics[i] = Metric{
412
- Name: meta.Name,
413
- Value: pv.value.Data,
414
- Type: pv.value.Type,
415
- Labels: meta.Labels,
416
- }
417
- }
418
- return Result{Metrics: metrics}, nil
419
-}
420
-
421
-func isMultiStepType(t StepType) bool {
422
- switch t {
423
- case StepTypePrometheusToJSONMulti,
424
- StepTypeJSONPathMulti,
425
- StepTypeSNMPWalkToJSONMulti,
426
- StepTypeCSVToJSONMulti:
427
- return true
428
- default:
429
- return false
430
- }
431
-}
432
-
433
-// valueToResult converts a Value to a Result with a single metric
434
-func valueToResult(value Value, err error) Result {
435
- if err != nil {
436
- return Result{Error: err}
437
- }
438
- return Result{
439
- Metrics: []Metric{{
440
- Name: "",
441
- Value: value.Data,
442
- Type: value.Type,
443
- }},
444
- Error: nil,
445
- }
446
-}
447
-
448
-func (p *Preprocessor) executeStep(itemID string, value Value, step Step) (Result, error) {
449
- // Multi-metric steps - return Result with multiple Metric objects directly
450
- // These are NEW step types (60+) that don't break Zabbix compatibility
451
- switch step.Type {
452
- case StepTypePrometheusToJSONMulti:
453
- return prometheusToJSONMulti(value, step.Params)
454
- case StepTypeJSONPathMulti:
455
- return jsonPathMulti(value, step.Params)
456
- case StepTypeSNMPWalkToJSONMulti:
457
- return snmpWalkToJSONMulti(value, step.Params, p.logger)
458
- case StepTypeCSVToJSONMulti:
459
- return csvToJSONMulti(value, step.Params)
460
- }
461
-
462
- // Single-metric steps - execute and wrap with valueToResult()
463
- // These maintain Zabbix compatibility (original step types 1-29)
464
- var result Value
465
- var err error
466
-
467
- switch step.Type {
468
- case StepTypeMultiplier:
469
- result, err = multiplyValue(value, step.Params)
470
- case StepTypeTrim:
471
- result, err = trimValue(value, step.Params, "both")
472
- case StepTypeRTrim:
473
- result, err = trimValue(value, step.Params, "right")
474
- case StepTypeLTrim:
475
- result, err = trimValue(value, step.Params, "left")
476
- case StepTypeRegexSubstitution:
477
- result, err = regexSubstitute(value, step.Params)
478
- case StepTypeBool2Dec:
479
- result, err = bool2Dec(value)
480
- case StepTypeOct2Dec:
481
- result, err = oct2Dec(value)
482
- case StepTypeHex2Dec:
483
- result, err = hex2Dec(value)
484
- case StepTypeDeltaValue:
485
- result, err = p.deltaValue(itemID, value, step.Params)
486
- case StepTypeDeltaSpeed:
487
- result, err = p.deltaSpeed(itemID, value, step.Params)
488
- case StepTypeStringReplace:
489
- result, err = stringReplace(value, step.Params)
490
- case StepTypeValidateRange:
491
- result, err = validateRange(value, step.Params)
492
- case StepTypeValidateRegex:
493
- result, err = validateRegex(value, step.Params)
494
- case StepTypeValidateNotRegex:
495
- result, err = validateNotRegex(value, step.Params)
496
- case StepTypeValidateNotSupported:
497
- result, err = validateNotSupported(value, step.Params)
498
- case StepTypeJSONPath:
499
- result, err = jsonpathExtract(value, step.Params)
500
- case StepTypeXPath:
501
- result, err = xpathExtract(value, step.Params)
502
- case StepTypePrometheusPattern:
503
- result, err = prometheusPattern(value, step.Params)
504
- case StepTypePrometheusToJSON:
505
- result, err = prometheusToJSON(value, step.Params)
506
- case StepTypeCSVToJSON:
507
- result, err = csvToJSON(value, step.Params)
508
- case StepTypeXMLToJSON:
509
- result, err = xmlToJSON(value, step.Params)
510
- case StepTypeErrorFieldJSON:
511
- result, err = errorFieldJSON(value, step.Params)
512
- case StepTypeErrorFieldXML:
513
- result, err = errorFieldXML(value, step.Params)
514
- case StepTypeErrorFieldRegex:
515
- result, err = errorFieldRegex(value, step.Params)
516
- case StepTypeThrottleValue:
517
- result, err = p.throttleValue(itemID, value, step.Params)
518
- case StepTypeThrottleTimedValue:
519
- result, err = p.throttleTimedValue(itemID, value, step.Params)
520
- case StepTypeJavaScript:
521
- result, err = javascriptExecute(value, step.Params, p.limits.JavaScript)
522
- case StepTypeSNMPWalkValue:
523
- result, err = snmpWalkToValue(value, step.Params)
524
- case StepTypeSNMPGetValue:
525
- result, err = snmpGetValue(value, step.Params)
526
- case StepTypeSNMPWalkToJSON:
527
- result, err = snmpWalkToJSON(value, step.Params, p.logger)
528
- default:
529
- return Result{Error: fmt.Errorf("unsupported preprocessing type: %d", step.Type)}, fmt.Errorf("unsupported preprocessing type: %d", step.Type)
530
- }
531
-
532
- return valueToResult(result, err), err
533
-}
534
-
535
-func (p *Preprocessor) handleError(err error, handler ErrorHandler) (Value, error) {
536
- switch handler.Action {
537
- case ErrorActionDefault:
538
- return Value{}, err
539
- case ErrorActionDiscard:
540
- // Zabbix behavior: discard means replace error with empty string value
541
- return Value{Data: "", Type: ValueTypeStr}, nil
542
- case ErrorActionSetValue:
543
- return Value{Data: handler.Params, Type: ValueTypeStr}, nil
544
- case ErrorActionSetError:
545
- return Value{}, fmt.Errorf("%s", handler.Params)
546
- default:
547
- return Value{}, err
548
- }
549
-}
550
-
551
-func validateStep(step Step) error {
552
- if step.Type < 0 {
553
- return fmt.Errorf("invalid step type: %d", step.Type)
554
- }
555
- return nil
556
-}
557
-
558
-func parseValueType(s string) (ValueType, error) {
559
- switch s {
560
- case "ITEM_VALUE_TYPE_STR":
561
- return ValueTypeStr, nil
562
- case "ITEM_VALUE_TYPE_UINT64":
563
- return ValueTypeUint64, nil
564
- case "ITEM_VALUE_TYPE_FLOAT":
565
- return ValueTypeFloat, nil
566
- default:
567
- return ValueTypeStr, fmt.Errorf("unknown value type: %s", s)
568
- }
569
-}
570
-
571
-func valueTypeToString(vt ValueType) string {
572
- switch vt {
573
- case ValueTypeStr:
574
- return "ITEM_VALUE_TYPE_STR"
575
- case ValueTypeUint64:
576
- return "ITEM_VALUE_TYPE_UINT64"
577
- case ValueTypeFloat:
578
- return "ITEM_VALUE_TYPE_FLOAT"
579
- default:
580
- return "UNKNOWN"
581
- }
582
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/preproc_pipeline_test.go
deleted
-22
@@ -1,22 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import "testing"
4
-
5
-func TestExecutePipelineCSVToJSONMultiChained(t *testing.T) {
6
- proc := NewPreprocessor("csv-multi-test")
7
- steps := []Step{
8
- {Type: StepTypeCSVToJSONMulti, Params: ",\n\"\n1"},
9
- {Type: StepTypeJavaScript, Params: `var row = JSON.parse(value); value = row["value"]; return value;`},
10
- }
11
- payload := "name,value\nrow1,10\nrow2,20"
12
- res, err := proc.ExecutePipeline("item1", Value{Data: payload, Type: ValueTypeStr}, steps)
13
- if err != nil {
14
- t.Fatalf("ExecutePipeline error: %v", err)
15
- }
16
- if len(res.Metrics) != 2 {
17
- t.Fatalf("expected 2 metrics, got %d", len(res.Metrics))
18
- }
19
- if res.Metrics[0].Value != "10" || res.Metrics[1].Value != "20" {
20
- t.Fatalf("unexpected values: %+v", res.Metrics)
21
- }
22
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/preproc_race_test.go
deleted
-266
@@ -1,266 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "sync"
6
- "testing"
7
- "time"
8
-)
9
-
10
-// TestConcurrentDeltaValue tests concurrent access to deltaValue operation
11
-func TestConcurrentDeltaValue(t *testing.T) {
12
- p := NewPreprocessor("test-shard")
13
- step := Step{
14
- Type: StepTypeDeltaValue,
15
- Params: "",
16
- }
17
-
18
- var wg sync.WaitGroup
19
- goroutines := 100
20
- iterations := 10
21
-
22
- for i := 0; i < goroutines; i++ {
23
- wg.Add(1)
24
- go func(id int) {
25
- defer wg.Done()
26
- itemID := fmt.Sprintf("item-%d", id) // Each goroutine uses different itemID
27
- for j := 0; j < iterations; j++ {
28
- value := Value{
29
- Data: fmt.Sprintf("%d", id*iterations+j),
30
- Type: ValueTypeFloat,
31
- Timestamp: time.Now(),
32
- }
33
- _, err := p.Execute(itemID, value, step)
34
- // First call returns error (no previous value), subsequent calls should succeed
35
- if err != nil && j > 0 {
36
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
37
- }
38
- }
39
- }(i)
40
- }
41
-
42
- wg.Wait()
43
-}
44
-
45
-// TestConcurrentDeltaSpeed tests concurrent access to deltaSpeed operation
46
-func TestConcurrentDeltaSpeed(t *testing.T) {
47
- p := NewPreprocessor("test-shard")
48
- step := Step{
49
- Type: StepTypeDeltaSpeed,
50
- Params: "",
51
- }
52
-
53
- var wg sync.WaitGroup
54
- goroutines := 100
55
- iterations := 10
56
-
57
- baseTime := time.Now()
58
-
59
- for i := 0; i < goroutines; i++ {
60
- wg.Add(1)
61
- go func(id int) {
62
- itemID := fmt.Sprintf("item-%d", id)
63
- defer wg.Done()
64
- for j := 0; j < iterations; j++ {
65
- value := Value{
66
- Data: fmt.Sprintf("%d", id*iterations+j),
67
- Type: ValueTypeFloat,
68
- Timestamp: baseTime.Add(time.Duration(id*iterations+j) * time.Second),
69
- }
70
- _, err := p.Execute(itemID, value, step)
71
- // First call returns error (no previous value), subsequent calls should succeed
72
- if err != nil && j > 0 {
73
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
74
- }
75
- }
76
- }(i)
77
- }
78
-
79
- wg.Wait()
80
-}
81
-
82
-// TestConcurrentThrottleValue tests concurrent access to throttleValue operation
83
-func TestConcurrentThrottleValue(t *testing.T) {
84
- p := NewPreprocessor("test-shard")
85
- step := Step{
86
- Type: StepTypeThrottleValue,
87
- Params: "",
88
- }
89
-
90
- var wg sync.WaitGroup
91
- goroutines := 100
92
- iterations := 10
93
-
94
- for i := 0; i < goroutines; i++ {
95
- wg.Add(1)
96
- go func(id int) {
97
- itemID := fmt.Sprintf("item-%d", id)
98
- defer wg.Done()
99
- for j := 0; j < iterations; j++ {
100
- value := Value{
101
- Data: fmt.Sprintf("value-%d-%d", id, j),
102
- Type: ValueTypeStr,
103
- Timestamp: time.Now(),
104
- }
105
- _, err := p.Execute(itemID, value, step)
106
- if err != nil {
107
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
108
- }
109
- }
110
- }(i)
111
- }
112
-
113
- wg.Wait()
114
-}
115
-
116
-// TestConcurrentThrottleTimedValue tests concurrent access to throttleTimedValue operation
117
-func TestConcurrentThrottleTimedValue(t *testing.T) {
118
- p := NewPreprocessor("test-shard")
119
- step := Step{
120
- Type: StepTypeThrottleTimedValue,
121
- Params: "1s",
122
- }
123
-
124
- var wg sync.WaitGroup
125
- goroutines := 100
126
- iterations := 10
127
-
128
- baseTime := time.Now()
129
-
130
- for i := 0; i < goroutines; i++ {
131
- wg.Add(1)
132
- go func(id int) {
133
- itemID := fmt.Sprintf("item-%d", id)
134
- defer wg.Done()
135
- for j := 0; j < iterations; j++ {
136
- value := Value{
137
- Data: fmt.Sprintf("value-%d-%d", id, j),
138
- Type: ValueTypeStr,
139
- Timestamp: baseTime.Add(time.Duration(id*iterations+j) * time.Millisecond * 100),
140
- }
141
- _, err := p.Execute(itemID, value, step)
142
- if err != nil {
143
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
144
- }
145
- }
146
- }(i)
147
- }
148
-
149
- wg.Wait()
150
-}
151
-
152
-// TestConcurrentMixedOperations tests concurrent access to mixed stateful operations
153
-func TestConcurrentMixedOperations(t *testing.T) {
154
- p := NewPreprocessor("test-shard")
155
-
156
- steps := []Step{
157
- {Type: StepTypeDeltaValue, Params: ""},
158
- {Type: StepTypeDeltaSpeed, Params: ""},
159
- {Type: StepTypeThrottleValue, Params: ""},
160
- {Type: StepTypeThrottleTimedValue, Params: "1s"},
161
- }
162
-
163
- var wg sync.WaitGroup
164
- goroutines := 50
165
- iterations := 10
166
-
167
- baseTime := time.Now()
168
-
169
- for i := 0; i < goroutines; i++ {
170
- wg.Add(1)
171
- go func(id int) {
172
- itemID := fmt.Sprintf("item-%d", id)
173
- defer wg.Done()
174
- step := steps[id%len(steps)]
175
- for j := 0; j < iterations; j++ {
176
- value := Value{
177
- Data: fmt.Sprintf("%d", id*iterations+j),
178
- Type: ValueTypeFloat,
179
- Timestamp: baseTime.Add(time.Duration(id*iterations+j) * time.Millisecond * 50),
180
- }
181
- _, err := p.Execute(itemID, value, step)
182
- // Delta operations fail on first call (no previous value)
183
- if err != nil && (step.Type == StepTypeDeltaValue || step.Type == StepTypeDeltaSpeed) && j > 0 {
184
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
185
- }
186
- }
187
- }(i)
188
- }
189
-
190
- wg.Wait()
191
-}
192
-
193
-// TestConcurrentPipelines tests concurrent execution of preprocessing pipelines
194
-func TestConcurrentPipelines(t *testing.T) {
195
- p := NewPreprocessor("test-shard")
196
-
197
- // Pipeline with multiple stateful operations
198
- pipeline := []Step{
199
- {Type: StepTypeTrim, Params: " "},
200
- {Type: StepTypeDeltaValue, Params: ""},
201
- {Type: StepTypeThrottleValue, Params: ""},
202
- }
203
-
204
- var wg sync.WaitGroup
205
- goroutines := 50
206
- iterations := 10
207
-
208
- for i := 0; i < goroutines; i++ {
209
- wg.Add(1)
210
- go func(id int) {
211
- itemID := fmt.Sprintf("item-%d", id)
212
- defer wg.Done()
213
- for j := 0; j < iterations; j++ {
214
- value := Value{
215
- Data: fmt.Sprintf(" %d ", id*iterations+j),
216
- Type: ValueTypeFloat,
217
- Timestamp: time.Now(),
218
- }
219
- _, err := p.ExecutePipeline(itemID, value, pipeline)
220
- // First call will fail at deltaValue (no previous value)
221
- if err != nil && j > 0 {
222
- t.Errorf("goroutine %d iteration %d: unexpected error: %v", id, j, err)
223
- }
224
- }
225
- }(i)
226
- }
227
-
228
- wg.Wait()
229
-}
230
-
231
-// TestConcurrentMultiplePreprocessors tests that separate preprocessor instances are isolated
232
-func TestConcurrentMultiplePreprocessors(t *testing.T) {
233
- preprocessors := make([]*Preprocessor, 10)
234
- for i := range preprocessors {
235
- preprocessors[i] = NewPreprocessor(fmt.Sprintf("shard-%d", i))
236
- }
237
-
238
- step := Step{
239
- Type: StepTypeDeltaValue,
240
- Params: "",
241
- }
242
-
243
- var wg sync.WaitGroup
244
-
245
- for i, p := range preprocessors {
246
- wg.Add(1)
247
- go func(id int, preprocessor *Preprocessor) {
248
- itemID := fmt.Sprintf("item-%d", id)
249
- defer wg.Done()
250
- for j := 0; j < 100; j++ {
251
- value := Value{
252
- Data: fmt.Sprintf("%d", j),
253
- Type: ValueTypeFloat,
254
- Timestamp: time.Now(),
255
- }
256
- _, err := preprocessor.Execute(itemID, value, step)
257
- // First call returns error (no previous value)
258
- if err != nil && j > 0 {
259
- t.Errorf("preprocessor %d iteration %d: unexpected error: %v", id, j, err)
260
- }
261
- }
262
- }(i, p)
263
- }
264
-
265
- wg.Wait()
266
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/preproc_test.go
deleted
-117
@@ -1,117 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "testing"
6
-)
7
-
8
-func TestZabbixTestSuite(t *testing.T) {
9
- // Load ALL official Zabbix test suites
10
- testCases, err := LoadAllTestCases()
11
- if err != nil {
12
- t.Fatalf("Failed to load test cases: %v", err)
13
- }
14
-
15
- passed := 0
16
- failed := 0
17
- skipped := 0
18
-
19
- for _, tc := range testCases {
20
- // Capture tc in closure for t.Run
21
- tc := tc
22
- t.Run(tc.Name, func(t *testing.T) {
23
- // Create a fresh preprocessor for each test to isolate state
24
- preprocessor := NewPreprocessor("test-shard")
25
- // Parse test input
26
- value, err := ValueFromTestInput(tc)
27
- if err != nil {
28
- t.Skipf("Failed to parse input: %v", err)
29
- skipped++
30
- return
31
- }
32
-
33
- step, err := StepFromTestInput(tc)
34
- if err != nil {
35
- t.Skipf("Failed to parse step: %v", err)
36
- skipped++
37
- return
38
- }
39
-
40
- // Load history value if provided (for stateful operations like delta, throttle)
41
- historyVal := HistoryValueFromTestInput(tc)
42
- itemID := "test-item" // Use consistent itemID for tests
43
- if historyVal != nil {
44
- // Pre-populate state through public API (no direct state manipulation)
45
- if err := preprocessor.PreloadState(itemID, step.Type, historyVal.Data, historyVal.Timestamp); err != nil {
46
- // Not a stateful operation, ignore
47
- t.Logf("Note: history value provided but step type %d is not stateful", step.Type)
48
- }
49
- }
50
-
51
- // Execute preprocessing with itemID
52
- result, err := preprocessor.Execute(itemID, value, step)
53
-
54
- // Check expected outcome
55
- if tc.Out.Return == "SUCCEED" {
56
- if err != nil {
57
- t.Errorf("Expected success but got error: %v", err)
58
- failed++
59
- return
60
- }
61
-
62
- // Extract result value from first metric
63
- if len(result.Metrics) == 0 {
64
- t.Errorf("No metrics returned")
65
- failed++
66
- return
67
- }
68
- resultData := result.Metrics[0].Value
69
-
70
- expected, _ := ExpectedOutputFromTestCase(tc)
71
- if resultData != expected {
72
- // Debug: print input details for trim tests
73
- if step.Type == StepTypeRTrim || step.Type == StepTypeLTrim || step.Type == StepTypeTrim {
74
- paramsDesc := ""
75
- for i, ch := range step.Params {
76
- if i > 0 {
77
- paramsDesc += ", "
78
- }
79
- paramsDesc += fmt.Sprintf("%c(0x%x)", ch, ch)
80
- }
81
- t.Logf("DEBUG: input='%s' params='%s'[%s] (len=%d) expected='%s' (len=%d) got='%s' (len=%d)", value.Data, step.Params, paramsDesc, len(step.Params), expected, len(expected), resultData, len(resultData))
82
- }
83
- t.Errorf("Expected '%s' but got '%s'", expected, resultData)
84
- failed++
85
- return
86
- }
87
-
88
- passed++
89
- } else { // FAIL expected
90
- if err == nil {
91
- resultData := ""
92
- if len(result.Metrics) > 0 {
93
- resultData = result.Metrics[0].Value
94
- }
95
- t.Errorf("Expected error but succeeded with '%s'", resultData)
96
- failed++
97
- return
98
- }
99
-
100
- passed++
101
- }
102
- })
103
- }
104
-
105
- fmt.Printf("\n=== Test Results ===\n")
106
- fmt.Printf("Passed: %d\n", passed)
107
- fmt.Printf("Failed: %d\n", failed)
108
- fmt.Printf("Skipped: %d\n", skipped)
109
- fmt.Printf("Total: %d\n", passed+failed+skipped)
110
- if passed+failed > 0 {
111
- fmt.Printf("Pass Rate: %.1f%%\n", float64(passed)*100/float64(passed+failed))
112
- }
113
-
114
- if failed > 0 {
115
- t.Fatalf("%d tests failed", failed)
116
- }
117
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/prometheus_step.go
deleted
-676
@@ -1,676 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "fmt"
6
- "regexp"
7
- "strconv"
8
- "strings"
9
-)
10
-
11
-// Prometheus regex patterns compiled once at package init
12
-var (
13
- // Format: metric_name{label="value"} number
14
- prometheusMetricWithLabelsRegex = regexp.MustCompile(`^([a-zA-Z_:][a-zA-Z0-9_:]*)\{([^}]*)\}\s+([-+]?[\d.eE+-]+)`)
15
- // Format: metric_name number
16
- prometheusMetricWithoutLabelsRegex = regexp.MustCompile(`^([a-zA-Z_:][a-zA-Z0-9_:]*)\s+([-+]?[\d.eE+-]+)`)
17
- // Format: label_name="label_value" (NOTE: this regex doesn't handle escaped quotes)
18
- // Use parsePrometheusLabels() for proper escape handling
19
- prometheusLabelRegex = regexp.MustCompile(`([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"`)
20
-)
21
-
22
-// parsePrometheusLabels parses Prometheus label string with proper escape handling.
23
-// Handles \", \\, and \n escape sequences per Prometheus spec.
24
-// Input: `method="GET",path="/foo\"bar",code="200"`
25
-// Returns map and order of labels.
26
-func parsePrometheusLabels(labelsStr string) (map[string]string, []string) {
27
- labels := make(map[string]string)
28
- var labelOrder []string
29
-
30
- if strings.TrimSpace(labelsStr) == "" {
31
- return labels, labelOrder
32
- }
33
-
34
- i := 0
35
- n := len(labelsStr)
36
-
37
- for i < n {
38
- // Skip whitespace and commas
39
- for i < n && (labelsStr[i] == ' ' || labelsStr[i] == ',' || labelsStr[i] == '\t') {
40
- i++
41
- }
42
- if i >= n {
43
- break
44
- }
45
-
46
- // Parse label name (alphanumeric + underscore, starts with letter or underscore)
47
- nameStart := i
48
- for i < n && (labelsStr[i] == '_' || (labelsStr[i] >= 'a' && labelsStr[i] <= 'z') ||
49
- (labelsStr[i] >= 'A' && labelsStr[i] <= 'Z') || (labelsStr[i] >= '0' && labelsStr[i] <= '9')) {
50
- i++
51
- }
52
- if i == nameStart {
53
- // No valid label name found, skip rest
54
- break
55
- }
56
- labelName := labelsStr[nameStart:i]
57
-
58
- // Skip whitespace
59
- for i < n && (labelsStr[i] == ' ' || labelsStr[i] == '\t') {
60
- i++
61
- }
62
-
63
- // Expect '='
64
- if i >= n || labelsStr[i] != '=' {
65
- break
66
- }
67
- i++
68
-
69
- // Skip whitespace
70
- for i < n && (labelsStr[i] == ' ' || labelsStr[i] == '\t') {
71
- i++
72
- }
73
-
74
- // Expect opening quote
75
- if i >= n || labelsStr[i] != '"' {
76
- break
77
- }
78
- i++
79
-
80
- // Parse label value with escape handling
81
- var valueBuilder strings.Builder
82
- for i < n {
83
- if labelsStr[i] == '\\' && i+1 < n {
84
- // Escape sequence
85
- nextChar := labelsStr[i+1]
86
- switch nextChar {
87
- case '"':
88
- valueBuilder.WriteByte('"')
89
- i += 2
90
- case '\\':
91
- valueBuilder.WriteByte('\\')
92
- i += 2
93
- case 'n':
94
- valueBuilder.WriteByte('\n')
95
- i += 2
96
- default:
97
- // Unknown escape, keep as-is
98
- valueBuilder.WriteByte(labelsStr[i])
99
- i++
100
- }
101
- } else if labelsStr[i] == '"' {
102
- // End of value
103
- i++
104
- break
105
- } else {
106
- valueBuilder.WriteByte(labelsStr[i])
107
- i++
108
- }
109
- }
110
-
111
- labels[labelName] = valueBuilder.String()
112
- labelOrder = append(labelOrder, labelName)
113
- }
114
-
115
- return labels, labelOrder
116
-}
117
-
118
-// prometheusMetric represents a parsed Prometheus metric
119
-type prometheusMetric struct {
120
- Name string
121
- Labels map[string]string
122
- LabelOrder []string // Order of label keys as they appear in input
123
- Value float64
124
- ValueStr string // Original string representation of value
125
- LineRaw string
126
-}
127
-
128
-// parsePrometheusText parses Prometheus text exposition format.
129
-//
130
-// Parses metrics in Prometheus text format as defined by the Prometheus documentation.
131
-// Supports:
132
-// - Metrics with labels: http_requests_total{method="GET",code="200"} 1027
133
-// - Metrics without labels: process_cpu_seconds 4.2
134
-// - Scientific notation: very_large_value 1.23e+10
135
-// - Comment lines (ignored): # HELP and # TYPE comments
136
-// - Label validation: ensures all labels match pattern label_name="value"
137
-//
138
-// Example Prometheus format:
139
-//
140
-// # HELP http_requests_total Total HTTP requests
141
-// # TYPE http_requests_total counter
142
-// http_requests_total{method="GET"} 100
143
-// http_requests_total{method="POST"} 50
144
-// memory_usage_bytes 1024
145
-//
146
-// Parameters:
147
-// - data: Prometheus text format data (one metric per line)
148
-//
149
-// Returns:
150
-// - []prometheusMetric: Parsed metrics with names, labels, and values
151
-// - error: Returns nil on success (invalid lines are silently skipped per Prometheus spec)
152
-//
153
-// Note: This parser uses pre-compiled package-level regexes for performance.
154
-func parsePrometheusText(data string) ([]prometheusMetric, error) {
155
- var metrics []prometheusMetric
156
- lines := strings.Split(data, "\n")
157
-
158
- for _, line := range lines {
159
- originalLine := line
160
- line = strings.TrimSpace(line)
161
- if line == "" || strings.HasPrefix(line, "#") {
162
- continue
163
- }
164
-
165
- var metricName, labelsStr, valueStr string
166
- var labels map[string]string
167
-
168
- var labelOrder []string
169
-
170
- // Try with labels first
171
- matches := prometheusMetricWithLabelsRegex.FindStringSubmatch(line)
172
- if len(matches) >= 4 {
173
- metricName = matches[1]
174
- labelsStr = matches[2]
175
- valueStr = matches[3]
176
-
177
- labels = make(map[string]string)
178
-
179
- // Parse labels with proper escape handling (\", \\, \n)
180
- if strings.TrimSpace(labelsStr) != "" {
181
- labels, labelOrder = parsePrometheusLabels(labelsStr)
182
- if len(labels) == 0 {
183
- // Labels present but none valid - invalid syntax
184
- continue
185
- }
186
- }
187
- } else {
188
- // Try without labels
189
- matches = prometheusMetricWithoutLabelsRegex.FindStringSubmatch(line)
190
- if len(matches) < 3 {
191
- continue
192
- }
193
- metricName = matches[1]
194
- valueStr = matches[2]
195
- labels = make(map[string]string)
196
- }
197
-
198
- value, err := strconv.ParseFloat(valueStr, 64)
199
- if err != nil {
200
- continue
201
- }
202
-
203
- metrics = append(metrics, prometheusMetric{
204
- Name: metricName,
205
- Labels: labels,
206
- LabelOrder: labelOrder,
207
- Value: value,
208
- ValueStr: valueStr,
209
- LineRaw: strings.TrimSpace(originalLine),
210
- })
211
- }
212
-
213
- return metrics, nil
214
-}
215
-
216
-// parsePrometheusMetadata extracts HELP and TYPE comments from Prometheus text
217
-func parsePrometheusMetadata(data string) (map[string]string, map[string]string) {
218
- helpMap := make(map[string]string)
219
- typeMap := make(map[string]string)
220
-
221
- lines := strings.Split(data, "\n")
222
- for _, line := range lines {
223
- line = strings.TrimSpace(line)
224
- if !strings.HasPrefix(line, "#") {
225
- continue
226
- }
227
-
228
- // Parse # HELP metric_name description
229
- if strings.HasPrefix(line, "# HELP ") {
230
- parts := strings.SplitN(line[7:], " ", 2)
231
- if len(parts) == 2 {
232
- helpMap[parts[0]] = parts[1]
233
- }
234
- }
235
-
236
- // Parse # TYPE metric_name type
237
- if strings.HasPrefix(line, "# TYPE ") {
238
- parts := strings.SplitN(line[7:], " ", 2)
239
- if len(parts) == 2 {
240
- typeMap[parts[0]] = parts[1]
241
- }
242
- }
243
- }
244
-
245
- return helpMap, typeMap
246
-}
247
-
248
-// prometheusPattern extracts metric values matching a label pattern.
249
-func prometheusPattern(value Value, paramStr string) (Value, error) {
250
- // paramStr format: "metric_name{label_selectors}\noutput\nlabel_name"
251
- // output can be "value", "label"
252
- // If output is "label" and label_name is provided, return that specific label
253
- parts := strings.SplitN(paramStr, "\n", 3)
254
- if len(parts) < 1 {
255
- return Value{}, fmt.Errorf("prometheus pattern requires metric pattern")
256
- }
257
-
258
- // Parse metric pattern (e.g., "cpu_usage_system{cpu=\"cpu-total\",host=~\".*\"}")
259
- metricPattern := strings.TrimSpace(parts[0])
260
- outputType := "value" // Default
261
- labelName := ""
262
- if len(parts) > 1 {
263
- outputType = strings.TrimSpace(parts[1])
264
- }
265
- if len(parts) > 2 {
266
- labelName = strings.TrimSpace(parts[2])
267
- }
268
-
269
- // Extract metric name, label selectors, and value comparison from pattern
270
- metricName, labelSelectors, valueComp := parseMetricPattern(metricPattern)
271
-
272
- // Parse Prometheus text format
273
- metrics, err := parsePrometheusText(value.Data)
274
- if err != nil {
275
- return Value{}, fmt.Errorf("failed to parse prometheus data: %w", err)
276
- }
277
-
278
- // Find matching metric
279
- for _, m := range metrics {
280
- // Check if metric name matches
281
- // Priority: __name__ selector > explicit metric name > no filter
282
- nameSelector, hasNameSelector := labelSelectors["__name__"]
283
- if hasNameSelector {
284
- // Use __name__ selector to match metric name
285
- matched := false
286
- switch nameSelector.operator {
287
- case "=":
288
- matched = m.Name == nameSelector.value
289
- case "=~":
290
- re, err := compileRegexSafe(nameSelector.value, 0)
291
- if err != nil {
292
- continue
293
- }
294
- matched, err = matchWithTimeout(re, m.Name, defaultRegexTimeout)
295
- if err != nil {
296
- continue
297
- }
298
- case "!=":
299
- matched = m.Name != nameSelector.value
300
- }
301
- if !matched {
302
- continue
303
- }
304
- } else if metricName != "" {
305
- // Use metric name from pattern
306
- if !matchesPattern(m.Name, metricName) {
307
- continue
308
- }
309
- }
310
-
311
- // Filter out __name__ from label selectors (already checked above)
312
- actualLabelSelectors := make(map[string]labelSelector)
313
- for k, v := range labelSelectors {
314
- if k != "__name__" {
315
- actualLabelSelectors[k] = v
316
- }
317
- }
318
-
319
- if !matchesLabelSelectors(m.Labels, actualLabelSelectors) {
320
- continue
321
- }
322
-
323
- // Check value comparison if present
324
- if valueComp != nil {
325
- if !matchesValueComparison(m.Value, valueComp) {
326
- continue
327
- }
328
- }
329
-
330
- // Return based on output type
331
- switch outputType {
332
- case "value":
333
- return Value{Data: fmt.Sprintf("%g", m.Value), Type: ValueTypeStr}, nil
334
- case "label":
335
- if labelName != "" {
336
- // Return specific label value
337
- if val, ok := m.Labels[labelName]; ok {
338
- return Value{Data: val, Type: ValueTypeStr}, nil
339
- }
340
- return Value{}, fmt.Errorf("label %s not found", labelName)
341
- }
342
- // Return all labels as comma-separated key=value pairs
343
- var labelPairs []string
344
- for k, v := range m.Labels {
345
- labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, v))
346
- }
347
- return Value{Data: strings.Join(labelPairs, ","), Type: ValueTypeStr}, nil
348
- default:
349
- // Unknown output type
350
- return Value{}, fmt.Errorf("unknown output type: %s", outputType)
351
- }
352
- }
353
-
354
- return Value{}, fmt.Errorf("prometheus pattern did not match any metrics")
355
-}
356
-
357
-// prometheusToJSON converts Prometheus text format to JSON.
358
-// prometheusToJSONMulti converts Prometheus text format to multiple Result metrics
359
-// Each Prometheus metric becomes a separate Result.Metric with labels
360
-func prometheusToJSONMulti(value Value, paramStr string) (Result, error) {
361
- // Parse Prometheus text format
362
- metrics, err := parsePrometheusText(value.Data)
363
- if err != nil {
364
- return Result{Error: err}, err
365
- }
366
-
367
- // If input is non-empty but no metrics were parsed, it's invalid data
368
- if len(metrics) == 0 && strings.TrimSpace(value.Data) != "" {
369
- err := fmt.Errorf("failed to parse prometheus data: no valid metrics found")
370
- return Result{Error: err}, err
371
- }
372
-
373
- // Convert each Prometheus metric to Result.Metric
374
- resultMetrics := make([]Metric, 0, len(metrics))
375
- for _, pm := range metrics {
376
- resultMetrics = append(resultMetrics, Metric{
377
- Name: pm.Name,
378
- Value: pm.ValueStr,
379
- Type: ValueTypeStr,
380
- Labels: pm.Labels, // Prometheus labels preserved
381
- })
382
- }
383
-
384
- return Result{Metrics: resultMetrics}, nil
385
-}
386
-
387
-func prometheusToJSON(value Value, paramStr string) (Value, error) {
388
- // Parse Prometheus text format
389
- metrics, err := parsePrometheusText(value.Data)
390
- if err != nil {
391
- return Value{}, fmt.Errorf("failed to parse prometheus data: %w", err)
392
- }
393
-
394
- // If input is non-empty but no metrics were parsed, it's invalid data
395
- if len(metrics) == 0 && strings.TrimSpace(value.Data) != "" {
396
- return Value{}, fmt.Errorf("failed to parse Prometheus data: no valid metrics found")
397
- }
398
-
399
- // Parse metadata (HELP and TYPE comments)
400
- helpMap, typeMap := parsePrometheusMetadata(value.Data)
401
-
402
- // Build result array with ordered fields
403
- var jsonParts []string
404
-
405
- for _, m := range metrics {
406
- // Build JSON manually to maintain field order: name, value, line_raw, labels, type, help
407
- var parts []string
408
-
409
- parts = append(parts, fmt.Sprintf(`"name":%s`, jsonString(m.Name)))
410
- parts = append(parts, fmt.Sprintf(`"value":%s`, jsonString(m.ValueStr)))
411
- parts = append(parts, fmt.Sprintf(`"line_raw":%s`, jsonString(m.LineRaw)))
412
-
413
- if len(m.Labels) > 0 {
414
- // Build labels JSON with preserved order
415
- var labelPairs []string
416
- for _, key := range m.LabelOrder {
417
- labelPairs = append(labelPairs, fmt.Sprintf(`%s:%s`, jsonString(key), jsonString(m.Labels[key])))
418
- }
419
- parts = append(parts, fmt.Sprintf(`"labels":{%s}`, strings.Join(labelPairs, ",")))
420
- }
421
-
422
- // Add type (default to "untyped")
423
- metricType := "untyped"
424
- if t, ok := typeMap[m.Name]; ok {
425
- metricType = t
426
- }
427
- parts = append(parts, fmt.Sprintf(`"type":%s`, jsonString(metricType)))
428
-
429
- // Add help if available
430
- if helpText, ok := helpMap[m.Name]; ok {
431
- parts = append(parts, fmt.Sprintf(`"help":%s`, jsonString(helpText)))
432
- }
433
-
434
- jsonParts = append(jsonParts, "{"+strings.Join(parts, ",")+"}")
435
- }
436
-
437
- resultJSON := "[" + strings.Join(jsonParts, ",") + "]"
438
- return Value{Data: resultJSON, Type: ValueTypeStr}, nil
439
-}
440
-
441
-// jsonString escapes a string for JSON
442
-func jsonString(s string) string {
443
- b, _ := json.Marshal(s)
444
- return string(b)
445
-}
446
-
447
-// matchesPattern checks if a string matches a simple wildcard pattern
448
-func matchesPattern(s, pattern string) bool {
449
- // Zabbix uses simple glob patterns, convert to regex
450
- regexPattern := regexp.QuoteMeta(pattern)
451
- regexPattern = strings.ReplaceAll(regexPattern, `\*`, ".*")
452
- regexPattern = strings.ReplaceAll(regexPattern, `\?`, ".")
453
- regexPattern = "^" + regexPattern + "$"
454
- re, err := compileRegexSafe(regexPattern, 0)
455
- if err != nil {
456
- return false
457
- }
458
- matched, err := matchWithTimeout(re, s, defaultRegexTimeout)
459
- if err != nil {
460
- return false
461
- }
462
- return matched
463
-}
464
-
465
-// parseMetricPattern parses a metric pattern like "metric_name{label=\"value\",label2=~\"regex\"} == 123.45"
466
-// Returns the metric name, label selectors, and optional value comparison
467
-func parseMetricPattern(pattern string) (string, map[string]labelSelector, *valueComparison) {
468
- // Check for value comparison operators (==, !=, >, <, >=, <=)
469
- var valueComp *valueComparison
470
-
471
- // Find value comparison operator outside of braces
472
- braceDepth := 0
473
- var compIdx int = -1
474
- var compOp string
475
-
476
- for i := 0; i < len(pattern); i++ {
477
- if pattern[i] == '{' {
478
- braceDepth++
479
- } else if pattern[i] == '}' {
480
- braceDepth--
481
- } else if braceDepth == 0 {
482
- // Check for comparison operators outside braces
483
- if i+2 <= len(pattern) {
484
- twoChar := pattern[i:min(i+2, len(pattern))]
485
- if twoChar == "==" || twoChar == "!=" || twoChar == ">=" || twoChar == "<=" {
486
- compOp = twoChar
487
- compIdx = i
488
- break
489
- }
490
- }
491
- if pattern[i] == '>' || pattern[i] == '<' {
492
- compOp = string(pattern[i])
493
- compIdx = i
494
- break
495
- }
496
- }
497
- }
498
-
499
- // Extract value comparison if present
500
- if compIdx != -1 {
501
- valueStr := strings.TrimSpace(pattern[compIdx+len(compOp):])
502
- pattern = strings.TrimSpace(pattern[:compIdx])
503
- if valueStr != "" {
504
- val, err := strconv.ParseFloat(valueStr, 64)
505
- if err == nil {
506
- valueComp = &valueComparison{
507
- operator: compOp,
508
- value: val,
509
- }
510
- }
511
- }
512
- }
513
-
514
- // Find the opening brace
515
- braceIdx := strings.Index(pattern, "{")
516
- if braceIdx == -1 {
517
- // No labels specified
518
- return pattern, nil, valueComp
519
- }
520
-
521
- metricName := pattern[:braceIdx]
522
- labelsStr := pattern[braceIdx+1:]
523
-
524
- // Remove trailing }
525
- if strings.HasSuffix(labelsStr, "}") {
526
- labelsStr = labelsStr[:len(labelsStr)-1]
527
- }
528
-
529
- // Parse label selectors
530
- selectors := make(map[string]labelSelector)
531
- if labelsStr == "" {
532
- return metricName, selectors, valueComp
533
- }
534
-
535
- // Split by comma, but respect quoted strings
536
- labelParts := splitLabelSelectors(labelsStr)
537
- for _, part := range labelParts {
538
- part = strings.TrimSpace(part)
539
- if part == "" {
540
- continue
541
- }
542
-
543
- // Check for regex operator =~ or !=
544
- var op string
545
- var splitIdx int
546
- if idx := strings.Index(part, "=~"); idx != -1 {
547
- op = "=~"
548
- splitIdx = idx
549
- } else if idx := strings.Index(part, "!="); idx != -1 {
550
- op = "!="
551
- splitIdx = idx
552
- } else if idx := strings.Index(part, "="); idx != -1 {
553
- op = "="
554
- splitIdx = idx
555
- } else {
556
- continue
557
- }
558
-
559
- labelName := strings.TrimSpace(part[:splitIdx])
560
- labelValue := strings.TrimSpace(part[splitIdx+len(op):])
561
-
562
- // Remove quotes from value
563
- labelValue = strings.Trim(labelValue, "\"")
564
-
565
- selectors[labelName] = labelSelector{
566
- operator: op,
567
- value: labelValue,
568
- }
569
- }
570
-
571
- return metricName, selectors, valueComp
572
-}
573
-
574
-func min(a, b int) int {
575
- if a < b {
576
- return a
577
- }
578
- return b
579
-}
580
-
581
-// labelSelector represents a label matching condition
582
-type labelSelector struct {
583
- operator string // "=", "=~", "!="
584
- value string
585
-}
586
-
587
-// valueComparison represents a metric value comparison
588
-type valueComparison struct {
589
- operator string // "==", "!=", ">", "<", ">=", "<="
590
- value float64
591
-}
592
-
593
-// splitLabelSelectors splits label selectors by comma, respecting quotes
594
-func splitLabelSelectors(s string) []string {
595
- var parts []string
596
- var current strings.Builder
597
- inQuotes := false
598
-
599
- for i := 0; i < len(s); i++ {
600
- ch := s[i]
601
-
602
- if ch == '"' {
603
- inQuotes = !inQuotes
604
- current.WriteByte(ch)
605
- } else if ch == ',' && !inQuotes {
606
- parts = append(parts, current.String())
607
- current.Reset()
608
- } else {
609
- current.WriteByte(ch)
610
- }
611
- }
612
-
613
- if current.Len() > 0 {
614
- parts = append(parts, current.String())
615
- }
616
-
617
- return parts
618
-}
619
-
620
-// matchesLabelSelectors checks if labels match all selectors
621
-// Also checks metric name if __name__ selector is present
622
-func matchesLabelSelectors(labels map[string]string, selectors map[string]labelSelector) bool {
623
- if len(selectors) == 0 {
624
- return true
625
- }
626
-
627
- for labelName, selector := range selectors {
628
- labelValue, exists := labels[labelName]
629
-
630
- switch selector.operator {
631
- case "=":
632
- if !exists || labelValue != selector.value {
633
- return false
634
- }
635
- case "=~":
636
- if !exists {
637
- return false
638
- }
639
- // Treat as regex
640
- re, err := compileRegexSafe(selector.value, 0)
641
- if err != nil {
642
- return false
643
- }
644
- matched, err := matchWithTimeout(re, labelValue, defaultRegexTimeout)
645
- if err != nil || !matched {
646
- return false
647
- }
648
- case "!=":
649
- if exists && labelValue == selector.value {
650
- return false
651
- }
652
- }
653
- }
654
-
655
- return true
656
-}
657
-
658
-// matchesValueComparison checks if a metric value matches a comparison
659
-func matchesValueComparison(metricValue float64, comp *valueComparison) bool {
660
- switch comp.operator {
661
- case "==":
662
- return metricValue == comp.value
663
- case "!=":
664
- return metricValue != comp.value
665
- case ">":
666
- return metricValue > comp.value
667
- case "<":
668
- return metricValue < comp.value
669
- case ">=":
670
- return metricValue >= comp.value
671
- case "<=":
672
- return metricValue <= comp.value
673
- default:
674
- return false
675
- }
676
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/regex_safe.go
deleted
-254
@@ -1,254 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "context"
5
- "fmt"
6
- "regexp"
7
- "sync"
8
- "time"
9
-)
10
-
11
-// Regex safety constants
12
-const (
13
- maxRegexNestingDepth = 10 // Maximum nesting depth for groups
14
- defaultRegexTimeout = 5 * time.Second // Default timeout for regex operations
15
- regexCacheSize = 100 // Maximum cached patterns
16
-)
17
-
18
-// regexCache caches compiled regular expressions for performance
19
-type regexCache struct {
20
- mu sync.RWMutex
21
- cache map[string]*regexp.Regexp
22
- order []string // LRU tracking
23
-}
24
-
25
-var globalRegexCache = ®exCache{
26
- cache: make(map[string]*regexp.Regexp),
27
- order: make([]string, 0, regexCacheSize),
28
-}
29
-
30
-// validateRegexComplexity performs basic complexity checks on regex patterns
31
-// maxPatternLength of 0 means no limit
32
-func validateRegexComplexity(pattern string, maxPatternLength int) error {
33
- if maxPatternLength > 0 && len(pattern) > maxPatternLength {
34
- return fmt.Errorf("regex pattern too long: %d chars (max %d)",
35
- len(pattern), maxPatternLength)
36
- }
37
-
38
- // Count nesting depth of parentheses
39
- depth := 0
40
- maxDepth := 0
41
- escaped := false
42
-
43
- for _, ch := range pattern {
44
- if escaped {
45
- escaped = false
46
- continue
47
- }
48
-
49
- if ch == '\\' {
50
- escaped = true
51
- continue
52
- }
53
-
54
- if ch == '(' {
55
- depth++
56
- if depth > maxDepth {
57
- maxDepth = depth
58
- }
59
- } else if ch == ')' {
60
- depth--
61
- if depth < 0 {
62
- return fmt.Errorf("unbalanced parentheses in regex pattern")
63
- }
64
- }
65
- }
66
-
67
- if depth != 0 {
68
- return fmt.Errorf("unbalanced parentheses in regex pattern")
69
- }
70
-
71
- if maxDepth > maxRegexNestingDepth {
72
- return fmt.Errorf("regex nesting too deep: %d levels (max %d)",
73
- maxDepth, maxRegexNestingDepth)
74
- }
75
-
76
- return nil
77
-}
78
-
79
-// compileRegexSafe compiles a regex with validation and caching
80
-// maxPatternLength of 0 means no limit (Zabbix default)
81
-func compileRegexSafe(pattern string, maxPatternLength int) (*regexp.Regexp, error) {
82
- // Check cache first (read lock)
83
- globalRegexCache.mu.RLock()
84
- if re, found := globalRegexCache.cache[pattern]; found {
85
- globalRegexCache.mu.RUnlock()
86
- return re, nil
87
- }
88
- globalRegexCache.mu.RUnlock()
89
-
90
- // Validate complexity before compiling
91
- if err := validateRegexComplexity(pattern, maxPatternLength); err != nil {
92
- return nil, err
93
- }
94
-
95
- // Compile pattern
96
- re, err := regexp.Compile(pattern)
97
- if err != nil {
98
- return nil, err
99
- }
100
-
101
- // Add to cache (write lock)
102
- globalRegexCache.mu.Lock()
103
- defer globalRegexCache.mu.Unlock()
104
-
105
- // Double-check cache after acquiring write lock (another goroutine might have added it)
106
- if cached, found := globalRegexCache.cache[pattern]; found {
107
- return cached, nil
108
- }
109
-
110
- // Evict oldest entry if cache is full (simple FIFO)
111
- if len(globalRegexCache.cache) >= regexCacheSize {
112
- oldest := globalRegexCache.order[0]
113
- delete(globalRegexCache.cache, oldest)
114
- globalRegexCache.order = globalRegexCache.order[1:]
115
- }
116
-
117
- // Add to cache
118
- globalRegexCache.cache[pattern] = re
119
- globalRegexCache.order = append(globalRegexCache.order, pattern)
120
-
121
- return re, nil
122
-}
123
-
124
-// matchWithTimeout runs a regex match with timeout protection
125
-func matchWithTimeout(re *regexp.Regexp, input string, timeout time.Duration) (bool, error) {
126
- if timeout == 0 {
127
- timeout = defaultRegexTimeout
128
- }
129
-
130
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
131
- defer cancel()
132
-
133
- resultChan := make(chan bool, 1)
134
- errChan := make(chan error, 1)
135
-
136
- go func() {
137
- defer func() {
138
- if r := recover(); r != nil {
139
- select {
140
- case errChan <- fmt.Errorf("regex match panicked: %v", r):
141
- case <-ctx.Done():
142
- return // Context cancelled, exit immediately
143
- }
144
- }
145
- }()
146
-
147
- matched := re.MatchString(input)
148
- select {
149
- case resultChan <- matched:
150
- case <-ctx.Done():
151
- return // Context cancelled, exit immediately
152
- }
153
- }()
154
-
155
- select {
156
- case matched := <-resultChan:
157
- return matched, nil
158
- case err := <-errChan:
159
- return false, err
160
- case <-ctx.Done():
161
- return false, fmt.Errorf("regex match timeout after %v", timeout)
162
- }
163
-}
164
-
165
-// findStringSubmatchWithTimeout runs FindStringSubmatch with timeout protection
166
-func findStringSubmatchWithTimeout(re *regexp.Regexp, input string, timeout time.Duration) ([]string, error) {
167
- if timeout == 0 {
168
- timeout = defaultRegexTimeout
169
- }
170
-
171
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
172
- defer cancel()
173
-
174
- resultChan := make(chan []string, 1)
175
- errChan := make(chan error, 1)
176
-
177
- go func() {
178
- defer func() {
179
- if r := recover(); r != nil {
180
- select {
181
- case errChan <- fmt.Errorf("regex find panicked: %v", r):
182
- case <-ctx.Done():
183
- return // Context cancelled, exit immediately
184
- }
185
- }
186
- }()
187
-
188
- matches := re.FindStringSubmatch(input)
189
- select {
190
- case resultChan <- matches:
191
- case <-ctx.Done():
192
- return // Context cancelled, exit immediately
193
- }
194
- }()
195
-
196
- select {
197
- case matches := <-resultChan:
198
- return matches, nil
199
- case err := <-errChan:
200
- return nil, err
201
- case <-ctx.Done():
202
- return nil, fmt.Errorf("regex find timeout after %v", timeout)
203
- }
204
-}
205
-
206
-// findStringSubmatchIndexWithTimeout runs FindStringSubmatchIndex with timeout protection
207
-func findStringSubmatchIndexWithTimeout(re *regexp.Regexp, input string, timeout time.Duration) ([]int, error) {
208
- if timeout == 0 {
209
- timeout = defaultRegexTimeout
210
- }
211
-
212
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
213
- defer cancel()
214
-
215
- resultChan := make(chan []int, 1)
216
- errChan := make(chan error, 1)
217
-
218
- go func() {
219
- defer func() {
220
- if r := recover(); r != nil {
221
- select {
222
- case errChan <- fmt.Errorf("regex find index panicked: %v", r):
223
- case <-ctx.Done():
224
- return // Context cancelled, exit immediately
225
- }
226
- }
227
- }()
228
-
229
- matches := re.FindStringSubmatchIndex(input)
230
- select {
231
- case resultChan <- matches:
232
- case <-ctx.Done():
233
- return // Context cancelled, exit immediately
234
- }
235
- }()
236
-
237
- select {
238
- case matches := <-resultChan:
239
- return matches, nil
240
- case err := <-errChan:
241
- return nil, err
242
- case <-ctx.Done():
243
- return nil, fmt.Errorf("regex find index timeout after %v", timeout)
244
- }
245
-}
246
-
247
-// ClearRegexCache clears the global regex cache (useful for testing)
248
-func ClearRegexCache() {
249
- globalRegexCache.mu.Lock()
250
- defer globalRegexCache.mu.Unlock()
251
-
252
- globalRegexCache.cache = make(map[string]*regexp.Regexp)
253
- globalRegexCache.order = make([]string, 0, regexCacheSize)
254
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/regex_safe_test.go
deleted
-316
@@ -1,316 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "regexp"
5
- "strings"
6
- "testing"
7
- "time"
8
-)
9
-
10
-// Test constants for regex pattern limits (used only in tests)
11
-const testMaxRegexPatternLength = 1000
12
-
13
-// TestRegexComplexityValidation tests that overly complex patterns are rejected
14
-func TestRegexComplexityValidation(t *testing.T) {
15
- tests := []struct {
16
- name string
17
- pattern string
18
- maxPatternLength int
19
- wantErr bool
20
- }{
21
- {
22
- name: "simple pattern",
23
- pattern: "hello.*world",
24
- maxPatternLength: 0, // no limit
25
- wantErr: false,
26
- },
27
- {
28
- name: "pattern too long with limit",
29
- pattern: strings.Repeat("a", testMaxRegexPatternLength+1),
30
- maxPatternLength: testMaxRegexPatternLength,
31
- wantErr: true,
32
- },
33
- {
34
- name: "pattern ok when no limit",
35
- pattern: strings.Repeat("a", testMaxRegexPatternLength+1),
36
- maxPatternLength: 0, // no limit
37
- wantErr: false,
38
- },
39
- {
40
- name: "excessive nesting",
41
- pattern: strings.Repeat("(", maxRegexNestingDepth+1) + strings.Repeat(")", maxRegexNestingDepth+1),
42
- maxPatternLength: 0,
43
- wantErr: true,
44
- },
45
- {
46
- name: "unbalanced parentheses - too many open",
47
- pattern: "((hello",
48
- maxPatternLength: 0,
49
- wantErr: true,
50
- },
51
- {
52
- name: "unbalanced parentheses - too many close",
53
- pattern: "hello))",
54
- maxPatternLength: 0,
55
- wantErr: true,
56
- },
57
- {
58
- name: "acceptable nesting",
59
- pattern: strings.Repeat("(", maxRegexNestingDepth) + strings.Repeat(")", maxRegexNestingDepth),
60
- maxPatternLength: 0,
61
- wantErr: false,
62
- },
63
- }
64
-
65
- for _, tt := range tests {
66
- t.Run(tt.name, func(t *testing.T) {
67
- err := validateRegexComplexity(tt.pattern, tt.maxPatternLength)
68
- if (err != nil) != tt.wantErr {
69
- t.Errorf("validateRegexComplexity() error = %v, wantErr %v", err, tt.wantErr)
70
- }
71
- })
72
- }
73
-}
74
-
75
-// TestRegexCaching tests that compiled regexes are cached
76
-func TestRegexCaching(t *testing.T) {
77
- ClearRegexCache()
78
-
79
- pattern := "test.*pattern"
80
-
81
- // First compilation
82
- re1, err := compileRegexSafe(pattern, 0)
83
- if err != nil {
84
- t.Fatalf("compileRegexSafe() error = %v", err)
85
- }
86
-
87
- // Second compilation should return cached instance
88
- re2, err := compileRegexSafe(pattern, 0)
89
- if err != nil {
90
- t.Fatalf("compileRegexSafe() error = %v", err)
91
- }
92
-
93
- // Should be the exact same pointer (cached)
94
- if re1 != re2 {
95
- t.Errorf("Expected cached regex to be same instance, got different instances")
96
- }
97
-}
98
-
99
-// TestRegexCacheEviction tests LRU cache eviction
100
-func TestRegexCacheEviction(t *testing.T) {
101
- ClearRegexCache()
102
-
103
- // Fill cache to capacity
104
- for i := 0; i < regexCacheSize; i++ {
105
- pattern := regexp.QuoteMeta(strings.Repeat("a", i+1))
106
- _, err := compileRegexSafe(pattern, 0)
107
- if err != nil {
108
- t.Fatalf("compileRegexSafe() error = %v", err)
109
- }
110
- }
111
-
112
- // Cache should be at capacity
113
- if len(globalRegexCache.cache) != regexCacheSize {
114
- t.Errorf("Expected cache size %d, got %d", regexCacheSize, len(globalRegexCache.cache))
115
- }
116
-
117
- // Add one more - should evict oldest
118
- newPattern := regexp.QuoteMeta(strings.Repeat("z", 200))
119
- _, err := compileRegexSafe(newPattern, 0)
120
- if err != nil {
121
- t.Fatalf("compileRegexSafe() error = %v", err)
122
- }
123
-
124
- // Cache should still be at capacity
125
- if len(globalRegexCache.cache) != regexCacheSize {
126
- t.Errorf("Expected cache size %d after eviction, got %d", regexCacheSize, len(globalRegexCache.cache))
127
- }
128
-
129
- // First pattern should be evicted
130
- firstPattern := regexp.QuoteMeta("a")
131
- _, exists := globalRegexCache.cache[firstPattern]
132
- if exists {
133
- t.Errorf("Expected oldest pattern to be evicted, but it's still in cache")
134
- }
135
-}
136
-
137
-// TestMatchWithTimeout tests that slow regex operations timeout
138
-func TestMatchWithTimeout(t *testing.T) {
139
- // Simple pattern that should complete quickly
140
- re := regexp.MustCompile("hello")
141
-
142
- // Should complete successfully
143
- matched, err := matchWithTimeout(re, "hello world", 100*time.Millisecond)
144
- if err != nil {
145
- t.Errorf("matchWithTimeout() error = %v", err)
146
- }
147
- if !matched {
148
- t.Errorf("Expected match, got no match")
149
- }
150
-}
151
-
152
-// TestRegexSubstituteWithSafeRegex tests regexSubstitute uses safe regex
153
-func TestRegexSubstituteWithSafeRegex(t *testing.T) {
154
- ClearRegexCache()
155
-
156
- value := Value{Data: "hello world", Type: ValueTypeStr}
157
- params := "world\nuniverse"
158
-
159
- result, err := regexSubstitute(value, params)
160
- if err != nil {
161
- t.Fatalf("regexSubstitute() error = %v", err)
162
- }
163
-
164
- if result.Data != "universe" {
165
- t.Errorf("Expected 'universe', got '%s'", result.Data)
166
- }
167
-
168
- // Verify pattern was cached
169
- pattern := "world"
170
- _, exists := globalRegexCache.cache[pattern]
171
- if !exists {
172
- t.Errorf("Expected pattern to be cached")
173
- }
174
-}
175
-
176
-// TestValidateRegexWithSafeRegex tests validateRegex uses safe regex
177
-func TestValidateRegexWithSafeRegex(t *testing.T) {
178
- ClearRegexCache()
179
-
180
- value := Value{Data: "test123", Type: ValueTypeStr}
181
- pattern := "^test[0-9]+$"
182
-
183
- result, err := validateRegex(value, pattern)
184
- if err != nil {
185
- t.Fatalf("validateRegex() error = %v", err)
186
- }
187
-
188
- if result.Data != value.Data {
189
- t.Errorf("Expected value unchanged, got '%s'", result.Data)
190
- }
191
-
192
- // Verify pattern was cached
193
- _, exists := globalRegexCache.cache[pattern]
194
- if !exists {
195
- t.Errorf("Expected pattern to be cached")
196
- }
197
-}
198
-
199
-// TestValidateNotRegexWithSafeRegex tests validateNotRegex uses safe regex
200
-func TestValidateNotRegexWithSafeRegex(t *testing.T) {
201
- ClearRegexCache()
202
-
203
- value := Value{Data: "hello", Type: ValueTypeStr}
204
- pattern := "^[0-9]+$"
205
-
206
- result, err := validateNotRegex(value, pattern)
207
- if err != nil {
208
- t.Fatalf("validateNotRegex() error = %v", err)
209
- }
210
-
211
- if result.Data != value.Data {
212
- t.Errorf("Expected value unchanged, got '%s'", result.Data)
213
- }
214
-
215
- // Verify pattern was cached
216
- _, exists := globalRegexCache.cache[pattern]
217
- if !exists {
218
- t.Errorf("Expected pattern to be cached")
219
- }
220
-}
221
-
222
-// TestErrorFieldRegexWithSafeRegex tests errorFieldRegex uses safe regex
223
-func TestErrorFieldRegexWithSafeRegex(t *testing.T) {
224
- ClearRegexCache()
225
-
226
- value := Value{Data: "ERROR: test failed", Type: ValueTypeStr}
227
- params := "ERROR: (.+)\n$1"
228
-
229
- _, err := errorFieldRegex(value, params)
230
- if err == nil {
231
- t.Fatal("Expected error, got nil")
232
- }
233
-
234
- if err.Error() != "test failed" {
235
- t.Errorf("Expected 'test failed', got '%s'", err.Error())
236
- }
237
-
238
- // Verify pattern was cached
239
- pattern := "ERROR: (.+)"
240
- _, exists := globalRegexCache.cache[pattern]
241
- if !exists {
242
- t.Errorf("Expected pattern to be cached")
243
- }
244
-}
245
-
246
-// TestRegexProtectionAgainstComplexPatterns tests that complex patterns are rejected
247
-func TestRegexProtectionAgainstComplexPatterns(t *testing.T) {
248
- tests := []struct {
249
- name string
250
- pattern string
251
- input string
252
- wantErr bool
253
- }{
254
- {
255
- name: "simple valid pattern",
256
- pattern: "^[a-z]+$",
257
- input: "hello",
258
- wantErr: false,
259
- },
260
- {
261
- name: "long pattern ok with no limit",
262
- pattern: ".*" + strings.Repeat("a", testMaxRegexPatternLength) + ".*", // Valid long pattern
263
- input: strings.Repeat("a", testMaxRegexPatternLength), // Input that matches
264
- wantErr: false, // No limit by default
265
- },
266
- {
267
- name: "excessive nesting depth",
268
- pattern: strings.Repeat("(a", maxRegexNestingDepth+1) + strings.Repeat(")", maxRegexNestingDepth+1),
269
- input: "aaaa",
270
- wantErr: true,
271
- },
272
- }
273
-
274
- for _, tt := range tests {
275
- t.Run(tt.name, func(t *testing.T) {
276
- value := Value{Data: tt.input, Type: ValueTypeStr}
277
- _, err := validateRegex(value, tt.pattern)
278
-
279
- if (err != nil) != tt.wantErr {
280
- t.Errorf("validateRegex() error = %v, wantErr %v", err, tt.wantErr)
281
- }
282
- })
283
- }
284
-}
285
-
286
-// TestConcurrentRegexCacheAccess tests thread safety of regex cache
287
-func TestConcurrentRegexCacheAccess(t *testing.T) {
288
- ClearRegexCache()
289
-
290
- // Launch multiple goroutines that compile the same pattern
291
- pattern := "test.*concurrent"
292
- done := make(chan bool, 10)
293
-
294
- for i := 0; i < 10; i++ {
295
- go func() {
296
- for j := 0; j < 100; j++ {
297
- _, err := compileRegexSafe(pattern, 0)
298
- if err != nil {
299
- t.Errorf("compileRegexSafe() error = %v", err)
300
- }
301
- }
302
- done <- true
303
- }()
304
- }
305
-
306
- // Wait for all goroutines to complete
307
- for i := 0; i < 10; i++ {
308
- <-done
309
- }
310
-
311
- // Pattern should be in cache exactly once
312
- _, exists := globalRegexCache.cache[pattern]
313
- if !exists {
314
- t.Errorf("Expected pattern to be cached")
315
- }
316
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/regression_test.go
deleted
-509
@@ -1,509 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// TestRegressionIsErrorPropagation tests that IsError flag is preserved through pipeline.
10
-// Issue: ExecutePipeline was resetting IsError to false, breaking validateNotSupported.
11
-func TestRegressionIsErrorPropagation(t *testing.T) {
12
- p := NewPreprocessor("test-shard")
13
-
14
- tests := []struct {
15
- name string
16
- inputData string
17
- inputError bool // IsError flag on input
18
- steps []Step
19
- expectError bool // Should return error?
20
- expectData string // Expected output data (if no error)
21
- }{
22
- {
23
- name: "Error input passes through unchanged when no error-handling step",
24
- inputData: "previous error message",
25
- inputError: true,
26
- steps: []Step{
27
- {Type: StepTypeTrim, Params: " "},
28
- },
29
- expectError: false,
30
- expectData: "previous error message", // Trim works on error message string
31
- },
32
- {
33
- name: "ValidateNotSupported sees IsError flag",
34
- inputData: "ZBX_NOTSUPPORTED: Agent is not available",
35
- inputError: true,
36
- steps: []Step{
37
- {Type: StepTypeValidateNotSupported, Params: "\nAgent is not available"},
38
- },
39
- expectError: true, // Pattern matches, so error is "supported" -> fail
40
- },
41
- {
42
- name: "IsError preserved through multiple successful steps",
43
- inputData: "error data",
44
- inputError: true,
45
- steps: []Step{
46
- {Type: StepTypeTrim, Params: " "},
47
- {Type: StepTypeStringReplace, Params: "data\ninfo"},
48
- {Type: StepTypeTrim, Params: " "},
49
- },
50
- expectError: false,
51
- expectData: "error info", // Processed but IsError flag should be preserved
52
- },
53
- {
54
- name: "Normal value not treated as error",
55
- inputData: "normal value",
56
- inputError: false,
57
- steps: []Step{
58
- {Type: StepTypeTrim, Params: " "},
59
- },
60
- expectError: false,
61
- expectData: "normal value",
62
- },
63
- }
64
-
65
- for _, tt := range tests {
66
- t.Run(tt.name, func(t *testing.T) {
67
- input := Value{
68
- Data: tt.inputData,
69
- Type: ValueTypeStr,
70
- Timestamp: time.Now(),
71
- IsError: tt.inputError,
72
- }
73
-
74
- result, err := p.ExecutePipeline("item1", input, tt.steps)
75
-
76
- if tt.expectError {
77
- if err == nil {
78
- t.Error("Expected error, got nil")
79
- }
80
- } else {
81
- if err != nil {
82
- t.Errorf("Expected no error, got: %v", err)
83
- }
84
- if len(result.Metrics) > 0 && result.Metrics[0].Value != tt.expectData {
85
- t.Errorf("Expected data %q, got %q", tt.expectData, result.Metrics[0].Value)
86
- }
87
- }
88
- })
89
- }
90
-}
91
-
92
-// TestRegressionPrometheusEscapedQuotes tests proper handling of escaped characters in labels.
93
-// Issue: Regex-based parsing failed on escaped quotes like path="/foo\"bar".
94
-func TestRegressionPrometheusEscapedQuotes(t *testing.T) {
95
- tests := []struct {
96
- name string
97
- input string
98
- labelKey string
99
- labelValue string
100
- }{
101
- {
102
- name: "Escaped double quote in label value",
103
- input: `http_requests{path="/foo\"bar"} 100`,
104
- labelKey: "path",
105
- labelValue: `/foo"bar`,
106
- },
107
- {
108
- name: "Escaped backslash in label value",
109
- input: `file_size{path="C:\\Users\\Admin"} 200`,
110
- labelKey: "path",
111
- labelValue: `C:\Users\Admin`,
112
- },
113
- {
114
- name: "Escaped newline in label value",
115
- input: `message{text="line1\nline2"} 50`,
116
- labelKey: "text",
117
- labelValue: "line1\nline2",
118
- },
119
- {
120
- name: "Multiple escapes in single label",
121
- input: `complex{data="quote\"slash\\newline\n"} 75`,
122
- labelKey: "data",
123
- labelValue: "quote\"slash\\newline\n",
124
- },
125
- {
126
- name: "Mixed normal and escaped labels",
127
- input: `metric{method="GET",path="/test\"path",code="200"} 300`,
128
- labelKey: "path",
129
- labelValue: `/test"path`,
130
- },
131
- }
132
-
133
- for _, tt := range tests {
134
- t.Run(tt.name, func(t *testing.T) {
135
- metrics, err := parsePrometheusText(tt.input)
136
- if err != nil {
137
- t.Fatalf("Failed to parse: %v", err)
138
- }
139
- if len(metrics) == 0 {
140
- t.Fatal("No metrics parsed")
141
- }
142
-
143
- metric := metrics[0]
144
- if val, ok := metric.Labels[tt.labelKey]; !ok {
145
- t.Errorf("Label %q not found in parsed metric", tt.labelKey)
146
- } else if val != tt.labelValue {
147
- t.Errorf("Label %q value mismatch:\nExpected: %q\nGot: %q", tt.labelKey, tt.labelValue, val)
148
- }
149
- })
150
- }
151
-}
152
-
153
-// TestRegressionPrometheusLabelOrder tests that label order is preserved.
154
-func TestRegressionPrometheusLabelOrder(t *testing.T) {
155
- input := `metric{first="1",second="2",third="3"} 100`
156
- metrics, err := parsePrometheusText(input)
157
- if err != nil {
158
- t.Fatalf("Failed to parse: %v", err)
159
- }
160
- if len(metrics) == 0 {
161
- t.Fatal("No metrics parsed")
162
- }
163
-
164
- expected := []string{"first", "second", "third"}
165
- if len(metrics[0].LabelOrder) != len(expected) {
166
- t.Fatalf("Label order length mismatch: expected %d, got %d", len(expected), len(metrics[0].LabelOrder))
167
- }
168
-
169
- for i, key := range expected {
170
- if metrics[0].LabelOrder[i] != key {
171
- t.Errorf("Label order[%d] mismatch: expected %q, got %q", i, key, metrics[0].LabelOrder[i])
172
- }
173
- }
174
-}
175
-
176
-// TestRegressionSNMPTextualOIDSupport tests that textual OIDs are properly translated.
177
-// Issue: snmpWalkToJSON only did direct map lookup, not full translateMIB.
178
-func TestRegressionSNMPTextualOIDSupport(t *testing.T) {
179
- tests := []struct {
180
- name string
181
- oid string
182
- expectedOK bool
183
- expected string // Expected numeric OID (if OK)
184
- }{
185
- {
186
- name: "IF-MIB::ifDescr translates correctly",
187
- oid: "IF-MIB::ifDescr",
188
- expectedOK: true,
189
- expected: ".1.3.6.1.2.1.2.2.1.2",
190
- },
191
- {
192
- name: "IF-MIB::ifIndex translates correctly",
193
- oid: "IF-MIB::ifIndex",
194
- expectedOK: true,
195
- expected: ".1.3.6.1.2.1.2.2.1.1",
196
- },
197
- {
198
- name: "SNMPv2-MIB::sysDescr translates correctly",
199
- oid: "SNMPv2-MIB::sysDescr",
200
- expectedOK: true,
201
- expected: ".1.3.6.1.2.1.1.1",
202
- },
203
- {
204
- name: "HOST-RESOURCES-MIB::hrStorageDescr translates correctly",
205
- oid: "HOST-RESOURCES-MIB::hrStorageDescr",
206
- expectedOK: true,
207
- expected: ".1.3.6.1.2.1.25.2.3.1.3",
208
- },
209
- {
210
- name: "Numeric OID passthrough",
211
- oid: ".1.3.6.1.2.1.2.2.1.2",
212
- expectedOK: true,
213
- expected: ".1.3.6.1.2.1.2.2.1.2",
214
- },
215
- {
216
- name: "Numeric OID without leading dot gets normalized",
217
- oid: "1.3.6.1.2.1.2.2.1.2",
218
- expectedOK: true,
219
- expected: "1.3.6.1.2.1.2.2.1.2", // translateMIB returns passthrough for numeric
220
- },
221
- {
222
- name: "Unknown MIB returns error",
223
- oid: "UNKNOWN-MIB::unknownOid",
224
- expectedOK: false,
225
- },
226
- }
227
-
228
- for _, tt := range tests {
229
- t.Run(tt.name, func(t *testing.T) {
230
- result, err := translateMIB(tt.oid)
231
- if tt.expectedOK {
232
- if err != nil {
233
- t.Errorf("Expected successful translation, got error: %v", err)
234
- }
235
- if result != tt.expected {
236
- t.Errorf("OID translation mismatch:\nExpected: %s\nGot: %s", tt.expected, result)
237
- }
238
- } else {
239
- if err == nil {
240
- t.Error("Expected error for unknown MIB, got nil")
241
- }
242
- }
243
- })
244
- }
245
-}
246
-
247
-// TestRegressionSNMPWalkToJSONUsesTranslateMIB tests that snmpWalkToJSON uses full translateMIB.
248
-func TestRegressionSNMPWalkToJSONUsesTranslateMIB(t *testing.T) {
249
- p := NewPreprocessor("test-shard")
250
-
251
- // SNMP walk data using numeric OIDs
252
- snmpData := `.1.3.6.1.2.1.2.2.1.1.1 = INTEGER: 1
253
-.1.3.6.1.2.1.2.2.1.1.2 = INTEGER: 2
254
-.1.3.6.1.2.1.2.2.1.2.1 = STRING: "eth0"
255
-.1.3.6.1.2.1.2.2.1.2.2 = STRING: "eth1"`
256
-
257
- // Use textual OID in params - this should work now
258
- params := `{#IFINDEX}
259
-IF-MIB::ifIndex
260
-0
261
-{#IFDESCR}
262
-IF-MIB::ifDescr
263
-0`
264
-
265
- input := Value{
266
- Data: snmpData,
267
- Type: ValueTypeStr,
268
- }
269
-
270
- step := Step{
271
- Type: StepTypeSNMPWalkToJSON,
272
- Params: params,
273
- }
274
-
275
- result, err := p.Execute("item1", input, step)
276
- if err != nil {
277
- t.Fatalf("SNMP walk to JSON failed: %v", err)
278
- }
279
-
280
- if len(result.Metrics) == 0 {
281
- t.Fatal("No metrics returned")
282
- }
283
-
284
- // The result should contain JSON with both indices
285
- output := result.Metrics[0].Value
286
- if !strings.Contains(output, `"eth0"`) || !strings.Contains(output, `"eth1"`) {
287
- t.Errorf("Expected output to contain interface descriptions, got: %s", output)
288
- }
289
- if !strings.Contains(output, `"{#IFINDEX}"`) || !strings.Contains(output, `"{#IFDESCR}"`) {
290
- t.Errorf("Expected output to contain macro names, got: %s", output)
291
- }
292
-}
293
-
294
-// TestRegressionJavaScriptVMIsolation tests that JS VMs are isolated between executions.
295
-// Issue: VM pooling with resetVM didn't clear prototype modifications.
296
-func TestRegressionJavaScriptVMIsolation(t *testing.T) {
297
- // First execution: modify Object prototype
298
- script1 := `
299
- Object.prototype.polluted = "malicious";
300
- return "done";
301
- `
302
- result1, err := javascriptExecute(Value{Data: "test"}, script1, DefaultLimits().JavaScript)
303
- if err != nil {
304
- t.Fatalf("First JS execution failed: %v", err)
305
- }
306
- if result1.Data != "done" {
307
- t.Errorf("Expected 'done', got %q", result1.Data)
308
- }
309
-
310
- // Second execution: check if prototype pollution persists
311
- script2 := `
312
- // If VMs were pooled without proper isolation, this would return "malicious"
313
- var obj = {};
314
- if (obj.polluted !== undefined) {
315
- return "POLLUTION DETECTED: " + obj.polluted;
316
- }
317
- return "clean";
318
- `
319
- result2, err := javascriptExecute(Value{Data: "test"}, script2, DefaultLimits().JavaScript)
320
- if err != nil {
321
- t.Fatalf("Second JS execution failed: %v", err)
322
- }
323
- if result2.Data != "clean" {
324
- t.Errorf("VM isolation failed! Got: %q", result2.Data)
325
- }
326
-}
327
-
328
-// TestRegressionJavaScriptGlobalVariableIsolation tests that global variables don't leak.
329
-func TestRegressionJavaScriptGlobalVariableIsolation(t *testing.T) {
330
- // First execution: define global variable
331
- script1 := `
332
- globalVar = "secret";
333
- return globalVar;
334
- `
335
- result1, err := javascriptExecute(Value{Data: "test"}, script1, DefaultLimits().JavaScript)
336
- if err != nil {
337
- t.Fatalf("First JS execution failed: %v", err)
338
- }
339
- if result1.Data != "secret" {
340
- t.Errorf("Expected 'secret', got %q", result1.Data)
341
- }
342
-
343
- // Second execution: check if global variable persists
344
- script2 := `
345
- if (typeof globalVar !== 'undefined') {
346
- return "LEAK DETECTED: " + globalVar;
347
- }
348
- return "isolated";
349
- `
350
- result2, err := javascriptExecute(Value{Data: "test"}, script2, DefaultLimits().JavaScript)
351
- if err != nil {
352
- t.Fatalf("Second JS execution failed: %v", err)
353
- }
354
- if result2.Data != "isolated" {
355
- t.Errorf("Global variable leaked! Got: %q", result2.Data)
356
- }
357
-}
358
-
359
-// TestRegressionPipelineDiscardedFlag tests that pipeline explicitly flags discarded values.
360
-// Issue: Pipeline returned Result{} with nil error, hiding discard vs misconfiguration.
361
-func TestRegressionPipelineDiscardedFlag(t *testing.T) {
362
- p := NewPreprocessor("test-shard")
363
-
364
- // Test 1: Normal successful pipeline - not discarded
365
- input1 := Value{
366
- Data: "100",
367
- Type: ValueTypeStr,
368
- Timestamp: time.Now(),
369
- }
370
-
371
- steps := []Step{
372
- {Type: StepTypeTrim, Params: " "},
373
- }
374
-
375
- result1, err := p.ExecutePipeline("item1", input1, steps)
376
- if err != nil {
377
- t.Fatalf("Normal execution failed: %v", err)
378
- }
379
- if result1.Discarded {
380
- t.Error("Normal result should not be discarded")
381
- }
382
- if len(result1.Metrics) == 0 {
383
- t.Error("Normal result should have metrics")
384
- }
385
-
386
- // Test 2: Error with ErrorActionDiscard - returns empty string, not discarded
387
- // Zabbix behavior: discard means replace error with empty string value
388
- input2 := Value{
389
- Data: "invalid",
390
- Type: ValueTypeStr,
391
- Timestamp: time.Now(),
392
- }
393
-
394
- stepsDiscard := []Step{
395
- {
396
- Type: StepTypeMultiplier,
397
- Params: "2",
398
- ErrorHandler: ErrorHandler{
399
- Action: ErrorActionDiscard,
400
- },
401
- },
402
- }
403
-
404
- result2, err := p.ExecutePipeline("item2", input2, stepsDiscard)
405
- if err != nil {
406
- t.Fatalf("Discard execution failed: %v", err)
407
- }
408
- // ErrorActionDiscard returns empty string, not zero metrics
409
- if result2.Discarded {
410
- t.Error("ErrorActionDiscard should return empty string, not discard flag")
411
- }
412
- if len(result2.Metrics) == 0 {
413
- t.Error("ErrorActionDiscard should return metrics (with empty value)")
414
- }
415
- if len(result2.Metrics) > 0 && result2.Metrics[0].Value != "" {
416
- t.Errorf("ErrorActionDiscard should return empty string, got %q", result2.Metrics[0].Value)
417
- }
418
-
419
- // Test 3: Empty Result detection - Result.Discarded flag is for when step returns 0 metrics
420
- // This can happen with multi-metric steps or certain edge cases
421
- // The flag allows callers to distinguish empty result from error
422
- if result1.Discarded || result2.Discarded {
423
- t.Error("Neither normal nor discard results should have Discarded=true")
424
- }
425
-}
426
-
427
-// TestRegressionSNMPTextualWalkOutput tests that SNMP walk output with textual OIDs is parsed.
428
-// Issue: parsers skipped lines not starting with '.', rejecting IF-MIB::ifDescr.1 format.
429
-func TestRegressionSNMPTextualWalkOutput(t *testing.T) {
430
- p := NewPreprocessor("test-shard")
431
-
432
- // SNMP walk data using textual OIDs (real snmpwalk output without -On flag)
433
- snmpData := `IF-MIB::ifIndex.1 = INTEGER: 1
434
-IF-MIB::ifIndex.2 = INTEGER: 2
435
-IF-MIB::ifDescr.1 = STRING: "eth0"
436
-IF-MIB::ifDescr.2 = STRING: "eth1"
437
-IF-MIB::ifSpeed.1 = Gauge32: 1000000000
438
-IF-MIB::ifSpeed.2 = Gauge32: 10000000000`
439
-
440
- // Use textual OIDs in params
441
- params := `{#IFINDEX}
442
-IF-MIB::ifIndex
443
-0
444
-{#IFDESCR}
445
-IF-MIB::ifDescr
446
-0`
447
-
448
- input := Value{
449
- Data: snmpData,
450
- Type: ValueTypeStr,
451
- }
452
-
453
- step := Step{
454
- Type: StepTypeSNMPWalkToJSON,
455
- Params: params,
456
- }
457
-
458
- result, err := p.Execute("item1", input, step)
459
- if err != nil {
460
- t.Fatalf("SNMP walk to JSON failed with textual OID output: %v", err)
461
- }
462
-
463
- if len(result.Metrics) == 0 {
464
- t.Fatal("No metrics returned from textual SNMP walk output")
465
- }
466
-
467
- output := result.Metrics[0].Value
468
- // Verify both interfaces were parsed
469
- if !strings.Contains(output, `"eth0"`) {
470
- t.Errorf("Expected 'eth0' in output, got: %s", output)
471
- }
472
- if !strings.Contains(output, `"eth1"`) {
473
- t.Errorf("Expected 'eth1' in output, got: %s", output)
474
- }
475
- if !strings.Contains(output, `"{#IFINDEX}"`) {
476
- t.Errorf("Expected macro {#IFINDEX} in output, got: %s", output)
477
- }
478
-}
479
-
480
-// TestRegressionJavaScriptArrayPrototypeIsolation tests Array.prototype isolation.
481
-func TestRegressionJavaScriptArrayPrototypeIsolation(t *testing.T) {
482
- // First execution: modify Array prototype
483
- script1 := `
484
- Array.prototype.myMethod = function() { return "injected"; };
485
- return [].myMethod();
486
- `
487
- result1, err := javascriptExecute(Value{Data: "test"}, script1, DefaultLimits().JavaScript)
488
- if err != nil {
489
- t.Fatalf("First JS execution failed: %v", err)
490
- }
491
- if result1.Data != "injected" {
492
- t.Errorf("Expected 'injected', got %q", result1.Data)
493
- }
494
-
495
- // Second execution: check if Array prototype modification persists
496
- script2 := `
497
- if (typeof [].myMethod === 'function') {
498
- return "ARRAY POLLUTION: " + [].myMethod();
499
- }
500
- return "array_clean";
501
- `
502
- result2, err := javascriptExecute(Value{Data: "test"}, script2, DefaultLimits().JavaScript)
503
- if err != nil {
504
- t.Fatalf("Second JS execution failed: %v", err)
505
- }
506
- if result2.Data != "array_clean" {
507
- t.Errorf("Array.prototype leaked! Got: %q", result2.Data)
508
- }
509
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/snmp_mib_test.go
deleted
-256
@@ -1,256 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "sync"
6
- "testing"
7
-)
8
-
9
-// TestTranslateMIB_Hardcoded tests translation using hardcoded map
10
-func TestTranslateMIB_Hardcoded(t *testing.T) {
11
- tests := []struct {
12
- name string
13
- mibName string
14
- expected string
15
- }{
16
- {"SNMPv2-MIB sysDescr", "SNMPv2-MIB::sysDescr", ".1.3.6.1.2.1.1.1"},
17
- {"SNMPv2-MIB sysUpTime", "SNMPv2-MIB::sysUpTime", ".1.3.6.1.2.1.1.3"},
18
- {"IF-MIB ifDescr", "IF-MIB::ifDescr", ".1.3.6.1.2.1.2.2.1.2"},
19
- {"IF-MIB ifOperStatus", "IF-MIB::ifOperStatus", ".1.3.6.1.2.1.2.2.1.8"},
20
- {"IF-MIB ifHCInOctets", "IF-MIB::ifHCInOctets", ".1.3.6.1.2.1.31.1.1.1.6"},
21
- {"HOST-RESOURCES hrSystemUptime", "HOST-RESOURCES-MIB::hrSystemUptime", ".1.3.6.1.2.1.25.1.1"},
22
- {"HOST-RESOURCES hrStorageDescr", "HOST-RESOURCES-MIB::hrStorageDescr", ".1.3.6.1.2.1.25.2.3.1.3"},
23
- {"HOST-RESOURCES hrProcessorLoad", "HOST-RESOURCES-MIB::hrProcessorLoad", ".1.3.6.1.2.1.25.3.3.1.2"},
24
- {"IP-MIB ipForwarding", "IP-MIB::ipForwarding", ".1.3.6.1.2.1.4.1"},
25
- {"TCP-MIB tcpCurrEstab", "TCP-MIB::tcpCurrEstab", ".1.3.6.1.2.1.6.9"},
26
- {"UDP-MIB udpInDatagrams", "UDP-MIB::udpInDatagrams", ".1.3.6.1.2.1.7.1"},
27
- }
28
-
29
- for _, tt := range tests {
30
- t.Run(tt.name, func(t *testing.T) {
31
- result, err := translateMIB(tt.mibName)
32
- if err != nil {
33
- t.Fatalf("translateMIB(%q) error: %v", tt.mibName, err)
34
- }
35
- if result != tt.expected {
36
- t.Errorf("translateMIB(%q) = %q, expected %q", tt.mibName, result, tt.expected)
37
- }
38
- })
39
- }
40
-}
41
-
42
-// TestTranslateMIB_NumericOID tests that numeric OIDs pass through unchanged
43
-func TestTranslateMIB_NumericOID(t *testing.T) {
44
- tests := []struct {
45
- name string
46
- oid string
47
- expected string
48
- }{
49
- {"With leading dot", ".1.3.6.1.2.1.1.1", ".1.3.6.1.2.1.1.1"},
50
- {"Without leading dot", "1.3.6.1.2.1.1.1", "1.3.6.1.2.1.1.1"},
51
- {"Short OID", ".1.3.6.1", ".1.3.6.1"},
52
- {"Single digit", ".1", ".1"},
53
- {"Complex OID", ".1.3.6.1.2.1.2.2.1.10.1", ".1.3.6.1.2.1.2.2.1.10.1"},
54
- }
55
-
56
- for _, tt := range tests {
57
- t.Run(tt.name, func(t *testing.T) {
58
- result, err := translateMIB(tt.oid)
59
- if err != nil {
60
- t.Fatalf("translateMIB(%q) error: %v", tt.oid, err)
61
- }
62
- if result != tt.expected {
63
- t.Errorf("translateMIB(%q) = %q, expected %q (should pass through)", tt.oid, result, tt.expected)
64
- }
65
- })
66
- }
67
-}
68
-
69
-// TestTranslateMIB_UnknownMIB tests error handling for unknown MIBs
70
-func TestTranslateMIB_UnknownMIB(t *testing.T) {
71
- // Clear cache to ensure fresh test
72
- mibTranslationCacheMu.Lock()
73
- mibTranslationCache = make(map[string]string)
74
- mibTranslationCacheMu.Unlock()
75
-
76
- // Unknown MIB that's not in hardcoded map
77
- _, err := translateMIB("UNKNOWN-MIB::unknownObject")
78
- if err == nil {
79
- t.Error("Expected error for unknown MIB, got nil")
80
- }
81
-
82
- expectedErrMsg := "unknown MIB: UNKNOWN-MIB::unknownObject"
83
- if err != nil && err.Error()[:len(expectedErrMsg)] != expectedErrMsg {
84
- t.Errorf("Error message = %q, expected to start with %q", err.Error(), expectedErrMsg)
85
- }
86
-}
87
-
88
-// TestTranslateMIB_Cache tests that translations are cached
89
-func TestTranslateMIB_Cache(t *testing.T) {
90
- // Clear cache
91
- mibTranslationCacheMu.Lock()
92
- mibTranslationCache = make(map[string]string)
93
- mibTranslationCacheMu.Unlock()
94
-
95
- // First call - should use hardcoded map
96
- result1, err := translateMIB("SNMPv2-MIB::sysDescr")
97
- if err != nil {
98
- t.Fatalf("First call failed: %v", err)
99
- }
100
-
101
- // Second call - should still work (from hardcoded map, not cache)
102
- result2, err := translateMIB("SNMPv2-MIB::sysDescr")
103
- if err != nil {
104
- t.Fatalf("Second call failed: %v", err)
105
- }
106
-
107
- if result1 != result2 {
108
- t.Errorf("Results differ: %q vs %q", result1, result2)
109
- }
110
-
111
- // Verify cache is not polluted with hardcoded entries
112
- mibTranslationCacheMu.RLock()
113
- cacheSize := len(mibTranslationCache)
114
- mibTranslationCacheMu.RUnlock()
115
-
116
- if cacheSize > 0 {
117
- t.Errorf("Cache should be empty (hardcoded entries shouldn't be cached), got %d entries", cacheSize)
118
- }
119
-}
120
-
121
-// TestTranslateMIB_ConcurrentAccess tests thread safety
122
-func TestTranslateMIB_ConcurrentAccess(t *testing.T) {
123
- // Clear cache
124
- mibTranslationCacheMu.Lock()
125
- mibTranslationCache = make(map[string]string)
126
- mibTranslationCacheMu.Unlock()
127
-
128
- mibs := []string{
129
- "SNMPv2-MIB::sysDescr",
130
- "IF-MIB::ifDescr",
131
- "HOST-RESOURCES-MIB::hrSystemUptime",
132
- "IP-MIB::ipForwarding",
133
- "TCP-MIB::tcpCurrEstab",
134
- ".1.3.6.1.2.1.1.1",
135
- ".1.3.6.1.2.1.2.2.1.2",
136
- }
137
-
138
- var wg sync.WaitGroup
139
- goroutines := 50
140
- iterations := 20
141
-
142
- for i := 0; i < goroutines; i++ {
143
- wg.Add(1)
144
- go func(id int) {
145
- defer wg.Done()
146
- for j := 0; j < iterations; j++ {
147
- mib := mibs[j%len(mibs)]
148
- _, err := translateMIB(mib)
149
- // Only numeric OIDs and hardcoded MIBs should succeed
150
- if err != nil && !strings.HasPrefix(mib, ".") && mibToOID[mib] == "" {
151
- // Unknown MIB - error expected
152
- continue
153
- }
154
- if err != nil {
155
- t.Errorf("goroutine %d iteration %d: translateMIB(%q) failed: %v", id, j, mib, err)
156
- }
157
- }
158
- }(i)
159
- }
160
-
161
- wg.Wait()
162
-}
163
-
164
-// TestTranslateMIB_CacheSizeLimit tests that cache respects size limit
165
-func TestTranslateMIB_CacheSizeLimit(t *testing.T) {
166
- // Save original limit
167
- originalLimit := mibCacheMaxSize
168
- defer func() { mibCacheMaxSize = originalLimit }()
169
-
170
- // Set small limit for testing
171
- mibCacheMaxSize = 5
172
-
173
- // Clear cache
174
- mibTranslationCacheMu.Lock()
175
- mibTranslationCache = make(map[string]string)
176
- mibTranslationCacheMu.Unlock()
177
-
178
- // Note: This test doesn't actually populate the cache since all tested MIBs
179
- // are in the hardcoded map. To test cache limit properly, we would need
180
- // snmptranslate available and translate unknown MIBs. For now, we just
181
- // verify the cache limit constant is respected in the code.
182
-
183
- // Verify cache size limit is applied in code (checked in translateMIB function)
184
- if mibCacheMaxSize != 5 {
185
- t.Errorf("mibCacheMaxSize should be 5, got %d", mibCacheMaxSize)
186
- }
187
-}
188
-
189
-// TestParseSNMPWalk_MIBTranslation tests that parseSNMPWalk uses MIB translation
190
-//func TestParseSNMPWalk_MIBTranslation(t *testing.T) {
191
-// tests := []struct {
192
-// name string
193
-// data string
194
-// targetMIB string
195
-// expectedOID string
196
-// shouldError bool
197
-// }{
198
-// {
199
-// name: "SNMPv2-MIB sysDescr",
200
-// data: ".1.3.6.1.2.1.1.1.0 = STRING: \"Linux server 5.4.0\"",
201
-// targetMIB: "SNMPv2-MIB::sysDescr.0",
202
-// expectedOID: ".1.3.6.1.2.1.1.1.0",
203
-// shouldError: false,
204
-// },
205
-// {
206
-// name: "IF-MIB ifDescr with index",
207
-// data: ".1.3.6.1.2.1.2.2.1.2.1 = STRING: \"eth0\"",
208
-// targetMIB: "IF-MIB::ifDescr.1",
209
-// expectedOID: ".1.3.6.1.2.1.2.2.1.2.1",
210
-// shouldError: false,
211
-// },
212
-// {
213
-// name: "Numeric OID",
214
-// data: ".1.3.6.1.2.1.1.1.0 = STRING: \"Test\"",
215
-// targetMIB: ".1.3.6.1.2.1.1.1.0",
216
-// expectedOID: ".1.3.6.1.2.1.1.1.0",
217
-// shouldError: false,
218
-// },
219
-// }
220
-//
221
-// for _, tt := range tests {
222
-// t.Run(tt.name, func(t *testing.T) {
223
-// result, err := parseSNMPWalk(tt.data, tt.targetMIB)
224
-// if tt.shouldError {
225
-// if err == nil {
226
-// t.Error("Expected error, got nil")
227
-// }
228
-// return
229
-// }
230
-// if err != nil {
231
-// t.Fatalf("parseSNMPWalk() error: %v", err)
232
-// }
233
-// if result.oid != tt.expectedOID {
234
-// t.Errorf("parseSNMPWalk() OID = %q, expected %q", result.oid, tt.expectedOID)
235
-// }
236
-// })
237
-// }
238
-//}
239
-
240
-// TestTranslateMIB_AllHardcodedMIBs verifies all hardcoded MIBs translate correctly
241
-func TestTranslateMIB_AllHardcodedMIBs(t *testing.T) {
242
- // Verify every entry in mibToOID map works
243
- for mibName, expectedOID := range mibToOID {
244
- result, err := translateMIB(mibName)
245
- if err != nil {
246
- t.Errorf("translateMIB(%q) error: %v", mibName, err)
247
- continue
248
- }
249
- if result != expectedOID {
250
- t.Errorf("translateMIB(%q) = %q, expected %q", mibName, result, expectedOID)
251
- }
252
- }
253
-
254
- // Report statistics
255
- t.Logf("Verified %d hardcoded MIB translations", len(mibToOID))
256
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/snmp_step.go
deleted
-1618
@@ -1,1618 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "context"
5
- "encoding/hex"
6
- "fmt"
7
- "os/exec"
8
- "sort"
9
- "strconv"
10
- "strings"
11
- "sync"
12
- "time"
13
- "unicode/utf8"
14
-)
15
-
16
-// snmpWalkToValue extracts value from SNMP walk output.
17
-// Format modes:
18
-// 0 = unchanged (return raw value as-is)
19
-// 1 = UTF-8 (decode hex-string to UTF-8 string)
20
-// 2 = MAC (format hex-string as MAC address with colons)
21
-// 3 = BITS (convert BITS to integer)
22
-func snmpWalkToValue(value Value, params string) (Value, error) {
23
- lines := strings.Split(params, "\n")
24
- if len(lines) < 2 {
25
- return Value{}, fmt.Errorf("snmp walk requires oid and format mode parameters")
26
- }
27
-
28
- targetOID := strings.TrimSpace(lines[0])
29
- formatMode := 0
30
- if len(lines) >= 2 {
31
- var err error
32
- formatMode, err = strconv.Atoi(strings.TrimSpace(lines[1]))
33
- if err != nil {
34
- return Value{}, fmt.Errorf("invalid format mode: %v", err)
35
- }
36
- }
37
-
38
- // Parse SNMP walk output
39
- extractedValue, err := parseSNMPWalk(value.Data, targetOID)
40
- if err != nil {
41
- return Value{}, err
42
- }
43
-
44
- // Apply format conversion (trim float zeros for WALK_VALUE)
45
- result, err := formatSNMPValue(extractedValue, formatMode, true)
46
- if err != nil {
47
- return Value{}, err
48
- }
49
-
50
- return Value{Data: result, Type: ValueTypeStr}, nil
51
-}
52
-
53
-// snmpEntry represents a parsed SNMP entry
54
-type snmpEntry struct {
55
- oid string
56
- valueType string
57
- value string
58
-}
59
-
60
-// Common MIB-to-OID mappings for offline operation
61
-// Covers ~95% of real-world SNMP monitoring use cases
62
-// Falls back to snmptranslate for rare MIBs (if installed)
63
-//
64
-// NOTE: This map is effectively read-only (never modified after initialization).
65
-// It is safe for concurrent access as all operations are reads (lookups).
66
-// Go maps are safe for concurrent reads without synchronization.
67
-var mibToOID = map[string]string{
68
- // ============================================================================
69
- // SNMPv2-MIB (System group) - RFC 3418
70
- // ============================================================================
71
- "SNMPv2-MIB::sysDescr": ".1.3.6.1.2.1.1.1",
72
- "SNMPv2-MIB::sysObjectID": ".1.3.6.1.2.1.1.2",
73
- "SNMPv2-MIB::sysUpTime": ".1.3.6.1.2.1.1.3",
74
- "SNMPv2-MIB::sysContact": ".1.3.6.1.2.1.1.4",
75
- "SNMPv2-MIB::sysName": ".1.3.6.1.2.1.1.5",
76
- "SNMPv2-MIB::sysLocation": ".1.3.6.1.2.1.1.6",
77
- "SNMPv2-MIB::sysServices": ".1.3.6.1.2.1.1.7",
78
- "SNMPv2-MIB::sysORLastChange": ".1.3.6.1.2.1.1.8",
79
- "SNMPv2-MIB::sysORIndex": ".1.3.6.1.2.1.1.9.1.1",
80
- "SNMPv2-MIB::sysORID": ".1.3.6.1.2.1.1.9.1.2",
81
- "SNMPv2-MIB::sysORDescr": ".1.3.6.1.2.1.1.9.1.3",
82
- "SNMPv2-MIB::sysORUpTime": ".1.3.6.1.2.1.1.9.1.4",
83
-
84
- // SNMPv2-MIB SNMP group
85
- "SNMPv2-MIB::snmpInPkts": ".1.3.6.1.2.1.11.1",
86
- "SNMPv2-MIB::snmpOutPkts": ".1.3.6.1.2.1.11.2",
87
- "SNMPv2-MIB::snmpInBadVersions": ".1.3.6.1.2.1.11.3",
88
- "SNMPv2-MIB::snmpInBadCommunityNames": ".1.3.6.1.2.1.11.4",
89
- "SNMPv2-MIB::snmpInBadCommunityUses": ".1.3.6.1.2.1.11.5",
90
- "SNMPv2-MIB::snmpInASNParseErrs": ".1.3.6.1.2.1.11.6",
91
- "SNMPv2-MIB::snmpInTooBigs": ".1.3.6.1.2.1.11.8",
92
- "SNMPv2-MIB::snmpInNoSuchNames": ".1.3.6.1.2.1.11.9",
93
- "SNMPv2-MIB::snmpInBadValues": ".1.3.6.1.2.1.11.10",
94
- "SNMPv2-MIB::snmpInReadOnlys": ".1.3.6.1.2.1.11.11",
95
- "SNMPv2-MIB::snmpInGenErrs": ".1.3.6.1.2.1.11.12",
96
- "SNMPv2-MIB::snmpInTotalReqVars": ".1.3.6.1.2.1.11.13",
97
- "SNMPv2-MIB::snmpInTotalSetVars": ".1.3.6.1.2.1.11.14",
98
- "SNMPv2-MIB::snmpInGetRequests": ".1.3.6.1.2.1.11.15",
99
- "SNMPv2-MIB::snmpInGetNexts": ".1.3.6.1.2.1.11.16",
100
- "SNMPv2-MIB::snmpInSetRequests": ".1.3.6.1.2.1.11.17",
101
- "SNMPv2-MIB::snmpInGetResponses": ".1.3.6.1.2.1.11.18",
102
- "SNMPv2-MIB::snmpInTraps": ".1.3.6.1.2.1.11.19",
103
- "SNMPv2-MIB::snmpOutTooBigs": ".1.3.6.1.2.1.11.20",
104
- "SNMPv2-MIB::snmpOutNoSuchNames": ".1.3.6.1.2.1.11.21",
105
- "SNMPv2-MIB::snmpOutBadValues": ".1.3.6.1.2.1.11.22",
106
- "SNMPv2-MIB::snmpOutGenErrs": ".1.3.6.1.2.1.11.24",
107
- "SNMPv2-MIB::snmpOutGetRequests": ".1.3.6.1.2.1.11.25",
108
- "SNMPv2-MIB::snmpOutGetNexts": ".1.3.6.1.2.1.11.26",
109
- "SNMPv2-MIB::snmpOutSetRequests": ".1.3.6.1.2.1.11.27",
110
- "SNMPv2-MIB::snmpOutGetResponses": ".1.3.6.1.2.1.11.28",
111
- "SNMPv2-MIB::snmpOutTraps": ".1.3.6.1.2.1.11.29",
112
- "SNMPv2-MIB::snmpEnableAuthenTraps": ".1.3.6.1.2.1.11.30",
113
- "SNMPv2-MIB::snmpSilentDrops": ".1.3.6.1.2.1.11.31",
114
- "SNMPv2-MIB::snmpProxyDrops": ".1.3.6.1.2.1.11.32",
115
-
116
- // ============================================================================
117
- // IF-MIB (Interfaces) - RFC 2863
118
- // ============================================================================
119
- // Interface counters (.1.3.6.1.2.1.2.x)
120
- "IF-MIB::ifNumber": ".1.3.6.1.2.1.2.1",
121
-
122
- // Legacy interfaces table (.1.3.6.1.2.1.2.2.1.x)
123
- "IF-MIB::ifIndex": ".1.3.6.1.2.1.2.2.1.1",
124
- "IF-MIB::ifDescr": ".1.3.6.1.2.1.2.2.1.2",
125
- "IF-MIB::ifType": ".1.3.6.1.2.1.2.2.1.3",
126
- "IF-MIB::ifMtu": ".1.3.6.1.2.1.2.2.1.4",
127
- "IF-MIB::ifSpeed": ".1.3.6.1.2.1.2.2.1.5",
128
- "IF-MIB::ifPhysAddress": ".1.3.6.1.2.1.2.2.1.6",
129
- "IF-MIB::ifAdminStatus": ".1.3.6.1.2.1.2.2.1.7",
130
- "IF-MIB::ifOperStatus": ".1.3.6.1.2.1.2.2.1.8",
131
- "IF-MIB::ifLastChange": ".1.3.6.1.2.1.2.2.1.9",
132
- "IF-MIB::ifInOctets": ".1.3.6.1.2.1.2.2.1.10",
133
- "IF-MIB::ifInUcastPkts": ".1.3.6.1.2.1.2.2.1.11",
134
- "IF-MIB::ifInNUcastPkts": ".1.3.6.1.2.1.2.2.1.12",
135
- "IF-MIB::ifInDiscards": ".1.3.6.1.2.1.2.2.1.13",
136
- "IF-MIB::ifInErrors": ".1.3.6.1.2.1.2.2.1.14",
137
- "IF-MIB::ifInUnknownProtos": ".1.3.6.1.2.1.2.2.1.15",
138
- "IF-MIB::ifOutOctets": ".1.3.6.1.2.1.2.2.1.16",
139
- "IF-MIB::ifOutUcastPkts": ".1.3.6.1.2.1.2.2.1.17",
140
- "IF-MIB::ifOutNUcastPkts": ".1.3.6.1.2.1.2.2.1.18",
141
- "IF-MIB::ifOutDiscards": ".1.3.6.1.2.1.2.2.1.19",
142
- "IF-MIB::ifOutErrors": ".1.3.6.1.2.1.2.2.1.20",
143
- "IF-MIB::ifOutQLen": ".1.3.6.1.2.1.2.2.1.21",
144
- "IF-MIB::ifSpecific": ".1.3.6.1.2.1.2.2.1.22",
145
-
146
- // IF-MIB Extended table (.1.3.6.1.2.1.31.1.1.1.x)
147
- "IF-MIB::ifName": ".1.3.6.1.2.1.31.1.1.1.1",
148
- "IF-MIB::ifInMulticastPkts": ".1.3.6.1.2.1.31.1.1.1.2",
149
- "IF-MIB::ifInBroadcastPkts": ".1.3.6.1.2.1.31.1.1.1.3",
150
- "IF-MIB::ifOutMulticastPkts": ".1.3.6.1.2.1.31.1.1.1.4",
151
- "IF-MIB::ifOutBroadcastPkts": ".1.3.6.1.2.1.31.1.1.1.5",
152
- "IF-MIB::ifHCInOctets": ".1.3.6.1.2.1.31.1.1.1.6",
153
- "IF-MIB::ifHCInUcastPkts": ".1.3.6.1.2.1.31.1.1.1.7",
154
- "IF-MIB::ifHCInMulticastPkts": ".1.3.6.1.2.1.31.1.1.1.8",
155
- "IF-MIB::ifHCInBroadcastPkts": ".1.3.6.1.2.1.31.1.1.1.9",
156
- "IF-MIB::ifHCOutOctets": ".1.3.6.1.2.1.31.1.1.1.10",
157
- "IF-MIB::ifHCOutUcastPkts": ".1.3.6.1.2.1.31.1.1.1.11",
158
- "IF-MIB::ifHCOutMulticastPkts": ".1.3.6.1.2.1.31.1.1.1.12",
159
- "IF-MIB::ifHCOutBroadcastPkts": ".1.3.6.1.2.1.31.1.1.1.13",
160
- "IF-MIB::ifLinkUpDownTrapEnable": ".1.3.6.1.2.1.31.1.1.1.14",
161
- "IF-MIB::ifHighSpeed": ".1.3.6.1.2.1.31.1.1.1.15",
162
- "IF-MIB::ifPromiscuousMode": ".1.3.6.1.2.1.31.1.1.1.16",
163
- "IF-MIB::ifConnectorPresent": ".1.3.6.1.2.1.31.1.1.1.17",
164
- "IF-MIB::ifAlias": ".1.3.6.1.2.1.31.1.1.1.18",
165
- "IF-MIB::ifCounterDiscontinuityTime": ".1.3.6.1.2.1.31.1.1.1.19",
166
-
167
- // IF-MIB Stack table
168
- "IF-MIB::ifStackHigherLayer": ".1.3.6.1.2.1.31.1.2.1.1",
169
- "IF-MIB::ifStackLowerLayer": ".1.3.6.1.2.1.31.1.2.1.2",
170
- "IF-MIB::ifStackStatus": ".1.3.6.1.2.1.31.1.2.1.3",
171
-
172
- // ============================================================================
173
- // IP-MIB - RFC 4293
174
- // ============================================================================
175
- "IP-MIB::ipForwarding": ".1.3.6.1.2.1.4.1",
176
- "IP-MIB::ipDefaultTTL": ".1.3.6.1.2.1.4.2",
177
- "IP-MIB::ipInReceives": ".1.3.6.1.2.1.4.3",
178
- "IP-MIB::ipInHdrErrors": ".1.3.6.1.2.1.4.4",
179
- "IP-MIB::ipInAddrErrors": ".1.3.6.1.2.1.4.5",
180
- "IP-MIB::ipForwDatagrams": ".1.3.6.1.2.1.4.6",
181
- "IP-MIB::ipInUnknownProtos": ".1.3.6.1.2.1.4.7",
182
- "IP-MIB::ipInDiscards": ".1.3.6.1.2.1.4.8",
183
- "IP-MIB::ipInDelivers": ".1.3.6.1.2.1.4.9",
184
- "IP-MIB::ipOutRequests": ".1.3.6.1.2.1.4.10",
185
- "IP-MIB::ipOutDiscards": ".1.3.6.1.2.1.4.11",
186
- "IP-MIB::ipOutNoRoutes": ".1.3.6.1.2.1.4.12",
187
- "IP-MIB::ipReasmTimeout": ".1.3.6.1.2.1.4.13",
188
- "IP-MIB::ipReasmReqds": ".1.3.6.1.2.1.4.14",
189
- "IP-MIB::ipReasmOKs": ".1.3.6.1.2.1.4.15",
190
- "IP-MIB::ipReasmFails": ".1.3.6.1.2.1.4.16",
191
- "IP-MIB::ipFragOKs": ".1.3.6.1.2.1.4.17",
192
- "IP-MIB::ipFragFails": ".1.3.6.1.2.1.4.18",
193
- "IP-MIB::ipFragCreates": ".1.3.6.1.2.1.4.19",
194
-
195
- // IP Address table
196
- "IP-MIB::ipAdEntAddr": ".1.3.6.1.2.1.4.20.1.1",
197
- "IP-MIB::ipAdEntIfIndex": ".1.3.6.1.2.1.4.20.1.2",
198
- "IP-MIB::ipAdEntNetMask": ".1.3.6.1.2.1.4.20.1.3",
199
- "IP-MIB::ipAdEntBcastAddr": ".1.3.6.1.2.1.4.20.1.4",
200
- "IP-MIB::ipAdEntReasmMaxSize": ".1.3.6.1.2.1.4.20.1.5",
201
-
202
- // IP Route table
203
- "IP-MIB::ipRouteDest": ".1.3.6.1.2.1.4.21.1.1",
204
- "IP-MIB::ipRouteIfIndex": ".1.3.6.1.2.1.4.21.1.2",
205
- "IP-MIB::ipRouteMetric1": ".1.3.6.1.2.1.4.21.1.3",
206
- "IP-MIB::ipRouteMetric2": ".1.3.6.1.2.1.4.21.1.4",
207
- "IP-MIB::ipRouteMetric3": ".1.3.6.1.2.1.4.21.1.5",
208
- "IP-MIB::ipRouteMetric4": ".1.3.6.1.2.1.4.21.1.6",
209
- "IP-MIB::ipRouteNextHop": ".1.3.6.1.2.1.4.21.1.7",
210
- "IP-MIB::ipRouteType": ".1.3.6.1.2.1.4.21.1.8",
211
- "IP-MIB::ipRouteProto": ".1.3.6.1.2.1.4.21.1.9",
212
- "IP-MIB::ipRouteAge": ".1.3.6.1.2.1.4.21.1.10",
213
- "IP-MIB::ipRouteMask": ".1.3.6.1.2.1.4.21.1.11",
214
- "IP-MIB::ipRouteMetric5": ".1.3.6.1.2.1.4.21.1.12",
215
- "IP-MIB::ipRouteInfo": ".1.3.6.1.2.1.4.21.1.13",
216
-
217
- // IP Net-to-Media table
218
- "IP-MIB::ipNetToMediaIfIndex": ".1.3.6.1.2.1.4.22.1.1",
219
- "IP-MIB::ipNetToMediaPhysAddress": ".1.3.6.1.2.1.4.22.1.2",
220
- "IP-MIB::ipNetToMediaNetAddress": ".1.3.6.1.2.1.4.22.1.3",
221
- "IP-MIB::ipNetToMediaType": ".1.3.6.1.2.1.4.22.1.4",
222
-
223
- // IP Forwarding table (newer)
224
- "IP-MIB::inetCidrRouteNumber": ".1.3.6.1.2.1.4.24.6",
225
-
226
- // ============================================================================
227
- // ICMP-MIB - RFC 4293
228
- // ============================================================================
229
- "IP-MIB::icmpInMsgs": ".1.3.6.1.2.1.5.1",
230
- "IP-MIB::icmpInErrors": ".1.3.6.1.2.1.5.2",
231
- "IP-MIB::icmpInDestUnreachs": ".1.3.6.1.2.1.5.3",
232
- "IP-MIB::icmpInTimeExcds": ".1.3.6.1.2.1.5.4",
233
- "IP-MIB::icmpInParmProbs": ".1.3.6.1.2.1.5.5",
234
- "IP-MIB::icmpInSrcQuenchs": ".1.3.6.1.2.1.5.6",
235
- "IP-MIB::icmpInRedirects": ".1.3.6.1.2.1.5.7",
236
- "IP-MIB::icmpInEchos": ".1.3.6.1.2.1.5.8",
237
- "IP-MIB::icmpInEchoReps": ".1.3.6.1.2.1.5.9",
238
- "IP-MIB::icmpInTimestamps": ".1.3.6.1.2.1.5.10",
239
- "IP-MIB::icmpInTimestampReps": ".1.3.6.1.2.1.5.11",
240
- "IP-MIB::icmpInAddrMasks": ".1.3.6.1.2.1.5.12",
241
- "IP-MIB::icmpInAddrMaskReps": ".1.3.6.1.2.1.5.13",
242
- "IP-MIB::icmpOutMsgs": ".1.3.6.1.2.1.5.14",
243
- "IP-MIB::icmpOutErrors": ".1.3.6.1.2.1.5.15",
244
- "IP-MIB::icmpOutDestUnreachs": ".1.3.6.1.2.1.5.16",
245
- "IP-MIB::icmpOutTimeExcds": ".1.3.6.1.2.1.5.17",
246
- "IP-MIB::icmpOutParmProbs": ".1.3.6.1.2.1.5.18",
247
- "IP-MIB::icmpOutSrcQuenchs": ".1.3.6.1.2.1.5.19",
248
- "IP-MIB::icmpOutRedirects": ".1.3.6.1.2.1.5.20",
249
- "IP-MIB::icmpOutEchos": ".1.3.6.1.2.1.5.21",
250
- "IP-MIB::icmpOutEchoReps": ".1.3.6.1.2.1.5.22",
251
- "IP-MIB::icmpOutTimestamps": ".1.3.6.1.2.1.5.23",
252
- "IP-MIB::icmpOutTimestampReps": ".1.3.6.1.2.1.5.24",
253
- "IP-MIB::icmpOutAddrMasks": ".1.3.6.1.2.1.5.25",
254
- "IP-MIB::icmpOutAddrMaskReps": ".1.3.6.1.2.1.5.26",
255
-
256
- // ============================================================================
257
- // TCP-MIB - RFC 4022
258
- // ============================================================================
259
- "TCP-MIB::tcpRtoAlgorithm": ".1.3.6.1.2.1.6.1",
260
- "TCP-MIB::tcpRtoMin": ".1.3.6.1.2.1.6.2",
261
- "TCP-MIB::tcpRtoMax": ".1.3.6.1.2.1.6.3",
262
- "TCP-MIB::tcpMaxConn": ".1.3.6.1.2.1.6.4",
263
- "TCP-MIB::tcpActiveOpens": ".1.3.6.1.2.1.6.5",
264
- "TCP-MIB::tcpPassiveOpens": ".1.3.6.1.2.1.6.6",
265
- "TCP-MIB::tcpAttemptFails": ".1.3.6.1.2.1.6.7",
266
- "TCP-MIB::tcpEstabResets": ".1.3.6.1.2.1.6.8",
267
- "TCP-MIB::tcpCurrEstab": ".1.3.6.1.2.1.6.9",
268
- "TCP-MIB::tcpInSegs": ".1.3.6.1.2.1.6.10",
269
- "TCP-MIB::tcpOutSegs": ".1.3.6.1.2.1.6.11",
270
- "TCP-MIB::tcpRetransSegs": ".1.3.6.1.2.1.6.12",
271
- "TCP-MIB::tcpInErrs": ".1.3.6.1.2.1.6.14",
272
- "TCP-MIB::tcpOutRsts": ".1.3.6.1.2.1.6.15",
273
- "TCP-MIB::tcpHCInSegs": ".1.3.6.1.2.1.6.17",
274
- "TCP-MIB::tcpHCOutSegs": ".1.3.6.1.2.1.6.18",
275
-
276
- // TCP Connection table
277
- "TCP-MIB::tcpConnState": ".1.3.6.1.2.1.6.13.1.1",
278
- "TCP-MIB::tcpConnLocalAddress": ".1.3.6.1.2.1.6.13.1.2",
279
- "TCP-MIB::tcpConnLocalPort": ".1.3.6.1.2.1.6.13.1.3",
280
- "TCP-MIB::tcpConnRemAddress": ".1.3.6.1.2.1.6.13.1.4",
281
- "TCP-MIB::tcpConnRemPort": ".1.3.6.1.2.1.6.13.1.5",
282
-
283
- // ============================================================================
284
- // UDP-MIB - RFC 4113
285
- // ============================================================================
286
- "UDP-MIB::udpInDatagrams": ".1.3.6.1.2.1.7.1",
287
- "UDP-MIB::udpNoPorts": ".1.3.6.1.2.1.7.2",
288
- "UDP-MIB::udpInErrors": ".1.3.6.1.2.1.7.3",
289
- "UDP-MIB::udpOutDatagrams": ".1.3.6.1.2.1.7.4",
290
- "UDP-MIB::udpHCInDatagrams": ".1.3.6.1.2.1.7.8",
291
- "UDP-MIB::udpHCOutDatagrams": ".1.3.6.1.2.1.7.9",
292
-
293
- // UDP Listener table
294
- "UDP-MIB::udpLocalAddress": ".1.3.6.1.2.1.7.5.1.1",
295
- "UDP-MIB::udpLocalPort": ".1.3.6.1.2.1.7.5.1.2",
296
-
297
- // ============================================================================
298
- // HOST-RESOURCES-MIB - RFC 2790
299
- // ============================================================================
300
- // System group (.1.3.6.1.2.1.25.1.x)
301
- "HOST-RESOURCES-MIB::hrSystemUptime": ".1.3.6.1.2.1.25.1.1",
302
- "HOST-RESOURCES-MIB::hrSystemDate": ".1.3.6.1.2.1.25.1.2",
303
- "HOST-RESOURCES-MIB::hrSystemInitialLoadDevice": ".1.3.6.1.2.1.25.1.3",
304
- "HOST-RESOURCES-MIB::hrSystemInitialLoadParameters": ".1.3.6.1.2.1.25.1.4",
305
- "HOST-RESOURCES-MIB::hrSystemNumUsers": ".1.3.6.1.2.1.25.1.5",
306
- "HOST-RESOURCES-MIB::hrSystemProcesses": ".1.3.6.1.2.1.25.1.6",
307
- "HOST-RESOURCES-MIB::hrSystemMaxProcesses": ".1.3.6.1.2.1.25.1.7",
308
-
309
- // Storage group (.1.3.6.1.2.1.25.2.x)
310
- "HOST-RESOURCES-MIB::hrMemorySize": ".1.3.6.1.2.1.25.2.2",
311
- "HOST-RESOURCES-MIB::hrStorageIndex": ".1.3.6.1.2.1.25.2.3.1.1",
312
- "HOST-RESOURCES-MIB::hrStorageType": ".1.3.6.1.2.1.25.2.3.1.2",
313
- "HOST-RESOURCES-MIB::hrStorageDescr": ".1.3.6.1.2.1.25.2.3.1.3",
314
- "HOST-RESOURCES-MIB::hrStorageAllocationUnits": ".1.3.6.1.2.1.25.2.3.1.4",
315
- "HOST-RESOURCES-MIB::hrStorageSize": ".1.3.6.1.2.1.25.2.3.1.5",
316
- "HOST-RESOURCES-MIB::hrStorageUsed": ".1.3.6.1.2.1.25.2.3.1.6",
317
- "HOST-RESOURCES-MIB::hrStorageAllocationFailures": ".1.3.6.1.2.1.25.2.3.1.7",
318
-
319
- // Device group (.1.3.6.1.2.1.25.3.x)
320
- "HOST-RESOURCES-MIB::hrDeviceIndex": ".1.3.6.1.2.1.25.3.2.1.1",
321
- "HOST-RESOURCES-MIB::hrDeviceType": ".1.3.6.1.2.1.25.3.2.1.2",
322
- "HOST-RESOURCES-MIB::hrDeviceDescr": ".1.3.6.1.2.1.25.3.2.1.3",
323
- "HOST-RESOURCES-MIB::hrDeviceID": ".1.3.6.1.2.1.25.3.2.1.4",
324
- "HOST-RESOURCES-MIB::hrDeviceStatus": ".1.3.6.1.2.1.25.3.2.1.5",
325
- "HOST-RESOURCES-MIB::hrDeviceErrors": ".1.3.6.1.2.1.25.3.2.1.6",
326
- "HOST-RESOURCES-MIB::hrProcessorFrwID": ".1.3.6.1.2.1.25.3.3.1.1",
327
- "HOST-RESOURCES-MIB::hrProcessorLoad": ".1.3.6.1.2.1.25.3.3.1.2",
328
- "HOST-RESOURCES-MIB::hrNetworkIfIndex": ".1.3.6.1.2.1.25.3.4.1.1",
329
- "HOST-RESOURCES-MIB::hrPrinterStatus": ".1.3.6.1.2.1.25.3.5.1.1",
330
- "HOST-RESOURCES-MIB::hrPrinterDetectedErrorState": ".1.3.6.1.2.1.25.3.5.1.2",
331
- "HOST-RESOURCES-MIB::hrDiskStorageAccess": ".1.3.6.1.2.1.25.3.6.1.1",
332
- "HOST-RESOURCES-MIB::hrDiskStorageMedia": ".1.3.6.1.2.1.25.3.6.1.2",
333
- "HOST-RESOURCES-MIB::hrDiskStorageRemoveble": ".1.3.6.1.2.1.25.3.6.1.3",
334
- "HOST-RESOURCES-MIB::hrDiskStorageCapacity": ".1.3.6.1.2.1.25.3.6.1.4",
335
- "HOST-RESOURCES-MIB::hrPartitionIndex": ".1.3.6.1.2.1.25.3.7.1.1",
336
- "HOST-RESOURCES-MIB::hrPartitionLabel": ".1.3.6.1.2.1.25.3.7.1.2",
337
- "HOST-RESOURCES-MIB::hrPartitionID": ".1.3.6.1.2.1.25.3.7.1.3",
338
- "HOST-RESOURCES-MIB::hrPartitionSize": ".1.3.6.1.2.1.25.3.7.1.4",
339
- "HOST-RESOURCES-MIB::hrPartitionFSIndex": ".1.3.6.1.2.1.25.3.7.1.5",
340
- "HOST-RESOURCES-MIB::hrFSIndex": ".1.3.6.1.2.1.25.3.8.1.1",
341
- "HOST-RESOURCES-MIB::hrFSMountPoint": ".1.3.6.1.2.1.25.3.8.1.2",
342
- "HOST-RESOURCES-MIB::hrFSRemoteMountPoint": ".1.3.6.1.2.1.25.3.8.1.3",
343
- "HOST-RESOURCES-MIB::hrFSType": ".1.3.6.1.2.1.25.3.8.1.4",
344
- "HOST-RESOURCES-MIB::hrFSAccess": ".1.3.6.1.2.1.25.3.8.1.5",
345
- "HOST-RESOURCES-MIB::hrFSBootable": ".1.3.6.1.2.1.25.3.8.1.6",
346
- "HOST-RESOURCES-MIB::hrFSStorageIndex": ".1.3.6.1.2.1.25.3.8.1.7",
347
- "HOST-RESOURCES-MIB::hrFSLastFullBackupDate": ".1.3.6.1.2.1.25.3.8.1.8",
348
- "HOST-RESOURCES-MIB::hrFSLastPartialBackupDate": ".1.3.6.1.2.1.25.3.8.1.9",
349
-
350
- // Running Software group (.1.3.6.1.2.1.25.4.x)
351
- "HOST-RESOURCES-MIB::hrSWOSIndex": ".1.3.6.1.2.1.25.4.1",
352
- "HOST-RESOURCES-MIB::hrSWRunIndex": ".1.3.6.1.2.1.25.4.2.1.1",
353
- "HOST-RESOURCES-MIB::hrSWRunName": ".1.3.6.1.2.1.25.4.2.1.2",
354
- "HOST-RESOURCES-MIB::hrSWRunID": ".1.3.6.1.2.1.25.4.2.1.3",
355
- "HOST-RESOURCES-MIB::hrSWRunPath": ".1.3.6.1.2.1.25.4.2.1.4",
356
- "HOST-RESOURCES-MIB::hrSWRunParameters": ".1.3.6.1.2.1.25.4.2.1.5",
357
- "HOST-RESOURCES-MIB::hrSWRunType": ".1.3.6.1.2.1.25.4.2.1.6",
358
- "HOST-RESOURCES-MIB::hrSWRunStatus": ".1.3.6.1.2.1.25.4.2.1.7",
359
-
360
- // Running Software Performance group
361
- "HOST-RESOURCES-MIB::hrSWRunPerfCPU": ".1.3.6.1.2.1.25.5.1.1.1",
362
- "HOST-RESOURCES-MIB::hrSWRunPerfMem": ".1.3.6.1.2.1.25.5.1.1.2",
363
-
364
- // Installed Software group (.1.3.6.1.2.1.25.6.x)
365
- "HOST-RESOURCES-MIB::hrSWInstalledLastChange": ".1.3.6.1.2.1.25.6.1",
366
- "HOST-RESOURCES-MIB::hrSWInstalledLastUpdateTime": ".1.3.6.1.2.1.25.6.2",
367
- "HOST-RESOURCES-MIB::hrSWInstalledIndex": ".1.3.6.1.2.1.25.6.3.1.1",
368
- "HOST-RESOURCES-MIB::hrSWInstalledName": ".1.3.6.1.2.1.25.6.3.1.2",
369
- "HOST-RESOURCES-MIB::hrSWInstalledID": ".1.3.6.1.2.1.25.6.3.1.3",
370
- "HOST-RESOURCES-MIB::hrSWInstalledType": ".1.3.6.1.2.1.25.6.3.1.4",
371
- "HOST-RESOURCES-MIB::hrSWInstalledDate": ".1.3.6.1.2.1.25.6.3.1.5",
372
-
373
- // ============================================================================
374
- // ENTITY-MIB - RFC 6933
375
- // ============================================================================
376
- "ENTITY-MIB::entPhysicalIndex": ".1.3.6.1.2.1.47.1.1.1.1.1",
377
- "ENTITY-MIB::entPhysicalDescr": ".1.3.6.1.2.1.47.1.1.1.1.2",
378
- "ENTITY-MIB::entPhysicalVendorType": ".1.3.6.1.2.1.47.1.1.1.1.3",
379
- "ENTITY-MIB::entPhysicalContainedIn": ".1.3.6.1.2.1.47.1.1.1.1.4",
380
- "ENTITY-MIB::entPhysicalClass": ".1.3.6.1.2.1.47.1.1.1.1.5",
381
- "ENTITY-MIB::entPhysicalParentRelPos": ".1.3.6.1.2.1.47.1.1.1.1.6",
382
- "ENTITY-MIB::entPhysicalName": ".1.3.6.1.2.1.47.1.1.1.1.7",
383
- "ENTITY-MIB::entPhysicalHardwareRev": ".1.3.6.1.2.1.47.1.1.1.1.8",
384
- "ENTITY-MIB::entPhysicalFirmwareRev": ".1.3.6.1.2.1.47.1.1.1.1.9",
385
- "ENTITY-MIB::entPhysicalSoftwareRev": ".1.3.6.1.2.1.47.1.1.1.1.10",
386
- "ENTITY-MIB::entPhysicalSerialNum": ".1.3.6.1.2.1.47.1.1.1.1.11",
387
- "ENTITY-MIB::entPhysicalMfgName": ".1.3.6.1.2.1.47.1.1.1.1.12",
388
- "ENTITY-MIB::entPhysicalModelName": ".1.3.6.1.2.1.47.1.1.1.1.13",
389
- "ENTITY-MIB::entPhysicalAlias": ".1.3.6.1.2.1.47.1.1.1.1.14",
390
- "ENTITY-MIB::entPhysicalAssetID": ".1.3.6.1.2.1.47.1.1.1.1.15",
391
- "ENTITY-MIB::entPhysicalIsFRU": ".1.3.6.1.2.1.47.1.1.1.1.16",
392
- "ENTITY-MIB::entPhysicalMfgDate": ".1.3.6.1.2.1.47.1.1.1.1.17",
393
- "ENTITY-MIB::entPhysicalUris": ".1.3.6.1.2.1.47.1.1.1.1.18",
394
-
395
- // ============================================================================
396
- // EtherLike-MIB - RFC 3635
397
- // ============================================================================
398
- "EtherLike-MIB::dot3StatsIndex": ".1.3.6.1.2.1.10.7.2.1.1",
399
- "EtherLike-MIB::dot3StatsAlignmentErrors": ".1.3.6.1.2.1.10.7.2.1.2",
400
- "EtherLike-MIB::dot3StatsFCSErrors": ".1.3.6.1.2.1.10.7.2.1.3",
401
- "EtherLike-MIB::dot3StatsSingleCollisionFrames": ".1.3.6.1.2.1.10.7.2.1.4",
402
- "EtherLike-MIB::dot3StatsMultipleCollisionFrames": ".1.3.6.1.2.1.10.7.2.1.5",
403
- "EtherLike-MIB::dot3StatsSQETestErrors": ".1.3.6.1.2.1.10.7.2.1.6",
404
- "EtherLike-MIB::dot3StatsDeferredTransmissions": ".1.3.6.1.2.1.10.7.2.1.7",
405
- "EtherLike-MIB::dot3StatsLateCollisions": ".1.3.6.1.2.1.10.7.2.1.8",
406
- "EtherLike-MIB::dot3StatsExcessiveCollisions": ".1.3.6.1.2.1.10.7.2.1.9",
407
- "EtherLike-MIB::dot3StatsInternalMacTransmitErrors": ".1.3.6.1.2.1.10.7.2.1.10",
408
- "EtherLike-MIB::dot3StatsCarrierSenseErrors": ".1.3.6.1.2.1.10.7.2.1.11",
409
- "EtherLike-MIB::dot3StatsFrameTooLongs": ".1.3.6.1.2.1.10.7.2.1.13",
410
- "EtherLike-MIB::dot3StatsInternalMacReceiveErrors": ".1.3.6.1.2.1.10.7.2.1.16",
411
- "EtherLike-MIB::dot3StatsSymbolErrors": ".1.3.6.1.2.1.10.7.2.1.18",
412
- "EtherLike-MIB::dot3StatsDuplexStatus": ".1.3.6.1.2.1.10.7.2.1.19",
413
-
414
- // ============================================================================
415
- // UCD-SNMP-MIB (Linux/Unix systems) - NET-SNMP
416
- // ============================================================================
417
- // Memory statistics
418
- "UCD-SNMP-MIB::memIndex": ".1.3.6.1.4.1.2021.4.1",
419
- "UCD-SNMP-MIB::memErrorName": ".1.3.6.1.4.1.2021.4.2",
420
- "UCD-SNMP-MIB::memTotalSwap": ".1.3.6.1.4.1.2021.4.3",
421
- "UCD-SNMP-MIB::memAvailSwap": ".1.3.6.1.4.1.2021.4.4",
422
- "UCD-SNMP-MIB::memTotalReal": ".1.3.6.1.4.1.2021.4.5",
423
- "UCD-SNMP-MIB::memAvailReal": ".1.3.6.1.4.1.2021.4.6",
424
- "UCD-SNMP-MIB::memTotalFree": ".1.3.6.1.4.1.2021.4.11",
425
- "UCD-SNMP-MIB::memMinimumSwap": ".1.3.6.1.4.1.2021.4.12",
426
- "UCD-SNMP-MIB::memShared": ".1.3.6.1.4.1.2021.4.13",
427
- "UCD-SNMP-MIB::memBuffer": ".1.3.6.1.4.1.2021.4.14",
428
- "UCD-SNMP-MIB::memCached": ".1.3.6.1.4.1.2021.4.15",
429
- "UCD-SNMP-MIB::memSwapError": ".1.3.6.1.4.1.2021.4.100",
430
- "UCD-SNMP-MIB::memSwapErrorMsg": ".1.3.6.1.4.1.2021.4.101",
431
-
432
- // CPU statistics
433
- "UCD-SNMP-MIB::ssIndex": ".1.3.6.1.4.1.2021.11.1",
434
- "UCD-SNMP-MIB::ssErrorName": ".1.3.6.1.4.1.2021.11.2",
435
- "UCD-SNMP-MIB::ssSwapIn": ".1.3.6.1.4.1.2021.11.3",
436
- "UCD-SNMP-MIB::ssSwapOut": ".1.3.6.1.4.1.2021.11.4",
437
- "UCD-SNMP-MIB::ssIOSent": ".1.3.6.1.4.1.2021.11.5",
438
- "UCD-SNMP-MIB::ssIOReceive": ".1.3.6.1.4.1.2021.11.6",
439
- "UCD-SNMP-MIB::ssSysInterrupts": ".1.3.6.1.4.1.2021.11.7",
440
- "UCD-SNMP-MIB::ssSysContext": ".1.3.6.1.4.1.2021.11.8",
441
- "UCD-SNMP-MIB::ssCpuUser": ".1.3.6.1.4.1.2021.11.9",
442
- "UCD-SNMP-MIB::ssCpuSystem": ".1.3.6.1.4.1.2021.11.10",
443
- "UCD-SNMP-MIB::ssCpuIdle": ".1.3.6.1.4.1.2021.11.11",
444
- "UCD-SNMP-MIB::ssCpuRawUser": ".1.3.6.1.4.1.2021.11.50",
445
- "UCD-SNMP-MIB::ssCpuRawNice": ".1.3.6.1.4.1.2021.11.51",
446
- "UCD-SNMP-MIB::ssCpuRawSystem": ".1.3.6.1.4.1.2021.11.52",
447
- "UCD-SNMP-MIB::ssCpuRawIdle": ".1.3.6.1.4.1.2021.11.53",
448
- "UCD-SNMP-MIB::ssCpuRawWait": ".1.3.6.1.4.1.2021.11.54",
449
- "UCD-SNMP-MIB::ssCpuRawKernel": ".1.3.6.1.4.1.2021.11.55",
450
- "UCD-SNMP-MIB::ssCpuRawInterrupt": ".1.3.6.1.4.1.2021.11.56",
451
- "UCD-SNMP-MIB::ssIORawSent": ".1.3.6.1.4.1.2021.11.57",
452
- "UCD-SNMP-MIB::ssIORawReceived": ".1.3.6.1.4.1.2021.11.58",
453
- "UCD-SNMP-MIB::ssRawInterrupts": ".1.3.6.1.4.1.2021.11.59",
454
- "UCD-SNMP-MIB::ssRawContexts": ".1.3.6.1.4.1.2021.11.60",
455
- "UCD-SNMP-MIB::ssCpuRawSoftIRQ": ".1.3.6.1.4.1.2021.11.61",
456
- "UCD-SNMP-MIB::ssCpuRawSteal": ".1.3.6.1.4.1.2021.11.64",
457
- "UCD-SNMP-MIB::ssCpuRawGuest": ".1.3.6.1.4.1.2021.11.65",
458
- "UCD-SNMP-MIB::ssCpuRawGuestNice": ".1.3.6.1.4.1.2021.11.66",
459
-
460
- // Load average
461
- "UCD-SNMP-MIB::laIndex": ".1.3.6.1.4.1.2021.10.1.1",
462
- "UCD-SNMP-MIB::laNames": ".1.3.6.1.4.1.2021.10.1.2",
463
- "UCD-SNMP-MIB::laLoad": ".1.3.6.1.4.1.2021.10.1.3",
464
- "UCD-SNMP-MIB::laConfig": ".1.3.6.1.4.1.2021.10.1.4",
465
- "UCD-SNMP-MIB::laLoadInt": ".1.3.6.1.4.1.2021.10.1.5",
466
- "UCD-SNMP-MIB::laLoadFloat": ".1.3.6.1.4.1.2021.10.1.6",
467
- "UCD-SNMP-MIB::laErrorFlag": ".1.3.6.1.4.1.2021.10.1.100",
468
- "UCD-SNMP-MIB::laErrMessage": ".1.3.6.1.4.1.2021.10.1.101",
469
-
470
- // Disk I/O statistics
471
- "UCD-SNMP-MIB::diskIOIndex": ".1.3.6.1.4.1.2021.13.15.1.1.1",
472
- "UCD-SNMP-MIB::diskIODevice": ".1.3.6.1.4.1.2021.13.15.1.1.2",
473
- "UCD-SNMP-MIB::diskIONRead": ".1.3.6.1.4.1.2021.13.15.1.1.3",
474
- "UCD-SNMP-MIB::diskIONWritten": ".1.3.6.1.4.1.2021.13.15.1.1.4",
475
- "UCD-SNMP-MIB::diskIOReads": ".1.3.6.1.4.1.2021.13.15.1.1.5",
476
- "UCD-SNMP-MIB::diskIOWrites": ".1.3.6.1.4.1.2021.13.15.1.1.6",
477
- "UCD-SNMP-MIB::diskIONReadX": ".1.3.6.1.4.1.2021.13.15.1.1.12",
478
- "UCD-SNMP-MIB::diskIONWrittenX": ".1.3.6.1.4.1.2021.13.15.1.1.13",
479
-
480
- // ============================================================================
481
- // UPS-MIB - RFC 1628
482
- // ============================================================================
483
- "UPS-MIB::upsIdentManufacturer": ".1.3.6.1.2.1.33.1.1.1",
484
- "UPS-MIB::upsIdentModel": ".1.3.6.1.2.1.33.1.1.2",
485
- "UPS-MIB::upsIdentUPSSoftwareVersion": ".1.3.6.1.2.1.33.1.1.3",
486
- "UPS-MIB::upsIdentAgentSoftwareVersion": ".1.3.6.1.2.1.33.1.1.4",
487
- "UPS-MIB::upsIdentName": ".1.3.6.1.2.1.33.1.1.5",
488
- "UPS-MIB::upsIdentAttachedDevices": ".1.3.6.1.2.1.33.1.1.6",
489
- "UPS-MIB::upsBatteryStatus": ".1.3.6.1.2.1.33.1.2.1",
490
- "UPS-MIB::upsSecondsOnBattery": ".1.3.6.1.2.1.33.1.2.2",
491
- "UPS-MIB::upsEstimatedMinutesRemaining": ".1.3.6.1.2.1.33.1.2.3",
492
- "UPS-MIB::upsEstimatedChargeRemaining": ".1.3.6.1.2.1.33.1.2.4",
493
- "UPS-MIB::upsBatteryVoltage": ".1.3.6.1.2.1.33.1.2.5",
494
- "UPS-MIB::upsBatteryCurrent": ".1.3.6.1.2.1.33.1.2.6",
495
- "UPS-MIB::upsBatteryTemperature": ".1.3.6.1.2.1.33.1.2.7",
496
- "UPS-MIB::upsInputLineBads": ".1.3.6.1.2.1.33.1.3.1",
497
- "UPS-MIB::upsInputNumLines": ".1.3.6.1.2.1.33.1.3.2",
498
- "UPS-MIB::upsInputLineIndex": ".1.3.6.1.2.1.33.1.3.3.1.1",
499
- "UPS-MIB::upsInputFrequency": ".1.3.6.1.2.1.33.1.3.3.1.2",
500
- "UPS-MIB::upsInputVoltage": ".1.3.6.1.2.1.33.1.3.3.1.3",
501
- "UPS-MIB::upsInputCurrent": ".1.3.6.1.2.1.33.1.3.3.1.4",
502
- "UPS-MIB::upsInputTruePower": ".1.3.6.1.2.1.33.1.3.3.1.5",
503
- "UPS-MIB::upsOutputSource": ".1.3.6.1.2.1.33.1.4.1",
504
- "UPS-MIB::upsOutputFrequency": ".1.3.6.1.2.1.33.1.4.2",
505
- "UPS-MIB::upsOutputNumLines": ".1.3.6.1.2.1.33.1.4.3",
506
- "UPS-MIB::upsOutputLineIndex": ".1.3.6.1.2.1.33.1.4.4.1.1",
507
- "UPS-MIB::upsOutputVoltage": ".1.3.6.1.2.1.33.1.4.4.1.2",
508
- "UPS-MIB::upsOutputCurrent": ".1.3.6.1.2.1.33.1.4.4.1.3",
509
- "UPS-MIB::upsOutputPower": ".1.3.6.1.2.1.33.1.4.4.1.4",
510
- "UPS-MIB::upsOutputPercentLoad": ".1.3.6.1.2.1.33.1.4.4.1.5",
511
- "UPS-MIB::upsBypassFrequency": ".1.3.6.1.2.1.33.1.5.1",
512
- "UPS-MIB::upsBypassNumLines": ".1.3.6.1.2.1.33.1.5.2",
513
- "UPS-MIB::upsAlarmsPresent": ".1.3.6.1.2.1.33.1.6.1",
514
- "UPS-MIB::upsTestId": ".1.3.6.1.2.1.33.1.7.1",
515
- "UPS-MIB::upsTestSpinLock": ".1.3.6.1.2.1.33.1.7.2",
516
- "UPS-MIB::upsTestResultsSummary": ".1.3.6.1.2.1.33.1.7.3",
517
- "UPS-MIB::upsTestResultsDetail": ".1.3.6.1.2.1.33.1.7.4",
518
- "UPS-MIB::upsTestStartTime": ".1.3.6.1.2.1.33.1.7.5",
519
- "UPS-MIB::upsTestElapsedTime": ".1.3.6.1.2.1.33.1.7.6",
520
-
521
- // ============================================================================
522
- // LLDP-MIB - IEEE 802.1AB
523
- // ============================================================================
524
- "LLDP-MIB::lldpLocChassisIdSubtype": ".1.0.8802.1.1.2.1.3.1",
525
- "LLDP-MIB::lldpLocChassisId": ".1.0.8802.1.1.2.1.3.2",
526
- "LLDP-MIB::lldpLocSysName": ".1.0.8802.1.1.2.1.3.3",
527
- "LLDP-MIB::lldpLocSysDesc": ".1.0.8802.1.1.2.1.3.4",
528
- "LLDP-MIB::lldpLocSysCapSupported": ".1.0.8802.1.1.2.1.3.5",
529
- "LLDP-MIB::lldpLocSysCapEnabled": ".1.0.8802.1.1.2.1.3.6",
530
- "LLDP-MIB::lldpRemChassisIdSubtype": ".1.0.8802.1.1.2.1.4.1.1.4",
531
- "LLDP-MIB::lldpRemChassisId": ".1.0.8802.1.1.2.1.4.1.1.5",
532
- "LLDP-MIB::lldpRemPortIdSubtype": ".1.0.8802.1.1.2.1.4.1.1.6",
533
- "LLDP-MIB::lldpRemPortId": ".1.0.8802.1.1.2.1.4.1.1.7",
534
- "LLDP-MIB::lldpRemPortDesc": ".1.0.8802.1.1.2.1.4.1.1.8",
535
- "LLDP-MIB::lldpRemSysName": ".1.0.8802.1.1.2.1.4.1.1.9",
536
- "LLDP-MIB::lldpRemSysDesc": ".1.0.8802.1.1.2.1.4.1.1.10",
537
- "LLDP-MIB::lldpRemSysCapSupported": ".1.0.8802.1.1.2.1.4.1.1.11",
538
- "LLDP-MIB::lldpRemSysCapEnabled": ".1.0.8802.1.1.2.1.4.1.1.12",
539
-
540
- // ============================================================================
541
- // Printer-MIB - RFC 3805
542
- // ============================================================================
543
- "Printer-MIB::prtGeneralConfigChanges": ".1.3.6.1.2.1.43.5.1.1.1",
544
- "Printer-MIB::prtGeneralCurrentLocalization": ".1.3.6.1.2.1.43.5.1.1.2",
545
- "Printer-MIB::prtGeneralReset": ".1.3.6.1.2.1.43.5.1.1.3",
546
- "Printer-MIB::prtGeneralCurrentOperator": ".1.3.6.1.2.1.43.5.1.1.4",
547
- "Printer-MIB::prtGeneralServicePerson": ".1.3.6.1.2.1.43.5.1.1.5",
548
- "Printer-MIB::prtInputDefaultIndex": ".1.3.6.1.2.1.43.5.1.1.6",
549
- "Printer-MIB::prtOutputDefaultIndex": ".1.3.6.1.2.1.43.5.1.1.7",
550
- "Printer-MIB::prtMarkerDefaultIndex": ".1.3.6.1.2.1.43.5.1.1.8",
551
- "Printer-MIB::prtMediaPathDefaultIndex": ".1.3.6.1.2.1.43.5.1.1.9",
552
- "Printer-MIB::prtConsoleLocalization": ".1.3.6.1.2.1.43.5.1.1.10",
553
- "Printer-MIB::prtConsoleNumberOfDisplayLines": ".1.3.6.1.2.1.43.5.1.1.11",
554
- "Printer-MIB::prtConsoleNumberOfDisplayChars": ".1.3.6.1.2.1.43.5.1.1.12",
555
- "Printer-MIB::prtConsoleDisable": ".1.3.6.1.2.1.43.5.1.1.13",
556
- "Printer-MIB::prtCoverIndex": ".1.3.6.1.2.1.43.6.1.1.1",
557
- "Printer-MIB::prtCoverDescription": ".1.3.6.1.2.1.43.6.1.1.2",
558
- "Printer-MIB::prtCoverStatus": ".1.3.6.1.2.1.43.6.1.1.3",
559
- "Printer-MIB::prtMarkerSuppliesIndex": ".1.3.6.1.2.1.43.11.1.1.1",
560
- "Printer-MIB::prtMarkerSuppliesMarkerIndex": ".1.3.6.1.2.1.43.11.1.1.2",
561
- "Printer-MIB::prtMarkerSuppliesColorantIndex": ".1.3.6.1.2.1.43.11.1.1.3",
562
- "Printer-MIB::prtMarkerSuppliesClass": ".1.3.6.1.2.1.43.11.1.1.4",
563
- "Printer-MIB::prtMarkerSuppliesType": ".1.3.6.1.2.1.43.11.1.1.5",
564
- "Printer-MIB::prtMarkerSuppliesDescription": ".1.3.6.1.2.1.43.11.1.1.6",
565
- "Printer-MIB::prtMarkerSuppliesSupplyUnit": ".1.3.6.1.2.1.43.11.1.1.7",
566
- "Printer-MIB::prtMarkerSuppliesMaxCapacity": ".1.3.6.1.2.1.43.11.1.1.8",
567
- "Printer-MIB::prtMarkerSuppliesLevel": ".1.3.6.1.2.1.43.11.1.1.9",
568
- "Printer-MIB::prtAlertIndex": ".1.3.6.1.2.1.43.18.1.1.1",
569
- "Printer-MIB::prtAlertSeverityLevel": ".1.3.6.1.2.1.43.18.1.1.2",
570
- "Printer-MIB::prtAlertTrainingLevel": ".1.3.6.1.2.1.43.18.1.1.3",
571
- "Printer-MIB::prtAlertGroup": ".1.3.6.1.2.1.43.18.1.1.4",
572
- "Printer-MIB::prtAlertGroupIndex": ".1.3.6.1.2.1.43.18.1.1.5",
573
- "Printer-MIB::prtAlertLocation": ".1.3.6.1.2.1.43.18.1.1.6",
574
- "Printer-MIB::prtAlertCode": ".1.3.6.1.2.1.43.18.1.1.7",
575
- "Printer-MIB::prtAlertDescription": ".1.3.6.1.2.1.43.18.1.1.8",
576
- "Printer-MIB::prtAlertTime": ".1.3.6.1.2.1.43.18.1.1.9",
577
-
578
- // ============================================================================
579
- // BGP4-MIB - RFC 4273
580
- // ============================================================================
581
- "BGP4-MIB::bgpVersion": ".1.3.6.1.2.1.15.1",
582
- "BGP4-MIB::bgpLocalAs": ".1.3.6.1.2.1.15.2",
583
- "BGP4-MIB::bgpPeerIdentifier": ".1.3.6.1.2.1.15.3.1.1",
584
- "BGP4-MIB::bgpPeerState": ".1.3.6.1.2.1.15.3.1.2",
585
- "BGP4-MIB::bgpPeerAdminStatus": ".1.3.6.1.2.1.15.3.1.3",
586
- "BGP4-MIB::bgpPeerNegotiatedVersion": ".1.3.6.1.2.1.15.3.1.4",
587
- "BGP4-MIB::bgpPeerLocalAddr": ".1.3.6.1.2.1.15.3.1.5",
588
- "BGP4-MIB::bgpPeerLocalPort": ".1.3.6.1.2.1.15.3.1.6",
589
- "BGP4-MIB::bgpPeerRemoteAddr": ".1.3.6.1.2.1.15.3.1.7",
590
- "BGP4-MIB::bgpPeerRemotePort": ".1.3.6.1.2.1.15.3.1.8",
591
- "BGP4-MIB::bgpPeerRemoteAs": ".1.3.6.1.2.1.15.3.1.9",
592
- "BGP4-MIB::bgpPeerInUpdates": ".1.3.6.1.2.1.15.3.1.10",
593
- "BGP4-MIB::bgpPeerOutUpdates": ".1.3.6.1.2.1.15.3.1.11",
594
- "BGP4-MIB::bgpPeerInTotalMessages": ".1.3.6.1.2.1.15.3.1.12",
595
- "BGP4-MIB::bgpPeerOutTotalMessages": ".1.3.6.1.2.1.15.3.1.13",
596
- "BGP4-MIB::bgpPeerLastError": ".1.3.6.1.2.1.15.3.1.14",
597
- "BGP4-MIB::bgpPeerFsmEstablishedTransitions": ".1.3.6.1.2.1.15.3.1.15",
598
- "BGP4-MIB::bgpPeerFsmEstablishedTime": ".1.3.6.1.2.1.15.3.1.16",
599
- "BGP4-MIB::bgpPeerConnectRetryInterval": ".1.3.6.1.2.1.15.3.1.17",
600
- "BGP4-MIB::bgpPeerHoldTime": ".1.3.6.1.2.1.15.3.1.18",
601
- "BGP4-MIB::bgpPeerKeepAlive": ".1.3.6.1.2.1.15.3.1.19",
602
- "BGP4-MIB::bgpPeerHoldTimeConfigured": ".1.3.6.1.2.1.15.3.1.20",
603
- "BGP4-MIB::bgpPeerKeepAliveConfigured": ".1.3.6.1.2.1.15.3.1.21",
604
- "BGP4-MIB::bgpPeerMinASOriginationInterval": ".1.3.6.1.2.1.15.3.1.22",
605
- "BGP4-MIB::bgpPeerMinRouteAdvertisementInterval": ".1.3.6.1.2.1.15.3.1.23",
606
- "BGP4-MIB::bgpPeerInUpdateElapsedTime": ".1.3.6.1.2.1.15.3.1.24",
607
-
608
- // ============================================================================
609
- // OSPF-MIB - RFC 4750
610
- // ============================================================================
611
- "OSPF-MIB::ospfRouterId": ".1.3.6.1.2.1.14.1.1",
612
- "OSPF-MIB::ospfAdminStat": ".1.3.6.1.2.1.14.1.2",
613
- "OSPF-MIB::ospfVersionNumber": ".1.3.6.1.2.1.14.1.3",
614
- "OSPF-MIB::ospfAreaBdrRtrStatus": ".1.3.6.1.2.1.14.1.4",
615
- "OSPF-MIB::ospfASBdrRtrStatus": ".1.3.6.1.2.1.14.1.5",
616
- "OSPF-MIB::ospfExternLsaCount": ".1.3.6.1.2.1.14.1.6",
617
- "OSPF-MIB::ospfExternLsaCksumSum": ".1.3.6.1.2.1.14.1.7",
618
- "OSPF-MIB::ospfTOSSupport": ".1.3.6.1.2.1.14.1.8",
619
- "OSPF-MIB::ospfOriginateNewLsas": ".1.3.6.1.2.1.14.1.9",
620
- "OSPF-MIB::ospfRxNewLsas": ".1.3.6.1.2.1.14.1.10",
621
- "OSPF-MIB::ospfExtLsdbLimit": ".1.3.6.1.2.1.14.1.11",
622
- "OSPF-MIB::ospfMulticastExtensions": ".1.3.6.1.2.1.14.1.12",
623
- "OSPF-MIB::ospfExitOverflowInterval": ".1.3.6.1.2.1.14.1.13",
624
- "OSPF-MIB::ospfDemandExtensions": ".1.3.6.1.2.1.14.1.14",
625
-
626
- // ============================================================================
627
- // DISMAN-EVENT-MIB - RFC 2981
628
- // ============================================================================
629
- "DISMAN-EVENT-MIB::sysUpTimeInstance": ".1.3.6.1.2.1.1.3.0",
630
-}
631
-
632
-// Translation cache for snmptranslate results
633
-// Thread-safe cache to avoid repeated exec calls
634
-var (
635
- mibTranslationCache = make(map[string]string)
636
- mibTranslationCacheMu sync.RWMutex
637
- mibCacheMaxSize = 1000 // Reasonable limit for production
638
-)
639
-
640
-// translateMIB translates MIB name to numeric OID
641
-// Strategy: hardcoded map → cache → snmptranslate (optional) → error
642
-// validateMIBName checks if a MIB name contains only safe characters.
643
-// This prevents command injection when passing to snmptranslate.
644
-// Valid MIB names: alphanumeric, dots, colons, hyphens, underscores.
645
-func validateMIBName(mibName string) error {
646
- if mibName == "" {
647
- return fmt.Errorf("empty MIB name")
648
- }
649
-
650
- // Max length check (reasonable limit for MIB names)
651
- if len(mibName) > 256 {
652
- return fmt.Errorf("MIB name too long")
653
- }
654
-
655
- // Check each character for safety
656
- for i, ch := range mibName {
657
- if (ch >= 'a' && ch <= 'z') ||
658
- (ch >= 'A' && ch <= 'Z') ||
659
- (ch >= '0' && ch <= '9') ||
660
- ch == '.' || ch == ':' || ch == '-' || ch == '_' {
661
- continue
662
- }
663
- return fmt.Errorf("invalid character '%c' at position %d in MIB name", ch, i)
664
- }
665
-
666
- return nil
667
-}
668
-
669
-func translateMIB(mibName string) (string, error) {
670
- // 1. Check if already numeric OID (passthrough)
671
- if strings.HasPrefix(mibName, ".") || (len(mibName) > 0 && mibName[0] >= '0' && mibName[0] <= '9') {
672
- return mibName, nil
673
- }
674
-
675
- // 2. Validate MIB name BEFORE any cache operations (defense in depth)
676
- if err := validateMIBName(mibName); err != nil {
677
- return "", fmt.Errorf("invalid MIB name: %w", err)
678
- }
679
-
680
- // 3. Check hardcoded map (covers ~90% of use cases)
681
- if oid, found := mibToOID[mibName]; found {
682
- return oid, nil
683
- }
684
-
685
- // 4. Check translation cache (from previous snmptranslate calls)
686
- mibTranslationCacheMu.RLock()
687
- if oid, found := mibTranslationCache[mibName]; found {
688
- mibTranslationCacheMu.RUnlock()
689
- return oid, nil
690
- }
691
- mibTranslationCacheMu.RUnlock()
692
-
693
- // 5. Try snmptranslate (optional - fails gracefully if not installed)
694
- // Use timeout to prevent hanging on slow/stuck snmptranslate
695
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
696
- defer cancel()
697
- cmd := exec.CommandContext(ctx, "snmptranslate", "-On", mibName)
698
- output, err := cmd.Output()
699
- if err == nil {
700
- oid := strings.TrimSpace(string(output))
701
- // Validate OID format before caching
702
- if strings.HasPrefix(oid, ".") {
703
- // Cache the result (with size limit)
704
- mibTranslationCacheMu.Lock()
705
- if len(mibTranslationCache) < mibCacheMaxSize {
706
- mibTranslationCache[mibName] = oid
707
- }
708
- mibTranslationCacheMu.Unlock()
709
- return oid, nil
710
- }
711
- }
712
-
713
- // 5. Unknown MIB - fail with helpful error message
714
- return "", fmt.Errorf("unknown MIB: %s (install net-snmp or use numeric OIDs)", mibName)
715
-}
716
-
717
-// parseSNMPWalkAll parses all SNMP entries from snmpwalk output.
718
-// Returns a slice of all parsed entries, preserving order.
719
-// This handles the same complex formats as parseSNMPWalk but returns all entries.
720
-func parseSNMPWalkAll(data string, logger Logger) ([]snmpEntry, error) {
721
- lines := strings.Split(data, "\n")
722
- if len(lines) > 0 && lines[len(lines)-1] == "" {
723
- lines = lines[:len(lines)-1]
724
- }
725
-
726
- var entries []snmpEntry
727
-
728
- for i := 0; i < len(lines); {
729
- line := lines[i]
730
-
731
- // Normalize textual OIDs to numeric format (e.g., IF-MIB::ifDescr.1 -> .1.3.6.1.2.1.2.2.1.2.1)
732
- line = normalizeTextualOIDLine(line)
733
-
734
- // Check if this is a new entry (starts with OID)
735
- if !strings.HasPrefix(strings.TrimSpace(line), ".") {
736
- if strings.TrimSpace(line) != "" {
737
- logger.Debug("skipping non-OID line in snmp walk output", "line", line, "lineNumber", i+1)
738
- }
739
- i++
740
- continue
741
- }
742
-
743
- // Parse line using helper
744
- oid, valueType, valueStr, ok := parseSNMPLine(line)
745
- if !ok {
746
- logger.Debug("skipping malformed snmp line", "line", line, "lineNumber", i+1)
747
- i++
748
- continue
749
- }
750
-
751
- // Handle "Wrong Type" warnings - extract actual type and value
752
- if strings.HasPrefix(valueType, "Wrong Type") {
753
- actualTypeParts := strings.SplitN(valueStr, ":", 2)
754
- if len(actualTypeParts) == 2 {
755
- valueType = strings.TrimSpace(actualTypeParts[0])
756
- valueStr = strings.TrimLeft(actualTypeParts[1], " ")
757
- } else {
758
- return nil, fmt.Errorf("cannot parse snmp walk output")
759
- }
760
- }
761
-
762
- // Collect multiline values using helper
763
- finalValue, consumed := collectMultilineValue(valueType, valueStr, lines, i)
764
-
765
- entries = append(entries, snmpEntry{
766
- oid: oid,
767
- valueType: valueType,
768
- value: finalValue,
769
- })
770
-
771
- i += consumed + 1
772
- }
773
-
774
- return entries, nil
775
-}
776
-
777
-// parseSNMPWalk parses SNMP walk text output and extracts value for target OID.
778
-//
779
-// This custom parser processes text output from the snmpwalk command-line tool
780
-// (not binary SNMP protocol data). It handles complex SNMP data formats including:
781
-// - Multiple value types: STRING, INTEGER, Hex-STRING, Timeticks, Counter32, etc.
782
-// - Multiline quoted strings with proper quote tracking
783
-// - Multiline hex strings with continuation lines
784
-// - MIB name to numeric OID translation
785
-// - "Wrong Type" warnings with type/value extraction
786
-// - NULL and empty string handling
787
-//
788
-// Example SNMP walk format:
789
-//
790
-// .1.3.6.1.2.1.1.1.0 = STRING: "Linux server 5.4.0"
791
-// .1.3.6.1.2.1.1.3.0 = Timeticks: (12345) 0:02:03.45
792
-//
793
-// Parameters:
794
-// - data: Text output from snmpwalk command (one or more OID=value lines)
795
-// - targetOID: OID to extract (supports MIB names like "SNMPv2-MIB::sysDescr.0")
796
-//
797
-// Returns:
798
-// - snmpEntry: Parsed entry containing OID, value type, and value
799
-// - error: Parsing error if format is invalid or target OID not found
800
-//
801
-// This function cannot use gosnmp library because it parses command-line tool output,
802
-// not binary SNMP protocol data. No standard library exists for this text format.
803
-// normalizeOID adds a leading dot to numeric OIDs if missing
804
-func normalizeOID(oid string) string {
805
- if !strings.HasPrefix(oid, ".") && len(oid) > 0 && oid[0] >= '0' && oid[0] <= '9' {
806
- return "." + oid
807
- }
808
- return oid
809
-}
810
-
811
-// normalizeTextualOIDLine converts textual OID lines to numeric OID format.
812
-// Input: "IF-MIB::ifDescr.1 = STRING: \"eth0\""
813
-// Output: ".1.3.6.1.2.1.2.2.1.2.1 = STRING: \"eth0\""
814
-//
815
-// Returns original line unchanged if:
816
-// - Not a textual OID format (no "::" before "=")
817
-// - MIB name is not in the built-in translation table (translateMIB fails)
818
-//
819
-// IMPORTANT: Unknown MIBs (vendor-specific, custom, or not in hardcoded map) are
820
-// returned as-is. Since the parser expects lines starting with ".", these untranslated
821
-// textual OID lines will be silently skipped. For full textual OID support, either:
822
-// - Use numeric OIDs in snmpwalk output (snmpwalk -On)
823
-// - Extend translateMIB() with additional MIB mappings
824
-// - Use snmptranslate externally before passing to this library
825
-func normalizeTextualOIDLine(line string) string {
826
- trimmed := strings.TrimSpace(line)
827
- if trimmed == "" || strings.HasPrefix(trimmed, "#") {
828
- return line
829
- }
830
-
831
- // Check for textual OID format (contains :: before =)
832
- eqIdx := strings.Index(trimmed, "=")
833
- if eqIdx == -1 {
834
- return line
835
- }
836
-
837
- oidPart := strings.TrimSpace(trimmed[:eqIdx])
838
-
839
- // Check if OID contains :: (textual format)
840
- if !strings.Contains(oidPart, "::") {
841
- return line
842
- }
843
-
844
- // Extract MIB name and instance suffix
845
- // Format: MIB-NAME::objectName.instance or MIB-NAME::objectName
846
- // Split on :: to get MIB::object
847
- parts := strings.SplitN(oidPart, "::", 2)
848
- if len(parts) != 2 {
849
- return line
850
- }
851
-
852
- mibPrefix := parts[0]
853
- objectAndInstance := parts[1]
854
-
855
- // Split object name from instance (e.g., "ifDescr.1" -> "ifDescr", "1")
856
- var objectName, instanceSuffix string
857
- dotIdx := strings.Index(objectAndInstance, ".")
858
- if dotIdx == -1 {
859
- objectName = objectAndInstance
860
- instanceSuffix = ""
861
- } else {
862
- objectName = objectAndInstance[:dotIdx]
863
- instanceSuffix = objectAndInstance[dotIdx:] // Includes the dot
864
- }
865
-
866
- // Construct full MIB name for translation
867
- fullMIBName := mibPrefix + "::" + objectName
868
-
869
- // Try to translate
870
- numericOID, err := translateMIB(fullMIBName)
871
- if err != nil {
872
- // Can't translate - return original line (will be skipped later)
873
- return line
874
- }
875
-
876
- // Reconstruct line with numeric OID
877
- newOID := numericOID + instanceSuffix
878
- rest := trimmed[eqIdx:]
879
- return newOID + " " + rest
880
-}
881
-
882
-// parseSNMPLine parses a single SNMP walk output line into OID, type, and value
883
-func parseSNMPLine(line string) (oid, valueType, value string, ok bool) {
884
- // Split on = to get OID and rest
885
- parts := strings.SplitN(line, "=", 2)
886
- if len(parts) != 2 {
887
- return "", "", "", false
888
- }
889
-
890
- oid = strings.TrimSpace(parts[0])
891
- rest := strings.TrimSpace(parts[1])
892
-
893
- // Extract type and value (format: "TYPE: value")
894
- typeParts := strings.SplitN(rest, ":", 2)
895
-
896
- if len(typeParts) == 2 {
897
- // Format: "TYPE: value"
898
- valueType = strings.TrimSpace(typeParts[0])
899
- value = strings.TrimLeft(typeParts[1], " ")
900
- return oid, valueType, value, true
901
- }
902
-
903
- // No colon - could be "NULL", empty quotes "", or raw value
904
- trimmed := strings.TrimSpace(typeParts[0])
905
- if trimmed == "NULL" {
906
- return oid, "NULL", "", true
907
- }
908
- if trimmed == "\"\"" {
909
- return oid, "EMPTY", "", true
910
- }
911
- // Arbitrary value without type
912
- return oid, "", trimmed, true
913
-}
914
-
915
-// isMultilineValue determines if an SNMP value requires multiline processing
916
-func isMultilineValue(valueType, value string) bool {
917
- if valueType == "Hex-STRING" {
918
- return true
919
- }
920
- if valueType == "STRING" {
921
- // Quoted string without closing quote, or unquoted string
922
- if strings.HasPrefix(value, "\"") && !strings.HasSuffix(strings.TrimRight(value, " \t"), "\"") {
923
- return true
924
- }
925
- if !strings.HasPrefix(value, "\"") {
926
- return true
927
- }
928
- }
929
- return false
930
-}
931
-
932
-// appendContinuationLine adds a continuation line to a multiline SNMP value
933
-func appendContinuationLine(entry *snmpEntry, line string) {
934
- if entry.valueType == "Hex-STRING" {
935
- // For Hex-STRING, append with space separator (trimmed)
936
- entry.value += " " + strings.TrimSpace(line)
937
- } else {
938
- // For STRING, append with newline preserved
939
- entry.value += "\n" + line
940
- }
941
-}
942
-
943
-// isStringClosed checks if a multiline STRING value is now complete
944
-func isStringClosed(valueType, line string) bool {
945
- if valueType != "STRING" {
946
- return false
947
- }
948
- trimmed := strings.TrimRight(line, " \t")
949
- // Closed if ends with unescaped quote
950
- return strings.HasSuffix(trimmed, "\"") && !strings.HasSuffix(trimmed, "\\\"")
951
-}
952
-
953
-// collectMultilineValue collects continuation lines for multiline SNMP values
954
-// Returns the complete value and the number of continuation lines consumed
955
-func collectMultilineValue(valueType, initialValue string, lines []string, startIdx int) (string, int) {
956
- value := initialValue
957
- consumed := 0
958
-
959
- if valueType == "STRING" && strings.HasPrefix(initialValue, "\"") {
960
- trimmed := strings.TrimRight(initialValue, " \t")
961
- isMultiline := (trimmed == "\"") || (!strings.HasSuffix(trimmed, "\""))
962
-
963
- if isMultiline {
964
- // Multiline quoted string
965
- for startIdx+consumed+1 < len(lines) && !strings.HasPrefix(strings.TrimSpace(lines[startIdx+consumed+1]), ".") {
966
- consumed++
967
- value += "\n" + lines[startIdx+consumed]
968
- if isStringClosed(valueType, lines[startIdx+consumed]) {
969
- break
970
- }
971
- }
972
- }
973
- } else if valueType == "Hex-STRING" {
974
- // Multiline hex-string - continuation lines are indented hex bytes
975
- for startIdx+consumed+1 < len(lines) && !strings.HasPrefix(strings.TrimSpace(lines[startIdx+consumed+1]), ".") {
976
- consumed++
977
- value += " " + strings.TrimSpace(lines[startIdx+consumed])
978
- }
979
- }
980
-
981
- return value, consumed
982
-}
983
-
984
-func parseSNMPWalk(data string, targetOID string) (snmpEntry, error) {
985
- // Translate MIB name to numeric OID (if needed)
986
- translatedOID, err := translateMIB(targetOID)
987
- if err != nil {
988
- return snmpEntry{}, err
989
- }
990
- targetOID = normalizeOID(translatedOID)
991
-
992
- // Split lines and remove trailing empty line (from YAML | formatting)
993
- lines := strings.Split(data, "\n")
994
- if len(lines) > 0 && lines[len(lines)-1] == "" {
995
- lines = lines[:len(lines)-1]
996
- }
997
-
998
- var current snmpEntry
999
- inMultiline := false
1000
- wentMultiline := false // Track if we actually appended continuation lines
1001
-
1002
- for i := 0; i < len(lines); i++ {
1003
- line := lines[i]
1004
-
1005
- // Normalize textual OIDs to numeric format (e.g., IF-MIB::ifDescr.1 -> .1.3.6.1.2.1.2.2.1.2.1)
1006
- line = normalizeTextualOIDLine(line)
1007
-
1008
- // Check if this is a new entry (starts with OID)
1009
- if strings.HasPrefix(strings.TrimSpace(line), ".") {
1010
- // If we were in multiline and found our target, check if it's complete
1011
- if inMultiline && current.oid == targetOID {
1012
- // Check for broken quoting - if we started with a quote but never closed it
1013
- if current.valueType == "STRING" && strings.HasPrefix(strings.TrimSpace(current.value), "\"") {
1014
- // Still in quoted mode but hit new OID - broken quoting
1015
- return snmpEntry{}, fmt.Errorf("malformed quoted string: missing closing quote")
1016
- }
1017
- // Only preserve whitespace for STRING if we actually went multiline
1018
- if current.valueType != "STRING" || !wentMultiline {
1019
- current.value = strings.TrimSpace(current.value)
1020
- }
1021
- return current, nil
1022
- }
1023
-
1024
- // Parse new entry using helper
1025
- oid, valueType, valueStr, ok := parseSNMPLine(line)
1026
- if !ok {
1027
- continue
1028
- }
1029
-
1030
- current = snmpEntry{
1031
- oid: oid,
1032
- valueType: valueType,
1033
- value: valueStr,
1034
- }
1035
-
1036
- // Check if this is a multiline value using helper
1037
- inMultiline = isMultilineValue(valueType, valueStr)
1038
-
1039
- // If not multiline and this is our target, we're done
1040
- if !inMultiline && oid == targetOID {
1041
- return current, nil
1042
- }
1043
- } else if inMultiline {
1044
- // Continuation line
1045
- wentMultiline = true
1046
- appendContinuationLine(¤t, line)
1047
-
1048
- // Check if STRING is now closed using helper
1049
- if isStringClosed(current.valueType, line) {
1050
- inMultiline = false
1051
- if current.oid == targetOID {
1052
- // Don't trim - preserve exact formatting
1053
- return current, nil
1054
- }
1055
- }
1056
- }
1057
- }
1058
-
1059
- // Check if last entry matches (for cases where file ends without newline)
1060
- if current.oid == targetOID {
1061
- // Only preserve whitespace for STRING if we actually went multiline
1062
- if current.valueType != "STRING" || !wentMultiline {
1063
- current.value = strings.TrimSpace(current.value)
1064
- }
1065
- return current, nil
1066
- }
1067
-
1068
- return snmpEntry{}, fmt.Errorf("OID not found: %s", targetOID)
1069
-}
1070
-
1071
-// formatSNMPValue applies format conversion to SNMP value
1072
-// trimFloatZeros: whether to trim trailing zeros from Opaque floats (true for WALK_VALUE, false for WALK_TO_JSON)
1073
-func formatSNMPValue(entry snmpEntry, formatMode int, trimFloatZeros bool) (string, error) {
1074
- switch entry.valueType {
1075
- case "STRING":
1076
- return formatSTRING(entry.value, formatMode)
1077
- case "Hex-STRING":
1078
- return formatHexSTRING(entry.value, formatMode)
1079
- case "INTEGER":
1080
- return strings.TrimSpace(entry.value), nil
1081
- case "BITS":
1082
- if formatMode == 3 {
1083
- return convertBITSToInteger(entry.value)
1084
- }
1085
- return strings.TrimSpace(entry.value), nil
1086
- case "IpAddress", "Counter32", "Gauge32", "Counter64", "Timeticks":
1087
- // Return value as-is for these types
1088
- return strings.TrimSpace(entry.value), nil
1089
- case "Opaque":
1090
- // Opaque can have wrapped types - parse them
1091
- return formatOpaque(entry.value, trimFloatZeros), nil
1092
- case "OID":
1093
- return strings.TrimSpace(entry.value), nil
1094
- case "NULL":
1095
- // NULL type returns "NULL" as a string
1096
- return "NULL", nil
1097
- case "EMPTY":
1098
- // Empty quotes - return empty string
1099
- return "", nil
1100
- case "":
1101
- // No type specified - return value as-is (arbitrary number)
1102
- return strings.TrimSpace(entry.value), nil
1103
- default:
1104
- // Unknown type - return value as-is
1105
- return strings.TrimSpace(entry.value), nil
1106
- }
1107
-}
1108
-
1109
-// formatSTRING handles STRING formatting
1110
-func formatSTRING(value string, formatMode int) (string, error) {
1111
- // Check if it's a quoted string (trim only for quote detection)
1112
- trimmed := strings.TrimSpace(value)
1113
- if strings.HasPrefix(trimmed, "\"") && strings.HasSuffix(trimmed, "\"") && len(trimmed) >= 2 {
1114
- // Unescape the quoted string
1115
- unquoted := trimmed[1 : len(trimmed)-1]
1116
- // Replace escape sequences
1117
- unquoted = strings.ReplaceAll(unquoted, "\\\"", "\"")
1118
- unquoted = strings.ReplaceAll(unquoted, "\\\\", "\\")
1119
- return unquoted, nil
1120
- }
1121
- // Unquoted string - return as-is (preserve whitespace)
1122
- return value, nil
1123
-}
1124
-
1125
-// formatHexSTRING handles Hex-STRING formatting
1126
-func formatHexSTRING(value string, formatMode int) (string, error) {
1127
- // Clean up the hex string (remove extra spaces)
1128
- value = strings.TrimSpace(value)
1129
-
1130
- switch formatMode {
1131
- case 0:
1132
- // Unchanged - normalize spaces
1133
- parts := strings.Fields(value)
1134
- return strings.Join(parts, " "), nil
1135
-
1136
- case 1:
1137
- // UTF-8 conversion
1138
- bytes, err := parseHexBytes(value)
1139
- if err != nil {
1140
- return "", err
1141
- }
1142
- // Remove null terminator if present
1143
- if len(bytes) > 0 && bytes[len(bytes)-1] == 0 {
1144
- bytes = bytes[:len(bytes)-1]
1145
- }
1146
- // Convert to string and replace invalid UTF-8 bytes with '?'
1147
- result := string(bytes)
1148
- if !utf8.ValidString(result) {
1149
- // Iterate over bytes and decode runes manually to properly handle
1150
- // both invalid bytes and legitimate U+FFFD characters
1151
- var fixed strings.Builder
1152
- for i := 0; i < len(bytes); {
1153
- r, size := utf8.DecodeRune(bytes[i:])
1154
- // If DecodeRune returns (RuneError, 1), it's an invalid byte
1155
- // If it returns (RuneError, 3), it's a valid encoding of U+FFFD
1156
- if r == utf8.RuneError && size == 1 {
1157
- fixed.WriteByte('?')
1158
- i++
1159
- } else {
1160
- fixed.WriteRune(r)
1161
- i += size
1162
- }
1163
- }
1164
- return fixed.String(), nil
1165
- }
1166
- return result, nil
1167
-
1168
- case 2:
1169
- // MAC address format
1170
- bytes, err := parseHexBytes(value)
1171
- if err != nil {
1172
- return "", err
1173
- }
1174
- // Format as colon-separated hex
1175
- var parts []string
1176
- for _, b := range bytes {
1177
- parts = append(parts, fmt.Sprintf("%02X", b))
1178
- }
1179
- return strings.Join(parts, ":"), nil
1180
-
1181
- default:
1182
- // Unknown format mode - return unchanged
1183
- parts := strings.Fields(value)
1184
- return strings.Join(parts, " "), nil
1185
- }
1186
-}
1187
-
1188
-// parseHexBytes parses hex string like "74 65 73 74" into bytes
1189
-func parseHexBytes(hexStr string) ([]byte, error) {
1190
- parts := strings.Fields(hexStr)
1191
- var bytes []byte
1192
- for _, part := range parts {
1193
- b, err := hex.DecodeString(part)
1194
- if err != nil {
1195
- return nil, fmt.Errorf("invalid hex string: %v", err)
1196
- }
1197
- bytes = append(bytes, b...)
1198
- }
1199
- return bytes, nil
1200
-}
1201
-
1202
-// convertBITSToInteger converts BITS hex string to integer (little-endian)
1203
-func convertBITSToInteger(value string) (string, error) {
1204
- bytes, err := parseHexBytes(value)
1205
- if err != nil {
1206
- return "", err
1207
- }
1208
-
1209
- // BITS uses little-endian byte order
1210
- // Reverse the bytes
1211
- for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
1212
- bytes[i], bytes[j] = bytes[j], bytes[i]
1213
- }
1214
-
1215
- // Convert bytes to integer (big-endian after reversal)
1216
- var result uint64
1217
- for _, b := range bytes {
1218
- result = (result << 8) | uint64(b)
1219
- }
1220
-
1221
- return fmt.Sprintf("%d", result), nil
1222
-}
1223
-
1224
-// formatOpaque handles Opaque wrapped types
1225
-func formatOpaque(value string, trimFloatZeros bool) string {
1226
- // Opaque can have wrapped types like "Float: 0.460000" or "STRING: \"hello\""
1227
- // Extract the wrapped type and value
1228
- parts := strings.SplitN(value, ":", 2)
1229
- if len(parts) == 2 {
1230
- wrappedType := strings.TrimSpace(parts[0])
1231
- val := strings.TrimSpace(parts[1])
1232
-
1233
- // For STRING wrapped type, strip quotes
1234
- if wrappedType == "STRING" && len(val) >= 2 && strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"") {
1235
- val = val[1 : len(val)-1]
1236
- }
1237
-
1238
- // For Float wrapped type, optionally trim trailing zeros
1239
- if wrappedType == "Float" && trimFloatZeros && strings.Contains(val, ".") {
1240
- val = strings.TrimRight(val, "0")
1241
- val = strings.TrimRight(val, ".")
1242
- }
1243
-
1244
- // For other types (Unsigned32, etc.), return value as-is
1245
- return val
1246
- }
1247
- return strings.TrimSpace(value)
1248
-}
1249
-
1250
-// snmpGetValue formats raw SNMP hex-string value.
1251
-// Format modes:
1252
-// 0 = unchanged (return raw hex as-is with normalized spaces)
1253
-// 1 = UTF-8 (decode hex-string to UTF-8 string)
1254
-// 2 = MAC (format hex-string as MAC address with colons)
1255
-// 3 = BITS (convert BITS to integer)
1256
-func snmpGetValue(value Value, params string) (Value, error) {
1257
- lines := strings.Split(params, "\n")
1258
- if len(lines) < 1 {
1259
- return Value{}, fmt.Errorf("snmp get value requires format mode parameter")
1260
- }
1261
-
1262
- formatMode := 0
1263
- if len(lines) >= 1 && strings.TrimSpace(lines[0]) != "" {
1264
- var err error
1265
- formatMode, err = strconv.Atoi(strings.TrimSpace(lines[0]))
1266
- if err != nil {
1267
- return Value{}, fmt.Errorf("invalid format mode: %v", err)
1268
- }
1269
- }
1270
-
1271
- // For BITS mode, convert directly
1272
- if formatMode == 3 {
1273
- result, err := convertBITSToInteger(value.Data)
1274
- if err != nil {
1275
- return Value{}, err
1276
- }
1277
- return Value{Data: result, Type: ValueTypeStr}, nil
1278
- }
1279
-
1280
- // Apply format conversion to hex-string input
1281
- result, err := formatHexSTRING(value.Data, formatMode)
1282
- if err != nil {
1283
- return Value{}, err
1284
- }
1285
-
1286
- return Value{Data: result, Type: ValueTypeStr}, nil
1287
-}
1288
-
1289
-// snmpWalkToJSON converts SNMP walk output to JSON discovery format.
1290
-// Params format (one entry per 3 lines):
1291
-//
1292
-// MACRO_NAME
1293
-// OID_PREFIX
1294
-// FORMAT_MODE
1295
-func snmpWalkToJSON(value Value, params string, logger Logger) (Value, error) {
1296
- // Parse params into macro definitions
1297
- type macroDef struct {
1298
- name string
1299
- oidPrefix string
1300
- formatMode int
1301
- }
1302
-
1303
- var macros []macroDef
1304
- lines := strings.Split(params, "\n")
1305
-
1306
- // Validate params format: must be triplets (macro, oid, format)
1307
- // Exception: empty params (all whitespace) is valid and returns []
1308
- trimmedParams := strings.TrimSpace(params)
1309
- if trimmedParams != "" && len(lines)%3 != 0 {
1310
- return Value{}, fmt.Errorf("snmp walk to json requires parameters in triplets (macro, oid, format)")
1311
- }
1312
-
1313
- for i := 0; i+2 < len(lines); i += 3 {
1314
- name := strings.TrimSpace(lines[i])
1315
- oidPrefix := strings.TrimSpace(lines[i+1])
1316
- formatModeStr := strings.TrimSpace(lines[i+2])
1317
-
1318
- if name == "" || oidPrefix == "" {
1319
- continue
1320
- }
1321
-
1322
- formatMode := 0
1323
- if formatModeStr != "" {
1324
- var err error
1325
- formatMode, err = strconv.Atoi(formatModeStr)
1326
- if err != nil {
1327
- return Value{}, fmt.Errorf("invalid format mode: %v", err)
1328
- }
1329
- }
1330
-
1331
- // Translate MIB name to numeric OID (supports textual OIDs like IF-MIB::ifDescr)
1332
- translatedOID, err := translateMIB(oidPrefix)
1333
- if err != nil {
1334
- return Value{}, err
1335
- }
1336
- oidPrefix = normalizeOID(translatedOID)
1337
-
1338
- macros = append(macros, macroDef{
1339
- name: name,
1340
- oidPrefix: oidPrefix,
1341
- formatMode: formatMode,
1342
- })
1343
- }
1344
-
1345
- // If no macros defined, return empty array
1346
- if len(macros) == 0 {
1347
- return Value{Data: "[]", Type: ValueTypeStr}, nil
1348
- }
1349
-
1350
- // Parse SNMP walk data using shared parser
1351
- entries, err := parseSNMPWalkAll(value.Data, logger)
1352
- if err != nil {
1353
- return Value{}, err
1354
- }
1355
-
1356
- // Group entries by index (last OID component)
1357
- type indexData struct {
1358
- index string
1359
- values map[string]string
1360
- }
1361
- indices := make(map[string]*indexData)
1362
-
1363
- // Process each parsed entry
1364
- for _, entry := range entries {
1365
- // Check which macro this OID matches
1366
- for _, macro := range macros {
1367
- // Build match prefix - add dot only if not already present
1368
- matchPrefix := macro.oidPrefix
1369
- if !strings.HasSuffix(matchPrefix, ".") {
1370
- matchPrefix += "."
1371
- }
1372
-
1373
- if strings.HasPrefix(entry.oid, matchPrefix) {
1374
- // Extract index (everything after the prefix)
1375
- index := strings.TrimPrefix(entry.oid, matchPrefix)
1376
-
1377
- // Get or create index data
1378
- if indices[index] == nil {
1379
- indices[index] = &indexData{
1380
- index: index,
1381
- values: make(map[string]string),
1382
- }
1383
- }
1384
-
1385
- // Format the value (preserve float zeros for WALK_TO_JSON)
1386
- formatted, err := formatSNMPValue(entry, macro.formatMode, false)
1387
- if err != nil {
1388
- return Value{}, err
1389
- }
1390
-
1391
- indices[index].values[macro.name] = formatted
1392
- break
1393
- }
1394
- }
1395
- }
1396
-
1397
- // If no data found for any macro, check if it's due to bad data or just empty input
1398
- if len(indices) == 0 {
1399
- // If we found valid SNMP entries but none matched our macros, that's an error
1400
- // If we found no valid entries at all (empty or bad data), return error only for bad data
1401
- if len(entries) > 0 {
1402
- // Found valid SNMP entries but none matched - this is an error
1403
- return Value{}, fmt.Errorf("cannot convert snmp walk to json")
1404
- }
1405
- // No valid entries found - could be empty input (ok) or bad data (error)
1406
- // Distinguish by checking if input has non-empty, non-whitespace content
1407
- trimmedData := strings.TrimSpace(value.Data)
1408
- if trimmedData != "" {
1409
- // Non-empty input but no valid SNMP entries - bad data
1410
- return Value{}, fmt.Errorf("cannot convert snmp walk to json")
1411
- }
1412
- // Empty input - return empty array
1413
- return Value{Data: "[]", Type: ValueTypeStr}, nil
1414
- }
1415
-
1416
- // Convert to JSON array, sorted by index in descending order
1417
- var indexList []string
1418
- for idx := range indices {
1419
- indexList = append(indexList, idx)
1420
- }
1421
- sort.Sort(sort.Reverse(sort.StringSlice(indexList)))
1422
-
1423
- // Build JSON manually to preserve field order: {#SNMPINDEX} first, then macros in order
1424
- var jsonParts []string
1425
- for _, idx := range indexList {
1426
- data := indices[idx]
1427
-
1428
- // Start with {#SNMPINDEX}
1429
- var fields []string
1430
- fields = append(fields, fmt.Sprintf("\"{#SNMPINDEX}\":%q", data.index))
1431
-
1432
- // Add macros in definition order
1433
- for _, macro := range macros {
1434
- if val, ok := data.values[macro.name]; ok {
1435
- // Handle NULL values as JSON null
1436
- if val == "NULL" {
1437
- fields = append(fields, fmt.Sprintf("%q:null", macro.name))
1438
- } else {
1439
- fields = append(fields, fmt.Sprintf("%q:%q", macro.name, val))
1440
- }
1441
- }
1442
- }
1443
-
1444
- jsonParts = append(jsonParts, "{"+strings.Join(fields, ",")+"}")
1445
- }
1446
-
1447
- return Value{Data: "[" + strings.Join(jsonParts, ",") + "]", Type: ValueTypeStr}, nil
1448
-}
1449
-
1450
-// snmpWalkToJSONMulti converts SNMP walk data to multiple discovery metrics
1451
-// Each discovery item becomes a separate Result.Metric with index label
1452
-func snmpWalkToJSONMulti(value Value, params string, logger Logger) (Result, error) {
1453
- // Parse params into macro definitions
1454
- type macroDef struct {
1455
- name string
1456
- oidPrefix string
1457
- formatMode int
1458
- }
1459
-
1460
- var macros []macroDef
1461
- lines := strings.Split(params, "\n")
1462
-
1463
- // Validate params format: must be triplets (macro, oid, format)
1464
- // Exception: empty params (all whitespace) is valid and returns []
1465
- trimmedParams := strings.TrimSpace(params)
1466
- if trimmedParams != "" && len(lines)%3 != 0 {
1467
- err := fmt.Errorf("snmp walk to json requires parameters in triplets (macro, oid, format)")
1468
- return Result{Error: err}, err
1469
- }
1470
-
1471
- for i := 0; i+2 < len(lines); i += 3 {
1472
- name := strings.TrimSpace(lines[i])
1473
- oidPrefix := strings.TrimSpace(lines[i+1])
1474
- formatModeStr := strings.TrimSpace(lines[i+2])
1475
-
1476
- if name == "" || oidPrefix == "" {
1477
- continue
1478
- }
1479
-
1480
- formatMode := 0
1481
- if formatModeStr != "" {
1482
- var err error
1483
- formatMode, err = strconv.Atoi(formatModeStr)
1484
- if err != nil {
1485
- err = fmt.Errorf("invalid format mode: %v", err)
1486
- return Result{Error: err}, err
1487
- }
1488
- }
1489
-
1490
- // Translate MIB name to numeric OID (supports textual OIDs like IF-MIB::ifDescr)
1491
- translatedOID, err := translateMIB(oidPrefix)
1492
- if err != nil {
1493
- return Result{Error: err}, err
1494
- }
1495
- oidPrefix = normalizeOID(translatedOID)
1496
-
1497
- macros = append(macros, macroDef{
1498
- name: name,
1499
- oidPrefix: oidPrefix,
1500
- formatMode: formatMode,
1501
- })
1502
- }
1503
-
1504
- // If no macros defined, return empty metrics array
1505
- if len(macros) == 0 {
1506
- return Result{Metrics: []Metric{}}, nil
1507
- }
1508
-
1509
- // Parse SNMP walk data using shared parser
1510
- entries, err := parseSNMPWalkAll(value.Data, logger)
1511
- if err != nil {
1512
- return Result{Error: err}, err
1513
- }
1514
-
1515
- // Group entries by index (last OID component)
1516
- type indexData struct {
1517
- index string
1518
- values map[string]string
1519
- }
1520
- indices := make(map[string]*indexData)
1521
-
1522
- // Process each parsed entry
1523
- for _, entry := range entries {
1524
- // Check which macro this OID matches
1525
- for _, macro := range macros {
1526
- // Build match prefix - add dot only if not already present
1527
- matchPrefix := macro.oidPrefix
1528
- if !strings.HasSuffix(matchPrefix, ".") {
1529
- matchPrefix += "."
1530
- }
1531
-
1532
- if strings.HasPrefix(entry.oid, matchPrefix) {
1533
- // Extract index (everything after the prefix)
1534
- index := strings.TrimPrefix(entry.oid, matchPrefix)
1535
-
1536
- // Get or create index data
1537
- if indices[index] == nil {
1538
- indices[index] = &indexData{
1539
- index: index,
1540
- values: make(map[string]string),
1541
- }
1542
- }
1543
-
1544
- // Format the value (preserve float zeros for WALK_TO_JSON)
1545
- formatted, err := formatSNMPValue(entry, macro.formatMode, false)
1546
- if err != nil {
1547
- return Result{Error: err}, err
1548
- }
1549
-
1550
- indices[index].values[macro.name] = formatted
1551
- break
1552
- }
1553
- }
1554
- }
1555
-
1556
- // If no data found for any macro, check if it's due to bad data or just empty input
1557
- if len(indices) == 0 {
1558
- // If we found valid SNMP entries but none matched our macros, that's an error
1559
- // If we found no valid entries at all (empty or bad data), return error only for bad data
1560
- if len(entries) > 0 {
1561
- // Found valid SNMP entries but none matched - this is an error
1562
- err := fmt.Errorf("cannot convert snmp walk to json")
1563
- return Result{Error: err}, err
1564
- }
1565
- // No valid entries found - could be empty input (ok) or bad data (error)
1566
- // Distinguish by checking if input has non-empty, non-whitespace content
1567
- trimmedData := strings.TrimSpace(value.Data)
1568
- if trimmedData != "" {
1569
- // Non-empty input but no valid SNMP entries - bad data
1570
- err := fmt.Errorf("cannot convert snmp walk to json")
1571
- return Result{Error: err}, err
1572
- }
1573
- // Empty input - return empty metrics array
1574
- return Result{Metrics: []Metric{}}, nil
1575
- }
1576
-
1577
- // Convert to multiple metrics, sorted by index in descending order
1578
- var indexList []string
1579
- for idx := range indices {
1580
- indexList = append(indexList, idx)
1581
- }
1582
- sort.Sort(sort.Reverse(sort.StringSlice(indexList)))
1583
-
1584
- // Build Result with multiple Metric objects
1585
- metrics := make([]Metric, 0, len(indexList))
1586
- for _, idx := range indexList {
1587
- data := indices[idx]
1588
-
1589
- // Build JSON object for this discovery item
1590
- // Start with {#SNMPINDEX}
1591
- var fields []string
1592
- fields = append(fields, fmt.Sprintf("\"{#SNMPINDEX}\":%q", data.index))
1593
-
1594
- // Add macros in definition order
1595
- for _, macro := range macros {
1596
- if val, ok := data.values[macro.name]; ok {
1597
- // Handle NULL values as JSON null
1598
- if val == "NULL" {
1599
- fields = append(fields, fmt.Sprintf("%q:null", macro.name))
1600
- } else {
1601
- fields = append(fields, fmt.Sprintf("%q:%q", macro.name, val))
1602
- }
1603
- }
1604
- }
1605
-
1606
- jsonObj := "{" + strings.Join(fields, ",") + "}"
1607
-
1608
- // Create metric with index label
1609
- metrics = append(metrics, Metric{
1610
- Name: "snmp_discovery",
1611
- Value: jsonObj,
1612
- Type: ValueTypeStr,
1613
- Labels: map[string]string{"index": data.index},
1614
- })
1615
- }
1616
-
1617
- return Result{Metrics: metrics}, nil
1618
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/state_isolation_test.go
deleted
-203
@@ -1,203 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "testing"
5
- "time"
6
-)
7
-
8
-// TestPerItemStateIsolation tests that different items have isolated state
9
-func TestPerItemStateIsolation(t *testing.T) {
10
- p := NewPreprocessor("test-shard")
11
-
12
- // Delta value test - two items should have independent state
13
- step := Step{Type: StepTypeDeltaValue, Params: ""}
14
-
15
- // Item 1: Send values 10, 20, 30
16
- value1_1 := Value{Data: "10", Type: ValueTypeFloat, Timestamp: time.Now()}
17
- value1_2 := Value{Data: "20", Type: ValueTypeFloat, Timestamp: time.Now()}
18
- value1_3 := Value{Data: "30", Type: ValueTypeFloat, Timestamp: time.Now()}
19
-
20
- // Item 2: Send values 100, 200, 300
21
- value2_1 := Value{Data: "100", Type: ValueTypeFloat, Timestamp: time.Now()}
22
- value2_2 := Value{Data: "200", Type: ValueTypeFloat, Timestamp: time.Now()}
23
- value2_3 := Value{Data: "300", Type: ValueTypeFloat, Timestamp: time.Now()}
24
-
25
- // First calls should return 0 (no previous value to delta from)
26
- result1_1, err1_1 := p.Execute("item1", value1_1, step)
27
- if err1_1 != nil {
28
- t.Fatalf("Unexpected error on first call for item1: %v", err1_1)
29
- }
30
- if len(result1_1.Metrics) == 0 || result1_1.Metrics[0].Value != "0" {
31
- t.Errorf("Expected 0 on first call for item1, got %v", result1_1.Metrics)
32
- }
33
-
34
- result2_1, err2_1 := p.Execute("item2", value2_1, step)
35
- if err2_1 != nil {
36
- t.Fatalf("Unexpected error on first call for item2: %v", err2_1)
37
- }
38
- if len(result2_1.Metrics) == 0 || result2_1.Metrics[0].Value != "0" {
39
- t.Errorf("Expected 0 on first call for item2, got %v", result2_1.Metrics)
40
- }
41
-
42
- // Second calls should succeed with delta
43
- result1_2, err1_2 := p.Execute("item1", value1_2, step)
44
- if err1_2 != nil {
45
- t.Fatalf("Unexpected error for item1 second call: %v", err1_2)
46
- }
47
- if len(result1_2.Metrics) == 0 || result1_2.Metrics[0].Value != "10" {
48
- t.Errorf("Expected delta of 10 for item1, got %v", result1_2.Metrics)
49
- }
50
-
51
- result2_2, err2_2 := p.Execute("item2", value2_2, step)
52
- if err2_2 != nil {
53
- t.Fatalf("Unexpected error for item2 second call: %v", err2_2)
54
- }
55
- if len(result2_2.Metrics) == 0 || result2_2.Metrics[0].Value != "100" {
56
- t.Errorf("Expected delta of 100 for item2, got %v", result2_2.Metrics)
57
- }
58
-
59
- // Third calls should also succeed with correct deltas
60
- result1_3, err1_3 := p.Execute("item1", value1_3, step)
61
- if err1_3 != nil {
62
- t.Fatalf("Unexpected error for item1 third call: %v", err1_3)
63
- }
64
- if len(result1_3.Metrics) == 0 || result1_3.Metrics[0].Value != "10" {
65
- t.Errorf("Expected delta of 10 for item1, got %v", result1_3.Metrics)
66
- }
67
-
68
- result2_3, err2_3 := p.Execute("item2", value2_3, step)
69
- if err2_3 != nil {
70
- t.Fatalf("Unexpected error for item2 third call: %v", err2_3)
71
- }
72
- if len(result2_3.Metrics) == 0 || result2_3.Metrics[0].Value != "100" {
73
- t.Errorf("Expected delta of 100 for item2, got %v", result2_3.Metrics)
74
- }
75
-}
76
-
77
-// TestPerItemStateIsolation_Throttle tests throttle state isolation
78
-func TestPerItemStateIsolation_Throttle(t *testing.T) {
79
- p := NewPreprocessor("test-shard")
80
- step := Step{Type: StepTypeThrottleValue, Params: ""}
81
-
82
- // Item 1 sends "A", "A", "B"
83
- result1_1, _ := p.Execute("item1", Value{Data: "A", Type: ValueTypeStr, Timestamp: time.Now()}, step)
84
- result1_2, _ := p.Execute("item1", Value{Data: "A", Type: ValueTypeStr, Timestamp: time.Now()}, step)
85
- result1_3, _ := p.Execute("item1", Value{Data: "B", Type: ValueTypeStr, Timestamp: time.Now()}, step)
86
-
87
- // Item 2 sends "X", "X", "Y"
88
- result2_1, _ := p.Execute("item2", Value{Data: "X", Type: ValueTypeStr, Timestamp: time.Now()}, step)
89
- result2_2, _ := p.Execute("item2", Value{Data: "X", Type: ValueTypeStr, Timestamp: time.Now()}, step)
90
- result2_3, _ := p.Execute("item2", Value{Data: "Y", Type: ValueTypeStr, Timestamp: time.Now()}, step)
91
-
92
- // Item 1: First value should pass, second should be throttled, third should pass
93
- if len(result1_1.Metrics) == 0 || result1_1.Metrics[0].Value != "A" {
94
- t.Errorf("Item1 first call: expected 'A', got %v", result1_1.Metrics)
95
- }
96
- if len(result1_2.Metrics) == 0 || result1_2.Metrics[0].Value != "" {
97
- t.Errorf("Item1 second call: expected empty (throttled), got %v", result1_2.Metrics)
98
- }
99
- if len(result1_3.Metrics) == 0 || result1_3.Metrics[0].Value != "B" {
100
- t.Errorf("Item1 third call: expected 'B', got %v", result1_3.Metrics)
101
- }
102
-
103
- // Item 2: Same pattern (independent of item1)
104
- if len(result2_1.Metrics) == 0 || result2_1.Metrics[0].Value != "X" {
105
- t.Errorf("Item2 first call: expected 'X', got %v", result2_1.Metrics)
106
- }
107
- if len(result2_2.Metrics) == 0 || result2_2.Metrics[0].Value != "" {
108
- t.Errorf("Item2 second call: expected empty (throttled), got %v", result2_2.Metrics)
109
- }
110
- if len(result2_3.Metrics) == 0 || result2_3.Metrics[0].Value != "Y" {
111
- t.Errorf("Item2 third call: expected 'Y', got %v", result2_3.Metrics)
112
- }
113
-}
114
-
115
-// TestShardIDInStateKey tests that shardID is part of state key
116
-func TestShardIDInStateKey(t *testing.T) {
117
- // Two preprocessors with different shards
118
- p1 := NewPreprocessor("shard1")
119
- p2 := NewPreprocessor("shard2")
120
-
121
- step := Step{Type: StepTypeDeltaValue, Params: ""}
122
-
123
- // Both use same itemID but different shards
124
- itemID := "same-item"
125
-
126
- // Shard 1: Send 10, 20
127
- _, _ = p1.Execute(itemID, Value{Data: "10", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
128
- result1, err1 := p1.Execute(itemID, Value{Data: "20", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
129
-
130
- // Shard 2: Send 100, 200
131
- _, _ = p2.Execute(itemID, Value{Data: "100", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
132
- result2, err2 := p2.Execute(itemID, Value{Data: "200", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
133
-
134
- // Both should succeed with correct deltas (proves state isolation)
135
- if err1 != nil {
136
- t.Fatalf("Shard1 error: %v", err1)
137
- }
138
- if len(result1.Metrics) == 0 || result1.Metrics[0].Value != "10" {
139
- t.Errorf("Shard1: expected delta of 10, got %v", result1.Metrics)
140
- }
141
-
142
- if err2 != nil {
143
- t.Fatalf("Shard2 error: %v", err2)
144
- }
145
- if len(result2.Metrics) == 0 || result2.Metrics[0].Value != "100" {
146
- t.Errorf("Shard2: expected delta of 100, got %v", result2.Metrics)
147
- }
148
-}
149
-
150
-// TestStateKeyFormat tests that state keys follow shardID:itemID:operation format
151
-func TestStateKeyFormat(t *testing.T) {
152
- p := NewPreprocessor("my-shard")
153
- step := Step{Type: StepTypeDeltaValue, Params: ""}
154
-
155
- // Execute to populate state
156
- _, _ = p.Execute("my-item", Value{Data: "10", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
157
-
158
- // Check that state key exists with correct format
159
- expectedKey := "my-shard:my-item:delta_value"
160
- if _, exists := p.state[expectedKey]; !exists {
161
- t.Errorf("Expected state key '%s' not found. Existing keys: %v", expectedKey, getStateKeys(p))
162
- }
163
-}
164
-
165
-func TestClearStateRemovesItemEntries(t *testing.T) {
166
- p := NewPreprocessor("clear-shard")
167
- step := Step{Type: StepTypeDeltaValue, Params: ""}
168
-
169
- // Populate state for two different items
170
- _, _ = p.Execute("item1", Value{Data: "10", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
171
- _, _ = p.Execute("item1", Value{Data: "20", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
172
- _, _ = p.Execute("item2", Value{Data: "100", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
173
- _, _ = p.Execute("item2", Value{Data: "200", Type: ValueTypeFloat, Timestamp: time.Now()}, step)
174
-
175
- if len(getStateKeys(p)) != 2 {
176
- t.Fatalf("expected state for two items, got keys %v", getStateKeys(p))
177
- }
178
-
179
- // Clear item1 state and verify only item2 remains
180
- p.ClearState("item1")
181
- keys := getStateKeys(p)
182
- if len(keys) != 1 || keys[0] != "clear-shard:item2:delta_value" {
183
- t.Fatalf("expected only item2 state to remain, got %v", keys)
184
- }
185
-
186
- // Ensure clearing non-existing item is no-op
187
- p.ClearState("missing")
188
- keys = getStateKeys(p)
189
- if len(keys) != 1 || keys[0] != "clear-shard:item2:delta_value" {
190
- t.Fatalf("clear of missing item altered state: %v", keys)
191
- }
192
-}
193
-
194
-// Helper to get state keys for debugging
195
-func getStateKeys(p *Preprocessor) []string {
196
- p.mu.RLock()
197
- defer p.mu.RUnlock()
198
- keys := make([]string, 0, len(p.state))
199
- for k := range p.state {
200
- keys = append(keys, k)
201
- }
202
- return keys
203
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/steps.go
deleted
-637
@@ -1,637 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "strconv"
6
- "strings"
7
- "time"
8
-)
9
-
10
-// Numeric parsing constants
11
-const (
12
- parseBase10 = 10 // Decimal base for integer parsing
13
- parseBase8 = 8 // Octal base for oct2dec
14
- parseBase16 = 16 // Hexadecimal base for hex2dec
15
- parseBitSize64 = 64 // 64-bit precision for float/int parsing
16
-)
17
-
18
-// formatFloat formats a float for output, avoiding scientific notation
19
-func formatFloat(f float64) string {
20
- // If it's a whole number, format as integer
21
- if f == float64(int64(f)) {
22
- return fmt.Sprintf("%d", int64(f))
23
- }
24
- // Otherwise use %g which will choose reasonable formatting
25
- return fmt.Sprintf("%g", f)
26
-}
27
-
28
-// multiplyValue multiplies a numeric value by a multiplier.
29
-func multiplyValue(value Value, paramStr string) (Value, error) {
30
- multiplier, err := strconv.ParseFloat(paramStr, parseBitSize64)
31
- if err != nil {
32
- return Value{}, fmt.Errorf("invalid multiplier: %w", err)
33
- }
34
-
35
- var numValue float64
36
- // For UINT64 type, parse as integer first (truncate any decimals)
37
- if value.Type == ValueTypeUint64 {
38
- intVal, err := strconv.ParseInt(value.Data, 10, 64)
39
- if err != nil {
40
- // Try float, then truncate to uint64
41
- floatVal, err := strconv.ParseFloat(value.Data, 64)
42
- if err != nil {
43
- return Value{}, fmt.Errorf("invalid numeric value: %w", err)
44
- }
45
- intVal = int64(floatVal)
46
- }
47
- numValue = float64(intVal)
48
- } else {
49
- var err error
50
- numValue, err = strconv.ParseFloat(value.Data, 64)
51
- if err != nil {
52
- return Value{}, fmt.Errorf("invalid numeric value: %w", err)
53
- }
54
- }
55
-
56
- result := numValue * multiplier
57
-
58
- // Convert back based on value type
59
- switch value.Type {
60
- case ValueTypeUint64:
61
- // Banker's rounding (round half to even) like Zabbix
62
- floor := int64(result)
63
- frac := result - float64(floor)
64
- var rounded int64
65
- if frac < 0.5 {
66
- rounded = floor
67
- } else if frac > 0.5 {
68
- rounded = floor + 1
69
- } else {
70
- // Exactly 0.5 - round to even
71
- if floor%2 == 0 {
72
- rounded = floor
73
- } else {
74
- rounded = floor + 1
75
- }
76
- }
77
- return Value{Data: fmt.Sprintf("%d", rounded), Type: ValueTypeUint64}, nil
78
- case ValueTypeFloat:
79
- return Value{Data: formatFloat(result), Type: ValueTypeFloat}, nil
80
- default:
81
- // String type - return result as string
82
- if result == float64(int64(result)) {
83
- return Value{Data: fmt.Sprintf("%d", int64(result)), Type: ValueTypeStr}, nil
84
- }
85
- return Value{Data: formatFloat(result), Type: ValueTypeStr}, nil
86
- }
87
-}
88
-
89
-// trimValue trims specified characters from a value.
90
-func trimValue(value Value, paramStr string, direction string) (Value, error) {
91
- chars := paramStr
92
- if chars == "" {
93
- chars = " \t\r\n" // Default whitespace
94
- } else {
95
- // Handle Zabbix escape sequences in params
96
- chars = interpretEscapeSequences(chars)
97
- }
98
-
99
- // Trim the value data
100
- result := value.Data
101
- switch direction {
102
- case "left":
103
- result = strings.TrimLeft(result, chars)
104
- case "right":
105
- result = strings.TrimRight(result, chars)
106
- case "both":
107
- result = strings.Trim(result, chars)
108
- }
109
-
110
- return Value{Data: result, Type: value.Type}, nil
111
-}
112
-
113
-// interpretEscapeSequences handles Zabbix escape sequences in trim/ltrim/rtrim params
114
-// Handles both single backslash (\s) and double backslash (\\s) formats
115
-func interpretEscapeSequences(s string) string {
116
- var result strings.Builder
117
- result.Grow(len(s)) // Pre-allocate capacity for efficiency
118
-
119
- for i := 0; i < len(s); i++ {
120
- if s[i] == '\\' && i+1 < len(s) {
121
- next := s[i+1]
122
-
123
- // Check for double-backslash escape sequences (\\s, \\n, etc.)
124
- // This is only for trim operations where \\s means full whitespace class
125
- if next == '\\' && i+2 < len(s) {
126
- third := s[i+2]
127
- switch third {
128
- case 's':
129
- // \\s means whitespace class: space, tab, newline, carriage return, and backslash
130
- result.WriteString(" \t\r\n\\")
131
- i += 2
132
- continue
133
- }
134
- }
135
-
136
- // Single backslash escape sequences
137
- switch next {
138
- case '\\':
139
- // Backslash-backslash is a literal backslash (consume both backslashes)
140
- result.WriteByte('\\')
141
- i++
142
- continue
143
- case 'n':
144
- result.WriteByte('\n')
145
- i++
146
- continue
147
- case 'r':
148
- result.WriteByte('\r')
149
- i++
150
- continue
151
- case 't':
152
- result.WriteByte('\t')
153
- i++
154
- continue
155
- case 's':
156
- result.WriteByte(' ')
157
- i++
158
- continue
159
- case ' ':
160
- // Backslash-space is an escape for space
161
- result.WriteByte(' ')
162
- i++
163
- continue
164
- default:
165
- // Not an escape sequence, just add the backslash
166
- result.WriteByte(s[i])
167
- }
168
- } else {
169
- result.WriteByte(s[i])
170
- }
171
- }
172
- return result.String()
173
-}
174
-
175
-// regexSubstitute performs regex find and replace.
176
-func regexSubstitute(value Value, paramStr string) (Value, error) {
177
- // paramStr format: "pattern\noutput"
178
- parts := strings.SplitN(paramStr, "\n", 2)
179
- if len(parts) != 2 {
180
- return Value{}, fmt.Errorf("regex substitution requires pattern and output")
181
- }
182
-
183
- pattern := parts[0]
184
- output := parts[1]
185
-
186
- // Debug: log the parsed parts
187
- //fmt.Printf("DEBUG regsub: pattern='%s' output='%s'\n", pattern, output)
188
-
189
- // Compile regex with safety checks and caching
190
- re, err := compileRegexSafe(pattern, 0)
191
- if err != nil {
192
- return Value{}, fmt.Errorf("invalid regex pattern: %w", err)
193
- }
194
-
195
- // Check if there's a match - if no match, return error (with timeout protection)
196
- matched, err := matchWithTimeout(re, value.Data, defaultRegexTimeout)
197
- if err != nil {
198
- return Value{}, fmt.Errorf("regex match failed: %w", err)
199
- }
200
- if !matched {
201
- return Value{}, fmt.Errorf("regex pattern does not match")
202
- }
203
-
204
- // Find match with capture groups (with timeout protection)
205
- // Zabbix regsub returns only the replacement string (not modified original)
206
- matches, err := findStringSubmatchIndexWithTimeout(re, value.Data, defaultRegexTimeout)
207
- if err != nil {
208
- return Value{}, fmt.Errorf("regex substitution failed: %w", err)
209
- }
210
- if matches == nil {
211
- return Value{}, fmt.Errorf("regex pattern does not match")
212
- }
213
-
214
- // Convert Zabbix-style backreferences (\1, \2) to Go-style ($1, $2)
215
- // Zabbix uses \N syntax, but Go's regexp.Expand() uses $N syntax
216
- // Must preserve escaped backslashes (\\1 should become literal \1, not $1)
217
- goOutput := convertBackreferences(output)
218
-
219
- // Use regexp.Expand() for proper capture group expansion
220
- // This correctly handles:
221
- // - Multi-digit backreferences (${10}, ${11}, etc.)
222
- // - Escaped backslashes
223
- // - All edge cases (unmatched groups, nested references, etc.)
224
- var replacement []byte
225
- replacement = re.Expand(replacement, []byte(goOutput), []byte(value.Data), matches)
226
-
227
- // Return the replacement string (not the modified original string)
228
- return Value{Data: string(replacement), Type: value.Type}, nil
229
-}
230
-
231
-// convertBackreferences converts Zabbix-style \N to Go-style ${N}
232
-// Preserves escaped backslashes: \\1 remains \1 (literal)
233
-// Uses ${N} instead of $N to avoid ambiguity (e.g., $1y vs ${1}y)
234
-func convertBackreferences(s string) string {
235
- var result strings.Builder
236
- i := 0
237
- for i < len(s) {
238
- if i+1 < len(s) && s[i] == '\\' {
239
- if s[i+1] == '\\' {
240
- // Escaped backslash: \\1 → \1 (literal)
241
- result.WriteByte('\\')
242
- i += 2
243
- continue
244
- } else if s[i+1] >= '0' && s[i+1] <= '9' {
245
- // Backreference: \1 → ${1}, \10 → ${10}, etc.
246
- result.WriteString("${")
247
- // Handle multi-digit backreferences (\10, \11, etc.)
248
- j := i + 1
249
- for j < len(s) && s[j] >= '0' && s[j] <= '9' {
250
- result.WriteByte(s[j])
251
- j++
252
- }
253
- result.WriteByte('}')
254
- i = j
255
- continue
256
- }
257
- }
258
- result.WriteByte(s[i])
259
- i++
260
- }
261
- return result.String()
262
-}
263
-
264
-// bool2Dec converts boolean values to decimal.
265
-func bool2Dec(value Value) (Value, error) {
266
- // Empty input is an error
267
- if value.Data == "" {
268
- return Value{}, fmt.Errorf("empty input for bool2dec")
269
- }
270
-
271
- v := strings.ToLower(strings.TrimSpace(value.Data))
272
-
273
- // Zabbix considers these as true (full words and single letters)
274
- trueValues := map[string]bool{
275
- "1": true, "true": true, "yes": true, "on": true,
276
- "t": true, "y": true, "ok": true,
277
- }
278
-
279
- // Zabbix considers these as false (full words and single letters)
280
- falseValues := map[string]bool{
281
- "0": true, "false": true, "no": true, "off": true,
282
- "f": true, "n": true, "err": true, "error": true,
283
- }
284
-
285
- var result int64
286
- if trueValues[v] {
287
- result = 1
288
- } else if falseValues[v] {
289
- result = 0
290
- } else {
291
- // Try to parse as number
292
- num, err := strconv.ParseInt(v, 10, 64)
293
- if err != nil {
294
- // If not a recognized value and not a number, return error
295
- return Value{}, fmt.Errorf("unrecognized boolean value: %s", v)
296
- }
297
- if num != 0 {
298
- result = 1
299
- }
300
- }
301
-
302
- return Value{Data: fmt.Sprintf("%d", result), Type: ValueTypeUint64}, nil
303
-}
304
-
305
-// oct2Dec converts octal string to decimal.
306
-func oct2Dec(value Value) (Value, error) {
307
- // Parse as octal
308
- num, err := strconv.ParseInt(strings.TrimSpace(value.Data), parseBase8, parseBitSize64)
309
- if err != nil {
310
- return Value{}, fmt.Errorf("invalid octal value: %w", err)
311
- }
312
-
313
- return Value{Data: fmt.Sprintf("%d", num), Type: ValueTypeUint64}, nil
314
-}
315
-
316
-// hex2Dec converts hexadecimal string to decimal.
317
-func hex2Dec(value Value) (Value, error) {
318
- // Parse as hexadecimal (with or without 0x prefix)
319
- data := strings.TrimSpace(value.Data)
320
- if strings.HasPrefix(data, "0x") || strings.HasPrefix(data, "0X") {
321
- data = data[2:]
322
- }
323
-
324
- // Remove spaces for parsing
325
- data = strings.ReplaceAll(data, " ", "")
326
-
327
- num, err := strconv.ParseInt(data, parseBase16, parseBitSize64)
328
- if err != nil {
329
- return Value{}, fmt.Errorf("invalid hexadecimal value: %w", err)
330
- }
331
-
332
- return Value{Data: fmt.Sprintf("%d", num), Type: ValueTypeUint64}, nil
333
-}
334
-
335
-// deltaValue calculates the difference between current and previous value.
336
-// Thread-safe for concurrent use. State isolated per shard and item.
337
-func (p *Preprocessor) deltaValue(itemID string, value Value, paramStr string) (Value, error) {
338
- // State key: shardID:itemID:operation
339
- stateKey := fmt.Sprintf("%s:%s:delta_value", p.shardID, itemID)
340
-
341
- // Parse numeric value before acquiring lock
342
- numValue, err := strconv.ParseFloat(value.Data, 64)
343
- if err != nil {
344
- return Value{}, fmt.Errorf("invalid numeric value: %w", err)
345
- }
346
-
347
- // Acquire exclusive lock for read-modify-write operation
348
- p.mu.Lock()
349
- defer p.mu.Unlock()
350
-
351
- state, hasState := p.state[stateKey]
352
-
353
- if !hasState {
354
- // First call - no previous value
355
- p.state[stateKey] = &OperationState{
356
- LastValue: value.Data,
357
- LastTimestamp: value.Timestamp,
358
- LastAccess: time.Now(),
359
- }
360
- return Value{Data: "0", Type: ValueTypeStr}, nil
361
- }
362
-
363
- // Update access time
364
- state.LastAccess = time.Now()
365
-
366
- // Calculate delta
367
- prevValue, err := strconv.ParseFloat(state.LastValue, 64)
368
- if err != nil {
369
- // If we can't parse previous, reset and return 0
370
- p.state[stateKey] = &OperationState{
371
- LastValue: value.Data,
372
- LastTimestamp: value.Timestamp,
373
- LastAccess: time.Now(),
374
- }
375
- return Value{Data: "0", Type: ValueTypeStr}, nil
376
- }
377
-
378
- delta := numValue - prevValue
379
-
380
- // Update state
381
- p.state[stateKey] = &OperationState{
382
- LastValue: value.Data,
383
- LastTimestamp: value.Timestamp,
384
- LastAccess: time.Now(),
385
- }
386
-
387
- // Discard negative deltas (value decreased)
388
- if delta < 0 {
389
- return Value{Data: "", Type: ValueTypeStr}, nil
390
- }
391
-
392
- // Return delta
393
- if delta == float64(int64(delta)) {
394
- return Value{Data: fmt.Sprintf("%d", int64(delta)), Type: ValueTypeStr}, nil
395
- }
396
- return Value{Data: fmt.Sprintf("%g", delta), Type: ValueTypeStr}, nil
397
-}
398
-
399
-// deltaSpeed calculates the speed (delta per second).
400
-// Thread-safe for concurrent use. State isolated per shard and item.
401
-func (p *Preprocessor) deltaSpeed(itemID string, value Value, paramStr string) (Value, error) {
402
- // State key: shardID:itemID:operation
403
- stateKey := fmt.Sprintf("%s:%s:delta_speed", p.shardID, itemID)
404
-
405
- // Parse numeric value before acquiring lock
406
- numValue, err := strconv.ParseFloat(value.Data, 64)
407
- if err != nil {
408
- return Value{}, fmt.Errorf("invalid numeric value: %w", err)
409
- }
410
-
411
- // Acquire exclusive lock for read-modify-write operation
412
- p.mu.Lock()
413
- defer p.mu.Unlock()
414
-
415
- state, hasState := p.state[stateKey]
416
-
417
- if !hasState {
418
- // First call - no previous value
419
- p.state[stateKey] = &OperationState{
420
- LastValue: value.Data,
421
- LastValueTime: value.Timestamp,
422
- LastTimestamp: value.Timestamp,
423
- LastAccess: time.Now(),
424
- }
425
- return Value{Data: "0", Type: ValueTypeStr}, nil
426
- }
427
-
428
- // Update access time
429
- state.LastAccess = time.Now()
430
-
431
- // Calculate delta
432
- prevValue, err := strconv.ParseFloat(state.LastValue, 64)
433
- if err != nil {
434
- // If we can't parse previous, reset and return 0
435
- p.state[stateKey] = &OperationState{
436
- LastValue: value.Data,
437
- LastValueTime: value.Timestamp,
438
- LastTimestamp: value.Timestamp,
439
- LastAccess: time.Now(),
440
- }
441
- return Value{Data: "0", Type: ValueTypeStr}, nil
442
- }
443
-
444
- delta := numValue - prevValue
445
- timeDiff := value.Timestamp.Sub(state.LastValueTime).Seconds()
446
-
447
- // Update state
448
- p.state[stateKey] = &OperationState{
449
- LastValue: value.Data,
450
- LastValueTime: value.Timestamp,
451
- LastTimestamp: value.Timestamp,
452
- LastAccess: time.Now(),
453
- }
454
-
455
- // Discard if time went backwards or stayed the same
456
- if timeDiff <= 0 {
457
- return Value{Data: "", Type: ValueTypeStr}, nil
458
- }
459
-
460
- // Discard if value decreased (negative delta)
461
- if delta < 0 {
462
- return Value{Data: "", Type: ValueTypeStr}, nil
463
- }
464
-
465
- speed := delta / timeDiff
466
-
467
- // Format based on input value type
468
- if value.Type == ValueTypeUint64 {
469
- // For UINT64, truncate to integer
470
- return Value{Data: fmt.Sprintf("%d", int64(speed)), Type: ValueTypeStr}, nil
471
- }
472
-
473
- // For other types, use float formatting
474
- if speed == float64(int64(speed)) {
475
- return Value{Data: fmt.Sprintf("%d", int64(speed)), Type: ValueTypeStr}, nil
476
- }
477
- return Value{Data: fmt.Sprintf("%g", speed), Type: ValueTypeStr}, nil
478
-}
479
-
480
-// stringReplace replaces one string with another.
481
-func stringReplace(value Value, paramStr string) (Value, error) {
482
- // paramStr format: "search\nreplace"
483
- parts := strings.SplitN(paramStr, "\n", 2)
484
- if len(parts) != 2 {
485
- return Value{}, fmt.Errorf("string replace requires search and replace parameters")
486
- }
487
-
488
- // Interpret escape sequences in search and replace strings
489
- search := interpretEscapeSequences(parts[0])
490
- replace := interpretEscapeSequences(parts[1])
491
-
492
- // Empty search string is not allowed
493
- if search == "" {
494
- return Value{}, fmt.Errorf("search string cannot be empty")
495
- }
496
-
497
- result := strings.ReplaceAll(value.Data, search, replace)
498
- return Value{Data: result, Type: value.Type}, nil
499
-}
500
-
501
-// validateRange validates that a value is within a numeric range.
502
-func validateRange(value Value, paramStr string) (Value, error) {
503
- // paramStr format: "min\nmax"
504
- parts := strings.SplitN(paramStr, "\n", 2)
505
- if len(parts) != 2 {
506
- return Value{}, fmt.Errorf("validate range requires min and max parameters")
507
- }
508
-
509
- minStr := strings.TrimSpace(parts[0])
510
- maxStr := strings.TrimSpace(parts[1])
511
-
512
- numValue, err := strconv.ParseFloat(value.Data, 64)
513
- if err != nil {
514
- return Value{}, fmt.Errorf("invalid numeric value: %w", err)
515
- }
516
-
517
- min, err := strconv.ParseFloat(minStr, 64)
518
- if err != nil {
519
- return Value{}, fmt.Errorf("invalid minimum value: %w", err)
520
- }
521
-
522
- max, err := strconv.ParseFloat(maxStr, 64)
523
- if err != nil {
524
- return Value{}, fmt.Errorf("invalid maximum value: %w", err)
525
- }
526
-
527
- if numValue < min || numValue > max {
528
- return Value{}, fmt.Errorf("value %g is out of range [%g, %g]", numValue, min, max)
529
- }
530
-
531
- return value, nil
532
-}
533
-
534
-// validateRegex validates that a value matches a regex pattern.
535
-func validateRegex(value Value, paramStr string) (Value, error) {
536
- pattern := strings.TrimSpace(paramStr)
537
- if pattern == "" {
538
- return Value{}, fmt.Errorf("validate regex requires a pattern")
539
- }
540
-
541
- re, err := compileRegexSafe(pattern, 0)
542
- if err != nil {
543
- return Value{}, fmt.Errorf("invalid regex pattern: %w", err)
544
- }
545
-
546
- matched, err := matchWithTimeout(re, value.Data, defaultRegexTimeout)
547
- if err != nil {
548
- return Value{}, fmt.Errorf("regex validation failed: %w", err)
549
- }
550
- if !matched {
551
- return Value{}, fmt.Errorf("value does not match regex pattern: %s", pattern)
552
- }
553
-
554
- return value, nil
555
-}
556
-
557
-// validateNotRegex validates that a value does NOT match a regex pattern.
558
-func validateNotRegex(value Value, paramStr string) (Value, error) {
559
- pattern := strings.TrimSpace(paramStr)
560
- if pattern == "" {
561
- return Value{}, fmt.Errorf("validate not regex requires a pattern")
562
- }
563
-
564
- re, err := compileRegexSafe(pattern, 0)
565
- if err != nil {
566
- return Value{}, fmt.Errorf("invalid regex pattern: %w", err)
567
- }
568
-
569
- matched, err := matchWithTimeout(re, value.Data, defaultRegexTimeout)
570
- if err != nil {
571
- return Value{}, fmt.Errorf("regex validation failed: %w", err)
572
- }
573
- if matched {
574
- return Value{}, fmt.Errorf("value matches regex pattern but should not: %s", pattern)
575
- }
576
-
577
- return value, nil
578
-}
579
-
580
-// validateNotSupported checks if an error from a previous step is "not supported".
581
-// - If input is a VALUE (not an error): pass through unchanged (succeed)
582
-// - If input is an ERROR:
583
-// - If params is empty: fail (any error is considered "supported")
584
-// - If params has pattern: check if error matches pattern
585
-// - Matches: fail (error is "supported"/expected)
586
-// - Doesn't match: succeed (error is "not supported"/unexpected)
587
-func validateNotSupported(value Value, paramStr string) (Value, error) {
588
- // If input is not an error, pass through unchanged
589
- if !value.IsError {
590
- return value, nil
591
- }
592
-
593
- // Input is an error - validate it
594
- paramStr = strings.TrimSpace(paramStr)
595
-
596
- // If no params provided, any error is considered "supported" - fail
597
- if paramStr == "" {
598
- return Value{}, fmt.Errorf("value is not supported")
599
- }
600
-
601
- // Parse params: "mode\npattern"
602
- parts := strings.SplitN(paramStr, "\n", 2)
603
- if len(parts) != 2 {
604
- // Invalid params format, treat as "any error is supported"
605
- return Value{}, fmt.Errorf("value is not supported")
606
- }
607
-
608
- // mode := strings.TrimSpace(parts[0]) // Mode not currently used
609
- pattern := strings.TrimSpace(parts[1])
610
-
611
- // Empty pattern means any error is supported
612
- if pattern == "" {
613
- return Value{}, fmt.Errorf("value is not supported")
614
- }
615
-
616
- // Check if error message matches pattern (regex match)
617
- re, err := compileRegexSafe(pattern, 0)
618
- if err != nil {
619
- // Invalid regex pattern - treat as validation failure
620
- return Value{}, fmt.Errorf("invalid pattern in validation: %w", err)
621
- }
622
-
623
- matched, err := matchWithTimeout(re, value.Data, defaultRegexTimeout)
624
- if err != nil {
625
- // Regex timeout or error
626
- return Value{}, fmt.Errorf("pattern matching failed: %w", err)
627
- }
628
-
629
- if matched {
630
- // Error matches pattern - it's "supported"/expected, so fail
631
- return Value{}, fmt.Errorf("value is not supported")
632
- }
633
-
634
- // Error doesn't match pattern - it's "not supported"/unexpected, so succeed
635
- // Pass through the error data unchanged
636
- return value, nil
637
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/testdata/item_preproc_csv_to_json.yaml
deleted
-517
@@ -1,517 +0,0 @@
1
----
2
-test case: 'missing second parameter'
3
-in:
4
- csv: ''
5
- params: ''
6
-out:
7
- result: ''
8
- return: 'FAIL'
9
----
10
-test case: 'missing third parameter'
11
-in:
12
- csv: ''
13
- params: "\n"
14
-out:
15
- result: ''
16
- return: 'FAIL'
17
----
18
-test case: 'invalid first parameter'
19
-in:
20
- csv: ''
21
- params: ",.\n\n0"
22
-out:
23
- result: ''
24
- return: 'FAIL'
25
----
26
-test case: 'invalid second parameter'
27
-in:
28
- csv: ''
29
- params: "\n,.\n0"
30
-out:
31
- result: ''
32
- return: 'FAIL'
33
----
34
-test case: 'empty input (1)'
35
-in:
36
- csv: ''
37
- params: "\n\n1"
38
-out:
39
- result: '[]'
40
- return: 'SUCCEED'
41
----
42
-test case: 'empty input (2)'
43
-in:
44
- csv: ''
45
- params: "\n\n0"
46
-out:
47
- result: '[]'
48
- return: 'SUCCEED'
49
----
50
-test case: 'single char (1)'
51
-in:
52
- csv: 'A'
53
- params: ",\n\n1"
54
-out:
55
- result: '[]'
56
- return: 'SUCCEED'
57
----
58
-test case: 'single char (2)'
59
-in:
60
- csv: 'A'
61
- params: ",\n\n0"
62
-out:
63
- result: '[{"1":"A"}]'
64
- return: 'SUCCEED'
65
----
66
-test case: 'single UTF8 4-bytes character (1)'
67
-in:
68
- csv: '😂'
69
- params: ",\n\n1"
70
-out:
71
- result: '[]'
72
- return: 'SUCCEED'
73
----
74
-test case: 'single UTF8 4-bytes character (2)'
75
-in:
76
- csv: '😂'
77
- params: ",\n\n0"
78
-out:
79
- result: '[{"1":"😂"}]'
80
- return: 'SUCCEED'
81
----
82
-test case: 'empty line (1)'
83
-in:
84
- csv: "\n"
85
- params: ",\n\n1"
86
-out:
87
- result: '[{"":""}]'
88
- return: 'SUCCEED'
89
----
90
-test case: 'empty line (2)'
91
-in:
92
- csv: "\n"
93
- params: ",\n\n0"
94
-out:
95
- result: '[{},{}]'
96
- return: 'SUCCEED'
97
----
98
-test case: 'multiple empty lines (1)'
99
-in:
100
- csv: "\n\n\n"
101
- params: ",\n\n1"
102
-out:
103
- result: '[{"":""},{"":""},{"":""}]'
104
- return: 'SUCCEED'
105
----
106
-test case: 'multiple empty lines (2)'
107
-in:
108
- csv: "\n\n\n"
109
- params: ",\n\n0"
110
-out:
111
- result: '[{},{},{},{}]'
112
- return: 'SUCCEED'
113
----
114
-test case: 'single column (1)'
115
-in:
116
- csv: |
117
- nr
118
- 1
119
- 2
120
- params: "\n\n1"
121
-out:
122
- result: '[{"nr":"1"},{"nr":"2"},{"nr":""}]'
123
- return: 'SUCCEED'
124
----
125
-test case: 'single column (2)'
126
-in:
127
- csv: |
128
- nr
129
- 1
130
- 2
131
- params: "\n\n0"
132
-out:
133
- result: '[{"1":"nr"},{"1":"1"},{"1":"2"},{}]'
134
- return: 'SUCCEED'
135
----
136
-test case: 'single line (1)'
137
-in:
138
- csv: "a,b,c"
139
- params: "\n\n1"
140
-out:
141
- result: '[]'
142
- return: 'SUCCEED'
143
----
144
-test case: 'single line (2)'
145
-in:
146
- csv: "a,b,c"
147
- params: "\n\n0"
148
-out:
149
- result: '[{"1":"a","2":"b","3":"c"}]'
150
- return: 'SUCCEED'
151
----
152
-test case: 'no trailing line break (1)'
153
-in:
154
- csv: "nr,\"name\",addr\n\"1\",abc,xyz\n\"2\",\"cba\",zyx"
155
- params: "\n\"\n1"
156
-out:
157
- result: '[{"nr":"1","name":"abc","addr":"xyz"},{"nr":"2","name":"cba","addr":"zyx"}]'
158
- return: 'SUCCEED'
159
----
160
-test case: 'no trailing line break (2)'
161
-in:
162
- csv: "nr,\"name\",addr\n\"1\",abc,xyz\n\"2\",\"cba\","
163
- params: "\n\"\n1"
164
-out:
165
- result: '[{"nr":"1","name":"abc","addr":"xyz"},{"nr":"2","name":"cba","addr":""}]'
166
- return: 'SUCCEED'
167
----
168
-test case: 'no trailing line break (3)'
169
-in:
170
- csv: "nr,\"name\",addr\n\"1\",abc,xyz\n\"2\",\"cba\",\"zyx\""
171
- params: "\n\"\n1"
172
-out:
173
- result: '[{"nr":"1","name":"abc","addr":"xyz"},{"nr":"2","name":"cba","addr":"zyx"}]'
174
- return: 'SUCCEED'
175
----
176
-test case: 'empty fields (1)'
177
-in:
178
- csv: ",,"
179
- params: "\n\n1"
180
-out:
181
- result: ''
182
- return: 'FAIL'
183
----
184
-test case: 'empty fields (2)'
185
-in:
186
- csv: ",,"
187
- params: "\n\n0"
188
-out:
189
- result: '[{"1":"","2":"","3":""}]'
190
- return: 'SUCCEED'
191
----
192
-test case: 'empty fields (3)'
193
-in:
194
- csv: "a,b,c\n,,"
195
- params: "\n\n1"
196
-out:
197
- result: '[{"a":"","b":"","c":""}]'
198
- return: 'SUCCEED'
199
----
200
-test case: 'delimiter in quoted field'
201
-in:
202
- csv: '`fld,1`,`fld,2`'
203
- params: "\n`\n0"
204
-out:
205
- result: '[{"1":"fld,1","2":"fld,2"}]'
206
- return: 'SUCCEED'
207
----
208
-test case: 'quotation character in unquoted field'
209
-in:
210
- csv: 'fld`1,fld`2'
211
- params: "\n`\n0"
212
-out:
213
- result: '[{"1":"fld`1","2":"fld`2"}]'
214
- return: 'SUCCEED'
215
----
216
-test case: 'quotation character in unquoted field (2)'
217
-in:
218
- csv: ' `fld`1` , `fld`2` '
219
- params: "\n`\n0"
220
-out:
221
- result: '[{"1":" `fld`1` ","2":" `fld`2` "}]'
222
- return: 'SUCCEED'
223
----
224
-test case: 'escaped quotation character'
225
-in:
226
- csv: '`fld``1`,`fld``2`'
227
- params: "\n`\n0"
228
-out:
229
- result: '[{"1":"fld`1","2":"fld`2"}]'
230
- return: 'SUCCEED'
231
----
232
-test case: 'delimiter set in sep line'
233
-in:
234
- csv: |-
235
- sEp=.
236
- col1,.;col2,.;
237
- fld1,.;fld2,.;
238
- params: "\n\n1"
239
-out:
240
- result: '[{"col1,":"fld1,",";col2,":";fld2,",";":";"}]'
241
- return: 'SUCCEED'
242
----
243
-test case: 'cr/nl line breaks'
244
-in:
245
- csv: "Sep=.\r\nname.addr\r\nabc.xyz\r\n\r\nfld1.fld2\r\n\r\n\r\nfld3.fld4\r\n"
246
- params: "\n\n1"
247
-out:
248
- result: '[{"name":"abc","addr":"xyz"},{"name":"","addr":""},{"name":"fld1","addr":"fld2"},{"name":"","addr":""},{"name":"","addr":""},{"name":"fld3","addr":"fld4"},{"name":"","addr":""}]'
249
- return: 'SUCCEED'
250
----
251
-test case: 'various length rows'
252
-in:
253
- csv: |-
254
- fld1,fld2,fld3
255
- fld1,fld2,fld3,fld4
256
- fld1,fld2
257
- fld1,fld2,fld3,fld4,fld5
258
- params: "\n\n0"
259
-out:
260
- result: '[{"1":"fld1","2":"fld2","3":"fld3"},{"1":"fld1","2":"fld2","3":"fld3","4":"fld4"},{"1":"fld1","2":"fld2"},{"1":"fld1","2":"fld2","3":"fld3","4":"fld4","5":"fld5"}]'
261
- return: 'SUCCEED'
262
----
263
-test case: 'equal delimiter and quotation characters'
264
-in:
265
- csv: |-
266
- ,fld1,,fld2
267
- params: ",\n,\n0"
268
-out:
269
- result: '[{"1":"","2":"fld1","3":"","4":"fld2"}]'
270
- return: 'SUCCEED'
271
----
272
-test case: 'UTF8 2-byte delimiter'
273
-in:
274
- csv: |-
275
- col1фcol2
276
- fld1фfld2
277
- params: "ф\n\n1"
278
-out:
279
- result: '[{"col1":"fld1","col2":"fld2"}]'
280
- return: 'SUCCEED'
281
----
282
-test case: 'UTF8 3-byte delimiter set in sep line'
283
-in:
284
- csv: |-
285
- sep=ꢂ
286
- col1ꢂcol2
287
- fld1ꢂfld2
288
- params: "\n\n1"
289
-out:
290
- result: '[{"col1":"fld1","col2":"fld2"}]'
291
- return: 'SUCCEED'
292
----
293
-test case: 'UTF8 4-byte delimiter'
294
-in:
295
- csv: |-
296
- col1😀col2
297
- fld1😀fld2
298
- params: "😀\n\n1"
299
-out:
300
- result: '[{"col1":"fld1","col2":"fld2"}]'
301
- return: 'SUCCEED'
302
----
303
-test case: 'UTF8 2-byte quotation character'
304
-in:
305
- csv: |-
306
- ыcol1ы,ыcol,2ы
307
- ыfld1ы,ыfld,2ы
308
- params: "\nы\n1"
309
-out:
310
- result: '[{"col1":"fld1","col,2":"fld,2"}]'
311
- return: 'SUCCEED'
312
----
313
-test case: 'UTF8 3-byte quotation character'
314
-in:
315
- csv: |-
316
- ꢂcol1ꢂ,ꢂcol,2ꢂ
317
- ꢂfld1ꢂ,ꢂfld,2ꢂ
318
- params: "\nꢂ\n1"
319
-out:
320
- result: '[{"col1":"fld1","col,2":"fld,2"}]'
321
- return: 'SUCCEED'
322
----
323
-test case: 'UTF8 multi-byte delimiter and quotation characters'
324
-in:
325
- csv: |-
326
- 😀col1😀ꢂ😀col2😀ꢂ😀😀
327
- fld1ꢂ😀fld2😀ꢂ
328
- params: "ꢂ\n😀\n1"
329
-out:
330
- result: '[{"col1":"fld1","col2":"fld2","":""}]'
331
- return: 'SUCCEED'
332
----
333
-test case: 'delimiter set to space'
334
-in:
335
- csv: |-
336
- col1 col2 `col 3`
337
- fld1 fld2 `fld 3`
338
- params: " \n`\n1"
339
-out:
340
- result: '[{"col1":"fld1","col2":"fld2","col 3":"fld 3","":""}]'
341
- return: 'SUCCEED'
342
----
343
-test case: 'quotation character set to space'
344
-in:
345
- csv: |-
346
- col1, col2 ,col 3
347
- fld1 ,fld2, fld 3
348
- params: "\n \n1"
349
-out:
350
- result: '[{"col1":"fld1","col2":"fld2","col 3":"fld 3"}]'
351
- return: 'SUCCEED'
352
----
353
-test case: 'sep line only (1)'
354
-in:
355
- csv: 'sep=.'
356
- params: "\n\n1"
357
-out:
358
- result: '[]'
359
- return: 'SUCCEED'
360
----
361
-test case: 'sep line only (2)'
362
-in:
363
- csv: 'sep=.'
364
- params: "\n\n0"
365
-out:
366
- result: '[]'
367
- return: 'SUCCEED'
368
----
369
-test case: 'sep line only (3)'
370
-in:
371
- csv: "sep=.\n"
372
- params: "\n\n1"
373
-out:
374
- result: '[]'
375
- return: 'SUCCEED'
376
----
377
-test case: 'sep line only (4)'
378
-in:
379
- csv: 'sep=.'
380
- params: "\n\n0"
381
-out:
382
- result: '[]'
383
- return: 'SUCCEED'
384
----
385
-test case: 'unsupported sep line (1)'
386
-in:
387
- csv: |-
388
- sep=.;
389
- fld1,fld2
390
- params: "\n\n0"
391
-out:
392
- result: '[{"1":"sep=.;"},{"1":"fld1","2":"fld2"}]'
393
- return: 'SUCCEED'
394
----
395
-test case: 'unsupported sep line (2)'
396
-in:
397
- csv: |-
398
- sep=
399
- fld1,fld2
400
- params: "\n\n0"
401
-out:
402
- result: '[{"1":"sep="},{"1":"fld1","2":"fld2"}]'
403
- return: 'SUCCEED'
404
----
405
-test case: 'duplicated column names (1)'
406
-in:
407
- csv: |-
408
- col1,col1
409
- fld1,fld2
410
- params: "\n\n1"
411
-out:
412
- result: ''
413
- return: 'FAIL'
414
----
415
-test case: 'duplicated column names (2)'
416
-in:
417
- csv: |-
418
- ,
419
- fld1,fld2
420
- params: "\n\n1"
421
-out:
422
- result: ''
423
- return: 'FAIL'
424
----
425
-test case: 'more fields in data row than in header'
426
-in:
427
- csv: |-
428
- col1,col1
429
- fld1,fld2,fld3
430
- params: "\n\n1"
431
-out:
432
- result: ''
433
- return: 'FAIL'
434
----
435
-test case: 'unclosed quoted field'
436
-in:
437
- csv: 'col1,"col1'
438
- params: "\n\"\n0"
439
-out:
440
- result: ''
441
- return: 'FAIL'
442
----
443
-test case: 'unclosed quoted field (UTF8-2)'
444
-in:
445
- csv: 'col1,ыcol1'
446
- params: "\nы\n0"
447
-out:
448
- result: ''
449
- return: 'FAIL'
450
----
451
-test case: 'unclosed quoted field (UTF8-3)'
452
-in:
453
- csv: 'col1,ꢂcol1'
454
- params: "\nꢂ\n0"
455
-out:
456
- result: ''
457
- return: 'FAIL'
458
----
459
-test case: 'unclosed quoted field (UTF8-4)'
460
-in:
461
- csv: 'col1,😀col1'
462
- params: "\n😀\n0"
463
-out:
464
- result: ''
465
- return: 'FAIL'
466
----
467
-test case: 'escaped quotation character in unclosed quoted field (UTF8-3)'
468
-in:
469
- csv: 'col1,ꢂcol1ꢂꢂ'
470
- params: "\nꢂ\n0"
471
-out:
472
- result: ''
473
- return: 'FAIL'
474
----
475
-test case: 'missing delimiter or line break after quoted field'
476
-in:
477
- csv: 'col1,"col1" '
478
- params: "\n\"\n0"
479
-out:
480
- result: ''
481
- return: 'FAIL'
482
----
483
-test case: 'unsupported line break'
484
-in:
485
- csv: "col1,col1\n\rfld1,fld2"
486
- params: "\n\n0"
487
-out:
488
- result: ''
489
- return: 'FAIL'
490
----
491
-test case: 'CSV sample input (1)'
492
-in:
493
- csv: |
494
- sEp=.
495
- ;c`2;`;```;😀ꢂыa;col.5;`col6`;col7
496
- fld.1;`fld.2`;f`3;`;```;;``;aыꢂ😀
497
-
498
- ``;fld2`ы;fld3ꢂ`;`😀`;;``
499
-
500
-
501
- ;c`2;`;```;😀ꢂыa;col.5;`col6`;col7
502
- params: ";\n`\n1"
503
-out:
504
- result: '[{"":"fld.1","c`2":"fld.2",";`":"f`3","😀ꢂыa":";`","col.5":"","col6":"","col7":"aыꢂ😀"},{"":"","c`2":"",";`":"","😀ꢂыa":"","col.5":"","col6":"","col7":""},{"":"","c`2":"fld2`ы",";`":"fld3ꢂ`","😀ꢂыa":"😀","col.5":"","col6":"","col7":""},{"":"","c`2":"",";`":"","😀ꢂыa":"","col.5":"","col6":"","col7":""},{"":"","c`2":"",";`":"","😀ꢂыa":"","col.5":"","col6":"","col7":""},{"":"","c`2":"c`2",";`":";`","😀ꢂыa":"😀ꢂыa","col.5":"col.5","col6":"col6","col7":"col7"},{"":"","c`2":"",";`":"","😀ꢂыa":"","col.5":"","col6":"","col7":""}]'
505
- return: 'SUCCEED'
506
----
507
-test case: 'CSV sample input (2)'
508
-in:
509
- csv: |-
510
- sEp=.
511
- col1,.;col2,.;
512
- fld1,.;fld2,.;
513
- params: ";\n\n1"
514
-out:
515
- result: '[{"col1,.":"fld1,.","col2,.":"fld2,.","":""}]'
516
- return: 'SUCCEED'
517
-...
src/go/plugin/scripts.d/pkg/zabbixpreproc/testdata/item_preproc_xpath.yaml
deleted
-105
@@ -1,105 +0,0 @@
1
----
2
-test case: 'empty input parameters'
3
-in:
4
- xml: ''
5
- xpath: ''
6
-out:
7
- result: ''
8
- return: 'FAIL'
9
----
10
-test case: 'single start tag'
11
-in:
12
- xml: '<a>'
13
- xpath: ''
14
-out:
15
- result: ''
16
- return: 'FAIL'
17
----
18
-test case: 'single end tag'
19
-in:
20
- xml: '<a/>'
21
- xpath: ''
22
-out:
23
- result: ''
24
- return: 'FAIL'
25
----
26
-test case: 'wrong operation format'
27
-in:
28
- xml: '<a/>'
29
- xpath: '/a[\'
30
-out:
31
- result: ''
32
- return: 'FAIL'
33
----
34
-test case: 'wrong operation expression'
35
-in:
36
- xml: '<a/>'
37
- xpath: '1 div 0'
38
-out:
39
- result: ''
40
- return: 'FAIL'
41
----
42
-test case: 'wrong operation format 2'
43
-in:
44
- xml: '<a/>'
45
- xpath: '-a'
46
-out:
47
- result: ''
48
- return: 'FAIL'
49
----
50
-test case: 'empty output'
51
-in:
52
- xml: '<a/>'
53
- xpath: '/b'
54
-out:
55
- result: ''
56
- return: 'SUCCEED'
57
----
58
-test case: 'successful expression'
59
-in:
60
- xml: '<a/>'
61
- xpath: '3 div 2'
62
-out:
63
- result: '1.5'
64
- return: 'SUCCEED'
65
----
66
-test case: 'return end tag'
67
-in:
68
- xml: '<a/>'
69
- xpath: '/a'
70
-out:
71
- result: '<a/>'
72
- return: 'SUCCEED'
73
----
74
-test case: 'return text'
75
-in:
76
- xml: '<a>1</a>'
77
- xpath: '/a/text()'
78
-out:
79
- result: '1'
80
- return: 'SUCCEED'
81
----
82
-test case: 'return string'
83
-in:
84
- xml: '<a>1</a>'
85
- xpath: 'string(/a)'
86
-out:
87
- result: '1'
88
- return: 'SUCCEED'
89
----
90
-test case: 'return attribute'
91
-in:
92
- xml: '<a b="10">1</a>'
93
- xpath: 'string(/a/@b)'
94
-out:
95
- result: '10'
96
- return: 'SUCCEED'
97
----
98
-test case: 'return pattern'
99
-in:
100
- xml: '<a><b x="1"/><c x="2"/><d x="1"/></a>'
101
- xpath: '//*[@x="1"]'
102
-out:
103
- result: '<b x="1"/><d x="1"/>'
104
- return: 'SUCCEED'
105
-...
src/go/plugin/scripts.d/pkg/zabbixpreproc/testdata/zbx_item_preproc.yaml
deleted
-4630
@@ -1,4630 +0,0 @@
1
----
2
-test case: string(10) * 10
3
-in:
4
- value:
5
- value_type: ITEM_VALUE_TYPE_STR
6
- time: 2017-10-29 03:15:00 +03:00
7
- data: 10
8
- step:
9
- type: ZBX_PREPROC_MULTIPLIER
10
- params: 10
11
-out:
12
- return: SUCCEED
13
- value: 100
14
----
15
-test case: string(10x) * 10
16
-in:
17
- value:
18
- value_type: ITEM_VALUE_TYPE_STR
19
- time: 2017-10-29 03:15:00 +03:00
20
- data: 10x
21
- step:
22
- type: ZBX_PREPROC_MULTIPLIER
23
- params: 10
24
-out:
25
- return: FAIL
26
----
27
-test case: string(10) * abc
28
-in:
29
- value:
30
- value_type: ITEM_VALUE_TYPE_STR
31
- time: 2017-10-29 03:15:00 +03:00
32
- data: 10
33
- step:
34
- type: ZBX_PREPROC_MULTIPLIER
35
- params: abc
36
-out:
37
- return: FAIL
38
----
39
-test case: string(1.5) * 3
40
-in:
41
- value:
42
- value_type: ITEM_VALUE_TYPE_STR
43
- time: 2017-10-29 03:15:00 +03:00
44
- data: 1.5
45
- step:
46
- type: ZBX_PREPROC_MULTIPLIER
47
- params: 3
48
-out:
49
- return: SUCCEED
50
- value: 4.5
51
----
52
-test case: uint64(1.5) * 3
53
-in:
54
- value:
55
- value_type: ITEM_VALUE_TYPE_UINT64
56
- time: 2017-10-29 03:15:00 +03:00
57
- data: 1.5
58
- step:
59
- type: ZBX_PREPROC_MULTIPLIER
60
- params: 3
61
-out:
62
- return: SUCCEED
63
- value: 3
64
----
65
-test case: uint64(3) * 1.5
66
-in:
67
- value:
68
- value_type: ITEM_VALUE_TYPE_UINT64
69
- time: 2017-10-29 03:15:00 +03:00
70
- data: 3
71
- step:
72
- type: ZBX_PREPROC_MULTIPLIER
73
- params: 1.5
74
-out:
75
- return: SUCCEED
76
- value: 4
77
----
78
-test case: float(1.5) * 3
79
-in:
80
- value:
81
- value_type: ITEM_VALUE_TYPE_FLOAT
82
- time: 2017-10-29 03:15:00 +03:00
83
- data: 1.5
84
- step:
85
- type: ZBX_PREPROC_MULTIPLIER
86
- params: 3
87
-out:
88
- return: SUCCEED
89
- value: 4.5
90
----
91
-test case: float(3) * 1.5
92
-in:
93
- value:
94
- value_type: ITEM_VALUE_TYPE_FLOAT
95
- time: 2017-10-29 03:15:00 +03:00
96
- data: 3
97
- step:
98
- type: ZBX_PREPROC_MULTIPLIER
99
- params: 1.5
100
-out:
101
- return: SUCCEED
102
- value: 4.5
103
----
104
-test case: rtrim(01abc01, 01)
105
-in:
106
- value:
107
- value_type: ITEM_VALUE_TYPE_STR
108
- time: 2017-10-29 03:15:00 +03:00
109
- data: 01abc01
110
- step:
111
- type: ZBX_PREPROC_RTRIM
112
- params: 01
113
-out:
114
- return: SUCCEED
115
- value: 01abc
116
----
117
-test case: rtrim(01abc01, abc)
118
-in:
119
- value:
120
- value_type: ITEM_VALUE_TYPE_STR
121
- time: 2017-10-29 03:15:00 +03:00
122
- data: 01abc01
123
- step:
124
- type: ZBX_PREPROC_RTRIM
125
- params: abc
126
-out:
127
- return: SUCCEED
128
- value: 01abc01
129
----
130
-test case: ltrim(01abc01, 01)
131
-in:
132
- value:
133
- value_type: ITEM_VALUE_TYPE_STR
134
- time: 2017-10-29 03:15:00 +03:00
135
- data: 01abc01
136
- step:
137
- type: ZBX_PREPROC_LTRIM
138
- params: 01
139
-out:
140
- return: SUCCEED
141
- value: abc01
142
----
143
-test case: ltrim(01abc01, abc)
144
-in:
145
- value:
146
- value_type: ITEM_VALUE_TYPE_STR
147
- time: 2017-10-29 03:15:00 +03:00
148
- data: 01abc01
149
- step:
150
- type: ZBX_PREPROC_LTRIM
151
- params: abc
152
-out:
153
- return: SUCCEED
154
- value: 01abc01
155
----
156
-test case: trim(01abc01, 01)
157
-in:
158
- value:
159
- value_type: ITEM_VALUE_TYPE_STR
160
- time: 2017-10-29 03:15:00 +03:00
161
- data: 01abc01
162
- step:
163
- type: ZBX_PREPROC_TRIM
164
- params: 01
165
-out:
166
- return: SUCCEED
167
- value: abc
168
----
169
-test case: trim(01abc01, abc)
170
-in:
171
- value:
172
- value_type: ITEM_VALUE_TYPE_STR
173
- time: 2017-10-29 03:15:00 +03:00
174
- data: 01abc01
175
- step:
176
- type: ZBX_PREPROC_TRIM
177
- params: abc
178
-out:
179
- return: SUCCEED
180
- value: 01abc01
181
----
182
-test case: trim(\\s, abc)
183
-in:
184
- value:
185
- value_type: ITEM_VALUE_TYPE_STR
186
- time: 2017-10-29 03:15:00 +03:00
187
- data: "\\ abc \\"
188
- step:
189
- type: ZBX_PREPROC_TRIM
190
- params: "\\\\s"
191
-out:
192
- return: SUCCEED
193
- value: "abc"
194
----
195
-test case: regsub("test 123 number", "([0-9]+", 1)
196
-in:
197
- value:
198
- value_type: ITEM_VALUE_TYPE_STR
199
- time: 2017-10-29 03:15:00 +03:00
200
- data: test 123 number
201
- step:
202
- type: ZBX_PREPROC_REGSUB
203
- params: "([0-9]+\n\\1"
204
-out:
205
- return: FAIL
206
----
207
-test case: regsub("test 123 number", "([0-9]+)", \1)
208
-in:
209
- value:
210
- value_type: ITEM_VALUE_TYPE_STR
211
- time: 2017-10-29 03:15:00 +03:00
212
- data: test 123 number
213
- step:
214
- type: ZBX_PREPROC_REGSUB
215
- params: "([0-9]+)\n\\1"
216
-out:
217
- return: SUCCEED
218
- value: 123
219
----
220
-test case: regsub("test 123 number", "([0-9]+)", x\1y)
221
-in:
222
- value:
223
- value_type: ITEM_VALUE_TYPE_STR
224
- time: 2017-10-29 03:15:00 +03:00
225
- data: test 123 number
226
- step:
227
- type: ZBX_PREPROC_REGSUB
228
- params: "([0-9]+)\nx\\1y"
229
-out:
230
- return: SUCCEED
231
- value: x123y
232
----
233
-test case: regsub("test 123 number", "([0-9]+)", )
234
-in:
235
- value:
236
- value_type: ITEM_VALUE_TYPE_STR
237
- time: 2017-10-29 03:15:00 +03:00
238
- data: test 123 number
239
- step:
240
- type: ZBX_PREPROC_REGSUB
241
- params: "([0-9]+)"
242
-out:
243
- return: FAIL
244
----
245
-test case: regsub("test abc number", "([0-9]+)", \1)
246
-in:
247
- value:
248
- value_type: ITEM_VALUE_TYPE_STR
249
- time: 2017-10-29 03:15:00 +03:00
250
- data: test abc number
251
- step:
252
- type: ZBX_PREPROC_REGSUB
253
- params: "([0-9]+)\n\\1"
254
-out:
255
- return: FAIL
256
----
257
-test case: regsub("", "([0-9]+)", \1)
258
-in:
259
- value:
260
- value_type: ITEM_VALUE_TYPE_STR
261
- time: 2017-10-29 03:15:00 +03:00
262
- data:
263
- step:
264
- type: ZBX_PREPROC_REGSUB
265
- params: "([0-9]+)\n\\1"
266
-out:
267
- return: FAIL
268
----
269
-test case: regsub("", "^$", ok)
270
-in:
271
- value:
272
- value_type: ITEM_VALUE_TYPE_STR
273
- time: 2017-10-29 03:15:00 +03:00
274
- data: ""
275
- step:
276
- type: ZBX_PREPROC_REGSUB
277
- params: "^$\nok"
278
-out:
279
- return: SUCCEED
280
- value: ok
281
----
282
-test case: bool2dec()
283
-in:
284
- value:
285
- value_type: ITEM_VALUE_TYPE_STR
286
- time: 2017-10-29 03:15:00 +03:00
287
- data: ""
288
- step:
289
- type: ZBX_PREPROC_BOOL2DEC
290
-out:
291
- return: FAIL
292
----
293
-test case: bool2dec(abc)
294
-in:
295
- value:
296
- value_type: ITEM_VALUE_TYPE_STR
297
- time: 2017-10-29 03:15:00 +03:00
298
- data: "abc"
299
- step:
300
- type: ZBX_PREPROC_BOOL2DEC
301
-out:
302
- return: FAIL
303
----
304
-test case: bool2dec(1)
305
-in:
306
- value:
307
- value_type: ITEM_VALUE_TYPE_STR
308
- time: 2017-10-29 03:15:00 +03:00
309
- data: "1"
310
- step:
311
- type: ZBX_PREPROC_BOOL2DEC
312
-out:
313
- return: SUCCEED
314
- value: 1
315
----
316
-test case: bool2dec(0)
317
-in:
318
- value:
319
- value_type: ITEM_VALUE_TYPE_STR
320
- time: 2017-10-29 03:15:00 +03:00
321
- data: "0"
322
- step:
323
- type: ZBX_PREPROC_BOOL2DEC
324
-out:
325
- return: SUCCEED
326
- value: 0
327
----
328
-test case: bool2dec(true)
329
-in:
330
- value:
331
- value_type: ITEM_VALUE_TYPE_STR
332
- time: 2017-10-29 03:15:00 +03:00
333
- data: "true"
334
- step:
335
- type: ZBX_PREPROC_BOOL2DEC
336
-out:
337
- return: SUCCEED
338
- value: 1
339
----
340
-test case: bool2dec(t)
341
-in:
342
- value:
343
- value_type: ITEM_VALUE_TYPE_STR
344
- time: 2017-10-29 03:15:00 +03:00
345
- data: "t"
346
- step:
347
- type: ZBX_PREPROC_BOOL2DEC
348
-out:
349
- return: SUCCEED
350
- value: 1
351
----
352
-test case: bool2dec(yes)
353
-in:
354
- value:
355
- value_type: ITEM_VALUE_TYPE_STR
356
- time: 2017-10-29 03:15:00 +03:00
357
- data: "yes"
358
- step:
359
- type: ZBX_PREPROC_BOOL2DEC
360
-out:
361
- return: SUCCEED
362
- value: 1
363
----
364
-test case: bool2dec(y)
365
-in:
366
- value:
367
- value_type: ITEM_VALUE_TYPE_STR
368
- time: 2017-10-29 03:15:00 +03:00
369
- data: "y"
370
- step:
371
- type: ZBX_PREPROC_BOOL2DEC
372
-out:
373
- return: SUCCEED
374
- value: 1
375
----
376
-test case: bool2dec(ok)
377
-in:
378
- value:
379
- value_type: ITEM_VALUE_TYPE_STR
380
- time: 2017-10-29 03:15:00 +03:00
381
- data: "ok"
382
- step:
383
- type: ZBX_PREPROC_BOOL2DEC
384
-out:
385
- return: SUCCEED
386
- value: 1
387
----
388
-test case: bool2dec(on)
389
-in:
390
- value:
391
- value_type: ITEM_VALUE_TYPE_STR
392
- time: 2017-10-29 03:15:00 +03:00
393
- data: "on"
394
- step:
395
- type: ZBX_PREPROC_BOOL2DEC
396
-out:
397
- return: SUCCEED
398
- value: 1
399
----
400
-test case: bool2dec(false)
401
-in:
402
- value:
403
- value_type: ITEM_VALUE_TYPE_STR
404
- time: 2017-10-29 03:15:00 +03:00
405
- data: "false"
406
- step:
407
- type: ZBX_PREPROC_BOOL2DEC
408
-out:
409
- return: SUCCEED
410
- value: 0
411
----
412
-test case: bool2dec(f)
413
-in:
414
- value:
415
- value_type: ITEM_VALUE_TYPE_STR
416
- time: 2017-10-29 03:15:00 +03:00
417
- data: "f"
418
- step:
419
- type: ZBX_PREPROC_BOOL2DEC
420
-out:
421
- return: SUCCEED
422
- value: 0
423
----
424
-test case: bool2dec(no)
425
-in:
426
- value:
427
- value_type: ITEM_VALUE_TYPE_STR
428
- time: 2017-10-29 03:15:00 +03:00
429
- data: "no"
430
- step:
431
- type: ZBX_PREPROC_BOOL2DEC
432
-out:
433
- return: SUCCEED
434
- value: 0
435
----
436
-test case: bool2dec(n)
437
-in:
438
- value:
439
- value_type: ITEM_VALUE_TYPE_STR
440
- time: 2017-10-29 03:15:00 +03:00
441
- data: "n"
442
- step:
443
- type: ZBX_PREPROC_BOOL2DEC
444
-out:
445
- return: SUCCEED
446
- value: 0
447
----
448
-test case: bool2dec(err)
449
-in:
450
- value:
451
- value_type: ITEM_VALUE_TYPE_STR
452
- time: 2017-10-29 03:15:00 +03:00
453
- data: "err"
454
- step:
455
- type: ZBX_PREPROC_BOOL2DEC
456
-out:
457
- return: SUCCEED
458
- value: 0
459
----
460
-test case: bool2dec(off)
461
-in:
462
- value:
463
- value_type: ITEM_VALUE_TYPE_STR
464
- time: 2017-10-29 03:15:00 +03:00
465
- data: "off"
466
- step:
467
- type: ZBX_PREPROC_BOOL2DEC
468
-out:
469
- return: SUCCEED
470
- value: 0
471
----
472
-test case: oct2dec(0)
473
-in:
474
- value:
475
- value_type: ITEM_VALUE_TYPE_STR
476
- time: 2017-10-29 03:15:00 +03:00
477
- data: 0
478
- step:
479
- type: ZBX_PREPROC_OCT2DEC
480
-out:
481
- return: SUCCEED
482
- value: 0
483
----
484
-test case: oct2dec(7)
485
-in:
486
- value:
487
- value_type: ITEM_VALUE_TYPE_STR
488
- time: 2017-10-29 03:15:00 +03:00
489
- data: 7
490
- step:
491
- type: ZBX_PREPROC_OCT2DEC
492
-out:
493
- return: SUCCEED
494
- value: 7
495
----
496
-test case: oct2dec(10)
497
-in:
498
- value:
499
- value_type: ITEM_VALUE_TYPE_STR
500
- time: 2017-10-29 03:15:00 +03:00
501
- data: 10
502
- step:
503
- type: ZBX_PREPROC_OCT2DEC
504
-out:
505
- return: SUCCEED
506
- value: 8
507
----
508
-test case: oct2dec(8)
509
-in:
510
- value:
511
- value_type: ITEM_VALUE_TYPE_STR
512
- time: 2017-10-29 03:15:00 +03:00
513
- data: 8
514
- step:
515
- type: ZBX_PREPROC_OCT2DEC
516
-out:
517
- return: FAIL
518
----
519
-test case: hex2dec(F)
520
-in:
521
- value:
522
- value_type: ITEM_VALUE_TYPE_STR
523
- time: 2017-10-29 03:15:00 +03:00
524
- data: F
525
- step:
526
- type: ZBX_PREPROC_HEX2DEC
527
-out:
528
- return: SUCCEED
529
- value: 15
530
----
531
-test case: hex2dec(a)
532
-in:
533
- value:
534
- value_type: ITEM_VALUE_TYPE_STR
535
- time: 2017-10-29 03:15:00 +03:00
536
- data: a
537
- step:
538
- type: ZBX_PREPROC_HEX2DEC
539
-out:
540
- return: SUCCEED
541
- value: 10
542
----
543
-test case: hex2dec(10)
544
-in:
545
- value:
546
- value_type: ITEM_VALUE_TYPE_STR
547
- time: 2017-10-29 03:15:00 +03:00
548
- data: 10
549
- step:
550
- type: ZBX_PREPROC_HEX2DEC
551
-out:
552
- return: SUCCEED
553
- value: 16
554
----
555
-test case: hex2dec(g)
556
-in:
557
- value:
558
- value_type: ITEM_VALUE_TYPE_STR
559
- time: 2017-10-29 03:15:00 +03:00
560
- data: g
561
- step:
562
- type: ZBX_PREPROC_HEX2DEC
563
-out:
564
- return: FAIL
565
----
566
-test case: hex2dec(ff ff)
567
-in:
568
- value:
569
- value_type: ITEM_VALUE_TYPE_STR
570
- time: 2017-10-29 03:15:00 +03:00
571
- data: ff ff
572
- step:
573
- type: ZBX_PREPROC_HEX2DEC
574
-out:
575
- return: SUCCEED
576
- value: 65535
577
----
578
-test case: deltavalue(5, 10)
579
-in:
580
- value:
581
- value_type: ITEM_VALUE_TYPE_STR
582
- time: 2017-10-29 03:15:00 +03:00
583
- data: 10
584
- history:
585
- variant: ZBX_VARIANT_UI64
586
- time: 2017-10-29 03:14:00 +03:00
587
- data: 5
588
- step:
589
- type: ZBX_PREPROC_DELTA_VALUE
590
-out:
591
- return: SUCCEED
592
- value: 5
593
- history:
594
- data: 10
595
- time: 2017-10-29 03:15:00 +03:00
596
----
597
-test case: deltavalue(-4.5, 5.5)
598
-in:
599
- value:
600
- value_type: ITEM_VALUE_TYPE_STR
601
- time: 2017-10-29 03:15:00 +03:00
602
- data: 5.5
603
- history:
604
- variant: ZBX_VARIANT_DBL
605
- time: 2017-10-29 03:14:00 +03:00
606
- data: -4.5
607
- step:
608
- type: ZBX_PREPROC_DELTA_VALUE
609
-out:
610
- return: SUCCEED
611
- value: 10
612
- history:
613
- time: 2017-10-29 03:15:00 +03:00
614
- data: 5.5
615
----
616
-test case: deltavalue(10, 10)
617
-in:
618
- value:
619
- value_type: ITEM_VALUE_TYPE_STR
620
- time: 2017-10-29 03:15:00 +03:00
621
- data: 10
622
- history:
623
- variant: ZBX_VARIANT_UI64
624
- time: 2017-10-29 03:14:00 +03:00
625
- data: 10
626
- step:
627
- type: ZBX_PREPROC_DELTA_VALUE
628
-out:
629
- return: SUCCEED
630
- value: 0
631
- history:
632
- time: 2017-10-29 03:15:00 +03:00
633
- data: 10
634
----
635
-test case: deltavalue(10, 9)
636
-in:
637
- value:
638
- value_type: ITEM_VALUE_TYPE_STR
639
- time: 2017-10-29 03:15:00 +03:00
640
- data: 0
641
- history:
642
- variant: ZBX_VARIANT_UI64
643
- time: 2017-10-29 03:14:00 +03:00
644
- data: 10
645
- step:
646
- type: ZBX_PREPROC_DELTA_VALUE
647
-out:
648
- return: SUCCEED
649
- history:
650
- time: 2017-10-29 03:15:00 +03:00
651
- data: 0
652
----
653
-test case: deltavalue(0, 1.5)
654
-in:
655
- value:
656
- value_type: ITEM_VALUE_TYPE_STR
657
- time: 2017-10-29 03:15:00 +03:00
658
- data: 1.5
659
- history:
660
- variant: ZBX_VARIANT_DBL
661
- time: 2017-10-29 03:14:00 +03:00
662
- data: 0
663
- step:
664
- type: ZBX_PREPROC_DELTA_VALUE
665
-out:
666
- return: SUCCEED
667
- value: 1.5
668
- history:
669
- time: 2017-10-29 03:15:00 +03:00
670
- data: 1.5
671
----
672
-test case: deltaspeed(2, 1, 10s)
673
-in:
674
- value:
675
- value_type: ITEM_VALUE_TYPE_STR
676
- time: 2017-10-29 03:15:00 +03:00
677
- data: 1
678
- history:
679
- variant: ZBX_VARIANT_UI64
680
- time: 2017-10-29 03:14:50 +03:00
681
- data: 2
682
- step:
683
- type: ZBX_PREPROC_DELTA_SPEED
684
-out:
685
- return: SUCCEED
686
- history:
687
- time: 2017-10-29 03:15:00 +03:00
688
- data: 1
689
----
690
-test case: deltaspeed(1, 2, -10s)
691
-in:
692
- value:
693
- value_type: ITEM_VALUE_TYPE_STR
694
- time: 2017-10-29 03:15:00 +03:00
695
- data: 2
696
- history:
697
- variant: ZBX_VARIANT_UI64
698
- time: 2017-10-29 03:15:10 +03:00
699
- data: 1
700
- step:
701
- type: ZBX_PREPROC_DELTA_SPEED
702
-out:
703
- return: SUCCEED
704
- history:
705
- time: 2017-10-29 03:15:00 +03:00
706
- data: 2
707
----
708
-test case: deltaspeed(1, 2, 10s)
709
-in:
710
- value:
711
- value_type: ITEM_VALUE_TYPE_FLOAT
712
- time: 2017-10-29 03:15:00 +03:00
713
- data: 2
714
- history:
715
- variant: ZBX_VARIANT_UI64
716
- time: 2017-10-29 03:14:50 +03:00
717
- data: 1
718
- step:
719
- type: ZBX_PREPROC_DELTA_SPEED
720
-out:
721
- return: SUCCEED
722
- value: 0.1
723
- history:
724
- time: 2017-10-29 03:15:00 +03:00
725
- data: 2
726
----
727
-test case: deltaspeed(2, 3, 10s)
728
-in:
729
- value:
730
- value_type: ITEM_VALUE_TYPE_UINT64
731
- time: 2017-10-29 03:15:00 +03:00
732
- data: 3
733
- history:
734
- variant: ZBX_VARIANT_UI64
735
- time: 2017-10-29 03:14:50 +03:00
736
- data: 2
737
- step:
738
- type: ZBX_PREPROC_DELTA_SPEED
739
-out:
740
- return: SUCCEED
741
- value: 0
742
- history:
743
- time: 2017-10-29 03:15:00 +03:00
744
- data: 3
745
----
746
-test case: deltaspeed(2, 3, 1s)
747
-in:
748
- value:
749
- value_type: ITEM_VALUE_TYPE_UINT64
750
- time: 2017-10-29 03:15:00 +03:00
751
- data: 3
752
- history:
753
- variant: ZBX_VARIANT_UI64
754
- time: 2017-10-29 03:14:59 +03:00
755
- data: 2
756
- step:
757
- type: ZBX_PREPROC_DELTA_SPEED
758
-out:
759
- return: SUCCEED
760
- value: 1
761
- history:
762
- time: 2017-10-29 03:15:00 +03:00
763
- data: 3
764
----
765
-test case: xpath1
766
-in:
767
- value:
768
- value_type: ITEM_VALUE_TYPE_STR
769
- time: 2017-10-29 03:15:00 +03:00
770
- data: ""
771
- step:
772
- type: ZBX_PREPROC_XPATH
773
- params: ""
774
-out:
775
- return: FAIL
776
----
777
-test case: xpath2
778
-in:
779
- value:
780
- value_type: ITEM_VALUE_TYPE_STR
781
- time: 2017-10-29 03:15:00 +03:00
782
- data: <a/>
783
- step:
784
- type: ZBX_PREPROC_XPATH
785
- params: ""
786
-out:
787
- return: FAIL
788
----
789
-test case: xpath3
790
-in:
791
- value:
792
- value_type: ITEM_VALUE_TYPE_STR
793
- time: 2017-10-29 03:15:00 +03:00
794
- data: <a/>
795
- step:
796
- type: ZBX_PREPROC_XPATH
797
- params: |-
798
- /a["]
799
-out:
800
- return: FAIL
801
----
802
-test case: xpath4
803
-in:
804
- value:
805
- value_type: ITEM_VALUE_TYPE_STR
806
- time: 2017-10-29 03:15:00 +03:00
807
- data: <a/>
808
- step:
809
- type: ZBX_PREPROC_XPATH
810
- params: 1 div 0
811
-out:
812
- return: FAIL
813
----
814
-test case: xpath5
815
-in:
816
- value:
817
- value_type: ITEM_VALUE_TYPE_STR
818
- time: 2017-10-29 03:15:00 +03:00
819
- data: <a/>
820
- step:
821
- type: ZBX_PREPROC_XPATH
822
- params: -a
823
-out:
824
- return: FAIL
825
----
826
-test case: xpath6
827
-in:
828
- value:
829
- value_type: ITEM_VALUE_TYPE_STR
830
- time: 2017-10-29 03:15:00 +03:00
831
- data: <a/>
832
- step:
833
- type: ZBX_PREPROC_XPATH
834
- params: /b
835
-out:
836
- return: SUCCEED
837
- value: ""
838
----
839
-test case: xpath7
840
-in:
841
- value:
842
- value_type: ITEM_VALUE_TYPE_STR
843
- time: 2017-10-29 03:15:00 +03:00
844
- data: <a/>
845
- step:
846
- type: ZBX_PREPROC_XPATH
847
- params: 3 div 2
848
-out:
849
- return: SUCCEED
850
- value: 1.5
851
----
852
-test case: xpath8
853
-in:
854
- value:
855
- value_type: ITEM_VALUE_TYPE_STR
856
- time: 2017-10-29 03:15:00 +03:00
857
- data: <a/>
858
- step:
859
- type: ZBX_PREPROC_XPATH
860
- params: /a
861
-out:
862
- return: SUCCEED
863
- value: <a/>
864
----
865
-test case: xpath9
866
-in:
867
- value:
868
- value_type: ITEM_VALUE_TYPE_STR
869
- time: 2017-10-29 03:15:00 +03:00
870
- data: <a>1</a>
871
- step:
872
- type: ZBX_PREPROC_XPATH
873
- params: /a/text()
874
-out:
875
- return: SUCCEED
876
- value: 1
877
----
878
-test case: xpath10
879
-in:
880
- value:
881
- value_type: ITEM_VALUE_TYPE_STR
882
- time: 2017-10-29 03:15:00 +03:00
883
- data: <a>1</a>
884
- step:
885
- type: ZBX_PREPROC_XPATH
886
- params: string(/a)
887
-out:
888
- return: SUCCEED
889
- value: 1
890
----
891
-test case: xpath11
892
-in:
893
- value:
894
- value_type: ITEM_VALUE_TYPE_STR
895
- time: 2017-10-29 03:15:00 +03:00
896
- data: <a b="10">1</a>
897
- step:
898
- type: ZBX_PREPROC_XPATH
899
- params: string(/a/@b)
900
-out:
901
- return: SUCCEED
902
- value: 10
903
----
904
-test case: xpath12
905
-in:
906
- value:
907
- value_type: ITEM_VALUE_TYPE_STR
908
- time: 2017-10-29 03:15:00 +03:00
909
- data: <a><b x="1"/><c x="2"/><d x="1"/></a>
910
- step:
911
- type: ZBX_PREPROC_XPATH
912
- params: //*[@x="1"]
913
-out:
914
- return: SUCCEED
915
- value: <b x="1"/><d x="1"/>
916
----
917
-test case: jsonpath1
918
-in:
919
- value:
920
- value_type: ITEM_VALUE_TYPE_STR
921
- time: 2017-10-29 03:15:00 +03:00
922
- data: abc
923
- step:
924
- type: ZBX_PREPROC_JSONPATH
925
- params:
926
-out:
927
- return: FAIL
928
----
929
-test case: jsonpath2
930
-in:
931
- value:
932
- value_type: ITEM_VALUE_TYPE_STR
933
- time: 2017-10-29 03:15:00 +03:00
934
- data: |-
935
- {"a":{"b":[1, 2, 3]}}
936
- step:
937
- type: ZBX_PREPROC_JSONPATH
938
- params: $abc
939
-out:
940
- return: FAIL
941
----
942
-test case: jsonpath3
943
-in:
944
- value:
945
- value_type: ITEM_VALUE_TYPE_STR
946
- time: 2017-10-29 03:15:00 +03:00
947
- data: |-
948
- {"a":{"b":[1, 2, 3]}}
949
- step:
950
- type: ZBX_PREPROC_JSONPATH
951
- params: $.abc
952
-out:
953
- return: FAIL
954
----
955
-test case: jsonpath4
956
-in:
957
- value:
958
- value_type: ITEM_VALUE_TYPE_STR
959
- time: 2017-10-29 03:15:00 +03:00
960
- data: |-
961
- {"a":{"b":[1, 2, 3]}}
962
- step:
963
- type: ZBX_PREPROC_JSONPATH
964
- params: $.a
965
-out:
966
- return: SUCCEED
967
- value: |-
968
- {"b":[1,2,3]}
969
----
970
-test case: jsonpath5
971
-in:
972
- value:
973
- value_type: ITEM_VALUE_TYPE_STR
974
- time: 2017-10-29 03:15:00 +03:00
975
- data: |-
976
- {"a":{"b":[1, 2, 3]}}
977
- step:
978
- type: ZBX_PREPROC_JSONPATH
979
- params: $.a['b']
980
-out:
981
- return: SUCCEED
982
- value: |-
983
- [1,2,3]
984
----
985
-test case: jsonpath6
986
-in:
987
- value:
988
- value_type: ITEM_VALUE_TYPE_STR
989
- time: 2017-10-29 03:15:00 +03:00
990
- data: |-
991
- {"a":{"b":[1, 2, 3]}}
992
- step:
993
- type: ZBX_PREPROC_JSONPATH
994
- params: $.a['b'][1]
995
-out:
996
- return: SUCCEED
997
- value: 2
998
----
999
-test case: jsonpath7
1000
-in:
1001
- value:
1002
- value_type: ITEM_VALUE_TYPE_STR
1003
- time: 2017-10-29 03:15:00 +03:00
1004
- data: |-
1005
- {"a":{"b c":["one", "two", "three"]}}
1006
- step:
1007
- type: ZBX_PREPROC_JSONPATH
1008
- params: $.a['b c']
1009
-out:
1010
- return: SUCCEED
1011
- value: '["one","two","three"]'
1012
----
1013
-test case: jsonpath8
1014
-in:
1015
- value:
1016
- value_type: ITEM_VALUE_TYPE_STR
1017
- time: 2017-10-29 03:15:00 +03:00
1018
- data: |-
1019
- {"a":{"b c":["one", "two \"2\"", 3]}}
1020
- step:
1021
- type: ZBX_PREPROC_JSONPATH
1022
- params: $.a['b c'][1]
1023
-out:
1024
- return: SUCCEED
1025
- value: two "2"
1026
----
1027
-test case: jsonpath9
1028
-in:
1029
- value:
1030
- value_type: ITEM_VALUE_TYPE_STR
1031
- time: 2017-10-29 03:15:00 +03:00
1032
- data: |-
1033
- {"a":{"b c":["one", "two \"2\"", 3]}}
1034
- step:
1035
- type: ZBX_PREPROC_JSONPATH
1036
- params: $.a['b c'][2]
1037
-out:
1038
- return: SUCCEED
1039
- value: 3
1040
----
1041
-test case: jsonpath10
1042
-in:
1043
- value:
1044
- value_type: ITEM_VALUE_TYPE_STR
1045
- time: 2017-10-29 03:15:00 +03:00
1046
- data: |-
1047
- {"a":{"b":[1, 2, 3]}}
1048
- step:
1049
- type: ZBX_PREPROC_JSONPATH
1050
- params: $.a['b'][3]
1051
-out:
1052
- return: FAIL
1053
----
1054
-test case: jsonpath11
1055
-in:
1056
- value:
1057
- value_type: ITEM_VALUE_TYPE_STR
1058
- time: 2017-10-29 03:15:00 +03:00
1059
- data: |-
1060
- {"a":{"b":[1, 2, 3]}}
1061
- step:
1062
- type: ZBX_PREPROC_JSONPATH
1063
- params: $.a['b'][
1064
-out:
1065
- return: FAIL
1066
----
1067
-test case: jsonpath12
1068
-in:
1069
- value:
1070
- value_type: ITEM_VALUE_TYPE_STR
1071
- time: 2017-10-29 03:15:00 +03:00
1072
- data: |-
1073
- {"a":{"b":[1, 2, 3]}}
1074
- step:
1075
- type: ZBX_PREPROC_JSONPATH
1076
- params: $.a['b][3]
1077
-out:
1078
- return: FAIL
1079
----
1080
-test case: jsonpath13
1081
-in:
1082
- value:
1083
- value_type: ITEM_VALUE_TYPE_STR
1084
- time: 2017-10-29 03:15:00 +03:00
1085
- data: |-
1086
- {"a":{"\ud800": 1}}
1087
- step:
1088
- type: ZBX_PREPROC_JSONPATH
1089
- params: $.a
1090
-out:
1091
- return: FAIL
1092
----
1093
-test case: validate_range(1, 5, 10)
1094
-in:
1095
- value:
1096
- value_type: ITEM_VALUE_TYPE_STR
1097
- time: 2017-10-29 03:15:00 +03:00
1098
- data: 1
1099
- step:
1100
- type: ZBX_PREPROC_VALIDATE_RANGE
1101
- params: |-
1102
- 5
1103
- 10
1104
-out:
1105
- return: FAIL
1106
----
1107
-test case: validate_range(5, 5, 10)
1108
-in:
1109
- value:
1110
- value_type: ITEM_VALUE_TYPE_STR
1111
- time: 2017-10-29 03:15:00 +03:00
1112
- data: 5
1113
- step:
1114
- type: ZBX_PREPROC_VALIDATE_RANGE
1115
- params: |-
1116
- 5
1117
- 10
1118
-out:
1119
- return: SUCCEED
1120
- value: 5
1121
----
1122
-test case: validate_range(10, 5, 10)
1123
-in:
1124
- value:
1125
- value_type: ITEM_VALUE_TYPE_STR
1126
- time: 2017-10-29 03:15:00 +03:00
1127
- data: 10
1128
- step:
1129
- type: ZBX_PREPROC_VALIDATE_RANGE
1130
- params: |-
1131
- 5
1132
- 10
1133
-out:
1134
- return: SUCCEED
1135
- value: 10
1136
----
1137
-test case: validate_range(10.1, 5, 10)
1138
-in:
1139
- value:
1140
- value_type: ITEM_VALUE_TYPE_STR
1141
- time: 2017-10-29 03:15:00 +03:00
1142
- data: 10.1
1143
- step:
1144
- type: ZBX_PREPROC_VALIDATE_RANGE
1145
- params: |-
1146
- 5
1147
- 10
1148
-out:
1149
- return: FAIL
1150
----
1151
-test case: validate_regex(abc 123 xyz, ([0-9+))
1152
-in:
1153
- value:
1154
- value_type: ITEM_VALUE_TYPE_STR
1155
- time: 2017-10-29 03:15:00 +03:00
1156
- data: abc 123 xyz
1157
- step:
1158
- type: ZBX_PREPROC_VALIDATE_REGEX
1159
- params: ([0-9+)
1160
-out:
1161
- return: FAIL
1162
----
1163
-test case: validate_regex(abc opq xyz, ([0-9]+))
1164
-in:
1165
- value:
1166
- value_type: ITEM_VALUE_TYPE_STR
1167
- time: 2017-10-29 03:15:00 +03:00
1168
- data: abc opq xyz
1169
- step:
1170
- type: ZBX_PREPROC_VALIDATE_REGEX
1171
- params: ([0-9]+)
1172
-out:
1173
- return: FAIL
1174
----
1175
-test case: validate_regex(abc 123 xyz, ([0-9]+))
1176
-in:
1177
- value:
1178
- value_type: ITEM_VALUE_TYPE_STR
1179
- time: 2017-10-29 03:15:00 +03:00
1180
- data: abc 123 xyz
1181
- step:
1182
- type: ZBX_PREPROC_VALIDATE_REGEX
1183
- params: ([0-9]+)
1184
-out:
1185
- return: SUCCEED
1186
- value: abc 123 xyz
1187
----
1188
-test case: validate_regex(abc 123 xyz, ([0-9+))
1189
-in:
1190
- value:
1191
- value_type: ITEM_VALUE_TYPE_STR
1192
- time: 2017-10-29 03:15:00 +03:00
1193
- data: abc 123 xyz
1194
- step:
1195
- type: ZBX_PREPROC_VALIDATE_NOT_REGEX
1196
- params: ([0-9+)
1197
-out:
1198
- return: FAIL
1199
----
1200
-test case: validate_regex(abc opq xyz, ([0-9]+))
1201
-in:
1202
- value:
1203
- value_type: ITEM_VALUE_TYPE_STR
1204
- time: 2017-10-29 03:15:00 +03:00
1205
- data: abc opq xyz
1206
- step:
1207
- type: ZBX_PREPROC_VALIDATE_NOT_REGEX
1208
- params: ([0-9]+)
1209
-out:
1210
- return: SUCCEED
1211
- value: abc opq xyz
1212
----
1213
-test case: validate_regex(abc 123 xyz, ([0-9]+))
1214
-in:
1215
- value:
1216
- value_type: ITEM_VALUE_TYPE_STR
1217
- time: 2017-10-29 03:15:00 +03:00
1218
- data: abc 123 xyz
1219
- step:
1220
- type: ZBX_PREPROC_VALIDATE_NOT_REGEX
1221
- params: ([0-9]+)
1222
-out:
1223
- return: FAIL
1224
----
1225
-test case: string(10) * 10 (discard)
1226
-in:
1227
- value:
1228
- value_type: ITEM_VALUE_TYPE_STR
1229
- time: 2017-10-29 03:15:00 +03:00
1230
- data: x
1231
- step:
1232
- type: ZBX_PREPROC_MULTIPLIER
1233
- params: 10
1234
- error_handler: ZBX_PREPROC_FAIL_DISCARD_VALUE
1235
-out:
1236
- return: SUCCEED
1237
----
1238
-test case: string(10) * 10 (set value)
1239
-in:
1240
- value:
1241
- value_type: ITEM_VALUE_TYPE_STR
1242
- time: 2017-10-29 03:15:00 +03:00
1243
- data: x
1244
- step:
1245
- type: ZBX_PREPROC_MULTIPLIER
1246
- params: 10
1247
- error_handler: ZBX_PREPROC_FAIL_SET_VALUE
1248
- error_handler_params: invalid
1249
-out:
1250
- return: SUCCEED
1251
- value: invalid
1252
----
1253
-test case: string(10) * 10 (set error)
1254
-in:
1255
- value:
1256
- value_type: ITEM_VALUE_TYPE_STR
1257
- time: 2017-10-29 03:15:00 +03:00
1258
- data: x
1259
- step:
1260
- type: ZBX_PREPROC_MULTIPLIER
1261
- params: 10
1262
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
1263
- error_handler_params: custom error
1264
-out:
1265
- return: FAIL
1266
- error: custom error
1267
----
1268
-test case: jsonerror(x, $.error)
1269
-in:
1270
- value:
1271
- value_type: ITEM_VALUE_TYPE_STR
1272
- time: 2017-10-29 03:15:00 +03:00
1273
- data: x
1274
- step:
1275
- type: ZBX_PREPROC_ERROR_FIELD_JSON
1276
- params: $.error
1277
-out:
1278
- return: SUCCEED
1279
- value: x
1280
----
1281
-test case: jsonerror({"error":"error message"}, $error)
1282
-in:
1283
- value:
1284
- value_type: ITEM_VALUE_TYPE_STR
1285
- time: 2017-10-29 03:15:00 +03:00
1286
- data: |-
1287
- {"error":"error message"}
1288
- step:
1289
- type: ZBX_PREPROC_ERROR_FIELD_JSON
1290
- params: $error
1291
-out:
1292
- return: FAIL
1293
----
1294
-test case: jsonerror({"data":"123"}, $.error)
1295
-in:
1296
- value:
1297
- value_type: ITEM_VALUE_TYPE_STR
1298
- time: 2017-10-29 03:15:00 +03:00
1299
- data: |-
1300
- {"data":"123"}
1301
- step:
1302
- type: ZBX_PREPROC_ERROR_FIELD_JSON
1303
- params: $.error
1304
-out:
1305
- return: SUCCEED
1306
- value: |-
1307
- {"data":"123"}
1308
----
1309
-test case: jsonerror({"error":"error message"}, $.error)
1310
-in:
1311
- value:
1312
- value_type: ITEM_VALUE_TYPE_STR
1313
- time: 2017-10-29 03:15:00 +03:00
1314
- data: |-
1315
- {"error":"error message"}
1316
- step:
1317
- type: ZBX_PREPROC_ERROR_FIELD_JSON
1318
- params: $.error
1319
-out:
1320
- return: FAIL
1321
- error: error message
1322
----
1323
-test case: xmlerror(x, //error)
1324
-in:
1325
- value:
1326
- value_type: ITEM_VALUE_TYPE_STR
1327
- time: 2017-10-29 03:15:00 +03:00
1328
- data: x
1329
- step:
1330
- type: ZBX_PREPROC_ERROR_FIELD_XML
1331
- params: //error
1332
-out:
1333
- return: SUCCEED
1334
- value: x
1335
----
1336
-test case: xmlerror(<a><error>custom error</error></a>, "//text(")
1337
-in:
1338
- value:
1339
- value_type: ITEM_VALUE_TYPE_STR
1340
- time: 2017-10-29 03:15:00 +03:00
1341
- data: <a><error>custom error</error></a>
1342
- step:
1343
- type: ZBX_PREPROC_ERROR_FIELD_XML
1344
- params: //text(
1345
-out:
1346
- return: FAIL
1347
----
1348
-test case: xmlerror(<a><error>custom error</error></a>, "//error/text()")
1349
-in:
1350
- value:
1351
- value_type: ITEM_VALUE_TYPE_STR
1352
- time: 2017-10-29 03:15:00 +03:00
1353
- data: <a><error>custom error</error></a>
1354
- step:
1355
- type: ZBX_PREPROC_ERROR_FIELD_XML
1356
- params: //error/text()
1357
-out:
1358
- return: FAIL
1359
- error: custom error
1360
----
1361
-test case: xmlerror(<a><value>1</value></a>, "//error/text()")
1362
-in:
1363
- value:
1364
- value_type: ITEM_VALUE_TYPE_STR
1365
- time: 2017-10-29 03:15:00 +03:00
1366
- data: <a><value>1</value></a>
1367
- step:
1368
- type: ZBX_PREPROC_ERROR_FIELD_XML
1369
- params: //error/text()
1370
-out:
1371
- return: SUCCEED
1372
- value: <a><value>1</value></a>
1373
----
1374
-test case: regexerror("error:123, "error:([0-9+)")
1375
-in:
1376
- value:
1377
- value_type: ITEM_VALUE_TYPE_STR
1378
- time: 2017-10-29 03:15:00 +03:00
1379
- data: error:123
1380
- step:
1381
- type: ZBX_PREPROC_ERROR_FIELD_REGEX
1382
- params: ([0-9+)
1383
-out:
1384
- return: FAIL
1385
----
1386
-test case: regexerror("error:123, "error:([0-9]+)")
1387
-in:
1388
- value:
1389
- value_type: ITEM_VALUE_TYPE_STR
1390
- time: 2017-10-29 03:15:00 +03:00
1391
- data: error:123
1392
- step:
1393
- type: ZBX_PREPROC_ERROR_FIELD_REGEX
1394
- params: |-
1395
- error:([0-9]+)
1396
- \1
1397
-out:
1398
- return: FAIL
1399
- error: 123
1400
----
1401
-test case: regexerror("value:123, "error:([0-9]+)")
1402
-in:
1403
- value:
1404
- value_type: ITEM_VALUE_TYPE_STR
1405
- time: 2017-10-29 03:15:00 +03:00
1406
- data: value:123
1407
- step:
1408
- type: ZBX_PREPROC_ERROR_FIELD_REGEX
1409
- params: |-
1410
- error:([0-9]+)
1411
- \1
1412
-out:
1413
- return: SUCCEED
1414
- value: value:123
1415
----
1416
-test case: throttle(1, 123)
1417
-in:
1418
- value:
1419
- value_type: ITEM_VALUE_TYPE_STR
1420
- time: 2017-10-29 03:15:00 +03:00
1421
- data: 123
1422
- history:
1423
- variant: ZBX_VARIANT_STR
1424
- time: 2017-10-29 03:14:00 +03:00
1425
- data: 1
1426
- step:
1427
- type: ZBX_PREPROC_THROTTLE_VALUE
1428
-out:
1429
- return: SUCCEED
1430
- value: 123
1431
- history:
1432
- time: 2017-10-29 03:15:00 +03:00
1433
- data: 123
1434
----
1435
-test case: throttle(, 123)
1436
-in:
1437
- value:
1438
- value_type: ITEM_VALUE_TYPE_STR
1439
- time: 2017-10-29 03:15:00 +03:00
1440
- data: 123
1441
- step:
1442
- type: ZBX_PREPROC_THROTTLE_VALUE
1443
-out:
1444
- return: SUCCEED
1445
- value: 123
1446
- history:
1447
- time: 2017-10-29 03:15:00 +03:00
1448
- data: 123
1449
----
1450
-test case: throttle(123, 123)
1451
-in:
1452
- value:
1453
- value_type: ITEM_VALUE_TYPE_STR
1454
- time: 2017-10-29 03:15:00 +03:00
1455
- data: 123
1456
- history:
1457
- variant: ZBX_VARIANT_STR
1458
- time: 2017-10-29 03:14:00 +03:00
1459
- data: 123
1460
- step:
1461
- type: ZBX_PREPROC_THROTTLE_VALUE
1462
-out:
1463
- return: SUCCEED
1464
- history:
1465
- time: 2017-10-29 03:14:00 +03:00
1466
- data: 123
1467
----
1468
-test case: throttle_timed(, abc)
1469
-in:
1470
- value:
1471
- value_type: ITEM_VALUE_TYPE_STR
1472
- time: 2017-10-29 03:15:00 +03:00
1473
- data: abc
1474
- step:
1475
- type: ZBX_PREPROC_THROTTLE_TIMED_VALUE
1476
- params: 1m
1477
-out:
1478
- return: SUCCEED
1479
- value: abc
1480
- history:
1481
- time: 2017-10-29 03:15:00 +03:00
1482
- data: abc
1483
----
1484
-test case: throttle_timed(xyz, abc)
1485
-in:
1486
- value:
1487
- value_type: ITEM_VALUE_TYPE_STR
1488
- time: 2017-10-29 03:15:00 +03:00
1489
- data: abc
1490
- history:
1491
- variant: ZBX_VARIANT_STR
1492
- time: 2017-10-29 03:14:30 +03:00
1493
- data: xyz
1494
- step:
1495
- type: ZBX_PREPROC_THROTTLE_TIMED_VALUE
1496
- params: 1m
1497
-out:
1498
- return: SUCCEED
1499
- value: abc
1500
- history:
1501
- time: 2017-10-29 03:15:00 +03:00
1502
- data: abc
1503
----
1504
-test case: throttle_timed(abc, abc, 30s)
1505
-in:
1506
- value:
1507
- value_type: ITEM_VALUE_TYPE_STR
1508
- time: 2017-10-29 03:15:00 +03:00
1509
- data: abc
1510
- history:
1511
- variant: ZBX_VARIANT_STR
1512
- time: 2017-10-29 03:14:30 +03:00
1513
- data: abc
1514
- step:
1515
- type: ZBX_PREPROC_THROTTLE_TIMED_VALUE
1516
- params: 1m
1517
-out:
1518
- return: SUCCEED
1519
- history:
1520
- time: 2017-10-29 03:14:30 +03:00
1521
- data: abc
1522
----
1523
-test case: throttle_timed(abc, abc, 1m)
1524
-in:
1525
- value:
1526
- value_type: ITEM_VALUE_TYPE_STR
1527
- time: 2017-10-29 03:15:00 +03:00
1528
- data: abc
1529
- history:
1530
- variant: ZBX_VARIANT_STR
1531
- time: 2017-10-29 03:14:00 +03:00
1532
- data: abc
1533
- step:
1534
- type: ZBX_PREPROC_THROTTLE_TIMED_VALUE
1535
- params: 1m
1536
-out:
1537
- return: SUCCEED
1538
- value: abc
1539
- history:
1540
- time: 2017-10-29 03:15:00 +03:00
1541
- data: abc
1542
----
1543
-test case: float(1.5e0) * 1
1544
-in:
1545
- value:
1546
- value_type: ITEM_VALUE_TYPE_FLOAT
1547
- time: 2017-10-29 03:15:00 +03:00
1548
- data: 1.5e0
1549
- step:
1550
- type: ZBX_PREPROC_MULTIPLIER
1551
- params: 1
1552
-out:
1553
- return: SUCCEED
1554
- value: 1.5
1555
----
1556
-test case: float(1) * 1.5e0
1557
-in:
1558
- value:
1559
- value_type: ITEM_VALUE_TYPE_FLOAT
1560
- time: 2017-10-29 03:15:00 +03:00
1561
- data: 1
1562
- step:
1563
- type: ZBX_PREPROC_MULTIPLIER
1564
- params: 1.5e0
1565
-out:
1566
- return: SUCCEED
1567
- value: 1.5
1568
----
1569
-test case: float(1.5e0) * 1.5e0
1570
-in:
1571
- value:
1572
- value_type: ITEM_VALUE_TYPE_FLOAT
1573
- time: 2017-10-29 03:15:00 +03:00
1574
- data: 1.5e0
1575
- step:
1576
- type: ZBX_PREPROC_MULTIPLIER
1577
- params: 1.5e0
1578
-out:
1579
- return: SUCCEED
1580
- value: 2.25
1581
----
1582
-test case: float(1.5e1) * 1
1583
-in:
1584
- value:
1585
- value_type: ITEM_VALUE_TYPE_FLOAT
1586
- time: 2017-10-29 03:15:00 +03:00
1587
- data: 1.5e1
1588
- step:
1589
- type: ZBX_PREPROC_MULTIPLIER
1590
- params: 1
1591
-out:
1592
- return: SUCCEED
1593
- value: 15
1594
----
1595
-test case: float(1) * 1.5e1
1596
-in:
1597
- value:
1598
- value_type: ITEM_VALUE_TYPE_FLOAT
1599
- time: 2017-10-29 03:15:00 +03:00
1600
- data: 1
1601
- step:
1602
- type: ZBX_PREPROC_MULTIPLIER
1603
- params: 1.5e1
1604
-out:
1605
- return: SUCCEED
1606
- value: 15
1607
----
1608
-test case: float(1.5e1) * 1.5e1
1609
-in:
1610
- value:
1611
- value_type: ITEM_VALUE_TYPE_FLOAT
1612
- time: 2017-10-29 03:15:00 +03:00
1613
- data: 1.5e1
1614
- step:
1615
- type: ZBX_PREPROC_MULTIPLIER
1616
- params: 1.5e1
1617
-out:
1618
- return: SUCCEED
1619
- value: 225
1620
----
1621
-test case: float(1.5e10) * 1
1622
-in:
1623
- value:
1624
- value_type: ITEM_VALUE_TYPE_FLOAT
1625
- time: 2017-10-29 03:15:00 +03:00
1626
- data: 1.5e10
1627
- step:
1628
- type: ZBX_PREPROC_MULTIPLIER
1629
- params: 1
1630
-out:
1631
- return: SUCCEED
1632
- value: 15000000000
1633
----
1634
-test case: float(1) * 1.5e10
1635
-in:
1636
- value:
1637
- value_type: ITEM_VALUE_TYPE_FLOAT
1638
- time: 2017-10-29 03:15:00 +03:00
1639
- data: 1
1640
- step:
1641
- type: ZBX_PREPROC_MULTIPLIER
1642
- params: 1.5e10
1643
-out:
1644
- return: SUCCEED
1645
- value: 15000000000
1646
----
1647
-test case: float(1.5e10) * 1.5e10
1648
-in:
1649
- value:
1650
- value_type: ITEM_VALUE_TYPE_FLOAT
1651
- time: 2017-10-29 03:15:00 +03:00
1652
- data: 1.5e10
1653
- step:
1654
- type: ZBX_PREPROC_MULTIPLIER
1655
- params: 1.5e10
1656
-out:
1657
- return: SUCCEED
1658
- value: 225000000000000000000
1659
----
1660
-test case: prometheus_getmetric1
1661
-in:
1662
- value:
1663
- value_type: ITEM_VALUE_TYPE_STR
1664
- time: 2017-10-29 03:15:00 +03:00
1665
- data: |
1666
- # HELP cpu_usage_system Telegraf collected metric
1667
- # TYPE cpu_usage_system gauge
1668
- cpu_usage_system{cpu="cpu-total",host="host1"} 1.1940298507220641
1669
- cpu_usage_system{cpu="cpu0",host="host1"} 1.1940298507220641
1670
- cpu_usage_system{cpu="cpu1",host="host1"} 1.1340298507220641
1671
- step:
1672
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1673
- params: "cpu_usage_system{cpu=\"cpu-total\",host=~\".*\"}\nvalue\n"
1674
-out:
1675
- return: SUCCEED
1676
- value: 1.1940298507220641
1677
----
1678
-test case: prometheus_getmetric2
1679
-in:
1680
- value:
1681
- value_type: ITEM_VALUE_TYPE_STR
1682
- time: 2017-10-29 03:15:00 +03:00
1683
- data: metric_without_timestamp_and_labels 12.47
1684
- step:
1685
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1686
- params: "metric_without_timestamp_and_labels\nvalue\n"
1687
-out:
1688
- return: SUCCEED
1689
- value: 12.47
1690
----
1691
-test case: prometheus_getmetric3
1692
-in:
1693
- value:
1694
- value_type: ITEM_VALUE_TYPE_STR
1695
- time: 2017-10-29 03:15:00 +03:00
1696
- data: |
1697
- # HELP cpu_usage_system Telegraf collected metric
1698
- # TYPE cpu_usage_system gauge
1699
- cpu_usage_system{cpu="cpu-total",host="host1"} 1.1940298507220641
1700
- cpu_usage_system{cpu="cpu0",host="host1"} 1.1940298507220641
1701
- cpu_usage_system{cpu="cpu1",host="host1"} 1.1340298507220641
1702
- step:
1703
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1704
- params: "{cpu=\"cpu0\",__name__=\"cpu_usage_system\"}\nvalue\n"
1705
-out:
1706
- return: SUCCEED
1707
- value: 1.1940298507220641
1708
----
1709
-test case: prometheus_getmetric4
1710
-in:
1711
- value:
1712
- value_type: ITEM_VALUE_TYPE_STR
1713
- time: 2017-10-29 03:15:00 +03:00
1714
- data: |
1715
- # HELP http_requests_total The total number of HTTP requests.
1716
- # TYPE http_requests_total counter
1717
- http_requests_total{method="post",code="200"} 1027 1395066363000
1718
- http_requests_total{method="post",code="400"} 3 1395066363000
1719
- step:
1720
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1721
- params: "http_requests_total{code=\"200\"}\nvalue\n"
1722
-out:
1723
- return: SUCCEED
1724
- value: 1027
1725
----
1726
-test case: prometheus_getmetric5
1727
-in:
1728
- value:
1729
- value_type: ITEM_VALUE_TYPE_STR
1730
- time: 2017-10-29 03:15:00 +03:00
1731
- data: |
1732
- # HELP cpu_usage_system Telegraf collected metric
1733
- # TYPE cpu_usage_system gauge
1734
- cpu_usage_system{cpu="cpu1",host="host1"} 1.1340298507220641
1735
- step:
1736
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1737
- params: "\nvalue\n"
1738
-out:
1739
- return: SUCCEED
1740
- value: 1.1340298507220641
1741
----
1742
-test case: prometheus_getmetric6
1743
-in:
1744
- value:
1745
- value_type: ITEM_VALUE_TYPE_STR
1746
- time: 2017-10-29 03:15:00 +03:00
1747
- data: |
1748
- # HELP cpu_usage_system Telegraf collected metric
1749
- # TYPE cpu_usage_system gauge
1750
- cpu_usage_system{cpu="cpu1",host="host1"} 1.1340298507220641
1751
- step:
1752
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1753
- params: "cpu_usage_system\nvalue\n"
1754
-out:
1755
- return: SUCCEED
1756
- value: 1.1340298507220641
1757
----
1758
-test case: prometheus_getmetric7
1759
-in:
1760
- value:
1761
- value_type: ITEM_VALUE_TYPE_STR
1762
- time: 2017-10-29 03:15:00 +03:00
1763
- data: |
1764
- # HELP cpu_usage_system Telegraf collected metric
1765
- # TYPE cpu_usage_system gauge
1766
- cpu_usage_system{cpu="cpu-total",host="host1"} 1.1940298507220641
1767
- cpu_usage_system{cpu="cpu0",host="host2"} 1.1940298507220641
1768
- cpu_usage_system{cpu="cpu1",host="host3"} 1.1340298507220641
1769
- step:
1770
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1771
- params: "cpu_usage_system{cpu=\"cpu0\"} == 1.1940298507220641\nlabel\nhost"
1772
-out:
1773
- return: SUCCEED
1774
- value: host2
1775
----
1776
-test case: prometheus_getmetric8
1777
-in:
1778
- value:
1779
- value_type: ITEM_VALUE_TYPE_STR
1780
- time: 2017-10-29 03:15:00 +03:00
1781
- data: >
1782
- AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:_0123456789{
1783
- AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz_0123456789="anything"} 123123123
1784
- step:
1785
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1786
- params: |-
1787
- AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:_0123456789 == 123123123
1788
- label
1789
- AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz_0123456789
1790
-out:
1791
- return: SUCCEED
1792
- value: anything
1793
----
1794
-test case: prometheus_getmetric9
1795
-in:
1796
- value:
1797
- value_type: ITEM_VALUE_TYPE_STR
1798
- time: 2017-10-29 03:15:00 +03:00
1799
- data: |
1800
- # HELP cpu_usage_system Telegraf collected metric
1801
- # TYPE cpu_usage_system gauge
1802
- cpu_usage_system{cpu="cpu-total",host="host1"} 1.1940298507220641
1803
- cpu_usage_system{cpu="cpu0",host="host2"} 1.1940298507220641
1804
- cpu_usage_system{cpu="cpu1",host="host3"} 1.1340298507220641
1805
- step:
1806
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1807
- params: |-
1808
- cpu_usage_system{cpu="cpu0"} == 1.1940298507220641
1809
- label
1810
- host
1811
-out:
1812
- return: SUCCEED
1813
- value: host2
1814
----
1815
-test case: prometheus_getmetric10
1816
-in:
1817
- value:
1818
- value_type: ITEM_VALUE_TYPE_STR
1819
- time: 2017-10-29 03:15:00 +03:00
1820
- data: |
1821
- # HELP cpu_usage_system Telegraf collected metric
1822
- # TYPE cpu_usage_system gauge
1823
- cpu_usage_system{cpu="cpu-total",host="host1"} 1.1940298507220641
1824
- cpu_usage_system{cpu="cpu0",host="host2"} 1.1940298507220641
1825
- cpu_usage_system{cpu="cpu1",host="host,3"} 1.1340298507220641
1826
- step:
1827
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1828
- params: |-
1829
- cpu_usage_system{cpu="cpu1"}
1830
- label
1831
- host
1832
-out:
1833
- return: SUCCEED
1834
- value: host,3
1835
----
1836
-test case: prometheus_getmetric11
1837
-in:
1838
- value:
1839
- value_type: ITEM_VALUE_TYPE_STR
1840
- time: 2017-10-29 03:15:00 +03:00
1841
- data: |
1842
- # HELP cpu_usage_system Telegraf collected metric
1843
- # TYPE cpu_usage_system gauge
1844
- cpu_usage_system{cpu="cpu-total"} 1.1940298507220641
1845
- cpu_usage_system{cpu="cpu0"} 1.1940298507220641
1846
- cpu_usage_system{cpu="cpu1"} 1.1340298507220641
1847
- step:
1848
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1849
- params: |-
1850
- cpu_usage_system{cpu="cpu-total"}
1851
- gpu
1852
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
1853
- error_handler_params: custom error
1854
-out:
1855
- return: FAIL
1856
- error: custom error
1857
----
1858
-test case: prometheus_getmetric12
1859
-in:
1860
- value:
1861
- value_type: ITEM_VALUE_TYPE_STR
1862
- time: 2017-10-29 03:15:00 +03:00
1863
- data: |
1864
- # HELP cpu_usage_system Telegraf collected metric
1865
- # TYPE cpu_usage_system gauge
1866
- cpu_usage_system{cpu="cpu-total"} 1.1940298507220641
1867
- cpu_usage_system{cpu="cpu0"} 1.1940298507220641
1868
- cpu_usage_system{cpu="cpu1"} 1.1340298507220641
1869
- step:
1870
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1871
- params: |-
1872
- cpu_usage_system{cpu="cpu"}
1873
- \value
1874
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
1875
- error_handler_params: custom error
1876
-out:
1877
- return: FAIL
1878
- error: custom error
1879
----
1880
-test case: prometheus_getmetric13
1881
-in:
1882
- value:
1883
- value_type: ITEM_VALUE_TYPE_STR
1884
- time: 2017-10-29 03:15:00 +03:00
1885
- data: |
1886
- wmi_service_state{name="dhcp",state="continue pending"} 0
1887
- wmi_service_state{name="dhcp",state="continue pending"} 1
1888
- wmi_service_state{name="dhcp",state="pause pending"} 1
1889
- wmi_service_state{name="dhcp",state="paused"} 1
1890
- wmi_service_state{name="dhcp",state="running"} 1
1891
- wmi_service_state{name="dhcp",state="start pending"} 1
1892
- wmi_service_state{name="dhcp",state="stop pending"} 1
1893
- wmi_service_state{name="dhcp",state="stopped"} 1
1894
- wmi_service_state{name="dhcp",state="unknown"} 1
1895
- wmi_service_state{name="dhcp",state="continue pending"} 1
1896
- wmi_service_state{name="dhcp",state="pause pending"} 1
1897
- wmi_service_state{name="dhcp",state="paused"} 1
1898
- wmi_service_state{name="dhcp",state="running"} 1
1899
- wmi_service_state{name="dhcp",state="start pending"} 1
1900
- wmi_service_state{name="dhcp",state="stop pending"} 1
1901
- wmi_service_state{name="dhcp",state="stopped"} 1
1902
- wmi_service_state{name="dhcp",state="unknown"} 1
1903
- wmi_service_state{name="dhcp",state="unknown"} 0
1904
- step:
1905
- type: ZBX_PREPROC_PROMETHEUS_PATTERN
1906
- params: |-
1907
- wmi_service_state == 1
1908
- \value
1909
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
1910
- error_handler_params: custom error
1911
-out:
1912
- return: FAIL
1913
- error: custom error
1914
----
1915
-test case: prometheus_to_json1
1916
-in:
1917
- value:
1918
- value_type: ITEM_VALUE_TYPE_STR
1919
- time: 2017-10-29 03:15:00 +03:00
1920
- data: |
1921
- # HELP cpu_usage_system Telegraf collected metric
1922
- # TYPE cpu_usage_system gauge
1923
- cpu_usage_system{cpu="cpu-total"} 1.1940298507220641
1924
- cpu_usage_system{cpu="cpu0"} 1.1940298507220641
1925
- cpu_usage_system{cpu="cpu1"} 1.1340298507220641
1926
- step:
1927
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
1928
- params: cpu_usage_system
1929
-out:
1930
- return: SUCCEED
1931
- value: '[{"name":"cpu_usage_system","value":"1.1940298507220641","line_raw":"cpu_usage_system{cpu=\"cpu-total\"} 1.1940298507220641","labels":{"cpu":"cpu-total"},"type":"gauge","help":"Telegraf collected metric"},{"name":"cpu_usage_system","value":"1.1940298507220641","line_raw":"cpu_usage_system{cpu=\"cpu0\"} 1.1940298507220641","labels":{"cpu":"cpu0"},"type":"gauge","help":"Telegraf collected metric"},{"name":"cpu_usage_system","value":"1.1340298507220641","line_raw":"cpu_usage_system{cpu=\"cpu1\"} 1.1340298507220641","labels":{"cpu":"cpu1"},"type":"gauge","help":"Telegraf collected metric"}]'
1932
----
1933
-test case: prometheus_to_json2
1934
-in:
1935
- value:
1936
- value_type: ITEM_VALUE_TYPE_STR
1937
- time: 2017-10-29 03:15:00 +03:00
1938
- data: |
1939
- # HELP wmi_os_timezone OperatingSystem.LocalDateTime
1940
- # TYPE wmi_os_timezone gauge
1941
- wmi_os_timezone{timezone="MSK"} 1
1942
- step:
1943
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
1944
- params: wmi_os_timezone
1945
-out:
1946
- return: SUCCEED
1947
- value: '[{"name":"wmi_os_timezone","value":"1","line_raw":"wmi_os_timezone{timezone=\"MSK\"} 1","labels":{"timezone":"MSK"},"type":"gauge","help":"OperatingSystem.LocalDateTime"}]'
1948
----
1949
-test case: prometheus_to_json3
1950
-in:
1951
- value:
1952
- value_type: ITEM_VALUE_TYPE_STR
1953
- time: 2017-10-29 03:15:00 +03:00
1954
- data: |
1955
- random_date{year="2019",month="february",day="02/12/2019"} 1
1956
- random_date{year="2019",month="march",day="03/07/2019"} 2
1957
- step:
1958
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
1959
- params: random_date{year="2019",day=~"^([0-2][0-9]|(3)[0-1])(\\/)(((0)[0-9])|((1)[0-2]))(\\/)\\d{4}$"}
1960
-out:
1961
- return: SUCCEED
1962
- value: '[{"name":"random_date","value":"1","line_raw":"random_date{year=\"2019\",month=\"february\",day=\"02/12/2019\"} 1","labels":{"year":"2019","month":"february","day":"02/12/2019"},"type":"untyped"},{"name":"random_date","value":"2","line_raw":"random_date{year=\"2019\",month=\"march\",day=\"03/07/2019\"} 2","labels":{"year":"2019","month":"march","day":"03/07/2019"},"type":"untyped"}]'
1963
----
1964
-test case: prometheus_to_json4
1965
-in:
1966
- value:
1967
- value_type: ITEM_VALUE_TYPE_STR
1968
- time: 2017-10-29 03:15:00 +03:00
1969
- data: metric_without_timestamp_and_labels 12.47
1970
- step:
1971
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
1972
- params: metric_without_timestamp_and_labels
1973
-out:
1974
- return: SUCCEED
1975
- value: '[{"name":"metric_without_timestamp_and_labels","value":"12.47","line_raw":"metric_without_timestamp_and_labels 12.47","type":"untyped"}]'
1976
----
1977
-test case: prometheus_to_json5
1978
-in:
1979
- value:
1980
- value_type: ITEM_VALUE_TYPE_STR
1981
- time: 2017-10-29 03:15:00 +03:00
1982
- data: wmi_os_timezone{timezone} 1
1983
- step:
1984
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
1985
- params: wmi_os_timezone
1986
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
1987
- error_handler_params: custom error
1988
-out:
1989
- return: FAIL
1990
- error: custom error
1991
----
1992
-test case: csv_to_json1
1993
-in:
1994
- value:
1995
- value_type: ITEM_VALUE_TYPE_STR
1996
- time: 2017-10-29 03:15:00 +03:00
1997
- data: |
1998
- sep=.
1999
- A.B.C
2000
- 1.2.3
2001
- step:
2002
- type: ZBX_PREPROC_CSV_TO_JSON
2003
- params: "\n\n1"
2004
-out:
2005
- return: SUCCEED
2006
- value: '[{"A":"1","B":"2","C":"3"},{"A":"","B":"","C":""}]'
2007
----
2008
-test case: csv_to_json2
2009
-in:
2010
- value:
2011
- value_type: ITEM_VALUE_TYPE_STR
2012
- time: 2017-10-29 03:15:00 +03:00
2013
- data: |
2014
- sep=.
2015
- A.`B.``B`.C
2016
- 1.`2`.`3.`
2017
- step:
2018
- type: ZBX_PREPROC_CSV_TO_JSON
2019
- params: "\n`\n1"
2020
-out:
2021
- return: SUCCEED
2022
- value: '[{"A":"1","B.`B":"2","C":"3."},{"A":"","B.`B":"","C":""}]'
2023
----
2024
-test case: csv_to_json3
2025
-in:
2026
- value:
2027
- value_type: ITEM_VALUE_TYPE_STR
2028
- time: 2017-10-29 03:15:00 +03:00
2029
- data: 1,2,3
2030
- step:
2031
- type: ZBX_PREPROC_CSV_TO_JSON
2032
- params: "\n\n0"
2033
-out:
2034
- return: SUCCEED
2035
- value: '[{"1":"1","2":"2","3":"3"}]'
2036
----
2037
-test case: csv_to_json4
2038
-in:
2039
- value:
2040
- value_type: ITEM_VALUE_TYPE_STR
2041
- time: 2017-10-29 03:15:00 +03:00
2042
- data: |
2043
- A,B,C
2044
- 1,2,3,4
2045
- step:
2046
- type: ZBX_PREPROC_PROMETHEUS_TO_JSON
2047
- params: "\n\n1"
2048
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
2049
- error_handler_params: custom error
2050
-out:
2051
- return: FAIL
2052
- error: custom error
2053
----
2054
-test case: replace a to b
2055
-in:
2056
- value:
2057
- value_type: ITEM_VALUE_TYPE_STR
2058
- time: 2017-10-29 03:15:00 +03:00
2059
- data: a
2060
- step:
2061
- type: ZBX_PREPROC_STR_REPLACE
2062
- params: "a\nb"
2063
-out:
2064
- return: SUCCEED
2065
- value: b
2066
----
2067
-test case: replace a to bbb
2068
-in:
2069
- value:
2070
- value_type: ITEM_VALUE_TYPE_STR
2071
- time: 2017-10-29 03:15:00 +03:00
2072
- data: a1a2a3a
2073
- step:
2074
- type: ZBX_PREPROC_STR_REPLACE
2075
- params: "a\nbbb"
2076
-out:
2077
- return: SUCCEED
2078
- value: bbb1bbb2bbb3bbb
2079
----
2080
-test case: replace a to a
2081
-in:
2082
- value:
2083
- value_type: ITEM_VALUE_TYPE_STR
2084
- time: 2017-10-29 03:15:00 +03:00
2085
- data: a
2086
- step:
2087
- type: ZBX_PREPROC_STR_REPLACE
2088
- params: "a\na"
2089
-out:
2090
- return: SUCCEED
2091
- value: a
2092
----
2093
-test case: replace a to nothing
2094
-in:
2095
- value:
2096
- value_type: ITEM_VALUE_TYPE_STR
2097
- time: 2017-10-29 03:15:00 +03:00
2098
- data: a
2099
- step:
2100
- type: ZBX_PREPROC_STR_REPLACE
2101
- params: "a\n"
2102
-out:
2103
- return: SUCCEED
2104
- value: ""
2105
----
2106
-test case: replace a to nothing in between
2107
-in:
2108
- value:
2109
- value_type: ITEM_VALUE_TYPE_STR
2110
- time: 2017-10-29 03:15:00 +03:00
2111
- data: abac
2112
- step:
2113
- type: ZBX_PREPROC_STR_REPLACE
2114
- params: "a\n"
2115
-out:
2116
- return: SUCCEED
2117
- value: "bc"
2118
----
2119
-test case: replace non printable characters
2120
-in:
2121
- value:
2122
- value_type: ITEM_VALUE_TYPE_STR
2123
- time: 2017-10-29 03:15:00 +03:00
2124
- data: "\n\r "
2125
- step:
2126
- type: ZBX_PREPROC_STR_REPLACE
2127
- params: "\\n\\r\\t\\s\nOK"
2128
-out:
2129
- return: SUCCEED
2130
- value: "OK"
2131
----
2132
-test case: replace non printable characters mixed
2133
-in:
2134
- value:
2135
- value_type: ITEM_VALUE_TYPE_STR
2136
- time: 2017-10-29 03:15:00 +03:00
2137
- data: "\n1\r2 3 4"
2138
- step:
2139
- type: ZBX_PREPROC_STR_REPLACE
2140
- params: "\\n1\\r2\\t3\\s4\nOK"
2141
-out:
2142
- return: SUCCEED
2143
- value: "OK"
2144
----
2145
-test case: replace non printable characters 2
2146
-in:
2147
- value:
2148
- value_type: ITEM_VALUE_TYPE_STR
2149
- time: 2017-10-29 03:15:00 +03:00
2150
- data: "\n\r "
2151
- step:
2152
- type: ZBX_PREPROC_STR_REPLACE
2153
- params: "\\n\\r \nOK"
2154
-out:
2155
- return: SUCCEED
2156
- value: "OK"
2157
----
2158
-test case: replace non printable characters in both search and replace
2159
-in:
2160
- value:
2161
- value_type: ITEM_VALUE_TYPE_STR
2162
- time: 2017-10-29 03:15:00 +03:00
2163
- data: "\n\r "
2164
- step:
2165
- type: ZBX_PREPROC_STR_REPLACE
2166
- params: "\\n\\r\\t\\s\n\\n\\r\\tOK\\s"
2167
-out:
2168
- return: SUCCEED
2169
- value: "\n\r\tOK "
2170
----
2171
-test case: replace \
2172
-in:
2173
- value:
2174
- value_type: ITEM_VALUE_TYPE_STR
2175
- time: 2017-10-29 03:15:00 +03:00
2176
- data: "\\"
2177
- step:
2178
- type: ZBX_PREPROC_STR_REPLACE
2179
- params: "\\\nOK"
2180
-out:
2181
- return: SUCCEED
2182
- value: "OK"
2183
----
2184
-test case: replace 2 characters "\t" to OK
2185
-in:
2186
- value:
2187
- value_type: ITEM_VALUE_TYPE_STR
2188
- time: 2017-10-29 03:15:00 +03:00
2189
- data: "\\t"
2190
- step:
2191
- type: ZBX_PREPROC_STR_REPLACE
2192
- params: "\\\\t\nOK"
2193
-out:
2194
- return: SUCCEED
2195
- value: "OK"
2196
----
2197
-test case: Missing second parameter
2198
-in:
2199
- value:
2200
- value_type: ITEM_VALUE_TYPE_STR
2201
- time: 2017-10-29 03:15:00 +03:00
2202
- data: "a"
2203
- step:
2204
- type: ZBX_PREPROC_STR_REPLACE
2205
- params: "OK"
2206
-out:
2207
- return: FAIL
2208
- value: "a"
2209
----
2210
-test case: Missing first parameter
2211
-in:
2212
- value:
2213
- value_type: ITEM_VALUE_TYPE_STR
2214
- time: 2017-10-29 03:15:00 +03:00
2215
- data: "a"
2216
- step:
2217
- type: ZBX_PREPROC_STR_REPLACE
2218
- params: "\nOK"
2219
-out:
2220
- return: FAIL
2221
- value: "a"
2222
----
2223
-test case: Single slash in search
2224
-in:
2225
- value:
2226
- value_type: ITEM_VALUE_TYPE_STR
2227
- time: 2017-10-29 03:15:00 +03:00
2228
- data: "\\"
2229
- step:
2230
- type: ZBX_PREPROC_STR_REPLACE
2231
- params: "\\\nOK"
2232
-out:
2233
- return: SUCCEED
2234
- value: "OK"
2235
----
2236
-test case: Single slash in replace
2237
-in:
2238
- value:
2239
- value_type: ITEM_VALUE_TYPE_STR
2240
- time: 2017-10-29 03:15:00 +03:00
2241
- data: "OK"
2242
- step:
2243
- type: ZBX_PREPROC_STR_REPLACE
2244
- params: "OK\n\\"
2245
-out:
2246
- return: SUCCEED
2247
- value: "\\"
2248
----
2249
-test case: No match
2250
-in:
2251
- value:
2252
- value_type: ITEM_VALUE_TYPE_STR
2253
- time: 2017-10-29 03:15:00 +03:00
2254
- data: "O K"
2255
- step:
2256
- type: ZBX_PREPROC_STR_REPLACE
2257
- params: "OK\ntest"
2258
-out:
2259
- return: SUCCEED
2260
- value: "O K"
2261
----
2262
-test case: SNMP walk to value - bad data
2263
-in:
2264
- value:
2265
- value_type: ITEM_VALUE_TYPE_STR
2266
- time: 2017-10-29 03:15:00 +03:00
2267
- data: |
2268
- aaa
2269
- bbb
2270
- step:
2271
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2272
- params: |-
2273
- .1.3.6.1.1
2274
- 0
2275
-out:
2276
- return: FAIL
2277
----
2278
-test case: SNMP walk to value - bad param
2279
-in:
2280
- value:
2281
- value_type: ITEM_VALUE_TYPE_STR
2282
- time: 2017-10-29 03:15:00 +03:00
2283
- data: |
2284
- .1.3.6.1.1 = Hex-STRING: 71 71
2285
- step:
2286
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2287
-out:
2288
- return: FAIL
2289
----
2290
-test case: SNMP walk to value - string
2291
-in:
2292
- value:
2293
- value_type: ITEM_VALUE_TYPE_STR
2294
- time: 2017-10-29 03:15:00 +03:00
2295
- data: |
2296
- .1.3.6.1.1 = STRING: "AAA"
2297
- step:
2298
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2299
- params: |-
2300
- .1.3.6.1.1
2301
- 0
2302
-out:
2303
- return: SUCCEED
2304
- value: 'AAA'
2305
----
2306
-test case: SNMP walk to value - NULL
2307
-in:
2308
- value:
2309
- value_type: ITEM_VALUE_TYPE_STR
2310
- time: 2017-10-29 03:15:00 +03:00
2311
- data: |
2312
- .1.3.6.1.1 = NULL
2313
- step:
2314
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2315
- params: |-
2316
- .1.3.6.1.1
2317
- 0
2318
-out:
2319
- return: SUCCEED
2320
- value: 'NULL'
2321
----
2322
-test case: SNMP walk to value - Arbitrary number
2323
-in:
2324
- value:
2325
- value_type: ITEM_VALUE_TYPE_STR
2326
- time: 2017-10-29 03:15:00 +03:00
2327
- data: |
2328
- .1.3.6.1.1 = 123
2329
- step:
2330
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2331
- params: |-
2332
- .1.3.6.1.1
2333
- 0
2334
-out:
2335
- return: SUCCEED
2336
- value: 123
2337
----
2338
-test case: SNMP walk to value - INTEGER
2339
-in:
2340
- value:
2341
- value_type: ITEM_VALUE_TYPE_STR
2342
- time: 2017-10-29 03:15:00 +03:00
2343
- data: |
2344
- .1.3.6.1.1 = INTEGER: 123
2345
- step:
2346
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2347
- params: |-
2348
- .1.3.6.1.1
2349
- 0
2350
-out:
2351
- return: SUCCEED
2352
- value: 123
2353
----
2354
-test case: SNMP walk to value - IpAddress
2355
-in:
2356
- value:
2357
- value_type: ITEM_VALUE_TYPE_STR
2358
- time: 2017-10-29 03:15:00 +03:00
2359
- data: |
2360
- .1.3.6.1.1 = IpAddress: 127.0.0.1
2361
- step:
2362
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2363
- params: |-
2364
- .1.3.6.1.1
2365
- 0
2366
-out:
2367
- return: SUCCEED
2368
- value: '127.0.0.1'
2369
----
2370
-test case: SNMP walk to value - Hex-STRING
2371
-in:
2372
- value:
2373
- value_type: ITEM_VALUE_TYPE_STR
2374
- time: 2017-10-29 03:15:00 +03:00
2375
- data: |
2376
- .1.3.6.1.1 = Hex-STRING: 71 71 AA
2377
- step:
2378
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2379
- params: |-
2380
- .1.3.6.1.1
2381
- 0
2382
-out:
2383
- return: SUCCEED
2384
- value: '71 71 AA'
2385
----
2386
-test case: SNMP walk to value - Hex-STRING (multiline)
2387
-in:
2388
- value:
2389
- value_type: ITEM_VALUE_TYPE_STR
2390
- time: 2017-10-29 03:15:00 +03:00
2391
- data: |
2392
- .1.3.6.1.1 = Hex-STRING: 71 71
2393
- AA BB CC
2394
- DD
2395
- step:
2396
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2397
- params: |-
2398
- .1.3.6.1.1
2399
- 0
2400
-out:
2401
- return: SUCCEED
2402
- value: '71 71 AA BB CC DD'
2403
----
2404
-test case: SNMP walk to value - Hex-STRING (multiline) - space on the last line
2405
-in:
2406
- value:
2407
- value_type: ITEM_VALUE_TYPE_STR
2408
- time: 2017-10-29 03:15:00 +03:00
2409
- data: |
2410
- .1.3.6.1.1 = Hex-STRING: 71 71
2411
- AA BB CC
2412
- DD
2413
- step:
2414
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2415
- params: |-
2416
- .1.3.6.1.1
2417
- 0
2418
-out:
2419
- return: SUCCEED
2420
- value: '71 71 AA BB CC DD'
2421
----
2422
-test case: SNMP walk to value - Hex-STRING (multiline) - with succeeding string
2423
-in:
2424
- value:
2425
- value_type: ITEM_VALUE_TYPE_STR
2426
- time: 2017-10-29 03:15:00 +03:00
2427
- data: |
2428
- .1.3.6.1.1 = Hex-STRING: 71 71
2429
- AA BB CC
2430
- DD
2431
- .1.3.6.1.2 = Hex-STRING: 74 65 73 74 20 D1 91
2432
- step:
2433
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2434
- params: |-
2435
- .1.3.6.1.1
2436
- 0
2437
-out:
2438
- return: SUCCEED
2439
- value: '71 71 AA BB CC DD'
2440
----
2441
-test case: SNMP walk to value - Hex-STRING - to UTF8
2442
-in:
2443
- value:
2444
- value_type: ITEM_VALUE_TYPE_STR
2445
- time: 2017-10-29 03:15:00 +03:00
2446
- data: |
2447
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1 91
2448
- step:
2449
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2450
- params: |-
2451
- .1.3.6.1.1
2452
- 1
2453
-out:
2454
- return: SUCCEED
2455
- value: 'test ё'
2456
----
2457
-test case: SNMP walk to value - Hex-STRING - to UTF8 (null terminated)
2458
-in:
2459
- value:
2460
- value_type: ITEM_VALUE_TYPE_STR
2461
- time: 2017-10-29 03:15:00 +03:00
2462
- data: |
2463
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1 91 00
2464
- step:
2465
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2466
- params: |-
2467
- .1.3.6.1.1
2468
- 1
2469
-out:
2470
- return: SUCCEED
2471
- value: 'test ё'
2472
----
2473
-test case: SNMP walk to value - Hex-STRING - to UTF8 - invalid hex string
2474
-in:
2475
- value:
2476
- value_type: ITEM_VALUE_TYPE_STR
2477
- time: 2017-10-29 03:15:00 +03:00
2478
- data: |
2479
- .1.3.6.1.1 = Hex-STRING: 74 65 X3 74 20 D1 91
2480
- step:
2481
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2482
- params: |-
2483
- .1.3.6.1.1
2484
- 1
2485
-out:
2486
- return: FAIL
2487
----
2488
-test case: SNMP walk to value - Hex-STRING - to UTF8 - invalid hex string (2)
2489
-in:
2490
- value:
2491
- value_type: ITEM_VALUE_TYPE_STR
2492
- time: 2017-10-29 03:15:00 +03:00
2493
- data: |
2494
- .1.3.6.1.1 = Hex-STRING: 74 65 74 20 D1 9
2495
- step:
2496
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2497
- params: |-
2498
- .1.3.6.1.1
2499
- 1
2500
-out:
2501
- return: FAIL
2502
----
2503
-test case: SNMP walk to value - Hex-STRING - to UTF8 - invalid sequence
2504
-in:
2505
- value:
2506
- value_type: ITEM_VALUE_TYPE_STR
2507
- time: 2017-10-29 03:15:00 +03:00
2508
- data: |
2509
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1 91 FF
2510
- step:
2511
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2512
- params: |-
2513
- .1.3.6.1.1
2514
- 1
2515
-out:
2516
- return: SUCCEED
2517
- value: 'test ё?'
2518
----
2519
-test case: SNMP walk to value - Hex-STRING - to MAC
2520
-in:
2521
- value:
2522
- value_type: ITEM_VALUE_TYPE_STR
2523
- time: 2017-10-29 03:15:00 +03:00
2524
- data: |
2525
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1
2526
- step:
2527
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2528
- params: |-
2529
- .1.3.6.1.1
2530
- 2
2531
-out:
2532
- return: SUCCEED
2533
- value: '74:65:73:74:20:D1'
2534
----
2535
-test case: SNMP walk to value - Hex-STRING - to MAC - invalid hex string
2536
-in:
2537
- value:
2538
- value_type: ITEM_VALUE_TYPE_STR
2539
- time: 2017-10-29 03:15:00 +03:00
2540
- data: |
2541
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 XX
2542
- step:
2543
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2544
- params: |-
2545
- .1.3.6.1.1
2546
- 2
2547
-out:
2548
- return: FAIL
2549
----
2550
-test case: SNMP walk to value - BITS to integer - 1
2551
-in:
2552
- value:
2553
- value_type: ITEM_VALUE_TYPE_STR
2554
- time: 2017-10-29 03:15:00 +03:00
2555
- data: |
2556
- .1.3.6.1.1 = BITS: FE EE 15 15
2557
- step:
2558
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2559
- params: |-
2560
- .1.3.6.1.1
2561
- 3
2562
-out:
2563
- return: SUCCEED
2564
- value: '353758974'
2565
----
2566
-test case: SNMP walk to value - BITS to integer - 2
2567
-in:
2568
- value:
2569
- value_type: ITEM_VALUE_TYPE_STR
2570
- time: 2017-10-29 03:15:00 +03:00
2571
- data: |
2572
- .1.3.6.1.1 = BITS: 01
2573
- step:
2574
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2575
- params: |-
2576
- .1.3.6.1.1
2577
- 3
2578
-out:
2579
- return: SUCCEED
2580
- value: '1'
2581
----
2582
-test case: SNMP walk to value - BITS to integer - 3
2583
-in:
2584
- value:
2585
- value_type: ITEM_VALUE_TYPE_STR
2586
- time: 2017-10-29 03:15:00 +03:00
2587
- data: |
2588
- .1.3.6.1.1 = BITS: 00 00 00
2589
- step:
2590
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2591
- params: |-
2592
- .1.3.6.1.1
2593
- 3
2594
-out:
2595
- return: SUCCEED
2596
- value: '0'
2597
----
2598
-test case: SNMP walk to value - BITS to integer - 4
2599
-in:
2600
- value:
2601
- value_type: ITEM_VALUE_TYPE_STR
2602
- time: 2017-10-29 03:15:00 +03:00
2603
- data: |
2604
- .1.3.6.1.1 = BITS: 01 02 03 04 05 06 07 08 09 10
2605
- step:
2606
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2607
- params: |-
2608
- .1.3.6.1.1
2609
- 3
2610
-out:
2611
- return: SUCCEED
2612
- value: '578437695752307201'
2613
----
2614
-test case: SNMP walk to value - BITS to integer - 5
2615
-in:
2616
- value:
2617
- value_type: ITEM_VALUE_TYPE_STR
2618
- time: 2017-10-29 03:15:00 +03:00
2619
- data: |
2620
- .1.3.6.1.1 = BITS: 01 02 03 04 05 06 07 08
2621
- step:
2622
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2623
- params: |-
2624
- .1.3.6.1.1
2625
- 3
2626
-out:
2627
- return: SUCCEED
2628
- value: '578437695752307201'
2629
----
2630
-test case: SNMP walk to value - BITS to integer - 6
2631
-in:
2632
- value:
2633
- value_type: ITEM_VALUE_TYPE_STR
2634
- time: 2017-10-29 03:15:00 +03:00
2635
- data: |
2636
- .1.3.6.1.1 = BITS: 01 02 03 04 05 06 07 0
2637
- step:
2638
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2639
- params: |-
2640
- .1.3.6.1.1
2641
- 3
2642
-out:
2643
- return: FAIL
2644
----
2645
-test case: SNMP walk to value - BITS to integer - 7
2646
-in:
2647
- value:
2648
- value_type: ITEM_VALUE_TYPE_STR
2649
- time: 2017-10-29 03:15:00 +03:00
2650
- data: |
2651
- .1.3.6.1.1 = BITS: QW 11
2652
- step:
2653
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2654
- params: |-
2655
- .1.3.6.1.1
2656
- 3
2657
-out:
2658
- return: FAIL
2659
----
2660
-test case: SNMP walk to value - Opaque wrapped type
2661
-in:
2662
- value:
2663
- value_type: ITEM_VALUE_TYPE_STR
2664
- time: 2017-10-29 03:15:00 +03:00
2665
- data: |
2666
- .1.3.6.1.1 = Opaque: Float: 0.460000
2667
- step:
2668
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2669
- params: |-
2670
- .1.3.6.1.1
2671
- 0
2672
-out:
2673
- return: SUCCEED
2674
- value: 0.460000
2675
----
2676
-test case: SNMP walk to value - Counter32
2677
-in:
2678
- value:
2679
- value_type: ITEM_VALUE_TYPE_STR
2680
- time: 2017-10-29 03:15:00 +03:00
2681
- data: |
2682
- .1.3.6.1.1 = Counter32: 12345678
2683
- step:
2684
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2685
- params: |-
2686
- .1.3.6.1.1
2687
- 0
2688
-out:
2689
- return: SUCCEED
2690
- value: 12345678
2691
----
2692
-test case: SNMP walk to value - STRING with newline
2693
-in:
2694
- value:
2695
- value_type: ITEM_VALUE_TYPE_STR
2696
- time: 2017-10-29 03:15:00 +03:00
2697
- data: |
2698
- .1.3.6.1.1 = STRING: "line1
2699
- line2"
2700
- .1.3.6.2.1 = STRING: "line2
2701
- line3"
2702
- step:
2703
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2704
- params: |-
2705
- .1.3.6.1.1
2706
- 0
2707
-out:
2708
- return: SUCCEED
2709
- value: |-
2710
- line1
2711
- line2
2712
----
2713
-test case: SNMP walk to value - unquoted STRING with newline
2714
-in:
2715
- value:
2716
- value_type: ITEM_VALUE_TYPE_STR
2717
- time: 2017-10-29 03:15:00 +03:00
2718
- data: |
2719
- .1.3.6.1.1 = STRING: line1
2720
- line2
2721
- .1.3.6.2.1 = STRING: line2
2722
- line3
2723
- step:
2724
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2725
- params: |-
2726
- .1.3.6.1.1
2727
- 0
2728
-out:
2729
- return: SUCCEED
2730
- value: |-
2731
- line1
2732
- line2
2733
----
2734
-test case: SNMP walk to value - unquoted STRING with newline and empty string
2735
-in:
2736
- value:
2737
- value_type: ITEM_VALUE_TYPE_STR
2738
- time: 2017-10-29 03:15:00 +03:00
2739
- data: |
2740
- .1.3.6.1.1 = STRING: line1
2741
-
2742
- .1.3.6.2.1 = STRING: line2
2743
- line3
2744
- step:
2745
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2746
- params: |-
2747
- .1.3.6.1.1
2748
- 0
2749
-out:
2750
- return: SUCCEED
2751
- value: "line1\n"
2752
----
2753
-test case: SNMP walk to value - quoted STRING with quoted substring
2754
-in:
2755
- value:
2756
- value_type: ITEM_VALUE_TYPE_STR
2757
- time: 2017-10-29 03:15:00 +03:00
2758
- data: |
2759
- .1.3.6.1.1 = STRING: "foo
2760
- \"bar\"
2761
- baz"
2762
- .1.3.6.2.1 = STRING: line2
2763
- line3
2764
- step:
2765
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2766
- params: |-
2767
- .1.3.6.1.1
2768
- 0
2769
-out:
2770
- return: SUCCEED
2771
- value: |-
2772
- foo
2773
- "bar"
2774
- baz
2775
----
2776
-test case: SNMP walk to value - quoted STRING with broken quoting
2777
-in:
2778
- value:
2779
- value_type: ITEM_VALUE_TYPE_STR
2780
- time: 2017-10-29 03:15:00 +03:00
2781
- data: |
2782
- .1.3.6.1.1 = STRING: "foo
2783
- \
2784
- .1.3.6.2.1 = STRING: line2
2785
- line3
2786
- step:
2787
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2788
- params: |-
2789
- .1.3.6.1.1
2790
- 0
2791
-out:
2792
- return: FAIL
2793
----
2794
-test case: SNMP walk to value - backslashes in quoted string
2795
-in:
2796
- value:
2797
- value_type: ITEM_VALUE_TYPE_STR
2798
- time: 2017-10-29 03:15:00 +03:00
2799
- data: |
2800
- .1.3.6.1.1 = STRING: "foo\\bar\\baz"
2801
- step:
2802
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2803
- params: |-
2804
- .1.3.6.1.1
2805
- 0
2806
-out:
2807
- return: SUCCEED
2808
- value: foo\bar\baz
2809
----
2810
-test case: SNMP walk to value - backslashes in unquoted string
2811
-in:
2812
- value:
2813
- value_type: ITEM_VALUE_TYPE_STR
2814
- time: 2017-10-29 03:15:00 +03:00
2815
- data: |
2816
- .1.3.6.1.1 = STRING: foo\bar\baz
2817
- step:
2818
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2819
- params: |-
2820
- .1.3.6.1.1
2821
- 0
2822
-out:
2823
- return: SUCCEED
2824
- value: foo\bar\baz
2825
----
2826
-test case: SNMP walk to value - Empty type
2827
-in:
2828
- value:
2829
- value_type: ITEM_VALUE_TYPE_STR
2830
- time: 2017-10-29 03:15:00 +03:00
2831
- data: |
2832
- .1.3.6.1.1 = ""
2833
- step:
2834
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2835
- params: |-
2836
- .1.3.6.1.1
2837
- 0
2838
-out:
2839
- return: SUCCEED
2840
- value: ''
2841
----
2842
-test case: SNMP walk to value - OID without prepending dot
2843
-in:
2844
- value:
2845
- value_type: ITEM_VALUE_TYPE_STR
2846
- time: 2017-10-29 03:15:00 +03:00
2847
- data: |
2848
- .1.3.6.1.1 = 123
2849
- step:
2850
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2851
- params: |-
2852
- 1.3.6.1.1
2853
- 0
2854
-out:
2855
- return: SUCCEED
2856
- value: 123
2857
----
2858
-test case: SNMP walk to value - MIB translation 1
2859
-in:
2860
- value:
2861
- value_type: ITEM_VALUE_TYPE_STR
2862
- time: 2017-10-29 03:15:00 +03:00
2863
- data: |
2864
- .1.3.6.1.2.1.2.2.1.2 = 20
2865
- step:
2866
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2867
- params: |-
2868
- IF-MIB::ifDescr
2869
- 1
2870
- netsnmp_required: y
2871
-out:
2872
- return: SUCCEED
2873
- value: 20
2874
----
2875
-test case: SNMP walk to value - MIB translation 2
2876
-in:
2877
- value:
2878
- value_type: ITEM_VALUE_TYPE_STR
2879
- time: 2017-10-29 03:15:00 +03:00
2880
- data: |
2881
- .1.3.6.1.2.1.1.1 = 20
2882
- step:
2883
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2884
- params: |-
2885
- SNMPv2-MIB::sysDescr
2886
- 0
2887
- netsnmp_required: y
2888
-out:
2889
- return: SUCCEED
2890
- value: 20
2891
----
2892
-test case: SNMP walk to value - bad param
2893
-in:
2894
- value:
2895
- value_type: ITEM_VALUE_TYPE_STR
2896
- time: 2017-10-29 03:15:00 +03:00
2897
- data: |
2898
- 71 71
2899
- step:
2900
- type: ZBX_PREPROC_SNMP_WALK_VALUE
2901
-out:
2902
- return: FAIL
2903
----
2904
-test case: SNMP get to value - Hex-STRING - to UTF8
2905
-in:
2906
- value:
2907
- value_type: ITEM_VALUE_TYPE_STR
2908
- time: 2017-10-29 03:15:00 +03:00
2909
- data: |
2910
- 74 65 73 74 20 D1 91
2911
- step:
2912
- type: ZBX_PREPROC_SNMP_GET_VALUE
2913
- params: '1'
2914
-out:
2915
- return: SUCCEED
2916
- value: 'test ё'
2917
----
2918
-test case: SNMP get to value - Hex-STRING - to UTF8 (null terminated)
2919
-in:
2920
- value:
2921
- value_type: ITEM_VALUE_TYPE_STR
2922
- time: 2017-10-29 03:15:00 +03:00
2923
- data: |
2924
- 74 65 73 74 20 D1 91 00
2925
- step:
2926
- type: ZBX_PREPROC_SNMP_GET_VALUE
2927
- params: '1'
2928
-out:
2929
- return: SUCCEED
2930
- value: 'test ё'
2931
----
2932
-test case: SNMP get to value - Hex-STRING - to UTF8 - invalid hex string
2933
-in:
2934
- value:
2935
- value_type: ITEM_VALUE_TYPE_STR
2936
- time: 2017-10-29 03:15:00 +03:00
2937
- data: |
2938
- 74 65 X3 74 20 D1 91
2939
- step:
2940
- type: ZBX_PREPROC_SNMP_GET_VALUE
2941
- params: '1'
2942
-out:
2943
- return: FAIL
2944
----
2945
-test case: SNMP get to value - Hex-STRING - to UTF8 - invalid hex string (2)
2946
-in:
2947
- value:
2948
- value_type: ITEM_VALUE_TYPE_STR
2949
- time: 2017-10-29 03:15:00 +03:00
2950
- data: |
2951
- 74 65 74 20 D1 9
2952
- step:
2953
- type: ZBX_PREPROC_SNMP_GET_VALUE
2954
- params: '1'
2955
-out:
2956
- return: FAIL
2957
----
2958
-test case: SNMP get to value - Hex-STRING - to UTF8 - invalid sequence
2959
-in:
2960
- value:
2961
- value_type: ITEM_VALUE_TYPE_STR
2962
- time: 2017-10-29 03:15:00 +03:00
2963
- data: |
2964
- 74 65 73 74 20 D1 91 FF
2965
- step:
2966
- type: ZBX_PREPROC_SNMP_GET_VALUE
2967
- params: '1'
2968
-out:
2969
- return: SUCCEED
2970
- value: 'test ё?'
2971
----
2972
-test case: SNMP get to value - Hex-STRING - to MAC
2973
-in:
2974
- value:
2975
- value_type: ITEM_VALUE_TYPE_STR
2976
- time: 2017-10-29 03:15:00 +03:00
2977
- data: |
2978
- 74 65 73 74 20 D1
2979
- step:
2980
- type: ZBX_PREPROC_SNMP_GET_VALUE
2981
- params: '2'
2982
-out:
2983
- return: SUCCEED
2984
- value: '74:65:73:74:20:D1'
2985
----
2986
-test case: SNMP get to value - Hex-STRING - to MAC - invalid hex string
2987
-in:
2988
- value:
2989
- value_type: ITEM_VALUE_TYPE_STR
2990
- time: 2017-10-29 03:15:00 +03:00
2991
- data: |
2992
- 74 65 73 74 XX
2993
- step:
2994
- type: ZBX_PREPROC_SNMP_GET_VALUE
2995
- params: '2'
2996
-out:
2997
- return: FAIL
2998
----
2999
-test case: SNMP get to value - BITS to integer - 1
3000
-in:
3001
- value:
3002
- value_type: ITEM_VALUE_TYPE_STR
3003
- time: 2017-10-29 03:15:00 +03:00
3004
- data: |
3005
- FE EE 15 15
3006
- step:
3007
- type: ZBX_PREPROC_SNMP_GET_VALUE
3008
- params: '3'
3009
-out:
3010
- return: SUCCEED
3011
- value: '353758974'
3012
----
3013
-test case: SNMP get to value - BITS to integer - 2
3014
-in:
3015
- value:
3016
- value_type: ITEM_VALUE_TYPE_STR
3017
- time: 2017-10-29 03:15:00 +03:00
3018
- data: |
3019
- 01
3020
- step:
3021
- type: ZBX_PREPROC_SNMP_GET_VALUE
3022
- params: '3'
3023
-out:
3024
- return: SUCCEED
3025
- value: '1'
3026
----
3027
-test case: SNMP get to value - BITS to integer - 3
3028
-in:
3029
- value:
3030
- value_type: ITEM_VALUE_TYPE_STR
3031
- time: 2017-10-29 03:15:00 +03:00
3032
- data: |
3033
- 00 00 00
3034
- step:
3035
- type: ZBX_PREPROC_SNMP_GET_VALUE
3036
- params: '3'
3037
-out:
3038
- return: SUCCEED
3039
- value: '0'
3040
----
3041
-test case: SNMP get to value - BITS to integer - 4
3042
-in:
3043
- value:
3044
- value_type: ITEM_VALUE_TYPE_STR
3045
- time: 2017-10-29 03:15:00 +03:00
3046
- data: |
3047
- 01 02 03 04 05 06 07 08 09 10
3048
- step:
3049
- type: ZBX_PREPROC_SNMP_GET_VALUE
3050
- params: '3'
3051
-out:
3052
- return: SUCCEED
3053
- value: '578437695752307201'
3054
----
3055
-test case: SNMP get to value - BITS to integer - 5
3056
-in:
3057
- value:
3058
- value_type: ITEM_VALUE_TYPE_STR
3059
- time: 2017-10-29 03:15:00 +03:00
3060
- data: |
3061
- 01 02 03 04 05 06 07 08
3062
- step:
3063
- type: ZBX_PREPROC_SNMP_GET_VALUE
3064
- params: '3'
3065
-out:
3066
- return: SUCCEED
3067
- value: '578437695752307201'
3068
----
3069
-test case: SNMP get to value - BITS to integer - 6
3070
-in:
3071
- value:
3072
- value_type: ITEM_VALUE_TYPE_STR
3073
- time: 2017-10-29 03:15:00 +03:00
3074
- data: |
3075
- 01 02 03 04 05 06 07 0
3076
- step:
3077
- type: ZBX_PREPROC_SNMP_GET_VALUE
3078
- params: '3'
3079
-out:
3080
- return: FAIL
3081
----
3082
-test case: SNMP get to value - BITS to integer - 7
3083
-in:
3084
- value:
3085
- value_type: ITEM_VALUE_TYPE_STR
3086
- time: 2017-10-29 03:15:00 +03:00
3087
- data: |
3088
- .QW 11
3089
- step:
3090
- type: ZBX_PREPROC_SNMP_GET_VALUE
3091
- params: '3'
3092
-out:
3093
- return: FAIL
3094
----
3095
-test case: SNMP walk to JSON - bad data
3096
-in:
3097
- value:
3098
- value_type: ITEM_VALUE_TYPE_STR
3099
- time: 2017-10-29 03:15:00 +03:00
3100
- data: |
3101
- bad data
3102
- step:
3103
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3104
- params: |-
3105
- AAA
3106
- .1.3.6.1
3107
- 0
3108
- BBB
3109
- .1.3.6.2
3110
- 0
3111
-out:
3112
- return: FAIL
3113
----
3114
-test case: SNMP walk to JSON - no data
3115
-in:
3116
- value:
3117
- value_type: ITEM_VALUE_TYPE_STR
3118
- time: 2017-10-29 03:15:00 +03:00
3119
- data: |
3120
- .1.3.6.1.1 = 123
3121
- step:
3122
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3123
- params: |-
3124
- BBB
3125
- .1.3.6.2
3126
- 0
3127
-out:
3128
- return: FAIL
3129
----
3130
-test case: SNMP walk to JSON - Empty snmp data
3131
-in:
3132
- value:
3133
- value_type: ITEM_VALUE_TYPE_STR
3134
- time: 2017-10-29 03:15:00 +03:00
3135
- data: |
3136
- step:
3137
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3138
- params: |-
3139
- MACRO1
3140
- .1.3.6.1
3141
- 0
3142
-out:
3143
- return: SUCCEED
3144
- value: '[]'
3145
----
3146
-test case: SNMP walk to JSON - Empty snmp data and empty params
3147
-in:
3148
- value:
3149
- value_type: ITEM_VALUE_TYPE_STR
3150
- time: 2017-10-29 03:15:00 +03:00
3151
- data: |
3152
- step:
3153
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3154
- params: |-
3155
-out:
3156
- return: SUCCEED
3157
- value: '[]'
3158
----
3159
-test case: SNMP walk to JSON - STRING type
3160
-in:
3161
- value:
3162
- value_type: ITEM_VALUE_TYPE_STR
3163
- time: 2017-10-29 03:15:00 +03:00
3164
- data: |
3165
- .1.3.6.1.1 = STRING: "xxx"
3166
- .1.3.6.1.2 = STRING: "yyy"
3167
- .1.3.6.2.1 = STRING: "aaa"
3168
- .1.3.6.2.2 = STRING: "bbb"
3169
- step:
3170
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3171
- params: |-
3172
- MACRO1
3173
- .1.3.6.1
3174
- 0
3175
- MACRO2
3176
- .1.3.6.2
3177
- 0
3178
-out:
3179
- return: SUCCEED
3180
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3181
----
3182
-test case: SNMP walk to JSON - Counter/Gauge types
3183
-in:
3184
- value:
3185
- value_type: ITEM_VALUE_TYPE_STR
3186
- time: 2017-10-29 03:15:00 +03:00
3187
- data: |
3188
- .1.3.6.1.1 = Counter32: 32
3189
- .1.3.6.1.2 = Counter64: 64
3190
- .1.3.6.2.1 = Gauge32: 32
3191
- .1.3.6.2.2 = Counter64: 64
3192
- step:
3193
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3194
- params: |-
3195
- MACRO1
3196
- .1.3.6.1
3197
- 0
3198
- MACRO2
3199
- .1.3.6.2
3200
- 0
3201
-out:
3202
- return: SUCCEED
3203
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"64","MACRO2":"64"},{"{#SNMPINDEX}":"1","MACRO1":"32","MACRO2":"32"}]'
3204
----
3205
-test case: SNMP walk to JSON - NULL
3206
-in:
3207
- value:
3208
- value_type: ITEM_VALUE_TYPE_STR
3209
- time: 2017-10-29 03:15:00 +03:00
3210
- data: |
3211
- .1.3.6.1.1 = NULL
3212
- .1.3.6.1.2 = Counter64: 64
3213
- .1.3.6.2.1 = Gauge32: 32
3214
- .1.3.6.2.2 = NULL
3215
- step:
3216
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3217
- params: |-
3218
- MACRO1
3219
- .1.3.6.1
3220
- 0
3221
- MACRO2
3222
- .1.3.6.2
3223
- 0
3224
-out:
3225
- return: SUCCEED
3226
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"64","MACRO2":null},{"{#SNMPINDEX}":"1","MACRO1":null,"MACRO2":"32"}]'
3227
----
3228
-test case: SNMP walk to JSON - Empty type
3229
-in:
3230
- value:
3231
- value_type: ITEM_VALUE_TYPE_STR
3232
- time: 2017-10-29 03:15:00 +03:00
3233
- data: |
3234
- .1.3.6.1.1 = STRING: "aaa"
3235
- .1.3.6.1.2 = Counter64: 64
3236
- .1.3.6.2.1 = ""
3237
- .1.3.6.2.2 = Counter64: 64
3238
- step:
3239
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3240
- params: |-
3241
- MACRO1
3242
- .1.3.6.1
3243
- 0
3244
- MACRO2
3245
- .1.3.6.2
3246
- 0
3247
-out:
3248
- return: SUCCEED
3249
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"64","MACRO2":"64"},{"{#SNMPINDEX}":"1","MACRO1":"aaa","MACRO2":""}]'
3250
----
3251
-test case: SNMP walk to JSON - Empty type
3252
-in:
3253
- value:
3254
- value_type: ITEM_VALUE_TYPE_STR
3255
- time: 2017-10-29 03:15:00 +03:00
3256
- data: |
3257
- .1.3.6.1.1 = 123
3258
- .1.3.6.1.2 = 456
3259
- .1.3.6.2.1 = ""
3260
- .1.3.6.2.2 = Counter64: 64
3261
- step:
3262
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3263
- params: |-
3264
- MACRO1
3265
- .1.3.6.1
3266
- 0
3267
- MACRO2
3268
- .1.3.6.2
3269
- 0
3270
-out:
3271
- return: SUCCEED
3272
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"456","MACRO2":"64"},{"{#SNMPINDEX}":"1","MACRO1":"123","MACRO2":""}]'
3273
----
3274
-test case: SNMP walk to JSON - Integer/Timetick
3275
-in:
3276
- value:
3277
- value_type: ITEM_VALUE_TYPE_STR
3278
- time: 2017-10-29 03:15:00 +03:00
3279
- data: |
3280
- .1.3.6.1.1 = 1000
3281
- .1.3.6.1.2 = 2000
3282
- .1.3.6.2.1 = 3000
3283
- .1.3.6.2.2 = 4000
3284
- step:
3285
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3286
- params: |-
3287
- MACRO1
3288
- .1.3.6.1
3289
- 0
3290
- MACRO2
3291
- .1.3.6.2
3292
- 0
3293
-out:
3294
- return: SUCCEED
3295
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"2000","MACRO2":"4000"},{"{#SNMPINDEX}":"1","MACRO1":"1000","MACRO2":"3000"}]'
3296
----
3297
-test case: SNMP walk to JSON - IpAddress
3298
-in:
3299
- value:
3300
- value_type: ITEM_VALUE_TYPE_STR
3301
- time: 2017-10-29 03:15:00 +03:00
3302
- data: |
3303
- .1.3.6.1.1 = IpAddress: 10.0.0.0
3304
- .1.3.6.1.2 = IpAddress: 172.16.0.0
3305
- .1.3.6.2.1 = IpAddress: 10.255.255.255
3306
- .1.3.6.2.2 = IpAddress: 172.31.255.255
3307
- step:
3308
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3309
- params: |-
3310
- MACRO1
3311
- .1.3.6.1
3312
- 0
3313
- MACRO2
3314
- .1.3.6.2
3315
- 0
3316
-out:
3317
- return: SUCCEED
3318
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"172.16.0.0","MACRO2":"172.31.255.255"},{"{#SNMPINDEX}":"1","MACRO1":"10.0.0.0","MACRO2":"10.255.255.255"}]'
3319
----
3320
-test case: SNMP walk to JSON - STRING type with newline
3321
-in:
3322
- value:
3323
- value_type: ITEM_VALUE_TYPE_STR
3324
- time: 2017-10-29 03:15:00 +03:00
3325
- data: |
3326
- .1.3.6.1.1 = STRING: "x
3327
- xx
3328
- "
3329
- .1.3.6.1.2 = STRING: "y
3330
- yy"
3331
- .1.3.6.2.1 = STRING: "
3332
- aaa"
3333
- .1.3.6.2.2 = STRING: "bbb
3334
- "
3335
- step:
3336
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3337
- params: |-
3338
- MACRO1
3339
- .1.3.6.1
3340
- 0
3341
- MACRO2
3342
- .1.3.6.2
3343
- 0
3344
-out:
3345
- return: SUCCEED
3346
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"y\nyy","MACRO2":"bbb\n"},{"{#SNMPINDEX}":"1","MACRO1":"x\nxx\n","MACRO2":"\naaa"}]'
3347
----
3348
-test case: SNMP walk to JSON - Opaque wrapped type
3349
-in:
3350
- value:
3351
- value_type: ITEM_VALUE_TYPE_STR
3352
- time: 2017-10-29 03:15:00 +03:00
3353
- data: |
3354
- .1.3.6.1.1 = Opaque: Float: 0.110000
3355
- .1.3.6.1.2 = Opaque: STRING: "hello1.2"
3356
- .1.3.6.2.1 = Opaque: Float: 0.210000
3357
- .1.3.6.2.2 = Opaque: Unsigned32: 220000
3358
- step:
3359
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3360
- params: |-
3361
- MACRO1
3362
- .1.3.6.1
3363
- 0
3364
- MACRO2
3365
- .1.3.6.2
3366
- 0
3367
-out:
3368
- return: SUCCEED
3369
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"hello1.2","MACRO2":"220000"},{"{#SNMPINDEX}":"1","MACRO1":"0.110000","MACRO2":"0.210000"}]'
3370
----
3371
-test case: SNMP walk to JSON - OID is wanted without prepending dot - 1
3372
-in:
3373
- value:
3374
- value_type: ITEM_VALUE_TYPE_STR
3375
- time: 2017-10-29 03:15:00 +03:00
3376
- data: |
3377
- .1.3.6.1.1 = 1000
3378
- .1.3.6.1.2 = 2000
3379
- .1.3.6.2.1 = 3000
3380
- .1.3.6.2.2 = 4000
3381
- step:
3382
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3383
- params: |-
3384
- MACRO1
3385
- 1.3.6.1
3386
- 0
3387
- MACRO2
3388
- 1.3.6.2
3389
- 0
3390
-out:
3391
- return: SUCCEED
3392
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"2000","MACRO2":"4000"},{"{#SNMPINDEX}":"1","MACRO1":"1000","MACRO2":"3000"}]'
3393
----
3394
-test case: SNMP walk to JSON - OID is wanted without prepending dot - 2
3395
-in:
3396
- value:
3397
- value_type: ITEM_VALUE_TYPE_STR
3398
- time: 2017-10-29 03:15:00 +03:00
3399
- data: |
3400
- .1.3.6.1.1 = 1000
3401
- .1.3.6.1.2 = 2000
3402
- .1.3.6.2.1 = 3000
3403
- .1.3.6.2.2 = 4000
3404
- step:
3405
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3406
- params: |-
3407
- MACRO1
3408
- 1.3.6.1
3409
- 0
3410
- MACRO2
3411
- .1.3.6.2
3412
- 0
3413
-out:
3414
- return: SUCCEED
3415
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"2000","MACRO2":"4000"},{"{#SNMPINDEX}":"1","MACRO1":"1000","MACRO2":"3000"}]'
3416
----
3417
-test case: SNMP walk to JSON - MIB translation 1
3418
-in:
3419
- value:
3420
- value_type: ITEM_VALUE_TYPE_STR
3421
- time: 2017-10-29 03:15:00 +03:00
3422
- data: |
3423
- .1.3.6.1.2.1.2.2.1.2.1 = 20
3424
- .1.3.6.1.2.1.2.2.1.2.2 = 30
3425
- step:
3426
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3427
- params: |-
3428
- IFDESCR
3429
- IF-MIB::ifDescr
3430
- 0
3431
- netsnmp_required: y
3432
-out:
3433
- return: SUCCEED
3434
- value: '[{"{#SNMPINDEX}":"2","IFDESCR":"30"},{"{#SNMPINDEX}":"1","IFDESCR":"20"}]'
3435
----
3436
-test case: SNMP walk to JSON - No more variables 1
3437
-in:
3438
- value:
3439
- value_type: ITEM_VALUE_TYPE_STR
3440
- time: 2017-10-29 03:15:00 +03:00
3441
- data: |
3442
- .1.3.6.1.1 = 123
3443
- .1.3.6.1.2 = 456
3444
- .1.3.6.2.1 = STRING: "TEST"
3445
- .1.3.6.2.2 = Counter64: 64
3446
- .1.3.6.3.1 = No more variables left in this MIB View (It is past the end of the MIB tree)
3447
- step:
3448
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3449
- params: |-
3450
- MACRO1
3451
- .1.3.6.1
3452
- 0
3453
- MACRO2
3454
- .1.3.6.2
3455
- 0
3456
-out:
3457
- return: SUCCEED
3458
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"456","MACRO2":"64"},{"{#SNMPINDEX}":"1","MACRO1":"123","MACRO2":"TEST"}]'
3459
----
3460
-test case: SNMP walk to JSON - No more variables 2
3461
-in:
3462
- value:
3463
- value_type: ITEM_VALUE_TYPE_STR
3464
- time: 2017-10-29 03:15:00 +03:00
3465
- data: |
3466
- .1.3.6.1.1 = 123
3467
- .1.3.6.1.2 = 456
3468
- .1.3.6.2.1 = STRING: "TEST"
3469
- .1.3.6.2.2 = Counter64: 64
3470
- .1.3.6.3.1 = No more variables left in this MIB View (It is past the end of the MIB tree)
3471
- .1.3.7.1.1 = 123
3472
- .1.3.7.1.2 = 456
3473
- .1.3.7.2.1 = STRING: "TEST"
3474
- .1.3.7.2.2 = Counter64: 64
3475
- step:
3476
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3477
- params: |-
3478
- MACRO1
3479
- .1.3.6.1
3480
- 0
3481
- MACRO2
3482
- .1.3.6.2
3483
- 0
3484
-out:
3485
- return: SUCCEED
3486
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"456","MACRO2":"64"},{"{#SNMPINDEX}":"1","MACRO1":"123","MACRO2":"TEST"}]'
3487
----
3488
-test case: SNMP walk to JSON - Duplicate OIDs
3489
-in:
3490
- value:
3491
- value_type: ITEM_VALUE_TYPE_STR
3492
- time: 2017-10-29 03:15:00 +03:00
3493
- data: |
3494
- .1.3.6.1.1 = 11
3495
- .1.3.6.1.2 = 12
3496
- .1.3.6.2.1 = 21
3497
- .1.3.6.2.2 = 22
3498
- .1.3.6.1.1 = 11
3499
- .1.3.6.1.2 = 12
3500
- step:
3501
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3502
- params: |-
3503
- MACRO1
3504
- .1.3.6.1
3505
- 0
3506
- MACRO2
3507
- .1.3.6.2
3508
- 0
3509
-out:
3510
- return: SUCCEED
3511
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"12","MACRO2":"22"},{"{#SNMPINDEX}":"1","MACRO1":"11","MACRO2":"21"}]'
3512
----
3513
-test case: SNMP walk to JSON - Hex-STRING type - unchanged
3514
-in:
3515
- value:
3516
- value_type: ITEM_VALUE_TYPE_STR
3517
- time: 2017-10-29 03:15:00 +03:00
3518
- data: |
3519
- .1.3.6.1.1 = Hex-STRING: AA BE FF
3520
- .1.3.6.1.2 = Hex-STRING: FF FF
3521
- .1.3.6.2.1 = Hex-STRING: DD CD
3522
- .1.3.6.2.2 = Hex-STRING: 99 A5 F1
3523
- step:
3524
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3525
- params: |-
3526
- MACRO1
3527
- .1.3.6.1
3528
- 0
3529
- MACRO2
3530
- .1.3.6.2
3531
- 0
3532
-out:
3533
- return: SUCCEED
3534
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"FF FF","MACRO2":"99 A5 F1"},{"{#SNMPINDEX}":"1","MACRO1":"AA BE FF","MACRO2":"DD CD"}]'
3535
----
3536
-test case: SNMP walk to JSON - Hex-STRING type - to UTF8
3537
-in:
3538
- value:
3539
- value_type: ITEM_VALUE_TYPE_STR
3540
- time: 2017-10-29 03:15:00 +03:00
3541
- data: |
3542
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1 8B
3543
- .1.3.6.1.2 = Hex-STRING: 74 65 73 74 20 D1 84
3544
- .1.3.6.2.1 = Hex-STRING: D1 91
3545
- .1.3.6.2.2 = Hex-STRING: EA 9A 85
3546
- step:
3547
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3548
- params: |-
3549
- MACRO1
3550
- .1.3.6.1
3551
- 1
3552
- MACRO2
3553
- .1.3.6.2
3554
- 1
3555
-out:
3556
- return: SUCCEED
3557
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"test ф","MACRO2":"ꚅ"},{"{#SNMPINDEX}":"1","MACRO1":"test ы","MACRO2":"ё"}]'
3558
----
3559
-test case: SNMP walk to JSON - Hex-STRING type - to UTF8 - invalid sequence
3560
-in:
3561
- value:
3562
- value_type: ITEM_VALUE_TYPE_STR
3563
- time: 2017-10-29 03:15:00 +03:00
3564
- data: |
3565
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 FF FF
3566
- step:
3567
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3568
- params: |-
3569
- MACRO1
3570
- .1.3.6.1
3571
- 1
3572
-out:
3573
- return: SUCCEED
3574
- value: '[{"{#SNMPINDEX}":"1","MACRO1":"test ??"}]'
3575
----
3576
-test case: SNMP walk to JSON - Hex-STRING type - to UTF8 - invalid hex string
3577
-in:
3578
- value:
3579
- value_type: ITEM_VALUE_TYPE_STR
3580
- time: 2017-10-29 03:15:00 +03:00
3581
- data: |
3582
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1 8B XX
3583
- step:
3584
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3585
- params: |-
3586
- MACRO1
3587
- .1.3.6.1
3588
- 1
3589
-out:
3590
- return: FAIL
3591
----
3592
-test case: SNMP walk to JSON - Hex-STRING type - to UTF8 - null terminated
3593
-in:
3594
- value:
3595
- value_type: ITEM_VALUE_TYPE_STR
3596
- time: 2017-10-29 03:15:00 +03:00
3597
- data: |
3598
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 00
3599
- step:
3600
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3601
- params: |-
3602
- MACRO1
3603
- .1.3.6.1
3604
- 1
3605
-out:
3606
- return: SUCCEED
3607
- value: '[{"{#SNMPINDEX}":"1","MACRO1":"test"}]'
3608
----
3609
-test case: SNMP walk to JSON - Hex-STRING type - to MAC
3610
-in:
3611
- value:
3612
- value_type: ITEM_VALUE_TYPE_STR
3613
- time: 2017-10-29 03:15:00 +03:00
3614
- data: |
3615
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 D1
3616
- .1.3.6.1.2 = Hex-STRING: FF FF FF FF FF FF
3617
- .1.3.6.2.1 = Hex-STRING: 00 00 00 00 00 00
3618
- .1.3.6.2.2 = Hex-STRING: EA 9A 85 11 22 33
3619
- step:
3620
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3621
- params: |-
3622
- MACRO1
3623
- .1.3.6.1
3624
- 2
3625
- MACRO2
3626
- .1.3.6.2
3627
- 2
3628
-out:
3629
- return: SUCCEED
3630
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"FF:FF:FF:FF:FF:FF","MACRO2":"EA:9A:85:11:22:33"},{"{#SNMPINDEX}":"1","MACRO1":"74:65:73:74:20:D1","MACRO2":"00:00:00:00:00:00"}]'
3631
----
3632
-test case: SNMP walk to JSON - Hex-STRING type - to MAC - invalid MAC, invalid hex string
3633
-in:
3634
- value:
3635
- value_type: ITEM_VALUE_TYPE_STR
3636
- time: 2017-10-29 03:15:00 +03:00
3637
- data: |
3638
- .1.3.6.1.1 = Hex-STRING: 74 65 73 74 20 XX
3639
- step:
3640
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3641
- params: |-
3642
- MACRO1
3643
- .1.3.6.1
3644
- 2
3645
-out:
3646
- return: FAIL
3647
----
3648
-test case: SNMP walk to JSON - Hex-STRING type - mixed 'treat-as' values
3649
-in:
3650
- value:
3651
- value_type: ITEM_VALUE_TYPE_STR
3652
- time: 2017-10-29 03:15:00 +03:00
3653
- data: |
3654
- .1.3.6.1.1 = Hex-STRING: D1 8B
3655
- .1.3.6.1.2 = Hex-STRING: D1 91
3656
- .1.3.6.2.1 = Hex-STRING: 00 00 00 00 00 00
3657
- .1.3.6.2.2 = Hex-STRING: EA 9A 85 11 22 33
3658
- step:
3659
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3660
- params: |-
3661
- MACRO1
3662
- .1.3.6.1
3663
- 1
3664
- MACRO2
3665
- .1.3.6.2
3666
- 0
3667
-out:
3668
- return: SUCCEED
3669
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"ё","MACRO2":"EA 9A 85 11 22 33"},{"{#SNMPINDEX}":"1","MACRO1":"ы","MACRO2":"00 00 00 00 00 00"}]'
3670
----
3671
-test case: SNMP walk to value - Hex-STRING (multiline)
3672
-in:
3673
- value:
3674
- value_type: ITEM_VALUE_TYPE_STR
3675
- time: 2017-10-29 03:15:00 +03:00
3676
- data: |
3677
- .1.3.6.1.1 = Hex-STRING: 71 71
3678
- AA BB CC
3679
- DD
3680
- .1.3.6.1.2 = Hex-STRING: D1 91
3681
- .1.3.6.2.1 = Hex-STRING: 00 00 00 00 00 00
3682
- .1.3.6.2.2 = Hex-STRING: EA 9A 85 11 22 33
3683
- step:
3684
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3685
- params: |-
3686
- MACRO1
3687
- .1.3.6.1
3688
- 0
3689
- MACRO2
3690
- .1.3.6.2
3691
- 0
3692
-out:
3693
- return: SUCCEED
3694
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"D1 91","MACRO2":"EA 9A 85 11 22 33"},{"{#SNMPINDEX}":"1","MACRO1":"71 71 AA BB CC DD","MACRO2":"00 00 00 00 00 00"}]'
3695
----
3696
-test case: SNMP walk to value - Hex-STRING (multiline) - to MAC
3697
-in:
3698
- value:
3699
- value_type: ITEM_VALUE_TYPE_STR
3700
- time: 2017-10-29 03:15:00 +03:00
3701
- data: |
3702
- .1.3.6.1.1 = Hex-STRING: 71 71
3703
- AA BB CC
3704
- DD
3705
- .1.3.6.1.2 = Hex-STRING: D1 91
3706
- .1.3.6.2.1 = Hex-STRING: 00 00 00 00 00 00
3707
- .1.3.6.2.2 = Hex-STRING: EA 9A 85 11 22 33
3708
- step:
3709
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3710
- params: |-
3711
- MACRO1
3712
- .1.3.6.1
3713
- 2
3714
- MACRO2
3715
- .1.3.6.2
3716
- 0
3717
-out:
3718
- return: SUCCEED
3719
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"D1:91","MACRO2":"EA 9A 85 11 22 33"},{"{#SNMPINDEX}":"1","MACRO1":"71:71:AA:BB:CC:DD","MACRO2":"00 00 00 00 00 00"}]'
3720
----
3721
-test case: SNMP walk to value - Hex-STRING (multiline) - space on the last line
3722
-in:
3723
- value:
3724
- value_type: ITEM_VALUE_TYPE_STR
3725
- time: 2017-10-29 03:15:00 +03:00
3726
- data: |
3727
- .1.3.6.1.1 = Hex-STRING: 71 71
3728
- AA BB CC
3729
- DD
3730
- .1.3.6.1.2 = Hex-STRING: D1 91
3731
- .1.3.6.2.1 = Hex-STRING: 00 00 00 00 00 00
3732
- .1.3.6.2.2 = Hex-STRING: EA 9A 85 11 22 33
3733
- step:
3734
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3735
- params: |-
3736
- MACRO1
3737
- .1.3.6.1
3738
- 0
3739
- MACRO2
3740
- .1.3.6.2
3741
- 0
3742
-out:
3743
- return: SUCCEED
3744
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"D1 91","MACRO2":"EA 9A 85 11 22 33"},{"{#SNMPINDEX}":"1","MACRO1":"71 71 AA BB CC DD","MACRO2":"00 00 00 00 00 00"}]'
3745
----
3746
-test case: SNMP walk to JSON - BITS - unchanged
3747
-in:
3748
- value:
3749
- value_type: ITEM_VALUE_TYPE_STR
3750
- time: 2017-10-29 03:15:00 +03:00
3751
- data: |
3752
- .1.3.6.1.1 = BITS: 09 08 07
3753
- .1.3.6.1.2 = BITS: 55 66
3754
- .1.3.6.2.1 = BITS: 01 02
3755
- .1.3.6.2.2 = BITS: 99 10 20
3756
- step:
3757
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3758
- params: |-
3759
- MACRO1
3760
- .1.3.6.1
3761
- 0
3762
- MACRO2
3763
- .1.3.6.2
3764
- 0
3765
-out:
3766
- return: SUCCEED
3767
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"55 66","MACRO2":"99 10 20"},{"{#SNMPINDEX}":"1","MACRO1":"09 08 07","MACRO2":"01 02"}]'
3768
----
3769
-test case: SNMP walk to JSON - BITS - unchanged
3770
-in:
3771
- value:
3772
- value_type: ITEM_VALUE_TYPE_STR
3773
- time: 2017-10-29 03:15:00 +03:00
3774
- data: |
3775
- .1.3.6.1.1 = BITS: 09 08 07
3776
- .1.3.6.1.2 = BITS: 55 66
3777
- .1.3.6.2.1 = BITS: 01 02
3778
- .1.3.6.2.2 = BITS: 99 10 20
3779
- step:
3780
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3781
- params: |-
3782
- MACRO1
3783
- .1.3.6.1
3784
- 3
3785
- MACRO2
3786
- .1.3.6.2
3787
- 3
3788
-out:
3789
- return: SUCCEED
3790
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"26197","MACRO2":"2101401"},{"{#SNMPINDEX}":"1","MACRO1":"460809","MACRO2":"513"}]'
3791
----
3792
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 1 - prefix without dot
3793
-in:
3794
- value:
3795
- value_type: ITEM_VALUE_TYPE_STR
3796
- time: 2017-10-29 03:15:00 +03:00
3797
- data: |
3798
- .1.3.6.1.1 = STRING: "xxx"
3799
- .1.3.6.1.2 = STRING: "yyy"
3800
- .1.3.6.10.1 = STRING: "no"
3801
- .1.3.6.11.2 = STRING: "no"
3802
- .1.3.6.2.1 = STRING: "aaa"
3803
- .1.3.6.2.2 = STRING: "bbb"
3804
- .1.3.6.20.1 = STRING: "no"
3805
- .1.3.6.20.2 = STRING: "no"
3806
- step:
3807
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3808
- params: |-
3809
- MACRO1
3810
- 1.3.6.1
3811
- 0
3812
- MACRO2
3813
- 1.3.6.2
3814
- 0
3815
-out:
3816
- return: SUCCEED
3817
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3818
----
3819
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 1
3820
-in:
3821
- value:
3822
- value_type: ITEM_VALUE_TYPE_STR
3823
- time: 2017-10-29 03:15:00 +03:00
3824
- data: |
3825
- .1.3.6.1.1 = STRING: "xxx"
3826
- .1.3.6.1.2 = STRING: "yyy"
3827
- .1.3.6.10.1 = STRING: "no"
3828
- .1.3.6.11.2 = STRING: "no"
3829
- .1.3.6.2.1 = STRING: "aaa"
3830
- .1.3.6.2.2 = STRING: "bbb"
3831
- .1.3.6.20.1 = STRING: "no"
3832
- .1.3.6.20.2 = STRING: "no"
3833
- step:
3834
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3835
- params: |-
3836
- MACRO1
3837
- .1.3.6.1
3838
- 0
3839
- MACRO2
3840
- .1.3.6.2
3841
- 0
3842
-out:
3843
- return: SUCCEED
3844
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3845
----
3846
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 2 - prefix without dot
3847
-in:
3848
- value:
3849
- value_type: ITEM_VALUE_TYPE_STR
3850
- time: 2017-10-29 03:15:00 +03:00
3851
- data: |
3852
- .1.3.6.1.1 = STRING: "xxx"
3853
- .1.3.6.1.2 = STRING: "yyy"
3854
- .1.3.6.101.1 = STRING: "no"
3855
- .1.3.6.12123.2 = STRING: "no"
3856
- .1.3.6.2.1 = STRING: "aaa"
3857
- .1.3.6.2.2 = STRING: "bbb"
3858
- .1.3.6.20123.1 = STRING: "no"
3859
- .1.3.6.21123.2 = STRING: "no"
3860
- .1.3.6.3.1 = STRING: "no"
3861
- .1.3.6.333.1 = STRING: "no"
3862
- step:
3863
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3864
- params: |-
3865
- MACRO1
3866
- 1.3.6.1
3867
- 0
3868
- MACRO2
3869
- 1.3.6.2
3870
- 0
3871
-out:
3872
- return: SUCCEED
3873
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3874
----
3875
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 2
3876
-in:
3877
- value:
3878
- value_type: ITEM_VALUE_TYPE_STR
3879
- time: 2017-10-29 03:15:00 +03:00
3880
- data: |
3881
- .1.3.6.1.1 = STRING: "xxx"
3882
- .1.3.6.1.2 = STRING: "yyy"
3883
- .1.3.6.101.1 = STRING: "no"
3884
- .1.3.6.12123.2 = STRING: "no"
3885
- .1.3.6.2.1 = STRING: "aaa"
3886
- .1.3.6.2.2 = STRING: "bbb"
3887
- .1.3.6.20123.1 = STRING: "no"
3888
- .1.3.6.21123.2 = STRING: "no"
3889
- .1.3.6.3.1 = STRING: "no"
3890
- .1.3.6.333.1 = STRING: "no"
3891
- step:
3892
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3893
- params: |-
3894
- MACRO1
3895
- .1.3.6.1
3896
- 0
3897
- MACRO2
3898
- .1.3.6.2
3899
- 0
3900
-out:
3901
- return: SUCCEED
3902
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3903
----
3904
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 3 (dot at the end of param)
3905
-in:
3906
- value:
3907
- value_type: ITEM_VALUE_TYPE_STR
3908
- time: 2017-10-29 03:15:00 +03:00
3909
- data: |
3910
- .1.3.6.1.1 = STRING: "xxx"
3911
- .1.3.6.1.2 = STRING: "yyy"
3912
- .1.3.6.101.1 = STRING: "no"
3913
- .1.3.6.12123.2 = STRING: "no"
3914
- .1.3.6.2.1 = STRING: "aaa"
3915
- .1.3.6.2.2 = STRING: "bbb"
3916
- .1.3.6.20123.1 = STRING: "no"
3917
- .1.3.6.21123.2 = STRING: "no"
3918
- .1.3.6.3.1 = STRING: "no"
3919
- .1.3.6.333.1 = STRING: "no"
3920
- step:
3921
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3922
- params: |-
3923
- MACRO1
3924
- .1.3.6.1
3925
- 0
3926
- MACRO2
3927
- .1.3.6.2.
3928
- 0
3929
-out:
3930
- return: SUCCEED
3931
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3932
----
3933
-test case: SNMP walk to JSON - suppressed output of chosen oids that are longer than prefix 4 (dot at the end of param)
3934
-in:
3935
- value:
3936
- value_type: ITEM_VALUE_TYPE_STR
3937
- time: 2017-10-29 03:15:00 +03:00
3938
- data: |
3939
- .1.3.6.1.1 = STRING: "xxx"
3940
- .1.3.6.1.2 = STRING: "yyy"
3941
- .1.3.6.101.1 = STRING: "no"
3942
- .1.3.6.12123.2 = STRING: "no"
3943
- .1.3.6.2.1 = STRING: "aaa"
3944
- .1.3.6.2.2 = STRING: "bbb"
3945
- .1.3.6.20123.1 = STRING: "no"
3946
- .1.3.6.21123.2 = STRING: "no"
3947
- .1.3.6.3.1 = STRING: "no"
3948
- .1.3.6.333.1 = STRING: "no"
3949
- step:
3950
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3951
- params: |-
3952
- MACRO1
3953
- .1.3.6.1
3954
- 0
3955
- MACRO2
3956
- 1.3.6.2.
3957
- 0
3958
-out:
3959
- return: SUCCEED
3960
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
3961
----
3962
-test case: SNMP walk to JSON - oid to be walked is a leaf and index cannot be chosen
3963
-in:
3964
- value:
3965
- value_type: ITEM_VALUE_TYPE_STR
3966
- time: 2017-10-29 03:15:00 +03:00
3967
- data: |
3968
- .1.3.6.1 = STRING: "xxx"
3969
- .1.3.6.2.1 = STRING: "aaa"
3970
- .1.3.6.2.2 = STRING: "bbb"
3971
- step:
3972
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3973
- params: |-
3974
- MACRO1
3975
- .1.3.6.1
3976
- 0
3977
- MACRO2
3978
- .1.3.6.2
3979
- 0
3980
-out:
3981
- return: SUCCEED
3982
- value: '[{"{#SNMPINDEX}":"2","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO2":"aaa"}]'
3983
----
3984
-test case: SNMP walk to JSON - param with dot at the end - 1
3985
-in:
3986
- value:
3987
- value_type: ITEM_VALUE_TYPE_STR
3988
- time: 2017-10-29 03:15:00 +03:00
3989
- data: |
3990
- .1.3.6.2.1 = STRING: "123"
3991
- .1.3.6.2.2 = STRING: "234"
3992
- step:
3993
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
3994
- params: |-
3995
- MACRO1
3996
- .1.3.6.2.
3997
- 0
3998
-out:
3999
- return: SUCCEED
4000
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"234"},{"{#SNMPINDEX}":"1","MACRO1":"123"}]'
4001
----
4002
-test case: SNMP walk to JSON - param with dot at the end - 2
4003
-in:
4004
- value:
4005
- value_type: ITEM_VALUE_TYPE_STR
4006
- time: 2017-10-29 03:15:00 +03:00
4007
- data: |
4008
- .1.3.6.1.1 = STRING: "xxx"
4009
- .1.3.6.1.2 = STRING: "yyy"
4010
- .1.3.6.2.1 = STRING: "aaa"
4011
- .1.3.6.2.2 = STRING: "bbb"
4012
- step:
4013
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4014
- params: |-
4015
- MACRO1
4016
- .1.3.6.1.
4017
- 0
4018
- MACRO2
4019
- .1.3.6.2
4020
- 0
4021
-out:
4022
- return: SUCCEED
4023
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
4024
----
4025
-test case: SNMP walk to JSON - param with dot at the end - 3
4026
-in:
4027
- value:
4028
- value_type: ITEM_VALUE_TYPE_STR
4029
- time: 2017-10-29 03:15:00 +03:00
4030
- data: |
4031
- .1.3.6.1.1 = STRING: "xxx"
4032
- .1.3.6.1.2 = STRING: "yyy"
4033
- .1.3.6.2.1 = STRING: "aaa"
4034
- .1.3.6.2.2 = STRING: "bbb"
4035
- step:
4036
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4037
- params: |-
4038
- MACRO1
4039
- .1.3.6.1.
4040
- 0
4041
- MACRO2
4042
- 1.3.6.2
4043
- 0
4044
-out:
4045
- return: SUCCEED
4046
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
4047
----
4048
-test case: SNMP walk to JSON - param with dot at the end - 4
4049
-in:
4050
- value:
4051
- value_type: ITEM_VALUE_TYPE_STR
4052
- time: 2017-10-29 03:15:00 +03:00
4053
- data: |
4054
- .1.3.6.1.1 = STRING: "xxx"
4055
- .1.3.6.1.2 = STRING: "yyy"
4056
- .1.3.6.2.1 = STRING: "aaa"
4057
- .1.3.6.2.2 = STRING: "bbb"
4058
- step:
4059
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4060
- params: |-
4061
- MACRO1
4062
- 1.3.6.1.
4063
- 0
4064
- MACRO2
4065
- 1.3.6.2
4066
- 0
4067
-out:
4068
- return: SUCCEED
4069
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"bbb"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
4070
----
4071
-test case: SNMP walk to JSON - invalid params - 1
4072
-in:
4073
- value:
4074
- value_type: ITEM_VALUE_TYPE_STR
4075
- time: 2017-10-29 03:15:00 +03:00
4076
- data: |
4077
- .1.3.6.1.1 = STRING: "xxx"
4078
- .1.3.6.1.2 = STRING: "yyy"
4079
- .1.3.6.2.1 = STRING: "aaa"
4080
- .1.3.6.2.2 = STRING: "bbb"
4081
- step:
4082
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4083
- params: |-
4084
- MACRO1
4085
-out:
4086
- return: FAIL
4087
----
4088
-test case: SNMP walk to JSON - invalid params - 2
4089
-in:
4090
- value:
4091
- value_type: ITEM_VALUE_TYPE_STR
4092
- time: 2017-10-29 03:15:00 +03:00
4093
- data: |
4094
- .1.3.6.1.1 = STRING: "xxx"
4095
- .1.3.6.1.2 = STRING: "yyy"
4096
- .1.3.6.2.1 = STRING: "aaa"
4097
- .1.3.6.2.2 = STRING: "bbb"
4098
- step:
4099
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4100
- params: |-
4101
- MACRO1
4102
- 1.3.6.1.
4103
-out:
4104
- return: FAIL
4105
----
4106
-test case: SNMP walk to JSON - invalid params - 3
4107
-in:
4108
- value:
4109
- value_type: ITEM_VALUE_TYPE_STR
4110
- time: 2017-10-29 03:15:00 +03:00
4111
- data: |
4112
- .1.3.6.1.1 = STRING: "xxx"
4113
- .1.3.6.1.2 = STRING: "yyy"
4114
- .1.3.6.2.1 = STRING: "aaa"
4115
- .1.3.6.2.2 = STRING: "bbb"
4116
- step:
4117
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4118
- params: |-
4119
- MACRO1
4120
- 1.3.6.1.
4121
- 0
4122
- MACRO2
4123
-out:
4124
- return: FAIL
4125
----
4126
-test case: SNMP walk to JSON - invalid params - 4
4127
-in:
4128
- value:
4129
- value_type: ITEM_VALUE_TYPE_STR
4130
- time: 2017-10-29 03:15:00 +03:00
4131
- data: |
4132
- .1.3.6.1.1 = STRING: "xxx"
4133
- .1.3.6.1.2 = STRING: "yyy"
4134
- .1.3.6.2.1 = STRING: "aaa"
4135
- .1.3.6.2.2 = STRING: "bbb"
4136
- step:
4137
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4138
- params: |-
4139
- MACRO1
4140
- 1.3.6.1.
4141
- 0
4142
- MACRO2
4143
- 1.3.6.2
4144
-out:
4145
- return: FAIL
4146
----
4147
-test case: SNMP walk to JSON - invalid params - 5
4148
-in:
4149
- value:
4150
- value_type: ITEM_VALUE_TYPE_STR
4151
- time: 2017-10-29 03:15:00 +03:00
4152
- data: |
4153
- .1.3.6.1.1 = STRING: "xxx"
4154
- .1.3.6.1.2 = STRING: "yyy"
4155
- .1.3.6.2.1 = STRING: "aaa"
4156
- .1.3.6.2.2 = STRING: "bbb"
4157
- step:
4158
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4159
- params: |-
4160
- MACRO1
4161
- 1.3.6.1.
4162
- 0
4163
- MACRO2
4164
- 1.3.6.2
4165
- 1
4166
- X
4167
-out:
4168
- return: FAIL
4169
----
4170
-test case: SNMP walk to JSON - wrong type warning
4171
-in:
4172
- value:
4173
- value_type: ITEM_VALUE_TYPE_STR
4174
- time: 2017-10-29 03:15:00 +03:00
4175
- data: |
4176
- .1.3.6.1.1 = STRING: "xxx"
4177
- .1.3.6.1.2 = STRING: "yyy"
4178
- .1.3.6.2.1 = STRING: "aaa"
4179
- .1.3.6.2.2 = Wrong Type (should be Gauge32 or Unsigned32): Counter32: 123
4180
- step:
4181
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4182
- params: |-
4183
- MACRO1
4184
- 1.3.6.1.
4185
- 0
4186
- MACRO2
4187
- 1.3.6.2
4188
- 0
4189
-out:
4190
- return: SUCCEED
4191
- value: '[{"{#SNMPINDEX}":"2","MACRO1":"yyy","MACRO2":"123"},{"{#SNMPINDEX}":"1","MACRO1":"xxx","MACRO2":"aaa"}]'
4192
----
4193
-test case: SNMP walk to JSON - wrong type warning invalid format
4194
-in:
4195
- value:
4196
- value_type: ITEM_VALUE_TYPE_STR
4197
- time: 2017-10-29 03:15:00 +03:00
4198
- data: |
4199
- .1.3.6.1.1 = STRING: "xxx"
4200
- .1.3.6.1.2 = STRING: "yyy"
4201
- .1.3.6.2.1 = STRING: "aaa"
4202
- .1.3.6.2.2 = Wrong Type (should be Gauge32 or Unsigned32): Counter32 123
4203
- .1.3.6.2.3 = STRING: "bbb"
4204
- step:
4205
- type: ZBX_PREPROC_SNMP_WALK_TO_JSON
4206
- params: |-
4207
- MACRO1
4208
- 1.3.6.1.
4209
- 0
4210
- MACRO2
4211
- 1.3.6.2
4212
- 0
4213
-out:
4214
- return: FAIL
4215
----
4216
-test case: HMAC in JavaScript - Invalid algorithm
4217
-in:
4218
- value:
4219
- value_type: ITEM_VALUE_TYPE_STR
4220
- time: 2017-10-29 03:15:00 +03:00
4221
- data: "k"
4222
- step:
4223
- type: ZBX_PREPROC_SCRIPT
4224
- params: "return hmac('xxx', 'xxx', 'yyy');"
4225
-out:
4226
- return: FAIL
4227
----
4228
-test case: HMAC in JavaScript - MD5
4229
-in:
4230
- value:
4231
- value_type: ITEM_VALUE_TYPE_STR
4232
- time: 2017-10-29 03:15:00 +03:00
4233
- data: "k"
4234
- step:
4235
- type: ZBX_PREPROC_SCRIPT
4236
- params: "return hmac('md5', 'xxx', 'yyy');"
4237
-out:
4238
- return: SUCCEED
4239
- value: 'd8e8efd954dabfdbfd69398d92ad62d1'
4240
----
4241
-test case: HMAC in JavaScript - SHA-256
4242
-in:
4243
- value:
4244
- value_type: ITEM_VALUE_TYPE_STR
4245
- time: 2017-10-29 03:15:00 +03:00
4246
- data: "k"
4247
- step:
4248
- type: ZBX_PREPROC_SCRIPT
4249
- params: "return hmac('sha256', 'xxx', 'yyy');"
4250
-out:
4251
- return: SUCCEED
4252
- value: '97f50eed1079c9460f2bb9ddf7dc2e4258471f301ad2c1f14ad50144c4de4303'
4253
----
4254
-test case: HMAC in JavaScript - Invalid key
4255
-in:
4256
- value:
4257
- value_type: ITEM_VALUE_TYPE_STR
4258
- time: 2017-10-29 03:15:00 +03:00
4259
- data: "k"
4260
- step:
4261
- type: ZBX_PREPROC_SCRIPT
4262
- params: "return hmac('sha256', null, 'yyy');"
4263
-out:
4264
- return: FAIL
4265
----
4266
-test case: HMAC in JavaScript - Missing key
4267
-in:
4268
- value:
4269
- value_type: ITEM_VALUE_TYPE_STR
4270
- time: 2017-10-29 03:15:00 +03:00
4271
- data: "k"
4272
- step:
4273
- type: ZBX_PREPROC_SCRIPT
4274
- params: "return hmac('sha256');"
4275
-out:
4276
- return: FAIL
4277
----
4278
-test case: HMAC in JavaScript - Invalid data
4279
-in:
4280
- value:
4281
- value_type: ITEM_VALUE_TYPE_STR
4282
- time: 2017-10-29 03:15:00 +03:00
4283
- data: "k"
4284
- step:
4285
- type: ZBX_PREPROC_SCRIPT
4286
- params: "return hmac('sha256', 'xxx', null);"
4287
-out:
4288
- return: FAIL
4289
----
4290
-test case: HMAC in JavaScript - Missing data
4291
-in:
4292
- value:
4293
- value_type: ITEM_VALUE_TYPE_STR
4294
- time: 2017-10-29 03:15:00 +03:00
4295
- data: "k"
4296
- step:
4297
- type: ZBX_PREPROC_SCRIPT
4298
- params: "return hmac('sha256', 'xxx');"
4299
-out:
4300
- return: FAIL
4301
----
4302
-test case: RS256 in JavaScript - single line pkey w/o newlines
4303
-in:
4304
- encryption_required: 1
4305
- value:
4306
- value_type: ITEM_VALUE_TYPE_STR
4307
- time: 2017-10-29 03:15:00 +03:00
4308
- data: "k"
4309
- step:
4310
- type: ZBX_PREPROC_SCRIPT
4311
- params: "return sign('sha256', '-----BEGIN RSA PRIVATE KEY-----MIICXgIBAAKBgQCsDnSry3s269UFKpYvptshcmKZ4ZXxqj6771qr/oEOtRa/o+8JmFtucELQg7HrzRUwxG9htmCuSZ+eOcMQ7vJOrgS+1Ol01P+Ol/PdFFeT5utqzG2NaYSIs/oLAaTkiDUkDBhnG6Ns0xWgJlRomR2W4RI8Os/zKh9eccRaZorjQwIDAQABAoGBAKT300vo9QHqyruCYq/bxx1hpEKw9ejZd/8P9xsUEb/9R4uF7iqAB6JzRszQiKZzY473uWexyy8w7jdyYKL6yB2vCMLdy68nxoIYcjSrk3F/kC47ga7NKCS8T21OFarxhRzxqgF6rg/mfrgeT7nxD1EM0vk6mCSmftbaVZE7M/KRAkEA4FDqvtjen9RrROe2Al23h/1gDqgL9rJ5SzSbwwYmzRA/XYlfH7k9RPGQahzq15IHrdxN04Ov2dVRyybbR0A1awJBAMRb3+P1yKzIkkTGYE2ZWctBcDCnzwwaKKZuWX7OfA+ZPNUm+L6Owg4gztHIAA6DyizXxaHhh8qcL0A6nx7hJ4kCQQCjrces3LiNrcVcMSuZTGMYYuVNrIeWSqLBIrEpvHphlaJ3ET8M+fDgNtgm5dTi2daqoZ9UYLnXXJXjhAXrVsnjAkEAnn8ahcNLLEzp5CHMclaqKGq1yFBry/UBgvXnv3ekpGKou9UtS4OoGAdbafHqOc0fUHsKx9Rn65+OdGXdMuvDIQJAJ+L5i2iRo4TuNpHxMCPN/3bamMlDktIfPlVLbuEcinG1XZ9zs9Y1J738iCF2d56wlApbYGqw4vp9+CdX62LoMw==-----END RSA PRIVATE KEY-----', 'test');"
4312
-out:
4313
- return: SUCCEED
4314
- value: '272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5'
4315
----
4316
-test case: RS256 in JavaScript - single line pkey with newlines
4317
-in:
4318
- encryption_required: 1
4319
- value:
4320
- value_type: ITEM_VALUE_TYPE_STR
4321
- time: 2017-10-29 03:15:00 +03:00
4322
- data: "a"
4323
- step:
4324
- type: ZBX_PREPROC_SCRIPT
4325
- params: |
4326
- var pkey = '-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQCsDnSry3s269UFKpYvptshcmKZ4ZXxqj6771qr/oEOtRa/o+8J\nmFtucELQg7HrzRUwxG9htmCuSZ+eOcMQ7vJOrgS+1Ol01P+Ol/PdFFeT5utqzG2N\naYSIs/oLAaTkiDUkDBhnG6Ns0xWgJlRomR2W4RI8Os/zKh9eccRaZorjQwIDAQAB\nAoGBAKT300vo9QHqyruCYq/bxx1hpEKw9ejZd/8P9xsUEb/9R4uF7iqAB6JzRszQ\niKZzY473uWexyy8w7jdyYKL6yB2vCMLdy68nxoIYcjSrk3F/kC47ga7NKCS8T21O\nFarxhRzxqgF6rg/mfrgeT7nxD1EM0vk6mCSmftbaVZE7M/KRAkEA4FDqvtjen9Rr\nROe2Al23h/1gDqgL9rJ5SzSbwwYmzRA/XYlfH7k9RPGQahzq15IHrdxN04Ov2dVR\nyybbR0A1awJBAMRb3+P1yKzIkkTGYE2ZWctBcDCnzwwaKKZuWX7OfA+ZPNUm+L6O\nwg4gztHIAA6DyizXxaHhh8qcL0A6nx7hJ4kCQQCjrces3LiNrcVcMSuZTGMYYuVN\nrIeWSqLBIrEpvHphlaJ3ET8M+fDgNtgm5dTi2daqoZ9UYLnXXJXjhAXrVsnjAkEA\nnn8ahcNLLEzp5CHMclaqKGq1yFBry/UBgvXnv3ekpGKou9UtS4OoGAdbafHqOc0f\nUHsKx9Rn65+OdGXdMuvDIQJAJ+L5i2iRo4TuNpHxMCPN/3bamMlDktIfPlVLbuEc\ninG1XZ9zs9Y1J738iCF2d56wlApbYGqw4vp9+CdX62LoMw==\n-----END RSA PRIVATE KEY-----';
4327
- return sign('sha256', pkey, 'test');
4328
-out:
4329
- return: SUCCEED
4330
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4331
----
4332
-test case: RS256 in JavaScript - single line pkey with missing newline before END block
4333
-in:
4334
- encryption_required: 1
4335
- value:
4336
- value_type: ITEM_VALUE_TYPE_STR
4337
- time: 2017-10-29 03:15:00 +03:00
4338
- data: "a"
4339
- step:
4340
- type: ZBX_PREPROC_SCRIPT
4341
- params: |
4342
- var pkey = '-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQCsDnSry3s269UFKpYvptshcmKZ4ZXxqj6771qr/oEOtRa/o+8J\nmFtucELQg7HrzRUwxG9htmCuSZ+eOcMQ7vJOrgS+1Ol01P+Ol/PdFFeT5utqzG2N\naYSIs/oLAaTkiDUkDBhnG6Ns0xWgJlRomR2W4RI8Os/zKh9eccRaZorjQwIDAQAB\nAoGBAKT300vo9QHqyruCYq/bxx1hpEKw9ejZd/8P9xsUEb/9R4uF7iqAB6JzRszQ\niKZzY473uWexyy8w7jdyYKL6yB2vCMLdy68nxoIYcjSrk3F/kC47ga7NKCS8T21O\nFarxhRzxqgF6rg/mfrgeT7nxD1EM0vk6mCSmftbaVZE7M/KRAkEA4FDqvtjen9Rr\nROe2Al23h/1gDqgL9rJ5SzSbwwYmzRA/XYlfH7k9RPGQahzq15IHrdxN04Ov2dVR\nyybbR0A1awJBAMRb3+P1yKzIkkTGYE2ZWctBcDCnzwwaKKZuWX7OfA+ZPNUm+L6O\nwg4gztHIAA6DyizXxaHhh8qcL0A6nx7hJ4kCQQCjrces3LiNrcVcMSuZTGMYYuVN\nrIeWSqLBIrEpvHphlaJ3ET8M+fDgNtgm5dTi2daqoZ9UYLnXXJXjhAXrVsnjAkEA\nnn8ahcNLLEzp5CHMclaqKGq1yFBry/UBgvXnv3ekpGKou9UtS4OoGAdbafHqOc0f\nUHsKx9Rn65+OdGXdMuvDIQJAJ+L5i2iRo4TuNpHxMCPN/3bamMlDktIfPlVLbuEc\ninG1XZ9zs9Y1J738iCF2d56wlApbYGqw4vp9+CdX62LoMw==-----END RSA PRIVATE KEY-----';
4343
- return sign('sha256', pkey, 'test');
4344
-out:
4345
- return: SUCCEED
4346
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4347
----
4348
-test case: RS256 in JavaScript - single line pkey with missing newline before BEGIN block
4349
-in:
4350
- encryption_required: 1
4351
- value:
4352
- value_type: ITEM_VALUE_TYPE_STR
4353
- time: 2017-10-29 03:15:00 +03:00
4354
- data: "a"
4355
- step:
4356
- type: ZBX_PREPROC_SCRIPT
4357
- params: |
4358
- var pkey = '-----BEGIN RSA PRIVATE KEY-----MIICXgIBAAKBgQCsDnSry3s269UFKpYvptshcmKZ4ZXxqj6771qr/oEOtRa/o+8J\nmFtucELQg7HrzRUwxG9htmCuSZ+eOcMQ7vJOrgS+1Ol01P+Ol/PdFFeT5utqzG2N\naYSIs/oLAaTkiDUkDBhnG6Ns0xWgJlRomR2W4RI8Os/zKh9eccRaZorjQwIDAQAB\nAoGBAKT300vo9QHqyruCYq/bxx1hpEKw9ejZd/8P9xsUEb/9R4uF7iqAB6JzRszQ\niKZzY473uWexyy8w7jdyYKL6yB2vCMLdy68nxoIYcjSrk3F/kC47ga7NKCS8T21O\nFarxhRzxqgF6rg/mfrgeT7nxD1EM0vk6mCSmftbaVZE7M/KRAkEA4FDqvtjen9Rr\nROe2Al23h/1gDqgL9rJ5SzSbwwYmzRA/XYlfH7k9RPGQahzq15IHrdxN04Ov2dVR\nyybbR0A1awJBAMRb3+P1yKzIkkTGYE2ZWctBcDCnzwwaKKZuWX7OfA+ZPNUm+L6O\nwg4gztHIAA6DyizXxaHhh8qcL0A6nx7hJ4kCQQCjrces3LiNrcVcMSuZTGMYYuVN\nrIeWSqLBIrEpvHphlaJ3ET8M+fDgNtgm5dTi2daqoZ9UYLnXXJXjhAXrVsnjAkEA\nnn8ahcNLLEzp5CHMclaqKGq1yFBry/UBgvXnv3ekpGKou9UtS4OoGAdbafHqOc0f\nUHsKx9Rn65+OdGXdMuvDIQJAJ+L5i2iRo4TuNpHxMCPN/3bamMlDktIfPlVLbuEc\ninG1XZ9zs9Y1J738iCF2d56wlApbYGqw4vp9+CdX62LoMw==\n-----END RSA PRIVATE KEY-----';
4359
- return sign('sha256', pkey, 'test');
4360
-out:
4361
- return: SUCCEED
4362
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4363
----
4364
-test case: RS256 in JavaScript - spaces instead of newlines
4365
-in:
4366
- encryption_required: 1
4367
- value:
4368
- value_type: ITEM_VALUE_TYPE_STR
4369
- time: 2017-10-29 03:15:00 +03:00
4370
- data: "a"
4371
- step:
4372
- type: ZBX_PREPROC_SCRIPT
4373
- params: |
4374
- var pkey = '-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQCsDnSry3s269UFKpYvptshcmKZ4ZXxqj6771qr/oEOtRa/o+8J mFtucELQg7HrzRUwxG9htmCuSZ+eOcMQ7vJOrgS+1Ol01P+Ol/PdFFeT5utqzG2N aYSIs/oLAaTkiDUkDBhnG6Ns0xWgJlRomR2W4RI8Os/zKh9eccRaZorjQwIDAQAB AoGBAKT300vo9QHqyruCYq/bxx1hpEKw9ejZd/8P9xsUEb/9R4uF7iqAB6JzRszQ iKZzY473uWexyy8w7jdyYKL6yB2vCMLdy68nxoIYcjSrk3F/kC47ga7NKCS8T21O FarxhRzxqgF6rg/mfrgeT7nxD1EM0vk6mCSmftbaVZE7M/KRAkEA4FDqvtjen9Rr ROe2Al23h/1gDqgL9rJ5SzSbwwYmzRA/XYlfH7k9RPGQahzq15IHrdxN04Ov2dVR yybbR0A1awJBAMRb3+P1yKzIkkTGYE2ZWctBcDCnzwwaKKZuWX7OfA+ZPNUm+L6O wg4gztHIAA6DyizXxaHhh8qcL0A6nx7hJ4kCQQCjrces3LiNrcVcMSuZTGMYYuVN rIeWSqLBIrEpvHphlaJ3ET8M+fDgNtgm5dTi2daqoZ9UYLnXXJXjhAXrVsnjAkEA nn8ahcNLLEzp5CHMclaqKGq1yFBry/UBgvXnv3ekpGKou9UtS4OoGAdbafHqOc0f UHsKx9Rn65+OdGXdMuvDIQJAJ+L5i2iRo4TuNpHxMCPN/3bamMlDktIfPlVLbuEc inG1XZ9zs9Y1J738iCF2d56wlApbYGqw4vp9+CdX62LoMw== -----END RSA PRIVATE KEY-----';
4375
- return sign('sha256', pkey, 'test');
4376
-out:
4377
- return: SUCCEED
4378
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4379
----
4380
-test case: RS256 in JavaScript - PKCS#8
4381
-in:
4382
- encryption_required: 1
4383
- value:
4384
- value_type: ITEM_VALUE_TYPE_STR
4385
- time: 2017-10-29 03:15:00 +03:00
4386
- data: "a"
4387
- step:
4388
- type: ZBX_PREPROC_SCRIPT
4389
- params: |
4390
- var pkey = '-----BEGIN PRIVATE KEY-----MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKwOdKvLezbr1QUqli+m2yFyYpnhlfGqPrvvWqv+gQ61Fr+j7wmYW25wQtCDsevNFTDEb2G2YK5Jn545wxDu8k6uBL7U6XTU/46X890UV5Pm62rMbY1phIiz+gsBpOSINSQMGGcbo2zTFaAmVGiZHZbhEjw6z/MqH15xxFpmiuNDAgMBAAECgYEApPfTS+j1AerKu4Jir9vHHWGkQrD16Nl3/w/3GxQRv/1Hi4XuKoAHonNGzNCIpnNjjve5Z7HLLzDuN3JgovrIHa8Iwt3LryfGghhyNKuTcX+QLjuBrs0oJLxPbU4VqvGFHPGqAXquD+Z+uB5PufEPUQzS+TqYJKZ+1tpVkTsz8pECQQDgUOq+2N6f1GtE57YCXbeH/WAOqAv2snlLNJvDBibNED9diV8fuT1E8ZBqHOrXkget3E3Tg6/Z1VHLJttHQDVrAkEAxFvf4/XIrMiSRMZgTZlZy0FwMKfPDBoopm5Zfs58D5k81Sb4vo7CDiDO0cgADoPKLNfFoeGHypwvQDqfHuEniQJBAKOtx6zcuI2txVwxK5lMYxhi5U2sh5ZKosEisSm8emGVoncRPwz58OA22Cbl1OLZ1qqhn1RguddcleOEBetWyeMCQQCefxqFw0ssTOnkIcxyVqooarXIUGvL9QGC9ee/d6SkYqi71S1Lg6gYB1tp8eo5zR9QewrH1Gfrn450Zd0y68MhAkAn4vmLaJGjhO42kfEwI83/dtqYyUOS0h8+VUtu4RyKcbVdn3Oz1jUnvfyIIXZ3nrCUCltgarDi+n34J1frYugz-----END PRIVATE KEY-----';
4391
- return sign('sha256', pkey, 'test');
4392
-out:
4393
- return: SUCCEED
4394
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4395
----
4396
-test case: RS256 in JavaScript - data as Uint8Array
4397
-in:
4398
- encryption_required: 1
4399
- value:
4400
- value_type: ITEM_VALUE_TYPE_STR
4401
- time: 2017-10-29 03:15:00 +03:00
4402
- data: "a"
4403
- step:
4404
- type: ZBX_PREPROC_SCRIPT
4405
- params: |
4406
- var pkey = '-----BEGIN PRIVATE KEY-----MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKwOdKvLezbr1QUqli+m2yFyYpnhlfGqPrvvWqv+gQ61Fr+j7wmYW25wQtCDsevNFTDEb2G2YK5Jn545wxDu8k6uBL7U6XTU/46X890UV5Pm62rMbY1phIiz+gsBpOSINSQMGGcbo2zTFaAmVGiZHZbhEjw6z/MqH15xxFpmiuNDAgMBAAECgYEApPfTS+j1AerKu4Jir9vHHWGkQrD16Nl3/w/3GxQRv/1Hi4XuKoAHonNGzNCIpnNjjve5Z7HLLzDuN3JgovrIHa8Iwt3LryfGghhyNKuTcX+QLjuBrs0oJLxPbU4VqvGFHPGqAXquD+Z+uB5PufEPUQzS+TqYJKZ+1tpVkTsz8pECQQDgUOq+2N6f1GtE57YCXbeH/WAOqAv2snlLNJvDBibNED9diV8fuT1E8ZBqHOrXkget3E3Tg6/Z1VHLJttHQDVrAkEAxFvf4/XIrMiSRMZgTZlZy0FwMKfPDBoopm5Zfs58D5k81Sb4vo7CDiDO0cgADoPKLNfFoeGHypwvQDqfHuEniQJBAKOtx6zcuI2txVwxK5lMYxhi5U2sh5ZKosEisSm8emGVoncRPwz58OA22Cbl1OLZ1qqhn1RguddcleOEBetWyeMCQQCefxqFw0ssTOnkIcxyVqooarXIUGvL9QGC9ee/d6SkYqi71S1Lg6gYB1tp8eo5zR9QewrH1Gfrn450Zd0y68MhAkAn4vmLaJGjhO42kfEwI83/dtqYyUOS0h8+VUtu4RyKcbVdn3Oz1jUnvfyIIXZ3nrCUCltgarDi+n34J1frYugz-----END PRIVATE KEY-----';
4407
- return sign('sha256', pkey, new Uint8Array([0x74, 0x65, 0x73, 0x74]));
4408
-out:
4409
- return: SUCCEED
4410
- value: "272f1ef9012b81365bd29f929cfd63dc8a0bafbfaf1fb0bb1a6982d04260ee888573e7591e0e8076a3d8eb9da7a85bd57e61b4504abcfd62815f88cb0c6955fe4bab6d0b4361182182e316a96c83d5ab9ac8e314b5f11eabcefbeb520ec41b04b52323aedcf53b630a232f0a63c66a4cc4411241160ccb30409525c9c51a06d5"
4411
----
4412
-test case: RS256 in JavaScript - Invalid algorithm
4413
-in:
4414
- encryption_required: 1
4415
- value:
4416
- value_type: ITEM_VALUE_TYPE_STR
4417
- time: 2017-10-29 03:15:00 +03:00
4418
- data: "a"
4419
- step:
4420
- type: ZBX_PREPROC_SCRIPT
4421
- params: "return sign('q', pkey, 'test');"
4422
-out:
4423
- return: FAIL
4424
----
4425
-test case: RS256 in JavaScript - Invalid pkey
4426
-in:
4427
- encryption_required: 1
4428
- value:
4429
- value_type: ITEM_VALUE_TYPE_STR
4430
- time: 2017-10-29 03:15:00 +03:00
4431
- data: "a"
4432
- step:
4433
- type: ZBX_PREPROC_SCRIPT
4434
- params: "return sign('q', 'invalid', 'test');"
4435
-out:
4436
- return: FAIL
4437
----
4438
-test case: RS256 in JavaScript - Missing pkey
4439
-in:
4440
- encryption_required: 1
4441
- value:
4442
- value_type: ITEM_VALUE_TYPE_STR
4443
- time: 2017-10-29 03:15:00 +03:00
4444
- data: "a"
4445
- step:
4446
- type: ZBX_PREPROC_SCRIPT
4447
- params: "return sign('q');"
4448
-out:
4449
- return: FAIL
4450
----
4451
-test case: RS256 in JavaScript - Invalid data
4452
-in:
4453
- encryption_required: 1
4454
- value:
4455
- value_type: ITEM_VALUE_TYPE_STR
4456
- time: 2017-10-29 03:15:00 +03:00
4457
- data: "a"
4458
- step:
4459
- type: ZBX_PREPROC_SCRIPT
4460
- params: "return sign('q', 'invalid', '');"
4461
-out:
4462
- return: FAIL
4463
----
4464
-test case: RS256 in JavaScript - Missing data
4465
-in:
4466
- encryption_required: 1
4467
- value:
4468
- value_type: ITEM_VALUE_TYPE_STR
4469
- time: 2017-10-29 03:15:00 +03:00
4470
- data: "a"
4471
- step:
4472
- type: ZBX_PREPROC_SCRIPT
4473
- params: "return sign('q', 'pkey');"
4474
-out:
4475
- return: FAIL
4476
----
4477
-test case: Check for not supported without parameter (false)
4478
-in:
4479
- value:
4480
- value_type: ITEM_VALUE_TYPE_STR
4481
- time: 2017-10-29 03:15:00 +03:00
4482
- data: "no error"
4483
- step:
4484
- type: ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
4485
- params: ""
4486
- error_handler: ZBX_PREPROC_FAIL_SET_VALUE
4487
- error_handler_params: error_value
4488
-out:
4489
- return: SUCCEED
4490
- value: "no error"
4491
----
4492
-test case: Check for not supported without parameter
4493
-in:
4494
- error:
4495
- time: 2017-10-29 03:15:00 +03:00
4496
- data: "error any"
4497
- step:
4498
- type: ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
4499
- params: ""
4500
- error_handler: ZBX_PREPROC_FAIL_SET_VALUE
4501
- error_handler_params: error_value
4502
-out:
4503
- return: SUCCEED
4504
- value: error_value
4505
----
4506
-test case: Check for not supported with parameter (false)
4507
-in:
4508
- error:
4509
- time: 2017-10-29 03:15:00 +03:00
4510
- data: "error any"
4511
- step:
4512
- type: ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
4513
- params: "0\n123"
4514
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
4515
- error_handler_params: custom error
4516
-out:
4517
- return: SUCCEED
4518
- value: "error any"
4519
----
4520
-test case: Check for not supported with parameter
4521
-in:
4522
- error:
4523
- time: 2017-10-29 03:15:00 +03:00
4524
- data: "error 123"
4525
- step:
4526
- type: ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
4527
- params: "0\n123"
4528
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
4529
- error_handler_params: custom error
4530
-out:
4531
- return: FAIL
4532
- error: custom error
4533
----
4534
-test case: Check for not supported with parameter and capturing group
4535
-in:
4536
- error:
4537
- time: 2017-10-29 03:15:00 +03:00
4538
- data: "error 123"
4539
- step:
4540
- type: ZBX_PREPROC_VALIDATE_NOT_SUPPORTED
4541
- params: "0\n123"
4542
- error_handler: ZBX_PREPROC_FAIL_SET_ERROR
4543
- error_handler_params: custom error \0
4544
-out:
4545
- return: FAIL
4546
- error: custom error 123
4547
----
4548
-test case: btoa in JavaScript - binary
4549
-in:
4550
- value:
4551
- value_type: ITEM_VALUE_TYPE_STR
4552
- time: 2017-10-29 03:15:00 +03:00
4553
- data: "k"
4554
- step:
4555
- type: ZBX_PREPROC_SCRIPT
4556
- params: "return btoa(new Uint8Array([49, 0, 51]));"
4557
-out:
4558
- return: SUCCEED
4559
- value: 'MQAz'
4560
----
4561
-test case: Crash by accessing internal object pointer in JavaScript
4562
-in:
4563
- value:
4564
- value_type: ITEM_VALUE_TYPE_STR
4565
- time: 2017-10-29 03:15:00 +03:00
4566
- data: "a"
4567
- step:
4568
- type: ZBX_PREPROC_SCRIPT
4569
- params: |
4570
- var http = new HttpRequest();
4571
- var fake = {};
4572
- fake[atob('//9k')] = 'xyz';
4573
- fake.get = http.get;
4574
- fake.get('http://localhost');
4575
- return 0;
4576
- script_uses_curl: 1
4577
-out:
4578
- return: FAIL
4579
----
4580
-test case: Memory leak with global variable and exception
4581
-in:
4582
- value:
4583
- value_type: ITEM_VALUE_TYPE_STR
4584
- time: 2017-10-29 03:15:00 +03:00
4585
- data: "a"
4586
- step:
4587
- type: ZBX_PREPROC_SCRIPT
4588
- params: |
4589
- req = new HttpRequest();
4590
- throw 0;
4591
- script_uses_curl: 1
4592
-out:
4593
- return: FAIL
4594
----
4595
-test case: Memory leak with finalizer
4596
-in:
4597
- value:
4598
- value_type: ITEM_VALUE_TYPE_STR
4599
- time: 2017-10-29 03:15:00 +03:00
4600
- data: "a"
4601
- step:
4602
- type: ZBX_PREPROC_SCRIPT
4603
- params: |
4604
- var req = new HttpRequest();
4605
-
4606
- req[atob('gkZpbmFsaXplcg==')] = null;
4607
-
4608
- return 0;
4609
- script_uses_curl: 1
4610
-out:
4611
- value: "0"
4612
- return: SUCCEED
4613
----
4614
- test case: SNMP walk to value - duplicate oids
4615
- in:
4616
- value:
4617
- value_type: ITEM_VALUE_TYPE_STR
4618
- time: 2017-10-29 03:15:00 +03:00
4619
- data: |
4620
- .1.3.6.1.1 = STRING: "AAA"
4621
- .1.3.6.1.1 = STRING: "AAA"
4622
- step:
4623
- type: ZBX_PREPROC_SNMP_WALK_VALUE
4624
- params: |-
4625
- .1.3.6.1.1
4626
- 0
4627
- out:
4628
- return: SUCCEED
4629
- value: "AAA"
4630
-...
src/go/plugin/scripts.d/pkg/zabbixpreproc/testloader_test.go
deleted
-460
@@ -1,460 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "os"
6
- "strings"
7
- "time"
8
-
9
- "gopkg.in/yaml.v3"
10
-)
11
-
12
-// valueInput represents the input value in a test case
13
-type valueInput struct {
14
- ValueType string `yaml:"value_type"`
15
- Time string `yaml:"time"`
16
- Data interface{} `yaml:"data"`
17
-}
18
-
19
-// stepInput represents the preprocessing step in a test case
20
-type stepInput struct {
21
- Type string `yaml:"type"`
22
- Params string `yaml:"params"`
23
- ErrorHandler string `yaml:"error_handler"`
24
- ErrorHandlerParam string `yaml:"error_handler_params"`
25
-}
26
-
27
-// testOutput represents the expected output in a test case
28
-type testOutput struct {
29
- Return string `yaml:"return"`
30
- Value interface{} `yaml:"value"`
31
- Error string `yaml:"error"`
32
-}
33
-
34
-// testInput represents the input section of a test case
35
-type testInputSection struct {
36
- Value valueInput `yaml:"value"`
37
- Error *valueInput `yaml:"error"` // Input is an error instead of a value
38
- HistoryValue *valueInput `yaml:"history_value"` // Deprecated field
39
- History *valueInput `yaml:"history"` // Actual field used in tests
40
- Step stepInput `yaml:"step"`
41
-}
42
-
43
-// TestCase represents a single Zabbix preprocessing test case.
44
-type TestCase struct {
45
- Name string
46
- In testInputSection
47
- Out testOutput
48
-}
49
-
50
-// LoadTestCases loads Zabbix YAML test cases from file.
51
-func LoadTestCases(filename string) ([]TestCase, error) {
52
- data, err := os.ReadFile(filename)
53
- if err != nil {
54
- return nil, fmt.Errorf("failed to read test file: %w", err)
55
- }
56
-
57
- // Split YAML documents - split on "---" at start of line
58
- docStr := string(data)
59
- // Handle both with and without leading ---
60
- if strings.HasPrefix(docStr, "---") {
61
- docStr = docStr[3:] // Remove leading ---
62
- }
63
-
64
- docs := strings.Split(docStr, "\n---")
65
- var testCases []TestCase
66
-
67
- for _, doc := range docs {
68
- doc = strings.TrimSpace(doc)
69
- if doc == "" {
70
- continue
71
- }
72
-
73
- var rawTC struct {
74
- TestCase string `yaml:"test case"`
75
- In testInputSection `yaml:"in"`
76
- Out testOutput `yaml:"out"`
77
- }
78
-
79
- if err := yaml.Unmarshal([]byte(doc), &rawTC); err != nil {
80
- // Log warning for unparseable YAML (don't silently skip)
81
- fmt.Fprintf(os.Stderr, "WARNING: Failed to parse YAML document in test file: %v\n", err)
82
- continue
83
- }
84
-
85
- if rawTC.TestCase == "" {
86
- continue
87
- }
88
-
89
- testCases = append(testCases, TestCase{
90
- Name: rawTC.TestCase,
91
- In: rawTC.In,
92
- Out: rawTC.Out,
93
- })
94
- }
95
-
96
- return testCases, nil
97
-}
98
-
99
-// LoadXPathTestCases loads XPath-specific test cases and converts to standard format
100
-func LoadXPathTestCases(filename string) ([]TestCase, error) {
101
- data, err := os.ReadFile(filename)
102
- if err != nil {
103
- return nil, fmt.Errorf("failed to read XPath test file: %w", err)
104
- }
105
-
106
- // Split YAML documents
107
- docStr := string(data)
108
- if strings.HasPrefix(docStr, "---") {
109
- docStr = docStr[3:]
110
- }
111
-
112
- docs := strings.Split(docStr, "\n---")
113
- var testCases []TestCase
114
-
115
- for _, doc := range docs {
116
- doc = strings.TrimSpace(doc)
117
- if doc == "" {
118
- continue
119
- }
120
-
121
- var rawTC struct {
122
- TestCase string `yaml:"test case"`
123
- In struct {
124
- XML string `yaml:"xml"`
125
- XPath string `yaml:"xpath"`
126
- } `yaml:"in"`
127
- Out struct {
128
- Result string `yaml:"result"`
129
- Return string `yaml:"return"`
130
- } `yaml:"out"`
131
- }
132
-
133
- if err := yaml.Unmarshal([]byte(doc), &rawTC); err != nil {
134
- // Log warning for unparseable YAML (don't silently skip)
135
- fmt.Fprintf(os.Stderr, "WARNING: Failed to parse XPath test YAML: %v\n", err)
136
- continue
137
- }
138
-
139
- if rawTC.TestCase == "" {
140
- continue
141
- }
142
-
143
- // Convert to standard format
144
- tc := TestCase{
145
- Name: rawTC.TestCase,
146
- In: testInputSection{
147
- Value: valueInput{
148
- ValueType: "ITEM_VALUE_TYPE_STR",
149
- Data: rawTC.In.XML,
150
- },
151
- Step: stepInput{
152
- Type: "ZBX_PREPROC_XPATH",
153
- Params: rawTC.In.XPath,
154
- },
155
- },
156
- Out: testOutput{
157
- Return: rawTC.Out.Return,
158
- Value: rawTC.Out.Result,
159
- },
160
- }
161
-
162
- testCases = append(testCases, tc)
163
- }
164
-
165
- return testCases, nil
166
-}
167
-
168
-// LoadCSVTestCases loads CSV-specific test cases and converts to standard format
169
-func LoadCSVTestCases(filename string) ([]TestCase, error) {
170
- data, err := os.ReadFile(filename)
171
- if err != nil {
172
- return nil, fmt.Errorf("failed to read CSV test file: %w", err)
173
- }
174
-
175
- // Split YAML documents
176
- docStr := string(data)
177
- if strings.HasPrefix(docStr, "---") {
178
- docStr = docStr[3:]
179
- }
180
-
181
- docs := strings.Split(docStr, "\n---")
182
- var testCases []TestCase
183
-
184
- for _, doc := range docs {
185
- doc = strings.TrimSpace(doc)
186
- if doc == "" {
187
- continue
188
- }
189
-
190
- var rawTC struct {
191
- TestCase string `yaml:"test case"`
192
- In struct {
193
- CSV string `yaml:"csv"`
194
- Params string `yaml:"params"`
195
- } `yaml:"in"`
196
- Out struct {
197
- Result string `yaml:"result"`
198
- Return string `yaml:"return"`
199
- } `yaml:"out"`
200
- }
201
-
202
- if err := yaml.Unmarshal([]byte(doc), &rawTC); err != nil {
203
- // Log warning for unparseable YAML (don't silently skip)
204
- fmt.Fprintf(os.Stderr, "WARNING: Failed to parse CSV test YAML: %v\n", err)
205
- continue
206
- }
207
-
208
- if rawTC.TestCase == "" {
209
- continue
210
- }
211
-
212
- // Convert to standard format
213
- tc := TestCase{
214
- Name: rawTC.TestCase,
215
- In: testInputSection{
216
- Value: valueInput{
217
- ValueType: "ITEM_VALUE_TYPE_STR",
218
- Data: rawTC.In.CSV,
219
- },
220
- Step: stepInput{
221
- Type: "ZBX_PREPROC_CSV_TO_JSON",
222
- Params: rawTC.In.Params,
223
- },
224
- },
225
- Out: testOutput{
226
- Return: rawTC.Out.Return,
227
- Value: rawTC.Out.Result,
228
- },
229
- }
230
-
231
- testCases = append(testCases, tc)
232
- }
233
-
234
- return testCases, nil
235
-}
236
-
237
-// LoadAllTestCases loads all official Zabbix test suites
238
-func LoadAllTestCases() ([]TestCase, error) {
239
- var allTests []TestCase
240
-
241
- // Load main test suite
242
- mainTests, err := LoadTestCases("testdata/zbx_item_preproc.yaml")
243
- if err != nil {
244
- return nil, fmt.Errorf("failed to load main test suite: %w", err)
245
- }
246
- allTests = append(allTests, mainTests...)
247
-
248
- // Load XPath test suite
249
- xpathTests, err := LoadXPathTestCases("testdata/item_preproc_xpath.yaml")
250
- if err != nil {
251
- return nil, fmt.Errorf("failed to load XPath test suite: %w", err)
252
- }
253
- allTests = append(allTests, xpathTests...)
254
-
255
- // Load CSV test suite
256
- csvTests, err := LoadCSVTestCases("testdata/item_preproc_csv_to_json.yaml")
257
- if err != nil {
258
- return nil, fmt.Errorf("failed to load CSV test suite: %w", err)
259
- }
260
- allTests = append(allTests, csvTests...)
261
-
262
- return allTests, nil
263
-}
264
-
265
-// StepTypeFromString converts Zabbix step type string to StepType.
266
-func StepTypeFromString(s string) (StepType, error) {
267
- switch s {
268
- case "ZBX_PREPROC_MULTIPLIER":
269
- return StepTypeMultiplier, nil
270
- case "ZBX_PREPROC_TRIM":
271
- return StepTypeTrim, nil
272
- case "ZBX_PREPROC_RTRIM":
273
- return StepTypeRTrim, nil
274
- case "ZBX_PREPROC_LTRIM":
275
- return StepTypeLTrim, nil
276
- case "ZBX_PREPROC_REGSUB":
277
- return StepTypeRegexSubstitution, nil
278
- case "ZBX_PREPROC_BOOL2DEC":
279
- return StepTypeBool2Dec, nil
280
- case "ZBX_PREPROC_OCT2DEC":
281
- return StepTypeOct2Dec, nil
282
- case "ZBX_PREPROC_HEX2DEC":
283
- return StepTypeHex2Dec, nil
284
- case "ZBX_PREPROC_DELTA_VALUE":
285
- return StepTypeDeltaValue, nil
286
- case "ZBX_PREPROC_DELTA_SPEED":
287
- return StepTypeDeltaSpeed, nil
288
- case "ZBX_PREPROC_XPATH":
289
- return StepTypeXPath, nil
290
- case "ZBX_PREPROC_JSONPATH":
291
- return StepTypeJSONPath, nil
292
- case "ZBX_PREPROC_VALIDATE_RANGE":
293
- return StepTypeValidateRange, nil
294
- case "ZBX_PREPROC_VALIDATE_REGEX":
295
- return StepTypeValidateRegex, nil
296
- case "ZBX_PREPROC_VALIDATE_NOT_REGEX":
297
- return StepTypeValidateNotRegex, nil
298
- case "ZBX_PREPROC_ERROR_FIELD_JSON":
299
- return StepTypeErrorFieldJSON, nil
300
- case "ZBX_PREPROC_ERROR_FIELD_XML":
301
- return StepTypeErrorFieldXML, nil
302
- case "ZBX_PREPROC_ERROR_FIELD_REGEX":
303
- return StepTypeErrorFieldRegex, nil
304
- case "ZBX_PREPROC_THROTTLE_VALUE":
305
- return StepTypeThrottleValue, nil
306
- case "ZBX_PREPROC_THROTTLE_TIMED_VALUE":
307
- return StepTypeThrottleTimedValue, nil
308
- case "ZBX_PREPROC_PROMETHEUS_PATTERN":
309
- return StepTypePrometheusPattern, nil
310
- case "ZBX_PREPROC_PROMETHEUS_TO_JSON":
311
- return StepTypePrometheusToJSON, nil
312
- case "ZBX_PREPROC_CSV_TO_JSON":
313
- return StepTypeCSVToJSON, nil
314
- case "ZBX_PREPROC_STR_REPLACE":
315
- return StepTypeStringReplace, nil
316
- case "ZBX_PREPROC_SNMP_WALK_VALUE":
317
- return StepTypeSNMPWalkValue, nil
318
- case "ZBX_PREPROC_SCRIPT":
319
- return StepTypeJavaScript, nil
320
- case "ZBX_PREPROC_VALIDATE_NOT_SUPPORTED":
321
- return StepTypeValidateNotSupported, nil
322
- case "ZBX_PREPROC_XML_TO_JSON":
323
- return StepTypeXMLToJSON, nil
324
- case "ZBX_PREPROC_SNMP_GET_VALUE":
325
- return StepTypeSNMPGetValue, nil
326
- case "ZBX_PREPROC_SNMP_WALK_TO_JSON":
327
- return StepTypeSNMPWalkToJSON, nil
328
- default:
329
- return -1, fmt.Errorf("unknown step type: %s", s)
330
- }
331
-}
332
-
333
-// ValueFromTestInput converts test input to Value.
334
-func ValueFromTestInput(tc TestCase) (Value, error) {
335
- // Check if this is an error input (from a previous step failure)
336
- if tc.In.Error != nil {
337
- data := ""
338
- if tc.In.Error.Data != nil {
339
- data = fmt.Sprint(tc.In.Error.Data)
340
- }
341
-
342
- timestamp := time.Now()
343
- if tc.In.Error.Time != "" {
344
- t, err := time.Parse("2006-01-02 15:04:05 -07:00", tc.In.Error.Time)
345
- if err == nil {
346
- timestamp = t
347
- }
348
- }
349
-
350
- return Value{
351
- Data: data,
352
- Type: ValueTypeStr, // Errors are always strings
353
- Timestamp: timestamp,
354
- IsError: true, // Mark as error input
355
- }, nil
356
- }
357
-
358
- // Normal value input
359
- vt, err := parseValueType(tc.In.Value.ValueType)
360
- if err != nil {
361
- return Value{}, err
362
- }
363
-
364
- data := ""
365
- if tc.In.Value.Data != nil {
366
- data = fmt.Sprint(tc.In.Value.Data)
367
- }
368
-
369
- timestamp := time.Now()
370
- if tc.In.Value.Time != "" {
371
- // Parse time in format: 2017-10-29 03:15:00 +03:00
372
- t, err := time.Parse("2006-01-02 15:04:05 -07:00", tc.In.Value.Time)
373
- if err == nil {
374
- timestamp = t
375
- }
376
- }
377
-
378
- return Value{
379
- Data: data,
380
- Type: vt,
381
- Timestamp: timestamp,
382
- IsError: false, // Normal value, not an error
383
- }, nil
384
-}
385
-
386
-// HistoryValueFromTestInput converts history value from test input to Value
387
-func HistoryValueFromTestInput(tc TestCase) *Value {
388
- // Check both possible field names
389
- histValue := tc.In.HistoryValue
390
- if histValue == nil {
391
- histValue = tc.In.History
392
- }
393
- if histValue == nil {
394
- return nil
395
- }
396
-
397
- data := ""
398
- if histValue.Data != nil {
399
- data = fmt.Sprint(histValue.Data)
400
- }
401
-
402
- timestamp := time.Now()
403
- if histValue.Time != "" {
404
- t, err := time.Parse("2006-01-02 15:04:05 -07:00", histValue.Time)
405
- if err == nil {
406
- timestamp = t
407
- }
408
- }
409
-
410
- return &Value{
411
- Data: data,
412
- Type: ValueTypeStr,
413
- Timestamp: timestamp,
414
- }
415
-}
416
-
417
-// StepFromTestInput converts test input to Step.
418
-func StepFromTestInput(tc TestCase) (Step, error) {
419
- stepType, err := StepTypeFromString(tc.In.Step.Type)
420
- if err != nil {
421
- return Step{}, err
422
- }
423
-
424
- // Parse error handler if provided
425
- errorHandler := ErrorHandler{Action: ErrorActionDefault}
426
- if tc.In.Step.ErrorHandler != "" {
427
- errorHandler = parseErrorHandler(tc.In.Step.ErrorHandler, tc.In.Step.ErrorHandlerParam)
428
- }
429
-
430
- return Step{
431
- Type: stepType,
432
- Params: tc.In.Step.Params,
433
- ErrorHandler: errorHandler,
434
- }, nil
435
-}
436
-
437
-// parseErrorHandler converts Zabbix error handler string to ErrorHandler
438
-func parseErrorHandler(handlerStr, paramStr string) ErrorHandler {
439
- switch handlerStr {
440
- case "ZBX_PREPROC_FAIL_DEFAULT":
441
- return ErrorHandler{Action: ErrorActionDefault}
442
- case "ZBX_PREPROC_FAIL_DISCARD_VALUE":
443
- return ErrorHandler{Action: ErrorActionDiscard}
444
- case "ZBX_PREPROC_FAIL_SET_VALUE":
445
- return ErrorHandler{Action: ErrorActionSetValue, Params: paramStr}
446
- case "ZBX_PREPROC_FAIL_SET_ERROR":
447
- return ErrorHandler{Action: ErrorActionSetError, Params: paramStr}
448
- default:
449
- return ErrorHandler{Action: ErrorActionDefault}
450
- }
451
-}
452
-
453
-// ExpectedOutputFromTestCase gets expected output from test case.
454
-func ExpectedOutputFromTestCase(tc TestCase) (string, error) {
455
- output := ""
456
- if tc.Out.Value != nil {
457
- output = fmt.Sprint(tc.Out.Value)
458
- }
459
- return output, nil
460
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/validation.go
deleted
-186
@@ -1,186 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "strings"
6
-)
7
-
8
-// ValidateStep performs early validation of step parameters.
9
-// This catches common errors before execution, useful for pipeline validation.
10
-// Note: Does not perform deep validation (regex compilation, JSONPath parsing, etc.)
11
-// as those are checked during execution.
12
-func ValidateStep(step Step) error {
13
- // Check step type is in valid range
14
- if step.Type < 0 {
15
- return fmt.Errorf("invalid step type: %d (must be >= 0)", step.Type)
16
- }
17
-
18
- // Validate parameters based on step type
19
- switch step.Type {
20
- // Steps that require non-empty parameters
21
- case StepTypeMultiplier:
22
- if step.Params == "" {
23
- return fmt.Errorf("multiplier step requires a numeric parameter")
24
- }
25
-
26
- case StepTypeRegexSubstitution:
27
- if step.Params == "" {
28
- return fmt.Errorf("regex substitution requires pattern and replacement (pattern\\nreplacement)")
29
- }
30
- if !strings.Contains(step.Params, "\n") {
31
- return fmt.Errorf("regex substitution requires newline-separated pattern and replacement")
32
- }
33
-
34
- case StepTypeStringReplace:
35
- if step.Params == "" {
36
- return fmt.Errorf("string replace requires search and replacement (search\\nreplacement)")
37
- }
38
- if !strings.Contains(step.Params, "\n") {
39
- return fmt.Errorf("string replace requires newline-separated search and replacement")
40
- }
41
-
42
- case StepTypeJSONPath, StepTypeJSONPathMulti:
43
- if step.Params == "" {
44
- return fmt.Errorf("jsonpath step requires a jsonpath expression parameter")
45
- }
46
-
47
- case StepTypeXPath:
48
- if step.Params == "" {
49
- return fmt.Errorf("xpath step requires an xpath expression parameter")
50
- }
51
-
52
- case StepTypePrometheusPattern:
53
- if step.Params == "" {
54
- return fmt.Errorf("prometheus pattern requires a pattern parameter")
55
- }
56
-
57
- case StepTypePrometheusToJSON, StepTypePrometheusToJSONMulti:
58
- if step.Params == "" {
59
- return fmt.Errorf("prometheus to json requires a pattern parameter")
60
- }
61
-
62
- case StepTypeCSVToJSON, StepTypeCSVToJSONMulti:
63
- parts := strings.Split(step.Params, "\n")
64
- if len(parts) < 3 {
65
- return fmt.Errorf("csv to json requires 3 parameters: delimiter, quote, header (got %d)", len(parts))
66
- }
67
-
68
- case StepTypeSNMPWalkValue:
69
- parts := strings.Split(step.Params, "\n")
70
- if len(parts) < 2 {
71
- return fmt.Errorf("snmp walk value requires 2 parameters: oid, format (got %d)", len(parts))
72
- }
73
-
74
- case StepTypeSNMPGetValue:
75
- if step.Params == "" {
76
- return fmt.Errorf("snmp get value requires format mode parameter")
77
- }
78
-
79
- case StepTypeSNMPWalkToJSON, StepTypeSNMPWalkToJSONMulti:
80
- if step.Params == "" {
81
- return fmt.Errorf("snmp walk to json requires at least one triplet (macro, oid, format)")
82
- }
83
- parts := strings.Split(step.Params, "\n")
84
- if len(parts)%3 != 0 {
85
- return fmt.Errorf("snmp walk to json requires parameters in triplets of (macro, oid, format), got %d parameters", len(parts))
86
- }
87
-
88
- case StepTypeValidateRange:
89
- if step.Params == "" {
90
- return fmt.Errorf("validate range requires min and max parameters")
91
- }
92
-
93
- case StepTypeValidateRegex:
94
- if step.Params == "" {
95
- return fmt.Errorf("validate regex requires a pattern parameter")
96
- }
97
-
98
- case StepTypeValidateNotRegex:
99
- if step.Params == "" {
100
- return fmt.Errorf("validate not regex requires a pattern parameter")
101
- }
102
-
103
- case StepTypeValidateNotSupported:
104
- if step.Params == "" {
105
- return fmt.Errorf("validate not supported requires an error message parameter")
106
- }
107
-
108
- case StepTypeErrorFieldJSON:
109
- if step.Params == "" {
110
- return fmt.Errorf("error field json requires a jsonpath expression parameter")
111
- }
112
-
113
- case StepTypeErrorFieldXML:
114
- if step.Params == "" {
115
- return fmt.Errorf("error field xml requires an xpath expression parameter")
116
- }
117
-
118
- case StepTypeErrorFieldRegex:
119
- if step.Params == "" {
120
- return fmt.Errorf("error field regex requires a pattern parameter")
121
- }
122
-
123
- case StepTypeThrottleValue:
124
- if step.Params == "" {
125
- return fmt.Errorf("throttle value requires a time period parameter")
126
- }
127
-
128
- case StepTypeThrottleTimedValue:
129
- if step.Params == "" {
130
- return fmt.Errorf("throttle timed value requires a time period parameter")
131
- }
132
-
133
- case StepTypeJavaScript:
134
- if step.Params == "" {
135
- return fmt.Errorf("javascript step requires code parameter")
136
- }
137
-
138
- case StepTypeDeltaValue, StepTypeDeltaSpeed:
139
- // Params optional for delta steps (defaults handled in execution)
140
-
141
- case StepTypeTrim, StepTypeRTrim, StepTypeLTrim:
142
- // Params optional for trim (defaults to whitespace)
143
-
144
- case StepTypeBool2Dec, StepTypeOct2Dec, StepTypeHex2Dec:
145
- // No parameters required for conversion steps
146
-
147
- default:
148
- // Unknown step type - not necessarily an error (future extensibility)
149
- // The actual execution will handle this
150
- }
151
-
152
- // Validate error handler
153
- switch step.ErrorHandler.Action {
154
- case ErrorActionDefault, ErrorActionDiscard:
155
- // No params required
156
-
157
- case ErrorActionSetValue:
158
- // Params should contain the value to set (can be empty string)
159
-
160
- case ErrorActionSetError:
161
- if step.ErrorHandler.Params == "" {
162
- return fmt.Errorf("error handler set_error requires an error message parameter")
163
- }
164
-
165
- default:
166
- return fmt.Errorf("invalid error handler action: %d", step.ErrorHandler.Action)
167
- }
168
-
169
- return nil
170
-}
171
-
172
-// ValidatePipeline validates all steps in a pipeline before execution.
173
-// This is useful for catching configuration errors early, especially in multi-step pipelines.
174
-func ValidatePipeline(steps []Step) error {
175
- if len(steps) == 0 {
176
- return fmt.Errorf("pipeline cannot be empty")
177
- }
178
-
179
- for i, step := range steps {
180
- if err := ValidateStep(step); err != nil {
181
- return fmt.Errorf("step %d: %w", i, err)
182
- }
183
- }
184
-
185
- return nil
186
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/validation_test.go
deleted
-292
@@ -1,292 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "strings"
5
- "testing"
6
-)
7
-
8
-func TestValidateStep(t *testing.T) {
9
- tests := []struct {
10
- name string
11
- step Step
12
- wantErr bool
13
- errMsg string
14
- }{
15
- // Valid steps
16
- {
17
- name: "valid multiplier",
18
- step: Step{Type: StepTypeMultiplier, Params: "2.5"},
19
- wantErr: false,
20
- },
21
- {
22
- name: "valid jsonpath",
23
- step: Step{Type: StepTypeJSONPath, Params: "$.cpu"},
24
- wantErr: false,
25
- },
26
- {
27
- name: "valid csv to json",
28
- step: Step{Type: StepTypeCSVToJSON, Params: ",\n\"\n1"},
29
- wantErr: false,
30
- },
31
- {
32
- name: "valid snmp walk to json",
33
- step: Step{Type: StepTypeSNMPWalkToJSON, Params: "{#NAME}\n.1.3.6.1.2.1.1\n1"},
34
- wantErr: false,
35
- },
36
- {
37
- name: "valid trim with params",
38
- step: Step{Type: StepTypeTrim, Params: " "},
39
- wantErr: false,
40
- },
41
- {
42
- name: "valid trim without params",
43
- step: Step{Type: StepTypeTrim, Params: ""},
44
- wantErr: false,
45
- },
46
- {
47
- name: "valid bool2dec",
48
- step: Step{Type: StepTypeBool2Dec, Params: ""},
49
- wantErr: false,
50
- },
51
-
52
- // Invalid steps - negative type
53
- {
54
- name: "negative step type",
55
- step: Step{Type: StepType(-1), Params: ""},
56
- wantErr: true,
57
- errMsg: "invalid step type",
58
- },
59
-
60
- // Invalid steps - missing required params
61
- {
62
- name: "multiplier missing params",
63
- step: Step{Type: StepTypeMultiplier, Params: ""},
64
- wantErr: true,
65
- errMsg: "multiplier step requires a numeric parameter",
66
- },
67
- {
68
- name: "jsonpath missing params",
69
- step: Step{Type: StepTypeJSONPath, Params: ""},
70
- wantErr: true,
71
- errMsg: "jsonpath step requires",
72
- },
73
- {
74
- name: "xpath missing params",
75
- step: Step{Type: StepTypeXPath, Params: ""},
76
- wantErr: true,
77
- errMsg: "xpath step requires",
78
- },
79
- {
80
- name: "prometheus pattern missing params",
81
- step: Step{Type: StepTypePrometheusPattern, Params: ""},
82
- wantErr: true,
83
- errMsg: "prometheus pattern requires",
84
- },
85
- {
86
- name: "javascript missing params",
87
- step: Step{Type: StepTypeJavaScript, Params: ""},
88
- wantErr: true,
89
- errMsg: "javascript step requires",
90
- },
91
-
92
- // Invalid steps - malformed params
93
- {
94
- name: "regex substitution missing newline",
95
- step: Step{Type: StepTypeRegexSubstitution, Params: "pattern"},
96
- wantErr: true,
97
- errMsg: "requires newline-separated",
98
- },
99
- {
100
- name: "string replace missing newline",
101
- step: Step{Type: StepTypeStringReplace, Params: "search"},
102
- wantErr: true,
103
- errMsg: "requires newline-separated",
104
- },
105
- {
106
- name: "csv to json insufficient params",
107
- step: Step{Type: StepTypeCSVToJSON, Params: ",\n\""},
108
- wantErr: true,
109
- errMsg: "requires 3 parameters",
110
- },
111
- {
112
- name: "snmp walk value insufficient params",
113
- step: Step{Type: StepTypeSNMPWalkValue, Params: ".1.3.6.1"},
114
- wantErr: true,
115
- errMsg: "requires 2 parameters",
116
- },
117
- {
118
- name: "snmp walk to json non-triplet params",
119
- step: Step{Type: StepTypeSNMPWalkToJSON, Params: "{#NAME}\n.1.3.6.1"},
120
- wantErr: true,
121
- errMsg: "requires parameters in triplets",
122
- },
123
- {
124
- name: "snmp walk to json empty params",
125
- step: Step{Type: StepTypeSNMPWalkToJSON, Params: ""},
126
- wantErr: true,
127
- errMsg: "requires at least one triplet",
128
- },
129
-
130
- // Error handler validation
131
- {
132
- name: "valid error handler - default",
133
- step: Step{
134
- Type: StepTypeJSONPath,
135
- Params: "$.value",
136
- ErrorHandler: ErrorHandler{Action: ErrorActionDefault},
137
- },
138
- wantErr: false,
139
- },
140
- {
141
- name: "valid error handler - discard",
142
- step: Step{
143
- Type: StepTypeJSONPath,
144
- Params: "$.value",
145
- ErrorHandler: ErrorHandler{Action: ErrorActionDiscard},
146
- },
147
- wantErr: false,
148
- },
149
- {
150
- name: "valid error handler - set value",
151
- step: Step{
152
- Type: StepTypeJSONPath,
153
- Params: "$.value",
154
- ErrorHandler: ErrorHandler{Action: ErrorActionSetValue, Params: "0"},
155
- },
156
- wantErr: false,
157
- },
158
- {
159
- name: "valid error handler - set value empty",
160
- step: Step{
161
- Type: StepTypeJSONPath,
162
- Params: "$.value",
163
- ErrorHandler: ErrorHandler{Action: ErrorActionSetValue, Params: ""},
164
- },
165
- wantErr: false,
166
- },
167
- {
168
- name: "valid error handler - set error",
169
- step: Step{
170
- Type: StepTypeJSONPath,
171
- Params: "$.value",
172
- ErrorHandler: ErrorHandler{Action: ErrorActionSetError, Params: "Custom error"},
173
- },
174
- wantErr: false,
175
- },
176
- {
177
- name: "invalid error handler - set error missing message",
178
- step: Step{
179
- Type: StepTypeJSONPath,
180
- Params: "$.value",
181
- ErrorHandler: ErrorHandler{Action: ErrorActionSetError, Params: ""},
182
- },
183
- wantErr: true,
184
- errMsg: "requires an error message parameter",
185
- },
186
- {
187
- name: "invalid error handler - unknown action",
188
- step: Step{
189
- Type: StepTypeJSONPath,
190
- Params: "$.value",
191
- ErrorHandler: ErrorHandler{Action: ErrorAction(99)},
192
- },
193
- wantErr: true,
194
- errMsg: "invalid error handler action",
195
- },
196
- }
197
-
198
- for _, tt := range tests {
199
- t.Run(tt.name, func(t *testing.T) {
200
- err := ValidateStep(tt.step)
201
- if tt.wantErr {
202
- if err == nil {
203
- t.Errorf("ValidateStep() expected error containing %q, got nil", tt.errMsg)
204
- return
205
- }
206
- if !strings.Contains(err.Error(), tt.errMsg) {
207
- t.Errorf("ValidateStep() error = %v, want error containing %q", err, tt.errMsg)
208
- }
209
- } else {
210
- if err != nil {
211
- t.Errorf("ValidateStep() unexpected error = %v", err)
212
- }
213
- }
214
- })
215
- }
216
-}
217
-
218
-func TestValidatePipeline(t *testing.T) {
219
- tests := []struct {
220
- name string
221
- steps []Step
222
- wantErr bool
223
- errMsg string
224
- }{
225
- {
226
- name: "valid pipeline",
227
- steps: []Step{
228
- {Type: StepTypeJSONPath, Params: "$.cpu"},
229
- {Type: StepTypeMultiplier, Params: "100"},
230
- {Type: StepTypeTrim, Params: ""},
231
- },
232
- wantErr: false,
233
- },
234
- {
235
- name: "empty pipeline",
236
- steps: []Step{},
237
- wantErr: true,
238
- errMsg: "pipeline cannot be empty",
239
- },
240
- {
241
- name: "pipeline with invalid step at index 1",
242
- steps: []Step{
243
- {Type: StepTypeJSONPath, Params: "$.cpu"},
244
- {Type: StepTypeMultiplier, Params: ""}, // Missing params
245
- {Type: StepTypeTrim, Params: ""},
246
- },
247
- wantErr: true,
248
- errMsg: "step 1:",
249
- },
250
- {
251
- name: "pipeline with invalid step at index 0",
252
- steps: []Step{
253
- {Type: StepTypeJSONPath, Params: ""}, // Missing params
254
- {Type: StepTypeMultiplier, Params: "100"},
255
- },
256
- wantErr: true,
257
- errMsg: "step 0:",
258
- },
259
- {
260
- name: "pipeline with invalid error handler",
261
- steps: []Step{
262
- {Type: StepTypeJSONPath, Params: "$.cpu"},
263
- {
264
- Type: StepTypeMultiplier,
265
- Params: "100",
266
- ErrorHandler: ErrorHandler{Action: ErrorActionSetError, Params: ""},
267
- },
268
- },
269
- wantErr: true,
270
- errMsg: "step 1:",
271
- },
272
- }
273
-
274
- for _, tt := range tests {
275
- t.Run(tt.name, func(t *testing.T) {
276
- err := ValidatePipeline(tt.steps)
277
- if tt.wantErr {
278
- if err == nil {
279
- t.Errorf("ValidatePipeline() expected error containing %q, got nil", tt.errMsg)
280
- return
281
- }
282
- if !strings.Contains(err.Error(), tt.errMsg) {
283
- t.Errorf("ValidatePipeline() error = %v, want error containing %q", err, tt.errMsg)
284
- }
285
- } else {
286
- if err != nil {
287
- t.Errorf("ValidatePipeline() unexpected error = %v", err)
288
- }
289
- }
290
- })
291
- }
292
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/xml_step.go
deleted
-144
@@ -1,144 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "fmt"
6
- "strings"
7
-
8
- "github.com/antchfx/xmlquery"
9
-)
10
-
11
-// xmlToJSON converts XML to JSON following Zabbix serialization rules.
12
-//
13
-// Zabbix XML to JSON Serialization Rules:
14
-// 1. Attributes: Prepended with '@'
15
-// <xml foo="FOO"> → {"xml": {"@foo": "FOO"}}
16
-// 2. Self-closing elements: Become null
17
-// <foo/> → {"foo": null}
18
-// 3. Empty attributes: Preserved as empty strings
19
-// bar="" → "@bar": ""
20
-// 4. Repeated elements: Consolidated into arrays
21
-// <foo>A</foo><foo>B</foo> → "foo": ["A", "B"]
22
-// 5. Simple text elements: Direct string values
23
-// <foo>BAZ</foo> → {"foo": "BAZ"}
24
-// 6. Text with attributes: Uses #text key
25
-// <foo bar="BAR">BAZ</foo> → {"foo": {"@bar": "BAR", "#text": "BAZ"}}
26
-func xmlToJSON(value Value, paramStr string) (Value, error) {
27
- // Parse XML
28
- doc, err := xmlquery.Parse(strings.NewReader(value.Data))
29
- if err != nil {
30
- return Value{}, fmt.Errorf("invalid XML: %w", err)
31
- }
32
-
33
- // Convert to JSON structure
34
- result := convertNodeToJSON(doc)
35
-
36
- // Serialize to JSON string
37
- jsonBytes, err := json.Marshal(result)
38
- if err != nil {
39
- return Value{}, fmt.Errorf("JSON serialization failed: %w", err)
40
- }
41
-
42
- return Value{Data: string(jsonBytes), Type: ValueTypeStr}, nil
43
-}
44
-
45
-// convertNodeToJSON converts an XML node to a JSON-compatible structure
46
-func convertNodeToJSON(node *xmlquery.Node) interface{} {
47
- if node == nil {
48
- return nil
49
- }
50
-
51
- // Handle document node - process root element
52
- if node.Type == xmlquery.DocumentNode {
53
- for child := node.FirstChild; child != nil; child = child.NextSibling {
54
- if child.Type == xmlquery.ElementNode {
55
- // Return the root element as a map with element name as key
56
- return map[string]interface{}{
57
- child.Data: convertElementToJSON(child),
58
- }
59
- }
60
- }
61
- return nil
62
- }
63
-
64
- // For element nodes, wrap in a map with element name
65
- if node.Type == xmlquery.ElementNode {
66
- return map[string]interface{}{
67
- node.Data: convertElementToJSON(node),
68
- }
69
- }
70
-
71
- return nil
72
-}
73
-
74
-// convertElementToJSON converts an XML element to JSON following Zabbix rules
75
-func convertElementToJSON(node *xmlquery.Node) interface{} {
76
- // Collect attributes (Rule 1: prepend with '@')
77
- attrs := make(map[string]interface{})
78
- for _, attr := range node.Attr {
79
- // Rule 3: Empty attributes preserved as empty strings
80
- attrs["@"+attr.Name.Local] = attr.Value
81
- }
82
-
83
- // Collect child elements grouped by name
84
- children := make(map[string][]interface{})
85
- var textContent strings.Builder
86
- hasChildren := false
87
-
88
- for child := node.FirstChild; child != nil; child = child.NextSibling {
89
- switch child.Type {
90
- case xmlquery.ElementNode:
91
- hasChildren = true
92
- childValue := convertElementToJSON(child)
93
- children[child.Data] = append(children[child.Data], childValue)
94
-
95
- case xmlquery.TextNode, xmlquery.CharDataNode:
96
- // Collect text content (trim each segment but preserve structure)
97
- text := strings.TrimSpace(child.Data)
98
- if text != "" {
99
- if textContent.Len() > 0 {
100
- textContent.WriteString(" ")
101
- }
102
- textContent.WriteString(text)
103
- }
104
- }
105
- }
106
-
107
- text := textContent.String()
108
-
109
- // Rule 2: Self-closing elements (no attributes, no children, no text) → null
110
- if len(attrs) == 0 && !hasChildren && text == "" {
111
- return nil
112
- }
113
-
114
- // Rule 5: Simple text elements (no attributes, no children, only text) → direct string
115
- if len(attrs) == 0 && !hasChildren && text != "" {
116
- return text
117
- }
118
-
119
- // Build result object
120
- result := make(map[string]interface{})
121
-
122
- // Add attributes first
123
- for k, v := range attrs {
124
- result[k] = v
125
- }
126
-
127
- // Rule 6: Text with attributes → use #text key
128
- if text != "" {
129
- result["#text"] = text
130
- }
131
-
132
- // Rule 4: Repeated elements → arrays
133
- for name, values := range children {
134
- if len(values) == 1 {
135
- // Single child - add directly
136
- result[name] = values[0]
137
- } else {
138
- // Multiple children with same name - create array
139
- result[name] = values
140
- }
141
- }
142
-
143
- return result
144
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/xml_step_test.go
deleted
-502
@@ -1,502 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "testing"
6
- "time"
7
-)
8
-
9
-// TestXMLToJSON_Rule1_Attributes tests Rule 1: Attributes prepended with '@'
10
-func TestXMLToJSON_Rule1_Attributes(t *testing.T) {
11
- tests := []struct {
12
- name string
13
- xml string
14
- expected string
15
- }{
16
- {
17
- name: "Single attribute",
18
- xml: `<xml foo="FOO"></xml>`,
19
- expected: `{"xml":{"@foo":"FOO"}}`,
20
- },
21
- {
22
- name: "Multiple attributes",
23
- xml: `<root id="123" name="test" active="true"></root>`,
24
- expected: `{"root":{"@active":"true","@id":"123","@name":"test"}}`,
25
- },
26
- {
27
- name: "Nested element with attributes",
28
- xml: `<root><child attr="value"/></root>`,
29
- expected: `{"root":{"child":{"@attr":"value"}}}`,
30
- },
31
- }
32
-
33
- for _, tt := range tests {
34
- t.Run(tt.name, func(t *testing.T) {
35
- value := Value{
36
- Data: tt.xml,
37
- Type: ValueTypeStr,
38
- Timestamp: time.Now(),
39
- }
40
-
41
- result, err := xmlToJSON(value, "")
42
- if err != nil {
43
- t.Fatalf("xmlToJSON() error = %v", err)
44
- }
45
-
46
- // Compare JSON (normalize by parsing and re-marshaling)
47
- var got, want interface{}
48
- if err := json.Unmarshal([]byte(result.Data), &got); err != nil {
49
- t.Fatalf("Failed to parse result JSON: %v", err)
50
- }
51
- if err := json.Unmarshal([]byte(tt.expected), &want); err != nil {
52
- t.Fatalf("Failed to parse expected JSON: %v", err)
53
- }
54
-
55
- gotJSON, _ := json.Marshal(got)
56
- wantJSON, _ := json.Marshal(want)
57
-
58
- if string(gotJSON) != string(wantJSON) {
59
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
60
- }
61
- })
62
- }
63
-}
64
-
65
-// TestXMLToJSON_Rule2_SelfClosingElements tests Rule 2: Self-closing elements become null
66
-func TestXMLToJSON_Rule2_SelfClosingElements(t *testing.T) {
67
- tests := []struct {
68
- name string
69
- xml string
70
- expected string
71
- }{
72
- {
73
- name: "Single self-closing element",
74
- xml: `<root><foo/></root>`,
75
- expected: `{"root":{"foo":null}}`,
76
- },
77
- {
78
- name: "Multiple self-closing elements",
79
- xml: `<root><foo/><bar/><baz/></root>`,
80
- expected: `{"root":{"bar":null,"baz":null,"foo":null}}`,
81
- },
82
- {
83
- name: "Root self-closing",
84
- xml: `<empty/>`,
85
- expected: `{"empty":null}`,
86
- },
87
- }
88
-
89
- for _, tt := range tests {
90
- t.Run(tt.name, func(t *testing.T) {
91
- value := Value{
92
- Data: tt.xml,
93
- Type: ValueTypeStr,
94
- Timestamp: time.Now(),
95
- }
96
-
97
- result, err := xmlToJSON(value, "")
98
- if err != nil {
99
- t.Fatalf("xmlToJSON() error = %v", err)
100
- }
101
-
102
- var got, want interface{}
103
- json.Unmarshal([]byte(result.Data), &got)
104
- json.Unmarshal([]byte(tt.expected), &want)
105
-
106
- gotJSON, _ := json.Marshal(got)
107
- wantJSON, _ := json.Marshal(want)
108
-
109
- if string(gotJSON) != string(wantJSON) {
110
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
111
- }
112
- })
113
- }
114
-}
115
-
116
-// TestXMLToJSON_Rule3_EmptyAttributes tests Rule 3: Empty attributes preserved as empty strings
117
-func TestXMLToJSON_Rule3_EmptyAttributes(t *testing.T) {
118
- tests := []struct {
119
- name string
120
- xml string
121
- expected string
122
- }{
123
- {
124
- name: "Empty attribute value",
125
- xml: `<root bar=""></root>`,
126
- expected: `{"root":{"@bar":""}}`,
127
- },
128
- {
129
- name: "Mix of empty and non-empty attributes",
130
- xml: `<root foo="FOO" bar="" baz="BAZ"></root>`,
131
- expected: `{"root":{"@bar":"","@baz":"BAZ","@foo":"FOO"}}`,
132
- },
133
- }
134
-
135
- for _, tt := range tests {
136
- t.Run(tt.name, func(t *testing.T) {
137
- value := Value{
138
- Data: tt.xml,
139
- Type: ValueTypeStr,
140
- Timestamp: time.Now(),
141
- }
142
-
143
- result, err := xmlToJSON(value, "")
144
- if err != nil {
145
- t.Fatalf("xmlToJSON() error = %v", err)
146
- }
147
-
148
- var got, want interface{}
149
- json.Unmarshal([]byte(result.Data), &got)
150
- json.Unmarshal([]byte(tt.expected), &want)
151
-
152
- gotJSON, _ := json.Marshal(got)
153
- wantJSON, _ := json.Marshal(want)
154
-
155
- if string(gotJSON) != string(wantJSON) {
156
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
157
- }
158
- })
159
- }
160
-}
161
-
162
-// TestXMLToJSON_Rule4_RepeatedElements tests Rule 4: Repeated elements consolidated into arrays
163
-func TestXMLToJSON_Rule4_RepeatedElements(t *testing.T) {
164
- tests := []struct {
165
- name string
166
- xml string
167
- expected string
168
- }{
169
- {
170
- name: "Three identical elements",
171
- xml: `<root><foo>BAR</foo><foo>BAZ</foo><foo>QUX</foo></root>`,
172
- expected: `{"root":{"foo":["BAR","BAZ","QUX"]}}`,
173
- },
174
- {
175
- name: "Two identical elements",
176
- xml: `<root><item>A</item><item>B</item></root>`,
177
- expected: `{"root":{"item":["A","B"]}}`,
178
- },
179
- {
180
- name: "Mixed single and repeated elements",
181
- xml: `<root><foo>A</foo><bar>B</bar><foo>C</foo></root>`,
182
- expected: `{"root":{"bar":"B","foo":["A","C"]}}`,
183
- },
184
- {
185
- name: "Repeated complex elements",
186
- xml: `<root><item id="1">A</item><item id="2">B</item></root>`,
187
- expected: `{"root":{"item":[{"#text":"A","@id":"1"},{"#text":"B","@id":"2"}]}}`,
188
- },
189
- }
190
-
191
- for _, tt := range tests {
192
- t.Run(tt.name, func(t *testing.T) {
193
- value := Value{
194
- Data: tt.xml,
195
- Type: ValueTypeStr,
196
- Timestamp: time.Now(),
197
- }
198
-
199
- result, err := xmlToJSON(value, "")
200
- if err != nil {
201
- t.Fatalf("xmlToJSON() error = %v", err)
202
- }
203
-
204
- var got, want interface{}
205
- json.Unmarshal([]byte(result.Data), &got)
206
- json.Unmarshal([]byte(tt.expected), &want)
207
-
208
- gotJSON, _ := json.Marshal(got)
209
- wantJSON, _ := json.Marshal(want)
210
-
211
- if string(gotJSON) != string(wantJSON) {
212
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
213
- }
214
- })
215
- }
216
-}
217
-
218
-// TestXMLToJSON_Rule5_SimpleTextElements tests Rule 5: Simple text elements become direct strings
219
-func TestXMLToJSON_Rule5_SimpleTextElements(t *testing.T) {
220
- tests := []struct {
221
- name string
222
- xml string
223
- expected string
224
- }{
225
- {
226
- name: "Simple text element",
227
- xml: `<root><foo>BAZ</foo></root>`,
228
- expected: `{"root":{"foo":"BAZ"}}`,
229
- },
230
- {
231
- name: "Multiple simple text elements",
232
- xml: `<root><name>John</name><age>30</age><city>NYC</city></root>`,
233
- expected: `{"root":{"age":"30","city":"NYC","name":"John"}}`,
234
- },
235
- {
236
- name: "Nested simple text",
237
- xml: `<root><outer><inner>value</inner></outer></root>`,
238
- expected: `{"root":{"outer":{"inner":"value"}}}`,
239
- },
240
- {
241
- name: "Text with whitespace trimming",
242
- xml: `<root><foo> BAR </foo></root>`,
243
- expected: `{"root":{"foo":"BAR"}}`,
244
- },
245
- }
246
-
247
- for _, tt := range tests {
248
- t.Run(tt.name, func(t *testing.T) {
249
- value := Value{
250
- Data: tt.xml,
251
- Type: ValueTypeStr,
252
- Timestamp: time.Now(),
253
- }
254
-
255
- result, err := xmlToJSON(value, "")
256
- if err != nil {
257
- t.Fatalf("xmlToJSON() error = %v", err)
258
- }
259
-
260
- var got, want interface{}
261
- json.Unmarshal([]byte(result.Data), &got)
262
- json.Unmarshal([]byte(tt.expected), &want)
263
-
264
- gotJSON, _ := json.Marshal(got)
265
- wantJSON, _ := json.Marshal(want)
266
-
267
- if string(gotJSON) != string(wantJSON) {
268
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
269
- }
270
- })
271
- }
272
-}
273
-
274
-// TestXMLToJSON_Rule6_TextWithAttributes tests Rule 6: Text with attributes uses #text key
275
-func TestXMLToJSON_Rule6_TextWithAttributes(t *testing.T) {
276
- tests := []struct {
277
- name string
278
- xml string
279
- expected string
280
- }{
281
- {
282
- name: "Text with single attribute",
283
- xml: `<root><foo bar="BAR">BAZ</foo></root>`,
284
- expected: `{"root":{"foo":{"#text":"BAZ","@bar":"BAR"}}}`,
285
- },
286
- {
287
- name: "Text with multiple attributes",
288
- xml: `<root><item id="1" type="text">Value</item></root>`,
289
- expected: `{"root":{"item":{"#text":"Value","@id":"1","@type":"text"}}}`,
290
- },
291
- {
292
- name: "Root element with text and attributes",
293
- xml: `<root version="1.0">Content</root>`,
294
- expected: `{"root":{"#text":"Content","@version":"1.0"}}`,
295
- },
296
- }
297
-
298
- for _, tt := range tests {
299
- t.Run(tt.name, func(t *testing.T) {
300
- value := Value{
301
- Data: tt.xml,
302
- Type: ValueTypeStr,
303
- Timestamp: time.Now(),
304
- }
305
-
306
- result, err := xmlToJSON(value, "")
307
- if err != nil {
308
- t.Fatalf("xmlToJSON() error = %v", err)
309
- }
310
-
311
- var got, want interface{}
312
- json.Unmarshal([]byte(result.Data), &got)
313
- json.Unmarshal([]byte(tt.expected), &want)
314
-
315
- gotJSON, _ := json.Marshal(got)
316
- wantJSON, _ := json.Marshal(want)
317
-
318
- if string(gotJSON) != string(wantJSON) {
319
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
320
- }
321
- })
322
- }
323
-}
324
-
325
-// TestXMLToJSON_ComplexCombinations tests complex combinations of all rules
326
-func TestXMLToJSON_ComplexCombinations(t *testing.T) {
327
- tests := []struct {
328
- name string
329
- xml string
330
- expected string
331
- }{
332
- {
333
- name: "Complex nested structure",
334
- xml: `<root>
335
- <user id="123" active="true">
336
- <name>John Doe</name>
337
- <email/>
338
- <tags>
339
- <tag>admin</tag>
340
- <tag>user</tag>
341
- </tags>
342
- </user>
343
- </root>`,
344
- expected: `{"root":{"user":{"@active":"true","@id":"123","email":null,"name":"John Doe","tags":{"tag":["admin","user"]}}}}`,
345
- },
346
- {
347
- name: "Mixed attributes, text, and children",
348
- xml: `<config version="2.0">
349
- <setting name="timeout">30</setting>
350
- <setting name="retries">3</setting>
351
- <debug enabled="false"/>
352
- </config>`,
353
- expected: `{"config":{"@version":"2.0","debug":{"@enabled":"false"},"setting":[{"#text":"30","@name":"timeout"},{"#text":"3","@name":"retries"}]}}`,
354
- },
355
- {
356
- name: "Real-world API response example",
357
- xml: `<response status="success">
358
- <data>
359
- <item id="1" type="product">Widget</item>
360
- <item id="2" type="product">Gadget</item>
361
- <metadata>
362
- <total>2</total>
363
- <page>1</page>
364
- </metadata>
365
- </data>
366
- </response>`,
367
- expected: `{"response":{"@status":"success","data":{"item":[{"#text":"Widget","@id":"1","@type":"product"},{"#text":"Gadget","@id":"2","@type":"product"}],"metadata":{"page":"1","total":"2"}}}}`,
368
- },
369
- }
370
-
371
- for _, tt := range tests {
372
- t.Run(tt.name, func(t *testing.T) {
373
- value := Value{
374
- Data: tt.xml,
375
- Type: ValueTypeStr,
376
- Timestamp: time.Now(),
377
- }
378
-
379
- result, err := xmlToJSON(value, "")
380
- if err != nil {
381
- t.Fatalf("xmlToJSON() error = %v", err)
382
- }
383
-
384
- var got, want interface{}
385
- json.Unmarshal([]byte(result.Data), &got)
386
- json.Unmarshal([]byte(tt.expected), &want)
387
-
388
- gotJSON, _ := json.Marshal(got)
389
- wantJSON, _ := json.Marshal(want)
390
-
391
- if string(gotJSON) != string(wantJSON) {
392
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
393
- }
394
- })
395
- }
396
-}
397
-
398
-// TestXMLToJSON_ErrorCases tests error handling
399
-func TestXMLToJSON_ErrorCases(t *testing.T) {
400
- tests := []struct {
401
- name string
402
- xml string
403
- wantErr bool
404
- }{
405
- {
406
- name: "Invalid XML - unclosed tag",
407
- xml: `<root><foo>`,
408
- wantErr: true,
409
- },
410
- {
411
- name: "Invalid XML - mismatched tags",
412
- xml: `<root><foo></bar></root>`,
413
- wantErr: true,
414
- },
415
- {
416
- name: "Empty input",
417
- xml: ``,
418
- wantErr: true,
419
- },
420
- {
421
- name: "Not XML",
422
- xml: `{"json": "data"}`,
423
- wantErr: true,
424
- },
425
- }
426
-
427
- for _, tt := range tests {
428
- t.Run(tt.name, func(t *testing.T) {
429
- value := Value{
430
- Data: tt.xml,
431
- Type: ValueTypeStr,
432
- Timestamp: time.Now(),
433
- }
434
-
435
- _, err := xmlToJSON(value, "")
436
- if (err != nil) != tt.wantErr {
437
- t.Errorf("xmlToJSON() error = %v, wantErr %v", err, tt.wantErr)
438
- }
439
- })
440
- }
441
-}
442
-
443
-// TestXMLToJSON_EdgeCases tests edge cases and special scenarios
444
-func TestXMLToJSON_EdgeCases(t *testing.T) {
445
- tests := []struct {
446
- name string
447
- xml string
448
- expected string
449
- }{
450
- {
451
- name: "Empty root element with attributes",
452
- xml: `<root id="1"/>`,
453
- expected: `{"root":{"@id":"1"}}`,
454
- },
455
- {
456
- name: "Only whitespace text",
457
- xml: `<root> </root>`,
458
- expected: `{"root":null}`,
459
- },
460
- {
461
- name: "CDATA section",
462
- xml: `<root><![CDATA[Special <chars> & stuff]]></root>`,
463
- expected: `{"root":"Special <chars> & stuff"}`,
464
- },
465
- {
466
- name: "XML with namespaces",
467
- xml: `<root xmlns:foo="http://example.com"><bar>value</bar></root>`,
468
- expected: `{"root":{"@foo":"http://example.com","bar":"value"}}`,
469
- },
470
- {
471
- name: "Numeric-looking values",
472
- xml: `<root><num>123</num><float>45.67</float></root>`,
473
- expected: `{"root":{"float":"45.67","num":"123"}}`,
474
- },
475
- }
476
-
477
- for _, tt := range tests {
478
- t.Run(tt.name, func(t *testing.T) {
479
- value := Value{
480
- Data: tt.xml,
481
- Type: ValueTypeStr,
482
- Timestamp: time.Now(),
483
- }
484
-
485
- result, err := xmlToJSON(value, "")
486
- if err != nil {
487
- t.Fatalf("xmlToJSON() error = %v", err)
488
- }
489
-
490
- var got, want interface{}
491
- json.Unmarshal([]byte(result.Data), &got)
492
- json.Unmarshal([]byte(tt.expected), &want)
493
-
494
- gotJSON, _ := json.Marshal(got)
495
- wantJSON, _ := json.Marshal(want)
496
-
497
- if string(gotJSON) != string(wantJSON) {
498
- t.Errorf("xmlToJSON() = %s, want %s", gotJSON, wantJSON)
499
- }
500
- })
501
- }
502
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/xml_to_json_test.go
deleted
-267
@@ -1,267 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "encoding/json"
5
- "testing"
6
-)
7
-
8
-// TestXMLToJSONZabbixDocumentation tests all examples from official Zabbix docs.
9
-// Source: https://www.zabbix.com/documentation/6.0/en/manual/config/items/preprocessing/javascript/javascript_objects
10
-func TestXMLToJSONZabbixDocumentation(t *testing.T) {
11
- tests := []struct {
12
- name string
13
- input string
14
- expected string
15
- rule string
16
- }{
17
- {
18
- name: "Rule 1: Attributes prepended with @",
19
- input: `<xml foo="FOO"><bar><baz>BAZ</baz></bar></xml>`,
20
- expected: `{"xml":{"@foo":"FOO","bar":{"baz":"BAZ"}}}`,
21
- rule: "XML attributes will be converted to keys that have their names prepended with '@'",
22
- },
23
- {
24
- name: "Rule 2: Self-closing elements become null",
25
- input: `<xml><foo/></xml>`,
26
- expected: `{"xml":{"foo":null}}`,
27
- rule: "Self-closing elements (<foo/>) will be converted as having 'null' value",
28
- },
29
- {
30
- name: "Rule 3: Empty attributes preserved as empty strings",
31
- input: `<xml><foo bar="" /></xml>`,
32
- expected: `{"xml":{"foo":{"@bar":""}}}`,
33
- rule: "Empty attributes (with \"\" value) will be converted as having empty string ('') value",
34
- },
35
- {
36
- name: "Rule 4: Multiple same-named children become arrays",
37
- input: `<xml><foo>BAR</foo><foo>BAZ</foo><foo>QUX</foo></xml>`,
38
- expected: `{"xml":{"foo":["BAR","BAZ","QUX"]}}`,
39
- rule: "Multiple child nodes with the same element name will be converted to a single key that has an array of values",
40
- },
41
- {
42
- name: "Rule 5: Simple text elements become strings",
43
- input: `<xml><foo>BAZ</foo></xml>`,
44
- expected: `{"xml":{"foo":"BAZ"}}`,
45
- rule: "If a text element has no attributes and no children, it will be converted as a string",
46
- },
47
- {
48
- name: "Rule 6: Text with attributes uses #text",
49
- input: `<xml><foo bar="BAR">BAZ</foo></xml>`,
50
- expected: `{"xml":{"foo":{"@bar":"BAR","#text":"BAZ"}}}`,
51
- rule: "If a text element has no children but has attributes: text content will be converted to an element with the key '#text'",
52
- },
53
- }
54
-
55
- p := NewPreprocessor("test-shard")
56
-
57
- for _, tt := range tests {
58
- t.Run(tt.name, func(t *testing.T) {
59
- input := Value{Data: tt.input, Type: ValueTypeStr}
60
- step := Step{Type: StepTypeXMLToJSON}
61
-
62
- result, err := p.Execute("item1", input, step)
63
- if err != nil {
64
- t.Fatalf("XML to JSON failed: %v", err)
65
- }
66
-
67
- if len(result.Metrics) == 0 {
68
- t.Fatal("Expected metrics in result")
69
- }
70
-
71
- // Compare as JSON structures (order-independent)
72
- var expected, actual interface{}
73
- if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil {
74
- t.Fatalf("Failed to parse expected JSON: %v", err)
75
- }
76
- if err := json.Unmarshal([]byte(result.Metrics[0].Value), &actual); err != nil {
77
- t.Fatalf("Failed to parse actual JSON: %v\nGot: %s", err, result.Metrics[0].Value)
78
- }
79
-
80
- // Marshal back to normalize for comparison
81
- expectedNorm, _ := json.Marshal(expected)
82
- actualNorm, _ := json.Marshal(actual)
83
-
84
- if string(expectedNorm) != string(actualNorm) {
85
- t.Errorf("Rule: %s\nExpected: %s\nGot: %s", tt.rule, tt.expected, result.Metrics[0].Value)
86
- }
87
- })
88
- }
89
-}
90
-
91
-// TestXMLToJSONEdgeCases tests edge cases not in official docs
92
-func TestXMLToJSONEdgeCases(t *testing.T) {
93
- p := NewPreprocessor("test-shard")
94
-
95
- tests := []struct {
96
- name string
97
- input string
98
- expected string
99
- }{
100
- {
101
- name: "Nested elements with attributes",
102
- input: `<root><parent id="1"><child name="test">value</child></parent></root>`,
103
- expected: `{"root":{"parent":{"@id":"1","child":{"@name":"test","#text":"value"}}}}`,
104
- },
105
- {
106
- name: "Multiple different children",
107
- input: `<data><a>1</a><b>2</b><c>3</c></data>`,
108
- expected: `{"data":{"a":"1","b":"2","c":"3"}}`,
109
- },
110
- {
111
- name: "Empty root element",
112
- input: `<empty/>`,
113
- expected: `{"empty":null}`,
114
- },
115
- {
116
- name: "Root with only attributes",
117
- input: `<config version="1.0" debug="true"/>`,
118
- expected: `{"config":{"@version":"1.0","@debug":"true"}}`,
119
- },
120
- {
121
- name: "Mixed repeated and unique children",
122
- input: `<list><item>A</item><item>B</item><unique>C</unique></list>`,
123
- expected: `{"list":{"item":["A","B"],"unique":"C"}}`,
124
- },
125
- {
126
- name: "Deeply nested structure",
127
- input: `<a><b><c><d>deep</d></c></b></a>`,
128
- expected: `{"a":{"b":{"c":{"d":"deep"}}}}`,
129
- },
130
- {
131
- name: "Numeric text content",
132
- input: `<metric><value>42</value><timestamp>1234567890</timestamp></metric>`,
133
- expected: `{"metric":{"value":"42","timestamp":"1234567890"}}`,
134
- },
135
- {
136
- name: "Special characters in text",
137
- input: `<data><msg>Hello & World</msg></data>`,
138
- expected: `{"data":{"msg":"Hello & World"}}`,
139
- },
140
- {
141
- name: "Boolean-like text values",
142
- input: `<flags><enabled>true</enabled><disabled>false</disabled></flags>`,
143
- expected: `{"flags":{"enabled":"true","disabled":"false"}}`,
144
- },
145
- }
146
-
147
- for _, tt := range tests {
148
- t.Run(tt.name, func(t *testing.T) {
149
- input := Value{Data: tt.input, Type: ValueTypeStr}
150
- step := Step{Type: StepTypeXMLToJSON}
151
-
152
- result, err := p.Execute("item1", input, step)
153
- if err != nil {
154
- t.Fatalf("XML to JSON failed: %v", err)
155
- }
156
-
157
- if len(result.Metrics) == 0 {
158
- t.Fatal("Expected metrics in result")
159
- }
160
-
161
- // Compare as JSON structures
162
- var expected, actual interface{}
163
- if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil {
164
- t.Fatalf("Failed to parse expected JSON: %v", err)
165
- }
166
- if err := json.Unmarshal([]byte(result.Metrics[0].Value), &actual); err != nil {
167
- t.Fatalf("Failed to parse actual JSON: %v\nGot: %s", err, result.Metrics[0].Value)
168
- }
169
-
170
- expectedNorm, _ := json.Marshal(expected)
171
- actualNorm, _ := json.Marshal(actual)
172
-
173
- if string(expectedNorm) != string(actualNorm) {
174
- t.Errorf("Expected: %s\nGot: %s", tt.expected, result.Metrics[0].Value)
175
- }
176
- })
177
- }
178
-}
179
-
180
-// TestXMLToJSONErrorHandling tests invalid XML inputs
181
-func TestXMLToJSONErrorHandling(t *testing.T) {
182
- p := NewPreprocessor("test-shard")
183
-
184
- tests := []struct {
185
- name string
186
- input string
187
- }{
188
- {
189
- name: "Malformed XML - unclosed tag",
190
- input: `<root><unclosed>`,
191
- },
192
- {
193
- name: "Malformed XML - mismatched tags",
194
- input: `<root><child></wrong></root>`,
195
- },
196
- {
197
- name: "Empty input",
198
- input: ``,
199
- },
200
- {
201
- name: "Not XML at all",
202
- input: `This is just plain text`,
203
- },
204
- }
205
-
206
- for _, tt := range tests {
207
- t.Run(tt.name, func(t *testing.T) {
208
- input := Value{Data: tt.input, Type: ValueTypeStr}
209
- step := Step{Type: StepTypeXMLToJSON}
210
-
211
- _, err := p.Execute("item1", input, step)
212
- if err == nil {
213
- t.Errorf("Expected error for invalid XML input: %s", tt.input)
214
- }
215
- })
216
- }
217
-}
218
-
219
-// TestXMLToJSONLLDCompatible tests that output is compatible with Zabbix LLD
220
-func TestXMLToJSONLLDCompatible(t *testing.T) {
221
- p := NewPreprocessor("test-shard")
222
-
223
- // Example: XML that could be used for LLD discovery
224
- input := `<discovery>
225
- <host name="server1" ip="192.168.1.1"/>
226
- <host name="server2" ip="192.168.1.2"/>
227
- <host name="server3" ip="192.168.1.3"/>
228
- </discovery>`
229
-
230
- step := Step{Type: StepTypeXMLToJSON}
231
- result, err := p.Execute("item1", Value{Data: input, Type: ValueTypeStr}, step)
232
- if err != nil {
233
- t.Fatalf("XML to JSON failed: %v", err)
234
- }
235
-
236
- // Parse result
237
- var data map[string]interface{}
238
- if err := json.Unmarshal([]byte(result.Metrics[0].Value), &data); err != nil {
239
- t.Fatalf("Failed to parse JSON: %v", err)
240
- }
241
-
242
- // Verify structure is compatible with LLD
243
- discovery, ok := data["discovery"].(map[string]interface{})
244
- if !ok {
245
- t.Fatal("Expected 'discovery' key in output")
246
- }
247
-
248
- hosts, ok := discovery["host"].([]interface{})
249
- if !ok {
250
- t.Fatal("Expected 'host' to be an array (multiple hosts)")
251
- }
252
-
253
- if len(hosts) != 3 {
254
- t.Errorf("Expected 3 hosts, got %d", len(hosts))
255
- }
256
-
257
- // Each host should have @name and @ip attributes
258
- for i, h := range hosts {
259
- host := h.(map[string]interface{})
260
- if _, ok := host["@name"]; !ok {
261
- t.Errorf("Host %d missing @name attribute", i)
262
- }
263
- if _, ok := host["@ip"]; !ok {
264
- t.Errorf("Host %d missing @ip attribute", i)
265
- }
266
- }
267
-}
src/go/plugin/scripts.d/pkg/zabbixpreproc/xpath_step.go
deleted
-107
@@ -1,107 +0,0 @@
1
-package zabbixpreproc
2
-
3
-import (
4
- "fmt"
5
- "math"
6
- "regexp"
7
- "strings"
8
-
9
- "github.com/antchfx/xmlquery"
10
- "github.com/antchfx/xpath"
11
-)
12
-
13
-// XPath regex patterns compiled once at package init
14
-var (
15
- // Matches empty XML tags like <tag></tag> or <tag attr="value"></tag>
16
- xpathEmptyTagRegex = regexp.MustCompile(`<([a-zA-Z][a-zA-Z0-9]*)(\s+[^>]*)?></([a-zA-Z][a-zA-Z0-9]*)>`)
17
-)
18
-
19
-// xpathExtract extracts values from XML using XPath expression.
20
-func xpathExtract(value Value, paramStr string) (Value, error) {
21
- expr := strings.TrimSpace(paramStr)
22
- if expr == "" {
23
- return Value{}, fmt.Errorf("xpath expression is required")
24
- }
25
-
26
- // Parse XML
27
- doc, err := xmlquery.Parse(strings.NewReader(value.Data))
28
- if err != nil {
29
- return Value{}, fmt.Errorf("invalid XML: %w", err)
30
- }
31
-
32
- // Compile and evaluate XPath expression
33
- xpathExpr, err := xpath.Compile(expr)
34
- if err != nil {
35
- return Value{}, fmt.Errorf("invalid xpath expression: %w", err)
36
- }
37
-
38
- // Evaluate the expression
39
- result := xpathExpr.Evaluate(xmlquery.CreateXPathNavigator(doc))
40
-
41
- // Handle different result types
42
- switch v := result.(type) {
43
- case *xpath.NodeIterator:
44
- // Node selection - collect nodes
45
- var output strings.Builder
46
- for v.MoveNext() {
47
- node := v.Current().(*xmlquery.NodeNavigator).Current()
48
-
49
- // For text and attribute nodes, return the data
50
- // For element nodes, return the XML representation
51
- if node.Type == xmlquery.TextNode || node.Type == xmlquery.AttributeNode {
52
- output.WriteString(node.Data)
53
- } else {
54
- xml := node.OutputXML(true)
55
- // Convert empty tags to self-closing format (e.g., <a></a> to <a/>)
56
- xml = convertToSelfClosingTags(xml)
57
- output.WriteString(xml)
58
- }
59
- }
60
- return Value{Data: output.String(), Type: ValueTypeStr}, nil
61
-
62
- case string:
63
- // String result from string() function
64
- return Value{Data: v, Type: ValueTypeStr}, nil
65
-
66
- case float64:
67
- // Numeric result from arithmetic expressions
68
- // Check for invalid numeric results
69
- if math.IsInf(v, 0) || math.IsNaN(v) {
70
- return Value{}, fmt.Errorf("invalid numeric result from XPath expression")
71
- }
72
- return Value{Data: fmt.Sprintf("%g", v), Type: ValueTypeStr}, nil
73
-
74
- case bool:
75
- // Boolean result from boolean expressions
76
- if v {
77
- return Value{Data: "true", Type: ValueTypeStr}, nil
78
- }
79
- return Value{Data: "false", Type: ValueTypeStr}, nil
80
-
81
- default:
82
- // Unknown type - convert to string
83
- return Value{Data: fmt.Sprint(v), Type: ValueTypeStr}, nil
84
- }
85
-}
86
-
87
-// convertToSelfClosingTags converts empty XML tags to self-closing format
88
-func convertToSelfClosingTags(xml string) string {
89
- // Match patterns like <tag></tag> or <tag attr="value"></tag>
90
- // Note: Go regex doesn't support backreferences in pattern, so we match and check manually
91
- return xpathEmptyTagRegex.ReplaceAllStringFunc(xml, func(match string) string {
92
- // Extract the tag names from opening and closing tags
93
- submatches := xpathEmptyTagRegex.FindStringSubmatch(match)
94
- if len(submatches) >= 4 {
95
- openTag := submatches[1]
96
- attrs := submatches[2]
97
- closeTag := submatches[3]
98
-
99
- // Only convert if opening and closing tags match
100
- if openTag == closeTag {
101
- return "<" + openTag + attrs + "/>"
102
- }
103
- }
104
- // Return unchanged if tags don't match
105
- return match
106
- })
107
-}