logstash-output-amazon_es 2.0.1-java → 6.4.0-java

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 (35) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTORS +12 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +10 -199
  5. data/README.md +34 -65
  6. data/lib/logstash/outputs/amazon_es.rb +218 -423
  7. data/lib/logstash/outputs/amazon_es/common.rb +347 -0
  8. data/lib/logstash/outputs/amazon_es/common_configs.rb +141 -0
  9. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es2x.json +95 -0
  10. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es5x.json +46 -0
  11. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es6x.json +45 -0
  12. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es7x.json +46 -0
  13. data/lib/logstash/outputs/amazon_es/http_client.rb +359 -74
  14. data/lib/logstash/outputs/amazon_es/http_client/manticore_adapter.rb +169 -0
  15. data/lib/logstash/outputs/amazon_es/http_client/pool.rb +457 -0
  16. data/lib/logstash/outputs/amazon_es/http_client_builder.rb +164 -0
  17. data/lib/logstash/outputs/amazon_es/template_manager.rb +36 -0
  18. data/logstash-output-amazon_es.gemspec +13 -22
  19. data/spec/es_spec_helper.rb +37 -0
  20. data/spec/unit/http_client_builder_spec.rb +189 -0
  21. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +105 -0
  22. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +198 -0
  23. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +222 -0
  24. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
  25. data/spec/unit/outputs/elasticsearch_spec.rb +615 -0
  26. data/spec/unit/outputs/error_whitelist_spec.rb +60 -0
  27. metadata +49 -110
  28. data/lib/logstash/outputs/amazon_es/aws_transport.rb +0 -109
  29. data/lib/logstash/outputs/amazon_es/aws_v4_signer.rb +0 -7
  30. data/lib/logstash/outputs/amazon_es/aws_v4_signer_impl.rb +0 -62
  31. data/lib/logstash/outputs/amazon_es/elasticsearch-template.json +0 -41
  32. data/spec/amazon_es_spec_helper.rb +0 -69
  33. data/spec/unit/outputs/amazon_es_spec.rb +0 -50
  34. data/spec/unit/outputs/elasticsearch/protocol_spec.rb +0 -36
  35. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +0 -58
