fluent-plugin-opensearch 1.0.0

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 +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