logstash-output-elasticsearch-test 10.3.0-x86_64-linux

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +397 -0
  3. data/CONTRIBUTORS +33 -0
  4. data/Gemfile +15 -0
  5. data/LICENSE +13 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +106 -0
  8. data/docs/index.asciidoc +899 -0
  9. data/lib/logstash/outputs/elasticsearch/common.rb +441 -0
  10. data/lib/logstash/outputs/elasticsearch/common_configs.rb +167 -0
  11. data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
  12. data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es2x.json +95 -0
  13. data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es5x.json +46 -0
  14. data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es6x.json +45 -0
  15. data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es7x.json +44 -0
  16. data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es8x.json +44 -0
  17. data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +131 -0
  18. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +495 -0
  19. data/lib/logstash/outputs/elasticsearch/http_client.rb +432 -0
  20. data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +159 -0
  21. data/lib/logstash/outputs/elasticsearch/ilm.rb +113 -0
  22. data/lib/logstash/outputs/elasticsearch/template_manager.rb +61 -0
  23. data/lib/logstash/outputs/elasticsearch.rb +263 -0
  24. data/logstash-output-elasticsearch.gemspec +33 -0
  25. data/spec/es_spec_helper.rb +189 -0
  26. data/spec/fixtures/_nodes/2x_1x.json +27 -0
  27. data/spec/fixtures/_nodes/5x_6x.json +81 -0
  28. data/spec/fixtures/_nodes/7x.json +92 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  31. data/spec/fixtures/scripts/groovy/scripted_update.groovy +2 -0
  32. data/spec/fixtures/scripts/groovy/scripted_update_nested.groovy +2 -0
  33. data/spec/fixtures/scripts/groovy/scripted_upsert.groovy +2 -0
  34. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  35. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  36. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  37. data/spec/fixtures/template-with-policy-es6x.json +48 -0
  38. data/spec/fixtures/template-with-policy-es7x.json +45 -0
  39. data/spec/fixtures/test_certs/ca/ca.crt +32 -0
  40. data/spec/fixtures/test_certs/ca/ca.key +51 -0
  41. data/spec/fixtures/test_certs/test.crt +36 -0
  42. data/spec/fixtures/test_certs/test.key +51 -0
  43. data/spec/integration/outputs/compressed_indexing_spec.rb +69 -0
  44. data/spec/integration/outputs/create_spec.rb +67 -0
  45. data/spec/integration/outputs/delete_spec.rb +65 -0
  46. data/spec/integration/outputs/groovy_update_spec.rb +150 -0
  47. data/spec/integration/outputs/ilm_spec.rb +531 -0
  48. data/spec/integration/outputs/index_spec.rb +178 -0
  49. data/spec/integration/outputs/index_version_spec.rb +102 -0
  50. data/spec/integration/outputs/ingest_pipeline_spec.rb +74 -0
  51. data/spec/integration/outputs/metrics_spec.rb +70 -0
  52. data/spec/integration/outputs/no_es_on_startup_spec.rb +58 -0
  53. data/spec/integration/outputs/painless_update_spec.rb +189 -0
  54. data/spec/integration/outputs/parent_spec.rb +102 -0
  55. data/spec/integration/outputs/retry_spec.rb +169 -0
  56. data/spec/integration/outputs/routing_spec.rb +61 -0
  57. data/spec/integration/outputs/sniffer_spec.rb +133 -0
  58. data/spec/integration/outputs/templates_5x_spec.rb +98 -0
  59. data/spec/integration/outputs/templates_spec.rb +98 -0
  60. data/spec/integration/outputs/update_spec.rb +116 -0
  61. data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
  62. data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
  63. data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
  64. data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
  65. data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
  66. data/spec/unit/http_client_builder_spec.rb +185 -0
  67. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +149 -0
  68. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +274 -0
  69. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +250 -0
  70. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
  71. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +72 -0
  72. data/spec/unit/outputs/elasticsearch_spec.rb +675 -0
  73. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +82 -0
  74. data/spec/unit/outputs/error_whitelist_spec.rb +54 -0
  75. metadata +300 -0
