elasticsearch-transport-pixlee 1.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE.txt +13 -0
  5. data/README.md +441 -0
  6. data/Rakefile +80 -0
  7. data/elasticsearch-transport.gemspec +74 -0
  8. data/lib/elasticsearch-transport.rb +1 -0
  9. data/lib/elasticsearch/transport.rb +30 -0
  10. data/lib/elasticsearch/transport/client.rb +195 -0
  11. data/lib/elasticsearch/transport/transport/base.rb +261 -0
  12. data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
  13. data/lib/elasticsearch/transport/transport/connections/connection.rb +121 -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 +87 -0
  17. data/lib/elasticsearch/transport/transport/http/faraday.rb +60 -0
  18. data/lib/elasticsearch/transport/transport/http/manticore.rb +124 -0
  19. data/lib/elasticsearch/transport/transport/response.rb +21 -0
  20. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
  21. data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
  22. data/lib/elasticsearch/transport/version.rb +5 -0
  23. data/test/integration/client_test.rb +144 -0
  24. data/test/integration/transport_test.rb +73 -0
  25. data/test/profile/client_benchmark_test.rb +125 -0
  26. data/test/test_helper.rb +76 -0
  27. data/test/unit/client_test.rb +274 -0
  28. data/test/unit/connection_collection_test.rb +88 -0
  29. data/test/unit/connection_selector_test.rb +64 -0
  30. data/test/unit/connection_test.rb +100 -0
  31. data/test/unit/response_test.rb +15 -0
  32. data/test/unit/serializer_test.rb +16 -0
  33. data/test/unit/sniffer_test.rb +145 -0
  34. data/test/unit/transport_base_test.rb +478 -0
  35. data/test/unit/transport_curb_test.rb +97 -0
  36. data/test/unit/transport_faraday_test.rb +140 -0
  37. data/test/unit/transport_manticore_test.rb +118 -0
  38. metadata +408 -0
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::Connections::CollectionTest < Test::Unit::TestCase
4
+ include Elasticsearch::Transport::Transport::Connections
5
+
6
+ context "Connection collection" do
7
+
8
+ should "have empty array as default connections array" do
9
+ assert_equal [], Collection.new.connections
10
+ end
11
+
12
+ should "have default selector class" do
13
+ assert_not_nil Collection.new.selector
14
+ end
15
+
16
+ should "initialize a custom selector class" do
17
+ c = Collection.new :selector_class => Selector::Random
18
+ assert_instance_of Selector::Random, c.selector
19
+ end
20
+
21
+ should "take a custom selector instance" do
22
+ c = Collection.new :selector => Selector::Random.new
23
+ assert_instance_of Selector::Random, c.selector
24
+ end
25
+
26
+ should "get connection from selector" do
27
+ c = Collection.new
28
+ c.selector.expects(:select).returns('OK')
29
+ assert_equal 'OK', c.get_connection
30
+ end
31
+
32
+ should "return an array of hosts" do
33
+ c = Collection.new :connections => [ Connection.new(:host => 'foo'), Connection.new(:host => 'bar') ]
34
+ assert_equal ['foo', 'bar'], c.hosts
35
+ end
36
+
37
+ should "be enumerable" do
38
+ c = Collection.new :connections => [ Connection.new(:host => 'foo'), Connection.new(:host => 'bar') ]
39
+
40
+ assert_equal ['FOO', 'BAR'], c.map { |i| i.host.upcase }
41
+ assert_equal 'foo', c[0].host
42
+ assert_equal 'bar', c[1].host
43
+ assert_equal 2, c.size
44
+ end
45
+
46
+ context "with the dead pool" do
47
+ setup do
48
+ @collection = Collection.new :connections => [ Connection.new(:host => 'foo'), Connection.new(:host => 'bar') ]
49
+ @collection[1].dead!
50
+ end
51
+
52
+ should "not iterate over dead connections" do
53
+ assert_equal 1, @collection.size
54
+ assert_equal ['FOO'], @collection.map { |c| c.host.upcase }
55
+ assert_equal @collection.connections, @collection.alive
56
+ end
57
+
58
+ should "have dead connections collection" do
59
+ assert_equal 1, @collection.dead.size
60
+ assert_equal ['BAR'], @collection.dead.map { |c| c.host.upcase }
61
+ end
62
+
63
+ should "not return dead connections, when alive connections exist" do
64
+ assert_equal 1, @collection.size
65
+ @collection.all.size.times { refute @collection.get_connection.dead? }
66
+ end
67
+
68
+ should "resurrect dead connection with least failures when no alive is available" do
69
+ c1 = Connection.new(:host => 'foo').dead!.dead!
70
+ c2 = Connection.new(:host => 'bar').dead!
71
+
72
+ @collection = Collection.new :connections => [ c1, c2 ]
73
+
74
+ assert_equal 0, @collection.size
75
+ assert_not_nil @collection.get_connection
76
+ assert_equal 1, @collection.size
77
+ assert_equal c2, @collection.first
78
+ end
79
+
80
+ should "return all connections" do
81
+ assert_equal 2, @collection.all.size
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::Connections::SelectorTest < Test::Unit::TestCase
4
+ include Elasticsearch::Transport::Transport::Connections::Selector
5
+
6
+ class DummyStrategySelector
7
+ include Elasticsearch::Transport::Transport::Connections::Selector::Base
8
+ end
9
+
10
+ class BackupStrategySelector
11
+ include Elasticsearch::Transport::Transport::Connections::Selector::Base
12
+
13
+ def select(options={})
14
+ connections.reject do |c|
15
+ c.host[:attributes] && c.host[:attributes][:backup]
16
+ end.send( defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' ? :sample : :choice)
17
+ end
18
+ end
19
+
20
+ context "Connection selector" do
21
+
22
+ should "be initialized with connections" do
23
+ assert_equal [1, 2], Random.new(:connections => [1, 2]).connections
24
+ end
25
+
26
+ should "have the abstract select method" do
27
+ assert_raise(NoMethodError) { DummyStrategySelector.new.select }
28
+ end
29
+
30
+ context "in random strategy" do
31
+ setup do
32
+ @selector = Random.new :connections => ['A', 'B', 'C']
33
+ end
34
+
35
+ should "pick a connection" do
36
+ assert_not_nil @selector.select
37
+ end
38
+ end
39
+
40
+ context "in round-robin strategy" do
41
+ setup do
42
+ @selector = RoundRobin.new :connections => ['A', 'B', 'C']
43
+ end
44
+
45
+ should "rotate over connections" do
46
+ assert_equal 'A', @selector.select
47
+ assert_equal 'B', @selector.select
48
+ assert_equal 'C', @selector.select
49
+ assert_equal 'A', @selector.select
50
+ end
51
+ end
52
+
53
+ context "with a custom strategy" do
54
+
55
+ should "return proper connection" do
56
+ selector = BackupStrategySelector.new :connections => [ stub(:host => { :hostname => 'host1' }),
57
+ stub(:host => { :hostname => 'host2', :attributes => { :backup => true }}) ]
58
+ 10.times { assert_equal 'host1', selector.select.host[:hostname] }
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,100 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::Connections::ConnectionTest < Test::Unit::TestCase
4
+ include Elasticsearch::Transport::Transport::Connections
5
+
6
+ context "Connection" do
7
+
8
+ should "be initialized with :host, :connection, and :options" do
9
+ c = Connection.new :host => 'x', :connection => 'y', :options => {}
10
+ assert_equal 'x', c.host
11
+ assert_equal 'y', c.connection
12
+ assert_instance_of Hash, c.options
13
+ end
14
+
15
+ should "return full path" do
16
+ c = Connection.new
17
+ assert_equal '_search', c.full_path('_search')
18
+ assert_equal '_search', c.full_path('_search', {})
19
+ assert_equal '_search?foo=bar', c.full_path('_search', {:foo => 'bar'})
20
+ assert_equal '_search?foo=bar+bam', c.full_path('_search', {:foo => 'bar bam'})
21
+ end
22
+
23
+ should "return full url" do
24
+ c = Connection.new :host => { :protocol => 'http', :host => 'localhost', :port => '9200' }
25
+ assert_equal 'http://localhost:9200/_search?foo=bar', c.full_url('_search', {:foo => 'bar'})
26
+ end
27
+
28
+ should "return full url with credentials" do
29
+ c = Connection.new :host => { :protocol => 'http', :user => 'U', :password => 'P', :host => 'localhost', :port => '9200' }
30
+ assert_equal 'http://U:P@localhost:9200/_search?foo=bar', c.full_url('_search', {:foo => 'bar'})
31
+ end
32
+
33
+ should "return full url with path" do
34
+ c = Connection.new :host => { :protocol => 'http', :host => 'localhost', :port => '9200', :path => '/foo' }
35
+ assert_equal 'http://localhost:9200/foo/_search?foo=bar', c.full_url('_search', {:foo => 'bar'})
36
+ end
37
+
38
+ should "have a string representation" do
39
+ c = Connection.new :host => 'x'
40
+ assert_match /host: x/, c.to_s
41
+ assert_match /alive/, c.to_s
42
+ end
43
+
44
+ should "not be dead by default" do
45
+ c = Connection.new
46
+ assert ! c.dead?
47
+ end
48
+
49
+ should "be dead when marked" do
50
+ c = Connection.new.dead!
51
+ assert c.dead?
52
+ assert_equal 1, c.failures
53
+ assert_in_delta c.dead_since, Time.now, 1
54
+ end
55
+
56
+ should "be alive when marked" do
57
+ c = Connection.new.dead!
58
+ assert c.dead?
59
+ assert_equal 1, c.failures
60
+ assert_in_delta c.dead_since, Time.now, 1
61
+
62
+ c.alive!
63
+ assert ! c.dead?
64
+ assert_equal 1, c.failures
65
+ end
66
+
67
+ should "be healthy when marked" do
68
+ c = Connection.new.dead!
69
+ assert c.dead?
70
+ assert_equal 1, c.failures
71
+ assert_in_delta c.dead_since, Time.now, 1
72
+
73
+ c.healthy!
74
+ assert ! c.dead?
75
+ assert_equal 0, c.failures
76
+ end
77
+
78
+ should "be resurrected if timeout passed" do
79
+ c = Connection.new.dead!
80
+
81
+ now = Time.now + 60
82
+ Time.stubs(:now).returns(now)
83
+
84
+ assert c.resurrect!, c.inspect
85
+ assert ! c.dead?, c.inspect
86
+ end
87
+
88
+ should "be resurrected if timeout passed for multiple failures" do
89
+ c = Connection.new.dead!.dead!
90
+
91
+ now = Time.now + 60*2
92
+ Time.stubs(:now).returns(now)
93
+
94
+ assert c.resurrect!, c.inspect
95
+ assert ! c.dead?, c.inspect
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::ResponseTest < Test::Unit::TestCase
4
+ context "Response" do
5
+
6
+ should "force-encode the body into UTF" do
7
+ body = "Hello Encoding!".encode(Encoding::ISO_8859_1)
8
+ assert_equal 'ISO-8859-1', body.encoding.name
9
+
10
+ response = Elasticsearch::Transport::Transport::Response.new 200, body
11
+ assert_equal 'UTF-8', response.body.encoding.name
12
+ end unless RUBY_1_8
13
+
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::SerializerTest < Test::Unit::TestCase
4
+
5
+ context "Serializer" do
6
+
7
+ should "use MultiJson by default" do
8
+ ::MultiJson.expects(:load)
9
+ ::MultiJson.expects(:dump)
10
+ Elasticsearch::Transport::Transport::Serializer::MultiJson.new.load('{}')
11
+ Elasticsearch::Transport::Transport::Serializer::MultiJson.new.dump({})
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,145 @@
1
+ require 'test_helper'
2
+
3
+ class Elasticsearch::Transport::Transport::SnifferTest < Test::Unit::TestCase
4
+
5
+ class DummyTransport
6
+ include Elasticsearch::Transport::Transport::Base
7
+ def __build_connections; hosts; end
8
+ end
9
+
10
+ def __nodes_info(json)
11
+ Elasticsearch::Transport::Transport::Response.new 200, MultiJson.load(json)
12
+ end
13
+
14
+ context "Sniffer" do
15
+ setup do
16
+ @transport = DummyTransport.new
17
+ @sniffer = Elasticsearch::Transport::Transport::Sniffer.new @transport
18
+ end
19
+
20
+ should "be initialized with a transport instance" do
21
+ assert_equal @transport, @sniffer.transport
22
+ end
23
+
24
+ should "return an array of hosts as hashes" do
25
+ @transport.expects(:perform_request).returns __nodes_info <<-JSON
26
+ {
27
+ "ok" : true,
28
+ "cluster_name" : "elasticsearch_test",
29
+ "nodes" : {
30
+ "N1" : {
31
+ "name" : "Node 1",
32
+ "transport_address" : "inet[/192.168.1.23:9300]",
33
+ "hostname" : "testhost1",
34
+ "version" : "0.20.6",
35
+ "http_address" : "inet[/192.168.1.23:9200]",
36
+ "thrift_address" : "/192.168.1.23:9500",
37
+ "memcached_address" : "inet[/192.168.1.23:11211]"
38
+ }
39
+ }
40
+ }
41
+ JSON
42
+
43
+ hosts = @sniffer.hosts
44
+
45
+ assert_equal 1, hosts.size
46
+ assert_equal '192.168.1.23', hosts.first[:host]
47
+ assert_equal '9200', hosts.first[:port]
48
+ assert_equal 'Node 1', hosts.first['name']
49
+ end
50
+
51
+ should "skip hosts without a matching transport protocol" do
52
+ @transport = DummyTransport.new :options => { :protocol => 'memcached' }
53
+ @sniffer = Elasticsearch::Transport::Transport::Sniffer.new @transport
54
+
55
+ @transport.expects(:perform_request).returns __nodes_info <<-JSON
56
+ {
57
+ "ok" : true,
58
+ "cluster_name" : "elasticsearch_test",
59
+ "nodes" : {
60
+ "N1" : {
61
+ "name" : "Memcached Node",
62
+ "http_address" : "inet[/192.168.1.23:9200]",
63
+ "memcached_address" : "inet[/192.168.1.23:11211]"
64
+ },
65
+ "N2" : {
66
+ "name" : "HTTP Node",
67
+ "http_address" : "inet[/192.168.1.23:9200]"
68
+ }
69
+ }
70
+ }
71
+ JSON
72
+
73
+ hosts = @sniffer.hosts
74
+
75
+ assert_equal 1, hosts.size
76
+ assert_equal '192.168.1.23', hosts.first[:host]
77
+ assert_equal '11211', hosts.first[:port]
78
+ assert_equal 'Memcached Node', hosts.first['name']
79
+ end
80
+
81
+ should "have configurable timeout" do
82
+ @transport = DummyTransport.new :options => { :sniffer_timeout => 0.001 }
83
+ @sniffer = Elasticsearch::Transport::Transport::Sniffer.new @transport
84
+ assert_equal 0.001, @sniffer.timeout
85
+ end
86
+
87
+ should "have settable timeout" do
88
+ @transport = DummyTransport.new
89
+ @sniffer = Elasticsearch::Transport::Transport::Sniffer.new @transport
90
+ assert_equal 1, @sniffer.timeout
91
+
92
+ @sniffer.timeout = 2
93
+ assert_equal 2, @sniffer.timeout
94
+ end
95
+
96
+ should "raise error on timeout" do
97
+ @transport.expects(:perform_request).raises(Elasticsearch::Transport::Transport::SnifferTimeoutError)
98
+
99
+ # TODO: Try to inject sleep into `perform_request` or make this test less ridiculous anyhow...
100
+ assert_raise Elasticsearch::Transport::Transport::SnifferTimeoutError do
101
+ @sniffer.hosts
102
+ end
103
+ end
104
+
105
+ should "randomize hosts" do
106
+ @transport = DummyTransport.new :options => { :randomize_hosts => true }
107
+ @sniffer = Elasticsearch::Transport::Transport::Sniffer.new @transport
108
+
109
+ @transport.expects(:perform_request).returns __nodes_info <<-JSON
110
+ {
111
+ "ok" : true,
112
+ "cluster_name" : "elasticsearch_test",
113
+ "nodes" : {
114
+ "N1" : {
115
+ "name" : "Node 1",
116
+ "http_address" : "inet[/192.168.1.23:9200]"
117
+ },
118
+ "N2" : {
119
+ "name" : "Node 2",
120
+ "http_address" : "inet[/192.168.1.23:9201]"
121
+ },
122
+ "N3" : {
123
+ "name" : "Node 3",
124
+ "http_address" : "inet[/192.168.1.23:9202]"
125
+ },
126
+ "N4" : {
127
+ "name" : "Node 4",
128
+ "http_address" : "inet[/192.168.1.23:9203]"
129
+ },
130
+ "N5" : {
131
+ "name" : "Node 5",
132
+ "http_address" : "inet[/192.168.1.23:9204]"
133
+ }
134
+ }
135
+ }
136
+ JSON
137
+
138
+ Array.any_instance.expects(:shuffle!)
139
+
140
+ hosts = @sniffer.hosts
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,478 @@
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
+
61
+ context "when combining the URL" do
62
+ setup do
63
+ @transport = DummyTransport.new
64
+ @basic_parts = { :protocol => 'http', :host => 'myhost', :port => 8080 }
65
+ end
66
+
67
+ should "combine basic parts" do
68
+ assert_equal 'http://myhost:8080', @transport.__full_url(@basic_parts)
69
+ end
70
+
71
+ should "combine path" do
72
+ assert_equal 'http://myhost:8080/api', @transport.__full_url(@basic_parts.merge :path => '/api')
73
+ end
74
+
75
+ should "combine authentication credentials" do
76
+ assert_equal 'http://U:P@myhost:8080', @transport.__full_url(@basic_parts.merge :user => 'U', :password => 'P')
77
+ end
78
+ end
79
+ end
80
+
81
+ context "getting a connection" do
82
+ setup do
83
+ @transport = DummyTransportPerformer.new :options => { :reload_connections => 5 }
84
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
85
+ @transport.stubs(:sniffer).returns(stub :hosts => [])
86
+ end
87
+
88
+ should "get a connection" do
89
+ assert_not_nil @transport.get_connection
90
+ end
91
+
92
+ should "increment the counter" do
93
+ assert_equal 0, @transport.counter
94
+ 3.times { @transport.get_connection }
95
+ assert_equal 3, @transport.counter
96
+ end
97
+
98
+ should "reload connections when it hits the threshold" do
99
+ @transport.expects(:reload_connections!).twice
100
+ 12.times { @transport.get_connection }
101
+ assert_equal 12, @transport.counter
102
+ end
103
+
104
+ should "not reload connections by default" do
105
+ @transport = DummyTransportPerformer.new
106
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
107
+ @transport.expects(:reload_connections!).never
108
+
109
+ 10_010.times { @transport.get_connection }
110
+ assert_equal 10_010, @transport.counter
111
+ end
112
+
113
+ should "not reload connections when the option is set to false" do
114
+ @transport = DummyTransportPerformer.new :options => { :reload_connections => false }
115
+ @transport.stubs(:connections).returns(stub :get_connection => Object.new)
116
+ @transport.expects(:reload_connections!).never
117
+
118
+ 10_010.times { @transport.get_connection }
119
+ assert_equal 10_010, @transport.counter
120
+ end
121
+ end
122
+
123
+ context "performing a request" do
124
+ setup do
125
+ @transport = DummyTransportPerformer.new
126
+ end
127
+
128
+ should "raise an error when no block is passed" do
129
+ assert_raise NoMethodError do
130
+ @transport.peform_request 'GET', '/'
131
+ end
132
+ end
133
+
134
+ should "get the connection" do
135
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
136
+ @transport.perform_request 'GET', '/' do; Elasticsearch::Transport::Transport::Response.new 200, 'OK'; end
137
+ end
138
+
139
+ should "raise an error when no connection is available" do
140
+ @transport.expects(:get_connection).returns(nil)
141
+ assert_raise Elasticsearch::Transport::Transport::Error do
142
+ @transport.perform_request 'GET', '/' do; Elasticsearch::Transport::Transport::Response.new 200, 'OK'; end
143
+ end
144
+ end
145
+
146
+ should "call the passed block" do
147
+ x = 0
148
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
149
+
150
+ @transport.perform_request 'GET', '/' do |connection, url|
151
+ x += 1
152
+ Elasticsearch::Transport::Transport::Response.new 200, 'OK'
153
+ end
154
+
155
+ assert_equal 1, x
156
+ end
157
+
158
+ should "deserialize a response JSON body" do
159
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
160
+ @transport.serializer.expects(:load).returns({'foo' => 'bar'})
161
+
162
+ response = @transport.perform_request 'GET', '/' do
163
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}', {"content-type" => 'application/json'}
164
+ end
165
+
166
+ assert_instance_of Elasticsearch::Transport::Transport::Response, response
167
+ assert_equal 'bar', response.body['foo']
168
+ end
169
+
170
+ should "not deserialize a response string body" do
171
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
172
+ @transport.serializer.expects(:load).never
173
+ response = @transport.perform_request 'GET', '/' do
174
+ Elasticsearch::Transport::Transport::Response.new 200, 'FOOBAR', {"content-type" => 'text/plain'}
175
+ end
176
+
177
+ assert_instance_of Elasticsearch::Transport::Transport::Response, response
178
+ assert_equal 'FOOBAR', response.body
179
+ end
180
+
181
+ should "serialize non-String objects" do
182
+ @transport.serializer.expects(:dump).times(3)
183
+ @transport.__convert_to_json({:foo => 'bar'})
184
+ @transport.__convert_to_json([1, 2, 3])
185
+ @transport.__convert_to_json(nil)
186
+ end
187
+
188
+ should "not serialize a String object" do
189
+ @transport.serializer.expects(:dump).never
190
+ @transport.__convert_to_json('{"foo":"bar"}')
191
+ end
192
+
193
+ should "raise an error for HTTP status 404" do
194
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
195
+ assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
196
+ @transport.perform_request 'GET', '/' do
197
+ Elasticsearch::Transport::Transport::Response.new 404, 'NOT FOUND'
198
+ end
199
+ end
200
+ end
201
+
202
+ should "raise an error for HTTP status 404 with application/json content type" do
203
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
204
+ assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
205
+ @transport.perform_request 'GET', '/' do
206
+ Elasticsearch::Transport::Transport::Response.new 404, 'NOT FOUND', {"content-type" => 'application/json'}
207
+ end
208
+ end
209
+ end
210
+
211
+ should "raise an error on server failure" do
212
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
213
+ assert_raise Elasticsearch::Transport::Transport::Errors::InternalServerError do
214
+ @transport.perform_request 'GET', '/' do
215
+ Elasticsearch::Transport::Transport::Response.new 500, 'ERROR'
216
+ end
217
+ end
218
+ end
219
+
220
+ should "raise an error on connection failure" do
221
+ @transport.expects(:get_connection).returns(stub_everything :failures => 1)
222
+
223
+ # `block.expects(:call).raises(::Errno::ECONNREFUSED)` fails on Ruby 1.8
224
+ block = lambda { |a, b| raise ::Errno::ECONNREFUSED }
225
+
226
+ assert_raise ::Errno::ECONNREFUSED do
227
+ @transport.perform_request 'GET', '/', &block
228
+ end
229
+ end
230
+
231
+ should "mark the connection as dead on failure" do
232
+ c = stub_everything :failures => 1
233
+ @transport.expects(:get_connection).returns(c)
234
+
235
+ block = lambda { |a,b| raise ::Errno::ECONNREFUSED }
236
+
237
+ c.expects(:dead!)
238
+
239
+ assert_raise( ::Errno::ECONNREFUSED ) { @transport.perform_request 'GET', '/', &block }
240
+ end
241
+ end
242
+
243
+ context "performing a request with reload connections on connection failures" do
244
+ setup do
245
+ fake_collection = stub_everything :get_connection => stub_everything(:failures => 1),
246
+ :all => stub_everything(:size => 2)
247
+ @transport = DummyTransportPerformer.new :options => { :reload_on_failure => 2 }
248
+ @transport.stubs(:connections).
249
+ returns(fake_collection)
250
+ @block = lambda { |c, u| puts "UNREACHABLE" }
251
+ end
252
+
253
+ should "reload connections when host is unreachable" do
254
+ @block.expects(:call).times(2).
255
+ raises(Errno::ECONNREFUSED).
256
+ then.returns(stub_everything :failures => 1)
257
+
258
+ @transport.expects(:reload_connections!).returns([])
259
+
260
+ @transport.perform_request('GET', '/', &@block)
261
+ assert_equal 2, @transport.counter
262
+ end
263
+ end unless RUBY_1_8
264
+
265
+ context "performing a request with retry on connection failures" do
266
+ setup do
267
+ @transport = DummyTransportPerformer.new :options => { :retry_on_failure => true }
268
+ @transport.stubs(:connections).returns(stub :get_connection => stub_everything(:failures => 1))
269
+ @block = Proc.new { |c, u| puts "UNREACHABLE" }
270
+ end
271
+
272
+ should "retry DEFAULT_MAX_RETRIES when host is unreachable" do
273
+ @block.expects(:call).times(4).
274
+ raises(Errno::ECONNREFUSED).
275
+ then.raises(Errno::ECONNREFUSED).
276
+ then.raises(Errno::ECONNREFUSED).
277
+ then.returns(stub_everything :failures => 1)
278
+
279
+ assert_nothing_raised do
280
+ @transport.perform_request('GET', '/', &@block)
281
+ assert_equal 4, @transport.counter
282
+ end
283
+ end
284
+
285
+ should "raise an error after max tries" do
286
+ @block.expects(:call).times(4).
287
+ raises(Errno::ECONNREFUSED).
288
+ then.raises(Errno::ECONNREFUSED).
289
+ then.raises(Errno::ECONNREFUSED).
290
+ then.raises(Errno::ECONNREFUSED).
291
+ then.returns(stub_everything :failures => 1)
292
+
293
+ assert_raise Errno::ECONNREFUSED do
294
+ @transport.perform_request('GET', '/', &@block)
295
+ end
296
+ end
297
+ end unless RUBY_1_8
298
+
299
+ context "logging" do
300
+ setup do
301
+ @transport = DummyTransportPerformer.new :options => { :logger => Logger.new('/dev/null') }
302
+
303
+ fake_connection = stub :full_url => 'localhost:9200/_search?size=1',
304
+ :host => 'localhost',
305
+ :connection => stub_everything,
306
+ :failures => 0,
307
+ :healthy! => true
308
+
309
+ @transport.stubs(:get_connection).returns(fake_connection)
310
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
311
+ @transport.serializer.stubs(:dump).returns '{"foo":"bar"}'
312
+ end
313
+
314
+ should "log the request and response" do
315
+ @transport.logger.expects(:info). with do |line|
316
+ line =~ %r|POST localhost\:9200/_search\?size=1 \[status\:200, request:.*s, query:n/a\]|
317
+ end
318
+ @transport.logger.expects(:debug). with '> {"foo":"bar"}'
319
+ @transport.logger.expects(:debug). with '< {"foo":"bar"}'
320
+
321
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:foo => 'bar'} do
322
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}'
323
+ end
324
+ end
325
+
326
+ should "log a failed Elasticsearch request" do
327
+ @block = Proc.new { |c, u| puts "ERROR" }
328
+ @block.expects(:call).returns(Elasticsearch::Transport::Transport::Response.new 500, 'ERROR')
329
+
330
+ @transport.expects(:__log)
331
+ @transport.logger.expects(:fatal)
332
+
333
+ assert_raise Elasticsearch::Transport::Transport::Errors::InternalServerError do
334
+ @transport.perform_request('POST', '_search', &@block)
335
+ end
336
+ end unless RUBY_1_8
337
+
338
+ should "log and re-raise a Ruby exception" do
339
+ @block = Proc.new { |c, u| puts "ERROR" }
340
+ @block.expects(:call).raises(Exception)
341
+
342
+ @transport.expects(:__log).never
343
+ @transport.logger.expects(:fatal)
344
+
345
+ assert_raise(Exception) { @transport.perform_request('POST', '_search', &@block) }
346
+ end unless RUBY_1_8
347
+ end
348
+
349
+ context "tracing" do
350
+ setup do
351
+ @transport = DummyTransportPerformer.new :options => { :tracer => Logger.new('/dev/null') }
352
+
353
+ fake_connection = stub :full_url => 'localhost:9200/_search?size=1',
354
+ :host => 'localhost',
355
+ :connection => stub_everything,
356
+ :failures => 0,
357
+ :healthy! => true
358
+
359
+ @transport.stubs(:get_connection).returns(fake_connection)
360
+ @transport.serializer.stubs(:load).returns 'foo' => 'bar'
361
+ @transport.serializer.stubs(:dump).returns <<-JSON.gsub(/^ /, '')
362
+ {
363
+ "foo" : {
364
+ "bar" : {
365
+ "bam" : true
366
+ }
367
+ }
368
+ }
369
+ JSON
370
+ end
371
+
372
+ should "trace the request" do
373
+ @transport.tracer.expects(:info). with do |message|
374
+ message == <<-CURL.gsub(/^ /, '')
375
+ curl -X POST 'http://localhost:9200/_search?pretty&size=1' -d '{
376
+ "foo" : {
377
+ "bar" : {
378
+ "bam" : true
379
+ }
380
+ }
381
+ }
382
+ '
383
+ CURL
384
+ end.once
385
+
386
+ @transport.perform_request 'POST', '_search', {:size => 1}, {:q => 'foo'} do
387
+ Elasticsearch::Transport::Transport::Response.new 200, '{"foo":"bar"}'
388
+ end
389
+ end
390
+
391
+ should "trace a failed Elasticsearch request" do
392
+ @block = Proc.new { |c, u| puts "ERROR" }
393
+ @block.expects(:call).returns(Elasticsearch::Transport::Transport::Response.new 500, 'ERROR')
394
+
395
+ @transport.expects(:__trace)
396
+
397
+ assert_raise Elasticsearch::Transport::Transport::Errors::InternalServerError do
398
+ @transport.perform_request('POST', '_search', &@block)
399
+ end
400
+ end unless RUBY_1_8
401
+
402
+ end
403
+
404
+ context "reloading connections" do
405
+ setup do
406
+ @transport = DummyTransport.new :options => { :logger => Logger.new('/dev/null') }
407
+ end
408
+
409
+ should "rebuild connections" do
410
+ @transport.sniffer.expects(:hosts).returns([])
411
+ @transport.expects(:__rebuild_connections)
412
+ @transport.reload_connections!
413
+ end
414
+
415
+ should "log error and continue when timing out while sniffing hosts" do
416
+ @transport.sniffer.expects(:hosts).raises(Elasticsearch::Transport::Transport::SnifferTimeoutError)
417
+ @transport.logger.expects(:error)
418
+ assert_nothing_raised do
419
+ @transport.reload_connections!
420
+ end
421
+ end
422
+ end
423
+
424
+ context "rebuilding connections" do
425
+ setup do
426
+ @transport = DummyTransport.new
427
+ end
428
+
429
+ should "should replace the connections" do
430
+ assert_equal [], @transport.connections
431
+ @transport.__rebuild_connections :hosts => ['foo', 'bar']
432
+ assert_equal ['foo', 'bar'], @transport.connections
433
+ end
434
+ end
435
+
436
+ context "resurrecting connections" do
437
+ setup do
438
+ @transport = DummyTransportPerformer.new
439
+ end
440
+
441
+ should "delegate to dead connections" do
442
+ @transport.connections.expects(:dead).returns([])
443
+ @transport.resurrect_dead_connections!
444
+ end
445
+
446
+ should "not resurrect connections until timeout" do
447
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
448
+ @transport.expects(:resurrect_dead_connections!).never
449
+ 5.times { @transport.get_connection }
450
+ end
451
+
452
+ should "resurrect connections after timeout" do
453
+ @transport.connections.expects(:get_connection).returns(stub_everything :failures => 1).times(5)
454
+ @transport.expects(:resurrect_dead_connections!)
455
+
456
+ 4.times { @transport.get_connection }
457
+
458
+ now = Time.now + 60*2
459
+ Time.stubs(:now).returns(now)
460
+
461
+ @transport.get_connection
462
+ end
463
+
464
+ should "mark connection healthy if it succeeds" do
465
+ c = stub_everything(:failures => 1)
466
+ @transport.expects(:get_connection).returns(c)
467
+ c.expects(:healthy!)
468
+
469
+ @transport.perform_request('GET', '/') { |connection, url| Elasticsearch::Transport::Transport::Response.new 200, 'OK' }
470
+ end
471
+ end
472
+
473
+ context "errors" do
474
+ should "raise highest-level Error exception for any ServerError" do
475
+ assert_kind_of Elasticsearch::Transport::Transport::Error, Elasticsearch::Transport::Transport::ServerError.new
476
+ end
477
+ end
478
+ end