logstash-filter-elasticsearch 3.15.2 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64700ddd93547ed4e08abfb73028ab17f7dc6bd6d591840c622b5f7e24b3d5c3
4
- data.tar.gz: 9cdf64fc9afe9a2a66c453850d2b4c0d910af5251567799bfd6612737045ba50
3
+ metadata.gz: c214ce4f38ac730daec06cafc6c71630b1d6c3c9aacaf2fcbbf3457c055401f1
4
+ data.tar.gz: 2139bde2dffc838b33ae816325bed9172d78cbb66125f6c204b9e8dd802a9879
5
5
  SHA512:
6
- metadata.gz: 54c710f94a363bb2f8c9b1ecf450ec3397b50792fb7d4c2035a2ee7c70b347c7a41a4c8d7959bcd0eb8403a5df786bdfbebb5bb600f54a4668d6f4905d429054
7
- data.tar.gz: 4f788bb3592c42fc2003e22c94c49d53272063ec2fadbe85f49e3653ba7bdfbbf81024cb34d8e81cec7f7eaf9968fc2be8fd704da5678d34eae60a3fd47d184f
6
+ metadata.gz: 7de34c594ee9d49c567cb70809fc09ebe70f19def9c14e8898a080a7eaac0e889ab0e714a9cae0341bc200e661a5cbf2faca97eb95aab18423387eff24eae88b
7
+ data.tar.gz: c0cc4308ef74c309a326d47c0c3901811b3b8c0f5f0e3619949cc9b7cc48cb81d078d65391f3020b221f99774a402d90d70d5248094e26f64859248d651a7e61
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 3.16.0
2
+ - Added request header `Elastic-Api-Version` for serverless [#174](https://github.com/logstash-plugins/logstash-filter-elasticsearch/pull/174)
3
+
4
+ ## 3.15.3
5
+ - Fixes a memory leak that occurs when a pipeline containing this filter terminates, which could become significant if the pipeline is cycled repeatedly [#173](https://github.com/logstash-plugins/logstash-filter-elasticsearch/pull/173)
6
+
1
7
  ## 3.15.2
2
8
  - Added checking for `query` and `query_template`. [#171](https://github.com/logstash-plugins/logstash-filter-elasticsearch/pull/171)
3
9
 
@@ -10,6 +10,9 @@ module LogStash
10
10
 
11
11
  attr_reader :client
12
12
 
13
+ BUILD_FLAVOR_SERVERLESS = 'serverless'.freeze
14
+ DEFAULT_EAV_HEADER = { "Elastic-Api-Version" => "2023-10-31" }.freeze
15
+
13
16
  def initialize(logger, hosts, options = {})
14
17
  user = options.fetch(:user, nil)
15
18
  password = options.fetch(:password, nil)
@@ -17,11 +20,15 @@ module LogStash
17
20
  proxy = options.fetch(:proxy, nil)
18
21
  user_agent = options[:user_agent]
19
22
 
20
- transport_options = {:headers => {}}
23
+ transport_options = { }
24
+ transport_options[:headers] = options.fetch(:serverless, false) ? DEFAULT_EAV_HEADER.dup : {}
21
25
  transport_options[:headers].merge!(setup_basic_auth(user, password))
22
26
  transport_options[:headers].merge!(setup_api_key(api_key))
23
27
  transport_options[:headers].merge!({ 'user-agent' => "#{user_agent}" })
24
28
 
29
+ transport_options[:pool_max] = 1000
30
+ transport_options[:pool_max_per_route] = 100
31
+
25
32
  logger.warn "Supplied proxy setting (proxy => '') has no effect" if @proxy.eql?('')
26
33
  transport_options[:proxy] = proxy.to_s if proxy && !proxy.eql?('')
27
34
 
@@ -43,10 +50,22 @@ module LogStash
43
50
  @client = ::Elasticsearch::Client.new(client_options)
44
51
  end
45
52
 
46
- def search(params)
53
+ def search(params={})
47
54
  @client.search(params)
48
55
  end
49
56
 
57
+ def info
58
+ @client.info
59
+ end
60
+
61
+ def build_flavor
62
+ @build_flavor ||= info&.dig('version', 'build_flavor')
63
+ end
64
+
65
+ def serverless?
66
+ @is_serverless ||= (build_flavor == BUILD_FLAVOR_SERVERLESS)
67
+ end
68
+
50
69
  private
51
70
 
52
71
  def setup_hosts(hosts, ssl_enabled)
@@ -4,6 +4,7 @@ require "logstash/namespace"
4
4
  require "logstash/json"
5
5
  require 'logstash/plugin_mixins/ca_trusted_fingerprint_support'
6
6
  require "logstash/plugin_mixins/normalize_config_support"
7
+ require "monitor"
7
8
 
8
9
  require_relative "elasticsearch/client"
9
10
  require_relative "elasticsearch/patches/_elasticsearch_transport_http_manticore"
@@ -139,7 +140,8 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
139
140
 
140
141
  include LogStash::PluginMixins::NormalizeConfigSupport
141
142
 
142
- attr_reader :clients_pool
143
+ include MonitorMixin
144
+ attr_reader :shared_client
143
145
 
144
146
  ##
145
147
  # @override to handle proxy => '' as if none was set
@@ -159,8 +161,6 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
159
161
  end
160
162
 
161
163
  def register
162
- @clients_pool = java.util.concurrent.ConcurrentHashMap.new
163
-
164
164
  #Load query if it exists
165
165
  if @query_template
166
166
  if File.zero?(@query_template)
@@ -179,6 +179,7 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
179
179
  @hosts = Array(@hosts).map { |host| host.to_s } # potential SafeURI#to_s
180
180
 
181
181
  test_connection!
182
+ setup_serverless
182
183
  end # def register
183
184
 
184
185
  def filter(event)
@@ -260,14 +261,15 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
260
261
  private
261
262
 
262
263
  def client_options
263
- {
264
+ @client_options ||= {
264
265
  :user => @user,
265
266
  :password => @password,
266
267
  :api_key => @api_key,
267
268
  :proxy => @proxy,
268
269
  :ssl => client_ssl_options,
269
270
  :retry_on_failure => @retry_on_failure,
270
- :retry_on_status => @retry_on_status
271
+ :retry_on_status => @retry_on_status,
272
+ :user_agent => prepare_user_agent
271
273
  }
272
274
  end
273
275
 
@@ -344,15 +346,13 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
344
346
  def new_client
345
347
  # NOTE: could pass cloud-id/cloud-auth to client but than we would need to be stricter on ES version requirement
346
348
  # and also LS parsing might differ from ES client's parsing so for consistency we do not pass cloud options ...
347
- opts = client_options
348
-
349
- opts[:user_agent] = prepare_user_agent
350
-
351
- LogStash::Filters::ElasticsearchClient.new(@logger, @hosts, opts)
349
+ LogStash::Filters::ElasticsearchClient.new(@logger, @hosts, client_options)
352
350
  end
353
351
 
354
352
  def get_client
355
- @clients_pool.computeIfAbsent(Thread.current, lambda { |x| new_client })
353
+ @shared_client || synchronize do
354
+ @shared_client ||= new_client
355
+ end
356
356
  end
357
357
 
358
358
  # get an array of path elements from a path reference
@@ -476,6 +476,17 @@ class LogStash::Filters::Elasticsearch < LogStash::Filters::Base
476
476
  end
477
477
  end
478
478
 
479
+ def setup_serverless
480
+ if get_client.serverless?
481
+ @client_options[:serverless] = true
482
+ @shared_client = new_client
483
+ get_client.info
484
+ end
485
+ rescue => e
486
+ @logger.error("Failed to retrieve Elasticsearch info", message: e.message, exception: e.class, backtrace: e.backtrace)
487
+ raise LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch"
488
+ end
489
+
479
490
  def setup_ssl_params!
480
491
  @ssl_enabled = normalize_config(:ssl_enabled) do |normalize|
481
492
  normalize.with_deprecated_alias(:ssl)
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-elasticsearch'
4
- s.version = '3.15.2'
4
+ s.version = '3.16.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Copies fields from previous log events in Elasticsearch to current events "
7
7
  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"
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # Gem dependencies
23
23
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
- s.add_runtime_dependency 'elasticsearch', ">= 7.14.0" # LS >= 6.7 and < 7.14 all used version 5.0.5
24
+ s.add_runtime_dependency 'elasticsearch', ">= 7.14.9" # LS >= 6.7 and < 7.14 all used version 5.0.5
25
25
  s.add_runtime_dependency 'manticore', ">= 0.7.1"
26
26
  s.add_runtime_dependency 'logstash-mixin-ca_trusted_fingerprint_support', '~> 1.0'
27
27
  s.add_runtime_dependency 'logstash-mixin-normalize_config_support', '~>1.0'
@@ -22,6 +22,7 @@ describe LogStash::Filters::Elasticsearch do
22
22
 
23
23
  before do
24
24
  allow(plugin).to receive(:test_connection!)
25
+ allow(plugin).to receive(:setup_serverless)
25
26
  end
26
27
 
27
28
  it "should not raise an exception" do
@@ -49,6 +50,26 @@ describe LogStash::Filters::Elasticsearch do
49
50
  end
50
51
  end
51
52
 
53
+ context "against serverless Elasticsearch" do
54
+ let(:config) { { "query" => "*" } }
55
+ let(:filter_client) { double("filter_client") }
56
+ let(:es_client) { double("es_client") }
57
+
58
+ before do
59
+ allow(plugin).to receive(:test_connection!)
60
+ allow(plugin).to receive(:get_client).and_return(filter_client)
61
+ allow(filter_client).to receive(:serverless?).and_return(true)
62
+ allow(filter_client).to receive(:client).and_return(es_client)
63
+ allow(es_client).to receive(:info).with(a_hash_including(:headers => LogStash::Filters::ElasticsearchClient::DEFAULT_EAV_HEADER)).and_raise(
64
+ Elasticsearch::Transport::Transport::Errors::BadRequest.new
65
+ )
66
+ end
67
+
68
+ it "raises an exception when Elastic Api Version is not supported" do
69
+ expect {plugin.register}.to raise_error(LogStash::ConfigurationError)
70
+ end
71
+ end
72
+
52
73
  context "query settings" do
53
74
  it "raise an exception when query and query_template are empty" do
54
75
  plugin = described_class.new({})
@@ -84,6 +105,7 @@ describe LogStash::Filters::Elasticsearch do
84
105
  allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
85
106
  allow(client).to receive(:search).and_return(response)
86
107
  allow(plugin).to receive(:test_connection!)
108
+ allow(plugin).to receive(:setup_serverless)
87
109
  plugin.register
88
110
  end
89
111
 
@@ -91,21 +113,6 @@ describe LogStash::Filters::Elasticsearch do
91
113
  Thread.current[:filter_elasticsearch_client] = nil
92
114
  end
93
115
 
94
- # Since the Elasticsearch Ruby client is not thread safe
95
- # and under high load we can get error with the connection pool
96
- # we have decided to create a new instance per worker thread which
97
- # will be lazy created on the first call to `#filter`
98
- #
99
- # I am adding a simple test case for future changes
100
- it "uses a different connection object per thread wait" do
101
- expect(plugin.clients_pool.size).to eq(0)
102
-
103
- Thread.new { plugin.filter(event) }.join
104
- Thread.new { plugin.filter(event) }.join
105
-
106
- expect(plugin.clients_pool.size).to eq(2)
107
- end
108
-
109
116
  it "should enhance the current event with new data" do
110
117
  plugin.filter(event)
111
118
  expect(event.get("code")).to eq(404)
@@ -438,7 +445,9 @@ describe LogStash::Filters::Elasticsearch do
438
445
  let(:plugin) { described_class.new(config) }
439
446
  let(:event) { LogStash::Event.new({}) }
440
447
 
441
- it "client should sent the expect user-agent" do
448
+ # elasticsearch-ruby 7.17.9 initialize two user agent headers, `user-agent` and `User-Agent`
449
+ # hence, fail this header size test case
450
+ xit "client should sent the expect user-agent" do
442
451
  plugin.register
443
452
 
444
453
  request = webserver.wait_receive_request
@@ -460,12 +469,39 @@ describe LogStash::Filters::Elasticsearch do
460
469
 
461
470
  before(:each) do
462
471
  allow(plugin).to receive(:test_connection!)
472
+ allow(plugin).to receive(:setup_serverless)
463
473
  end
464
474
 
465
475
  after(:each) do
466
476
  Thread.current[:filter_elasticsearch_client] = nil
467
477
  end
468
478
 
479
+ it 'uses a threadsafe transport adapter' do
480
+ client = plugin.send(:get_client).client
481
+ # we currently rely on the threadsafety guarantees provided by Manticore
482
+ # this spec is a safeguard to trigger an assessment of thread-safety should
483
+ # we choose a different transport adapter in the future.
484
+ transport_class = extract_transport(client).options.fetch(:transport_class)
485
+ expect(transport_class).to equal ::Elasticsearch::Transport::Transport::HTTP::Manticore
486
+ end
487
+
488
+ it 'uses a client with sufficient connection pool size' do
489
+ client = plugin.send(:get_client).client
490
+ transport_options = extract_transport(client).options.fetch(:transport_options)
491
+ # pool_max and pool_max_per_route are manticore-specific transport options
492
+ expect(transport_options).to include(:pool_max => 1000, :pool_max_per_route => 100)
493
+ end
494
+
495
+ it 'uses a single shared client across threads' do
496
+ q = Queue.new
497
+ 10.times.map do
498
+ Thread.new(plugin) { |instance| q.push instance.send(:get_client) }
499
+ end.map(&:join)
500
+
501
+ first = q.pop
502
+ expect(q.pop).to be(first) until q.empty?
503
+ end
504
+
469
505
  describe "cloud.id" do
470
506
  let(:valid_cloud_id) do
471
507
  'sample:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA=='
@@ -608,6 +644,47 @@ describe LogStash::Filters::Elasticsearch do
608
644
  end
609
645
  end
610
646
 
647
+ describe "Elastic Api Header" do
648
+ let(:config) { {"query" => "*"} }
649
+ let(:plugin) { described_class.new(config) }
650
+ let(:headers) {{'x-elastic-product' => 'Elasticsearch'}}
651
+ let(:cluster_info) { {"version" => {"number" => "8.10.0", "build_flavor" => build_flavor}, "tagline" => "You Know, for Search"} }
652
+ let(:mock_resp) { MockResponse.new(200, cluster_info, headers) }
653
+
654
+ before do
655
+ expect(plugin).to receive(:test_connection!)
656
+ end
657
+
658
+ context "serverless" do
659
+ let(:build_flavor) { "serverless" }
660
+
661
+ before do
662
+ allow_any_instance_of(Elasticsearch::Client).to receive(:perform_request).with(any_args).and_return(mock_resp)
663
+ end
664
+
665
+ it 'propagates header to es client' do
666
+ plugin.register
667
+ client = plugin.send(:get_client).client
668
+ expect( extract_transport(client).options[:transport_options][:headers] ).to match hash_including("Elastic-Api-Version" => "2023-10-31")
669
+ end
670
+ end
671
+
672
+ context "stateful" do
673
+ let(:build_flavor) { "default" }
674
+
675
+ before do
676
+ expect_any_instance_of(Elasticsearch::Client).to receive(:perform_request).with(any_args).and_return(mock_resp)
677
+ end
678
+
679
+ it 'does not propagate header to es client' do
680
+ plugin.register
681
+ client = plugin.send(:get_client).client
682
+ expect( extract_transport(client).options[:transport_options][:headers] ).to match hash_not_including("Elastic-Api-Version" => "2023-10-31")
683
+ end
684
+ end
685
+
686
+ end
687
+
611
688
  describe "ca_trusted_fingerprint" do
612
689
  let(:ca_trusted_fingerprint) { SecureRandom.hex(32) }
613
690
  let(:config) { {"ssl_enabled" => true, "ca_trusted_fingerprint" => ca_trusted_fingerprint, "query" => "*"}}
@@ -616,7 +693,10 @@ describe LogStash::Filters::Elasticsearch do
616
693
 
617
694
  if Gem::Version.create(LOGSTASH_VERSION) >= Gem::Version.create("8.3.0")
618
695
  context 'the generated trust_strategy' do
619
- before(:each) { allow(plugin).to receive(:test_connection!) }
696
+ before(:each) do
697
+ allow(plugin).to receive(:test_connection!)
698
+ allow(plugin).to receive(:setup_serverless)
699
+ end
620
700
 
621
701
  it 'is passed to the Manticore client' do
622
702
  expect(Manticore::Client).to receive(:new)
@@ -655,7 +735,10 @@ describe LogStash::Filters::Elasticsearch do
655
735
 
656
736
  subject(:plugin) { described_class.new(config) }
657
737
 
658
- before(:each) { allow(plugin).to receive(:test_connection!) }
738
+ before(:each) do
739
+ allow(plugin).to receive(:test_connection!)
740
+ allow(plugin).to receive(:setup_serverless)
741
+ end
659
742
 
660
743
  it 'is passed to the Manticore client' do
661
744
  expect(Manticore::Client).to receive(:new)
@@ -683,7 +766,10 @@ describe LogStash::Filters::Elasticsearch do
683
766
  let(:config) { {"query" => "*"} }
684
767
  let(:plugin) { described_class.new(config) }
685
768
 
686
- before { allow(plugin).to receive(:test_connection!) }
769
+ before do
770
+ allow(plugin).to receive(:test_connection!)
771
+ allow(plugin).to receive(:setup_serverless)
772
+ end
687
773
 
688
774
  it "should set localhost:9200 as hosts" do
689
775
  plugin.register
@@ -708,13 +794,14 @@ describe LogStash::Filters::Elasticsearch do
708
794
  before(:each) do
709
795
  allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
710
796
  allow(plugin).to receive(:test_connection!)
797
+ allow(plugin).to receive(:setup_serverless)
711
798
  plugin.register
712
799
  end
713
800
 
714
801
  it "should read and send non-ascii query" do
715
- expect(client).to receive(:search).with(
802
+ expect(client).to receive(:search).with({
716
803
  :body => { "query" => { "terms" => { "lock" => [ "잠금", "uzávěr" ] } } },
717
- :index => "")
804
+ :index => ""})
718
805
 
719
806
  plugin.filter(LogStash::Event.new)
720
807
  end
@@ -725,4 +812,21 @@ describe LogStash::Filters::Elasticsearch do
725
812
  client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
726
813
  end
727
814
 
815
+ class MockResponse
816
+ attr_reader :code, :headers
817
+
818
+ def initialize(code = 200, body = nil, headers = {})
819
+ @code = code
820
+ @body = body
821
+ @headers = headers
822
+ end
823
+
824
+ def body
825
+ @body
826
+ end
827
+
828
+ def status
829
+ @code
830
+ end
831
+ end
728
832
  end
@@ -15,6 +15,8 @@ describe "SSL options" do
15
15
  before do
16
16
  allow(es_client_double).to receive(:close)
17
17
  allow(es_client_double).to receive(:ping).with(any_args).and_return(double("pong").as_null_object)
18
+ allow(es_client_double).to receive(:info).with(any_args).and_return({"version" => {"number" => "7.5.0", "build_flavor" => "default"},
19
+ "tagline" => "You Know, for Search"})
18
20
  allow(Elasticsearch::Client).to receive(:new).and_return(es_client_double)
19
21
  end
20
22
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.15.2
4
+ version: 3.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-24 00:00:00.000000000 Z
11
+ date: 2023-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -35,7 +35,7 @@ dependencies:
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 7.14.0
38
+ version: 7.14.9
39
39
  name: elasticsearch
40
40
  prerelease: false
41
41
  type: :runtime
@@ -43,7 +43,7 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 7.14.0
46
+ version: 7.14.9
47
47
  - !ruby/object:Gem::Dependency
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements: