logstash-input-snmp 1.2.6 → 1.2.8

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: 4bcb5f73151249317b6f7b36c028a3f863e7d4a88ba6da977fbc88d255355364
4
+ data.tar.gz: 85ef765af5f93aefb92a8f40d9738101ada3b5173bb9b4b8de3d4ec21a406578
5
5
  SHA512:
6
- metadata.gz: cfa32f9ecea52e10fcf38ece4ac80f2037da7a3dfed55560decba78eb80bdfc559a22952d8d85b83e44fbedcbe667b5f01d21d037406eea55a09fa5a684d4cee
7
- data.tar.gz: ec8eca95c464381397b581260803d238d1fb1bf57bd7ae8add5d2718aa438509f0be70473592ba07bb12d19abe5a8aaff49cb5208b010aff283e92782a91c4a4
6
+ metadata.gz: 423f10e23a58b04a72c47d70beb06053dedc8d07e1ec1e36c835ca783c6791bb6ccd12d701d3e50bd4ebe173185a4240ae21a93c36d7ecdf38bf4e2ae6d58f25
7
+ data.tar.gz: f452759e6c453d088b002721ae1520b1c22291e16ff609d8f308e57c3e9aa9dc29b008c809a9a6a76e76b00e68c0399fc96b63f23dc066aa403a682f19712f2e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.2.8
2
+ - 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 [#61](https://github.com/logstash-plugins/logstash-input-snmp/issues/61)
3
+
4
+ ## 1.2.7
5
+ - Added integration tests to ensure SNMP server and IPv6 connections [#87](https://github.com/logstash-plugins/logstash-input-snmp/pull/87)
6
+
1
7
  ## 1.2.6
2
8
  - Docs: example on setting IPv6 hosts [#89](https://github.com/logstash-plugins/logstash-input-snmp/pull/89)
3
9
 
data/docs/index.asciidoc CHANGED
@@ -163,6 +163,7 @@ input {
163
163
  ===== `interval`
164
164
 
165
165
  The `interval` option specifies the polling interval in seconds.
166
+ If polling all configured hosts takes longer than this interval, a warning will be emitted to the logs.
166
167
 
167
168
  * Value type is <<number,number>>
168
169
  * Default value is `30`
@@ -161,9 +161,9 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
161
161
  end
162
162
 
163
163
  def run(queue)
164
- # for now a naive single threaded poller which sleeps for the given interval between
164
+ # for now a naive single threaded poller which sleeps off the remaining interval between
165
165
  # each run. each run polls all the defined hosts for the get and walk options.
166
- while !stop?
166
+ stoppable_interval_runner.every(@interval, "polling hosts") do
167
167
  @client_definitions.each do |definition|
168
168
  result = {}
169
169
  if !definition[:get].empty?
@@ -207,11 +207,13 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
207
207
  queue << event
208
208
  end
209
209
  end
210
-
211
- Stud.stoppable_sleep(@interval) { stop? }
212
210
  end
213
211
  end
214
212
 
213
+ def stoppable_interval_runner
214
+ StoppableIntervalRunner.new(self)
215
+ end
216
+
215
217
  def close
216
218
  @client_definitions.each do |definition|
217
219
  begin
@@ -222,6 +224,9 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
222
224
  end
223
225
  end
224
226
 
227
+ def stop
228
+ end
229
+
225
230
  private
226
231
 
227
232
  OID_REGEX = /^\.?([0-9\.]+)$/
@@ -295,4 +300,45 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
295
300
  def validate_strip!
296
301
  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
302
  end
303
+
304
+ ##
305
+ # The StoppableIntervalRunner is capable of running a block of code at a
306
+ # repeating interval, while respecting the stop condition of the plugin.
307
+ class StoppableIntervalRunner
308
+ ##
309
+ # @param plugin [#logger,#stop?]
310
+ def initialize(plugin)
311
+ @plugin = plugin
312
+ end
313
+
314
+ ##
315
+ # Runs the provided block repeatedly using the provided interval.
316
+ # After executing the block, the remainder of the interval if any is slept off
317
+ # using an interruptible sleep.
318
+ # If no time remains, a warning is emitted to the logs.
319
+ #
320
+ # @param interval_seconds [Integer,Float]
321
+ # @param desc [String] (default: "operation"): a description to use when logging
322
+ # @yield
323
+ def every(interval_seconds, desc="operation", &block)
324
+ until @plugin.stop?
325
+ start_time = Time.now
326
+
327
+ yield
328
+
329
+ duration_seconds = Time.now - start_time
330
+ if duration_seconds >= interval_seconds
331
+ @plugin.logger.warn("#{desc} took longer than the configured interval", :interval_seconds => interval_seconds, :duration_seconds => duration_seconds.round(3))
332
+ else
333
+ remaining_interval = interval_seconds - duration_seconds
334
+ sleep(remaining_interval)
335
+ end
336
+ end
337
+ end
338
+
339
+ # @api private
340
+ def sleep(duration)
341
+ Stud.stoppable_sleep(duration) { @plugin.stop? }
342
+ end
343
+ end
298
344
  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.2.8'
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"
@@ -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
File without changes
@@ -171,5 +171,95 @@ describe LogStash::Inputs::Snmp do
171
171
  expect(event.get("host")).to eq("udp:127.0.0.1/161,public")
172
172
  end
173
173
  end
174
+
175
+ context "StoppableIntervalRunner" do
176
+ let(:stop_holder) { Struct.new(:value).new(false) }
177
+
178
+ before(:each) do
179
+ allow(plugin).to receive(:stop?) { stop_holder.value }
180
+ end
181
+
182
+ let(:plugin) do
183
+ double("Plugin").tap do |dbl|
184
+ allow(dbl).to receive(:logger).and_return(double("Logger").as_null_object)
185
+ allow(dbl).to receive(:stop?) { stop_holder.value }
186
+ end
187
+ end
188
+
189
+ subject(:interval_runner) { LogStash::Inputs::Snmp::StoppableIntervalRunner.new(plugin) }
190
+
191
+ context "#every" do
192
+ context "when the plugin is stopped" do
193
+ let(:interval_seconds) { 2 }
194
+ it 'does not yield the block' do
195
+ stop_holder.value = true
196
+ expect { |yielder| interval_runner.every(interval_seconds, &yielder) }.to_not yield_control
197
+ end
198
+ end
199
+
200
+ context "when the yield takes shorter than the interval" do
201
+ let(:duration_seconds) { 1 }
202
+ let(:interval_seconds) { 2 }
203
+
204
+ it 'sleeps off the remainder' do
205
+ allow(interval_runner).to receive(:sleep).and_call_original
206
+
207
+ interval_runner.every(interval_seconds) do
208
+ Kernel::sleep(duration_seconds) # non-stoppable
209
+ stop_holder.value = true # prevent re-runs
210
+ end
211
+
212
+ expect(interval_runner).to have_received(:sleep).with(a_value_within(0.1).of(1))
213
+ end
214
+ end
215
+
216
+ context "when the yield takes longer than the interval" do
217
+ let(:duration_seconds) { 2 }
218
+ let(:interval_seconds) { 1 }
219
+
220
+ it 'logs a warning to the plugin' do
221
+ allow(interval_runner).to receive(:sleep).and_call_original
222
+
223
+ interval_runner.every(interval_seconds) do
224
+ Kernel::sleep(duration_seconds) # non-stoppable
225
+ stop_holder.value = true # prevent re-runs
226
+ end
227
+
228
+ expect(interval_runner).to_not have_received(:sleep)
229
+
230
+ 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)))
231
+ end
232
+ end
233
+
234
+ it 'runs regularly until the plugin is stopped' do
235
+ timestamps = []
236
+
237
+ thread = Thread.new do
238
+ interval_runner.every(1) do
239
+ timestamps << Time.now
240
+ Kernel::sleep(Random::rand(0.8))
241
+ end
242
+ end
243
+
244
+ Kernel::sleep(5)
245
+ expect(thread).to be_alive
246
+
247
+ stop_holder.value = true
248
+ Kernel::sleep(1)
249
+
250
+ aggregate_failures do
251
+ expect(thread).to_not be_alive
252
+ expect(timestamps.count).to be_within(1).of(5)
253
+
254
+ timestamps.each_cons(2) do |previous, current|
255
+ # ensure each start time is very close to 1s after the previous.
256
+ expect(current - previous).to be_within(0.05).of(1)
257
+ end
258
+
259
+ thread.kill if thread.alive?
260
+ end
261
+ end
262
+ end
263
+ end
174
264
  end
175
265
 
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.2.8
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-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -414,9 +414,12 @@ files:
414
414
  - logstash-input-snmp.gemspec
415
415
  - spec/fixtures/RFC1213-MIB.dic
416
416
  - spec/fixtures/collision.dic
417
+ - spec/inputs/integration/it_spec.rb
417
418
  - spec/inputs/snmp/base_client_spec.rb
419
+ - spec/inputs/snmp/interval_runner_spec.rb
418
420
  - spec/inputs/snmp/mib_spec.rb
419
421
  - spec/inputs/snmp_spec.rb
422
+ - vendor/jar-dependencies/org/snmp4j/snmp4j/2.5.11/snmp4j-2.5.11.jar
420
423
  - vendor/jar-dependencies/org/snmp4j/snmp4j/2.8.4/snmp4j-2.8.4.jar
421
424
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
422
425
  licenses:
@@ -440,14 +443,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
440
443
  - !ruby/object:Gem::Version
441
444
  version: '0'
442
445
  requirements: []
443
- rubyforge_project:
444
- rubygems_version: 2.6.13
446
+ rubygems_version: 3.1.6
445
447
  signing_key:
446
448
  specification_version: 4
447
449
  summary: SNMP input plugin
448
450
  test_files:
449
451
  - spec/fixtures/RFC1213-MIB.dic
450
452
  - spec/fixtures/collision.dic
453
+ - spec/inputs/integration/it_spec.rb
451
454
  - spec/inputs/snmp/base_client_spec.rb
455
+ - spec/inputs/snmp/interval_runner_spec.rb
452
456
  - spec/inputs/snmp/mib_spec.rb
453
457
  - spec/inputs/snmp_spec.rb