logstash-output-elasticsearch-test 11.16.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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +649 -0
  3. data/CONTRIBUTORS +34 -0
  4. data/Gemfile +16 -0
  5. data/LICENSE +202 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +106 -0
  8. data/docs/index.asciidoc +1369 -0
  9. data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +282 -0
  10. data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
  11. data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +155 -0
  12. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +534 -0
  13. data/lib/logstash/outputs/elasticsearch/http_client.rb +497 -0
  14. data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +201 -0
  15. data/lib/logstash/outputs/elasticsearch/ilm.rb +92 -0
  16. data/lib/logstash/outputs/elasticsearch/license_checker.rb +52 -0
  17. data/lib/logstash/outputs/elasticsearch/template_manager.rb +131 -0
  18. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-6x.json +45 -0
  19. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-7x.json +44 -0
  20. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-8x.json +50 -0
  21. data/lib/logstash/outputs/elasticsearch.rb +699 -0
  22. data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +237 -0
  23. data/lib/logstash/plugin_mixins/elasticsearch/common.rb +409 -0
  24. data/lib/logstash/plugin_mixins/elasticsearch/noop_license_checker.rb +9 -0
  25. data/logstash-output-elasticsearch.gemspec +40 -0
  26. data/spec/es_spec_helper.rb +225 -0
  27. data/spec/fixtures/_nodes/6x.json +81 -0
  28. data/spec/fixtures/_nodes/7x.json +92 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/license_check/active.json +16 -0
  31. data/spec/fixtures/license_check/inactive.json +5 -0
  32. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  33. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  34. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  35. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  36. data/spec/fixtures/template-with-policy-es6x.json +48 -0
  37. data/spec/fixtures/template-with-policy-es7x.json +45 -0
  38. data/spec/fixtures/template-with-policy-es8x.json +50 -0
  39. data/spec/fixtures/test_certs/ca.crt +29 -0
  40. data/spec/fixtures/test_certs/ca.der.sha256 +1 -0
  41. data/spec/fixtures/test_certs/ca.key +51 -0
  42. data/spec/fixtures/test_certs/renew.sh +13 -0
  43. data/spec/fixtures/test_certs/test.crt +30 -0
  44. data/spec/fixtures/test_certs/test.der.sha256 +1 -0
  45. data/spec/fixtures/test_certs/test.key +51 -0
  46. data/spec/fixtures/test_certs/test.p12 +0 -0
  47. data/spec/fixtures/test_certs/test_invalid.crt +36 -0
  48. data/spec/fixtures/test_certs/test_invalid.key +51 -0
  49. data/spec/fixtures/test_certs/test_invalid.p12 +0 -0
  50. data/spec/fixtures/test_certs/test_self_signed.crt +32 -0
  51. data/spec/fixtures/test_certs/test_self_signed.key +54 -0
  52. data/spec/fixtures/test_certs/test_self_signed.p12 +0 -0
  53. data/spec/integration/outputs/compressed_indexing_spec.rb +70 -0
  54. data/spec/integration/outputs/create_spec.rb +67 -0
  55. data/spec/integration/outputs/data_stream_spec.rb +68 -0
  56. data/spec/integration/outputs/delete_spec.rb +63 -0
  57. data/spec/integration/outputs/ilm_spec.rb +534 -0
  58. data/spec/integration/outputs/index_spec.rb +421 -0
  59. data/spec/integration/outputs/index_version_spec.rb +98 -0
  60. data/spec/integration/outputs/ingest_pipeline_spec.rb +75 -0
  61. data/spec/integration/outputs/metrics_spec.rb +66 -0
  62. data/spec/integration/outputs/no_es_on_startup_spec.rb +78 -0
  63. data/spec/integration/outputs/painless_update_spec.rb +99 -0
  64. data/spec/integration/outputs/parent_spec.rb +94 -0
  65. data/spec/integration/outputs/retry_spec.rb +182 -0
  66. data/spec/integration/outputs/routing_spec.rb +61 -0
  67. data/spec/integration/outputs/sniffer_spec.rb +94 -0
  68. data/spec/integration/outputs/templates_spec.rb +133 -0
  69. data/spec/integration/outputs/unsupported_actions_spec.rb +75 -0
  70. data/spec/integration/outputs/update_spec.rb +114 -0
  71. data/spec/spec_helper.rb +10 -0
  72. data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
  73. data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
  74. data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
  75. data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
  76. data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
  77. data/spec/unit/http_client_builder_spec.rb +185 -0
  78. data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +612 -0
  79. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +151 -0
  80. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +501 -0
  81. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +339 -0
  82. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +189 -0
  83. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +103 -0
  84. data/spec/unit/outputs/elasticsearch_spec.rb +1573 -0
  85. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +197 -0
  86. data/spec/unit/outputs/error_whitelist_spec.rb +56 -0
  87. data/spec/unit/outputs/license_check_spec.rb +57 -0
  88. metadata +423 -0
