fluent-plugin-elasticsearch-dext 5.0.2

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.editorconfig +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  6. data/.github/workflows/issue-auto-closer.yml +12 -0
  7. data/.github/workflows/linux.yml +26 -0
  8. data/.github/workflows/macos.yml +26 -0
  9. data/.github/workflows/windows.yml +26 -0
  10. data/.gitignore +18 -0
  11. data/.travis.yml +40 -0
  12. data/CONTRIBUTING.md +24 -0
  13. data/Gemfile +11 -0
  14. data/History.md +553 -0
  15. data/ISSUE_TEMPLATE.md +30 -0
  16. data/LICENSE.txt +201 -0
  17. data/PULL_REQUEST_TEMPLATE.md +10 -0
  18. data/README.ElasticsearchGenID.md +116 -0
  19. data/README.ElasticsearchInput.md +293 -0
  20. data/README.Troubleshooting.md +601 -0
  21. data/README.md +1467 -0
  22. data/Rakefile +11 -0
  23. data/appveyor.yml +20 -0
  24. data/fluent-plugin-elasticsearch.gemspec +35 -0
  25. data/gemfiles/Gemfile.elasticsearch.v6 +12 -0
  26. data/lib/fluent/log-ext.rb +38 -0
  27. data/lib/fluent/plugin/default-ilm-policy.json +14 -0
  28. data/lib/fluent/plugin/elasticsearch_constants.rb +13 -0
  29. data/lib/fluent/plugin/elasticsearch_error.rb +5 -0
  30. data/lib/fluent/plugin/elasticsearch_error_handler.rb +129 -0
  31. data/lib/fluent/plugin/elasticsearch_fallback_selector.rb +9 -0
  32. data/lib/fluent/plugin/elasticsearch_index_lifecycle_management.rb +67 -0
  33. data/lib/fluent/plugin/elasticsearch_index_template.rb +211 -0
  34. data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +10 -0
  35. data/lib/fluent/plugin/elasticsearch_tls.rb +70 -0
  36. data/lib/fluent/plugin/filter_elasticsearch_genid.rb +77 -0
  37. data/lib/fluent/plugin/in_elasticsearch.rb +325 -0
  38. data/lib/fluent/plugin/oj_serializer.rb +22 -0
  39. data/lib/fluent/plugin/out_elasticsearch.rb +1108 -0
  40. data/lib/fluent/plugin/out_elasticsearch_data_stream.rb +218 -0
  41. data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +282 -0
  42. data/test/helper.rb +24 -0
  43. data/test/plugin/test_alias_template.json +9 -0
  44. data/test/plugin/test_elasticsearch_error_handler.rb +646 -0
  45. data/test/plugin/test_elasticsearch_fallback_selector.rb +74 -0
  46. data/test/plugin/test_elasticsearch_index_lifecycle_management.rb +66 -0
  47. data/test/plugin/test_elasticsearch_tls.rb +145 -0
  48. data/test/plugin/test_filter_elasticsearch_genid.rb +215 -0
  49. data/test/plugin/test_in_elasticsearch.rb +459 -0
  50. data/test/plugin/test_index_alias_template.json +11 -0
  51. data/test/plugin/test_index_template.json +25 -0
  52. data/test/plugin/test_oj_serializer.rb +19 -0
  53. data/test/plugin/test_out_elasticsearch.rb +5688 -0
  54. data/test/plugin/test_out_elasticsearch_data_stream.rb +337 -0
  55. data/test/plugin/test_out_elasticsearch_dynamic.rb +1134 -0
  56. data/test/plugin/test_template.json +23 -0
  57. data/test/test_log-ext.rb +35 -0
  58. metadata +236 -0
