logstash-output-elasticsearch 11.17.0-java → 11.19.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37124c3a166313a2fb9f3831273def114770178158439a44ebfa1bc1d32f9d0f
4
- data.tar.gz: a09fd3ce2c54908fedc14dc2d780bb3ec48d7091d05e2e8fcb5789e4ec1e30b9
3
+ metadata.gz: '0490f311461b39c49e3edaf045af9a7172dbd97da8bc456bf044a031c18e5afb'
4
+ data.tar.gz: fbf05190ed352c12f0948aecfa6ed08e760b03985b83aa2e6830167a00c431bc
5
5
  SHA512:
6
- metadata.gz: caac996badd1bbdeb231fad3f40f96a50386baf78ee356587d5fc5d2b4a095f1073bee417a99aab081c13cb1a18802785abed618dc85db504f56145c620c46b6
7
- data.tar.gz: 697a89b810998154a44338e8e73f02351b72afa00fc35e1fc115a22ce5ecfeddd27d29bd8fdf4fb779a3b17fbaa2f4f361a2c30b68c0b5ce0cd49a6edeba1a1d
6
+ metadata.gz: b50084b6fb4fd31e7592be7e35e23963dce4cf284763baa4c1c042d1f159f20cebe09c1013ace38a986dbf4c8ee3cc9ae4eda7f38042956bd10dbdfc0ad20114
7
+ data.tar.gz: 91643fa15c48a29a02659967c75e0b879f98af2a6e7f1582c83b141f67826337ccaa3d0513b1372266dd068cbccfcdbbe59a5fcf36ceff81318126dd1bdf64cc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 11.19.0
2
+ - Added `filter_path` to bulk requests to reduce the size of responses from elasticsearch
3
+
4
+ ## 11.18.0
5
+ - Added request header `Elastic-Api-Version` for serverless [#1147](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1147)
6
+
1
7
  ## 11.17.0
2
8
  - Added support to http compression level. Deprecated `http_compression` in favour of `compression_level` and enabled compression level 1 by default. [#1148](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1148)
3
9
 
data/docs/index.asciidoc CHANGED
@@ -423,10 +423,12 @@ Elasticsearch {ref}/security-api-create-api-key.html[Create API key API].
423
423
  ===== `bulk_path`
424
424
 
425
425
  * Value type is <<string,string>>
426
- * There is no default value for this setting.
426
+ * The default value for this settings is `/_bulk?filter_path=errors,items.*.error,items.*.status`
427
427
 
428
428
  HTTP Path to perform the _bulk requests to
429
- this defaults to a concatenation of the path parameter and "_bulk"
429
+ * This default bulk path is the concatenation of the value of `path` parameter and `/_bulk?filter_path=errors,items.*.error,items.*.status`
430
+ * The `filter_path` query parameter is appended to the bulk path to reduce the payload between logstash and elasticsearch. However, if a custom `filter_path` query parameter is included in the `bulk_path` setting, then that value will be used.
431
+
430
432
 
431
433
  [id="plugins-{type}s-{plugin}-ca_trusted_fingerprint"]
432
434
  ===== `ca_trusted_fingerprint`
@@ -16,6 +16,18 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
16
16
  @response_body = response_body
17
17
  end
18
18
 
19
+ def invalid_eav_header?
20
+ @response_code == 400 && @response_body&.include?(ELASTIC_API_VERSION)
21
+ end
22
+
23
+ def invalid_credentials?
24
+ @response_code == 401
25
+ end
26
+
27
+ def forbidden?
28
+ @response_code == 403
29
+ end
30
+
19
31
  end
20
32
  class HostUnreachableError < Error;
21
33
  attr_reader :original_error, :url
@@ -48,7 +60,9 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
48
60
  :sniffer_delay => 10,
49
61
  }.freeze
50
62
 
51
- BUILD_FLAVOUR_SERVERLESS = 'serverless'.freeze
63
+ BUILD_FLAVOR_SERVERLESS = 'serverless'.freeze
64
+ ELASTIC_API_VERSION = "Elastic-Api-Version".freeze
65
+ DEFAULT_EAV_HEADER = { ELASTIC_API_VERSION => "2023-10-31" }.freeze
52
66
 
53
67
  def initialize(logger, adapter, initial_urls=[], options={})
54
68
  @logger = logger
@@ -77,7 +91,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
77
91
  @license_checker = options[:license_checker] || LogStash::PluginMixins::ElasticSearch::NoopLicenseChecker::INSTANCE
78
92
 
79
93
  @last_es_version = Concurrent::AtomicReference.new
80
- @build_flavour = Concurrent::AtomicReference.new
94
+ @build_flavor = Concurrent::AtomicReference.new
81
95
  end
82
96
 
83
97
  def start
@@ -232,39 +246,56 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
232
246
  end
233
247
 
234
248
  def health_check_request(url)
235
- response = perform_request_to_url(url, :head, @healthcheck_path)
236
- raise BadResponseCodeError.new(response.code, url, nil, response.body) unless (200..299).cover?(response.code)
249
+ logger.debug("Running health check to see if an Elasticsearch connection is working",
250
+ :healthcheck_url => url.sanitized.to_s, :path => @healthcheck_path)
251
+ begin
252
+ response = perform_request_to_url(url, :head, @healthcheck_path)
253
+ return response, nil
254
+ rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
255
+ logger.warn("Health check failed", code: e.response_code, url: e.url, message: e.message)
256
+ return nil, e
257
+ end
237
258
  end
238
259
 
239
260
  def healthcheck!(register_phase = true)
240
261
  # Try to keep locking granularity low such that we don't affect IO...
241
262
  @state_mutex.synchronize { @url_info.select {|url,meta| meta[:state] != :alive } }.each do |url,meta|
242
263
  begin
243
- logger.debug("Running health check to see if an Elasticsearch connection is working",
244
- :healthcheck_url => url.sanitized.to_s, :path => @healthcheck_path)
245
- health_check_request(url)
264
+ _, health_bad_code_err = health_check_request(url)
265
+ root_response, root_bad_code_err = get_root_path(url) if health_bad_code_err.nil? || register_phase
246
266
 
247
267
  # when called from resurrectionist skip the product check done during register phase
248
268
  if register_phase
249
- if !elasticsearch?(url)
250
- raise LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch"
251
- end
269
+ raise LogStash::ConfigurationError,
270
+ "Could not read Elasticsearch. Please check the credentials" if root_bad_code_err&.invalid_credentials?
271
+ raise LogStash::ConfigurationError,
272
+ "Could not read Elasticsearch. Please check the privileges" if root_bad_code_err&.forbidden?
273
+ # when customer_headers is invalid
274
+ raise LogStash::ConfigurationError,
275
+ "The Elastic-Api-Version header is not valid" if root_bad_code_err&.invalid_eav_header?
276
+ # when it is not Elasticserach
277
+ raise LogStash::ConfigurationError,
278
+ "Could not connect to a compatible version of Elasticsearch" if root_bad_code_err.nil? && !elasticsearch?(root_response)
279
+
280
+ test_serverless_connection(url, root_response)
252
281
  end
282
+
283
+ raise health_bad_code_err if health_bad_code_err
284
+ raise root_bad_code_err if root_bad_code_err
285
+
253
286
  # If no exception was raised it must have succeeded!
254
287
  logger.warn("Restored connection to ES instance", url: url.sanitized.to_s)
255
- # We reconnected to this node, check its ES version
256
- version_info = get_es_version(url)
257
- es_version = version_info.fetch('number', nil)
258
- build_flavour = version_info.fetch('build_flavor', nil)
259
-
260
- if es_version.nil?
261
- logger.warn("Failed to retrieve Elasticsearch version data from connected endpoint, connection aborted", :url => url.sanitized.to_s)
262
- next
263
- end
288
+
289
+ # We check its ES version
290
+ es_version, build_flavor = parse_es_version(root_response)
291
+ logger.warn("Failed to retrieve Elasticsearch build flavor") if build_flavor.nil?
292
+ logger.warn("Failed to retrieve Elasticsearch version data from connected endpoint, connection aborted", :url => url.sanitized.to_s) if es_version.nil?
293
+ next if es_version.nil?
294
+
264
295
  @state_mutex.synchronize do
265
296
  meta[:version] = es_version
266
297
  set_last_es_version(es_version, url)
267
- set_build_flavour(build_flavour)
298
+ set_build_flavor(build_flavor)
268
299
 
269
300
  alive = @license_checker.appropriate_license?(self, url)
270
301
  meta[:state] = alive ? :alive : :dead
@@ -275,40 +306,21 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
275
306
  end
276
307
  end
277
308
 
278
- def elasticsearch?(url)
309
+ def get_root_path(url, params={})
279
310
  begin
280
- response = perform_request_to_url(url, :get, ROOT_URI_PATH)
311
+ resp = perform_request_to_url(url, :get, ROOT_URI_PATH, params)
312
+ return resp, nil
281
313
  rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
282
- return false if response.code == 401 || response.code == 403
283
- raise e
314
+ logger.warn("Elasticsearch main endpoint returns #{e.response_code}", message: e.message, body: e.response_body)
315
+ return nil, e
284
316
  end
285
-
286
- version_info = LogStash::Json.load(response.body)
287
- return false if version_info['version'].nil?
288
-
289
- version = ::Gem::Version.new(version_info["version"]['number'])
290
- return false if version < ::Gem::Version.new('6.0.0')
291
-
292
- if VERSION_6_TO_7.satisfied_by?(version)
293
- return valid_tagline?(version_info)
294
- elsif VERSION_7_TO_7_14.satisfied_by?(version)
295
- build_flavor = version_info["version"]['build_flavor']
296
- return false if build_flavor.nil? || build_flavor != 'default' || !valid_tagline?(version_info)
297
- else
298
- # case >= 7.14
299
- lower_headers = response.headers.transform_keys {|key| key.to_s.downcase }
300
- product_header = lower_headers['x-elastic-product']
301
- return false if product_header != 'Elasticsearch'
302
- end
303
- return true
304
- rescue => e
305
- logger.error("Unable to retrieve Elasticsearch version", url: url.sanitized.to_s, exception: e.class, message: e.message)
306
- false
307
317
  end
308
318
 
309
- def valid_tagline?(version_info)
310
- tagline = version_info['tagline']
311
- tagline == "You Know, for Search"
319
+ def test_serverless_connection(url, root_response)
320
+ _, build_flavor = parse_es_version(root_response)
321
+ params = { :headers => DEFAULT_EAV_HEADER }
322
+ _, bad_code_err = get_root_path(url, params) if build_flavor == BUILD_FLAVOR_SERVERLESS
323
+ raise LogStash::ConfigurationError, "The Elastic-Api-Version header is not valid" if bad_code_err&.invalid_eav_header?
312
324
  end
313
325
 
314
326
  def stop_resurrectionist
@@ -334,6 +346,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
334
346
  end
335
347
 
336
348
  def perform_request_to_url(url, method, path, params={}, body=nil)
349
+ params[:headers] = DEFAULT_EAV_HEADER.merge(params[:headers] || {}) if serverless?
337
350
  @adapter.perform_request(url, method, path, params, body)
338
351
  end
339
352
 
@@ -476,15 +489,6 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
476
489
  end
477
490
  end
478
491
 
479
- def get_es_version(url)
480
- response = perform_request_to_url(url, :get, ROOT_URI_PATH)
481
- return nil unless (200..299).cover?(response.code)
482
-
483
- response = LogStash::Json.load(response.body)
484
-
485
- response.fetch('version', {})
486
- end
487
-
488
492
  def last_es_version
489
493
  @last_es_version.get
490
494
  end
@@ -494,7 +498,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
494
498
  end
495
499
 
496
500
  def serverless?
497
- @build_flavour.get == BUILD_FLAVOUR_SERVERLESS
501
+ @build_flavor.get == BUILD_FLAVOR_SERVERLESS
498
502
  end
499
503
 
500
504
  private
@@ -526,9 +530,50 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
526
530
  previous_major: @maximum_seen_major_version, new_major: major, node_url: url.sanitized.to_s)
