logstash-input-elasticsearch 4.13.0 → 4.16.0

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: d87078bdf63844901c7684624bc15bd6af23c71c728adb20b86ba76738957926
4
- data.tar.gz: 34d8ce81035665b93623aec9008c511000b80a463c0cf482fb459249c763aeaf
3
+ metadata.gz: 1c5466b50a56ec047ac20dc019d9e24862756b518613b63edf44583900a090e2
4
+ data.tar.gz: 6b3915bd640318ebd6fa4b87f676d7e43d4fba7fe54893efdb6eb39f70481da8
5
5
  SHA512:
6
- metadata.gz: 4b2c5aa7a229bdbc89df35276c22a72f1cdbefc03a9069dfb74e64aad64d121aeb3ee67a9b632fd8ab445d1a256cce23f67feabb4ff379ed78a15507f28b1dce
7
- data.tar.gz: 7c7a45cd3817d9e746a03eb166293e389eee16fc512bb2e0ecc546158d67f1f4e0f54bc191212e02ce1af206fe5f98374d70defa3f8ef218bed9e210ac8a8a65
6
+ metadata.gz: 930202045f30125060cce1f3d45b22c0336976a6cdcbf5931f4b98410785bd425cf665224edfe4994953ad2d53cdaa994b31be9db86fd05f538a722ff788ba97
7
+ data.tar.gz: 8cf1f45481575f653867831ab38fb7efd286c12432b2afc2c6940c32d4eb91f913dd7b89c284ed4e11e9205536cc75b318f399b92136777f84ac6dec75d2de2d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 4.16.0
2
+ - Added `ssl_certificate_verification` option to control SSL certificate verification [#180](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/180)
3
+
4
+ ## 4.15.0
5
+ - Feat: add `retries` option. allow retry for failing query [#179](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/179)
6
+
7
+ ## 4.14.0
8
+ - Refactor: switch to using scheduler mixin [#177](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/177)
9
+
1
10
  ## 4.13.0
2
11
  - Added support for `ca_trusted_fingerprint` when run on Logstash 8.3+ [#178](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/178)
3
12
 
data/docs/index.asciidoc CHANGED
@@ -122,8 +122,10 @@ This plugin supports the following configuration options plus the <<plugins-{typ
122
122
  | <<plugins-{type}s-{plugin}-size>> |<<number,number>>|No
123
123
  | <<plugins-{type}s-{plugin}-slices>> |<<number,number>>|No
124
124
  | <<plugins-{type}s-{plugin}-ssl>> |<<boolean,boolean>>|No
125
+ | <<plugins-{type}s-{plugin}-ssl_certificate_verification>> |<<boolean,boolean>>|No
125
126
  | <<plugins-{type}s-{plugin}-socket_timeout_seconds>> | <<number,number>>|No
126
127
  | <<plugins-{type}s-{plugin}-target>> | {logstash-ref}/field-references-deepdive.html[field reference] | No
128
+ | <<plugins-{type}s-{plugin}-retries>> | <<number,number>>|No
127
129
  | <<plugins-{type}s-{plugin}-user>> |<<string,string>>|No
128
130
  |=======================================================================
129
131
 
@@ -339,6 +341,17 @@ The maximum amount of time, in seconds, for a single request to Elasticsearch.
339
341
  Request timeouts tend to occur when an individual page of data is very large, such as when it contains large-payload
340
342
  documents and/or the <<plugins-{type}s-{plugin}-size>> has been specified as a large value.
341
343
 
344
+
345
+ [id="plugins-{type}s-{plugin}-retries"]
346
+ ===== `retries`
347
+
348
+ * Value type is <<number,number>>
349
+ * Default value is `0`
350
+
351
+ The number of times to re-run the query after the first failure. If the query fails after all retries, it logs an error message.
352
+ The default is 0 (no retry). This value should be equal to or greater than zero.
353
+
354
+
342
355
  [id="plugins-{type}s-{plugin}-schedule"]
343
356
  ===== `schedule`
344
357
 
@@ -402,6 +415,20 @@ instructions into the query.
402
415
  If enabled, SSL will be used when communicating with the Elasticsearch
403
416
  server (i.e. HTTPS will be used instead of plain HTTP).
404
417
 
418
+ [id="plugins-{type}s-{plugin}-ssl_certificate_verification"]
419
+ ===== `ssl_certificate_verification`
420
+
421
+ * Value type is <<boolean,boolean>>
422
+ * Default value is `true`
423
+
424
+ Option to validate the server's certificate. Disabling this severely compromises security.
425
+ When certificate validation is disabled, this plugin implicitly trusts the machine
426
+ resolved at the given address without validating its proof-of-identity.
427
+ In this scenario, the plugin can transmit credentials to or process data from an untrustworthy
428
+ man-in-the-middle or other compromised infrastructure.
429
+ More information on the importance of certificate verification:
430
+ **https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf**.
431
+
405
432
  [id="plugins-{type}s-{plugin}-socket_timeout_seconds"]
406
433
  ===== `socket_timeout_seconds`
407
434
 
@@ -424,6 +451,7 @@ When the `target` is set to a field reference, the `_source` of the hit is place
424
451
  This option can be useful to avoid populating unknown fields when a downstream schema such as ECS is enforced.
425
452
  It is also possible to target an entry in the event's metadata, which will be available during event processing but not exported to your outputs (e.g., `target \=> "[@metadata][_source]"`).
426
453
 
454
+
427
455
  [id="plugins-{type}s-{plugin}-user"]
428
456
  ===== `user`
429
457
 
@@ -0,0 +1,18 @@
1
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2
+ # or more contributor license agreements. Licensed under the Elastic License;
3
+ # you may not use this file except in compliance with the Elastic License.
4
+
5
+ require 'stud/try'
6
+
7
+ module LogStash module Helpers
8
+ class LoggableTry < Stud::Try
9
+ def initialize(logger, name)
10
+ @logger = logger
11
+ @name = name
12
+ end
13
+
14
+ def log_failure(exception, fail_count, message)
15
+ @logger.warn("Attempt to #{@name} but failed. #{message}", fail_count: fail_count, exception: exception.message)
16
+ end
17
+ end
18
+ end end
@@ -8,7 +8,9 @@ require 'logstash/plugin_mixins/event_support/event_factory_adapter'
8
8
  require 'logstash/plugin_mixins/ecs_compatibility_support'
9
9
  require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
10
10
  require 'logstash/plugin_mixins/ca_trusted_fingerprint_support'
11
+ require "logstash/plugin_mixins/scheduler"
11
12
  require "base64"
13
+ require 'logstash/helpers/loggable_try'
12
14
 
13
15
  require "elasticsearch"
14
16
  require "elasticsearch/transport/transport/http/manticore"
@@ -78,6 +80,8 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
78
80
 
79
81
  extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
80
82
 
83
+ include LogStash::PluginMixins::Scheduler
84
+
81
85
  config_name "elasticsearch"
82
86
 
83
87
  # List of elasticsearch hosts to use for querying.
@@ -96,6 +100,9 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
96
100
  # This allows you to set the maximum number of hits returned per scroll.
97
101
  config :size, :validate => :number, :default => 1000
98
102
 
103
+ # The number of retries to run the query. If the query fails after all retries, it logs an error message.
104
+ config :retries, :validate => :number, :default => 0
105
+
99
106
  # This parameter controls the keepalive time in seconds of the scrolling
100
107
  # request and initiates the scrolling process. The timeout applies per
101
108
  # round trip (i.e. between the previous scroll request, to the next).
@@ -183,6 +190,11 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
183
190
  # SSL Certificate Authority file in PEM encoded format, must also include any chain certificates as necessary
184
191
  config :ca_file, :validate => :path
185
192
 
193
+ # Option to validate the server's certificate. Disabling this severely compromises security.
194
+ # For more information on the importance of certificate verification please read
195
+ # https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf
196
+ config :ssl_certificate_verification, :validate => :boolean, :default => true
197
+
186
198
  # Schedule of when to periodically run statement, in Cron format
187
199
  # for example: "* * * * *" (execute query every minute, on the minute)
188
200
  #
@@ -218,6 +230,8 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
218
230
  @slices < 1 && fail(LogStash::ConfigurationError, "Elasticsearch Input Plugin's `slices` option must be greater than zero, got `#{@slices}`")
219
231
  end
220
232
 
233
+ @retries < 0 && fail(LogStash::ConfigurationError, "Elasticsearch Input Plugin's `retries` option must be equal or greater than zero, got `#{@retries}`")
234
+
221
235
  validate_authentication
222
236
  fill_user_password_from_cloud_auth
223
237
  fill_hosts_from_cloud_id
@@ -251,37 +265,75 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
251
265
 
252
266
  def run(output_queue)
253
267
  if @schedule
254
- @scheduler = Rufus::Scheduler.new(:max_work_threads => 1)
255
- @scheduler.cron @schedule do
256
- do_run(output_queue)
257
- end
258
-
259
- @scheduler.join
268
+ scheduler.cron(@schedule) { do_run(output_queue) }
269
+ scheduler.join
260
270
  else
261
271
  do_run(output_queue)
262
272
  end
263
273
  end
264
274
 
265
- def stop
266
- @scheduler.stop if @scheduler
267
- end
268
-
269
275
  private
270
-
276
+ JOB_NAME = "run query"
271
277
  def do_run(output_queue)
272
278
  # if configured to run a single slice, don't bother spinning up threads
273
- return do_run_slice(output_queue) if @slices.nil? || @slices <= 1
279
+ if @slices.nil? || @slices <= 1
280
+ success, events = retryable_slice
281
+ success && events.each { |event| output_queue << event }
282
+ return
283
+ end
274
284
 
275
285
  logger.warn("managed slices for query is very large (#{@slices}); consider reducing") if @slices > 8
276
286
 
287
+ slice_results = parallel_slice # array of tuple(ok, events)
288
+
289
+ # insert events to queue if all slices success
290
+ if slice_results.all?(&:first)
291
+ slice_results.flat_map { |success, events| events }
292
+ .each { |event| output_queue << event }
293
+ end
294
+
295
+ logger.trace("#{@slices} slices completed")
296
+ end
297
+
298
+ def retryable(job_name, &block)
299
+ begin
300
+ stud_try = ::LogStash::Helpers::LoggableTry.new(logger, job_name)
301
+ output = stud_try.try((@retries + 1).times) { yield }
302
+ [true, output]
303
+ rescue => e
304
+ error_details = {:message => e.message, :cause => e.cause}
305
+ error_details[:backtrace] = e.backtrace if logger.debug?
306
+ logger.error("Tried #{job_name} unsuccessfully", error_details)
307
+ [false, nil]
308
+ end
309
+ end
310
+
311
+
312
+ # @return [(ok, events)] : Array of tuple(Boolean, [Logstash::Event])
313
+ def parallel_slice
314
+ pipeline_id = execution_context&.pipeline_id || 'main'
277
315
  @slices.times.map do |slice_id|
278
316
  Thread.new do
279
- LogStash::Util::set_thread_name("#{@id}_slice_#{slice_id}")
280
- do_run_slice(output_queue, slice_id)
317
+ LogStash::Util::set_thread_name("[#{pipeline_id}]|input|elasticsearch|slice_#{slice_id}")
318
+ retryable_slice(slice_id)
281
319
  end
282
- end.map(&:join)
320
+ end.map do |t|
321
+ t.join
322
+ t.value
323
+ end
283
324
  end
284
325
 
326
+ # @param scroll_id [Integer]
327
+ # @return (ok, events) [Boolean, Array(Logstash::Event)]
328
+ def retryable_slice(slice_id=nil)
329
+ retryable(JOB_NAME) do
330
+ output = []
331
+ do_run_slice(output, slice_id)
332
+ output
333
+ end
334
+ end
335
+
336
+
285
337
  def do_run_slice(output_queue, slice_id=nil)
286
338
  slice_query = @base_query
287
339
  slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @slices}) unless slice_id.nil?
@@ -318,11 +370,6 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
318
370
  r = scroll_request(scroll_id)
319
371
  r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
320
372
  [r['hits']['hits'].any?, r['_scroll_id']]
321
- rescue => e
322
- # this will typically be triggered by a scroll timeout
323
- logger.error("Scroll request error, aborting scroll", message: e.message, exception: e.class)
324
- # return no hits and original scroll_id so we can try to clear it
325
- [false, scroll_id]
326
373
  end
327
374
 
328
375
  def push_hit(hit, output_queue)
@@ -357,7 +404,7 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
357
404
  logger.warn("Ignoring clear_scroll exception", message: e.message, exception: e.class)
358
405
  end
359
406
 
360
- def scroll_request scroll_id
407
+ def scroll_request(scroll_id)
361
408
  @client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
362
409
  end
363
410
 
@@ -390,6 +437,11 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
390
437
  ssl_options[:ssl] = true if @ssl
391
438
  ssl_options[:ca_file] = @ca_file if @ssl && @ca_file
392
439
  ssl_options[:trust_strategy] = trust_strategy_for_ca_trusted_fingerprint
440
+ if @ssl && !@ssl_certificate_verification
441
+ logger.warn "You have enabled encryption but DISABLED certificate verification, " +
442
+ "to make sure your data is secure remove `ssl_certificate_verification => false`"
443
+ ssl_options[:verify] = :disable
444
+ end
393
445
 
394
446
  ssl_options
395
447
  end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-input-elasticsearch'
4
- s.version = '4.13.0'
4
+ s.version = '4.16.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Reads query results from an Elasticsearch cluster"
7
7
  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"
@@ -24,13 +24,13 @@ Gem::Specification.new do |s|
24
24
  s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
25
25
  s.add_runtime_dependency 'logstash-mixin-event_support', '~> 1.0'
26
26
  s.add_runtime_dependency "logstash-mixin-validator_support", '~> 1.0'
27
+ s.add_runtime_dependency "logstash-mixin-scheduler", '~> 1.0'
27
28
 
28
29
  s.add_runtime_dependency 'elasticsearch', '>= 7.17.1'
29
30
  s.add_runtime_dependency 'logstash-mixin-ca_trusted_fingerprint_support', '~> 1.0'
30
31
 
31
32
  s.add_runtime_dependency 'tzinfo'
32
33
  s.add_runtime_dependency 'tzinfo-data'
33
- s.add_runtime_dependency 'rufus-scheduler'
34
34
  s.add_runtime_dependency 'manticore', ">= 0.7.1"
35
35
 
36
36
  s.add_development_dependency 'logstash-codec-plain'
@@ -51,6 +51,17 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
51
51
  expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
52
52
  end
53
53
  end
54
+
55
+ context "retry" do
56
+ let(:config) do
57
+ {
58
+ "retries" => -1
59
+ }
60
+ end
61
+ it "should raise an exception with negative number" do
62
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
63
+ end
64
+ end
54
65
  end
55
66
 
56
67
  it_behaves_like "an interruptible input plugin" do
@@ -193,7 +204,7 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
193
204
  context 'with `slices => 1`' do
194
205
  let(:slices) { 1 }
195
206
  it 'runs just one slice' do
196
- expect(plugin).to receive(:do_run_slice).with(duck_type(:<<))
207
+ expect(plugin).to receive(:do_run_slice).with(duck_type(:<<), nil)
197
208
  expect(Thread).to_not receive(:new)
198
209
 
199
210
  plugin.register
@@ -204,7 +215,7 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
204
215
  context 'without slices directive' do
205
216
  let(:config) { super().tap { |h| h.delete('slices') } }
206
217
  it 'runs just one slice' do
207
- expect(plugin).to receive(:do_run_slice).with(duck_type(:<<))
218
+ expect(plugin).to receive(:do_run_slice).with(duck_type(:<<), nil)
208
219
  expect(Thread).to_not receive(:new)
209
220
 
210
221
  plugin.register
@@ -315,7 +326,6 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
315
326
  end
316
327
  # END SLICE 1
317
328
 
318
- let(:client) { Elasticsearch::Client.new }
319
329
 
320
330
  # RSpec mocks validations are not threadsafe.
321
331
  # Allow caller to synchronize.
@@ -329,69 +339,112 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
329
339
  end
330
340
  end
331
341
 
332
- before(:each) do
333
- expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
334
- plugin.register
342
+ describe "with normal response" do
343
+ before(:each) do
344
+ expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
345
+ plugin.register
335
346
 
336
- expect(client).to receive(:clear_scroll).and_return(nil)
347
+ expect(client).to receive(:clear_scroll).and_return(nil)
337
348
 
338
- # SLICE0 is a three-page scroll in which the last page is empty
339
- slice0_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 0, 'max' => 2}))
340
- expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
341
- expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll1 })).and_return(slice0_response1)
342
- expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll2 })).and_return(slice0_response2)
343
- allow(client).to receive(:ping)
349
+ # SLICE0 is a three-page scroll in which the last page is empty
350
+ slice0_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 0, 'max' => 2}))
351
+ expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
352
+ expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll1 })).and_return(slice0_response1)
353
+ expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll2 })).and_return(slice0_response2)
354
+ allow(client).to receive(:ping)
344
355
 
