elastomer-client 2.0.1 → 2.1.0

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
  SHA1:
3
- metadata.gz: ffa99916d4477ed77f1e67b038ddc8de6c70dcf4
4
- data.tar.gz: 5700c42af44a02684b86d29d9381cdcacd0aaf50
3
+ metadata.gz: b2f48910ab846142b9a55a868ce832dbc4a8c1ec
4
+ data.tar.gz: 3dd8c645093818313fd9f4fb5a974591bcd33516
5
5
  SHA512:
6
- metadata.gz: 6f73679c1b09cafca5499fb515795025528b194cc0794aac1ecdcac2d3a084fa69c7f08f89837de61ecaea1d75d48f9e668d5df15351b8eaa9c8c7246a412f29
7
- data.tar.gz: 169c324aac607fd8e3531a2c07aa8191df935390e603d8f7498fec3b093cc2a7a039df6e5280d9bf92018a2dc0e7723a97f02408bb9c90aad3abc88ee90989f9
6
+ metadata.gz: 94812c6c1f76f535858d04a1fa91fafa86d65e57990d56adea77117a374b450e1d1d5267afa5d937e12490a318be32d9fee399f6758f9977fa3c8803acc9f9c0
7
+ data.tar.gz: ec5999dafd6c14225eea303331db69ce85b19819e2fcda67df2f45ef78f95e7b7eaa73849c0a4fcb2e8c7bba819e731ec444f4f7c9e4b3797233bd07c144a79a
@@ -1,3 +1,7 @@
1
+ ## 2.1.0 (2016-01-02)
2
+ - Added enforcement of maximum request size
3
+ - Added some exception wrapping
4
+
1
5
  ## 2.0.1 (2016-09-01)
2
6
  - Fix bug in delete by query when routing is required
3
7
 
@@ -8,6 +8,7 @@ require "elastomer/version"
8
8
  module Elastomer
9
9
 
10
10
  class Client
11
+ MAX_REQUEST_SIZE = 250 * 2**20 # 250 MB
11
12
 
12
13
  # Create a new client that can be used to make HTTP requests to the
13
14
  # Elasticsearch server.
@@ -20,6 +21,7 @@ module Elastomer
20
21
  # :open_timeout - the timeout in seconds when opening an HTTP connection
21
22
  # :adapter - the Faraday adapter to use (defaults to :excon)
22
23
  # :opaque_id - set to `true` to use the 'X-Opaque-Id' request header
24
+ # :max_request_size - the maximum allowed request size in bytes (defaults to 250 MB)
23
25
  #
24
26
  def initialize( opts = {} )
25
27
  host = opts.fetch :host, "localhost"
@@ -30,14 +32,15 @@ module Elastomer
30
32
  @host = uri.host
31
33
  @port = uri.port
32
34
 
33
- @read_timeout = opts.fetch :read_timeout, 5
34
- @open_timeout = opts.fetch :open_timeout, 2
35
- @adapter = opts.fetch :adapter, Faraday.default_adapter
36
- @opaque_id = opts.fetch :opaque_id, false
35
+ @read_timeout = opts.fetch :read_timeout, 5
36
+ @open_timeout = opts.fetch :open_timeout, 2
37
+ @adapter = opts.fetch :adapter, Faraday.default_adapter
38
+ @opaque_id = opts.fetch :opaque_id, false
39
+ @max_request_size = opts.fetch :max_request_size, MAX_REQUEST_SIZE
37
40
  end
38
41
 
39
42
  attr_reader :host, :port, :url
40
- attr_reader :read_timeout, :open_timeout
43
+ attr_reader :read_timeout, :open_timeout, :max_request_size
41
44
 
42
45
  # Returns true if the server is available; returns false otherwise.
43
46
  def ping
@@ -71,9 +74,10 @@ module Elastomer
71
74
  # Returns a Faraday::Connection
72
75
  def connection
73
76
  @connection ||= Faraday.new(url) do |conn|
74
- conn.request :encode_json
75
- conn.response :parse_json
76
- conn.request :opaque_id if @opaque_id
77
+ conn.request(:encode_json)
78
+ conn.response(:parse_json)
79
+ conn.request(:opaque_id) if @opaque_id
80
+ conn.request(:limit_size, :max_request_size => max_request_size) if max_request_size
77
81
 
78
82
  if @adapter.is_a?(Array)
79
83
  conn.adapter(*@adapter)
@@ -295,7 +299,27 @@ module Elastomer
295
299
  # containing and 'error' field.
296
300
  def handle_errors( response )
297
301
  raise ServerError, response if response.status >= 500
298
- raise RequestError, response if response.body.is_a?(Hash) && response.body["error"]
302
+
303
+ if response.body.is_a?(Hash) && (error = response.body["error"])
304
+ # ES 2.X style
305
+ if error.is_a?(Hash)
306
+ root_cause = Array(error["root_cause"]).first || error
307
+ case root_cause["type"]
308
+ when "index_not_found_exception"; raise IndexNotFoundError, response
309
+ when "query_parsing_exception"; raise QueryParsingError, response
310
+ end
311
+
312
+ # ES 1.X style
313
+ elsif error.is_a?(String)
314
+ case error
315
+ when %r/IndexMissingException/; raise IndexNotFoundError, response
316
+ when %r/QueryParsingException/; raise QueryParsingError, response
317
+ when %r/ParseException/; raise QueryParsingError, response
318
+ end
319
+ end
320
+
321
+ raise RequestError, response
322
+ end
299
323
 
