logstash-output-amazon_es 2.0.1-java → 6.4.0-java

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