527
531
  end
528
532
 
529
- def set_build_flavour(flavour)
530
- @build_flavour.set(flavour)
533
+ def set_build_flavor(flavor)
534
+ @build_flavor.set(flavor)
535
+ end
536
+
537
+ def parse_es_version(response)
538
+ return nil, nil unless (200..299).cover?(response&.code)
539
+
540
+ response = LogStash::Json.load(response&.body)
541
+ version_info = response.fetch('version', {})
542
+ es_version = version_info.fetch('number', nil)
543
+ build_flavor = version_info.fetch('build_flavor', nil)
544
+
545
+ return es_version, build_flavor
546
+ end
547
+
548
+ def elasticsearch?(response)
549
+ return false if response.nil?
550
+
551
+ version_info = LogStash::Json.load(response.body)
552
+ return false if version_info['version'].nil?
553
+
554
+ version = ::Gem::Version.new(version_info["version"]['number'])
555
+ return false if version < ::Gem::Version.new('6.0.0')
556
+
557
+ if VERSION_6_TO_7.satisfied_by?(version)
558
+ return valid_tagline?(version_info)
559
+ elsif VERSION_7_TO_7_14.satisfied_by?(version)
560
+ build_flavor = version_info["version"]['build_flavor']
561
+ return false if build_flavor.nil? || build_flavor != 'default' || !valid_tagline?(version_info)
562
+ else
563
+ # case >= 7.14
564
+ lower_headers = response.headers.transform_keys {|key| key.to_s.downcase }
565
+ product_header = lower_headers['x-elastic-product']
566
+ return false if product_header != 'Elasticsearch'
567
+ end
568
+ return true
569
+ rescue => e
570
+ logger.error("Unable to retrieve Elasticsearch version", exception: e.class, message: e.message)
571
+ false
531
572
  end