345
- # SLICE1 is a two-page scroll in which the last page has no next scroll id
346
- slice1_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 1, 'max' => 2}))
347
- expect(client).to receive(:search).with(hash_including(:body => slice1_query)).and_return(slice1_response0)
348
- expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice1_scroll1 })).and_return(slice1_response1)
356
+ # SLICE1 is a two-page scroll in which the last page has no next scroll id
357
+ slice1_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 1, 'max' => 2}))
358
+ expect(client).to receive(:search).with(hash_including(:body => slice1_query)).and_return(slice1_response0)
359
+ expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice1_scroll1 })).and_return(slice1_response1)
349
360
 
350
- synchronize_method!(plugin, :scroll_request)
351
- synchronize_method!(plugin, :search_request)
352
- end
361
+ synchronize_method!(plugin, :scroll_request)
362
+ synchronize_method!(plugin, :search_request)
363
+ end
353
364
 
354
- let(:emitted_events) do
355
- queue = Queue.new # since we are running slices in threads, we need a thread-safe queue.
356
- plugin.run(queue)
357
- events = []
358
- events << queue.pop until queue.empty?
359
- events
360
- end
365
+ let(:client) { Elasticsearch::Client.new }
361
366
 
362
- let(:emitted_event_ids) do
363
- emitted_events.map { |event| event.get('[@metadata][_id]') }
364
- end
367
+ let(:emitted_events) do
368
+ queue = Queue.new # since we are running slices in threads, we need a thread-safe queue.
369
+ plugin.run(queue)
370
+ events = []
371
+ events << queue.pop until queue.empty?
372
+ events
373
+ end
365
374
 