@@ -0,0 +1,95 @@
1
+ {
2
+ "template" : "logstash-*",
3
+ "settings" : {
4
+ "index.refresh_interval" : "5s"
5
+ },
6
+ "mappings" : {
7
+ "_default_" : {
8
+ "_all" : {"enabled" : true, "omit_norms" : true},
9
+ "dynamic_templates" : [ {
10
+ "message_field" : {
11
+ "path_match" : "message",
12
+ "match_mapping_type" : "string",
13
+ "mapping" : {
14
+ "type" : "string", "index" : "analyzed", "omit_norms" : true,
15
+ "fielddata" : { "format" : "disabled" }
16
+ }
17
+ }
18
+ }, {
19
+ "string_fields" : {
20
+ "match" : "*",
21
+ "match_mapping_type" : "string",
22
+ "mapping" : {
23
+ "type" : "string", "index" : "analyzed", "omit_norms" : true,
24
+ "fielddata" : { "format" : "disabled" },
25
+ "fields" : {
26
+ "raw" : {"type": "string", "index" : "not_analyzed", "doc_values" : true, "ignore_above" : 256}
27
+ }
28
+ }
29
+ }
30
+ }, {
31
+ "float_fields" : {
32
+ "match" : "*",
33
+ "match_mapping_type" : "float",
34
+ "mapping" : { "type" : "float", "doc_values" : true }
35
+ }
36
+ }, {
37
+ "double_fields" : {
38
+ "match" : "*",
39
+ "match_mapping_type" : "double",
40
+ "mapping" : { "type" : "double", "doc_values" : true }
41
+ }
42
+ }, {
43
+ "byte_fields" : {
44
+ "match" : "*",
45
+ "match_mapping_type" : "byte",
46
+ "mapping" : { "type" : "byte", "doc_values" : true }
47
+ }
48
+ }, {
49
+ "short_fields" : {
50
+ "match" : "*",
51
+ "match_mapping_type" : "short",
52
+ "mapping" : { "type" : "short", "doc_values" : true }
53
+ }
54
+ }, {
55
+ "integer_fields" : {
56
+ "match" : "*",
57
+ "match_mapping_type" : "integer",
58
+ "mapping" : { "type" : "integer", "doc_values" : true }
59
+ }
60
+ }, {
61
+ "long_fields" : {
62
+ "match" : "*",
63
+ "match_mapping_type" : "long",
64
+ "mapping" : { "type" : "long", "doc_values" : true }
65
+ }
66
+ }, {
67
+ "date_fields" : {
68
+ "match" : "*",
69
+ "match_mapping_type" : "date",
70
+ "mapping" : { "type" : "date", "doc_values" : true }
71
+ }
72
+ }, {
73
+ "geo_point_fields" : {
74
+ "match" : "*",
75
+ "match_mapping_type" : "geo_point",
76
+ "mapping" : { "type" : "geo_point", "doc_values" : true }
77
+ }
78
+ } ],
79
+ "properties" : {
80
+ "@timestamp": { "type": "date", "doc_values" : true },
81
+ "@version": { "type": "string", "index": "not_analyzed", "doc_values" : true },
82
+ "geoip" : {
83
+ "type" : "object",
84
+ "dynamic": true,
85
+ "properties" : {
86
+ "ip": { "type": "ip", "doc_values" : true },
87
+ "location" : { "type" : "geo_point", "doc_values" : true },
88
+ "latitude" : { "type" : "float", "doc_values" : true },
89
+ "longitude" : { "type" : "float", "doc_values" : true }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "template" : "logstash-*",
3
+ "version" : 50001,
4
+ "settings" : {
5
+ "index.refresh_interval" : "5s"
6
+ },
7
+ "mappings" : {
8
+ "_default_" : {
9
+ "_all" : {"enabled" : true, "norms" : false},
10
+ "dynamic_templates" : [ {
11
+ "message_field" : {
12
+ "path_match" : "message",
13
+ "match_mapping_type" : "string",
14
+ "mapping" : {
15
+ "type" : "text",
16
+ "norms" : false
17
+ }
18
+ }
19
+ }, {
20
+ "string_fields" : {
21
+ "match" : "*",
22
+ "match_mapping_type" : "string",
23
+ "mapping" : {
24
+ "type" : "text", "norms" : false,
25
+ "fields" : {
26
+ "keyword" : { "type": "keyword", "ignore_above": 256 }
27
+ }
28
+ }
29
+ }
30
+ } ],
31
+ "properties" : {
32
+ "@timestamp": { "type": "date", "include_in_all": false },
33
+ "@version": { "type": "keyword", "include_in_all": false },
34
+ "geoip" : {
35
+ "dynamic": true,
36
+ "properties" : {
37
+ "ip": { "type": "ip" },
38
+ "location" : { "type" : "geo_point" },
39
+ "latitude" : { "type" : "half_float" },
40
+ "longitude" : { "type" : "half_float" }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "template" : "logstash-*",
3
+ "version" : 60001,
4
+ "settings" : {
5
+ "index.refresh_interval" : "5s"
6
+ },
7
+ "mappings" : {
8
+ "_default_" : {
9
+ "dynamic_templates" : [ {
10
+ "message_field" : {
11
+ "path_match" : "message",
12
+ "match_mapping_type" : "string",
13
+ "mapping" : {
14
+ "type" : "text",
15
+ "norms" : false
16
+ }
17
+ }
18
+ }, {
19
+ "string_fields" : {
20
+ "match" : "*",
21
+ "match_mapping_type" : "string",
22
+ "mapping" : {
23
+ "type" : "text", "norms" : false,
24
+ "fields" : {
25
+ "keyword" : { "type": "keyword", "ignore_above": 256 }
26
+ }
27
+ }
28
+ }
29
+ } ],
30
+ "properties" : {
31
+ "@timestamp": { "type": "date"},
32
+ "@version": { "type": "keyword"},
33
+ "geoip" : {
34
+ "dynamic": true,
35
+ "properties" : {
36
+ "ip": { "type": "ip" },
37
+ "location" : { "type" : "geo_point" },
38
+ "latitude" : { "type" : "half_float" },
39
+ "longitude" : { "type" : "half_float" }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "template" : "logstash-*",
3
+ "version" : 60001,
4
+ "settings" : {
5
+ "index.refresh_interval" : "5s",
6
+ "number_of_shards": 1
7
+ },
8
+ "mappings" : {
9
+ "_doc" : {
10
+ "dynamic_templates" : [ {
11
+ "message_field" : {
12
+ "path_match" : "message",
13
+ "match_mapping_type" : "string",
14
+ "mapping" : {
15
+ "type" : "text",
16
+ "norms" : false
17
+ }
18
+ }
19
+ }, {
20
+ "string_fields" : {
21
+ "match" : "*",
22
+ "match_mapping_type" : "string",
23
+ "mapping" : {
24
+ "type" : "text", "norms" : false,
25
+ "fields" : {
26
+ "keyword" : { "type": "keyword", "ignore_above": 256 }
27
+ }
28
+ }
29
+ }
30
+ } ],
31
+ "properties" : {
32
+ "@timestamp": { "type": "date"},
33
+ "@version": { "type": "keyword"},
34
+ "geoip" : {
35
+ "dynamic": true,
36
+ "properties" : {
37
+ "ip": { "type": "ip" },
38
+ "location" : { "type" : "geo_point" },
39
+ "latitude" : { "type" : "half_float" },
40
+ "longitude" : { "type" : "half_float" }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -1,21 +1,83 @@
1
1
  require "logstash/outputs/amazon_es"
2
2
  require "cabin"
3
3
  require "base64"
4
- require "elasticsearch"
4
+ require 'logstash/outputs/amazon_es/http_client/pool'
5
+ require 'logstash/outputs/amazon_es/http_client/manticore_adapter'
6
+ require 'cgi'
7
+ require 'zlib'
8
+ require 'stringio'
5
9
 
6
- require_relative "aws_transport"
10
+ module LogStash; module Outputs; class ElasticSearch;
11
+ # This is a constant instead of a config option because
12
+ # there really isn't a good reason to configure it.
13
+ #
14
+ # The criteria used are:
15
+ # 1. We need a number that's less than 100MiB because ES
16
+ # won't accept bulks larger than that.
17
+ # 2. It must be large enough to amortize the connection constant
18
+ # across multiple requests.
19
+ # 3. It must be small enough that even if multiple threads hit this size
20
+ # we won't use a lot of heap.
21
+ #
22
+ # We wound up agreeing that a number greater than 10 MiB and less than 100MiB
23
+ # made sense. We picked one on the lowish side to not use too much heap.
24
+ TARGET_BULK_BYTES = 20 * 1024 * 1024 # 20MiB
7
25
 
8
- module LogStash::Outputs::AES
9
26
  class HttpClient
10
- attr_reader :client, :options, :client_options
11
- DEFAULT_OPTIONS = {
12
- :port => 80
13
- }
27
+ attr_reader :client, :options, :logger, :pool, :action_count, :recv_count
28
+ # This is here in case we use DEFAULT_OPTIONS in the future
29
+ # DEFAULT_OPTIONS = {
30
+ # :setting => value
31
+ # }
14
32
 
33
+ #
34
+ # The `options` is a hash where the following symbol keys have meaning:
35
+ #
36
+ # * `:hosts` - array of String. Set a list of hosts to use for communication.
37
+ # * `:port` - number. set the port to use to communicate with Elasticsearch
38
+ # * `:user` - String. The user to use for authentication.
39
+ # * `:password` - String. The password to use for authentication.
40
+ # * `:timeout` - Float. A duration value, in seconds, after which a socket
41
+ # operation or request will be aborted if not yet successfull
42
+ # * `:client_settings` - a hash; see below for keys.
43
+ #
44
+ # The `client_settings` key is a has that can contain other settings:
45
+ #
46
+ # * `:ssl` - Boolean. Enable or disable SSL/TLS.
47
+ # * `:proxy` - String. Choose a HTTP HTTProxy to use.
48
+ # * `:path` - String. The leading path for prefixing Elasticsearch
49
+ # * `:headers` - Hash. Pairs of headers and their values
50
+ # requests. This is sometimes used if you are proxying Elasticsearch access
51
+ # through a special http path, such as using mod_rewrite.
15
52
  def initialize(options={})
16
- @logger = Cabin::Channel.get
17
- @options = DEFAULT_OPTIONS.merge(options)
18
- @client = build_client(@options)
53
+ @logger = options[:logger]
54
+ @metric = options[:metric]
55
+ @bulk_request_metrics = @metric.namespace(:bulk_requests)
56
+ @bulk_response_metrics = @bulk_request_metrics.namespace(:responses)
57
+
58
+ # Again, in case we use DEFAULT_OPTIONS in the future, uncomment this.
59
+ # @options = DEFAULT_OPTIONS.merge(options)
60
+ @options = options
61
+
62
+ @url_template = build_url_template
63
+ puts 'url template'
64
+ puts @url_template
65
+
66
+ @pool = build_pool(@options)
67
+ # mutex to prevent requests and sniffing to access the
68
+ # connection pool at the same time
69
+ @bulk_path = @options[:bulk_path]
70
+ end
71
+
72
+ def build_url_template
73
+ {
74
+ :scheme => self.scheme,
75
+ :user => self.user,
76
+ :password => self.password,
77
+ :host => "URLTEMPLATE",
78
+ :port => self.port,
79
+ :path => self.path
80
+ }
19
81
  end
20
82
 
21
83
  def template_install(name, template, force=false)
@@ -26,94 +88,317 @@ module LogStash::Outputs::AES
26
88
  template_put(name, template)
27
89
  end
28
90
 
91
+ def maximum_seen_major_version
92
+ @pool.maximum_seen_major_version
93
+ end
94
+
29
95
  def bulk(actions)
30
- bulk_body = actions.collect do |action, args, source|
31
- if action == 'update'
32
- if args[:_id]
33
- source = { 'doc' => source }
34
- if @options[:doc_as_upsert]
35
- source['doc_as_upsert'] = true
36
- else
37
- source['upsert'] = args[:_upsert] if args[:_upsert]
38
- end
39
- else
40
- raise(LogStash::ConfigurationError, "Specifying action => 'update' without a document '_id' is not supported.")
41
- end
42
- end
96
+ @action_count ||= 0
97
+ @action_count += actions.size
43
98
 
44
- args.delete(:_upsert)
99
+ return if actions.empty?
45
100
 
46
- if source
101
+ bulk_actions = actions.collect do |action, args, source|
102
+ args, source = update_action_builder(args, source) if action == 'update'
103
+
104
+ if source && action != 'delete'
47
105
  next [ { action => args }, source ]
48
106
  else
49
107
  next { action => args }
50
108
  end
51
- end.flatten
109
+ end
110
+
111
+ body_stream = StringIO.new
112
+ if http_compression
113
+ body_stream.set_encoding "BINARY"
114
+ stream_writer = Zlib::GzipWriter.new(body_stream, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
115
+ else
116
+ stream_writer = body_stream
117
+ end
118
+ bulk_responses = []
119
+ bulk_actions.each do |action|
120
+ as_json = action.is_a?(Array) ?
121
+ action.map {|line| LogStash::Json.dump(line)}.join("\n") :
122
+ LogStash::Json.dump(action)
123
+ as_json << "\n"
124
+ if (body_stream.size + as_json.bytesize) > TARGET_BULK_BYTES
125
+ bulk_responses << bulk_send(body_stream) unless body_stream.size == 0
126
+ end
127
+ stream_writer.write(as_json)
128
+ end
129
+ stream_writer.close if http_compression
130
+ bulk_responses << bulk_send(body_stream) if body_stream.size > 0
131
+ body_stream.close if !http_compression
132
+ join_bulk_responses(bulk_responses)
133
+ end
134
+
135
+ def join_bulk_responses(bulk_responses)
136
+ {
137
+ "errors" => bulk_responses.any? {|r| r["errors"] == true},
138
+ "items" => bulk_responses.reduce([]) {|m,r| m.concat(r.fetch("items", []))}
139
+ }
140
+ end
141
+
142
+ def bulk_send(body_stream)
143
+ params = http_compression ? {:headers => {"Content-Encoding" => "gzip"}} : {}
144
+ # Discard the URL
145
+ response = @pool.post(@bulk_path, params, body_stream.string)
146
+ if !body_stream.closed?
147
+ body_stream.truncate(0)
148
+ body_stream.seek(0)
149
+ end
150
+
151
+ @bulk_response_metrics.increment(response.code.to_s)
152
+
153
+ if response.code != 200
154
+ url = ::LogStash::Util::SafeURI.new(response.final_url)
155
+ raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
156
+ response.code, url, body_stream.to_s, response.body
157
+ )
158
+ end
52
159
 
53
- bulk_response = @client.bulk(:body => bulk_body)
160
+ LogStash::Json.load(response.body)
161
+ end
162
+
163
+ def get(path)
164
+ response = @pool.get(path, nil)
165
+ LogStash::Json.load(response.body)
166
+ end
167
+
168
+ def post(path, params = {}, body_string)
169
+ response = @pool.post(path, params, body_string)
170
+ LogStash::Json.load(response.body)
171
+ end
172
+
173
+ def close
174
+ @pool.close
175
+ end
176
+
177
+ def calculate_property(uris, property, default, sniff_check)
178
+ values = uris.map(&property).uniq
179
+
180
+ if sniff_check && values.size > 1
181
+ raise LogStash::ConfigurationError, "Cannot have multiple values for #{property} in hosts when sniffing is enabled!"
182
+ end
183
+
184
+ uri_value = values.first
185
+
186
+ default = nil if default.is_a?(String) && default.empty? # Blanks are as good as nil
187
+ uri_value = nil if uri_value.is_a?(String) && uri_value.empty?
188
+
189
+
190
+ if default && uri_value && (default != uri_value)
191
+ raise LogStash::ConfigurationError, "Explicit value for '#{property}' was declared, but it is different in one of the URLs given! Please make sure your URLs are inline with explicit values. The URLs have the property set to '#{uri_value}', but it was also set to '#{default}' explicitly"
192
+ end
193
+
194
+ uri_value || default
195
+ end
196
+
197
+ def sniffing
198
+ @options[:sniffing]
199
+ end
200
+
201
+ def user
202
+ calculate_property(uris, :user, @options[:user], sniffing)
203
+ end
204
+
205
+ def password
206
+ calculate_property(uris, :password, @options[:password], sniffing)
207
+ end
54
208
 
55
- self.class.normalize_bulk_response(bulk_response)
209
+ def path
210
+ calculated = calculate_property(uris, :path, client_settings[:path], sniffing)
211
+ calculated = "/#{calculated}" if calculated && !calculated.start_with?("/")
212
+ calculated
56
213
  end
57
214
 
58
- private
59
- def build_client(options)
60
- hosts = options[:hosts]
61
- port = options[:port]
62
- protocol = options[:protocol]
63
- client_settings = options[:client_settings] || {}
215
+ def scheme
216
+ explicit_scheme = if ssl_options && ssl_options.has_key?(:enabled)
217
+ ssl_options[:enabled] ? 'https' : 'http'
218
+ else
219
+ nil
220
+ end
221
+
222
+ calculated_scheme = calculate_property(uris, :scheme, explicit_scheme, sniffing)
64
223
 
65
- uris = hosts.map do |host|
66
- "#{protocol}://#{host}:#{port}#{client_settings[:path]}".gsub(/[\/]+$/,'')
224
+ if calculated_scheme && calculated_scheme !~ /https?/
225
+ raise LogStash::ConfigurationError, "Bad scheme '#{calculated_scheme}' found should be one of http/https"
67
226
  end
68
227
 
69
- @client_options = {
70
- :hosts => uris,
71
- :region => options[:region],
72
- :transport_options => {
73
- :request => {:open_timeout => 0, :timeout => 60}, # ELB timeouts are set at 60
74
- :proxy => client_settings[:proxy],
75
- },
76
- :transport_class => Elasticsearch::Transport::Transport::HTTP::AWS
228
+ if calculated_scheme && explicit_scheme && calculated_scheme != explicit_scheme
229
+ raise LogStash::ConfigurationError, "SSL option was explicitly set to #{ssl_options[:enabled]} but a URL was also declared with a scheme of '#{explicit_scheme}'. Please reconcile this"
230
+ end
231
+
232
+ calculated_scheme # May be nil if explicit_scheme is nil!
233
+ end
234
+
235
+ def port
236
+ # We don't set the 'default' here because the default is what the user
237
+ # indicated, so we use an || outside of calculate_property. This lets people
238
+ # Enter things like foo:123, bar and wind up with foo:123, bar:80
239
+ #calculate_property(uris, :port, nil, sniffing) || 9200
240
+ calculate_property(uris, :port, @options[:port], sniffing) || 9200
241
+ end
242
+
243
+ def uris
244
+ @options[:hosts]
245
+ end
246
+
247
+ def client_settings
248
+ @options[:client_settings] || {}
249
+ end
250
+
251
+ def ssl_options
252
+ client_settings.fetch(:ssl, {})
253
+ end
254
+
255
+ def http_compression
256
+ client_settings.fetch(:http_compression, false)
257
+ end
258
+
259
+ def build_adapter(options)
260
+ timeout = options[:timeout] || 0
261
+
262
+ adapter_options = {
263
+ :socket_timeout => timeout,
264
+ :request_timeout => timeout,
77
265
  }
78
- internal_options = @client_options.clone
79
- internal_options[:aws_access_key_id] = options[:aws_access_key_id]
80
- internal_options[:aws_secret_access_key] = options[:aws_secret_access_key]
81
266
 
82
- if options[:user] && options[:password] then
83
- token = Base64.strict_encode64(options[:user] + ":" + options[:password])
84
- internal_options[:headers] = { "Authorization" => "Basic #{token}" }
267
+ adapter_options[:proxy] = client_settings[:proxy] if client_settings[:proxy]
268
+
269
+ adapter_options[:check_connection_timeout] = client_settings[:check_connection_timeout] if client_settings[:check_connection_timeout]
270
+
271
+ # Having this explicitly set to nil is an error
272
+ if client_settings[:pool_max]
273
+ adapter_options[:pool_max] = client_settings[:pool_max]
85
274
  end
86
275
 
87
- Elasticsearch::Client.new(internal_options)
88
- end
89
-
90
- def self.normalize_bulk_response(bulk_response)
91
- if bulk_response["errors"]
92
- # The structure of the response from the REST Bulk API is follows:
93
- # {"took"=>74, "errors"=>true, "items"=>[{"create"=>{"_index"=>"logstash-2014.11.17",
94
- # "_type"=>"logs",
95
- # "_id"=>"AUxTS2C55Jrgi-hC6rQF",
96
- # "_version"=>1,
97
- # "status"=>400,
98
- # "error"=>"MapperParsingException[failed to parse]..."}}]}
99
- # where each `item` is a hash of {OPTYPE => Hash[]}. calling first, will retrieve
100
- # this hash as a single array with two elements, where the value is the second element (i.first[1])
101
- # then the status of that item is retrieved.
102
- {"errors" => true, "statuses" => bulk_response["items"].map { |i| i.first[1]['status'] }}
103
- else
104
- {"errors" => false}
276
+ # Having this explicitly set to nil is an error
277
+ if client_settings[:pool_max_per_route]
278
+ adapter_options[:pool_max_per_route] = client_settings[:pool_max_per_route]
105
279
  end
280
+
281
+ adapter_options[:ssl] = ssl_options if self.scheme == 'https'
282
+
283
+ adapter_options[:headers] = client_settings[:headers] if client_settings[:headers]
284
+
285
+ adapter_options[:region] = options[:region]
286
+
287
+ adapter_options[:port] = options[:port]
288
+
289
+ adapter_options[:protocol] = options[:protocol]
290
+
291
+ adapter_options[:aws_access_key_id] = options[:aws_access_key_id]
292
+
293
+ adapter_options[:aws_secret_access_key] = options[:aws_secret_access_key]
294
+
295
+ adapter_class = ::LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter
296
+ adapter = adapter_class.new(@logger, adapter_options)
297
+ end
298
+
299
+ def build_pool(options)
300
+ adapter = build_adapter(options)
301
+
302
+ pool_options = {
303
+ :sniffing => sniffing,
304
+ :sniffer_delay => options[:sniffer_delay],
305
+ :sniffing_path => options[:sniffing_path],
306
+ :healthcheck_path => options[:healthcheck_path],
307
+ :resurrect_delay => options[:resurrect_delay],
308
+ :url_normalizer => self.method(:host_to_url),
309
+ :metric => options[:metric]
310
+ }
311
+ pool_options[:scheme] = self.scheme if self.scheme
312
+
313
+ pool_class = ::LogStash::Outputs::ElasticSearch::HttpClient::Pool
314
+ full_urls = @options[:hosts].map {|h| host_to_url(h) }
315
+ pool = pool_class.new(@logger, adapter, full_urls, pool_options)
316
+ pool.start
317
+ pool
318
+ end
319
+
320
+ def host_to_url(h)
321
+
322
+ # Never override the calculated scheme
323
+ #raw_scheme = @url_template[:scheme] || 'http'
324
+
325
+ raw_scheme = @options[:protocol] || 'https'
326
+
327
+ raw_user = h.user || @url_template[:user]
328
+ raw_password = h.password || @url_template[:password]
329
+ postfixed_userinfo = raw_user && raw_password ? "#{raw_user}:#{raw_password}@" : nil
330
+
331
+ raw_host = h.host # Always replace this!
332
+ raw_port = h.port || @url_template[:port]
333
+
334
+ raw_path = !h.path.nil? && !h.path.empty? && h.path != "/" ? h.path : @url_template[:path]
335
+ prefixed_raw_path = raw_path && !raw_path.empty? ? raw_path : "/"
336
+
337
+ parameters = client_settings[:parameters]
338
+ raw_query = if parameters && !parameters.empty?
339
+ combined = h.query ?
340
+ Hash[URI::decode_www_form(h.query)].merge(parameters) :
341
+ parameters
342
+ query_str = combined.flat_map {|k,v|
343
+ values = Array(v)
344
+ values.map {|av| "#{k}=#{av}"}
345
+ }.join("&")
346
+ query_str
347
+ else
348
+ h.query
349
+ end
350
+ prefixed_raw_query = raw_query && !raw_query.empty? ? "?#{raw_query}" : nil
351
+
352
+ raw_url = "#{raw_scheme}://#{postfixed_userinfo}#{raw_host}:#{raw_port}#{prefixed_raw_path}#{prefixed_raw_query}"
353
+
354
+ ::LogStash::Util::SafeURI.new(raw_url)
106
355
  end
107
356
 
108
357
  def template_exists?(name)
109
- @client.indices.get_template(:name => name)
110
- return true
111
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
112
- return false
358
+ response = @pool.head("/_template/#{name}")
359
+ response.code >= 200 && response.code <= 299
113
360
  end
114
361
 
115
362
  def template_put(name, template)
116
- @client.indices.put_template(:name => name, :body => template)
363
+ path = "_template/#{name}"
364
+ logger.info("Installing amazon_es template to #{path}")
365
+ @pool.put(path, nil, LogStash::Json.dump(template))
366
+ end
367
+
368
+ # Build a bulk item for an amazon_es update action
369
+ def update_action_builder(args, source)
370
+ if args[:_script]
371
+ # Use the event as a hash from your script with variable name defined
372
+ # by script_var_name (default: "event")
373
+ # Ex: event["@timestamp"]
374
+ source_orig = source
375
+ source = { 'script' => {'params' => { @options[:script_var_name] => source_orig }} }
376
+ if @options[:scripted_upsert]
377
+ source['scripted_upsert'] = true
378
+ source['upsert'] = {}
379
+ elsif @options[:doc_as_upsert]
380
+ source['upsert'] = source_orig
381
+ else
382
+ source['upsert'] = args.delete(:_upsert) if args[:_upsert]
383
+ end
384
+ case @options[:script_type]
385
+ when 'indexed'
386
+ source['script']['id'] = args.delete(:_script)
387
+ when 'file'
388
+ source['script']['file'] = args.delete(:_script)
389
+ when 'inline'
390
+ source['script']['inline'] = args.delete(:_script)
391
+ end
392
+ source['script']['lang'] = @options[:script_lang] if @options[:script_lang] != ''
393
+ else
394
+ source = { 'doc' => source }
395
+ if @options[:doc_as_upsert]
396
+ source['doc_as_upsert'] = true
397
+ else
398
+ source['upsert'] = args.delete(:_upsert) if args[:_upsert]
399
+ end
400
+ end
401
+ [args, source]
117
402
  end
118
403
  end
119
- end
404
+ end end end