logstash-input-snmp 1.2.6 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/CONTRIBUTORS +1 -0
- data/docs/index.asciidoc +94 -48
- data/lib/logstash/inputs/snmp/base_client.rb +2 -2
- data/lib/logstash/inputs/snmp.rb +140 -41
- data/logstash-input-snmp.gemspec +6 -2
- data/spec/inputs/integration/it_spec.rb +203 -0
- data/spec/inputs/snmp_spec.rb +242 -23
- metadata +56 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13fdcfbd3e486a466a7fa32e1de3e6bbdccd30779f5eb4fce9ef8645355c8322
|
4
|
+
data.tar.gz: b8e80f54fbe399f262e48aff1fd614d578b16d493cded3e15446593ebffd1f93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc44139cfc59066ffa89fc916903e4507972b9650cc056ee0bb897a8028dcf93abf30c07627926ce717a0da275d084ebf45b7e631355d04752ef55e868eef3f7
|
7
|
+
data.tar.gz: 1ca064497bd573c6d3de4c00bf0d744fb14339926d61203d6a7e74fcbb6887c64f784e32ecb579db11fc2ff849c329b7479df92a44b24803d653b316ab41cbd6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## 1.3.1
|
2
|
+
- Refactor: handle no response(s) wout error logging [#105](https://github.com/logstash-plugins/logstash-input-snmp/pull/105)
|
3
|
+
|
4
|
+
## 1.3.0
|
5
|
+
- Feat: ECS compliance + optional target [#99](https://github.com/logstash-plugins/logstash-input-snmp/pull/99)
|
6
|
+
- Internal: update to Gradle 7 [#102](https://github.com/logstash-plugins/logstash-input-snmp/pull/102)
|
7
|
+
|
8
|
+
## 1.2.8
|
9
|
+
- Fixed interval handling to only sleep off the _remainder_ of the interval (if any), and to log a helpful warning when crawling the hosts takes longer than the configured interval [#100](https://github.com/logstash-plugins/logstash-input-snmp/pull/100). Fixes [#61](https://github.com/logstash-plugins/logstash-input-snmp/issues/61).
|
10
|
+
|
11
|
+
## 1.2.7
|
12
|
+
- Added integration tests to ensure SNMP server and IPv6 connections [#90](https://github.com/logstash-plugins/logstash-input-snmp/issues/90). Fixes[#87](https://github.com/logstash-plugins/logstash-input-snmp/issues/87).
|
13
|
+
|
1
14
|
## 1.2.6
|
2
15
|
- Docs: example on setting IPv6 hosts [#89](https://github.com/logstash-plugins/logstash-input-snmp/pull/89)
|
3
16
|
|
data/CONTRIBUTORS
CHANGED
@@ -4,6 +4,7 @@ reports, or in general have helped logstash along its way.
|
|
4
4
|
Contributors:
|
5
5
|
* Colin Surprenant - colin.surprenant@gmail.com
|
6
6
|
* Dan Major - axrayn@gmail.com
|
7
|
+
* Patrick Prugger - pprugger@gmx.at
|
7
8
|
|
8
9
|
Note: If you've sent us patches, bug reports, or otherwise contributed to
|
9
10
|
Logstash, and you aren't on the list above and want to be, please let us know
|
data/docs/index.asciidoc
CHANGED
@@ -26,6 +26,22 @@ to gather information related to the current state of the devices operation.
|
|
26
26
|
|
27
27
|
The SNMP input plugin supports SNMP v1, v2c, and v3 over UDP and TCP transport protocols.
|
28
28
|
|
29
|
+
[id="plugins-{type}s-{plugin}-ecs"]
|
30
|
+
==== Compatibility with the Elastic Common Schema (ECS)
|
31
|
+
|
32
|
+
Because SNMP data has specific field names based on OIDs, we recommend setting a <<plugins-{type}s-{plugin}-target>>.
|
33
|
+
Metadata fields follow a specific naming convention when <<plugins-{type}s-{plugin}-ecs_compatibility,ECS compatibility mode>> is enabled.
|
34
|
+
|
35
|
+
[cols="<l,<l,e,<e"]
|
36
|
+
|=======================================================================
|
37
|
+
|ECS disabled |ECS v1, v8 |Description
|
38
|
+
|[@metadata][host_protocol] |[@metadata][input][snmp][host][protocol] |The protocol used to retrieve data e.g. "udp"
|
39
|
+
|[@metadata][host_address] |[@metadata][input][snmp][host][address] |The host IP e.g. "192.168.1.1"
|
40
|
+
|[@metadata][host_port] |[@metadata][input][snmp][host][port] |The host's port e.g. "161"
|
41
|
+
|[@metadata][host_community] |[@metadata][input][snmp][host][community] |The configured community e.g. "public"
|
42
|
+
|[host] |[host][ip] |Same as `[@metadata][host_address]`, host's IP address
|
43
|
+
|=======================================================================
|
44
|
+
|
29
45
|
[id="plugins-{type}s-{plugin}-import-mibs"]
|
30
46
|
==== Importing MIBs
|
31
47
|
|
@@ -55,6 +71,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
|
|
55
71
|
[cols="<,<,<",options="header",]
|
56
72
|
|=======================================================================
|
57
73
|
|Setting |Input type|Required
|
74
|
+
| <<plugins-{type}s-{plugin}-ecs_compatibility>> |<<string,string>>|No
|
58
75
|
| <<plugins-{type}s-{plugin}-get>> |<<array,array>>|No
|
59
76
|
| <<plugins-{type}s-{plugin}-hosts>> |<<array,array>>|No
|
60
77
|
| <<plugins-{type}s-{plugin}-interval>> |<<number,number>>|No
|
@@ -63,6 +80,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
|
|
63
80
|
| <<plugins-{type}s-{plugin}-oid_path_length>> |<<number,number>>|No
|
64
81
|
| <<plugins-{type}s-{plugin}-walk>> |<<array,array>>|No
|
65
82
|
| <<plugins-{type}s-{plugin}-tables>> |<<array,array>>|No
|
83
|
+
| <<plugins-{type}s-{plugin}-target>> |<<string,string>>|No
|
66
84
|
|=======================================================================
|
67
85
|
|
68
86
|
==== SNMPv3 Authentication Options
|
@@ -86,14 +104,14 @@ Also see <<plugins-{type}s-{plugin}-common-options>> for a list of options suppo
|
|
86
104
|
input plugins.
|
87
105
|
|
88
106
|
[id="plugins-{type}s-{plugin}-get"]
|
89
|
-
===== `get`
|
107
|
+
===== `get`
|
108
|
+
|
109
|
+
* Value type is <<array,array>>
|
110
|
+
* There is no default value for this setting
|
90
111
|
|
91
112
|
Use the `get` option to query for scalar values for the given OID(s).
|
92
113
|
One or more OID(s) are specified as an array of strings of OID(s).
|
93
114
|
|
94
|
-
* Value type is <<array,array>>
|
95
|
-
* There is no default value for this setting
|
96
|
-
|
97
115
|
Example
|
98
116
|
[source,ruby]
|
99
117
|
-----
|
@@ -108,14 +126,14 @@ input {
|
|
108
126
|
[id="plugins-{type}s-{plugin}-hosts"]
|
109
127
|
===== `hosts`
|
110
128
|
|
129
|
+
* Value type is <<array,array>>
|
130
|
+
* There is no default value for this setting
|
131
|
+
|
111
132
|
The `hosts` option specifies the list of hosts to query the configured `get` and `walk` options.
|
112
133
|
|
113
134
|
Each host definition is a hash and must define the `host` key and value.
|
114
135
|
`host` must use the format `{tcp|udp}:{ip address}/{port}`, for example `host => "udp:127.0.0.1/161"`
|
115
136
|
|
116
|
-
* Value type is <<array,array>>
|
117
|
-
* There is no default value for this setting
|
118
|
-
|
119
137
|
Each host definition can optionally include the following keys and values:
|
120
138
|
|
121
139
|
* `community` the community string, default is `public`.
|
@@ -160,56 +178,58 @@ input {
|
|
160
178
|
-----
|
161
179
|
|
162
180
|
[id="plugins-{type}s-{plugin}-interval"]
|
163
|
-
===== `interval`
|
181
|
+
===== `interval`
|
164
182
|
|
165
|
-
|
183
|
+
* Value type is <<number,number>>
|
184
|
+
* Default value is `30`
|
166
185
|
|
167
|
-
|
168
|
-
|
186
|
+
The `interval` option specifies the polling interval in seconds.
|
187
|
+
If polling all configured hosts takes longer than this interval, a warning will be emitted to the logs.
|
169
188
|
|
170
189
|
[id="plugins-{type}s-{plugin}-mib_paths"]
|
171
|
-
===== `mib_paths`
|
190
|
+
===== `mib_paths`
|
172
191
|
|
173
|
-
|
174
|
-
|
192
|
+
* Value type is <<path,path>>
|
193
|
+
* There is no default value for this setting
|
175
194
|
|
176
|
-
|
177
|
-
|
195
|
+
The `mib_paths` option specifies the location of one or more imported MIB files.
|
196
|
+
The value can be either a dir path containing the imported MIB `.dic` files or a
|
197
|
+
file path to a single MIB `.dic` file.
|
178
198
|
|
179
199
|
This plugin includes the IETF MIBs.
|
180
200
|
If you require other MIBs, you need to import them. See <<plugins-{type}s-{plugin}-import-mibs>>.
|
181
201
|
|
182
202
|
[id="plugins-{type}s-{plugin}-oid_root_skip"]
|
183
|
-
===== `oid_root_skip`
|
203
|
+
===== `oid_root_skip`
|
204
|
+
|
205
|
+
* Value type is <<number,number>>
|
206
|
+
* Default value is `0`
|
184
207
|
|
185
208
|
The `oid_root_skip` option specifies the number of OID root digits to ignore in the event field name.
|
186
209
|
For example, in a numeric OID like "1.3.6.1.2.1.1.1.0" the first 5 digits could be ignored by setting `oid_root_skip => 5`
|
187
210
|
which would result in a field name "1.1.1.0". Similarly when a MIB is used an OID such
|
188
211
|
"1.3.6.1.2.mib-2.system.sysDescr.0" would become "mib-2.system.sysDescr.0"
|
189
212
|
|
190
|
-
* Value type is <<number,number>>
|
191
|
-
* Default value is `0`
|
192
|
-
|
193
213
|
[id="plugins-{type}s-{plugin}-oid_path_length"]
|
194
214
|
===== `oid_path_length`
|
195
215
|
|
216
|
+
* Value type is <<number,number>>
|
217
|
+
* Default value is `0`
|
218
|
+
|
196
219
|
The `oid_path_length` option specifies the number of OID root digits to retain in the event field name.
|
197
220
|
For example, in a numeric OID like "1.3.6.1.2.1.1.1.0" the last 2 digits could be retained by setting `oid_path_length => 2`
|
198
221
|
which would result in a field name "1.0". Similarly when a MIB is used an OID such
|
199
222
|
"1.3.6.1.2.mib-2.system.sysDescr.0" would become "sysDescr.0"
|
200
223
|
|
201
|
-
* Value type is <<number,number>>
|
202
|
-
* Default value is `0`
|
203
|
-
|
204
224
|
[id="plugins-{type}s-{plugin}-walk"]
|
205
225
|
===== `walk`
|
206
226
|
|
227
|
+
* Value type is <<array,array>>
|
228
|
+
* There is no default value for this setting
|
229
|
+
|
207
230
|
Use the `walk` option to retrieve the subtree of information for the given OID(s).
|
208
231
|
One or more OID(s) are specified as an array of strings of OID(s).
|
209
232
|
|
210
|
-
* Value type is <<array,array>>
|
211
|
-
* There is no default value for this setting
|
212
|
-
|
213
233
|
Queries the subtree of information starting at the given OID(s).
|
214
234
|
|
215
235
|
Example
|
@@ -225,14 +245,14 @@ Example
|
|
225
245
|
[id="plugins-{type}s-{plugin}-tables"]
|
226
246
|
===== `tables`
|
227
247
|
|
248
|
+
* Value type is <<array,array>>
|
249
|
+
* There is no default value for this setting
|
250
|
+
* Results are returned under a field using the table name
|
251
|
+
|
228
252
|
The `tables` option is used to query for tabular values for the given column OID(s).
|
229
253
|
|
230
254
|
Each table definition is a hash and must define the name key and value and the columns to return.
|
231
255
|
|
232
|
-
* Value type is <<array,array>>
|
233
|
-
* There is no default value for this setting
|
234
|
-
* Results are returned under a field using the table name
|
235
|
-
|
236
256
|
*Specifying a single table*
|
237
257
|
|
238
258
|
[source,ruby]
|
@@ -266,10 +286,10 @@ These options are required only if you are using SNMPv3.
|
|
266
286
|
[id="plugins-{type}s-{plugin}-auth_pass"]
|
267
287
|
===== `auth_pass`
|
268
288
|
|
269
|
-
|
289
|
+
* Value type is <<password,password>>
|
290
|
+
* There is no default value for this setting
|
270
291
|
|
271
|
-
|
272
|
-
* There is no default value for this setting
|
292
|
+
The `auth_pass` option specifies the SNMPv3 authentication passphrase or password.
|
273
293
|
|
274
294
|
[id="plugins-{type}s-{plugin}-auth_protocol"]
|
275
295
|
===== `auth_protocol`
|
@@ -279,38 +299,66 @@ The `auth_protocol` option specifies the SNMPv3 authentication protocol or type
|
|
279
299
|
* Value can be any of: `md5`, `sha`, `sha2`, `hmac128sha224`, `hmac192sha256`, `hmac256sha384`, `hmac384sha512`
|
280
300
|
* There is no default value for this setting
|
281
301
|
|
302
|
+
[id="plugins-{type}s-{plugin}-ecs_compatibility"]
|
303
|
+
===== `ecs_compatibility`
|
304
|
+
|
305
|
+
* Value type is <<string,string>>
|
306
|
+
* Supported values are:
|
307
|
+
** `disabled`: does not use ECS-compatible field names (fields might be set at the root of the event)
|
308
|
+
** `v1`, `v8`: avoids field names that might conflict with Elastic Common Schema (for example, the `host` field)
|
309
|
+
* Default value depends on which version of Logstash is running:
|
310
|
+
** When Logstash provides a `pipeline.ecs_compatibility` setting, its value is used as the default
|
311
|
+
** Otherwise, the default value is `disabled`.
|
312
|
+
|
313
|
+
Controls this plugin's compatibility with the {ecs-ref}[Elastic Common Schema (ECS)].
|
314
|
+
|
282
315
|
[id="plugins-{type}s-{plugin}-priv_pass"]
|
283
316
|
===== `priv_pass`
|
284
317
|
|
285
|
-
|
318
|
+
* Value type is <<password,password>>
|
319
|
+
* There is no default value for this setting
|
286
320
|
|
287
|
-
|
288
|
-
* There is no default value for this setting
|
321
|
+
The `priv_pass` option specifies the SNMPv3 encryption password.
|
289
322
|
|
290
323
|
[id="plugins-{type}s-{plugin}-priv_protocol"]
|
291
324
|
===== `priv_protocol`
|
292
325
|
|
293
|
-
|
326
|
+
* Value can be any of: `des`, `3des`, `aes`, `aes128`, `aes192`, `aes256`
|
327
|
+
* Note that `aes` and `aes128` are equivalent
|
328
|
+
* There is no default value for this setting
|
294
329
|
|
295
|
-
|
296
|
-
* Note that `aes` and `aes128` are equivalent
|
297
|
-
* There is no default value for this setting
|
330
|
+
The `priv_protocol` option specifies the SNMPv3 privacy/encryption protocol.
|
298
331
|
|
299
332
|
[id="plugins-{type}s-{plugin}-security_name"]
|
300
333
|
===== `security_name`
|
301
334
|
|
302
|
-
|
335
|
+
* Value type is <<string,string>>
|
336
|
+
* There is no default value for this setting
|
303
337
|
|
304
|
-
|
305
|
-
* There is no default value for this setting
|
338
|
+
The `security_name` option specifies the SNMPv3 security name or user name.
|
306
339
|
|
307
340
|
[id="plugins-{type}s-{plugin}-security_level"]
|
308
341
|
===== `security_level`
|
309
342
|
|
310
|
-
|
343
|
+
* Value can be any of: `noAuthNoPriv`, `authNoPriv`, `authPriv`
|
344
|
+
* There is no default value for this setting
|
311
345
|
|
312
|
-
|
313
|
-
|
346
|
+
The `security_level` option specifies the SNMPv3 security level between
|
347
|
+
Authentication, No Privacy; Authentication, Privacy; or no Authentication, no Privacy.
|
348
|
+
|
349
|
+
[id="plugins-{type}s-{plugin}-target"]
|
350
|
+
===== `target`
|
351
|
+
|
352
|
+
* Value type is <<string,string>>
|
353
|
+
* There is no default value for this setting
|
354
|
+
|
355
|
+
The name of the field under which SNMP payloads are assigned.
|
356
|
+
If not specified data will be stored in the root of the event.
|
357
|
+
|
358
|
+
Setting a target is recommended when <<plugins-{type}s-{plugin}-ecs_compatibility>> is enabled.
|
359
|
+
|
360
|
+
[id="plugins-{type}s-{plugin}-examples"]
|
361
|
+
==== Configuration examples
|
314
362
|
|
315
363
|
*Specifying SNMPv3 settings*
|
316
364
|
|
@@ -331,8 +379,6 @@ input {
|
|
331
379
|
|
332
380
|
-----
|
333
381
|
|
334
|
-
==== More configuration examples
|
335
|
-
|
336
382
|
*Using both `get` and `walk` in the same poll cycle for each host(s)*
|
337
383
|
|
338
384
|
[source,ruby]
|
@@ -52,13 +52,13 @@ module LogStash
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def walk(oid, strip_root = 0, path_length = 0)
|
55
|
-
result = {}
|
56
|
-
|
57
55
|
pdufactory = get_pdu_factory
|
58
56
|
treeUtils = TreeUtils.new(@snmp, pdufactory)
|
59
57
|
events = treeUtils.getSubtree(@target, OID.new(oid))
|
60
58
|
return nil if events.nil? || events.size == 0
|
61
59
|
|
60
|
+
result = {}
|
61
|
+
|
62
62
|
events.each do |event|
|
63
63
|
next if event.nil?
|
64
64
|
|
data/lib/logstash/inputs/snmp.rb
CHANGED
@@ -7,11 +7,24 @@ require_relative "snmp/client"
|
|
7
7
|
require_relative "snmp/clientv3"
|
8
8
|
require_relative "snmp/mib"
|
9
9
|
|
10
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support'
|
11
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
|
12
|
+
require 'logstash/plugin_mixins/event_support/event_factory_adapter'
|
13
|
+
require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
|
14
|
+
|
10
15
|
# Generate a repeating message.
|
11
16
|
#
|
12
17
|
# This plugin is intented only as an example.
|
13
18
|
|
14
19
|
class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
20
|
+
|
21
|
+
include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
|
22
|
+
include LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck
|
23
|
+
|
24
|
+
include LogStash::PluginMixins::EventSupport::EventFactoryAdapter
|
25
|
+
|
26
|
+
extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
|
27
|
+
|
15
28
|
config_name "snmp"
|
16
29
|
|
17
30
|
# List of OIDs for which we want to retrieve the scalar value
|
@@ -67,9 +80,6 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
|
67
80
|
# The default, `30`, means poll each host every 30 seconds.
|
68
81
|
config :interval, :validate => :number, :default => 30
|
69
82
|
|
70
|
-
# Add the default "host" field to the event.
|
71
|
-
config :add_field, :validate => :hash, :default => { "host" => "%{[@metadata][host_address]}" }
|
72
|
-
|
73
83
|
# SNMPv3 Credentials
|
74
84
|
#
|
75
85
|
# A single user can be configured and will be used for all defined SNMPv3 hosts.
|
@@ -94,9 +104,29 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
|
94
104
|
# The SNMPv3 security level can be Authentication, No Privacy; Authentication, Privacy; or no Authentication, no Privacy
|
95
105
|
config :security_level, :validate => ["noAuthNoPriv", "authNoPriv", "authPriv"]
|
96
106
|
|
107
|
+
# Defines a target field for placing fields.
|
108
|
+
# If this setting is omitted, data gets stored at the root (top level) of the event.
|
109
|
+
# The target is only relevant while decoding data into a new event.
|
110
|
+
config :target, :validate => :field_reference
|
111
|
+
|
97
112
|
BASE_MIB_PATH = ::File.join(__FILE__, "..", "..", "..", "mibs")
|
98
113
|
PROVIDED_MIB_PATHS = [::File.join(BASE_MIB_PATH, "logstash"), ::File.join(BASE_MIB_PATH, "ietf")].map { |path| ::File.expand_path(path) }
|
99
114
|
|
115
|
+
def initialize(params={})
|
116
|
+
super(params)
|
117
|
+
|
118
|
+
@host_protocol_field = ecs_select[disabled: '[@metadata][host_protocol]', v1: '[@metadata][input][snmp][host][protocol]']
|
119
|
+
@host_address_field = ecs_select[disabled: '[@metadata][host_address]', v1: '[@metadata][input][snmp][host][address]']
|
120
|
+
@host_port_field = ecs_select[disabled: '[@metadata][host_port]', v1: '[@metadata][input][snmp][host][port]']
|
121
|
+
@host_community_field = ecs_select[disabled: '[@metadata][host_community]', v1: '[@metadata][input][snmp][host][community]']
|
122
|
+
|
123
|
+
# Add the default "host" field to the event, for backwards compatibility, or host.ip in ecs mode
|
124
|
+
unless params.key?('add_field')
|
125
|
+
host_ip_field = ecs_select[disabled: "host", v1: "[host][ip]"]
|
126
|
+
@add_field = { host_ip_field => "%{#{@host_address_field}}" }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
100
130
|
def register
|
101
131
|
validate_oids!
|
102
132
|
validate_hosts!
|
@@ -161,57 +191,82 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
|
161
191
|
end
|
162
192
|
|
163
193
|
def run(queue)
|
164
|
-
# for now a naive single threaded poller which sleeps
|
165
|
-
# each run. each run polls all the defined hosts for the get and walk options.
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
194
|
+
# for now a naive single threaded poller which sleeps off the remaining interval between
|
195
|
+
# each run. each run polls all the defined hosts for the get, table and walk options.
|
196
|
+
stoppable_interval_runner.every(@interval, "polling hosts") do
|
197
|
+
poll_clients(queue)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def poll_clients(queue)
|
202
|
+
@client_definitions.each do |definition|
|
203
|
+
client = definition[:client]
|
204
|
+
host = definition[:host_address]
|
205
|
+
result = {}
|
206
|
+
|
207
|
+
if !definition[:get].empty?
|
208
|
+
oids = definition[:get]
|
209
|
+
begin
|
210
|
+
data = client.get(oids, @oid_root_skip, @oid_path_length)
|
211
|
+
if data
|
212
|
+
result.update(data)
|
213
|
+
else
|
214
|
+
logger.debug? && logger.debug("get operation returned no response", host: host, oids: oids)
|
174
215
|
end
|
216
|
+
rescue => e
|
217
|
+
logger.error("error invoking get operation, ignoring", host: host, oids: oids, exception: e, backtrace: e.backtrace)
|
175
218
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
219
|
+
end
|
220
|
+
|
221
|
+
if !definition[:walk].empty?
|
222
|
+
definition[:walk].each do |oid|
|
223
|
+
begin
|
224
|
+
data = client.walk(oid, @oid_root_skip, @oid_path_length)
|
225
|
+
if data
|
226
|
+
result.update(data)
|
227
|
+
else
|
228
|
+
logger.debug? && logger.debug("walk operation returned no response", host: host, oid: oid)
|
182
229
|
end
|
230
|
+
rescue => e
|
231
|
+
logger.error("error invoking walk operation, ignoring", host: host, oid: oid, exception: e, backtrace: e.backtrace)
|
183
232
|
end
|
184
233
|
end
|
234
|
+
end
|
185
235
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
236
|
+
if !Array(@tables).empty?
|
237
|
+
@tables.each do |table_entry|
|
238
|
+
begin
|
239
|
+
data = client.table(table_entry, @oid_root_skip, @oid_path_length)
|
240
|
+
if data
|
241
|
+
result.update(data)
|
242
|
+
else
|
243
|
+
logger.debug? && logger.debug("table operation returned no response", host: host, table: table_entry)
|
192
244
|
end
|
245
|
+
rescue => e
|
246
|
+
logger.error("error invoking table operation, ignoring",
|
247
|
+
host: host, table_name: table_entry['name'], exception: e, backtrace: e.backtrace)
|
193
248
|
end
|
194
249
|
end
|
195
|
-
|
196
|
-
unless result.empty?
|
197
|
-
metadata = {
|
198
|
-
"host_protocol" => definition[:host_protocol],
|
199
|
-
"host_address" => definition[:host_address],
|
200
|
-
"host_port" => definition[:host_port],
|
201
|
-
"host_community" => definition[:host_community],
|
202
|
-
}
|
203
|
-
result["@metadata"] = metadata
|
204
|
-
|
205
|
-
event = LogStash::Event.new(result)
|
206
|
-
decorate(event)
|
207
|
-
queue << event
|
208
|
-
end
|
209
250
|
end
|
210
251
|
|
211
|
-
|
252
|
+
unless result.empty?
|
253
|
+
event = targeted_event_factory.new_event(result)
|
254
|
+
event.set(@host_protocol_field, definition[:host_protocol])
|
255
|
+
event.set(@host_address_field, definition[:host_address])
|
256
|
+
event.set(@host_port_field, definition[:host_port])
|
257
|
+
event.set(@host_community_field, definition[:host_community])
|
258
|
+
decorate(event)
|
259
|
+
queue << event
|
260
|
+
else
|
261
|
+
logger.debug? && logger.debug("no snmp data retrieved", host: definition[:host_address])
|
262
|
+
end
|
212
263
|
end
|
213
264
|
end
|
214
265
|
|
266
|
+
def stoppable_interval_runner
|
267
|
+
StoppableIntervalRunner.new(self)
|
268
|
+
end
|
269
|
+
|
215
270
|
def close
|
216
271
|
@client_definitions.each do |definition|
|
217
272
|
begin
|
@@ -222,10 +277,13 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
|
222
277
|
end
|
223
278
|
end
|
224
279
|
|
280
|
+
def stop
|
281
|
+
end
|
282
|
+
|
225
283
|
private
|
226
284
|
|
227
285
|
OID_REGEX = /^\.?([0-9\.]+)$/
|
228
|
-
HOST_REGEX = /^(?<host_protocol
|
286
|
+
HOST_REGEX = /^(?<host_protocol>\w+):(?<host_address>.+)\/(?<host_port>\d+)$/i
|
229
287
|
VERSION_REGEX =/^1|2c|3$/
|
230
288
|
|
231
289
|
def validate_oids!
|
@@ -295,4 +353,45 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
|
|
295
353
|
def validate_strip!
|
296
354
|
raise(LogStash::ConfigurationError, "you can not specify both oid_root_skip and oid_path_length") if @oid_root_skip > 0 and @oid_path_length > 0
|
297
355
|
end
|
356
|
+
|
357
|
+
##
|
358
|
+
# The StoppableIntervalRunner is capable of running a block of code at a
|
359
|
+
# repeating interval, while respecting the stop condition of the plugin.
|
360
|
+
class StoppableIntervalRunner
|
361
|
+
##
|
362
|
+
# @param plugin [#logger,#stop?]
|
363
|
+
def initialize(plugin)
|
364
|
+
@plugin = plugin
|
365
|
+
end
|
366
|
+
|
367
|
+
##
|
368
|
+
# Runs the provided block repeatedly using the provided interval.
|
369
|
+
# After executing the block, the remainder of the interval if any is slept off
|
370
|
+
# using an interruptible sleep.
|
371
|
+
# If no time remains, a warning is emitted to the logs.
|
372
|
+
#
|
373
|
+
# @param interval_seconds [Integer,Float]
|
374
|
+
# @param desc [String] (default: "operation"): a description to use when logging
|
375
|
+
# @yield
|
376
|
+
def every(interval_seconds, desc="operation", &block)
|
377
|
+
until @plugin.stop?
|
378
|
+
start_time = Time.now
|
379
|
+
|
380
|
+
yield
|
381
|
+
|
382
|
+
duration_seconds = Time.now - start_time
|
383
|
+
if duration_seconds >= interval_seconds
|
384
|
+
@plugin.logger.warn("#{desc} took longer than the configured interval", :interval_seconds => interval_seconds, :duration_seconds => duration_seconds.round(3))
|
385
|
+
else
|
386
|
+
remaining_interval = interval_seconds - duration_seconds
|
387
|
+
sleep(remaining_interval)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# @api private
|
393
|
+
def sleep(duration)
|
394
|
+
Stud.stoppable_sleep(duration) { @plugin.stop? }
|
395
|
+
end
|
396
|
+
end
|
298
397
|
end
|
data/logstash-input-snmp.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-input-snmp'
|
3
|
-
s.version = '1.
|
3
|
+
s.version = '1.3.1'
|
4
4
|
s.licenses = ['Apache-2.0']
|
5
5
|
s.summary = "SNMP input plugin"
|
6
6
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
@@ -20,8 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
# Gem dependencies
|
22
22
|
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
23
|
-
s.add_runtime_dependency '
|
23
|
+
s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
|
24
|
+
s.add_runtime_dependency 'logstash-mixin-event_support', '~> 1.0'
|
25
|
+
s.add_runtime_dependency 'logstash-mixin-validator_support', '~> 1.0'
|
24
26
|
s.add_runtime_dependency 'logstash-codec-plain'
|
27
|
+
s.add_runtime_dependency 'stud', '>= 0.0.22', '< 0.1.0'
|
28
|
+
|
25
29
|
s.add_development_dependency 'logstash-devutils'
|
26
30
|
s.add_development_dependency 'rspec-wait'
|
27
31
|
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require "logstash/devutils/rspec/spec_helper"
|
2
|
+
require "logstash/inputs/snmp"
|
3
|
+
|
4
|
+
describe LogStash::Inputs::Snmp do
|
5
|
+
let(:config) { {"get" => ["1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.3.0", "1.3.6.1.2.1.1.5.0"]} }
|
6
|
+
let(:plugin) { LogStash::Inputs::Snmp.new(config)}
|
7
|
+
|
8
|
+
shared_examples "snmp plugin return single event" do
|
9
|
+
it "should have OID value" do
|
10
|
+
plugin.register
|
11
|
+
queue = []
|
12
|
+
stop_plugin_after_seconds(plugin)
|
13
|
+
plugin.run(queue)
|
14
|
+
plugin.close
|
15
|
+
event = queue.pop
|
16
|
+
|
17
|
+
expect(event).to be_a(LogStash::Event)
|
18
|
+
expect(event.get("iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.sysUpTimeInstance")).to be_a Integer
|
19
|
+
expect(event.get("iso.org.dod.internet.mgmt.mib-2.system.sysName.0")).to be_a String
|
20
|
+
expect(event.get("iso.org.dod.internet.mgmt.mib-2.system.sysDescr.0")).to be_a String
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
shared_examples "snmp plugin return one udp event and one tcp event" do |config|
|
25
|
+
it "should have one udp from snmp1 and one tcp from snmp2" do
|
26
|
+
events = input(config) { |_, queue| 2.times.collect { queue.pop } }
|
27
|
+
udp = 0; tcp = 0
|
28
|
+
events.each { |event|
|
29
|
+
if event.get("[@metadata][host_protocol]") == "udp"
|
30
|
+
udp += 1
|
31
|
+
expect(event.get("[@metadata][host_protocol]")).to eq("udp")
|
32
|
+
expect(event.get("[@metadata][host_address]")).to eq("snmp1")
|
33
|
+
expect(event.get("[@metadata][host_port]")).to eq("161")
|
34
|
+
else
|
35
|
+
tcp += 1
|
36
|
+
expect(event.get("[@metadata][host_protocol]")).to eq("tcp")
|
37
|
+
expect(event.get("[@metadata][host_address]")).to eq("snmp2")
|
38
|
+
expect(event.get("[@metadata][host_port]")).to eq("162")
|
39
|
+
end
|
40
|
+
}
|
41
|
+
expect(udp).to eq(1)
|
42
|
+
expect(tcp).to eq(1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "against single snmp server with snmpv2 and udp", :integration => true do
|
47
|
+
let(:config) { super().merge({"hosts" => [{"host" => "udp:snmp1/161", "community" => "public"}]})}
|
48
|
+
it_behaves_like "snmp plugin return single event"
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "against single server with snmpv3 and tcp", :integration => true do
|
52
|
+
let(:config) { super().merge({
|
53
|
+
"hosts" => [{"host" => "tcp:snmp1/161", "version" => "3"}],
|
54
|
+
"security_name" => "user_1",
|
55
|
+
"auth_protocol" => "sha",
|
56
|
+
"auth_pass" => "STrP@SSPhr@sE",
|
57
|
+
"priv_protocol" => "aes",
|
58
|
+
"priv_pass" => "STr0ngP@SSWRD161",
|
59
|
+
"security_level" => "authPriv"
|
60
|
+
})}
|
61
|
+
|
62
|
+
it_behaves_like "snmp plugin return single event"
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "invalid user against snmpv3 server", :integration => true do
|
66
|
+
let(:config) { super().merge({
|
67
|
+
"hosts" => [{"host" => "tcp:snmp1/161", "version" => "3"}],
|
68
|
+
"security_name" => "user_2",
|
69
|
+
"auth_protocol" => "sha",
|
70
|
+
"auth_pass" => "STrP@SSPhr@sE",
|
71
|
+
"priv_protocol" => "aes",
|
72
|
+
"priv_pass" => "STr0ngP@SSWRD161",
|
73
|
+
"security_level" => "authPriv"
|
74
|
+
})}
|
75
|
+
|
76
|
+
it "should have error log" do
|
77
|
+
expect(plugin.logger).to receive(:error).once
|
78
|
+
plugin.register
|
79
|
+
queue = []
|
80
|
+
stop_plugin_after_seconds(plugin)
|
81
|
+
plugin.run(queue)
|
82
|
+
plugin.close
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "single input plugin on single server with snmpv2 and mix of udp and tcp", :integration => true do
|
87
|
+
let(:config) { super().merge({"hosts" => [{"host" => "udp:snmp1/161", "community" => "public"}, {"host" => "tcp:snmp1/161", "community" => "public"}]})}
|
88
|
+
it "should return two events " do
|
89
|
+
plugin.register
|
90
|
+
queue = []
|
91
|
+
stop_plugin_after_seconds(plugin)
|
92
|
+
plugin.run(queue)
|
93
|
+
plugin.close
|
94
|
+
|
95
|
+
host_cnt_snmp1 = queue.select {|event| event.get("host") == "snmp1"}.size
|
96
|
+
expect(queue.size).to eq(2)
|
97
|
+
expect(host_cnt_snmp1).to eq(2)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "single input plugin on multiple udp hosts", :integration => true do
|
102
|
+
let(:config) { super().merge({"hosts" => [{"host" => "udp:snmp1/161", "community" => "public"}, {"host" => "udp:snmp2/162", "community" => "public"}]})}
|
103
|
+
it "should return two events, one per host" do
|
104
|
+
plugin.register
|
105
|
+
queue = []
|
106
|
+
stop_plugin_after_seconds(plugin)
|
107
|
+
plugin.run(queue)
|
108
|
+
plugin.close
|
109
|
+
|
110
|
+
hosts = queue.map { |event| event.get("host") }.sort
|
111
|
+
expect(queue.size).to eq(2)
|
112
|
+
expect(hosts).to eq(["snmp1", "snmp2"])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "multiple pipelines and mix of udp tcp hosts", :integration => true do
|
117
|
+
let(:config) { {"get" => ["1.3.6.1.2.1.1.1.0"], "hosts" => [{"host" => "udp:snmp1/161", "community" => "public"}]} }
|
118
|
+
let(:config2) { {"get" => ["1.3.6.1.2.1.1.1.0"], "hosts" => [{"host" => "tcp:snmp2/162", "community" => "public"}]} }
|
119
|
+
let(:plugin) { LogStash::Inputs::Snmp.new(config)}
|
120
|
+
let(:plugin2) { LogStash::Inputs::Snmp.new(config2)}
|
121
|
+
|
122
|
+
it "should return two events, one per host" do
|
123
|
+
plugin.register
|
124
|
+
plugin2.register
|
125
|
+
queue = []
|
126
|
+
queue2 = []
|
127
|
+
t = Thread.new {
|
128
|
+
stop_plugin_after_seconds(plugin)
|
129
|
+
plugin.run(queue)
|
130
|
+
}
|
131
|
+
t2 = Thread.new {
|
132
|
+
stop_plugin_after_seconds(plugin2)
|
133
|
+
plugin2.run(queue2)
|
134
|
+
}
|
135
|
+
t.join(2100)
|
136
|
+
t2.join(2100)
|
137
|
+
plugin.close
|
138
|
+
plugin2.close
|
139
|
+
|
140
|
+
hosts = [queue.pop, queue2.pop].map { |event| event.get("host") }.sort
|
141
|
+
expect(hosts).to eq(["snmp1", "snmp2"])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "multiple plugin inputs and mix of udp tcp hosts", :integration => true do
|
146
|
+
config = <<-CONFIG
|
147
|
+
input {
|
148
|
+
snmp {
|
149
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
150
|
+
hosts => [{host => "udp:snmp1/161" community => "public"}]
|
151
|
+
}
|
152
|
+
snmp {
|
153
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
154
|
+
hosts => [{host => "tcp:snmp2/162" community => "public"}]
|
155
|
+
}
|
156
|
+
}
|
157
|
+
CONFIG
|
158
|
+
|
159
|
+
it_behaves_like "snmp plugin return one udp event and one tcp event", config
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "two plugins on different hosts with snmpv3 with same security name with different credentials and mix of udp and tcp", :integration => true do
|
163
|
+
config = <<-CONFIG
|
164
|
+
input {
|
165
|
+
snmp {
|
166
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
167
|
+
hosts => [{host => "udp:snmp1/161" version => "3"}]
|
168
|
+
security_name => "user_1"
|
169
|
+
auth_protocol => "sha"
|
170
|
+
auth_pass => "STrP@SSPhr@sE"
|
171
|
+
priv_protocol => "aes"
|
172
|
+
priv_pass => "STr0ngP@SSWRD161"
|
173
|
+
security_level => "authPriv"
|
174
|
+
}
|
175
|
+
snmp {
|
176
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
177
|
+
hosts => [{host => "tcp:snmp2/162" version => "3"}]
|
178
|
+
security_name => "user_1"
|
179
|
+
auth_protocol => "sha"
|
180
|
+
auth_pass => "STrP@SSPhr@sE"
|
181
|
+
priv_protocol => "aes"
|
182
|
+
priv_pass => "STr0ngP@SSWRD162"
|
183
|
+
security_level => "authPriv"
|
184
|
+
}
|
185
|
+
}
|
186
|
+
CONFIG
|
187
|
+
|
188
|
+
it_behaves_like "snmp plugin return one udp event and one tcp event", config
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "single host with tcp over ipv6", :integration => true do
|
192
|
+
let(:config) { super().merge({"hosts" => [{"host" => "tcp:[2001:3984:3989::161]/161"}]})}
|
193
|
+
it_behaves_like "snmp plugin return single event"
|
194
|
+
end
|
195
|
+
|
196
|
+
def stop_plugin_after_seconds(plugin)
|
197
|
+
Thread.new{
|
198
|
+
sleep(2)
|
199
|
+
plugin.do_stop
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
data/spec/inputs/snmp_spec.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require "logstash/devutils/rspec/spec_helper"
|
3
3
|
require "logstash/devutils/rspec/shared_examples"
|
4
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support/spec_helper'
|
4
5
|
require "logstash/inputs/snmp"
|
5
6
|
|
6
|
-
describe LogStash::Inputs::Snmp do
|
7
|
+
describe LogStash::Inputs::Snmp, :ecs_compatibility_support do
|
8
|
+
|
7
9
|
let(:mock_client) { double("LogStash::SnmpClient") }
|
8
10
|
|
9
11
|
it_behaves_like "an interruptible input plugin" do
|
@@ -130,45 +132,262 @@ describe LogStash::Inputs::Snmp do
|
|
130
132
|
end
|
131
133
|
end
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
+
ecs_compatibility_matrix(:disabled, :v1, :v8) do |ecs_select|
|
136
|
+
|
137
|
+
before(:each) do
|
138
|
+
allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
|
139
|
+
|
135
140
|
expect(LogStash::SnmpClient).to receive(:new).and_return(mock_client)
|
136
|
-
expect(mock_client).to receive(:get).and_return({"foo" => "bar"})
|
137
141
|
# devutils in v6 calls close on the test pipelines while it does not in v7+
|
138
|
-
|
142
|
+
allow(mock_client).to receive(:close).at_most(:once)
|
139
143
|
end
|
140
144
|
|
141
|
-
|
142
|
-
|
145
|
+
context 'mocked get' do
|
146
|
+
|
147
|
+
before do
|
148
|
+
expect(mock_client).to receive(:get).and_return({"foo" => "bar"})
|
149
|
+
end
|
150
|
+
|
151
|
+
it "shoud add @metadata fields and add default host field" do
|
152
|
+
config = <<-CONFIG
|
143
153
|
input {
|
144
154
|
snmp {
|
145
155
|
get => ["1.3.6.1.2.1.1.1.0"]
|
146
|
-
hosts => [{host => "udp:127.0.0.1/161" community => "public"}]
|
156
|
+
hosts => [{ host => "udp:127.0.0.1/161" community => "public" }]
|
147
157
|
}
|
148
158
|
}
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
159
|
+
CONFIG
|
160
|
+
event = input(config) { |_, queue| queue.pop }
|
161
|
+
|
162
|
+
if ecs_select.active_mode == :disabled
|
163
|
+
expect(event.get("[@metadata][host_protocol]")).to eq("udp")
|
164
|
+
expect(event.get("[@metadata][host_address]")).to eq("127.0.0.1")
|
165
|
+
expect(event.get("[@metadata][host_port]")).to eq("161")
|
166
|
+
expect(event.get("[@metadata][host_community]")).to eq("public")
|
167
|
+
expect(event.get("host")).to eql("127.0.0.1")
|
168
|
+
else
|
169
|
+
expect(event.get("[@metadata][input][snmp][host][protocol]")).to eq("udp")
|
170
|
+
expect(event.get("[@metadata][input][snmp][host][address]")).to eq("127.0.0.1")
|
171
|
+
expect(event.get("[@metadata][input][snmp][host][port]")).to eq('161')
|
172
|
+
expect(event.get("[@metadata][input][snmp][host][community]")).to eq("public")
|
173
|
+
expect(event.get("host")).to eql('ip' => "127.0.0.1")
|
174
|
+
end
|
175
|
+
end
|
158
176
|
|
159
|
-
|
160
|
-
|
177
|
+
it "should add custom host field (legacy metadata)" do
|
178
|
+
config = <<-CONFIG
|
161
179
|
input {
|
162
180
|
snmp {
|
163
181
|
get => ["1.3.6.1.2.1.1.1.0"]
|
164
|
-
hosts => [{host => "udp:127.0.0.1/161" community => "public"}]
|
182
|
+
hosts => [{ host => "udp:127.0.0.1/161" community => "public" }]
|
165
183
|
add_field => { host => "%{[@metadata][host_protocol]}:%{[@metadata][host_address]}/%{[@metadata][host_port]},%{[@metadata][host_community]}" }
|
166
184
|
}
|
167
185
|
}
|
168
|
-
|
169
|
-
|
186
|
+
CONFIG
|
187
|
+
event = input(config) { |_, queue| queue.pop }
|
188
|
+
|
189
|
+
expect(event.get("host")).to eq("udp:127.0.0.1/161,public")
|
190
|
+
end if ecs_select.active_mode == :disabled
|
191
|
+
|
192
|
+
it "should add custom host field (ECS mode)" do
|
193
|
+
config = <<-CONFIG
|
194
|
+
input {
|
195
|
+
snmp {
|
196
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
197
|
+
hosts => [{ host => "tcp:192.168.1.11/1161" }]
|
198
|
+
add_field => { "[host][formatted]" => "%{[@metadata][input][snmp][host][protocol]}://%{[@metadata][input][snmp][host][address]}:%{[@metadata][input][snmp][host][port]}" }
|
199
|
+
}
|
200
|
+
}
|
201
|
+
CONFIG
|
202
|
+
event = input(config) { |_, queue| queue.pop }
|
203
|
+
|
204
|
+
expect(event.get("host")).to eq('formatted' => "tcp://192.168.1.11:1161")
|
205
|
+
end if ecs_select.active_mode != :disabled
|
206
|
+
|
207
|
+
it "should target event data" do
|
208
|
+
config = <<-CONFIG
|
209
|
+
input {
|
210
|
+
snmp {
|
211
|
+
get => ["1.3.6.1.2.1.1.1.0"]
|
212
|
+
hosts => [{ host => "udp:127.0.0.1/161" community => "public" }]
|
213
|
+
target => "snmp_data"
|
214
|
+
}
|
215
|
+
}
|
216
|
+
CONFIG
|
217
|
+
event = input(config) { |_, queue| queue.pop }
|
218
|
+
|
219
|
+
expect( event.include?('foo') ).to be false
|
220
|
+
expect( event.get('[snmp_data]') ).to eql 'foo' => 'bar'
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'mocked nil get response' do
|
226
|
+
|
227
|
+
let(:config) do
|
228
|
+
{
|
229
|
+
'get' => ["1.3.6.1.2.1.1.1.0"],
|
230
|
+
"hosts" => [{"host" => "udp:127.0.0.1/161", "community" => "public"}]
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
let(:logger) { double("Logger").as_null_object }
|
235
|
+
|
236
|
+
before do
|
237
|
+
expect(mock_client).to receive(:get).once.and_return(nil)
|
238
|
+
allow_any_instance_of(described_class).to receive(:logger).and_return(logger)
|
239
|
+
expect(logger).not_to receive(:error)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'generates no events when client returns no response' do
|
243
|
+
input = described_class.new(config).tap { |input| input.register }
|
244
|
+
input.poll_clients queue = Queue.new
|
245
|
+
|
246
|
+
expect( queue.size ).to eql 0
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'mocked nil table response' do
|
251
|
+
|
252
|
+
let(:config) do
|
253
|
+
{
|
254
|
+
'tables' => [
|
255
|
+
{ 'name' => "a1Table", 'columns' => ["1.3.6.1.4.1.3375.2.2.5.2.3.1.1"] }
|
256
|
+
],
|
257
|
+
"hosts" => [{"host" => "udp:127.0.0.1/161", "community" => "public"}]
|
258
|
+
}
|
259
|
+
end
|
260
|
+
|
261
|
+
let(:logger) { double("Logger").as_null_object }
|
262
|
+
|
263
|
+
before do
|
264
|
+
expect(mock_client).to receive(:table).once.and_return(nil)
|
265
|
+
allow_any_instance_of(described_class).to receive(:logger).and_return(logger)
|
266
|
+
expect(logger).not_to receive(:error)
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'generates no events when client returns no response' do
|
270
|
+
input = described_class.new(config).tap { |input| input.register }
|
271
|
+
input.poll_clients queue = Queue.new
|
272
|
+
|
273
|
+
expect( queue.size ).to eql 0
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'mocked nil walk response' do
|
278
|
+
|
279
|
+
let(:config) do
|
280
|
+
{
|
281
|
+
'walk' => ["1.3.6.1.2.1.1"],
|
282
|
+
"hosts" => [{"host" => "udp:127.0.0.1/161", "community" => "public"}]
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
let(:logger) { double("Logger").as_null_object }
|
287
|
+
|
288
|
+
before do
|
289
|
+
expect(mock_client).to receive(:walk).once.and_return(nil)
|
290
|
+
allow_any_instance_of(described_class).to receive(:logger).and_return(logger)
|
291
|
+
expect(logger).not_to receive(:error)
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'generates no events when client returns no response' do
|
295
|
+
input = described_class.new(config).tap { |input| input.register }
|
296
|
+
input.poll_clients queue = Queue.new
|
297
|
+
|
298
|
+
expect( queue.size ).to eql 0
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
context "StoppableIntervalRunner" do
|
305
|
+
let(:stop_holder) { Struct.new(:value).new(false) }
|
306
|
+
|
307
|
+
before(:each) do
|
308
|
+
allow(plugin).to receive(:stop?) { stop_holder.value }
|
309
|
+
end
|
310
|
+
|
311
|
+
let(:plugin) do
|
312
|
+
double("Plugin").tap do |dbl|
|
313
|
+
allow(dbl).to receive(:logger).and_return(double("Logger").as_null_object)
|
314
|
+
allow(dbl).to receive(:stop?) { stop_holder.value }
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
subject(:interval_runner) { LogStash::Inputs::Snmp::StoppableIntervalRunner.new(plugin) }
|
319
|
+
|
320
|
+
context "#every" do
|
321
|
+
context "when the plugin is stopped" do
|
322
|
+
let(:interval_seconds) { 2 }
|
323
|
+
it 'does not yield the block' do
|
324
|
+
stop_holder.value = true
|
325
|
+
expect { |yielder| interval_runner.every(interval_seconds, &yielder) }.to_not yield_control
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context "when the yield takes shorter than the interval" do
|
330
|
+
let(:duration_seconds) { 1 }
|
331
|
+
let(:interval_seconds) { 2 }
|
332
|
+
|
333
|
+
it 'sleeps off the remainder' do
|
334
|
+
allow(interval_runner).to receive(:sleep).and_call_original
|
335
|
+
|
336
|
+
interval_runner.every(interval_seconds) do
|
337
|
+
Kernel::sleep(duration_seconds) # non-stoppable
|
338
|
+
stop_holder.value = true # prevent re-runs
|
339
|
+
end
|
340
|
+
|
341
|
+
expect(interval_runner).to have_received(:sleep).with(a_value_within(0.1).of(1))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
context "when the yield takes longer than the interval" do
|
346
|
+
let(:duration_seconds) { 2 }
|
347
|
+
let(:interval_seconds) { 1 }
|
348
|
+
|
349
|
+
it 'logs a warning to the plugin' do
|
350
|
+
allow(interval_runner).to receive(:sleep).and_call_original
|
170
351
|
|
171
|
-
|
352
|
+
interval_runner.every(interval_seconds) do
|
353
|
+
Kernel::sleep(duration_seconds) # non-stoppable
|
354
|
+
stop_holder.value = true # prevent re-runs
|
355
|
+
end
|
356
|
+
|
357
|
+
expect(interval_runner).to_not have_received(:sleep)
|
358
|
+
|
359
|
+
expect(plugin.logger).to have_received(:warn).with(a_string_including("took longer"), a_hash_including(:interval_seconds => interval_seconds, :duration_seconds => a_value_within(0.1).of(duration_seconds)))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'runs regularly until the plugin is stopped' do
|
364
|
+
timestamps = []
|
365
|
+
|
366
|
+
thread = Thread.new do
|
367
|
+
interval_runner.every(1) do
|
368
|
+
timestamps << Time.now
|
369
|
+
Kernel::sleep(Random::rand(0.8))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
Kernel::sleep(5)
|
374
|
+
expect(thread).to be_alive
|
375
|
+
|
376
|
+
stop_holder.value = true
|
377
|
+
Kernel::sleep(1)
|
378
|
+
|
379
|
+
aggregate_failures do
|
380
|
+
expect(thread).to_not be_alive
|
381
|
+
expect(timestamps.count).to be_within(1).of(5)
|
382
|
+
|
383
|
+
timestamps.each_cons(2) do |previous, current|
|
384
|
+
# ensure each start time is very close to 1s after the previous.
|
385
|
+
expect(current - previous).to be_within(0.05).of(1)
|
386
|
+
end
|
387
|
+
|
388
|
+
thread.kill if thread.alive?
|
389
|
+
end
|
390
|
+
end
|
172
391
|
end
|
173
392
|
end
|
174
393
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-input-snmp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elasticsearch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -33,23 +33,45 @@ dependencies:
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
35
35
|
requirements:
|
36
|
-
- - "
|
36
|
+
- - "~>"
|
37
37
|
- !ruby/object:Gem::Version
|
38
|
-
version:
|
39
|
-
|
38
|
+
version: '1.3'
|
39
|
+
name: logstash-mixin-ecs_compatibility_support
|
40
|
+
prerelease: false
|
41
|
+
type: :runtime
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
40
45
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
42
|
-
|
46
|
+
version: '1.3'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.0'
|
53
|
+
name: logstash-mixin-event_support
|
43
54
|
prerelease: false
|
44
55
|
type: :runtime
|
45
56
|
version_requirements: !ruby/object:Gem::Requirement
|
46
57
|
requirements:
|
47
|
-
- - "
|
58
|
+
- - "~>"
|
48
59
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
50
|
-
|
60
|
+
version: '1.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
51
65
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
66
|
+
version: '1.0'
|
67
|
+
name: logstash-mixin-validator_support
|
68
|
+
prerelease: false
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.0'
|
53
75
|
- !ruby/object:Gem::Dependency
|
54
76
|
requirement: !ruby/object:Gem::Requirement
|
55
77
|
requirements:
|
@@ -64,6 +86,26 @@ dependencies:
|
|
64
86
|
- - ">="
|
65
87
|
- !ruby/object:Gem::Version
|
66
88
|
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 0.0.22
|
95
|
+
- - "<"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 0.1.0
|
98
|
+
name: stud
|
99
|
+
prerelease: false
|
100
|
+
type: :runtime
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 0.0.22
|
106
|
+
- - "<"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 0.1.0
|
67
109
|
- !ruby/object:Gem::Dependency
|
68
110
|
requirement: !ruby/object:Gem::Requirement
|
69
111
|
requirements:
|
@@ -414,6 +456,7 @@ files:
|
|
414
456
|
- logstash-input-snmp.gemspec
|
415
457
|
- spec/fixtures/RFC1213-MIB.dic
|
416
458
|
- spec/fixtures/collision.dic
|
459
|
+
- spec/inputs/integration/it_spec.rb
|
417
460
|
- spec/inputs/snmp/base_client_spec.rb
|
418
461
|
- spec/inputs/snmp/mib_spec.rb
|
419
462
|
- spec/inputs/snmp_spec.rb
|
@@ -440,14 +483,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
440
483
|
- !ruby/object:Gem::Version
|
441
484
|
version: '0'
|
442
485
|
requirements: []
|
443
|
-
|
444
|
-
rubygems_version: 2.6.13
|
486
|
+
rubygems_version: 3.1.6
|
445
487
|
signing_key:
|
446
488
|
specification_version: 4
|
447
489
|
summary: SNMP input plugin
|
448
490
|
test_files:
|
449
491
|
- spec/fixtures/RFC1213-MIB.dic
|
450
492
|
- spec/fixtures/collision.dic
|
493
|
+
- spec/inputs/integration/it_spec.rb
|
451
494
|
- spec/inputs/snmp/base_client_spec.rb
|
452
495
|
- spec/inputs/snmp/mib_spec.rb
|
453
496
|
- spec/inputs/snmp_spec.rb
|