fluent-plugin-input-opensearch 1.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  6. data/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +9 -0
  7. data/.github/workflows/coverage.yaml +22 -0
  8. data/.github/workflows/issue-auto-closer.yml +12 -0
  9. data/.github/workflows/linux.yml +26 -0
  10. data/.github/workflows/macos.yml +26 -0
  11. data/.github/workflows/windows.yml +26 -0
  12. data/.gitignore +18 -0
  13. data/CONTRIBUTING.md +24 -0
  14. data/Gemfile +10 -0
  15. data/History.md +67 -0
  16. data/LICENSE.txt +201 -0
  17. data/README.OpenSearchGenID.md +116 -0
  18. data/README.OpenSearchInput.md +314 -0
  19. data/README.Troubleshooting.md +482 -0
  20. data/README.md +1622 -0
  21. data/Rakefile +37 -0
  22. data/fluent-plugin-opensearch.gemspec +39 -0
  23. data/gemfiles/Gemfile.elasticsearch.v6 +12 -0
  24. data/lib/fluent/log-ext.rb +64 -0
  25. data/lib/fluent/plugin/filter_opensearch_genid.rb +103 -0
  26. data/lib/fluent/plugin/in_opensearch.rb +410 -0
  27. data/lib/fluent/plugin/oj_serializer.rb +48 -0
  28. data/lib/fluent/plugin/opensearch_constants.rb +39 -0
  29. data/lib/fluent/plugin/opensearch_error.rb +31 -0
  30. data/lib/fluent/plugin/opensearch_error_handler.rb +182 -0
  31. data/lib/fluent/plugin/opensearch_fallback_selector.rb +36 -0
  32. data/lib/fluent/plugin/opensearch_index_template.rb +155 -0
  33. data/lib/fluent/plugin/opensearch_simple_sniffer.rb +36 -0
  34. data/lib/fluent/plugin/opensearch_tls.rb +96 -0
  35. data/lib/fluent/plugin/out_opensearch.rb +1158 -0
  36. data/lib/fluent/plugin/out_opensearch_data_stream.rb +229 -0
  37. data/test/helper.rb +60 -0
  38. data/test/plugin/datastream_template.json +4 -0
  39. data/test/plugin/test_alias_template.json +9 -0
  40. data/test/plugin/test_filter_opensearch_genid.rb +241 -0
  41. data/test/plugin/test_in_opensearch.rb +500 -0
  42. data/test/plugin/test_index_alias_template.json +11 -0
  43. data/test/plugin/test_index_template.json +25 -0
  44. data/test/plugin/test_oj_serializer.rb +45 -0
  45. data/test/plugin/test_opensearch_error_handler.rb +770 -0
  46. data/test/plugin/test_opensearch_fallback_selector.rb +100 -0
  47. data/test/plugin/test_opensearch_tls.rb +171 -0
  48. data/test/plugin/test_out_opensearch.rb +3980 -0
  49. data/test/plugin/test_out_opensearch_data_stream.rb +746 -0
  50. data/test/plugin/test_template.json +23 -0
  51. data/test/test_log-ext.rb +61 -0
  52. metadata +291 -0
