elastic-transport 8.0.0.pre1

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/check_license_headers.rb +33 -0
  3. data/.github/license-header.txt +16 -0
  4. data/.github/workflows/license.yml +13 -0
  5. data/.github/workflows/tests.yml +45 -0
  6. data/.gitignore +19 -0
  7. data/CHANGELOG.md +224 -0
  8. data/Gemfile +38 -0
  9. data/LICENSE +202 -0
  10. data/README.md +552 -0
  11. data/Rakefile +87 -0
  12. data/elastic-transport.gemspec +74 -0
  13. data/lib/elastic/transport/client.rb +276 -0
  14. data/lib/elastic/transport/meta_header.rb +135 -0
  15. data/lib/elastic/transport/redacted.rb +73 -0
  16. data/lib/elastic/transport/transport/base.rb +450 -0
  17. data/lib/elastic/transport/transport/connections/collection.rb +126 -0
  18. data/lib/elastic/transport/transport/connections/connection.rb +160 -0
  19. data/lib/elastic/transport/transport/connections/selector.rb +91 -0
  20. data/lib/elastic/transport/transport/errors.rb +91 -0
  21. data/lib/elastic/transport/transport/http/curb.rb +120 -0
  22. data/lib/elastic/transport/transport/http/faraday.rb +95 -0
  23. data/lib/elastic/transport/transport/http/manticore.rb +179 -0
  24. data/lib/elastic/transport/transport/loggable.rb +83 -0
  25. data/lib/elastic/transport/transport/response.rb +36 -0
  26. data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
  27. data/lib/elastic/transport/transport/sniffer.rb +101 -0
  28. data/lib/elastic/transport/version.rb +22 -0
  29. data/lib/elastic/transport.rb +37 -0
  30. data/lib/elastic-transport.rb +18 -0
  31. data/spec/elasticsearch/connections/collection_spec.rb +266 -0
  32. data/spec/elasticsearch/connections/selector_spec.rb +166 -0
  33. data/spec/elasticsearch/transport/base_spec.rb +264 -0
  34. data/spec/elasticsearch/transport/client_spec.rb +1651 -0
  35. data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
  36. data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
  37. data/spec/spec_helper.rb +90 -0
  38. data/test/integration/transport_test.rb +98 -0
  39. data/test/profile/client_benchmark_test.rb +132 -0
  40. data/test/test_helper.rb +83 -0
  41. data/test/unit/connection_test.rb +135 -0
  42. data/test/unit/response_test.rb +30 -0
  43. data/test/unit/serializer_test.rb +33 -0
  44. data/test/unit/transport_base_test.rb +664 -0
  45. data/test/unit/transport_curb_test.rb +135 -0
  46. data/test/unit/transport_faraday_test.rb +228 -0
  47. data/test/unit/transport_manticore_test.rb +251 -0
  48. metadata +412 -0