366
- it 'emits the hits on the first page of the first slice' do
367
- expect(emitted_event_ids).to include('slice0-response0-item0')
368
- expect(emitted_event_ids).to include('slice0-response0-item1')
369
- end
370
- it 'emits the hits on the second page of the first slice' do
371
- expect(emitted_event_ids).to include('slice0-response1-item0')
372
- end
375
+ let(:emitted_event_ids) do
376
+ emitted_events.map { |event| event.get('[@metadata][_id]') }
377
+ end
373
378
 
374
- it 'emits the hits on the first page of the second slice' do
375
- expect(emitted_event_ids).to include('slice1-response0-item0')
376
- expect(emitted_event_ids).to include('slice1-response0-item1')
377
- end
379
+ it 'emits the hits on the first page of the first slice' do
380
+ expect(emitted_event_ids).to include('slice0-response0-item0')
381
+ expect(emitted_event_ids).to include('slice0-response0-item1')
382
+ end
383
+ it 'emits the hits on the second page of the first slice' do
384
+ expect(emitted_event_ids).to include('slice0-response1-item0')
385
+ end
378
386
 
379
- it 'emits the hitson the second page of the second slice' do
380
- expect(emitted_event_ids).to include('slice1-response1-item0')
381
- expect(emitted_event_ids).to include('slice1-response1-item1')
382
- end
387
+ it 'emits the hits on the first page of the second slice' do
388
+ expect(emitted_event_ids).to include('slice1-response0-item0')
389
+ expect(emitted_event_ids).to include('slice1-response0-item1')
390
+ end
391
+
392
+ it 'emits the hits on the second page of the second slice' do
393
+ expect(emitted_event_ids).to include('slice1-response1-item0')
394
+ expect(emitted_event_ids).to include('slice1-response1-item1')
395
+ end
383
396
 
