elastic-transport 8.0.0.pre1
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/.github/check_license_headers.rb +33 -0
- data/.github/license-header.txt +16 -0
- data/.github/workflows/license.yml +13 -0
- data/.github/workflows/tests.yml +45 -0
- data/.gitignore +19 -0
- data/CHANGELOG.md +224 -0
- data/Gemfile +38 -0
- data/LICENSE +202 -0
- data/README.md +552 -0
- data/Rakefile +87 -0
- data/elastic-transport.gemspec +74 -0
- data/lib/elastic/transport/client.rb +276 -0
- data/lib/elastic/transport/meta_header.rb +135 -0
- data/lib/elastic/transport/redacted.rb +73 -0
- data/lib/elastic/transport/transport/base.rb +450 -0
- data/lib/elastic/transport/transport/connections/collection.rb +126 -0
- data/lib/elastic/transport/transport/connections/connection.rb +160 -0
- data/lib/elastic/transport/transport/connections/selector.rb +91 -0
- data/lib/elastic/transport/transport/errors.rb +91 -0
- data/lib/elastic/transport/transport/http/curb.rb +120 -0
- data/lib/elastic/transport/transport/http/faraday.rb +95 -0
- data/lib/elastic/transport/transport/http/manticore.rb +179 -0
- data/lib/elastic/transport/transport/loggable.rb +83 -0
- data/lib/elastic/transport/transport/response.rb +36 -0
- data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
- data/lib/elastic/transport/transport/sniffer.rb +101 -0
- data/lib/elastic/transport/version.rb +22 -0
- data/lib/elastic/transport.rb +37 -0
- data/lib/elastic-transport.rb +18 -0
- data/spec/elasticsearch/connections/collection_spec.rb +266 -0
- data/spec/elasticsearch/connections/selector_spec.rb +166 -0
- data/spec/elasticsearch/transport/base_spec.rb +264 -0
- data/spec/elasticsearch/transport/client_spec.rb +1651 -0
- data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
- data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
- data/spec/spec_helper.rb +90 -0
- data/test/integration/transport_test.rb +98 -0
- data/test/profile/client_benchmark_test.rb +132 -0
- data/test/test_helper.rb +83 -0
- data/test/unit/connection_test.rb +135 -0
- data/test/unit/response_test.rb +30 -0
- data/test/unit/serializer_test.rb +33 -0
- data/test/unit/transport_base_test.rb +664 -0
- data/test/unit/transport_curb_test.rb +135 -0
- data/test/unit/transport_faraday_test.rb +228 -0
- data/test/unit/transport_manticore_test.rb +251 -0
- metadata +412 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
module Connections
|
22
|
+
# Wraps the collection of connections for the transport object as an Enumerable object.
|
23
|
+
#
|
24
|
+
# @see Base#connections
|
25
|
+
# @see Selector::Base#select
|
26
|
+
# @see Connection
|
27
|
+
#
|
28
|
+
class Collection
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
DEFAULT_SELECTOR = Selector::RoundRobin
|
32
|
+
|
33
|
+
attr_reader :selector
|
34
|
+
|
35
|
+
# @option arguments [Array] :connections An array of {Connection} objects.
|
36
|
+
# @option arguments [Constant] :selector_class The class to be used as a connection selector strategy.
|
37
|
+
# @option arguments [Object] :selector The selector strategy object.
|
38
|
+
#
|
39
|
+
def initialize(arguments={})
|
40
|
+
selector_class = arguments[:selector_class] || DEFAULT_SELECTOR
|
41
|
+
@connections = arguments[:connections] || []
|
42
|
+
@selector = arguments[:selector] || selector_class.new(arguments.merge(:connections => self))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an Array of hosts information in this collection as Hashes.
|
46
|
+
#
|
47
|
+
# @return [Array]
|
48
|
+
#
|
49
|
+
def hosts
|
50
|
+
@connections.to_a.map { |c| c.host }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an Array of alive connections.
|
54
|
+
#
|
55
|
+
# @return [Array]
|
56
|
+
#
|
57
|
+
def connections
|
58
|
+
@connections.reject { |c| c.dead? }
|
59
|
+
end
|
60
|
+
alias :alive :connections
|
61
|
+
|
62
|
+
# Returns an Array of dead connections.
|
63
|
+
#
|
64
|
+
# @return [Array]
|
65
|
+
#
|
66
|
+
def dead
|
67
|
+
@connections.select { |c| c.dead? }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns an Array of all connections, both dead and alive
|
71
|
+
#
|
72
|
+
# @return [Array]
|
73
|
+
#
|
74
|
+
def all
|
75
|
+
@connections
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a connection.
|
79
|
+
#
|
80
|
+
# If there are no alive connections, returns a connection with least failures.
|
81
|
+
# Delegates to selector's `#select` method to get the connection.
|
82
|
+
#
|
83
|
+
# @return [Connection]
|
84
|
+
#
|
85
|
+
def get_connection(options={})
|
86
|
+
selector.select(options) || @connections.min_by(&:failures)
|
87
|
+
end
|
88
|
+
|
89
|
+
def each(&block)
|
90
|
+
connections.each(&block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def slice(*args)
|
94
|
+
connections.slice(*args)
|
95
|
+
end
|
96
|
+
alias :[] :slice
|
97
|
+
|
98
|
+
def size
|
99
|
+
connections.size
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add connection(s) to the collection
|
103
|
+
#
|
104
|
+
# @param connections [Connection,Array] A connection or an array of connections to add
|
105
|
+
# @return [self]
|
106
|
+
#
|
107
|
+
def add(connections)
|
108
|
+
@connections += Array(connections).to_a
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Remove connection(s) from the collection
|
113
|
+
#
|
114
|
+
# @param connections [Connection,Array] A connection or an array of connections to remove
|
115
|
+
# @return [self]
|
116
|
+
#
|
117
|
+
def remove(connections)
|
118
|
+
@connections -= Array(connections).to_a
|
119
|
+
self
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
module Connections
|
22
|
+
# Wraps the connection information and logic.
|
23
|
+
#
|
24
|
+
# The Connection instance wraps the host information (hostname, port, attributes, etc),
|
25
|
+
# as well as the "session" (a transport client object, such as a {HTTP::Faraday} instance).
|
26
|
+
#
|
27
|
+
# It provides methods to construct and properly encode the URLs and paths for passing them
|
28
|
+
# to the transport client object.
|
29
|
+
#
|
30
|
+
# It provides methods to handle connection livecycle (dead, alive, healthy).
|
31
|
+
#
|
32
|
+
class Connection
|
33
|
+
DEFAULT_RESURRECT_TIMEOUT = 60
|
34
|
+
|
35
|
+
attr_reader :host, :connection, :options, :failures, :dead_since
|
36
|
+
|
37
|
+
# @option arguments [Hash] :host Host information (example: `{host: 'localhost', port: 9200}`)
|
38
|
+
# @option arguments [Object] :connection The transport-specific physical connection or "session"
|
39
|
+
# @option arguments [Hash] :options Options (usually passed in from transport)
|
40
|
+
#
|
41
|
+
def initialize(arguments={})
|
42
|
+
@host = arguments[:host].is_a?(Hash) ? Redacted.new(arguments[:host]) : arguments[:host]
|
43
|
+
@connection = arguments[:connection]
|
44
|
+
@options = arguments[:options] || {}
|
45
|
+
@state_mutex = Mutex.new
|
46
|
+
|
47
|
+
@options[:resurrect_timeout] ||= DEFAULT_RESURRECT_TIMEOUT
|
48
|
+
@dead = false
|
49
|
+
@failures = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the complete endpoint URL with host, port, path and serialized parameters.
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
#
|
56
|
+
def full_url(path, params = {})
|
57
|
+
url = "#{host[:protocol]}://"
|
58
|
+
url += "#{CGI.escape(host[:user])}:#{CGI.escape(host[:password])}@" if host[:user]
|
59
|
+
url += "#{host[:host]}:#{host[:port]}"
|
60
|
+
url += "#{host[:path]}" if host[:path]
|
61
|
+
full_path = full_path(path, params)
|
62
|
+
url += '/' unless full_path.match?(/^\//)
|
63
|
+
url += full_path
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the complete endpoint path with serialized parameters.
|
67
|
+
#
|
68
|
+
# @return [String]
|
69
|
+
#
|
70
|
+
def full_path(path, params={})
|
71
|
+
path + (params.empty? ? '' : "?#{::Faraday::Utils::ParamsHash[params].to_query}")
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns true when this connection has been marked dead, false otherwise.
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
#
|
78
|
+
def dead?
|
79
|
+
@dead || false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Marks this connection as dead, incrementing the `failures` counter and
|
83
|
+
# storing the current time as `dead_since`.
|
84
|
+
#
|
85
|
+
# @return [self]
|
86
|
+
#
|
87
|
+
def dead!
|
88
|
+
@state_mutex.synchronize do
|
89
|
+
@dead = true
|
90
|
+
@failures += 1
|
91
|
+
@dead_since = Time.now
|
92
|
+
end
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Marks this connection as alive, ie. it is eligible to be returned from the pool by the selector.
|
97
|
+
#
|
98
|
+
# @return [self]
|
99
|
+
#
|
100
|
+
def alive!
|
101
|
+
@state_mutex.synchronize do
|
102
|
+
@dead = false
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Marks this connection as healthy, ie. a request has been successfully performed with it.
|
108
|
+
#
|
109
|
+
# @return [self]
|
110
|
+
#
|
111
|
+
def healthy!
|
112
|
+
@state_mutex.synchronize do
|
113
|
+
@dead = false
|
114
|
+
@failures = 0
|
115
|
+
end
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
# Marks this connection as alive, if the required timeout has passed.
|
120
|
+
#
|
121
|
+
# @return [self,nil]
|
122
|
+
# @see DEFAULT_RESURRECT_TIMEOUT
|
123
|
+
# @see #resurrectable?
|
124
|
+
#
|
125
|
+
def resurrect!
|
126
|
+
alive! if resurrectable?
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns true if the connection is eligible to be resurrected as alive, false otherwise.
|
130
|
+
#
|
131
|
+
# @return [Boolean]
|
132
|
+
#
|
133
|
+
def resurrectable?
|
134
|
+
@state_mutex.synchronize {
|
135
|
+
Time.now > @dead_since + ( @options[:resurrect_timeout] * 2 ** (@failures-1) )
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
# Equality operator based on connection protocol, host, port and attributes
|
140
|
+
#
|
141
|
+
# @return [Boolean]
|
142
|
+
#
|
143
|
+
def ==(other)
|
144
|
+
self.host[:protocol] == other.host[:protocol] && \
|
145
|
+
self.host[:host] == other.host[:host] && \
|
146
|
+
self.host[:port].to_i == other.host[:port].to_i && \
|
147
|
+
self.host[:attributes] == other.host[:attributes]
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [String]
|
151
|
+
#
|
152
|
+
def to_s
|
153
|
+
"<#{self.class.name} host: #{host} (#{dead? ? 'dead since ' + dead_since.to_s : 'alive'})>"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
module Connections
|
22
|
+
module Selector
|
23
|
+
# @abstract Common functionality for connection selector implementations.
|
24
|
+
#
|
25
|
+
module Base
|
26
|
+
attr_reader :connections
|
27
|
+
|
28
|
+
# @option arguments [Connections::Collection] :connections Collection with connections.
|
29
|
+
#
|
30
|
+
def initialize(arguments={})
|
31
|
+
@connections = arguments[:connections]
|
32
|
+
end
|
33
|
+
|
34
|
+
# @abstract Selector strategies implement this method to
|
35
|
+
# select and return a connection from the pool.
|
36
|
+
#
|
37
|
+
# @return [Connection]
|
38
|
+
#
|
39
|
+
def select(options={})
|
40
|
+
raise NoMethodError, "Implement this method in the selector implementation."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# "Random connection" selector strategy.
|
45
|
+
#
|
46
|
+
class Random
|
47
|
+
include Base
|
48
|
+
|
49
|
+
# Returns a random connection from the collection.
|
50
|
+
#
|
51
|
+
# @return [Connections::Connection]
|
52
|
+
#
|
53
|
+
def select(options={})
|
54
|
+
connections.to_a.sample
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# "Round-robin" selector strategy (default).
|
59
|
+
#
|
60
|
+
class RoundRobin
|
61
|
+
include Base
|
62
|
+
|
63
|
+
# @option arguments [Connections::Collection] :connections Collection with connections.
|
64
|
+
#
|
65
|
+
def initialize(arguments = {})
|
66
|
+
super
|
67
|
+
@mutex = Mutex.new
|
68
|
+
@current = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the next connection from the collection, rotating them in round-robin fashion.
|
72
|
+
#
|
73
|
+
# @return [Connections::Connection]
|
74
|
+
#
|
75
|
+
def select(options={})
|
76
|
+
@mutex.synchronize do
|
77
|
+
conns = connections
|
78
|
+
if @current && (@current < conns.size-1)
|
79
|
+
@current += 1
|
80
|
+
else
|
81
|
+
@current = 0
|
82
|
+
end
|
83
|
+
conns[@current]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
|
22
|
+
# Generic client error
|
23
|
+
#
|
24
|
+
class Error < StandardError; end
|
25
|
+
|
26
|
+
# Reloading connections timeout (1 sec by default)
|
27
|
+
#
|
28
|
+
class SnifferTimeoutError < Timeout::Error; end
|
29
|
+
|
30
|
+
# Elastic server error (HTTP status 5xx)
|
31
|
+
#
|
32
|
+
class ServerError < Error; end
|
33
|
+
|
34
|
+
module Errors; end
|
35
|
+
|
36
|
+
HTTP_STATUSES = {
|
37
|
+
300 => 'MultipleChoices',
|
38
|
+
301 => 'MovedPermanently',
|
39
|
+
302 => 'Found',
|
40
|
+
303 => 'SeeOther',
|
41
|
+
304 => 'NotModified',
|
42
|
+
305 => 'UseProxy',
|
43
|
+
307 => 'TemporaryRedirect',
|
44
|
+
308 => 'PermanentRedirect',
|
45
|
+
|
46
|
+
400 => 'BadRequest',
|
47
|
+
401 => 'Unauthorized',
|
48
|
+
402 => 'PaymentRequired',
|
49
|
+
403 => 'Forbidden',
|
50
|
+
404 => 'NotFound',
|
51
|
+
405 => 'MethodNotAllowed',
|
52
|
+
406 => 'NotAcceptable',
|
53
|
+
407 => 'ProxyAuthenticationRequired',
|
54
|
+
408 => 'RequestTimeout',
|
55
|
+
409 => 'Conflict',
|
56
|
+
410 => 'Gone',
|
57
|
+
411 => 'LengthRequired',
|
58
|
+
412 => 'PreconditionFailed',
|
59
|
+
413 => 'RequestEntityTooLarge',
|
60
|
+
414 => 'RequestURITooLong',
|
61
|
+
415 => 'UnsupportedMediaType',
|
62
|
+
416 => 'RequestedRangeNotSatisfiable',
|
63
|
+
417 => 'ExpectationFailed',
|
64
|
+
418 => 'ImATeapot',
|
65
|
+
421 => 'TooManyConnectionsFromThisIP',
|
66
|
+
426 => 'UpgradeRequired',
|
67
|
+
429 => 'TooManyRequests',
|
68
|
+
450 => 'BlockedByWindowsParentalControls',
|
69
|
+
494 => 'RequestHeaderTooLarge',
|
70
|
+
497 => 'HTTPToHTTPS',
|
71
|
+
499 => 'ClientClosedRequest',
|
72
|
+
|
73
|
+
500 => 'InternalServerError',
|
74
|
+
501 => 'NotImplemented',
|
75
|
+
502 => 'BadGateway',
|
76
|
+
503 => 'ServiceUnavailable',
|
77
|
+
504 => 'GatewayTimeout',
|
78
|
+
505 => 'HTTPVersionNotSupported',
|
79
|
+
506 => 'VariantAlsoNegotiates',
|
80
|
+
510 => 'NotExtended'
|
81
|
+
}
|
82
|
+
|
83
|
+
ERRORS = HTTP_STATUSES.inject({}) do |sum, error|
|
84
|
+
status, name = error
|
85
|
+
sum[status] = Errors.const_set name, Class.new(ServerError)
|
86
|
+
sum
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|