@@ -0,0 +1,3980 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The fluent-plugin-opensearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright fluent-plugin-opensearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Uken Inc. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Uken Inc. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ require_relative '../helper'
28
+ require 'date'
29
+ require 'fluent/test/helpers'
30
+ require 'json'
31
+ require 'fluent/test/driver/output'
32
+ require 'flexmock/test_unit'
33
+ require 'fluent/plugin/out_opensearch'
34
+
35
+ class OpenSearchOutputTest < Test::Unit::TestCase
36
+ include FlexMock::TestCase
37
+ include Fluent::Test::Helpers
38
+
39
+ attr_accessor :index_cmds, :index_command_counts, :index_cmds_all_requests
40
+
41
+ def setup
42
+ Fluent::Test.setup
43
+ @driver = nil
44
+ log = Fluent::Engine.log
45
+ log.out.logs.slice!(0, log.out.logs.length)
46
+ end
47
+
48
+ def driver(conf='', os_version=1, client_version="\"1.2\"")
49
+ # For request stub to detect compatibility.
50
+ @os_version ||= os_version
51
+ @client_version ||= client_version
52
+ if @os_version
53
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
54
+ def detect_os_major_version
55
+ #{@os_version}
56
+ end
57
+ CODE
58
+ end
59
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
60
+ def client_library_version
61
+ #{@client_version}
62
+ end
63
+ CODE
64
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutput) {
65
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
66
+ if !defined?(Fluent::Plugin::Output)
67
+ def format(tag, time, record)
68
+ [time, record].to_msgpack
69
+ end
70
+ end
71
+ }.configure(conf)
72
+ end
73
+
74
+ def default_type_name
75
+ Fluent::Plugin::OpenSearchOutput::DEFAULT_TYPE_NAME
76
+ end
77
+
78
+ def sample_record(content={})
79
+ {'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}.merge(content)
80
+ end
81
+
82
+ def nested_sample_record
83
+ {'nested' =>
84
+ {'age' => 26, 'parent_id' => 'parent', 'routing_id' => 'routing', 'request_id' => '42'}
85
+ }
86
+ end
87
+
88
+ def stub_opensearch_info(url="http://localhost:9200/", version="1.2.2")
89
+ body ="{\"version\":{\"number\":\"#{version}\", \"distribution\":\"opensearch\"},\"tagline\":\"The OpenSearch Project: https://opensearch.org/\"}"
90
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'json' } })
91
+ end
92
+
93
+ def stub_opensearch(url="http://localhost:9200/_bulk")
94
+ stub_request(:post, url).with do |req|
95
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
96
+ end
97
+ end
98
+
99
+ def stub_opensearch_all_requests(url="http://localhost:9200/_bulk")
100
+ @index_cmds_all_requests = Array.new
101
+ stub_request(:post, url).with do |req|
102
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
103
+ @index_cmds_all_requests << @index_cmds
104
+ end
105
+ end
106
+
107
+ def stub_opensearch_unavailable(url="http://localhost:9200/_bulk")
108
+ stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
109
+ end
110
+
111
+ def stub_opensearch_timeout(url="http://localhost:9200/_bulk")
112
+ stub_request(:post, url).to_timeout
113
+ end
114
+
115
+ def stub_opensearch_with_store_index_command_counts(url="http://localhost:9200/_bulk")
116
+ if @index_command_counts == nil
117
+ @index_command_counts = {}
118
+ @index_command_counts.default = 0
119
+ end
120
+
121
+ stub_request(:post, url).with do |req|
122
+ index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
123
+ @index_command_counts[url] += index_cmds.size
124
+ end
125
+ end
126
+
127
+ def make_response_body(req, error_el = nil, error_status = nil, error = nil)
128
+ req_index_cmds = req.body.split("\n").map { |r| JSON.parse(r) }
129
+ items = []
130
+ count = 0
131
+ ids = 1
132
+ op = nil
133
+ index = nil
134
+ type = nil
135
+ id = nil
136
+ req_index_cmds.each do |cmd|
137
+ if count.even?
138
+ op = cmd.keys[0]
139
+ index = cmd[op]['_index']
140
+ type = cmd[op]['_type']
141
+ if cmd[op].has_key?('_id')
142
+ id = cmd[op]['_id']
143
+ else
144
+ # Note: this appears to be an undocumented feature of OpenSearch (and Elasticsearch)
145
+ # https://www.elastic.co/guide/en/elasticsearch/reference/2.4/docs-bulk.html
146
+ # When you submit an "index" write_operation, with no "_id" field in the
147
+ # metadata header, OpenSearch will turn this into a "create"
148
+ # operation in the response.
149
+ if "index" == op
150
+ op = "create"
151
+ end
152
+ id = ids
153
+ ids += 1
154
+ end
155
+ else
156
+ item = {
157
+ op => {
158
+ '_index' => index, '_type' => type, '_id' => id, '_version' => 1,
159
+ '_shards' => { 'total' => 1, 'successful' => 1, 'failed' => 0 },
160
+ 'status' => op == 'create' ? 201 : 200
161
+ }
162
+ }
163
+ items.push(item)
164
+ end
165
+ count += 1
166
+ end
167
+ if !error_el.nil? && !error_status.nil? && !error.nil?
168
+ op = items[error_el].keys[0]
169
+ items[error_el][op].delete('_version')
170
+ items[error_el][op].delete('_shards')
171
+ items[error_el][op]['error'] = error
172
+ items[error_el][op]['status'] = error_status
173
+ errors = true
174
+ else
175
+ errors = false
176
+ end
177
+ @index_cmds = items
178
+ body = { 'took' => 6, 'errors' => errors, 'items' => items }
179
+ return body.to_json
180
+ end
181
+
182
+ def stub_opensearch_bad_argument(url="http://localhost:9200/_bulk")
183
+ error = {
184
+ "type" => "mapper_parsing_exception",
185
+ "reason" => "failed to parse [...]",
186
+ "caused_by" => {
187
+ "type" => "illegal_argument_exception",
188
+ "reason" => "Invalid format: \"...\""
189
+ }
190
+ }
191
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 400, error), :headers => { 'Content-Type' => 'json' } } })
192
+ end
193
+
194
+ def stub_opensearch_bulk_error(url="http://localhost:9200/_bulk")
195
+ error = {
196
+ "type" => "some-unrecognized-error",
197
+ "reason" => "some message printed here ...",
198
+ }
199
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 500, error), :headers => { 'Content-Type' => 'json' } } })
200
+ end
201
+
202
+ def stub_opensearch_bulk_rejected(url="http://localhost:9200/_bulk")
203
+ error = {
204
+ "status" => 500,
205
+ "type" => "rejected_execution_exception",
206
+ "reason" => "rejected execution of org.opensearch.transport.TransportService$4@1a34d37a on OpenSearchThreadPoolExecutor[bulk, queue capacity = 50, org.opensearch.common.util.concurrent.OpenSearchThreadPoolExecutor@312a2162[Running, pool size = 32, active threads = 32, queued tasks = 50, completed tasks = 327053]]"
207
+ }
208
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 429, error), :headers => { 'Content-Type' => 'json' } } })
209
+ end
210
+
211
+ def stub_opensearch_out_of_memory(url="http://localhost:9200/_bulk")
212
+ error = {
213
+ "status" => 500,
214
+ "type" => "out_of_memory_error",
215
+ "reason" => "Java heap space"
216
+ }
217
+ stub_request(:post, url).to_return(lambda { |req| { :status => 200, :body => make_response_body(req, 1, 500, error), :headers => { 'Content-Type' => 'json' } } })
218
+ end
219
+
220
+ def stub_opensearch_unexpected_response_op(url="http://localhost:9200/_bulk")
221
+ error = {
222
+ "category" => "some-other-type",
223
+ "reason" => "some-other-reason"
224
+ }
225
+ 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' } } })
226
+ end
227
+
228
+ def assert_logs_include(logs, msg, exp_matches=1)
229
+ matches = logs.grep(/#{msg}/)
230
+ assert_equal(exp_matches, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
231
+ end
232
+
233
+ def assert_logs_include_compare_size(exp_matches=1, operator="<=", logs="", msg="")
234
+ matches = logs.grep(/#{msg}/)
235
+ assert_compare(exp_matches, operator, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
236
+ end
237
+
238
+ def alias_endpoint
239
+ "_aliases"
240
+ end
241
+
242
+ def test_configure
243
+ config = %{
244
+ host logs.google.com
245
+ port 777
246
+ scheme https
247
+ path /os/
248
+ user john
249
+ password doe
250
+ }
251
+ instance = driver(config).instance
252
+
253
+ assert_equal 'logs.google.com', instance.host
254
+ assert_equal 777, instance.port
255
+ assert_equal :https, instance.scheme
256
+ assert_equal '/os/', instance.path
257
+ assert_equal 'john', instance.user
258
+ assert_equal 'doe', instance.password
259
+ assert_equal Fluent::Plugin::OpenSearchTLS::DEFAULT_VERSION, instance.ssl_version
260
+ assert_nil instance.ssl_max_version
261
+ assert_nil instance.ssl_min_version
262
+ if Fluent::Plugin::OpenSearchTLS::USE_TLS_MINMAX_VERSION
263
+ if defined?(OpenSSL::SSL::TLS1_3_VERSION)
264
+ assert_equal({max_version: OpenSSL::SSL::TLS1_3_VERSION, min_version: OpenSSL::SSL::TLS1_2_VERSION},
265
+ instance.ssl_version_options)
266
+ else
267
+ assert_equal({max_version: nil, min_version: OpenSSL::SSL::TLS1_2_VERSION},
268
+ instance.ssl_version_options)
269
+ end
270
+ else
271
+ assert_equal({version: Fluent::Plugin::OpensearchTLS::DEFAULT_VERSION},
272
+ instance.ssl_version_options)
273
+ end
274
+ assert_nil instance.client_key
275
+ assert_nil instance.client_cert
276
+ assert_nil instance.client_key_pass
277
+ assert_false instance.with_transporter_log
278
+ assert_equal "_doc", default_type_name
279
+ assert_equal :excon, instance.http_backend
280
+ assert_false instance.prefer_oj_serializer
281
+ assert_equal ["out_of_memory_error", "rejected_execution_exception"], instance.unrecoverable_error_types
282
+ assert_true instance.verify_os_version_at_startup
283
+ assert_equal Fluent::Plugin::OpenSearchOutput::DEFAULT_OPENSEARCH_VERSION, instance.default_opensearch_version
284
+ assert_false instance.log_os_400_reason
285
+ assert_equal(-1, Fluent::Plugin::OpenSearchOutput::DEFAULT_TARGET_BULK_BYTES)
286
+ assert_false instance.compression
287
+ assert_equal :no_compression, instance.compression_level
288
+ assert_true instance.http_backend_excon_nonblock
289
+
290
+ assert_nil instance.endpoint
291
+ end
292
+
293
+ test 'configure endpoint section' do
294
+ config = Fluent::Config::Element.new(
295
+ 'ROOT', '', {
296
+ '@type' => 'opensearch',
297
+ }, [
298
+ Fluent::Config::Element.new('endpoint', '', {
299
+ 'url' => "https://search-opensearch.aws.example.com/",
300
+ 'region' => "local",
301
+ 'access_key_id' => 'YOUR_AWESOME_KEY',
302
+ 'secret_access_key' => 'YOUR_AWESOME_SECRET',
303
+ }, []),
304
+ Fluent::Config::Element.new('buffer', 'tag', {}, [])
305
+
306
+ ])
307
+ instance = driver(config).instance
308
+
309
+ assert_equal "https://search-opensearch.aws.example.com", instance.endpoint.url
310
+ assert_equal "local", instance.endpoint.region
311
+ assert_equal "YOUR_AWESOME_KEY", instance.endpoint.access_key_id
312
+ assert_equal "YOUR_AWESOME_SECRET", instance.endpoint.secret_access_key
313
+ assert_nil instance.endpoint.assume_role_arn
314
+ assert_nil instance.endpoint.ecs_container_credentials_relative_uri
315
+ assert_equal "fluentd", instance.endpoint.assume_role_session_name
316
+ assert_nil instance.endpoint.assume_role_web_identity_token_file
317
+ assert_nil instance.endpoint.sts_credentials_region
318
+ assert_equal :es, instance.endpoint.aws_service_name
319
+ end
320
+
321
+ data("OpenSearch Service" => [:es, 'es'],
322
+ "OpenSearch Serverless" => [:aoss, 'aoss'])
323
+ test 'configure endpoint section w/ aws_service_name' do |data|
324
+ expected, conf = data
325
+ config = Fluent::Config::Element.new(
326
+ 'ROOT', '', {
327
+ '@type' => 'opensearch',
328
+ }, [
329
+ Fluent::Config::Element.new('endpoint', '', {
330
+ 'url' => "https://search-opensearch.aws.example.com/",
331
+ 'region' => "local",
332
+ 'access_key_id' => 'YOUR_AWESOME_KEY',
333
+ 'secret_access_key' => 'YOUR_AWESOME_SECRET',
334
+ 'aws_service_name' => conf,
335
+ }, []),
336
+ Fluent::Config::Element.new('buffer', 'tag', {}, [])
337
+
338
+ ])
339
+ instance = driver(config).instance
340
+
341
+ assert_equal "https://search-opensearch.aws.example.com", instance.endpoint.url
342
+ assert_equal "local", instance.endpoint.region
343
+ assert_equal "YOUR_AWESOME_KEY", instance.endpoint.access_key_id
344
+ assert_equal "YOUR_AWESOME_SECRET", instance.endpoint.secret_access_key
345
+ assert_nil instance.endpoint.assume_role_arn
346
+ assert_nil instance.endpoint.ecs_container_credentials_relative_uri
347
+ assert_equal "fluentd", instance.endpoint.assume_role_session_name
348
+ assert_nil instance.endpoint.assume_role_web_identity_token_file
349
+ assert_nil instance.endpoint.sts_credentials_region
350
+ assert_equal expected, instance.endpoint.aws_service_name
351
+ end
352
+
353
+ test 'configure compression' do
354
+ config = %{
355
+ compression_level best_compression
356
+ }
357
+ instance = driver(config).instance
358
+
359
+ assert_equal true, instance.compression
360
+ end
361
+
362
+ test 'check compression strategy' do
363
+ config = %{
364
+ compression_level best_speed
365
+ }
366
+ instance = driver(config).instance
367
+
368
+ assert_equal Zlib::BEST_SPEED, instance.compression_strategy
369
+ end
370
+
371
+ test 'check content-encoding header with compression' do
372
+ config = %{
373
+ compression_level best_compression
374
+ }
375
+ instance = driver(config).instance
376
+
377
+ assert_equal nil, instance.client.transport.transport.options[:transport_options][:headers]["Content-Encoding"]
378
+
379
+ stub_request(:post, "http://localhost:9200/_bulk").
380
+ to_return(status: 200, body: "", headers: {})
381
+ stub_opensearch_info
382
+ driver.run(default_tag: 'test') do
383
+ driver.feed(sample_record)
384
+ end
385
+ compressable = instance.compressable_connection
386
+
387
+ assert_equal "gzip", instance.client(nil, compressable).transport.transport.options[:transport_options][:headers]["Content-Encoding"]
388
+ end
389
+
390
+ test 'check compression option is passed to transport' do
391
+ config = %{
392
+ compression_level best_compression
393
+ }
394
+ instance = driver(config).instance
395
+
396
+ assert_equal false, instance.client.transport.transport.options[:compression]
397
+
398
+ stub_request(:post, "http://localhost:9200/_bulk").
399
+ to_return(status: 200, body: "", headers: {})
400
+ stub_opensearch_info
401
+ driver.run(default_tag: 'test') do
402
+ driver.feed(sample_record)
403
+ end
404
+ compressable = instance.compressable_connection
405
+
406
+ assert_equal true, instance.client(nil, compressable).transport.transport.options[:compression]
407
+ end
408
+
409
+ test 'invalid specification of times of retrying template installation' do
410
+ config = %{
411
+ max_retry_putting_template -3
412
+ }
413
+ assert_raise(Fluent::ConfigError) {
414
+ driver(config)
415
+ }
416
+ end
417
+
418
+ test 'invalid specification of times of retrying get es version' do
419
+ config = %{
420
+ max_retry_get_os_version -3
421
+ }
422
+ assert_raise(Fluent::ConfigError) {
423
+ driver(config)
424
+ }
425
+ end
426
+
427
+ sub_test_case 'Check client.info response' do
428
+ def create_driver(conf='', os_version=1, client_version="\"1.20\"")
429
+ # For request stub to detect compatibility.
430
+ @client_version ||= client_version
431
+ @default_opensearch_version ||= os_version
432
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
433
+ def detect_os_major_version
434
+ @_os_info ||= client.info
435
+ begin
436
+ unless version = @_os_info.dig("version", "number")
437
+ version = @default_opensearch_version
438
+ end
439
+ rescue NoMethodError => e
440
+ log.warn "#{@_os_info} can not dig version information. Assuming OpenSearch #{@default_opensearch_version}", error: e
441
+ version = @default_opensearch_version
442
+ end
443
+ version.to_i
444
+ end
445
+ CODE
446
+
447
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
448
+ def client_library_version
449
+ #{@client_version}
450
+ end
451
+ CODE
452
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutput) {
453
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
454
+ if !defined?(Fluent::Plugin::Output)
455
+ def format(tag, time, record)
456
+ [time, record].to_msgpack
457
+ end
458
+ end
459
+ }.configure(conf)
460
+ end
461
+
462
+ def stub_opensearch_info_bad(url="http://localhost:9200/", version="6.4.2")
463
+ body ="{\"version\":{\"number\":\"#{version}\",\"build_flavor\":\"default\"},\"tagline\":\"You Know, for Search\"}"
464
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'text/plain' } })
465
+ end
466
+
467
+ test 'handle invalid client.info' do
468
+ stub_opensearch_info_bad("https://logs.fluentd.com:24225/es//", "7.7.1")
469
+ config = %{
470
+ host logs.fluentd.com
471
+ port 24225
472
+ scheme https
473
+ path /es/
474
+ user john
475
+ password doe
476
+ default_elasticsearch_version 7
477
+ scheme https
478
+ @log_level info
479
+ }
480
+ assert_raise(NoMethodError) do
481
+ _d = create_driver(config, 1, "\"1.2.2\"")
482
+ end
483
+ end
484
+ end
485
+
486
+ sub_test_case 'Check TLS handshake stuck warning log' do
487
+ test 'warning TLS log' do
488
+ config = %{
489
+ scheme https
490
+ http_backend_excon_nonblock false
491
+ ssl_version TLSv1_2
492
+ @log_level info
493
+ }
494
+ driver(config)
495
+ logs = driver.logs
496
+ assert_logs_include(logs, /TLS handshake will be stucked with block connection.\n Consider to set `http_backend_excon_nonblock` as true\n/)
497
+ end
498
+ end
499
+
500
+ test 'Detected insecure security' do
501
+ config = %{
502
+ ssl_version TLSv1_1
503
+ @log_level warn
504
+ scheme https
505
+ }
506
+ driver(config, 6)
507
+ logs = driver.logs
508
+ assert_logs_include(logs, /Detected OpenSearch 1.x or above and enabled insecure security/, 1)
509
+ end
510
+
511
+ test 'Detected Elasticsearch 7 and secure security' do
512
+ config = %{
513
+ ssl_version TLSv1_2
514
+ @log_level warn
515
+ scheme https
516
+ }
517
+ driver(config, 7)
518
+ logs = driver.logs
519
+ assert_logs_include(logs, /Detected ES 6.x or above and enabled insecure security/, 0)
520
+ end
521
+
522
+ test 'Pass OpenSearch and client library are same' do
523
+ config = %{
524
+ @log_level warn
525
+ validate_client_version true
526
+ }
527
+ assert_nothing_raised do
528
+ driver(config, 1, "\"1.2.2\"")
529
+ end
530
+ end
531
+
532
+ test 'Detected Elasticsearch and client library mismatch' do
533
+ config = %{
534
+ @log_level warn
535
+ validate_client_version true
536
+ }
537
+ assert_raise_message(/Detected OpenSearch 1 but you use OpenSearch client 2.0/) do
538
+ driver(config, 1, "\"2.0.0\"")
539
+ end
540
+ end
541
+
542
+ sub_test_case "placeholder substitution needed?" do
543
+ data("host placeholder" => ["host", "host-${tag}.google.com"],
544
+ "index_name_placeholder" => ["index_name", "logstash-${tag}"],
545
+ "template_name_placeholder" => ["template_name", "logstash-${tag}"],
546
+ "customize_template" => ["customize_template", '{"<<TAG>>":"${tag}"}'],
547
+ "logstash_prefix_placeholder" => ["logstash_prefix", "fluentd-${tag}"],
548
+ "application_name_placeholder" => ["application_name", "fluentd-${tag}"],
549
+ )
550
+ test 'tag placeholder' do |data|
551
+ param, value = data
552
+ config = Fluent::Config::Element.new(
553
+ 'ROOT', '', {
554
+ '@type' => 'opensearch',
555
+ param => value
556
+ }, [
557
+ Fluent::Config::Element.new('buffer', 'tag', {}, [])
558
+ ])
559
+ driver(config)
560
+
561
+ assert_true driver.instance.placeholder_substitution_needed_for_template?
562
+ end
563
+
564
+
565
+ data("host placeholder" => ["host", "host-%Y%m%d.google.com"],
566
+ "index_name_placeholder" => ["index_name", "logstash-%Y%m%d"],
567
+ "template_name_placeholder" => ["template_name", "logstash-%Y%m%d"],
568
+ "customize_template" => ["customize_template", '{"<<TAG>>":"fluentd-%Y%m%d"}'],
569
+ "logstash_prefix_placeholder" => ["logstash_prefix", "fluentd-%Y%m%d"],
570
+ "application_name_placeholder" => ["application_name", "fluentd-%Y%m%d"],
571
+ )
572
+ test 'time placeholder' do |data|
573
+ param, value = data
574
+ config = Fluent::Config::Element.new(
575
+ 'ROOT', '', {
576
+ '@type' => 'opensearch',
577
+ param => value
578
+ }, [
579
+ Fluent::Config::Element.new('buffer', 'time', {
580
+ 'timekey' => '1d'
581
+ }, [])
582
+ ])
583
+ driver(config)
584
+
585
+ assert_true driver.instance.placeholder_substitution_needed_for_template?
586
+ end
587
+
588
+ data("host placeholder" => ["host", "host-${mykey}.google.com"],
589
+ "index_name_placeholder" => ["index_name", "logstash-${mykey}"],
590
+ "template_name_placeholder" => ["template_name", "logstash-${mykey}"],
591
+ "customize_template" => ["customize_template", '{"<<TAG>>":"${mykey}"}'],
592
+ "logstash_prefix_placeholder" => ["logstash_prefix", "fluentd-${mykey}"],
593
+ "logstash_dateformat_placeholder" => ["logstash_dateformat", "${mykey}"],
594
+ "application_name_placeholder" => ["application_name", "fluentd-${mykey}"],
595
+ )
596
+ test 'custom placeholder' do |data|
597
+ param, value = data
598
+ config = Fluent::Config::Element.new(
599
+ 'ROOT', '', {
600
+ '@type' => 'elasticsearch',
601
+ param => value
602
+ }, [
603
+ Fluent::Config::Element.new('buffer', 'mykey', {
604
+ 'chunk_keys' => 'mykey',
605
+ 'timekey' => '1d',
606
+ }, [])
607
+ ])
608
+ driver(config)
609
+
610
+ assert_true driver.instance.placeholder_substitution_needed_for_template?
611
+ end
612
+
613
+ data("host placeholder" => ["host", "host-${tag}.google.com"],
614
+ "index_name_placeholder" => ["index_name", "logstash-${es_index}-%Y%m%d"],
615
+ "template_name_placeholder" => ["template_name", "logstash-${tag}-%Y%m%d"],
616
+ "customize_template" => ["customize_template", '{"<<TAG>>":"${os_index}"}'],
617
+ "logstash_prefix_placeholder" => ["logstash_prefix", "fluentd-${os_index}-%Y%m%d"],
618
+ "logstash_dateformat_placeholder" => ["logstash_dateformat", "${os_index}"],
619
+ "application_name_placeholder" => ["application_name", "fluentd-${tag}-${os_index}-%Y%m%d"],
620
+ )
621
+ test 'mixed placeholder' do |data|
622
+ param, value = data
623
+ config = Fluent::Config::Element.new(
624
+ 'ROOT', '', {
625
+ '@type' => 'opensearch',
626
+ param => value
627
+ }, [
628
+ Fluent::Config::Element.new('buffer', 'tag,time,os_index', {
629
+ 'chunk_keys' => 'os_index',
630
+ 'timekey' => '1d',
631
+ }, [])
632
+ ])
633
+ driver(config)
634
+
635
+ assert_true driver.instance.placeholder_substitution_needed_for_template?
636
+ end
637
+ end
638
+
639
+ sub_test_case 'chunk_keys requirement' do
640
+ test 'tag in chunk_keys' do
641
+ assert_nothing_raised do
642
+ driver(Fluent::Config::Element.new(
643
+ 'ROOT', '', {
644
+ '@type' => 'opensearch',
645
+ 'host' => 'log.google.com',
646
+ 'port' => 777,
647
+ 'scheme' => 'https',
648
+ 'path' => '/os/',
649
+ 'user' => 'john',
650
+ 'password' => 'doe',
651
+ }, [
652
+ Fluent::Config::Element.new('buffer', 'tag', {
653
+ 'chunk_keys' => 'tag'
654
+ }, [])
655
+ ]
656
+ ))
657
+ end
658
+ end
659
+
660
+ test '_index in chunk_keys' do
661
+ assert_nothing_raised do
662
+ driver(Fluent::Config::Element.new(
663
+ 'ROOT', '', {
664
+ '@type' => 'opensearch',
665
+ 'host' => 'log.google.com',
666
+ 'port' => 777,
667
+ 'scheme' => 'https',
668
+ 'path' => '/os/',
669
+ 'user' => 'john',
670
+ 'password' => 'doe',
671
+ }, [
672
+ Fluent::Config::Element.new('buffer', '_index', {
673
+ 'chunk_keys' => '_index'
674
+ }, [])
675
+ ]
676
+ ))
677
+ end
678
+ end
679
+
680
+ test 'lack of tag and _index in chunk_keys' do
681
+ assert_raise_message(/'tag' or '_index' in chunk_keys is required./) do
682
+ driver(Fluent::Config::Element.new(
683
+ 'ROOT', '', {
684
+ '@type' => 'opensearch',
685
+ 'host' => 'log.google.com',
686
+ 'port' => 777,
687
+ 'scheme' => 'https',
688
+ 'path' => '/os/',
689
+ 'user' => 'john',
690
+ 'password' => 'doe',
691
+ }, [
692
+ Fluent::Config::Element.new('buffer', 'mykey', {
693
+ 'chunk_keys' => 'mykey'
694
+ }, [])
695
+ ]
696
+ ))
697
+ end
698
+ end
699
+ end
700
+
701
+ test 'Detected exclusive features which are host placeholder, template installation, and verify OpenSearch version at startup' do
702
+ cwd = File.dirname(__FILE__)
703
+ template_file = File.join(cwd, 'test_template.json')
704
+
705
+ assert_raise_message(/host placeholder, template installation, and verify OpenSearch version at startup are exclusive feature at same time./) do
706
+ config = %{
707
+ host logs-${tag}.google.com
708
+ port 777
709
+ scheme https
710
+ path /os/
711
+ user john
712
+ password doe
713
+ template_name logstash
714
+ template_file #{template_file}
715
+ verify_os_version_at_startup true
716
+ default_opensearch_version 1
717
+ }
718
+ driver(config)
719
+ end
720
+ end
721
+
722
+ class GetOpenSearchVersionTest < self
723
+ def create_driver(conf='', client_version="\"1.0\"")
724
+ # For request stub to detect compatibility.
725
+ @client_version ||= client_version
726
+ # Ensure original implementation existence.
727
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
728
+ def detect_os_major_version
729
+ @_os_info ||= client.info
730
+ unless version = @_os_info.dig("version", "number")
731
+ version = @default_opensearch_version
732
+ end
733
+ version.to_i
734
+ end
735
+ CODE
736
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
737
+ def client_library_version
738
+ #{@client_version}
739
+ end
740
+ CODE
741
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutput).configure(conf)
742
+ end
743
+
744
+ def test_retry_get_os_version
745
+ config = %{
746
+ host logs.google.com
747
+ port 778
748
+ scheme https
749
+ path /os/
750
+ user john
751
+ password doe
752
+ verify_os_version_at_startup true
753
+ max_retry_get_os_version 3
754
+ }
755
+
756
+ connection_resets = 0
757
+ stub_request(:get, "https://logs.google.com:778/os//").
758
+ with(basic_auth: ['john', 'doe']) do |req|
759
+ connection_resets += 1
760
+ raise Faraday::ConnectionFailed, "Test message"
761
+ end
762
+
763
+ assert_raise(Fluent::Plugin::OpenSearchError::RetryableOperationExhaustedFailure) do
764
+ create_driver(config)
765
+ end
766
+
767
+ assert_equal(4, connection_resets)
768
+ end
769
+ end
770
+
771
+ class GetOpenSearchVersionWithFallbackTest < self
772
+ def create_driver(conf='', client_version="\"1.2\"")
773
+ # For request stub to detect compatibility.
774
+ @client_version ||= client_version
775
+ # Ensure original implementation existence.
776
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
777
+ def detect_os_major_version
778
+ @_os_info ||= client.info
779
+ unless version = @_os_info.dig("version", "number")
780
+ version = @default_opensearch_version
781
+ end
782
+ version.to_i
783
+ end
784
+ CODE
785
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
786
+ def client_library_version
787
+ #{@client_version}
788
+ end
789
+ CODE
790
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutput).configure(conf)
791
+ end
792
+
793
+ data("OpenSearch 1" => ["1.2", 1])
794
+ def test_retry_get_os_version_without_fail_on_detecting_os_version_retry_exceeded(data)
795
+ client_version, os_major_version = data
796
+ config = %{
797
+ host logs.google.com
798
+ port 778
799
+ scheme https
800
+ path /os/
801
+ user john
802
+ password doe
803
+ verify_os_version_at_startup true
804
+ max_retry_get_os_version 2
805
+ fail_on_detecting_os_version_retry_exceed false
806
+ default_opensearch_version #{os_major_version}
807
+ @log_level info
808
+ }
809
+
810
+ connection_resets = 0
811
+ stub_request(:get, "https://logs.google.com:778/os//").
812
+ with(basic_auth: ['john', 'doe']) do |req|
813
+ connection_resets += 1
814
+ raise Faraday::ConnectionFailed, "Test message"
815
+ end
816
+
817
+ d = create_driver(config, client_version)
818
+
819
+ assert_equal os_major_version, d.instance.default_opensearch_version
820
+ assert_equal 3, connection_resets
821
+ assert_equal os_major_version, d.instance.instance_variable_get(:@last_seen_major_version)
822
+ end
823
+ end
824
+
825
+ data("legacy_template" => [true, "_template"],
826
+ "new_template" => [false, "_index_template"])
827
+ def test_template_already_present(data)
828
+ use_legacy_template_flag, endpoint = data
829
+ config = %{
830
+ host logs.google.com
831
+ port 777
832
+ scheme https
833
+ path /os/
834
+ user john
835
+ password doe
836
+ template_name logstash
837
+ template_file /abc123
838
+ use_legacy_template #{use_legacy_template_flag}
839
+ }
840
+
841
+ # connection start
842
+ stub_request(:head, "https://logs.google.com:777/os//").
843
+ with(basic_auth: ['john', 'doe']).
844
+ to_return(:status => 200, :body => "", :headers => {})
845
+ # check if template exists
846
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash").
847
+ with(basic_auth: ['john', 'doe']).
848
+ to_return(:status => 200, :body => "", :headers => {})
849
+ stub_opensearch_info("https://logs.google.com:777/os//")
850
+
851
+ driver(config)
852
+
853
+ assert_not_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash")
854
+ end
855
+
856
+ data("legacy_template" => [true, "_template"],
857
+ "new_template" => [false, "_index_template"])
858
+ def test_template_create(data)
859
+ use_legacy_template_flag, endpoint = data
860
+ cwd = File.dirname(__FILE__)
861
+ template_file = if use_legacy_template_flag
862
+ File.join(cwd, 'test_template.json')
863
+ else
864
+ File.join(cwd, 'test_index_template.json')
865
+ end
866
+
867
+ config = %{
868
+ host logs.google.com
869
+ port 777
870
+ scheme https
871
+ path /os/
872
+ user john
873
+ password doe
874
+ template_name logstash
875
+ template_file #{template_file}
876
+ use_legacy_template #{use_legacy_template_flag}
877
+ }
878
+
879
+ # connection start
880
+ stub_request(:head, "https://logs.google.com:777/os//").
881
+ with(basic_auth: ['john', 'doe']).
882
+ to_return(:status => 200, :body => "", :headers => {})
883
+ # check if template exists
884
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash").
885
+ with(basic_auth: ['john', 'doe']).
886
+ to_return(:status => 404, :body => "", :headers => {})
887
+ # creation
888
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash").
889
+ with(basic_auth: ['john', 'doe']).
890
+ to_return(:status => 200, :body => "", :headers => {})
891
+ stub_opensearch_info("https://logs.google.com:777/os//")
892
+
893
+ driver(config)
894
+
895
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash", times: 1)
896
+ end
897
+
898
+ data("legacy_template" => [true, "_template"],
899
+ "new_template" => [false, "_index_template"])
900
+ def test_custom_template_create(data)
901
+ use_legacy_template_flag, endpoint = data
902
+ cwd = File.dirname(__FILE__)
903
+ template_file = if use_legacy_template_flag
904
+ File.join(cwd, 'test_alias_template.json')
905
+ else
906
+ File.join(cwd, 'test_index_alias_template.json')
907
+ end
908
+
909
+ config = %{
910
+ host logs.google.com
911
+ port 777
912
+ scheme https
913
+ path /os/
914
+ user john
915
+ password doe
916
+ template_name myapp_alias_template
917
+ template_file #{template_file}
918
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
919
+ use_legacy_template #{use_legacy_template_flag}
920
+ }
921
+
922
+ # connection start
923
+ stub_request(:head, "https://logs.google.com:777/os//").
924
+ with(basic_auth: ['john', 'doe']).
925
+ to_return(:status => 200, :body => "", :headers => {})
926
+ # check if template exists
927
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template").
928
+ with(basic_auth: ['john', 'doe']).
929
+ to_return(:status => 404, :body => "", :headers => {})
930
+ # creation
931
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template").
932
+ with(basic_auth: ['john', 'doe']).
933
+ to_return(:status => 200, :body => "", :headers => {})
934
+ stub_opensearch_info("https://logs.google.com:777/os//")
935
+
936
+ driver(config)
937
+
938
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template", times: 1)
939
+ end
940
+
941
+ data("legacy_template" => [true, "_template"],
942
+ "new_template" => [false, "_index_template"])
943
+ def test_custom_template_create_with_customize_template_related_placeholders(data)
944
+ use_legacy_template_flag, endpoint = data
945
+ cwd = File.dirname(__FILE__)
946
+ template_file = if use_legacy_template_flag
947
+ File.join(cwd, 'test_alias_template.json')
948
+ else
949
+ File.join(cwd, 'test_index_alias_template.json')
950
+ end
951
+
952
+ config = %{
953
+ host logs.google.com
954
+ port 777
955
+ scheme https
956
+ path /os/
957
+ user john
958
+ password doe
959
+ template_name myapp_alias_template-${tag}
960
+ template_file #{template_file}
961
+ customize_template {"--appid--": "${tag}-logs","--index_prefix--":"${tag}"}
962
+ use_legacy_template #{use_legacy_template_flag}
963
+ }
964
+
965
+ # connection start
966
+ stub_request(:head, "https://logs.google.com:777/os//").
967
+ with(basic_auth: ['john', 'doe']).
968
+ to_return(:status => 200, :body => "", :headers => {})
969
+ # check if template exists
970
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template-test.template").
971
+ with(basic_auth: ['john', 'doe']).
972
+ to_return(:status => 404, :body => "", :headers => {})
973
+ # creation
974
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template-test.template").
975
+ with(basic_auth: ['john', 'doe']).
976
+ to_return(:status => 200, :body => "", :headers => {})
977
+
978
+ stub_request(:put, "https://logs.google.com:777/os//%3Cfluentd-test-default-000001%3E").
979
+ to_return(status: 200, body: "", headers: {})
980
+
981
+ driver(config)
982
+
983
+ stub_opensearch("https://logs.google.com:777/os//_bulk")
984
+ stub_opensearch_info("https://logs.google.com:777/os//")
985
+ driver.run(default_tag: 'test.template') do
986
+ driver.feed(sample_record)
987
+ end
988
+
989
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template-test.template", times: 1)
990
+ end
991
+
992
+ data("legacy_template" => [true, "_template"],
993
+ "new_template" => [false, "_index_template"])
994
+ def test_custom_template_installation_for_host_placeholder(data)
995
+ use_legacy_template_flag, endpoint = data
996
+ cwd = File.dirname(__FILE__)
997
+ template_file = if use_legacy_template_flag
998
+ File.join(cwd, 'test_template.json')
999
+ else
1000
+ File.join(cwd, 'test_index_template.json')
1001
+ end
1002
+
1003
+ config = %{
1004
+ host logs-${tag}.google.com
1005
+ port 777
1006
+ scheme https
1007
+ path /os/
1008
+ user john
1009
+ password doe
1010
+ template_name logstash
1011
+ template_file #{template_file}
1012
+ verify_os_version_at_startup false
1013
+ default_elasticsearch_version 6
1014
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
1015
+ use_legacy_template #{use_legacy_template_flag}
1016
+ }
1017
+
1018
+ # connection start
1019
+ stub_request(:head, "https://logs-test.google.com:777/os//").
1020
+ with(basic_auth: ['john', 'doe']).
1021
+ to_return(:status => 200, :body => "", :headers => {})
1022
+ # check if template exists
1023
+ stub_request(:get, "https://logs-test.google.com:777/os//#{endpoint}/logstash").
1024
+ with(basic_auth: ['john', 'doe']).
1025
+ to_return(:status => 404, :body => "", :headers => {})
1026
+ stub_request(:put, "https://logs-test.google.com:777/os//#{endpoint}/logstash").
1027
+ with(basic_auth: ['john', 'doe']).
1028
+ to_return(status: 200, body: "", headers: {})
1029
+
1030
+ driver(config)
1031
+
1032
+ stub_opensearch("https://logs-test.google.com:777/os//_bulk")
1033
+ stub_opensearch_info("https://logs-test.google.com:777/os//")
1034
+ driver.run(default_tag: 'test') do
1035
+ driver.feed(sample_record)
1036
+ end
1037
+ end
1038
+
1039
+ data("legacy_template" => [true, "_template"],
1040
+ "new_template" => [false, "_index_template"])
1041
+ def test_template_overwrite(data)
1042
+ use_legacy_template_flag, endpoint = data
1043
+ cwd = File.dirname(__FILE__)
1044
+ template_file = if use_legacy_template_flag
1045
+ File.join(cwd, 'test_template.json')
1046
+ else
1047
+ File.join(cwd, 'test_index_template.json')
1048
+ end
1049
+
1050
+ config = %{
1051
+ host logs.google.com
1052
+ port 777
1053
+ scheme https
1054
+ path /os/
1055
+ user john
1056
+ password doe
1057
+ template_name logstash
1058
+ template_file #{template_file}
1059
+ template_overwrite true
1060
+ use_legacy_template #{use_legacy_template_flag}
1061
+ }
1062
+
1063
+ # connection start
1064
+ stub_request(:head, "https://logs.google.com:777/os//").
1065
+ with(basic_auth: ['john', 'doe']).
1066
+ to_return(:status => 200, :body => "", :headers => {})
1067
+ # check if template exists
1068
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash").
1069
+ with(basic_auth: ['john', 'doe']).
1070
+ to_return(:status => 200, :body => "", :headers => {})
1071
+ # creation
1072
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash").
1073
+ with(basic_auth: ['john', 'doe']).
1074
+ to_return(:status => 200, :body => "", :headers => {})
1075
+ stub_opensearch_info("https://logs.google.com:777/os//")
1076
+
1077
+ driver(config)
1078
+
1079
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash", times: 1)
1080
+ end
1081
+
1082
+ data("legacy_template" => [true, "_template"],
1083
+ "new_template" => [false, "_index_template"])
1084
+ def test_custom_template_overwrite(data)
1085
+ use_legacy_template_flag, endpoint = data
1086
+ cwd = File.dirname(__FILE__)
1087
+ template_file = if use_legacy_template_flag
1088
+ File.join(cwd, 'test_template.json')
1089
+ else
1090
+ File.join(cwd, 'test_index_template.json')
1091
+ end
1092
+
1093
+ config = %{
1094
+ host logs.google.com
1095
+ port 777
1096
+ scheme https
1097
+ path /os/
1098
+ user john
1099
+ password doe
1100
+ template_name myapp_alias_template
1101
+ template_file #{template_file}
1102
+ template_overwrite true
1103
+ customize_template {"--appid--": "myapp-logs","--index_prefix--":"mylogs"}
1104
+ use_legacy_template #{use_legacy_template_flag}
1105
+ }
1106
+
1107
+ # connection start
1108
+ stub_request(:head, "https://logs.google.com:777/os//").
1109
+ with(basic_auth: ['john', 'doe']).
1110
+ to_return(:status => 200, :body => "", :headers => {})
1111
+ # check if template exists
1112
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template").
1113
+ with(basic_auth: ['john', 'doe']).
1114
+ to_return(:status => 200, :body => "", :headers => {})
1115
+ # creation
1116
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template").
1117
+ with(basic_auth: ['john', 'doe']).
1118
+ to_return(:status => 200, :body => "", :headers => {})
1119
+ stub_opensearch_info("https://logs.google.com:777/os//")
1120
+
1121
+ driver(config)
1122
+
1123
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/myapp_alias_template", times: 1)
1124
+ end
1125
+
1126
+ def test_template_create_invalid_filename
1127
+ config = %{
1128
+ host logs.google.com
1129
+ port 777
1130
+ scheme https
1131
+ path /os/
1132
+ user john
1133
+ password doe
1134
+ template_name logstash
1135
+ template_file /abc123
1136
+ }
1137
+
1138
+ # connection start
1139
+ stub_request(:head, "https://logs.google.com:777/os//").
1140
+ with(basic_auth: ['john', 'doe']).
1141
+ to_return(:status => 200, :body => "", :headers => {})
1142
+ # check if template exists
1143
+ stub_request(:get, "https://logs.google.com:777/os//_template/logstash").
1144
+ with(basic_auth: ['john', 'doe']).
1145
+ to_return(:status => 404, :body => "", :headers => {})
1146
+ stub_opensearch_info("https://logs.google.com:777/os//")
1147
+
1148
+ assert_raise(RuntimeError) {
1149
+ driver(config)
1150
+ }
1151
+ end
1152
+
1153
+ data("legacy_template" => [true, "_template"],
1154
+ "new_template" => [false, "_index_template"])
1155
+ def test_template_create_for_host_placeholder(data)
1156
+ use_legacy_template_flag, endpoint = data
1157
+ cwd = File.dirname(__FILE__)
1158
+ template_file = if use_legacy_template_flag
1159
+ File.join(cwd, 'test_template.json')
1160
+ else
1161
+ File.join(cwd, 'test_index_template.json')
1162
+ end
1163
+
1164
+ config = %{
1165
+ host logs-${tag}.google.com
1166
+ port 777
1167
+ scheme https
1168
+ path /os/
1169
+ user john
1170
+ password doe
1171
+ template_name logstash
1172
+ template_file #{template_file}
1173
+ verify_os_version_at_startup false
1174
+ default_elasticsearch_version 6
1175
+ use_legacy_template #{use_legacy_template_flag}
1176
+ }
1177
+
1178
+ # connection start
1179
+ stub_request(:head, "https://logs-test.google.com:777/os//").
1180
+ with(basic_auth: ['john', 'doe']).
1181
+ to_return(:status => 200, :body => "", :headers => {})
1182
+ # check if template exists
1183
+ stub_request(:get, "https://logs-test.google.com:777/os//#{endpoint}/logstash").
1184
+ with(basic_auth: ['john', 'doe']).
1185
+ to_return(:status => 404, :body => "", :headers => {})
1186
+ stub_request(:put, "https://logs-test.google.com:777/os//#{endpoint}/logstash").
1187
+ with(basic_auth: ['john', 'doe']).
1188
+ to_return(status: 200, body: "", headers: {})
1189
+ stub_request(:post, "https://logs-test.google.com:777/os//_bulk").
1190
+ with(basic_auth: ['john', 'doe']).
1191
+ to_return(status: 200, body: "", headers: {})
1192
+
1193
+ driver(config)
1194
+
1195
+ stub_opensearch("https://logs-test.google.com:777/os//_bulk")
1196
+ stub_opensearch_info("https://logs-test.google.com:777/os//")
1197
+ driver.run(default_tag: 'test') do
1198
+ driver.feed(sample_record)
1199
+ end
1200
+ end
1201
+
1202
+ data("legacy_template" => [true, "_template"],
1203
+ "new_template" => [false, "_index_template"])
1204
+ def test_template_retry_install_fails(data)
1205
+ use_legacy_template_flag, endpoint = data
1206
+ cwd = File.dirname(__FILE__)
1207
+ template_file = if use_legacy_template_flag
1208
+ File.join(cwd, 'test_template.json')
1209
+ else
1210
+ File.join(cwd, 'test_index_template.json')
1211
+ end
1212
+
1213
+ config = %{
1214
+ host logs.google.com
1215
+ port 778
1216
+ scheme https
1217
+ path /os/
1218
+ user john
1219
+ password doe
1220
+ template_name logstash
1221
+ template_file #{template_file}
1222
+ max_retry_putting_template 3
1223
+ use_legacy_template #{use_legacy_template_flag}
1224
+ }
1225
+
1226
+ connection_resets = 0
1227
+ # check if template exists
1228
+ stub_request(:get, "https://logs.google.com:778/os//#{endpoint}/logstash")
1229
+ .with(basic_auth: ['john', 'doe']) do |req|
1230
+ connection_resets += 1
1231
+ raise Faraday::ConnectionFailed, "Test message"
1232
+ end
1233
+ stub_opensearch_info("https://logs.google.com:778/os//")
1234
+
1235
+ assert_raise(Fluent::Plugin::OpenSearchError::RetryableOperationExhaustedFailure) do
1236
+ driver(config)
1237
+ end
1238
+
1239
+ assert_equal(4, connection_resets)
1240
+ end
1241
+
1242
+ transport_errors_handled_separately = [OpenSearch::Transport::Transport::Errors::NotFound]
1243
+ transport_errors = OpenSearch::Transport::Transport::Errors.constants.map { |err| [err, OpenSearch::Transport::Transport::Errors.const_get(err)] }
1244
+ transport_errors_hash = Hash[transport_errors.select { |err| !transport_errors_handled_separately.include?(err[1]) } ]
1245
+
1246
+ data(transport_errors_hash)
1247
+ def test_template_retry_transport_errors(error)
1248
+ endpoint, use_legacy_template_flag = ["_index_template".freeze, false]
1249
+ cwd = File.dirname(__FILE__)
1250
+ template_file = File.join(cwd, 'test_index_template.json')
1251
+
1252
+ config = %{
1253
+ host logs.google.com
1254
+ port 778
1255
+ scheme https
1256
+ path /os/
1257
+ user john
1258
+ password doe
1259
+ template_name logstash
1260
+ template_file #{template_file}
1261
+ max_retry_putting_template 0
1262
+ use_legacy_template #{use_legacy_template_flag}
1263
+ }
1264
+
1265
+ retries = 0
1266
+ stub_request(:get, "https://logs.google.com:778/os//#{endpoint}/logstash")
1267
+ .with(basic_auth: ['john', 'doe']) do |req|
1268
+ retries += 1
1269
+ raise error
1270
+ end
1271
+ stub_opensearch_info("https://logs.google.com:778/os//")
1272
+
1273
+ assert_raise(Fluent::Plugin::OpenSearchError::RetryableOperationExhaustedFailure) do
1274
+ driver(config)
1275
+ end
1276
+
1277
+ assert_equal(1, retries)
1278
+ end
1279
+
1280
+ data("legacy_template" => [true, "_template"],
1281
+ "new_template" => [false, "_index_template"])
1282
+ def test_template_retry_install_does_not_fail(data)
1283
+ use_legacy_template_flag, endpoint = data
1284
+ cwd = File.dirname(__FILE__)
1285
+ template_file = if use_legacy_template_flag
1286
+ File.join(cwd, 'test_template.json')
1287
+ else
1288
+ File.join(cwd, 'test_index_template.json')
1289
+ end
1290
+
1291
+ config = %{
1292
+ host logs.google.com
1293
+ port 778
1294
+ scheme https
1295
+ path /os/
1296
+ user john
1297
+ password doe
1298
+ template_name logstash
1299
+ template_file #{template_file}
1300
+ max_retry_putting_template 3
1301
+ fail_on_putting_template_retry_exceed false
1302
+ use_legacy_template #{use_legacy_template_flag}
1303
+ }
1304
+
1305
+ connection_resets = 0
1306
+ # check if template exists
1307
+ stub_request(:get, "https://logs.google.com:778/os//#{endpoint}/logstash")
1308
+ .with(basic_auth: ['john', 'doe']) do |req|
1309
+ connection_resets += 1
1310
+ raise Faraday::ConnectionFailed, "Test message"
1311
+ end
1312
+ stub_opensearch_info("https://logs.google.com:778/os//")
1313
+
1314
+ driver(config)
1315
+
1316
+ assert_equal(4, connection_resets)
1317
+ end
1318
+
1319
+ data("legacy_template" => [true, "_template"],
1320
+ "new_template" => [false, "_index_template"])
1321
+ def test_templates_create(data)
1322
+ use_legacy_template_flag, endpoint = data
1323
+ cwd = File.dirname(__FILE__)
1324
+ template_file = if use_legacy_template_flag
1325
+ File.join(cwd, 'test_template.json')
1326
+ else
1327
+ File.join(cwd, 'test_index_template.json')
1328
+ end
1329
+
1330
+ config = %{
1331
+ host logs.google.com
1332
+ port 777
1333
+ scheme https
1334
+ path /os/
1335
+ user john
1336
+ password doe
1337
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}","logstash3":"#{template_file}" }
1338
+ use_legacy_template #{use_legacy_template_flag}
1339
+ }
1340
+
1341
+ stub_request(:head, "https://logs.google.com:777/os//").
1342
+ with(basic_auth: ['john', 'doe']).
1343
+ to_return(:status => 200, :body => "", :headers => {})
1344
+ # check if template exists
1345
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1346
+ with(basic_auth: ['john', 'doe']).
1347
+ to_return(:status => 404, :body => "", :headers => {})
1348
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1349
+ with(basic_auth: ['john', 'doe']).
1350
+ to_return(:status => 404, :body => "", :headers => {})
1351
+
1352
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash3").
1353
+ with(basic_auth: ['john', 'doe']).
1354
+ to_return(:status => 200, :body => "", :headers => {}) #exists
1355
+
1356
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1357
+ with(basic_auth: ['john', 'doe']).
1358
+ to_return(:status => 200, :body => "", :headers => {})
1359
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1360
+ with(basic_auth: ['john', 'doe']).
1361
+ to_return(:status => 200, :body => "", :headers => {})
1362
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash3").
1363
+ with(basic_auth: ['john', 'doe']).
1364
+ to_return(:status => 200, :body => "", :headers => {})
1365
+ stub_opensearch_info("https://logs.google.com:777/os//")
1366
+
1367
+ driver(config)
1368
+
1369
+ assert_requested( :put, "https://logs.google.com:777/os//#{endpoint}/logstash1", times: 1)
1370
+ assert_requested( :put, "https://logs.google.com:777/os//#{endpoint}/logstash2", times: 1)
1371
+ assert_not_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash3") #exists
1372
+ end
1373
+
1374
+ data("legacy_template" => [true, "_template"],
1375
+ "new_template" => [false, "_index_template"])
1376
+ def test_templates_overwrite(data)
1377
+ use_legacy_template_flag, endpoint = data
1378
+ cwd = File.dirname(__FILE__)
1379
+ template_file = if use_legacy_template_flag
1380
+ File.join(cwd, 'test_template.json')
1381
+ else
1382
+ File.join(cwd, 'test_index_template.json')
1383
+ end
1384
+
1385
+ config = %{
1386
+ host logs.google.com
1387
+ port 777
1388
+ scheme https
1389
+ path /os/
1390
+ user john
1391
+ password doe
1392
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}","logstash3":"#{template_file}" }
1393
+ template_overwrite true
1394
+ use_legacy_template #{use_legacy_template_flag}
1395
+ }
1396
+
1397
+ stub_request(:head, "https://logs.google.com:777/os//").
1398
+ with(basic_auth: ['john', 'doe']).
1399
+ to_return(:status => 200, :body => "", :headers => {})
1400
+ # check if template exists
1401
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1402
+ with(basic_auth: ['john', 'doe']).
1403
+ to_return(:status => 200, :body => "", :headers => {})
1404
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1405
+ with(basic_auth: ['john', 'doe']).
1406
+ to_return(:status => 200, :body => "", :headers => {})
1407
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash3").
1408
+ with(basic_auth: ['john', 'doe']).
1409
+ to_return(:status => 200, :body => "", :headers => {}) #exists
1410
+
1411
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1412
+ with(basic_auth: ['john', 'doe']).
1413
+ to_return(:status => 200, :body => "", :headers => {})
1414
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1415
+ with(basic_auth: ['john', 'doe']).
1416
+ to_return(:status => 200, :body => "", :headers => {})
1417
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash3").
1418
+ with(basic_auth: ['john', 'doe']).
1419
+ to_return(:status => 200, :body => "", :headers => {})
1420
+ stub_opensearch_info("https://logs.google.com:777/os//")
1421
+
1422
+ driver(config)
1423
+
1424
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1", times: 1)
1425
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2", times: 1)
1426
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash3", times: 1)
1427
+ end
1428
+
1429
+ data("legacy_template" => [true, "_template"],
1430
+ "new_template" => [false, "_index_template"])
1431
+ def test_templates_are_also_used(data)
1432
+ use_legacy_template_flag, endpoint = data
1433
+ cwd = File.dirname(__FILE__)
1434
+ template_file = if use_legacy_template_flag
1435
+ File.join(cwd, 'test_template.json')
1436
+ else
1437
+ File.join(cwd, 'test_index_template.json')
1438
+ end
1439
+
1440
+ config = %{
1441
+ host logs.google.com
1442
+ port 777
1443
+ scheme https
1444
+ path /os/
1445
+ user john
1446
+ password doe
1447
+ template_name logstash
1448
+ template_file #{template_file}
1449
+ templates {"logstash1":"#{template_file}", "logstash2":"#{template_file}" }
1450
+ use_legacy_template #{use_legacy_template_flag}
1451
+ }
1452
+ # connection start
1453
+ stub_request(:head, "https://logs.google.com:777/os//").
1454
+ with(basic_auth: ['john', 'doe']).
1455
+ to_return(:status => 200, :body => "", :headers => {})
1456
+ # check if template exists
1457
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash").
1458
+ with(basic_auth: ['john', 'doe']).
1459
+ to_return(:status => 404, :body => "", :headers => {})
1460
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1461
+ with(basic_auth: ['john', 'doe']).
1462
+ to_return(:status => 404, :body => "", :headers => {})
1463
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1464
+ with(basic_auth: ['john', 'doe']).
1465
+ to_return(:status => 404, :body => "", :headers => {})
1466
+ #creation
1467
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash").
1468
+ with(basic_auth: ['john', 'doe']).
1469
+ to_return(:status => 200, :body => "", :headers => {})
1470
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1471
+ with(basic_auth: ['john', 'doe']).
1472
+ to_return(:status => 200, :body => "", :headers => {})
1473
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1474
+ with(basic_auth: ['john', 'doe']).
1475
+ to_return(:status => 200, :body => "", :headers => {})
1476
+ stub_opensearch_info("https://logs.google.com:777/os//")
1477
+
1478
+ driver(config)
1479
+
1480
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash", times: 1)
1481
+
1482
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1")
1483
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2")
1484
+ end
1485
+
1486
+ data("legacy_template" => [true, "_template"],
1487
+ "new_template" => [false, "_index_template"])
1488
+ def test_templates_can_be_partially_created_if_error_occurs(data)
1489
+ use_legacy_template_flag, endpoint = data
1490
+ cwd = File.dirname(__FILE__)
1491
+ template_file = if use_legacy_template_flag
1492
+ File.join(cwd, 'test_template.json')
1493
+ else
1494
+ File.join(cwd, 'test_index_template.json')
1495
+ end
1496
+
1497
+ config = %{
1498
+ host logs.google.com
1499
+ port 777
1500
+ scheme https
1501
+ path /os/
1502
+ user john
1503
+ password doe
1504
+ templates {"logstash1":"#{template_file}", "logstash2":"/abc" }
1505
+ use_legacy_template #{use_legacy_template_flag}
1506
+ }
1507
+ stub_request(:head, "https://logs.google.com:777/os//").
1508
+ with(basic_auth: ['john', 'doe']).
1509
+ to_return(:status => 200, :body => "", :headers => {})
1510
+ # check if template exists
1511
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1512
+ with(basic_auth: ['john', 'doe']).
1513
+ to_return(:status => 404, :body => "", :headers => {})
1514
+ stub_request(:get, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1515
+ with(basic_auth: ['john', 'doe']).
1516
+ to_return(:status => 404, :body => "", :headers => {})
1517
+
1518
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1").
1519
+ with(basic_auth: ['john', 'doe']).
1520
+ to_return(:status => 200, :body => "", :headers => {})
1521
+ stub_request(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2").
1522
+ with(basic_auth: ['john', 'doe']).
1523
+ to_return(:status => 200, :body => "", :headers => {})
1524
+ stub_opensearch_info("https://logs.google.com:777/os//")
1525
+
1526
+ assert_raise(RuntimeError) {
1527
+ driver(config)
1528
+ }
1529
+
1530
+ assert_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash1", times: 1)
1531
+ assert_not_requested(:put, "https://logs.google.com:777/os//#{endpoint}/logstash2")
1532
+ end
1533
+
1534
+ def test_legacy_hosts_list
1535
+ config = %{
1536
+ hosts host1:50,host2:100,host3
1537
+ scheme https
1538
+ path /os/
1539
+ port 123
1540
+ }
1541
+ stub_opensearch_info("https://host1:50")
1542
+ stub_opensearch_info("https://host2:100")
1543
+ stub_opensearch_info("https://host3:123")
1544
+ instance = driver(config).instance
1545
+
1546
+ assert_equal 3, instance.get_connection_options[:hosts].length
1547
+ host1, host2, host3 = instance.get_connection_options[:hosts]
1548
+
1549
+ assert_equal 'host1', host1[:host]
1550
+ assert_equal 50, host1[:port]
1551
+ assert_equal 'https', host1[:scheme]
1552
+ assert_equal '/os/', host2[:path]
1553
+ assert_equal 'host3', host3[:host]
1554
+ assert_equal 123, host3[:port]
1555
+ assert_equal 'https', host3[:scheme]
1556
+ assert_equal '/os/', host3[:path]
1557
+ end
1558
+
1559
+ def test_hosts_list
1560
+ config = %{
1561
+ hosts https://john:password@host1:443/elastic/,http://host2
1562
+ path /default_path
1563
+ user default_user
1564
+ password default_password
1565
+ }
1566
+ stub_opensearch_info("https://john:password@host1:443/elastic/")
1567
+ stub_opensearch_info("http://host2")
1568
+ instance = driver(config).instance
1569
+
1570
+ assert_equal 2, instance.get_connection_options[:hosts].length
1571
+ host1, host2 = instance.get_connection_options[:hosts]
1572
+
1573
+ assert_equal 'host1', host1[:host]
1574
+ assert_equal 443, host1[:port]
1575
+ assert_equal 'https', host1[:scheme]
1576
+ assert_equal 'john', host1[:user]
1577
+ assert_equal 'password', host1[:password]
1578
+ assert_equal '/elastic/', host1[:path]
1579
+
1580
+ assert_equal 'host2', host2[:host]
1581
+ assert_equal 'http', host2[:scheme]
1582
+ assert_equal 'default_user', host2[:user]
1583
+ assert_equal 'default_password', host2[:password]
1584
+ assert_equal '/default_path', host2[:path]
1585
+ end
1586
+
1587
+ def test_hosts_list_with_escape_placeholders
1588
+ config = %{
1589
+ hosts https://%{j+hn}:%{passw@rd}@host1:443/elastic/,http://host2
1590
+ path /default_path
1591
+ user default_user
1592
+ password default_password
1593
+ }
1594
+ stub_opensearch_info("https://j%2Bhn:passw%40rd@host1:443/elastic/")
1595
+ stub_opensearch_info("http://host2")
1596
+
1597
+ instance = driver(config).instance
1598
+
1599
+ assert_equal 2, instance.get_connection_options[:hosts].length
1600
+ host1, host2 = instance.get_connection_options[:hosts]
1601
+
1602
+ assert_equal 'host1', host1[:host]
1603
+ assert_equal 443, host1[:port]
1604
+ assert_equal 'https', host1[:scheme]
1605
+ assert_equal 'j%2Bhn', host1[:user]
1606
+ assert_equal 'passw%40rd', host1[:password]
1607
+ assert_equal '/elastic/', host1[:path]
1608
+
1609
+ assert_equal 'host2', host2[:host]
1610
+ assert_equal 'http', host2[:scheme]
1611
+ assert_equal 'default_user', host2[:user]
1612
+ assert_equal 'default_password', host2[:password]
1613
+ assert_equal '/default_path', host2[:path]
1614
+ end
1615
+
1616
+ class IPv6AdressStringHostsTest < self
1617
+ def test_legacy_hosts_list
1618
+ config = %{
1619
+ hosts "[2404:7a80:d440:3000:192a:a292:bd7f:ca19]:50,host2:100,host3"
1620
+ scheme https
1621
+ path /os/
1622
+ port 123
1623
+ }
1624
+ instance = driver(config).instance
1625
+
1626
+ assert_raise(URI::InvalidURIError) do
1627
+ instance.get_connection_options[:hosts].length
1628
+ end
1629
+ end
1630
+
1631
+ def test_hosts_list
1632
+ config = %{
1633
+ hosts https://john:password@[2404:7a80:d440:3000:192a:a292:bd7f:ca19]:443/opensearch/,http://host2
1634
+ path /default_path
1635
+ user default_user
1636
+ password default_password
1637
+ }
1638
+ instance = driver(config).instance
1639
+
1640
+ assert_equal 2, instance.get_connection_options[:hosts].length
1641
+ host1, host2 = instance.get_connection_options[:hosts]
1642
+
1643
+ assert_equal '[2404:7a80:d440:3000:192a:a292:bd7f:ca19]', host1[:host]
1644
+ assert_equal 443, host1[:port]
1645
+ assert_equal 'https', host1[:scheme]
1646
+ assert_equal 'john', host1[:user]
1647
+ assert_equal 'password', host1[:password]
1648
+ assert_equal '/opensearch/', host1[:path]
1649
+
1650
+ assert_equal 'host2', host2[:host]
1651
+ assert_equal 'http', host2[:scheme]
1652
+ assert_equal 'default_user', host2[:user]
1653
+ assert_equal 'default_password', host2[:password]
1654
+ assert_equal '/default_path', host2[:path]
1655
+ end
1656
+
1657
+ def test_hosts_list_with_escape_placeholders
1658
+ config = %{
1659
+ hosts https://%{j+hn}:%{passw@rd}@[2404:7a80:d440:3000:192a:a292:bd7f:ca19]:443/opensearch/,http://host2
1660
+ path /default_path
1661
+ user default_user
1662
+ password default_password
1663
+ }
1664
+ instance = driver(config).instance
1665
+
1666
+ assert_equal 2, instance.get_connection_options[:hosts].length
1667
+ host1, host2 = instance.get_connection_options[:hosts]
1668
+
1669
+ assert_equal '[2404:7a80:d440:3000:192a:a292:bd7f:ca19]', host1[:host]
1670
+ assert_equal 443, host1[:port]
1671
+ assert_equal 'https', host1[:scheme]
1672
+ assert_equal 'j%2Bhn', host1[:user]
1673
+ assert_equal 'passw%40rd', host1[:password]
1674
+ assert_equal '/opensearch/', host1[:path]
1675
+
1676
+ assert_equal 'host2', host2[:host]
1677
+ assert_equal 'http', host2[:scheme]
1678
+ assert_equal 'default_user', host2[:user]
1679
+ assert_equal 'default_password', host2[:password]
1680
+ assert_equal '/default_path', host2[:path]
1681
+ end
1682
+ end
1683
+
1684
+ def test_single_host_params_and_defaults
1685
+ config = %{
1686
+ host logs.google.com
1687
+ user john
1688
+ password doe
1689
+ }
1690
+ instance = driver(config).instance
1691
+
1692
+ assert_equal 1, instance.get_connection_options[:hosts].length
1693
+ host1 = instance.get_connection_options[:hosts][0]
1694
+
1695
+ assert_equal 'logs.google.com', host1[:host]
1696
+ assert_equal 9200, host1[:port]
1697
+ assert_equal 'http', host1[:scheme]
1698
+ assert_equal 'john', host1[:user]
1699
+ assert_equal 'doe', host1[:password]
1700
+ assert_equal nil, host1[:path]
1701
+ end
1702
+
1703
+ def test_single_host_params_and_defaults_with_escape_placeholders
1704
+ config = %{
1705
+ host logs.google.com
1706
+ user %{j+hn}
1707
+ password %{d@e}
1708
+ }
1709
+ instance = driver(config).instance
1710
+
1711
+ assert_equal 1, instance.get_connection_options[:hosts].length
1712
+ host1 = instance.get_connection_options[:hosts][0]
1713
+
1714
+ assert_equal 'logs.google.com', host1[:host]
1715
+ assert_equal 9200, host1[:port]
1716
+ assert_equal 'http', host1[:scheme]
1717
+ assert_equal 'j%2Bhn', host1[:user]
1718
+ assert_equal 'd%40e', host1[:password]
1719
+ assert_equal nil, host1[:path]
1720
+ end
1721
+
1722
+ def test_host_and_port_are_ignored_if_specify_hosts
1723
+ config = %{
1724
+ host logs.google.com
1725
+ port 9200
1726
+ hosts host1:50,host2:100
1727
+ }
1728
+ instance = driver(config).instance
1729
+
1730
+ params = instance.get_connection_options[:hosts]
1731
+ hosts = params.map { |p| p[:host] }
1732
+ ports = params.map { |p| p[:port] }
1733
+ assert(hosts.none? { |h| h == 'logs.google.com' })
1734
+ assert(ports.none? { |p| p == 9200 })
1735
+ end
1736
+
1737
+ class IPv6AdressStringHostTest < self
1738
+ def test_single_host_params_and_defaults
1739
+ config = %{
1740
+ host 2404:7a80:d440:3000:192a:a292:bd7f:ca19
1741
+ user john
1742
+ password doe
1743
+ }
1744
+ instance = driver(config).instance
1745
+
1746
+ assert_equal 1, instance.get_connection_options[:hosts].length
1747
+ host1 = instance.get_connection_options[:hosts][0]
1748
+
1749
+ assert_equal '[2404:7a80:d440:3000:192a:a292:bd7f:ca19]', host1[:host]
1750
+ assert_equal 9200, host1[:port]
1751
+ assert_equal 'http', host1[:scheme]
1752
+ assert_equal 'john', host1[:user]
1753
+ assert_equal 'doe', host1[:password]
1754
+ assert_equal nil, host1[:path]
1755
+ end
1756
+
1757
+ def test_single_host_params_and_defaults_with_escape_placeholders
1758
+ config = %{
1759
+ host 2404:7a80:d440:3000:192a:a292:bd7f:ca19
1760
+ user %{j+hn}
1761
+ password %{d@e}
1762
+ }
1763
+ instance = driver(config).instance
1764
+
1765
+ assert_equal 1, instance.get_connection_options[:hosts].length
1766
+ host1 = instance.get_connection_options[:hosts][0]
1767
+
1768
+ assert_equal '[2404:7a80:d440:3000:192a:a292:bd7f:ca19]', host1[:host]
1769
+ assert_equal 9200, host1[:port]
1770
+ assert_equal 'http', host1[:scheme]
1771
+ assert_equal 'j%2Bhn', host1[:user]
1772
+ assert_equal 'd%40e', host1[:password]
1773
+ assert_equal nil, host1[:path]
1774
+ end
1775
+ end
1776
+
1777
+ def test_password_is_required_if_specify_user
1778
+ config = %{
1779
+ user john
1780
+ }
1781
+
1782
+ assert_raise(Fluent::ConfigError) do
1783
+ driver(config)
1784
+ end
1785
+ end
1786
+
1787
+ def test_content_type_header
1788
+ stub_request(:head, "http://localhost:9200/").
1789
+ to_return(:status => 200, :body => "", :headers => {})
1790
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1791
+ with(headers: { "Content-Type" => "application/x-ndjson" })
1792
+ stub_opensearch_info
1793
+ driver.run(default_tag: 'test') do
1794
+ driver.feed(sample_record)
1795
+ end
1796
+ assert_requested(elastic_request)
1797
+ end
1798
+
1799
+ def test_custom_headers
1800
+ stub_request(:head, "http://localhost:9200/").
1801
+ to_return(:status => 200, :body => "", :headers => {})
1802
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1803
+ with(headers: {'custom' => 'header1','and_others' => 'header2' })
1804
+ stub_opensearch_info
1805
+ driver.configure(%[custom_headers {"custom":"header1", "and_others":"header2"}])
1806
+ driver.run(default_tag: 'test') do
1807
+ driver.feed(sample_record)
1808
+ end
1809
+ assert_requested(elastic_request)
1810
+ end
1811
+
1812
+ def test_write_message_with_bad_chunk
1813
+ driver.configure("target_index_key bad_value\n@log_level debug\n")
1814
+ stub_opensearch
1815
+ stub_opensearch_info
1816
+ driver.run(default_tag: 'test') do
1817
+ driver.feed({'bad_value'=>"\255"})
1818
+ end
1819
+ error_log = driver.error_events.map {|e| e.last.message }
1820
+
1821
+ assert_logs_include(error_log, /(input string invalid)|(invalid byte sequence in UTF-8)/)
1822
+ end
1823
+
1824
+ data('OpenSearch 1' => [1, 'fluentd'],
1825
+ )
1826
+ def test_writes_to_default_index(data)
1827
+ version, index_name = data
1828
+ stub_opensearch
1829
+ stub_opensearch_info
1830
+ driver("", version)
1831
+ driver.run(default_tag: 'test') do
1832
+ driver.feed(sample_record)
1833
+ end
1834
+ assert_equal(index_name, index_cmds.first['index']['_index'])
1835
+ end
1836
+
1837
+ # gzip compress data
1838
+ def gzip(string, strategy)
1839
+ wio = StringIO.new("w")
1840
+ w_gz = Zlib::GzipWriter.new(wio, strategy = strategy)
1841
+ w_gz.write(string)
1842
+ w_gz.close
1843
+ wio.string
1844
+ end
1845
+
1846
+
1847
+ def test_writes_to_default_index_with_compression
1848
+ config = %[
1849
+ compression_level default_compression
1850
+ ]
1851
+
1852
+ bodystr = %({
1853
+ "took" : 500,
1854
+ "errors" : false,
1855
+ "items" : [
1856
+ {
1857
+ "create": {
1858
+ "_index" : "fluentd",
1859
+ "_type" : "fluentd"
1860
+ }
1861
+ }
1862
+ ]
1863
+ })
1864
+
1865
+ compressed_body = gzip(bodystr, Zlib::DEFAULT_COMPRESSION)
1866
+
1867
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
1868
+ to_return(:status => 200, :headers => {'Content-Type' => 'Application/json'}, :body => compressed_body)
1869
+ stub_opensearch_info("http://localhost:9200/")
1870
+
1871
+ driver(config)
1872
+ driver.run(default_tag: 'test') do
1873
+ driver.feed(sample_record)
1874
+ end
1875
+
1876
+ assert_requested(elastic_request)
1877
+ end
1878
+
1879
+ data('OpenSearch 1' => [1, Fluent::Plugin::OpenSearchOutput::DEFAULT_TYPE_NAME],
1880
+ )
1881
+ def test_writes_to_default_type(data)
1882
+ version, index_type = data
1883
+ stub_opensearch
1884
+ stub_opensearch_info
1885
+ driver("", version)
1886
+ driver.run(default_tag: 'test') do
1887
+ driver.feed(sample_record)
1888
+ end
1889
+ assert_equal(index_type, index_cmds.first['index']['_type'])
1890
+ end
1891
+
1892
+ def test_writes_to_speficied_index
1893
+ driver.configure("index_name myindex\n")
1894
+ stub_opensearch
1895
+ stub_opensearch_info
1896
+ driver.run(default_tag: 'test') do
1897
+ driver.feed(sample_record)
1898
+ end
1899
+ assert_equal('myindex', index_cmds.first['index']['_index'])
1900
+ end
1901
+
1902
+ def test_writes_with_huge_records
1903
+ driver.configure(Fluent::Config::Element.new(
1904
+ 'ROOT', '', {
1905
+ '@type' => 'opensearch',
1906
+ 'bulk_message_request_threshold' => 20 * 1024 * 1024,
1907
+ }, [
1908
+ Fluent::Config::Element.new('buffer', 'tag', {
1909
+ 'chunk_keys' => ['tag', 'time'],
1910
+ 'chunk_limit_size' => '64MB',
1911
+ }, [])
1912
+ ]
1913
+ ))
1914
+ request = stub_opensearch
1915
+ stub_opensearch_info
1916
+ driver.run(default_tag: 'test') do
1917
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1918
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1919
+ end
1920
+ assert_requested(request, times: 2)
1921
+ end
1922
+
1923
+ def test_writes_with_record_metadata
1924
+ chunk_id_key = "metadata_key".freeze
1925
+ driver.configure(Fluent::Config::Element.new(
1926
+ 'ROOT', '', {
1927
+ '@type' => 'opensearch',
1928
+ }, [
1929
+ Fluent::Config::Element.new('metadata', '', {
1930
+ 'include_chunk_id' => true,
1931
+ 'chunk_id_key' => chunk_id_key,
1932
+ }, [])
1933
+ ]
1934
+ ))
1935
+ stub_request(:post, "http://localhost:9200/_bulk").
1936
+ with(
1937
+ body: /{"index":{"_index":"fluentd","_type":"_doc"}}\n{"age":26,"request_id":"42","parent_id":"parent","routing_id":"routing","#{chunk_id_key}":".*"}\n/) do |req|
1938
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
1939
+ end
1940
+ stub_opensearch_info
1941
+ driver.run(default_tag: 'test', shutdown: false) do
1942
+ driver.feed(sample_record)
1943
+ end
1944
+ assert_true index_cmds[1].has_key?(chunk_id_key)
1945
+ first_chunk_id = index_cmds[1].fetch(chunk_id_key)
1946
+
1947
+ driver.run(default_tag: 'test') do
1948
+ driver.feed(sample_record)
1949
+ end
1950
+ assert_true index_cmds[1].has_key?(chunk_id_key)
1951
+ second_chunk_id = index_cmds[1].fetch(chunk_id_key)
1952
+ assert do
1953
+ first_chunk_id != second_chunk_id
1954
+ end
1955
+ end
1956
+
1957
+ def test_writes_with_huge_records_but_uncheck
1958
+ driver.configure(Fluent::Config::Element.new(
1959
+ 'ROOT', '', {
1960
+ '@type' => 'opensearch',
1961
+ 'bulk_message_request_threshold' => -1,
1962
+ }, [
1963
+ Fluent::Config::Element.new('buffer', 'tag', {
1964
+ 'chunk_keys' => ['tag', 'time'],
1965
+ 'chunk_limit_size' => '64MB',
1966
+ }, [])
1967
+ ]
1968
+ ))
1969
+ request = stub_opensearch
1970
+ stub_opensearch_info
1971
+ driver.run(default_tag: 'test') do
1972
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1973
+ driver.feed(sample_record('huge_record' => ("a" * 20 * 1024 * 1024)))
1974
+ end
1975
+ assert_false(driver.instance.split_request?({}, nil))
1976
+ assert_requested(request, times: 1)
1977
+ end
1978
+
1979
+ class IndexNamePlaceholdersTest < self
1980
+ def test_writes_to_speficied_index_with_tag_placeholder
1981
+ driver.configure("index_name myindex.${tag}\n")
1982
+ stub_opensearch
1983
+ stub_opensearch_info
1984
+ driver.run(default_tag: 'test') do
1985
+ driver.feed(sample_record)
1986
+ end
1987
+ assert_equal('myindex.test', index_cmds.first['index']['_index'])
1988
+ end
1989
+
1990
+ def test_writes_to_speficied_index_with_time_placeholder
1991
+ driver.configure(Fluent::Config::Element.new(
1992
+ 'ROOT', '', {
1993
+ '@type' => 'opensearch',
1994
+ 'index_name' => 'myindex.%Y.%m.%d',
1995
+ }, [
1996
+ Fluent::Config::Element.new('buffer', 'tag,time', {
1997
+ 'chunk_keys' => ['tag', 'time'],
1998
+ 'timekey' => 3600,
1999
+ }, [])
2000
+ ]
2001
+ ))
2002
+ stub_opensearch
2003
+ stub_opensearch_info
2004
+ time = Time.parse Date.today.iso8601
2005
+ driver.run(default_tag: 'test') do
2006
+ driver.feed(time.to_i, sample_record)
2007
+ end
2008
+ assert_equal("myindex.#{time.utc.strftime("%Y.%m.%d")}", index_cmds.first['index']['_index'])
2009
+ end
2010
+
2011
+ def test_writes_to_speficied_index_with_custom_key_placeholder
2012
+ driver.configure(Fluent::Config::Element.new(
2013
+ 'ROOT', '', {
2014
+ '@type' => 'opensearch',
2015
+ 'index_name' => 'myindex.${pipeline_id}',
2016
+ }, [
2017
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
2018
+ ]
2019
+ ))
2020
+ time = Time.parse Date.today.iso8601
2021
+ pipeline_id = "mypipeline"
2022
+ logstash_index = "myindex.#{pipeline_id}"
2023
+ stub_opensearch
2024
+ stub_opensearch_info
2025
+ driver.run(default_tag: 'test') do
2026
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
2027
+ end
2028
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2029
+ end
2030
+ end
2031
+
2032
+ def test_writes_to_speficied_index_uppercase
2033
+ driver.configure("index_name MyIndex\n")
2034
+ stub_opensearch
2035
+ stub_opensearch_info
2036
+ driver.run(default_tag: 'test') do
2037
+ driver.feed(sample_record)
2038
+ end
2039
+ # Allthough index_name has upper-case characters,
2040
+ # it should be set as lower-case when sent to elasticsearch.
2041
+ assert_equal('myindex', index_cmds.first['index']['_index'])
2042
+ end
2043
+
2044
+ def test_writes_to_target_index_key
2045
+ driver.configure("target_index_key @target_index\n")
2046
+ stub_opensearch
2047
+ stub_opensearch_info
2048
+ record = sample_record.clone
2049
+ driver.run(default_tag: 'test') do
2050
+ driver.feed(sample_record.merge('@target_index' => 'local-override'))
2051
+ end
2052
+ assert_equal('local-override', index_cmds.first['index']['_index'])
2053
+ assert_nil(index_cmds[1]['@target_index'])
2054
+ end
2055
+
2056
+ def test_writes_to_target_index_key_logstash
2057
+ driver.configure("target_index_key @target_index
2058
+ logstash_format true")
2059
+ time = Time.parse Date.today.iso8601
2060
+ stub_opensearch
2061
+ stub_opensearch_info
2062
+ driver.run(default_tag: 'test') do
2063
+ driver.feed(time.to_i, sample_record.merge('@target_index' => 'local-override'))
2064
+ end
2065
+ assert_equal('local-override', index_cmds.first['index']['_index'])
2066
+ end
2067
+
2068
+ def test_writes_to_target_index_key_logstash_uppercase
2069
+ driver.configure("target_index_key @target_index
2070
+ logstash_format true")
2071
+ time = Time.parse Date.today.iso8601
2072
+ stub_opensearch
2073
+ stub_opensearch_info
2074
+ driver.run(default_tag: 'test') do
2075
+ driver.feed(time.to_i, sample_record.merge('@target_index' => 'LOCAL-OVERRIDE'))
2076
+ end
2077
+ # Allthough @target_index has upper-case characters,
2078
+ # it should be set as lower-case when sent to elasticsearch.
2079
+ assert_equal('local-override', index_cmds.first['index']['_index'])
2080
+ end
2081
+
2082
+ def test_writes_to_default_index_with_pipeline
2083
+ pipeline = "fluentd"
2084
+ driver.configure("pipeline #{pipeline}")
2085
+ stub_opensearch
2086
+ stub_opensearch_info
2087
+ driver.run(default_tag: 'test') do
2088
+ driver.feed(sample_record)
2089
+ end
2090
+ assert_equal(pipeline, index_cmds.first['index']['pipeline'])
2091
+ end
2092
+
2093
+ def stub_opensearch_affinity_target_index_search_with_body(url="http://localhost:9200/logstash-*/_search", ids, return_body_str)
2094
+ # Note: ids used in query is unique list of ids
2095
+ stub_request(:post, url)
2096
+ .with(
2097
+ body: "{\"query\":{\"ids\":{\"values\":#{ids.uniq.to_json}}},\"_source\":false,\"sort\":[{\"_index\":{\"order\":\"desc\"}}]}",
2098
+ )
2099
+ .to_return(lambda do |req|
2100
+ { :status => 200,
2101
+ :headers => { 'Content-Type' => 'json' },
2102
+ :body => return_body_str
2103
+ }
2104
+ end)
2105
+ end
2106
+
2107
+ def stub_opensearch_affinity_target_index_search(url="http://localhost:9200/logstash-*/_search", ids, indices)
2108
+ # Example ids and indices arrays.
2109
+ # [ "3408a2c8eecd4fbfb82e45012b54fa82", "2816fc6ef4524b3f8f7e869002005433"]
2110
+ # [ "logstash-2021.04.28", "logstash-2021.04.29"]
2111
+ body = %({
2112
+ "took" : 31,
2113
+ "timed_out" : false,
2114
+ "_shards" : {
2115
+ "total" : 52,
2116
+ "successful" : 52,
2117
+ "skipped" : 48,
2118
+ "failed" : 0
2119
+ },
2120
+ "hits" : {
2121
+ "total" : {
2122
+ "value" : 356,
2123
+ "relation" : "eq"
2124
+ },
2125
+ "max_score" : null,
2126
+ "hits" : [
2127
+ {
2128
+ "_index" : "#{indices[0]}",
2129
+ "_type" : "_doc",
2130
+ "_id" : "#{ids[0]}",
2131
+ "_score" : null,
2132
+ "sort" : [
2133
+ "#{indices[0]}"
2134
+ ]
2135
+ },
2136
+ {
2137
+ "_index" : "#{indices[1]}",
2138
+ "_type" : "_doc",
2139
+ "_id" : "#{ids[1]}",
2140
+ "_score" : null,
2141
+ "sort" : [
2142
+ "#{indices[1]}"
2143
+ ]
2144
+ }
2145
+ ]
2146
+ }
2147
+ })
2148
+ stub_opensearch_affinity_target_index_search_with_body(ids, body)
2149
+ end
2150
+
2151
+ def stub_opensearch_affinity_target_index_search_return_empty(url="http://localhost:9200/logstash-*/_search", ids)
2152
+ empty_body = %({
2153
+ "took" : 5,
2154
+ "timed_out" : false,
2155
+ "_shards" : {
2156
+ "total" : 54,
2157
+ "successful" : 54,
2158
+ "skipped" : 53,
2159
+ "failed" : 0
2160
+ },
2161
+ "hits" : {
2162
+ "total" : {
2163
+ "value" : 0,
2164
+ "relation" : "eq"
2165
+ },
2166
+ "max_score" : null,
2167
+ "hits" : [ ]
2168
+ }
2169
+ })
2170
+ stub_opensearch_affinity_target_index_search_with_body(ids, empty_body)
2171
+ end
2172
+
2173
+ def test_writes_to_affinity_target_index
2174
+ driver.configure("target_index_affinity true
2175
+ logstash_format true
2176
+ id_key my_id
2177
+ write_operation update")
2178
+
2179
+ my_id_value = "3408a2c8eecd4fbfb82e45012b54fa82"
2180
+ ids = [my_id_value]
2181
+ indices = ["logstash-2021.04.28"]
2182
+ stub_opensearch
2183
+ stub_opensearch_info
2184
+ stub_opensearch_affinity_target_index_search(ids, indices)
2185
+ driver.run(default_tag: 'test') do
2186
+ driver.feed(sample_record('my_id' => my_id_value))
2187
+ end
2188
+ assert_equal('logstash-2021.04.28', index_cmds.first['update']['_index'])
2189
+ end
2190
+
2191
+ def test_writes_to_affinity_target_index_write_operation_upsert
2192
+ driver.configure("target_index_affinity true
2193
+ logstash_format true
2194
+ id_key my_id
2195
+ write_operation upsert")
2196
+
2197
+ my_id_value = "3408a2c8eecd4fbfb82e45012b54fa82"
2198
+ ids = [my_id_value]
2199
+ indices = ["logstash-2021.04.28"]
2200
+ stub_opensearch
2201
+ stub_opensearch_info
2202
+ stub_opensearch_affinity_target_index_search(ids, indices)
2203
+ driver.run(default_tag: 'test') do
2204
+ driver.feed(sample_record('my_id' => my_id_value))
2205
+ end
2206
+ assert_equal('logstash-2021.04.28', index_cmds.first['update']['_index'])
2207
+ end
2208
+
2209
+ def test_writes_to_affinity_target_index_index_not_exists_yet
2210
+ driver.configure("target_index_affinity true
2211
+ logstash_format true
2212
+ id_key my_id
2213
+ write_operation update")
2214
+
2215
+ my_id_value = "3408a2c8eecd4fbfb82e45012b54fa82"
2216
+ ids = [my_id_value]
2217
+ stub_opensearch
2218
+ stub_opensearch_info
2219
+ stub_opensearch_affinity_target_index_search_return_empty(ids)
2220
+ time = Time.parse Date.today.iso8601
2221
+ driver.run(default_tag: 'test') do
2222
+ driver.feed(time.to_i, sample_record('my_id' => my_id_value))
2223
+ end
2224
+ assert_equal("logstash-#{time.utc.strftime("%Y.%m.%d")}", index_cmds.first['update']['_index'])
2225
+ end
2226
+
2227
+ def test_writes_to_affinity_target_index_multiple_indices
2228
+ driver.configure("target_index_affinity true
2229
+ logstash_format true
2230
+ id_key my_id
2231
+ write_operation update")
2232
+
2233
+ my_id_value = "2816fc6ef4524b3f8f7e869002005433"
2234
+ my_id_value2 = "3408a2c8eecd4fbfb82e45012b54fa82"
2235
+ ids = [my_id_value, my_id_value2]
2236
+ indices = ["logstash-2021.04.29", "logstash-2021.04.28"]
2237
+ stub_opensearch_info
2238
+ stub_opensearch_all_requests
2239
+ stub_opensearch_affinity_target_index_search(ids, indices)
2240
+ driver.run(default_tag: 'test') do
2241
+ driver.feed(sample_record('my_id' => my_id_value))
2242
+ driver.feed(sample_record('my_id' => my_id_value2))
2243
+ end
2244
+ assert_equal(2, index_cmds_all_requests.count)
2245
+ assert_equal('logstash-2021.04.29', index_cmds_all_requests[0].first['update']['_index'])
2246
+ assert_equal(my_id_value, index_cmds_all_requests[0].first['update']['_id'])
2247
+ assert_equal('logstash-2021.04.28', index_cmds_all_requests[1].first['update']['_index'])
2248
+ assert_equal(my_id_value2, index_cmds_all_requests[1].first['update']['_id'])
2249
+ end
2250
+
2251
+ def test_writes_to_affinity_target_index_same_id_dublicated_write_to_oldest_index
2252
+ driver.configure("target_index_affinity true
2253
+ logstash_format true
2254
+ id_key my_id
2255
+ write_operation update")
2256
+
2257
+ my_id_value = "2816fc6ef4524b3f8f7e869002005433"
2258
+ # It may happen than same id has inserted to two index while data inserted during rollover period
2259
+ ids = [my_id_value, my_id_value]
2260
+ # Simulate the used sorting here, as search sorts indices in DESC order to pick only oldest index per single _id
2261
+ indices = ["logstash-2021.04.29", "logstash-2021.04.28"]
2262
+
2263
+ stub_opensearch_info
2264
+ stub_opensearch_all_requests
2265
+ stub_opensearch_affinity_target_index_search(ids, indices)
2266
+ driver.run(default_tag: 'test') do
2267
+ driver.feed(sample_record('my_id' => my_id_value))
2268
+ driver.feed(sample_record('my_id' => my_id_value))
2269
+ end
2270
+ assert_equal('logstash-2021.04.28', index_cmds.first['update']['_index'])
2271
+
2272
+ assert_equal(1, index_cmds_all_requests.count)
2273
+ assert_equal('logstash-2021.04.28', index_cmds_all_requests[0].first['update']['_index'])
2274
+ assert_equal(my_id_value, index_cmds_all_requests[0].first['update']['_id'])
2275
+ end
2276
+
2277
+ class PipelinePlaceholdersTest < self
2278
+ def test_writes_to_default_index_with_pipeline_tag_placeholder
2279
+ pipeline = "fluentd-${tag}"
2280
+ driver.configure("pipeline #{pipeline}")
2281
+ stub_opensearch
2282
+ stub_opensearch_info
2283
+ driver.run(default_tag: 'test.builtin.placeholder') do
2284
+ driver.feed(sample_record)
2285
+ end
2286
+ assert_equal("fluentd-test.builtin.placeholder", index_cmds.first['index']['pipeline'])
2287
+ end
2288
+
2289
+ def test_writes_to_default_index_with_pipeline_time_placeholder
2290
+ driver.configure(Fluent::Config::Element.new(
2291
+ 'ROOT', '', {
2292
+ '@type' => 'elasticsearch',
2293
+ 'pipeline' => 'fluentd-%Y%m%d',
2294
+ }, [
2295
+ Fluent::Config::Element.new('buffer', 'tag,time', {
2296
+ 'chunk_keys' => ['tag', 'time'],
2297
+ 'timekey' => 3600,
2298
+ }, [])
2299
+ ]
2300
+ ))
2301
+ time = Time.parse Date.today.iso8601
2302
+ pipeline = "fluentd-#{time.getutc.strftime("%Y%m%d")}"
2303
+ stub_opensearch
2304
+ stub_opensearch_info
2305
+ driver.run(default_tag: 'test') do
2306
+ driver.feed(time.to_i, sample_record)
2307
+ end
2308
+ assert_equal(pipeline, index_cmds.first['index']['pipeline'])
2309
+ end
2310
+
2311
+ def test_writes_to_default_index_with_pipeline_custom_key_placeholder
2312
+ driver.configure(Fluent::Config::Element.new(
2313
+ 'ROOT', '', {
2314
+ '@type' => 'elasticsearch',
2315
+ 'pipeline' => 'fluentd-${pipeline_id}',
2316
+ }, [
2317
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
2318
+ ]
2319
+ ))
2320
+ time = Time.parse Date.today.iso8601
2321
+ pipeline_id = "mypipeline"
2322
+ logstash_index = "fluentd-#{pipeline_id}"
2323
+ stub_opensearch
2324
+ stub_opensearch_info
2325
+ driver.run(default_tag: 'test') do
2326
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
2327
+ end
2328
+ assert_equal(logstash_index, index_cmds.first['index']['pipeline'])
2329
+ end
2330
+ end
2331
+
2332
+ def test_writes_to_target_index_key_fallack
2333
+ driver.configure("target_index_key @target_index\n")
2334
+ stub_opensearch
2335
+ stub_opensearch_info
2336
+ driver.run(default_tag: 'test') do
2337
+ driver.feed(sample_record)
2338
+ end
2339
+ assert_equal('fluentd', index_cmds.first['index']['_index'])
2340
+ end
2341
+
2342
+ def test_writes_to_target_index_key_fallack_logstash
2343
+ driver.configure("target_index_key @target_index\n
2344
+ logstash_format true")
2345
+ time = Time.parse Date.today.iso8601
2346
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m.%d")}"
2347
+ stub_opensearch
2348
+ stub_opensearch_info
2349
+ driver.run(default_tag: 'test') do
2350
+ driver.feed(time.to_i, sample_record)
2351
+ end
2352
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2353
+ end
2354
+
2355
+ data(
2356
+ "OpenSearch default" => {"os_version" => 1, "_type" => "_doc", "suppress_type" => false},
2357
+ "Suppressed type" => {"os_version" => 1, "_type" => nil, "suppress_type" => true},
2358
+ "OpenSearch 2" => {"os_version" => 2, "_type" => nil, "suppress_type" => true},
2359
+ )
2360
+ def test_writes_to_speficied_type(data)
2361
+ driver('', data["os_version"]).configure("suppress_type_name #{data['suppress_type']}")
2362
+ stub_opensearch
2363
+ stub_opensearch_info
2364
+ driver.run(default_tag: 'test') do
2365
+ driver.feed(sample_record)
2366
+ end
2367
+ if data["suppress_type"] || data["os_version"] >= 2
2368
+ assert_false(index_cmds.first['index'].has_key?("_type"))
2369
+ else
2370
+ assert_true(index_cmds.first['index'].has_key?("_type"))
2371
+ assert_equal(data['_type'], index_cmds.first['index']['_type'])
2372
+ end
2373
+ end
2374
+
2375
+ def test_writes_to_speficied_host
2376
+ driver.configure("host 192.168.33.50\n")
2377
+ elastic_request = stub_opensearch("http://192.168.33.50:9200/_bulk")
2378
+ stub_opensearch_info("http://192.168.33.50:9200/")
2379
+ driver.run(default_tag: 'test') do
2380
+ driver.feed(sample_record)
2381
+ end
2382
+ assert_requested(elastic_request)
2383
+ end
2384
+
2385
+ def test_writes_to_speficied_port
2386
+ driver.configure("port 9201\n")
2387
+ elastic_request = stub_opensearch("http://localhost:9201/_bulk")
2388
+ stub_opensearch_info("http://localhost:9201")
2389
+ driver.run(default_tag: 'test') do
2390
+ driver.feed(sample_record)
2391
+ end
2392
+ assert_requested(elastic_request)
2393
+ end
2394
+
2395
+ def test_writes_to_multi_hosts
2396
+ hosts = [['192.168.33.50', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
2397
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
2398
+
2399
+ driver.configure("hosts #{hosts_string}")
2400
+
2401
+ hosts.each do |host_info|
2402
+ host, port = host_info
2403
+ stub_opensearch_with_store_index_command_counts("http://#{host}:#{port}/_bulk")
2404
+ stub_opensearch_info("http://#{host}:#{port}/")
2405
+ end
2406
+
2407
+ driver.run(default_tag: 'test') do
2408
+ 1000.times do
2409
+ driver.feed(sample_record.merge('age'=>rand(100)))
2410
+ end
2411
+ end
2412
+
2413
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
2414
+ # it's Fluentd test driver's constraint
2415
+ # so @index_command_counts.size is always 1
2416
+
2417
+ assert(@index_command_counts.size > 0, "not working with hosts options")
2418
+
2419
+ total = 0
2420
+ @index_command_counts.each do |url, count|
2421
+ total += count
2422
+ end
2423
+ assert_equal(2000, total)
2424
+ end
2425
+
2426
+ def test_nested_record_with_flattening_on
2427
+ driver.configure("flatten_hashes true
2428
+ flatten_hashes_separator |")
2429
+
2430
+ original_hash = {"foo" => {"bar" => "baz"}, "people" => [
2431
+ {"age" => "25", "height" => "1ft"},
2432
+ {"age" => "30", "height" => "2ft"}
2433
+ ]}
2434
+
2435
+ expected_output = {"foo|bar"=>"baz", "people" => [
2436
+ {"age" => "25", "height" => "1ft"},
2437
+ {"age" => "30", "height" => "2ft"}
2438
+ ]}
2439
+
2440
+ stub_opensearch
2441
+ stub_opensearch_info
2442
+ driver.run(default_tag: 'test') do
2443
+ driver.feed(original_hash)
2444
+ end
2445
+ assert_equal expected_output, index_cmds[1]
2446
+ end
2447
+
2448
+ def test_nested_record_with_flattening_off
2449
+ # flattening off by default
2450
+
2451
+ original_hash = {"foo" => {"bar" => "baz"}}
2452
+ expected_output = {"foo" => {"bar" => "baz"}}
2453
+
2454
+ stub_opensearch
2455
+ stub_opensearch_info
2456
+ driver.run(default_tag: 'test') do
2457
+ driver.feed(original_hash)
2458
+ end
2459
+ assert_equal expected_output, index_cmds[1]
2460
+ end
2461
+
2462
+ def test_makes_bulk_request
2463
+ stub_opensearch
2464
+ stub_opensearch_info
2465
+ driver.run(default_tag: 'test') do
2466
+ driver.feed(sample_record)
2467
+ driver.feed(sample_record.merge('age' => 27))
2468
+ end
2469
+ assert_equal(4, index_cmds.count)
2470
+ end
2471
+
2472
+ def test_all_re
2473
+ stub_opensearch
2474
+ stub_opensearch_info
2475
+ driver.run(default_tag: 'test') do
2476
+ driver.feed(sample_record)
2477
+ driver.feed(sample_record.merge('age' => 27))
2478
+ end
2479
+ assert_equal(26, index_cmds[1]['age'])
2480
+ assert_equal(27, index_cmds[3]['age'])
2481
+ end
2482
+
2483
+ def test_writes_to_logstash_index
2484
+ driver.configure("logstash_format true\n")
2485
+ #
2486
+ # This is 1 second past midnight in BST, so the UTC index should be the day before
2487
+ dt = DateTime.new(2015, 6, 1, 0, 0, 1, "+01:00")
2488
+ logstash_index = "logstash-2015.05.31"
2489
+ stub_opensearch
2490
+ stub_opensearch_info
2491
+ driver.run(default_tag: 'test') do
2492
+ driver.feed(dt.to_time.to_i, sample_record)
2493
+ end
2494
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2495
+ end
2496
+
2497
+ def test_writes_to_logstash_non_utc_index
2498
+ driver.configure("logstash_format true
2499
+ utc_index false")
2500
+ # When using `utc_index false` the index time will be the local day of
2501
+ # ingestion time
2502
+ time = Date.today.to_time
2503
+ index = "logstash-#{time.strftime("%Y.%m.%d")}"
2504
+ stub_opensearch
2505
+ stub_opensearch_info
2506
+ driver.run(default_tag: 'test') do
2507
+ driver.feed(time.to_i, sample_record)
2508
+ end
2509
+ assert_equal(index, index_cmds.first['index']['_index'])
2510
+ end
2511
+
2512
+ def test_writes_to_logstash_index_with_specified_prefix
2513
+ driver.configure("logstash_format true
2514
+ logstash_prefix myprefix")
2515
+ time = Time.parse Date.today.iso8601
2516
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
2517
+ stub_opensearch
2518
+ stub_opensearch_info
2519
+ driver.run(default_tag: 'test') do
2520
+ driver.feed(time.to_i, sample_record)
2521
+ end
2522
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2523
+ end
2524
+
2525
+ def test_writes_to_logstash_index_with_specified_prefix_and_separator
2526
+ separator = '_'
2527
+ driver.configure("logstash_format true
2528
+ logstash_prefix_separator #{separator}
2529
+ logstash_prefix myprefix")
2530
+ time = Time.parse Date.today.iso8601
2531
+ logstash_index = "myprefix#{separator}#{time.getutc.strftime("%Y.%m.%d")}"
2532
+ stub_opensearch
2533
+ stub_opensearch_info
2534
+ driver.run(default_tag: 'test') do
2535
+ driver.feed(time.to_i, sample_record)
2536
+ end
2537
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2538
+ end
2539
+
2540
+ class LogStashPrefixPlaceholdersTest < self
2541
+ def test_writes_to_logstash_index_with_specified_prefix_and_tag_placeholder
2542
+ driver.configure("logstash_format true
2543
+ logstash_prefix myprefix-${tag}")
2544
+ time = Time.parse Date.today.iso8601
2545
+ logstash_index = "myprefix-test-#{time.getutc.strftime("%Y.%m.%d")}"
2546
+ stub_opensearch
2547
+ stub_opensearch_info
2548
+ driver.run(default_tag: 'test') do
2549
+ driver.feed(time.to_i, sample_record)
2550
+ end
2551
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2552
+ end
2553
+
2554
+ def test_writes_to_logstash_index_with_specified_prefix_and_time_placeholder
2555
+ driver.configure(Fluent::Config::Element.new(
2556
+ 'ROOT', '', {
2557
+ '@type' => 'opensearch',
2558
+ 'logstash_format' => true,
2559
+ 'logstash_prefix' => 'myprefix-%H',
2560
+ }, [
2561
+ Fluent::Config::Element.new('buffer', 'tag,time', {
2562
+ 'chunk_keys' => ['tag', 'time'],
2563
+ 'timekey' => 3600,
2564
+ }, [])
2565
+ ]
2566
+ ))
2567
+ time = Time.parse Date.today.iso8601
2568
+ logstash_index = "myprefix-#{time.getutc.strftime("%H")}-#{time.getutc.strftime("%Y.%m.%d")}"
2569
+ stub_opensearch
2570
+ stub_opensearch_info
2571
+ driver.run(default_tag: 'test') do
2572
+ driver.feed(time.to_i, sample_record)
2573
+ end
2574
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2575
+ end
2576
+
2577
+ def test_writes_to_logstash_index_with_specified_prefix_and_custom_key_placeholder
2578
+ driver.configure(Fluent::Config::Element.new(
2579
+ 'ROOT', '', {
2580
+ '@type' => 'opensearch',
2581
+ 'logstash_format' => true,
2582
+ 'logstash_prefix' => 'myprefix-${pipeline_id}',
2583
+ }, [
2584
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
2585
+ ]
2586
+ ))
2587
+ time = Time.parse Date.today.iso8601
2588
+ pipeline_id = "mypipeline"
2589
+ logstash_index = "myprefix-#{pipeline_id}-#{time.getutc.strftime("%Y.%m.%d")}"
2590
+ stub_opensearch
2591
+ stub_opensearch_info
2592
+ driver.run(default_tag: 'test') do
2593
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
2594
+ end
2595
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2596
+ end
2597
+ end
2598
+
2599
+ class LogStashDateformatPlaceholdersTest < self
2600
+ def test_writes_to_logstash_index_with_specified_prefix_and_dateformat_placeholder_pattern_1
2601
+ driver.configure(Fluent::Config::Element.new(
2602
+ 'ROOT', '', {
2603
+ '@type' => 'opensearch',
2604
+ 'logstash_format' => true,
2605
+ 'logstash_dateformat' => '${indexformat}',
2606
+ 'logstash_prefix' => 'myprefix',
2607
+ }, [
2608
+ Fluent::Config::Element.new('buffer', 'tag,time,indexformat', {
2609
+ 'chunk_keys' => ['tag', 'time', 'indexformat'],
2610
+ 'timekey' => 3600,
2611
+ }, [])
2612
+ ]
2613
+ ))
2614
+ time = Time.parse Date.today.iso8601
2615
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
2616
+ stub_opensearch
2617
+ stub_opensearch_info
2618
+ driver.run(default_tag: 'test') do
2619
+ driver.feed(time.to_i, sample_record.merge('indexformat' => '%Y.%m.%d'))
2620
+ end
2621
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2622
+ end
2623
+
2624
+ def test_writes_to_logstash_index_with_specified_prefix_and_dateformat_placeholder_pattern_2
2625
+ driver.configure(Fluent::Config::Element.new(
2626
+ 'ROOT', '', {
2627
+ '@type' => 'opensearch',
2628
+ 'logstash_format' => true,
2629
+ 'logstash_dateformat' => '${indexformat}',
2630
+ 'logstash_prefix' => 'myprefix',
2631
+ }, [
2632
+ Fluent::Config::Element.new('buffer', 'tag,time,indexformat', {
2633
+ 'chunk_keys' => ['tag', 'time', 'indexformat'],
2634
+ 'timekey' => 3600,
2635
+ }, [])
2636
+ ]
2637
+ ))
2638
+ time = Time.parse Date.today.iso8601
2639
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m")}"
2640
+ stub_opensearch
2641
+ stub_opensearch_info
2642
+ driver.run(default_tag: 'test') do
2643
+ driver.feed(time.to_i, sample_record.merge('indexformat' => '%Y.%m'))
2644
+ end
2645
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2646
+ end
2647
+ end
2648
+
2649
+ class HostnamePlaceholders < self
2650
+ def test_writes_to_extracted_host
2651
+ driver.configure("host ${tag}\n")
2652
+ time = Time.parse Date.today.iso8601
2653
+ elastic_request = stub_opensearch("http://extracted-host:9200/_bulk")
2654
+ stub_opensearch_info("http://extracted-host:9200/")
2655
+ driver.run(default_tag: 'extracted-host') do
2656
+ driver.feed(time.to_i, sample_record)
2657
+ end
2658
+ assert_requested(elastic_request)
2659
+ end
2660
+
2661
+ def test_writes_to_multi_hosts_with_placeholders
2662
+ hosts = [['${tag}', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
2663
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
2664
+
2665
+ driver.configure("hosts #{hosts_string}")
2666
+
2667
+ hosts.each do |host_info|
2668
+ host, port = host_info
2669
+ host = "extracted-host" if host == '${tag}'
2670
+ stub_opensearch_with_store_index_command_counts("http://#{host}:#{port}/_bulk")
2671
+ stub_opensearch_info("http://#{host}:#{port}")
2672
+ end
2673
+
2674
+ driver.run(default_tag: 'extracted-host') do
2675
+ 1000.times do
2676
+ driver.feed(sample_record.merge('age'=>rand(100)))
2677
+ end
2678
+ end
2679
+
2680
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
2681
+ # it's Fluentd test driver's constraint
2682
+ # so @index_command_counts.size is always 1
2683
+
2684
+ assert(@index_command_counts.size > 0, "not working with hosts options")
2685
+
2686
+ total = 0
2687
+ @index_command_counts.each do |url, count|
2688
+ total += count
2689
+ end
2690
+ assert_equal(2000, total)
2691
+ end
2692
+
2693
+ def test_writes_to_extracted_host_with_time_placeholder
2694
+ driver.configure(Fluent::Config::Element.new(
2695
+ 'ROOT', '', {
2696
+ '@type' => 'elasticsearch',
2697
+ 'host' => 'host-%Y%m%d',
2698
+ }, [
2699
+ Fluent::Config::Element.new('buffer', 'tag,time', {
2700
+ 'chunk_keys' => ['tag', 'time'],
2701
+ 'timekey' => 3600,
2702
+ }, [])
2703
+ ]
2704
+ ))
2705
+ stub_opensearch
2706
+ stub_opensearch_info
2707
+ time = Time.parse Date.today.iso8601
2708
+ elastic_request = stub_opensearch("http://host-#{time.utc.strftime('%Y%m%d')}:9200/_bulk")
2709
+ stub_opensearch_info("http://host-#{time.utc.strftime('%Y%m%d')}:9200/")
2710
+ driver.run(default_tag: 'test') do
2711
+ driver.feed(time.to_i, sample_record)
2712
+ end
2713
+ assert_requested(elastic_request)
2714
+ end
2715
+
2716
+ def test_writes_to_extracted_host_with_custom_key_placeholder
2717
+ driver.configure(Fluent::Config::Element.new(
2718
+ 'ROOT', '', {
2719
+ '@type' => 'opensearch',
2720
+ 'host' => 'myhost-${pipeline_id}',
2721
+ }, [
2722
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
2723
+ ]
2724
+ ))
2725
+ time = Time.parse Date.today.iso8601
2726
+ first_pipeline_id = "1"
2727
+ second_pipeline_id = "2"
2728
+ first_request = stub_opensearch("http://myhost-1:9200/_bulk")
2729
+ second_request = stub_opensearch("http://myhost-2:9200/_bulk")
2730
+ stub_opensearch_info("http://myhost-1:9200/")
2731
+ stub_opensearch_info("http://myhost-2:9200/")
2732
+ driver.run(default_tag: 'test') do
2733
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => first_pipeline_id}))
2734
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => second_pipeline_id}))
2735
+ end
2736
+ assert_requested(first_request)
2737
+ assert_requested(second_request)
2738
+ end
2739
+
2740
+ def test_writes_to_extracted_host_with_placeholder_replaced_in_exception_message
2741
+ driver.configure(Fluent::Config::Element.new(
2742
+ 'ROOT', '', {
2743
+ '@type' => 'opensearch',
2744
+ 'host' => 'myhost-${pipeline_id}',
2745
+ }, [
2746
+ Fluent::Config::Element.new('buffer', 'tag,pipeline_id', {}, [])
2747
+ ]
2748
+ ))
2749
+ time = Time.parse Date.today.iso8601
2750
+ pipeline_id = "1"
2751
+ request = stub_opensearch_unavailable("http://myhost-1:9200/_bulk")
2752
+ stub_opensearch_info("http://myhost-1:9200/")
2753
+ exception = assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
2754
+ driver.run(default_tag: 'test') do
2755
+ driver.feed(time.to_i, sample_record.merge({"pipeline_id" => pipeline_id}))
2756
+ end
2757
+ }
2758
+ assert_equal("could not push logs to OpenSearch cluster ({:host=>\"myhost-1\", :port=>9200, :scheme=>\"http\"}): [503] ", exception.message)
2759
+ end
2760
+ end
2761
+
2762
+ def test_writes_to_logstash_index_with_specified_prefix_uppercase
2763
+ driver.configure("logstash_format true
2764
+ logstash_prefix MyPrefix")
2765
+ time = Time.parse Date.today.iso8601
2766
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
2767
+ stub_opensearch
2768
+ stub_opensearch_info
2769
+ driver.run(default_tag: 'test') do
2770
+ driver.feed(time.to_i, sample_record)
2771
+ end
2772
+ # Allthough logstash_prefix has upper-case characters,
2773
+ # it should be set as lower-case when sent to elasticsearch.
2774
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2775
+ end
2776
+
2777
+ def test_writes_to_logstash_index_with_specified_dateformat
2778
+ driver.configure("logstash_format true
2779
+ logstash_dateformat %Y.%m")
2780
+ time = Time.parse Date.today.iso8601
2781
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m")}"
2782
+ stub_opensearch
2783
+ stub_opensearch_info
2784
+ driver.run(default_tag: 'test') do
2785
+ driver.feed(time.to_i, sample_record)
2786
+ end
2787
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2788
+ end
2789
+
2790
+ def test_writes_to_logstash_index_with_specified_prefix_and_dateformat
2791
+ driver.configure("logstash_format true
2792
+ logstash_prefix myprefix
2793
+ logstash_dateformat %Y.%m")
2794
+ time = Time.parse Date.today.iso8601
2795
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m")}"
2796
+ stub_opensearch
2797
+ stub_opensearch_info
2798
+ driver.run(default_tag: 'test') do
2799
+ driver.feed(time.to_i, sample_record)
2800
+ end
2801
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
2802
+ end
2803
+
2804
+ def test_error_if_tag_not_in_chunk_keys
2805
+ assert_raise(Fluent::ConfigError) {
2806
+ config = %{
2807
+ <buffer foo>
2808
+ </buffer>
2809
+ }
2810
+ driver.configure(config)
2811
+ }
2812
+ end
2813
+
2814
+ def test_can_use_custom_chunk_along_with_tag
2815
+ config = %{
2816
+ <buffer tag, foo>
2817
+ </buffer>
2818
+ }
2819
+ driver.configure(config)
2820
+ end
2821
+
2822
+ def test_doesnt_add_logstash_timestamp_by_default
2823
+ stub_opensearch
2824
+ stub_opensearch_info
2825
+ driver.run(default_tag: 'test') do
2826
+ driver.feed(sample_record)
2827
+ end
2828
+ assert_nil(index_cmds[1]['@timestamp'])
2829
+ end
2830
+
2831
+ def test_adds_timestamp_when_logstash
2832
+ driver.configure("logstash_format true\n")
2833
+ stub_opensearch
2834
+ stub_opensearch_info
2835
+ ts = DateTime.now
2836
+ time = Fluent::EventTime.from_time(ts.to_time)
2837
+ driver.run(default_tag: 'test') do
2838
+ driver.feed(time, sample_record)
2839
+ end
2840
+ assert(index_cmds[1].has_key? '@timestamp')
2841
+ assert_equal(ts.iso8601(9), index_cmds[1]['@timestamp'])
2842
+ end
2843
+
2844
+ def test_adds_timestamp_when_include_timestamp
2845
+ driver.configure("include_timestamp true\n")
2846
+ stub_opensearch
2847
+ stub_opensearch_info
2848
+ ts = DateTime.now
2849
+ time = Fluent::EventTime.from_time(ts.to_time)
2850
+ driver.run(default_tag: 'test') do
2851
+ driver.feed(time, sample_record)
2852
+ end
2853
+ assert(index_cmds[1].has_key? '@timestamp')
2854
+ assert_equal(ts.iso8601(9), index_cmds[1]['@timestamp'])
2855
+ end
2856
+
2857
+ def test_uses_custom_timestamp_when_included_in_record
2858
+ driver.configure("logstash_format true\n")
2859
+ stub_opensearch
2860
+ stub_opensearch_info
2861
+ ts = DateTime.new(2001,2,3).iso8601
2862
+ driver.run(default_tag: 'test') do
2863
+ driver.feed(sample_record.merge!('@timestamp' => ts))
2864
+ end
2865
+ assert(index_cmds[1].has_key? '@timestamp')
2866
+ assert_equal(ts, index_cmds[1]['@timestamp'])
2867
+ end
2868
+
2869
+ def test_uses_custom_timestamp_when_included_in_record_without_logstash
2870
+ driver.configure("include_timestamp true\n")
2871
+ stub_opensearch
2872
+ stub_opensearch_info
2873
+ ts = DateTime.new(2001,2,3).iso8601
2874
+ driver.run(default_tag: 'test') do
2875
+ driver.feed(sample_record.merge!('@timestamp' => ts))
2876
+ end
2877
+ assert(index_cmds[1].has_key? '@timestamp')
2878
+ assert_equal(ts, index_cmds[1]['@timestamp'])
2879
+ end
2880
+
2881
+ def test_uses_custom_time_key
2882
+ driver.configure("logstash_format true
2883
+ time_key vtm\n")
2884
+ stub_opensearch
2885
+ stub_opensearch_info
2886
+ ts = DateTime.new(2001,2,3).iso8601(9)
2887
+ driver.run(default_tag: 'test') do
2888
+ driver.feed(sample_record.merge!('vtm' => ts))
2889
+ end
2890
+ assert(index_cmds[1].has_key? '@timestamp')
2891
+ assert_equal(ts, index_cmds[1]['@timestamp'])
2892
+ end
2893
+
2894
+ def test_uses_custom_time_key_with_float_record
2895
+ driver.configure("logstash_format true
2896
+ time_precision 3
2897
+ time_key vtm\n")
2898
+ stub_opensearch
2899
+ stub_opensearch_info
2900
+ time = Time.now
2901
+ float_time = time.to_f
2902
+ driver.run(default_tag: 'test') do
2903
+ driver.feed(sample_record.merge!('vtm' => float_time))
2904
+ end
2905
+ assert(index_cmds[1].has_key? '@timestamp')
2906
+ assert_equal(time.to_datetime.iso8601(3), index_cmds[1]['@timestamp'])
2907
+ end
2908
+
2909
+ def test_uses_custom_time_key_with_format
2910
+ driver.configure("logstash_format true
2911
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
2912
+ time_key vtm\n")
2913
+ stub_opensearch
2914
+ stub_opensearch_info
2915
+ ts = "2001-02-03 13:14:01.673+02:00"
2916
+ driver.run(default_tag: 'test') do
2917
+ driver.feed(sample_record.merge!('vtm' => ts))
2918
+ end
2919
+ assert(index_cmds[1].has_key? '@timestamp')
2920
+ assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
2921
+ assert_equal("logstash-2001.02.03", index_cmds[0]['index']['_index'])
2922
+ end
2923
+
2924
+ def test_uses_custom_time_key_with_float_record_and_format
2925
+ driver.configure("logstash_format true
2926
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
2927
+ time_key vtm\n")
2928
+ stub_opensearch
2929
+ stub_opensearch_info
2930
+ ts = "2001-02-03 13:14:01.673+02:00"
2931
+ time = Time.parse(ts)
2932
+ current_zone_offset = Time.new(2001, 02, 03).to_datetime.offset
2933
+ float_time = time.to_f
2934
+ driver.run(default_tag: 'test') do
2935
+ driver.feed(sample_record.merge!('vtm' => float_time))
2936
+ end
2937
+ assert(index_cmds[1].has_key? '@timestamp')
2938
+ assert_equal(DateTime.parse(ts).new_offset(current_zone_offset).iso8601(9), index_cmds[1]['@timestamp'])
2939
+ end
2940
+
2941
+ def test_uses_custom_time_key_with_format_without_logstash
2942
+ driver.configure("include_timestamp true
2943
+ index_name test
2944
+ time_key_format %Y-%m-%d %H:%M:%S.%N%z
2945
+ time_key vtm\n")
2946
+ stub_opensearch
2947
+ stub_opensearch_info
2948
+ ts = "2001-02-03 13:14:01.673+02:00"
2949
+ driver.run(default_tag: 'test') do
2950
+ driver.feed(sample_record.merge!('vtm' => ts))
2951
+ end
2952
+ assert(index_cmds[1].has_key? '@timestamp')
2953
+ assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
2954
+ assert_equal("test", index_cmds[0]['index']['_index'])
2955
+ end
2956
+
2957
+ def test_uses_custom_time_key_exclude_timekey
2958
+ driver.configure("logstash_format true
2959
+ time_key vtm
2960
+ time_key_exclude_timestamp true\n")
2961
+ stub_opensearch
2962
+ stub_opensearch_info
2963
+ ts = DateTime.new(2001,2,3).iso8601
2964
+ driver.run(default_tag: 'test') do
2965
+ driver.feed(sample_record.merge!('vtm' => ts))
2966
+ end
2967
+ assert(!index_cmds[1].key?('@timestamp'), '@timestamp should be messing')
2968
+ end
2969
+
2970
+ def test_uses_custom_time_key_format
2971
+ driver.configure("logstash_format true
2972
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n")
2973
+ stub_opensearch
2974
+ stub_opensearch_info
2975
+ ts = "2001-02-03T13:14:01.673+02:00"
2976
+ driver.run(default_tag: 'test') do
2977
+ driver.feed(sample_record.merge!('@timestamp' => ts))
2978
+ end
2979
+ assert_equal("logstash-2001.02.03", index_cmds[0]['index']['_index'])
2980
+ assert(index_cmds[1].has_key? '@timestamp')
2981
+ assert_equal(ts, index_cmds[1]['@timestamp'])
2982
+ end
2983
+
2984
+ def test_uses_custom_time_key_format_without_logstash
2985
+ driver.configure("include_timestamp true
2986
+ index_name test
2987
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n")
2988
+ stub_opensearch
2989
+ stub_opensearch_info
2990
+ ts = "2001-02-03T13:14:01.673+02:00"
2991
+ driver.run(default_tag: 'test') do
2992
+ driver.feed(sample_record.merge!('@timestamp' => ts))
2993
+ end
2994
+ assert_equal("test", index_cmds[0]['index']['_index'])
2995
+ assert(index_cmds[1].has_key? '@timestamp')
2996
+ assert_equal(ts, index_cmds[1]['@timestamp'])
2997
+ end
2998
+
2999
+ data(:default => nil,
3000
+ :custom_tag => 'es_plugin.output.time.error')
3001
+ def test_uses_custom_time_key_format_logs_an_error(tag_for_error)
3002
+ tag_config = tag_for_error ? "time_parse_error_tag #{tag_for_error}" : ''
3003
+ tag_for_error = 'opensearch_plugin.output.time.error' if tag_for_error.nil?
3004
+ driver.configure("logstash_format true
3005
+ time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n#{tag_config}\n")
3006
+ stub_opensearch
3007
+ stub_opensearch_info
3008
+
3009
+ ts = "2001/02/03 13:14:01,673+02:00"
3010
+ index = "logstash-#{Time.now.getutc.strftime("%Y.%m.%d")}"
3011
+
3012
+ flexmock(driver.instance.router).should_receive(:emit_error_event)
3013
+ .with(tag_for_error, Fluent::EventTime, Hash, ArgumentError).once
3014
+ driver.run(default_tag: 'test') do
3015
+ driver.feed(sample_record.merge!('@timestamp' => ts))
3016
+ end
3017
+
3018
+ assert_equal(index, index_cmds[0]['index']['_index'])
3019
+ assert(index_cmds[1].has_key? '@timestamp')
3020
+ assert_equal(ts, index_cmds[1]['@timestamp'])
3021
+ end
3022
+
3023
+
3024
+ def test_uses_custom_time_key_format_obscure_format
3025
+ driver.configure("logstash_format true
3026
+ time_key_format %a %b %d %H:%M:%S %Z %Y\n")
3027
+ stub_opensearch
3028
+ stub_opensearch_info
3029
+ ts = "Thu Nov 29 14:33:20 GMT 2001"
3030
+ driver.run(default_tag: 'test') do
3031
+ driver.feed(sample_record.merge!('@timestamp' => ts))
3032
+ end
3033
+ assert_equal("logstash-2001.11.29", index_cmds[0]['index']['_index'])
3034
+ assert(index_cmds[1].has_key? '@timestamp')
3035
+ assert_equal(ts, index_cmds[1]['@timestamp'])
3036
+ end
3037
+
3038
+ def test_uses_nanosecond_precision_by_default
3039
+ driver.configure("logstash_format true\n")
3040
+ stub_opensearch
3041
+ stub_opensearch_info
3042
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
3043
+ driver.run(default_tag: 'test') do
3044
+ driver.feed(time, sample_record)
3045
+ end
3046
+ assert(index_cmds[1].has_key? '@timestamp')
3047
+ assert_equal(Time.at(time).iso8601(9), index_cmds[1]['@timestamp'])
3048
+ end
3049
+
3050
+ def test_uses_subsecond_precision_when_configured
3051
+ driver.configure("logstash_format true
3052
+ time_precision 3\n")
3053
+ stub_opensearch
3054
+ stub_opensearch_info
3055
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
3056
+ driver.run(default_tag: 'test') do
3057
+ driver.feed(time, sample_record)
3058
+ end
3059
+ assert(index_cmds[1].has_key? '@timestamp')
3060
+ assert_equal(Time.at(time).iso8601(3), index_cmds[1]['@timestamp'])
3061
+ end
3062
+
3063
+ def test_doesnt_add_tag_key_by_default
3064
+ stub_opensearch
3065
+ stub_opensearch_info
3066
+ driver.run(default_tag: 'test') do
3067
+ driver.feed(sample_record)
3068
+ end
3069
+ assert_nil(index_cmds[1]['tag'])
3070
+ end
3071
+
3072
+ def test_adds_tag_key_when_configured
3073
+ driver.configure("include_tag_key true\n")
3074
+ stub_opensearch
3075
+ stub_opensearch_info
3076
+ driver.run(default_tag: 'mytag') do
3077
+ driver.feed(sample_record)
3078
+ end
3079
+ assert(index_cmds[1].has_key?('tag'))
3080
+ assert_equal('mytag', index_cmds[1]['tag'])
3081
+ end
3082
+
3083
+ def test_adds_id_key_when_configured
3084
+ driver.configure("id_key request_id\n")
3085
+ stub_opensearch
3086
+ stub_opensearch_info
3087
+ driver.run(default_tag: 'test') do
3088
+ driver.feed(sample_record)
3089
+ end
3090
+ assert_equal('42', index_cmds[0]['index']['_id'])
3091
+ end
3092
+
3093
+ class NestedIdKeyTest < self
3094
+ def test_adds_nested_id_key_with_dot
3095
+ driver.configure("id_key nested.request_id\n")
3096
+ stub_opensearch
3097
+ stub_opensearch_info
3098
+ driver.run(default_tag: 'test') do
3099
+ driver.feed(nested_sample_record)
3100
+ end
3101
+ assert_equal('42', index_cmds[0]['index']['_id'])
3102
+ end
3103
+
3104
+ def test_adds_nested_id_key_with_dollar_dot
3105
+ driver.configure("id_key $.nested.request_id\n")
3106
+ stub_opensearch
3107
+ stub_opensearch_info
3108
+ driver.run(default_tag: 'test') do
3109
+ driver.feed(nested_sample_record)
3110
+ end
3111
+ assert_equal('42', index_cmds[0]['index']['_id'])
3112
+ end
3113
+
3114
+ def test_adds_nested_id_key_with_bracket
3115
+ driver.configure("id_key $['nested']['request_id']\n")
3116
+ stub_opensearch
3117
+ stub_opensearch_info
3118
+ driver.run(default_tag: 'test') do
3119
+ driver.feed(nested_sample_record)
3120
+ end
3121
+ assert_equal('42', index_cmds[0]['index']['_id'])
3122
+ end
3123
+ end
3124
+
3125
+ def test_doesnt_add_id_key_if_missing_when_configured
3126
+ driver.configure("id_key another_request_id\n")
3127
+ stub_opensearch
3128
+ stub_opensearch_info
3129
+ driver.run(default_tag: 'test') do
3130
+ driver.feed(sample_record)
3131
+ end
3132
+ assert(!index_cmds[0]['index'].has_key?('_id'))
3133
+ end
3134
+
3135
+ def test_adds_id_key_when_not_configured
3136
+ stub_opensearch
3137
+ stub_opensearch_info
3138
+ driver.run(default_tag: 'test') do
3139
+ driver.feed(sample_record)
3140
+ end
3141
+ assert(!index_cmds[0]['index'].has_key?('_id'))
3142
+ end
3143
+
3144
+ def test_adds_parent_key_when_configured
3145
+ driver.configure("parent_key parent_id\n")
3146
+ stub_opensearch
3147
+ stub_opensearch_info
3148
+ driver.run(default_tag: 'test') do
3149
+ driver.feed(sample_record)
3150
+ end
3151
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
3152
+ end
3153
+
3154
+ class NestedParentKeyTest < self
3155
+ def test_adds_nested_parent_key_with_dot
3156
+ driver.configure("parent_key nested.parent_id\n")
3157
+ stub_opensearch
3158
+ stub_opensearch_info
3159
+ driver.run(default_tag: 'test') do
3160
+ driver.feed(nested_sample_record)
3161
+ end
3162
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
3163
+ end
3164
+
3165
+ def test_adds_nested_parent_key_with_dollar_dot
3166
+ driver.configure("parent_key $.nested.parent_id\n")
3167
+ stub_opensearch
3168
+ stub_opensearch_info
3169
+ driver.run(default_tag: 'test') do
3170
+ driver.feed(nested_sample_record)
3171
+ end
3172
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
3173
+ end
3174
+
3175
+ def test_adds_nested_parent_key_with_bracket
3176
+ driver.configure("parent_key $['nested']['parent_id']\n")
3177
+ stub_opensearch
3178
+ stub_opensearch_info
3179
+ driver.run(default_tag: 'test') do
3180
+ driver.feed(nested_sample_record)
3181
+ end
3182
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
3183
+ end
3184
+ end
3185
+
3186
+ def test_doesnt_add_parent_key_if_missing_when_configured
3187
+ driver.configure("parent_key another_parent_id\n")
3188
+ stub_opensearch
3189
+ stub_opensearch_info
3190
+ driver.run(default_tag: 'test') do
3191
+ driver.feed(sample_record)
3192
+ end
3193
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
3194
+ end
3195
+
3196
+ def test_adds_parent_key_when_not_configured
3197
+ stub_opensearch
3198
+ stub_opensearch_info
3199
+ driver.run(default_tag: 'test') do
3200
+ driver.feed(sample_record)
3201
+ end
3202
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
3203
+ end
3204
+
3205
+ class AddsRoutingKeyWhenConfiguredTest < self
3206
+ def test_os1
3207
+ driver("routing_key routing_id\n", 1)
3208
+ stub_opensearch
3209
+ stub_opensearch_info
3210
+ driver.run(default_tag: 'test') do
3211
+ driver.feed(sample_record)
3212
+ end
3213
+ assert_equal('routing', index_cmds[0]['index']['routing'])
3214
+ end
3215
+ end
3216
+
3217
+ class NestedRoutingKeyTest < self
3218
+ def test_adds_nested_routing_key_with_dot
3219
+ driver.configure("routing_key nested.routing_id\n")
3220
+ stub_opensearch
3221
+ stub_opensearch_info
3222
+ driver.run(default_tag: 'test') do
3223
+ driver.feed(nested_sample_record)
3224
+ end
3225
+ assert_equal('routing', index_cmds[0]['index']['routing'])
3226
+ end
3227
+
3228
+ def test_adds_nested_routing_key_with_dollar_dot
3229
+ driver.configure("routing_key $.nested.routing_id\n")
3230
+ stub_opensearch
3231
+ stub_opensearch_info
3232
+ driver.run(default_tag: 'test') do
3233
+ driver.feed(nested_sample_record)
3234
+ end
3235
+ assert_equal('routing', index_cmds[0]['index']['routing'])
3236
+ end
3237
+
3238
+ def test_adds_nested_routing_key_with_bracket
3239
+ driver.configure("routing_key $['nested']['routing_id']\n")
3240
+ stub_opensearch
3241
+ stub_opensearch_info
3242
+ driver.run(default_tag: 'test') do
3243
+ driver.feed(nested_sample_record)
3244
+ end
3245
+ assert_equal('routing', index_cmds[0]['index']['routing'])
3246
+ end
3247
+ end
3248
+
3249
+ def test_doesnt_add_routing_key_if_missing_when_configured
3250
+ driver.configure("routing_key another_routing_id\n")
3251
+ stub_opensearch
3252
+ stub_opensearch_info
3253
+ driver.run(default_tag: 'test') do
3254
+ driver.feed(sample_record)
3255
+ end
3256
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
3257
+ end
3258
+
3259
+ def test_adds_routing_key_when_not_configured
3260
+ stub_opensearch
3261
+ stub_opensearch_info
3262
+ driver.run(default_tag: 'test') do
3263
+ driver.feed(sample_record)
3264
+ end
3265
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
3266
+ end
3267
+
3268
+ def test_remove_one_key
3269
+ driver.configure("remove_keys key1\n")
3270
+ stub_opensearch
3271
+ stub_opensearch_info
3272
+ driver.run(default_tag: 'test') do
3273
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
3274
+ end
3275
+ assert(!index_cmds[1].has_key?('key1'))
3276
+ assert(index_cmds[1].has_key?('key2'))
3277
+ end
3278
+
3279
+ def test_remove_multi_keys
3280
+ driver.configure("remove_keys key1, key2\n")
3281
+ stub_opensearch
3282
+ stub_opensearch_info
3283
+ driver.run(default_tag: 'test') do
3284
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
3285
+ end
3286
+ assert(!index_cmds[1].has_key?('key1'))
3287
+ assert(!index_cmds[1].has_key?('key2'))
3288
+ end
3289
+
3290
+ def test_request_error
3291
+ stub_opensearch_info
3292
+ stub_opensearch_unavailable
3293
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
3294
+ driver.run(default_tag: 'test', shutdown: false) do
3295
+ driver.feed(sample_record)
3296
+ end
3297
+ }
3298
+ end
3299
+
3300
+ def test_request_forever
3301
+ omit("retry_forever test is unstable.") if ENV["CI"]
3302
+ stub_opensearch
3303
+ stub_opensearch_info
3304
+ driver.configure(Fluent::Config::Element.new(
3305
+ 'ROOT', '', {
3306
+ '@type' => 'opensearch',
3307
+ }, [
3308
+ Fluent::Config::Element.new('buffer', '', {
3309
+ 'retry_forever' => true
3310
+ }, [])
3311
+ ]
3312
+ ))
3313
+ stub_opensearch_timeout
3314
+ assert_raise(Timeout::Error) {
3315
+ driver.run(default_tag: 'test', timeout: 10, force_flush_retry: true) do
3316
+ driver.feed(sample_record)
3317
+ end
3318
+ }
3319
+ end
3320
+
3321
+ def test_connection_failed
3322
+ connection_resets = 0
3323
+
3324
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
3325
+ connection_resets += 1
3326
+ raise Faraday::ConnectionFailed, "Test message"
3327
+ end
3328
+ stub_opensearch_info
3329
+
3330
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
3331
+ driver.run(default_tag: 'test', shutdown: false) do
3332
+ driver.feed(sample_record)
3333
+ end
3334
+ }
3335
+ assert_equal(1, connection_resets)
3336
+ end
3337
+
3338
+ def test_reconnect_on_error_enabled
3339
+ connection_resets = 0
3340
+
3341
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
3342
+ connection_resets += 1
3343
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
3344
+ end
3345
+ stub_opensearch_info
3346
+
3347
+ driver.configure("reconnect_on_error true\n")
3348
+
3349
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
3350
+ driver.run(default_tag: 'test', shutdown: false) do
3351
+ driver.feed(sample_record)
3352
+ end
3353
+ }
3354
+
3355
+ assert_raise(Timeout::Error) {
3356
+ driver.run(default_tag: 'test', shutdown: false) do
3357
+ driver.feed(sample_record)
3358
+ end
3359
+ }
3360
+ # FIXME: Consider keywords arguments in #run and how to test this later.
3361
+ # Because v0.14 test driver does not have 1 to 1 correspondence between #run and #flush in tests.
3362
+ assert_equal(1, connection_resets)
3363
+ end
3364
+
3365
+ def test_reconnect_on_error_disabled
3366
+ connection_resets = 0
3367
+
3368
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
3369
+ connection_resets += 1
3370
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
3371
+ end
3372
+ stub_opensearch_info
3373
+
3374
+ driver.configure("reconnect_on_error false\n")
3375
+
3376
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
3377
+ driver.run(default_tag: 'test', shutdown: false) do
3378
+ driver.feed(sample_record)
3379
+ end
3380
+ }
3381
+
3382
+ assert_raise(Timeout::Error) {
3383
+ driver.run(default_tag: 'test', shutdown: false) do
3384
+ driver.feed(sample_record)
3385
+ end
3386
+ }
3387
+ assert_equal(1, connection_resets)
3388
+ end
3389
+
3390
+ def test_bulk_error_retags_when_configured
3391
+ driver.configure("retry_tag retry\n")
3392
+ stub_request(:post, 'http://localhost:9200/_bulk')
3393
+ .to_return(lambda do |req|
3394
+ { :status => 200,
3395
+ :headers => { 'Content-Type' => 'json' },
3396
+ :body => %({
3397
+ "took" : 1,
3398
+ "errors" : true,
3399
+ "items" : [
3400
+ {
3401
+ "create" : {
3402
+ "_index" : "foo",
3403
+ "_type" : "bar",
3404
+ "_id" : "abc",
3405
+ "status" : 500,
3406
+ "error" : {
3407
+ "type" : "some unrecognized type",
3408
+ "reason":"some error to cause version mismatch"
3409
+ }
3410
+ }
3411
+ }
3412
+ ]
3413
+ })
3414
+ }
3415
+ end)
3416
+ stub_opensearch_info
3417
+
3418
+ driver.run(default_tag: 'test') do
3419
+ driver.feed(1, sample_record)
3420
+ end
3421
+
3422
+ assert_equal [['retry', 1, sample_record]], driver.events
3423
+ end
3424
+
3425
+ class FulfilledBufferRetryStreamTest < self
3426
+ def test_bulk_error_retags_with_error_when_configured_and_fullfilled_buffer
3427
+ def create_driver(conf='', os_version=1, client_version="\"1.0\"")
3428
+ @client_version ||= client_version
3429
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
3430
+ def retry_stream_retryable?
3431
+ false
3432
+ end
3433
+ CODE
3434
+ # For request stub to detect compatibility.
3435
+ @os_version ||= os_version
3436
+ @client_version ||= client_version
3437
+ if @os_version
3438
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
3439
+ def detect_os_major_version
3440
+ #{@os_version}
3441
+ end
3442
+ CODE
3443
+ end
3444
+ Fluent::Plugin::OpenSearchOutput.module_eval(<<-CODE)
3445
+ def client_library_version
3446
+ #{@client_version}
3447
+ end
3448
+ CODE
3449
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutput).configure(conf)
3450
+ end
3451
+ driver = create_driver("retry_tag retry\n")
3452
+ stub_request(:post, 'http://localhost:9200/_bulk')
3453
+ .to_return(lambda do |req|
3454
+ { :status => 200,
3455
+ :headers => { 'Content-Type' => 'json' },
3456
+ :body => %({
3457
+ "took" : 1,
3458
+ "errors" : true,
3459
+ "items" : [
3460
+ {
3461
+ "create" : {
3462
+ "_index" : "foo",
3463
+ "_type" : "bar",
3464
+ "_id" : "abc1",
3465
+ "status" : 403,
3466
+ "error" : {
3467
+ "type" : "cluster_block_exception",
3468
+ "reason":"index [foo] blocked by: [FORBIDDEN/8/index write (api)]"
3469
+ }
3470
+ }
3471
+ },
3472
+ {
3473
+ "create" : {
3474
+ "_index" : "foo",
3475
+ "_type" : "bar",
3476
+ "_id" : "abc2",
3477
+ "status" : 403,
3478
+ "error" : {
3479
+ "type" : "cluster_block_exception",
3480
+ "reason":"index [foo] blocked by: [FORBIDDEN/8/index write (api)]"
3481
+ }
3482
+ }
3483
+ }
3484
+ ]
3485
+ })
3486
+ }
3487
+ end)
3488
+ stub_opensearch_info
3489
+
3490
+ # Check buffer fulfillment condition
3491
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RetryStreamEmitFailure) do
3492
+ driver.run(default_tag: 'test') do
3493
+ driver.feed(1, sample_record)
3494
+ driver.feed(1, sample_record)
3495
+ end
3496
+ end
3497
+
3498
+ assert_equal [], driver.events
3499
+ end
3500
+ end
3501
+
3502
+ def test_create_should_write_records_with_ids_and_skip_those_without
3503
+ driver.configure("write_operation create\nid_key my_id\n@log_level debug")
3504
+ stub_request(:post, 'http://localhost:9200/_bulk')
3505
+ .to_return(lambda do |req|
3506
+ { :status => 200,
3507
+ :headers => { 'Content-Type' => 'json' },
3508
+ :body => %({
3509
+ "took" : 1,
3510
+ "errors" : true,
3511
+ "items" : [
3512
+ {
3513
+ "create" : {
3514
+ "_index" : "foo",
3515
+ "_type" : "bar",
3516
+ "_id" : "abc"
3517
+ }
3518
+ },
3519
+ {
3520
+ "create" : {
3521
+ "_index" : "foo",
3522
+ "_type" : "bar",
3523
+ "_id" : "xyz",
3524
+ "status" : 500,
3525
+ "error" : {
3526
+ "type" : "some unrecognized type",
3527
+ "reason":"some error to cause version mismatch"
3528
+ }
3529
+ }
3530
+ }
3531
+ ]
3532
+ })
3533
+ }
3534
+ end)
3535
+ stub_opensearch_info
3536
+
3537
+ sample_record1 = sample_record('my_id' => 'abc')
3538
+ sample_record4 = sample_record('my_id' => 'xyz')
3539
+
3540
+ driver.run(default_tag: 'test') do
3541
+ driver.feed(1, sample_record1)
3542
+ driver.feed(2, sample_record)
3543
+ driver.feed(3, sample_record)
3544
+ driver.feed(4, sample_record4)
3545
+ end
3546
+
3547
+ logs = driver.logs
3548
+ # one record succeeded while the other should be 'retried'
3549
+ assert_equal [['test', 4, sample_record4]], driver.events
3550
+ assert_logs_include(logs, /(Dropping record)/, 2)
3551
+ end
3552
+
3553
+ def test_create_should_write_records_with_ids_and_emit_those_without
3554
+ driver.configure("write_operation create\nid_key my_id\nemit_error_for_missing_id true\n@log_level debug")
3555
+ stub_request(:post, 'http://localhost:9200/_bulk')
3556
+ .to_return(lambda do |req|
3557
+ { :status => 200,
3558
+ :headers => { 'Content-Type' => 'json' },
3559
+ :body => %({
3560
+ "took" : 1,
3561
+ "errors" : true,
3562
+ "items" : [
3563
+ {
3564
+ "create" : {
3565
+ "_index" : "foo",
3566
+ "_type" : "bar",
3567
+ "_id" : "abc"
3568
+ }
3569
+ },
3570
+ {
3571
+ "create" : {
3572
+ "_index" : "foo",
3573
+ "_type" : "bar",
3574
+ "_id" : "xyz",
3575
+ "status" : 500,
3576
+ "error" : {
3577
+ "type" : "some unrecognized type",
3578
+ "reason":"some error to cause version mismatch"
3579
+ }
3580
+ }
3581
+ }
3582
+ ]
3583
+ })
3584
+ }
3585
+ end)
3586
+ stub_opensearch_info
3587
+
3588
+ sample_record1 = sample_record('my_id' => 'abc')
3589
+ sample_record4 = sample_record('my_id' => 'xyz')
3590
+
3591
+ driver.run(default_tag: 'test') do
3592
+ driver.feed(1, sample_record1)
3593
+ driver.feed(2, sample_record)
3594
+ driver.feed(3, sample_record)
3595
+ driver.feed(4, sample_record4)
3596
+ end
3597
+
3598
+ error_log = driver.error_events.map {|e| e.last.message }
3599
+ # one record succeeded while the other should be 'retried'
3600
+ assert_equal [['test', 4, sample_record4]], driver.events
3601
+ assert_logs_include(error_log, /(Missing '_id' field)/, 2)
3602
+ end
3603
+
3604
+ def test_bulk_error
3605
+ stub_request(:post, 'http://localhost:9200/_bulk')
3606
+ .to_return(lambda do |req|
3607
+ { :status => 200,
3608
+ :headers => { 'Content-Type' => 'json' },
3609
+ :body => %({
3610
+ "took" : 1,
3611
+ "errors" : true,
3612
+ "items" : [
3613
+ {
3614
+ "create" : {
3615
+ "_index" : "foo",
3616
+ "_type" : "bar",
3617
+ "_id" : "abc",
3618
+ "status" : 500,
3619
+ "error" : {
3620
+ "type" : "some unrecognized type",
3621
+ "reason":"some error to cause version mismatch"
3622
+ }
3623
+ }
3624
+ },
3625
+ {
3626
+ "create" : {
3627
+ "_index" : "foo",
3628
+ "_type" : "bar",
3629
+ "_id" : "abc",
3630
+ "status" : 201
3631
+ }
3632
+ },
3633
+ {
3634
+ "create" : {
3635
+ "_index" : "foo",
3636
+ "_type" : "bar",
3637
+ "_id" : "abc",
3638
+ "status" : 500,
3639
+ "error" : {
3640
+ "type" : "some unrecognized type",
3641
+ "reason":"some error to cause version mismatch"
3642
+ }
3643
+ }
3644
+ },
3645
+ {
3646
+ "create" : {
3647
+ "_index" : "foo",
3648
+ "_type" : "bar",
3649
+ "_id" : "abc",
3650
+ "_id" : "abc",
3651
+ "status" : 409
3652
+ }
3653
+ }
3654
+ ]
3655
+ })
3656
+ }
3657
+ end)
3658
+ stub_opensearch_info
3659
+
3660
+ driver.run(default_tag: 'test') do
3661
+ driver.feed(1, sample_record)
3662
+ driver.feed(2, sample_record)
3663
+ driver.feed(3, sample_record)
3664
+ driver.feed(4, sample_record)
3665
+ end
3666
+
3667
+ expect = [['test', 1, sample_record],
3668
+ ['test', 3, sample_record]]
3669
+ assert_equal expect, driver.events
3670
+ end
3671
+
3672
+ def test_update_should_not_write_if_theres_no_id
3673
+ driver.configure("write_operation update\n")
3674
+ stub_opensearch
3675
+ stub_opensearch_info
3676
+ driver.run(default_tag: 'test') do
3677
+ driver.feed(sample_record)
3678
+ end
3679
+ assert_nil(index_cmds)
3680
+ end
3681
+
3682
+ def test_upsert_should_not_write_if_theres_no_id
3683
+ driver.configure("write_operation upsert\n")
3684
+ stub_opensearch
3685
+ stub_opensearch_info
3686
+ driver.run(default_tag: 'test') do
3687
+ driver.feed(sample_record)
3688
+ end
3689
+ assert_nil(index_cmds)
3690
+ end
3691
+
3692
+ def test_create_should_not_write_if_theres_no_id
3693
+ driver.configure("write_operation create\n")
3694
+ stub_opensearch
3695
+ stub_opensearch_info
3696
+ driver.run(default_tag: 'test') do
3697
+ driver.feed(sample_record)
3698
+ end
3699
+ assert_nil(index_cmds)
3700
+ end
3701
+
3702
+ def test_update_should_write_update_op_and_doc_as_upsert_is_false
3703
+ driver.configure("write_operation update
3704
+ id_key request_id")
3705
+ stub_opensearch
3706
+ stub_opensearch_info
3707
+ driver.run(default_tag: 'test') do
3708
+ driver.feed(sample_record)
3709
+ end
3710
+ assert(index_cmds[0].has_key?("update"))
3711
+ assert(!index_cmds[1]["doc_as_upsert"])
3712
+ assert(!index_cmds[1]["upsert"])
3713
+ end
3714
+
3715
+ def test_update_should_remove_keys_from_doc_when_keys_are_skipped
3716
+ driver.configure("write_operation update
3717
+ id_key request_id
3718
+ remove_keys_on_update parent_id")
3719
+ stub_opensearch
3720
+ stub_opensearch_info
3721
+ driver.run(default_tag: 'test') do
3722
+ driver.feed(sample_record)
3723
+ end
3724
+ assert(index_cmds[1]["doc"])
3725
+ assert(!index_cmds[1]["doc"]["parent_id"])
3726
+ end
3727
+
3728
+ def test_upsert_should_write_update_op_and_doc_as_upsert_is_true
3729
+ driver.configure("write_operation upsert
3730
+ id_key request_id")
3731
+ stub_opensearch
3732
+ stub_opensearch_info
3733
+ driver.run(default_tag: 'test') do
3734
+ driver.feed(sample_record)
3735
+ end
3736
+ assert(index_cmds[0].has_key?("update"))
3737
+ assert(index_cmds[1]["doc_as_upsert"])
3738
+ assert(!index_cmds[1]["upsert"])
3739
+ end
3740
+
3741
+ def test_upsert_should_write_update_op_upsert_and_doc_when_keys_are_skipped
3742
+ driver.configure("write_operation upsert
3743
+ id_key request_id
3744
+ remove_keys_on_update parent_id")
3745
+ stub_opensearch
3746
+ stub_opensearch_info
3747
+ driver.run(default_tag: 'test') do
3748
+ driver.feed(sample_record)
3749
+ end
3750
+ assert(index_cmds[0].has_key?("update"))
3751
+ assert(!index_cmds[1]["doc_as_upsert"])
3752
+ assert(index_cmds[1]["upsert"])
3753
+ assert(index_cmds[1]["doc"])
3754
+ end
3755
+
3756
+ def test_upsert_should_remove_keys_from_doc_when_keys_are_skipped
3757
+ driver.configure("write_operation upsert
3758
+ id_key request_id
3759
+ remove_keys_on_update parent_id")
3760
+ stub_opensearch
3761
+ stub_opensearch_info
3762
+ driver.run(default_tag: 'test') do
3763
+ driver.feed(sample_record)
3764
+ end
3765
+ assert(index_cmds[1]["upsert"] != index_cmds[1]["doc"])
3766
+ assert(!index_cmds[1]["doc"]["parent_id"])
3767
+ assert(index_cmds[1]["upsert"]["parent_id"])
3768
+ end
3769
+
3770
+ def test_upsert_should_remove_multiple_keys_when_keys_are_skipped
3771
+ driver.configure("write_operation upsert
3772
+ id_key id
3773
+ remove_keys_on_update foo,baz")
3774
+ stub_opensearch
3775
+ stub_opensearch_info
3776
+ driver.run(default_tag: 'test') do
3777
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "zip" => "zam")
3778
+ end
3779
+ assert(
3780
+ index_cmds[1]["doc"] == {
3781
+ "id" => 1,
3782
+ "zip" => "zam",
3783
+ }
3784
+ )
3785
+ assert(
3786
+ index_cmds[1]["upsert"] == {
3787
+ "id" => 1,
3788
+ "foo" => "bar",
3789
+ "baz" => "quix",
3790
+ "zip" => "zam",
3791
+ }
3792
+ )
3793
+ end
3794
+
3795
+ def test_upsert_should_remove_keys_from_when_the_keys_are_in_the_record
3796
+ driver.configure("write_operation upsert
3797
+ id_key id
3798
+ remove_keys_on_update_key keys_to_skip")
3799
+ stub_opensearch
3800
+ stub_opensearch_info
3801
+ driver.run(default_tag: 'test') do
3802
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "keys_to_skip" => ["baz"])
3803
+ end
3804
+ assert(
3805
+ index_cmds[1]["doc"] == {
3806
+ "id" => 1,
3807
+ "foo" => "bar",
3808
+ }
3809
+ )
3810
+ assert(
3811
+ index_cmds[1]["upsert"] == {
3812
+ "id" => 1,
3813
+ "foo" => "bar",
3814
+ "baz" => "quix",
3815
+ }
3816
+ )
3817
+ end
3818
+
3819
+ def test_upsert_should_remove_keys_from_key_on_record_has_higher_presedence_than_config
3820
+ driver.configure("write_operation upsert
3821
+ id_key id
3822
+ remove_keys_on_update foo,bar
3823
+ remove_keys_on_update_key keys_to_skip")
3824
+ stub_opensearch
3825
+ stub_opensearch_info
3826
+ driver.run(default_tag: 'test') do
3827
+ driver.feed("id" => 1, "foo" => "bar", "baz" => "quix", "keys_to_skip" => ["baz"])
3828
+ end
3829
+ assert(
3830
+ index_cmds[1]["doc"] == {
3831
+ "id" => 1,
3832
+ # we only expect baz to be stripped here, if the config was more important
3833
+ # foo would be stripped too.
3834
+ "foo" => "bar",
3835
+ }
3836
+ )
3837
+ assert(
3838
+ index_cmds[1]["upsert"] == {
3839
+ "id" => 1,
3840
+ "foo" => "bar",
3841
+ "baz" => "quix",
3842
+ }
3843
+ )
3844
+ end
3845
+
3846
+ def test_create_should_write_create_op
3847
+ driver.configure("write_operation create
3848
+ id_key request_id")
3849
+ stub_opensearch
3850
+ stub_opensearch_info
3851
+ driver.run(default_tag: 'test') do
3852
+ driver.feed(sample_record)
3853
+ end
3854
+ assert(index_cmds[0].has_key?("create"))
3855
+ end
3856
+
3857
+ def test_include_index_in_url
3858
+ stub_opensearch('http://localhost:9200/logstash-2018.01.01/_bulk')
3859
+ stub_opensearch_info('http://localhost:9200/')
3860
+
3861
+ driver.configure("index_name logstash-2018.01.01
3862
+ include_index_in_url true")
3863
+ driver.run(default_tag: 'test') do
3864
+ driver.feed(sample_record)
3865
+ end
3866
+
3867
+ assert_equal(2, index_cmds.length)
3868
+ assert_equal(nil, index_cmds.first['index']['_index'])
3869
+ end
3870
+
3871
+ def test_use_simple_sniffer
3872
+ require 'fluent/plugin/opensearch_simple_sniffer'
3873
+ stub_opensearch
3874
+ stub_opensearch_info
3875
+ config = %[
3876
+ sniffer_class_name Fluent::Plugin::OpenSearchSimpleSniffer
3877
+ log_level debug
3878
+ with_transporter_log true
3879
+ reload_connections true
3880
+ reload_after 1
3881
+ ]
3882
+ driver(config, nil)
3883
+ driver.run(default_tag: 'test') do
3884
+ driver.feed(sample_record)
3885
+ end
3886
+ log = driver.logs
3887
+ # 2 or 3 - one for the ping, one for the _bulk, (and client.info)
3888
+ assert_logs_include_compare_size(3, ">", log, /In Fluent::Plugin::OpenSearchSimpleSniffer hosts/)
3889
+ assert_logs_include_compare_size(1, "<=", log, /In Fluent::Plugin::OpenSearchSimpleSniffer hosts/)
3890
+ end
3891
+
3892
+ def test_suppress_doc_wrap
3893
+ driver.configure('write_operation update
3894
+ id_key id
3895
+ remove_keys id
3896
+ suppress_doc_wrap true')
3897
+ stub_opensearch
3898
+ stub_opensearch_info
3899
+ doc_body = {'field' => 'value'}
3900
+ script_body = {'source' => 'ctx._source.counter += params.param1',
3901
+ 'lang' => 'painless',
3902
+ 'params' => {'param1' => 1}}
3903
+ upsert_body = {'counter' => 1}
3904
+ driver.run(default_tag: 'test') do
3905
+ driver.feed('id' => 1, 'doc' => doc_body)
3906
+ driver.feed('id' => 2, 'script' => script_body, 'upsert' => upsert_body)
3907
+ end
3908
+ assert(
3909
+ index_cmds[1] == {'doc' => doc_body}
3910
+ )
3911
+ assert(
3912
+ index_cmds[3] == {
3913
+ 'script' => script_body,
3914
+ 'upsert' => upsert_body
3915
+ }
3916
+ )
3917
+ end
3918
+
3919
+ def test_suppress_doc_wrap_should_handle_record_as_is_at_upsert
3920
+ driver.configure('write_operation upsert
3921
+ id_key id
3922
+ remove_keys id
3923
+ suppress_doc_wrap true')
3924
+ stub_opensearch
3925
+ stub_opensearch_info
3926
+ doc_body = {'field' => 'value'}
3927
+ script_body = {'source' => 'ctx._source.counter += params.param1',
3928
+ 'lang' => 'painless',
3929
+ 'params' => {'param1' => 1}}
3930
+ upsert_body = {'counter' => 1}
3931
+ driver.run(default_tag: 'test') do
3932
+ driver.feed('id' => 1, 'doc' => doc_body, 'doc_as_upsert' => true)
3933
+ driver.feed('id' => 2, 'script' => script_body, 'upsert' => upsert_body)
3934
+ end
3935
+ assert(
3936
+ index_cmds[1] == {
3937
+ 'doc' => doc_body,
3938
+ 'doc_as_upsert' => true
3939
+ }
3940
+ )
3941
+ assert(
3942
+ index_cmds[3] == {
3943
+ 'script' => script_body,
3944
+ 'upsert' => upsert_body
3945
+ }
3946
+ )
3947
+ end
3948
+
3949
+ def test_ignore_exception
3950
+ driver.configure('ignore_exceptions ["OpenSearch::Transport::Transport::Errors::ServiceUnavailable"]')
3951
+ stub_opensearch_unavailable
3952
+ stub_opensearch_info
3953
+
3954
+ driver.run(default_tag: 'test') do
3955
+ driver.feed(sample_record)
3956
+ end
3957
+ end
3958
+
3959
+ def test_ignore_exception_with_superclass
3960
+ driver.configure('ignore_exceptions ["OpenSearch::Transport::Transport::ServerError"]')
3961
+ stub_opensearch_unavailable
3962
+ stub_opensearch_info
3963
+
3964
+ driver.run(default_tag: 'test') do
3965
+ driver.feed(sample_record)
3966
+ end
3967
+ end
3968
+
3969
+ def test_ignore_excetion_handles_appropriate_ones
3970
+ driver.configure('ignore_exceptions ["Faraday::ConnectionFailed"]')
3971
+ stub_opensearch_unavailable
3972
+ stub_opensearch_info
3973
+
3974
+ assert_raise(Fluent::Plugin::OpenSearchOutput::RecoverableRequestFailure) {
3975
+ driver.run(default_tag: 'test', shutdown: false) do
3976
+ driver.feed(sample_record)
3977
+ end
3978
+ }
3979
+ end
3980
+ end