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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 149791948ca146c248ba111642501748a2c0eacedc6b5774e4c8dd02ce9fb746
4
- data.tar.gz: e5ac1d8d8d52481ea6b57a37ec09428a36b88c4c374c379aad3d5950c326e24a
3
+ metadata.gz: 13fdcfbd3e486a466a7fa32e1de3e6bbdccd30779f5eb4fce9ef8645355c8322
4
+ data.tar.gz: b8e80f54fbe399f262e48aff1fd614d578b16d493cded3e15446593ebffd1f93
5
5
  SHA512:
6
- metadata.gz: cfa32f9ecea52e10fcf38ece4ac80f2037da7a3dfed55560decba78eb80bdfc559a22952d8d85b83e44fbedcbe667b5f01d21d037406eea55a09fa5a684d4cee
7
- data.tar.gz: ec8eca95c464381397b581260803d238d1fb1bf57bd7ae8add5d2718aa438509f0be70473592ba07bb12d19abe5a8aaff49cb5208b010aff283e92782a91c4a4
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
- The `interval` option specifies the polling interval in seconds.
183
+ * Value type is <<number,number>>
184
+ * Default value is `30`
166
185
 
167
- * Value type is <<number,number>>
168
- * Default value is `30`
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
- The `mib_paths` option specifies the location of one or more imported MIB files. The value can be either a dir path containing
174
- the imported MIB `.dic` files or a file path to a single MIB `.dic` file.
192
+ * Value type is <<path,path>>
193
+ * There is no default value for this setting
175
194
 
176
- * Value type is <<path,path>>
177
- * There is no default value for this setting
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
- The `auth_pass` option specifies the SNMPv3 authentication passphrase or password
289
+ * Value type is <<password,password>>
290
+ * There is no default value for this setting
270
291
 
271
- * Value type is <<password,password>>
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
- The `priv_pass` option specifies the SNMPv3 encryption password
318
+ * Value type is <<password,password>>
319
+ * There is no default value for this setting
286
320
 
287
- * Value type is <<password,password>>
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
- The `priv_protocol` option specifies the SNMPv3 privacy/encryption protocol.
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
- * Value can be any of: `des`, `3des`, `aes`, `aes128`, `aes192`, `aes256`
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
- The `security_name` option specifies the SNMPv3 security name or user name
335
+ * Value type is <<string,string>>
336
+ * There is no default value for this setting
303
337
 
304
- * Value type is <<string,string>>
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
- The `security_level` option specifies the SNMPv3 security level between Authentication, No Privacy; Authentication, Privacy; or no Authentication, no Privacy
343
+ * Value can be any of: `noAuthNoPriv`, `authNoPriv`, `authPriv`
344
+ * There is no default value for this setting
311
345
 
312
- * Value can be any of: `noAuthNoPriv`, `authNoPriv`, `authPriv`
313
- * There is no default value for this setting
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
 
@@ -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 for the given interval between
165
- # each run. each run polls all the defined hosts for the get and walk options.
166
- while !stop?
167
- @client_definitions.each do |definition|
168
- result = {}
169
- if !definition[:get].empty?
170
- begin
171
- result = result.merge(definition[:client].get(definition[:get], @oid_root_skip, @oid_path_length))
172
- rescue => e
173
- logger.error("error invoking get operation on #{definition[:host_address]} for OIDs: #{definition[:get]}, ignoring", :exception => e, :backtrace => e.backtrace)
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
- if !definition[:walk].empty?
177
- definition[:walk].each do |oid|
178
- begin
179
- result = result.merge(definition[:client].walk(oid, @oid_root_skip, @oid_path_length))
180
- rescue => e
181
- logger.error("error invoking walk operation on OID: #{oid}, ignoring", :exception => e, :backtrace => e.backtrace)
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
- if !Array(@tables).empty?
187
- @tables.each do |table_entry|
188
- begin
189
- result = result.merge(definition[:client].table(table_entry, @oid_root_skip, @oid_path_length))
190
- rescue => e
191
- logger.error("error invoking table operation on OID: #{table_entry['name']}, ignoring", :exception => e, :backtrace => e.backtrace)
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
- Stud.stoppable_sleep(@interval) { stop? }
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>udp|tcp):(?<host_address>.+)\/(?<host_port>\d+)$/i
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-snmp'
3
- s.version = '1.2.6'
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 'stud', '>= 0.0.22', '< 0.1.0'
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
@@ -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
- context "@metadata" do
134
- before do
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
- expect(mock_client).to receive(:close).at_most(:once)
142
+ allow(mock_client).to receive(:close).at_most(:once)
139
143
  end
140
144
 
141
- it "shoud add @metadata fields and add default host field" do
142
- config = <<-CONFIG
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
- CONFIG
150
- event = input(config) { |_, queue| queue.pop }
151
-
152
- expect(event.get("[@metadata][host_protocol]")).to eq("udp")
153
- expect(event.get("[@metadata][host_address]")).to eq("127.0.0.1")
154
- expect(event.get("[@metadata][host_port]")).to eq("161")
155
- expect(event.get("[@metadata][host_community]")).to eq("public")
156
- expect(event.get("host")).to eq("127.0.0.1")
157
- end
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
- it "shoud add custom host field" do
160
- config = <<-CONFIG
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
- CONFIG
169
- event = input(config) { |_, queue| queue.pop }
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
- expect(event.get("host")).to eq("udp:127.0.0.1/161,public")
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.2.6
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: 2020-09-21 00:00:00.000000000 Z
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: 0.0.22
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: 0.1.0
42
- name: stud
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: 0.0.22
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: 0.1.0
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
- rubyforge_project:
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