fluent-plugin-elasticsearch 5.0.0 → 5.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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