300
324
  response
301
325
  end
@@ -158,7 +158,7 @@ module Elastomer
158
158
  # immediately.
159
159
  #
160
160
  class Bulk
161
- DEFAULT_REQUEST_SIZE = 10 * 2**20 # 10 MB
161
+ DEFAULT_REQUEST_SIZE = 10 * 2**20 # 10 MB
162
162
 
163
163
  # Create a new bulk client for handling some of the details of
164
164
  # accumulating documents to index and then formatting them properly for
@@ -194,7 +194,10 @@ module Elastomer
194
194
  if value.nil?
195
195
  @request_size = nil
196
196
  else
197
- @request_size = value.to_i <= 0 ? nil : value.to_i
197
+ value = value.to_i
198
+ value = nil if value <= 0
199
+ value = client.max_request_size if value > client.max_request_size
200
+ @request_size = value
198
201
  end
199
202
  end
200
203
 
@@ -370,26 +373,55 @@ module Elastomer
370
373
  # document - Optional document for the action as a Hash or JSON encoded String
371
374
  #
372
375
  # Returns the response from the bulk call if one was made or nil.
376
+ # Raises RequestSizeError if the given action is larger than the
377
+ # configured requst size or the client.max_request_size
373
378
  def add_to_actions( action, document = nil )
374
- action = MultiJson.dump action
375
- @actions << action
376
- @current_request_size += action.bytesize
377
- @current_action_count += 1
378
-
379
- unless document.nil?
380
- document = MultiJson.dump document unless String === document
381
- @actions << document
382
- @current_request_size += document.bytesize
379
+ action = MultiJson.dump(action)
380
+ size = action.bytesize
381
+
382
+ if document
383
+ document = MultiJson.dump(document) unless document.is_a?(String)
384
+ size += document.bytesize
383
385
  end
384
386
 
385
- if (request_size && @current_request_size >= request_size) ||
386
- (action_count && @current_action_count >= action_count)
387
- call
388
- else
389
- nil
387
+ check_action_size!(size)
388
+
389
+ response = nil
390
+ begin
391
+ response = call if ready_to_send?(size)
392
+ rescue StandardError => err
393
+ raise err
394
+ ensure
395
+ @actions << action
396
+ @actions << document unless document.nil?
397
+ @current_request_size += size
398
+ @current_action_count += 1
390
399
  end
400
+
401
+ response
402
+ end
403
+
404
+ # Internal: Determines if adding `size` more bytes and one more action
405
+ # will bring the current bulk request over the `request_size` limit or the
406
+ # `action_count` limit. If this method returns true, then it is time to
407
+ # dispatch the bulk request.
408
+ #
409
+ # Returns `true` of `false`
410
+ def ready_to_send?( size )
411
+ total_request_size = @current_request_size + size
412
+ total_action_count = @current_action_count + 1
413
+
414
+ (request_size && total_request_size >= request_size) ||
415
+ (action_count && total_action_count > action_count)
391
416
  end
392
417
 
393
- end # Bulk
394
- end # Client
395
- end # Elastomer
418
+ # Internal: Raises a RequestSizeError if the given size is larger than
419
+ # the configured requst size or the client.max_request_size
420
+ def check_action_size!( size )
421
+ return unless (request_size && size > request_size) || (size > client.max_request_size)
422
+ raise RequestSizeError, "Bulk action of size `#{size}` is too large"
423
+ end
424
+
425
+ end
426
+ end
427
+ end
@@ -81,6 +81,12 @@ module Elastomer
81
81
  SSLError = Class.new Error
82
82
  ServerError = Class.new Error
83
83
  RequestError = Class.new Error
84
+ RequestSizeError = Class.new Error
85
+
86
+ # Provide some nice errors for common Elasticsearch exceptions. These are
87
+ # all subclasses of the more general RequestError
88
+ IndexNotFoundError = Class.new RequestError
89
+ QueryParsingError = Class.new RequestError
84
90
 
85
91
  ServerError.fatal = false
86
92
  TimeoutError.fatal = false
@@ -0,0 +1,30 @@
1
+ module Elastomer
2
+ module Middleware
3
+
4
+ # Request middleware that raises an exception if the request body exceeds a
5
+ # `max_request_size`.
6
+ class LimitSize < Faraday::Middleware
7
+
8
+ def initialize(app = nil, options = {})
9
+ super(app)
10
+ @max_request_size = options.fetch(:max_request_size)
11
+ end
12
+
13
+ attr_reader :max_request_size
14
+
15
+ def call(env)
16
+ if body = env[:body]
17
+ if body.is_a?(String) && body.bytesize > max_request_size
18
+ raise ::Elastomer::Client::RequestSizeError,
19
+ "Request of size `#{body.bytesize}` exceeds the maximum requst size: #{max_request_size}"
20
+ end
21
+ end
22
+ @app.call(env)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ Faraday::Request.register_middleware \
30
+ :limit_size => ::Elastomer::Middleware::LimitSize
@@ -1,5 +1,5 @@
1
1
  module Elastomer
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.0"
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -177,14 +177,14 @@ describe Elastomer::Client::Bulk do
177
177
  ary << b.index(document)