384
- it 'does not double-emit' do
385
- expect(emitted_event_ids.uniq).to eq(emitted_event_ids)
397
+ it 'does not double-emit' do
398
+ expect(emitted_event_ids.uniq).to eq(emitted_event_ids)
399
+ end
400
+
401
+ it 'emits events with appropriate fields' do
402
+ emitted_events.each do |event|
403
+ expect(event).to be_a(LogStash::Event)
404
+ expect(event.get('message')).to eq(['hello, world'])
405
+ expect(event.get('[@metadata][_id]')).to_not be_nil
406
+ expect(event.get('[@metadata][_id]')).to_not be_empty
407
+ expect(event.get('[@metadata][_index]')).to start_with('logstash-')
408
+ end
409
+ end
386
410
  end
387
411
 
388
- it 'emits events with appropriate fields' do
389
- emitted_events.each do |event|
390
- expect(event).to be_a(LogStash::Event)
391
- expect(event.get('message')).to eq(['hello, world'])
392
- expect(event.get('[@metadata][_id]')).to_not be_nil
393
- expect(event.get('[@metadata][_id]')).to_not be_empty
394
- expect(event.get('[@metadata][_index]')).to start_with('logstash-')
412
+ describe "with scroll request fail" do
413
+ before(:each) do
414
+ expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
415
+ plugin.register
416
+
417
+ expect(client).to receive(:clear_scroll).and_return(nil)
418
+
419
+ # SLICE0 is a three-page scroll in which the second page throw exception
420
+ slice0_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 0, 'max' => 2}))
421
+ expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
422
+ expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll1 })).and_raise("boom")
423
+ allow(client).to receive(:ping)
424
+
425
+ # SLICE1 is a two-page scroll in which the last page has no next scroll id
426
+ slice1_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 1, 'max' => 2}))
427
+ expect(client).to receive(:search).with(hash_including(:body => slice1_query)).and_return(slice1_response0)
428
+ expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice1_scroll1 })).and_return(slice1_response1)
429
+
430
+ synchronize_method!(plugin, :scroll_request)
431
+ synchronize_method!(plugin, :search_request)
432
+ end
433
+
434
+ let(:client) { Elasticsearch::Client.new }
435
+
436
+ it 'does not insert event to queue' do
437
+ expect(plugin).to receive(:parallel_slice).and_wrap_original do |m, *args|
438
+ slice0, slice1 = m.call
439
+ expect(slice0[0]).to be_falsey
440
+ expect(slice1[0]).to be_truthy
441
+ expect(slice1[1].size).to eq(4) # four items from SLICE1
442
+ [slice0, slice1]
443
+ end
444
+
445
+ queue = Queue.new
446
+ plugin.run(queue)
447
+ expect(queue.size).to eq(0)
395
448
  end
