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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +13 -0
- data/README.md +441 -0
- data/Rakefile +80 -0
- data/elasticsearch-transport.gemspec +74 -0
- data/lib/elasticsearch-transport.rb +1 -0
- data/lib/elasticsearch/transport.rb +30 -0
- data/lib/elasticsearch/transport/client.rb +195 -0
- data/lib/elasticsearch/transport/transport/base.rb +261 -0
- data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
- data/lib/elasticsearch/transport/transport/connections/connection.rb +121 -0
- data/lib/elasticsearch/transport/transport/connections/selector.rb +63 -0
- data/lib/elasticsearch/transport/transport/errors.rb +73 -0
- data/lib/elasticsearch/transport/transport/http/curb.rb +87 -0
- data/lib/elasticsearch/transport/transport/http/faraday.rb +60 -0
- data/lib/elasticsearch/transport/transport/http/manticore.rb +124 -0
- data/lib/elasticsearch/transport/transport/response.rb +21 -0
- data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
- data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
- data/lib/elasticsearch/transport/version.rb +5 -0
- data/test/integration/client_test.rb +144 -0
- data/test/integration/transport_test.rb +73 -0
- data/test/profile/client_benchmark_test.rb +125 -0
- data/test/test_helper.rb +76 -0
- data/test/unit/client_test.rb +274 -0
- data/test/unit/connection_collection_test.rb +88 -0
- data/test/unit/connection_selector_test.rb +64 -0
- data/test/unit/connection_test.rb +100 -0
- data/test/unit/response_test.rb +15 -0
- data/test/unit/serializer_test.rb +16 -0
- data/test/unit/sniffer_test.rb +145 -0
- data/test/unit/transport_base_test.rb +478 -0
- data/test/unit/transport_curb_test.rb +97 -0
- data/test/unit/transport_faraday_test.rb +140 -0
- data/test/unit/transport_manticore_test.rb +118 -0
- 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
|