@@ -0,0 +1,30 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'test_helper'
19
+
20
+ class Elastic::Transport::Transport::ResponseTest < Minitest::Test
21
+ context "Response" do
22
+ should "force-encode the body into UTF" do
23
+ body = "Hello Encoding!".encode(Encoding::ISO_8859_1)
24
+ assert_equal 'ISO-8859-1', body.encoding.name
25
+
26
+ response = Elastic::Transport::Transport::Response.new 200, body
27
+ assert_equal 'UTF-8', response.body.encoding.name
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'test_helper'
19
+
20
+ class Elastic::Transport::Transport::SerializerTest < Minitest::Test
21
+
22
+ context "Serializer" do
23
+
24
+ should "use MultiJson by default" do
25
+ ::MultiJson.expects(:load)
26
+ ::MultiJson.expects(:dump)
27
+ Elastic::Transport::Transport::Serializer::MultiJson.new.load('{}')
28
+ Elastic::Transport::Transport::Serializer::MultiJson.new.dump({})
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,664 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ require 'test_helper'
19
+
20
+ class Elastic::Transport::Transport::BaseTest < Minitest::Test
21
+
22
+ class EmptyTransport
23
+ include Elastic::Transport::Transport::Base
24
+ end
25
+
26
+ class DummyTransport
27
+ include Elastic::Transport::Transport::Base
28
+ def __build_connection(host, options={}, block=nil)
29
+ Elastic::Transport::Transport::Connections::Connection.new :host => host, :connection => Object.new
30
+ end
31
+ end
32
+
33
+ class DummyTransportPerformer < DummyTransport
34
+ def perform_request(method, path, params={}, body=nil, &block); super; end
35
+ end
36
+
37
+ class DummySerializer
38
+ def initialize(*); end
39
+ end
40
+
41
+ class DummySniffer
42
+ def initialize(*); end
43
+ end
44
+
45
+ context "Transport::Base" do
46
+ should "raise exception when it doesn't implement __build_connection" do
47
+ assert_raise NoMethodError do
48
+ EmptyTransport.new.__build_connection({ :host => 'foo'}, {})
49
+ end
50
+ end
51
+
52
+ should "build connections on initialization" do
53
+ DummyTransport.any_instance.expects(:__build_connections)
54
+ transport = DummyTransport.new
55
+ end
56
+
57
+ should "have default serializer" do
58
+ transport = DummyTransport.new
59
+ assert_instance_of Elastic::Transport::Transport::Base::DEFAULT_SERIALIZER_CLASS, transport.serializer
60
+ end
61
+
62
+ should "have custom serializer" do
63
+ transport = DummyTransport.new :options => { :serializer_class => DummySerializer }
64
+ assert_instance_of DummySerializer, transport.serializer
65
+
66
+ transport = DummyTransport.new :options => { :serializer => DummySerializer.new }
67
+ assert_instance_of DummySerializer, transport.serializer
68
+ end
69
+
70
+ should "have default sniffer" do
71
+ transport = DummyTransport.new
72
+ assert_instance_of Elastic::Transport::Transport::Sniffer, transport.sniffer
73
+ end
74
+
75
+ should "have custom sniffer" do
76
+ transport = DummyTransport.new :options => { :sniffer_class => DummySniffer }
77
+ assert_instance_of DummySniffer, transport.sniffer
78
+ end
79
+
80
+ context "when combining the URL" do
81
+ setup do
82
+ @transport = DummyTransport.new
83
+ @basic_parts = { :protocol => 'http', :host => 'myhost', :port => 8080 }
84
+ end
85
+
86
+ should "combine basic parts" do
87
+ assert_equal 'http://myhost:8080', @transport.__full_url(@basic_parts)
88
+ end
89
+
90
+ should "combine path" do
91
+ assert_equal 'http://myhost:8080/api', @transport.__full_url(@basic_parts.merge :path => '/api')
92
+ end
93
+
94
+ should "combine authentication credentials" do
95
+ assert_equal 'http://U:P@myhost:8080', @transport.__full_url(@basic_parts.merge :user => 'U', :password => 'P')
96
+ end
97
+
98
+ should "escape the username and password" do
99
+ assert_equal 'http://user%40domain:foo%2Fbar@myhost:8080',
100
+ @transport.__full_url(@basic_parts.merge :user => 'user@domain', :password => 'foo/bar')
101
+ end
102
+ end
103
+ end
104
+
105
+ context "getting a connection" do
106
+ setup do
107
+ @transport = DummyTransportPerformer.new :options => { :reload_connections => 5 }
108
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
109
+ @transport.stubs(:sniffer).returns(stub :hosts => [])
110
+ end
111
+
112
+ should "get a connection" do
113
+ assert_not_nil @transport.get_connection
114
+ end
115
+
116
+ should "increment the counter" do
117
+ assert_equal 0, @transport.counter
118
+ 3.times { @transport.get_connection }
119
+ assert_equal 3, @transport.counter
120
+ end
121
+
122
+ should "reload connections when it hits the threshold" do
123
+ @transport.expects(:reload_connections!).twice
124
+ 12.times { @transport.get_connection }
125
+ assert_equal 12, @transport.counter
126
+ end
127
+
128
+ should "not reload connections by default" do
129
+ @transport = DummyTransportPerformer.new
130
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
131
+ @transport.expects(:reload_connections!).never
132
+
133
+ 10_010.times { @transport.get_connection }
134
+ assert_equal 10_010, @transport.counter
135
+ end
136
+
137
+ should "not reload connections when the option is set to false" do
138
+ @transport = DummyTransportPerformer.new :options => { :reload_connections => false }
139
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
140
+ @transport.expects(:reload_connections!).never
141
+
142
+ 10_010.times { @transport.get_connection }
143
+ assert_equal 10_010, @transport.counter
144
+ end
145
+ end
146
+
147
+ context "performing a request" do
148
+ setup do
149
+ @transport = DummyTransportPerformer.new
150
+ end
151
+
152
+ should "raise an error when no block is passed" do
153
+ assert_raise NoMethodError do
154
+ @transport.peform_request 'GET', '/'
155
+ end
156
+ end
157
+
158
+ should "get the connection" do
159
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
160
+ @transport.perform_request 'GET', '/' do; Elastic::Transport::Transport::Response.new 200, 'OK'; end
161
+ end
162
+
163
+ should "raise an error when no connection is available" do
164
+ @transport.expects(:get_connection).returns(nil)
165
+ assert_raise Elastic::Transport::Transport::Error do
166
+ @transport.perform_request 'GET', '/' do; Elastic::Transport::Transport::Response.new 200, 'OK'; end
167
+ end
168
+ end
169
+
170
+ should "call the passed block" do
171
+ x = 0
172
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
173
+
174
+ @transport.perform_request 'GET', '/' do |connection, url|
175
+ x += 1
176
+ Elastic::Transport::Transport::Response.new 200, 'OK'
177
+ end
178
+
179
+ assert_equal 1, x
180
+ end
181
+
182
+ should "deserialize a response JSON body" do
183
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
184
+ @transport.serializer.expects(:load).returns({'foo' => 'bar'})
185
+
186
+ response = @transport.perform_request 'GET', '/' do
187
+ Elastic::Transport::Transport::Response.new 200, '{"foo":"bar"}', {"content-type" => 'application/json'}
188
+ end
189
+
190
+ assert_instance_of Elastic::Transport::Transport::Response, response
191
+ assert_equal 'bar', response.body['foo']
192
+ end
193
+
194
+ should "not deserialize a response string body" do
195
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
196
+ @transport.serializer.expects(:load).never
197
+ response = @transport.perform_request 'GET', '/' do
198
+ Elastic::Transport::Transport::Response.new 200, 'FOOBAR', {"content-type" => 'text/plain'}
199
+ end
200
+
201
+ assert_instance_of Elastic::Transport::Transport::Response, response
202
+ assert_equal 'FOOBAR', response.body
203
+ end
204
+
205
+ should "not deserialize an empty response body" do
206
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
207
+ @transport.serializer.expects(:load).never
208
+ response = @transport.perform_request 'GET', '/' do
209
+ Elastic::Transport::Transport::Response.new 200, '', {"content-type" => 'application/json'}
210
+ end
211
+
212
+ assert_instance_of Elastic::Transport::Transport::Response, response
213
+ assert_equal '', response.body
214
+ end
215
+
216
+ should "serialize non-String objects" do
217
+ @transport.serializer.expects(:dump).times(3)
218
+ @transport.__convert_to_json({:foo => 'bar'})
219
+ @transport.__convert_to_json([1, 2, 3])
220
+ @transport.__convert_to_json(nil)
221
+ end
222
+
223
+ should "not serialize a String object" do
224
+ @transport.serializer.expects(:dump).never
225
+ @transport.__convert_to_json('{"foo":"bar"}')
226
+ end
227
+
228
+ should "raise an error for HTTP status 404" do
229
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
230
+ assert_raise Elastic::Transport::Transport::Errors::NotFound do
231
+ @transport.perform_request 'GET', '/' do
232
+ Elastic::Transport::Transport::Response.new 404, 'NOT FOUND'
233
+ end
234
+ end
235
+ end
236
+
237
+ should "raise an error for HTTP status 404 with application/json content type" do
238
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
239
+ assert_raise Elastic::Transport::Transport::Errors::NotFound do
240
+ @transport.perform_request 'GET', '/' do
241
+ Elastic::Transport::Transport::Response.new 404, 'NOT FOUND', {"content-type" => 'application/json'}
242
+ end
243
+ end
244
+ end
245
+
246
+ should "raise an error on server failure" do
247
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
248
+ assert_raise Elastic::Transport::Transport::Errors::InternalServerError do
249
+ @transport.perform_request 'GET', '/' do
250
+ Elastic::Transport::Transport::Response.new 500, 'ERROR'
251
+ end
252
+ end
253
+ end
254
+
255
+ should "raise an error on connection failure" do
256
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
257
+
258
+ # `block.expects(:call).raises(::Errno::ECONNREFUSED)` fails on Ruby 1.8
259
+ block = lambda { |a,b| raise ::Errno::ECONNREFUSED }
260
+
261
+ assert_raise ::Errno::ECONNREFUSED do
262
+ @transport.perform_request 'GET', '/', &block
263
+ end
264
+ end
265
+
266
+ should 'raise TooManyRequestsError on 429' do
267
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
268
+ assert_raise Elastic::Transport::Transport::Errors::TooManyRequests do
269
+ @transport.perform_request 'GET', '/' do
270
+ Elastic::Transport::Transport::Response.new 429, 'ERROR'
271
+ end
272
+ end
273
+ end
274
+
275
+ should "not raise an error when the :ignore argument has been passed" do
276
+ @transport.stubs(:get_connection).returns(stub_everything :failures => 1)
277
+
278
+ assert_raise Elastic::Transport::Transport::Errors::BadRequest do
279
+ @transport.perform_request 'GET', '/' do
280
+ Elastic::Transport::Transport::Response.new 400, 'CLIENT ERROR'
281
+ end
282
+ end
283
+
284
+ # No `BadRequest` error
285
+ @transport.perform_request 'GET', '/', :ignore => 400 do
286
+ Elastic::Transport::Transport::Response.new 400, 'CLIENT ERROR'
287
+ end
288
+ end
289
+
290
+ should "mark the connection as dead on failure" do
291
+ c = stub_everything :failures => 1
292
+ @transport.expects(:get_connection).returns(c)
293
+
294
+ block = lambda { |a,b| raise ::Errno::ECONNREFUSED }
295
+
296
+ c.expects(:dead!)
297
+
298
+ assert_raise( ::Errno::ECONNREFUSED ) { @transport.perform_request 'GET', '/', &block }
299
+ end
300
+ end
301
+
302
+ context "performing a request with reload connections on connection failures" do
303
+ setup do
304
+ fake_collection = stub_everything :get_connection => stub_everything(:failures => 1),
305
+ :all => stub_everything(:size => 2)
306
+ @transport = DummyTransportPerformer.new :options => { :reload_on_failure => 2 }
307
+ @transport.stubs(:connections).
308
+ returns(fake_collection)
309
+ @block = lambda { |c, u| puts "UNREACHABLE" }
310
+ end
311
+
312
+ should "reload connections when host is unreachable" do
313
+ @block.expects(:call).times(2).
314
+ raises(Errno::ECONNREFUSED).
315
+ then.returns(stub_everything :failures => 1)
316
+
317
+ @transport.expects(:reload_connections!).returns([])
318
+
319
+ @transport.perform_request('GET', '/', &@block)
320
+ assert_equal 2, @transport.counter
321
+ end
322
+ end
323
+
324
+ context "performing a request with retry on connection failures" do
325
+ setup do
326
+ @transport = DummyTransportPerformer.new :options => { :retry_on_failure => true }
327
+ @transport.stubs(:connections).returns(stub :get_connection => stub_everything(:failures => 1))
328
+ @block = Proc.new { |c, u| puts "UNREACHABLE" }
329
+ end
330
+
331
+ should "retry DEFAULT_MAX_RETRIES when host is unreachable" do
332
+ @block.expects(:call).times(4).
333
+ raises(Errno::ECONNREFUSED).
334
+ then.raises(Errno::ECONNREFUSED).
335
+ then.raises(Errno::ECONNREFUSED).
336
+ then.returns(stub_everything :failures => 1)
337
+
338
+ assert_nothing_raised do
339
+ @transport.perform_request('GET', '/', &@block)
340
+ assert_equal 4, @transport.counter
341
+ end
342
+ end
343
+
344
+ should "raise an error after max tries" do
345
+ @block.expects(:call).times(4).
346
+ raises(Errno::ECONNREFUSED).
347
+ then.raises(Errno::ECONNREFUSED).
348
+ then.raises(Errno::ECONNREFUSED).
349
+ then.raises(Errno::ECONNREFUSED).
350
+ then.returns(stub_everything :failures => 1)
351
+
352
+ assert_raise Errno::ECONNREFUSED do
353
+ @transport.perform_request('GET', '/', &@block)
354
+ end
355
+ end
356
+ end
357
+
358
+ context "performing a request with retry on status" do
359
+ setup do
360
+ DummyTransportPerformer.any_instance.stubs(:connections).returns(stub :get_connection => stub_everything(:failures => 1))
361
+
362
+ logger = Logger.new(STDERR)
363
+ logger.level = Logger::DEBUG
364
+ DummyTransportPerformer.any_instance.stubs(:logger).returns(logger)
365
+ @block = Proc.new { |c, u| puts "ERROR" }
366
+ end
367
+
368
+ should "not retry when the status code does not match" do
369
+ @transport = DummyTransportPerformer.new :options => { :retry_on_status => 500 }
370
+ assert_equal [500], @transport.instance_variable_get(:@retry_on_status)
371
+
372
+ @block.expects(:call).
373
+ returns(Elastic::Transport::Transport::Response.new 400, 'Bad Request').
374
+ times(1)
375
+
376
+ @transport.logger.
377
+ expects(:warn).
378
+ with( regexp_matches(/Attempt \d to get response/) ).
379
+ never
380
+
381
+ assert_raise Elastic::Transport::Transport::Errors::BadRequest do
382
+ @transport.perform_request('GET', '/', {}, nil, &@block)
383
+ end
384
+ end
385
+
386
+ should "retry when the status code does match" do
387
+ @transport = DummyTransportPerformer.new :options => { :retry_on_status => 500 }
388
+ assert_equal [500], @transport.instance_variable_get(:@retry_on_status)
389
+
390
+ @block.expects(:call).
391
+ returns(Elastic::Transport::Transport::Response.new 500, 'Internal Error').
392
+ times(4)
393
+
394
+ @transport.logger.
395
+ expects(:warn).
396
+ with( regexp_matches(/Attempt \d to get response/) ).
397
+ times(4)
398
+
399
+ assert_raise Elastic::Transport::Transport::Errors::InternalServerError do
400
+ @transport.perform_request('GET', '/', &@block)
401
+ end
402
+ end
403
+ end
404
+
405
+ context "logging" do
406
+ setup do
407
+ @transport = DummyTransportPerformer.new :options => { :logger => Logger.new('/dev/null') }
408
+
409
+ fake_connection = stub :full_url => 'localhost:9200/_search?size=1',
410
+ :host => 'localhost',
411
+ :connection => stub_everything,
412
+ :failures => 0,
413
+ :healthy! => true
414
+
415
+ @transport.stubs(:get_connection).returns(fake_connection)
416
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
417
+ @transport.serializer.stubs(:dump).returns '{"foo":"bar"}'
418
+ end
419
+
420
+ should "log the request and response" do
421
+ @transport.logger.expects(:info). with do |line|
422
+ line =~ %r|POST localhost\:9200/_search\?size=1 \[status\:200, request:.*s, query:n/a\]|
423
+ end
424
+ @transport.logger.expects(:debug). with '> {"foo":"bar"}'
425
+ @transport.logger.expects(:debug). with '< {"foo":"bar"}'
426
+
427
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:foo => 'bar'} do
428
+ Elastic::Transport::Transport::Response.new 200, '{"foo":"bar"}'
429
+ end
430
+ end
431
+
432
+ should "sanitize password in the URL" do
433
+ fake_connection = stub :full_url => 'http://user:password@localhost:9200/_search?size=1',
434
+ :host => 'localhost',
435
+ :connection => stub_everything,
436
+ :failures => 0,
437
+ :healthy! => true
438
+ @transport.stubs(:get_connection).returns(fake_connection)
439
+
440
+ @transport.logger.expects(:info).with do |message|
441
+ assert_match(/http:\/\/user:\*{1,15}@localhost\:9200/, message)
442
+ true
443
+ end
444
+
445
+
446
+ @transport.perform_request('GET', '/') {Elastic::Transport::Transport::Response.new 200, '{"foo":"bar"}' }
447
+ end
448
+
449
+ should "log a failed Elasticsearch request as fatal" do
450
+ @block = Proc.new { |c, u| puts "ERROR" }
451
+ @block.expects(:call).returns(Elastic::Transport::Transport::Response.new 500, 'ERROR')
452
+
453
+ @transport.expects(:__log_response)
454
+ @transport.logger.expects(:fatal)
455
+
456
+ assert_raise Elastic::Transport::Transport::Errors::InternalServerError do
457
+ @transport.perform_request('POST', '_search', &@block)
458
+ end
459
+ end
460
+
461
+ should "not log a failed Elasticsearch request as fatal" do
462
+ @block = Proc.new { |c, u| puts "ERROR" }
463
+ @block.expects(:call).returns(Elastic::Transport::Transport::Response.new 500, 'ERROR')
464
+
465
+ @transport.expects(:__log_response).once
466
+ @transport.logger.expects(:fatal).never
467
+
468
+ # No `BadRequest` error
469
+ @transport.perform_request('POST', '_search', :ignore => 500, &@block)
470
+ end
471
+
472
+ should "log and re-raise a Ruby exception" do
473
+ @block = Proc.new { |c, u| puts "ERROR" }
474
+ @block.expects(:call).raises(Exception)
475
+
476
+ @transport.expects(:__log_response).never
477
+ @transport.logger.expects(:fatal)
478
+
479
+ assert_raise(Exception) { @transport.perform_request('POST', '_search', &@block) }
480
+ end
481
+ end
482
+
483
+ context "tracing" do
484
+ setup do
485
+ @transport = DummyTransportPerformer.new :options => { :tracer => Logger.new('/dev/null') }
486
+
487
+ fake_connection = stub :full_url => 'localhost:9200/_search?size=1',
488
+ :host => 'localhost',
489
+ :connection => stub_everything,
490
+ :failures => 0,
491
+ :healthy! => true
492
+
493
+ @transport.stubs(:get_connection).returns(fake_connection)
494
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
495
+ @transport.serializer.stubs(:dump).returns <<-JSON.gsub(/^ /, '')
496
+ {
497
+ "foo" : {
498
+ "bar" : {
499
+ "bam" : true
500
+ }
501
+ }
502
+ }
503
+ JSON
504
+ end
505
+
506
+ should "trace the request" do
507
+ @transport.tracer.expects(:info). with do |message|
508
+ message == <<-CURL.gsub(/^ /, '')
509
+ curl -X POST 'http://localhost:9200/_search?pretty&size=1' -d '{
510
+ "foo" : {
511
+ "bar" : {
512
+ "bam" : true
513
+ }
514
+ }
515
+ }
516
+ '
517
+ CURL
518
+ end.once
519
+
520
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:q => 'foo'} do
521
+ Elastic::Transport::Transport::Response.new 200, '{"foo":"bar"}'
522
+ end
523
+ end
524
+
525
+ should "trace a failed Elasticsearch request" do
526
+ @block = Proc.new { |c, u| puts "ERROR" }
527
+ @block.expects(:call).returns(Elastic::Transport::Transport::Response.new 500, 'ERROR')
528
+
529
+ @transport.expects(:__trace)
530
+
531
+ assert_raise Elastic::Transport::Transport::Errors::InternalServerError do
532
+ @transport.perform_request('POST', '_search', &@block)
533
+ end
534
+ end
535
+
536
+ end
537
+
538
+ context "reloading connections" do
539
+ setup do
540
+ @transport = DummyTransport.new :options => { :logger => Logger.new('/dev/null') }
541
+ end
542
+
543
+ should "rebuild connections" do
544
+ @transport.sniffer.expects(:hosts).returns([])
545
+ @transport.expects(:__rebuild_connections)
546
+ @transport.reload_connections!
547
+ end
548
+
549
+ should "log error and continue when timing out while sniffing hosts" do
550
+ @transport.sniffer.expects(:hosts).raises(Elastic::Transport::Transport::SnifferTimeoutError)
551
+ @transport.logger.expects(:error)
552
+ assert_nothing_raised do
553
+ @transport.reload_connections!
554
+ end
555
+ end
556
+
557
+ should "keep existing connections" do
558
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 } ], :options => { :http => {} }
559
+ assert_equal 1, @transport.connections.size
560
+
561
+ old_connection_id = @transport.connections.first.object_id
562
+
563
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 },
564
+ { :host => 'node2', :port => 2 } ],
565
+ :options => { :http => {} }
566
+
567
+ assert_equal 2, @transport.connections.size
568
+ assert_equal old_connection_id, @transport.connections.first.object_id
569
+ end
570
+
571
+ should "remove dead connections" do
572
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 },
573
+ { :host => 'node2', :port => 2 } ],
574
+ :options => { :http => {} }
575
+ assert_equal 2, @transport.connections.size
576
+
577
+ @transport.connections[1].dead!
578
+
579
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 } ], :options => { :http => {} }
580
+
581
+ assert_equal 1, @transport.connections.size
582
+ assert_equal 1, @transport.connections.all.size
583
+ end
584
+
585
+ should "not duplicate connections" do
586
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 },
587
+ { :host => 'node2', :port => 2 } ],
588
+ :options => { :http => {} }
589
+ assert_equal 2, @transport.connections.size
590
+
591
+ @transport.connections[0].dead!
592
+
593
+ @transport.__rebuild_connections :hosts => [ { :host => 'node1', :port => 1 },
594
+ { :host => 'node2', :port => 2 } ],
595
+ :options => { :http => {} }
596
+
597
+ assert_equal 2, @transport.connections.all.size
598
+ assert_equal 1, @transport.connections.size
599
+ end
600
+ end
601
+
602
+ context "rebuilding connections" do
603
+ setup do
604
+ @transport = DummyTransport.new
605
+ end
606
+
607
+ should "close connections" do
608
+ @transport.expects(:__close_connections)
609
+ @transport.__rebuild_connections :hosts => [ { :scheme => 'http', :host => 'foo', :port => 1 } ], :options => { :http => {} }
610
+ end
611
+
612
+ should "should replace the connections" do
613
+ assert_equal 0, @transport.connections.size
614
+
615
+ @transport.__rebuild_connections :hosts => [{ :scheme => 'http', :host => 'foo', :port => 1 }],
616
+ :options => { :http => {} }
617
+
618
+ assert_equal 1, @transport.connections.size
619
+ end
620
+ end
621
+
622
+ context "resurrecting connections" do
623
+ setup do
624
+ @transport = DummyTransportPerformer.new
625
+ end
626
+
627
+ should "delegate to dead connections" do
628
+ @transport.connections.expects(:dead).returns([])
629
+ @transport.resurrect_dead_connections!
630
+ end
631
+
632
+ should "not resurrect connections until timeout" do
633
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
634
+ @transport.expects(:resurrect_dead_connections!).never
635
+ 5.times { @transport.get_connection }
636
+ end
637
+
638
+ should "resurrect connections after timeout" do
639
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
640
+ @transport.expects(:resurrect_dead_connections!)
641
+
642
+ 4.times { @transport.get_connection }
643
+
644
+ now = Time.now + 60*2
645
+ Time.stubs(:now).returns(now)
646
+
647
+ @transport.get_connection
648
+ end
649
+
650
+ should "mark connection healthy if it succeeds" do
651
+ c = stub_everything(:failures => 1)
652
+ @transport.expects(:get_connection).returns(c)
653
+ c.expects(:healthy!)
654
+
655
+ @transport.perform_request('GET', '/') { |connection, url| Elastic::Transport::Transport::Response.new 200, 'OK' }
656
+ end
657
+ end
658
+
659
+ context "errors" do
660
+ should "raise highest-level Error exception for any ServerError" do
661
+ assert_kind_of Elastic::Transport::Transport::Error, Elastic::Transport::Transport::ServerError.new
662
+ end
663
+ end
664
+ end