elastomer-client 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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