elastic-transport 8.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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