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