elastic-transport 8.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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