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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/check_license_headers.rb +33 -0
  3. data/.github/license-header.txt +16 -0
  4. data/.github/workflows/license.yml +13 -0
  5. data/.github/workflows/tests.yml +45 -0
  6. data/.gitignore +19 -0
  7. data/CHANGELOG.md +224 -0
  8. data/Gemfile +38 -0
  9. data/LICENSE +202 -0
  10. data/README.md +552 -0
  11. data/Rakefile +87 -0
  12. data/elastic-transport.gemspec +74 -0
  13. data/lib/elastic/transport/client.rb +276 -0
  14. data/lib/elastic/transport/meta_header.rb +135 -0
  15. data/lib/elastic/transport/redacted.rb +73 -0
  16. data/lib/elastic/transport/transport/base.rb +450 -0
  17. data/lib/elastic/transport/transport/connections/collection.rb +126 -0
  18. data/lib/elastic/transport/transport/connections/connection.rb +160 -0
  19. data/lib/elastic/transport/transport/connections/selector.rb +91 -0
  20. data/lib/elastic/transport/transport/errors.rb +91 -0
  21. data/lib/elastic/transport/transport/http/curb.rb +120 -0
  22. data/lib/elastic/transport/transport/http/faraday.rb +95 -0
  23. data/lib/elastic/transport/transport/http/manticore.rb +179 -0
  24. data/lib/elastic/transport/transport/loggable.rb +83 -0
  25. data/lib/elastic/transport/transport/response.rb +36 -0
  26. data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
  27. data/lib/elastic/transport/transport/sniffer.rb +101 -0
  28. data/lib/elastic/transport/version.rb +22 -0
  29. data/lib/elastic/transport.rb +37 -0
  30. data/lib/elastic-transport.rb +18 -0
  31. data/spec/elasticsearch/connections/collection_spec.rb +266 -0
  32. data/spec/elasticsearch/connections/selector_spec.rb +166 -0
  33. data/spec/elasticsearch/transport/base_spec.rb +264 -0
  34. data/spec/elasticsearch/transport/client_spec.rb +1651 -0
  35. data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
  36. data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
  37. data/spec/spec_helper.rb +90 -0
  38. data/test/integration/transport_test.rb +98 -0
  39. data/test/profile/client_benchmark_test.rb +132 -0
  40. data/test/test_helper.rb +83 -0
  41. data/test/unit/connection_test.rb +135 -0
  42. data/test/unit/response_test.rb +30 -0
  43. data/test/unit/serializer_test.rb +33 -0
  44. data/test/unit/transport_base_test.rb +664 -0
  45. data/test/unit/transport_curb_test.rb +135 -0
  46. data/test/unit/transport_faraday_test.rb +228 -0
  47. data/test/unit/transport_manticore_test.rb +251 -0
  48. 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