532
573
 
574
+ def valid_tagline?(version_info)
575
+ tagline = version_info['tagline']
576
+ tagline == "You Know, for Search"
577
+ end
533
578
  end
534
579
  end; end; end; end;
@@ -177,6 +177,7 @@ module LogStash; module Outputs; class ElasticSearch;
177
177
 
178
178
  def bulk_send(body_stream, batch_actions)
179
179
  params = compression_level? ? {:headers => {"Content-Encoding" => "gzip"}} : {}
180
+
180
181
  response = @pool.post(@bulk_path, params, body_stream.string)
181
182
 
182
183
  @bulk_response_metrics.increment(response.code.to_s)
@@ -209,7 +210,7 @@ module LogStash; module Outputs; class ElasticSearch;
209
210
  end
210
211
 
211
212
  def get(path)
212
- response = @pool.get(path, nil)
213
+ response = @pool.get(path)
213
214
  LogStash::Json.load(response.body)
214
215
  end
215
216
 
@@ -33,9 +33,9 @@ module LogStash; module Outputs; class ElasticSearch;
33
33
  end
34
34
 
35
35
  common_options[:bulk_path] = if params["bulk_path"]
36
- dedup_slashes("/#{params["bulk_path"]}")
36
+ resolve_filter_path(dedup_slashes("/#{params["bulk_path"]}"))
37
37
  else
38
- dedup_slashes("/#{params["path"]}/_bulk")
38
+ resolve_filter_path(dedup_slashes("/#{params["path"]}/_bulk"))
39
39
  end
40
40
 
41
41
  common_options[:sniffing_path] = if params["sniffing_path"]
@@ -197,5 +197,16 @@ module LogStash; module Outputs; class ElasticSearch;
197
197
  def self.dedup_slashes(url)
198
198
  url.gsub(/\/+/, "/")
199
199
  end