178
178
  }
179
179
  ary.compact!
180
- assert_equal 3, ary.length
180
+ assert_equal 4, ary.length
181
181
 
182
182
  document = {:_id => 10, :_type => "tweet", :author => "pea53", :message => "tweet 10 is a 102 character request"}
183
183
  ary << b.index(document)
184
184
  end
185
185
  ary.compact!
186
186
 
187
- assert_equal 4, ary.length
187
+ assert_equal 5, ary.length
188
188
  ary.each { |a| a["items"].each { |b| assert_bulk_index(b) } }
189
189
 
190
190
  @index.refresh
@@ -208,7 +208,7 @@ describe Elastomer::Client::Bulk do
208
208
  ary << b.index(document)
209
209
  }
210
210
  ary.compact!
211
- assert_equal 3, ary.length
211
+ assert_equal 2, ary.length
212
212
 
213
213
  document = {:_id => 10, :_type => "tweet", :author => "pea53", :message => "this is tweet number 10"}
214
214
  ary << b.index(document)
@@ -224,6 +224,30 @@ describe Elastomer::Client::Bulk do
224
224
  assert_equal 10, h["hits"]["total"]
225
225
  end
226
226
 
227
+ it "rejects documents that excceed the allowed bulk request size" do
228
+ ary = []
229
+ ary << @index.bulk(:request_size => 300) do |b|
230
+ 2.times { |num|
231
+ document = {:_id => num, :_type => "tweet", :author => "pea53", :message => "tweet #{num} is a 100 character request"}
232
+ ary << b.index(document)
233
+ }
234
+ ary.compact!
235
+ assert_equal 0, ary.length
236
+
237
+ document = {:_id => 342, :_type => "tweet", :author => "pea53", :message => "a"*290}
238
+ assert_raises(Elastomer::Client::RequestSizeError) { b.index(document) }
239
+ end
240
+ ary.compact!
241
+
242
+ assert_equal 1, ary.length
243
+ ary.each { |a| a["items"].each { |b| assert_bulk_index(b) } }
244
+
245
+ @index.refresh
246
+ h = @index.docs.search :q => "*:*", :size => 0
247
+
248
+ assert_equal 2, h["hits"]["total"]
249
+ end
250
+
227
251
  it "uses :id from parameters" do
228
252
  response = @index.bulk do |b|
229
253
  document = { :_type => "tweet", :author => "pea53", :message => "just a test tweet" }
@@ -279,6 +279,14 @@ describe Elastomer::Client::Docs do
279
279
  end
280
280
  end
281
281
 
282
+ it "generates QueryParsingError exceptions on bad input when searching" do
283
+ query = {:query => {:query_string => {:query => "OR should fail"}}}
284
+ assert_raises(Elastomer::Client::QueryParsingError) { @docs.search(query, :type => %w[doc1 doc2]) }
285
+
286
+ query = {:query => {:foo_is_not_valid => {}}}
287
+ assert_raises(Elastomer::Client::QueryParsingError) { @docs.search(query, :type => %w[doc1 doc2]) }
288
+ end
289
+
282
290
  it "counts documents" do
283
291
  h = @docs.count :q => "*:*"
284
292
  assert_equal 0, h["count"]
@@ -259,6 +259,13 @@ describe Elastomer::Client::Index do
259
259
  assert_equal %w[just few words analyze], tokens
260
260
  end
261
261
 
262
+ describe "when an index does not exist" do
263
+ it "raises an IndexNotFoundError on delete" do
264
+ index = $client.index("index-that-does-not-exist")
265
+ assert_raises(Elastomer::Client::IndexNotFoundError) { index.delete }
266
+ end
267
+ end
268
+
262
269
  describe "when an index exists" do
263
270
  before do
264
271
  suggest = {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastomer-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Pease
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-09-01 00:00:00.000000000 Z
12
+ date: 2016-09-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: addressable
@@ -215,6 +215,7 @@ files:
215
215
  - lib/elastomer/client/warmer.rb
216
216
  - lib/elastomer/core_ext/time.rb
217
217
  - lib/elastomer/middleware/encode_json.rb
218
+ - lib/elastomer/middleware/limit_size.rb
218
219
  - lib/elastomer/middleware/opaque_id.rb
219
220
  - lib/elastomer/middleware/parse_json.rb
220
221
  - lib/elastomer/notifications.rb
@@ -267,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
268
  version: '0'
268
269
  requirements: []
269
270
  rubyforge_project:
270
- rubygems_version: 2.2.5
271
+ rubygems_version: 2.2.3
271
272
  signing_key:
272
273
  specification_version: 4
273
274
  summary: A library for interacting with Elasticsearch