logstash-input-elasticsearch 4.6.1 → 4.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3fee8b4ee34b945816ffa2fe5588fcc6107b5c995aadb311a903863898fdfe2
4
- data.tar.gz: '09644097ce9d8707ca6c629f72a439692b29bf23a0d536135d5fb8c4c9ee057e'
3
+ metadata.gz: f2739ba5eed141d75ad674402bb35485e0be50c6a7593556dbcda575120bbb54
4
+ data.tar.gz: 1affc8bfa4aad83cda6c59f0a6b3dcf7ca24a5d7e0e53cacc66f45b88d353ce1
5
5
  SHA512:
6
- metadata.gz: c9dae7fc0d75b73a9624be4bd356cbd7019858e32f67542ec61d6114792756655f39acc49b57054a593d0cab32f28a2ed42dbe94856ca1d31d6413e91f68bf2c
7
- data.tar.gz: e25e5fcaf0a3d8871bb3ddea91e1137ec88c380cd790b0e09912be2423f416a89650a23cf5921e816cb92cbfd22a8d57c7476f5f5f800aff420ef745dd55a161
6
+ metadata.gz: d4d2eb3be415ddf52ed7b17d06999fe93022004fcf218d11919c750e3b98f8a1ddc943d7d0911232a43fcc8f237a08ffcbd63ba5ec1dd59de9498061f4b4bf25
7
+ data.tar.gz: 74524dcd7cacd49a324049f80b5cabf5bd2a96c8c634ed15877139371cc8cb89bb38c074a576a0214a3785e218ee5dc0f0966b59de75394e679d8eef34ad14ed
@@ -1,3 +1,19 @@
1
+ ## 4.8.1
2
+ - Fixed connection error when using multiple `slices`. [#133](https://github.com/logstash-plugins/logstash-input-elasticsearch/issues/133)
3
+
4
+ ## 4.8.0
5
+ - Added the ability to configure connection-, request-, and socket-timeouts with `connect_timeout_seconds`, `request_timeout_seconds`, and `socket_timeout_seconds` [#121](https://github.com/logstash-plugins/logstash-input-elasticsearch/issues/121)
6
+
7
+ ## 4.7.1
8
+ - [DOC] Updated sliced scroll link to resolve to correct location after doc structure change [#135](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/135)
9
+ - [DOC] Added usage example of docinfo metadata [#98](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/98)
10
+
11
+ ## 4.7.0
12
+ - Added api_key support [#131](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/131)
13
+
14
+ ## 4.6.2
15
+ - Added scroll clearing and better handling of scroll expiration [#128](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/128)
16
+
1
17
  ## 4.6.1
2
18
  - [DOC] Removed outdated compatibility notice [#124](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/124)
3
19
 
@@ -68,6 +68,16 @@ Further documentation describing this syntax can be found
68
68
  https://github.com/jmettraux/rufus-scheduler#parsing-cronlines-and-time-strings[here].
69
69
 
70
70
 
71
+ [id="plugins-{type}s-{plugin}-auth"]
72
+ ==== Authentication
73
+
74
+ Authentication to a secure Elasticsearch cluster is possible using _one_ of the following options:
75
+
76
+ * <<plugins-{type}s-{plugin}-user>> AND <<plugins-{type}s-{plugin}-password>>
77
+ * <<plugins-{type}s-{plugin}-cloud_auth>>
78
+ * <<plugins-{type}s-{plugin}-api_key>>
79
+
80
+
71
81
  [id="plugins-{type}s-{plugin}-options"]
72
82
  ==== Elasticsearch Input Configuration Options
73
83
 
@@ -76,9 +86,11 @@ This plugin supports the following configuration options plus the <<plugins-{typ
76
86
  [cols="<,<,<",options="header",]
77
87
  |=======================================================================
78
88
  |Setting |Input type|Required
89
+ | <<plugins-{type}s-{plugin}-api_key>> |<<password,password>>|No
79
90
  | <<plugins-{type}s-{plugin}-ca_file>> |a valid filesystem path|No
80
91
  | <<plugins-{type}s-{plugin}-cloud_auth>> |<<password,password>>|No
81
92
  | <<plugins-{type}s-{plugin}-cloud_id>> |<<string,string>>|No
93
+ | <<plugins-{type}s-{plugin}-connect_timeout_seconds>> | <<number,number>>|No
82
94
  | <<plugins-{type}s-{plugin}-docinfo>> |<<boolean,boolean>>|No
83
95
  | <<plugins-{type}s-{plugin}-docinfo_fields>> |<<array,array>>|No
84
96
  | <<plugins-{type}s-{plugin}-docinfo_target>> |<<string,string>>|No
@@ -87,11 +99,13 @@ This plugin supports the following configuration options plus the <<plugins-{typ
87
99
  | <<plugins-{type}s-{plugin}-password>> |<<password,password>>|No
88
100
  | <<plugins-{type}s-{plugin}-proxy>> |<<uri,uri>>|No
89
101
  | <<plugins-{type}s-{plugin}-query>> |<<string,string>>|No
102
+ | <<plugins-{type}s-{plugin}-request_timeout_seconds>> | <<number,number>>|No
90
103
  | <<plugins-{type}s-{plugin}-schedule>> |<<string,string>>|No
91
104
  | <<plugins-{type}s-{plugin}-scroll>> |<<string,string>>|No
92
105
  | <<plugins-{type}s-{plugin}-size>> |<<number,number>>|No
93
106
  | <<plugins-{type}s-{plugin}-slices>> |<<number,number>>|No
94
107
  | <<plugins-{type}s-{plugin}-ssl>> |<<boolean,boolean>>|No
108
+ | <<plugins-{type}s-{plugin}-socket_timeout_seconds>> | <<number,number>>|No
95
109
  | <<plugins-{type}s-{plugin}-user>> |<<string,string>>|No
96
110
  |=======================================================================
97
111
 
@@ -100,6 +114,16 @@ input plugins.
100
114
 
101
115
  &nbsp;
102
116
 
117
+ [id="plugins-{type}s-{plugin}-api_key"]
118
+ ===== `api_key`
119
+
120
+ * Value type is <<password,password>>
121
+ * There is no default value for this setting.
122
+
123
+ Authenticate using Elasticsearch API key. Note that this option also requires enabling the `ssl` option.
124
+
125
+ Format is `id:api_key` where `id` and `api_key` are as returned by the Elasticsearch https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html[Create API key API].
126
+
103
127
  [id="plugins-{type}s-{plugin}-ca_file"]
104
128
  ===== `ca_file`
105
129
 
@@ -128,6 +152,15 @@ Cloud ID, from the Elastic Cloud web console. If set `hosts` should not be used.
128
152
 
129
153
  For more info, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_id[Logstash-to-Cloud documentation]
130
154
 
155
+ [id="plugins-{type}s-{plugin}-connect_timeout_seconds"]
156
+ ===== `connect_timeout_seconds`
157
+
158
+ * Value type is <<number,number>>
159
+ * Default value is `10`
160
+
161
+ The maximum amount of time, in seconds, to wait while establishing a connection to Elasticsearch.
162
+ Connect timeouts tend to occur when Elasticsearch or an intermediate proxy is overloaded with requests and has exhausted its connection pool.
163
+
131
164
  [id="plugins-{type}s-{plugin}-docinfo"]
132
165
  ===== `docinfo`
133
166
 
@@ -163,6 +196,19 @@ Example
163
196
  }
164
197
  }
165
198
 
199
+ If set, you can use metadata information in the <<plugins-{type}s-{plugin}-add_field>> common option.
200
+
201
+ Example
202
+ [source, ruby]
203
+ input {
204
+ elasticsearch {
205
+ docinfo => true
206
+ add_field => {
207
+ identifier => %{[@metadata][_index]}:%{[@metadata][_type]}:%{[@metadata][_id]}"
208
+ }
209
+ }
210
+ }
211
+
166
212
 
167
213
  [id="plugins-{type}s-{plugin}-docinfo_fields"]
168
214
  ===== `docinfo_fields`
@@ -237,6 +283,16 @@ The query to be executed. Read the
237
283
  https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html[Elasticsearch query DSL documentation]
238
284
  for more information.
239
285
 
286
+ [id="plugins-{type}s-{plugin}-request_timeout_seconds"]
287
+ ===== `request_timeout_seconds`
288
+
289
+ * Value type is <<number,number>>
290
+ * Default value is `60`
291
+
292
+ The maximum amount of time, in seconds, for a single request to Elasticsearch.
293
+ Request timeouts tend to occur when an individual page of data is very large, such as when it contains large-payload
294
+ documents and/or the <<plugins-{type}s-{plugin}-size>> has been specified as a large value.
295
+
240
296
  [id="plugins-{type}s-{plugin}-schedule"]
241
297
  ===== `schedule`
242
298
 
@@ -275,8 +331,8 @@ This allows you to set the maximum number of hits returned per scroll.
275
331
  * Sensible values range from 2 to about 8.
276
332
 
277
333
  In some cases, it is possible to improve overall throughput by consuming multiple
278
- distinct slices of a query simultaneously using the
279
- https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#sliced-scroll[Sliced Scroll API],
334
+ distinct slices of a query simultaneously using
335
+ https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#slice-scroll[sliced scrolls],
280
336
  especially if the pipeline is spending significant time waiting on Elasticsearch
281
337
  to provide results.
282
338
 
@@ -300,6 +356,15 @@ instructions into the query.
300
356
  If enabled, SSL will be used when communicating with the Elasticsearch
301
357
  server (i.e. HTTPS will be used instead of plain HTTP).
302
358
 
359
+ [id="plugins-{type}s-{plugin}-socket_timeout_seconds"]
360
+ ===== `socket_timeout_seconds`
361
+
362
+ * Value type is <<number,number>>
363
+ * Default value is `60`
364
+
365
+ The maximum amount of time, in seconds, to wait on an incomplete response from Elasticsearch while no additional data has been appended.
366
+ Socket timeouts usually occur while waiting for the first byte of a response, such as when executing a particularly complex query.
367
+
303
368
  [id="plugins-{type}s-{plugin}-user"]
304
369
  ===== `user`
305
370
 
@@ -315,4 +380,4 @@ empty string authentication will be disabled.
315
380
  [id="plugins-{type}s-{plugin}-common-options"]
316
381
  include::{include_path}/{type}.asciidoc[]
317
382
 
318
- :default_codec!:
383
+ :default_codec!:
@@ -4,6 +4,7 @@ require "logstash/namespace"
4
4
  require "logstash/json"
5
5
  require "logstash/util/safe_uri"
6
6
  require "base64"
7
+ require_relative "patch"
7
8
 
8
9
 
9
10
  # .Compatibility Note
@@ -70,11 +71,6 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
70
71
  # Port defaults to 9200
71
72
  config :hosts, :validate => :array
72
73
 
73
- # Cloud ID, from the Elastic Cloud web console. If set `hosts` should not be used.
74
- #
75
- # For more info, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_id[Logstash-to-Cloud documentation]
76
- config :cloud_id, :validate => :string
77
-
78
74
  # The index or alias to search.
79
75
  config :index, :validate => :string, :default => "logstash-*"
80
76
 
@@ -140,11 +136,29 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
140
136
  # Basic Auth - password
141
137
  config :password, :validate => :password
142
138
 
139
+ # Connection Timeout, in Seconds
140
+ config :connect_timeout_seconds, :validate => :positive_whole_number, :default => 10
141
+
142
+ # Request Timeout, in Seconds
143
+ config :request_timeout_seconds, :validate => :positive_whole_number, :default => 60
144
+
145
+ # Socket Timeout, in Seconds
146
+ config :socket_timeout_seconds, :validate => :positive_whole_number, :default => 60
147
+
148
+ # Cloud ID, from the Elastic Cloud web console. If set `hosts` should not be used.
149
+ #
150
+ # For more info, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_id[Logstash-to-Cloud documentation]
151
+ config :cloud_id, :validate => :string
152
+
143
153
  # Cloud authentication string ("<username>:<password>" format) is an alternative for the `user`/`password` configuration.
144
154
  #
145
155
  # For more info, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_auth[Logstash-to-Cloud documentation]
146
156
  config :cloud_auth, :validate => :password
147
157
 
158
+ # Authenticate using Elasticsearch API key.
159
+ # format is id:api_key (as returned by https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html[Create API key])
160
+ config :api_key, :validate => :password
161
+
148
162
  # Set the address of a forward HTTP proxy.
149
163
  config :proxy, :validate => :uri_or_empty
150
164
 
@@ -177,54 +191,34 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
177
191
  @slices < 1 && fail(LogStash::ConfigurationError, "Elasticsearch Input Plugin's `slices` option must be greater than zero, got `#{@slices}`")
178
192
  end
179
193
 
180
- transport_options = {}
181
-
194
+ validate_authentication
182
195
  fill_user_password_from_cloud_auth
196
+ fill_hosts_from_cloud_id
183
197
 
184
- if @user && @password
185
- token = Base64.strict_encode64("#{@user}:#{@password.value}")
186
- transport_options[:headers] = { :Authorization => "Basic #{token}" }
187
- end
188
198
 
189
- fill_hosts_from_cloud_id
190
- @hosts = Array(@hosts).map { |host| host.to_s } # potential SafeURI#to_s
199
+ transport_options = {:headers => {}}
200
+ transport_options[:headers].merge!(setup_basic_auth(user, password))
201
+ transport_options[:headers].merge!(setup_api_key(api_key))
202
+ transport_options[:request_timeout] = @request_timeout_seconds unless @request_timeout_seconds.nil?
203
+ transport_options[:connect_timeout] = @connect_timeout_seconds unless @connect_timeout_seconds.nil?
204
+ transport_options[:socket_timeout] = @socket_timeout_seconds unless @socket_timeout_seconds.nil?
191
205
 
192
- hosts = if @ssl
193
- @hosts.map do |h|
194
- host, port = h.split(":")
195
- { :host => host, :scheme => 'https', :port => port }
196
- end
197
- else
198
- @hosts
199
- end
200
- ssl_options = { :ssl => true, :ca_file => @ca_file } if @ssl && @ca_file
201
- ssl_options ||= {}
206
+ hosts = setup_hosts
207
+ ssl_options = setup_ssl
202
208
 
203
209
  @logger.warn "Supplied proxy setting (proxy => '') has no effect" if @proxy.eql?('')
204
210
 
205
211
  transport_options[:proxy] = @proxy.to_s if @proxy && !@proxy.eql?('')
206
212
 
207
- @client = Elasticsearch::Client.new(:hosts => hosts, :transport_options => transport_options,
208
- :transport_class => ::Elasticsearch::Transport::Transport::HTTP::Manticore,
209
- :ssl => ssl_options)
213
+ @client = Elasticsearch::Client.new(
214
+ :hosts => hosts,
215
+ :transport_options => transport_options,
216
+ :transport_class => ::Elasticsearch::Transport::Transport::HTTP::Manticore,
217
+ :ssl => ssl_options
218
+ )
210
219
  end
211
220
 
212
- ##
213
- # @override to handle proxy => '' as if none was set
214
- # @param value [Array<Object>]
215
- # @param validator [nil,Array,Symbol]
216
- # @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
217
- # @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
218
- def self.validate_value(value, validator)
219
- return super unless validator == :uri_or_empty
220
-
221
- value = deep_replace(value)
222
- value = hash_or_array(value)
223
221
 
224
- return true, value.first if value.size == 1 && value.first.empty?
225
-
226
- return super(value, :uri)
227
- end
228
222
 
229
223
  def run(output_queue)
230
224
  if @schedule
@@ -266,25 +260,41 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
266
260
  slice_options = @options.merge(:body => LogStash::Json.dump(slice_query) )
267
261
 
268
262
  logger.info("Slice starting", slice_id: slice_id, slices: @slices) unless slice_id.nil?
269
- r = search_request(slice_options)
270
263
 
271
- r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
272
- logger.debug("Slice progress", slice_id: slice_id, slices: @slices) unless slice_id.nil?
273
-
274
- has_hits = r['hits']['hits'].any?
264
+ scroll_id = nil
265
+ begin
266
+ r = search_request(slice_options)
275
267
 
276
- while has_hits && r['_scroll_id'] && !stop?
277
- r = process_next_scroll(output_queue, r['_scroll_id'])
268
+ r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
278
269
  logger.debug("Slice progress", slice_id: slice_id, slices: @slices) unless slice_id.nil?
279
- has_hits = r['has_hits']
270
+
271
+ has_hits = r['hits']['hits'].any?
272
+ scroll_id = r['_scroll_id']
273
+
274
+ while has_hits && scroll_id && !stop?
275
+ has_hits, scroll_id = process_next_scroll(output_queue, scroll_id)
276
+ logger.debug("Slice progress", slice_id: slice_id, slices: @slices) if logger.debug? && slice_id
277
+ end
278
+ logger.info("Slice complete", slice_id: slice_id, slices: @slices) unless slice_id.nil?
279
+ ensure
280
+ clear_scroll(scroll_id)
280
281
  end
281
- logger.info("Slice complete", slice_id: slice_id, slices: @slices) unless slice_id.nil?
282
282
  end
283
283
 
284
+ ##
285
+ # @param output_queue [#<<]
286
+ # @param scroll_id [String]: a scroll id to resume
287
+ # @return [Array(Boolean,String)]: a tuple representing whether the response
288
+ #
284
289
  def process_next_scroll(output_queue, scroll_id)
285
290
  r = scroll_request(scroll_id)
286
291
  r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
287
- {'has_hits' => r['hits']['hits'].any?, '_scroll_id' => r['_scroll_id']}
292
+ [r['hits']['hits'].any?, r['_scroll_id']]
293
+ rescue => e
294
+ # this will typically be triggered by a scroll timeout
295
+ logger.error("Scroll request error, aborting scroll", error: e.inspect)
296
+ # return no hits and original scroll_id so we can try to clear it
297
+ [false, scroll_id]
288
298
  end
289
299
 
290
300
  def push_hit(hit, output_queue)
@@ -313,39 +323,86 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
313
323
  output_queue << event
314
324
  end
315
325
 
326
+ def clear_scroll(scroll_id)
327
+ @client.clear_scroll(scroll_id: scroll_id) if scroll_id
328
+ rescue => e
329
+ # ignore & log any clear_scroll errors
330
+ logger.warn("Ignoring clear_scroll exception", message: e.message)
331
+ end
332
+
316
333
  def scroll_request scroll_id
317
- client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
334
+ @client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
318
335
  end
319
336
 
320
337
  def search_request(options)
321
- client.search(options)
338
+ @client.search(options)
322
339
  end
323
340
 
324
- attr_reader :client
325
-
326
341
  def hosts_default?(hosts)
327
342
  hosts.nil? || ( hosts.is_a?(Array) && hosts.empty? )
328
343
  end
329
344
 
330
- def fill_hosts_from_cloud_id
331
- return unless @cloud_id
345
+ def validate_authentication
346
+ authn_options = 0
347
+ authn_options += 1 if @cloud_auth
348
+ authn_options += 1 if (@api_key && @api_key.value)
349
+ authn_options += 1 if (@user || (@password && @password.value))
332
350
 
333
- if @hosts && !hosts_default?(@hosts)
334
- raise LogStash::ConfigurationError, 'Both cloud_id and hosts specified, please only use one of those.'
351
+ if authn_options > 1
352
+ raise LogStash::ConfigurationError, 'Multiple authentication options are specified, please only use one of user/password, cloud_auth or api_key'
335
353
  end
336
- @hosts = parse_host_uri_from_cloud_id(@cloud_id)
354
+
355
+ if @api_key && @api_key.value && @ssl != true
356
+ raise(LogStash::ConfigurationError, "Using api_key authentication requires SSL/TLS secured communication using the `ssl => true` option")
357
+ end
358
+ end
359
+
360
+ def setup_ssl
361
+ @ssl && @ca_file ? { :ssl => true, :ca_file => @ca_file } : {}
362
+ end
363
+
364
+ def setup_hosts
365
+ @hosts = Array(@hosts).map { |host| host.to_s } # potential SafeURI#to_s
366
+ if @ssl
367
+ @hosts.map do |h|
368
+ host, port = h.split(":")
369
+ { :host => host, :scheme => 'https', :port => port }
370
+ end
371
+ else
372
+ @hosts
373
+ end
374
+ end
375
+
376
+ def setup_basic_auth(user, password)
377
+ return {} unless user && password && password.value
378
+
379
+ token = ::Base64.strict_encode64("#{user}:#{password.value}")
380
+ { Authorization: "Basic #{token}" }
381
+ end
382
+
383
+ def setup_api_key(api_key)
384
+ return {} unless (api_key && api_key.value)
385
+
386
+ token = ::Base64.strict_encode64(api_key.value)
387
+ { Authorization: "ApiKey #{token}" }
337
388
  end
338
389
 
339
390
  def fill_user_password_from_cloud_auth
340
391
  return unless @cloud_auth
341
392
 
342
- if @user || @password
343
- raise LogStash::ConfigurationError, 'Both cloud_auth and user/password specified, please only use one.'
344
- end
345
393
  @user, @password = parse_user_password_from_cloud_auth(@cloud_auth)
346
394
  params['user'], params['password'] = @user, @password
347
395
  end
348
396
 
397
+ def fill_hosts_from_cloud_id
398
+ return unless @cloud_id
399
+
400
+ if @hosts && !hosts_default?(@hosts)
401
+ raise LogStash::ConfigurationError, 'Both cloud_id and hosts specified, please only use one of those.'
402
+ end
403
+ @hosts = parse_host_uri_from_cloud_id(@cloud_id)
404
+ end
405
+
349
406
  def parse_host_uri_from_cloud_id(cloud_id)
350
407
  begin # might not be available on older LS
351
408
  require 'logstash/util/cloud_setting_id'
@@ -380,4 +437,42 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
380
437
  [ cloud_auth.username, cloud_auth.password ]
381
438
  end
382
439
 
440
+ module URIOrEmptyValidator
441
+ ##
442
+ # @override to provide :uri_or_empty validator
443
+ # @param value [Array<Object>]
444
+ # @param validator [nil,Array,Symbol]
445
+ # @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
446
+ # @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
447
+ def validate_value(value, validator)
448
+ return super unless validator == :uri_or_empty
449
+
450
+ value = deep_replace(value)
451
+ value = hash_or_array(value)
452
+
453
+ return true, value.first if value.size == 1 && value.first.empty?
454
+
455
+ return super(value, :uri)
456
+ end
457
+ end
458
+ extend(URIOrEmptyValidator)
459
+
460
+ module PositiveWholeNumberValidator
461
+ ##
462
+ # @override to provide :positive_whole_number validator
463
+ # @param value [Array<Object>]
464
+ # @param validator [nil,Array,Symbol]
465
+ # @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
466
+ # @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
467
+ def validate_value(value, validator)
468
+ return super unless validator == :positive_whole_number
469
+
470
+ is_number, coerced_number = super(value, :number)
471
+
472
+ return [true, coerced_number.to_i] if is_number && coerced_number.denominator == 1 && coerced_number > 0
473
+
474
+ return [false, "Expected positive whole number, got `#{value.inspect}`"]
475
+ end
476
+ end
477
+ extend(PositiveWholeNumberValidator)
383
478
  end
@@ -0,0 +1,48 @@
1
+ if Gem.loaded_specs['elasticsearch-transport'].version >= Gem::Version.new("7.2.0")
2
+ # elasticsearch-transport versions prior to 7.2.0 suffered of a race condition on accessing
3
+ # the connection pool. This issue was fixed with https://github.com/elastic/elasticsearch-ruby/commit/15f9d78591a6e8823948494d94b15b0ca38819d1
4
+ # This plugin, at the moment, is forced to use v5.x so we have to monkey patch the gem. When this requirement
5
+ # ceases, this patch could be removed.
6
+ puts "WARN remove the patch code into logstash-input-elasticsearch plugin"
7
+ else
8
+ module Elasticsearch
9
+ module Transport
10
+ module Transport
11
+ module Connections
12
+ module Selector
13
+
14
+ # "Round-robin" selector strategy (default).
15
+ #
16
+ class RoundRobin
17
+ include Base
18
+
19
+ # @option arguments [Connections::Collection] :connections Collection with connections.
20
+ #
21
+ def initialize(arguments = {})
22
+ super
23
+ @mutex = Mutex.new
24
+ @current = nil
25
+ end
26
+
27
+ # Returns the next connection from the collection, rotating them in round-robin fashion.
28
+ #
29
+ # @return [Connections::Connection]
30
+ #
31
+ def select(options={})
32
+ @mutex.synchronize do
33
+ conns = connections
34
+ if @current && (@current < conns.size-1)
35
+ @current += 1
36
+ else
37
+ @current = 0
38
+ end
39
+ conns[@current]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-input-elasticsearch'
4
- s.version = '4.6.1'
4
+ s.version = '4.8.1'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Reads query results from an Elasticsearch cluster"
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -8,9 +8,13 @@ require "stud/temporary"
8
8
  require "time"
9
9
  require "date"
10
10
 
11
- describe LogStash::Inputs::Elasticsearch do
11
+ class LogStash::Inputs::TestableElasticsearch < LogStash::Inputs::Elasticsearch
12
+ attr_reader :client
13
+ end
14
+
15
+ describe LogStash::Inputs::TestableElasticsearch do
12
16
 
13
- let(:plugin) { LogStash::Inputs::Elasticsearch.new(config) }
17
+ let(:plugin) { LogStash::Inputs::TestableElasticsearch.new(config) }
14
18
  let(:queue) { Queue.new }
15
19
 
16
20
  it_behaves_like "an interruptible input plugin" do
@@ -32,6 +36,7 @@ describe LogStash::Inputs::Elasticsearch do
32
36
  }
33
37
  allow(esclient).to receive(:search) { { "hits" => { "hits" => [hit] } } }
34
38
  allow(esclient).to receive(:scroll) { { "hits" => { "hits" => [hit] } } }
39
+ allow(esclient).to receive(:clear_scroll).and_return(nil)
35
40
  end
36
41
  end
37
42
 
@@ -76,6 +81,7 @@ describe LogStash::Inputs::Elasticsearch do
76
81
  expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
77
82
  expect(client).to receive(:search).with(any_args).and_return(response)
78
83
  expect(client).to receive(:scroll).with({ :body => { :scroll_id => "cXVlcnlUaGVuRmV0Y2g" }, :scroll=> "1m" }).and_return(scroll_reponse)
84
+ expect(client).to receive(:clear_scroll).and_return(nil)
79
85
 
80
86
  event = input(config) do |pipeline, queue|
81
87
  queue.pop
@@ -257,6 +263,8 @@ describe LogStash::Inputs::Elasticsearch do
257
263
  expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
258
264
  plugin.register
259
265
 
266
+ expect(client).to receive(:clear_scroll).and_return(nil)
267
+
260
268
  # SLICE0 is a three-page scroll in which the last page is empty
261
269
  slice0_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 0, 'max' => 2}))
262
270
  expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
@@ -360,6 +368,7 @@ describe LogStash::Inputs::Elasticsearch do
360
368
  expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
361
369
  expect(client).to receive(:search).with(any_args).and_return(response)
362
370
  allow(client).to receive(:scroll).with({ :body => {:scroll_id => "cXVlcnlUaGVuRmV0Y2g"}, :scroll => "1m" }).and_return(scroll_reponse)
371
+ allow(client).to receive(:clear_scroll).and_return(nil)
363
372
  end
364
373
 
365
374
  context 'when defining docinfo' do
@@ -405,6 +414,7 @@ describe LogStash::Inputs::Elasticsearch do
405
414
  "docinfo_target" => 'metadata_with_string'
406
415
  } }
407
416
  it 'thows an exception if the `docinfo_target` exist but is not of type hash' do
417
+ expect(client).not_to receive(:clear_scroll)
408
418
  plugin.register
409
419
  expect { plugin.run([]) }.to raise_error(Exception, /incompatible event/)
410
420
  end
@@ -573,7 +583,37 @@ describe LogStash::Inputs::Elasticsearch do
573
583
  let(:config) { super.merge({ 'cloud_auth' => 'elastic:my-passwd-00', 'user' => 'another' }) }
574
584
 
575
585
  it "should fail" do
576
- expect { plugin.register }.to raise_error LogStash::ConfigurationError, /cloud_auth and user/
586
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Multiple authentication options are specified/
587
+ end
588
+ end
589
+ end if LOGSTASH_VERSION > '6.0'
590
+
591
+ describe "api_key" do
592
+ context "without ssl" do
593
+ let(:config) { super.merge({ 'api_key' => LogStash::Util::Password.new('foo:bar') }) }
594
+
595
+ it "should fail" do
596
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, /api_key authentication requires SSL\/TLS/
597
+ end
598
+ end
599
+
600
+ context "with ssl" do
601
+ let(:config) { super.merge({ 'api_key' => LogStash::Util::Password.new('foo:bar'), "ssl" => true }) }
602
+
603
+ it "should set authorization" do
604
+ plugin.register
605
+ client = plugin.send(:client)
606
+ auth_header = client.transport.options[:transport_options][:headers][:Authorization]
607
+
608
+ expect( auth_header ).to eql "ApiKey #{Base64.strict_encode64('foo:bar')}"
609
+ end
610
+
611
+ context 'user also set' do
612
+ let(:config) { super.merge({ 'api_key' => 'foo:bar', 'user' => 'another' }) }
613
+
614
+ it "should fail" do
615
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Multiple authentication options are specified/
616
+ end
577
617
  end
578
618
  end
579
619
  end if LOGSTASH_VERSION > '6.0'
@@ -600,6 +640,54 @@ describe LogStash::Inputs::Elasticsearch do
600
640
  end
601
641
  end
602
642
  end
643
+
644
+ shared_examples'configurable timeout' do |config_name, manticore_transport_option|
645
+ let(:config_value) { fail NotImplementedError }
646
+ let(:config) { super().merge(config_name => config_value) }
647
+ {
648
+ :string => 'banana',
649
+ :negative => -123,
650
+ :zero => 0,
651
+ }.each do |value_desc, value|
652
+ let(:config_value) { value }
653
+ context "with an invalid #{value_desc} value" do
654
+ it 'prevents instantiation with a helpful message' do
655
+ expect(described_class.logger).to receive(:error).with(/Expected positive whole number/)
656
+ expect { described_class.new(config) }.to raise_error(LogStash::ConfigurationError)
657
+ end
658
+ end
659
+ end
660
+
661
+ context 'with a valid value' do
662
+ let(:config_value) { 17 }
663
+
664
+ it "instantiates the elasticsearch client with the timeout value set via #{manticore_transport_option} in the transport options" do
665
+ expect(Elasticsearch::Client).to receive(:new) do |new_elasticsearch_client_params|
666
+ # We rely on Manticore-specific transport options, fail early if we are using a different
667
+ # transport or are allowing the client to determine its own transport class.
668
+ expect(new_elasticsearch_client_params).to include(:transport_class)
669
+ expect(new_elasticsearch_client_params[:transport_class].name).to match(/\bManticore\b/)
670
+
671
+ expect(new_elasticsearch_client_params).to include(:transport_options)
672
+ transport_options = new_elasticsearch_client_params[:transport_options]
673
+ expect(transport_options).to include(manticore_transport_option)
674
+ expect(transport_options[manticore_transport_option]).to eq(config_value.to_i)
675
+ end
676
+
677
+ plugin.register
678
+ end
679
+ end
680
+ end
681
+
682
+ context 'connect_timeout_seconds' do
683
+ include_examples('configurable timeout', 'connect_timeout_seconds', :connect_timeout)
684
+ end
685
+ context 'request_timeout_seconds' do
686
+ include_examples('configurable timeout', 'request_timeout_seconds', :request_timeout)
687
+ end
688
+ context 'socket_timeout_seconds' do
689
+ include_examples('configurable timeout', 'socket_timeout_seconds', :socket_timeout)
690
+ end
603
691
  end
604
692
 
605
693
  context "when scheduling" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.1
4
+ version: 4.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-08 00:00:00.000000000 Z
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -200,6 +200,7 @@ files:
200
200
  - README.md
201
201
  - docs/index.asciidoc
202
202
  - lib/logstash/inputs/elasticsearch.rb
203
+ - lib/logstash/inputs/patch.rb
203
204
  - logstash-input-elasticsearch.gemspec
204
205
  - spec/es_helper.rb
205
206
  - spec/fixtures/test_certs/ca/ca.crt