fluent-plugin-input-opensearch 1.1.9

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  6. data/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +9 -0
  7. data/.github/workflows/coverage.yaml +22 -0
  8. data/.github/workflows/issue-auto-closer.yml +12 -0
  9. data/.github/workflows/linux.yml +26 -0
  10. data/.github/workflows/macos.yml +26 -0
  11. data/.github/workflows/windows.yml +26 -0
  12. data/.gitignore +18 -0
  13. data/CONTRIBUTING.md +24 -0
  14. data/Gemfile +10 -0
  15. data/History.md +67 -0
  16. data/LICENSE.txt +201 -0
  17. data/README.OpenSearchGenID.md +116 -0
  18. data/README.OpenSearchInput.md +314 -0
  19. data/README.Troubleshooting.md +482 -0
  20. data/README.md +1622 -0
  21. data/Rakefile +37 -0
  22. data/fluent-plugin-opensearch.gemspec +39 -0
  23. data/gemfiles/Gemfile.elasticsearch.v6 +12 -0
  24. data/lib/fluent/log-ext.rb +64 -0
  25. data/lib/fluent/plugin/filter_opensearch_genid.rb +103 -0
  26. data/lib/fluent/plugin/in_opensearch.rb +410 -0
  27. data/lib/fluent/plugin/oj_serializer.rb +48 -0
  28. data/lib/fluent/plugin/opensearch_constants.rb +39 -0
  29. data/lib/fluent/plugin/opensearch_error.rb +31 -0
  30. data/lib/fluent/plugin/opensearch_error_handler.rb +182 -0
  31. data/lib/fluent/plugin/opensearch_fallback_selector.rb +36 -0
  32. data/lib/fluent/plugin/opensearch_index_template.rb +155 -0
  33. data/lib/fluent/plugin/opensearch_simple_sniffer.rb +36 -0
  34. data/lib/fluent/plugin/opensearch_tls.rb +96 -0
  35. data/lib/fluent/plugin/out_opensearch.rb +1158 -0
  36. data/lib/fluent/plugin/out_opensearch_data_stream.rb +229 -0
  37. data/test/helper.rb +60 -0
  38. data/test/plugin/datastream_template.json +4 -0
  39. data/test/plugin/test_alias_template.json +9 -0
  40. data/test/plugin/test_filter_opensearch_genid.rb +241 -0
  41. data/test/plugin/test_in_opensearch.rb +500 -0
  42. data/test/plugin/test_index_alias_template.json +11 -0
  43. data/test/plugin/test_index_template.json +25 -0
  44. data/test/plugin/test_oj_serializer.rb +45 -0
  45. data/test/plugin/test_opensearch_error_handler.rb +770 -0
  46. data/test/plugin/test_opensearch_fallback_selector.rb +100 -0
  47. data/test/plugin/test_opensearch_tls.rb +171 -0
  48. data/test/plugin/test_out_opensearch.rb +3980 -0
  49. data/test/plugin/test_out_opensearch_data_stream.rb +746 -0
  50. data/test/plugin/test_template.json +23 -0
  51. data/test/test_log-ext.rb +61 -0
  52. metadata +291 -0
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The fluent-plugin-opensearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright fluent-plugin-opensearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Uken Inc. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Uken Inc. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ require 'bundler/gem_tasks'
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ test.warning = false
35
+ end
36
+
37
+ task :default => :test
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'fluent-plugin-input-opensearch'
6
+ s.version = '1.1.9'
7
+ s.authors = ['imcotop']
8
+ s.email = ['imcotop@icloud.com']
9
+ s.description = %q{Opensearch output plugin for Fluent event collector}
10
+ s.summary = s.description
11
+ s.homepage = 'https://github.com/fluent/fluent-plugin-opensearch'
12
+ s.license = 'Apache-2.0'
13
+
14
+ s.files = `git ls-files`.split($/)
15
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ['lib']
18
+
19
+ if s.respond_to?(:metadata)
20
+ s.metadata["changelog_uri"] = "https://github.com/fluent/fluent-plugin-opensearch/blob/master/History.md"
21
+ end
22
+
23
+ s.required_ruby_version = Gem::Requirement.new(">= 2.3".freeze)
24
+
25
+ s.add_runtime_dependency 'fluentd', '>= 0.14.22'
26
+ s.add_runtime_dependency 'opensearch-ruby', '>= 3.0.1'
27
+ s.add_runtime_dependency "aws-sdk-core", "~> 3"
28
+ s.add_runtime_dependency 'excon', '>= 0'
29
+ s.add_runtime_dependency 'faraday', '>= 2.0.0'
30
+ s.add_runtime_dependency 'faraday-excon', '>= 2.0.0'
31
+ s.add_runtime_dependency "faraday_middleware-aws-sigv4", "~> 1.0.1"
32
+
33
+ s.add_development_dependency 'rake', '>= 0'
34
+ s.add_development_dependency 'webrick', '~> 1.7.0'
35
+ s.add_development_dependency 'webmock', '~> 3.18.1'
36
+ s.add_development_dependency 'test-unit', '~> 3.3.0'
37
+ s.add_development_dependency 'minitest', '~> 5.8'
38
+ s.add_development_dependency 'flexmock', '~> 2.0'
39
+ end
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-elasticsearch.gemspec
4
+ gemspec :path => "../"
5
+
6
+ gem 'simplecov', require: false
7
+ gem 'coveralls', ">= 0.8.0", require: false
8
+ gem 'strptime', require: false if RUBY_ENGINE == "ruby" && RUBY_VERSION =~ /^2/
9
+ gem "irb" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.6"
10
+ gem "elasticsearch", "~> 6.8.1"
11
+ gem "elasticsearch-xpack"
12
+ gem "oj"
@@ -0,0 +1,64 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The fluent-plugin-opensearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed toUken Inc. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ require 'fluent/log'
28
+ # For opensearch-ruby v1.0.0 or later which is based on elasticsearch-ruby v7.14 master tree
29
+ # logger for Elasticsearch::Loggable required the following methods:
30
+ #
31
+ # * debug?
32
+ # * info?
33
+ # * warn?
34
+ # * error?
35
+ # * fatal?
36
+
37
+ module Fluent
38
+ class Log
39
+ # OpenSearch::Loggable does not request trace? method.
40
+ # def trace?
41
+ # @level <= LEVEL_TRACE
42
+ # end
43
+
44
+ def debug?
45
+ @level <= LEVEL_DEBUG
46
+ end
47
+
48
+ def info?
49
+ @level <= LEVEL_INFO
50
+ end
51
+
52
+ def warn?
53
+ @level <= LEVEL_WARN
54
+ end
55
+
56
+ def error?
57
+ @level <= LEVEL_ERROR
58
+ end
59
+
60
+ def fatal?
61
+ @level <= LEVEL_FATAL
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,103 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The fluent-plugin-opensearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright fluent-plugin-opensearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Uken Inc. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Uken Inc. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ require 'securerandom'
28
+ require 'base64'
29
+ require 'fluent/plugin/filter'
30
+
31
+ module Fluent::Plugin
32
+ class OpenSearchGenidFilter < Filter
33
+ Fluent::Plugin.register_filter('opensearch_genid', self)
34
+
35
+ config_param :hash_id_key, :string, :default => '_hash'
36
+ config_param :include_tag_in_seed, :bool, :default => false
37
+ config_param :include_time_in_seed, :bool, :default => false
38
+ config_param :use_record_as_seed, :bool, :default => false
39
+ config_param :use_entire_record, :bool, :default => false
40
+ config_param :record_keys, :array, :default => []
41
+ config_param :separator, :string, :default => '_'
42
+ config_param :hash_type, :enum, list: [:md5, :sha1, :sha256, :sha512], :default => :sha1
43
+
44
+ def initialize
45
+ super
46
+ end
47
+
48
+ def configure(conf)
49
+ super
50
+
51
+ if !@use_entire_record
52
+ if @record_keys.empty? && @use_record_as_seed
53
+ raise Fluent::ConfigError, "When using record as hash seed, users must specify `record_keys`."
54
+ end
55
+ end
56
+
57
+ if @use_record_as_seed
58
+ class << self
59
+ alias_method :filter, :filter_seed_as_record
60
+ end
61
+ else
62
+ class << self
63
+ alias_method :filter, :filter_simple
64
+ end
65
+ end
66
+ end
67
+
68
+ def filter(tag, time, record)
69
+ # for safety.
70
+ end
71
+
72
+ def filter_simple(tag, time, record)
73
+ record[@hash_id_key] = Base64.strict_encode64(SecureRandom.uuid)
74
+ record
75
+ end
76
+
77
+ def filter_seed_as_record(tag, time, record)
78
+ seed = ""
79
+ seed += tag + separator if @include_tag_in_seed
80
+ seed += time.to_s + separator if @include_time_in_seed
81
+ if @use_entire_record
82
+ record.each {|k,v| seed += "|#{k}|#{v}"}
83
+ else
84
+ seed += record_keys.map {|k| record[k]}.join(separator)
85
+ end
86
+ record[@hash_id_key] = Base64.strict_encode64(encode_hash(@hash_type, seed))
87
+ record
88
+ end
89
+
90
+ def encode_hash(type, seed)
91
+ case type
92
+ when :md5
93
+ Digest::MD5.digest(seed)
94
+ when :sha1
95
+ Digest::SHA1.digest(seed)
96
+ when :sha256
97
+ Digest::SHA256.digest(seed)
98
+ when :sha512
99
+ Digest::SHA512.digest(seed)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,410 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The fluent-plugin-opensearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright fluent-plugin-opensearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Uken Inc. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Uken Inc. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ require 'opensearch'
28
+
29
+ require 'faraday/excon'
30
+ require 'fluent/log-ext'
31
+ require 'fluent/plugin/input'
32
+ require_relative 'opensearch_constants'
33
+
34
+ module Fluent::Plugin
35
+ class OpenSearchInput < Input
36
+ class UnrecoverableRequestFailure < Fluent::UnrecoverableError; end
37
+
38
+ DEFAULT_RELOAD_AFTER = -1
39
+ DEFAULT_STORAGE_TYPE = 'local'
40
+ METADATA = "@metadata".freeze
41
+
42
+ helpers :timer, :thread
43
+
44
+ Fluent::Plugin.register_input('opensearch', self)
45
+
46
+ config_param :tag, :string
47
+ config_param :host, :string, :default => 'localhost'
48
+ config_param :port, :integer, :default => 9200
49
+ config_param :user, :string, :default => nil
50
+ config_param :password, :string, :default => nil, :secret => true
51
+ config_param :path, :string, :default => nil
52
+ config_param :scheme, :enum, :list => [:https, :http], :default => :http
53
+ config_param :hosts, :string, :default => nil
54
+ config_param :index_name, :string, :default => "fluentd"
55
+ config_param :parse_timestamp, :bool, :default => false
56
+ config_param :timestamp_key_format, :string, :default => nil
57
+ config_param :timestamp_parse_error_tag, :string, :default => 'opensearch_plugin.input.time.error'
58
+ config_param :query, :hash, :default => {"sort" => [ "_doc" ]}
59
+ config_param :scroll, :string, :default => "1m"
60
+ config_param :size, :integer, :default => 1000
61
+ config_param :num_slices, :integer, :default => 1
62
+ config_param :interval, :size, :default => 5
63
+ config_param :repeat, :bool, :default => true
64
+ config_param :http_backend, :enum, list: [:excon, :typhoeus], :default => :excon
65
+ config_param :request_timeout, :time, :default => 5
66
+ config_param :reload_connections, :bool, :default => true
67
+ config_param :reload_on_failure, :bool, :default => false
68
+ config_param :resurrect_after, :time, :default => 60
69
+ config_param :reload_after, :integer, :default => DEFAULT_RELOAD_AFTER
70
+ config_param :ssl_verify , :bool, :default => true
71
+ config_param :client_key, :string, :default => nil
72
+ config_param :client_cert, :string, :default => nil
73
+ config_param :client_key_pass, :string, :default => nil, :secret => true
74
+ config_param :ca_file, :string, :default => nil
75
+ config_param :ssl_version, :enum, list: [:SSLv23, :TLSv1, :TLSv1_1, :TLSv1_2], :default => :TLSv1_2
76
+ config_param :with_transporter_log, :bool, :default => false
77
+ config_param :emit_error_label_event, :bool, :default => true
78
+ config_param :sniffer_class_name, :string, :default => nil
79
+ config_param :custom_headers, :hash, :default => {}
80
+ config_param :docinfo_fields, :array, :default => ['_index', '_type', '_id']
81
+ config_param :docinfo_target, :string, :default => METADATA
82
+ config_param :docinfo, :bool, :default => false
83
+ config_param :infinite_check_connection, :bool, :default => true
84
+
85
+ include Fluent::Plugin::OpenSearchConstants
86
+
87
+ def initialize
88
+ super
89
+ end
90
+
91
+ def configure(conf)
92
+ super
93
+
94
+ @timestamp_parser = create_time_parser
95
+ @backend_options = backend_options
96
+
97
+ raise Fluent::ConfigError, "`password` must be present if `user` is present" if @user && @password.nil?
98
+
99
+ if @user && m = @user.match(/%{(?<user>.*)}/)
100
+ @user = URI.encode_www_form_component(m["user"])
101
+ end
102
+ if @password && m = @password.match(/%{(?<password>.*)}/)
103
+ @password = URI.encode_www_form_component(m["password"])
104
+ end
105
+
106
+ @transport_logger = nil
107
+ if @with_transporter_log
108
+ @transport_logger = log
109
+ log_level = conf['@log_level'] || conf['log_level']
110
+ log.warn "Consider to specify log_level with @log_level." unless log_level
111
+ end
112
+ @current_config = nil
113
+ # Specify @sniffer_class before calling #client.
114
+ @sniffer_class = nil
115
+ begin
116
+ @sniffer_class = Object.const_get(@sniffer_class_name) if @sniffer_class_name
117
+ rescue Exception => ex
118
+ raise Fluent::ConfigError, "Could not load sniffer class #{@sniffer_class_name}: #{ex}"
119
+ end
120
+
121
+ @options = {
122
+ :index => @index_name,
123
+ :scroll => @scroll,
124
+ :size => @size
125
+ }
126
+ @base_query = @query
127
+ end
128
+
129
+ def backend_options
130
+ case @http_backend
131
+ when :excon
132
+ { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
133
+ when :typhoeus
134
+ require 'typhoeus'
135
+ { sslkey: @client_key, sslcert: @client_cert, keypasswd: @client_key_pass }
136
+ end
137
+ rescue LoadError => ex
138
+ log.error_backtrace(ex.backtrace)
139
+ raise Fluent::ConfigError, "You must install #{@http_backend} gem. Exception: #{ex}"
140
+ end
141
+
142
+ def get_escaped_userinfo(host_str)
143
+ if m = host_str.match(/(?<scheme>.*)%{(?<user>.*)}:%{(?<password>.*)}(?<path>@.*)/)
144
+ m["scheme"] +
145
+ URI.encode_www_form_component(m["user"]) +
146
+ ':' +
147
+ URI.encode_www_form_component(m["password"]) +
148
+ m["path"]
149
+ else
150
+ host_str
151
+ end
152
+ end
153
+
154
+ def get_connection_options(con_host=nil)
155
+
156
+ hosts = if con_host || @hosts
157
+ (con_host || @hosts).split(',').map do |host_str|
158
+ # Support legacy hosts format host:port,host:port,host:port...
159
+ if host_str.match(%r{^[^:]+(\:\d+)?$})
160
+ {
161
+ host: host_str.split(':')[0],
162
+ port: (host_str.split(':')[1] || @port).to_i,
163
+ scheme: @scheme.to_s
164
+ }
165
+ else
166
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
167
+ uri = URI(get_escaped_userinfo(host_str))
168
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
169
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
170
+ hash
171
+ end
172
+ end
173
+ end.compact
174
+ else
175
+ [{host: @host, port: @port, scheme: @scheme.to_s}]
176
+ end.each do |host|
177
+ host.merge!(user: @user, password: @password) if !host[:user] && @user
178
+ host.merge!(path: @path) if !host[:path] && @path
179
+ end
180
+
181
+ {
182
+ hosts: get_reachable_hosts(hosts)
183
+ }
184
+ end
185
+
186
+ def emit_error_label_event(&block)
187
+ # If `emit_error_label_event` is specified as false, error event emittions are not occurred.
188
+ if emit_error_label_event
189
+ block.call
190
+ end
191
+ end
192
+
193
+ def start
194
+ super
195
+
196
+ timer_execute(:in_opensearch_timer, @interval, repeat: @repeat, &method(:run))
197
+ end
198
+
199
+ # We might be able to use
200
+ # Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
201
+ # [sec,nsec] where as we want something we can call `strftime` on...
202
+ def create_time_parser
203
+ if @timestamp_key_format
204
+ begin
205
+ # Strptime doesn't support all formats, but for those it does it's
206
+ # blazingly fast.
207
+ strptime = Strptime.new(@timestamp_key_format)
208
+ Proc.new { |value|
209
+ value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
210
+ strptime.exec(value).to_time
211
+ }
212
+ rescue
213
+ # Can happen if Strptime doesn't recognize the format; or
214
+ # if strptime couldn't be required (because it's not installed -- it's
215
+ # ruby 2 only)
216
+ Proc.new { |value|
217
+ value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
218
+ DateTime.strptime(value, @timestamp_key_format).to_time
219
+ }
220
+ end
221
+ else
222
+ Proc.new { |value|
223
+ value = convert_numeric_time_into_string(value) if value.is_a?(Numeric)
224
+ DateTime.parse(value).to_time
225
+ }
226
+ end
227
+ end
228
+
229
+ def convert_numeric_time_into_string(numeric_time, timestamp_key_format = "%Y-%m-%dT%H:%M:%S.%N%z")
230
+ numeric_time_parser = Fluent::NumericTimeParser.new(:float)
231
+ Time.at(numeric_time_parser.parse(numeric_time).to_r).strftime(timestamp_key_format)
232
+ end
233
+
234
+ def parse_time(value, event_time, tag)
235
+ @timestamp_parser.call(value)
236
+ rescue => e
237
+ emit_error_label_event do
238
+ router.emit_error_event(@timestamp_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @timestamp_key_format, 'value' => value}, e)
239
+ end
240
+ return Time.at(event_time).to_time
241
+ end
242
+
243
+ def get_reachable_hosts(hosts=nil)
244
+ reachable_hosts = []
245
+ attempt = 0
246
+ loop do
247
+ hosts.each do |host|
248
+ begin
249
+ if @infinite_check_connection == true
250
+ check_host = OpenSearch::Client.new(
251
+ host: ["#{host[:scheme]}://#{host[:host]}:#{host[:port]}"],
252
+ user: host[:user],
253
+ password: host[:password],
254
+ reload_connections: true,
255
+ resurrect_after: @resurrect_after,
256
+ reload_on_failure: @reload_on_failure,
257
+ transport_options: { ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version } }
258
+ )
259
+ response = check_host.ping #https://github.com/opensearch-project/opensearch-ruby/blob/136e1c975fc91b8cb80d7d1134e32c6dbefdb3eb/lib/opensearch/api/actions/ping.rb#L33
260
+ if response == true
261
+ reachable_hosts << host
262
+ else
263
+ log.warn "Connection to #{host[:scheme]}://#{host[:host]}:#{host[:port]} failed with status code #{response.status}"
264
+ end
265
+ else
266
+ reachable_hosts << host
267
+ end
268
+ rescue => e
269
+ log.warn "Failed to connect to #{host[:scheme]}://#{host[:host]}:#{host[:port]}"
270
+ end
271
+ end
272
+ break unless reachable_hosts.empty?
273
+ log.info "Attempt ##{attempt += 1} to get reachable hosts"
274
+ log.info "No reachable hosts found. Retrying in #{@request_timeout} seconds..."
275
+ sleep(@request_timeout)
276
+ end
277
+ reachable_hosts
278
+ end
279
+
280
+ def client(host = nil)
281
+ # check here to see if we already have a client connection for the given host
282
+ connection_options = get_connection_options(host)
283
+
284
+ @_os = nil unless is_existing_connection(connection_options[:hosts])
285
+
286
+ @_os ||= begin
287
+ @current_config = connection_options[:hosts].clone
288
+ adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
289
+ local_reload_connections = @reload_connections
290
+ if local_reload_connections && @reload_after > DEFAULT_RELOAD_AFTER
291
+ local_reload_connections = @reload_after
292
+ end
293
+
294
+ headers = { 'Content-Type' => "application/json" }.merge(@custom_headers)
295
+
296
+ transport = OpenSearch::Transport::Transport::HTTP::Faraday.new(
297
+ connection_options.merge(
298
+ options: {
299
+ reload_connections: local_reload_connections,
300
+ reload_on_failure: @reload_on_failure,
301
+ resurrect_after: @resurrect_after,
302
+ logger: @transport_logger,
303
+ transport_options: {
304
+ headers: headers,
305
+ request: { timeout: @request_timeout },
306
+ ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version }
307
+ },
308
+ http: {
309
+ user: @user,
310
+ password: @password
311
+ },
312
+ sniffer_class: @sniffer_class,
313
+ }), &adapter_conf)
314
+ OpenSearch::Client.new transport: transport
315
+ end
316
+ end
317
+
318
+ def is_existing_connection(host)
319
+ # check if the host provided match the current connection
320
+ return false if @_os.nil?
321
+ return false if @current_config.nil?
322
+ return false if host.length != @current_config.length
323
+
324
+ for i in 0...host.length
325
+ if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
326
+ return false
327
+ end
328
+ end
329
+
330
+ return true
331
+ end
332
+
333
+ def run
334
+ return run_slice if @num_slices <= 1
335
+
336
+ log.warn("Large slice number is specified:(#{@num_slices}). Consider reducing num_slices") if @num_slices > 8
337
+
338
+ @num_slices.times.map do |slice_id|
339
+ thread_create(:"in_opensearch_thread_#{slice_id}") do
340
+ run_slice(slice_id)
341
+ end
342
+ end
343
+ rescue Faraday::ConnectionFailed => e
344
+ log.warn "Connection to OpenSearch failed during search in the 'run' method: #{e.message}. Retrying..."
345
+ retry
346
+ end
347
+
348
+ def run_slice(slice_id=nil)
349
+ slice_query = @base_query
350
+ slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @num_slices}) unless slice_id.nil?
351
+ result = client.search(@options.merge(:body => Yajl.dump(slice_query) ))
352
+ es = Fluent::MultiEventStream.new
353
+
354
+ result["hits"]["hits"].each {|hit| process_events(hit, es)}
355
+ has_hits = result['hits']['hits'].any?
356
+ scroll_id = result['_scroll_id']
357
+
358
+ while has_hits && scroll_id
359
+ result = process_next_scroll_request(es, scroll_id)
360
+ has_hits = result['has_hits']
361
+ scroll_id = result['_scroll_id']
362
+ end
363
+
364
+ router.emit_stream(@tag, es)
365
+ clear_scroll(scroll_id)
366
+ end
367
+
368
+ def clear_scroll(scroll_id)
369
+ client.clear_scroll(scroll_id: scroll_id) if scroll_id
370
+ rescue => e
371
+ # ignore & log any clear_scroll errors
372
+ log.warn("Ignoring clear_scroll exception", message: e.message, exception: e.class)
373
+ end
374
+
375
+ def process_scroll_request(scroll_id)
376
+ client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
377
+ end
378
+
379
+ def process_next_scroll_request(es, scroll_id)
380
+ result = process_scroll_request(scroll_id)
381
+ result['hits']['hits'].each { |hit| process_events(hit, es) }
382
+ {'has_hits' => result['hits']['hits'].any?, '_scroll_id' => result['_scroll_id']}
383
+ end
384
+
385
+ def process_events(hit, es)
386
+ event = hit["_source"]
387
+ time = Fluent::Engine.now
388
+ if @parse_timestamp
389
+ if event.has_key?(TIMESTAMP_FIELD)
390
+ rts = event[TIMESTAMP_FIELD]
391
+ time = parse_time(rts, time, @tag)
392
+ end
393
+ end
394
+ if @docinfo
395
+ docinfo_target = event[@docinfo_target] || {}
396
+
397
+ unless docinfo_target.is_a?(Hash)
398
+ raise UnrecoverableError, "incompatible type for the docinfo_target=#{@docinfo_target} field in the `_source` document, expected a hash got:", :type => docinfo_target.class, :event => event
399
+ end
400
+
401
+ @docinfo_fields.each do |field|
402
+ docinfo_target[field] = hit[field]
403
+ end
404
+
405
+ event[@docinfo_target] = docinfo_target
406
+ end
407
+ es.add(time, event)
408
+ end
409
+ end
410
+ end