logstash-integration-snmp 4.4.1-java → 4.5.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b6d3ed64fef4ea132eb28af384a760e37a7329e9d37b7246fda9dcbdf7ef6e
4
- data.tar.gz: e889f678af61a031038e5ee1264f7a9ecf56e023f71a4473015a364223934534
3
+ metadata.gz: de5f1db167874981118fb7f26f3c5c60f68c0f1e8254201328960057e6f7ffd3
4
+ data.tar.gz: 98ea051464282e1ac1d9633f5c21bc5014aa3a9e9b8a4458bc2ab71d7c8067e5
5
5
  SHA512:
6
- metadata.gz: 45a570a9cbc17e81139c45ecaa4418c5cc001becfb591798e07e159919b9d253c2f5b80ee61f6b908a5e25de8ea1b417b3790604d68842a51938aff5b79c2636
7
- data.tar.gz: baba3b6c0be0212e7765a63b81bdcdf6a844e4f2ad3c4516a84cd9352ef6c39cade73e8df553c95ea1b5275b471beac62c532c205f8d59c02c36c6703eb1fdde
6
+ metadata.gz: b7c8077ac31d32e5e3dff1f792381f12051f71ce5f697d39bcc3a20cb0dfe29e853e9f2464166b42235c9f873c00315907317cf4cd0e7860352584edbe72407f
7
+ data.tar.gz: bc5d5cddb8f70b44cba17303def6e912c72ef7348724fafd66d97290b34924bd67dcc8af60209d89afc96e5025b90f9337f2c6792e4cb84c30612f078c227ff2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 4.5.0
2
+ - Add `map_enum_values` option to replace integer values that have a MIB named-number (enum) definition with their textual name. [#99](https://github.com/logstash-plugins/logstash-integration-snmp/pull/99)
3
+ - Add `split_tables` option to emit one event per `tables` row instead of a single aggregated event. [#100](https://github.com/logstash-plugins/logstash-integration-snmp/pull/100)
4
+
1
5
  ## 4.4.1
2
6
  - Fix that avoids random null fields on table read. [#98](https://github.com/logstash-plugins/logstash-integration-snmp/pull/98)
3
7
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.4.1
1
+ 4.5.0
@@ -78,6 +78,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
78
78
  | <<plugins-{type}s-{plugin}-allow_partial_response>> |<<boolean,boolean>>|No
79
79
  | <<plugins-{type}s-{plugin}-interval>> |<<number,number>>|No
80
80
  | <<plugins-{type}s-{plugin}-local_engine_id>> |<<string,string>>|No
81
+ | <<plugins-{type}s-{plugin}-map_enum_values>> |<<boolean,boolean>>|No
81
82
  | <<plugins-{type}s-{plugin}-max_repetitions>> |<<number,number>>|No
82
83
  | <<plugins-{type}s-{plugin}-mib_paths>> |<<path,path>>|No
83
84
  | <<plugins-{type}s-{plugin}-oid_mapping_format>> |<<string,string>>, one of `["default", "ruby_snmp", "dotted_string"]`|No
@@ -87,6 +88,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
87
88
  | <<plugins-{type}s-{plugin}-poll_hosts_timeout>> |<<number,number>>|No
88
89
  | <<plugins-{type}s-{plugin}-tag_on_failure>> |<<array,array>>|No
89
90
  | <<plugins-{type}s-{plugin}-walk>> |<<array,array>>|No
91
+ | <<plugins-{type}s-{plugin}-split_tables>> |<<boolean,boolean>>|No
90
92
  | <<plugins-{type}s-{plugin}-tables>> |<<array,array>>|No
91
93
  | <<plugins-{type}s-{plugin}-target>> |<<string,string>>|No
92
94
  | <<plugins-{type}s-{plugin}-threads>> |<<number,number>>|No
@@ -244,6 +246,15 @@ input {
244
246
  }
245
247
  -----
246
248
 
249
+ [id="plugins-{type}s-{plugin}-map_enum_values"]
250
+ ===== `map_enum_values`
251
+
252
+ * Value type is <<boolean,boolean>>
253
+ * Default value is `false`
254
+
255
+ When enabled, integer values that have a named-number (enum) definition in the loaded MIBs
256
+ are replaced with their textual name (for example, `ifOperStatus` `1` becomes `"up"`).
257
+
247
258
  [id="plugins-{type}s-{plugin}-mib_paths"]
248
259
  ===== `mib_paths`
249
260
 
@@ -397,6 +408,20 @@ filter {
397
408
  }
398
409
  -----
399
410
 
411
+ [id="plugins-{type}s-{plugin}-split_tables"]
412
+ ===== `split_tables`
413
+
414
+ * Value type is <<boolean,boolean>>
415
+ * Default value is `false`
416
+
417
+ When enabled, each <<plugins-{type}s-{plugin}-tables>> row is emitted as its own event
418
+ instead of a single aggregated event. This is equivalent to using a `split` filter on the table
419
+ field.
420
+
421
+ The `get` and `walk` fields from the same poll are copied onto every row event. Keep them to
422
+ device-level values, such as the `system` group. Walking table data (for example `ifTable`)
423
+ alongside `split_tables` copies every row's walked values onto every row event.
424
+
400
425
  [id="plugins-{type}s-{plugin}-tables"]
401
426
  ===== `tables`
402
427
 
@@ -94,6 +94,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
94
94
  | <<plugins-{type}s-{plugin}-community>> |<<array,array>>|No
95
95
  | <<plugins-{type}s-{plugin}-ecs_compatibility>> |<<string,string>>|No
96
96
  | <<plugins-{type}s-{plugin}-host>> |<<string,string>>|No
97
+ | <<plugins-{type}s-{plugin}-map_enum_values>> |<<boolean,boolean>>|No
97
98
  | <<plugins-{type}s-{plugin}-mib_paths>> |<<path,path>>|No
98
99
  | <<plugins-{type}s-{plugin}-oid_mapping_format>> |<<string,string>>, one of `["default", "ruby_snmp", "dotted_string"]`|No
99
100
  | <<plugins-{type}s-{plugin}-oid_map_field_values>> |<<boolean,boolean>>|Yes
@@ -180,6 +181,15 @@ Controls this plugin's compatibility with the {ecs-ref}[Elastic Common Schema (E
180
181
 
181
182
  The address to listen on.
182
183
 
184
+ [id="plugins-{type}s-{plugin}-map_enum_values"]
185
+ ===== `map_enum_values`
186
+
187
+ * Value type is <<boolean,boolean>>
188
+ * Default value is `false`
189
+
190
+ When enabled, integer values that have a named-number (enum) definition in the loaded MIBs
191
+ are replaced with their textual name (for example, `ifOperStatus` `1` becomes `"up"`).
192
+
183
193
  [id="plugins-{type}s-{plugin}-mib_paths"]
184
194
  ===== `mib_paths`
185
195
 
@@ -79,6 +79,10 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
79
79
  # When disabled, only data from fully successful operations is included.
80
80
  config :allow_partial_response, :validate => :boolean, :default => false
81
81
 
82
+ # When enabled, emits one event per `tables` row instead of a single aggregated event.
83
+ # Each row event carries the `get`/`walk` scalar fields. Equivalent to using a `split` filter.
84
+ config :split_tables, :validate => :boolean, :default => false
85
+
82
86
  def initialize(params={})
83
87
  super(params)
84
88
 
@@ -97,6 +101,8 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
97
101
  def register
98
102
  validate_config!
99
103
 
104
+ @table_names = Array(@tables).map { |table_entry| table_entry['name'] }
105
+
100
106
  mib_manager = build_mib_manager!
101
107
 
102
108
  # setup client definitions per provided host
@@ -200,17 +206,15 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
200
206
  definition = req[:definition]
201
207
  result_consumer = lambda do |request_result|
202
208
  result = request_result.data
203
- if result&.any? || request_result.has_errors
204
- event = targeted_event_factory.new_event(result)
205
- event.set(@host_protocol_field, definition[:host_protocol])
206
- event.set(@host_address_field, definition[:host_address])
207
- event.set(@host_port_field, definition[:host_port])
208
- event.set(@host_community_field, definition[:host_community])
209
- decorate(event)
210
- @tag_on_failure.each { |tag| event.tag(tag) } if request_result.has_errors
211
- queue << event
212
- else
209
+ unless result&.any? || request_result.has_errors
213
210
  logger.debug? && logger.debug('No SNMP data retrieved', host: definition[:host_address])
211
+ next
212
+ end
213
+
214
+ if @split_tables && @table_names.any?
215
+ emit_split_table_events(queue, definition, request_result, result)
216
+ else
217
+ emit_event(queue, definition, request_result, result)
214
218
  end
215
219
  end
216
220
 
@@ -225,6 +229,37 @@ class LogStash::Inputs::Snmp < LogStash::Inputs::Base
225
229
  end
226
230
  end
227
231
 
232
+ def emit_event(queue, definition, request_result, fields)
233
+ event = targeted_event_factory.new_event(fields)
234
+ event.set(@host_protocol_field, definition[:host_protocol])
235
+ event.set(@host_address_field, definition[:host_address])
236
+ event.set(@host_port_field, definition[:host_port])
237
+ event.set(@host_community_field, definition[:host_community])
238
+ decorate(event)
239
+ @tag_on_failure.each { |tag| event.tag(tag) } if request_result.has_errors
240
+ queue << event
241
+ end
242
+
243
+ # Splits `tables` results into one event per row, keeping the `get`/`walk` scalar fields
244
+ # on each row event. With no rows, scalar data or a failed poll is emitted as a single event.
245
+ def emit_split_table_events(queue, definition, request_result, result)
246
+ scalars = {}
247
+ tables = {}
248
+ result.each do |key, value|
249
+ (@table_names.include?(key) ? tables : scalars)[key] = value
250
+ end
251
+
252
+ emitted = false
253
+ tables.each do |name, rows|
254
+ rows.each do |row|
255
+ emit_event(queue, definition, request_result, scalars.merge(name => row))
256
+ emitted = true
257
+ end
258
+ end
259
+
260
+ emit_event(queue, definition, request_result, scalars) if !emitted && (scalars.any? || request_result.has_errors)
261
+ end
262
+
228
263
  def poll_hosts_timeout(max_host_timeout)
229
264
  return @poll_hosts_timeout if @poll_hosts_timeout
230
265
 
@@ -52,6 +52,10 @@ module LogStash
52
52
  # set on the `oid_mapping_format`.
53
53
  base.config :oid_map_field_values, :validate => :boolean, :default => false
54
54
 
55
+ # Replaces integer values that have a named-number (enum) definition in the loaded
56
+ # MIBs with their textual name (e.g. `ifOperStatus` 1 -> "up").
57
+ base.config :map_enum_values, :validate => :boolean, :default => false
58
+
55
59
  # Number of OID root digits to ignore in event field name. For example, in a numeric OID
56
60
  # like 1.3.6.1.2.1.1.1.0" the first 5 digits could be ignored by setting oid_root_skip => 5
57
61
  # which would result in a field name "1.1.1.0". Similarly when a MIB is used an OID such
@@ -122,6 +126,7 @@ module LogStash
122
126
  end
123
127
 
124
128
  client_builder.setMapOidVariableValues(@oid_map_field_values)
129
+ client_builder.setMapEnumValues(@map_enum_values)
125
130
  client_builder.build
126
131
  end
127
132
 
@@ -4,4 +4,4 @@ require 'jar_dependencies'
4
4
  require_jar('org.snmp4j', 'snmp4j-log4j', '2.8.11')
5
5
  require_jar('org.snmp4j', 'snmp4j', '3.8.0')
6
6
  require_jar('org.snakeyaml', 'snakeyaml-engine', '2.7')
7
- require_jar('org.logstash.integrations', 'plugin', '4.4.1')
7
+ require_jar('org.logstash.integrations', 'plugin', '4.5.0')
@@ -440,6 +440,42 @@ describe LogStash::Inputs::Snmp, :integration => true do
440
440
  end
441
441
  end
442
442
 
443
+ context 'with map_enum_values' do
444
+ let(:config) do
445
+ super().merge({
446
+ 'hosts' => [{ 'host' => "udp:snmpsim/163", 'version' => '3' }],
447
+ 'security_name' => 'testuser',
448
+ 'auth_protocol' => 'sha',
449
+ 'auth_pass' => 'authp123',
450
+ 'priv_protocol' => 'aes256with3desKey',
451
+ 'priv_pass' => 'privpass123',
452
+ 'security_level' => 'authPriv',
453
+ 'get' => ['1.3.6.1.2.1.2.2.1.8.1'], # ifOperStatus.1 = 1 (up)
454
+ 'use_provided_mibs' => true,
455
+ 'mib_paths' => [],
456
+ 'map_enum_values' => map_enum_values
457
+ })
458
+ end
459
+
460
+ context 'set to false' do
461
+ let(:map_enum_values) { false }
462
+
463
+ it 'should not translate enum values' do
464
+ event = run_plugin_and_get_queue(plugin).pop
465
+ expect(event.get('iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.1')).to eq(1)
466
+ end
467
+ end
468
+
469
+ context 'set to true' do
470
+ let(:map_enum_values) { true }
471
+
472
+ it 'should translate enum values' do
473
+ event = run_plugin_and_get_queue(plugin).pop
474
+ expect(event.get('iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.1')).to eq('up')
475
+ end
476
+ end
477
+ end
478
+
443
479
  def run_plugin_and_get_queue(plugin, timeout: 30, register: true)
444
480
  poll_clients_latch = Concurrent::CountDownLatch.new(1)
445
481
 
@@ -161,6 +161,7 @@ shared_examples 'a common SNMP plugin' do
161
161
  it 'should not raise' do
162
162
  expect(client_builder).to receive(:addUsmUser)
163
163
  expect(client_builder).to receive(:setMapOidVariableValues)
164
+ expect(client_builder).to receive(:setMapEnumValues)
164
165
  expect(client_builder).to receive(:build)
165
166
  expect { plugin.build_snmp_client!(client_builder, validate_usm_user: true) }.to_not raise_error
166
167
  end
@@ -192,6 +193,7 @@ shared_examples 'a common SNMP plugin' do
192
193
  it 'should not raise' do
193
194
  expect(client_builder).to receive(:addUsmUser)
194
195
  expect(client_builder).to receive(:setMapOidVariableValues)
196
+ expect(client_builder).to receive(:setMapEnumValues)
195
197
  expect(client_builder).to receive(:build)
196
198
  expect { plugin.build_snmp_client!(client_builder, validate_usm_user: true) }.to_not raise_error
197
199
  end
@@ -203,6 +205,7 @@ shared_examples 'a common SNMP plugin' do
203
205
  it 'should not raise' do
204
206
  expect(client_builder).to receive(:addUsmUser)
205
207
  expect(client_builder).to receive(:setMapOidVariableValues)
208
+ expect(client_builder).to receive(:setMapEnumValues)
206
209
  expect(client_builder).to receive(:build)
207
210
  expect { plugin.build_snmp_client!(client_builder, validate_usm_user: true) }.to_not raise_error
208
211
  end
@@ -334,6 +334,150 @@ describe LogStash::Inputs::Snmp, :ecs_compatibility_support do
334
334
  end
335
335
  end
336
336
 
337
+ context 'mocked tables' do
338
+ before(:each) do
339
+ allow(mock_aggregator_request).to receive(:get)
340
+ allow(mock_aggregator_request).to receive(:table)
341
+ end
342
+
343
+ context 'with split_tables' do
344
+ let(:config) do
345
+ super().merge({
346
+ 'hosts' => [{ 'host' => "udp:127.0.0.1/161" }],
347
+ 'get' => ["1.3.6.1.2.1.1.5.0"],
348
+ 'tables' => [{ 'name' => 'interfaces', 'columns' => ['1.3.6.1.2.1.2.2.1.2'] }],
349
+ 'split_tables' => true
350
+ })
351
+ end
352
+
353
+ before(:each) do
354
+ expect(mock_aggregator_request).to receive(:get_result_async) do |consumer|
355
+ consumer.call(RequestResult.new({
356
+ 'sysName' => 'test-agent',
357
+ 'interfaces' => [{ 'index' => '1', 'ifDescr' => 'lo' }, { 'index' => '2', 'ifDescr' => 'eth0' }]
358
+ }, false))
359
+ end
360
+ end
361
+
362
+ it 'emits one event per row, with scalar fields on each' do
363
+ plugin.register
364
+ plugin.run(queue)
365
+ events = 2.times.map { queue.pop }
366
+
367
+ expect(events.map { |e| e.get('[interfaces][index]') }).to contain_exactly('1', '2')
368
+ events.each { |e| expect(e.get('sysName')).to eq('test-agent') }
369
+ end
370
+ end
371
+
372
+ context 'with split_tables and multiple tables' do
373
+ let(:config) do
374
+ super().merge({
375
+ 'hosts' => [{ 'host' => "udp:127.0.0.1/161" }],
376
+ 'tables' => [
377
+ { 'name' => 'interfaces', 'columns' => ['1.3.6.1.2.1.2.2.1.2'] },
378
+ { 'name' => 'cpu', 'columns' => ['1.3.6.1.2.1.25.3.3.1.2'] }
379
+ ],
380
+ 'split_tables' => true
381
+ })
382
+ end
383
+
384
+ before(:each) do
385
+ expect(mock_aggregator_request).to receive(:get_result_async) do |consumer|
386
+ consumer.call(RequestResult.new({
387
+ 'interfaces' => [{ 'index' => '1' }, { 'index' => '2' }],
388
+ 'cpu' => [{ 'index' => '1' }]
389
+ }, false))
390
+ end
391
+ end
392
+
393
+ it 'emits rows per table without a cross-product' do
394
+ plugin.register
395
+ plugin.run(queue)
396
+ events = 3.times.map { queue.pop }
397
+
398
+ expect(events.count { |e| e.get('interfaces') }).to eq(2)
399
+ expect(events.count { |e| e.get('cpu') }).to eq(1)
400
+ end
401
+ end
402
+
403
+ context 'with split_tables and an empty table' do
404
+ let(:config) do
405
+ super().merge({
406
+ 'hosts' => [{ 'host' => "udp:127.0.0.1/161" }],
407
+ 'get' => ["1.3.6.1.2.1.1.5.0"],
408
+ 'tables' => [{ 'name' => 'interfaces', 'columns' => ['1.3.6.1.2.1.2.2.1.2'] }],
409
+ 'split_tables' => true
410
+ })
411
+ end
412
+
413
+ before(:each) do
414
+ expect(mock_aggregator_request).to receive(:get_result_async) do |consumer|
415
+ consumer.call(RequestResult.new({ 'sysName' => 'test-agent', 'interfaces' => [] }, false))
416
+ end
417
+ end
418
+
419
+ it 'emits a single event with the scalar fields' do
420
+ plugin.register
421
+ plugin.run(queue)
422
+ event = queue.pop
423
+
424
+ expect(event.get('sysName')).to eq('test-agent')
425
+ expect(event.get('interfaces')).to be_nil
426
+ end
427
+ end
428
+
429
+ context 'with split_tables when all operations fail' do
430
+ let(:config) do
431
+ super().merge({
432
+ 'hosts' => [{ 'host' => "udp:127.0.0.1/161" }],
433
+ 'tables' => [{ 'name' => 'interfaces', 'columns' => ['1.3.6.1.2.1.2.2.1.2'] }],
434
+ 'split_tables' => true
435
+ })
436
+ end
437
+
438
+ before(:each) do
439
+ expect(mock_aggregator_request).to receive(:get_result_async) do |consumer|
440
+ consumer.call(RequestResult.new({}, true))
441
+ end
442
+ end
443
+
444
+ it 'emits a single event with the failure tag' do
445
+ plugin.register
446
+ plugin.run(queue)
447
+
448
+ expect(queue.size).to eq(1)
449
+ expect(queue.pop.get('tags')).to include('_snmpfailure')
450
+ end
451
+ end
452
+
453
+ context 'without split_tables (default)' do
454
+ let(:config) do
455
+ super().merge({
456
+ 'hosts' => [{ 'host' => "udp:127.0.0.1/161" }],
457
+ 'tables' => [{ 'name' => 'interfaces', 'columns' => ['1.3.6.1.2.1.2.2.1.2'] }]
458
+ })
459
+ end
460
+
461
+ before(:each) do
462
+ expect(mock_aggregator_request).to receive(:get_result_async) do |consumer|
463
+ consumer.call(RequestResult.new({
464
+ 'interfaces' => [{ 'index' => '1' }, { 'index' => '2' }]
465
+ }, false))
466
+ end
467
+ end
468
+
469
+ it 'emits a single aggregated event' do
470
+ plugin.register
471
+ plugin.run(queue)
472
+
473
+ expect(queue.size).to eq(1)
474
+ event = queue.pop
475
+ expect(event.get('[interfaces][0][index]')).to eq('1')
476
+ expect(event.get('[interfaces][1][index]')).to eq('2')
477
+ end
478
+ end
479
+ end
480
+
337
481
  context 'mocked result with errors' do
338
482
  let(:config) do
339
483
  super().merge({
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-integration-snmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.1
4
+ version: 4.5.0
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-01 00:00:00.000000000 Z
10
+ date: 2026-10-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: logstash-core-plugin-api
@@ -468,7 +468,7 @@ files:
468
468
  - spec/unit/inputs/common_spec.rb
469
469
  - spec/unit/inputs/snmp_spec.rb
470
470
  - spec/unit/inputs/snmptrap_spec.rb
471
- - vendor/jar-dependencies/org/logstash/integrations/plugin/4.4.1/plugin-4.4.1.jar
471
+ - vendor/jar-dependencies/org/logstash/integrations/plugin/4.5.0/plugin-4.5.0.jar
472
472
  - vendor/jar-dependencies/org/snakeyaml/snakeyaml-engine/2.7/snakeyaml-engine-2.7.jar
473
473
  - vendor/jar-dependencies/org/snmp4j/snmp4j-log4j/2.8.11/snmp4j-log4j-2.8.11.jar
474
474
  - vendor/jar-dependencies/org/snmp4j/snmp4j/3.8.0/snmp4j-3.8.0.jar