fluent-plugin-opensearch 1.0.0

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 +37 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  6. data/.github/workflows/coverage.yaml +22 -0
  7. data/.github/workflows/issue-auto-closer.yml +12 -0
  8. data/.github/workflows/linux.yml +26 -0
  9. data/.github/workflows/macos.yml +26 -0
  10. data/.github/workflows/windows.yml +26 -0
  11. data/.gitignore +18 -0
  12. data/CONTRIBUTING.md +24 -0
  13. data/Gemfile +10 -0
  14. data/History.md +6 -0
  15. data/ISSUE_TEMPLATE.md +26 -0
  16. data/LICENSE.txt +201 -0
  17. data/PULL_REQUEST_TEMPLATE.md +9 -0
  18. data/README.OpenSearchGenID.md +116 -0
  19. data/README.OpenSearchInput.md +291 -0
  20. data/README.Troubleshooting.md +482 -0
  21. data/README.md +1556 -0
  22. data/Rakefile +37 -0
  23. data/fluent-plugin-opensearch.gemspec +38 -0
  24. data/gemfiles/Gemfile.elasticsearch.v6 +12 -0
  25. data/lib/fluent/log-ext.rb +64 -0
  26. data/lib/fluent/plugin/filter_opensearch_genid.rb +103 -0
  27. data/lib/fluent/plugin/in_opensearch.rb +351 -0
  28. data/lib/fluent/plugin/oj_serializer.rb +48 -0
  29. data/lib/fluent/plugin/opensearch_constants.rb +39 -0
  30. data/lib/fluent/plugin/opensearch_error.rb +31 -0
  31. data/lib/fluent/plugin/opensearch_error_handler.rb +166 -0
  32. data/lib/fluent/plugin/opensearch_fallback_selector.rb +36 -0
  33. data/lib/fluent/plugin/opensearch_index_template.rb +155 -0
  34. data/lib/fluent/plugin/opensearch_simple_sniffer.rb +36 -0
  35. data/lib/fluent/plugin/opensearch_tls.rb +96 -0
  36. data/lib/fluent/plugin/out_opensearch.rb +1124 -0
  37. data/lib/fluent/plugin/out_opensearch_data_stream.rb +214 -0
  38. data/test/helper.rb +61 -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 +493 -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 +689 -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 +3953 -0
  49. data/test/plugin/test_out_opensearch_data_stream.rb +474 -0
  50. data/test/plugin/test_template.json +23 -0
  51. data/test/test_log-ext.rb +61 -0
  52. metadata +262 -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,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'fluent-plugin-opensearch'