@@ -0,0 +1,421 @@
1
+ require_relative "../../../spec/es_spec_helper"
2
+ require "logstash/outputs/elasticsearch"
3
+ require 'cgi'
4
+
5
+ describe "TARGET_BULK_BYTES", :integration => true do
6
+ let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
7
+ let(:event_count) { 1000 }
8
+ let(:events) { event_count.times.map { event }.to_a }
9
+ let(:config) {
10
+ {
11
+ "hosts" => get_host_port,
12
+ "index" => index
13
+ }
14
+ }
15
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
16
+ let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
17
+
18
+ subject { LogStash::Outputs::ElasticSearch.new(config) }
19
+
20
+ before do
21
+ subject.register
22
+ allow(subject.client).to receive(:bulk_send).with(any_args).and_call_original
23
+ subject.multi_receive(events)
24
+ end
25
+
26
+ describe "batches that are too large for one" do
27
+ let(:event) { LogStash::Event.new("message" => "a " * (((target_bulk_bytes/2) / event_count)+1)) }
28
+
29
+ it "should send in two batches" do
30
+ expect(subject.client).to have_received(:bulk_send).twice do |payload|
31
+ expect(payload.size).to be <= target_bulk_bytes
32
+ end
33
+ end
34
+
35
+ describe "batches that fit in one" do
36
+ # Normally you'd want to generate a request that's just 1 byte below the limit, but it's
37
+ # impossible to know how many bytes an event will serialize as with bulk proto overhead
38
+ let(:event) { LogStash::Event.new("message" => "a") }
39
+
40
+ it "should send in one batch" do
41
+ expect(subject.client).to have_received(:bulk_send).once do |payload|
42
+ expect(payload.size).to be <= target_bulk_bytes
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def curl_and_get_json_response(url, method: :get, retrieve_err_payload: false); require 'open3'
50
+ cmd = "curl -s -v --show-error #{curl_opts} -X #{method.to_s.upcase} -k #{url}"
51
+ begin
52
+ out, err, status = Open3.capture3(cmd)
53
+ rescue Errno::ENOENT
54
+ fail "curl not available, make sure curl binary is installed and available on $PATH"
55
+ end
56
+
57
+ if status.success?
58
+ http_status = err.match(/< HTTP\/1.1 (\d+)/)[1] || '0' # < HTTP/1.1 200 OK\r\n
59
+
60
+ if http_status.strip[0].to_i > 2
61
+ error = (LogStash::Json.load(out)['error']) rescue nil
62
+ if error
63
+ if retrieve_err_payload
64
+ return error
65
+ else
66
+ fail "#{cmd.inspect} received an error: #{http_status}\n\n#{error.inspect}"
67
+ end
68
+ else
69
+ warn out
70
+ fail "#{cmd.inspect} unexpected response: #{http_status}\n\n#{err}"
71
+ end
72
+ end
73
+
74
+ LogStash::Json.load(out)
75
+ else
76
+ warn out
77
+ fail "#{cmd.inspect} process failed: #{status}\n\n#{err}"
78
+ end
79
+ end
80
+
81
+ describe "indexing with sprintf resolution", :integration => true do
82
+ let(:message) { "Hello from #{__FILE__}" }
83
+ let(:event) { LogStash::Event.new("message" => message, "type" => type) }
84
+ let (:index) { "%{[index_name]}_dynamic" }
85
+ let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
86
+ let(:event_count) { 1 }
87
+ let(:user) { "simpleuser" }
88
+ let(:password) { "abc123" }
89
+ let(:config) do
90
+ {
91
+ "hosts" => [ get_host_port ],
92
+ "user" => user,
93
+ "password" => password,
94
+ "index" => index
95
+ }
96
+ end
97
+ let(:events) { event_count.times.map { event }.to_a }
98
+ subject { LogStash::Outputs::ElasticSearch.new(config) }
99
+
100
+ let(:es_url) { "http://#{get_host_port}" }
101
+ let(:index_url) { "#{es_url}/#{index}" }
102
+
103
+ let(:curl_opts) { nil }
104
+
105
+ let(:es_admin) { 'admin' } # default user added in ES -> 8.x requires auth credentials for /_refresh etc
106
+ let(:es_admin_pass) { 'elastic' }
107
+
108
+ let(:initial_events) { [] }
109
+
110
+ let(:do_register) { true }
111
+
112
+ before do
113
+ subject.register if do_register
114
+ subject.multi_receive(initial_events) if initial_events
115
+ end
116
+
117
+ after do
118
+ subject.do_close
119
+ end
120
+
121
+ let(:event) { LogStash::Event.new("message" => message, "type" => type, "index_name" => "test") }
122
+
123
+ it "should index successfully when field is resolved" do
124
+ expected_index_name = "test_dynamic"
125
+ subject.multi_receive(events)
126
+
127
+ # curl_and_get_json_response "#{es_url}/_refresh", method: :post
128
+
129
+ result = curl_and_get_json_response "#{es_url}/#{expected_index_name}"
130
+
131
+ expect(result[expected_index_name]).not_to be(nil)
132
+ end
133
+
134
+ context "when dynamic field doesn't resolve the index_name" do
135
+ let(:event) { LogStash::Event.new("message" => message, "type" => type) }
136
+ let(:dlq_writer) { double('DLQ writer') }
137
+ before { subject.instance_variable_set('@dlq_writer', dlq_writer) }
138
+
139
+ it "should doesn't create an index name with unresolved placeholders" do
140
+ expect(dlq_writer).to receive(:write).once.with(event, a_string_including("Badly formatted index, after interpolation still contains placeholder"))
141
+ subject.multi_receive(events)
142
+
143
+ escaped_index_name = CGI.escape("%{[index_name]}_dynamic")
144
+ result = curl_and_get_json_response "#{es_url}/#{escaped_index_name}", retrieve_err_payload: true
145
+ expect(result["root_cause"].first()["type"]).to eq("index_not_found_exception")
146
+ end
147
+ end
148
+ end
149
+
150
+ describe "indexing" do
151
+ let(:message) { "Hello from #{__FILE__}" }
152
+ let(:event) { LogStash::Event.new("message" => message, "type" => type) }
153
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
154
+ let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
155
+ let(:event_count) { 1 + rand(2) }
156
+ let(:config) { "not implemented" }
157
+ let(:events) { event_count.times.map { event }.to_a }
158
+ subject { LogStash::Outputs::ElasticSearch.new(config) }
159
+
160
+ let(:es_url) { "http://#{get_host_port}" }
161
+ let(:index_url) { "#{es_url}/#{index}" }
162
+
163
+ let(:curl_opts) { nil }
164
+
165
+ let(:es_admin) { 'admin' } # default user added in ES -> 8.x requires auth credentials for /_refresh etc
166
+ let(:es_admin_pass) { 'elastic' }
167
+
168
+ let(:initial_events) { [] }
169
+
170
+ let(:do_register) { true }
171
+
172
+ before do
173
+ subject.register if do_register
174
+ subject.multi_receive(initial_events) if initial_events
175
+ end
176
+
177
+ after do
178
+ subject.do_close
179
+ end
180
+
181
+ shared_examples "an indexer" do |secure|
182
+ before(:each) do
183
+ host_unreachable_error_class = LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError
184
+ allow(host_unreachable_error_class).to receive(:new).with(any_args).and_wrap_original do |m, original, url|
185
+ if original.message.include?("PKIX path building failed")
186
+ $stderr.puts "Client not connecting due to PKIX path building failure; " +
187
+ "shutting plugin down to prevent infinite retries"
188
+ subject.close # premature shutdown to prevent infinite retry
189
+ end
190
+ m.call(original, url)
191
+ end
192
+ end
193
+
194
+ it "ships events" do
195
+ subject.multi_receive(events)
196
+
197
+ curl_and_get_json_response "#{es_url}/_refresh", method: :post
198
+
199
+ result = curl_and_get_json_response "#{index_url}/_count?q=*"
200
+ cur_count = result["count"]
201
+ expect(cur_count).to eq(event_count)
202
+
203
+ result = curl_and_get_json_response "#{index_url}/_search?q=*&size=1000"
204
+ result["hits"]["hits"].each do |doc|
205
+ expect(doc["_source"]["message"]).to eq(message)
206
+
207
+ if ESHelper.es_version_satisfies?("< 8")
208
+ expect(doc["_type"]).to eq(type)
209
+ else
210
+ expect(doc).not_to include("_type")
211
+ end
212
+ expect(doc["_index"]).to eq(index)
213
+ end
214
+ end
215
+
216
+ it "sets the correct content-type header" do
217
+ expected_manticore_opts = {:headers => {"Content-Type" => "application/json"}, :body => anything}
218
+ if secure
219
+ expected_manticore_opts = {
220
+ :headers => {"Content-Type" => "application/json"},
221
+ :body => anything,
222
+ :auth => {
223
+ :user => user,
224
+ :password => password,
225
+ :eager => true
226
+ }}
227
+ end
228
+ expect(subject.client.pool.adapter.client).to receive(:send).
229
+ with(anything, anything, expected_manticore_opts).at_least(:once).
230
+ and_call_original
231
+ subject.multi_receive(events)
232
+ end
233
+ end
234
+
235
+ shared_examples "PKIX path failure" do
236
+ let(:do_register) { false }
237
+ let(:host_unreachable_error_class) { LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError }
238
+
239
+ before(:each) do
240
+ limit_execution
241
+ end
242
+
243
+ let(:limit_execution) do
244
+ Thread.new { sleep 5; subject.close }
245
+ end
246
+
247
+ it 'fails to establish TLS' do
248
+ allow(host_unreachable_error_class).to receive(:new).with(any_args).and_call_original.at_least(:once)
249
+
250
+ subject.register
251
+ limit_execution.join
252
+
253
+ sleep 1
254
+
255
+ expect(host_unreachable_error_class).to have_received(:new).at_least(:once) do |original, url|
256
+ expect(original.message).to include("PKIX path building failed")
257
+ end
258
+ end
259
+ end
260
+
261
+ describe "an indexer with custom index_type", :integration => true do
262
+ let(:config) {
263
+ {
264
+ "hosts" => get_host_port,
265
+ "index" => index
266
+ }
267
+ }
268
+ it_behaves_like("an indexer")
269
+ end
270
+
271
+ describe "an indexer with no type value set (default to doc)", :integration => true do
272
+ let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
273
+ let(:config) {
274
+ {
275
+ "hosts" => get_host_port,
276
+ "index" => index
277
+ }
278
+ }
279
+ it_behaves_like("an indexer")
280
+ end
281
+
282
+ describe "a secured indexer", :secure_integration => true do
283
+ let(:user) { "simpleuser" }
284
+ let(:password) { "abc123" }
285
+ let(:cacert) { "spec/fixtures/test_certs/ca.crt" }
286
+ let(:es_url) { "https://#{get_host_port}" }
287
+ let(:config) do
288
+ {
289
+ "hosts" => [ get_host_port ],
290
+ "user" => user,
291
+ "password" => password,
292
+ "ssl_enabled" => true,
293
+ "ssl_certificate_authorities" => cacert,
294
+ "index" => index
295
+ }
296
+ end
297
+
298
+ let(:curl_opts) { "-u #{user}:#{password}" }
299
+
300
+ if ENV['ES_SSL_KEY_INVALID'] == 'true' # test_invalid.crt (configured in ES) has SAN: DNS:localhost
301
+ # javax.net.ssl.SSLPeerUnverifiedException: Host name 'elasticsearch' does not match the certificate subject ...
302
+
303
+ context "when no keystore nor ca cert set and verification is disabled" do
304
+ let(:config) do
305
+ super().tap { |config| config.delete('ssl_certificate_authorities') }.merge('ssl_verification_mode' => 'none')
306
+ end
307
+
308
+ include_examples("an indexer", true)
309
+ end
310
+
311
+ context "when keystore is set and verification is disabled" do
312
+ let(:config) do
313
+ super().merge(
314
+ 'ssl_verification_mode' => 'none',
315
+ 'ssl_keystore_path' => 'spec/fixtures/test_certs/test.p12',
316
+ 'ssl_keystore_password' => '1234567890'
317
+ )
318
+ end
319
+
320
+ include_examples("an indexer", true)
321
+ end
322
+
323
+ context "when keystore has self-signed cert and verification is disabled" do
324
+ let(:config) do
325
+ super().tap { |config| config.delete('ssl_certificate_authorities') }.merge(
326
+ 'ssl_verification_mode' => 'none',
327
+ 'ssl_keystore_path' => 'spec/fixtures/test_certs/test_self_signed.p12',
328
+ 'ssl_keystore_password' => '1234567890'
329
+ )
330
+ end
331
+
332
+ include_examples("an indexer", true)
333
+ end
334
+
335
+ else
336
+
337
+ let(:curl_opts) { "#{super()} --tlsv1.2 --tls-max 1.3 -u #{es_admin}:#{es_admin_pass}" } # due ES 8.x we need user/password
338
+
339
+ it_behaves_like("an indexer", true)
340
+
341
+ describe "with a password requiring escaping" do
342
+ let(:user) { "f@ncyuser" }
343
+ let(:password) { "ab%12#" }
344
+
345
+ include_examples("an indexer", true)
346
+ end
347
+
348
+ describe "with a user/password requiring escaping in the URL" do
349
+ let(:config) do
350
+ {
351
+ "hosts" => ["https://#{CGI.escape(user)}:#{CGI.escape(password)}@elasticsearch:9200"],
352
+ "ssl_enabled" => true,
353
+ "ssl_certificate_authorities" => "spec/fixtures/test_certs/test.crt",
354
+ "index" => index
355
+ }
356
+ end
357
+
358
+ include_examples("an indexer", true)
359
+ end
360
+
361
+ context "without providing `ssl_certificate_authorities`" do
362
+ let(:config) do
363
+ super().tap do |c|
364
+ c.delete("ssl_certificate_authorities")
365
+ end
366
+ end
367
+
368
+ it_behaves_like("PKIX path failure")
369
+ end
370
+
371
+ if Gem::Version.new(LOGSTASH_VERSION) >= Gem::Version.new("8.3.0")
372
+ context "with `ca_trusted_fingerprint` instead of `ssl_certificate_authorities`" do
373
+ let(:config) do
374
+ super().tap do |c|
375
+ c.delete("ssl_certificate_authorities")
376
+ c.update("ca_trusted_fingerprint" => ca_trusted_fingerprint)
377
+ end
378
+ end
379
+ let(:ca_trusted_fingerprint) { File.read("spec/fixtures/test_certs/test.der.sha256").chomp }
380
+
381
+
382
+ it_behaves_like("an indexer", true)
383
+
384
+ context 'with an invalid `ca_trusted_fingerprint`' do
385
+ let(:ca_trusted_fingerprint) { super().reverse }
386
+
387
+ it_behaves_like("PKIX path failure")
388
+ end
389
+ end
390
+ end
391
+
392
+ context 'with enforced TLSv1.3 protocol' do
393
+ let(:config) { super().merge 'ssl_supported_protocols' => [ 'TLSv1.3' ] }
394
+
395
+ it_behaves_like("an indexer", true)
396
+ end
397
+
398
+ context 'with enforced TLSv1.2 protocol (while ES only enabled TLSv1.3)' do
399
+ let(:config) { super().merge 'ssl_supported_protocols' => [ 'TLSv1.2' ] }
400
+
401
+ let(:initial_events) { nil }
402
+
403
+ it "does not ship events" do
404
+ curl_and_get_json_response index_url, method: :put # make sure index exists
405
+ Thread.start { subject.multi_receive(events) } # we'll be stuck in a retry loop
406
+ sleep 2.5
407
+
408
+ curl_and_get_json_response "#{es_url}/_refresh", method: :post
409
+
410
+ result = curl_and_get_json_response "#{index_url}/_count?q=*"
411
+ cur_count = result["count"]
412
+ expect(cur_count).to eq(0) # ES output keeps re-trying but ends up with a
413
+ # [Manticore::ClientProtocolException] Received fatal alert: protocol_version
414
+ end
415
+
416
+ end if ENV['ES_SSL_SUPPORTED_PROTOCOLS'] == 'TLSv1.3'
417
+
418
+ end
419
+
420
+ end
421
+ end
@@ -0,0 +1,98 @@
1
+ require_relative "../../../spec/es_spec_helper"
2
+ require "logstash/outputs/elasticsearch"
3
+
4
+ describe "Versioned indexing", :integration => true do
5
+ require "logstash/outputs/elasticsearch"
6
+
7
+ let(:es) { get_client }
8
+
9
+ before :each do
10
+ # Delete all templates first.
11
+ # Clean ES of data before we start.
12
+ es.indices.delete_template(:name => "*")
13
+ # This can fail if there are no indexes, ignore failure.
14
+ es.indices.delete(:index => "*") rescue nil
15
+ es.indices.refresh
16
+ end
17
+
18
+ context "when index only" do
19
+ subject { LogStash::Outputs::ElasticSearch.new(settings) }
20
+
21
+ before do
22
+ subject.register
23
+ end
24
+
25
+ describe "unversioned output" do
26
+ let(:settings) do
27
+ {
28
+ "manage_template" => true,
29
+ "index" => "logstash-index",
30
+ "template_overwrite" => true,
31
+ "hosts" => get_host_port(),
32
+ "action" => "index",
33
+ "document_id" => "%{my_id}"
34
+ }
35
+ end
36
+
37
+ it "should default to ES version" do
38
+ subject.multi_receive([LogStash::Event.new("my_id" => "123", "message" => "foo")])
39
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => "123", :refresh => true)
40
+ expect(r["_version"]).to eq(1)
41
+ expect(r["_source"]["message"]).to eq('foo')
42
+ subject.multi_receive([LogStash::Event.new("my_id" => "123", "message" => "foobar")])
43
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => "123", :refresh => true)
44
+ expect(r2["_version"]).to eq(2)
45
+ expect(r2["_source"]["message"]).to eq('foobar')
46
+ end
47
+ end
48
+
49
+ describe "versioned output" do
50
+ let(:settings) do
51
+ {
52
+ "manage_template" => true,
53
+ "index" => "logstash-index",
54
+ "template_overwrite" => true,
55
+ "hosts" => get_host_port(),
56
+ "action" => "index",
57
+ "document_id" => "%{my_id}",
58
+ "version" => "%{my_version}",
59
+ "version_type" => "external",
60
+ }
61
+ end
62
+
63
+ it "should respect the external version" do
64
+ id = "ev1"
65
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
66
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
67
+ expect(r["_version"]).to eq(99)
68
+ expect(r["_source"]["message"]).to eq('foo')
69
+ end
70
+
71
+ it "should ignore non-monotonic external version updates" do
72
+ id = "ev2"
73
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
74
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
75
+ expect(r["_version"]).to eq(99)
76
+ expect(r["_source"]["message"]).to eq('foo')
77
+
78
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "98", "message" => "foo")])
79
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
80
+ expect(r2["_version"]).to eq(99)
81
+ expect(r2["_source"]["message"]).to eq('foo')
82
+ end
83
+
84
+ it "should commit monotonic external version updates" do
85
+ id = "ev3"
86
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
87
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
88
+ expect(r["_version"]).to eq(99)
89
+ expect(r["_source"]["message"]).to eq('foo')
90
+
91
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "100", "message" => "foo")])
92
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
93
+ expect(r2["_version"]).to eq(100)
94
+ expect(r2["_source"]["message"]).to eq('foo')
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "../../../spec/es_spec_helper"
2
+
3
+ describe "Ingest pipeline execution behavior", :integration => true do
4
+ subject! do
5
+ require "logstash/outputs/elasticsearch"
6
+ settings = {
7
+ "hosts" => "#{get_host_port()}",
8
+ "pipeline" => "apache-logs",
9
+ "data_stream" => 'false',
10
+ "ecs_compatibility" => "disabled", # specs are tightly tied to non-ECS defaults
11
+ }
12
+ next LogStash::Outputs::ElasticSearch.new(settings)
13
+ end
14
+
15
+ let(:http_client) { Manticore::Client.new }
16
+ let(:ingest_url) { "http://#{get_host_port()}/_ingest/pipeline/apache-logs" }
17
+ let(:apache_logs_pipeline) { '
18
+ {
19
+ "description" : "Pipeline to parse Apache logs",
20
+ "processors" : [
21
+ {
22
+ "grok": {
23
+ "field": "message",
24
+ "patterns": ["%{COMBINEDAPACHELOG}"]
25
+ }
26
+ }
27
+ ]
28
+ }'
29
+ }
30
+
31
+ before :each do
32
+ # Delete all templates first.
33
+ require "elasticsearch"
34
+
35
+ # Clean ES of data before we start.
36
+ @es = get_client
37
+ @es.indices.delete_template(:name => "*")
38
+
39
+ # This can fail if there are no indexes, ignore failure.
40
+ @es.indices.delete(:index => "*") rescue nil
41
+
42
+ # delete existing ingest pipeline
43
+ http_client.delete(ingest_url).call
44
+
45
+ # register pipeline
46
+ http_client.put(ingest_url, :body => apache_logs_pipeline, :headers => {"Content-Type" => "application/json" }).call
47
+
48
+ #TODO: Use esclient
49
+ #@es.ingest.put_pipeline :id => 'apache_pipeline', :body => pipeline_defintion
50
+
51
+ subject.register
52
+ subject.multi_receive([LogStash::Event.new("message" => '183.60.215.50 - - [01/Jun/2015:18:00:00 +0000] "GET /scripts/netcat-webserver HTTP/1.1" 200 182 "-" "Mozilla/5.0 (compatible; EasouSpider; +http://www.easou.com/search/spider.html)"')])
53
+ @es.indices.refresh
54
+
55
+ #Wait or fail until everything's indexed.
56
+ Stud::try(10.times) do
57
+ r = @es.search(index: 'logstash-*')
58
+ expect(r).to have_hits(1)
59
+ sleep(0.1)
60
+ end
61
+ end
62
+
63
+ it "indexes using the proper pipeline" do
64
+ results = @es.search(:index => 'logstash-*', :q => "message:\"netcat\"")
65
+ expect(results).to have_hits(1)
66
+ expect(results["hits"]["hits"][0]["_source"]["response"]).to eq("200")
67
+ expect(results["hits"]["hits"][0]["_source"]["bytes"]).to eq("182")
68
+ expect(results["hits"]["hits"][0]["_source"]["verb"]).to eq("GET")
69
+ expect(results["hits"]["hits"][0]["_source"]["request"]).to eq("/scripts/netcat-webserver")
70
+ expect(results["hits"]["hits"][0]["_source"]["auth"]).to eq("-")
71
+ expect(results["hits"]["hits"][0]["_source"]["ident"]).to eq("-")
72
+ expect(results["hits"]["hits"][0]["_source"]["clientip"]).to eq("183.60.215.50")
73
+ expect(results["hits"]["hits"][0]["_source"]["junkfieldaaaa"]).to eq(nil)
74
+ end
75
+ end
@@ -0,0 +1,66 @@
1
+ require_relative "../../../spec/es_spec_helper"
2
+
3
+ describe "metrics", :integration => true do
4
+ subject! do
5
+ require "logstash/outputs/elasticsearch"
6
+ settings = {
7
+ "manage_template" => false,
8
+ "hosts" => "#{get_host_port()}"
9
+ }
10
+ plugin = LogStash::Outputs::ElasticSearch.new(settings)
11
+ end
12
+
13
+ let(:metric) { subject.metric }
14
+ let(:bulk_request_metrics) { subject.instance_variable_get(:@bulk_request_metrics) }
15
+ let(:document_level_metrics) { subject.instance_variable_get(:@document_level_metrics) }
16
+
17
+ before :each do
18
+ require "elasticsearch"
19
+
20
+ # Clean ES of data before we start.
21
+ @es = get_client
22
+ clean(@es)
23
+ subject.register
24
+ end
25
+
26
+ context "after a succesful bulk insert" do
27
+ let(:bulk) { [
28
+ LogStash::Event.new("message" => "sample message here"),
29
+ LogStash::Event.new("somemessage" => { "message" => "sample nested message here" }),
30
+ LogStash::Event.new("somevalue" => 100),
31
+ LogStash::Event.new("somevalue" => 10),
32
+ LogStash::Event.new("somevalue" => 1),
33
+ LogStash::Event.new("country" => "us"),
34
+ LogStash::Event.new("country" => "at"),
35
+ LogStash::Event.new("geoip" => { "location" => [ 0.0, 0.0 ] })
36
+ ]}
37
+
38
+ it "increases successful bulk request metric" do
39
+ expect(bulk_request_metrics).to receive(:increment).with(:successes).once
40
+ subject.multi_receive(bulk)
41
+ end
42
+
43
+ it "increases number of successful inserted documents" do
44
+ expect(document_level_metrics).to receive(:increment).with(:successes, bulk.size).once
45
+ subject.multi_receive(bulk)
46
+ end
47
+ end
48
+
49
+ context "after a bulk insert that generates errors" do
50
+ let(:bulk) { [
51
+ LogStash::Event.new("message" => "sample message here"),
52
+ LogStash::Event.new("message" => { "message" => "sample nested message here" }),
53
+ ]}
54
+ it "increases bulk request with error metric" do
55
+ expect(bulk_request_metrics).to receive(:increment).with(:with_errors).once
56
+ expect(bulk_request_metrics).to_not receive(:increment).with(:successes)
57
+ subject.multi_receive(bulk)
58
+ end
59
+
60
+ it "increases number of successful and non retryable documents" do
61
+ expect(document_level_metrics).to receive(:increment).with(:dlq_routed).once
62
+ expect(document_level_metrics).to receive(:increment).with(:successes).once
63
+ subject.multi_receive(bulk)
64
+ end
65
+ end
66
+ end