396
449
  end
397
450
  end
@@ -645,6 +698,14 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
645
698
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Multiple authentication options are specified/
646
699
  end
647
700
  end
701
+
702
+ context 'ssl verification disabled' do
703
+ let(:config) { super().merge({ 'ssl_certificate_verification' => false }) }
704
+ it 'should warn data security risk' do
705
+ expect(plugin.logger).to receive(:warn).once.with("You have enabled encryption but DISABLED certificate verification, to make sure your data is secure remove `ssl_certificate_verification => false`")
706
+ plugin.register
707
+ end
708
+ end
648
709
  end
649
710
  end if LOGSTASH_VERSION > '6.0'
650
711
 
@@ -890,16 +951,93 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
890
951
  queue << LogStash::Event.new({})
891
952
  }.at_least(:twice)
892
953
  runner = Thread.start { plugin.run(queue) }
893
- sleep 3.0
954
+ expect(queue.pop).not_to be_nil
955
+ cron_jobs = plugin.instance_variable_get(:@_scheduler).instance_variable_get(:@impl).jobs
956
+ expect(cron_jobs[0].next_time - cron_jobs[0].last_time).to be <= 5.0
957
+ expect(queue.pop).not_to be_nil
894
958
  ensure
895
959
  plugin.do_stop
896
960
  runner.join if runner
