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,166 @@
|
|
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::Connections::Selector do
|
21
|
+
before do
|
22
|
+
class BackupStrategySelector
|
23
|
+
include Elastic::Transport::Transport::Connections::Selector::Base
|
24
|
+
|
25
|
+
def select(options={})
|
26
|
+
connections.reject do |c|
|
27
|
+
c.host[:attributes] && c.host[:attributes][:backup]
|
28
|
+
end.sample
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
after do
|
34
|
+
Object.send(:remove_const, :BackupStrategySelector)
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:backup_strategy_selector) do
|
38
|
+
BackupStrategySelector.new
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'the Random selector' do
|
42
|
+
let(:connections) do
|
43
|
+
[1, 2]
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:selector) do
|
47
|
+
described_class::Random.new(connections: connections)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'is initialized with connections' do
|
51
|
+
expect(selector.connections).to eq(connections)
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#select' do
|
55
|
+
let(:connections) do
|
56
|
+
(0..19).to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns a connection' do
|
60
|
+
expect(selector.select).to be_a(Integer)
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when multiple threads are used' do
|
64
|
+
it 'allows threads to select connections in parallel' do
|
65
|
+
expect(10.times.collect do
|
66
|
+
threads = []
|
67
|
+
20.times do
|
68
|
+
threads << Thread.new do
|
69
|
+
selector.select
|
70
|
+
end
|
71
|
+
end
|
72
|
+
threads.map { |t| t.join }
|
73
|
+
selector.select
|
74
|
+
end).to all(be_a(Integer))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'the RoundRobin selector' do
|
81
|
+
let(:connections) do
|
82
|
+
['A', 'B', 'C']
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:selector) do
|
86
|
+
described_class::RoundRobin.new(connections: connections)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'is initialized with connections' do
|
90
|
+
expect(selector.connections).to eq(connections)
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#select' do
|
94
|
+
it 'rotates over the connections' do
|
95
|
+
expect(selector.select).to eq('A')
|
96
|
+
expect(selector.select).to eq('B')
|
97
|
+
expect(selector.select).to eq('C')
|
98
|
+
expect(selector.select).to eq('A')
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when multiple threads are used' do
|
102
|
+
let(:connections) do
|
103
|
+
(0..19).to_a
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns a connection' do
|
107
|
+
expect(selector.select).to be_a(Integer)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'allows threads to select connections in parallel' do
|
111
|
+
expect(10.times.collect do
|
112
|
+
threads = []
|
113
|
+
20.times do
|
114
|
+
threads << Thread.new do
|
115
|
+
selector.select
|
116
|
+
end
|
117
|
+
end
|
118
|
+
threads.map { |t| t.join }
|
119
|
+
selector.select
|
120
|
+
end).to eq((0..9).to_a)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'a custom selector' do
|
127
|
+
let(:connections) do
|
128
|
+
[
|
129
|
+
double(host: { hostname: 'host1' }),
|
130
|
+
double(host: { hostname: 'host2', attributes: { backup: true } })
|
131
|
+
]
|
132
|
+
end
|
133
|
+
|
134
|
+
let(:selector) do
|
135
|
+
BackupStrategySelector.new(connections: connections)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'is initialized with connections' do
|
139
|
+
expect(selector.connections).to eq(connections)
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#select' do
|
143
|
+
it 'applies the custom strategy' do
|
144
|
+
10.times { expect(selector.select.host[:hostname]).to eq('host1') }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when the Base module is included in a class' do
|
150
|
+
before do
|
151
|
+
class ExampleSelector
|
152
|
+
include Elastic::Transport::Transport::Connections::Selector::Base
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
after do
|
157
|
+
Object.send(:remove_const, :ExampleSelector)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'requires the #select method to be redefined' do
|
161
|
+
expect do
|
162
|
+
ExampleSelector.new.select
|
163
|
+
end.to raise_exception(NoMethodError)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,264 @@
|
|
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::Base do
|
21
|
+
context 'when a host is printed in a logged message' do
|
22
|
+
shared_examples_for 'a redacted string' do
|
23
|
+
let(:client) do
|
24
|
+
Elastic::Transport::Client.new(arguments)
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:logger) do
|
28
|
+
double('logger', error?: true, error: '')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not include the password in the logged string' do
|
32
|
+
expect(logger).not_to receive(:error).with(/secret_password/)
|
33
|
+
expect {
|
34
|
+
client.perform_request('GET', '/_cluster/stats')
|
35
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'replaces the password with the string \'REDACTED\'' do
|
39
|
+
expect(logger).to receive(:error).with(/REDACTED/)
|
40
|
+
expect {
|
41
|
+
client.perform_request('GET', '/_cluster/stats')
|
42
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when the user and password are provided as separate arguments' do
|
47
|
+
let(:arguments) do
|
48
|
+
{
|
49
|
+
hosts: 'fake',
|
50
|
+
logger: logger,
|
51
|
+
password: 'secret_password',
|
52
|
+
user: 'test'
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
it_behaves_like 'a redacted string'
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when the user and password are provided in the string URI' do
|
60
|
+
let(:arguments) do
|
61
|
+
{
|
62
|
+
hosts: 'http://test:secret_password@fake',
|
63
|
+
logger: logger
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
it_behaves_like 'a redacted string'
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when the user and password are provided in the URI object' do
|
71
|
+
let(:arguments) do
|
72
|
+
{
|
73
|
+
hosts: URI.parse('http://test:secret_password@fake'),
|
74
|
+
logger: logger
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
it_behaves_like 'a redacted string'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when reload_on_failure is true and and hosts are unreachable' do
|
83
|
+
let(:client) do
|
84
|
+
Elastic::Transport::Client.new(arguments)
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:arguments) do
|
88
|
+
{
|
89
|
+
hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
|
90
|
+
reload_on_failure: true,
|
91
|
+
sniffer_timeout: 5
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'raises an exception' do
|
96
|
+
expect { client.perform_request('GET', '/info') }.to raise_exception(Faraday::ConnectionFailed)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when the client has `retry_on_failure` set to an integer' do
|
101
|
+
let(:client) do
|
102
|
+
Elastic::Transport::Client.new(arguments)
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:arguments) do
|
106
|
+
{
|
107
|
+
hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
|
108
|
+
retry_on_failure: 2
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when `perform_request` is called without a `retry_on_failure` option value' do
|
113
|
+
before do
|
114
|
+
expect(client.transport).to receive(:get_connection).exactly(3).times.and_call_original
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'uses the client `retry_on_failure` value' do
|
118
|
+
expect {
|
119
|
+
client.transport.perform_request('GET', '/info')
|
120
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when `perform_request` is called with a `retry_on_status` option value' do
|
125
|
+
before do
|
126
|
+
expect(client.transport).to receive(:__raise_transport_error).exactly(6).times.and_call_original
|
127
|
+
end
|
128
|
+
|
129
|
+
let(:arguments) do
|
130
|
+
{
|
131
|
+
hosts: ELASTICSEARCH_HOSTS,
|
132
|
+
retry_on_status: ['404']
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'retries on 404 status the specified number of max_retries' do
|
137
|
+
expect do
|
138
|
+
client.transport.perform_request('GET', 'myindex/_doc/1?routing=FOOBARBAZ', {}, nil, nil, retry_on_failure: 5)
|
139
|
+
end.to raise_exception(Elastic::Transport::Transport::Errors::NotFound)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when `perform_request` is called with a `retry_on_failure` option value' do
|
144
|
+
before do
|
145
|
+
expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'uses the option `retry_on_failure` value' do
|
149
|
+
expect do
|
150
|
+
client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
|
151
|
+
end.to raise_exception(Faraday::ConnectionFailed)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'when the client has `retry_on_failure` set to true' do
|
157
|
+
let(:client) do
|
158
|
+
Elastic::Transport::Client.new(arguments)
|
159
|
+
end
|
160
|
+
|
161
|
+
let(:arguments) do
|
162
|
+
{
|
163
|
+
hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
|
164
|
+
retry_on_failure: true
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'when `perform_request` is called without a `retry_on_failure` option value' do
|
169
|
+
before do
|
170
|
+
expect(client.transport).to receive(:get_connection).exactly(4).times.and_call_original
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'uses the default `MAX_RETRIES` value' do
|
174
|
+
expect {
|
175
|
+
client.transport.perform_request('GET', '/info')
|
176
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'when `perform_request` is called with a `retry_on_failure` option value' do
|
181
|
+
before do
|
182
|
+
expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'uses the option `retry_on_failure` value' do
|
186
|
+
expect {
|
187
|
+
client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
|
188
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'when the client has `retry_on_failure` set to false' do
|
194
|
+
let(:client) do
|
195
|
+
Elastic::Transport::Client.new(arguments)
|
196
|
+
end
|
197
|
+
|
198
|
+
let(:arguments) do
|
199
|
+
{
|
200
|
+
hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
|
201
|
+
retry_on_failure: false
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'when `perform_request` is called without a `retry_on_failure` option value' do
|
206
|
+
before do
|
207
|
+
expect(client.transport).to receive(:get_connection).once.and_call_original
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'does not retry' do
|
211
|
+
expect {
|
212
|
+
client.transport.perform_request('GET', '/info')
|
213
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'when `perform_request` is called with a `retry_on_failure` option value' do
|
218
|
+
|
219
|
+
before do
|
220
|
+
expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'uses the option `retry_on_failure` value' do
|
224
|
+
expect {
|
225
|
+
client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
|
226
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'when the client has no `retry_on_failure` set' do
|
232
|
+
let(:client) do
|
233
|
+
Elastic::Transport::Client.new(arguments)
|
234
|
+
end
|
235
|
+
|
236
|
+
let(:arguments) do
|
237
|
+
{ hosts: ['http://unavailable:9200', 'http://unavailable:9201'] }
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'when `perform_request` is called without a `retry_on_failure` option value' do
|
241
|
+
before do
|
242
|
+
expect(client.transport).to receive(:get_connection).exactly(1).times.and_call_original
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'does not retry' do
|
246
|
+
expect do
|
247
|
+
client.transport.perform_request('GET', '/info')
|
248
|
+
end.to raise_exception(Faraday::ConnectionFailed)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'when `perform_request` is called with a `retry_on_failure` option value' do
|
253
|
+
before do
|
254
|
+
expect(client.transport).to receive(:get_connection).exactly(6).times.and_call_original
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'uses the option `retry_on_failure` value' do
|
258
|
+
expect do
|
259
|
+
client.transport.perform_request('GET', '/info', {}, nil, nil, retry_on_failure: 5)
|
260
|
+
end.to raise_exception(Faraday::ConnectionFailed)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,1651 @@
|
|
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
|
+
let(:client) do
|
22
|
+
described_class.new.tap do |_client|
|
23
|
+
allow(_client).to receive(:__build_connections)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has a default transport' do
|
28
|
+
expect(client.transport).to be_a(Elastic::Transport::Client::DEFAULT_TRANSPORT_CLASS)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'preserves the Faraday default user agent header' do
|
32
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Faraday/)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'identifies the Ruby client in the User-Agent header' do
|
36
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/elastic-transport-ruby\/#{Elastic::Transport::VERSION}/)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'identifies the Ruby version in the User-Agent header' do
|
40
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RUBY_VERSION}/)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'identifies the host_os in the User-Agent header' do
|
44
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase}/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'identifies the target_cpu in the User-Agent header' do
|
48
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['target_cpu']}/)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'sets the \'Content-Type\' header to \'application/json\' by default' do
|
52
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'uses localhost by default' do
|
56
|
+
expect(client.transport.hosts[0][:host]).to eq('localhost')
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when a User-Agent header is specified as client option' do
|
60
|
+
let(:client) do
|
61
|
+
described_class.new(transport_options: { headers: { 'User-Agent' => 'testing' } })
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'sets the specified User-Agent header' do
|
65
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when a user-agent header is specified as client option in lower-case' do
|
70
|
+
|
71
|
+
let(:client) do
|
72
|
+
described_class.new(transport_options: { headers: { 'user-agent' => 'testing' } })
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets the specified User-Agent header' do
|
76
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when a Content-Type header is specified as client option' do
|
81
|
+
|
82
|
+
let(:client) do
|
83
|
+
described_class.new(transport_options: { headers: { 'Content-Type' => 'testing' } })
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'sets the specified Content-Type header' do
|
87
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when a content-type header is specified as client option in lower-case' do
|
92
|
+
|
93
|
+
let(:client) do
|
94
|
+
described_class.new(transport_options: { headers: { 'content-type' => 'testing' } })
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets the specified Content-Type header' do
|
98
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when the Curb transport class is used', unless: jruby? do
|
103
|
+
|
104
|
+
let(:client) do
|
105
|
+
described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'preserves the Curb default user agent header' do
|
109
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/Curb/)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'identifies the Ruby client in the User-Agent header' do
|
113
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/elastic-transport-ruby\/#{Elastic::Transport::VERSION}/)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'identifies the Ruby version in the User-Agent header' do
|
117
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RUBY_VERSION}/)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'identifies the host_os in the User-Agent header' do
|
121
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase}/)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'identifies the target_cpu in the User-Agent header' do
|
125
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to match(/#{RbConfig::CONFIG['target_cpu']}/)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'sets the \'Content-Type\' header to \'application/json\' by default' do
|
129
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('application/json')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'uses localhost by default' do
|
133
|
+
expect(client.transport.hosts[0][:host]).to eq('localhost')
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when a User-Agent header is specified as a client option' do
|
137
|
+
|
138
|
+
let(:client) do
|
139
|
+
described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
|
140
|
+
transport_options: { headers: { 'User-Agent' => 'testing' } })
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'sets the specified User-Agent header' do
|
144
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when a user-agent header is specified as a client option as lower-case' do
|
149
|
+
|
150
|
+
let(:client) do
|
151
|
+
described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
|
152
|
+
transport_options: { headers: { 'user-agent' => 'testing' } })
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'sets the specified User-Agent header' do
|
156
|
+
expect(client.transport.connections.first.connection.headers['User-Agent']).to eq('testing')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'when a Content-Type header is specified as client option' do
|
161
|
+
|
162
|
+
let(:client) do
|
163
|
+
described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
|
164
|
+
transport_options: { headers: { 'Content-Type' => 'testing' } })
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'sets the specified Content-Type header' do
|
168
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when a content-type header is specified as client option in lower-case' do
|
173
|
+
|
174
|
+
let(:client) do
|
175
|
+
described_class.new(transport_class: Elastic::Transport::Transport::HTTP::Curb,
|
176
|
+
transport_options: { headers: { 'content-type' => 'testing' } })
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'sets the specified Content-Type header' do
|
180
|
+
expect(client.transport.connections.first.connection.headers['Content-Type']).to eq('testing')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'adapter' do
|
186
|
+
context 'when no adapter is specified' do
|
187
|
+
fork do
|
188
|
+
let(:client) { described_class.new }
|
189
|
+
let(:adapter) { client.transport.connections.all.first.connection.builder.adapter }
|
190
|
+
|
191
|
+
it 'uses Faraday NetHttp' do
|
192
|
+
expect(adapter).to eq Faraday::Adapter::NetHttp
|
193
|
+
end
|
194
|
+
end unless jruby?
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'when the adapter is patron' do
|
198
|
+
let(:adapter) do
|
199
|
+
client.transport.connections.all.first.connection.builder.adapter
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:client) do
|
203
|
+
described_class.new(adapter: :patron, enable_meta_header: false)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'uses Faraday with the adapter' do
|
207
|
+
expect(adapter).to eq Faraday::Adapter::Patron
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'when the adapter is typhoeus' do
|
212
|
+
let(:adapter) do
|
213
|
+
client.transport.connections.all.first.connection.builder.adapter
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:client) do
|
217
|
+
described_class.new(adapter: :typhoeus, enable_meta_header: false)
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'uses Faraday with the adapter' do
|
221
|
+
expect(adapter).to eq Faraday::Adapter::Typhoeus
|
222
|
+
end
|
223
|
+
end unless jruby?
|
224
|
+
|
225
|
+
context 'when the adapter is specified as a string key' do
|
226
|
+
let(:adapter) do
|
227
|
+
client.transport.connections.all.first.connection.builder.adapter
|
228
|
+
end
|
229
|
+
|
230
|
+
let(:client) do
|
231
|
+
described_class.new(adapter: :patron, enable_meta_header: false)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'uses Faraday with the adapter' do
|
235
|
+
expect(adapter).to eq Faraday::Adapter::Patron
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'when the adapter can be detected', unless: jruby? do
|
240
|
+
around do |example|
|
241
|
+
require 'patron'; load 'patron.rb'
|
242
|
+
example.run
|
243
|
+
end
|
244
|
+
|
245
|
+
let(:adapter) do
|
246
|
+
client.transport.connections.all.first.connection.builder.adapter
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'uses the detected adapter' do
|
250
|
+
expect(adapter).to eq Faraday::Adapter::Patron
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when the Faraday adapter is configured' do
|
255
|
+
let(:client) do
|
256
|
+
described_class.new do |faraday|
|
257
|
+
faraday.adapter :patron
|
258
|
+
faraday.response :logger
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
let(:adapter) do
|
263
|
+
client.transport.connections.all.first.connection.builder.adapter
|
264
|
+
end
|
265
|
+
|
266
|
+
let(:handlers) do
|
267
|
+
client.transport.connections.all.first.connection.builder.handlers
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'sets the adapter' do
|
271
|
+
expect(adapter).to eq Faraday::Adapter::Patron
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'sets the logger' do
|
275
|
+
expect(handlers).to include(Faraday::Response::Logger)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
shared_examples_for 'a client that extracts hosts' do
|
281
|
+
context 'when the host is a String' do
|
282
|
+
context 'when there is a protocol specified' do
|
283
|
+
context 'when credentials are specified \'http://USERNAME:PASSWORD@myhost:8080\'' do
|
284
|
+
let(:host) do
|
285
|
+
'http://USERNAME:PASSWORD@myhost:8080'
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'extracts the credentials' do
|
289
|
+
expect(hosts[0][:user]).to eq('USERNAME')
|
290
|
+
expect(hosts[0][:password]).to eq('PASSWORD')
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'extracts the host' do
|
294
|
+
expect(hosts[0][:host]).to eq('myhost')
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'extracts the port' do
|
298
|
+
expect(hosts[0][:port]).to be(8080)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context 'when there is a trailing slash \'http://myhost/\'' do
|
303
|
+
let(:host) do
|
304
|
+
'http://myhost/'
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'extracts the host' do
|
308
|
+
expect(hosts[0][:host]).to eq('myhost')
|
309
|
+
expect(hosts[0][:scheme]).to eq('http')
|
310
|
+
expect(hosts[0][:path]).to eq('')
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'extracts the scheme' do
|
314
|
+
expect(hosts[0][:scheme]).to eq('http')
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'extracts the path' do
|
318
|
+
expect(hosts[0][:path]).to eq('')
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context 'when there is a trailing slash with a path \'http://myhost/foo/bar/\'' do
|
323
|
+
let(:host) do
|
324
|
+
'http://myhost/foo/bar/'
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'extracts the host' do
|
328
|
+
expect(hosts[0][:host]).to eq('myhost')
|
329
|
+
expect(hosts[0][:scheme]).to eq('http')
|
330
|
+
expect(hosts[0][:path]).to eq('/foo/bar')
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
context 'when the protocol is http' do
|
335
|
+
context 'when there is no port specified \'http://myhost\'' do
|
336
|
+
let(:host) do
|
337
|
+
'http://myhost'
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'extracts the host' do
|
341
|
+
expect(hosts[0][:host]).to eq('myhost')
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'extracts the protocol' do
|
345
|
+
expect(hosts[0][:protocol]).to eq('http')
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'defaults to port 9200' do
|
349
|
+
expect(hosts[0][:port]).to be(9200)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
context 'when there is a port specified \'http://myhost:7101\'' do
|
354
|
+
|
355
|
+
let(:host) do
|
356
|
+
'http://myhost:7101'
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'extracts the host' do
|
360
|
+
expect(hosts[0][:host]).to eq('myhost')
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'extracts the protocol' do
|
364
|
+
expect(hosts[0][:protocol]).to eq('http')
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'extracts the port' do
|
368
|
+
expect(hosts[0][:port]).to be(7101)
|
369
|
+
end
|
370
|
+
|
371
|
+
context 'when there is a path specified \'http://myhost:7101/api\'' do
|
372
|
+
|
373
|
+
let(:host) do
|
374
|
+
'http://myhost:7101/api'
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'sets the path' do
|
378
|
+
expect(hosts[0][:host]).to eq('myhost')
|
379
|
+
expect(hosts[0][:protocol]).to eq('http')
|
380
|
+
expect(hosts[0][:path]).to eq('/api')
|
381
|
+
expect(hosts[0][:port]).to be(7101)
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'extracts the host' do
|
385
|
+
expect(hosts[0][:host]).to eq('myhost')
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'extracts the protocol' do
|
389
|
+
expect(hosts[0][:protocol]).to eq('http')
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'extracts the port' do
|
393
|
+
expect(hosts[0][:port]).to be(7101)
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'extracts the path' do
|
397
|
+
expect(hosts[0][:path]).to eq('/api')
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
context 'when the protocol is https' do
|
404
|
+
|
405
|
+
context 'when there is no port specified \'https://myhost\'' do
|
406
|
+
|
407
|
+
let(:host) do
|
408
|
+
'https://myhost'
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'extracts the host' do
|
412
|
+
expect(hosts[0][:host]).to eq('myhost')
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'extracts the protocol' do
|
416
|
+
expect(hosts[0][:protocol]).to eq('https')
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'defaults to port 443' do
|
420
|
+
expect(hosts[0][:port]).to be(443)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
context 'when there is a port specified \'https://myhost:7101\'' do
|
425
|
+
|
426
|
+
let(:host) do
|
427
|
+
'https://myhost:7101'
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'extracts the host' do
|
431
|
+
expect(hosts[0][:host]).to eq('myhost')
|
432
|
+
end
|
433
|
+
|
434
|
+
it 'extracts the protocol' do
|
435
|
+
expect(hosts[0][:protocol]).to eq('https')
|
436
|
+
end
|
437
|
+
|
438
|
+
it 'extracts the port' do
|
439
|
+
expect(hosts[0][:port]).to be(7101)
|
440
|
+
end
|
441
|
+
|
442
|
+
context 'when there is a path specified \'https://myhost:7101/api\'' do
|
443
|
+
|
444
|
+
let(:host) do
|
445
|
+
'https://myhost:7101/api'
|
446
|
+
end
|
447
|
+
|
448
|
+
it 'extracts the host' do
|
449
|
+
expect(hosts[0][:host]).to eq('myhost')
|
450
|
+
end
|
451
|
+
|
452
|
+
it 'extracts the protocol' do
|
453
|
+
expect(hosts[0][:protocol]).to eq('https')
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'extracts the port' do
|
457
|
+
expect(hosts[0][:port]).to be(7101)
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'extracts the path' do
|
461
|
+
expect(hosts[0][:path]).to eq('/api')
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
context 'when IPv6 format is used' do
|
467
|
+
|
468
|
+
around do |example|
|
469
|
+
original_setting = Faraday.ignore_env_proxy
|
470
|
+
Faraday.ignore_env_proxy = true
|
471
|
+
example.run
|
472
|
+
Faraday.ignore_env_proxy = original_setting
|
473
|
+
end
|
474
|
+
|
475
|
+
let(:host) do
|
476
|
+
'https://[2090:db8:85a3:9811::1f]:8080'
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'extracts the host' do
|
480
|
+
expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'extracts the protocol' do
|
484
|
+
expect(hosts[0][:protocol]).to eq('https')
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'extracts the port' do
|
488
|
+
expect(hosts[0][:port]).to be(8080)
|
489
|
+
end
|
490
|
+
|
491
|
+
it 'creates the correct full url' do
|
492
|
+
expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:8080')
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
context 'when no protocol is specified \'myhost\'' do
|
499
|
+
|
500
|
+
let(:host) do
|
501
|
+
'myhost'
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'defaults to http' do
|
505
|
+
expect(hosts[0][:host]).to eq('myhost')
|
506
|
+
expect(hosts[0][:protocol]).to eq('http')
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'uses port 9200' do
|
510
|
+
expect(hosts[0][:port]).to be(9200)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'when the host is a Hash' do
|
516
|
+
|
517
|
+
let(:host) do
|
518
|
+
{ :host => 'myhost', :scheme => 'https' }
|
519
|
+
end
|
520
|
+
|
521
|
+
it 'extracts the host' do
|
522
|
+
expect(hosts[0][:host]).to eq('myhost')
|
523
|
+
end
|
524
|
+
|
525
|
+
it 'extracts the protocol' do
|
526
|
+
expect(hosts[0][:protocol]).to eq('https')
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'extracts the port' do
|
530
|
+
expect(hosts[0][:port]).to be(9200)
|
531
|
+
end
|
532
|
+
|
533
|
+
context 'when IPv6 format is used' do
|
534
|
+
|
535
|
+
around do |example|
|
536
|
+
original_setting = Faraday.ignore_env_proxy
|
537
|
+
Faraday.ignore_env_proxy = true
|
538
|
+
example.run
|
539
|
+
Faraday.ignore_env_proxy = original_setting
|
540
|
+
end
|
541
|
+
|
542
|
+
let(:host) do
|
543
|
+
{ host: '[2090:db8:85a3:9811::1f]', scheme: 'https', port: '443' }
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'extracts the host' do
|
547
|
+
expect(hosts[0][:host]).to eq('[2090:db8:85a3:9811::1f]')
|
548
|
+
expect(hosts[0][:scheme]).to eq('https')
|
549
|
+
expect(hosts[0][:port]).to be(443)
|
550
|
+
end
|
551
|
+
|
552
|
+
it 'creates the correct full url' do
|
553
|
+
expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://[2090:db8:85a3:9811::1f]:443')
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
context 'when the host is localhost as a IPv6 address' do
|
558
|
+
|
559
|
+
around do |example|
|
560
|
+
original_setting = Faraday.ignore_env_proxy
|
561
|
+
Faraday.ignore_env_proxy = true
|
562
|
+
example.run
|
563
|
+
Faraday.ignore_env_proxy = original_setting
|
564
|
+
end
|
565
|
+
|
566
|
+
let(:host) do
|
567
|
+
{ host: '[::1]' }
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'extracts the host' do
|
571
|
+
expect(hosts[0][:host]).to eq('[::1]')
|
572
|
+
expect(hosts[0][:port]).to be(9200)
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'creates the correct full url' do
|
576
|
+
expect(client.transport.__full_url(client.transport.hosts[0])).to eq('http://[::1]:9200')
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
context 'when the port is specified as a String' do
|
581
|
+
|
582
|
+
let(:host) do
|
583
|
+
{ host: 'myhost', scheme: 'https', port: '443' }
|
584
|
+
end
|
585
|
+
|
586
|
+
it 'extracts the host' do
|
587
|
+
expect(hosts[0][:host]).to eq('myhost')
|
588
|
+
end
|
589
|
+
|
590
|
+
it 'extracts the protocol' do
|
591
|
+
expect(hosts[0][:scheme]).to eq('https')
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'converts the port to an integer' do
|
595
|
+
expect(hosts[0][:port]).to be(443)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
context 'when the port is specified as an Integer' do
|
600
|
+
|
601
|
+
let(:host) do
|
602
|
+
{ host: 'myhost', scheme: 'https', port: 443 }
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'extracts the host' do
|
606
|
+
expect(hosts[0][:host]).to eq('myhost')
|
607
|
+
end
|
608
|
+
|
609
|
+
it 'extracts the protocol' do
|
610
|
+
expect(hosts[0][:scheme]).to eq('https')
|
611
|
+
end
|
612
|
+
|
613
|
+
it 'extracts port as an integer' do
|
614
|
+
expect(hosts[0][:port]).to be(443)
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
context 'when the hosts are a Hashie:Mash' do
|
620
|
+
|
621
|
+
let(:host) do
|
622
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https')
|
623
|
+
end
|
624
|
+
|
625
|
+
it 'extracts the host' do
|
626
|
+
expect(hosts[0][:host]).to eq('myhost')
|
627
|
+
end
|
628
|
+
|
629
|
+
it 'extracts the protocol' do
|
630
|
+
expect(hosts[0][:scheme]).to eq('https')
|
631
|
+
end
|
632
|
+
|
633
|
+
it 'converts the port to an integer' do
|
634
|
+
expect(hosts[0][:port]).to be(9200)
|
635
|
+
end
|
636
|
+
|
637
|
+
context 'when the port is specified as a String' do
|
638
|
+
|
639
|
+
let(:host) do
|
640
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https', port: '443')
|
641
|
+
end
|
642
|
+
|
643
|
+
it 'extracts the host' do
|
644
|
+
expect(hosts[0][:host]).to eq('myhost')
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'extracts the protocol' do
|
648
|
+
expect(hosts[0][:scheme]).to eq('https')
|
649
|
+
end
|
650
|
+
|
651
|
+
it 'converts the port to an integer' do
|
652
|
+
expect(hosts[0][:port]).to be(443)
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
context 'when the port is specified as an Integer' do
|
657
|
+
|
658
|
+
let(:host) do
|
659
|
+
Hashie::Mash.new(host: 'myhost', scheme: 'https', port: 443)
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'extracts the host' do
|
663
|
+
expect(hosts[0][:host]).to eq('myhost')
|
664
|
+
end
|
665
|
+
|
666
|
+
it 'extracts the protocol' do
|
667
|
+
expect(hosts[0][:scheme]).to eq('https')
|
668
|
+
end
|
669
|
+
|
670
|
+
it 'extracts port as an integer' do
|
671
|
+
expect(hosts[0][:port]).to be(443)
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
context 'when the hosts are an array' do
|
677
|
+
|
678
|
+
context 'when there is one host' do
|
679
|
+
|
680
|
+
let(:host) do
|
681
|
+
['myhost']
|
682
|
+
end
|
683
|
+
|
684
|
+
it 'extracts the host' do
|
685
|
+
expect(hosts[0][:host]).to eq('myhost')
|
686
|
+
end
|
687
|
+
|
688
|
+
it 'extracts the protocol' do
|
689
|
+
expect(hosts[0][:protocol]).to eq('http')
|
690
|
+
end
|
691
|
+
|
692
|
+
it 'defaults to port 9200' do
|
693
|
+
expect(hosts[0][:port]).to be(9200)
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
context 'when there is one host with a protocol and no port' do
|
698
|
+
|
699
|
+
let(:host) do
|
700
|
+
['http://myhost']
|
701
|
+
end
|
702
|
+
|
703
|
+
it 'extracts the host' do
|
704
|
+
expect(hosts[0][:host]).to eq('myhost')
|
705
|
+
end
|
706
|
+
|
707
|
+
it 'extracts the protocol' do
|
708
|
+
expect(hosts[0][:scheme]).to eq('http')
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'defaults to port 9200' do
|
712
|
+
expect(hosts[0][:port]).to be(9200)
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
context 'when there is one host with a protocol and the default http port explicitly provided' do
|
717
|
+
let(:host) do
|
718
|
+
['http://myhost:80']
|
719
|
+
end
|
720
|
+
|
721
|
+
it 'respects the explicit port' do
|
722
|
+
expect(hosts[0][:port]).to be(80)
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
context 'when there is one host with a protocol and the default https port explicitly provided' do
|
727
|
+
let(:host) do
|
728
|
+
['https://myhost:443']
|
729
|
+
end
|
730
|
+
|
731
|
+
it 'respects the explicit port' do
|
732
|
+
expect(hosts[0][:port]).to be(443)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
context 'when there is one host with a protocol and no port' do
|
737
|
+
let(:host) do
|
738
|
+
['https://myhost']
|
739
|
+
end
|
740
|
+
|
741
|
+
it 'extracts the host' do
|
742
|
+
expect(hosts[0][:host]).to eq('myhost')
|
743
|
+
end
|
744
|
+
|
745
|
+
it 'extracts the protocol' do
|
746
|
+
expect(hosts[0][:scheme]).to eq('https')
|
747
|
+
end
|
748
|
+
|
749
|
+
it 'defaults to port 443' do
|
750
|
+
expect(hosts[0][:port]).to be(443)
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
context 'when there is one host with a protocol, path, and no port' do
|
755
|
+
let(:host) do
|
756
|
+
['http://myhost/foo/bar']
|
757
|
+
end
|
758
|
+
|
759
|
+
it 'extracts the host' do
|
760
|
+
expect(hosts[0][:host]).to eq('myhost')
|
761
|
+
end
|
762
|
+
|
763
|
+
it 'extracts the protocol' do
|
764
|
+
expect(hosts[0][:scheme]).to eq('http')
|
765
|
+
end
|
766
|
+
|
767
|
+
it 'defaults to port 9200' do
|
768
|
+
expect(hosts[0][:port]).to be(9200)
|
769
|
+
end
|
770
|
+
|
771
|
+
it 'extracts the path' do
|
772
|
+
expect(hosts[0][:path]).to eq('/foo/bar')
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
context 'when there is more than one host' do
|
777
|
+
let(:host) do
|
778
|
+
['host1', 'host2']
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'extracts the hosts' do
|
782
|
+
expect(hosts[0][:host]).to eq('host1')
|
783
|
+
expect(hosts[0][:protocol]).to eq('http')
|
784
|
+
expect(hosts[0][:port]).to be(9200)
|
785
|
+
expect(hosts[1][:host]).to eq('host2')
|
786
|
+
expect(hosts[1][:protocol]).to eq('http')
|
787
|
+
expect(hosts[1][:port]).to be(9200)
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
context 'when ports are also specified' do
|
792
|
+
let(:host) do
|
793
|
+
['host1:1000', 'host2:2000']
|
794
|
+
end
|
795
|
+
|
796
|
+
it 'extracts the hosts' do
|
797
|
+
expect(hosts[0][:host]).to eq('host1')
|
798
|
+
expect(hosts[0][:protocol]).to eq('http')
|
799
|
+
expect(hosts[0][:port]).to be(1000)
|
800
|
+
expect(hosts[1][:host]).to eq('host2')
|
801
|
+
expect(hosts[1][:protocol]).to eq('http')
|
802
|
+
expect(hosts[1][:port]).to be(2000)
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
context 'when the hosts is an instance of URI' do
|
808
|
+
let(:host) do
|
809
|
+
URI.parse('https://USERNAME:PASSWORD@myhost:4430')
|
810
|
+
end
|
811
|
+
|
812
|
+
it 'extracts the host' do
|
813
|
+
expect(hosts[0][:host]).to eq('myhost')
|
814
|
+
expect(hosts[0][:scheme]).to eq('https')
|
815
|
+
expect(hosts[0][:port]).to be(4430)
|
816
|
+
expect(hosts[0][:user]).to eq('USERNAME')
|
817
|
+
expect(hosts[0][:password]).to eq('PASSWORD')
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
context 'when the hosts is invalid' do
|
822
|
+
let(:host) do
|
823
|
+
123
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'extracts the host' do
|
827
|
+
expect {
|
828
|
+
hosts
|
829
|
+
}.to raise_exception(ArgumentError)
|
830
|
+
end
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
context 'when hosts are specified with the \'host\' key' do
|
835
|
+
let(:client) do
|
836
|
+
described_class.new(host: ['host1', 'host2', 'host3', 'host4'], randomize_hosts: true)
|
837
|
+
end
|
838
|
+
|
839
|
+
let(:hosts) do
|
840
|
+
client.transport.hosts
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'sets the hosts in random order' do
|
844
|
+
expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
context 'when hosts are specified with the \'host\' key as a String' do
|
849
|
+
let(:client) do
|
850
|
+
described_class.new('host' => ['host1', 'host2', 'host3', 'host4'], 'randomize_hosts' => true)
|
851
|
+
end
|
852
|
+
|
853
|
+
let(:hosts) do
|
854
|
+
client.transport.hosts
|
855
|
+
end
|
856
|
+
|
857
|
+
it 'sets the hosts in random order' do
|
858
|
+
expect(hosts.all? { |host| client.transport.hosts.include?(host) }).to be(true)
|
859
|
+
end
|
860
|
+
end
|
861
|
+
|
862
|
+
context 'when hosts are specified with the \'hosts\' key' do
|
863
|
+
let(:client) do
|
864
|
+
described_class.new(hosts: host)
|
865
|
+
end
|
866
|
+
|
867
|
+
let(:hosts) do
|
868
|
+
client.transport.hosts
|
869
|
+
end
|
870
|
+
|
871
|
+
it_behaves_like 'a client that extracts hosts'
|
872
|
+
end
|
873
|
+
|
874
|
+
context 'when hosts are specified with the \'hosts\' key as a String' do
|
875
|
+
let(:client) do
|
876
|
+
described_class.new('hosts' => host)
|
877
|
+
end
|
878
|
+
|
879
|
+
let(:hosts) do
|
880
|
+
client.transport.hosts
|
881
|
+
end
|
882
|
+
|
883
|
+
it_behaves_like 'a client that extracts hosts'
|
884
|
+
end
|
885
|
+
|
886
|
+
context 'when hosts are specified with the \'url\' key' do
|
887
|
+
let(:client) do
|
888
|
+
described_class.new(url: host)
|
889
|
+
end
|
890
|
+
|
891
|
+
let(:hosts) do
|
892
|
+
client.transport.hosts
|
893
|
+
end
|
894
|
+
|
895
|
+
it_behaves_like 'a client that extracts hosts'
|
896
|
+
end
|
897
|
+
|
898
|
+
context 'when hosts are specified with the \'url\' key as a String' do
|
899
|
+
let(:client) do
|
900
|
+
described_class.new('url' => host)
|
901
|
+
end
|
902
|
+
|
903
|
+
let(:hosts) do
|
904
|
+
client.transport.hosts
|
905
|
+
end
|
906
|
+
|
907
|
+
it_behaves_like 'a client that extracts hosts'
|
908
|
+
end
|
909
|
+
|
910
|
+
context 'when hosts are specified with the \'urls\' key' do
|
911
|
+
let(:client) do
|
912
|
+
described_class.new(urls: host)
|
913
|
+
end
|
914
|
+
|
915
|
+
let(:hosts) do
|
916
|
+
client.transport.hosts
|
917
|
+
end
|
918
|
+
|
919
|
+
it_behaves_like 'a client that extracts hosts'
|
920
|
+
end
|
921
|
+
|
922
|
+
context 'when hosts are specified with the \'urls\' key as a String' do
|
923
|
+
let(:client) do
|
924
|
+
described_class.new('urls' => host)
|
925
|
+
end
|
926
|
+
|
927
|
+
let(:hosts) do
|
928
|
+
client.transport.hosts
|
929
|
+
end
|
930
|
+
|
931
|
+
it_behaves_like 'a client that extracts hosts'
|
932
|
+
end
|
933
|
+
|
934
|
+
context 'when the URL is set in the ELASTICSEARCH_URL environment variable' do
|
935
|
+
context 'when there is only one host specified' do
|
936
|
+
around do |example|
|
937
|
+
before_url = ENV['ELASTICSEARCH_URL']
|
938
|
+
ENV['ELASTICSEARCH_URL'] = 'example.com'
|
939
|
+
example.run
|
940
|
+
ENV['ELASTICSEARCH_URL'] = before_url
|
941
|
+
end
|
942
|
+
|
943
|
+
it 'sets the host' do
|
944
|
+
expect(client.transport.hosts[0][:host]).to eq('example.com')
|
945
|
+
expect(client.transport.hosts.size).to eq(1)
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
context 'when mutliple hosts are specified as a comma-separated String list' do
|
950
|
+
around do |example|
|
951
|
+
before_url = ENV['ELASTICSEARCH_URL']
|
952
|
+
ENV['ELASTICSEARCH_URL'] = 'example.com, other.com'
|
953
|
+
example.run
|
954
|
+
ENV['ELASTICSEARCH_URL'] = before_url
|
955
|
+
end
|
956
|
+
|
957
|
+
it 'sets the hosts' do
|
958
|
+
expect(client.transport.hosts[0][:host]).to eq('example.com')
|
959
|
+
expect(client.transport.hosts[1][:host]).to eq('other.com')
|
960
|
+
expect(client.transport.hosts.size).to eq(2)
|
961
|
+
end
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
context 'when options are defined' do
|
966
|
+
|
967
|
+
context 'when scheme is specified' do
|
968
|
+
|
969
|
+
let(:client) do
|
970
|
+
described_class.new(scheme: 'https')
|
971
|
+
end
|
972
|
+
|
973
|
+
it 'sets the scheme' do
|
974
|
+
expect(client.transport.connections[0].full_url('')).to match(/https/)
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
context 'when scheme is specified as a String key' do
|
979
|
+
|
980
|
+
let(:client) do
|
981
|
+
described_class.new('scheme' => 'https')
|
982
|
+
end
|
983
|
+
|
984
|
+
it 'sets the scheme' do
|
985
|
+
expect(client.transport.connections[0].full_url('')).to match(/https/)
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
989
|
+
context 'when user and password are specified' do
|
990
|
+
|
991
|
+
let(:client) do
|
992
|
+
described_class.new(user: 'USERNAME', password: 'PASSWORD')
|
993
|
+
end
|
994
|
+
|
995
|
+
it 'sets the user and password' do
|
996
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
997
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
998
|
+
end
|
999
|
+
|
1000
|
+
context 'when the connections are reloaded' do
|
1001
|
+
|
1002
|
+
before do
|
1003
|
+
allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
|
1004
|
+
client.transport.reload_connections!
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
it 'sets keeps user and password' do
|
1008
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
1009
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
1010
|
+
expect(client.transport.connections[0].full_url('')).to match(/foobar/)
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
context 'when user and password are specified as String keys' do
|
1016
|
+
|
1017
|
+
let(:client) do
|
1018
|
+
described_class.new('user' => 'USERNAME', 'password' => 'PASSWORD')
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
it 'sets the user and password' do
|
1022
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
1023
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
context 'when the connections are reloaded' do
|
1027
|
+
|
1028
|
+
before do
|
1029
|
+
allow(client.transport.sniffer).to receive(:hosts).and_return([{ host: 'foobar', port: 4567, id: 'foobar4567' }])
|
1030
|
+
client.transport.reload_connections!
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
it 'sets keeps user and password' do
|
1034
|
+
expect(client.transport.connections[0].full_url('')).to match(/USERNAME/)
|
1035
|
+
expect(client.transport.connections[0].full_url('')).to match(/PASSWORD/)
|
1036
|
+
expect(client.transport.connections[0].full_url('')).to match(/foobar/)
|
1037
|
+
end
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
context 'when port is specified' do
|
1042
|
+
|
1043
|
+
let(:client) do
|
1044
|
+
described_class.new(host: 'node1', port: 1234)
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it 'sets the port' do
|
1048
|
+
expect(client.transport.connections[0].full_url('')).to match(/1234/)
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
context 'when the log option is true' do
|
1053
|
+
|
1054
|
+
let(:client) do
|
1055
|
+
described_class.new(log: true)
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
it 'has a default logger for transport' do
|
1059
|
+
expect(client.transport.logger.info).to eq(described_class::DEFAULT_LOGGER.call.info)
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
context 'when the trace option is true' do
|
1064
|
+
|
1065
|
+
let(:client) do
|
1066
|
+
described_class.new(trace: true)
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
it 'has a default logger for transport' do
|
1070
|
+
expect(client.transport.tracer.info).to eq(described_class::DEFAULT_TRACER.call.info)
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
context 'when a custom transport class is specified' do
|
1075
|
+
|
1076
|
+
let(:transport_class) do
|
1077
|
+
Class.new { def initialize(*); end }
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
let(:client) do
|
1081
|
+
described_class.new(transport_class: transport_class)
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
it 'allows the custom transport class to be defined' do
|
1085
|
+
expect(client.transport).to be_a(transport_class)
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
context 'when a custom transport instance is specified' do
|
1090
|
+
|
1091
|
+
let(:transport_instance) do
|
1092
|
+
Class.new { def initialize(*); end }.new
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
let(:client) do
|
1096
|
+
described_class.new(transport: transport_instance)
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
it 'allows the custom transport class to be defined' do
|
1100
|
+
expect(client.transport).to be(transport_instance)
|
1101
|
+
end
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
context 'when \'transport_options\' are defined' do
|
1105
|
+
|
1106
|
+
let(:client) do
|
1107
|
+
described_class.new(transport_options: { request: { timeout: 1 } })
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
it 'sets the options on the transport' do
|
1111
|
+
expect(client.transport.options[:transport_options][:request]).to eq(timeout: 1)
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
context 'when \'request_timeout\' is defined' do
|
1116
|
+
|
1117
|
+
let(:client) do
|
1118
|
+
described_class.new(request_timeout: 120)
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
it 'sets the options on the transport' do
|
1122
|
+
expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
context 'when \'request_timeout\' is defined as a String key' do
|
1127
|
+
|
1128
|
+
let(:client) do
|
1129
|
+
described_class.new('request_timeout' => 120)
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
it 'sets the options on the transport' do
|
1133
|
+
expect(client.transport.options[:transport_options][:request]).to eq(timeout: 120)
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
describe '#perform_request' do
|
1139
|
+
|
1140
|
+
let(:transport_instance) do
|
1141
|
+
Class.new { def initialize(*); end }.new
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
let(:client) do
|
1145
|
+
described_class.new(transport: transport_instance)
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
it 'delegates performing requests to the transport' do
|
1149
|
+
expect(transport_instance).to receive(:perform_request).and_return(true)
|
1150
|
+
expect(client.perform_request('GET', '/')).to be(true)
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
context 'when the \'send_get_body_as\' option is specified' do
|
1154
|
+
|
1155
|
+
let(:client) do
|
1156
|
+
described_class.new(transport: transport_instance, :send_get_body_as => 'POST')
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
before do
|
1160
|
+
expect(transport_instance).to receive(:perform_request).with('POST', '/', {},
|
1161
|
+
'{"foo":"bar"}',
|
1162
|
+
'{"Content-Type":"application/x-ndjson"}').and_return(true)
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
let(:request) do
|
1166
|
+
client.perform_request('POST', '/', {}, '{"foo":"bar"}', '{"Content-Type":"application/x-ndjson"}')
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
it 'sets the option' do
|
1170
|
+
expect(request).to be(true)
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
context 'when Elasticsearch response includes a warning header' do
|
1175
|
+
let(:client) do
|
1176
|
+
Elastic::Transport::Client.new(hosts: hosts)
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
let(:warning) { 'Elasticsearch warning: "deprecation warning"' }
|
1180
|
+
|
1181
|
+
it 'prints a warning' do
|
1182
|
+
allow_any_instance_of(Elastic::Transport::Transport::Response).to receive(:headers) do
|
1183
|
+
{ 'warning' => warning }
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
begin
|
1187
|
+
stderr = $stderr
|
1188
|
+
fake_stderr = StringIO.new
|
1189
|
+
$stderr = fake_stderr
|
1190
|
+
|
1191
|
+
client.perform_request('GET', '/')
|
1192
|
+
fake_stderr.rewind
|
1193
|
+
expect(fake_stderr.string).to eq("warning: #{warning}\n")
|
1194
|
+
ensure
|
1195
|
+
$stderr = stderr
|
1196
|
+
end
|
1197
|
+
end
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
context 'when the client connects to Elasticsearch' do
|
1202
|
+
let(:logger) do
|
1203
|
+
Logger.new(STDERR).tap do |logger|
|
1204
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
1205
|
+
color = case severity
|
1206
|
+
when /INFO/ then :green
|
1207
|
+
when /ERROR|WARN|FATAL/ then :red
|
1208
|
+
when /DEBUG/ then :cyan
|
1209
|
+
else :white
|
1210
|
+
end
|
1211
|
+
ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
|
1212
|
+
end
|
1213
|
+
end unless ENV['QUIET']
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
let(:port) do
|
1217
|
+
TEST_PORT
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
let(:transport_options) do
|
1221
|
+
{}
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
let(:options) do
|
1225
|
+
{}
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
let(:client) do
|
1229
|
+
described_class.new({ host: hosts, logger: logger }.merge!(transport_options: transport_options).merge!(options))
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
context 'when a request is made' do
|
1233
|
+
let!(:response) do
|
1234
|
+
client.perform_request('GET', '_cluster/health')
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
it 'connects to the cluster' do
|
1238
|
+
expect(response.body['number_of_nodes']).to be >= (1)
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
describe '#initialize' do
|
1243
|
+
context 'when options are specified' do
|
1244
|
+
let(:transport_options) do
|
1245
|
+
{ headers: { accept: 'application/yaml', content_type: 'application/yaml' } }
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
let(:response) do
|
1249
|
+
client.perform_request('GET', '_cluster/health')
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
it 'applies the options to the client' do
|
1253
|
+
expect(response.body).to match(/---\n/)
|
1254
|
+
expect(response.headers['content-type']).to eq('application/yaml')
|
1255
|
+
end
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
context 'when a block is provided' do
|
1259
|
+
let(:client) do
|
1260
|
+
Elastic::Transport::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
|
1261
|
+
client.headers['Accept'] = 'application/yaml'
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
let(:response) do
|
1266
|
+
client.perform_request('GET', '_cluster/health')
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
it 'executes the block' do
|
1270
|
+
expect(response.body).to match(/---\n/)
|
1271
|
+
expect(response.headers['content-type']).to eq('application/yaml')
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
context 'when the Faraday adapter is set in the block' do
|
1275
|
+
let(:client) do
|
1276
|
+
Elastic::Transport::Client.new(host: ELASTICSEARCH_HOSTS.first, logger: logger) do |client|
|
1277
|
+
client.adapter(:net_http_persistent)
|
1278
|
+
end
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
let(:handler_name) do
|
1282
|
+
client.transport.connections.first.connection.builder.adapter.name
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
let(:response) do
|
1286
|
+
client.perform_request('GET', '_cluster/health')
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
it 'sets the adapter' do
|
1290
|
+
expect(handler_name).to eq('Faraday::Adapter::NetHttpPersistent')
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
it 'uses the adapter to connect' do
|
1294
|
+
expect(response.status).to eq(200)
|
1295
|
+
end
|
1296
|
+
end
|
1297
|
+
end
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
describe '#options' do
|
1301
|
+
context 'when retry_on_failure is true' do
|
1302
|
+
context 'when a node is unreachable' do
|
1303
|
+
let(:hosts) do
|
1304
|
+
[ELASTICSEARCH_HOSTS.first, "foobar1", "foobar2"]
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
let(:options) do
|
1308
|
+
{ retry_on_failure: true }
|
1309
|
+
end
|
1310
|
+
|
1311
|
+
let(:responses) do
|
1312
|
+
5.times.collect do
|
1313
|
+
client.perform_request('GET', '_nodes/_local')
|
1314
|
+
end
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
it 'retries on failure' do
|
1318
|
+
expect(responses.all? { true }).to be(true)
|
1319
|
+
end
|
1320
|
+
end
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
context 'when retry_on_failure is an integer' do
|
1324
|
+
let(:hosts) do
|
1325
|
+
[ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2', 'foobar3']
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
let(:options) do
|
1329
|
+
{ retry_on_failure: 1 }
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
it 'retries only the specified number of times' do
|
1333
|
+
expect(client.perform_request('GET', '_nodes/_local'))
|
1334
|
+
expect {
|
1335
|
+
client.perform_request('GET', '_nodes/_local')
|
1336
|
+
}.to raise_exception(Faraday::ConnectionFailed)
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
context 'when reload_on_failure is true' do
|
1341
|
+
let(:hosts) do
|
1342
|
+
[ELASTICSEARCH_HOSTS.first, 'foobar1', 'foobar2']
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
let(:options) do
|
1346
|
+
{ reload_on_failure: true }
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
let(:responses) do
|
1350
|
+
5.times.collect do
|
1351
|
+
client.perform_request('GET', '_nodes/_local')
|
1352
|
+
end
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
it 'reloads the connections' do
|
1356
|
+
expect(client.transport.connections.size).to eq(3)
|
1357
|
+
expect(responses.all? { true }).to be(true)
|
1358
|
+
expect(client.transport.connections.size).to be >= (1)
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
context 'when retry_on_status is specified' do
|
1363
|
+
let(:options) do
|
1364
|
+
{ retry_on_status: 400 }
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
let(:logger) do
|
1368
|
+
double('logger', :debug? => false, :warn? => true, :fatal? => false, :error? => false)
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
before do
|
1372
|
+
expect(logger).to receive(:warn).exactly(4).times
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
it 'retries when the status matches' do
|
1376
|
+
expect {
|
1377
|
+
client.perform_request('PUT', '_foobar')
|
1378
|
+
}.to raise_exception(Elastic::Transport::Transport::Errors::BadRequest)
|
1379
|
+
end
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
context 'when the \'compression\' option is set to true' do
|
1383
|
+
context 'when using Faraday as the transport' do
|
1384
|
+
context 'when using the Net::HTTP adapter' do
|
1385
|
+
let(:client) do
|
1386
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :net_http)
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
it 'compresses the request and decompresses the response' do
|
1390
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
it 'sets the Accept-Encoding header' do
|
1394
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
it 'preserves the other headers' do
|
1398
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1399
|
+
end
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
context 'when using the HTTPClient adapter' do
|
1403
|
+
let(:client) do
|
1404
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :httpclient, enable_meta_header: false)
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
it 'compresses the request and decompresses the response' do
|
1408
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
it 'sets the Accept-Encoding header' do
|
1412
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
it 'preserves the other headers' do
|
1416
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1417
|
+
end
|
1418
|
+
end
|
1419
|
+
|
1420
|
+
context 'when using the Patron adapter', unless: jruby? do
|
1421
|
+
let(:client) do
|
1422
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :patron)
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
it 'compresses the request and decompresses the response' do
|
1426
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
it 'sets the Accept-Encoding header' do
|
1430
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1431
|
+
end
|
1432
|
+
|
1433
|
+
it 'preserves the other headers' do
|
1434
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
context 'when using the Net::HTTP::Persistent adapter' do
|
1439
|
+
let(:client) do
|
1440
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :net_http_persistent)
|
1441
|
+
end
|
1442
|
+
|
1443
|
+
it 'compresses the request and decompresses the response' do
|
1444
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
it 'sets the Accept-Encoding header' do
|
1448
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
it 'preserves the other headers' do
|
1452
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
context 'when using the Typhoeus adapter' do
|
1457
|
+
let(:client) do
|
1458
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS, compression: true, adapter: :typhoeus)
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
it 'compresses the request and decompresses the response' do
|
1462
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
it 'sets the Accept-Encoding header' do
|
1466
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
it 'preserves the other headers' do
|
1470
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1471
|
+
end
|
1472
|
+
end unless jruby?
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
context 'when using Curb as the transport', unless: jruby? do
|
1477
|
+
let(:client) do
|
1478
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS,
|
1479
|
+
compression: true,
|
1480
|
+
transport_class: Elastic::Transport::Transport::HTTP::Curb)
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
it 'compresses the request and decompresses the response' do
|
1484
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
it 'sets the Accept-Encoding header' do
|
1488
|
+
expect(client.transport.connections[0].connection.headers['Accept-Encoding'])
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
it 'preserves the other headers' do
|
1492
|
+
expect(client.transport.connections[0].connection.headers['User-Agent'])
|
1493
|
+
end
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
context 'when using Manticore as the transport', if: jruby? do
|
1497
|
+
let(:client) do
|
1498
|
+
described_class.new(hosts: ELASTICSEARCH_HOSTS,
|
1499
|
+
compression: true,
|
1500
|
+
transport_class: Elastic::Transport::Transport::HTTP::Manticore)
|
1501
|
+
end
|
1502
|
+
|
1503
|
+
it 'compresses the request and decompresses the response' do
|
1504
|
+
expect(client.perform_request('GET', '/').body).to be_a(Hash)
|
1505
|
+
end
|
1506
|
+
end
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
describe '#perform_request' do
|
1510
|
+
context 'when a request is made' do
|
1511
|
+
before do
|
1512
|
+
client.perform_request('DELETE', '_all')
|
1513
|
+
client.perform_request('DELETE', 'myindex') rescue
|
1514
|
+
client.perform_request('PUT', 'myindex', {}, { settings: { number_of_shards: 2, number_of_replicas: 0 } })
|
1515
|
+
client.perform_request('PUT', 'myindex/_doc/1', { routing: 'XYZ', timeout: '1s' }, { foo: 'bar' })
|
1516
|
+
client.perform_request('GET', '_cluster/health?wait_for_status=green&timeout=2s', {})
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
let(:response) do
|
1520
|
+
client.perform_request('GET', 'myindex/_doc/1?routing=XYZ')
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
it 'handles paths and URL paramters' do
|
1524
|
+
expect(response.status).to eq(200)
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
it 'returns response body' do
|
1528
|
+
expect(response.body['_source']).to eq('foo' => 'bar')
|
1529
|
+
end
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
context 'when an invalid url is specified' do
|
1533
|
+
it 'raises an exception' do
|
1534
|
+
expect {
|
1535
|
+
client.perform_request('GET', 'myindex/_doc/1?routing=FOOBARBAZ')
|
1536
|
+
}.to raise_exception(Elastic::Transport::Transport::Errors::NotFound)
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
context 'when the \'ignore\' parameter is specified' do
|
1541
|
+
|
1542
|
+
let(:response) do
|
1543
|
+
client.perform_request('PUT', '_foobar', ignore: 400)
|
1544
|
+
end
|
1545
|
+
|
1546
|
+
it 'exposes the status in the response' do
|
1547
|
+
expect(response.status).to eq(400)
|
1548
|
+
end
|
1549
|
+
|
1550
|
+
it 'exposes the body of the response' do
|
1551
|
+
expect(response.body).to be_a(Hash)
|
1552
|
+
expect(response.body.inspect).to match(/invalid_index_name_exception/)
|
1553
|
+
end
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
context 'when request headers are specified' do
|
1557
|
+
|
1558
|
+
let(:response) do
|
1559
|
+
client.perform_request('GET', '/', {}, nil, { 'Content-Type' => 'application/yaml' })
|
1560
|
+
end
|
1561
|
+
|
1562
|
+
it 'passes them to the transport' do
|
1563
|
+
expect(response.body).to match(/---/)
|
1564
|
+
end
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
describe 'selector' do
|
1568
|
+
|
1569
|
+
context 'when the round-robin selector is used' do
|
1570
|
+
|
1571
|
+
let(:nodes) do
|
1572
|
+
3.times.collect do
|
1573
|
+
client.perform_request('GET', '_nodes/_local').body['nodes'].to_a[0][1]['name']
|
1574
|
+
end
|
1575
|
+
end
|
1576
|
+
|
1577
|
+
let(:node_names) do
|
1578
|
+
client.nodes.stats['nodes'].collect do |name, stats|
|
1579
|
+
stats['name']
|
1580
|
+
end
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
let(:expected_names) do
|
1584
|
+
3.times.collect do |i|
|
1585
|
+
node_names[i % node_names.size]
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
|
1589
|
+
# it 'rotates nodes' do
|
1590
|
+
# pending 'Better way to detect rotating nodes'
|
1591
|
+
# expect(nodes).to eq(expected_names)
|
1592
|
+
# end
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
context 'when patron is used as an adapter', unless: jruby? do
|
1597
|
+
before do
|
1598
|
+
require 'patron'
|
1599
|
+
end
|
1600
|
+
|
1601
|
+
let(:options) do
|
1602
|
+
{ adapter: :patron }
|
1603
|
+
end
|
1604
|
+
|
1605
|
+
let(:adapter) do
|
1606
|
+
client.transport.connections.first.connection.builder.adapter
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
it 'uses the patron connection handler' do
|
1610
|
+
expect(adapter).to eq('Faraday::Adapter::Patron')
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
it 'keeps connections open' do
|
1614
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
1615
|
+
connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1616
|
+
client.transport.reload_connections!
|
1617
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
1618
|
+
connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1619
|
+
expect(connections_after).to be >= (connections_before)
|
1620
|
+
end
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
context 'when typhoeus is used as an adapter', unless: jruby? do
|
1624
|
+
before do
|
1625
|
+
require 'typhoeus'
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
let(:options) do
|
1629
|
+
{ adapter: :typhoeus }
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
let(:adapter) do
|
1633
|
+
client.transport.connections.first.connection.builder.adapter
|
1634
|
+
end
|
1635
|
+
|
1636
|
+
it 'uses the patron connection handler' do
|
1637
|
+
expect(adapter).to eq('Faraday::Adapter::Typhoeus')
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
it 'keeps connections open' do
|
1641
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
1642
|
+
connections_before = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1643
|
+
client.transport.reload_connections!
|
1644
|
+
response = client.perform_request('GET', '_nodes/stats/http')
|
1645
|
+
connections_after = response.body['nodes'].values.find { |n| n['name'] == node_names.first }['http']['total_opened']
|
1646
|
+
expect(connections_after).to be >= (connections_before)
|
1647
|
+
end
|
1648
|
+
end
|
1649
|
+
end
|
1650
|
+
end
|
1651
|
+
end
|