@@ -0,0 +1,337 @@
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_elasticsearch_data_stream'
7
+
8
+ class ElasticsearchOutputDataStreamTest < Test::Unit::TestCase
9
+ include FlexMock::TestCase
10
+ include Fluent::Test::Helpers
11
+
12
+ attr_accessor :bulk_records
13
+
14
+ REQUIRED_ELASTIC_MESSAGE = "Elasticsearch 7.9.0 or later is needed."
15
+ ELASTIC_DATA_STREAM_TYPE = "elasticsearch_data_stream"
16
+
17
+ def setup
18
+ Fluent::Test.setup
19
+ @driver = nil
20
+ log = Fluent::Engine.log
21
+ log.out.logs.slice!(0, log.out.logs.length)
22
+ @bulk_records = 0
23
+ end
24
+
25
+ def driver(conf='', es_version=5, client_version="\"5.0\"")
26
+ # For request stub to detect compatibility.
27
+ @es_version ||= es_version
28
+ @client_version ||= client_version
29
+ Fluent::Plugin::ElasticsearchOutputDataStream.module_eval(<<-CODE)
30
+ def detect_es_major_version
31
+ #{@es_version}
32
+ end
33
+ CODE
34
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutputDataStream) {
35
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
36
+ if !defined?(Fluent::Plugin::Output)
37
+ def format(tag, time, record)
38
+ [time, record].to_msgpack
39
+ end
40
+ end
41
+ }.configure(conf)
42
+ end
43
+
44
+ def sample_data_stream
45
+ {
46
+ 'data_streams': [
47
+ {
48
+ 'name' => 'my-data-stream',
49
+ 'timestamp_field' => {
50
+ 'name' => '@timestamp'
51
+ }
52
+ }
53
+ ]
54
+ }
55
+ end
56
+
57
+ def sample_record
58
+ {'@timestamp' => Time.now.iso8601, 'message' => 'Sample record'}
59
+ end
60
+
61
+ RESPONSE_ACKNOWLEDGED = {"acknowledged": true}
62
+ DUPLICATED_DATA_STREAM_EXCEPTION = {"error": {}, "status": 400}
63
+ NONEXISTENT_DATA_STREAM_EXCEPTION = {"error": {}, "status": 404}
64
+
65
+ def stub_ilm_policy(name="foo")
66
+ stub_request(:put, "http://localhost:9200/_ilm/policy/#{name}_policy").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
67
+ end
68
+
69
+ def stub_index_template(name="foo")
70
+ stub_request(:put, "http://localhost:9200/_index_template/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
71
+ end
72
+
73
+ def stub_data_stream(name="foo")
74
+ stub_request(:put, "http://localhost:9200/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
75
+ end
76
+
77
+ def stub_existent_data_stream?(name="foo")
78
+ stub_request(:get, "http://localhost:9200/_data_stream/#{name}").to_return(:status => [200, RESPONSE_ACKNOWLEDGED])
79
+ end
80
+
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])
83
+ end
84
+
85
+ def stub_bulk_feed(name="foo")
86
+ stub_request(:post, "http://localhost:9200/#{name}/_bulk").with do |req|
87
+ # bulk data must be pair of OP and records
88
+ # {"create": {}}\n
89
+ # {"@timestamp": ...}
90
+ @bulk_records += req.body.split("\n").size / 2
91
+ end
92
+ end
93
+
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)
99
+ end
100
+
101
+ def data_stream_supported?
102
+ Gem::Version.create(::Elasticsearch::Transport::VERSION) >= Gem::Version.create("7.9.0")
103
+ end
104
+
105
+ # ref. https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-create-data-stream.html
106
+ class DataStreamNameTest < self
107
+
108
+ def test_missing_data_stream_name
109
+ conf = config_element(
110
+ 'ROOT', '', {
111
+ '@type' => 'elasticsearch_datastream'
112
+ })
113
+ assert_raise Fluent::ConfigError.new("'data_stream_name' parameter is required") do
114
+ driver(conf).run
115
+ end
116
+ end
117
+
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)
126
+ end
127
+ end
128
+
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)
151
+ end
152
+ end
153
+
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)
168
+ end
169
+ end
170
+
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)
182
+ end
183
+ end
184
+
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)
194
+ end
195
+ end
196
+ end
197
+
198
+ def test_datastream_configure
199
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
200
+
201
+ stub_default
202
+ conf = config_element(
203
+ 'ROOT', '', {
204
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
205
+ 'data_stream_name' => 'foo'
206
+ })
207
+ assert_equal "foo", driver(conf).instance.data_stream_name
208
+ end
209
+
210
+ def test_nonexistent_data_stream
211
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
212
+
213
+ stub_ilm_policy
214
+ stub_index_template
215
+ stub_nonexistent_data_stream?
216
+ stub_data_stream
217
+ conf = config_element(
218
+ 'ROOT', '', {
219
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
220
+ 'data_stream_name' => 'foo'
221
+ })
222
+ assert_equal "foo", driver(conf).instance.data_stream_name
223
+ end
224
+
225
+ def test_placeholder
226
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
227
+
228
+ name = "foo_test"
229
+ stub_default(name)
230
+ stub_bulk_feed(name)
231
+ conf = config_element(
232
+ 'ROOT', '', {
233
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
234
+ 'data_stream_name' => 'foo_${tag}'
235
+ })
236
+ driver(conf).run(default_tag: 'test') do
237
+ driver.feed(sample_record)
238
+ end
239
+ assert_equal 1, @bulk_records
240
+ end
241
+
242
+ def test_time_placeholder
243
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
244
+
245
+ time = Time.now
246
+ name = "foo_#{time.strftime("%Y%m%d")}"
247
+ stub_default(name)
248
+ stub_bulk_feed(name)
249
+ conf = config_element(
250
+ 'ROOT', '', {
251
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
252
+ 'data_stream_name' => 'foo_%Y%m%d'
253
+ }, [config_element('buffer', 'time', {
254
+ 'timekey' => '1d'
255
+ }, [])]
256
+ )
257
+ driver(conf).run(default_tag: 'test') do
258
+ driver.feed(sample_record)
259
+ end
260
+ assert_equal 1, @bulk_records
261
+ end
262
+
263
+ def test_custom_record_placeholder
264
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
265
+
266
+ keys = ["bar", "baz"]
267
+ keys.each do |key|
268
+ name = "foo_#{key}"
269
+ stub_default(name)
270
+ stub_bulk_feed(name)
271
+ end
272
+ conf = config_element(
273
+ 'ROOT', '', {
274
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
275
+ 'data_stream_name' => 'foo_${key1}'
276
+ }, [config_element('buffer', 'tag,key1', {
277
+ 'timekey' => '1d'
278
+ }, [])]
279
+ )
280
+ driver(conf).run(default_tag: 'test') do
281
+ keys.each do |key|
282
+ record = sample_record.merge({"key1" => key})
283
+ driver.feed(record)
284
+ end
285
+ end
286
+ assert_equal keys.count, @bulk_records
287
+ end
288
+
289
+ def test_bulk_insert_feed
290
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
291
+
292
+ stub_default
293
+ stub_bulk_feed
294
+ conf = config_element(
295
+ 'ROOT', '', {
296
+ '@type' => ELASTIC_DATA_STREAM_TYPE,
297
+ 'data_stream_name' => 'foo'
298
+ })
299
+ driver(conf).run(default_tag: 'test') do
300
+ driver.feed(sample_record)
301
+ end
302
+ assert_equal 1, @bulk_records
303
+ end
304
+
305
+ def test_template_retry_install_fails
306
+ omit REQUIRED_ELASTIC_MESSAGE unless data_stream_supported?
307
+
308
+ cwd = File.dirname(__FILE__)
309
+ template_file = File.join(cwd, 'test_index_template.json')
310
+
311
+ 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}
320
+ max_retry_putting_template 3
321
+ }
322
+
323
+ connection_resets = 0
324
+ # check if template exists
325
+ stub_request(:get, "https://logs.google.com:778/_index_template/logstash")
326
+ .with(basic_auth: ['john', 'doe']) do |req|
327
+ connection_resets += 1
328
+ raise Faraday::ConnectionFailed, "Test message"
329
+ end
330
+
331
+ assert_raise(Fluent::Plugin::ElasticsearchError::RetryableOperationExhaustedFailure) do
332
+ driver(config)
333
+ end
334
+
335
+ assert_equal(4, connection_resets)
336
+ end
337
+ end
@@ -0,0 +1,1134 @@
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_elasticsearch_dynamic'
7
+
8
+ class ElasticsearchOutputDynamic < Test::Unit::TestCase
9
+ include FlexMock::TestCase
10
+ include Fluent::Test::Helpers
11
+
12
+ attr_accessor :index_cmds, :index_command_counts
13
+
14
+ def setup
15
+ Fluent::Test.setup
16
+ @driver = nil
17
+ end
18
+
19
+ def driver(conf='', es_version=5)
20
+ # For request stub to detect compatibility.
21
+ @es_version ||= es_version
22
+ Fluent::Plugin::ElasticsearchOutputDynamic.module_eval(<<-CODE)
23
+ def detect_es_major_version
24
+ #{@es_version}
25
+ end
26
+ CODE
27
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutputDynamic) {
28
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
29
+ if !defined?(Fluent::Plugin::Output)
30
+ def format(tag, time, record)
31
+ [time, record].to_msgpack
32
+ end
33
+ end
34
+ }.configure(conf)
35
+ end
36
+
37
+ def default_type_name
38
+ Fluent::Plugin::ElasticsearchOutput::DEFAULT_TYPE_NAME
39
+ end
40
+
41
+ def sample_record
42
+ {'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}
43
+ end
44
+
45
+ def nested_sample_record
46
+ {'nested' =>
47
+ {'age' => 26, 'parent_id' => 'parent', 'routing_id' => 'routing', 'request_id' => '42'}
48
+ }
49
+ end
50
+
51
+ def stub_elastic(url="http://localhost:9200/_bulk")
52
+ stub_request(:post, url).with do |req|
53
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
54
+ end
55
+ end
56
+
57
+ def stub_elastic_unavailable(url="http://localhost:9200/_bulk")
58
+ stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
59
+ end
60
+
61
+ def stub_elastic_timeout(url="http://localhost:9200/_bulk")
62
+ stub_request(:post, url).to_timeout
63
+ end
64
+
65
+ def stub_elastic_with_store_index_command_counts(url="http://localhost:9200/_bulk")
66
+ if @index_command_counts == nil
67
+ @index_command_counts = {}
68
+ @index_command_counts.default = 0
69
+ end
70
+
71
+ stub_request(:post, url).with do |req|
72
+ index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
73
+ @index_command_counts[url] += index_cmds.size
74
+ end
75
+ end
76
+
77
+ def assert_logs_include(logs, msg, exp_matches=1)
78
+ matches = logs.grep /#{msg}/
79
+ assert_equal(exp_matches, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
80
+ end
81
+
82
+ def test_configure
83
+ config = %{
84
+ host logs.google.com
85
+ port 777
86
+ scheme https
87
+ path /es/
88
+ user john
89
+ password doe
90
+ }
91
+ instance = driver(config).instance
92
+
93
+ conf = instance.dynamic_config
94
+ assert_equal 'logs.google.com', conf['host']
95
+ assert_equal "777", conf['port']
96
+ assert_equal :https, instance.scheme
97
+ assert_equal 'john', instance.user
98
+ assert_equal 'doe', instance.password
99
+ assert_equal '/es/', instance.path
100
+ assert_equal Fluent::Plugin::ElasticsearchTLS::DEFAULT_VERSION, instance.ssl_version
101
+ assert_nil instance.ssl_max_version
102
+ assert_nil instance.ssl_min_version
103
+ if Fluent::Plugin::ElasticsearchTLS::USE_TLS_MINMAX_VERSION
104
+ if defined?(OpenSSL::SSL::TLS1_3_VERSION)
105
+ assert_equal({max_version: OpenSSL::SSL::TLS1_3_VERSION, min_version: OpenSSL::SSL::TLS1_2_VERSION},
106
+ instance.ssl_version_options)
107
+ else
108
+ assert_equal({max_version: nil, min_version: OpenSSL::SSL::TLS1_2_VERSION},
109
+ instance.ssl_version_options)
110
+ end
111
+ else
112
+ assert_equal({version: Fluent::Plugin::ElasticsearchTLS::DEFAULT_VERSION},
113
+ instance.ssl_version_options)
114
+ end
115
+ assert_nil instance.client_key
116
+ assert_nil instance.client_cert
117
+ assert_nil instance.client_key_pass
118
+ assert_false instance.with_transporter_log
119
+ assert_equal :"application/json", instance.content_type
120
+ assert_equal :excon, instance.http_backend
121
+ assert_false instance.compression
122
+ assert_equal :no_compression, instance.compression_level
123
+ end
124
+
125
+ test 'configure compression' do
126
+ omit "elastisearch-ruby v7.2.0 or later is needed." if Gem::Version.create(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
127
+
128
+ config = %{
129
+ compression_level best_compression
130
+ }
131
+ instance = driver(config).instance
132
+
133
+ assert_equal true, instance.compression
134
+ end
135
+
136
+ test 'check compression strategy' do
137
+ omit "elastisearch-ruby v7.2.0 or later is needed." if Gem::Version.create(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
138
+
139
+ config = %{
140
+ compression_level best_speed
141
+ }
142
+ instance = driver(config).instance
143
+
144
+ assert_equal Zlib::BEST_SPEED, instance.compression_strategy
145
+ end
146
+
147
+ test 'check content-encoding header with compression' do
148
+ omit "elastisearch-ruby v7.2.0 or later is needed." if Gem::Version.create(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
149
+
150
+ config = %{
151
+ compression_level best_compression
152
+ }
153
+ instance = driver(config).instance
154
+
155
+ assert_equal nil, instance.client.transport.options[:transport_options][:headers]["Content-Encoding"]
156
+
157
+ stub_request(:post, "http://localhost:9200/_bulk").
158
+ to_return(status: 200, body: "", headers: {})
159
+ driver.run(default_tag: 'test') do
160
+ driver.feed(sample_record)
161
+ end
162
+ compressable = instance.compressable_connection
163
+
164
+ assert_equal "gzip", instance.client(nil, compressable).transport.options[:transport_options][:headers]["Content-Encoding"]
165
+ end
166
+
167
+ test 'check compression option is passed to transport' do
168
+ omit "elastisearch-ruby v7.2.0 or later is needed." if Gem::Version.create(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
169
+
170
+ config = %{
171
+ compression_level best_compression
172
+ }
173
+ instance = driver(config).instance
174
+
175
+ assert_equal false, instance.client.transport.options[:compression]
176
+
177
+ stub_request(:post, "http://localhost:9200/_bulk").
178
+ to_return(status: 200, body: "", headers: {})
179
+ driver.run(default_tag: 'test') do
180
+ driver.feed(sample_record)
181
+ end
182
+ compressable = instance.compressable_connection
183
+
184
+ assert_equal true, instance.client(nil, compressable).transport.options[:compression]
185
+ end
186
+
187
+ test 'configure Content-Type' do
188
+ config = %{
189
+ content_type application/x-ndjson
190
+ }
191
+ instance = driver(config).instance
192
+ assert_equal :"application/x-ndjson", instance.content_type
193
+ end
194
+
195
+ test 'invalid Content-Type' do
196
+ config = %{
197
+ content_type nonexistent/invalid
198
+ }
199
+ assert_raise(Fluent::ConfigError) {
200
+ driver(config)
201
+ }
202
+ end
203
+
204
+ test 'Detected Elasticsearch 7' do
205
+ config = %{
206
+ type_name changed
207
+ }
208
+ instance = driver(config, 7).instance
209
+ assert_equal '_doc', instance.type_name
210
+ end
211
+
212
+ def test_defaults
213
+ config = %{
214
+ host logs.google.com
215
+ scheme https
216
+ path /es/
217
+ user john
218
+ password doe
219
+ }
220
+ instance = driver(config).instance
221
+
222
+ conf = instance.dynamic_config
223
+ assert_equal "9200", conf['port']
224
+ assert_equal "false", conf['logstash_format']
225
+ assert_equal "true", conf['utc_index']
226
+ assert_equal false, instance.time_key_exclude_timestamp
227
+ end
228
+
229
+ def test_legacy_hosts_list
230
+ config = %{
231
+ hosts host1:50,host2:100,host3
232
+ scheme https
233
+ path /es/
234
+ port 123
235
+ }
236
+ instance = driver(config).instance
237
+
238
+ assert_equal 3, instance.get_connection_options(nil)[:hosts].length
239
+ host1, host2, host3 = instance.get_connection_options(nil)[:hosts]
240
+
241
+ assert_equal 'host1', host1[:host]
242
+ assert_equal 50, host1[:port]
243
+ assert_equal 'https', host1[:scheme]
244
+ assert_equal '/es/', host2[:path]
245
+ assert_equal 'host3', host3[:host]
246
+ assert_equal 123, host3[:port]
247
+ assert_equal 'https', host3[:scheme]
248
+ assert_equal '/es/', host3[:path]
249
+ end
250
+
251
+ def test_hosts_list
252
+ config = %{
253
+ hosts https://john:password@host1:443/elastic/,http://host2
254
+ path /default_path
255
+ user default_user
256
+ password default_password
257
+ }
258
+ instance = driver(config).instance
259
+
260
+ assert_equal 2, instance.get_connection_options(nil)[:hosts].length
261
+ host1, host2 = instance.get_connection_options(nil)[:hosts]
262
+
263
+ assert_equal 'host1', host1[:host]
264
+ assert_equal 443, host1[:port]
265
+ assert_equal 'https', host1[:scheme]
266
+ assert_equal 'john', host1[:user]
267
+ assert_equal 'password', host1[:password]
268
+ assert_equal '/elastic/', host1[:path]
269
+
270
+ assert_equal 'host2', host2[:host]
271
+ assert_equal 'http', host2[:scheme]
272
+ assert_equal 'default_user', host2[:user]
273
+ assert_equal 'default_password', host2[:password]
274
+ assert_equal '/default_path', host2[:path]
275
+ end
276
+
277
+ def test_hosts_list_with_escape_placeholders
278
+ config = %{
279
+ hosts https://%{j+hn}:%{passw@rd}@host1:443/elastic/,http://host2
280
+ path /default_path
281
+ user default_user
282
+ password default_password
283
+ }
284
+ instance = driver(config).instance
285
+
286
+ assert_equal 2, instance.get_connection_options(nil)[:hosts].length
287
+ host1, host2 = instance.get_connection_options(nil)[:hosts]
288
+
289
+ assert_equal 'host1', host1[:host]
290
+ assert_equal 443, host1[:port]
291
+ assert_equal 'https', host1[:scheme]
292
+ assert_equal 'j%2Bhn', host1[:user]
293
+ assert_equal 'passw%40rd', host1[:password]
294
+ assert_equal '/elastic/', host1[:path]
295
+
296
+ assert_equal 'host2', host2[:host]
297
+ assert_equal 'http', host2[:scheme]
298
+ assert_equal 'default_user', host2[:user]
299
+ assert_equal 'default_password', host2[:password]
300
+ assert_equal '/default_path', host2[:path]
301
+ end
302
+
303
+ def test_single_host_params_and_defaults
304
+ config = %{
305
+ host logs.google.com
306
+ user john
307
+ password doe
308
+ }
309
+ instance = driver(config).instance
310
+
311
+ assert_equal 1, instance.get_connection_options(nil)[:hosts].length
312
+ host1 = instance.get_connection_options(nil)[:hosts][0]
313
+
314
+ assert_equal 'logs.google.com', host1[:host]
315
+ assert_equal 9200, host1[:port]
316
+ assert_equal 'http', host1[:scheme]
317
+ assert_equal 'john', host1[:user]
318
+ assert_equal 'doe', host1[:password]
319
+ assert_equal nil, host1[:path]
320
+ end
321
+
322
+ def test_single_host_params_and_defaults_with_escape_placeholders
323
+ config = %{
324
+ host logs.google.com
325
+ user %{j+hn}
326
+ password %{d@e}
327
+ }
328
+ instance = driver(config).instance
329
+
330
+ assert_equal 1, instance.get_connection_options(nil)[:hosts].length
331
+ host1 = instance.get_connection_options(nil)[:hosts][0]
332
+
333
+ assert_equal 'logs.google.com', host1[:host]
334
+ assert_equal 9200, host1[:port]
335
+ assert_equal 'http', host1[:scheme]
336
+ assert_equal 'j%2Bhn', host1[:user]
337
+ assert_equal 'd%40e', host1[:password]
338
+ assert_equal nil, host1[:path]
339
+ end
340
+
341
+ def test_content_type_header
342
+ stub_request(:head, "http://localhost:9200/").
343
+ to_return(:status => 200, :body => "", :headers => {})
344
+ if Elasticsearch::VERSION >= "6.0.2"
345
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
346
+ with(headers: { "Content-Type" => "application/x-ndjson" })
347
+ else
348
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
349
+ with(headers: { "Content-Type" => "application/json" })
350
+ end
351
+ driver.run(default_tag: 'test') do
352
+ driver.feed(sample_record)
353
+ end
354
+ assert_requested(elastic_request)
355
+ end
356
+
357
+ def test_writes_to_default_index
358
+ stub_elastic
359
+ driver.run(default_tag: 'test') do
360
+ driver.feed(sample_record)
361
+ end
362
+ assert_equal('fluentd', index_cmds.first['index']['_index'])
363
+ end
364
+
365
+ # gzip compress data
366
+ def gzip(string, strategy)
367
+ wio = StringIO.new("w")
368
+ w_gz = Zlib::GzipWriter.new(wio, strategy = strategy)
369
+ w_gz.write(string)
370
+ w_gz.close
371
+ wio.string
372
+ end
373
+
374
+ def test_writes_to_default_index_with_compression
375
+ omit "elastisearch-ruby v7.2.0 or later is needed." if Gem::Version.create(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
376
+
377
+ config = %[
378
+ compression_level default_compression
379
+ ]
380
+
381
+ bodystr = %({
382
+ "took" : 500,
383
+ "errors" : false,
384
+ "items" : [
385
+ {
386
+ "create": {
387
+ "_index" : "fluentd",
388
+ "_type" : "fluentd"
389
+ }
390
+ }
391
+ ]
392
+ })
393
+
394
+ compressed_body = gzip(bodystr, Zlib::DEFAULT_COMPRESSION)
395
+
396
+ elastic_request = stub_request(:post, "http://localhost:9200/_bulk").
397
+ to_return(:status => 200, :headers => {'Content-Type' => 'Application/json'}, :body => compressed_body)
398
+
399
+ driver(config)
400
+ driver.run(default_tag: 'test') do
401
+ driver.feed(sample_record)
402
+ end
403
+
404
+ assert_requested(elastic_request)
405
+ end
406
+
407
+ def test_writes_to_default_type
408
+ stub_elastic
409
+ driver.run(default_tag: 'test') do
410
+ driver.feed(sample_record)
411
+ end
412
+ assert_equal(default_type_name, index_cmds.first['index']['_type'])
413
+ end
414
+
415
+ def test_writes_to_specified_index
416
+ driver.configure("index_name myindex\n")
417
+ stub_elastic
418
+ driver.run(default_tag: 'test') do
419
+ driver.feed(sample_record)
420
+ end
421
+ assert_equal('myindex', index_cmds.first['index']['_index'])
422
+ end
423
+
424
+ def test_writes_to_specified_index_uppercase
425
+ driver.configure("index_name MyIndex\n")
426
+ stub_elastic
427
+ driver.run(default_tag: 'test') do
428
+ driver.feed(sample_record)
429
+ end
430
+ assert_equal('myindex', index_cmds.first['index']['_index'])
431
+ end
432
+
433
+ def test_writes_to_specified_type
434
+ driver.configure("type_name mytype\n")
435
+ stub_elastic
436
+ driver.run(default_tag: 'test') do
437
+ driver.feed(sample_record)
438
+ end
439
+ assert_equal('mytype', index_cmds.first['index']['_type'])
440
+ end
441
+
442
+ def test_writes_to_specified_host
443
+ driver.configure("host 192.168.33.50\n")
444
+ elastic_request = stub_elastic("http://192.168.33.50:9200/_bulk")
445
+ driver.run(default_tag: 'test') do
446
+ driver.feed(sample_record)
447
+ end
448
+ assert_requested(elastic_request)
449
+ end
450
+
451
+ def test_writes_to_specified_port
452
+ driver.configure("port 9201\n")
453
+ elastic_request = stub_elastic("http://localhost:9201/_bulk")
454
+ driver.run(default_tag: 'test') do
455
+ driver.feed(sample_record)
456
+ end
457
+ assert_requested(elastic_request)
458
+ end
459
+
460
+ def test_writes_to_multi_hosts
461
+ hosts = [['192.168.33.50', 9201], ['192.168.33.51', 9201], ['192.168.33.52', 9201]]
462
+ hosts_string = hosts.map {|x| "#{x[0]}:#{x[1]}"}.compact.join(',')
463
+
464
+ driver.configure("hosts #{hosts_string}")
465
+
466
+ hosts.each do |host_info|
467
+ host, port = host_info
468
+ stub_elastic_with_store_index_command_counts("http://#{host}:#{port}/_bulk")
469
+ end
470
+
471
+ driver.run(default_tag: 'test') do
472
+ 1000.times do
473
+ driver.feed(sample_record.merge('age'=>rand(100)))
474
+ end
475
+ end
476
+ # @note: we cannot make multi chunks with options (flush_interval, buffer_chunk_limit)
477
+ # it's Fluentd test driver's constraint
478
+ # so @index_command_counts.size is always 1
479
+
480
+ assert(@index_command_counts.size > 0, "not working with hosts options")
481
+
482
+ total = 0
483
+ @index_command_counts.each do |url, count|
484
+ total += count
485
+ end
486
+ assert_equal(2000, total)
487
+ end
488
+
489
+ def test_nested_record_with_flattening_on
490
+ driver.configure("flatten_hashes true
491
+ flatten_hashes_separator |")
492
+
493
+ original_hash = {"foo" => {"bar" => "baz"}, "people" => [
494
+ {"age" => "25", "height" => "1ft"},
495
+ {"age" => "30", "height" => "2ft"}
496
+ ]}
497
+
498
+ expected_output = {"foo|bar"=>"baz", "people" => [
499
+ {"age" => "25", "height" => "1ft"},
500
+ {"age" => "30", "height" => "2ft"}
501
+ ]}
502
+
503
+ stub_elastic
504
+ driver.run(default_tag: 'test') do
505
+ driver.feed(original_hash)
506
+ end
507
+ assert_equal expected_output, index_cmds[1]
508
+ end
509
+
510
+ def test_nested_record_with_flattening_off
511
+ # flattening off by default
512
+
513
+ original_hash = {"foo" => {"bar" => "baz"}}
514
+ expected_output = {"foo" => {"bar" => "baz"}}
515
+
516
+ stub_elastic
517
+ driver.run(default_tag: 'test') do
518
+ driver.feed(original_hash)
519
+ end
520
+ assert_equal expected_output, index_cmds[1]
521
+ end
522
+
523
+ def test_makes_bulk_request
524
+ stub_elastic
525
+ driver.run(default_tag: 'test') do
526
+ driver.feed(sample_record)
527
+ driver.feed(sample_record.merge('age' => 27))
528
+ end
529
+ assert_equal(4, index_cmds.count)
530
+ end
531
+
532
+ def test_all_records_are_preserved_in_bulk
533
+ stub_elastic
534
+ driver.run(default_tag: 'test') do
535
+ driver.feed(sample_record)
536
+ driver.feed(sample_record.merge('age' => 27))
537
+ end
538
+ assert_equal(26, index_cmds[1]['age'])
539
+ assert_equal(27, index_cmds[3]['age'])
540
+ end
541
+
542
+ def test_writes_to_logstash_index
543
+ driver.configure("logstash_format true\n")
544
+ time = Time.parse Date.today.iso8601
545
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m.%d")}"
546
+ stub_elastic
547
+ driver.run(default_tag: 'test') do
548
+ driver.feed(time.to_i, sample_record)
549
+ end
550
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
551
+ end
552
+
553
+ def test_writes_to_logstash_utc_index
554
+ driver.configure("logstash_format true
555
+ utc_index false")
556
+ time = Time.parse Date.today.iso8601
557
+ utc_index = "logstash-#{time.strftime("%Y.%m.%d")}"
558
+ stub_elastic
559
+ driver.run(default_tag: 'test') do
560
+ driver.feed(time.to_i, sample_record)
561
+ end
562
+ assert_equal(utc_index, index_cmds.first['index']['_index'])
563
+ end
564
+
565
+ def test_writes_to_logstash_index_with_specified_prefix
566
+ driver.configure("logstash_format true
567
+ logstash_prefix myprefix")
568
+ time = Time.parse Date.today.iso8601
569
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
570
+ stub_elastic
571
+ driver.run(default_tag: 'test') do
572
+ driver.feed(time.to_i, sample_record)
573
+ end
574
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
575
+ end
576
+
577
+ def test_writes_to_logstash_index_with_specified_prefix_and_separator
578
+ separator = '_'
579
+ driver.configure("logstash_format true
580
+ logstash_prefix_separator #{separator}
581
+ logstash_prefix myprefix")
582
+ time = Time.parse Date.today.iso8601
583
+ logstash_index = "myprefix#{separator}#{time.getutc.strftime("%Y.%m.%d")}"
584
+ stub_elastic
585
+ driver.run(default_tag: 'test') do
586
+ driver.feed(time.to_i, sample_record)
587
+ end
588
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
589
+ end
590
+
591
+ def test_writes_to_logstash_index_with_specified_prefix_uppercase
592
+ driver.configure("logstash_format true
593
+ logstash_prefix MyPrefix")
594
+ time = Time.parse Date.today.iso8601
595
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m.%d")}"
596
+ stub_elastic
597
+ driver.run(default_tag: 'test') do
598
+ driver.feed(time.to_i, sample_record)
599
+ end
600
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
601
+ end
602
+
603
+ def test_writes_to_logstash_index_with_specified_dateformat
604
+ driver.configure("logstash_format true
605
+ logstash_dateformat %Y.%m")
606
+ time = Time.parse Date.today.iso8601
607
+ logstash_index = "logstash-#{time.getutc.strftime("%Y.%m")}"
608
+ stub_elastic
609
+ driver.run(default_tag: 'test') do
610
+ driver.feed(time.to_i, sample_record)
611
+ end
612
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
613
+ end
614
+
615
+ def test_writes_to_logstash_index_with_specified_prefix_and_dateformat
616
+ driver.configure("logstash_format true
617
+ logstash_prefix myprefix
618
+ logstash_dateformat %Y.%m")
619
+ time = Time.parse Date.today.iso8601
620
+ logstash_index = "myprefix-#{time.getutc.strftime("%Y.%m")}"
621
+ stub_elastic
622
+ driver.run(default_tag: 'test') do
623
+ driver.feed(time.to_i, sample_record)
624
+ end
625
+ assert_equal(logstash_index, index_cmds.first['index']['_index'])
626
+ end
627
+
628
+ def test_doesnt_add_logstash_timestamp_by_default
629
+ stub_elastic
630
+ driver.run(default_tag: 'test') do
631
+ driver.feed(sample_record)
632
+ end
633
+ assert_nil(index_cmds[1]['@timestamp'])
634
+ end
635
+
636
+ def test_adds_logstash_timestamp_when_configured
637
+ driver.configure("logstash_format true\n")
638
+ stub_elastic
639
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
640
+ driver.run(default_tag: 'test') do
641
+ driver.feed(time, sample_record)
642
+ end
643
+ assert(index_cmds[1].has_key? '@timestamp')
644
+ assert_equal(Time.at(time).iso8601(9), index_cmds[1]['@timestamp'])
645
+ end
646
+
647
+ def test_uses_subsecond_precision_when_configured
648
+ driver.configure("logstash_format true
649
+ time_precision 3\n")
650
+ stub_elastic
651
+ time = Fluent::EventTime.new(Time.now.to_i, 123456789)
652
+ driver.run(default_tag: 'test') do
653
+ driver.feed(time, sample_record)
654
+ end
655
+ assert(index_cmds[1].has_key? '@timestamp')
656
+ assert_equal(Time.at(time).iso8601(3), index_cmds[1]['@timestamp'])
657
+ end
658
+
659
+ def test_uses_custom_timestamp_when_included_in_record
660
+ driver.configure("include_timestamp true\n")
661
+ stub_elastic
662
+ ts = DateTime.new(2001,2,3).iso8601
663
+ driver.run(default_tag: 'test') do
664
+ driver.feed(sample_record.merge!('@timestamp' => ts))
665
+ end
666
+ assert(index_cmds[1].has_key? '@timestamp')
667
+ assert_equal(ts, index_cmds[1]['@timestamp'])
668
+ end
669
+
670
+ def test_uses_custom_timestamp_when_included_in_record_logstash
671
+ driver.configure("logstash_format true\n")
672
+ stub_elastic
673
+ ts = DateTime.new(2001,2,3).iso8601
674
+ driver.run(default_tag: 'test') do
675
+ driver.feed(sample_record.merge!('@timestamp' => ts))
676
+ end
677
+ assert(index_cmds[1].has_key? '@timestamp')
678
+ assert_equal(ts, index_cmds[1]['@timestamp'])
679
+ end
680
+
681
+ def test_uses_custom_time_key_logstash
682
+ driver.configure("logstash_format true
683
+ time_key vtm\n")
684
+ stub_elastic
685
+ ts = DateTime.new(2001,2,3).iso8601
686
+ driver.run(default_tag: 'test') do
687
+ driver.feed(sample_record.merge!('vtm' => ts))
688
+ end
689
+ assert(index_cmds[1].has_key? '@timestamp')
690
+ assert_equal(ts, index_cmds[1]['@timestamp'])
691
+ end
692
+
693
+ def test_uses_custom_time_key_timestamp
694
+ driver.configure("include_timestamp true
695
+ time_key vtm\n")
696
+ stub_elastic
697
+ ts = DateTime.new(2001,2,3).iso8601
698
+ driver.run(default_tag: 'test') do
699
+ driver.feed(sample_record.merge!('vtm' => ts))
700
+ end
701
+ assert(index_cmds[1].has_key? '@timestamp')
702
+ assert_equal(ts, index_cmds[1]['@timestamp'])
703
+ end
704
+
705
+ def test_uses_custom_time_key_timestamp_custom_index
706
+ driver.configure("include_timestamp true
707
+ index_name test
708
+ time_key vtm\n")
709
+ stub_elastic
710
+ ts = DateTime.new(2001,2,3).iso8601
711
+ driver.run(default_tag: 'test') do
712
+ driver.feed(sample_record.merge!('vtm' => ts))
713
+ end
714
+ assert(index_cmds[1].has_key? '@timestamp')
715
+ assert_equal(ts, index_cmds[1]['@timestamp'])
716
+ assert_equal('test', index_cmds.first['index']['_index'])
717
+ end
718
+
719
+ def test_uses_custom_time_key_exclude_timestamp
720
+ driver.configure("include_timestamp true
721
+ time_key vtm
722
+ time_key_exclude_timestamp true\n")
723
+ stub_elastic
724
+ ts = DateTime.new(2001,2,3).iso8601
725
+ driver.run(default_tag: 'test') do
726
+ driver.feed(sample_record.merge!('vtm' => ts))
727
+ end
728
+ assert(!index_cmds[1].key?('@timestamp'), '@timestamp should be missing')
729
+ end
730
+
731
+ def test_uses_custom_time_key_exclude_timestamp_logstash
732
+ driver.configure("logstash_format true
733
+ time_key vtm
734
+ time_key_exclude_timestamp true\n")
735
+ stub_elastic
736
+ ts = DateTime.new(2001,2,3).iso8601
737
+ driver.run(default_tag: 'test') do
738
+ driver.feed(sample_record.merge!('vtm' => ts))
739
+ end
740
+ assert(!index_cmds[1].key?('@timestamp'), '@timestamp should be missing')
741
+ end
742
+
743
+ def test_doesnt_add_tag_key_by_default
744
+ stub_elastic
745
+ driver.run(default_tag: 'test') do
746
+ driver.feed(sample_record)
747
+ end
748
+ assert_nil(index_cmds[1]['tag'])
749
+ end
750
+
751
+ def test_adds_tag_key_when_configured
752
+ driver.configure("include_tag_key true\n")
753
+ stub_elastic
754
+ driver.run(default_tag: 'mytag') do
755
+ driver.feed(sample_record)
756
+ end
757
+ assert(index_cmds[1].has_key?('tag'))
758
+ assert_equal('mytag', index_cmds[1]['tag'])
759
+ end
760
+
761
+ def test_adds_id_key_when_configured
762
+ driver.configure("id_key request_id\n")
763
+ stub_elastic
764
+ driver.run(default_tag: 'test') do
765
+ driver.feed(sample_record)
766
+ end
767
+ assert_equal('42', index_cmds[0]['index']['_id'])
768
+ end
769
+
770
+ class NestedIdKeyTest < self
771
+ def test_adds_nested_id_key_with_dot
772
+ driver.configure("id_key nested.request_id\n")
773
+ stub_elastic
774
+ driver.run(default_tag: 'test') do
775
+ driver.feed(nested_sample_record)
776
+ end
777
+ assert_equal('42', index_cmds[0]['index']['_id'])
778
+ end
779
+
780
+ def test_adds_nested_id_key_with_dollar_dot
781
+ driver.configure("id_key $.nested.request_id\n")
782
+ stub_elastic
783
+ driver.run(default_tag: 'test') do
784
+ driver.feed(nested_sample_record)
785
+ end
786
+ assert_equal('42', index_cmds[0]['index']['_id'])
787
+ end
788
+
789
+ def test_adds_nested_id_key_with_bracket
790
+ driver.configure("id_key $['nested']['request_id']\n")
791
+ stub_elastic
792
+ driver.run(default_tag: 'test') do
793
+ driver.feed(nested_sample_record)
794
+ end
795
+ assert_equal('42', index_cmds[0]['index']['_id'])
796
+ end
797
+ end
798
+
799
+ def test_doesnt_add_id_key_if_missing_when_configured
800
+ driver.configure("id_key another_request_id\n")
801
+ stub_elastic
802
+ driver.run(default_tag: 'test') do
803
+ driver.feed(sample_record)
804
+ end
805
+ assert(!index_cmds[0]['index'].has_key?('_id'))
806
+ end
807
+
808
+ def test_adds_id_key_when_not_configured
809
+ stub_elastic
810
+ driver.run(default_tag: 'test') do
811
+ driver.feed(sample_record)
812
+ end
813
+ assert(!index_cmds[0]['index'].has_key?('_id'))
814
+ end
815
+
816
+ def test_adds_parent_key_when_configured
817
+ driver.configure("parent_key parent_id\n")
818
+ stub_elastic
819
+ driver.run(default_tag: 'test') do
820
+ driver.feed(sample_record)
821
+ end
822
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
823
+ end
824
+
825
+ class NestedParentKeyTest < self
826
+ def test_adds_nested_parent_key_with_dot
827
+ driver.configure("parent_key nested.parent_id\n")
828
+ stub_elastic
829
+ driver.run(default_tag: 'test') do
830
+ driver.feed(nested_sample_record)
831
+ end
832
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
833
+ end
834
+
835
+ def test_adds_nested_parent_key_with_dollar_dot
836
+ driver.configure("parent_key $.nested.parent_id\n")
837
+ stub_elastic
838
+ driver.run(default_tag: 'test') do
839
+ driver.feed(nested_sample_record)
840
+ end
841
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
842
+ end
843
+
844
+ def test_adds_nested_parent_key_with_bracket
845
+ driver.configure("parent_key $['nested']['parent_id']\n")
846
+ stub_elastic
847
+ driver.run(default_tag: 'test') do
848
+ driver.feed(nested_sample_record)
849
+ end
850
+ assert_equal('parent', index_cmds[0]['index']['_parent'])
851
+ end
852
+ end
853
+
854
+ def test_doesnt_add_parent_key_if_missing_when_configured
855
+ driver.configure("parent_key another_parent_id\n")
856
+ stub_elastic
857
+ driver.run(default_tag: 'test') do
858
+ driver.feed(sample_record)
859
+ end
860
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
861
+ end
862
+
863
+ def test_adds_parent_key_when_not_configured
864
+ stub_elastic
865
+ driver.run(default_tag: 'test') do
866
+ driver.feed(sample_record)
867
+ end
868
+ assert(!index_cmds[0]['index'].has_key?('_parent'))
869
+ end
870
+
871
+ class AddsRoutingKeyWhenConfiguredTest < self
872
+ def test_es6
873
+ driver("routing_key routing_id\n", 6)
874
+ stub_elastic
875
+ driver.run(default_tag: 'test') do
876
+ driver.feed(sample_record)
877
+ end
878
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
879
+ end
880
+
881
+ def test_es7
882
+ driver("routing_key routing_id\n", 7)
883
+ stub_elastic
884
+ driver.run(default_tag: 'test') do
885
+ driver.feed(sample_record)
886
+ end
887
+ assert_equal('routing', index_cmds[0]['index']['routing'])
888
+ end
889
+ end
890
+
891
+ class NestedRoutingKeyTest < self
892
+ def test_adds_nested_routing_key_with_dot
893
+ driver.configure("routing_key nested.routing_id\n")
894
+ stub_elastic
895
+ driver.run(default_tag: 'test') do
896
+ driver.feed(nested_sample_record)
897
+ end
898
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
899
+ end
900
+
901
+ def test_adds_nested_routing_key_with_dollar_dot
902
+ driver.configure("routing_key $.nested.routing_id\n")
903
+ stub_elastic
904
+ driver.run(default_tag: 'test') do
905
+ driver.feed(nested_sample_record)
906
+ end
907
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
908
+ end
909
+
910
+ def test_adds_nested_routing_key_with_bracket
911
+ driver.configure("routing_key $['nested']['routing_id']\n")
912
+ stub_elastic
913
+ driver.run(default_tag: 'test') do
914
+ driver.feed(nested_sample_record)
915
+ end
916
+ assert_equal('routing', index_cmds[0]['index']['_routing'])
917
+ end
918
+ end
919
+
920
+ def test_doesnt_add_routing_key_if_missing_when_configured
921
+ driver.configure("routing_key another_routing_id\n")
922
+ stub_elastic
923
+ driver.run(default_tag: 'test') do
924
+ driver.feed(sample_record)
925
+ end
926
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
927
+ end
928
+
929
+ def test_adds_routing_key_when_not_configured
930
+ stub_elastic
931
+ driver.run(default_tag: 'test') do
932
+ driver.feed(sample_record)
933
+ end
934
+ assert(!index_cmds[0]['index'].has_key?('_routing'))
935
+ end
936
+
937
+ def test_remove_one_key
938
+ driver.configure("remove_keys key1\n")
939
+ stub_elastic
940
+ driver.run(default_tag: 'test') do
941
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
942
+ end
943
+ assert(!index_cmds[1].has_key?('key1'))
944
+ assert(index_cmds[1].has_key?('key2'))
945
+ end
946
+
947
+ def test_remove_multi_keys
948
+ driver.configure("remove_keys key1, key2\n")
949
+ stub_elastic
950
+ driver.run(default_tag: 'test') do
951
+ driver.feed(sample_record.merge('key1' => 'v1', 'key2' => 'v2'))
952
+ end
953
+ assert(!index_cmds[1].has_key?('key1'))
954
+ assert(!index_cmds[1].has_key?('key2'))
955
+ end
956
+
957
+ def test_request_error
958
+ stub_elastic_unavailable
959
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
960
+ driver.run(default_tag: 'test', shutdown: false) do
961
+ driver.feed(sample_record)
962
+ end
963
+ }
964
+ end
965
+
966
+ def test_request_forever
967
+ omit("retry_forever test is unstable.") if ENV["CI"]
968
+
969
+ stub_elastic
970
+ driver.configure(Fluent::Config::Element.new(
971
+ 'ROOT', '', {
972
+ '@type' => 'elasticsearch',
973
+ }, [
974
+ Fluent::Config::Element.new('buffer', '', {
975
+ 'retry_forever' => true
976
+ }, [])
977
+ ]
978
+ ))
979
+ stub_elastic_timeout
980
+ assert_raise(Timeout::Error) {
981
+ driver.run(default_tag: 'test', timeout: 10, force_flush_retry: true) do
982
+ driver.feed(sample_record)
983
+ end
984
+ }
985
+ end
986
+
987
+ def test_tag_parts_index_error_event
988
+ stub_elastic
989
+ driver.configure("logstash_prefix ${tag_parts[1]}\n")
990
+ flexmock(driver.instance.router).should_receive(:emit_error_event)
991
+ .with('test', Fluent::EventTime, Hash, TypeError).once
992
+ driver.run(default_tag: 'test') do
993
+ driver.feed(sample_record)
994
+ end
995
+ end
996
+
997
+ def test_connection_failed
998
+ connection_resets = 0
999
+
1000
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
1001
+ connection_resets += 1
1002
+ raise Faraday::ConnectionFailed, "Test message"
1003
+ end
1004
+
1005
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
1006
+ driver.run(default_tag: 'test', shutdown: false) do
1007
+ driver.feed(sample_record)
1008
+ end
1009
+ }
1010
+ assert_equal(1, connection_resets)
1011
+ end
1012
+
1013
+ def test_reconnect_on_error_enabled
1014
+ connection_resets = 0
1015
+
1016
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
1017
+ connection_resets += 1
1018
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
1019
+ end
1020
+
1021
+ driver.configure("reconnect_on_error true\n")
1022
+
1023
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
1024
+ driver.run(default_tag: 'test', shutdown: false) do
1025
+ driver.feed(sample_record)
1026
+ end
1027
+ }
1028
+
1029
+ assert_raise(Timeout::Error) {
1030
+ driver.run(default_tag: 'test', shutdown: false) do
1031
+ driver.feed(sample_record)
1032
+ end
1033
+ }
1034
+ # FIXME: Consider keywords arguments in #run and how to test this later.
1035
+ # Because v0.14 test driver does not have 1 to 1 correspondence between #run and #flush in tests.
1036
+ assert_equal(1, connection_resets)
1037
+ end
1038
+
1039
+ def test_reconnect_on_error_disabled
1040
+ connection_resets = 0
1041
+
1042
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
1043
+ connection_resets += 1
1044
+ raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
1045
+ end
1046
+
1047
+ driver.configure("reconnect_on_error false\n")
1048
+
1049
+ assert_raise(Fluent::Plugin::ElasticsearchOutput::RecoverableRequestFailure) {
1050
+ driver.run(default_tag: 'test', shutdown: false) do
1051
+ driver.feed(sample_record)
1052
+ end
1053
+ }
1054
+
1055
+ assert_raise(Timeout::Error) {
1056
+ driver.run(default_tag: 'test', shutdown: false) do
1057
+ driver.feed(sample_record)
1058
+ end
1059
+ }
1060
+ assert_equal(1, connection_resets)
1061
+ end
1062
+
1063
+ def test_update_should_not_write_if_theres_no_id
1064
+ driver.configure("write_operation update\n")
1065
+ stub_elastic
1066
+ driver.run(default_tag: 'test') do
1067
+ driver.feed(sample_record)
1068
+ end
1069
+ assert_nil(index_cmds)
1070
+ end
1071
+
1072
+ def test_upsert_should_not_write_if_theres_no_id
1073
+ driver.configure("write_operation upsert\n")
1074
+ stub_elastic
1075
+ driver.run(default_tag: 'test') do
1076
+ driver.feed(sample_record)
1077
+ end
1078
+ assert_nil(index_cmds)
1079
+ end
1080
+
1081
+ def test_create_should_not_write_if_theres_no_id
1082
+ driver.configure("write_operation create\n")
1083
+ stub_elastic
1084
+ driver.run(default_tag: 'test') do
1085
+ driver.feed(sample_record)
1086
+ end
1087
+ assert_nil(index_cmds)
1088
+ end
1089
+
1090
+ def test_update_should_write_update_op_and_doc_as_upsert_is_false
1091
+ driver.configure("write_operation update
1092
+ id_key request_id")
1093
+ stub_elastic
1094
+ driver.run(default_tag: 'test') do
1095
+ driver.feed(sample_record)
1096
+ end
1097
+ assert(index_cmds[0].has_key?("update"))
1098
+ assert(!index_cmds[1]["doc_as_upsert"])
1099
+ end
1100
+
1101
+ def test_upsert_should_write_update_op_and_doc_as_upsert_is_true
1102
+ driver.configure("write_operation upsert
1103
+ id_key request_id")
1104
+ stub_elastic
1105
+ driver.run(default_tag: 'test') do
1106
+ driver.feed(sample_record)
1107
+ end
1108
+ assert(index_cmds[0].has_key?("update"))
1109
+ assert(index_cmds[1]["doc_as_upsert"])
1110
+ end
1111
+
1112
+ def test_create_should_write_create_op
1113
+ driver.configure("write_operation create
1114
+ id_key request_id")
1115
+ stub_elastic
1116
+ driver.run(default_tag: 'test') do
1117
+ driver.feed(sample_record)
1118
+ end
1119
+ assert(index_cmds[0].has_key?("create"))
1120
+ end
1121
+
1122
+ def test_include_index_in_url
1123
+ stub_elastic('http://localhost:9200/logstash-2018.01.01/_bulk')
1124
+
1125
+ driver.configure("index_name logstash-2018.01.01
1126
+ include_index_in_url true")
1127
+ driver.run(default_tag: 'test') do
1128
+ driver.feed(sample_record)
1129
+ end
1130
+
1131
+ assert_equal(2, index_cmds.length)
1132
+ assert_equal(nil, index_cmds.first['index']['_index'])
1133
+ end
1134
+ end