elasticsearch-transport 6.1.0 → 6.2.0

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