elasticsearch-transport 5.0.5 → 6.8.2

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