logstash-input-elasticsearch 4.9.0 → 4.12.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,18 +7,46 @@ 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
- let(:esclient) { double("elasticsearch-client") }
22
50
  let(:config) do
23
51
  {
24
52
  "schedule" => "* * * * * UTC"
@@ -26,7 +54,8 @@ describe LogStash::Inputs::TestableElasticsearch do
26
54
  end
27
55
 
28
56
  before :each do
29
- allow(Elasticsearch::Client).to receive(:new).and_return(esclient)
57
+ @esclient = double("elasticsearch-client")
58
+ allow(Elasticsearch::Client).to receive(:new).and_return(@esclient)
30
59
  hit = {
31
60
  "_index" => "logstash-2014.10.12",
32
61
  "_type" => "logs",
@@ -34,13 +63,20 @@ describe LogStash::Inputs::TestableElasticsearch do
34
63
  "_score" => 1.0,
35
64
  "_source" => { "message" => ["ohayo"] }
36
65
  }
37
- allow(esclient).to receive(:search) { { "hits" => { "hits" => [hit] } } }
38
- allow(esclient).to receive(:scroll) { { "hits" => { "hits" => [hit] } } }
39
- allow(esclient).to receive(:clear_scroll).and_return(nil)
66
+ allow(@esclient).to receive(:search) { { "hits" => { "hits" => [hit] } } }
67
+ allow(@esclient).to receive(:scroll) { { "hits" => { "hits" => [hit] } } }
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
486
553
 
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[
554
+ meta_base = event.get(ecs_select.active_mode == :disabled ? "@metadata" : "[@metadata][input][elasticsearch]")
555
+ expect(meta_base.keys).to eq(fields)
556
+ end
557
+
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
@@ -729,7 +933,7 @@ describe LogStash::Inputs::TestableElasticsearch do
729
933
  {
730
934
  "hosts" => ["localhost"],
731
935
  "query" => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }',
732
- "schedule" => "* * * * * UTC"
936
+ "schedule" => "* * * * * * UTC" # every second
733
937
  }
734
938
  end
735
939
 
@@ -738,22 +942,21 @@ describe LogStash::Inputs::TestableElasticsearch do
738
942
  end
739
943
 
740
944
  it "should properly schedule" do
741
- Timecop.travel(Time.new(2000))
742
- Timecop.scale(60)
743
- runner = Thread.new do
744
- expect(plugin).to receive(:do_run) {
745
- queue << LogStash::Event.new({})
746
- }.at_least(:twice)
747
-
748
- plugin.run(queue)
749
- end
750
- sleep 3
945
+ expect(plugin).to receive(:do_run) {
946
+ queue << LogStash::Event.new({})
947
+ }.at_least(:twice)
948
+ runner = Thread.start { plugin.run(queue) }
949
+ sleep 3.0
751
950
  plugin.stop
752
- runner.kill
753
951
  runner.join
754
- expect(queue.size).to eq(2)
755
- Timecop.return
952
+ expect(queue.size).to be >= 2
756
953
  end
757
954
 
758
955
  end
956
+
957
+ # @note can be removed once we depends on elasticsearch gem >= 6.x
958
+ def extract_transport(client) # on 7.x client.transport is a ES::Transport::Client
959
+ client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
960
+ end
961
+
759
962
  end