fluent-plugin-elasticsearch 5.0.0 → 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/linux.yml +5 -2
  4. data/.github/workflows/macos.yml +5 -2
  5. data/.github/workflows/windows.yml +5 -2
  6. data/Gemfile +1 -1
  7. data/History.md +65 -1
  8. data/README.Troubleshooting.md +91 -0
  9. data/README.md +129 -4
  10. data/fluent-plugin-elasticsearch.gemspec +2 -1
  11. data/lib/fluent/plugin/elasticsearch_compat.rb +30 -0
  12. data/lib/fluent/plugin/elasticsearch_error_handler.rb +19 -4
  13. data/lib/fluent/plugin/elasticsearch_fallback_selector.rb +2 -2
  14. data/lib/fluent/plugin/elasticsearch_index_lifecycle_management.rb +18 -4
  15. data/lib/fluent/plugin/elasticsearch_index_template.rb +20 -4
  16. data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +2 -1
  17. data/lib/fluent/plugin/filter_elasticsearch_genid.rb +1 -1
  18. data/lib/fluent/plugin/in_elasticsearch.rb +2 -1
  19. data/lib/fluent/plugin/oj_serializer.rb +2 -1
  20. data/lib/fluent/plugin/out_elasticsearch.rb +80 -19
  21. data/lib/fluent/plugin/out_elasticsearch_data_stream.rb +132 -62
  22. data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +3 -1
  23. data/test/plugin/mock_chunk.dat +0 -0
  24. data/test/plugin/test_elasticsearch_error_handler.rb +130 -23
  25. data/test/plugin/test_elasticsearch_fallback_selector.rb +16 -8
  26. data/test/plugin/test_elasticsearch_index_lifecycle_management.rb +55 -15
  27. data/test/plugin/test_filter_elasticsearch_genid.rb +16 -16
  28. data/test/plugin/test_in_elasticsearch.rb +20 -0
  29. data/test/plugin/test_out_elasticsearch.rb +795 -134
  30. data/test/plugin/test_out_elasticsearch_data_stream.rb +717 -117
  31. data/test/plugin/test_out_elasticsearch_dynamic.rb +150 -18
  32. metadata +21 -5
  33. data/.travis.yml +0 -40
  34. data/appveyor.yml +0 -20
@@ -19,10 +19,26 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
19
19
  @driver = nil
20
20
  log = Fluent::Engine.log
21
21
  log.out.logs.slice!(0, log.out.logs.length)
22
- @bulk_records = 0
22
+ @bulk_records = []
23
23
  end
24
24
 
25
- def driver(conf='', es_version=5, client_version="\"5.0\"")
25
+ def elasticsearch_version
26
+ if Gem::Version.new(TRANSPORT_CLASS::VERSION) >= Gem::Version.new("7.14.0")
27
+ TRANSPORT_CLASS::VERSION
28
+ else
29
+ '5.0.0'.freeze
30
+ end
31
+ end
32
+
33
+ def ilm_endpoint
34
+ if Gem::Version.new(TRANSPORT_CLASS::VERSION) >= Gem::Version.new("8.0.0")
35
+ '_enrich'.freeze
36
+ else
37
+ '_ilm'.freeze
38
+ end
39
+ end
40
+
41
+ def driver(conf='', es_version=elasticsearch_version.to_i, client_version=elasticsearch_version)
26
42
  # For request stub to detect compatibility.
27
43
  @es_version ||= es_version
28
44
  @client_version ||= client_version
