fluent-plugin-elasticsearch-dext 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
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