elasticsearch-transport-pixlee 1.0.13
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 +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
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc "Run unit tests"
|
4
|
+
task :default => 'test:unit'
|
5
|
+
task :test => 'test:unit'
|
6
|
+
|
7
|
+
# ----- Test tasks ------------------------------------------------------------
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
namespace :test do
|
11
|
+
task :ci_reporter do
|
12
|
+
ENV['CI_REPORTS'] ||= 'tmp/reports'
|
13
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
14
|
+
require 'ci/reporter/rake/test_unit'
|
15
|
+
Rake::Task['ci:setup:testunit'].invoke
|
16
|
+
else
|
17
|
+
require 'ci/reporter/rake/minitest'
|
18
|
+
Rake::Task['ci:setup:minitest'].invoke
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Rake::TestTask.new(:unit) do |test|
|
23
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.test_files = FileList["test/unit/**/*_test.rb"]
|
26
|
+
# test.verbose = true
|
27
|
+
# test.warning = true
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::TestTask.new(:integration) do |test|
|
31
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
32
|
+
test.libs << 'lib' << 'test'
|
33
|
+
test.test_files = FileList["test/integration/**/*_test.rb"]
|
34
|
+
end
|
35
|
+
|
36
|
+
Rake::TestTask.new(:all) do |test|
|
37
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
38
|
+
test.libs << 'lib' << 'test'
|
39
|
+
test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"]
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::TestTask.new(:profile) do |test|
|
43
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
44
|
+
test.libs << 'lib' << 'test'
|
45
|
+
test.test_files = FileList["test/profile/**/*_test.rb"]
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :cluster do
|
49
|
+
desc "Start Elasticsearch nodes for tests"
|
50
|
+
task :start do
|
51
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__) << File.expand_path('../test', __FILE__)
|
52
|
+
require 'elasticsearch/extensions/test/cluster'
|
53
|
+
Elasticsearch::Extensions::Test::Cluster.start
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Stop Elasticsearch nodes for tests"
|
57
|
+
task :stop do
|
58
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__) << File.expand_path('../test', __FILE__)
|
59
|
+
require 'elasticsearch/extensions/test/cluster'
|
60
|
+
Elasticsearch::Extensions::Test::Cluster.stop
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# ----- Documentation tasks ---------------------------------------------------
|
66
|
+
|
67
|
+
require 'yard'
|
68
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
69
|
+
t.options = %w| --embed-mixins --markup=markdown |
|
70
|
+
end
|
71
|
+
|
72
|
+
# ----- Code analysis tasks ---------------------------------------------------
|
73
|
+
|
74
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
75
|
+
require 'cane/rake_task'
|
76
|
+
Cane::RakeTask.new(:quality) do |cane|
|
77
|
+
cane.abc_max = 15
|
78
|
+
cane.no_style = true
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elasticsearch/transport/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "elasticsearch-transport-pixlee"
|
8
|
+
s.version = Elasticsearch::Transport::VERSION
|
9
|
+
s.authors = ["Karel Minarik"]
|
10
|
+
s.email = ["karel.minarik@elasticsearch.org"]
|
11
|
+
s.summary = "Ruby client for Elasticsearch."
|
12
|
+
s.homepage = "https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport"
|
13
|
+
s.license = "Apache 2"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ]
|
21
|
+
s.rdoc_options = [ "--charset=UTF-8" ]
|
22
|
+
|
23
|
+
s.add_dependency "multi_json"
|
24
|
+
s.add_dependency "faraday"
|
25
|
+
|
26
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
27
|
+
s.add_dependency "system_timer"
|
28
|
+
end
|
29
|
+
|
30
|
+
s.add_development_dependency "bundler", "> 1"
|
31
|
+
s.add_development_dependency "rake"
|
32
|
+
|
33
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
34
|
+
s.add_development_dependency "elasticsearch-extensions"
|
35
|
+
end
|
36
|
+
|
37
|
+
s.add_development_dependency "ansi"
|
38
|
+
s.add_development_dependency "shoulda-context"
|
39
|
+
s.add_development_dependency "mocha"
|
40
|
+
s.add_development_dependency "turn"
|
41
|
+
s.add_development_dependency "yard"
|
42
|
+
s.add_development_dependency "pry"
|
43
|
+
s.add_development_dependency "ci_reporter", "~> 1.9"
|
44
|
+
|
45
|
+
# Gems for testing integrations
|
46
|
+
s.add_development_dependency "curb" unless defined? JRUBY_VERSION
|
47
|
+
s.add_development_dependency "patron" unless defined? JRUBY_VERSION
|
48
|
+
s.add_development_dependency "typhoeus", '~> 0.6'
|
49
|
+
s.add_development_dependency "manticore", '~> 0.3.5' if defined? JRUBY_VERSION
|
50
|
+
s.add_development_dependency "hashie"
|
51
|
+
|
52
|
+
# Prevent unit test failures on Ruby 1.8
|
53
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
54
|
+
s.add_development_dependency "test-unit", '~> 2'
|
55
|
+
s.add_development_dependency "json"
|
56
|
+
end
|
57
|
+
|
58
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
59
|
+
s.add_development_dependency "minitest", "~> 4.0"
|
60
|
+
s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) || defined?(Rubinius)
|
61
|
+
s.add_development_dependency "require-prof" unless defined?(JRUBY_VERSION) || defined?(Rubinius)
|
62
|
+
s.add_development_dependency "simplecov"
|
63
|
+
s.add_development_dependency "simplecov-rcov"
|
64
|
+
s.add_development_dependency "cane"
|
65
|
+
end
|
66
|
+
|
67
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
|
68
|
+
s.add_development_dependency "test-unit", '~> 2'
|
69
|
+
end
|
70
|
+
|
71
|
+
s.description = <<-DESC.gsub(/^ /, '')
|
72
|
+
Ruby client for Elasticsearch. See the `elasticsearch` gem for full integration.
|
73
|
+
DESC
|
74
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'elasticsearch/transport'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "time"
|
3
|
+
require "timeout"
|
4
|
+
require "multi_json"
|
5
|
+
require "faraday"
|
6
|
+
|
7
|
+
require "elasticsearch/transport/transport/serializer/multi_json"
|
8
|
+
require "elasticsearch/transport/transport/sniffer"
|
9
|
+
require "elasticsearch/transport/transport/response"
|
10
|
+
require "elasticsearch/transport/transport/errors"
|
11
|
+
require "elasticsearch/transport/transport/base"
|
12
|
+
require "elasticsearch/transport/transport/connections/selector"
|
13
|
+
require "elasticsearch/transport/transport/connections/connection"
|
14
|
+
require "elasticsearch/transport/transport/connections/collection"
|
15
|
+
require "elasticsearch/transport/transport/http/faraday"
|
16
|
+
require "elasticsearch/transport/client"
|
17
|
+
|
18
|
+
require "elasticsearch/transport/version"
|
19
|
+
|
20
|
+
module Elasticsearch
|
21
|
+
module Client
|
22
|
+
|
23
|
+
# A convenience wrapper for {::Elasticsearch::Transport::Client#initialize}.
|
24
|
+
#
|
25
|
+
def new(arguments={})
|
26
|
+
Elasticsearch::Transport::Client.new(arguments)
|
27
|
+
end
|
28
|
+
extend self
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
|
4
|
+
# Handles communication with an Elasticsearch cluster.
|
5
|
+
#
|
6
|
+
# See {file:README.md README} for usage and code examples.
|
7
|
+
#
|
8
|
+
class Client
|
9
|
+
DEFAULT_TRANSPORT_CLASS = Transport::HTTP::Faraday
|
10
|
+
|
11
|
+
DEFAULT_LOGGER = lambda do
|
12
|
+
require 'logger'
|
13
|
+
logger = Logger.new(STDERR)
|
14
|
+
logger.progname = 'elasticsearch'
|
15
|
+
logger.formatter = proc { |severity, datetime, progname, msg| "#{datetime}: #{msg}\n" }
|
16
|
+
logger
|
17
|
+
end
|
18
|
+
|
19
|
+
DEFAULT_TRACER = lambda do
|
20
|
+
require 'logger'
|
21
|
+
logger = Logger.new(STDERR)
|
22
|
+
logger.progname = 'elasticsearch.tracer'
|
23
|
+
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
24
|
+
logger
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the transport object.
|
28
|
+
#
|
29
|
+
# @see Elasticsearch::Transport::Transport::Base
|
30
|
+
# @see Elasticsearch::Transport::Transport::HTTP::Faraday
|
31
|
+
#
|
32
|
+
attr_accessor :transport
|
33
|
+
|
34
|
+
# Create a client connected to an Elasticsearch cluster.
|
35
|
+
#
|
36
|
+
# Specify the URL via arguments or set the `ELASTICSEARCH_URL` environment variable.
|
37
|
+
#
|
38
|
+
# @option arguments [String,Array] :hosts Single host passed as a String or Hash, or multiple hosts
|
39
|
+
# passed as an Array; `host` or `url` keys are also valid
|
40
|
+
#
|
41
|
+
# @option arguments [Boolean] :log Use the default logger (disabled by default)
|
42
|
+
#
|
43
|
+
# @option arguments [Boolean] :trace Use the default tracer (disabled by default)
|
44
|
+
#
|
45
|
+
# @option arguments [Object] :logger An instance of a Logger-compatible object
|
46
|
+
#
|
47
|
+
# @option arguments [Object] :tracer An instance of a Logger-compatible object
|
48
|
+
#
|
49
|
+
# @option arguments [Number] :resurrect_after After how many seconds a dead connection should be tried again
|
50
|
+
#
|
51
|
+
# @option arguments [Boolean,Number] :reload_connections Reload connections after X requests (false by default)
|
52
|
+
#
|
53
|
+
# @option arguments [Boolean] :randomize_hosts Shuffle connections on initialization and reload (false by default)
|
54
|
+
#
|
55
|
+
# @option arguments [Integer] :sniffer_timeout Timeout for reloading connections in seconds (1 by default)
|
56
|
+
#
|
57
|
+
# @option arguments [Boolean,Number] :retry_on_failure Retry X times when request fails before raising and
|
58
|
+
# exception (false by default)
|
59
|
+
#
|
60
|
+
# @option arguments [Boolean] :reload_on_failure Reload connections after failure (false by default)
|
61
|
+
#
|
62
|
+
# @option arguments [Integer] :request_timeout The request timeout to be passed to transport in options
|
63
|
+
#
|
64
|
+
# @option arguments [Symbol] :adapter A specific adapter for Faraday (e.g. `:patron`)
|
65
|
+
#
|
66
|
+
# @option arguments [Hash] :transport_options Options to be passed to the `Faraday::Connection` constructor
|
67
|
+
#
|
68
|
+
# @option arguments [Constant] :transport_class A specific transport class to use, will be initialized by
|
69
|
+
# the client and passed hosts and all arguments
|
70
|
+
#
|
71
|
+
# @option arguments [Object] :transport A specific transport instance
|
72
|
+
#
|
73
|
+
# @option arguments [Constant] :serializer_class A specific serializer class to use, will be initialized by
|
74
|
+
# the transport and passed the transport instance
|
75
|
+
#
|
76
|
+
# @option arguments [Constant] :selector An instance of selector strategy implemented with
|
77
|
+
# {Elasticsearch::Transport::Transport::Connections::Selector::Base}.
|
78
|
+
#
|
79
|
+
# @option arguments [String] :send_get_body_as Specify the HTTP method to use for GET requests with a body.
|
80
|
+
# (Default: GET)
|
81
|
+
#
|
82
|
+
def initialize(arguments={})
|
83
|
+
hosts = arguments[:hosts] || \
|
84
|
+
arguments[:host] || \
|
85
|
+
arguments[:url] || \
|
86
|
+
arguments[:urls] || \
|
87
|
+
ENV.fetch('ELASTICSEARCH_URL', 'localhost:9200')
|
88
|
+
|
89
|
+
arguments[:logger] ||= arguments[:log] ? DEFAULT_LOGGER.call() : nil
|
90
|
+
arguments[:tracer] ||= arguments[:trace] ? DEFAULT_TRACER.call() : nil
|
91
|
+
arguments[:reload_connections] ||= false
|
92
|
+
arguments[:retry_on_failure] ||= false
|
93
|
+
arguments[:reload_on_failure] ||= false
|
94
|
+
arguments[:randomize_hosts] ||= false
|
95
|
+
arguments[:transport_options] ||= {}
|
96
|
+
|
97
|
+
arguments[:transport_options].update(:request => { :timeout => arguments[:request_timeout] } ) if arguments[:request_timeout]
|
98
|
+
|
99
|
+
@send_get_body_as = arguments[:send_get_body_as] || 'GET'
|
100
|
+
|
101
|
+
transport_class = arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
|
102
|
+
|
103
|
+
@transport = arguments[:transport] || begin
|
104
|
+
if transport_class == Transport::HTTP::Faraday
|
105
|
+
transport_class.new(:hosts => __extract_hosts(hosts, arguments), :options => arguments) do |faraday|
|
106
|
+
faraday.adapter(arguments[:adapter] || __auto_detect_adapter)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
transport_class.new(:hosts => __extract_hosts(hosts, arguments), :options => arguments)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Performs a request through delegation to {#transport}.
|
115
|
+
#
|
116
|
+
def perform_request(method, path, params={}, body=nil)
|
117
|
+
method = @send_get_body_as if 'GET' == method && body
|
118
|
+
|
119
|
+
transport.perform_request method, path, params, body
|
120
|
+
end
|
121
|
+
|
122
|
+
# Normalizes and returns hosts configuration.
|
123
|
+
#
|
124
|
+
# Arrayifies the `hosts_config` argument and extracts `host` and `port` info from strings.
|
125
|
+
# Performs shuffling when the `randomize_hosts` option is set.
|
126
|
+
#
|
127
|
+
# TODO: Refactor, so it's available in Elasticsearch::Transport::Base as well
|
128
|
+
#
|
129
|
+
# @return [Array<Hash>]
|
130
|
+
# @raise [ArgumentError]
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
#
|
134
|
+
def __extract_hosts(hosts_config, options={})
|
135
|
+
if hosts_config.is_a?(Hash)
|
136
|
+
hosts = [ hosts_config ]
|
137
|
+
else
|
138
|
+
if hosts_config.is_a?(String) && hosts_config.include?(',')
|
139
|
+
hosts = hosts_config.split(/\s*,\s*/)
|
140
|
+
else
|
141
|
+
hosts = Array(hosts_config)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
result = hosts.map do |host|
|
146
|
+
host_parts = case host
|
147
|
+
when String
|
148
|
+
if host =~ /^[a-z]+\:\/\//
|
149
|
+
uri = URI.parse(host)
|
150
|
+
{ :scheme => uri.scheme, :user => uri.user, :password => uri.password, :host => uri.host, :path => uri.path, :port => uri.port }
|
151
|
+
else
|
152
|
+
host, port = host.split(':')
|
153
|
+
{ :host => host, :port => port }
|
154
|
+
end
|
155
|
+
when URI
|
156
|
+
{ :scheme => host.scheme, :user => host.user, :password => host.password, :host => host.host, :path => host.path, :port => host.port }
|
157
|
+
when Hash
|
158
|
+
host
|
159
|
+
else
|
160
|
+
raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
|
161
|
+
end
|
162
|
+
|
163
|
+
host_parts[:port] = host_parts[:port].to_i unless host_parts[:port].nil?
|
164
|
+
host_parts
|
165
|
+
end
|
166
|
+
|
167
|
+
result.shuffle! if options[:randomize_hosts]
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# Auto-detect the best adapter (HTTP "driver") available, based on libraries
|
172
|
+
# loaded by the user, preferring those with persistent connections
|
173
|
+
# ("keep-alive") by default
|
174
|
+
#
|
175
|
+
# @return [Symbol]
|
176
|
+
#
|
177
|
+
# @api private
|
178
|
+
#
|
179
|
+
def __auto_detect_adapter
|
180
|
+
case
|
181
|
+
when defined?(::Patron)
|
182
|
+
:patron
|
183
|
+
when defined?(::Typhoeus)
|
184
|
+
:typhoeus
|
185
|
+
when defined?(::HTTPClient)
|
186
|
+
:httpclient
|
187
|
+
when defined?(::Net::HTTP::Persistent)
|
188
|
+
:net_http_persistent
|
189
|
+
else
|
190
|
+
::Faraday.default_adapter
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# @abstract Module with common functionality for transport implementations.
|
6
|
+
#
|
7
|
+
module Base
|
8
|
+
DEFAULT_PORT = 9200
|
9
|
+
DEFAULT_PROTOCOL = 'http'
|
10
|
+
DEFAULT_RELOAD_AFTER = 10_000 # Requests
|
11
|
+
DEFAULT_RESURRECT_AFTER = 60 # Seconds
|
12
|
+
DEFAULT_MAX_RETRIES = 3 # Requests
|
13
|
+
DEFAULT_SERIALIZER_CLASS = Serializer::MultiJson
|
14
|
+
|
15
|
+
attr_reader :hosts, :options, :connections, :counter, :last_request_at, :protocol
|
16
|
+
attr_accessor :serializer, :sniffer, :logger, :tracer,
|
17
|
+
:reload_connections, :reload_after,
|
18
|
+
:resurrect_after, :max_retries
|
19
|
+
|
20
|
+
# Creates a new transport object.
|
21
|
+
#
|
22
|
+
# @param arguments [Hash] Settings and options for the transport
|
23
|
+
# @param block [Proc] Lambda or Proc which can be evaluated in the context of the "session" object
|
24
|
+
#
|
25
|
+
# @option arguments [Array] :hosts An Array of normalized hosts information
|
26
|
+
# @option arguments [Array] :options A Hash with options (usually passed by {Client})
|
27
|
+
#
|
28
|
+
# @see Client#initialize
|
29
|
+
#
|
30
|
+
def initialize(arguments={}, &block)
|
31
|
+
@hosts = arguments[:hosts] || []
|
32
|
+
@options = arguments[:options] || {}
|
33
|
+
@block = block
|
34
|
+
@connections = __build_connections
|
35
|
+
|
36
|
+
@serializer = options[:serializer] || ( options[:serializer_class] ? options[:serializer_class].new(self) : DEFAULT_SERIALIZER_CLASS.new(self) )
|
37
|
+
@protocol = options[:protocol] || DEFAULT_PROTOCOL
|
38
|
+
|
39
|
+
@logger = options[:logger]
|
40
|
+
@tracer = options[:tracer]
|
41
|
+
|
42
|
+
@sniffer = options[:sniffer_class] ? options[:sniffer_class].new(self) : Sniffer.new(self)
|
43
|
+
@counter = 0
|
44
|
+
@last_request_at = Time.now
|
45
|
+
@reload_connections = options[:reload_connections]
|
46
|
+
@reload_after = options[:reload_connections].is_a?(Fixnum) ? options[:reload_connections] : DEFAULT_RELOAD_AFTER
|
47
|
+
@resurrect_after = options[:resurrect_after] || DEFAULT_RESURRECT_AFTER
|
48
|
+
@max_retries = options[:retry_on_failure].is_a?(Fixnum) ? options[:retry_on_failure] : DEFAULT_MAX_RETRIES
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a connection from the connection pool by delegating to {Connections::Collection#get_connection}.
|
52
|
+
#
|
53
|
+
# Resurrects dead connection if the `resurrect_after` timeout has passed.
|
54
|
+
# Increments the counter and performs connection reloading if the `reload_connections` option is set.
|
55
|
+
#
|
56
|
+
# @return [Connections::Connection]
|
57
|
+
# @see Connections::Collection#get_connection
|
58
|
+
#
|
59
|
+
def get_connection(options={})
|
60
|
+
resurrect_dead_connections! if Time.now > @last_request_at + @resurrect_after
|
61
|
+
|
62
|
+
connection = connections.get_connection(options)
|
63
|
+
@counter += 1
|
64
|
+
|
65
|
+
reload_connections! if reload_connections && counter % reload_after == 0
|
66
|
+
connection
|
67
|
+
end
|
68
|
+
|
69
|
+
# Reloads and replaces the connection collection based on cluster information.
|
70
|
+
#
|
71
|
+
# @see Sniffer#hosts
|
72
|
+
#
|
73
|
+
def reload_connections!
|
74
|
+
hosts = sniffer.hosts
|
75
|
+
__rebuild_connections :hosts => hosts, :options => options
|
76
|
+
self
|
77
|
+
rescue SnifferTimeoutError
|
78
|
+
logger.error "[SnifferTimeoutError] Timeout when reloading connections." if logger
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Tries to "resurrect" all eligible dead connections.
|
83
|
+
#
|
84
|
+
# @see Connections::Connection#resurrect!
|
85
|
+
#
|
86
|
+
def resurrect_dead_connections!
|
87
|
+
connections.dead.each { |c| c.resurrect! }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Replaces the connections collection.
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
#
|
94
|
+
def __rebuild_connections(arguments={})
|
95
|
+
@hosts = arguments[:hosts] || []
|
96
|
+
@options = arguments[:options] || {}
|
97
|
+
@connections = __build_connections
|
98
|
+
end
|
99
|
+
|
100
|
+
# Log request and response information.
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
def __log(method, path, params, body, url, response, json, took, duration)
|
105
|
+
logger.info "#{method.to_s.upcase} #{url} " +
|
106
|
+
"[status:#{response.status}, request:#{sprintf('%.3fs', duration)}, query:#{took}]"
|
107
|
+
logger.debug "> #{__convert_to_json(body)}" if body
|
108
|
+
logger.debug "< #{response.body}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Log failed request.
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def __log_failed(response)
|
115
|
+
logger.fatal "[#{response.status}] #{response.body}"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Trace the request in the `curl` format.
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def __trace(method, path, params, body, url, response, json, took, duration)
|
122
|
+
trace_url = "http://localhost:9200/#{path}?pretty" +
|
123
|
+
( params.empty? ? '' : "&#{::Faraday::Utils::ParamsHash[params].to_query}" )
|
124
|
+
trace_body = body ? " -d '#{__convert_to_json(body, :pretty => true)}'" : ''
|
125
|
+
tracer.info "curl -X #{method.to_s.upcase} '#{trace_url}'#{trace_body}\n"
|
126
|
+
tracer.debug "# #{Time.now.iso8601} [#{response.status}] (#{format('%.3f', duration)}s)\n#"
|
127
|
+
tracer.debug json ? serializer.dump(json, :pretty => true).gsub(/^/, '# ').sub(/\}$/, "\n# }")+"\n" : "# #{response.body}\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Raise error specific for the HTTP response status or a generic server error
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def __raise_transport_error(response)
|
134
|
+
error = ERRORS[response.status] || ServerError
|
135
|
+
raise error.new "[#{response.status}] #{response.body}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Converts any non-String object to JSON
|
139
|
+
#
|
140
|
+
# @api private
|
141
|
+
def __convert_to_json(o=nil, options={})
|
142
|
+
o = o.is_a?(String) ? o : serializer.dump(o, options)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns a full URL based on information from host
|
146
|
+
#
|
147
|
+
# @param host [Hash] Host configuration passed in from {Client}
|
148
|
+
#
|
149
|
+
# @api private
|
150
|
+
def __full_url(host)
|
151
|
+
url = "#{host[:protocol]}://"
|
152
|
+
url += "#{host[:user]}:#{host[:password]}@" if host[:user]
|
153
|
+
url += "#{host[:host]}:#{host[:port]}"
|
154
|
+
url += "#{host[:path]}" if host[:path]
|
155
|
+
url
|
156
|
+
end
|
157
|
+
|
158
|
+
# Performs a request to Elasticsearch, while handling logging, tracing, marking dead connections,
|
159
|
+
# retrying the request and reloading the connections.
|
160
|
+
#
|
161
|
+
# @abstract The transport implementation has to implement this method either in full,
|
162
|
+
# or by invoking this method with a block. See {HTTP::Faraday#perform_request} for an example.
|
163
|
+
#
|
164
|
+
# @param method [String] Request method
|
165
|
+
# @param path [String] The API endpoint
|
166
|
+
# @param params [Hash] Request parameters (will be serialized by {Connections::Connection#full_url})
|
167
|
+
# @param body [Hash] Request body (will be serialized by the {#serializer})
|
168
|
+
# @param block [Proc] Code block to evaluate, passed from the implementation
|
169
|
+
#
|
170
|
+
# @return [Response]
|
171
|
+
# @raise [NoMethodError] If no block is passed
|
172
|
+
# @raise [ServerError] If request failed on server
|
173
|
+
# @raise [Error] If no connection is available
|
174
|
+
#
|
175
|
+
def perform_request(method, path, params={}, body=nil, &block)
|
176
|
+
raise NoMethodError, "Implement this method in your transport class" unless block_given?
|
177
|
+
start = Time.now if logger || tracer
|
178
|
+
tries = 0
|
179
|
+
|
180
|
+
begin
|
181
|
+
tries += 1
|
182
|
+
connection = get_connection or raise Error.new("Cannot get new connection from pool.")
|
183
|
+
|
184
|
+
if connection.connection.respond_to?(:params) && connection.connection.params.respond_to?(:to_hash)
|
185
|
+
params = connection.connection.params.merge(params.to_hash)
|
186
|
+
end
|
187
|
+
|
188
|
+
url = connection.full_url(path, params)
|
189
|
+
|
190
|
+
response = block.call(connection, url)
|
191
|
+
|
192
|
+
connection.healthy! if connection.failures > 0
|
193
|
+
|
194
|
+
rescue *host_unreachable_exceptions => e
|
195
|
+
logger.error "[#{e.class}] #{e.message} #{connection.host.inspect}" if logger
|
196
|
+
|
197
|
+
connection.dead!
|
198
|
+
|
199
|
+
if @options[:reload_on_failure] and tries < connections.all.size
|
200
|
+
logger.warn "[#{e.class}] Reloading connections (attempt #{tries} of #{connections.all.size})" if logger
|
201
|
+
reload_connections! and retry
|
202
|
+
end
|
203
|
+
|
204
|
+
if @options[:retry_on_failure]
|
205
|
+
logger.warn "[#{e.class}] Attempt #{tries} connecting to #{connection.host.inspect}" if logger
|
206
|
+
if tries <= max_retries
|
207
|
+
retry
|
208
|
+
else
|
209
|
+
logger.fatal "[#{e.class}] Cannot connect to #{connection.host.inspect} after #{tries} tries" if logger
|
210
|
+
raise e
|
211
|
+
end
|
212
|
+
else
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
|
216
|
+
rescue Exception => e
|
217
|
+
logger.fatal "[#{e.class}] #{e.message} (#{connection.host.inspect if connection})" if logger
|
218
|
+
raise e
|
219
|
+
end
|
220
|
+
|
221
|
+
duration = Time.now-start if logger || tracer
|
222
|
+
|
223
|
+
if response.status.to_i >= 300
|
224
|
+
__log method, path, params, body, url, response, nil, 'N/A', duration if logger
|
225
|
+
__trace method, path, params, body, url, response, nil, 'N/A', duration if tracer
|
226
|
+
__log_failed response if logger
|
227
|
+
__raise_transport_error response
|
228
|
+
end
|
229
|
+
|
230
|
+
json = serializer.load(response.body) if response.headers && response.headers["content-type"] =~ /json/
|
231
|
+
took = (json['took'] ? sprintf('%.3fs', json['took']/1000.0) : 'n/a') rescue 'n/a' if logger || tracer
|
232
|
+
|
233
|
+
__log method, path, params, body, url, response, json, took, duration if logger
|
234
|
+
__trace method, path, params, body, url, response, json, took, duration if tracer
|
235
|
+
|
236
|
+
Response.new response.status, json || response.body, response.headers
|
237
|
+
ensure
|
238
|
+
@last_request_at = Time.now
|
239
|
+
end
|
240
|
+
|
241
|
+
# @abstract Returns an Array of connection errors specific to the transport implementation.
|
242
|
+
# See {HTTP::Faraday#host_unreachable_exceptions} for an example.
|
243
|
+
#
|
244
|
+
# @return [Array]
|
245
|
+
#
|
246
|
+
def host_unreachable_exceptions
|
247
|
+
[Errno::ECONNREFUSED, Elasticsearch::Transport::Transport::Errors::BadGateway]
|
248
|
+
end
|
249
|
+
|
250
|
+
# @abstract A transport implementation must implement this method.
|
251
|
+
# See {HTTP::Faraday#__build_connections} for an example.
|
252
|
+
#
|
253
|
+
# @return [Connections::Collection]
|
254
|
+
# @api private
|
255
|
+
def __build_connections
|
256
|
+
raise NoMethodError, "Implement this method in your class"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|