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,746 @@
1
+ require_relative '../helper'
2
+ require 'date'
3
+ require 'fluent/test/helpers'
4
+ require 'fluent/test/driver/output'
5
+ require 'flexmock/test_unit'
6
+ require 'fluent/plugin/out_opensearch_data_stream'
7
+
8
+ class OpenSearchOutputDataStreamTest < Test::Unit::TestCase
9
+ include FlexMock::TestCase
10
+ include Fluent::Test::Helpers
11
+
12
+ attr_accessor :bulk_records, :index_cmds
13
+
14
+ OPENSEARCH_DATA_STREAM_TYPE = "opensearch_data_stream"
15
+
16
+ def setup
17
+ Fluent::Test.setup
18
+ @driver = nil
19
+ log = Fluent::Engine.log
20
+ log.out.logs.slice!(0, log.out.logs.length)
21
+ @bulk_records = 0
22
+ end
23
+
24
+ def driver(conf='', os_version=1, client_version="\"1.0\"")
25
+ # For request stub to detect compatibility.
26
+ @os_version ||= os_version
27
+ @client_version ||= client_version
28
+ Fluent::Plugin::OpenSearchOutputDataStream.module_eval(<<-CODE)
29
+ def detect_os_major_version
30
+ #{@os_version}
31
+ end
32
+ CODE
33
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::OpenSearchOutputDataStream) {
34
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
35
+ if !defined?(Fluent::Plugin::Output)
36
+ def format(tag, time, record)
37
+ [time, record].to_msgpack
38
+ end
39
+ end
40
+ }.configure(conf)
41
+ end
42
+
43
+ def sample_data_stream
44
+ {
45
+ 'data_streams': [
46
+ {
47
+ 'name' => 'foo',
48
+ 'timestamp_field' => {
49
+ 'name' => '@timestamp'
50
+ }
51
+ }
52
+ ]
53
+ }
54
+ end
55
+
56
+ def sample_record
57
+ {'@timestamp' => Time.now.iso8601, 'message' => 'Sample record'}
58
+ end
59
+
60
+ RESPONSE_ACKNOWLEDGED = {"acknowledged": true}
61
+ DUPLICATED_DATA_STREAM_EXCEPTION = {"error": {}, "status": 400}
62
+ NONEXISTENT_DATA_STREAM_EXCEPTION = {"error": {}, "status": 404}
63
+
64
+ def stub_index_template(name="foo_tpl", url="http://localhost:9200")
65
+ stub_request(:put, "#{url}/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
66
+ end
67
+
68
+ def stub_data_stream(name="foo", url="localhost:9200")
69
+ stub_request(:put, "#{url}/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
70
+ end
71
+
72
+ def stub_existent_data_stream?(name="foo", url="localhost:9200")
73
+ stub_request(:get, "#{url}/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
74
+ end
75
+
76
+ def stub_existent_template?(name="foo_tpl", url="localhost:9200")
77
+ stub_request(:get, "#{url}/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
78
+ end
79
+
80
+ def stub_nonexistent_data_stream?(name="foo", url="localhost:9200")
81
+ stub_request(:get, "#{url}/_data_stream/#{name}").to_return(:status => [404, OpenSearch::Transport::Transport::Errors::NotFound])
82
+ end
83
+
84
+ def stub_nonexistent_template?(name="foo_tpl", url="http://localhost:9200")
85
+ stub_request(:get, "#{url}/_index_template/#{name}").to_return(:status => [404, OpenSearch::Transport::Transport::Errors::NotFound])
86
+ end
87
+
88
+ def stub_nonexistent_template_retry?(name="foo_tpl", url="http://localhost:9200")
89
+ stub_request(:get, "#{url}/_index_template/#{name}").
90
+ to_return({ status: 500, body: 'Internal Server Error' }, { status: 404, body: '{}' })
91
+ end
92
+
93
+ def stub_bulk_feed(datastream_name="foo", template_name="foo_tpl", url="http://localhost:9200")
94
+ stub_request(:post, "#{url}/#{datastream_name}/_bulk").with do |req|
95
+ # bulk data must be pair of OP and records
96
+ # {"create": {}}\nhttp://localhost:9200/_data_stream/foo_bar
97
+ # {"@timestamp": ...}
98
+ @bulk_records += req.body.split("\n").size / 2
99
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
100
+ end
101
+ stub_request(:post, "http://#{url}#{template_name}/_bulk").with do |req|
102
+ # bulk data must be pair of OP and records
103
+ # {"create": {}}\nhttp://localhost:9200/_data_stream/foo_bar
104
+ # {"@timestamp": ...}
105
+ @bulk_records += req.body.split("\n").size / 2
106
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
107
+ end
108
+ end
109
+
110
+ def stub_opensearch_info(url="http://localhost:9200/", version="1.2.2", headers={})
111
+ body ="{\"version\":{\"number\":\"#{version}\", \"distribution\":\"opensearch\"},\"tagline\":\"The OpenSearch Project: https://opensearch.org/\"}"
112
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'json' }.merge(headers) })
113
+ end
114
+
115
+ def stub_default(datastream_name="foo", template_name="foo_tpl", host="http://localhost:9200")
116
+ stub_opensearch_info(host)
117
+ stub_nonexistent_template?(template_name)
118
+ stub_index_template(template_name)
119
+ stub_nonexistent_data_stream?(datastream_name)
120
+ stub_data_stream(datastream_name)
121
+ end
122
+
123
+ def stub_opensearch_with_store_index_command_counts(url="http://localhost:9200/_bulk")
124
+ if @index_command_counts == nil
125
+ @index_command_counts = {}
126
+ @index_command_counts.default = 0
127
+ end
128
+
129
+ stub_request(:post, url).with do |req|
130
+ index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
131
+ @index_command_counts[url] += index_cmds.size
132
+ end
133
+ end
134
+
135
+ # ref. https://opensearch.org/docs/latest/opensearch/data-streams/
136
+ class DataStreamNameTest < self
137
+
138
+ def test_missing_data_stream_name
139
+ conf = config_element(
140
+ 'ROOT', '', {
141
+ '@type' => 'opensearch_datastream'
142
+ })
143
+ assert_raise Fluent::ConfigError.new("'data_stream_name' parameter is required") do
144
+ driver(conf).run
145
+ end
146
+ end
147
+
148
+ sub_test_case "invalid uppercase" do
149
+ def test_stream_name
150
+ conf = config_element(
151
+ 'ROOT', '', {
152
+ '@type' => 'opensearch_datastream',
153
+ 'data_stream_name' => 'TEST',
154
+ 'data_stream_template_name' => 'template'
155
+ })
156
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must be lowercase only: <TEST>") do
157
+ driver(conf)
158
+ end
159
+ end
160
+ def test_stream_template_name
161
+ conf = config_element(
162
+ 'ROOT', '', {
163
+ '@type' => 'opensearch_datastream',
164
+ 'data_stream_name' => 'default',
165
+ 'data_stream_template_name' => 'TEST-TPL'
166
+ })
167
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must be lowercase only: <TEST-TPL>") do
168
+ driver(conf)
169
+ end
170
+ end
171
+ end
172
+
173
+ sub_test_case "invalid parameters" do
174
+ data("backslash" => "\\",
175
+ "slash" => "/",
176
+ "asterisk" => "*",
177
+ "question" => "?",
178
+ "doublequote" => "\"",
179
+ "lt" => "<",
180
+ "gt" => ">",
181
+ "bar" => "|",
182
+ "space" => " ",
183
+ "comma" => ",",
184
+ "sharp" => "#",
185
+ "colon" => ":")
186
+ def test_stream_name(data)
187
+ c, _ = data
188
+ conf = config_element(
189
+ 'ROOT', '', {
190
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
191
+ 'data_stream_name' => "TEST#{c}",
192
+ 'data_stream_template_name' => "data_stream"
193
+ })
194
+ label = Fluent::Plugin::OpenSearchOutputDataStream::INVALID_CHARACTERS.join(',')
195
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not contain invalid characters #{label}: <TEST#{c}>") do
196
+ driver(conf)
197
+ end
198
+ end
199
+
200
+ data("backslash" => "\\",
201
+ "slash" => "/",
202
+ "asterisk" => "*",
203
+ "question" => "?",
204
+ "doublequote" => "\"",
205
+ "lt" => "<",
206
+ "gt" => ">",
207
+ "bar" => "|",
208
+ "space" => " ",
209
+ "comma" => ",",
210
+ "sharp" => "#",
211
+ "colon" => ":")
212
+ def test_stream_template_name(data)
213
+ c, _ = data
214
+ conf = config_element(
215
+ 'ROOT', '', {
216
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
217
+ 'data_stream_name' => "default",
218
+ 'data_stream_template_name' => "TEST#{c}"
219
+ })
220
+ label = Fluent::Plugin::OpenSearchOutputDataStream::INVALID_CHARACTERS.join(',')
221
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not contain invalid characters #{label}: <TEST#{c}>") do
222
+ driver(conf)
223
+ end
224
+ end
225
+ end
226
+
227
+ sub_test_case "invalid start characters" do
228
+ data("hyphen" => "-",
229
+ "underscore" => "_",
230
+ "plus" => "+",
231
+ "period" => ".")
232
+ def test_stream_name(data)
233
+ c, _ = data
234
+ conf = config_element(
235
+ 'ROOT', '', {
236
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
237
+ 'data_stream_name' => "#{c}TEST",
238
+ 'data_stream_template_name' => "template"
239
+ })
240
+ label = Fluent::Plugin::OpenSearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
241
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not start with #{label}: <#{c}TEST>") do
242
+ driver(conf)
243
+ end
244
+ end
245
+
246
+ data("hyphen" => "-",
247
+ "underscore" => "_",
248
+ "plus" => "+",
249
+ "period" => ".")
250
+ def test_stream_template_name(data)
251
+ c, _ = data
252
+ conf = config_element(
253
+ 'ROOT', '', {
254
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
255
+ 'data_stream_name' => "default",
256
+ 'data_stream_template_name' => "#{c}TEST"
257
+ })
258
+ label = Fluent::Plugin::OpenSearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
259
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not start with #{label}: <#{c}TEST>") do
260
+ driver(conf)
261
+ end
262
+ end
263
+ end
264
+
265
+ sub_test_case "invalid dots" do
266
+ data("current" => ".",
267
+ "parents" => "..")
268
+ def test_stream_name
269
+ c, _ = data
270
+ conf = config_element(
271
+ 'ROOT', '', {
272
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
273
+ 'data_stream_name' => "#{c}",
274
+ 'data_stream_template_name' => "template"
275
+ })
276
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not be . or ..: <#{c}>") do
277
+ driver(conf)
278
+ end
279
+ end
280
+
281
+ data("current" => ".",
282
+ "parents" => "..")
283
+ def test_stream_template_name
284
+ c, _ = data
285
+ conf = config_element(
286
+ 'ROOT', '', {
287
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
288
+ 'data_stream_name' => "default",
289
+ 'data_stream_template_name' => "#{c}"
290
+ })
291
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not be . or ..: <#{c}>") do
292
+ driver(conf)
293
+ end
294
+ end
295
+ end
296
+
297
+ sub_test_case "invalid length" do
298
+ def test_stream_name
299
+ c = "a" * 256
300
+ conf = config_element(
301
+ 'ROOT', '', {
302
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
303
+ 'data_stream_name' => "#{c}",
304
+ 'data_stream_template_name' => "template"
305
+ })
306
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not be longer than 255 bytes: <#{c}>") do
307
+ driver(conf)
308
+ end
309
+ end
310
+
311
+ def test_stream_template_name
312
+ c = "a" * 256
313
+ conf = config_element(
314
+ 'ROOT', '', {
315
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
316
+ 'data_stream_name' => "default",
317
+ 'data_stream_template_name' => "#{c}"
318
+ })
319
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not be longer than 255 bytes: <#{c}>") do
320
+ driver(conf)
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ def test_datastream_configure
327
+ stub_default
328
+ conf = config_element(
329
+ 'ROOT', '', {
330
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
331
+ 'data_stream_name' => 'foo',
332
+ 'data_stream_template_name' => "foo_tpl"
333
+ })
334
+ assert_equal "foo", driver(conf).instance.data_stream_name
335
+ end
336
+
337
+ def test_hosts_list_configure
338
+ config = %{
339
+ hosts https://john:password@host1:443/elastic/,http://host2
340
+ path /default_path
341
+ user default_user
342
+ password default_password
343
+ data_stream_name default
344
+ }
345
+ stub_opensearch_info("https://host1:443/elastic//", "1.2.2",
346
+ {'Authorization'=>"Basic #{Base64.encode64('john:password').split.first}"})
347
+ stub_opensearch_info("http://host2/default_path/_data_stream/default", "1.2.2",
348
+ {'Authorization'=>"Basic #{Base64.encode64('john:password').split.first}"})
349
+ stub_existent_data_stream?("default", "https://host1/elastic/")
350
+ instance = driver(config).instance
351
+
352
+ assert_equal 2, instance.get_connection_options[:hosts].length
353
+ host1, host2 = instance.get_connection_options[:hosts]
354
+
355
+ assert_equal 'host1', host1[:host]
356
+ assert_equal 443, host1[:port]
357
+ assert_equal 'https', host1[:scheme]
358
+ assert_equal 'john', host1[:user]
359
+ assert_equal 'password', host1[:password]
360
+ assert_equal '/elastic/', host1[:path]
361
+
362
+ assert_equal 'host2', host2[:host]
363
+ assert_equal 'http', host2[:scheme]
364
+ assert_equal 'default_user', host2[:user]
365
+ assert_equal 'default_password', host2[:password]
366
+ assert_equal '/default_path', host2[:path]
367
+ end
368
+
369
+ def test_datastream_configure_retry
370
+ stub_opensearch_info
371
+ stub_nonexistent_template_retry?
372
+ stub_index_template
373
+ stub_nonexistent_data_stream?
374
+ stub_data_stream
375
+ conf = config_element(
376
+ 'ROOT', '', {
377
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
378
+ 'data_stream_name' => 'foo',
379
+ 'data_stream_template_name' => "foo_tpl"
380
+ })
381
+ assert_equal "foo", driver(conf).instance.data_stream_name
382
+ end
383
+
384
+ def test_template_file
385
+ stub_default
386
+ cwd = File.dirname(__FILE__)
387
+ conf = config_element(
388
+ 'ROOT', '', {
389
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
390
+ 'data_stream_name' => 'foo',
391
+ 'data_stream_template_name' => "foo_tpl",
392
+ 'template_file' => File.join(cwd, 'datastream_template.json')
393
+ })
394
+ assert_equal "foo", driver(conf).instance.data_stream_name
395
+ end
396
+
397
+ def test_existent_data_stream
398
+ stub_index_template
399
+ stub_existent_data_stream?
400
+ stub_data_stream
401
+ stub_opensearch_info
402
+ conf = config_element(
403
+ 'ROOT', '', {
404
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
405
+ 'data_stream_name' => 'foo',
406
+ 'data_stream_template_name' => "foo_tpl"
407
+ })
408
+ assert_equal "foo", driver(conf).instance.data_stream_name
409
+ end
410
+
411
+ def test_template_unset
412
+ stub_index_template
413
+ stub_existent_data_stream?
414
+ stub_data_stream
415
+ stub_opensearch_info
416
+ conf = config_element(
417
+ 'ROOT', '', {
418
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
419
+ 'data_stream_name' => 'foo',
420
+ })
421
+ assert_equal "foo", driver(conf).instance.data_stream_name
422
+ assert_equal "foo_template", driver(conf).instance.data_stream_template_name
423
+ end
424
+
425
+ def test_placeholder
426
+ dsname = "foo_test"
427
+ tplname = "foo_tpl_test"
428
+ stub_default(dsname, tplname)
429
+ stub_bulk_feed(dsname, tplname)
430
+ conf = config_element(
431
+ 'ROOT', '', {
432
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
433
+ 'data_stream_name' => 'foo_${tag}',
434
+ 'data_stream_template_name' => "foo_tpl_${tag}"
435
+ })
436
+ driver(conf).run(default_tag: 'test') do
437
+ driver.feed(sample_record)
438
+ end
439
+ assert_equal 1, @bulk_records
440
+ end
441
+
442
+ def test_placeholder_with_capital_letters
443
+ dsname = "foo_test.capital_letters"
444
+ tplname = "foo_tpl_test.capital_letters"
445
+ stub_default(dsname, tplname)
446
+ stub_bulk_feed(dsname, tplname)
447
+ conf = config_element(
448
+ 'ROOT', '', {
449
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
450
+ 'data_stream_name' => 'foo_${tag}',
451
+ 'data_stream_template_name' => "foo_tpl_${tag}"
452
+ })
453
+ driver(conf).run(default_tag: 'TEST.CAPITAL_LETTERS') do
454
+ driver.feed(sample_record)
455
+ end
456
+ assert_equal 1, @bulk_records
457
+ end
458
+
459
+ def test_placeholder_params_unset
460
+ dsname = "foo_test"
461
+ tplname = "foo_test_template"
462
+ stub_default(dsname, tplname)
463
+ stub_bulk_feed(dsname, tplname)
464
+ conf = config_element(
465
+ 'ROOT', '', {
466
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
467
+ 'data_stream_name' => 'foo_${tag}',
468
+ })
469
+ driver(conf).run(default_tag: 'test') do
470
+ driver.feed(sample_record)
471
+ end
472
+ assert_equal 1, @bulk_records
473
+ end
474
+
475
+
476
+ def test_time_placeholder
477
+ time = Time.now
478
+ dsname = "foo_#{time.strftime("%Y%m%d")}"
479
+ tplname = "foo_tpl_#{time.strftime("%Y%m%d")}"
480
+ stub_default(dsname, tplname)
481
+ stub_bulk_feed(dsname, tplname)
482
+ conf = config_element(
483
+ 'ROOT', '', {
484
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
485
+ 'data_stream_name' => 'foo_%Y%m%d',
486
+ 'data_stream_template_name' => 'foo_tpl_%Y%m%d'
487
+ }, [config_element('buffer', 'time', {
488
+ 'timekey' => '1d'
489
+ }, [])]
490
+ )
491
+ driver(conf).run(default_tag: 'test') do
492
+ driver.feed(sample_record)
493
+ end
494
+ assert_equal 1, @bulk_records
495
+ end
496
+
497
+ def test_custom_record_placeholder
498
+ keys = ["bar", "baz"]
499
+ keys.each do |key|
500
+ dsname = "foo_#{key}"
501
+ tplname = "foo_tpl_#{key}"
502
+ stub_default(dsname, tplname)
503
+ stub_bulk_feed(dsname, tplname)
504
+ end
505
+ conf = config_element(
506
+ 'ROOT', '', {
507
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
508
+ 'data_stream_name' => 'foo_${key1}',
509
+ 'data_stream_template_name' => 'foo_tpl_${key1}'
510
+ }, [config_element('buffer', 'tag,key1', {
511
+ 'timekey' => '1d'
512
+ }, [])]
513
+ )
514
+ driver(conf).run(default_tag: 'test') do
515
+ keys.each do |key|
516
+ record = sample_record.merge({"key1" => key})
517
+ driver.feed(record)
518
+ end
519
+ end
520
+ assert_equal keys.count, @bulk_records
521
+ end
522
+
523
+ def test_bulk_insert_feed
524
+ stub_default
525
+ stub_bulk_feed
526
+ conf = config_element(
527
+ 'ROOT', '', {
528
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
529
+ 'data_stream_name' => 'foo',
530
+ 'data_stream_template_name' => 'foo_tpl'
531
+ })
532
+ driver(conf).run(default_tag: 'test') do
533
+ driver.feed(sample_record)
534
+ end
535
+ assert_equal 1, @bulk_records
536
+ end
537
+
538
+ def test_placeholder_writes_to_multi_hosts
539
+ stub_default("foo_bar", "foo_tpl_bar")
540
+ hosts = [['192.168.33.50', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
541
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
542
+ hosts.each do |host_info|
543
+ host, port = host_info
544
+ stub_opensearch_with_store_index_command_counts("http://#{host}:#{port}/foo_bar/_bulk")
545
+ stub_opensearch_info("http://#{host}:#{port}/")
546
+ stub_request(:get, "http://#{host}:#{port}/_data_stream/foo_bar").
547
+ to_return(status: 200, body: "", headers: {})
548
+ end
549
+
550
+ conf = config_element(
551
+ 'ROOT', '', {
552
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
553
+ 'data_stream_name' => 'foo_${key1}',
554
+ 'data_stream_template_name' => 'foo_tpl_${key1}',
555
+ 'hosts' => "#{hosts_string}"
556
+ }, [config_element('buffer', 'tag,key1', {
557
+ 'timekey' => '1d'
558
+ }, [])])
559
+ driver(conf).run(default_tag: 'test') do
560
+ hashes = {
561
+ 'age' => rand(100),
562
+ 'key1' => 'bar'
563
+ }
564
+ 1000.times do
565
+ driver.feed(sample_record.merge(hashes))
566
+ end
567
+ end
568
+
569
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
570
+ # it's Fluentd test driver's constraint
571
+ # so @index_command_counts.size is always 1
572
+ assert(@index_command_counts.size > 0, "not working with hosts options")
573
+
574
+ total = 0
575
+ @index_command_counts.each do |_, count|
576
+ total += count
577
+ end
578
+ assert_equal(2000, total)
579
+ end
580
+
581
+ def test_template_retry_install_fails
582
+ cwd = File.dirname(__FILE__)
583
+ template_file = File.join(cwd, 'test_index_template.json')
584
+
585
+ config = %{
586
+ host logs.google.com
587
+ port 778
588
+ scheme https
589
+ data_stream_name foo
590
+ data_stream_template_name foo_tpl
591
+ user john
592
+ password doe
593
+ template_name logstash
594
+ template_file #{template_file}
595
+ max_retry_putting_template 3
596
+ }
597
+
598
+ connection_resets = 0
599
+ # check if template exists
600
+ stub_request(:get, "https://logs.google.com:778/_index_template/logstash")
601
+ .with(basic_auth: ['john', 'doe']) do |req|
602
+ connection_resets += 1
603
+ raise Faraday::ConnectionFailed, "Test message"
604
+ end
605
+ stub_opensearch_info("https://logs.google.com:778/")
606
+
607
+ assert_raise(Fluent::Plugin::OpenSearchError::RetryableOperationExhaustedFailure) do
608
+ driver(config)
609
+ end
610
+
611
+ assert_equal(4, connection_resets)
612
+ end
613
+
614
+ def test_uses_custom_time_key
615
+ stub_default
616
+ stub_bulk_feed
617
+ conf = config_element(
618
+ 'ROOT', '', {
619
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
620
+ 'data_stream_name' => 'foo',
621
+ 'data_stream_template_name' => 'foo_tpl',
622
+ 'time_key' => 'vtm'
623
+ })
624
+
625
+ ts = DateTime.new(2021,2,3).iso8601(9)
626
+ record = {
627
+ 'vtm' => ts,
628
+ 'message' => 'Sample Record'
629
+ }
630
+
631
+ driver(conf).run(default_tag: 'test') do
632
+ driver.feed(record)
633
+ end
634
+ assert(index_cmds[1].has_key? '@timestamp')
635
+ assert_equal(ts, index_cmds[1]['@timestamp'])
636
+ end
637
+
638
+ def test_uses_custom_time_key_with_format
639
+ stub_default
640
+ stub_bulk_feed
641
+ conf = config_element(
642
+ 'ROOT', '', {
643
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
644
+ 'data_stream_name' => 'foo',
645
+ 'data_stream_template_name' => 'foo_tpl',
646
+ 'time_key' => 'vtm',
647
+ 'time_key_format' => '%Y-%m-%d %H:%M:%S.%N%z'
648
+ })
649
+ ts = "2021-02-03 13:14:01.673+02:00"
650
+ record = {
651
+ 'vtm' => ts,
652
+ 'message' => 'Sample Record'
653
+ }
654
+ driver(conf).run(default_tag: 'test') do
655
+ driver.feed(record)
656
+ end
657
+ assert(index_cmds[1].has_key? '@timestamp')
658
+ assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
659
+ end
660
+
661
+ def test_record_no_timestamp
662
+ stub_default
663
+ stub_bulk_feed
664
+ stub_default
665
+ stub_bulk_feed
666
+ conf = config_element(
667
+ 'ROOT', '', {
668
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
669
+ 'data_stream_name' => 'foo',
670
+ 'data_stream_template_name' => 'foo_tpl'
671
+ })
672
+ record = {
673
+ 'message' => 'Sample Record'
674
+ }
675
+ driver(conf).run(default_tag: 'test') do
676
+ driver.feed(record)
677
+ end
678
+ assert(index_cmds[1].has_key? '@timestamp')
679
+ end
680
+
681
+ def test_record_with_include_tag_key
682
+ stub_default
683
+ stub_bulk_feed
684
+ stub_default
685
+ stub_bulk_feed
686
+ conf = config_element(
687
+ 'ROOT', '', {
688
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
689
+ 'data_stream_name' => 'foo',
690
+ 'data_stream_template_name' => 'foo_tpl',
691
+ 'include_tag_key' => true,
692
+ 'tag_key' => 'test_tag'
693
+ })
694
+ record = {
695
+ 'message' => 'Sample Record'
696
+ }
697
+ driver(conf).run(default_tag: 'test') do
698
+ driver.feed(record)
699
+ end
700
+ assert(index_cmds[1].has_key?('test_tag'))
701
+ end
702
+
703
+ def test_record_without_include_tag_key
704
+ stub_default
705
+ stub_bulk_feed
706
+ stub_default
707
+ stub_bulk_feed
708
+ conf = config_element(
709
+ 'ROOT', '', {
710
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
711
+ 'data_stream_name' => 'foo',
712
+ 'data_stream_template_name' => 'foo_tpl',
713
+ 'include_tag_key' => false
714
+ })
715
+ record = {
716
+ 'message' => 'Sample Record'
717
+ }
718
+ driver(conf).run(default_tag: 'test') do
719
+ driver.feed(record)
720
+ end
721
+ assert(!index_cmds[1].has_key?('test'))
722
+ end
723
+
724
+ def test_record_with_remove_keys
725
+ stub_default
726
+ stub_bulk_feed
727
+ stub_default
728
+ stub_bulk_feed
729
+ conf = config_element(
730
+ 'ROOT', '', {
731
+ '@type' => OPENSEARCH_DATA_STREAM_TYPE,
732
+ 'data_stream_name' => 'foo',
733
+ 'data_stream_template_name' => 'foo_tpl',
734
+ 'remove_keys' => 'remove_me'
735
+ })
736
+ record = {
737
+ 'message' => 'Sample Record',
738
+ 'remove_me' => 'foo'
739
+ }
740
+ driver(conf).run(default_tag: 'test') do
741
+ driver.feed(record)
742
+ end
743
+ assert(!index_cmds[1].has_key?('remove_me'))
744
+ end
745
+
746
+ end