fluent-plugin-input-opensearch 1.1.9

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