logstash-input-snmp 1.2.6 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|