elasticsearch-transport-pixlee 1.0.13

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