elasticsearch-transport 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +13 -0
  4. data/README.md +276 -0
  5. data/Rakefile +67 -0
  6. data/elasticsearch-transport.gemspec +52 -0
  7. data/lib/elasticsearch-transport.rb +1 -0
  8. data/lib/elasticsearch/transport.rb +29 -0
  9. data/lib/elasticsearch/transport/client.rb +123 -0
  10. data/lib/elasticsearch/transport/extensions/test_cluster.rb +163 -0
  11. data/lib/elasticsearch/transport/transport/base.rb +236 -0
  12. data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
  13. data/lib/elasticsearch/transport/transport/connections/connection.rb +117 -0
  14. data/lib/elasticsearch/transport/transport/connections/selector.rb +63 -0
  15. data/lib/elasticsearch/transport/transport/errors.rb +73 -0
  16. data/lib/elasticsearch/transport/transport/http/curb.rb +70 -0
  17. data/lib/elasticsearch/transport/transport/http/faraday.rb +59 -0
  18. data/lib/elasticsearch/transport/transport/response.rb +20 -0
  19. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
  20. data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
  21. data/lib/elasticsearch/transport/version.rb +5 -0
  22. data/test/integration/client_test.rb +117 -0
  23. data/test/integration/transport_test.rb +37 -0
  24. data/test/profile/client_benchmark_test.rb +107 -0
  25. data/test/test_extensions.rb +139 -0
  26. data/test/test_helper.rb +58 -0
  27. data/test/unit/client_test.rb +109 -0
  28. data/test/unit/connection_collection_test.rb +83 -0
  29. data/test/unit/connection_selector_test.rb +64 -0
  30. data/test/unit/connection_test.rb +90 -0
  31. data/test/unit/serializer_test.rb +16 -0
  32. data/test/unit/sniffer_test.rb +146 -0
  33. data/test/unit/transport_base_test.rb +402 -0
  34. data/test/unit/transport_curb_test.rb +59 -0
  35. data/test/unit/transport_faraday_test.rb +73 -0
  36. metadata +342 -0
