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.
- checksums.yaml +7 -0
- data/.github/check_license_headers.rb +33 -0
- data/.github/license-header.txt +16 -0
- data/.github/workflows/license.yml +13 -0
- data/.github/workflows/tests.yml +45 -0
- data/.gitignore +19 -0
- data/CHANGELOG.md +224 -0
- data/Gemfile +38 -0
- data/LICENSE +202 -0
- data/README.md +552 -0
- data/Rakefile +87 -0
- data/elastic-transport.gemspec +74 -0
- data/lib/elastic/transport/client.rb +276 -0
- data/lib/elastic/transport/meta_header.rb +135 -0
- data/lib/elastic/transport/redacted.rb +73 -0
- data/lib/elastic/transport/transport/base.rb +450 -0
- data/lib/elastic/transport/transport/connections/collection.rb +126 -0
- data/lib/elastic/transport/transport/connections/connection.rb +160 -0
- data/lib/elastic/transport/transport/connections/selector.rb +91 -0
- data/lib/elastic/transport/transport/errors.rb +91 -0
- data/lib/elastic/transport/transport/http/curb.rb +120 -0
- data/lib/elastic/transport/transport/http/faraday.rb +95 -0
- data/lib/elastic/transport/transport/http/manticore.rb +179 -0
- data/lib/elastic/transport/transport/loggable.rb +83 -0
- data/lib/elastic/transport/transport/response.rb +36 -0
- data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
- data/lib/elastic/transport/transport/sniffer.rb +101 -0
- data/lib/elastic/transport/version.rb +22 -0
- data/lib/elastic/transport.rb +37 -0
- data/lib/elastic-transport.rb +18 -0
- data/spec/elasticsearch/connections/collection_spec.rb +266 -0
- data/spec/elasticsearch/connections/selector_spec.rb +166 -0
- data/spec/elasticsearch/transport/base_spec.rb +264 -0
- data/spec/elasticsearch/transport/client_spec.rb +1651 -0
- data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
- data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
- data/spec/spec_helper.rb +90 -0
- data/test/integration/transport_test.rb +98 -0
- data/test/profile/client_benchmark_test.rb +132 -0
- data/test/test_helper.rb +83 -0
- data/test/unit/connection_test.rb +135 -0
- data/test/unit/response_test.rb +30 -0
- data/test/unit/serializer_test.rb +33 -0
- data/test/unit/transport_base_test.rb +664 -0
- data/test/unit/transport_curb_test.rb +135 -0
- data/test/unit/transport_faraday_test.rb +228 -0
- data/test/unit/transport_manticore_test.rb +251 -0
- 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
|