logstash-input-elasticsearch 4.9.0 → 4.12.1

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.
@@ -7,16 +7,45 @@ require "timecop"
7
7
  require "stud/temporary"
8
8
  require "time"
9
9
  require "date"
10
+ require "cabin"
11
+ require "webrick"
12
+ require "uri"
10
13
 
11
- class LogStash::Inputs::TestableElasticsearch < LogStash::Inputs::Elasticsearch
12
- attr_reader :client
13
- end
14
+ require 'logstash/plugin_mixins/ecs_compatibility_support/spec_helper'
14
15
 
15
- describe LogStash::Inputs::TestableElasticsearch do
16
+ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
16
17
 
17
- let(:plugin) { LogStash::Inputs::TestableElasticsearch.new(config) }
18
+ let(:plugin) { described_class.new(config) }
18
19
  let(:queue) { Queue.new }
19
20
 
21
+ before(:each) do
22
+ Elasticsearch::Client.send(:define_method, :ping) { } # define no-action ping method
23
+ end
24
+
25
+ context "register" do
26
+ let(:config) do
27
+ {
28
+ "schedule" => "* * * * * UTC"
29
+ }
30
+ end
31
+
32
+ context "against authentic Elasticsearch" do
33
+ it "should not raise an exception" do
34
+ expect { plugin.register }.to_not raise_error
35
+ end
36
+ end
37
+
38
+ context "against not authentic Elasticsearch" do
39
+ before(:each) do
40
+ Elasticsearch::Client.send(:define_method, :ping) { raise Elasticsearch::UnsupportedProductError.new("Fake error") } # define error ping method
41
+ end
42
+
43
+ it "should raise ConfigurationError" do
44
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
45
+ end
46
+ end
47
+ end
48
+
20
49
  it_behaves_like "an interruptible input plugin" do
21
50
  let(:esclient) { double("elasticsearch-client") }
22
51
  let(:config) do
@@ -37,10 +66,17 @@ describe LogStash::Inputs::TestableElasticsearch do
37
66
  allow(esclient).to receive(:search) { { "hits" => { "hits" => [hit] } } }
38
67
  allow(esclient).to receive(:scroll) { { "hits" => { "hits" => [hit] } } }
39
68
  allow(esclient).to receive(:clear_scroll).and_return(nil)
69
+ allow(esclient).to receive(:ping)
40
70
  end
41
71
  end
42
72
 
43
- context 'creating events from Elasticsearch' do
73
+
74
+ ecs_compatibility_matrix(:disabled, :v1, :v8) do |ecs_select|
75
+
76
+ before(:each) do
77
+ allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
78
+ end
79
+
44
80
  let(:config) do
45
81
  %q[
46
82
  input {
@@ -89,6 +125,7 @@ describe LogStash::Inputs::TestableElasticsearch do
89
125
  expect(client).to receive(:search).with(any_args).and_return(mock_response)
90
126
  expect(client).to receive(:scroll).with({ :body => { :scroll_id => "cXVlcnlUaGVuRmV0Y2g" }, :scroll=> "1m" }).and_return(mock_scroll_response)
91
127
  expect(client).to receive(:clear_scroll).and_return(nil)
128
+ expect(client).to receive(:ping)
92
129
  end
93
130
 
94
131
  it 'creates the events from the hits' do
@@ -97,7 +134,6 @@ describe LogStash::Inputs::TestableElasticsearch do
97
134
  end
98
135
 
99
136
  expect(event).to be_a(LogStash::Event)
100
- puts event.to_hash_with_metadata
101
137
  expect(event.get("message")).to eql [ "ohayo" ]
102
138
  end
103
139
 
@@ -120,10 +156,10 @@ describe LogStash::Inputs::TestableElasticsearch do
120
156
  end
121
157
 
122
158
  expect(event).to be_a(LogStash::Event)
123
- puts event.to_hash_with_metadata
124
159
  expect(event.get("[@metadata][_source][message]")).to eql [ "ohayo" ]
125
160
  end
126
161
  end
162
+
127
163
  end
128
164
 
129
165
  # This spec is an adapter-spec, ensuring that we send the right sequence of messages to our Elasticsearch Client
@@ -135,6 +171,7 @@ describe LogStash::Inputs::TestableElasticsearch do
135
171
  'query' => "#{LogStash::Json.dump(query)}",
136
172
  'slices' => slices,
137
173
  'docinfo' => true, # include ids
174
+ 'docinfo_target' => '[@metadata]'
138
175
  }
139
176
  end
140
177
  let(:query) do
@@ -166,7 +203,7 @@ describe LogStash::Inputs::TestableElasticsearch do
166
203
  end
167
204
 
168
205
  context 'without slices directive' do
169
- let(:config) { super.tap { |h| h.delete('slices') } }
206
+ let(:config) { super().tap { |h| h.delete('slices') } }
170
207
  it 'runs just one slice' do
171
208
  expect(plugin).to receive(:do_run_slice).with(duck_type(:<<))
172
209
  expect(Thread).to_not receive(:new)
@@ -304,6 +341,7 @@ describe LogStash::Inputs::TestableElasticsearch do
304
341
  expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
305
342
  expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll1 })).and_return(slice0_response1)