@@ -0,0 +1,402 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::BaseTest < Test::Unit::TestCase
4
+
5
+ class EmptyTransport
6
+ include Elasticsearch::Transport::Transport::Base
7
+ end
8
+
9
+ class DummyTransport
10
+ include Elasticsearch::Transport::Transport::Base
11
+ def __build_connections; hosts; end
12
+ end
13
+
14
+ class DummyTransportPerformer < DummyTransport
15
+ def perform_request(method, path, params={}, body=nil, &block); super; end
16
+ end
17
+
18
+ class DummySerializer
19
+ def initialize(*); end
20
+ end
21
+
22
+ class DummySniffer
23
+ def initialize(*); end
24
+ end
25
+
26
+ context "Transport::Base" do
27
+ should "raise exception when it doesn't implement __build_connections" do
28
+ assert_raise NoMethodError do
29
+ EmptyTransport.new.__build_connections
30
+ end
31
+ end
32
+
33
+ should "build connections on initialization" do
34
+ DummyTransport.any_instance.expects(:__build_connections)
35
+ transport = DummyTransport.new
36
+ end
37
+
38
+ should "have default serializer" do
39
+ transport = DummyTransport.new
40
+ assert_instance_of Elasticsearch::Transport::Transport::Base::DEFAULT_SERIALIZER_CLASS, transport.serializer
41
+ end
42
+
43
+ should "have custom serializer" do
44
+ transport = DummyTransport.new :options => { :serializer_class => DummySerializer }
45
+ assert_instance_of DummySerializer, transport.serializer
46
+
47
+ transport = DummyTransport.new :options => { :serializer => DummySerializer.new }
48
+ assert_instance_of DummySerializer, transport.serializer
49
+ end
50
+
51
+ should "have default sniffer" do
52
+ transport = DummyTransport.new
53
+ assert_instance_of Elasticsearch::Transport::Transport::Sniffer, transport.sniffer
54
+ end
55
+
56
+ should "have custom sniffer" do
57
+ transport = DummyTransport.new :options => { :sniffer_class => DummySniffer }
58
+ assert_instance_of DummySniffer, transport.sniffer
59
+ end
60
+ end
61
+
62
+ context "getting a connection" do
63
+ setup do
64
+ @transport = DummyTransportPerformer.new :options => { :reload_connections => 5 }
65
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
66
+ @transport.stubs(:sniffer).returns(stub :hosts => [])
67
+ end
68
+
69
+ should "get a connection" do
70
+ assert_not_nil @transport.get_connection
71
+ end
72
+
73
+ should "increment the counter" do
74
+ assert_equal 0, @transport.counter
75
+ 3.times { @transport.get_connection }
76
+ assert_equal 3, @transport.counter
77
+ end
78
+
79
+ should "reload connections when it hits the threshold" do
80
+ @transport.expects(:reload_connections!).twice
81
+ 12.times { @transport.get_connection }
82
+ assert_equal 12, @transport.counter
83
+ end
84
+ end
85
+
86
+ context "performing a request" do
87
+ setup do
88
+ @transport = DummyTransportPerformer.new
89
+ end
90
+
91
+ should "raise an error when no block is passed" do
92
+ assert_raise NoMethodError do
93
+ @transport.peform_request 'GET', '/'
94
+ end
95
+ end
96
+
97
+ should "get the connection" do
98
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
99
+ @transport.perform_request 'GET', '/' do; Elasticsearch::Transport::Transport::Response.new 200, 'OK'; end
100
+ end
101
+
102
+ should "raise an error when no connection is available" do
103
+ @transport.expects(:get_connection).returns(nil)
104
+ assert_raise Elasticsearch::Transport::Transport::Error do
105
+ @transport.perform_request 'GET', '/' do; Elasticsearch::Transport::Transport::Response.new 200, 'OK'; end
106
+ end
107
+ end
108
+
109
+ should "call the passed block" do
110
+ x = 0
111
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
112
+
113
+ @transport.perform_request 'GET', '/' do |connection, url|
114
+ x += 1
115
+ Elasticsearch::Transport::Transport::Response.new 200, 'OK'
116
+ end
117
+
118
+ assert_equal 1, x
119
+ end
120
+
121
+ should "deserialize a response JSON body" do
122
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
123
+ @transport.serializer.expects(:load).returns({'foo' => 'bar'})
124
+
125
+ response = @transport.perform_request 'GET', '/' do
126
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}'
127
+ end
128
+
129
+ assert_instance_of Elasticsearch::Transport::Transport::Response, response
130
+ assert_equal 'bar', response.body['foo']
131
+ end
132
+
133
+ should "not deserialize a response string body" do
134
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
135
+ @transport.serializer.expects(:load).never
136
+ response = @transport.perform_request 'GET', '/' do
137
+ Elasticsearch::Transport::Transport::Response.new 200, 'FOOBAR'
138
+ end
139
+
140
+ assert_instance_of Elasticsearch::Transport::Transport::Response, response
141
+ assert_equal 'FOOBAR', response.body
142
+ end
143
+
144
+ should "serialize non-String objects" do
145
+ @transport.serializer.expects(:dump).times(3)
146
+ @transport.__convert_to_json({:foo => 'bar'})
147
+ @transport.__convert_to_json([1, 2, 3])
148
+ @transport.__convert_to_json(nil)
149
+ end
150
+
151
+ should "not serialize a String object" do
152
+ @transport.serializer.expects(:dump).never
153
+ @transport.__convert_to_json('{"foo":"bar"}')
154
+ end
155
+
156
+ should "raise an error for HTTP status 404" do
157
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
158
+ assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
159
+ @transport.perform_request 'GET', '/' do
160
+ Elasticsearch::Transport::Transport::Response.new 404, 'NOT FOUND'
161
+ end
162
+ end
163
+ end
164
+
165
+ should "raise an error on server failure" do
166
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
167
+ assert_raise Elasticsearch::Transport::Transport::Errors::InternalServerError do
168
+ @transport.perform_request 'GET', '/' do
169
+ Elasticsearch::Transport::Transport::Response.new 500, 'ERROR'
170
+ end
171
+ end
172
+ end
173
+
174
+ should "raise an error on connection failure" do
175
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
176
+
177
+ # `block.expects(:call).raises(::Errno::ECONNREFUSED)` fails on Ruby 1.8
178
+ block = lambda { |a, b| raise ::Errno::ECONNREFUSED }
179
+
180
+ assert_raise ::Errno::ECONNREFUSED do
181
+ @transport.perform_request 'GET', '/', &block
182
+ end
183
+ end
184
+
185
+ should "mark the connection as dead on failure" do
186
+ c = stub_everything :failures => 1
187
+ @transport.expects(:get_connection).returns(c)
188
+
189
+ block = lambda { |a,b| raise ::Errno::ECONNREFUSED }
190
+
191
+ c.expects(:dead!)
192
+
193
+ assert_raise( ::Errno::ECONNREFUSED ) { @transport.perform_request 'GET', '/', &block }
194
+ end
195
+ end
196
+
197
+ context "performing a request with reload connections on connection failures" do
198
+ setup do
199
+ fake_collection = stub_everything :get_connection => stub_everything(:failures => 1),
200
+ :all => stub_everything(:size => 2)
201
+ @transport = DummyTransportPerformer.new :options => { :reload_on_failure => 2 }
202
+ @transport.stubs(:connections).
203
+ returns(fake_collection)
204
+ @block = lambda { |c, u| puts "UNREACHABLE" }
205
+ end
206
+
207
+ should "reload connections when host is unreachable" do
208
+ @block.expects(:call).times(2).
209
+ raises(Errno::ECONNREFUSED).
210
+ then.returns(stub_everything :failures => 1)
211
+
212
+ @transport.expects(:reload_connections!).returns([])
213
+
214
+ @transport.perform_request('GET', '/', &@block)
215
+ assert_equal 2, @transport.counter
216
+ end
217
+ end unless RUBY_1_8
218
+
219
+ context "performing a request with retry on connection failures" do
220
+ setup do
221
+ @transport = DummyTransportPerformer.new :options => { :retry_on_failure => true }
222
+ @transport.stubs(:connections).returns(stub :get_connection => stub_everything(:failures => 1))
223
+ @block = Proc.new { |c, u| puts "UNREACHABLE" }
224
+ end
225
+
226
+ should "retry DEFAULT_MAX_TRIES when host is unreachable" do
227
+ @block.expects(:call).times(3).
228
+ raises(Errno::ECONNREFUSED).
229
+ then.raises(Errno::ECONNREFUSED).
230
+ then.returns(stub_everything :failures => 1)
231
+
232
+ assert_nothing_raised do
233
+ @transport.perform_request('GET', '/', &@block)
234
+ assert_equal 3, @transport.counter
235
+ end
236
+ end
237
+
238
+ should "raise an error after max tries" do
239
+ @block.expects(:call).times(3).
240
+ raises(Errno::ECONNREFUSED).
241
+ then.raises(Errno::ECONNREFUSED).
242
+ then.raises(Errno::ECONNREFUSED).
243
+ then.raises(Errno::ECONNREFUSED)
244
+
245
+ assert_raise Errno::ECONNREFUSED do
246
+ @transport.perform_request('GET', '/', &@block)
247
+ end
248
+ end
249
+ end unless RUBY_1_8
250
+
251
+ context "logging" do
252
+ setup do
253
+ @transport = DummyTransportPerformer.new :options => { :logger => Logger.new('/dev/null') }
254
+ @transport.stubs(:get_connection).returns stub :full_url => 'localhost:9200/_search?size=1',
255
+ :host => 'localhost',
256
+ :failures => 0,
257
+ :healthy! => true
258
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
259
+ @transport.serializer.stubs(:dump).returns '{"foo":"bar"}'
260
+ end
261
+
262
+ should "log the request and response" do
263
+ @transport.logger.expects(:info). with "POST localhost:9200/_search?size=1 [status:200, request:0.000s, query:n/a]"
264
+ @transport.logger.expects(:debug). with '> {"foo":"bar"}'
265
+ @transport.logger.expects(:debug). with '< {"foo":"bar"}'
266
+
267
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:foo => 'bar'} do
268
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}'
269
+ end
270
+ end
271
+
272
+ should "log failed request" do
273
+ @block = Proc.new { |c, u| puts "ERROR" }
274
+ @block.expects(:call).returns(Elasticsearch::Transport::Transport::Response.new 500, 'ERROR')
275
+
276
+ @transport.logger.expects(:fatal)
277
+
278
+ assert_raise Elasticsearch::Transport::Transport::Errors::InternalServerError do
279
+ @transport.perform_request('POST', '_search', &@block)
280
+ end
281
+ end unless RUBY_1_8
282
+
283
+ should "log and re-raise exception" do
284
+ @block = Proc.new { |c, u| puts "ERROR" }
285
+ @block.expects(:call).raises(Exception)
286
+
287
+ @transport.logger.expects(:fatal)
288
+
289
+ assert_raise(Exception) { @transport.perform_request('POST', '_search', &@block) }
290
+ end unless RUBY_1_8
291
+ end
292
+
293
+ context "tracing" do
294
+ setup do
295
+ @transport = DummyTransportPerformer.new :options => { :tracer => Logger.new('/dev/null') }
296
+ @transport.stubs(:get_connection).returns stub :full_url => 'localhost:9200/_search?size=1',
297
+ :host => 'localhost',
298
+ :failures => 0,
299
+ :healthy! => true
300
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
301
+ @transport.serializer.stubs(:dump).returns <<-JSON.gsub(/^ /, '')
302
+ {
303
+ "foo" : {
304
+ "bar" : {
305
+ "bam" : true
306
+ }
307
+ }
308
+ }
309
+ JSON
310
+ end
311
+
312
+ should "trace the request" do
313
+ @transport.tracer.expects(:info). with do |message|
314
+ message == <<-CURL.gsub(/^ /, '')
315
+ curl -X POST 'http://localhost:9200/_search?pretty&size=1' -d '{
316
+ "foo" : {
317
+ "bar" : {
318
+ "bam" : true
319
+ }
320
+ }
321
+ }
322
+ '
323
+ CURL
324
+ end.once
325
+
326
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:q => 'foo'} do
327
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}'
328
+ end
329
+ end
330
+
331
+ end
332
+
333
+ context "reloading connections" do
334
+ setup do
335
+ @transport = DummyTransport.new :options => { :logger => Logger.new('/dev/null') }
336
+ end
337
+
338
+ should "rebuild connections" do
339
+ @transport.sniffer.expects(:hosts).returns([])
340
+ @transport.expects(:__rebuild_connections)
341
+ @transport.reload_connections!
342
+ end
343
+
344
+ should "log error and continue when timing out while sniffing hosts" do
345
+ @transport.sniffer.expects(:hosts).raises(Elasticsearch::Transport::Transport::SnifferTimeoutError)
346
+ @transport.logger.expects(:error)
347
+ assert_nothing_raised do
348
+ @transport.reload_connections!
349
+ end
350
+ end
351
+ end
352
+
353
+ context "rebuilding connections" do
354
+ setup do
355
+ @transport = DummyTransport.new
356
+ end
357
+
358
+ should "should replace the connections" do
359
+ assert_equal [], @transport.connections
360
+ @transport.__rebuild_connections :hosts => ['foo', 'bar']
361
+ assert_equal ['foo', 'bar'], @transport.connections
362
+ end
363
+ end
364
+
365
+ context "resurrecting connections" do
366
+ setup do
367
+ @transport = DummyTransportPerformer.new
368
+ end
369
+
370
+ should "delegate to dead connections" do
371
+ @transport.connections.expects(:dead).returns([])
372
+ @transport.resurrect_dead_connections!
373
+ end
374
+
375
+ should "not resurrect connections until timeout" do
376
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
377
+ @transport.expects(:resurrect_dead_connections!).never
378
+ 5.times { @transport.get_connection }
379
+ end
380
+
381
+ should "resurrect connections after timeout" do
382
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
383
+ @transport.expects(:resurrect_dead_connections!)
384
+
385
+ 4.times { @transport.get_connection }
386
+
387
+ now = Time.now + 60*2
388
+ Time.stubs(:now).returns(now)
389
+
390
+ @transport.get_connection
391
+ end
392
+
393
+ should "mark connection healthy if it succeeds" do
394
+ c = stub_everything(:failures => 1)
395
+ @transport.expects(:get_connection).returns(c)
396
+ c.expects(:healthy!)
397
+
398
+ @transport.perform_request('GET', '/') { |connection, url| Elasticsearch::Transport::Transport::Response.new 200, 'OK' }
399
+ end
400
+ end
401
+
402
+ end
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+ require 'elasticsearch/transport/transport/http/curb'
3
+ require 'curb'
4
+
5
+ class Elasticsearch::Transport::Transport::HTTP::FaradayTest < Test::Unit::TestCase
6
+ include Elasticsearch::Transport::Transport::HTTP
7
+
8
+ context "Curb transport" do
9
+ setup do
10
+ @transport = Curb.new :hosts => [ { :host => 'foobar', :port => 1234 } ]
11
+ end
12
+
13
+ should "implement __build_connections" do
14
+ assert_equal 1, @transport.hosts.size
15
+ assert_equal 1, @transport.connections.size
16
+
17
+ assert_instance_of ::Curl::Easy, @transport.connections.first.connection
18
+ assert_equal 'http://foobar:1234', @transport.connections.first.connection.url
19
+ end
20
+
21
+ should "perform the request" do
22
+ @transport.connections.first.connection.expects(:http).returns(stub_everything)
23
+ @transport.perform_request 'GET', '/'
24
+ end
25
+
26
+ should "serialize the request body" do
27
+ @transport.connections.first.connection.expects(:http_post).returns(stub_everything)
28
+ @transport.serializer.expects(:dump)
29
+ @transport.perform_request 'POST', '/', {}, {:foo => 'bar'}
30
+ end
31
+
32
+ should "not serialize a String request body" do
33
+ @transport.connections.first.connection.expects(:http_post).returns(stub_everything)
34
+ @transport.serializer.expects(:dump).never
35
+ @transport.perform_request 'POST', '/', {}, '{"foo":"bar"}'
36
+ end
37
+
38
+ should "handle HTTP methods" do
39
+ @transport.connections.first.connection.expects(:http).twice.returns(stub_everything)
40
+ @transport.connections.first.connection.expects(:http_put).returns(stub_everything)
41
+ @transport.connections.first.connection.expects(:http_post).returns(stub_everything)
42
+ @transport.connections.first.connection.expects(:http_delete).returns(stub_everything)
43
+
44
+ %w| HEAD GET PUT POST DELETE |.each { |method| @transport.perform_request method, '/' }
45
+
46
+ assert_raise(ArgumentError) { @transport.perform_request 'FOOBAR', '/' }
47
+ end
48
+
49
+ should "allow to set options for Curb" do
50
+ transport = Curb.new :hosts => [ { :host => 'foobar', :port => 1234 } ] do |curl|
51
+ curl.headers["User-Agent"] = "myapp-0.0"
52
+ end
53
+
54
+ assert_equal( {"Content-Type"=>"application/json", "User-Agent"=>"myapp-0.0"},
55
+ transport.connections.first.connection.headers )
56
+ end
57
+ end
58
+
59
+ end