@@ -45,7 +61,7 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
45
61
  {
46
62
  'data_streams': [
47
63
  {
48
- 'name' => 'my-data-stream',
64
+ 'name' => 'foo',
49
65
  'timestamp_field' => {
50
66
  'name' => '@timestamp'
51
67
  }
@@ -54,52 +70,110 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
54
70
  }
55
71
  end
56
72
 
73
+ SAMPLE_RECORD_TIMESTAMP = Time.now.iso8601
57
74
  def sample_record
58
- {'@timestamp' => Time.now.iso8601, 'message' => 'Sample record'}
75
+ {'@timestamp' => SAMPLE_RECORD_TIMESTAMP, 'message' => 'Sample record'}
76
+ end
77
+
78
+ def sample_record_no_timestamp
79
+ {'message' => 'Sample record no timestamp'}
59
80
  end
60
81
 
61
82
  RESPONSE_ACKNOWLEDGED = {"acknowledged": true}
62
83
  DUPLICATED_DATA_STREAM_EXCEPTION = {"error": {}, "status": 400}
63
84
  NONEXISTENT_DATA_STREAM_EXCEPTION = {"error": {}, "status": 404}
64
85
 
65
- def stub_ilm_policy(name="foo")
66
- stub_request(:put, "http://localhost:9200/_ilm/policy/#{name}_policy").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
86
+ def stub_ilm_policy(name="foo_ilm_policy", url="http://localhost:9200")
87
+ stub_request(:put, "#{url}/#{ilm_endpoint}/policy/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
88
+ end
89
+
90
+ def stub_index_template(name="foo_tpl", url="http://localhost:9200")
91
+ stub_request(:put, "#{url}/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
92
+ end
93
+
94
+ def stub_data_stream(name="foo", url="http://localhost:9200")
95
+ stub_request(:put, "#{url}/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
96
+ end
97
+
98
+ def stub_existent_data_stream?(name="foo", url="http://localhost:9200")
99
+ stub_request(:get, "#{url}/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
100
+ end
101
+
102
+ def stub_existent_ilm?(name="foo_ilm_policy", url="http://localhost:9200")
103
+
104
+ stub_request(:get, "#{url}/#{ilm_endpoint}/policy/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
105
+ end
106
+
107
+ def stub_existent_template?(name="foo_tpl", url="http://localhost:9200")
108
+ stub_request(:get, "#{url}/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
67
109
  end
68
110
 
69
- def stub_index_template(name="foo")
70
- stub_request(:put, "http://localhost:9200/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
111
+ def stub_nonexistent_data_stream?(name="foo", url="http://localhost:9200")
112
+ stub_request(:get, "#{url}/_data_stream/#{name}").to_return(:status => [404, TRANSPORT_CLASS::Transport::Errors::NotFound])
71
113
  end
72
114
 
73
- def stub_data_stream(name="foo")
74
- stub_request(:put, "http://localhost:9200/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
115
+ def stub_nonexistent_ilm?(name="foo_ilm_policy", url="http://localhost:9200")
116
+ stub_request(:get, "#{url}/#{ilm_endpoint}/policy/#{name}").to_return(:status => [404, TRANSPORT_CLASS::Transport::Errors::NotFound])
75
117
  end
76
118
 
77
- def stub_existent_data_stream?(name="foo")
78
- stub_request(:get, "http://localhost:9200/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
119
+ def stub_nonexistent_template?(name="foo_tpl", url="http://localhost:9200")
120
+ stub_request(:get, "#{url}/_index_template/#{name}").to_return(:status => [404, TRANSPORT_CLASS::Transport::Errors::NotFound])
79
121
  end
80
122
 
81
- def stub_nonexistent_data_stream?(name="foo")
82
- stub_request(:get, "http://localhost:9200/_data_stream/#{name}").to_return(:status => [200, Elasticsearch::Transport::Transport::Errors::NotFound])
123
+
124
+ def push_bulk_request(req_body)
125
+ # bulk data must be pair of OP and records
126
+ # {"create": {}}\nhttp://localhost:9200/_ilm/policy/foo_ilm_bar
127
+ # {"@timestamp": ...}
128
+ ops = req_body.split("\n")
129
+ @bulk_records += ops.values_at(
130
+ * ops.each_index.select {|i| i.odd? }
131
+ ).map{ |i| JSON.parse(i) }
132
+ end
133
+
134
+ def stub_nonexistent_template_retry?(name="foo_tpl", url="http://localhost:9200")
135
+ stub_request(:get, "#{url}/_index_template/#{name}").
136
+ to_return({ status: 500, body: 'Internal Server Error' }, { status: 404, body: '{}' })
83
137
  end
84
138
 
85
- def stub_bulk_feed(name="foo")
86
- stub_request(:post, "http://localhost:9200/#{name}/_bulk").with do |req|
139
+ def stub_bulk_feed(datastream_name="foo", ilm_name="foo_ilm_policy", template_name="foo_tpl", url="http://localhost:9200")
140
+ stub_request(:post, "#{url}/#{datastream_name}/_bulk").with do |req|
87
141
  # bulk data must be pair of OP and records
88
- # {"create": {}}\n
142
+ # {"create": {}}\nhttp://localhost:9200/_ilm/policy/foo_ilm_bar
89
143
  # {"@timestamp": ...}
90
- @bulk_records += req.body.split("\n").size / 2
144
+ push_bulk_request(req.body)
91
145
  end
146
+ stub_request(:post, "#{url}/#{ilm_name}/_bulk").with do |req|
147
+ # bulk data must be pair of OP and records
148
+ # {"create": {}}\nhttp://localhost:9200/_ilm/policy/foo_ilm_bar
149
+ # {"@timestamp": ...}
150
+ push_bulk_request(req.body)
151
+ end
152
+ stub_request(:post, "#{url}/#{template_name}/_bulk").with do |req|
153
+ # bulk data must be pair of OP and records
154
+ # {"create": {}}\nhttp://localhost:9200/_ilm/policy/foo_ilm_bar
155
+ # {"@timestamp": ...}
156
+ push_bulk_request(req.body)
157
+ end
158
+ end
159
+
160
+ def stub_elastic_info(url="http://localhost:9200/", version=elasticsearch_version, headers={})
161
+ body ="{\"version\":{\"number\":\"#{version}\", \"build_flavor\":\"default\"},\"tagline\" : \"You Know, for Search\"}"
162
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'json', 'x-elastic-product' => 'Elasticsearch' }.merge(headers) })
92
163
  end
93
164
 
94
- def stub_default(name="foo")
95
- stub_ilm_policy(name)
96
- stub_index_template(name)
97
- stub_existent_data_stream?(name)
98
- stub_data_stream(name)
165
+ def stub_default(datastream_name="foo", ilm_name="foo_ilm_policy", template_name="foo_tpl", host="http://localhost:9200")
166
+ stub_elastic_info(host)
167
+ stub_nonexistent_ilm?(ilm_name)
168
+ stub_ilm_policy(ilm_name)
169
+ stub_nonexistent_template?(template_name)
170
+ stub_index_template(template_name)
171
+ stub_nonexistent_data_stream?(datastream_name)
172
+ stub_data_stream(datastream_name)
99
173
  end
100
174
 
101
175
  def data_stream_supported?
102
- Gem::Version.create(::Elasticsearch::Transport::VERSION) >= Gem::Version.create("7.9.0")
176
+ Gem::Version.create(::TRANSPORT_CLASS::VERSION) >= Gem::Version.create("7.9.0")
103
177
  end
104
178
 
105
179
  # ref. https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-create-data-stream.html
@@ -115,82 +189,274 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
115
189
  end
116
190
  end
117
191
 
118
- def test_invalid_uppercase
119
- conf = config_element(
120
- 'ROOT', '', {
121
- '@type' => 'elasticsearch_datastream',
122
- 'data_stream_name' => 'TEST'
123
- })
124
- assert_raise Fluent::ConfigError.new("'data_stream_name' must be lowercase only: <TEST>") do
125
- driver(conf)
192
+ sub_test_case "invalid uppercase" do
193
+ def test_stream_name
194
+ conf = config_element(
195
+ 'ROOT', '', {
196
+ '@type' => 'elasticsearch_datastream',
197
+ 'data_stream_name' => 'TEST',
198
+ 'data_stream_ilm_name' => 'default-policy',
199
+ 'data_stream_template_name' => 'template'
200
+ })
201
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must be lowercase only: <TEST>") do
202
+ driver(conf)
203
+ end
204
+ end
205
+ def test_stream_ilm_name
206
+ conf = config_element(
207
+ 'ROOT', '', {
208
+ '@type' => 'elasticsearch_datastream',
209
+ 'data_stream_name' => 'data_stream',
210
+ 'data_stream_ilm_name' => 'TEST-ILM',
211
+ 'data_stream_template_name' => 'template'
212
+ })
213
+ assert_raise Fluent::ConfigError.new("'data_stream_ilm_name' must be lowercase only: <TEST-ILM>") do
214
+ driver(conf)
215
+ end
216
+ end
217
+ def test_stream_template_name
218
+ conf = config_element(
219
+ 'ROOT', '', {
220
+ '@type' => 'elasticsearch_datastream',
221
+ 'data_stream_name' => 'default',
222
+ 'data_stream_ilm_name' => 'default-policy',
223
+ 'data_stream_template_name' => 'TEST-TPL'
224
+ })
225
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must be lowercase only: <TEST-TPL>") do
226
+ driver(conf)
227
+ end
126
228
  end
127
229
  end
128
230
 
129
- data("backslash" => "\\",
130
- "slash" => "/",
131
- "asterisk" => "*",
132
- "question" => "?",
133
- "doublequote" => "\"",
134
- "lt" => "<",
135
- "gt" => ">",
136
- "bar" => "|",
137
- "space" => " ",
138
- "comma" => ",",
139
- "sharp" => "#",
140
- "colon" => ":")
141
- def test_invalid_characters(data)
142
- c, _ = data
143
- conf = config_element(
144
- 'ROOT', '', {
145
- '@type' => ELASTIC_DATA_STREAM_TYPE,
146
- 'data_stream_name' => "TEST#{c}"
147
- })
148
- label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_CHARACTERS.join(',')
149
- assert_raise Fluent::ConfigError.new("'data_stream_name' must not contain invalid characters #{label}: <TEST#{c}>") do
150
- driver(conf)
231
+ sub_test_case "invalid parameters" do
232
+ data("backslash" => "\\",
233
+ "slash" => "/",
234
+ "asterisk" => "*",
235
+ "question" => "?",
236
+ "doublequote" => "\"",
237
+ "lt" => "<",
238
+ "gt" => ">",
239
+ "bar" => "|",
240
+ "space" => " ",
241
+ "comma" => ",",
242
+ "sharp" => "#",
243
+ "colon" => ":")
244
+ def test_stream_name(data)
245
+ c, _ = data
246
+ conf = config_element(
247
+ 'ROOT', '', {
248
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
249
+ 'data_stream_name' => "TEST#{c}",
250
+ 'data_stream_ilm_name' => "default_policy",
251
+ 'data_stream_template_name' => "data_stream"
252
+ })
253
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_CHARACTERS.join(',')
254
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not contain invalid characters #{label}: <TEST#{c}>") do
255
+ driver(conf)
256
+ end
257
+ end
258
+
259
+ data("backslash" => "\\",
260
+ "slash" => "/",
261
+ "asterisk" => "*",
262
+ "question" => "?",
263
+ "doublequote" => "\"",
264
+ "lt" => "<",
265
+ "gt" => ">",
266
+ "bar" => "|",
267
+ "space" => " ",
268
+ "comma" => ",",
269
+ "sharp" => "#",
270
+ "colon" => ":")
271
+ def test_stream_ilm_name(data)
272
+ c, _ = data
273
+ conf = config_element(
274
+ 'ROOT', '', {
275
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
276
+ 'data_stream_name' => "default",
277
+ 'data_stream_ilm_name' => "TEST#{c}",
278
+ 'data_stream_template_name' => "data_stream"
279
+ })
280
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_CHARACTERS.join(',')
281
+ assert_raise Fluent::ConfigError.new("'data_stream_ilm_name' must not contain invalid characters #{label}: <TEST#{c}>") do
282
+ driver(conf)
283
+ end
284
+ end
285
+
286
+ data("backslash" => "\\",
287
+ "slash" => "/",
288
+ "asterisk" => "*",
289
+ "question" => "?",
290
+ "doublequote" => "\"",
291
+ "lt" => "<",
292
+ "gt" => ">",
293
+ "bar" => "|",
294
+ "space" => " ",
295
+ "comma" => ",",
296
+ "sharp" => "#",
297
+ "colon" => ":")
298
+ def test_stream_template_name(data)
299
+ c, _ = data
300
+ conf = config_element(
301
+ 'ROOT', '', {
302
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
303
+ 'data_stream_name' => "default",
304
+ 'data_stream_ilm_name' => "default_policy",
305
+ 'data_stream_template_name' => "TEST#{c}"
306
+ })
307
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_CHARACTERS.join(',')
308
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not contain invalid characters #{label}: <TEST#{c}>") do
309
+ driver(conf)
310
+ end
151
311
  end
152
312
  end
153
313
 
154
- data("hyphen" => "-",
155
- "underscore" => "_",
156
- "plus" => "+",
157
- "period" => ".")
158
- def test_invalid_start_characters(data)
159
- c, _ = data
160
- conf = config_element(
161
- 'ROOT', '', {
162
- '@type' => ELASTIC_DATA_STREAM_TYPE,
163
- 'data_stream_name' => "#{c}TEST"
164
- })
165
- label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
166
- assert_raise Fluent::ConfigError.new("'data_stream_name' must not start with #{label}: <#{c}TEST>") do
167
- driver(conf)
314
+ sub_test_case "invalid start characters" do
315
+ data("hyphen" => "-",
316
+ "underscore" => "_",
317
+ "plus" => "+",
318
+ "period" => ".")
319
+ def test_stream_name(data)
320
+ c, _ = data
321
+ conf = config_element(
322
+ 'ROOT', '', {
323
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
324
+ 'data_stream_name' => "#{c}TEST",
325
+ 'data_stream_ilm_name' => "default-policy",
326
+ 'data_stream_template_name' => "template"
327
+ })
328
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
329
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not start with #{label}: <#{c}TEST>") do
330
+ driver(conf)
331
+ end
332
+ end
333
+ data("hyphen" => "-",
334
+ "underscore" => "_",
335
+ "plus" => "+",
336
+ "period" => ".")
337
+ def test_stream_ilm_name(data)
338
+ c, _ = data
339
+ conf = config_element(
340
+ 'ROOT', '', {
341
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
342
+ 'data_stream_name' => "default",
343
+ 'data_stream_ilm_name' => "#{c}TEST",
344
+ 'data_stream_template_name' => "template"
345
+ })
346
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
347
+ assert_raise Fluent::ConfigError.new("'data_stream_ilm_name' must not start with #{label}: <#{c}TEST>") do
348
+ driver(conf)
349
+ end
350
+ end
351
+ data("hyphen" => "-",
352
+ "underscore" => "_",
353
+ "plus" => "+",
354
+ "period" => ".")
355
+ def test_stream_template_name(data)
356
+ c, _ = data
357
+ conf = config_element(
358
+ 'ROOT', '', {
359
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
360
+ 'data_stream_name' => "default",
361
+ 'data_stream_ilm_name' => "default-policy",
362
+ 'data_stream_template_name' => "#{c}TEST"
363
+ })
364
+ label = Fluent::Plugin::ElasticsearchOutputDataStream::INVALID_START_CHRACTERS.join(',')
365
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not start with #{label}: <#{c}TEST>") do
366
+ driver(conf)
367
+ end
168
368
  end
169
369
  end
170
370
 
171
- data("current" => ".",
172
- "parents" => "..")
173
- def test_invalid_dots
174
- c, _ = data
175
- conf = config_element(
176
- 'ROOT', '', {
177
- '@type' => ELASTIC_DATA_STREAM_TYPE,
178
- 'data_stream_name' => "#{c}"
179
- })
180
- assert_raise Fluent::ConfigError.new("'data_stream_name' must not be . or ..: <#{c}>") do
181
- driver(conf)
371
+ sub_test_case "invalid dots" do
372
+ data("current" => ".",
373
+ "parents" => "..")
374
+ def test_stream_name
375
+ c, _ = data
376
+ conf = config_element(
377
+ 'ROOT', '', {
378
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
379
+ 'data_stream_name' => "#{c}",
380
+ 'data_stream_ilm_name' => "default-policy",
381
+ 'data_stream_template_name' => "template"
382
+ })
383
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not be . or ..: <#{c}>") do
384
+ driver(conf)
385
+ end
386
+ end
387
+
388
+ data("current" => ".",
389
+ "parents" => "..")
390
+ def test_stream_ilm_name
391
+ c, _ = data
392
+ conf = config_element(
393
+ 'ROOT', '', {
394
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
395
+ 'data_stream_name' => "default",
396
+ 'data_stream_ilm_name' => "#{c}",
397
+ 'data_stream_template_name' => "template"
398
+ })
399
+ assert_raise Fluent::ConfigError.new("'data_stream_ilm_name' must not be . or ..: <#{c}>") do
400
+ driver(conf)
401
+ end
402
+ end
403
+
404
+ data("current" => ".",
405
+ "parents" => "..")
406
+ def test_stream_template_name
407
+ c, _ = data
408
+ conf = config_element(
409
+ 'ROOT', '', {
410
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
411
+ 'data_stream_name' => "default",
412
+ 'data_stream_ilm_name' => "default-policy",
413
+ 'data_stream_template_name' => "#{c}"
414
+ })
415
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not be . or ..: <#{c}>") do
416
+ driver(conf)
417
+ end
182
418
  end
183
419
  end
184
420
 
185
- def test_invalid_length
186
- c = "a" * 256
187
- conf = config_element(
188
- 'ROOT', '', {
189
- '@type' => ELASTIC_DATA_STREAM_TYPE,
190
- 'data_stream_name' => "#{c}"
191
- })
192
- assert_raise Fluent::ConfigError.new("'data_stream_name' must not be longer than 255 bytes: <#{c}>") do
193
- driver(conf)
421
+ sub_test_case "invalid length" do
422
+ def test_stream_name
423
+ c = "a" * 256
424
+ conf = config_element(
425
+ 'ROOT', '', {
426
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
427
+ 'data_stream_name' => "#{c}",
428
+ 'data_stream_ilm_name' => "default-policy",
429
+ 'data_stream_template_name' => "template"
430
+ })
431
+ assert_raise Fluent::ConfigError.new("'data_stream_name' must not be longer than 255 bytes: <#{c}>") do
432
+ driver(conf)
433
+ end
434
+ end
435
+ def test_stream_ilm_name
436
+ c = "a" * 256
437
+ conf = config_element(
438
+ 'ROOT', '', {
439
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
440
+ 'data_stream_name' => "default",
441
+ 'data_stream_ilm_name' => "#{c}",
442
+ 'data_stream_template_name' => "template"
443
+ })
444
+ assert_raise Fluent::ConfigError.new("'data_stream_ilm_name' must not be longer than 255 bytes: <#{c}>") do
445
+ driver(conf)
446
+ end
447
+ end
448
+ def test_stream_template_name
449
+ c = "a" * 256
450
+ conf = config_element(
451
+ 'ROOT', '', {
452
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
453
+ 'data_stream_name' => "default",
454
+ 'data_stream_ilm_name' => "default-policy",
455
+ 'data_stream_template_name' => "#{c}"
456
+ })
457
+ assert_raise Fluent::ConfigError.new("'data_stream_template_name' must not be longer than 255 bytes: <#{c}>") do
458
+ driver(conf)
459
+ end
194
460
  end
195
461
  end
196
462
  end
@@ -202,54 +468,192 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
202
468
  conf = config_element(
203
469
  'ROOT', '', {
204
470
  '@type' => ELASTIC_DATA_STREAM_TYPE,
205
- 'data_stream_name' => 'foo'
471
+ 'data_stream_name' => 'foo',
472
+ 'data_stream_ilm_name' => "foo_ilm_policy",
473
+ 'data_stream_template_name' => "foo_tpl"
206
474
  })
207
475
  assert_equal "foo", driver(conf).instance.data_stream_name
208
476
  end
209
477
 
210
- def test_nonexistent_data_stream
478
+ def test_datastream_configure_retry
479
+ stub_elastic_info
480
+ stub_nonexistent_ilm?
481
+ stub_ilm_policy
482
+ stub_nonexistent_template_retry?
483
+ stub_index_template
484
+ stub_nonexistent_data_stream?
485
+ stub_data_stream
486
+ conf = config_element(
487
+ 'ROOT', '', {
488
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
489
+ 'data_stream_name' => 'foo',
490
+ 'data_stream_ilm_name' => "foo_ilm_policy",
491
+ 'data_stream_template_name' => "foo_tpl"
492
+ })
493
+ assert_equal "foo", driver(conf).instance.data_stream_name
494
+ end
495
+
496
+ def test_hosts_list_configure
497
+ config = %{
498
+ hosts https://john:password@host1:443/elastic/,http://host2
499
+ path /default_path
500
+ user default_user
501
+ password default_password
502
+ data_stream_name default
503
+ }
504
+ stub_elastic_info("https://host1:443/elastic//", elasticsearch_version,
505
+ {'Authorization'=>"Basic #{Base64.encode64('john:password').split.first}"})
506
+ stub_elastic_info("http://host2/default_path/_data_stream/default", elasticsearch_version,
507
+ {'Authorization'=>"Basic #{Base64.encode64('john:password').split.first}"})
508
+ stub_existent_data_stream?("default", "https://host1/elastic/")
509
+ instance = driver(config).instance
510
+
511
+ assert_equal 2, instance.get_connection_options[:hosts].length
512
+ host1, host2 = instance.get_connection_options[:hosts]
513
+
514
+ assert_equal 'host1', host1[:host]
515
+ assert_equal 443, host1[:port]
516
+ assert_equal 'https', host1[:scheme]
517
+ assert_equal 'john', host1[:user]
518
+ assert_equal 'password', host1[:password]
519
+ assert_equal '/elastic/', host1[:path]
520
+
521
+ assert_equal 'host2', host2[:host]
522
+ assert_equal 'http', host2[:scheme]
523
+ assert_equal 'default_user', host2[:user]
524
+ assert_equal 'default_password', host2[:password]
525
+ assert_equal '/default_path', host2[:path]
526
+ end
527
+
528
+ def test_existent_data_stream
211
529
  omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
212
530
 
213
531
  stub_ilm_policy
214
532
  stub_index_template
215
- stub_nonexistent_data_stream?
533
+ stub_existent_data_stream?
534
+ stub_data_stream
535
+ stub_elastic_info
536
+ conf = config_element(
537
+ 'ROOT', '', {
538
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
539
+ 'data_stream_name' => 'foo',
540
+ 'data_stream_ilm_name' => "foo_ilm_policy",
541
+ 'data_stream_template_name' => "foo_tpl"
542
+ })
543
+ assert_equal "foo", driver(conf).instance.data_stream_name
544
+ end
545
+
546
+ def test_template_unset
547
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
548
+
549
+ stub_ilm_policy
550
+ stub_index_template
551
+ stub_existent_data_stream?
552
+ stub_data_stream
553
+ stub_elastic_info
554
+ conf = config_element(
555
+ 'ROOT', '', {
556
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
557
+ 'data_stream_name' => 'foo',
558
+ 'data_stream_ilm_name' => "foo_ilm_policy",
559
+ })
560
+ assert_equal "foo", driver(conf).instance.data_stream_name
561
+ assert_equal "foo_ilm_policy", driver(conf).instance.data_stream_ilm_name
562
+ assert_equal "foo_template", driver(conf).instance.data_stream_template_name
563
+ end
564
+
565
+ def test_ilm_unset
566
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
567
+
568
+ stub_ilm_policy
569
+ stub_index_template
570
+ stub_existent_data_stream?
216
571
  stub_data_stream
572
+ stub_elastic_info
217
573
  conf = config_element(
218
574
  'ROOT', '', {
219
575
  '@type' => ELASTIC_DATA_STREAM_TYPE,
220
- 'data_stream_name' => 'foo'
576
+ 'data_stream_name' => 'foo',
577
+ 'data_stream_template_name' => "foo_tpl"
221
578
  })
222
579
  assert_equal "foo", driver(conf).instance.data_stream_name
580
+ assert_equal "foo_tpl", driver(conf).instance.data_stream_template_name
581
+ end
582
+
583
+ def test_template_and_ilm_unset
584
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
585
+
586
+ stub_ilm_policy
587
+ stub_index_template
588
+ stub_existent_data_stream?
589
+ stub_data_stream
590
+ stub_elastic_info
591
+ conf = config_element(
592
+ 'ROOT', '', {
593
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
594
+ 'data_stream_name' => 'foo',
595
+ })
596
+ assert_equal "foo", driver(conf).instance.data_stream_name
597
+ assert_equal "foo_template", driver(conf).instance.data_stream_template_name
598
+ assert_equal "foo_policy", driver(conf).instance.data_stream_ilm_name
223
599
  end
224
600
 
225
601
  def test_placeholder
226
602
  omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
227
603
 
228
- name = "foo_test"
229
- stub_default(name)
230
- stub_bulk_feed(name)
604
+ dsname = "foo_test"
605
+ ilmname = "foo_ilm_test"
606
+ tplname = "foo_tpl_test"
607
+ stub_default(dsname, ilmname, tplname)
608
+ stub_bulk_feed(dsname, ilmname, tplname)
609
+ conf = config_element(
610
+ 'ROOT', '', {
611
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
612
+ 'data_stream_name' => 'foo_${tag}',
613
+ 'data_stream_ilm_name' => "foo_ilm_${tag}",
614
+ 'data_stream_template_name' => "foo_tpl_${tag}"
615
+ })
616
+ driver(conf).run(default_tag: 'test') do
617
+ driver.feed(sample_record)
618
+ end
619
+ assert_equal 1, @bulk_records.length
620
+ end
621
+
622
+ def test_placeholder_params_unset
623
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
624
+
625
+ dsname = "foo_test"
626
+ ilmname = "foo_test_policy"
627
+ tplname = "foo_test_template"
628
+ stub_default(dsname, ilmname, tplname)
629
+ stub_bulk_feed(dsname, ilmname, tplname)
231
630
  conf = config_element(
232
631
  'ROOT', '', {
233
632
  '@type' => ELASTIC_DATA_STREAM_TYPE,
234
- 'data_stream_name' => 'foo_${tag}'
633
+ 'data_stream_name' => 'foo_${tag}',
235
634
  })
236
635
  driver(conf).run(default_tag: 'test') do
237
636
  driver.feed(sample_record)
238
637
  end
239
- assert_equal 1, @bulk_records
638
+ assert_equal 1, @bulk_records.length
240
639
  end
241
640
 
641
+
242
642
  def test_time_placeholder
243
643
  omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
244
644
 
245
645
  time = Time.now
246
- name = "foo_#{time.strftime("%Y%m%d")}"
247
- stub_default(name)
248
- stub_bulk_feed(name)
646
+ dsname = "foo_#{time.strftime("%Y%m%d")}"
647
+ ilmname = "foo_ilm_#{time.strftime("%Y%m%d")}"
648
+ tplname = "foo_tpl_#{time.strftime("%Y%m%d")}"
649
+ stub_default(dsname, ilmname, tplname)
650
+ stub_bulk_feed(dsname, ilmname, tplname)
249
651
  conf = config_element(
250
652
  'ROOT', '', {
251
653
  '@type' => ELASTIC_DATA_STREAM_TYPE,
252
- 'data_stream_name' => 'foo_%Y%m%d'
654
+ 'data_stream_name' => 'foo_%Y%m%d',
655
+ 'data_stream_ilm_name' => 'foo_ilm_%Y%m%d',
656
+ 'data_stream_template_name' => 'foo_tpl_%Y%m%d'
253
657
  }, [config_element('buffer', 'time', {
254
658
  'timekey' => '1d'
255
659
  }, [])]
@@ -257,7 +661,7 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
257
661
  driver(conf).run(default_tag: 'test') do
258
662
  driver.feed(sample_record)
259
663
  end
260
- assert_equal 1, @bulk_records
664
+ assert_equal 1, @bulk_records.length
261
665
  end
262
666
 
263
667
  def test_custom_record_placeholder
@@ -265,14 +669,18 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
265
669
 
266
670
  keys = ["bar", "baz"]
267
671
  keys.each do |key|
268
- name = "foo_#{key}"
269
- stub_default(name)
270
- stub_bulk_feed(name)
672
+ dsname = "foo_#{key}"
673
+ ilmname = "foo_ilm_#{key}"
674
+ tplname = "foo_tpl_#{key}"
675
+ stub_default(dsname, ilmname, tplname)
676
+ stub_bulk_feed(dsname, ilmname, tplname)
271
677
  end
272
678
  conf = config_element(
273
679
  'ROOT', '', {
274
680
  '@type' => ELASTIC_DATA_STREAM_TYPE,
275
- 'data_stream_name' => 'foo_${key1}'
681
+ 'data_stream_name' => 'foo_${key1}',
682
+ 'data_stream_ilm_name' => 'foo_ilm_${key1}',
683
+ 'data_stream_template_name' => 'foo_tpl_${key1}'
276
684
  }, [config_element('buffer', 'tag,key1', {
277
685
  'timekey' => '1d'
278
686
  }, [])]
@@ -283,7 +691,7 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
283
691
  driver.feed(record)
284
692
  end
285
693
  end
286
- assert_equal keys.count, @bulk_records
694
+ assert_equal keys.count, @bulk_records.length
287
695
  end
288
696
 
289
697
  def test_bulk_insert_feed
@@ -294,12 +702,14 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
294
702
  conf = config_element(
295
703
  'ROOT', '', {
296
704
  '@type' => ELASTIC_DATA_STREAM_TYPE,
297
- 'data_stream_name' => 'foo'
705
+ 'data_stream_name' => 'foo',
706
+ 'data_stream_ilm_name' => 'foo_ilm_policy',
707
+ 'data_stream_template_name' => 'foo_tpl'
298
708
  })
299
709
  driver(conf).run(default_tag: 'test') do
300
710
  driver.feed(sample_record)
301
711
  end
302
- assert_equal 1, @bulk_records
712
+ assert_equal 1, @bulk_records.length
303
713
  end
304
714
 
305
715
  def test_template_retry_install_fails
@@ -309,14 +719,16 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
309
719
  template_file = File.join(cwd, 'test_index_template.json')
310
720
 
311
721
  config = %{
312
- host logs.google.com
313
- port 778
314
- scheme https
315
- data_stream_name foo
316
- user john
317
- password doe
318
- template_name logstash
319
- template_file #{template_file}
722
+ host logs.google.com
723
+ port 778
724
+ scheme https
725
+ data_stream_name foo
726
+ data_stream_ilm_name foo_ilm_policy
727
+ data_stream_template_name foo_tpl
728
+ user john
729
+ password doe
730
+ template_name logstash
731
+ template_file #{template_file}
320
732
  max_retry_putting_template 3
321
733
  }
322
734
 
@@ -327,6 +739,7 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
327
739
  connection_resets += 1
328
740
  raise Faraday::ConnectionFailed, "Test message"
329
741
  end
742
+ stub_elastic_info("https://logs.google.com:778/")
330
743
 
331
744
  assert_raise(Fluent::Plugin::ElasticsearchError::RetryableOperationExhaustedFailure) do
332
745
  driver(config)
@@ -334,4 +747,191 @@ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
334
747
 
335
748
  assert_equal(4, connection_resets)
336
749
  end
750
+
751
+ def test_doesnt_update_ilm_policy_if_overwrite_unset
752
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
753
+
754
+ config = %{
755
+ data_stream_name foo
756
+ data_stream_ilm_name foo_ilm_policy
757
+ data_stream_ilm_policy {"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"15d"}}}}}}
758
+ }
759
+
760
+ stub_elastic_info
761
+ stub_index_template
762
+ stub_existent_data_stream?
763
+ stub_existent_ilm?
764
+ stub_data_stream
765
+
766
+ stub_request(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy").
767
+ to_return(:status => 200, :body => "", :headers => {})
768
+
769
+ assert_nothing_raised {
770
+ driver(config)
771
+ }
772
+ assert_requested(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy", times: 0)
773
+ end
774
+
775
+ def test_updates_ilm_policy_if_overwrite_set
776
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
777
+
778
+ config = %{
779
+ data_stream_name foo
780
+ data_stream_ilm_name foo_ilm_policy
781
+ data_stream_ilm_policy {"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"15d"}}}}}}
782
+ data_stream_ilm_policy_overwrite true
783
+ }
784
+
785
+ stub_elastic_info
786
+ stub_index_template
787
+ stub_existent_data_stream?
788
+ stub_existent_ilm?
789
+ stub_data_stream
790
+
791
+ stub_request(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy").
792
+ to_return(:status => 200, :body => "", :headers => {})
793
+
794
+ assert_nothing_raised {
795
+ driver(config)
796
+ }
797
+
798
+ assert_requested(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy", times: 1)
799
+ assert_requested(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy",
800
+ body: '{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"15d"}}}}}}',
801
+ times: 1)
802
+ end
803
+
804
+ def test_creates_custom_ilm_policy_if_none_exists
805
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
806
+
807
+ config = %{
808
+ data_stream_name foo
809
+ data_stream_ilm_name foo_ilm_policy
810
+ data_stream_ilm_policy {"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"15d"}}}}}}
811
+ }
812
+
813
+ stub_elastic_info
814
+ stub_index_template("foo_template")
815
+ stub_data_stream
816
+ stub_nonexistent_data_stream?
817
+ stub_nonexistent_ilm?
818
+ stub_nonexistent_template?("foo_template")
819
+
820
+ stub_request(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy").
821
+ to_return(:status => 200, :body => "", :headers => {})
822
+
823
+ assert_nothing_raised {
824
+ driver(config)
825
+ }
826
+
827
+ assert_requested(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy", times: 1)
828
+ assert_requested(:put, "http://localhost:9200/#{ilm_endpoint}/policy/foo_ilm_policy",
829
+ body: '{"policy":{"phases":{"hot":{"actions":{"rollover":{"max_age":"15d"}}}}}}',
830
+ times: 1)
831
+ end
832
+
833
+ def test_doesnt_add_tag_key_when_not_configured
834
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
835
+
836
+ config = %{
837
+ data_stream_name foo
838
+ data_stream_template_name foo_tpl
839
+ data_stream_ilm_name foo_ilm_policy
840
+ }
841
+
842
+ stub_default
843
+ stub_bulk_feed
844
+ driver(config)
845
+ driver.run(default_tag: 'mytag') do
846
+ driver.feed(sample_record)
847
+ end
848
+
849
+ assert_equal(1, @bulk_records.length)
850
+ assert_false(@bulk_records[0].has_key?('tag'))
851
+ end
852
+
853
+
854
+ def test_adds_tag_key_when_configured
855
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
856
+
857
+ config = %{
858
+ data_stream_name foo
859
+ data_stream_template_name foo_tpl
860
+ data_stream_ilm_name foo_ilm_policy
861
+ include_tag_key true
862
+ }
863
+
864
+ stub_default
865
+ stub_bulk_feed
866
+ driver(config)
867
+ driver.run(default_tag: 'mytag') do
868
+ driver.feed(sample_record)
869
+ end
870
+
871
+ assert_equal(1, @bulk_records.length)
872
+ assert(@bulk_records[0].has_key?('tag'))
873
+ assert_equal('mytag', @bulk_records[0]['tag'])
874
+ end
875
+
876
+ def test_adds_custom_tag_key_when_configured
877
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
878
+
879
+ config = %{
880
+ data_stream_name foo
881
+ data_stream_template_name foo_tpl
882
+ data_stream_ilm_name foo_ilm_policy
883
+ include_tag_key true
884
+ tag_key custom_tag_key
885
+ }
886
+
887
+ stub_default
888
+ stub_bulk_feed
889
+ driver(config)
890
+ driver.run(default_tag: 'mytag') do
891
+ driver.feed(sample_record)
892
+ end
893
+
894
+ assert_equal(1, @bulk_records.length)
895
+ assert(@bulk_records[0].has_key?('custom_tag_key'))
896
+ assert_equal('mytag', @bulk_records[0]['custom_tag_key'])
897
+ end
898
+
899
+ def test_use_record_timestamp_if_present
900
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
901
+
902
+ stub_default
903
+ stub_bulk_feed
904
+ conf = config_element(
905
+ 'ROOT', '', {
906
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
907
+ 'data_stream_name' => 'foo',
908
+ 'data_stream_ilm_name' => 'foo_ilm_policy',
909
+ 'data_stream_template_name' => 'foo_tpl'
910
+ })
911
+ driver(conf).run(default_tag: 'test') do
912
+ driver.feed(sample_record)
913
+ end
914
+ assert_equal 1, @bulk_records.length
915
+ assert(@bulk_records[0].has_key?('@timestamp'))
916
+ assert_equal SAMPLE_RECORD_TIMESTAMP, @bulk_records[0]['@timestamp']
917
+ end
918
+
919
+ def test_add_timestamp_if_not_present_in_record
920
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
921
+
922
+ stub_default
923
+ stub_bulk_feed
924
+ conf = config_element(
925
+ 'ROOT', '', {
926
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
927
+ 'data_stream_name' => 'foo',
928
+ 'data_stream_ilm_name' => 'foo_ilm_policy',
929
+ 'data_stream_template_name' => 'foo_tpl'
930
+ })
931
+ driver(conf).run(default_tag: 'test') do
932
+ driver.feed(sample_record_no_timestamp)
933
+ end
934
+ assert_equal 1, @bulk_records.length
935
+ assert(@bulk_records[0].has_key?('@timestamp'))
936
+ end
337
937
  end