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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +397 -0
- data/CONTRIBUTORS +33 -0
- data/Gemfile +15 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +106 -0
- data/docs/index.asciidoc +899 -0
- data/lib/logstash/outputs/elasticsearch/common.rb +441 -0
- data/lib/logstash/outputs/elasticsearch/common_configs.rb +167 -0
- data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es2x.json +95 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es5x.json +46 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es6x.json +45 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es7x.json +44 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es8x.json +44 -0
- data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +131 -0
- data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +495 -0
- data/lib/logstash/outputs/elasticsearch/http_client.rb +432 -0
- data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +159 -0
- data/lib/logstash/outputs/elasticsearch/ilm.rb +113 -0
- data/lib/logstash/outputs/elasticsearch/template_manager.rb +61 -0
- data/lib/logstash/outputs/elasticsearch.rb +263 -0
- data/logstash-output-elasticsearch.gemspec +33 -0
- data/spec/es_spec_helper.rb +189 -0
- data/spec/fixtures/_nodes/2x_1x.json +27 -0
- data/spec/fixtures/_nodes/5x_6x.json +81 -0
- data/spec/fixtures/_nodes/7x.json +92 -0
- data/spec/fixtures/htpasswd +2 -0
- data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
- data/spec/fixtures/scripts/groovy/scripted_update.groovy +2 -0
- data/spec/fixtures/scripts/groovy/scripted_update_nested.groovy +2 -0
- data/spec/fixtures/scripts/groovy/scripted_upsert.groovy +2 -0
- data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
- data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
- data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
- data/spec/fixtures/template-with-policy-es6x.json +48 -0
- data/spec/fixtures/template-with-policy-es7x.json +45 -0
- data/spec/fixtures/test_certs/ca/ca.crt +32 -0
- data/spec/fixtures/test_certs/ca/ca.key +51 -0
- data/spec/fixtures/test_certs/test.crt +36 -0
- data/spec/fixtures/test_certs/test.key +51 -0
- data/spec/integration/outputs/compressed_indexing_spec.rb +69 -0
- data/spec/integration/outputs/create_spec.rb +67 -0
- data/spec/integration/outputs/delete_spec.rb +65 -0
- data/spec/integration/outputs/groovy_update_spec.rb +150 -0
- data/spec/integration/outputs/ilm_spec.rb +531 -0
- data/spec/integration/outputs/index_spec.rb +178 -0
- data/spec/integration/outputs/index_version_spec.rb +102 -0
- data/spec/integration/outputs/ingest_pipeline_spec.rb +74 -0
- data/spec/integration/outputs/metrics_spec.rb +70 -0
- data/spec/integration/outputs/no_es_on_startup_spec.rb +58 -0
- data/spec/integration/outputs/painless_update_spec.rb +189 -0
- data/spec/integration/outputs/parent_spec.rb +102 -0
- data/spec/integration/outputs/retry_spec.rb +169 -0
- data/spec/integration/outputs/routing_spec.rb +61 -0
- data/spec/integration/outputs/sniffer_spec.rb +133 -0
- data/spec/integration/outputs/templates_5x_spec.rb +98 -0
- data/spec/integration/outputs/templates_spec.rb +98 -0
- data/spec/integration/outputs/update_spec.rb +116 -0
- data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
- data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
- data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
- data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
- data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
- data/spec/unit/http_client_builder_spec.rb +185 -0
- data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +149 -0
- data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +274 -0
- data/spec/unit/outputs/elasticsearch/http_client_spec.rb +250 -0
- data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
- data/spec/unit/outputs/elasticsearch_proxy_spec.rb +72 -0
- data/spec/unit/outputs/elasticsearch_spec.rb +675 -0
- data/spec/unit/outputs/elasticsearch_ssl_spec.rb +82 -0
- data/spec/unit/outputs/error_whitelist_spec.rb +54 -0
- 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
|