897
961
  end
898
- expect(queue.size).to be >= 2
899
962
  end
900
963
 
901
964
  end
902
965
 
966
+ context "retries" do
967
+ let(:mock_response) do
968
+ {
969
+ "_scroll_id" => "cXVlcnlUaGVuRmV0Y2g",
970
+ "took" => 27,
971
+ "timed_out" => false,
972
+ "_shards" => {
973
+ "total" => 169,
974
+ "successful" => 169,
975
+ "failed" => 0
976
+ },
977
+ "hits" => {
978
+ "total" => 1,
979
+ "max_score" => 1.0,
980
+ "hits" => [ {
981
+ "_index" => "logstash-2014.10.12",
982
+ "_type" => "logs",
983
+ "_id" => "C5b2xLQwTZa76jBmHIbwHQ",
984
+ "_score" => 1.0,
985
+ "_source" => { "message" => ["ohayo"] }
986
+ } ]
987
+ }
988
+ }
989
+ end
990
+
991
+ let(:mock_scroll_response) do
992
+ {
993
+ "_scroll_id" => "r453Wc1jh0caLJhSDg",
994
+ "hits" => { "hits" => [] }
995
+ }
996
+ end
997
+
998
+ before(:each) do
999
+ client = Elasticsearch::Client.new
1000
+ allow(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
1001
+ allow(client).to receive(:search).with(any_args).and_return(mock_response)
1002
+ allow(client).to receive(:scroll).with({ :body => { :scroll_id => "cXVlcnlUaGVuRmV0Y2g" }, :scroll=> "1m" }).and_return(mock_scroll_response)
1003
+ allow(client).to receive(:clear_scroll).and_return(nil)
1004
+ allow(client).to receive(:ping)
1005
+ end
1006
+
1007
+ let(:config) do
1008
+ {
1009
+ "hosts" => ["localhost"],
1010
+ "query" => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }',
1011
+ "retries" => 1
1012
+ }
1013
+ end
1014
+
1015
+ it "retry and log error when all search request fail" do
1016
+ expect(plugin.logger).to receive(:error).with(/Tried .* unsuccessfully/,
1017
+ hash_including(:message => 'Manticore::UnknownException'))
1018
+ expect(plugin.logger).to receive(:warn).twice.with(/Attempt to .* but failed/,
1019
+ hash_including(:exception => "Manticore::UnknownException"))
1020
+ expect(plugin).to receive(:search_request).with(instance_of(Hash)).and_raise(Manticore::UnknownException).at_least(:twice)
1021
+
1022
+ plugin.register
1023
+
1024
+ expect{ plugin.run(queue) }.not_to raise_error
1025
+ expect(queue.size).to eq(0)
1026
+ end
1027
+
1028
+ it "retry successfully when search request fail for one time" do
1029
+ expect(plugin.logger).to receive(:warn).once.with(/Attempt to .* but failed/,
1030
+ hash_including(:exception => "Manticore::UnknownException"))
1031
+ expect(plugin).to receive(:search_request).with(instance_of(Hash)).once.and_raise(Manticore::UnknownException)
1032
+ expect(plugin).to receive(:search_request).with(instance_of(Hash)).once.and_call_original
1033
+
1034
+ plugin.register
1035
+
1036
+ expect{ plugin.run(queue) }.not_to raise_error
1037
+ expect(queue.size).to eq(1)
1038
+ end
1039
+ end
1040
+
903
1041
  # @note can be removed once we depends on elasticsearch gem >= 6.x
