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,274 @@
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
+ require 'spec_helper'
19
+
20
+ describe Elastic::Transport::Client do
21
+ context 'meta-header' do
22
+ let(:subject) { client.transport.connections.first.connection.headers }
23
+ let(:client) { described_class.new }
24
+ let(:regexp) { /^[a-z]{1,}=[a-z0-9.\-]{1,}(?:,[a-z]{1,}=[a-z0-9._\-]+)*$/ }
25
+ let(:adapter) { :net_http }
26
+ let(:adapter_code) { "nh=#{defined?(Net::HTTP::VERSION) ? Net::HTTP::VERSION : Net::HTTP::HTTPVersion}" }
27
+ let(:meta_header) do
28
+ if jruby?
29
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
30
+ else
31
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
32
+ end
33
+ end
34
+
35
+ context 'client_meta_version' do
36
+ let(:version) { ['7.1.0-alpha', '7.11.0.pre.1', '8.0.0-beta', '8.0.0.beta.2']}
37
+
38
+ it 'converts the version to X.X.Xp' do
39
+ expect(client.send(:client_meta_version, '7.0.0-alpha')).to eq('7.0.0p')
40
+ expect(client.send(:client_meta_version, '7.11.0.pre.1')).to eq('7.11.0p')
41
+ expect(client.send(:client_meta_version, '8.1.0-beta')).to eq('8.1.0p')
42
+ expect(client.send(:client_meta_version, '8.0.0.beta.2')).to eq('8.0.0p')
43
+ expect(client.send(:client_meta_version, '12.16.4.pre')).to eq('12.16.4p')
44
+ end
45
+ end
46
+
47
+ # We are testing this method in the previous block, so now using it inside the test to make the
48
+ # Elasticsearch version in the meta header string dynamic
49
+ def meta_version
50
+ client.send(:client_meta_version, Elastic::Transport::VERSION)
51
+ end
52
+
53
+ context 'single use of meta header' do
54
+ let(:client) do
55
+ described_class.new(adapter: adapter).tap do |klient|
56
+ allow(klient).to receive(:__build_connections)
57
+ end
58
+ end
59
+
60
+ it 'x-elastic-client-header value matches regexp' do
61
+ expect(subject['x-elastic-client-meta']).to match(regexp)
62
+ expect(subject).to include('x-elastic-client-meta' => meta_header)
63
+ end
64
+ end
65
+
66
+ context 'when using user-agent headers' do
67
+ let(:client) do
68
+ transport_options = { headers: { user_agent: 'My Ruby App' } }
69
+ described_class.new(transport_options: transport_options, adapter: adapter).tap do |klient|
70
+ allow(klient).to receive(:__build_connections)
71
+ end
72
+ end
73
+
74
+ it 'is friendly to previously set headers' do
75
+ expect(subject).to include(user_agent: 'My Ruby App')
76
+ expect(subject['x-elastic-client-meta']).to match(regexp)
77
+ expect(subject).to include('x-elastic-client-meta' => meta_header)
78
+ end
79
+ end
80
+
81
+ context 'adapters' do
82
+ let(:meta_header) do
83
+ if jruby?
84
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION},fd=#{Faraday::VERSION}"
85
+ else
86
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION},fd=#{Faraday::VERSION}"
87
+ end
88
+ end
89
+ let(:client) { described_class.new(adapter: adapter) }
90
+ let(:headers) { client.transport.connections.first.connection.headers }
91
+
92
+ context 'using net/http/persistent' do
93
+ let(:adapter) { :net_http_persistent }
94
+
95
+ it 'sets adapter in the meta header version to 0 when not loaded' do
96
+ was_required = defined?(Net::HTTP::Persistent)
97
+ if was_required
98
+ @klass = Net::HTTP::Persistent.clone
99
+ Net::HTTP.send(:remove_const, :Persistent)
100
+ end
101
+
102
+ expect(headers['x-elastic-client-meta']).to match(regexp)
103
+ meta = "#{meta_header},np=0"
104
+ expect(headers).to include('x-elastic-client-meta' => meta)
105
+
106
+ Net::HTTP::Persistent = @klass if was_required
107
+ end unless jruby?
108
+
109
+ it 'sets adapter in the meta header' do
110
+ require 'net/http/persistent'
111
+ expect(headers['x-elastic-client-meta']).to match(regexp)
112
+ meta = "#{meta_header},np=#{Net::HTTP::Persistent::VERSION}"
113
+ expect(headers).to include('x-elastic-client-meta' => meta)
114
+ end
115
+ end
116
+
117
+ context 'using httpclient' do
118
+ let(:adapter) { :httpclient }
119
+
120
+ it 'sets adapter in the meta header version to 0 when not loaded' do
121
+ was_required = defined?(HTTPClient)
122
+ if was_required
123
+ @klass = HTTPClient.clone
124
+ Object.send(:remove_const, :HTTPClient)
125
+ end
126
+
127
+ expect(headers['x-elastic-client-meta']).to match(regexp)
128
+ meta = "#{meta_header},hc=0"
129
+ expect(headers).to include('x-elastic-client-meta' => meta)
130
+
131
+ HTTPClient = @klass if was_required
132
+ end unless jruby?
133
+
134
+ it 'sets adapter in the meta header' do
135
+ require 'httpclient'
136
+
137
+ expect(headers['x-elastic-client-meta']).to match(regexp)
138
+ meta = "#{meta_header},hc=#{HTTPClient::VERSION}"
139
+ expect(headers).to include('x-elastic-client-meta' => meta)
140
+ end
141
+ end
142
+
143
+ context 'using typhoeus' do
144
+ let(:adapter) { :typhoeus }
145
+
146
+ it 'sets adapter in the meta header version to 0 when not loaded' do
147
+ was_required = defined?(Typhoeus)
148
+ if was_required
149
+ @klass = Typhoeus.clone
150
+ Object.send(:remove_const, :Typhoeus)
151
+ end
152
+
153
+ expect(headers['x-elastic-client-meta']).to match(regexp)
154
+ meta = "#{meta_header},ty=0"
155
+ expect(headers).to include('x-elastic-client-meta' => meta)
156
+
157
+ Typhoeus = @klass if was_required
158
+ end unless jruby?
159
+
160
+ it 'sets adapter in the meta header' do
161
+ require 'typhoeus'
162
+ expect(headers['x-elastic-client-meta']).to match(regexp)
163
+ meta = "#{meta_header},ty=#{Typhoeus::VERSION}"
164
+ expect(headers).to include('x-elastic-client-meta' => meta)
165
+ end
166
+ end
167
+
168
+ unless jruby?
169
+ let(:adapter) { :patron }
170
+
171
+ context 'using patron without requiring it' do
172
+ it 'sets adapter in the meta header version to 0 when not loaded' do
173
+ was_required = defined?(Patron)
174
+ if was_required
175
+ @klass = Patron.clone
176
+ Object.send(:remove_const, :Patron)
177
+ end
178
+
179
+ expect(headers['x-elastic-client-meta']).to match(regexp)
180
+ meta = "#{meta_header},pt=0"
181
+ expect(headers).to include('x-elastic-client-meta' => meta)
182
+
183
+ Patron = @klass if was_required
184
+ end
185
+ end
186
+
187
+ context 'using patron' do
188
+ it 'sets adapter in the meta header' do
189
+ require 'patron'
190
+ expect(headers['x-elastic-client-meta']).to match(regexp)
191
+ meta = "#{meta_header},pt=#{Patron::VERSION}"
192
+ expect(headers).to include('x-elastic-client-meta' => meta)
193
+ end
194
+ end
195
+ end
196
+
197
+ context 'using other' do
198
+ let(:adapter) { :some_other_adapter }
199
+
200
+ it 'sets adapter in the meta header without requiring' do
201
+ Faraday::Adapter.register_middleware some_other_adapter: Faraday::Adapter::NetHttpPersistent
202
+ expect(headers['x-elastic-client-meta']).to match(regexp)
203
+ expect(headers).to include('x-elastic-client-meta' => meta_header)
204
+ end
205
+
206
+ it 'sets adapter in the meta header' do
207
+ require 'net/http/persistent'
208
+ Faraday::Adapter.register_middleware some_other_adapter: Faraday::Adapter::NetHttpPersistent
209
+ expect(headers['x-elastic-client-meta']).to match(regexp)
210
+ expect(headers).to include('x-elastic-client-meta' => meta_header)
211
+ end
212
+ end
213
+ end
214
+
215
+ if defined?(JRUBY_VERSION)
216
+ context 'when using manticore' do
217
+ let(:client) do
218
+ Elastic::Transport::Client.new(transport_class: Elastic::Transport::Transport::HTTP::Manticore)
219
+ end
220
+ let(:subject) { client.transport.connections.first.connection.instance_variable_get("@options")[:headers]}
221
+
222
+ it 'sets manticore in the metaheader' do
223
+ expect(subject['x-elastic-client-meta']).to match(regexp)
224
+ expect(subject['x-elastic-client-meta']).to match(/mc=[0-9.]+/)
225
+ end
226
+ end
227
+ else
228
+ context 'when using curb' do
229
+ let(:client) do
230
+ Elastic::Transport::Client.new(transport_class: Elastic::Transport::Transport::HTTP::Curb)
231
+ end
232
+
233
+ it 'sets curb in the metaheader' do
234
+ expect(subject['x-elastic-client-meta']).to match(regexp)
235
+ expect(subject['x-elastic-client-meta']).to match(/cl=[0-9.]+/)
236
+ end
237
+ end
238
+ end
239
+
240
+ context 'when using custom transport implementation' do
241
+ class MyTransport
242
+ include Elastic::Transport::Transport::Base
243
+ def initialize(args); end
244
+ end
245
+ let(:client) { Elastic::Transport::Client.new(transport_class: MyTransport) }
246
+ let(:subject) { client.instance_variable_get('@arguments')[:transport_options][:headers] }
247
+ let(:meta_header) do
248
+ if jruby?
249
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION}"
250
+ else
251
+ "es=#{meta_version},rb=#{RUBY_VERSION},t=#{Elastic::Transport::VERSION}"
252
+ end
253
+ end
254
+
255
+ it 'doesnae set any info about the implementation in the metaheader' do
256
+ expect(subject['x-elastic-client-meta']).to match(regexp)
257
+ expect(subject).to include('x-elastic-client-meta' => meta_header)
258
+ end
259
+ end
260
+
261
+ context 'when using a different service version' do
262
+ before do
263
+ stub_const('Elastic::ELASTICSEARCH_SERVICE_VERSION', [:ent, '8.0.0'])
264
+ end
265
+
266
+ let(:client) { Elastic::Transport::Client.new }
267
+
268
+ it 'sets the service version in the metaheader' do
269
+ expect(subject['x-elastic-client-meta']).to match(regexp)
270
+ expect(subject['x-elastic-client-meta']).to start_with('ent=8.0.0')
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,275 @@
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
+ require 'spec_helper'
19
+
20
+ describe Elastic::Transport::Transport::Sniffer do
21
+ let(:transport) do
22
+ double('transport').tap do |t|
23
+ allow(t).to receive(:perform_request).and_return(response)
24
+ allow(t).to receive(:options).and_return(sniffer_timeout: 2)
25
+ end
26
+ end
27
+
28
+ let(:sniffer) do
29
+ described_class.new(transport)
30
+ end
31
+
32
+ let(:response) do
33
+ double('response').tap do |r|
34
+ allow(r).to receive(:body).and_return(raw_response)
35
+ end
36
+ end
37
+
38
+ let(:raw_response) do
39
+ { 'nodes' => { 'n1' => { 'http' => { 'publish_address' => publish_address } } } }
40
+ end
41
+
42
+ let(:publish_address) do
43
+ '127.0.0.1:9250'
44
+ end
45
+
46
+ describe '#initialize' do
47
+ it 'has a transport instance' do
48
+ expect(sniffer.transport).to be(transport)
49
+ end
50
+
51
+ it 'inherits the sniffer timeout from the transport object' do
52
+ expect(sniffer.timeout).to eq(2)
53
+ end
54
+ end
55
+
56
+ describe '#timeout' do
57
+ let(:sniffer) do
58
+ described_class.new(double('transport', options: {}))
59
+ end
60
+
61
+ before do
62
+ sniffer.timeout = 3
63
+ end
64
+
65
+ it 'allows the timeout to be configured' do
66
+ expect(sniffer.timeout).to eq(3)
67
+ end
68
+ end
69
+
70
+ describe '#hosts' do
71
+ let(:hosts) do
72
+ sniffer.hosts
73
+ end
74
+
75
+ context 'when the entire response is parsed' do
76
+ let(:raw_response) do
77
+ {
78
+ "cluster_name" => "elasticsearch_test",
79
+ "nodes" => {
80
+ "N1" => {
81
+ "name" => "Node 1",
82
+ "transport_address" => "127.0.0.1:9300",
83
+ "host" => "testhost1",
84
+ "ip" => "127.0.0.1",
85
+ "version" => "7.0.0",
86
+ "roles" => [
87
+ "master",
88
+ "data",
89
+ "ingest"
90
+ ],
91
+ "attributes" => {
92
+ "testattr" => "test"
93
+ },
94
+ "http" => {
95
+ "bound_address" => [
96
+ "[fe80::1]:9250",
97
+ "[::1]:9250",
98
+ "127.0.0.1:9250"
99
+ ],
100
+ "publish_address" => "127.0.0.1:9250",
101
+ "max_content_length_in_bytes" => 104857600
102
+ }
103
+ }
104
+ }
105
+ }
106
+ end
107
+
108
+ it 'parses the id' do
109
+ expect(sniffer.hosts[0][:id]).to eq('N1')
110
+ end
111
+
112
+ it 'parses the name' do
113
+ expect(sniffer.hosts[0][:name]).to eq('Node 1')
114
+ end
115
+
116
+ it 'parses the version' do
117
+ expect(sniffer.hosts[0][:version]).to eq('7.0.0')
118
+ end
119
+
120
+ it 'parses the host' do
121
+ expect(sniffer.hosts[0][:host]).to eq('127.0.0.1')
122
+ end
123
+
124
+ it 'parses the port' do
125
+ expect(sniffer.hosts[0][:port]).to eq('9250')
126
+ end
127
+
128
+ it 'parses the roles' do
129
+ expect(sniffer.hosts[0][:roles]).to eq(['master',
130
+ 'data',
131
+ 'ingest'])
132
+ end
133
+
134
+ it 'parses the attributes' do
135
+ expect(sniffer.hosts[0][:attributes]).to eq('testattr' => 'test')
136
+ end
137
+ end
138
+
139
+ context 'when the transport protocol does not match' do
140
+ let(:raw_response) do
141
+ { 'nodes' => { 'n1' => { 'foo' => { 'publish_address' => '127.0.0.1:9250' } } } }
142
+ end
143
+
144
+ it 'does not parse the addresses' do
145
+ expect(hosts).to eq([])
146
+ end
147
+ end
148
+
149
+ context 'when a list of nodes is returned' do
150
+ let(:raw_response) do
151
+ { 'nodes' => { 'n1' => { 'http' => { 'publish_address' => '127.0.0.1:9250' } },
152
+ 'n2' => { 'http' => { 'publish_address' => '127.0.0.1:9251' } } } }
153
+ end
154
+
155
+ it 'parses the response' do
156
+ expect(hosts.size).to eq(2)
157
+ end
158
+
159
+ it 'correctly parses the hosts' do
160
+ expect(hosts[0][:host]).to eq('127.0.0.1')
161
+ expect(hosts[1][:host]).to eq('127.0.0.1')
162
+ end
163
+
164
+ it 'correctly parses the ports' do
165
+ expect(hosts[0][:port]).to eq('9250')
166
+ expect(hosts[1][:port]).to eq('9251')
167
+ end
168
+ end
169
+
170
+ context 'when the host and port are an ip address and port' do
171
+ it 'parses the response' do
172
+ expect(hosts.size).to eq(1)
173
+ end
174
+
175
+ it 'correctly parses the host' do
176
+ expect(hosts[0][:host]).to eq('127.0.0.1')
177
+ end
178
+
179
+ it 'correctly parses the port' do
180
+ expect(hosts[0][:port]).to eq('9250')
181
+ end
182
+ end
183
+
184
+ context 'when the host and port are a hostname and port' do
185
+ let(:publish_address) do
186
+ 'testhost1.com:9250'
187
+ end
188
+
189
+ let(:hosts) do
190
+ sniffer.hosts
191
+ end
192
+
193
+ it 'parses the response' do
194
+ expect(hosts.size).to eq(1)
195
+ end
196
+
197
+ it 'correctly parses the host' do
198
+ expect(hosts[0][:host]).to eq('testhost1.com')
199
+ end
200
+
201
+ it 'correctly parses the port' do
202
+ expect(hosts[0][:port]).to eq('9250')
203
+ end
204
+ end
205
+
206
+ context 'when the host and port are in the format: hostname/ip:port' do
207
+ let(:publish_address) do
208
+ 'example.com/127.0.0.1:9250'
209
+ end
210
+
211
+ it 'parses the response' do
212
+ expect(hosts.size).to eq(1)
213
+ end
214
+
215
+ it 'uses the hostname' do
216
+ expect(hosts[0][:host]).to eq('example.com')
217
+ end
218
+
219
+ it 'correctly parses the port' do
220
+ expect(hosts[0][:port]).to eq('9250')
221
+ end
222
+
223
+ context 'when the address is IPv6' do
224
+ let(:publish_address) do
225
+ 'example.com/[::1]:9250'
226
+ end
227
+
228
+ it 'parses the response' do
229
+ expect(hosts.size).to eq(1)
230
+ end
231
+
232
+ it 'uses the hostname' do
233
+ expect(hosts[0][:host]).to eq('example.com')
234
+ end
235
+
236
+ it 'correctly parses the port' do
237
+ expect(hosts[0][:port]).to eq('9250')
238
+ end
239
+ end
240
+ end
241
+
242
+ context 'when the address is IPv6' do
243
+ let(:publish_address) do
244
+ '[::1]:9250'
245
+ end
246
+
247
+ it 'parses the response' do
248
+ expect(hosts.size).to eq(1)
249
+ end
250
+
251
+ it 'correctly parses the host' do
252
+ expect(hosts[0][:host]).to eq('::1')
253
+ end
254
+
255
+ it 'correctly parses the port' do
256
+ expect(hosts[0][:port]).to eq('9250')
257
+ end
258
+ end
259
+
260
+ context 'when the transport has :randomize_hosts option' do
261
+ let(:raw_response) do
262
+ { 'nodes' => { 'n1' => { 'http' => { 'publish_address' => '127.0.0.1:9250' } },
263
+ 'n2' => { 'http' => { 'publish_address' => '127.0.0.1:9251' } } } }
264
+ end
265
+
266
+ before do
267
+ allow(transport).to receive(:options).and_return(randomize_hosts: true)
268
+ end
269
+
270
+ it 'shuffles the list' do
271
+ expect(hosts.size).to eq(2)
272
+ end
273
+ end
274
+ end
275
+ end