fluent-plugin-elasticsearch2 3.5.5

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.
@@ -0,0 +1,44 @@
1
+ require 'helper'
2
+ require 'date'
3
+ require 'fluent/test/helpers'
4
+ require 'json'
5
+ require 'fluent/test/driver/filter'
6
+ require 'flexmock/test_unit'
7
+ require 'fluent/plugin/filter_elasticsearch_genid'
8
+
9
+ class ElasticsearchGenidFilterTest < Test::Unit::TestCase
10
+ include FlexMock::TestCase
11
+ include Fluent::Test::Helpers
12
+
13
+ def setup
14
+ Fluent::Test.setup
15
+ end
16
+
17
+ def create_driver(conf='')
18
+ Fluent::Test::Driver::Filter.new(Fluent::Plugin::ElasticsearchGenidFilter).configure(conf)
19
+ end
20
+
21
+ def sample_record
22
+ {'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}
23
+ end
24
+
25
+ def test_configure
26
+ d = create_driver
27
+ assert_equal '_hash', d.instance.hash_id_key
28
+ end
29
+
30
+ data("default" => {"hash_id_key" => "_hash"},
31
+ "custom_key" => {"hash_id_key" => "_edited"},
32
+ )
33
+ def test_filter(data)
34
+ d = create_driver("hash_id_key #{data["hash_id_key"]}")
35
+ flexmock(SecureRandom).should_receive(:uuid)
36
+ .and_return("13a0c028-bf7c-4ae2-ad03-ec09a40006df")
37
+ time = event_time("2017-10-15 15:00:23.34567890 UTC")
38
+ d.run(default_tag: 'test') do
39
+ d.feed(time, sample_record)
40
+ end
41
+ assert_equal(Base64.strict_encode64(SecureRandom.uuid),
42
+ d.filtered.map {|e| e.last}.first[d.instance.hash_id_key])
43
+ end
44
+ end
@@ -0,0 +1,2811 @@
1
+ require 'helper'
2
+ require 'date'
3
+ require 'fluent/test/helpers'
4
+ require 'json'
5
+ require 'fluent/test/driver/output'
6
+ require 'flexmock/test_unit'
7
+
8
+ class ElasticsearchOutput < Test::Unit::TestCase
9
+ include FlexMock::TestCase
10
+ include Fluent::Test::Helpers
11
+
12
+ attr_accessor :index_cmds, :index_command_counts
13
+
14
+ def setup
15
+ Fluent::Test.setup
16
+ require 'fluent/plugin/out_elasticsearch'
17
+ @driver = nil
18
+ log = Fluent::Engine.log
19
+ log.out.logs.slice!(0, log.out.logs.length)
20
+ end
21
+
22
+ def driver(conf='', es_version=5, client_version="\"5.0\"")
23
+ # For request stub to detect compatibility.
24
+ @es_version ||= es_version
25
+ @client_version ||= client_version
26
+ if @es_version
27
+ Fluent::Plugin::ElasticsearchOutput.module_eval(<<-CODE)
28
+ def detect_es_major_version
29
+ #{@es_version}
30
+ end
31
+ CODE
32
+ end
33
+ Fluent::Plugin::ElasticsearchOutput.module_eval(<<-CODE)
34
+ def client_library_version
35
+ #{@client_version}
36
+ end
37
+ CODE
38
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutput) {
39
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
40
+ if !defined?(Fluent::Plugin::Output)
41
+ def format(tag, time, record)
42
+ [time, record].to_msgpack
43
+ end
44
+ end
45
+ }.configure(conf)
46
+ end
47
+
48
+ def default_type_name
49
+ Fluent::Plugin::ElasticsearchOutput::DEFAULT_TYPE_NAME
50
+ end
51
+
52
+ def sample_record(content={})
53
+ {'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}.merge(content)
54
+ end
55
+
56
+ def nested_sample_record
57
+ {'nested' =>
58
+ {'age' => 26, 'parent_id' => 'parent', 'routing_id' => 'routing', 'request_id' => '42'}
59
+ }
60
+ end
61
+
62
+ def stub_elastic_info(url="http://localhost:9200/", version="6.4.2")
63
+ body ="{\"version\":{\"number\":\"#{version}\"}}"
64
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'json' } })
65
+ end
66
+
67
+ def stub_elastic(url="http://localhost:9200/_bulk")
68
+ stub_request(:post, url).with do |req|
69
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
70
+ end
71
+ end
72
+
73
+ def stub_elastic_unavailable(url="http://localhost:9200/_bulk")
74
+ stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
75
+ end
76
+
77
+ def stub_elastic_timeout(url="http://localhost:9200/_bulk")
78
+ stub_request(:post, url).to_timeout
79
+ end
80
+
81
+ def stub_elastic_with_store_index_command_counts(url="http://localhost:9200/_bulk")
82
+ if @index_command_counts == nil
83
+ @index_command_counts = {}
84
+ @index_command_counts.default = 0
85
+ end
86
+
87
+ stub_request(:post, url).with do |req|
88
+ index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
89
+ @index_command_counts[url] += index_cmds.size
90
+ end
91
+ end
92
+
93
+ def make_response_body(req, error_el = nil, error_status = nil, error = nil)
94
+ req_index_cmds = req.body.split("\n").map { |r| JSON.parse(r) }
95
+ items = []
96
+ count = 0
97
+ ids = 1
98
+ op = nil
99
+ index = nil
100
+ type = nil
101
+ id = nil
102
+ req_index_cmds.each do |cmd|
103
+ if count.even?
104
+ op = cmd.keys[0]
105
+ index = cmd[op]['_index']
106
+ type = cmd[op]['_type']
107
+ if cmd[op].has_key?('_id')
108
+ id = cmd[op]['_id']
109
+ else
110
+ # Note: this appears to be an undocumented feature of Elasticsearch
111
+ # https://www.elastic.co/guide/en/elasticsearch/reference/2.4/docs-bulk.html
112
+ # When you submit an "index" write_operation, with no "_id" field in the
113
+ # metadata header, Elasticsearch will turn this into a "create"
114
+ # operation in the response.
115
+ if "index" == op
116
+ op = "create"
117
+ end
118
+ id = ids
119
+ ids += 1
120
+ end
121
+ else
122
+ item = {
123
+ op => {
124
+ '_index' => index, '_type' => type, '_id' => id, '_version' => 1,
125
+ '_shards' => { 'total' => 1, 'successful' => 1, 'failed' => 0 },
126
+ 'status' => op == 'create' ? 201 : 200
127
+ }
128
+ }
129
+ items.push(item)
130
+ end
131
+ count += 1
132
+ end
133
+ if !error_el.nil? && !error_status.nil? && !error.nil?
134
+ op = items[error_el].keys[0]
135
+ items[error_el][op].delete('_version')
136
+ items[error_el][op].delete('_shards')
137
+ items[error_el][op]['error'] = error
138
+ items[error_el][op]['status'] = error_status
139
+ errors = true
140
+ else
141
+ errors = false
142
+ end
143
+ @index_cmds = items
144
+ body = { 'took' => 6, 'errors' => errors, 'items' => items }
145
+ return body.to_json
146
+ end
147
+
148
+ def stub_elastic_bad_argument(url="http://localhost:9200/_bulk")
149
+ error = {
150
+ "type" => "mapper_parsing_exception",
151
+ "reason" => "failed to parse [...]",
152
+ "caused_by" => {
153
+ "type" => "illegal_argument_exception",
154
+ "reason" => "Invalid format: \"...\""
155
+ }
156
+ }
157
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 400, error), :headers => { 'Content-Type' => 'json' } } })
158
+ end
159
+
160
+ def stub_elastic_bulk_error(url="http://localhost:9200/_bulk")
161
+ error = {
162
+ "type" => "some-unrecognized-error",
163
+ "reason" => "some message printed here ...",
164
+ }
165
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 500, error), :headers => { 'Content-Type' => 'json' } } })
166
+ end
167
+
168
+ def stub_elastic_bulk_rejected(url="http://localhost:9200/_bulk")
169
+ error = {
170
+ "status" => 500,
171
+ "type" => "es_rejected_execution_exception",
172
+ "reason" => "rejected execution of org.elasticsearch.transport.TransportService$4@1a34d37a on EsThreadPoolExecutor[bulk, queue capacity = 50, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@312a2162[Running, pool size = 32, active threads = 32, queued tasks = 50, completed tasks = 327053]]"
173
+ }
174
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 429, error), :headers => { 'Content-Type' => 'json' } } })
175
+ end
176
+
177
+ def stub_elastic_out_of_memory(url="http://localhost:9200/_bulk")
178
+ error = {
179
+ "status" => 500,
180
+ "type" => "out_of_memory_error",
181
+ "reason" => "Java heap space"
182
+ }
183
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 500, error), :headers => { 'Content-Type' => 'json' } } })
184
+ end
185
+
186
+ def stub_elastic_unexpected_response_op(url="http://localhost:9200/_bulk")
187
+ error = {
188
+ "category" => "some-other-type",
189
+ "reason" => "some-other-reason"
190
+ }
191
+ stub_request(:post, url).to_return(lambda { |req| bodystr = make_response_body(req, 0, 500, error); body = JSON.parse(bodystr); body['items'][0]['unknown'] = body['items'][0].delete('create'); { :status => 200, :body => body.to_json, :headers => { 'Content-Type' => 'json' } } })
192
+ end
193
+
194
+ def assert_logs_include(logs, msg, exp_matches=1)
195
+ matches = logs.grep /#{msg}/
196
+ assert_equal(exp_matches, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
197
+ end
198
+
199
+ def assert_logs_include_compare_size(exp_matches=1, operator="<=", logs="", msg="")
200
+ matches = logs.grep /#{msg}/
201
+ assert_compare(exp_matches, operator, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
202
+ end
203
+
204
+ def test_configure
205
+ config = %{
206
+ host logs.google.com
207
+ port 777
208
+ scheme https
209
+ path /es/
210
+ user john
211
+ password doe
212
+ }
213
+ instance = driver(config).instance
214
+
215
+ assert_equal 'logs.google.com', instance.host
216
+ assert_equal 777, instance.port
217
+ assert_equal :https, instance.scheme
218
+ assert_equal '/es/', instance.path
219
+ assert_equal 'john', instance.user
220
+ assert_equal 'doe', instance.password
221
+ assert_equal :TLSv1, instance.ssl_version
222
+ assert_nil instance.client_key
223
+ assert_nil instance.client_cert
224
+ assert_nil instance.client_key_pass
225
+ assert_false instance.with_transporter_log
226
+ assert_equal :"application/json", instance.content_type
227
+ assert_equal "fluentd", default_type_name
228
+ assert_equal :excon, instance.http_backend
229
+ assert_false instance.prefer_oj_serializer
230
+ assert_equal ["out_of_memory_error", "es_rejected_execution_exception"], instance.unrecoverable_error_types
231
+ assert_true instance.verify_es_version_at_startup
232
+ assert_equal Fluent::Plugin::ElasticsearchOutput::DEFAULT_ELASTICSEARCH_VERSION, instance.default_elasticsearch_version
233
+ assert_false instance.log_es_400_reason
234
+ assert_equal 20 * 1024 * 1024, Fluent::Plugin::ElasticsearchOutput::TARGET_BULK_BYTES
235
+ end
236
+
237
+ test 'configure Content-Type' do
238
+ config = %{
239
+ content_type application/x-ndjson
240
+ }
241
+ instance = driver(config).instance
242
+ assert_equal :"application/x-ndjson", instance.content_type
243
+ end
244
+
245
+ test 'invalid Content-Type' do
246
+ config = %{
247
+ content_type nonexistent/invalid
248
+ }
249
+ assert_raise(Fluent::ConfigError) {
250
+ driver(config)
251
+ }
252
+ end
253
+
254
+ test 'invalid specification of times of retrying template installation' do
255
+ config = %{
256
+ max_retry_putting_template -3
257
+ }
258
+ assert_raise(Fluent::ConfigError) {
259
+ driver(config)
260
+ }
261
+ end
262
+
263
+ test 'invalid specification of times of retrying get es version' do
264
+ config = %{
265
+ max_retry_get_es_version -3
266
+ }
267
+ assert_raise(Fluent::ConfigError) {
268
+ driver(config)
269
+ }
270
+ end
271
+
272
+ test 'Detected Elasticsearch 7' do
273
+ config = %{
274
+ type_name changed
275
+ }
276
+ instance = driver(config, 7).instance
277
+ assert_equal '_doc', instance.type_name
278
+ end
279
+
280
+ test 'Detected Elasticsearch 6 and insecure security' do
281
+ config = %{
282
+ ssl_version TLSv1_1
283
+ @log_level warn
284
+ scheme https
285
+ }
286
+ driver(config, 6)
287
+ logs = driver.logs
288
+ assert_logs_include(logs, /Detected ES 6.x or above and enabled insecure security/, 1)
289
+ end
290
+
291
+ test 'Detected Elasticsearch 7 and secure security' do
292
+ config = %{
293
+ ssl_version TLSv1_2
294
+ @log_level warn
295
+ scheme https
296
+ }
297
+ driver(config, 7)
298
+ logs = driver.logs
299
+ assert_logs_include(logs, /Detected ES 6.x or above and enabled insecure security/, 0)
300
+ end
301
+
302
+ test 'Pass Elasticsearch and client library are same' do
303
+ config = %{
304
+ @log_level warn
305
+ validate_client_version true
306
+ }
307
+ assert_nothing_raised do
308
+ driver(config, 6, "\"6.1.0\"")
309
+ end
310
+ end
311
+
312
+ test 'Detected Elasticsearch and client library mismatch' do
313
+ config = %{
314
+ @log_level warn
315
+ validate_client_version true
316
+ }
317
+ assert_raise_message(/Detected ES 7 but you use ES client 5.0/) do
318
+ driver(config, 7, "\"5.0.5\"")
319
+ end
320
+ end
321
+
322
+ test 'lack of tag in chunk_keys' do
323
+ assert_raise_message(/'tag' in chunk_keys is required./) do
324
+ driver(Fluent::Config::Element.new(
325
+ 'ROOT', '', {
326
+ '@type' => 'elasticsearch',
327
+ 'host' => 'log.google.com',
328
+ 'port' => 777,
329
+ 'scheme' => 'https',
330
+ 'path' => '/es/',
331
+ 'user' => 'john',
332
+ 'password' => 'doe',
333
+ }, [
334
+ Fluent::Config::Element.new('buffer', 'mykey', {
335
+ 'chunk_keys' => 'mykey'
336
+ }, [])
337
+ ]
338
+ ))
339
+ end
340
+ end
341
+
342
+ sub_test_case 'connection exceptions' do
343
+ test 'default connection exception' do
344
+ driver(Fluent::Config::Element.new(
345
+ 'ROOT', '', {
346
+ '@type' => 'elasticsearch',
347
+ 'host' => 'log.google.com',
348
+ 'port' => 777,
349
+ 'scheme' => 'https',
350
+ 'path' => '/es/',
351
+ 'user' => 'john',
352
+ 'password' => 'doe',
353
+ }, [
354
+ Fluent::Config::Element.new('buffer', 'tag', {
355
+ }, [])
356
+ ]
357
+ ))
358
+ logs = driver.logs
359
+ assert_logs_include(logs, /you should specify 2 or more 'flush_thread_count'/, 1)
360
+ end
361
+ end
362
+
363
+ class GetElasticsearchVersionTest < self
364
+ def create_driver(conf='', client_version="\"5.0\"")
365
+ # For request stub to detect compatibility.
366
+ @client_version ||= client_version
367
+ # Ensure original implementation existence.
368
+ Fluent::Plugin::ElasticsearchOutput.module_eval(<<-CODE)
369
+ def detect_es_major_version
370
+ @_es_info ||= client.info
371
+ @_es_info["version"]["number"].to_i
372
+ end
373
+ CODE
374
+ Fluent::Plugin::ElasticsearchOutput.module_eval(<<-CODE)
375
+ def client_library_version
376
+ #{@client_version}
377
+ end
378
+ CODE
379
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutput).configure(conf)
380
+ end
381
+
382
+ def test_retry_get_es_version
383
+ config = %{
384
+ host logs.google.com
385
+ port 778
386
+ scheme https
387
+ path /es/
388
+ user john
389
+ password doe
390
+ verify_es_version_at_startup true
391
+ max_retry_get_es_version 3
392
+ }
393
+
394
+ connection_resets = 0
395
+ stub_request(:get, "https://logs.google.com:778/es//").
396
+ with(basic_auth: ['john', 'doe']) do |req|
397
+ connection_resets += 1
398
+ raise Faraday::ConnectionFailed, "Test message"
399
+ end
400
+
401
+ assert_raise(Fluent::Plugin::ElasticsearchError::RetryableOperationExhaustedFailure) do
402
+ create_driver(config)
403
+ end
404
+
405
+ assert_equal(4, connection_resets)
406
+ end
407
+ end
408
+
409
+ def test_template_already_present
410
+ config = %{
411
+ host logs.google.com
412
+ port 777
413
+ scheme https
414
+ path /es/
415
+ user john
416
+ password doe
417
+ template_name logstash
418
+ template_file /abc123
419
+ }
420
+
421
+ # connection start
422
+ stub_request(:head, "https://logs.google.com:777/es//").
423
+ with(basic_auth: ['john', 'doe']).
424
+ to_return(:status => 200, :body => "", :headers => {})
425
+ # check if template exists
426
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash").
427
+ with(basic_auth: ['john', 'doe']).
428
+ to_return(:status => 200, :body => "", :headers => {})
429
+
430
+ driver(config)
431
+
432
+ assert_not_requested(:put, "https://logs.google.com:777/es//_template/logstash")
433
+ end
434
+
435
+ def test_template_create
436
+ cwd = File.dirname(__FILE__)
437
+ template_file = File.join(cwd, 'test_template.json')
438
+
439
+ config = %{
440
+ host logs.google.com
441
+ port 777
442
+ scheme https
443
+ path /es/
444
+ user john
445
+ password doe
446
+ template_name logstash
447
+ template_file #{template_file}
448
+ }
449
+
450
+ # connection start
451
+ stub_request(:head, "https://logs.google.com:777/es//").
452
+ with(basic_auth: ['john', 'doe']).
453
+ to_return(:status => 200, :body => "", :headers => {})
454
+ # check if template exists
455
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash").
456
+ with(basic_auth: ['john', 'doe']).
457
+ to_return(:status => 404, :body => "", :headers => {})
458
+ # creation
459
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash").
460
+ with(basic_auth: ['john', 'doe']).
461
+ to_return(:status => 200, :body => "", :headers => {})
462
+
463
+ driver(config)
464
+
465
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash", times: 1)
466
+ end
467
+
468
+ def test_custom_template_create
469
+ cwd = File.dirname(__FILE__)
470
+ template_file = File.join(cwd, 'test_alias_template.json')
471
+
472
+ config = %{
473
+ host logs.google.com
474
+ port 777
475
+ scheme https
476
+ path /es/
477
+ user john
478
+ password doe
479
+ template_name myapp_alias_template
480
+ template_file #{template_file}
481
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
482
+ }
483
+
484
+ # connection start
485
+ stub_request(:head, "https://logs.google.com:777/es//").
486
+ with(basic_auth: ['john', 'doe']).
487
+ to_return(:status => 200, :body => "", :headers => {})
488
+ # check if template exists
489
+ stub_request(:get, "https://logs.google.com:777/es//_template/myapp_alias_template").
490
+ with(basic_auth: ['john', 'doe']).
491
+ to_return(:status => 404, :body => "", :headers => {})
492
+ # creation
493
+ stub_request(:put, "https://logs.google.com:777/es//_template/myapp_alias_template").
494
+ with(basic_auth: ['john', 'doe']).
495
+ to_return(:status => 200, :body => "", :headers => {})
496
+
497
+ driver(config)
498
+
499
+ assert_requested(:put, "https://logs.google.com:777/es//_template/myapp_alias_template", times: 1)
500
+ end
501
+
502
+ def test_custom_template_with_rollover_index_create
503
+ cwd = File.dirname(__FILE__)
504
+ template_file = File.join(cwd, 'test_alias_template.json')
505
+
506
+ config = %{
507
+ host logs.google.com
508
+ port 777
509
+ scheme https
510
+ path /es/
511
+ user john
512
+ password doe
513
+ template_name myapp_alias_template
514
+ template_file #{template_file}
515
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
516
+ rollover_index true
517
+ index_date_pattern now/w{xxxx.ww}
518
+ deflector_alias myapp_deflector
519
+ index_prefix mylogs
520
+ application_name myapp
521
+ }
522
+
523
+ # connection start
524
+ stub_request(:head, "https://logs.google.com:777/es//").
525
+ with(basic_auth: ['john', 'doe']).
526
+ to_return(:status => 200, :body => "", :headers => {})
527
+ # check if template exists
528
+ stub_request(:get, "https://logs.google.com:777/es//_template/myapp_alias_template").
529
+ with(basic_auth: ['john', 'doe']).
530
+ to_return(:status => 404, :body => "", :headers => {})
531
+ # creation
532
+ stub_request(:put, "https://logs.google.com:777/es//_template/myapp_alias_template").
533
+ with(basic_auth: ['john', 'doe']).
534
+ to_return(:status => 200, :body => "", :headers => {})
535
+ # creation of index which can rollover
536
+ stub_request(:put, "https://logs.google.com:777/es//%3Cmylogs-myapp-%7Bnow%2Fw%7Bxxxx.ww%7D%7D-000001%3E").
537
+ with(basic_auth: ['john', 'doe']).
538
+ to_return(:status => 200, :body => "", :headers => {})
539
+ # check if alias exists
540
+ stub_request(:head, "https://logs.google.com:777/es//_alias/myapp_deflector").
541
+ with(basic_auth: ['john', 'doe']).
542
+ to_return(:status => 404, :body => "", :headers => {})
543
+ # put the alias for the index
544
+ stub_request(:put, "https://logs.google.com:777/es//%3Cmylogs-myapp-%7Bnow%2Fw%7Bxxxx.ww%7D%7D-000001%3E/_alias/myapp_deflector").
545
+ with(basic_auth: ['john', 'doe']).
546
+ to_return(:status => 200, :body => "", :headers => {})
547
+
548
+ driver(config)
549
+
550
+ assert_requested(:put, "https://logs.google.com:777/es//_template/myapp_alias_template", times: 1)
551
+ end
552
+
553
+
554
+ def test_template_overwrite
555
+ cwd = File.dirname(__FILE__)
556
+ template_file = File.join(cwd, 'test_template.json')
557
+
558
+ config = %{
559
+ host logs.google.com
560
+ port 777
561
+ scheme https
562
+ path /es/
563
+ user john
564
+ password doe
565
+ template_name logstash
566
+ template_file #{template_file}
567
+ template_overwrite true
568
+ }
569
+
570
+ # connection start
571
+ stub_request(:head, "https://logs.google.com:777/es//").
572
+ with(basic_auth: ['john', 'doe']).
573
+ to_return(:status => 200, :body => "", :headers => {})
574
+ # check if template exists
575
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash").
576
+ with(basic_auth: ['john', 'doe']).
577
+ to_return(:status => 200, :body => "", :headers => {})
578
+ # creation
579
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash").
580
+ with(basic_auth: ['john', 'doe']).
581
+ to_return(:status => 200, :body => "", :headers => {})
582
+
583
+ driver(config)
584
+
585
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash", times: 1)
586
+ end
587
+
588
+ def test_custom_template_overwrite
589
+ cwd = File.dirname(__FILE__)
590
+ template_file = File.join(cwd, 'test_template.json')
591
+
592
+ config = %{
593
+ host logs.google.com
594
+ port 777
595
+ scheme https
596
+ path /es/
597
+ user john
598
+ password doe
599
+ template_name myapp_alias_template
600
+ template_file #{template_file}
601
+ template_overwrite true
602
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
603
+ }
604
+
605
+ # connection start
606
+ stub_request(:head, "https://logs.google.com:777/es//").
607
+ with(basic_auth: ['john', 'doe']).
608
+ to_return(:status => 200, :body => "", :headers => {})
609
+ # check if template exists
610
+ stub_request(:get, "https://logs.google.com:777/es//_template/myapp_alias_template").
611
+ with(basic_auth: ['john', 'doe']).
612
+ to_return(:status => 200, :body => "", :headers => {})
613
+ # creation
614
+ stub_request(:put, "https://logs.google.com:777/es//_template/myapp_alias_template").
615
+ with(basic_auth: ['john', 'doe']).
616
+ to_return(:status => 200, :body => "", :headers => {})
617
+
618
+ driver(config)
619
+
620
+ assert_requested(:put, "https://logs.google.com:777/es//_template/myapp_alias_template", times: 1)
621
+ end
622
+
623
+ def test_custom_template_with_rollover_index_overwrite
624
+ cwd = File.dirname(__FILE__)
625
+ template_file = File.join(cwd, 'test_template.json')
626
+
627
+ config = %{
628
+ host logs.google.com
629
+ port 777
630
+ scheme https
631
+ path /es/
632
+ user john
633
+ password doe
634
+ template_name myapp_alias_template
635
+ template_file #{template_file}
636
+ template_overwrite true
637
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
638
+ deflector_alias myapp_deflector
639
+ rollover_index true
640
+ index_prefix mylogs
641
+ application_name myapp
642
+ }
643
+
644
+ # connection start
645
+ stub_request(:head, "https://logs.google.com:777/es//").
646
+ with(basic_auth: ['john', 'doe']).
647
+ to_return(:status => 200, :body => "", :headers => {})
648
+ # check if template exists
649
+ stub_request(:get, "https://logs.google.com:777/es//_template/myapp_alias_template").
650
+ with(basic_auth: ['john', 'doe']).
651
+ to_return(:status => 200, :body => "", :headers => {})
652
+ # creation
653
+ stub_request(:put, "https://logs.google.com:777/es//_template/myapp_alias_template").
654
+ with(basic_auth: ['john', 'doe']).
655
+ to_return(:status => 200, :body => "", :headers => {})
656
+ # creation of index which can rollover
657
+ stub_request(:put, "https://logs.google.com:777/es//%3Cmylogs-myapp-%7Bnow%2Fd%7D-000001%3E").
658
+ with(basic_auth: ['john', 'doe']).
659
+ to_return(:status => 200, :body => "", :headers => {})
660
+ # check if alias exists
661
+ stub_request(:head, "https://logs.google.com:777/es//_alias/myapp_deflector").
662
+ with(basic_auth: ['john', 'doe']).
663
+ to_return(:status => 404, :body => "", :headers => {})
664
+ # put the alias for the index
665
+ stub_request(:put, "https://logs.google.com:777/es//%3Cmylogs-myapp-%7Bnow%2Fd%7D-000001%3E/_alias/myapp_deflector").
666
+ with(basic_auth: ['john', 'doe']).
667
+ to_return(:status => 200, :body => "", :headers => {})
668
+
669
+ driver(config)
670
+
671
+ assert_requested(:put, "https://logs.google.com:777/es//_template/myapp_alias_template", times: 1)
672
+ end
673
+
674
+ def test_template_create_invalid_filename
675
+ config = %{
676
+ host logs.google.com
677
+ port 777
678
+ scheme https
679
+ path /es/
680
+ user john
681
+ password doe
682
+ template_name logstash
683
+ template_file /abc123
684
+ }
685
+
686
+ # connection start
687
+ stub_request(:head, "https://logs.google.com:777/es//").
688
+ with(basic_auth: ['john', 'doe']).
689
+ to_return(:status => 200, :body => "", :headers => {})
690
+ # check if template exists
691
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash").
692
+ with(basic_auth: ['john', 'doe']).
693
+ to_return(:status => 404, :body => "", :headers => {})
694
+
695
+ assert_raise(RuntimeError) {
696
+ driver(config)
697
+ }
698
+ end
699
+
700
+ def test_template_installation_exclusive_for_host_placeholder
701
+ cwd = File.dirname(__FILE__)
702
+ template_file = File.join(cwd, 'test_template.json')
703
+
704
+ config = %{
705
+ host logs-${tag}.google.com
706
+ port 777
707
+ scheme https
708
+ path /es/
709
+ user john
710
+ password doe
711
+ template_name logstash
712
+ template_file #{template_file}
713
+ }
714
+
715
+ assert_raise(Fluent::ConfigError) do
716
+ driver(config)
717
+ end
718
+ end
719
+
720
+ def test_template_retry_install_fails
721
+ cwd = File.dirname(__FILE__)
722
+ template_file = File.join(cwd, 'test_template.json')
723
+
724
+ config = %{
725
+ host logs.google.com
726
+ port 778
727
+ scheme https
728
+ path /es/
729
+ user john
730
+ password doe
731
+ template_name logstash
732
+ template_file #{template_file}
733
+ max_retry_putting_template 3
734
+ }
735
+
736
+ connection_resets = 0
737
+ # check if template exists
738
+ stub_request(:get, "https://logs.google.com:778/es//_template/logstash")
739
+ .with(basic_auth: ['john', 'doe']) do |req|
740
+ connection_resets += 1
741
+ raise Faraday::ConnectionFailed, "Test message"
742
+ end
743
+
744
+ assert_raise(Fluent::Plugin::ElasticsearchError::RetryableOperationExhaustedFailure) do
745
+ driver(config)
746
+ end
747
+
748
+ assert_equal(4, connection_resets)
749
+ end
750
+
751
+ def test_template_retry_install_does_not_fail
752
+ cwd = File.dirname(__FILE__)
753
+ template_file = File.join(cwd, 'test_template.json')
754
+
755
+ config = %{
756
+ host logs.google.com
757
+ port 778
758
+ scheme https
759
+ path /es/
760
+ user john
761
+ password doe
762
+ template_name logstash
763
+ template_file #{template_file}
764
+ max_retry_putting_template 3
765
+ fail_on_putting_template_retry_exceed false
766
+ }
767
+
768
+ connection_resets = 0
769
+ # check if template exists
770
+ stub_request(:get, "https://logs.google.com:778/es//_template/logstash")
771
+ .with(basic_auth: ['john', 'doe']) do |req|
772
+ connection_resets += 1
773
+ raise Faraday::ConnectionFailed, "Test message"
774
+ end
775
+
776
+ driver(config)
777
+
778
+ assert_equal(4, connection_resets)
779
+ end
780
+
781
+ def test_templates_create
782
+ cwd = File.dirname(__FILE__)
783
+ template_file = File.join(cwd, 'test_template.json')
784
+ config = %{
785
+ host logs.google.com
786
+ port 777
787
+ scheme https
788
+ path /es/
789
+ user john
790
+ password doe
791
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}","logstash3":"#{template_file}" }
792
+ }
793
+
794
+ stub_request(:head, "https://logs.google.com:777/es//").
795
+ with(basic_auth: ['john', 'doe']).
796
+ to_return(:status => 200, :body => "", :headers => {})
797
+ # check if template exists
798
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash1").
799
+ with(basic_auth: ['john', 'doe']).
800
+ to_return(:status => 404, :body => "", :headers => {})
801
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash2").
802
+ with(basic_auth: ['john', 'doe']).
803
+ to_return(:status => 404, :body => "", :headers => {})
804
+
805
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash3").
806
+ with(basic_auth: ['john', 'doe']).
807
+ to_return(:status => 200, :body => "", :headers => {}) #exists
808
+
809
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash1").
810
+ with(basic_auth: ['john', 'doe']).
811
+ to_return(:status => 200, :body => "", :headers => {})
812
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash2").
813
+ with(basic_auth: ['john', 'doe']).
814
+ to_return(:status => 200, :body => "", :headers => {})
815
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash3").
816
+ with(basic_auth: ['john', 'doe']).
817
+ to_return(:status => 200, :body => "", :headers => {})
818
+
819
+ driver(config)
820
+
821
+ assert_requested( :put, "https://logs.google.com:777/es//_template/logstash1", times: 1)
822
+ assert_requested( :put, "https://logs.google.com:777/es//_template/logstash2", times: 1)
823
+ assert_not_requested(:put, "https://logs.google.com:777/es//_template/logstash3") #exists
824
+ end
825
+
826
+ def test_templates_overwrite
827
+ cwd = File.dirname(__FILE__)
828
+ template_file = File.join(cwd, 'test_template.json')
829
+ config = %{
830
+ host logs.google.com
831
+ port 777
832
+ scheme https
833
+ path /es/
834
+ user john
835
+ password doe
836
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}","logstash3":"#{template_file}" }
837
+ template_overwrite true
838
+ }
839
+
840
+ stub_request(:head, "https://logs.google.com:777/es//").
841
+ with(basic_auth: ['john', 'doe']).
842
+ to_return(:status => 200, :body => "", :headers => {})
843
+ # check if template exists
844
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash1").
845
+ with(basic_auth: ['john', 'doe']).
846
+ to_return(:status => 200, :body => "", :headers => {})
847
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash2").
848
+ with(basic_auth: ['john', 'doe']).
849
+ to_return(:status => 200, :body => "", :headers => {})
850
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash3").
851
+ with(basic_auth: ['john', 'doe']).
852
+ to_return(:status => 200, :body => "", :headers => {}) #exists
853
+
854
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash1").
855
+ with(basic_auth: ['john', 'doe']).
856
+ to_return(:status => 200, :body => "", :headers => {})
857
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash2").
858
+ with(basic_auth: ['john', 'doe']).
859
+ to_return(:status => 200, :body => "", :headers => {})
860
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash3").
861
+ with(basic_auth: ['john', 'doe']).
862
+ to_return(:status => 200, :body => "", :headers => {})
863
+
864
+ driver(config)
865
+
866
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash1", times: 1)
867
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash2", times: 1)
868
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash3", times: 1)
869
+ end
870
+
871
+ def test_templates_not_used
872
+ cwd = File.dirname(__FILE__)
873
+ template_file = File.join(cwd, 'test_template.json')
874
+
875
+ config = %{
876
+ host logs.google.com
877
+ port 777
878
+ scheme https
879
+ path /es/
880
+ user john
881
+ password doe
882
+ template_name logstash
883
+ template_file #{template_file}
884
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}" }
885
+ }
886
+ # connection start
887
+ stub_request(:head, "https://logs.google.com:777/es//").
888
+ with(basic_auth: ['john', 'doe']).
889
+ to_return(:status => 200, :body => "", :headers => {})
890
+ # check if template exists
891
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash").
892
+ with(basic_auth: ['john', 'doe']).
893
+ to_return(:status => 404, :body => "", :headers => {})
894
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash1").
895
+ with(basic_auth: ['john', 'doe']).
896
+ to_return(:status => 404, :body => "", :headers => {})
897
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash2").
898
+ with(basic_auth: ['john', 'doe']).
899
+ to_return(:status => 404, :body => "", :headers => {})
900
+ #creation
901
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash").
902
+ with(basic_auth: ['john', 'doe']).
903
+ to_return(:status => 200, :body => "", :headers => {})
904
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash1").
905
+ with(basic_auth: ['john', 'doe']).
906
+ to_return(:status => 200, :body => "", :headers => {})
907
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash2").
908
+ with(basic_auth: ['john', 'doe']).
909
+ to_return(:status => 200, :body => "", :headers => {})
910
+
911
+ driver(config)
912
+
913
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash", times: 1)
914
+
915
+ assert_not_requested(:put, "https://logs.google.com:777/es//_template/logstash1")
916
+ assert_not_requested(:put, "https://logs.google.com:777/es//_template/logstash2")
917
+ end
918
+
919
+ def test_templates_can_be_partially_created_if_error_occurs
920
+ cwd = File.dirname(__FILE__)
921
+ template_file = File.join(cwd, 'test_template.json')
922
+ config = %{
923
+ host logs.google.com
924
+ port 777
925
+ scheme https
926
+ path /es/
927
+ user john
928
+ password doe
929
+ templates {"logstash1":"#{template_file}", "logstash2":"/abc" }
930
+ }
931
+ stub_request(:head, "https://logs.google.com:777/es//").
932
+ with(basic_auth: ['john', 'doe']).
933
+ to_return(:status => 200, :body => "", :headers => {})
934
+ # check if template exists
935
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash1").
936
+ with(basic_auth: ['john', 'doe']).
937
+ to_return(:status => 404, :body => "", :headers => {})
938
+ stub_request(:get, "https://logs.google.com:777/es//_template/logstash2").
939
+ with(basic_auth: ['john', 'doe']).
940
+ to_return(:status => 404, :body => "", :headers => {})
941
+
942
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash1").
943
+ with(basic_auth: ['john', 'doe']).
944
+ to_return(:status => 200, :body => "", :headers => {})
945
+ stub_request(:put, "https://logs.google.com:777/es//_template/logstash2").
946
+ with(basic_auth: ['john', 'doe']).
947
+ to_return(:status => 200, :body => "", :headers => {})
948
+
949
+ assert_raise(RuntimeError) {
950
+ driver(config)
951
+ }
952
+
953
+ assert_requested(:put, "https://logs.google.com:777/es//_template/logstash1", times: 1)
954
+ assert_not_requested(:put, "https://logs.google.com:777/es//_template/logstash2")
955
+ end
956
+
957
+ def test_legacy_hosts_list
958
+ config = %{
959
+ hosts host1:50,host2:100,host3
960
+ scheme https
961
+ path /es/
962
+ port 123
963
+ }
964
+ instance = driver(config).instance
965
+
966
+ assert_equal 3, instance.get_connection_options[:hosts].length
967
+ host1, host2, host3 = instance.get_connection_options[:hosts]
968
+
969
+ assert_equal 'host1', host1[:host]
970
+ assert_equal 50, host1[:port]
971
+ assert_equal 'https', host1[:scheme]
972
+ assert_equal '/es/', host2[:path]
973
+ assert_equal 'host3', host3[:host]
974
+ assert_equal 123, host3[:port]
975
+ assert_equal 'https', host3[:scheme]
976
+ assert_equal '/es/', host3[:path]
977
+ end
978
+
979
+ def test_hosts_list
980
+ config = %{
981
+ hosts https://john:password@host1:443/elastic/,http://host2
982
+ path /default_path
983
+ user default_user
984
+ password default_password
985
+ }
986
+ instance = driver(config).instance
987
+
988
+ assert_equal 2, instance.get_connection_options[:hosts].length
989
+ host1, host2 = instance.get_connection_options[:hosts]
990
+
991
+ assert_equal 'host1', host1[:host]
992
+ assert_equal 443, host1[:port]
993
+ assert_equal 'https', host1[:scheme]
994
+ assert_equal 'john', host1[:user]
995
+ assert_equal 'password', host1[:password]
996
+ assert_equal '/elastic/', host1[:path]
997
+
998
+ assert_equal 'host2', host2[:host]
999
+ assert_equal 'http', host2[:scheme]
1000
+ assert_equal 'default_user', host2[:user]
1001
+ assert_equal 'default_password', host2[:password]
1002
+ assert_equal '/default_path', host2[:path]
1003
+ end
1004
+
1005
+ def test_hosts_list_with_escape_placeholders
1006
+ config = %{
1007
+ hosts https://%{j+hn}:%{passw@rd}@host1:443/elastic/,http://host2
1008
+ path /default_path
1009
+ user default_user
1010
+ password default_password
1011
+ }
1012
+ instance = driver(config).instance
1013
+
1014
+ assert_equal 2, instance.get_connection_options[:hosts].length
1015
+ host1, host2 = instance.get_connection_options[:hosts]
1016
+
1017
+ assert_equal 'host1', host1[:host]
1018
+ assert_equal 443, host1[:port]
1019
+ assert_equal 'https', host1[:scheme]
1020
+ assert_equal 'j%2Bhn', host1[:user]
1021
+ assert_equal 'passw%40rd', host1[:password]
1022
+ assert_equal '/elastic/', host1[:path]
1023
+
1024
+ assert_equal 'host2', host2[:host]
1025
+ assert_equal 'http', host2[:scheme]
1026
+ assert_equal 'default_user', host2[:user]
1027
+ assert_equal 'default_password', host2[:password]
1028
+ assert_equal '/default_path', host2[:path]
1029
+ end
1030
+
1031
+ def test_single_host_params_and_defaults
1032
+ config = %{
1033
+ host logs.google.com
1034
+ user john
1035
+ password doe
1036
+ }
1037
+ instance = driver(config).instance
1038
+
1039
+ assert_equal 1, instance.get_connection_options[:hosts].length
1040
+ host1 = instance.get_connection_options[:hosts][0]
1041
+
1042
+ assert_equal 'logs.google.com', host1[:host]
1043
+ assert_equal 9200, host1[:port]
1044
+ assert_equal 'http', host1[:scheme]
1045
+ assert_equal 'john', host1[:user]
1046
+ assert_equal 'doe', host1[:password]
1047
+ assert_equal nil, host1[:path]
1048
+ end
1049
+
1050
+ def test_single_host_params_and_defaults_with_escape_placeholders
1051
+ config = %{
1052
+ host logs.google.com
1053
+ user %{j+hn}
1054
+ password %{d@e}
1055
+ }
1056
+ instance = driver(config).instance
1057
+
1058
+ assert_equal 1, instance.get_connection_options[:hosts].length
1059
+ host1 = instance.get_connection_options[:hosts][0]
1060
+
1061
+ assert_equal 'logs.google.com', host1[:host]
1062
+ assert_equal 9200, host1[:port]
1063
+ assert_equal 'http', host1[:scheme]
1064
+ assert_equal 'j%2Bhn', host1[:user]
1065
+ assert_equal 'd%40e', host1[:password]
1066
+ assert_equal nil, host1[:path]
1067
+ end
1068
+
1069
+ def test_host_and_port_are_ignored_if_specify_hosts
1070
+ config = %{
1071
+ host logs.google.com
1072
+ port 9200
1073
+ hosts host1:50,host2:100
1074
+ }
1075
+ instance = driver(config).instance
1076
+
1077
+ params = instance.get_connection_options[:hosts]
1078
+ hosts = params.map { |p| p[:host] }
1079
+ ports = params.map { |p| p[:port] }
1080
+ assert(hosts.none? { |h| h == 'logs.google.com' })
1081
+ assert(ports.none? { |p| p == 9200 })
1082
+ end
1083
+
1084
+ def test_password_is_required_if_specify_user
1085
+ config = %{
1086
+ user john
1087
+ }
1088
+
1089
+ assert_raise(Fluent::ConfigError) do
1090
+ driver(config)
1091
+ end
1092
+ end
1093
+
1094
+ def test_content_type_header
1095
+ stub_request(:head, "http://localhost:9200/").
1096
+ to_return(:status => 200, :body => "", :headers => {})
1097
+ if Elasticsearch::VERSION >= "6.0.2"
1098
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1099
+ with(headers: { "Content-Type" => "application/x-ndjson" })
1100
+ else
1101
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1102
+ with(headers: { "Content-Type" => "application/json" })
1103
+ end
1104
+ driver.run(default_tag: 'test') do
1105
+ driver.feed(sample_record)
1106
+ end
1107
+ assert_requested(elastic_request)
1108
+ end
1109
+
1110
+ def test_custom_headers
1111
+ stub_request(:head, "http://localhost:9200/").
1112
+ to_return(:status => 200, :body => "", :headers => {})
1113
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1114
+ with(headers: {'custom' => 'header1','and_others' => 'header2' })
1115
+ driver.configure(%[custom_headers {"custom":"header1", "and_others":"header2"}])
1116
+ driver.run(default_tag: 'test') do
1117
+ driver.feed(sample_record)
1118
+ end
1119
+ assert_requested(elastic_request)
1120
+ end
1121
+
1122
+ def test_write_message_with_bad_chunk
1123
+ driver.configure("target_index_key bad_value\n@log_level debug\n")
1124
+ stub_elastic
1125
+ driver.run(default_tag: 'test') do
1126
+ driver.feed({'bad_value'=>"\255"})
1127
+ end
1128
+ error_log = driver.error_events.map {|e| e.last.message }
1129
+
1130
+ assert_logs_include(error_log, /(input string invalid)|(invalid byte sequence in UTF-8)/)
1131
+ end
1132
+
1133
+ def test_writes_to_default_index
1134
+ stub_elastic
1135
+ driver.run(default_tag: 'test') do
1136
+ driver.feed(sample_record)
1137
+ end
1138
+ assert_equal('fluentd', index_cmds.first['index']['_index'])
1139
+ end
1140
+
1141
+ def test_writes_to_default_type
1142
+ stub_elastic
1143
+ driver.run(default_tag: 'test') do
1144
+ driver.feed(sample_record)
1145
+ end
1146
+ assert_equal(default_type_name, index_cmds.first['index']['_type'])
1147
+ end
1148
+
1149
+ def test_writes_to_speficied_index
1150
+ driver.configure("index_name myindex\n")
1151
+ stub_elastic
1152
+ driver.run(default_tag: 'test') do
1153
+ driver.feed(sample_record)
1154
+ end
1155
+ assert_equal('myindex', index_cmds.first['index']['_index'])
1156
+ end
1157
+
1158
+ def test_writes_with_huge_records
1159
+ driver.configure(Fluent::Config::Element.new(
1160
+ 'ROOT', '', {
1161
+ '@type' => 'elasticsearch',
1162
+ }, [
1163
+ Fluent::Config::Element.new('buffer', 'tag', {
1164
+ 'chunk_keys' => ['tag', 'time'],
1165
+ 'chunk_limit_size' => '64MB',
1166
+ }, [])
1167
+ ]
1168
+ ))
1169
+ request = stub_elastic
1170
+ driver.run(default_tag: 'test') do
1171
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1172
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1173
+ end
1174
+ assert_requested(request, times: 2)
1175
+ end
1176
+
1177
+ def test_writes_with_huge_records_but_uncheck
1178
+ driver.configure(Fluent::Config::Element.new(
1179
+ 'ROOT', '', {
1180
+ '@type' => 'elasticsearch',
1181
+ 'bulk_message_request_threshold' => -1,
1182
+ }, [
1183
+ Fluent::Config::Element.new('buffer', 'tag', {
1184
+ 'chunk_keys' => ['tag', 'time'],
1185
+ 'chunk_limit_size' => '64MB',
1186
+ }, [])
1187
+ ]
1188
+ ))
1189
+ request = stub_elastic
1190
+ driver.run(default_tag: 'test') do
1191
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1192
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1193
+ end
1194
+ assert_false(driver.instance.split_request?({}, nil))
1195
+ assert_requested(request, times: 1)
1196
+ end
1197
+
1198
+ class IndexNamePlaceholdersTest < self
1199
+ def test_writes_to_speficied_index_with_tag_placeholder
1200
+ driver.configure("index_name myindex.${tag}\n")
1201
+ stub_elastic
1202
+ driver.run(default_tag: 'test') do
1203
+ driver.feed(sample_record)
1204
+ end
1205
+ assert_equal('myindex.test', index_cmds.first['index']['_index'])
1206
+ end
1207
+
1208
+ def test_writes_to_speficied_index_with_time_placeholder
1209
+ driver.configure(Fluent::Config::Element.new(
1210
+ 'ROOT', '', {
1211
+ '@type' => 'elasticsearch',
1212
+ 'index_name' => 'myindex.%Y.%m.%d',
1213
+ }, [
1214
+ Fluent::Config::Element.new('buffer', 'tag,time', {
1215
+ 'chunk_keys' => ['tag', 'time'],
1216
+ 'timekey' => 3600,
1217
+ }, [])
1218
+ ]
1219
+ ))
1220
+ stub_elastic
1221
+ time = Time.parse Date.today.iso8601
1222
+ driver.run(default_tag: 'test') do
1223
+ driver.feed(time.to_i, sample_record)
1224
+ end
1225
+ assert_equal("myindex.#{time.utc.strftime("%Y.%m.%d")}", index_cmds.first['index']['_index'])
1226
+ end
1227
+
1228
+ def test_writes_to_speficied_index_with_custom_key_placeholder
1229
+ driver.configure(Fluent::Config::Element.new(
1230
+ 'ROOT', '', {
1231
+ '@type' => 'elasticsearch',
1232
+ 'index_name' => 'myindex.${pipeline_id}',
1233
+ }, [
1234
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
1235
+ ]
1236
+ ))
1237
+ time = Time.parse Date.today.iso8601
1238
+ pipeline_id = "mypipeline"
1239
+ logstash_index = "myindex.#{pipeline_id}"
1240
+ stub_elastic
1241
+ driver.run(default_tag: 'test') do
1242
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
1243
+ end
1244
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1245
+ end
1246
+ end
1247
+
1248
+ def test_writes_to_speficied_index_uppercase
1249
+ driver.configure("index_name MyIndex\n")
1250
+ stub_elastic
1251
+ driver.run(default_tag: 'test') do
1252
+ driver.feed(sample_record)
1253
+ end
1254
+ # Allthough index_name has upper-case characters,
1255
+ # it should be set as lower-case when sent to elasticsearch.
1256
+ assert_equal('myindex', index_cmds.first['index']['_index'])
1257
+ end
1258
+
1259
+ def test_writes_to_target_index_key
1260
+ driver.configure("target_index_key @target_index\n")
1261
+ stub_elastic
1262
+ record = sample_record.clone
1263
+ driver.run(default_tag: 'test') do
1264
+ driver.feed(sample_record.merge('@target_index' => 'local-override'))
1265
+ end
1266
+ assert_equal('local-override', index_cmds.first['index']['_index'])
1267
+ assert_nil(index_cmds[1]['@target_index'])
1268
+ end
1269
+
1270
+ def test_writes_to_target_index_key_logstash
1271
+ driver.configure("target_index_key @target_index
1272
+ logstash_format true")
1273
+ time = Time.parse Date.today.iso8601
1274
+ stub_elastic
1275
+ driver.run(default_tag: 'test') do
1276
+ driver.feed(time.to_i, sample_record.merge('@target_index' => 'local-override'))
1277
+ end
1278
+ assert_equal('local-override', index_cmds.first['index']['_index'])
1279
+ end
1280
+
1281
+ def test_writes_to_target_index_key_logstash_uppercase
1282
+ driver.configure("target_index_key @target_index
1283
+ logstash_format true")
1284
+ time = Time.parse Date.today.iso8601
1285
+ stub_elastic
1286
+ driver.run(default_tag: 'test') do
1287
+ driver.feed(time.to_i, sample_record.merge('@target_index' => 'LOCAL-OVERRIDE'))
1288
+ end
1289
+ # Allthough @target_index has upper-case characters,
1290
+ # it should be set as lower-case when sent to elasticsearch.
1291
+ assert_equal('local-override', index_cmds.first['index']['_index'])
1292
+ end
1293
+
1294
+ def test_writes_to_default_index_with_pipeline
1295
+ pipeline = "fluentd"
1296
+ driver.configure("pipeline #{pipeline}")
1297
+ stub_elastic
1298
+ driver.run(default_tag: 'test') do
1299
+ driver.feed(sample_record)
1300
+ end
1301
+ assert_equal(pipeline, index_cmds.first['index']['pipeline'])
1302
+ end
1303
+
1304
+ def test_writes_to_target_index_key_fallack
1305
+ driver.configure("target_index_key @target_index\n")
1306
+ stub_elastic
1307
+ driver.run(default_tag: 'test') do
1308
+ driver.feed(sample_record)
1309
+ end
1310
+ assert_equal('fluentd', index_cmds.first['index']['_index'])
1311
+ end
1312
+
1313
+ def test_writes_to_target_index_key_fallack_logstash
1314
+ driver.configure("target_index_key @target_index\n
1315
+ logstash_format true")
1316
+ time = Time.parse Date.today.iso8601
1317
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m.%d")}"
1318
+ stub_elastic
1319
+ driver.run(default_tag: 'test') do
1320
+ driver.feed(time.to_i, sample_record)
1321
+ end
1322
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1323
+ end
1324
+
1325
+ data("border" => {"es_version" => 6, "_type" => "mytype"},
1326
+ "fixed_behavior"=> {"es_version" => 7, "_type" => "_doc"},
1327
+ )
1328
+ def test_writes_to_speficied_type(data)
1329
+ driver('', data["es_version"]).configure("type_name mytype\n")
1330
+ stub_elastic
1331
+ driver.run(default_tag: 'test') do
1332
+ driver.feed(sample_record)
1333
+ end
1334
+ assert_equal(data['_type'], index_cmds.first['index']['_type'])
1335
+ end
1336
+
1337
+ data("border" => {"es_version" => 6, "_type" => "mytype.test"},
1338
+ "fixed_behavior"=> {"es_version" => 7, "_type" => "_doc"},
1339
+ )
1340
+ def test_writes_to_speficied_type_with_placeholders(data)
1341
+ driver('', data["es_version"]).configure("type_name mytype.${tag}\n")
1342
+ stub_elastic
1343
+ driver.run(default_tag: 'test') do
1344
+ driver.feed(sample_record)
1345
+ end
1346
+ assert_equal(data['_type'], index_cmds.first['index']['_type'])
1347
+ end
1348
+
1349
+ data("old" => {"es_version" => 2, "_type" => "local-override"},
1350
+ "old_behavior" => {"es_version" => 5, "_type" => "local-override"},
1351
+ "border" => {"es_version" => 6, "_type" => "fluentd"},
1352
+ "fixed_behavior"=> {"es_version" => 7, "_type" => "_doc"},
1353
+ )
1354
+ def test_writes_to_target_type_key(data)
1355
+ driver('', data["es_version"]).configure("target_type_key @target_type\n")
1356
+ stub_elastic
1357
+ record = sample_record.clone
1358
+ driver.run(default_tag: 'test') do
1359
+ driver.feed(sample_record.merge('@target_type' => 'local-override'))
1360
+ end
1361
+ assert_equal(data["_type"], index_cmds.first['index']['_type'])
1362
+ assert_nil(index_cmds[1]['@target_type'])
1363
+ end
1364
+
1365
+ def test_writes_to_target_type_key_fallack_to_default
1366
+ driver.configure("target_type_key @target_type\n")
1367
+ stub_elastic
1368
+ driver.run(default_tag: 'test') do
1369
+ driver.feed(sample_record)
1370
+ end
1371
+ assert_equal(default_type_name, index_cmds.first['index']['_type'])
1372
+ end
1373
+
1374
+ def test_writes_to_target_type_key_fallack_to_type_name
1375
+ driver.configure("target_type_key @target_type
1376
+ type_name mytype")
1377
+ stub_elastic
1378
+ driver.run(default_tag: 'test') do
1379
+ driver.feed(sample_record)
1380
+ end
1381
+ assert_equal('mytype', index_cmds.first['index']['_type'])
1382
+ end
1383
+
1384
+ data("old" => {"es_version" => 2, "_type" => "local-override"},
1385
+ "old_behavior" => {"es_version" => 5, "_type" => "local-override"},
1386
+ "border" => {"es_version" => 6, "_type" => "fluentd"},
1387
+ "fixed_behavior"=> {"es_version" => 7, "_type" => "_doc"},
1388
+ )
1389
+ def test_writes_to_target_type_key_nested(data)
1390
+ driver('', data["es_version"]).configure("target_type_key kubernetes.labels.log_type\n")
1391
+ stub_elastic
1392
+ driver.run(default_tag: 'test') do
1393
+ driver.feed(sample_record.merge('kubernetes' => {
1394
+ 'labels' => {
1395
+ 'log_type' => 'local-override'
1396
+ }
1397
+ }))
1398
+ end
1399
+ assert_equal(data["_type"], index_cmds.first['index']['_type'])
1400
+ assert_nil(index_cmds[1]['kubernetes']['labels']['log_type'])
1401
+ end
1402
+
1403
+ def test_writes_to_target_type_key_fallack_to_default_nested
1404
+ driver.configure("target_type_key kubernetes.labels.log_type\n")
1405
+ stub_elastic
1406
+ driver.run(default_tag: 'test') do
1407
+ driver.feed(sample_record.merge('kubernetes' => {
1408
+ 'labels' => {
1409
+ 'other_labels' => 'test'
1410
+ }
1411
+ }))
1412
+ end
1413
+ assert_equal(default_type_name, index_cmds.first['index']['_type'])
1414
+ end
1415
+
1416
+ def test_writes_to_speficied_host
1417
+ driver.configure("host 192.168.33.50\n")
1418
+ elastic_request = stub_elastic("http://192.168.33.50:9200/_bulk")
1419
+ driver.run(default_tag: 'test') do
1420
+ driver.feed(sample_record)
1421
+ end
1422
+ assert_requested(elastic_request)
1423
+ end
1424
+
1425
+ def test_writes_to_speficied_port
1426
+ driver.configure("port 9201\n")
1427
+ elastic_request = stub_elastic("http://localhost:9201/_bulk")
1428
+ driver.run(default_tag: 'test') do
1429
+ driver.feed(sample_record)
1430
+ end
1431
+ assert_requested(elastic_request)
1432
+ end
1433
+
1434
+ def test_writes_to_multi_hosts
1435
+ hosts = [['192.168.33.50', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
1436
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
1437
+
1438
+ driver.configure("hosts #{hosts_string}")
1439
+
1440
+ hosts.each do |host_info|
1441
+ host, port = host_info
1442
+ stub_elastic_with_store_index_command_counts("http://#{host}:#{port}/_bulk")
1443
+ end
1444
+
1445
+ driver.run(default_tag: 'test') do
1446
+ 1000.times do
1447
+ driver.feed(sample_record.merge('age'=>rand(100)))
1448
+ end
1449
+ end
1450
+
1451
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
1452
+ # it's Fluentd test driver's constraint
1453
+ # so @index_command_counts.size is always 1
1454
+
1455
+ assert(@index_command_counts.size > 0, "not working with hosts options")
1456
+
1457
+ total = 0
1458
+ @index_command_counts.each do |url, count|
1459
+ total += count
1460
+ end
1461
+ assert_equal(2000, total)
1462
+ end
1463
+
1464
+ def test_nested_record_with_flattening_on
1465
+ driver.configure("flatten_hashes true
1466
+ flatten_hashes_separator |")
1467
+
1468
+ original_hash = {"foo" => {"bar" => "baz"}, "people" => [
1469
+ {"age" => "25", "height" => "1ft"},
1470
+ {"age" => "30", "height" => "2ft"}
1471
+ ]}
1472
+
1473
+ expected_output = {"foo|bar"=>"baz", "people" => [
1474
+ {"age" => "25", "height" => "1ft"},
1475
+ {"age" => "30", "height" => "2ft"}
1476
+ ]}
1477
+
1478
+ stub_elastic
1479
+ driver.run(default_tag: 'test') do
1480
+ driver.feed(original_hash)
1481
+ end
1482
+ assert_equal expected_output, index_cmds[1]
1483
+ end
1484
+
1485
+ def test_nested_record_with_flattening_off
1486
+ # flattening off by default
1487
+
1488
+ original_hash = {"foo" => {"bar" => "baz"}}
1489
+ expected_output = {"foo" => {"bar" => "baz"}}
1490
+
1491
+ stub_elastic
1492
+ driver.run(default_tag: 'test') do
1493
+ driver.feed(original_hash)
1494
+ end
1495
+ assert_equal expected_output, index_cmds[1]
1496
+ end
1497
+
1498
+ def test_makes_bulk_request
1499
+ stub_elastic
1500
+ driver.run(default_tag: 'test') do
1501
+ driver.feed(sample_record)
1502
+ driver.feed(sample_record.merge('age' => 27))
1503
+ end
1504
+ assert_equal(4, index_cmds.count)
1505
+ end
1506
+
1507
+ def test_all_records_are_preserved_in_bulk
1508
+ stub_elastic
1509
+ driver.run(default_tag: 'test') do
1510
+ driver.feed(sample_record)
1511
+ driver.feed(sample_record.merge('age' => 27))
1512
+ end
1513
+ assert_equal(26, index_cmds[1]['age'])
1514
+ assert_equal(27, index_cmds[3]['age'])
1515
+ end
1516
+
1517
+ def test_writes_to_logstash_index
1518
+ driver.configure("logstash_format true\n")
1519
+ #
1520
+ # This is 1 second past midnight in BST, so the UTC index should be the day before
1521
+ dt = DateTime.new(2015, 6, 1, 0, 0, 1, "+01:00")
1522
+ logstash_index = "logstash-2015.05.31"
1523
+ stub_elastic
1524
+ driver.run(default_tag: 'test') do
1525
+ driver.feed(dt.to_time.to_i, sample_record)
1526
+ end
1527
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1528
+ end
1529
+
1530
+ def test_writes_to_logstash_non_utc_index
1531
+ driver.configure("logstash_format true
1532
+ utc_index false")
1533
+ # When using `utc_index false` the index time will be the local day of
1534
+ # ingestion time
1535
+ time = Date.today.to_time
1536
+ index = "logstash-#{time.strftime("%Y.%m.%d")}"
1537
+ stub_elastic
1538
+ driver.run(default_tag: 'test') do
1539
+ driver.feed(time.to_i, sample_record)
1540
+ end
1541
+ assert_equal(index, index_cmds.first['index']['_index'])
1542
+ end
1543
+
1544
+ def test_writes_to_logstash_index_with_specified_prefix
1545
+ driver.configure("logstash_format true
1546
+ logstash_prefix myprefix")
1547
+ time = Time.parse Date.today.iso8601
1548
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
1549
+ stub_elastic
1550
+ driver.run(default_tag: 'test') do
1551
+ driver.feed(time.to_i, sample_record)
1552
+ end
1553
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1554
+ end
1555
+
1556
+ def test_writes_to_logstash_index_with_specified_prefix_and_separator
1557
+ separator = '_'
1558
+ driver.configure("logstash_format true
1559
+ logstash_prefix_separator #{separator}
1560
+ logstash_prefix myprefix")
1561
+ time = Time.parse Date.today.iso8601
1562
+ logstash_index = "myprefix#{separator}#{time.getutc.strftime("%Y.%m.%d")}"
1563
+ stub_elastic
1564
+ driver.run(default_tag: 'test') do
1565
+ driver.feed(time.to_i, sample_record)
1566
+ end
1567
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1568
+ end
1569
+
1570
+ class LogStashPrefixPlaceholdersTest < self
1571
+ def test_writes_to_logstash_index_with_specified_prefix_and_tag_placeholder
1572
+ driver.configure("logstash_format true
1573
+ logstash_prefix myprefix-${tag}")
1574
+ time = Time.parse Date.today.iso8601
1575
+ logstash_index = "myprefix-test-#{time.getutc.strftime("%Y.%m.%d")}"
1576
+ stub_elastic
1577
+ driver.run(default_tag: 'test') do
1578
+ driver.feed(time.to_i, sample_record)
1579
+ end
1580
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1581
+ end
1582
+
1583
+ def test_writes_to_logstash_index_with_specified_prefix_and_time_placeholder
1584
+ driver.configure(Fluent::Config::Element.new(
1585
+ 'ROOT', '', {
1586
+ '@type' => 'elasticsearch',
1587
+ 'logstash_format' => true,
1588
+ 'logstash_prefix' => 'myprefix-%H',
1589
+ }, [
1590
+ Fluent::Config::Element.new('buffer', 'tag,time', {
1591
+ 'chunk_keys' => ['tag', 'time'],
1592
+ 'timekey' => 3600,
1593
+ }, [])
1594
+ ]
1595
+ ))
1596
+ time = Time.parse Date.today.iso8601
1597
+ logstash_index = "myprefix-#{time.getutc.strftime("%H")}-#{time.getutc.strftime("%Y.%m.%d")}"
1598
+ stub_elastic
1599
+ driver.run(default_tag: 'test') do
1600
+ driver.feed(time.to_i, sample_record)
1601
+ end
1602
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1603
+ end
1604
+
1605
+ def test_writes_to_logstash_index_with_specified_prefix_and_custom_key_placeholder
1606
+ driver.configure(Fluent::Config::Element.new(
1607
+ 'ROOT', '', {
1608
+ '@type' => 'elasticsearch',
1609
+ 'logstash_format' => true,
1610
+ 'logstash_prefix' => 'myprefix-${pipeline_id}',
1611
+ }, [
1612
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
1613
+ ]
1614
+ ))
1615
+ time = Time.parse Date.today.iso8601
1616
+ pipeline_id = "mypipeline"
1617
+ logstash_index = "myprefix-#{pipeline_id}-#{time.getutc.strftime("%Y.%m.%d")}"
1618
+ stub_elastic
1619
+ driver.run(default_tag: 'test') do
1620
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
1621
+ end
1622
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1623
+ end
1624
+ end
1625
+
1626
+ class HostnamePlaceholders < self
1627
+ def test_writes_to_extracted_host
1628
+ driver.configure("host ${tag}\n")
1629
+ time = Time.parse Date.today.iso8601
1630
+ elastic_request = stub_elastic("http://extracted-host:9200/_bulk")
1631
+ driver.run(default_tag: 'extracted-host') do
1632
+ driver.feed(time.to_i, sample_record)
1633
+ end
1634
+ assert_requested(elastic_request)
1635
+ end
1636
+
1637
+ def test_writes_to_multi_hosts_with_placeholders
1638
+ hosts = [['${tag}', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
1639
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
1640
+
1641
+ driver.configure("hosts #{hosts_string}")
1642
+
1643
+ hosts.each do |host_info|
1644
+ host, port = host_info
1645
+ host = "extracted-host" if host == '${tag}'
1646
+ stub_elastic_with_store_index_command_counts("http://#{host}:#{port}/_bulk")
1647
+ end
1648
+
1649
+ driver.run(default_tag: 'extracted-host') do
1650
+ 1000.times do
1651
+ driver.feed(sample_record.merge('age'=>rand(100)))
1652
+ end
1653
+ end
1654
+
1655
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
1656
+ # it's Fluentd test driver's constraint
1657
+ # so @index_command_counts.size is always 1
1658
+
1659
+ assert(@index_command_counts.size > 0, "not working with hosts options")
1660
+
1661
+ total = 0
1662
+ @index_command_counts.each do |url, count|
1663
+ total += count
1664
+ end
1665
+ assert_equal(2000, total)
1666
+ end
1667
+
1668
+ def test_writes_to_extracted_host_with_time_placeholder
1669
+ driver.configure(Fluent::Config::Element.new(
1670
+ 'ROOT', '', {
1671
+ '@type' => 'elasticsearch',
1672
+ 'host' => 'host-%Y%m%d',
1673
+ }, [
1674
+ Fluent::Config::Element.new('buffer', 'tag,time', {
1675
+ 'chunk_keys' => ['tag', 'time'],
1676
+ 'timekey' => 3600,
1677
+ }, [])
1678
+ ]
1679
+ ))
1680
+ stub_elastic
1681
+ time = Time.parse Date.today.iso8601
1682
+ elastic_request = stub_elastic("http://host-#{time.utc.strftime('%Y%m%d')}:9200/_bulk")
1683
+ driver.run(default_tag: 'test') do
1684
+ driver.feed(time.to_i, sample_record)
1685
+ end
1686
+ assert_requested(elastic_request)
1687
+ end
1688
+
1689
+ def test_writes_to_extracted_host_with_custom_key_placeholder
1690
+ driver.configure(Fluent::Config::Element.new(
1691
+ 'ROOT', '', {
1692
+ '@type' => 'elasticsearch',
1693
+ 'host' => 'myhost-${pipeline_id}',
1694
+ }, [
1695
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
1696
+ ]
1697
+ ))
1698
+ time = Time.parse Date.today.iso8601
1699
+ first_pipeline_id = "1"
1700
+ second_pipeline_id = "2"
1701
+ first_request = stub_elastic("http://myhost-1:9200/_bulk")
1702
+ second_request = stub_elastic("http://myhost-2:9200/_bulk")
1703
+ driver.run(default_tag: 'test') do
1704
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => first_pipeline_id}))
1705
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => second_pipeline_id}))
1706
+ end
1707
+ assert_requested(first_request)
1708
+ assert_requested(second_request)
1709
+ end
1710
+
1711
+ def test_writes_to_extracted_host_with_placeholder_replaced_in_exception_message
1712
+ driver.configure(Fluent::Config::Element.new(
1713
+ 'ROOT', '', {
1714
+ '@type' => 'elasticsearch',
1715
+ 'host' => 'myhost-${pipeline_id}',
1716
+ }, [
1717
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
1718
+ ]
1719
+ ))
1720
+ time = Time.parse Date.today.iso8601
1721
+ pipeline_id = "1"
1722
+ request = stub_elastic_unavailable("http://myhost-1:9200/_bulk")
1723
+ exception = assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
1724
+ driver.run(default_tag: 'test') do
1725
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
1726
+ end
1727
+ }
1728
+ assert_equal("could not push logs to Elasticsearch cluster ({:host=>\"myhost-1\", :port=>9200, :scheme=>\"http\"}): [503] ", exception.message)
1729
+ end
1730
+ end
1731
+
1732
+ def test_writes_to_logstash_index_with_specified_prefix_uppercase
1733
+ driver.configure("logstash_format true
1734
+ logstash_prefix MyPrefix")
1735
+ time = Time.parse Date.today.iso8601
1736
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
1737
+ stub_elastic
1738
+ driver.run(default_tag: 'test') do
1739
+ driver.feed(time.to_i, sample_record)
1740
+ end
1741
+ # Allthough logstash_prefix has upper-case characters,
1742
+ # it should be set as lower-case when sent to elasticsearch.
1743
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1744
+ end
1745
+
1746
+ def test_writes_to_logstash_index_with_specified_dateformat
1747
+ driver.configure("logstash_format true
1748
+ logstash_dateformat %Y.%m")
1749
+ time = Time.parse Date.today.iso8601
1750
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m")}"
1751
+ stub_elastic
1752
+ driver.run(default_tag: 'test') do
1753
+ driver.feed(time.to_i, sample_record)
1754
+ end
1755
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1756
+ end
1757
+
1758
+ def test_writes_to_logstash_index_with_specified_prefix_and_dateformat
1759
+ driver.configure("logstash_format true
1760
+ logstash_prefix myprefix
1761
+ logstash_dateformat %Y.%m")
1762
+ time = Time.parse Date.today.iso8601
1763
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m")}"
1764
+ stub_elastic
1765
+ driver.run(default_tag: 'test') do
1766
+ driver.feed(time.to_i, sample_record)
1767
+ end
1768
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
1769
+ end
1770
+
1771
+ def test_error_if_tag_not_in_chunk_keys
1772
+ assert_raise(Fluent::ConfigError) {
1773
+ config = %{
1774
+ <buffer foo>
1775
+ </buffer>
1776
+ }
1777
+ driver.configure(config)
1778
+ }
1779
+ end
1780
+
1781
+ def test_can_use_custom_chunk_along_with_tag
1782
+ config = %{
1783
+ <buffer tag, foo>
1784
+ </buffer>
1785
+ }
1786
+ driver.configure(config)
1787
+ end
1788
+
1789
+ def test_doesnt_add_logstash_timestamp_by_default
1790
+ stub_elastic
1791
+ driver.run(default_tag: 'test') do
1792
+ driver.feed(sample_record)
1793
+ end
1794
+ assert_nil(index_cmds[1]['@timestamp'])
1795
+ end
1796
+
1797
+ def test_adds_timestamp_when_logstash
1798
+ driver.configure("logstash_format true\n")
1799
+ stub_elastic
1800
+ ts = DateTime.now
1801
+ time = Fluent::EventTime.from_time(ts.to_time)
1802
+ driver.run(default_tag: 'test') do
1803
+ driver.feed(time, sample_record)
1804
+ end
1805
+ assert(index_cmds[1].has_key? '@timestamp')
1806
+ assert_equal(ts.iso8601(9), index_cmds[1]['@timestamp'])
1807
+ end
1808
+
1809
+ def test_adds_timestamp_when_include_timestamp
1810
+ driver.configure("include_timestamp true\n")
1811
+ stub_elastic
1812
+ ts = DateTime.now
1813
+ time = Fluent::EventTime.from_time(ts.to_time)
1814
+ driver.run(default_tag: 'test') do
1815
+ driver.feed(time, sample_record)
1816
+ end
1817
+ assert(index_cmds[1].has_key? '@timestamp')
1818
+ assert_equal(ts.iso8601(9), index_cmds[1]['@timestamp'])
1819
+ end
1820
+
1821
+ def test_uses_custom_timestamp_when_included_in_record
1822
+ driver.configure("logstash_format true\n")
1823
+ stub_elastic
1824
+ ts = DateTime.new(2001,2,3).iso8601
1825
+ driver.run(default_tag: 'test') do
1826
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1827
+ end
1828
+ assert(index_cmds[1].has_key? '@timestamp')
1829
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1830
+ end
1831
+
1832
+ def test_uses_custom_timestamp_when_included_in_record_without_logstash
1833
+ driver.configure("include_timestamp true\n")
1834
+ stub_elastic
1835
+ ts = DateTime.new(2001,2,3).iso8601
1836
+ driver.run(default_tag: 'test') do
1837
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1838
+ end
1839
+ assert(index_cmds[1].has_key? '@timestamp')
1840
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1841
+ end
1842
+
1843
+ def test_uses_custom_time_key
1844
+ driver.configure("logstash_format true
1845
+ time_key vtm\n")
1846
+ stub_elastic
1847
+ ts = DateTime.new(2001,2,3).iso8601(9)
1848
+ driver.run(default_tag: 'test') do
1849
+ driver.feed(sample_record.merge!('vtm' => ts))
1850
+ end
1851
+ assert(index_cmds[1].has_key? '@timestamp')
1852
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1853
+ end
1854
+
1855
+ def test_uses_custom_time_key_with_float_record
1856
+ driver.configure("logstash_format true
1857
+ time_precision 3
1858
+ time_key vtm\n")
1859
+ stub_elastic
1860
+ time = Time.now
1861
+ float_time = time.to_f
1862
+ driver.run(default_tag: 'test') do
1863
+ driver.feed(sample_record.merge!('vtm' => float_time))
1864
+ end
1865
+ assert(index_cmds[1].has_key? '@timestamp')
1866
+ assert_equal(time.to_datetime.iso8601(3), index_cmds[1]['@timestamp'])
1867
+ end
1868
+
1869
+ def test_uses_custom_time_key_with_format
1870
+ driver.configure("logstash_format true
1871
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
1872
+ time_key vtm\n")
1873
+ stub_elastic
1874
+ ts = "2001-02-03 13:14:01.673+02:00"
1875
+ driver.run(default_tag: 'test') do
1876
+ driver.feed(sample_record.merge!('vtm' => ts))
1877
+ end
1878
+ assert(index_cmds[1].has_key? '@timestamp')
1879
+ assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
1880
+ assert_equal("logstash-2001.02.03", index_cmds[0]['index']['_index'])
1881
+ end
1882
+
1883
+ def test_uses_custom_time_key_with_float_record_and_format
1884
+ driver.configure("logstash_format true
1885
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
1886
+ time_key vtm\n")
1887
+ stub_elastic
1888
+ ts = "2001-02-03 13:14:01.673+02:00"
1889
+ time = Time.parse(ts)
1890
+ current_zone_offset = Time.new(2001, 02, 03).to_datetime.offset
1891
+ float_time = time.to_f
1892
+ driver.run(default_tag: 'test') do
1893
+ driver.feed(sample_record.merge!('vtm' => float_time))
1894
+ end
1895
+ assert(index_cmds[1].has_key? '@timestamp')
1896
+ assert_equal(DateTime.parse(ts).new_offset(current_zone_offset).iso8601(9), index_cmds[1]['@timestamp'])
1897
+ end
1898
+
1899
+ def test_uses_custom_time_key_with_format_without_logstash
1900
+ driver.configure("include_timestamp true
1901
+ index_name test
1902
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
1903
+ time_key vtm\n")
1904
+ stub_elastic
1905
+ ts = "2001-02-03 13:14:01.673+02:00"
1906
+ driver.run(default_tag: 'test') do
1907
+ driver.feed(sample_record.merge!('vtm' => ts))
1908
+ end
1909
+ assert(index_cmds[1].has_key? '@timestamp')
1910
+ assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
1911
+ assert_equal("test", index_cmds[0]['index']['_index'])
1912
+ end
1913
+
1914
+ def test_uses_custom_time_key_exclude_timekey
1915
+ driver.configure("logstash_format true
1916
+ time_key vtm
1917
+ time_key_exclude_timestamp true\n")
1918
+ stub_elastic
1919
+ ts = DateTime.new(2001,2,3).iso8601
1920
+ driver.run(default_tag: 'test') do
1921
+ driver.feed(sample_record.merge!('vtm' => ts))
1922
+ end
1923
+ assert(!index_cmds[1].key?('@timestamp'), '@timestamp should be messing')
1924
+ end
1925
+
1926
+ def test_uses_custom_time_key_format
1927
+ driver.configure("logstash_format true
1928
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n")
1929
+ stub_elastic
1930
+ ts = "2001-02-03T13:14:01.673+02:00"
1931
+ driver.run(default_tag: 'test') do
1932
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1933
+ end
1934
+ assert_equal("logstash-2001.02.03", index_cmds[0]['index']['_index'])
1935
+ assert(index_cmds[1].has_key? '@timestamp')
1936
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1937
+ end
1938
+
1939
+ def test_uses_custom_time_key_format_without_logstash
1940
+ driver.configure("include_timestamp true
1941
+ index_name test
1942
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n")
1943
+ stub_elastic
1944
+ ts = "2001-02-03T13:14:01.673+02:00"
1945
+ driver.run(default_tag: 'test') do
1946
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1947
+ end
1948
+ assert_equal("test", index_cmds[0]['index']['_index'])
1949
+ assert(index_cmds[1].has_key? '@timestamp')
1950
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1951
+ end
1952
+
1953
+ data(:default => nil,
1954
+ :custom_tag => 'es_plugin.output.time.error')
1955
+ def test_uses_custom_time_key_format_logs_an_error(tag_for_error)
1956
+ tag_config = tag_for_error ? "time_parse_error_tag #{tag_for_error}" : ''
1957
+ tag_for_error = 'Fluent::ElasticsearchOutput::TimeParser.error' if tag_for_error.nil?
1958
+ driver.configure("logstash_format true
1959
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n#{tag_config}\n")
1960
+ stub_elastic
1961
+
1962
+ ts = "2001/02/03 13:14:01,673+02:00"
1963
+ index = "logstash-#{Date.today.strftime("%Y.%m.%d")}"
1964
+
1965
+ flexmock(driver.instance.router).should_receive(:emit_error_event)
1966
+ .with(tag_for_error, Fluent::EventTime, Hash, ArgumentError).once
1967
+ driver.run(default_tag: 'test') do
1968
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1969
+ end
1970
+
1971
+ assert_equal(index, index_cmds[0]['index']['_index'])
1972
+ assert(index_cmds[1].has_key? '@timestamp')
1973
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1974
+ end
1975
+
1976
+
1977
+ def test_uses_custom_time_key_format_obscure_format
1978
+ driver.configure("logstash_format true
1979
+ time_key_format %a %b %d %H:%M:%S %Z %Y\n")
1980
+ stub_elastic
1981
+ ts = "Thu Nov 29 14:33:20 GMT 2001"
1982
+ driver.run(default_tag: 'test') do
1983
+ driver.feed(sample_record.merge!('@timestamp' => ts))
1984
+ end
1985
+ assert_equal("logstash-2001.11.29", index_cmds[0]['index']['_index'])
1986
+ assert(index_cmds[1].has_key? '@timestamp')
1987
+ assert_equal(ts, index_cmds[1]['@timestamp'])
1988
+ end
1989
+
1990
+ def test_uses_nanosecond_precision_by_default
1991
+ driver.configure("logstash_format true\n")
1992
+ stub_elastic
1993
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
1994
+ driver.run(default_tag: 'test') do
1995
+ driver.feed(time, sample_record)
1996
+ end
1997
+ assert(index_cmds[1].has_key? '@timestamp')
1998
+ assert_equal(Time.at(time).iso8601(9), index_cmds[1]['@timestamp'])
1999
+ end
2000
+
2001
+ def test_uses_subsecond_precision_when_configured
2002
+ driver.configure("logstash_format true
2003
+ time_precision 3\n")
2004
+ stub_elastic
2005
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
2006
+ driver.run(default_tag: 'test') do
2007
+ driver.feed(time, sample_record)
2008
+ end
2009
+ assert(index_cmds[1].has_key? '@timestamp')
2010
+ assert_equal(Time.at(time).iso8601(3), index_cmds[1]['@timestamp'])
2011
+ end
2012
+
2013
+ def test_doesnt_add_tag_key_by_default
2014
+ stub_elastic
2015
+ driver.run(default_tag: 'test') do
2016
+ driver.feed(sample_record)
2017
+ end
2018
+ assert_nil(index_cmds[1]['tag'])
2019
+ end
2020
+
2021
+ def test_adds_tag_key_when_configured
2022
+ driver.configure("include_tag_key true\n")
2023
+ stub_elastic
2024
+ driver.run(default_tag: 'mytag') do
2025
+ driver.feed(sample_record)
2026
+ end
2027
+ assert(index_cmds[1].has_key?('tag'))
2028
+ assert_equal('mytag', index_cmds[1]['tag'])
2029
+ end
2030
+
2031
+ def test_adds_id_key_when_configured
2032
+ driver.configure("id_key request_id\n")
2033
+ stub_elastic
2034
+ driver.run(default_tag: 'test') do
2035
+ driver.feed(sample_record)
2036
+ end
2037
+ assert_equal('42', index_cmds[0]['index']['_id'])
2038
+ end
2039
+
2040
+ class NestedIdKeyTest < self
2041
+ def test_adds_nested_id_key_with_dot
2042
+ driver.configure("id_key nested.request_id\n")
2043
+ stub_elastic
2044
+ driver.run(default_tag: 'test') do
2045
+ driver.feed(nested_sample_record)
2046
+ end
2047
+ assert_equal('42', index_cmds[0]['index']['_id'])
2048
+ end
2049
+
2050
+ def test_adds_nested_id_key_with_dollar_dot
2051
+ driver.configure("id_key $.nested.request_id\n")
2052
+ stub_elastic
2053
+ driver.run(default_tag: 'test') do
2054
+ driver.feed(nested_sample_record)
2055
+ end
2056
+ assert_equal('42', index_cmds[0]['index']['_id'])
2057
+ end
2058
+
2059
+ def test_adds_nested_id_key_with_bracket
2060
+ driver.configure("id_key $['nested']['request_id']\n")
2061
+ stub_elastic
2062
+ driver.run(default_tag: 'test') do
2063
+ driver.feed(nested_sample_record)
2064
+ end
2065
+ assert_equal('42', index_cmds[0]['index']['_id'])
2066
+ end
2067
+ end
2068
+
2069
+ def test_doesnt_add_id_key_if_missing_when_configured
2070
+ driver.configure("id_key another_request_id\n")
2071
+ stub_elastic
2072
+ driver.run(default_tag: 'test') do
2073
+ driver.feed(sample_record)
2074
+ end
2075
+ assert(!index_cmds[0]['index'].has_key?('_id'))
2076
+ end
2077
+
2078
+ def test_adds_id_key_when_not_configured
2079
+ stub_elastic
2080
+ driver.run(default_tag: 'test') do
2081
+ driver.feed(sample_record)
2082
+ end
2083
+ assert(!index_cmds[0]['index'].has_key?('_id'))
2084
+ end
2085
+
2086
+ def test_adds_parent_key_when_configured
2087
+ driver.configure("parent_key parent_id\n")
2088
+ stub_elastic
2089
+ driver.run(default_tag: 'test') do
2090
+ driver.feed(sample_record)
2091
+ end
2092
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
2093
+ end
2094
+
2095
+ class NestedParentKeyTest < self
2096
+ def test_adds_nested_parent_key_with_dot
2097
+ driver.configure("parent_key nested.parent_id\n")
2098
+ stub_elastic
2099
+ driver.run(default_tag: 'test') do
2100
+ driver.feed(nested_sample_record)
2101
+ end
2102
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
2103
+ end
2104
+
2105
+ def test_adds_nested_parent_key_with_dollar_dot
2106
+ driver.configure("parent_key $.nested.parent_id\n")
2107
+ stub_elastic
2108
+ driver.run(default_tag: 'test') do
2109
+ driver.feed(nested_sample_record)
2110
+ end
2111
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
2112
+ end
2113
+
2114
+ def test_adds_nested_parent_key_with_bracket
2115
+ driver.configure("parent_key $['nested']['parent_id']\n")
2116
+ stub_elastic
2117
+ driver.run(default_tag: 'test') do
2118
+ driver.feed(nested_sample_record)
2119
+ end
2120
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
2121
+ end
2122
+ end
2123
+
2124
+ def test_doesnt_add_parent_key_if_missing_when_configured
2125
+ driver.configure("parent_key another_parent_id\n")
2126
+ stub_elastic
2127
+ driver.run(default_tag: 'test') do
2128
+ driver.feed(sample_record)
2129
+ end
2130
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
2131
+ end
2132
+
2133
+ def test_adds_parent_key_when_not_configured
2134
+ stub_elastic
2135
+ driver.run(default_tag: 'test') do
2136
+ driver.feed(sample_record)
2137
+ end
2138
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
2139
+ end
2140
+
2141
+ class AddsRoutingKeyWhenConfiguredTest < self
2142
+ def test_es6
2143
+ driver("routing_key routing_id\n", 6)
2144
+ stub_elastic
2145
+ driver.run(default_tag: 'test') do
2146
+ driver.feed(sample_record)
2147
+ end
2148
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
2149
+ end
2150
+
2151
+ def test_es7
2152
+ driver("routing_key routing_id\n", 7)
2153
+ stub_elastic
2154
+ driver.run(default_tag: 'test') do
2155
+ driver.feed(sample_record)
2156
+ end
2157
+ assert_equal('routing', index_cmds[0]['index']['routing'])
2158
+ end
2159
+ end
2160
+
2161
+ class NestedRoutingKeyTest < self
2162
+ def test_adds_nested_routing_key_with_dot
2163
+ driver.configure("routing_key nested.routing_id\n")
2164
+ stub_elastic
2165
+ driver.run(default_tag: 'test') do
2166
+ driver.feed(nested_sample_record)
2167
+ end
2168
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
2169
+ end
2170
+
2171
+ def test_adds_nested_routing_key_with_dollar_dot
2172
+ driver.configure("routing_key $.nested.routing_id\n")
2173
+ stub_elastic
2174
+ driver.run(default_tag: 'test') do
2175
+ driver.feed(nested_sample_record)
2176
+ end
2177
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
2178
+ end
2179
+
2180
+ def test_adds_nested_routing_key_with_bracket
2181
+ driver.configure("routing_key $['nested']['routing_id']\n")
2182
+ stub_elastic
2183
+ driver.run(default_tag: 'test') do
2184
+ driver.feed(nested_sample_record)
2185
+ end
2186
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
2187
+ end
2188
+ end
2189
+
2190
+ def test_doesnt_add_routing_key_if_missing_when_configured
2191
+ driver.configure("routing_key another_routing_id\n")
2192
+ stub_elastic
2193
+ driver.run(default_tag: 'test') do
2194
+ driver.feed(sample_record)
2195
+ end
2196
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
2197
+ end
2198
+
2199
+ def test_adds_routing_key_when_not_configured
2200
+ stub_elastic
2201
+ driver.run(default_tag: 'test') do
2202
+ driver.feed(sample_record)
2203
+ end
2204
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
2205
+ end
2206
+
2207
+ def test_remove_one_key
2208
+ driver.configure("remove_keys key1\n")
2209
+ stub_elastic
2210
+ driver.run(default_tag: 'test') do
2211
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
2212
+ end
2213
+ assert(!index_cmds[1].has_key?('key1'))
2214
+ assert(index_cmds[1].has_key?('key2'))
2215
+ end
2216
+
2217
+ def test_remove_multi_keys
2218
+ driver.configure("remove_keys key1, key2\n")
2219
+ stub_elastic
2220
+ driver.run(default_tag: 'test') do
2221
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
2222
+ end
2223
+ assert(!index_cmds[1].has_key?('key1'))
2224
+ assert(!index_cmds[1].has_key?('key2'))
2225
+ end
2226
+
2227
+ def test_request_error
2228
+ stub_elastic_unavailable
2229
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
2230
+ driver.run(default_tag: 'test', shutdown: false) do
2231
+ driver.feed(sample_record)
2232
+ end
2233
+ }
2234
+ end
2235
+
2236
+ def test_request_forever
2237
+ omit("retry_forever test is unstable.") if ENV["CI"]
2238
+ stub_elastic
2239
+ driver.configure(Fluent::Config::Element.new(
2240
+ 'ROOT', '', {
2241
+ '@type' => 'elasticsearch',
2242
+ }, [
2243
+ Fluent::Config::Element.new('buffer', '', {
2244
+ 'retry_forever' => true
2245
+ }, [])
2246
+ ]
2247
+ ))
2248
+ stub_elastic_timeout
2249
+ assert_raise(Timeout::Error) {
2250
+ driver.run(default_tag: 'test', timeout: 10, force_flush_retry: true) do
2251
+ driver.feed(sample_record)
2252
+ end
2253
+ }
2254
+ end
2255
+
2256
+ def test_connection_failed
2257
+ connection_resets = 0
2258
+
2259
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
2260
+ connection_resets += 1
2261
+ raise Faraday::ConnectionFailed, "Test message"
2262
+ end
2263
+
2264
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
2265
+ driver.run(default_tag: 'test', shutdown: false) do
2266
+ driver.feed(sample_record)
2267
+ end
2268
+ }
2269
+ assert_equal(1, connection_resets)
2270
+ end
2271
+
2272
+ def test_reconnect_on_error_enabled
2273
+ connection_resets = 0
2274
+
2275
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
2276
+ connection_resets += 1
2277
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
2278
+ end
2279
+
2280
+ driver.configure("reconnect_on_error true\n")
2281
+
2282
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
2283
+ driver.run(default_tag: 'test', shutdown: false) do
2284
+ driver.feed(sample_record)
2285
+ end
2286
+ }
2287
+
2288
+ assert_raise(Timeout::Error) {
2289
+ driver.run(default_tag: 'test', shutdown: false) do
2290
+ driver.feed(sample_record)
2291
+ end
2292
+ }
2293
+ # FIXME: Consider keywords arguments in #run and how to test this later.
2294
+ # Because v0.14 test driver does not have 1 to 1 correspondence between #run and #flush in tests.
2295
+ assert_equal(1, connection_resets)
2296
+ end
2297
+
2298
+ def test_reconnect_on_error_disabled
2299
+ connection_resets = 0
2300
+
2301
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
2302
+ connection_resets += 1
2303
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
2304
+ end
2305
+
2306
+ driver.configure("reconnect_on_error false\n")
2307
+
2308
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
2309
+ driver.run(default_tag: 'test', shutdown: false) do
2310
+ driver.feed(sample_record)
2311
+ end
2312
+ }
2313
+
2314
+ assert_raise(Timeout::Error) {
2315
+ driver.run(default_tag: 'test', shutdown: false) do
2316
+ driver.feed(sample_record)
2317
+ end
2318
+ }
2319
+ assert_equal(1, connection_resets)
2320
+ end
2321
+
2322
+ def test_bulk_error_retags_when_configured
2323
+ driver.configure("retry_tag retry\n")
2324
+ stub_request(:post, 'http://localhost:9200/_bulk')
2325
+ .to_return(lambda do |req|
2326
+ { :status => 200,
2327
+ :headers => { 'Content-Type' => 'json' },
2328
+ :body => %({
2329
+ "took" : 1,
2330
+ "errors" : true,
2331
+ "items" : [
2332
+ {
2333
+ "create" : {
2334
+ "_index" : "foo",
2335
+ "_type" : "bar",
2336
+ "_id" : "abc",
2337
+ "status" : 500,
2338
+ "error" : {
2339
+ "type" : "some unrecognized type",
2340
+ "reason":"some error to cause version mismatch"
2341
+ }
2342
+ }
2343
+ }
2344
+ ]
2345
+ })
2346
+ }
2347
+ end)
2348
+
2349
+ driver.run(default_tag: 'test') do
2350
+ driver.feed(1, sample_record)
2351
+ end
2352
+
2353
+ assert_equal [['retry', 1, sample_record]], driver.events
2354
+ end
2355
+
2356
+ def test_create_should_write_records_with_ids_and_skip_those_without
2357
+ driver.configure("write_operation create\nid_key my_id\n@log_level debug")
2358
+ stub_request(:post, 'http://localhost:9200/_bulk')
2359
+ .to_return(lambda do |req|
2360
+ { :status => 200,
2361
+ :headers => { 'Content-Type' => 'json' },
2362
+ :body => %({
2363
+ "took" : 1,
2364
+ "errors" : true,
2365
+ "items" : [
2366
+ {
2367
+ "create" : {
2368
+ "_index" : "foo",
2369
+ "_type" : "bar",
2370
+ "_id" : "abc"
2371
+ }
2372
+ },
2373
+ {
2374
+ "create" : {
2375
+ "_index" : "foo",
2376
+ "_type" : "bar",
2377
+ "_id" : "xyz",
2378
+ "status" : 500,
2379
+ "error" : {
2380
+ "type" : "some unrecognized type",
2381
+ "reason":"some error to cause version mismatch"
2382
+ }
2383
+ }
2384
+ }
2385
+ ]
2386
+ })
2387
+ }
2388
+ end)
2389
+ sample_record1 = sample_record('my_id' => 'abc')
2390
+ sample_record4 = sample_record('my_id' => 'xyz')
2391
+
2392
+ driver.run(default_tag: 'test') do
2393
+ driver.feed(1, sample_record1)
2394
+ driver.feed(2, sample_record)
2395
+ driver.feed(3, sample_record)
2396
+ driver.feed(4, sample_record4)
2397
+ end
2398
+
2399
+ logs = driver.logs
2400
+ # one record succeeded while the other should be 'retried'
2401
+ assert_equal [['test', 4, sample_record4]], driver.events
2402
+ assert_logs_include(logs, /(Dropping record)/, 2)
2403
+ end
2404
+
2405
+ def test_create_should_write_records_with_ids_and_emit_those_without
2406
+ driver.configure("write_operation create\nid_key my_id\nemit_error_for_missing_id true\n@log_level debug")
2407
+ stub_request(:post, 'http://localhost:9200/_bulk')
2408
+ .to_return(lambda do |req|
2409
+ { :status => 200,
2410
+ :headers => { 'Content-Type' => 'json' },
2411
+ :body => %({
2412
+ "took" : 1,
2413
+ "errors" : true,
2414
+ "items" : [
2415
+ {
2416
+ "create" : {
2417
+ "_index" : "foo",
2418
+ "_type" : "bar",
2419
+ "_id" : "abc"
2420
+ }
2421
+ },
2422
+ {
2423
+ "create" : {
2424
+ "_index" : "foo",
2425
+ "_type" : "bar",
2426
+ "_id" : "xyz",
2427
+ "status" : 500,
2428
+ "error" : {
2429
+ "type" : "some unrecognized type",
2430
+ "reason":"some error to cause version mismatch"
2431
+ }
2432
+ }
2433
+ }
2434
+ ]
2435
+ })
2436
+ }
2437
+ end)
2438
+ sample_record1 = sample_record('my_id' => 'abc')
2439
+ sample_record4 = sample_record('my_id' => 'xyz')
2440
+
2441
+ driver.run(default_tag: 'test') do
2442
+ driver.feed(1, sample_record1)
2443
+ driver.feed(2, sample_record)
2444
+ driver.feed(3, sample_record)
2445
+ driver.feed(4, sample_record4)
2446
+ end
2447
+
2448
+ error_log = driver.error_events.map {|e| e.last.message }
2449
+ # one record succeeded while the other should be 'retried'
2450
+ assert_equal [['test', 4, sample_record4]], driver.events
2451
+ assert_logs_include(error_log, /(Missing '_id' field)/, 2)
2452
+ end
2453
+
2454
+ def test_bulk_error
2455
+ stub_request(:post, 'http://localhost:9200/_bulk')
2456
+ .to_return(lambda do |req|
2457
+ { :status => 200,
2458
+ :headers => { 'Content-Type' => 'json' },
2459
+ :body => %({
2460
+ "took" : 1,
2461
+ "errors" : true,
2462
+ "items" : [
2463
+ {
2464
+ "create" : {
2465
+ "_index" : "foo",
2466
+ "_type" : "bar",
2467
+ "_id" : "abc",
2468
+ "status" : 500,
2469
+ "error" : {
2470
+ "type" : "some unrecognized type",
2471
+ "reason":"some error to cause version mismatch"
2472
+ }
2473
+ }
2474
+ },
2475
+ {
2476
+ "create" : {
2477
+ "_index" : "foo",
2478
+ "_type" : "bar",
2479
+ "_id" : "abc",
2480
+ "status" : 201
2481
+ }
2482
+ },
2483
+ {
2484
+ "create" : {
2485
+ "_index" : "foo",
2486
+ "_type" : "bar",
2487
+ "_id" : "abc",
2488
+ "status" : 500,
2489
+ "error" : {
2490
+ "type" : "some unrecognized type",
2491
+ "reason":"some error to cause version mismatch"
2492
+ }
2493
+ }
2494
+ },
2495
+ {
2496
+ "create" : {
2497
+ "_index" : "foo",
2498
+ "_type" : "bar",
2499
+ "_id" : "abc",
2500
+ "_id" : "abc",
2501
+ "status" : 409
2502
+ }
2503
+ }
2504
+ ]
2505
+ })
2506
+ }
2507
+ end)
2508
+
2509
+ driver.run(default_tag: 'test') do
2510
+ driver.feed(1, sample_record)
2511
+ driver.feed(2, sample_record)
2512
+ driver.feed(3, sample_record)
2513
+ driver.feed(4, sample_record)
2514
+ end
2515
+
2516
+ expect = [['test', 1, sample_record],
2517
+ ['test', 3, sample_record]]
2518
+ assert_equal expect, driver.events
2519
+ end
2520
+
2521
+ def test_update_should_not_write_if_theres_no_id
2522
+ driver.configure("write_operation update\n")
2523
+ stub_elastic
2524
+ driver.run(default_tag: 'test') do
2525
+ driver.feed(sample_record)
2526
+ end
2527
+ assert_nil(index_cmds)
2528
+ end
2529
+
2530
+ def test_upsert_should_not_write_if_theres_no_id
2531
+ driver.configure("write_operation upsert\n")
2532
+ stub_elastic
2533
+ driver.run(default_tag: 'test') do
2534
+ driver.feed(sample_record)
2535
+ end
2536
+ assert_nil(index_cmds)
2537
+ end
2538
+
2539
+ def test_create_should_not_write_if_theres_no_id
2540
+ driver.configure("write_operation create\n")
2541
+ stub_elastic
2542
+ driver.run(default_tag: 'test') do
2543
+ driver.feed(sample_record)
2544
+ end
2545
+ assert_nil(index_cmds)
2546
+ end
2547
+
2548
+ def test_update_should_write_update_op_and_doc_as_upsert_is_false
2549
+ driver.configure("write_operation update
2550
+ id_key request_id")
2551
+ stub_elastic
2552
+ driver.run(default_tag: 'test') do
2553
+ driver.feed(sample_record)
2554
+ end
2555
+ assert(index_cmds[0].has_key?("update"))
2556
+ assert(!index_cmds[1]["doc_as_upsert"])
2557
+ assert(!index_cmds[1]["upsert"])
2558
+ end
2559
+
2560
+ def test_update_should_remove_keys_from_doc_when_keys_are_skipped
2561
+ driver.configure("write_operation update
2562
+ id_key request_id
2563
+ remove_keys_on_update parent_id")
2564
+ stub_elastic
2565
+ driver.run(default_tag: 'test') do
2566
+ driver.feed(sample_record)
2567
+ end
2568
+ assert(index_cmds[1]["doc"])
2569
+ assert(!index_cmds[1]["doc"]["parent_id"])
2570
+ end
2571
+
2572
+ def test_upsert_should_write_update_op_and_doc_as_upsert_is_true
2573
+ driver.configure("write_operation upsert
2574
+ id_key request_id")
2575
+ stub_elastic
2576
+ driver.run(default_tag: 'test') do
2577
+ driver.feed(sample_record)
2578
+ end
2579
+ assert(index_cmds[0].has_key?("update"))
2580
+ assert(index_cmds[1]["doc_as_upsert"])
2581
+ assert(!index_cmds[1]["upsert"])
2582
+ end
2583
+
2584
+ def test_upsert_should_write_update_op_upsert_and_doc_when_keys_are_skipped
2585
+ driver.configure("write_operation upsert
2586
+ id_key request_id
2587
+ remove_keys_on_update parent_id")
2588
+ stub_elastic
2589
+ driver.run(default_tag: 'test') do
2590
+ driver.feed(sample_record)
2591
+ end
2592
+ assert(index_cmds[0].has_key?("update"))
2593
+ assert(!index_cmds[1]["doc_as_upsert"])
2594
+ assert(index_cmds[1]["upsert"])
2595
+ assert(index_cmds[1]["doc"])
2596
+ end
2597
+
2598
+ def test_upsert_should_remove_keys_from_doc_when_keys_are_skipped
2599
+ driver.configure("write_operation upsert
2600
+ id_key request_id
2601
+ remove_keys_on_update parent_id")
2602
+ stub_elastic
2603
+ driver.run(default_tag: 'test') do
2604
+ driver.feed(sample_record)
2605
+ end
2606
+ assert(index_cmds[1]["upsert"] != index_cmds[1]["doc"])
2607
+ assert(!index_cmds[1]["doc"]["parent_id"])
2608
+ assert(index_cmds[1]["upsert"]["parent_id"])
2609
+ end
2610
+
2611
+ def test_upsert_should_remove_multiple_keys_when_keys_are_skipped
2612
+ driver.configure("write_operation upsert
2613
+ id_key id
2614
+ remove_keys_on_update foo,baz")
2615
+ stub_elastic
2616
+ driver.run(default_tag: 'test') do
2617
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "zip" => "zam")
2618
+ end
2619
+ assert(
2620
+ index_cmds[1]["doc"] == {
2621
+ "id" => 1,
2622
+ "zip" => "zam",
2623
+ }
2624
+ )
2625
+ assert(
2626
+ index_cmds[1]["upsert"] == {
2627
+ "id" => 1,
2628
+ "foo" => "bar",
2629
+ "baz" => "quix",
2630
+ "zip" => "zam",
2631
+ }
2632
+ )
2633
+ end
2634
+
2635
+ def test_upsert_should_remove_keys_from_when_the_keys_are_in_the_record
2636
+ driver.configure("write_operation upsert
2637
+ id_key id
2638
+ remove_keys_on_update_key keys_to_skip")
2639
+ stub_elastic
2640
+ driver.run(default_tag: 'test') do
2641
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "keys_to_skip" => ["baz"])
2642
+ end
2643
+ assert(
2644
+ index_cmds[1]["doc"] == {
2645
+ "id" => 1,
2646
+ "foo" => "bar",
2647
+ }
2648
+ )
2649
+ assert(
2650
+ index_cmds[1]["upsert"] == {
2651
+ "id" => 1,
2652
+ "foo" => "bar",
2653
+ "baz" => "quix",
2654
+ }
2655
+ )
2656
+ end
2657
+
2658
+ def test_upsert_should_remove_keys_from_key_on_record_has_higher_presedence_than_config
2659
+ driver.configure("write_operation upsert
2660
+ id_key id
2661
+ remove_keys_on_update foo,bar
2662
+ remove_keys_on_update_key keys_to_skip")
2663
+ stub_elastic
2664
+ driver.run(default_tag: 'test') do
2665
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "keys_to_skip" => ["baz"])
2666
+ end
2667
+ assert(
2668
+ index_cmds[1]["doc"] == {
2669
+ "id" => 1,
2670
+ # we only expect baz to be stripped here, if the config was more important
2671
+ # foo would be stripped too.
2672
+ "foo" => "bar",
2673
+ }
2674
+ )
2675
+ assert(
2676
+ index_cmds[1]["upsert"] == {
2677
+ "id" => 1,
2678
+ "foo" => "bar",
2679
+ "baz" => "quix",
2680
+ }
2681
+ )
2682
+ end
2683
+
2684
+ def test_create_should_write_create_op
2685
+ driver.configure("write_operation create
2686
+ id_key request_id")
2687
+ stub_elastic
2688
+ driver.run(default_tag: 'test') do
2689
+ driver.feed(sample_record)
2690
+ end
2691
+ assert(index_cmds[0].has_key?("create"))
2692
+ end
2693
+
2694
+ def test_include_index_in_url
2695
+ stub_elastic('http://localhost:9200/logstash-2018.01.01/_bulk')
2696
+
2697
+ driver.configure("index_name logstash-2018.01.01
2698
+ include_index_in_url true")
2699
+ driver.run(default_tag: 'test') do
2700
+ driver.feed(sample_record)
2701
+ end
2702
+
2703
+ assert_equal(2, index_cmds.length)
2704
+ assert_equal(nil, index_cmds.first['index']['_index'])
2705
+ end
2706
+
2707
+ def test_use_simple_sniffer
2708
+ require 'fluent/plugin/elasticsearch_simple_sniffer'
2709
+ stub_elastic_info
2710
+ stub_elastic
2711
+ config = %[
2712
+ sniffer_class_name Fluent::Plugin::ElasticsearchSimpleSniffer
2713
+ log_level debug
2714
+ with_transporter_log true
2715
+ reload_connections true
2716
+ reload_after 1
2717
+ ]
2718
+ driver(config, nil)
2719
+ driver.run(default_tag: 'test') do
2720
+ driver.feed(sample_record)
2721
+ end
2722
+ log = driver.logs
2723
+ # 2 or 3 - one for the ping, one for the _bulk, (and client.info)
2724
+ assert_logs_include_compare_size(3, ">", log, /In Fluent::Plugin::ElasticsearchSimpleSniffer hosts/)
2725
+ assert_logs_include_compare_size(1, "<=", log, /In Fluent::Plugin::ElasticsearchSimpleSniffer hosts/)
2726
+ end
2727
+
2728
+ def test_suppress_doc_wrap
2729
+ driver.configure('write_operation update
2730
+ id_key id
2731
+ remove_keys id
2732
+ suppress_doc_wrap true')
2733
+ stub_elastic
2734
+ doc_body = {'field' => 'value'}
2735
+ script_body = {'source' => 'ctx._source.counter += params.param1',
2736
+ 'lang' => 'painless',
2737
+ 'params' => {'param1' => 1}}
2738
+ upsert_body = {'counter' => 1}
2739
+ driver.run(default_tag: 'test') do
2740
+ driver.feed('id' => 1, 'doc' => doc_body)
2741
+ driver.feed('id' => 2, 'script' => script_body, 'upsert' => upsert_body)
2742
+ end
2743
+ assert(
2744
+ index_cmds[1] == {'doc' => doc_body}
2745
+ )
2746
+ assert(
2747
+ index_cmds[3] == {
2748
+ 'script' => script_body,
2749
+ 'upsert' => upsert_body
2750
+ }
2751
+ )
2752
+ end
2753
+
2754
+ def test_suppress_doc_wrap_should_handle_record_as_is_at_upsert
2755
+ driver.configure('write_operation upsert
2756
+ id_key id
2757
+ remove_keys id
2758
+ suppress_doc_wrap true')
2759
+ stub_elastic
2760
+ doc_body = {'field' => 'value'}
2761
+ script_body = {'source' => 'ctx._source.counter += params.param1',
2762
+ 'lang' => 'painless',
2763
+ 'params' => {'param1' => 1}}
2764
+ upsert_body = {'counter' => 1}
2765
+ driver.run(default_tag: 'test') do
2766
+ driver.feed('id' => 1, 'doc' => doc_body, 'doc_as_upsert' => true)
2767
+ driver.feed('id' => 2, 'script' => script_body, 'upsert' => upsert_body)
2768
+ end
2769
+ assert(
2770
+ index_cmds[1] == {
2771
+ 'doc' => doc_body,
2772
+ 'doc_as_upsert' => true
2773
+ }
2774
+ )
2775
+ assert(
2776
+ index_cmds[3] == {
2777
+ 'script' => script_body,
2778
+ 'upsert' => upsert_body
2779
+ }
2780
+ )
2781
+ end
2782
+
2783
+ def test_ignore_exception
2784
+ driver.configure('ignore_exceptions ["Elasticsearch::Transport::Transport::Errors::ServiceUnavailable"]')
2785
+ stub_elastic_unavailable
2786
+
2787
+ driver.run(default_tag: 'test') do
2788
+ driver.feed(sample_record)
2789
+ end
2790
+ end
2791
+
2792
+ def test_ignore_exception_with_superclass
2793
+ driver.configure('ignore_exceptions ["Elasticsearch::Transport::Transport::ServerError"]')
2794
+ stub_elastic_unavailable
2795
+
2796
+ driver.run(default_tag: 'test') do
2797
+ driver.feed(sample_record)
2798
+ end
2799
+ end
2800
+
2801
+ def test_ignore_excetion_handles_appropriate_ones
2802
+ driver.configure('ignore_exceptions ["Faraday::ConnectionFailed"]')
2803
+ stub_elastic_unavailable
2804
+
2805
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
2806
+ driver.run(default_tag: 'test', shutdown: false) do
2807
+ driver.feed(sample_record)
2808
+ end
2809
+ }
2810
+ end
2811
+ end