6
+ s.version = '1.0.0'
7
+ s.authors = ['Hiroshi Hatake']
8
+ s.email = ['cosmo0920.wp@gmail.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 'excon', '>= 0'
27
+ s.add_runtime_dependency 'opensearch-ruby'
28
+ s.add_runtime_dependency "aws-sdk-core", "~> 3"
29
+ s.add_runtime_dependency "faraday_middleware-aws-sigv4"
30
+
31
+
32
+ s.add_development_dependency 'rake', '>= 0'
33
+ s.add_development_dependency 'webrick', '~> 1.7.0'
34
+ s.add_development_dependency 'webmock', '~> 3'
35
+ s.add_development_dependency 'test-unit', '~> 3.3.0'
36
+ s.add_development_dependency 'minitest', '~> 5.8'
37
+ s.add_development_dependency 'flexmock', '~> 2.0'
38
+ 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,351 @@
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 'fluent/log-ext'
30
+ require 'fluent/plugin/input'
31
+ require_relative 'opensearch_constants'
32
+
33
+ module Fluent::Plugin
34
+ class OpenSearchInput < Input
35
+ class UnrecoverableRequestFailure < Fluent::UnrecoverableError; end
36
+
37
+ DEFAULT_RELOAD_AFTER = -1
38
+ DEFAULT_STORAGE_TYPE = 'local'
39
+ METADATA = "@metadata".freeze
40
+
41
+ helpers :timer, :thread
42
+
43
+ Fluent::Plugin.register_input('opensearch', self)
44
+
45
+ config_param :tag, :string
46
+ config_param :host, :string, :default => 'localhost'
47
+ config_param :port, :integer, :default => 9200
48
+ config_param :user, :string, :default => nil
49
+ config_param :password, :string, :default => nil, :secret => true
50
+ config_param :path, :string, :default => nil
51
+ config_param :scheme, :enum, :list => [:https, :http], :default => :http
52
+ config_param :hosts, :string, :default => nil
53
+ config_param :index_name, :string, :default => "fluentd"
54
+ config_param :parse_timestamp, :bool, :default => false
55
+ config_param :timestamp_key_format, :string, :default => nil
56
+ config_param :timestamp_parse_error_tag, :string, :default => 'opensearch_plugin.input.time.error'
57
+ config_param :query, :hash, :default => {"sort" => [ "_doc" ]}
58
+ config_param :scroll, :string, :default => "1m"
59
+ config_param :size, :integer, :default => 1000
60
+ config_param :num_slices, :integer, :default => 1
61
+ config_param :interval, :size, :default => 5
62
+ config_param :repeat, :bool, :default => true
63
+ config_param :http_backend, :enum, list: [:excon, :typhoeus], :default => :excon
64
+ config_param :request_timeout, :time, :default => 5
65
+ config_param :reload_connections, :bool, :default => true
66
+ config_param :reload_on_failure, :bool, :default => false
67
+ config_param :resurrect_after, :time, :default => 60
68
+ config_param :reload_after, :integer, :default => DEFAULT_RELOAD_AFTER
69
+ config_param :ssl_verify , :bool, :default => true
70
+ config_param :client_key, :string, :default => nil
71
+ config_param :client_cert, :string, :default => nil
72
+ config_param :client_key_pass, :string, :default => nil, :secret => true
73
+ config_param :ca_file, :string, :default => nil
74
+ config_param :ssl_version, :enum, list: [:SSLv23, :TLSv1, :TLSv1_1, :TLSv1_2], :default => :TLSv1_2
75
+ config_param :with_transporter_log, :bool, :default => false
76
+ config_param :sniffer_class_name, :string, :default => nil
77
+ config_param :custom_headers, :hash, :default => {}
78
+ config_param :docinfo_fields, :array, :default => ['_index', '_type', '_id']
79
+ config_param :docinfo_target, :string, :default => METADATA
80
+ config_param :docinfo, :bool, :default => false
81
+
82
+ include Fluent::Plugin::OpenSearchConstants
83
+
84
+ def initialize
85
+ super
86
+ end
87
+
88
+ def configure(conf)
89
+ super
90
+
91
+ @timestamp_parser = create_time_parser
92
+ @backend_options = backend_options
93
+
94
+ raise Fluent::ConfigError, "`password` must be present if `user` is present" if @user && @password.nil?
95
+
96
+ if @user && m = @user.match(/%{(?<user>.*)}/)
97
+ @user = URI.encode_www_form_component(m["user"])
98
+ end
99
+ if @password && m = @password.match(/%{(?<password>.*)}/)
100
+ @password = URI.encode_www_form_component(m["password"])
101
+ end
102
+
103
+ @transport_logger = nil
104
+ if @with_transporter_log
105
+ @transport_logger = log
106
+ log_level = conf['@log_level'] || conf['log_level']
107
+ log.warn "Consider to specify log_level with @log_level." unless log_level
108
+ end
109
+ @current_config = nil
110
+ # Specify @sniffer_class before calling #client.
111
+ @sniffer_class = nil
112
+ begin
113
+ @sniffer_class = Object.const_get(@sniffer_class_name) if @sniffer_class_name
114
+ rescue Exception => ex
115
+ raise Fluent::ConfigError, "Could not load sniffer class #{@sniffer_class_name}: #{ex}"
116
+ end
117
+
118
+ @options = {
119
+ :index => @index_name,
120
+ :scroll => @scroll,
121
+ :size => @size
122
+ }
123
+ @base_query = @query
124
+ end
125
+
126
+ def backend_options
127
+ case @http_backend
128
+ when :excon
129
+ { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
130
+ when :typhoeus
131
+ require 'typhoeus'
132
+ { sslkey: @client_key, sslcert: @client_cert, keypasswd: @client_key_pass }
133
+ end
134
+ rescue LoadError => ex
135
+ log.error_backtrace(ex.backtrace)
136
+ raise Fluent::ConfigError, "You must install #{@http_backend} gem. Exception: #{ex}"
137
+ end
138
+
139
+ def get_escaped_userinfo(host_str)
140
+ if m = host_str.match(/(?<scheme>.*)%{(?<user>.*)}:%{(?<password>.*)}(?<path>@.*)/)
141
+ m["scheme"] +
142
+ URI.encode_www_form_component(m["user"]) +
143
+ ':' +
144
+ URI.encode_www_form_component(m["password"]) +
145
+ m["path"]
146
+ else
147
+ host_str
148
+ end
149
+ end
150
+
151
+ def get_connection_options(con_host=nil)
152
+
153
+ hosts = if con_host || @hosts
154
+ (con_host || @hosts).split(',').map do |host_str|
155
+ # Support legacy hosts format host:port,host:port,host:port...
156
+ if host_str.match(%r{^[^:]+(\:\d+)?$})
157
+ {
158
+ host: host_str.split(':')[0],
159
+ port: (host_str.split(':')[1] || @port).to_i,
160
+ scheme: @scheme.to_s
161
+ }
162
+ else
163
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
164
+ uri = URI(get_escaped_userinfo(host_str))
165
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
166
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
167
+ hash
168
+ end
169
+ end
170
+ end.compact
171
+ else
172
+ [{host: @host, port: @port, scheme: @scheme.to_s}]
173
+ end.each do |host|
174
+ host.merge!(user: @user, password: @password) if !host[:user] && @user
175
+ host.merge!(path: @path) if !host[:path] && @path
176
+ end
177
+
178
+ {
179
+ hosts: hosts
180
+ }
181
+ end
182
+
183
+ def start
184
+ super
185
+
186
+ timer_execute(:in_opensearch_timer, @interval, repeat: @repeat, &method(:run))
187
+ end
188
+
189
+ # We might be able to use
190
+ # Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
191
+ # [sec,nsec] where as we want something we can call `strftime` on...
192
+ def create_time_parser
193
+ if @timestamp_key_format
194
+ begin
195
+ # Strptime doesn't support all formats, but for those it does it's
196
+ # blazingly fast.
197
+ strptime = Strptime.new(@timestamp_key_format)
198
+ Proc.new { |value|
199
+ value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
200
+ strptime.exec(value).to_time
201
+ }
202
+ rescue
203
+ # Can happen if Strptime doesn't recognize the format; or
204
+ # if strptime couldn't be required (because it's not installed -- it's
205
+ # ruby 2 only)
206
+ Proc.new { |value|
207
+ value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
208
+ DateTime.strptime(value, @timestamp_key_format).to_time
209
+ }
210
+ end
211
+ else
212
+ Proc.new { |value|
213
+ value = convert_numeric_time_into_string(value) if value.is_a?(Numeric)
214
+ DateTime.parse(value).to_time
215
+ }
216
+ end
217
+ end
218
+
219
+ def convert_numeric_time_into_string(numeric_time, timestamp_key_format = "%Y-%m-%dT%H:%M:%S.%N%z")
220
+ numeric_time_parser = Fluent::NumericTimeParser.new(:float)
221
+ Time.at(numeric_time_parser.parse(numeric_time).to_r).strftime(timestamp_key_format)
222
+ end
223
+
224
+ def parse_time(value, event_time, tag)
225
+ @timestamp_parser.call(value)
226
+ rescue => e
227
+ router.emit_error_event(@timestamp_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @timestamp_key_format, 'value' => value}, e)
228
+ return Time.at(event_time).to_time
229
+ end
230
+
231
+ def client(host = nil)
232
+ # check here to see if we already have a client connection for the given host
233
+ connection_options = get_connection_options(host)
234
+
235
+ @_os = nil unless is_existing_connection(connection_options[:hosts])
236
+
237
+ @_os ||= begin
238
+ @current_config = connection_options[:hosts].clone
239
+ adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
240
+ local_reload_connections = @reload_connections
241
+ if local_reload_connections && @reload_after > DEFAULT_RELOAD_AFTER
242
+ local_reload_connections = @reload_after
243
+ end
244
+
245
+ headers = { 'Content-Type' => "application/json" }.merge(@custom_headers)
246
+
247
+ transport = OpenSearch::Transport::Transport::HTTP::Faraday.new(
248
+ connection_options.merge(
249
+ options: {
250
+ reload_connections: local_reload_connections,
251
+ reload_on_failure: @reload_on_failure,
252
+ resurrect_after: @resurrect_after,
253
+ logger: @transport_logger,
254
+ transport_options: {
255
+ headers: headers,
256
+ request: { timeout: @request_timeout },
257
+ ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version }
258
+ },
259
+ http: {
260
+ user: @user,
261
+ password: @password
262
+ },
263
+ sniffer_class: @sniffer_class,
264
+ }), &adapter_conf)
265
+ OpenSearch::Client.new transport: transport
266
+ end
267
+ end
268
+
269
+ def is_existing_connection(host)
270
+ # check if the host provided match the current connection
271
+ return false if @_os.nil?
272
+ return false if @current_config.nil?
273
+ return false if host.length != @current_config.length
274
+
275
+ for i in 0...host.length
276
+ if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
277
+ return false
278
+ end
279
+ end
280
+
281
+ return true
282
+ end
283
+
284
+ def run
285
+ return run_slice if @num_slices <= 1
286
+
287
+ log.warn("Large slice number is specified:(#{@num_slices}). Consider reducing num_slices") if @num_slices > 8
288
+
289
+ @num_slices.times.map do |slice_id|
290
+ thread_create(:"in_opensearch_thread_#{slice_id}") do
291
+ run_slice(slice_id)
292
+ end
293
+ end
294
+ end
295
+
296
+ def run_slice(slice_id=nil)
297
+ slice_query = @base_query
298
+ slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @num_slices}) unless slice_id.nil?
299
+ result = client.search(@options.merge(:body => Yajl.dump(slice_query) ))
300
+ es = Fluent::MultiEventStream.new
301
+
302
+ result["hits"]["hits"].each {|hit| process_events(hit, es)}
303
+ has_hits = result['hits']['hits'].any?
304
+ scroll_id = result['_scroll_id']
305
+
306
+ while has_hits && scroll_id
307
+ result = process_next_scroll_request(es, scroll_id)
308
+ has_hits = result['has_hits']
309
+ scroll_id = result['_scroll_id']
310
+ end
311
+
312
+ router.emit_stream(@tag, es)
313
+ client.clear_scroll(scroll_id: scroll_id) if scroll_id
314
+ end
315
+
316
+ def process_scroll_request(scroll_id)
317
+ client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
318
+ end
319
+
320
+ def process_next_scroll_request(es, scroll_id)
321
+ result = process_scroll_request(scroll_id)
322
+ result['hits']['hits'].each { |hit| process_events(hit, es) }
323
+ {'has_hits' => result['hits']['hits'].any?, '_scroll_id' => result['_scroll_id']}
324
+ end
325
+
326
+ def process_events(hit, es)
327
+ event = hit["_source"]
328
+ time = Fluent::Engine.now
329
+ if @parse_timestamp
330
+ if event.has_key?(TIMESTAMP_FIELD)
331
+ rts = event[TIMESTAMP_FIELD]
332
+ time = parse_time(rts, time, @tag)
333
+ end
334
+ end
335
+ if @docinfo
336
+ docinfo_target = event[@docinfo_target] || {}
337
+
338
+ unless docinfo_target.is_a?(Hash)
339
+ 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
340
+ end
341
+
342
+ @docinfo_fields.each do |field|
343
+ docinfo_target[field] = hit[field]
344
+ end
345
+
346
+ event[@docinfo_target] = docinfo_target
347
+ end
348
+ es.add(time, event)
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,48 @@
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 'oj'
28
+
29
+ module Fluent::Plugin
30
+ module Serializer
31
+
32
+ class Oj
33
+ include OpenSearch::Transport::Transport::Serializer::Base
34
+
35
+ # De-serialize a Hash from JSON string
36
+ #
37
+ def load(string, options={})
38
+ ::Oj.load(string, options)
39
+ end
40
+
41
+ # Serialize a Hash to JSON string
42
+ #
43
+ def dump(object, options={})
44
+ ::Oj.dump(object, options)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
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
+ module Fluent
28
+ module Plugin
29
+ module OpenSearchConstants
30
+ BODY_DELIMITER = "\n".freeze
31
+ UPDATE_OP = "update".freeze
32
+ UPSERT_OP = "upsert".freeze
33
+ CREATE_OP = "create".freeze
34
+ INDEX_OP = "index".freeze
35
+ ID_FIELD = "_id".freeze
36
+ TIMESTAMP_FIELD = "@timestamp".freeze
37
+ end
38
+ end
39
+ end