elasticsearch-transport 6.1.0 → 6.2.0
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 +5 -5
- data/Gemfile +5 -0
- data/Rakefile +12 -2
- data/elasticsearch-transport.gemspec +2 -2
- data/lib/elasticsearch/transport.rb +1 -0
- data/lib/elasticsearch/transport/client.rb +81 -62
- data/lib/elasticsearch/transport/redacted.rb +75 -0
- data/lib/elasticsearch/transport/transport/base.rb +2 -2
- data/lib/elasticsearch/transport/transport/connections/connection.rb +1 -1
- data/lib/elasticsearch/transport/transport/http/faraday.rb +4 -5
- data/lib/elasticsearch/transport/version.rb +1 -1
- data/spec/elasticsearch/transport/base_spec.rb +81 -0
- data/spec/elasticsearch/transport/client_spec.rb +929 -0
- data/spec/spec_helper.rb +61 -0
- data/test/integration/transport_test.rb +5 -5
- data/test/test_helper.rb +6 -0
- data/test/unit/transport_base_test.rb +16 -0
- metadata +15 -13
- data/test/integration/client_test.rb +0 -242
- data/test/unit/client_test.rb +0 -373
@@ -0,0 +1,929 @@
|
|
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 Elasticsearch::Transport::Client do
|
21
|
+
|
22
|
+
let(:client) do
|
23
|
+
described_class.new.tap do |_client|
|
24
|
+
allow(_client).to receive(:__build_connections)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'is aliased as Elasticsearch::Client' do
|
29
|
+
expect(Elasticsearch::Client.new).to be_a(described_class)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a default transport' do
|
33
|
+
expect(client.transport).to be_a(Elasticsearch::Transport::Client::DEFAULT_TRANSPORT_CLASS)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'uses Faraday as the default transport' do
|
37
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Faraday/)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'sets the \'Content-Type\' header to \'application/json\' by default' do
|
41
|
+
expect(client.transport.options[:transport_options][:headers]['Content-Type']).to eq('application/json')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'uses localhost by default' do
|
45
|
+
expect(client.transport.hosts[0][:host]).to eq('localhost')
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'adapter' do
|
49
|
+
|
50
|
+
context 'when no adapter is specified' do
|
51
|
+
|
52
|
+
let(:adapter) do
|
53
|
+
client.transport.connections.all.first.connection.builder.handlers
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'uses Faraday NetHttp' do
|
57
|
+
expect(adapter).to include(Faraday::Adapter::NetHttp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when the adapter is specified' do
|
62
|
+
|
63
|
+
let(:adapter) do
|
64
|
+
client.transport.connections.all.first.connection.builder.handlers
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:client) do
|
68
|
+
described_class.new(adapter: :typhoeus)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'uses Faraday' do
|
72
|
+
expect(adapter).to include(Faraday::Adapter::Typhoeus)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when the adapter can be detected', unless: jruby? do
|
77
|
+
|
78
|
+
around do |example|
|
79
|
+
require 'patron'; load 'patron.rb'
|
80
|
+
example.run
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:adapter) do
|
84
|
+
client.transport.connections.all.first.connection.builder.handlers
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'uses the detected adapter' do
|
88
|
+
expect(adapter).to include(Faraday::Adapter::Patron)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when the Faraday adapter is configured' do
|
93
|
+
|
94
|
+
let(:client) do
|
95
|
+
described_class.new do |faraday|
|
96
|
+
faraday.adapter :typhoeus
|
97
|
+
faraday.response :logger
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
let(:handlers) do
|
102
|
+
client.transport.connections.all.first.connection.builder.handlers
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'sets the adapter' do
|
106
|
+
expect(handlers).to include(Faraday::Adapter::Typhoeus)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'sets the logger' do
|
110
|
+
expect(handlers).to include(Faraday::Response::Logger)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
shared_examples_for 'a client that extracts hosts' do
|
116
|
+
|
117
|
+
context 'when the hosts are a String' do
|
118
|
+
|
119
|
+
let(:host) do
|
120
|
+
'myhost'
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'extracts the host' do
|
124
|
+
expect(hosts[0][:host]).to eq('myhost')
|
125
|
+
expect(hosts[0][:protocol]).to eq('http')
|
126
|
+
expect(hosts[0][:port]).to be(9200)
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'when a path is specified' do
|
130
|
+
|
131
|
+
let(:host) do
|
132
|
+
'https://myhost:8080/api'
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'extracts the host' do
|
136
|
+
expect(hosts[0][:host]).to eq('myhost')
|
137
|
+
expect(hosts[0][:scheme]).to eq('https')
|
138
|
+
expect(hosts[0][:path]).to eq('/api')
|
139
|
+
expect(hosts[0][:port]).to be(8080)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when a scheme is specified' do
|
144
|
+
|
145
|
+
let(:host) do
|
146
|
+
'https://myhost:8080'
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'extracts the host' do
|
150
|
+
expect(hosts[0][:host]).to eq('myhost')
|
151
|
+
expect(hosts[0][:scheme]).to eq('https')
|
152
|
+
expect(hosts[0][:port]).to be(8080)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'when credentials are specified' do
|
157
|
+
|
158
|
+
let(:host) do
|
159
|
+
'http://USERNAME:PASSWORD@myhost:8080'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'extracts the host' do
|
163
|
+
expect(hosts[0][:host]).to eq('myhost')
|
164
|
+
expect(hosts[0][:scheme]).to eq('http')
|
165
|
+
expect(hosts[0][:user]).to eq('USERNAME')
|
166
|
+
expect(hosts[0][:password]).to eq('PASSWORD')
|
167
|
+
expect(hosts[0][:port]).to be(8080)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when there is a trailing slash' do
|
172
|
+
|
173
|
+
let(:host) do
|
174
|
+
'http://myhost/'
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'extracts the host' do
|
178
|
+
expect(hosts[0][:host]).to eq('myhost')
|
179
|
+
expect(hosts[0][:scheme]).to eq('http')
|
180
|
+
expect(hosts[0][:path]).to eq('')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'when there is a trailing slash with a path' do
|
185
|
+
|
186
|
+
let(:host) do
|
187
|
+
'http://myhost/foo/bar/'
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'extracts the host' do
|
191
|
+
expect(hosts[0][:host]).to eq('myhost')
|
192
|
+
expect(hosts[0][:scheme]).to eq('http')
|
193
|
+
expect(hosts[0][:path]).to eq('/foo/bar')
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'when the hosts are a Hash' do
|
199
|
+
|
200
|
+
let(:host) do
|
201
|
+
{ :host => 'myhost', :scheme => 'https' }
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'extracts the host' do
|
205
|
+
expect(hosts[0][:host]).to eq('myhost')
|
206
|
+
expect(hosts[0][:scheme]).to eq('https')
|
207
|
+
expect(hosts[0][:port]).to be(9200)
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when the port is specified as a String' do
|
211
|
+
|
212
|
+
let(:host) do
|
213
|
+
{ host: 'myhost', scheme: 'https', port: '443' }
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'extracts the host' do
|
217
|
+
expect(hosts[0][:host]).to eq('myhost')
|
218
|
+
expect(hosts[0][:scheme]).to eq('https')
|
219
|
+
expect(hosts[0][:port]).to be(443)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when the port is specified as an Integer' do
|
224
|
+
|
225
|
+
let(:host) do
|
226
|
+
{ host: 'myhost', scheme: 'https', port: 443 }
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'extracts the host' do
|
230
|
+
expect(hosts[0][:host]).to eq('myhost')
|
231
|
+
expect(hosts[0][:scheme]).to eq('https')
|
232
|
+
expect(hosts[0][:port]).to be(443)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'when the hosts are a Hashie:Mash' do
|
238
|
+
|
239
|
+
let(:host) do
|
240
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https')
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'extracts the host' do
|
244
|
+
expect(hosts[0][:host]).to eq('myhost')
|
245
|
+
expect(hosts[0][:scheme]).to eq('https')
|
246
|
+
expect(hosts[0][:port]).to be(9200)
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'when the port is specified as a String' do
|
250
|
+
|
251
|
+
let(:host) do
|
252
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https', port: '443')
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'extracts the host' do
|
256
|
+
expect(hosts[0][:host]).to eq('myhost')
|
257
|
+
expect(hosts[0][:scheme]).to eq('https')
|
258
|
+
expect(hosts[0][:port]).to be(443)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'when the port is specified as an Integer' do
|
263
|
+
|
264
|
+
let(:host) do
|
265
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https', port: 443)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'extracts the host' do
|
269
|
+
expect(hosts[0][:host]).to eq('myhost')
|
270
|
+
expect(hosts[0][:scheme]).to eq('https')
|
271
|
+
expect(hosts[0][:port]).to be(443)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'when the hosts are an array' do
|
277
|
+
|
278
|
+
context 'when there is one host' do
|
279
|
+
|
280
|
+
let(:host) do
|
281
|
+
['myhost']
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'extracts the host' do
|
285
|
+
expect(hosts[0][:host]).to eq('myhost')
|
286
|
+
expect(hosts[0][:protocol]).to eq('http')
|
287
|
+
expect(hosts[0][:port]).to be(9200)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context 'when there is more than one host' do
|
292
|
+
|
293
|
+
let(:host) do
|
294
|
+
['host1', 'host2']
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'extracts the host' do
|
298
|
+
expect(hosts[0][:host]).to eq('host1')
|
299
|
+
expect(hosts[0][:protocol]).to eq('http')
|
300
|
+
expect(hosts[0][:port]).to be(9200)
|
301
|
+
expect(hosts[1][:host]).to eq('host2')
|
302
|
+
expect(hosts[1][:protocol]).to eq('http')
|
303
|
+
expect(hosts[1][:port]).to be(9200)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'when ports are also specified' do
|
308
|
+
|
309
|
+
let(:host) do
|
310
|
+
['host1:1000', 'host2:2000']
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'extracts the host' do
|
314
|
+
expect(hosts[0][:host]).to eq('host1')
|
315
|
+
expect(hosts[0][:protocol]).to eq('http')
|
316
|
+
expect(hosts[0][:port]).to be(1000)
|
317
|
+
expect(hosts[1][:host]).to eq('host2')
|
318
|
+
expect(hosts[1][:protocol]).to eq('http')
|
319
|
+
expect(hosts[1][:port]).to be(2000)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
context 'when the hosts is an instance of URI' do
|
325
|
+
|
326
|
+
let(:host) do
|
327
|
+
URI.parse('https://USERNAME:PASSWORD@myhost:4430')
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'extracts the host' do
|
331
|
+
expect(hosts[0][:host]).to eq('myhost')
|
332
|
+
expect(hosts[0][:scheme]).to eq('https')
|
333
|
+
expect(hosts[0][:port]).to be(4430)
|
334
|
+
expect(hosts[0][:user]).to eq('USERNAME')
|
335
|
+
expect(hosts[0][:password]).to eq('PASSWORD')
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context 'when the hosts is invalid' do
|
340
|
+
|
341
|
+
let(:host) do
|
342
|
+
123
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'extracts the host' do
|
346
|
+
expect {
|
347
|
+
hosts
|
348
|
+
}.to raise_exception(ArgumentError)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
context 'when hosts are specified with the \'host\' key' do
|
354
|
+
|
355
|
+
let(:client) do
|
356
|
+
described_class.new(hosts: ['host1', 'host2', 'host3', 'host4'], randomize_hosts: true)
|
357
|
+
end
|
358
|
+
|
359
|
+
let(:hosts) do
|
360
|
+
client.transport.hosts
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'sets the hosts in random order' do
|
364
|
+
expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
context 'when hosts are specified with the \'host\' key' do
|
369
|
+
|
370
|
+
let(:client) do
|
371
|
+
described_class.new(host: host)
|
372
|
+
end
|
373
|
+
|
374
|
+
let(:hosts) do
|
375
|
+
client.transport.hosts
|
376
|
+
end
|
377
|
+
|
378
|
+
it_behaves_like 'a client that extracts hosts'
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'when hosts are specified with the \'hosts\' key' do
|
382
|
+
|
383
|
+
let(:client) do
|
384
|
+
described_class.new(host: host)
|
385
|
+
end
|
386
|
+
|
387
|
+
let(:hosts) do
|
388
|
+
client.transport.hosts
|
389
|
+
end
|
390
|
+
|
391
|
+
it_behaves_like 'a client that extracts hosts'
|
392
|
+
end
|
393
|
+
|
394
|
+
context 'when hosts are specified with the \'url\' key' do
|
395
|
+
|
396
|
+
let(:client) do
|
397
|
+
described_class.new(host: host)
|
398
|
+
end
|
399
|
+
|
400
|
+
let(:hosts) do
|
401
|
+
client.transport.hosts
|
402
|
+
end
|
403
|
+
|
404
|
+
it_behaves_like 'a client that extracts hosts'
|
405
|
+
end
|
406
|
+
|
407
|
+
context 'when hosts are specified with the \'urls\' key' do
|
408
|
+
|
409
|
+
let(:client) do
|
410
|
+
described_class.new(host: host)
|
411
|
+
end
|
412
|
+
|
413
|
+
let(:hosts) do
|
414
|
+
client.transport.hosts
|
415
|
+
end
|
416
|
+
|
417
|
+
it_behaves_like 'a client that extracts hosts'
|
418
|
+
end
|
419
|
+
|
420
|
+
context 'when the URL is set in the ELASTICSEARCH_URL environment variable' do
|
421
|
+
|
422
|
+
context 'when there is only one host specified' do
|
423
|
+
|
424
|
+
around do |example|
|
425
|
+
before_url = ENV['ELASTICSEARCH_URL']
|
426
|
+
ENV['ELASTICSEARCH_URL'] = 'example.com'
|
427
|
+
example.run
|
428
|
+
ENV['ELASTICSEARCH_URL'] = before_url
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'sets the host' do
|
432
|
+
expect(client.transport.hosts[0][:host]).to eq('example.com')
|
433
|
+
expect(client.transport.hosts.size).to eq(1)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
context 'when mutliple hosts are specified as a comma-separated String list' do
|
438
|
+
|
439
|
+
around do |example|
|
440
|
+
before_url = ENV['ELASTICSEARCH_URL']
|
441
|
+
ENV['ELASTICSEARCH_URL'] = 'example.com, other.com'
|
442
|
+
example.run
|
443
|
+
ENV['ELASTICSEARCH_URL'] = before_url
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'sets the hosts' do
|
447
|
+
expect(client.transport.hosts[0][:host]).to eq('example.com')
|
448
|
+
expect(client.transport.hosts[1][:host]).to eq('other.com')
|
449
|
+
expect(client.transport.hosts.size).to eq(2)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
context 'when options are defined' do
|
455
|
+
|
456
|
+
context 'when scheme is specified' do
|
457
|
+
|
458
|
+
let(:client) do
|
459
|
+
described_class.new(scheme: 'https')
|
460
|
+
end
|
461
|
+
|
462
|
+
it 'sets the scheme' do
|
463
|
+
expect(client.transport.connections[0].full_url('')).to match(/https/)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
context 'when user and password are specified' do
|
468
|
+
|
469
|
+
let(:client) do
|
470
|
+
described_class.new(user: 'USERNAME', password: 'PASSWORD')
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'sets the user and password' do
|
474
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
475
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
476
|
+
end
|
477
|
+
|
478
|
+
context 'when the connections are reloaded' do
|
479
|
+
|
480
|
+
before do
|
481
|
+
allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
|
482
|
+
client.transport.reload_connections!
|
483
|
+
end
|
484
|
+
|
485
|
+
it 'sets keeps user and password' do
|
486
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
487
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
488
|
+
expect(client.transport.connections[0].full_url('')).to match(/foobar/)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
context 'when port is specified' do
|
494
|
+
|
495
|
+
let(:client) do
|
496
|
+
described_class.new(host: 'node1', port: 1234)
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'sets the port' do
|
500
|
+
expect(client.transport.connections[0].full_url('')).to match(/1234/)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
context 'when the log option is true' do
|
505
|
+
|
506
|
+
let(:client) do
|
507
|
+
described_class.new(log: true)
|
508
|
+
end
|
509
|
+
|
510
|
+
it 'has a default logger for transport' do
|
511
|
+
expect(client.transport.logger.info).to eq(described_class::DEFAULT_LOGGER.call.info)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'when the trace option is true' do
|
516
|
+
|
517
|
+
let(:client) do
|
518
|
+
described_class.new(trace: true)
|
519
|
+
end
|
520
|
+
|
521
|
+
it 'has a default logger for transport' do
|
522
|
+
expect(client.transport.tracer.info).to eq(described_class::DEFAULT_TRACER.call.info)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
context 'when a custom transport class is specified' do
|
527
|
+
|
528
|
+
let(:transport_class) do
|
529
|
+
Class.new { def initialize(*); end }
|
530
|
+
end
|
531
|
+
|
532
|
+
let(:client) do
|
533
|
+
described_class.new(transport_class: transport_class)
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'allows the custom transport class to be defined' do
|
537
|
+
expect(client.transport).to be_a(transport_class)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
context 'when a custom transport instance is specified' do
|
542
|
+
|
543
|
+
let(:transport_instance) do
|
544
|
+
Class.new { def initialize(*); end }.new
|
545
|
+
end
|
546
|
+
|
547
|
+
let(:client) do
|
548
|
+
described_class.new(transport: transport_instance)
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'allows the custom transport class to be defined' do
|
552
|
+
expect(client.transport).to be(transport_instance)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
context 'when \'transport_options\' are defined' do
|
557
|
+
|
558
|
+
let(:client) do
|
559
|
+
described_class.new(transport_options: { request: { timeout: 1 } })
|
560
|
+
end
|
561
|
+
|
562
|
+
it 'sets the options on the transport' do
|
563
|
+
expect(client.transport.options[:transport_options][:request]).to eq(timeout: 1)
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
context 'when \'request_timeout\' is defined' do
|
568
|
+
|
569
|
+
let(:client) do
|
570
|
+
described_class.new(request_timeout: 120)
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'sets the options on the transport' do
|
574
|
+
expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
describe '#perform_request' do
|
580
|
+
|
581
|
+
let(:transport_instance) do
|
582
|
+
Class.new { def initialize(*); end }.new
|
583
|
+
end
|
584
|
+
|
585
|
+
let(:client) do
|
586
|
+
described_class.new(transport: transport_instance)
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'delegates performing requests to the transport' do
|
590
|
+
expect(transport_instance).to receive(:perform_request).and_return(true)
|
591
|
+
expect(client.perform_request('GET', '/')).to be(true)
|
592
|
+
end
|
593
|
+
|
594
|
+
context 'when the \'send_get_body_as\' option is specified' do
|
595
|
+
|
596
|
+
let(:client) do
|
597
|
+
described_class.new(transport: transport_instance, :send_get_body_as => 'POST')
|
598
|
+
end
|
599
|
+
|
600
|
+
before do
|
601
|
+
expect(transport_instance).to receive(:perform_request).with('POST', '/', {},
|
602
|
+
'{"foo":"bar"}',
|
603
|
+
'{"Content-Type":"application/x-ndjson"}').and_return(true)
|
604
|
+
end
|
605
|
+
|
606
|
+
let(:request) do
|
607
|
+
client.perform_request('POST', '/', {}, '{"foo":"bar"}', '{"Content-Type":"application/x-ndjson"}')
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'sets the option' do
|
611
|
+
expect(request).to be(true)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
context 'when the client connects to Elasticsearch' do
|
617
|
+
|
618
|
+
let(:logger) do
|
619
|
+
Logger.new(STDERR).tap do |logger|
|
620
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
621
|
+
color = case severity
|
622
|
+
when /INFO/ then :green
|
623
|
+
when /ERROR|WARN|FATAL/ then :red
|
624
|
+
when /DEBUG/ then :cyan
|
625
|
+
else :white
|
626
|
+
end
|
627
|
+
ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
|
628
|
+
end
|
629
|
+
end unless ENV['QUIET']
|
630
|
+
end
|
631
|
+
|
632
|
+
let(:port) do
|
633
|
+
(ENV['TEST_CLUSTER_PORT'] || 9250).to_i
|
634
|
+
end
|
635
|
+
|
636
|
+
let(:transport_options) do
|
637
|
+
{}
|
638
|
+
end
|
639
|
+
|
640
|
+
let(:options) do
|
641
|
+
{}
|
642
|
+
end
|
643
|
+
|
644
|
+
let(:client) do
|
645
|
+
described_class.new({ host: hosts, logger: logger }.merge!(transport_options: transport_options).merge!(options))
|
646
|
+
end
|
647
|
+
|
648
|
+
context 'when a request is made' do
|
649
|
+
|
650
|
+
let!(:response) do
|
651
|
+
client.perform_request('GET', '_cluster/health')
|
652
|
+
end
|
653
|
+
|
654
|
+
it 'connects to the cluster' do
|
655
|
+
expect(response.body['number_of_nodes']).to be >(1)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
describe '#initialize' do
|
660
|
+
|
661
|
+
context 'when options are specified' do
|
662
|
+
|
663
|
+
let(:transport_options) do
|
664
|
+
{ headers: { accept: 'application/yaml', content_type: 'application/yaml' } }
|
665
|
+
end
|
666
|
+
|
667
|
+
let(:response) do
|
668
|
+
client.perform_request('GET', '_cluster/health')
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'applies the options to the client' do
|
672
|
+
expect(response.body).to match(/---\n/)
|
673
|
+
expect(response.headers['content-type']).to eq('application/yaml')
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
context 'when a block is provided' do
|
678
|
+
|
679
|
+
let(:client) do
|
680
|
+
Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
|
681
|
+
client.headers['Accept'] = 'application/yaml'
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
let(:response) do
|
686
|
+
client.perform_request('GET', '_cluster/health')
|
687
|
+
end
|
688
|
+
|
689
|
+
it 'executes the block' do
|
690
|
+
expect(response.body).to match(/---\n/)
|
691
|
+
expect(response.headers['content-type']).to eq('application/yaml')
|
692
|
+
end
|
693
|
+
|
694
|
+
context 'when the Faraday adapter is set in the block' do
|
695
|
+
|
696
|
+
let(:client) do
|
697
|
+
Elasticsearch::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
|
698
|
+
client.adapter(:net_http_persistent)
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
let(:connection_handler) do
|
703
|
+
client.transport.connections.first.connection.builder.handlers.first
|
704
|
+
end
|
705
|
+
|
706
|
+
let(:response) do
|
707
|
+
client.perform_request('GET', '_cluster/health')
|
708
|
+
end
|
709
|
+
|
710
|
+
it 'sets the adapter' do
|
711
|
+
expect(connection_handler.name).to eq('Faraday::Adapter::NetHttpPersistent')
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'uses the adapter to connect' do
|
715
|
+
expect(response.status).to eq(200)
|
716
|
+
end
|
717
|
+
end
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
describe '#options' do
|
722
|
+
|
723
|
+
context 'when retry_on_failure is true' do
|
724
|
+
|
725
|
+
context 'when a node is unreachable' do
|
726
|
+
|
727
|
+
let(:hosts) do
|
728
|
+
[ELASTICSEARCH_HOSTS.first, "foobar1", "foobar2"]
|
729
|
+
end
|
730
|
+
|
731
|
+
let(:options) do
|
732
|
+
{ retry_on_failure: true }
|
733
|
+
end
|
734
|
+
|
735
|
+
let(:responses) do
|
736
|
+
5.times.collect do
|
737
|
+
client.perform_request('GET', '_nodes/_local')
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
it 'retries on failure' do
|
742
|
+
expect(responses.all? { true }).to be(true)
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
context 'when retry_on_failure is an integer' do
|
748
|
+
|
749
|
+
let(:hosts) do
|
750
|
+
[ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2', 'foobar3']
|
751
|
+
end
|
752
|
+
|
753
|
+
let(:options) do
|
754
|
+
{ retry_on_failure: 1 }
|
755
|
+
end
|
756
|
+
|
757
|
+
it 'retries only the specified number of times' do
|
758
|
+
expect(client.perform_request('GET', '_nodes/_local'))
|
759
|
+
expect {
|
760
|
+
client.perform_request('GET', '_nodes/_local')
|
761
|
+
}.to raise_exception(Faraday::Error::ConnectionFailed)
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
context 'when reload_on_failure is true' do
|
766
|
+
|
767
|
+
let(:hosts) do
|
768
|
+
[ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2']
|
769
|
+
end
|
770
|
+
|
771
|
+
let(:options) do
|
772
|
+
{ reload_on_failure: true }
|
773
|
+
end
|
774
|
+
|
775
|
+
let(:responses) do
|
776
|
+
5.times.collect do
|
777
|
+
client.perform_request('GET', '_nodes/_local')
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'reloads the connections' do
|
782
|
+
expect(client.transport.connections.size).to eq(3)
|
783
|
+
expect(responses.all? { true }).to be(true)
|
784
|
+
expect(client.transport.connections.size).to eq(2)
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
context 'when retry_on_status is specified' do
|
789
|
+
|
790
|
+
let(:options) do
|
791
|
+
{ retry_on_status: 400 }
|
792
|
+
end
|
793
|
+
|
794
|
+
let(:logger) do
|
795
|
+
double('logger', :fatal => false)
|
796
|
+
end
|
797
|
+
|
798
|
+
before do
|
799
|
+
expect(logger).to receive(:warn).exactly(4).times
|
800
|
+
end
|
801
|
+
|
802
|
+
it 'retries when the status matches' do
|
803
|
+
expect {
|
804
|
+
client.perform_request('PUT', '_foobar')
|
805
|
+
}.to raise_exception(Elasticsearch::Transport::Transport::Errors::BadRequest)
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
describe '#perform_request' do
|
811
|
+
|
812
|
+
context 'when a request is made' do
|
813
|
+
|
814
|
+
before do
|
815
|
+
client.perform_request('DELETE', '_all')
|
816
|
+
client.perform_request('DELETE', 'myindex') rescue
|
817
|
+
client.perform_request('PUT', 'myindex', {}, { settings: { number_of_shards: 10 } })
|
818
|
+
client.perform_request('PUT', 'myindex/mydoc/1', { routing: 'XYZ', timeout: '1s' }, { foo: 'bar' })
|
819
|
+
client.perform_request('GET', '_cluster/health?wait_for_status=green', {})
|
820
|
+
end
|
821
|
+
|
822
|
+
let(:response) do
|
823
|
+
client.perform_request('GET', 'myindex/mydoc/1?routing=XYZ')
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'handles paths and URL paramters' do
|
827
|
+
expect(response.status).to eq(200)
|
828
|
+
end
|
829
|
+
|
830
|
+
it 'returns response body' do
|
831
|
+
expect(response.body['_source']).to eq('foo' => 'bar')
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
context 'when an invalid url is specified' do
|
836
|
+
|
837
|
+
it 'raises an exception' do
|
838
|
+
expect {
|
839
|
+
client.perform_request('GET', 'myindex/mydoc/1?routing=FOOBARBAZ')
|
840
|
+
}.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound)
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
context 'when the \'ignore\' parameter is specified' do
|
845
|
+
|
846
|
+
let(:response) do
|
847
|
+
client.perform_request('PUT', '_foobar', ignore: 400)
|
848
|
+
end
|
849
|
+
|
850
|
+
it 'exposes the status in the response' do
|
851
|
+
expect(response.status).to eq(400)
|
852
|
+
end
|
853
|
+
|
854
|
+
it 'exposes the body of the response' do
|
855
|
+
expect(response.body).to be_a(Hash)
|
856
|
+
expect(response.body.inspect).to match(/invalid_index_name_exception/)
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
context 'when request headers are specified' do
|
861
|
+
|
862
|
+
let(:response) do
|
863
|
+
client.perform_request('GET', '/', {}, nil, { 'Content-Type' => 'application/yaml' })
|
864
|
+
end
|
865
|
+
|
866
|
+
it 'passes them to the transport' do
|
867
|
+
expect(response.body).to match(/---/)
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
describe 'selector' do
|
872
|
+
|
873
|
+
context 'when the round-robin selector is used' do
|
874
|
+
|
875
|
+
let(:nodes) do
|
876
|
+
3.times.collect do
|
877
|
+
client.perform_request('GET', '_nodes/_local').body['nodes'].to_a[0][1]['name']
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
let(:node_names) do
|
882
|
+
client.nodes.stats['nodes'].collect do |name, stats|
|
883
|
+
stats['name']
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
let(:expected_names) do
|
888
|
+
3.times.collect do |i|
|
889
|
+
node_names[i % node_names.size]
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
# it 'rotates nodes' do
|
894
|
+
# pending 'Better way to detect rotating nodes'
|
895
|
+
# expect(nodes).to eq(expected_names)
|
896
|
+
# end
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
context 'when patron is used as an adapter', unless: jruby? do
|
901
|
+
|
902
|
+
before do
|
903
|
+
require 'patron'
|
904
|
+
end
|
905
|
+
|
906
|
+
let(:options) do
|
907
|
+
{ adapter: :patron }
|
908
|
+
end
|
909
|
+
|
910
|
+
let(:connection_handler) do
|
911
|
+
client.transport.connections.first.connection.builder.handlers.first
|
912
|
+
end
|
913
|
+
|
914
|
+
it 'uses the patron connection handler' do
|
915
|
+
expect(connection_handler).to eq('Faraday::Adapter::Patron')
|
916
|
+
end
|
917
|
+
|
918
|
+
it 'keeps connections open' do
|
919
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
920
|
+
connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
921
|
+
client.transport.reload_connections!
|
922
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
923
|
+
connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
924
|
+
expect(connections_after).to be >= (connections_before)
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
end
|
929
|
+
end
|