elasticsearch-transport 0.0.2

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 (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