@@ -0,0 +1,675 @@
1
+ require_relative "../../../spec/es_spec_helper"
2
+ require "flores/random"
3
+ require "logstash/outputs/elasticsearch"
4
+
5
+ describe LogStash::Outputs::ElasticSearch do
6
+ subject { described_class.new(options) }
7
+ let(:options) { {} }
8
+ let(:maximum_seen_major_version) { rand(100) }
9
+
10
+ let(:do_register) { true }
11
+
12
+ before(:each) do
13
+ if do_register
14
+ # Build the client and set mocks before calling register to avoid races.
15
+ subject.build_client
16
+
17
+ # Rspec mocks can't handle background threads, so... we can't use any
18
+ allow(subject.client.pool).to receive(:start_resurrectionist)
19
+ allow(subject.client.pool).to receive(:start_sniffer)
20
+ allow(subject.client.pool).to receive(:healthcheck!)
21
+ allow(subject.client).to receive(:maximum_seen_major_version).at_least(:once).and_return(maximum_seen_major_version)
22
+ allow(subject.client).to receive(:get_xpack_info)
23
+ subject.register
24
+ subject.client.pool.adapter.manticore.respond_with(:body => "{}")
25
+ end
26
+ end
27
+
28
+ after(:each) do
29
+ subject.close
30
+ end
31
+
32
+
33
+ context "with an active instance" do
34
+ let(:options) {
35
+ {
36
+ "index" => "my-index",
37
+ "hosts" => ["localhost","localhost:9202"],
38
+ "path" => "some-path",
39
+ "manage_template" => false
40
+ }
41
+ }
42
+
43
+ let(:manticore_urls) { subject.client.pool.urls }
44
+ let(:manticore_url) { manticore_urls.first }
45
+
46
+ describe "getting a document type" do
47
+ context "if document_type isn't set" do
48
+ let(:options) { super.merge("document_type" => nil)}
49
+ context "for 7.x elasticsearch clusters" do
50
+ let(:maximum_seen_major_version) { 7 }
51
+ it "should return '_doc'" do
52
+ expect(subject.send(:get_event_type, LogStash::Event.new("type" => "foo"))).to eql("_doc")
53
+ end
54
+ end
55
+
56
+ context "for 6.x elasticsearch clusters" do
57
+ let(:maximum_seen_major_version) { 6 }
58
+ it "should return 'doc'" do
59
+ expect(subject.send(:get_event_type, LogStash::Event.new("type" => "foo"))).to eql("doc")
60
+ end
61
+ end
62
+
63
+ context "for < 6.0 elasticsearch clusters" do
64
+ let(:maximum_seen_major_version) { 5 }
65
+ it "should get the type from the event" do
66
+ expect(subject.send(:get_event_type, LogStash::Event.new("type" => "foo"))).to eql("foo")
67
+ end
68
+ end
69
+ end
70
+
71
+ context "with 'document type set'" do
72
+ let(:options) { super.merge("document_type" => "bar")}
73
+ it "should get the event type from the 'document_type' setting" do
74
+ expect(subject.send(:get_event_type, LogStash::Event.new())).to eql("bar")
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "building an event action tuple" do
80
+ context "for 7.x elasticsearch clusters" do
81
+ let(:maximum_seen_major_version) { 7 }
82
+ it "should include '_type'" do
83
+ action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
84
+ action_params = action_tuple[1]
85
+ expect(action_params).to include(:_type => "_doc")
86
+ end
87
+
88
+ context "with 'document type set'" do
89
+ let(:options) { super.merge("document_type" => "bar")}
90
+ it "should get the event type from the 'document_type' setting" do
91
+ action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
92
+ action_params = action_tuple[1]
93
+ expect(action_params).to include(:_type => "bar")
94
+ end
95
+ end
96
+ end
97
+
98
+ context "for 8.x elasticsearch clusters" do
99
+ let(:maximum_seen_major_version) { 8 }
100
+ it "should not include '_type'" do
101
+ action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
102
+ action_params = action_tuple[1]
103
+ expect(action_params).not_to include(:_type)
104
+ end
105
+
106
+ context "with 'document type set'" do
107
+ let(:options) { super.merge("document_type" => "bar")}
108
+ it "should not include '_type'" do
109
+ action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
110
+ action_params = action_tuple[1]
111
+ expect(action_params).not_to include(:_type)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "with auth" do
118
+ let(:user) { "myuser" }
119
+ let(:password) { ::LogStash::Util::Password.new("mypassword") }
120
+
121
+ shared_examples "an authenticated config" do
122
+ it "should set the URL auth correctly" do
123
+ expect(manticore_url.user).to eq user
124
+ end
125
+ end
126
+
127
+ context "as part of a URL" do
128
+ let(:options) {
129
+ super.merge("hosts" => ["http://#{user}:#{password.value}@localhost:9200"])
130
+ }
131
+
132
+ include_examples("an authenticated config")
133
+ end
134
+
135
+ context "as a hash option" do
136
+ let(:options) {
137
+ super.merge!(
138
+ "user" => user,
139
+ "password" => password
140
+ )
141
+ }
142
+
143
+ include_examples("an authenticated config")
144
+ end
145
+ end
146
+
147
+ describe "with path" do
148
+ it "should properly create a URI with the path" do
149
+ expect(subject.path).to eql(options["path"])
150
+ end
151
+
152
+ it "should properly set the path on the HTTP client adding slashes" do
153
+ expect(manticore_url.path).to eql("/" + options["path"] + "/")
154
+ end
155
+
156
+ context "with extra slashes" do
157
+ let(:path) { "/slashed-path/ "}
158
+ let(:options) { super.merge("path" => "/some-path/") }
159
+
160
+ it "should properly set the path on the HTTP client without adding slashes" do
161
+ expect(manticore_url.path).to eql(options["path"])
162
+ end
163
+ end
164
+
165
+ context "with a URI based path" do
166
+ let(:options) do
167
+ o = super()
168
+ o.delete("path")
169
+ o["hosts"] = ["http://localhost:9200/mypath/"]
170
+ o
171
+ end
172
+ let(:client_host_path) { manticore_url.path }
173
+
174
+ it "should initialize without error" do
175
+ expect { subject }.not_to raise_error
176
+ end
177
+
178
+ it "should use the URI path" do
179
+ expect(client_host_path).to eql("/mypath/")
180
+ end
181
+
182
+ context "with a path option but no URL path" do
183
+ let(:options) do
184
+ o = super()
185
+ o["path"] = "/override/"
186
+ o["hosts"] = ["http://localhost:9200"]
187
+ o
188
+ end
189
+
190
+ it "should initialize without error" do
191
+ expect { subject }.not_to raise_error
192
+ end
193
+
194
+ it "should use the option path" do
195
+ expect(client_host_path).to eql("/override/")
196
+ end
197
+ end
198
+
199
+ # If you specify the path in two spots that is an error!
200
+ context "with a path option and a URL path" do
201
+ let(:do_register) { false } # Register will fail
202
+ let(:options) do
203
+ o = super()
204
+ o["path"] = "/override"
205
+ o["hosts"] = ["http://localhost:9200/mypath/"]
206
+ o
207
+ end
208
+
209
+ it "should initialize with an error" do
210
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "without a port specified" do
217
+ let(:options) { super.merge('hosts' => 'localhost') }
218
+ it "should properly set the default port (9200) on the HTTP client" do
219
+ expect(manticore_url.port).to eql(9200)
220
+ end
221
+ end
222
+ describe "with a port other than 9200 specified" do
223
+ let(:options) { super.merge('hosts' => 'localhost:9202') }
224
+ it "should properly set the specified port on the HTTP client" do
225
+ expect(manticore_url.port).to eql(9202)
226
+ end
227
+ end
228
+
229
+ describe "#multi_receive" do
230
+ let(:events) { [double("one"), double("two"), double("three")] }
231
+ let(:events_tuples) { [double("one t"), double("two t"), double("three t")] }
232
+
233
+ before do
234
+ allow(subject).to receive(:retrying_submit).with(anything)
235
+ events.each_with_index do |e,i|
236
+ et = events_tuples[i]
237
+ allow(subject).to receive(:event_action_tuple).with(e).and_return(et)
238
+ end
239
+ subject.multi_receive(events)
240
+ end
241
+
242
+ end
243
+
244
+ context "429 errors" do
245
+ let(:event) { ::LogStash::Event.new("foo" => "bar") }
246
+ let(:error) do
247
+ ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
248
+ 429, double("url").as_null_object, double("request body"), double("response body")
249
+ )
250
+ end
251
+ let(:logger) { double("logger").as_null_object }
252
+ let(:response) { { :errors => [], :items => [] } }
253
+
254
+ before(:each) do
255
+
256
+ i = 0
257
+ bulk_param = [["index", anything, event.to_hash]]
258
+
259
+ allow(subject).to receive(:logger).and_return(logger)
260
+
261
+ # Fail the first time bulk is called, succeed the next time
262
+ allow(subject.client).to receive(:bulk).with(bulk_param) do
263
+ i += 1
264
+ if i == 1
265
+ raise error
266
+ end
267
+ end.and_return(response)
268
+ subject.multi_receive([event])
269
+ end
270
+
271
+ it "should retry the 429 till it goes away" do
272
+ expect(subject.client).to have_received(:bulk).twice
273
+ end
274
+
275
+ it "should log a debug message" do
276
+ expect(subject.logger).to have_received(:debug).with(/Encountered a retryable error/i, anything)
277
+ end
278
+ end
279
+ end
280
+
281
+ context "with timeout set" do
282
+ let(:listener) { Flores::Random.tcp_listener }
283
+ let(:port) { listener[2] }
284
+ let(:options) do
285
+ {
286
+ "manage_template" => false,
287
+ "hosts" => "localhost:#{port}",
288
+ "timeout" => 0.1, # fast timeout
289
+ }
290
+ end
291
+
292
+ before do
293
+ # Expect a timeout to be logged.
294
+ expect(subject.logger).to receive(:error).with(/Attempted to send a bulk request to Elasticsearch/i, anything).at_least(:once)
295
+ expect(subject.client).to receive(:bulk).at_least(:twice).and_call_original
296
+ end
297
+
298
+ it "should fail after the timeout" do
299
+ #pending("This is tricky now that we do healthchecks on instantiation")
300
+ Thread.new { subject.multi_receive([LogStash::Event.new]) }
301
+
302
+ # Allow the timeout to occur
303
+ sleep 6
304
+ end
305
+ end
306
+
307
+ describe "the action option" do
308
+ context "with a sprintf action" do
309
+ let(:options) { {"action" => "%{myactionfield}" } }
310
+
311
+ let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
312
+
313
+ it "should interpolate the requested action value when creating an event_action_tuple" do
314
+ expect(subject.event_action_tuple(event).first).to eql("update")
315
+ end
316
+ end
317
+
318
+ context "with a sprintf action equals to update" do
319
+ let(:options) { {"action" => "%{myactionfield}", "upsert" => '{"message": "some text"}' } }
320
+
321
+ let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
322
+
323
+ it "should obtain specific action's params from event_action_tuple" do
324
+ expect(subject.event_action_tuple(event)[1]).to include(:_upsert)
325
+ end
326
+ end
327
+
328
+ context "with an invalid action" do
329
+ let(:options) { {"action" => "SOME Garbaaage"} }
330
+ let(:do_register) { false } # this is what we want to test, so we disable the before(:each) call
331
+
332
+ it "should raise a configuration error" do
333
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
334
+ end
335
+ end
336
+ end
337
+
338
+ describe "SSL end to end" do
339
+ let(:do_register) { false } # skip the register in the global before block, as is called here.
340
+
341
+ before(:each) do
342
+ stub_manticore_client!
343
+ subject.register
344
+ end
345
+
346
+ shared_examples("an encrypted client connection") do
347
+ it "should enable SSL in manticore" do
348
+ expect(subject.client.pool.urls.map(&:scheme).uniq).to eql(['https'])
349
+ end
350
+ end
351
+
352
+
353
+ context "With the 'ssl' option" do
354
+ let(:options) { {"ssl" => true}}
355
+
356
+ include_examples("an encrypted client connection")
357
+ end
358
+
359
+ context "With an https host" do
360
+ let(:options) { {"hosts" => "https://localhost"} }
361
+ include_examples("an encrypted client connection")
362
+ end
363
+ end
364
+
365
+ describe "retry_on_conflict" do
366
+ let(:num_retries) { 123 }
367
+ let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
368
+ let(:options) { { 'retry_on_conflict' => num_retries } }
369
+
370
+ context "with a regular index" do
371
+ let(:options) { super.merge("action" => "index") }
372
+
373
+ it "should not set the retry_on_conflict parameter when creating an event_action_tuple" do
374
+ allow(subject.client).to receive(:maximum_seen_major_version).and_return(maximum_seen_major_version)
375
+ action, params, event_data = subject.event_action_tuple(event)
376
+ expect(params).not_to include({subject.retry_on_conflict_action_name => num_retries})
377
+ end
378
+ end
379
+
380
+ context "using a plain update" do
381
+ let(:options) { super.merge("action" => "update", "retry_on_conflict" => num_retries, "document_id" => 1) }
382
+
383
+ it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
384
+ action, params, event_data = subject.event_action_tuple(event)
385
+ expect(params).to include({subject.retry_on_conflict_action_name => num_retries})
386
+ end
387
+ end
388
+
389
+ context "with a sprintf action that resolves to update" do
390
+ let(:options) { super.merge("action" => "%{myactionfield}", "retry_on_conflict" => num_retries, "document_id" => 1) }
391
+
392
+ it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
393
+ action, params, event_data = subject.event_action_tuple(event)
394
+ expect(params).to include({subject.retry_on_conflict_action_name => num_retries})
395
+ expect(action).to eq("update")
396
+ end
397
+ end
398
+ end
399
+
400
+ describe "sleep interval calculation" do
401
+ let(:retry_max_interval) { 64 }
402
+ let(:options) { { "retry_max_interval" => retry_max_interval } }
403
+
404
+ it "should double the given value" do
405
+ expect(subject.next_sleep_interval(2)).to eql(4)
406
+ expect(subject.next_sleep_interval(32)).to eql(64)
407
+ end
408
+
409
+ it "should not increase the value past the max retry interval" do
410
+ sleep_interval = 2
411
+ 100.times do
412
+ sleep_interval = subject.next_sleep_interval(sleep_interval)
413
+ expect(sleep_interval).to be <= retry_max_interval
414
+ end
415
+ end
416
+ end
417
+
418
+ describe "stale connection check" do
419
+ let(:validate_after_inactivity) { 123 }
420
+ let(:options) { { "validate_after_inactivity" => validate_after_inactivity } }
421
+ let(:do_register) { false }
422
+
423
+ before :each do
424
+ allow(::Manticore::Client).to receive(:new).with(any_args).and_call_original
425
+ end
426
+
427
+ after :each do
428
+ subject.close
429
+ end
430
+
431
+ it "should set the correct http client option for 'validate_after_inactivity'" do
432
+ subject.register
433
+ expect(::Manticore::Client).to have_received(:new) do |options|
434
+ expect(options[:check_connection_timeout]).to eq(validate_after_inactivity)
435
+ end
436
+ end
437
+ end
438
+
439
+ describe "custom parameters" do
440
+
441
+ let(:manticore_urls) { subject.client.pool.urls }
442
+ let(:manticore_url) { manticore_urls.first }
443
+
444
+ let(:custom_parameters_hash) { { "id" => 1, "name" => "logstash" } }
445
+ let(:custom_parameters_query) { custom_parameters_hash.map {|k,v| "#{k}=#{v}" }.join("&") }
446
+
447
+ context "using non-url hosts" do
448
+
449
+ let(:options) {
450
+ {
451
+ "index" => "my-index",
452
+ "hosts" => ["localhost:9202"],
453
+ "path" => "some-path",
454
+ "parameters" => custom_parameters_hash
455
+ }
456
+ }
457
+
458
+ it "creates a URI with the added parameters" do
459
+ expect(subject.parameters).to eql(custom_parameters_hash)
460
+ end
461
+
462
+ it "sets the query string on the HTTP client" do
463
+ expect(manticore_url.query).to eql(custom_parameters_query)
464
+ end
465
+ end
466
+
467
+ context "using url hosts" do
468
+
469
+ context "with embedded query parameters" do
470
+ let(:options) {
471
+ { "hosts" => ["http://localhost:9202/path?#{custom_parameters_query}"] }
472
+ }
473
+
474
+ it "sets the query string on the HTTP client" do
475
+ expect(manticore_url.query).to eql(custom_parameters_query)
476
+ end
477
+ end
478
+
479
+ context "with explicit query parameters" do
480
+ let(:options) {
481
+ {
482
+ "hosts" => ["http://localhost:9202/path"],
483
+ "parameters" => custom_parameters_hash
484
+ }
485
+ }
486
+
487
+ it "sets the query string on the HTTP client" do
488
+ expect(manticore_url.query).to eql(custom_parameters_query)
489
+ end
490
+ end
491
+
492
+ context "with explicit query parameters and existing url parameters" do
493
+ let(:existing_query_string) { "existing=param" }
494
+ let(:options) {
495
+ {
496
+ "hosts" => ["http://localhost:9202/path?#{existing_query_string}"],
497
+ "parameters" => custom_parameters_hash
498
+ }
499
+ }
500
+
501
+ it "keeps the existing query string" do
502
+ expect(manticore_url.query).to include(existing_query_string)
503
+ end
504
+
505
+ it "includes the new query string" do
506
+ expect(manticore_url.query).to include(custom_parameters_query)
507
+ end
508
+
509
+ it "appends the new query string to the existing one" do
510
+ expect(manticore_url.query).to eql("#{existing_query_string}&#{custom_parameters_query}")
511
+ end
512
+ end
513
+ end
514
+ end
515
+
516
+ describe "cloud.id" do
517
+ let(:do_register) { false }
518
+
519
+ let(:valid_cloud_id) do
520
+ 'sample:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA=='
521
+ end
522
+
523
+ let(:options) { { 'cloud_id' => valid_cloud_id } }
524
+
525
+ before(:each) do
526
+ stub_manticore_client!
527
+ end
528
+
529
+ it "should set host(s)" do
530
+ subject.register
531
+ es_url = subject.client.pool.urls.first
532
+ expect( es_url.to_s ).to eql('https://ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243/')
533
+ end
534
+
535
+ context 'invalid' do
536
+ let(:options) { { 'cloud_id' => 'invalid:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlv' } }
537
+
538
+ it "should fail" do
539
+ expect { subject.register }.to raise_error LogStash::ConfigurationError, /cloud_id.*? is invalid/
540
+ end
541
+ end
542
+
543
+ context 'hosts also set' do
544
+ let(:options) { { 'cloud_id' => valid_cloud_id, 'hosts' => [ 'localhost' ] } }
545
+
546
+ it "should fail" do
547
+ expect { subject.register }.to raise_error LogStash::ConfigurationError, /cloud_id and hosts/
548
+ end
549
+ end
550
+ end
551
+
552
+ describe "cloud.auth" do
553
+ let(:do_register) { false }
554
+
555
+ let(:options) { { 'cloud_auth' => LogStash::Util::Password.new('elastic:my-passwd-00') } }
556
+
557
+ before(:each) do
558
+ stub_manticore_client!
559
+ end
560
+
561
+ it "should set host(s)" do
562
+ subject.register
563
+ es_url = subject.client.pool.urls.first
564
+ expect( es_url.user ).to eql('elastic')
565
+ expect( es_url.password ).to eql('my-passwd-00')
566
+ end
567
+
568
+ context 'invalid' do
569
+ let(:options) { { 'cloud_auth' => 'invalid-format' } }
570
+
571
+ it "should fail" do
572
+ expect { subject.register }.to raise_error LogStash::ConfigurationError, /cloud_auth.*? format/
573
+ end
574
+ end
575
+
576
+ context 'user also set' do
577
+ let(:options) { { 'cloud_auth' => 'elastic:my-passwd-00', 'user' => 'another' } }
578
+
579
+ it "should fail" do
580
+ expect { subject.register }.to raise_error LogStash::ConfigurationError, /cloud_auth and user/
581
+ end
582
+ end
583
+ end
584
+
585
+ context 'handling elasticsearch document-level status meant for the DLQ' do
586
+ let(:options) { { "manage_template" => false } }
587
+
588
+ context 'when @dlq_writer is nil' do
589
+ before { subject.instance_variable_set '@dlq_writer', nil }
590
+
591
+ context 'resorting to previous behaviour of logging the error' do
592
+ context 'getting an invalid_index_name_exception' do
593
+ it 'should log at ERROR level' do
594
+ subject.instance_variable_set(:@logger, double("logger").as_null_object)
595
+ mock_response = { 'index' => { 'error' => { 'type' => 'invalid_index_name_exception' } } }
596
+ subject.handle_dlq_status("Could not index event to Elasticsearch.",
597
+ [:action, :params, :event], :some_status, mock_response)
598
+ end
599
+ end
600
+
601
+ context 'when getting any other exception' do
602
+ it 'should log at WARN level' do
603
+ dlog = double_logger = double("logger").as_null_object
604
+ subject.instance_variable_set(:@logger, dlog)
605
+ expect(dlog).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
606
+ mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
607
+ subject.handle_dlq_status("Could not index event to Elasticsearch.",
608
+ [:action, :params, :event], :some_status, mock_response)
609
+ end
610
+ end
611
+
612
+ context 'when the response does not include [error]' do
613
+ it 'should not fail, but just log a warning' do
614
+ dlog = double_logger = double("logger").as_null_object
615
+ subject.instance_variable_set(:@logger, dlog)
616
+ expect(dlog).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
617
+ mock_response = { 'index' => {} }
618
+ expect do
619
+ subject.handle_dlq_status("Could not index event to Elasticsearch.",
620
+ [:action, :params, :event], :some_status, mock_response)
621
+ end.to_not raise_error
622
+ end
623
+ end
624
+ end
625
+ end
626
+
627
+ # DLQ writer always nil, no matter what I try here. So mocking it all the way
628
+ context 'when DLQ is enabled' do
629
+ let(:dlq_writer) { double('DLQ writer') }
630
+ before { subject.instance_variable_set('@dlq_writer', dlq_writer) }
631
+
632
+ # Note: This is not quite the desired behaviour.
633
+ # We should still log when sending to the DLQ.
634
+ # This shall be solved by another issue, however: logstash-output-elasticsearch#772
635
+ it 'should send the event to the DLQ instead, and not log' do
636
+ expect(dlq_writer).to receive(:write).once.with(:event, /Could not index/)
637
+ mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
638
+ subject.handle_dlq_status("Could not index event to Elasticsearch.",
639
+ [:action, :params, :event], :some_status, mock_response)
640
+ end
641
+ end
642
+ end
643
+
644
+ describe "custom headers" do
645
+ let(:manticore_options) { subject.client.pool.adapter.manticore.instance_variable_get(:@options) }
646
+
647
+ context "when set" do
648
+ let(:headers) { { "X-Thing" => "Test" } }
649
+ let(:options) { { "custom_headers" => headers } }
650
+ it "should use the custom headers in the adapter options" do
651
+ expect(manticore_options[:headers]).to eq(headers)
652
+ end
653
+ end
654
+
655
+ context "when not set" do
656
+ it "should have no headers" do
657
+ expect(manticore_options[:headers]).to be_empty
658
+ end
659
+ end
660
+ end
661
+
662
+ @private
663
+
664
+ def stub_manticore_client!(manticore_double = nil)
665
+ manticore_double ||= double("manticore #{self.inspect}")
666
+ response_double = double("manticore response").as_null_object
667
+ # Allow healtchecks
668
+ allow(manticore_double).to receive(:head).with(any_args).and_return(response_double)
669
+ allow(manticore_double).to receive(:get).with(any_args).and_return(response_double)
670
+ allow(manticore_double).to receive(:close)
671
+
672
+ allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
673
+ end
674
+
675
+ end