elasticsearch-transport-sinneduy 1.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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 +284 -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,124 @@
|
|
1
|
+
require 'manticore'
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module Transport
|
5
|
+
module Transport
|
6
|
+
module HTTP
|
7
|
+
# Alternative HTTP transport implementation for JRuby,
|
8
|
+
# using the [_Manticore_](https://github.com/cheald/manticore) client,
|
9
|
+
#
|
10
|
+
# @example HTTP
|
11
|
+
#
|
12
|
+
# require 'elasticsearch/transport/transport/http/manticore'
|
13
|
+
#
|
14
|
+
# client = Elasticsearch::Client.new transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore
|
15
|
+
#
|
16
|
+
# client.transport.connections.first.connection
|
17
|
+
# => #<Manticore::Client:0x56bf7ca6 ...>
|
18
|
+
#
|
19
|
+
# client.info['status']
|
20
|
+
# => 200
|
21
|
+
#
|
22
|
+
# @example HTTPS (All SSL settings are optional,
|
23
|
+
# see http://www.rubydoc.info/gems/manticore/Manticore/Client:initialize)
|
24
|
+
#
|
25
|
+
# require 'elasticsearch/transport/transport/http/manticore'
|
26
|
+
#
|
27
|
+
# client = Elasticsearch::Client.new \
|
28
|
+
# url: 'https://elasticsearch.example.com',
|
29
|
+
# transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore,
|
30
|
+
# ssl: {
|
31
|
+
# truststore: '/tmp/truststore.jks',
|
32
|
+
# truststore_password: 'password',
|
33
|
+
# keystore: '/tmp/keystore.jks',
|
34
|
+
# keystore_password: 'secret',
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# client.transport.connections.first.connection
|
38
|
+
# => #<Manticore::Client:0xdeadbeef ...>
|
39
|
+
#
|
40
|
+
# client.info['status']
|
41
|
+
# => 200
|
42
|
+
#
|
43
|
+
# @see Transport::Base
|
44
|
+
#
|
45
|
+
class Manticore
|
46
|
+
include Base
|
47
|
+
|
48
|
+
# Performs the request by invoking {Transport::Base#perform_request} with a block.
|
49
|
+
#
|
50
|
+
# @return [Response]
|
51
|
+
# @see Transport::Base#perform_request
|
52
|
+
#
|
53
|
+
def perform_request(method, path, params={}, body=nil)
|
54
|
+
super do |connection, url|
|
55
|
+
params[:body] = __convert_to_json(body) if body
|
56
|
+
params = params.merge @request_options
|
57
|
+
case method
|
58
|
+
when "GET"
|
59
|
+
resp = connection.connection.get(url, params)
|
60
|
+
when "HEAD"
|
61
|
+
resp = connection.connection.head(url, params)
|
62
|
+
when "PUT"
|
63
|
+
resp = connection.connection.put(url, params)
|
64
|
+
when "POST"
|
65
|
+
resp = connection.connection.post(url, params)
|
66
|
+
when "DELETE"
|
67
|
+
resp = connection.connection.delete(url, params)
|
68
|
+
else
|
69
|
+
raise ArgumentError.new "Method #{method} not supported"
|
70
|
+
end
|
71
|
+
Response.new resp.code, resp.read_body, resp.headers
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Builds and returns a collection of connections.
|
76
|
+
# Each connection is a Manticore::Client
|
77
|
+
#
|
78
|
+
# @return [Connections::Collection]
|
79
|
+
#
|
80
|
+
def __build_connections
|
81
|
+
@request_options = {}
|
82
|
+
|
83
|
+
if options.key?(:headers)
|
84
|
+
@request_options[:headers] = options[:headers]
|
85
|
+
end
|
86
|
+
|
87
|
+
client_options = options[:transport_options] || {}
|
88
|
+
client_options[:ssl] = options[:ssl] || {}
|
89
|
+
|
90
|
+
Connections::Collection.new \
|
91
|
+
:connections => hosts.map { |host|
|
92
|
+
host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
|
93
|
+
host[:port] ||= DEFAULT_PORT
|
94
|
+
|
95
|
+
host.delete(:user) # auth is not supported here.
|
96
|
+
host.delete(:password) # use the headers
|
97
|
+
|
98
|
+
url = __full_url(host)
|
99
|
+
|
100
|
+
Connections::Connection.new \
|
101
|
+
:host => host,
|
102
|
+
:connection => ::Manticore::Client.new(client_options)
|
103
|
+
},
|
104
|
+
:selector_class => options[:selector_class],
|
105
|
+
:selector => options[:selector]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns an array of implementation specific connection errors.
|
109
|
+
#
|
110
|
+
# @return [Array]
|
111
|
+
#
|
112
|
+
def host_unreachable_exceptions
|
113
|
+
[
|
114
|
+
::Manticore::Timeout,
|
115
|
+
::Manticore::SocketException,
|
116
|
+
::Manticore::ClientProtocolException,
|
117
|
+
::Manticore::ResolutionFailure
|
118
|
+
]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# Wraps the response from Elasticsearch.
|
6
|
+
#
|
7
|
+
class Response
|
8
|
+
attr_reader :status, :body, :headers
|
9
|
+
|
10
|
+
# @param status [Integer] Response status code
|
11
|
+
# @param body [String] Response body
|
12
|
+
# @param headers [Hash] Response headers
|
13
|
+
def initialize(status, body, headers={})
|
14
|
+
@status, @body, @headers = status, body, headers
|
15
|
+
@body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
module Transport
|
4
|
+
module Serializer
|
5
|
+
|
6
|
+
# An abstract class for implementing serializer implementations
|
7
|
+
#
|
8
|
+
module Base
|
9
|
+
# @param transport [Object] The instance of transport which uses this serializer
|
10
|
+
#
|
11
|
+
def initialize(transport=nil)
|
12
|
+
@transport = transport
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# A default JSON serializer (using [MultiJSON](http://rubygems.org/gems/multi_json))
|
17
|
+
#
|
18
|
+
class MultiJson
|
19
|
+
include Base
|
20
|
+
|
21
|
+
# De-serialize a Hash from JSON string
|
22
|
+
#
|
23
|
+
def load(string, options={})
|
24
|
+
::MultiJson.load(string, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Serialize a Hash to JSON string
|
28
|
+
#
|
29
|
+
def dump(object, options={})
|
30
|
+
::MultiJson.dump(object, options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# Handles node discovery ("sniffing").
|
6
|
+
#
|
7
|
+
class Sniffer
|
8
|
+
RE_URL = /\/([^:]*):([0-9]+)\]/ # Use named groups on Ruby 1.9: /\/(?<host>[^:]*):(?<port>[0-9]+)\]/
|
9
|
+
|
10
|
+
attr_reader :transport
|
11
|
+
attr_accessor :timeout
|
12
|
+
|
13
|
+
# @param transport [Object] A transport instance.
|
14
|
+
#
|
15
|
+
def initialize(transport)
|
16
|
+
@transport = transport
|
17
|
+
@timeout = transport.options[:sniffer_timeout] || 1
|
18
|
+
end
|
19
|
+
|
20
|
+
# Retrieves the node list from the Elasticsearch's
|
21
|
+
# [_Nodes Info API_](http://www.elasticsearch.org/guide/reference/api/admin-cluster-nodes-info/)
|
22
|
+
# and returns a normalized Array of information suitable for passing to transport.
|
23
|
+
#
|
24
|
+
# Shuffles the collection before returning it when the `randomize_hosts` option is set for transport.
|
25
|
+
#
|
26
|
+
# @return [Array<Hash>]
|
27
|
+
# @raise [SnifferTimeoutError]
|
28
|
+
#
|
29
|
+
def hosts
|
30
|
+
Timeout::timeout(timeout, SnifferTimeoutError) do
|
31
|
+
nodes = transport.perform_request('GET', '_nodes/http').body
|
32
|
+
hosts = nodes['nodes'].map do |id,info|
|
33
|
+
if matches = info["#{transport.protocol}_address"].to_s.match(RE_URL)
|
34
|
+
# TODO: Implement lightweight "indifferent access" here
|
35
|
+
info.merge :host => matches[1], :port => matches[2], :id => id
|
36
|
+
end
|
37
|
+
end.compact
|
38
|
+
|
39
|
+
hosts.shuffle! if transport.options[:randomize_hosts]
|
40
|
+
hosts
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
|
4
|
+
startup do
|
5
|
+
Elasticsearch::Extensions::Test::Cluster.start(nodes: 2) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running?
|
6
|
+
end
|
7
|
+
|
8
|
+
context "Elasticsearch client" do
|
9
|
+
teardown do
|
10
|
+
begin; Object.send(:remove_const, :Typhoeus); rescue NameError; end
|
11
|
+
begin; Object.send(:remove_const, :Patron); rescue NameError; end
|
12
|
+
end
|
13
|
+
|
14
|
+
setup do
|
15
|
+
@port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
|
16
|
+
system "curl -X DELETE http://localhost:#{@port}/_all > /dev/null 2>&1"
|
17
|
+
|
18
|
+
@logger = Logger.new(STDERR)
|
19
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
20
|
+
color = case severity
|
21
|
+
when /INFO/ then :green
|
22
|
+
when /ERROR|WARN|FATAL/ then :red
|
23
|
+
when /DEBUG/ then :cyan
|
24
|
+
else :white
|
25
|
+
end
|
26
|
+
ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
@client = Elasticsearch::Client.new host: "localhost:#{@port}"
|
30
|
+
end
|
31
|
+
|
32
|
+
should "connect to the cluster" do
|
33
|
+
assert_nothing_raised do
|
34
|
+
response = @client.perform_request 'GET', '_cluster/health'
|
35
|
+
assert_equal 2, response.body['number_of_nodes']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
should "handle paths and URL parameters" do
|
40
|
+
@client.perform_request 'PUT', 'myindex/mydoc/1', {routing: 'XYZ'}, {foo: 'bar'}
|
41
|
+
@client.perform_request 'GET', '_cluster/health?wait_for_status=green', {}
|
42
|
+
|
43
|
+
response = @client.perform_request 'GET', 'myindex/mydoc/1?routing=XYZ'
|
44
|
+
assert_equal 200, response.status
|
45
|
+
assert_equal 'bar', response.body['_source']['foo']
|
46
|
+
|
47
|
+
assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do
|
48
|
+
@client.perform_request 'GET', 'myindex/mydoc/1?routing=ABC'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
should "pass options to the transport" do
|
53
|
+
@client = Elasticsearch::Client.new \
|
54
|
+
host: "localhost:#{@port}",
|
55
|
+
logger: (ENV['QUIET'] ? nil : @logger),
|
56
|
+
transport_options: { headers: { content_type: 'application/yaml' } }
|
57
|
+
|
58
|
+
response = @client.perform_request 'GET', '_cluster/health'
|
59
|
+
assert_match /---\ncluster_name:/, response.body.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with round robin selector" do
|
63
|
+
setup do
|
64
|
+
@client = Elasticsearch::Client.new \
|
65
|
+
hosts: ["localhost:#{@port}", "localhost:#{@port+1}" ],
|
66
|
+
logger: (ENV['QUIET'] ? nil : @logger)
|
67
|
+
end
|
68
|
+
|
69
|
+
should "rotate nodes" do
|
70
|
+
# Hit node 1
|
71
|
+
response = @client.perform_request 'GET', '_nodes/_local'
|
72
|
+
assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
|
73
|
+
|
74
|
+
# Hit node 2
|
75
|
+
response = @client.perform_request 'GET', '_nodes/_local'
|
76
|
+
assert_equal 'node-2', response.body['nodes'].to_a[0][1]['name']
|
77
|
+
|
78
|
+
# Hit node 1
|
79
|
+
response = @client.perform_request 'GET', '_nodes/_local'
|
80
|
+
assert_equal 'node-1', response.body['nodes'].to_a[0][1]['name']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with a sick node and retry on failure" do
|
85
|
+
setup do
|
86
|
+
@port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
|
87
|
+
@client = Elasticsearch::Client.new \
|
88
|
+
hosts: ["localhost:#{@port}", "foobar1"],
|
89
|
+
logger: (ENV['QUIET'] ? nil : @logger),
|
90
|
+
retry_on_failure: true
|
91
|
+
end
|
92
|
+
|
93
|
+
should "retry the request with next server" do
|
94
|
+
assert_nothing_raised do
|
95
|
+
5.times { @client.perform_request 'GET', '_nodes/_local' }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
should "raise exception when it cannot get any healthy server" do
|
100
|
+
@client = Elasticsearch::Client.new \
|
101
|
+
hosts: ["localhost:#{@port}", "foobar1", "foobar2", "foobar3"],
|
102
|
+
logger: (ENV['QUIET'] ? nil : @logger),
|
103
|
+
retry_on_failure: 1
|
104
|
+
|
105
|
+
assert_nothing_raised do
|
106
|
+
# First hit is OK
|
107
|
+
@client.perform_request 'GET', '_nodes/_local'
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_raise Faraday::Error::ConnectionFailed do
|
111
|
+
# Second hit fails
|
112
|
+
@client.perform_request 'GET', '_nodes/_local'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "with a sick node and reloading on failure" do
|
118
|
+
setup do
|
119
|
+
@client = Elasticsearch::Client.new \
|
120
|
+
hosts: ["localhost:#{@port}", "foobar1", "foobar2"],
|
121
|
+
logger: (ENV['QUIET'] ? nil : @logger),
|
122
|
+
reload_on_failure: true
|
123
|
+
end
|
124
|
+
|
125
|
+
should "reload the connections" do
|
126
|
+
assert_equal 3, @client.transport.connections.size
|
127
|
+
assert_nothing_raised do
|
128
|
+
5.times { @client.perform_request 'GET', '_nodes/_local' }
|
129
|
+
end
|
130
|
+
assert_equal 2, @client.transport.connections.size
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "with Faraday adapters" do
|
135
|
+
should "automatically use the Patron client when loaded" do
|
136
|
+
require 'patron'
|
137
|
+
client = Elasticsearch::Transport::Client.new host: "localhost:#{@port}"
|
138
|
+
|
139
|
+
response = @client.perform_request 'GET', '_cluster/health'
|
140
|
+
assert_equal 200, response.status
|
141
|
+
end unless JRUBY
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Elasticsearch::Transport::ClientIntegrationTest < Elasticsearch::Test::IntegrationTestCase
|
4
|
+
startup do
|
5
|
+
Elasticsearch::Extensions::Test::Cluster.start(nodes: 2) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running?
|
6
|
+
end
|
7
|
+
|
8
|
+
context "Transport" do
|
9
|
+
setup do
|
10
|
+
@port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
|
11
|
+
begin; Object.send(:remove_const, :Patron); rescue NameError; end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "allow to customize the Faraday adapter" do
|
15
|
+
require 'typhoeus'
|
16
|
+
require 'typhoeus/adapters/faraday'
|
17
|
+
|
18
|
+
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
|
19
|
+
:hosts => [ { :host => 'localhost', :port => @port } ] do |f|
|
20
|
+
f.response :logger
|
21
|
+
f.adapter :typhoeus
|
22
|
+
end
|
23
|
+
|
24
|
+
client = Elasticsearch::Transport::Client.new transport: transport
|
25
|
+
client.perform_request 'GET', ''
|
26
|
+
end
|
27
|
+
|
28
|
+
should "allow to define connection parameters and pass them" do
|
29
|
+
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new \
|
30
|
+
:hosts => [ { :host => 'localhost', :port => @port } ],
|
31
|
+
:options => { :transport_options => {
|
32
|
+
:params => { :format => 'yaml' }
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
client = Elasticsearch::Transport::Client.new transport: transport
|
37
|
+
response = client.perform_request 'GET', ''
|
38
|
+
|
39
|
+
assert response.body.start_with?("---\n"), "Response body should be YAML: #{response.body.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
should "use the Curb client" do
|
43
|
+
require 'curb'
|
44
|
+
require 'elasticsearch/transport/transport/http/curb'
|
45
|
+
|
46
|
+
transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
|
47
|
+
:hosts => [ { :host => 'localhost', :port => @port } ] do |curl|
|
48
|
+
curl.verbose = true
|
49
|
+
end
|
50
|
+
|
51
|
+
client = Elasticsearch::Transport::Client.new transport: transport
|
52
|
+
client.perform_request 'GET', ''
|
53
|
+
end unless JRUBY
|
54
|
+
|
55
|
+
should "deserialize JSON responses in the Curb client" do
|
56
|
+
require 'curb'
|
57
|
+
require 'elasticsearch/transport/transport/http/curb'
|
58
|
+
|
59
|
+
transport = Elasticsearch::Transport::Transport::HTTP::Curb.new \
|
60
|
+
:hosts => [ { :host => 'localhost', :port => @port } ] do |curl|
|
61
|
+
curl.verbose = true
|
62
|
+
end
|
63
|
+
|
64
|
+
client = Elasticsearch::Transport::Client.new transport: transport
|
65
|
+
response = client.perform_request 'GET', ''
|
66
|
+
|
67
|
+
assert_respond_to(response.body, :to_hash)
|
68
|
+
assert_not_nil response.body['name']
|
69
|
+
assert_equal 'application/json', response.headers['content-type']
|
70
|
+
end unless JRUBY
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|