904
1042
  def extract_transport(client) # on 7.x client.transport is a ES::Transport::Client
905
1043
  client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.13.0
4
+ version: 4.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-24 00:00:00.000000000 Z
11
+ date: 2022-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +72,20 @@ dependencies:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '1.0'
75
+ - !ruby/object:Gem::Dependency
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.0'
81
+ name: logstash-mixin-scheduler
82
+ prerelease: false
83
+ type: :runtime
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
75
89
  - !ruby/object:Gem::Dependency
76
90
  requirement: !ruby/object:Gem::Requirement
77
91
  requirements:
@@ -128,20 +142,6 @@ dependencies:
128
142
  - - ">="
129
143
  - !ruby/object:Gem::Version
130
144
  version: '0'
131
- - !ruby/object:Gem::Dependency
132
- requirement: !ruby/object:Gem::Requirement
133
- requirements:
134
- - - ">="
135
- - !ruby/object:Gem::Version
136
- version: '0'
137
- name: rufus-scheduler
138
- prerelease: false
139
- type: :runtime
140
- version_requirements: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - ">="
143
- - !ruby/object:Gem::Version
144
- version: '0'
145
145
  - !ruby/object:Gem::Dependency
146
146
  requirement: !ruby/object:Gem::Requirement
147
147
  requirements:
@@ -255,6 +255,7 @@ files:
255
255
  - NOTICE.TXT
256
256
  - README.md
257
257
  - docs/index.asciidoc
258
+ - lib/logstash/helpers/loggable_try.rb
258
259
  - lib/logstash/inputs/elasticsearch.rb
259
260
  - lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_connections_selector.rb
260
261
  - lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_http_manticore.rb