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

Sign up to get free protection for your applications and to get access to all the features.
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