306
343
  expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll2 })).and_return(slice0_response2)
344
+ allow(client).to receive(:ping)
307
345
 
308
346
  # SLICE1 is a two-page scroll in which the last page has no next scroll id
309
347
  slice1_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 1, 'max' => 2}))
@@ -403,129 +441,143 @@ describe LogStash::Inputs::TestableElasticsearch do
403
441
  expect(client).to receive(:search).with(any_args).and_return(response)
404
442
  allow(client).to receive(:scroll).with({ :body => {:scroll_id => "cXVlcnlUaGVuRmV0Y2g"}, :scroll => "1m" }).and_return(scroll_reponse)
405
443
  allow(client).to receive(:clear_scroll).and_return(nil)
444
+ allow(client).to receive(:ping).and_return(nil)
406
445
  end
407
446
 
408
- context 'when defining docinfo' do
409
- let(:config_metadata) do
410
- %q[
411
- input {
412
- elasticsearch {
413
- hosts => ["localhost"]
414
- query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
415
- docinfo => true
416
- }
417
- }
418
- ]
419
- end
447
+ ecs_compatibility_matrix(:disabled, :v1, :v8) do |ecs_select|
420
448
 
421
- it 'merges the values if the `docinfo_target` already exist in the `_source` document' do
422
- config_metadata_with_hash = %Q[
423
- input {
424
- elasticsearch {
425
- hosts => ["localhost"]
426
- query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
427
- docinfo => true
428
- docinfo_target => 'metadata_with_hash'
449
+ before(:each) do
450
+ allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
451
+ end
452
+
453
+ context 'with docinfo enabled' do
454
+ let(:config_metadata) do
455
+ %q[
456
+ input {
457
+ elasticsearch {
458
+ hosts => ["localhost"]
459
+ query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
460
+ docinfo => true
461
+ }
429
462
  }
430
- }
431
- ]
432
-
433
- event = input(config_metadata_with_hash) do |pipeline, queue|
434
- queue.pop
463
+ ]
435
464
  end
436
465
 
437
- expect(event.get("[metadata_with_hash][_index]")).to eq('logstash-2014.10.12')
438
- expect(event.get("[metadata_with_hash][_type]")).to eq('logs')
439
- expect(event.get("[metadata_with_hash][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
440
- expect(event.get("[metadata_with_hash][awesome]")).to eq("logstash")
441
- end
442
-
443
- context 'if the `docinfo_target` exist but is not of type hash' do
444
- let (:config) { {
445
- "hosts" => ["localhost"],
446
- "query" => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }',
447
- "docinfo" => true,
448
- "docinfo_target" => 'metadata_with_string'
449
- } }
450
- it 'thows an exception if the `docinfo_target` exist but is not of type hash' do
451
- expect(client).not_to receive(:clear_scroll)
452
- plugin.register
453
- expect { plugin.run([]) }.to raise_error(Exception, /incompatible event/)
466
+ it "provides document info under metadata" do
467
+ event = input(config_metadata) do |pipeline, queue|
468
+ queue.pop
469
+ end
470
+
471
+ if ecs_select.active_mode == :disabled
472
+ expect(event.get("[@metadata][_index]")).to eq('logstash-2014.10.12')
473
+ expect(event.get("[@metadata][_type]")).to eq('logs')
474
+ expect(event.get("[@metadata][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
475
+ else
476
+ expect(event.get("[@metadata][input][elasticsearch][_index]")).to eq('logstash-2014.10.12')
477
+ expect(event.get("[@metadata][input][elasticsearch][_type]")).to eq('logs')
478
+ expect(event.get("[@metadata][input][elasticsearch][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
479
+ end
454
480
  end
455
- end
456
481
 
457
- it "should move the document info to the @metadata field" do
458
- event = input(config_metadata) do |pipeline, queue|
459
- queue.pop
482
+ it 'merges values if the `docinfo_target` already exist in the `_source` document' do
483
+ config_metadata_with_hash = %Q[
484
+ input {
485
+ elasticsearch {
486
+ hosts => ["localhost"]
487
+ query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
488
+ docinfo => true
489
+ docinfo_target => 'metadata_with_hash'
490
+ }
491
+ }
492
+ ]
493
+
494
+ event = input(config_metadata_with_hash) do |pipeline, queue|
495
+ queue.pop
496
+ end
497
+
498
+ expect(event.get("[metadata_with_hash][_index]")).to eq('logstash-2014.10.12')
499
+ expect(event.get("[metadata_with_hash][_type]")).to eq('logs')
500
+ expect(event.get("[metadata_with_hash][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
501
+ expect(event.get("[metadata_with_hash][awesome]")).to eq("logstash")
460
502
  end
461
503
 
462
- expect(event.get("[@metadata][_index]")).to eq('logstash-2014.10.12')
463
- expect(event.get("[@metadata][_type]")).to eq('logs')
464
- expect(event.get("[@metadata][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
465
- end
504
+ context 'if the `docinfo_target` exist but is not of type hash' do
505
+ let (:config) { {
506
+ "hosts" => ["localhost"],
507
+ "query" => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }',
508
+ "docinfo" => true,
509
+ "docinfo_target" => 'metadata_with_string'
510
+ } }
511
+ it 'thows an exception if the `docinfo_target` exist but is not of type hash' do
512
+ expect(client).not_to receive(:clear_scroll)
513
+ plugin.register
514
+ expect { plugin.run([]) }.to raise_error(Exception, /incompatible event/)
515
+ end
516
+ end
466
517
 
467
- it 'should move the document information to the specified field' do
468
- config = %q[
469
- input {
470
- elasticsearch {
471
- hosts => ["localhost"]
472
- query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
473
- docinfo => true
474
- docinfo_target => 'meta'
518
+ it 'should move the document information to the specified field' do
519
+ config = %q[
520
+ input {
521
+ elasticsearch {
522
+ hosts => ["localhost"]
523
+ query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
524
+ docinfo => true
525
+ docinfo_target => 'meta'
526
+ }
475
527
  }
476
- }
477
- ]
478
- event = input(config) do |pipeline, queue|
479
- queue.pop
528
+ ]
529
+ event = input(config) do |pipeline, queue|
530
+ queue.pop
531
+ end
532
+
533
+ expect(event.get("[meta][_index]")).to eq('logstash-2014.10.12')
534
+ expect(event.get("[meta][_type]")).to eq('logs')
535
+ expect(event.get("[meta][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
480
536
  end
481
537
 
482
- expect(event.get("[meta][_index]")).to eq('logstash-2014.10.12')
483
- expect(event.get("[meta][_type]")).to eq('logs')
484
- expect(event.get("[meta][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
485
- end
538
+ it "allows to specify which fields from the document info to save to metadata" do
539
+ fields = ["_index"]
540
+ config = %Q[
541
+ input {
542
+ elasticsearch {
543
+ hosts => ["localhost"]
544
+ query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
545
+ docinfo => true
546
+ docinfo_fields => #{fields}
547
+ }
548
+ }]
549
+
550
+ event = input(config) do |pipeline, queue|
551
+ queue.pop
552
+ end
553
+
554
+ meta_base = event.get(ecs_select.active_mode == :disabled ? "@metadata" : "[@metadata][input][elasticsearch]")
555
+ expect(meta_base.keys).to eq(fields)
556
+ end
486
557
 
487
- it "should allow to specify which fields from the document info to save to the @metadata field" do
488
- fields = ["_index"]
489
- config = %Q[
558
+ it 'should be able to reference metadata fields in `add_field` decorations' do
559
+ config = %q[
490
560
  input {
491
561
  elasticsearch {
492
562
  hosts => ["localhost"]
493
563
  query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
494
564
  docinfo => true
495
- docinfo_fields => #{fields}
496
- }
497
- }]
498
-
499
- event = input(config) do |pipeline, queue|
500
- queue.pop
501
- end
502
-
503
- expect(event.get("@metadata").keys).to eq(fields)
504
- expect(event.get("[@metadata][_type]")).to eq(nil)
505
- expect(event.get("[@metadata][_index]")).to eq('logstash-2014.10.12')
506
- expect(event.get("[@metadata][_id]")).to eq(nil)
507
- end
508
-
509
- it 'should be able to reference metadata fields in `add_field` decorations' do
510
- config = %q[
511
- input {
512
- elasticsearch {
513
- hosts => ["localhost"]
514
- query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
515
- docinfo => true
516
- add_field => {
517
- 'identifier' => "foo:%{[@metadata][_type]}:%{[@metadata][_id]}"
565
+ add_field => {
566
+ 'identifier' => "foo:%{[@metadata][_type]}:%{[@metadata][_id]}"
567
+ }
518
568
  }
519
569
  }
520
- }
521
- ]
570
+ ]
522
571
 
523
- event = input(config) do |pipeline, queue|
524
- queue.pop
525
- end
572
+ event = input(config) do |pipeline, queue|
573
+ queue.pop
574
+ end
575
+
576
+ expect(event.get('identifier')).to eq('foo:logs:C5b2xLQwTZa76jBmHIbwHQ')
577
+ end if ecs_select.active_mode == :disabled
526
578
 
527
- expect(event.get('identifier')).to eq('foo:logs:C5b2xLQwTZa76jBmHIbwHQ')
528
579
  end
580
+
529
581
  end
530
582
 
531
583
  context "when not defining the docinfo" do
@@ -542,9 +594,7 @@ describe LogStash::Inputs::TestableElasticsearch do
542
594
  queue.pop
543
595
  end
544
596
 
545
- expect(event.get("[@metadata][_index]")).to eq(nil)
546
- expect(event.get("[@metadata][_type]")).to eq(nil)
547
- expect(event.get("[@metadata][_id]")).to eq(nil)
597
+ expect(event.get("[@metadata]")).to be_empty
548
598
  end
549
599
  end
550
600
  end
@@ -563,22 +613,23 @@ describe LogStash::Inputs::TestableElasticsearch do
563
613
  'sample:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA=='
564
614
  end
565
615
 
566
- let(:config) { super.merge({ 'cloud_id' => valid_cloud_id }) }
616
+ let(:config) { super().merge({ 'cloud_id' => valid_cloud_id }) }
567
617
 
568
618
  it "should set host(s)" do
569
619
  plugin.register
570
620
  client = plugin.send(:client)
571
- expect( client.transport.hosts ).to eql [{
572
- :scheme => "https",
573
- :host => "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io",
574
- :port => 9243,
575
- :path => "",
576
- :protocol => "https"
577
- }]
621
+
622
+ expect( client.transport.instance_variable_get(:@seeds) ).to eql [{
623
+ :scheme => "https",
624
+ :host => "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io",
625
+ :port => 9243,
626
+ :path => "",
627
+ :protocol => "https"
628
+ }]
578
629
  end
579
630
 
580
631
  context 'invalid' do
581
- let(:config) { super.merge({ 'cloud_id' => 'invalid:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlv' }) }
632
+ let(:config) { super().merge({ 'cloud_id' => 'invalid:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlv' }) }
582
633
 
583
634
  it "should fail" do
584
635
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /cloud_id.*? is invalid/
@@ -586,7 +637,7 @@ describe LogStash::Inputs::TestableElasticsearch do
586
637
  end
587
638
 
588
639
  context 'hosts also set' do
589
- let(:config) { super.merge({ 'cloud_id' => valid_cloud_id, 'hosts' => [ 'localhost:9200' ] }) }
640
+ let(:config) { super().merge({ 'cloud_id' => valid_cloud_id, 'hosts' => [ 'localhost:9200' ] }) }
590
641
 
591
642
  it "should fail" do
592
643
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /cloud_id and hosts/
@@ -595,18 +646,18 @@ describe LogStash::Inputs::TestableElasticsearch do
595
646
  end if LOGSTASH_VERSION > '6.0'
596
647
 
597
648
  describe "cloud.auth" do
598
- let(:config) { super.merge({ 'cloud_auth' => LogStash::Util::Password.new('elastic:my-passwd-00') }) }
649
+ let(:config) { super().merge({ 'cloud_auth' => LogStash::Util::Password.new('elastic:my-passwd-00') }) }
599
650
 
600
651
  it "should set authorization" do
601
652
  plugin.register
602
653
  client = plugin.send(:client)
603
- auth_header = client.transport.options[:transport_options][:headers][:Authorization]
654
+ auth_header = extract_transport(client).options[:transport_options][:headers]['Authorization']
604
655
 
605
656
  expect( auth_header ).to eql "Basic #{Base64.encode64('elastic:my-passwd-00').rstrip}"
606
657
  end
607
658
 
608
659
  context 'invalid' do
609
- let(:config) { super.merge({ 'cloud_auth' => 'invalid-format' }) }
660
+ let(:config) { super().merge({ 'cloud_auth' => 'invalid-format' }) }
610
661
 
611
662
  it "should fail" do
612
663
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /cloud_auth.*? format/
@@ -614,7 +665,7 @@ describe LogStash::Inputs::TestableElasticsearch do
614
665
  end
615
666
 
616
667
  context 'user also set' do
617
- let(:config) { super.merge({ 'cloud_auth' => 'elastic:my-passwd-00', 'user' => 'another' }) }
668
+ let(:config) { super().merge({ 'cloud_auth' => 'elastic:my-passwd-00', 'user' => 'another' }) }
618
669
 
619
670
  it "should fail" do
620
671
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Multiple authentication options are specified/
@@ -624,7 +675,7 @@ describe LogStash::Inputs::TestableElasticsearch do
624
675
 
625
676
  describe "api_key" do
626
677
  context "without ssl" do
627
- let(:config) { super.merge({ 'api_key' => LogStash::Util::Password.new('foo:bar') }) }
678
+ let(:config) { super().merge({ 'api_key' => LogStash::Util::Password.new('foo:bar') }) }
628
679
 
629
680
  it "should fail" do
630
681
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /api_key authentication requires SSL\/TLS/
@@ -632,18 +683,18 @@ describe LogStash::Inputs::TestableElasticsearch do
632
683
  end
633
684
 
634
685
  context "with ssl" do
635
- let(:config) { super.merge({ 'api_key' => LogStash::Util::Password.new('foo:bar'), "ssl" => true }) }
686
+ let(:config) { super().merge({ 'api_key' => LogStash::Util::Password.new('foo:bar'), "ssl" => true }) }
636
687
 
637
688
  it "should set authorization" do
638
689
  plugin.register
639
690
  client = plugin.send(:client)
640
- auth_header = client.transport.options[:transport_options][:headers][:Authorization]
691
+ auth_header = extract_transport(client).options[:transport_options][:headers]['Authorization']
641
692
 
642
693
  expect( auth_header ).to eql "ApiKey #{Base64.strict_encode64('foo:bar')}"
643
694
  end
644
695
 
645
696
  context 'user also set' do
646
- let(:config) { super.merge({ 'api_key' => 'foo:bar', 'user' => 'another' }) }
697
+ let(:config) { super().merge({ 'api_key' => 'foo:bar', 'user' => 'another' }) }
647
698
 
648
699
  it "should fail" do
649
700
  expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Multiple authentication options are specified/
@@ -653,29 +704,179 @@ describe LogStash::Inputs::TestableElasticsearch do
653
704
  end if LOGSTASH_VERSION > '6.0'
654
705
 
655
706
  describe "proxy" do
656
- let(:config) { super.merge({ 'proxy' => 'http://localhost:1234' }) }
707
+ let(:config) { super().merge({ 'proxy' => 'http://localhost:1234' }) }
657
708
 
658
709
  it "should set proxy" do
659
710
  plugin.register
660
711
  client = plugin.send(:client)
661
- proxy = client.transport.options[:transport_options][:proxy]
712
+ proxy = extract_transport(client).options[:transport_options][:proxy]
662
713
 
663
714
  expect( proxy ).to eql "http://localhost:1234"
664
715
  end
665
716
 
666
717
  context 'invalid' do
667
- let(:config) { super.merge({ 'proxy' => '${A_MISSING_ENV_VAR:}' }) }
718
+ let(:config) { super().merge({ 'proxy' => '${A_MISSING_ENV_VAR:}' }) }
668
719
 
669
720
  it "should not set proxy" do
670
721
  plugin.register
671
722
  client = plugin.send(:client)
672
723
 
673
- expect( client.transport.options[:transport_options] ).to_not include(:proxy)
724
+ expect( extract_transport(client).options[:transport_options] ).to_not include(:proxy)
674
725
  end
675
726
  end
676
727
  end
677
728
 
678
- shared_examples'configurable timeout' do |config_name, manticore_transport_option|
729
+ class StoppableServer
730
+
731
+ attr_reader :port
732
+
733
+ def initialize()
734
+ queue = Queue.new
735
+ @first_req_waiter = java.util.concurrent.CountDownLatch.new(1)
736
+ @first_request = nil
737
+
738
+ @t = java.lang.Thread.new(
739
+ proc do
740
+ begin
741
+ @server = WEBrick::HTTPServer.new :Port => 0, :DocumentRoot => ".",
742
+ :Logger => Cabin::Channel.get, # silence WEBrick logging
743
+ :StartCallback => Proc.new {
744
+ queue.push("started")
745
+ }
746
+ @port = @server.config[:Port]
747
+ @server.mount_proc '/' do |req, res|
748
+ res.body = '''
749
+ {
750
+ "name": "ce7ccfb438e8",
751
+ "cluster_name": "docker-cluster",
752
+ "cluster_uuid": "DyR1hN03QvuCWXRy3jtb0g",
753
+ "version": {
754
+ "number": "7.13.1",
755
+ "build_flavor": "default",
756
+ "build_type": "docker",
757
+ "build_hash": "9a7758028e4ea59bcab41c12004603c5a7dd84a9",
758
+ "build_date": "2021-05-28T17:40:59.346932922Z",
759
+ "build_snapshot": false,
760
+ "lucene_version": "8.8.2",
761
+ "minimum_wire_compatibility_version": "6.8.0",
762
+ "minimum_index_compatibility_version": "6.0.0-beta1"
763
+ },
764
+ "tagline": "You Know, for Search"
765
+ }
766
+ '''
767
+ res.status = 200
768
+ res['Content-Type'] = 'application/json'
769
+ @first_request = req
770
+ @first_req_waiter.countDown()
771
+ end
772
+
773
+ @server.mount_proc '/logstash_unit_test/_search' do |req, res|
774
+ res.body = '''
775
+ {
776
+ "took" : 1,
777
+ "timed_out" : false,
778
+ "_shards" : {
779
+ "total" : 1,
780
+ "successful" : 1,
781
+ "skipped" : 0,
782
+ "failed" : 0
783
+ },
784
+ "hits" : {
785
+ "total" : {
786
+ "value" : 10000,
787
+ "relation" : "gte"
788
+ },
789
+ "max_score" : 1.0,
790
+ "hits" : [
791
+ {
792
+ "_index" : "test_bulk_index_2",
793
+ "_type" : "_doc",
794
+ "_id" : "sHe6A3wBesqF7ydicQvG",
795
+ "_score" : 1.0,
796
+ "_source" : {
797
+ "@timestamp" : "2021-09-20T15:02:02.557Z",
798
+ "message" : "{\"name\": \"Andrea\"}",
799
+ "@version" : "1",
800
+ "host" : "kalispera",
801
+ "sequence" : 5
802
+ }
803
+ }
804
+ ]
805
+ }
806
+ }
807
+ '''
808
+ res.status = 200
809
+ res['Content-Type'] = 'application/json'
810
+ @first_request = req
811
+ @first_req_waiter.countDown()
812
+ end
813
+
814
+
815
+
816
+ @server.start
817
+ rescue => e
818
+ puts "Error in webserver thread #{e}"
819
+ # ignore
820
+ end
821
+ end
822
+ )
823
+ @t.daemon = true
824
+ @t.start
825
+ queue.pop # blocks until the server is up
826
+ end
827
+
828
+ def stop
829
+ @server.shutdown
830
+ end
831
+
832
+ def wait_receive_request
833
+ @first_req_waiter.await(2, java.util.concurrent.TimeUnit::SECONDS)
834
+ @first_request
835
+ end
836
+ end
837
+
838
+ describe "'user-agent' header" do
839
+ let!(:webserver) { StoppableServer.new } # webserver must be started before the call, so no lazy "let"
840
+
841
+ after :each do
842
+ webserver.stop
843
+ end
844
+
845
+ it "server should be started" do
846
+ require 'net/http'
847
+ response = nil
848
+ Net::HTTP.start('localhost', webserver.port) {|http|
849
+ response = http.request_get('/')
850
+ }
851
+ expect(response.code.to_i).to eq(200)
852
+ end
853
+
854
+ context "used by plugin" do
855
+ let(:config) do
856
+ {
857
+ "hosts" => ["localhost:#{webserver.port}"],
858
+ "query" => '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }',
859
+ "index" => "logstash_unit_test"
860
+ }
861
+ end
862
+ let(:plugin) { described_class.new(config) }
863
+ let(:event) { LogStash::Event.new({}) }
864
+
865
+ it "client should sent the expect user-agent" do
866
+ plugin.register
867
+
868
+ queue = []
869
+ plugin.run(queue)
870
+
871
+ request = webserver.wait_receive_request
872
+
873
+ expect(request.header['user-agent'].size).to eq(1)
874
+ expect(request.header['user-agent'][0]).to match(/logstash\/\d*\.\d*\.\d* \(OS=.*; JVM=.*\) logstash-input-elasticsearch\/\d*\.\d*\.\d*/)
875
+ end
876
+ end
877
+ end
878
+
879
+ shared_examples 'configurable timeout' do |config_name, manticore_transport_option|
679
880
  let(:config_value) { fail NotImplementedError }
680
881
  let(:config) { super().merge(config_name => config_value) }
681
882
  {
@@ -706,6 +907,9 @@ describe LogStash::Inputs::TestableElasticsearch do
706
907
  transport_options = new_elasticsearch_client_params[:transport_options]
707
908
  expect(transport_options).to include(manticore_transport_option)
708
909
  expect(transport_options[manticore_transport_option]).to eq(config_value.to_i)
910
+ mock_client = double("fake_client")
911
+ allow(mock_client).to receive(:ping)
912
+ mock_client
709
913
  end
710
914
 
711
915
  plugin.register
@@ -756,4 +960,10 @@ describe LogStash::Inputs::TestableElasticsearch do
756
960
  end
757
961
 
758
962
  end
963
+
964
+ # @note can be removed once we depends on elasticsearch gem >= 6.x
965
+ def extract_transport(client) # on 7.x client.transport is a ES::Transport::Client
966
+ client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
967
+ end
968
+
759
969
  end