200
+
201
+ # Set a `filter_path` query parameter if it is not already set to be
202
+ # `filter_path=errors,items.*.error,items.*.status` to reduce the payload between Logstash and Elasticsearch
203
+ def self.resolve_filter_path(url)
204
+ return url if url.match?(/(?:[&|?])filter_path=/)
205
+ ("#{url}#{query_param_separator(url)}filter_path=errors,items.*.error,items.*.status")
206
+ end
207
+
208
+ def self.query_param_separator(url)
209
+ url.match?(/\?[^\s#]+/) ? '&' : '?'
210
+ end
200
211
  end
201
212
  end; end; end
@@ -596,7 +596,9 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
596
596
  def install_template
597
597
  TemplateManager.install_template(self)
598
598
  rescue => e
599
- @logger.error("Failed to install template", message: e.message, exception: e.class, backtrace: e.backtrace)
599
+ details = { message: e.message, exception: e.class, backtrace: e.backtrace }
600
+ details[:body] = e.response_body if e.respond_to?(:response_body)
601
+ @logger.error("Failed to install template", details)
600
602
  end
601
603
 
602
604
  def setup_ecs_compatibility_related_defaults
@@ -179,7 +179,9 @@ module LogStash; module PluginMixins; module ElasticSearch
179
179
  cluster_info = client.get('/')
180
180
  plugin_metadata.set(:cluster_uuid, cluster_info['cluster_uuid'])
181
181
  rescue => e
182
- @logger.error("Unable to retrieve Elasticsearch cluster uuid", message: e.message, exception: e.class, backtrace: e.backtrace)
182
+ details = { message: e.message, exception: e.class, backtrace: e.backtrace }
183
+ details[:body] = e.response_body if e.respond_to?(:response_body)
184
+ @logger.error("Unable to retrieve Elasticsearch cluster uuid", details)
183
185
  end
184
186
 
185
187
  def retrying_submit(actions)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-elasticsearch'
3
- s.version = '11.17.0'
3
+ s.version = '11.19.0'
4
4
  s.licenses = ['apache-2.0']
5
5
  s.summary = "Stores logs in Elasticsearch"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -156,7 +156,7 @@ describe "indexing" do
156
156
  let(:config) { "not implemented" }
157
157
  let(:events) { event_count.times.map { event }.to_a }
158
158
  subject { LogStash::Outputs::ElasticSearch.new(config) }
159
-
159
+ let(:filter_path) { "filter_path=errors,items.*.error,items.*.status"}
160
160
  let(:es_url) { "http://#{get_host_port}" }
161
161
  let(:index_url) { "#{es_url}/#{index}" }
162
162
 
@@ -178,7 +178,7 @@ describe "indexing" do
178
178
  subject.do_close
179
179
  end
180
180
 
181
- shared_examples "an indexer" do |secure|
181
+ shared_examples "an indexer" do |secure, expected_path|
182
182
  before(:each) do
183
183
  host_unreachable_error_class = LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError
184
184
  allow(host_unreachable_error_class).to receive(:new).with(any_args).and_wrap_original do |m, original, url|
@@ -212,13 +212,13 @@ describe "indexing" do
212
212
  expect(doc["_index"]).to eq(index)
213
213
  end
214
214
  end
215
-
215
+
216
216
  it "sets the correct content-type header" do
217
217
  expected_manticore_opts = {:headers => {"Content-Type" => "application/json"}, :body => anything}
218
218
  if secure
219
219
  expected_manticore_opts = {
220
- :headers => {"Content-Type" => "application/json"},
221
- :body => anything,
220
+ :headers => {"Content-Type" => "application/json"},
221
+ :body => anything,
222
222
  :auth => {
223
223
  :user => user,
224
224
  :password => password,
@@ -230,6 +230,20 @@ describe "indexing" do
230
230
  and_call_original
231
231
  subject.multi_receive(events)
232
232
  end
233
+
234
+ it "sets the bulk path URL and filter path parameter correctly" do
235
+ expect(subject.client.pool.adapter.client).to receive(:send).
236
+ with(anything, expected_path != nil ? expected_path : anything, anything).at_least(:once).and_call_original
237
+ subject.multi_receive(events)
238
+ end
239
+
240
+ it "receives a filtered response" do
241
+ expect(subject.client).to receive(:join_bulk_responses).
242
+ with([{"errors"=>false, "items"=>[{"index"=>{"status"=>201}}]}]).
243
+ and_call_original
244
+ subject.multi_receive([event])
245
+ end
246
+
233
247
  end
234
248
 
235
249
  shared_examples "PKIX path failure" do
@@ -269,6 +283,65 @@ describe "indexing" do
269
283
  it_behaves_like("an indexer")
270
284
  end
271
285
 
286
+ describe "an indexer with custom bulk path", :integration => true do
287
+ let(:bulk_path) { "/_bulk?routing=true"}
288
+ let(:config) {
289
+ {
290
+ "hosts" => get_host_port,
291
+ "index" => index,
292
+ "http_compression" => false,
293
+ "bulk_path" => bulk_path
294
+ }
295
+ }
296
+ it_behaves_like("an indexer", false) do
297
+ let (:expected_path) { "#{es_url}#{bulk_path}&#{filter_path}" }
298
+ end
299
+ end
300
+
301
+ describe "an indexer with filter path as second parameter", :integration => true do
302
+ let(:bulk_path) { "/_bulk?routing=true&#{filter_path}"}
303
+ let(:config) {
304
+ {
305
+ "hosts" => get_host_port,
306
+ "index" => index,
307
+ "http_compression" => false,
308
+ "bulk_path" => bulk_path
309
+ }
310
+ }
311
+ it_behaves_like("an indexer", false) do
312
+ let (:expected_path) { "#{es_url}/#{bulk_path}" }
313
+ end
314
+ end
315
+
316
+ describe "an indexer with filter path as first parameter", :integration => true do
317
+ let(:bulk_path) { "/_bulk?#{filter_path}&routing=true"}
318
+ let(:config) {
319
+ {
320
+ "hosts" => get_host_port,
321
+ "index" => index,
322
+ "http_compression" => false,
323
+ "bulk_path" => bulk_path
324
+ }
325
+ }
326
+ it_behaves_like("an indexer", false) do
327
+ let (:expected_path) { "#{es_url}/#{bulk_path}" }
328
+ end
329
+ end
330
+
331
+ describe "an indexer with the standard bulk path", :integration => true do
332
+ let(:config) {
333
+ {
334
+ "hosts" => get_host_port,
335
+ "index" => index,
336
+ "http_compression" => false
337
+ }
338
+ }
339
+ it_behaves_like("an indexer", false) do
340
+ let (:expected_path) { "#{es_url}/_bulk?#{filter_path}" }
341
+ end
342
+
343
+ end
344
+
272
345
  describe "an indexer with no type value set (default to doc)", :integration => true do
273
346
  let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
274
347
  let(:config) {
@@ -296,7 +369,7 @@ describe "indexing" do
296
369
  "index" => index,
297
370
  "http_compression" => false
298
371
  }
299
- end
372
+ end
300
373
 
301
374
  let(:curl_opts) { "-u #{user}:#{password}" }
302
375
 
@@ -36,7 +36,17 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
36
36
  end
37
37
  end
38
38
 
39
- describe "healthcheck_path" do
39
+ describe "bulk_path" do
40
+ let (:filter_path) {"filter_path=errors,items.*.error,items.*.status"}
41
+
42
+ shared_examples("filter_path added to bulk path appropriately") do
43
+ it "sets the bulk_path option to the expected bulk path" do
44
+ expect(described_class).to receive(:create_http_client) do |options|
45
+ expect(options[:bulk_path]).to eq(expected_bulk_path)
46
+ end
47
+ described_class.build(logger, hosts, options)
48
+ end
49
+ end
40
50
 
41
51
  context "when setting bulk_path" do
42
52
  let(:bulk_path) { "/meh" }
@@ -44,21 +54,31 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
44
54
 
45
55
  context "when using path" do
46
56
  let(:options) { super().merge("path" => "/path") }
47
- it "ignores the path setting" do
48
- expect(described_class).to receive(:create_http_client) do |options|
49
- expect(options[:bulk_path]).to eq(bulk_path)
50
- end
51
- described_class.build(logger, hosts, options)
52
- end
57
+ let(:expected_bulk_path) { "#{bulk_path}?#{filter_path}" }
58
+
59
+ it_behaves_like "filter_path added to bulk path appropriately"
60
+ end
61
+
62
+ context "when setting a filter path as first parameter" do
63
+ let (:filter_path) {"filter_path=error"}
64
+ let(:bulk_path) { "/meh?#{filter_path}&routing=true" }
65
+ let(:expected_bulk_path) { bulk_path }
66
+
67
+ it_behaves_like "filter_path added to bulk path appropriately"
68
+ end
69
+
70
+ context "when setting a filter path as second parameter" do
71
+ let (:filter_path) {"filter_path=error"}
72
+ let(:bulk_path) { "/meh?routing=true&#{filter_path}" }
73
+ let(:expected_bulk_path) { bulk_path }
74
+
75
+ it_behaves_like "filter_path added to bulk path appropriately"
53
76
  end
77
+
54
78
  context "when not using path" do
79
+ let(:expected_bulk_path) { "#{bulk_path}?#{filter_path}"}
55
80
 
56
- it "uses the bulk_path setting" do
57
- expect(described_class).to receive(:create_http_client) do |options|
58
- expect(options[:bulk_path]).to eq(bulk_path)
59
- end
60
- described_class.build(logger, hosts, options)
61
- end
81
+ it_behaves_like "filter_path added to bulk path appropriately"
62
82
  end
63
83
  end
64
84
 
@@ -66,25 +86,20 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
66
86
 
67
87
  context "when using path" do
68
88
  let(:path) { "/meh" }
89
+ let(:expected_bulk_path) { "#{path}/_bulk?#{filter_path}"}
69
90
  let(:options) { super().merge("path" => path) }
70
- it "sets bulk_path to path+_bulk" do
71
- expect(described_class).to receive(:create_http_client) do |options|
72
- expect(options[:bulk_path]).to eq("#{path}/_bulk")
73
- end
74
- described_class.build(logger, hosts, options)
75
- end
91
+
92
+ it_behaves_like "filter_path added to bulk path appropriately"
76
93
  end
77
94
 
78
95
  context "when not using path" do
79
- it "sets the bulk_path to _bulk" do
80
- expect(described_class).to receive(:create_http_client) do |options|
81
- expect(options[:bulk_path]).to eq("/_bulk")
82
- end
83
- described_class.build(logger, hosts, options)
84
- end
96
+ let(:expected_bulk_path) { "/_bulk?#{filter_path}"}
97
+
98
+ it_behaves_like "filter_path added to bulk path appropriately"
85
99
  end
86
100
  end
87
101
  end
102
+
88
103
  describe "healthcheck_path" do
89
104
  context "when setting healthcheck_path" do
90
105
  let(:healthcheck_path) { "/meh" }
@@ -7,8 +7,14 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
7
7
  let(:adapter) { LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(logger, {}) }
8
8
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
9
9
  let(:options) { {:resurrect_delay => 3, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests
10
- let(:es_version_info) { [ { "number" => '0.0.0', "build_flavor" => 'default'} ] }
11
10
  let(:license_status) { 'active' }
11
+ let(:root_response) { MockResponse.new(200,
12
+ {"tagline" => "You Know, for Search",
13
+ "version" => {
14
+ "number" => '8.9.0',
15
+ "build_flavor" => 'default'} },
16
+ { "X-Elastic-Product" => "Elasticsearch" }
17
+ ) }
12
18
 
13
19
  subject { described_class.new(logger, adapter, initial_urls, options) }
14
20
 
@@ -22,7 +28,6 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
22
28
 
23
29
  allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
24
30
 
25
- allow(subject).to receive(:get_es_version).with(any_args).and_return(*es_version_info)
26
31
  allow(subject.license_checker).to receive(:license_status).and_return(license_status)
27
32
  end
28
33
 
@@ -37,35 +42,42 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
37
42
  end
38
43
  end
39
44
 
40
- describe "the resurrectionist" do
41
- before(:each) { subject.start }
42
- it "should start the resurrectionist when created" do
43
- expect(subject.resurrectionist_alive?).to eql(true)
44
- end
45
+ describe "healthcheck" do
45
46
 
46
- it "should attempt to resurrect connections after the ressurrect delay" do
47
- expect(subject).to receive(:healthcheck!).once
48
- sleep(subject.resurrect_delay + 1)
47
+ describe "the resurrectionist" do
48
+ before(:each) { subject.start }
49
+ it "should start the resurrectionist when created" do
50
+ expect(subject.resurrectionist_alive?).to eql(true)
51
+ end
52
+
53
+ it "should attempt to resurrect connections after the ressurrect delay" do
54
+ expect(subject).to receive(:healthcheck!).once
55
+ sleep(subject.resurrect_delay + 1)
56
+ end
49
57
  end
50
58
 
51
- describe "healthcheck url handling" do
59
+ describe "healthcheck path handling" do
52
60
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
53
- let(:success_response) { double("Response", :code => 200) }
61
+ let(:healthcheck_response) { double("Response", :code => 200) }
54
62
 
55
63
  before(:example) do
64
+ subject.start
65
+
66
+ expect(adapter).to receive(:perform_request).with(anything, :head, eq(healthcheck_path), anything, anything) do |url, _, _, _, _|
67
+ expect(url.path).to be_empty
68
+ healthcheck_response
69
+ end
70
+
56
71
  expect(adapter).to receive(:perform_request).with(anything, :get, "/", anything, anything) do |url, _, _, _, _|
57
72
  expect(url.path).to be_empty
73
+ root_response
58
74
  end
59
75
  end
60
76
 
61
77
  context "and not setting healthcheck_path" do
78
+ let(:healthcheck_path) { "/" }
62
79
  it "performs the healthcheck to the root" do
63
- expect(adapter).to receive(:perform_request).with(anything, :head, "/", anything, anything) do |url, _, _, _, _|
64
- expect(url.path).to be_empty
65
-
66
- success_response
67
- end
68
- expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch")
80
+ subject.healthcheck!
69
81
  end
70
82
  end
71
83
 
@@ -73,14 +85,116 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
73
85
  let(:healthcheck_path) { "/my/health" }
74
86
  let(:options) { super().merge(:healthcheck_path => healthcheck_path) }
75
87
  it "performs the healthcheck to the healthcheck_path" do
76
- expect(adapter).to receive(:perform_request).with(anything, :head, eq(healthcheck_path), anything, anything) do |url, _, _, _, _|
77
- expect(url.path).to be_empty
88
+ subject.healthcheck!
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "register phase" do
94
+ shared_examples_for "root path returns bad code error" do |err_msg|
95
+ before :each do
96
+ subject.update_initial_urls
97
+ expect(subject).to receive(:elasticsearch?).never
98
+ end
99
+
100
+ it "raises ConfigurationError" do
101
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", nil])
102
+ expect(subject).to receive(:get_root_path).with(anything).and_return([nil,
103
+ ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(mock_resp.code, nil, nil, mock_resp.body)])
104
+ expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, err_msg)
105
+ end
106
+ end
107
+
108
+ context "with 200 without version" do
109
+ let(:mock_resp) { MockResponse.new(200, {"tagline" => "You Know, for Search"}) }
78
110
 
79
- success_response
80
- end
111
+ it "raises ConfigurationError" do
112
+ subject.update_initial_urls
113
+
114
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", nil])
115
+ expect(subject).to receive(:get_root_path).with(anything).and_return([mock_resp, nil])
81
116
  expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch")
82
117
  end
83
118
  end
119
+
120
+ context "with 200 serverless" do
121
+ let(:good_resp) { MockResponse.new(200,
122
+ { "tagline" => "You Know, for Search",
123
+ "version" => { "number" => '8.10.0', "build_flavor" => 'serverless'}
124
+ },
125
+ { "X-Elastic-Product" => "Elasticsearch" }
126
+ ) }
127
+ let(:bad_400_err) do
128
+ ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(400,
129
+ nil, nil,
130
+ "The requested [Elastic-Api-Version] header value of [2024-10-31] is not valid. Only [2023-10-31] is supported")
131
+ end
132
+
133
+ it "raises ConfigurationError when the serverless connection test fails" do
134
+ subject.update_initial_urls
135
+
136
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", nil])
137
+ expect(subject).to receive(:get_root_path).with(anything).and_return([good_resp, nil])
138
+ expect(subject).to receive(:get_root_path).with(anything, hash_including(:headers => LogStash::Outputs::ElasticSearch::HttpClient::Pool::DEFAULT_EAV_HEADER)).and_return([nil, bad_400_err])
139
+ expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, "The Elastic-Api-Version header is not valid")
140
+ end
141
+
142
+ it "passes when the serverless connection test succeeds" do
143
+ subject.update_initial_urls
144
+
145
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", nil])
146
+ expect(subject).to receive(:get_root_path).with(anything).and_return([good_resp, nil])
147
+ expect(subject).to receive(:get_root_path).with(anything, hash_including(:headers => LogStash::Outputs::ElasticSearch::HttpClient::Pool::DEFAULT_EAV_HEADER)).and_return([good_resp, nil])
148
+ expect { subject.healthcheck! }.not_to raise_error
149
+ end
150
+ end
151
+
152
+ context "with 200 default" do
153
+ let(:good_resp) { MockResponse.new(200,
154
+ { "tagline" => "You Know, for Search",
155
+ "version" => { "number" => '8.10.0', "build_flavor" => 'default'}
156
+ },
157
+ { "X-Elastic-Product" => "Elasticsearch" }
158
+ ) }
159
+
160
+ it "passes without checking serverless connection" do
161
+ subject.update_initial_urls
162
+
163
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", nil])
164
+ expect(subject).to receive(:get_root_path).with(anything).and_return([good_resp, nil])
165
+ expect(subject).not_to receive(:get_root_path).with(anything, hash_including(:headers => LogStash::Outputs::ElasticSearch::HttpClient::Pool::DEFAULT_EAV_HEADER))
166
+ expect { subject.healthcheck! }.not_to raise_error
167
+ end
168
+ end
169
+
170
+ context "with 400" do
171
+ let(:mock_resp) { MockResponse.new(400, "The requested [Elastic-Api-Version] header value of [2024-10-31] is not valid. Only [2023-10-31] is supported") }
172
+ it_behaves_like "root path returns bad code error", "The Elastic-Api-Version header is not valid"
173
+ end
174
+
175
+ context "with 401" do
176
+ let(:mock_resp) { MockResponse.new(401, "missing authentication") }
177
+ it_behaves_like "root path returns bad code error", "Could not read Elasticsearch. Please check the credentials"
178
+ end
179
+
180
+ context "with 403" do
181
+ let(:mock_resp) { MockResponse.new(403, "Forbidden") }
182
+ it_behaves_like "root path returns bad code error", "Could not read Elasticsearch. Please check the privileges"
183
+ end
184
+ end
185
+
186
+ describe "non register phase" do
187
+ let(:health_bad_code_err) { ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(400, nil, nil, nil) }
188
+
189
+ before :each do
190
+ subject.update_initial_urls
191
+ end
192
+
193
+ it "does not call root path when health check request fails" do
194
+ expect(subject).to receive(:health_check_request).with(anything).and_return(["", health_bad_code_err])
195
+ expect(subject).to receive(:get_root_path).never
196
+ subject.healthcheck!(false)
197
+ end
84
198
  end
85
199
  end
86
200
 
@@ -251,23 +365,23 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
251
365
  ::LogStash::Util::SafeURI.new("http://otherhost:9201")
252
366
  ] }
253
367
 
254
- let(:valid_response) { MockResponse.new(200, {"tagline" => "You Know, for Search",
255
- "version" => {
256
- "number" => '7.13.0',
257
- "build_flavor" => 'default'}
258
- }) }
259
-
260
- before(:each) do
261
- allow(subject).to receive(:perform_request_to_url).and_return(valid_response)
262
- subject.start
263
- end
264
-
265
- it "picks the largest major version" do
266
- expect(subject.maximum_seen_major_version).to eq(0)
267
- end
368
+ let(:root_response) { MockResponse.new(200, {"tagline" => "You Know, for Search",
369
+ "version" => {
370
+ "number" => '0.0.0',
371
+ "build_flavor" => 'default'}
372
+ }) }
373
+ let(:root_response2) { MockResponse.new(200, {"tagline" => "You Know, for Search",
374
+ "version" => {
375
+ "number" => '6.0.0',
376
+ "build_flavor" => 'default'}
377
+ }) }
268
378
 
269
379
  context "if there are nodes with multiple major versions" do
270
- let(:es_version_info) { [ { "number" => '0.0.0', "build_flavor" => 'default'}, { "number" => '6.0.0', "build_flavor" => 'default'} ] }
380
+ before(:each) do
381
+ allow(subject).to receive(:perform_request_to_url).and_return(root_response, root_response2)
382
+ subject.start
383
+ end
384
+
271
385
  it "picks the largest major version" do
272
386
  expect(subject.maximum_seen_major_version).to eq(6)
273
387
  end
@@ -275,32 +389,31 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
275
389
  end
276
390
 
277
391
 
278
- describe "build flavour tracking" do
392
+ describe "build flavor tracking" do
279
393
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://somehost:9200")] }
280
394
 
281
- let(:es_version_info) { [ { "number" => '8.9.0', "build_flavor" => "serverless" } ] }
282
-
283
- let(:valid_response) { MockResponse.new(200,
395
+ let(:root_response) { MockResponse.new(200,
284
396
  {"tagline" => "You Know, for Search",
285
397
  "version" => {
286
398
  "number" => '8.9.0',
287
- "build_flavor" => LogStash::Outputs::ElasticSearch::HttpClient::Pool::BUILD_FLAVOUR_SERVERLESS} },
399
+ "build_flavor" => LogStash::Outputs::ElasticSearch::HttpClient::Pool::BUILD_FLAVOR_SERVERLESS} },
288
400
  { "X-Elastic-Product" => "Elasticsearch" }
289
401
  ) }
290
402
 
291
403
  before(:each) do
292
- allow(subject).to receive(:perform_request_to_url).and_return(valid_response)
404
+ allow(subject).to receive(:perform_request_to_url).and_return(root_response)
293
405
  subject.start
294
406
  end
295
407
 
296
- it "picks the build flavour" do
408
+ it "picks the build flavor" do
297
409
  expect(subject.serverless?).to be_truthy
298
410
  end
299
411
  end
300
412
 
301
413
  describe "license checking" do
302
414
  before(:each) do
303
- allow(subject).to receive(:health_check_request)
415
+ allow(subject).to receive(:health_check_request).and_return(["", nil])
416
+ allow(subject).to receive(:perform_request_to_url).and_return(root_response)
304
417
  allow(subject).to receive(:elasticsearch?).and_return(true)
305
418
  end
306
419
 
@@ -327,6 +440,36 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
327
440
  end
328
441
  end
329
442
 
443
+ describe "elastic api version header" do
444
+ let(:eav) { "Elastic-Api-Version" }
445
+
446
+ context "when it is serverless" do
447
+ before(:each) do
448
+ expect(subject).to receive(:serverless?).and_return(true)
449
+ end
450
+
451
+ it "add the default header" do
452
+ expect(adapter).to receive(:perform_request).with(anything, :get, "/", anything, anything) do |_, _, _, params, _|
453
+ expect(params[:headers]).to eq({ "User-Agent" => "chromium", "Elastic-Api-Version" => "2023-10-31"})
454
+ end
455
+ subject.perform_request_to_url(initial_urls, :get, "/", { :headers => { "User-Agent" => "chromium" }} )
456
+ end
457
+ end
458
+
459
+ context "when it is stateful" do
460
+ before(:each) do
461
+ expect(subject).to receive(:serverless?).and_return(false)
462
+ end
463
+
464
+ it "add the default header" do
465
+ expect(adapter).to receive(:perform_request).with(anything, :get, "/", anything, anything) do |_, _, _, params, _|
466
+ expect(params[:headers]).to be_nil
467
+ end
468
+ subject.perform_request_to_url(initial_urls, :get, "/" )
469
+ end
470
+ end
471
+ end
472
+
330
473
  # TODO: extract to ElasticSearchOutputLicenseChecker unit spec
331
474
  describe "license checking with ElasticSearchOutputLicenseChecker" do
332
475
  let(:options) do
@@ -334,7 +477,8 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
334
477
  end
335
478
 
336
479
  before(:each) do
337
- allow(subject).to receive(:health_check_request)
480
+ allow(subject).to receive(:health_check_request).and_return(["", nil])
481
+ allow(subject).to receive(:perform_request_to_url).and_return(root_response)
338
482
  allow(subject).to receive(:elasticsearch?).and_return(true)
339
483
  end
340
484
 
@@ -388,114 +532,71 @@ describe "#elasticsearch?" do
388
532
  let(:adapter) { double("Manticore Adapter") }
389
533
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
390
534
  let(:options) { {:resurrect_delay => 2, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests
391
- let(:es_version_info) { [{ "number" => '0.0.0', "build_flavor" => 'default'}] }
392
- let(:license_status) { 'active' }
393
535
 
394
536
  subject { LogStash::Outputs::ElasticSearch::HttpClient::Pool.new(logger, adapter, initial_urls, options) }
395
537
 
396
- let(:url) { ::LogStash::Util::SafeURI.new("http://localhost:9200") }
397
-
398
- context "in case HTTP error code" do
399
- it "should fail for 401" do
400
- allow(adapter).to receive(:perform_request)
401
- .with(anything, :get, "/", anything, anything)
402
- .and_return(MockResponse.new(401))
403
-
404
- expect(subject.elasticsearch?(url)).to be false
405
- end
406
-
407
- it "should fail for 403" do
408
- allow(adapter).to receive(:perform_request)
409
- .with(anything, :get, "/", anything, anything)
410
- .and_return(status: 403)
411
- expect(subject.elasticsearch?(url)).to be false
412
- end
413
- end
414
-
415
538
  context "when connecting to a cluster which reply without 'version' field" do
416
539
  it "should fail" do
417
- allow(adapter).to receive(:perform_request)
418
- .with(anything, :get, "/", anything, anything)
419
- .and_return(body: {"field" => "funky.com"}.to_json)
420
- expect(subject.elasticsearch?(url)).to be false
540
+ resp = MockResponse.new(200, {"field" => "funky.com"} )
541
+ expect(subject.send(:elasticsearch?, resp)).to be false
421
542
  end
422
543
  end
423
544
 
424
545
  context "when connecting to a cluster with version < 6.0.0" do
425
546
  it "should fail" do
426
- allow(adapter).to receive(:perform_request)
427
- .with(anything, :get, "/", anything, anything)
428
- .and_return(200, {"version" => { "number" => "5.0.0"}}.to_json)
429
- expect(subject.elasticsearch?(url)).to be false
547
+ resp = MockResponse.new(200, {"version" => { "number" => "5.0.0" }})
548
+ expect(subject.send(:elasticsearch?, resp)).to be false
430
549
  end
431
550
  end
432
551
 
433
552
  context "when connecting to a cluster with version in [6.0.0..7.0.0)" do
434
553
  it "must be successful with valid 'tagline'" do
435
- allow(adapter).to receive(:perform_request)
436
- .with(anything, :get, "/", anything, anything)
437
- .and_return(MockResponse.new(200, {"version" => {"number" => "6.5.0"}, "tagline" => "You Know, for Search"}))
438
- expect(subject.elasticsearch?(url)).to be true
554
+ resp = MockResponse.new(200, {"version" => {"number" => "6.5.0"}, "tagline" => "You Know, for Search"} )
555
+ expect(subject.send(:elasticsearch?, resp)).to be true
439
556
  end
440
557
 
441
558
  it "should fail if invalid 'tagline'" do
442
- allow(adapter).to receive(:perform_request)
443
- .with(anything, :get, "/", anything, anything)
444
- .and_return(MockResponse.new(200, {"version" => {"number" => "6.5.0"}, "tagline" => "You don't know"}))
445
- expect(subject.elasticsearch?(url)).to be false
559
+ resp = MockResponse.new(200, {"version" => {"number" => "6.5.0"}, "tagline" => "You don't know"} )
560
+ expect(subject.send(:elasticsearch?, resp)).to be false
446
561
  end
447
562
 
448
563
  it "should fail if 'tagline' is not present" do
449
- allow(adapter).to receive(:perform_request)
450
- .with(anything, :get, "/", anything, anything)
451
- .and_return(MockResponse.new(200, {"version" => {"number" => "6.5.0"}}))
452
- expect(subject.elasticsearch?(url)).to be false
564
+ resp = MockResponse.new(200, {"version" => {"number" => "6.5.0"}} )
565
+ expect(subject.send(:elasticsearch?, resp)).to be false
453
566
  end
454
567
  end
455
568
 
456
569
  context "when connecting to a cluster with version in [7.0.0..7.14.0)" do
457
570
  it "must be successful is 'build_flavor' is 'default' and tagline is correct" do
458
- allow(adapter).to receive(:perform_request)
459
- .with(anything, :get, "/", anything, anything)
460
- .and_return(MockResponse.new(200, {"version": {"number": "7.5.0", "build_flavor": "default"}, "tagline": "You Know, for Search"}))
461
- expect(subject.elasticsearch?(url)).to be true
571
+ resp = MockResponse.new(200, {"version": {"number": "7.5.0", "build_flavor": "default"}, "tagline": "You Know, for Search"} )
572
+ expect(subject.send(:elasticsearch?, resp)).to be true
462
573
  end
463
574
 
464
575
  it "should fail if 'build_flavor' is not 'default' and tagline is correct" do
465
- allow(adapter).to receive(:perform_request)
466
- .with(anything, :get, "/", anything, anything)
467
- .and_return(MockResponse.new(200, {"version": {"number": "7.5.0", "build_flavor": "oss"}, "tagline": "You Know, for Search"}))
468
- expect(subject.elasticsearch?(url)).to be false
576
+ resp = MockResponse.new(200, {"version": {"number": "7.5.0", "build_flavor": "oss"}, "tagline": "You Know, for Search"} )
577
+ expect(subject.send(:elasticsearch?, resp)).to be false
469
578
  end
470
579
 
471
580
  it "should fail if 'build_flavor' is not present and tagline is correct" do
472
- allow(adapter).to receive(:perform_request)
473
- .with(anything, :get, "/", anything, anything)
474
- .and_return(MockResponse.new(200, {"version": {"number": "7.5.0"}, "tagline": "You Know, for Search"}))
475
- expect(subject.elasticsearch?(url)).to be false
581
+ resp = MockResponse.new(200, {"version": {"number": "7.5.0"}, "tagline": "You Know, for Search"} )
582
+ expect(subject.send(:elasticsearch?, resp)).to be false
476
583
  end
477
584
  end
478
585
 
479
586
  context "when connecting to a cluster with version >= 7.14.0" do
480
587
  it "should fail if 'X-elastic-product' header is not present" do
481
- allow(adapter).to receive(:perform_request)
482
- .with(anything, :get, "/", anything, anything)
483
- .and_return(MockResponse.new(200, {"version": {"number": "7.14.0"}}))
484
- expect(subject.elasticsearch?(url)).to be false
588
+ resp = MockResponse.new(200, {"version": {"number": "7.14.0"}} )
589
+ expect(subject.send(:elasticsearch?, resp)).to be false
485
590
  end
486
591
 
487
592
  it "should fail if 'X-elastic-product' header is present but with bad value" do
488
- allow(adapter).to receive(:perform_request)
489
- .with(anything, :get, "/", anything, anything)
490
- .and_return(MockResponse.new(200, {"version": {"number": "7.14.0"}}, {'X-elastic-product' => 'not good'}))
491
- expect(subject.elasticsearch?(url)).to be false
593
+ resp = MockResponse.new(200, {"version": {"number": "7.14.0"}}, {'X-elastic-product' => 'not good'} )
594
+ expect(subject.send(:elasticsearch?, resp)).to be false
492
595
  end
493
596
 
494
597
  it "must be successful when 'X-elastic-product' header is present with 'Elasticsearch' value" do
495
- allow(adapter).to receive(:perform_request)
496
- .with(anything, :get, "/", anything, anything)
497
- .and_return(MockResponse.new(200, {"version": {"number": "7.14.0"}}, {'X-elastic-product' => 'Elasticsearch'}))
498
- expect(subject.elasticsearch?(url)).to be true
598
+ resp = MockResponse.new(200, {"version": {"number": "7.14.0"}}, {'X-elastic-product' => 'Elasticsearch'} )
599
+ expect(subject.send(:elasticsearch?, resp)).to be true
499
600
  end
500
601
  end
501
602
  end
@@ -135,7 +135,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
135
135
  }
136
136
 
137
137
  it "returns the hash response" do
138
- expect(subject.pool).to receive(:get).with(path, nil).and_return(get_response)
138
+ expect(subject.pool).to receive(:get).with(path).and_return(get_response)
139
139
  expect(subject.get(path)["body"]).to eq(body)
140
140
  end
141
141
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.17.0
4
+ version: 11.19.0
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-14 00:00:00.000000000 Z
11
+ date: 2023-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement