logstash-input-elasticsearch 4.21.2 → 4.23.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.
@@ -1152,7 +1152,7 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
1152
1152
 
1153
1153
  context "when there's an exception" do
1154
1154
  before(:each) do
1155
- allow(client).to receive(:search).and_raise RuntimeError
1155
+ allow(client).to receive(:search).and_raise RuntimeError.new("test exception")
1156
1156
  end
1157
1157
  it 'produces no events' do
1158
1158
  plugin.run queue
@@ -1297,6 +1297,10 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
1297
1297
 
1298
1298
  let(:mock_queue) { double('queue', :<< => nil) }
1299
1299
 
1300
+ before(:each) do
1301
+ plugin.send(:setup_cursor_tracker)
1302
+ end
1303
+
1300
1304
  it 'pushes a generated event to the queue' do
1301
1305
  plugin.send(:push_hit, hit, mock_queue)
1302
1306
  expect(mock_queue).to have_received(:<<) do |event|
@@ -1353,4 +1357,129 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
1353
1357
  client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
1354
1358
  end
1355
1359
 
1360
+ describe "#ESQL" do
1361
+ let(:config) do
1362
+ {
1363
+ "query" => "FROM test-index | STATS count() BY field",
1364
+ "query_type" => "esql",
1365
+ "retries" => 3
1366
+ }
1367
+ end
1368
+ let(:es_version) { LogStash::Inputs::Elasticsearch::ES_ESQL_SUPPORT_VERSION }
1369
+ let(:ls_version) { LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION }
1370
+
1371
+ before(:each) do
1372
+ stub_const("LOGSTASH_VERSION", ls_version)
1373
+ end
1374
+
1375
+ describe "#initialize" do
1376
+ it "sets up the ESQL client with correct parameters" do
1377
+ expect(plugin.instance_variable_get(:@query_type)).to eq(config["query_type"])
1378
+ expect(plugin.instance_variable_get(:@query)).to eq(config["query"])
1379
+ expect(plugin.instance_variable_get(:@retries)).to eq(config["retries"])
1380
+ end
1381
+ end
1382
+
1383
+ describe "#register" do
1384
+ before(:each) do
1385
+ Elasticsearch::Client.send(:define_method, :ping) { }
1386
+ allow_any_instance_of(Elasticsearch::Client).to receive(:info).and_return(cluster_info)
1387
+ end
1388
+ it "creates ES|QL executor" do
1389
+ plugin.register
1390
+ expect(plugin.instance_variable_get(:@query_executor)).to be_an_instance_of(LogStash::Inputs::Elasticsearch::Esql)
1391
+ end
1392
+ end
1393
+
1394
+ describe "#validation" do
1395
+
1396
+ describe "LS version" do
1397
+ context "when compatible" do
1398
+
1399
+ it "does not raise an error" do
1400
+ expect { plugin.send(:validate_ls_version_for_esql_support!) }.not_to raise_error
1401
+ end
1402
+ end
1403
+
1404
+ context "when incompatible" do
1405
+ before(:each) do
1406
+ stub_const("LOGSTASH_VERSION", "8.10.0")
1407
+ end
1408
+
1409
+ it "raises a runtime error" do
1410
+ expect { plugin.send(:validate_ls_version_for_esql_support!) }
1411
+ .to raise_error(RuntimeError, /Current version of Logstash does not include Elasticsearch client which supports ES|QL. Please upgrade Logstash to at least #{ls_version}/)
1412
+ end
1413
+ end
1414
+ end
1415
+
1416
+ describe "ES version" do
1417
+ before(:each) do
1418
+ allow(plugin).to receive(:es_version).and_return("8.10.5")
1419
+ end
1420
+
1421
+ context "when incompatible" do
1422
+ it "raises a runtime error" do
1423
+ expect { plugin.send(:validate_es_for_esql_support!) }
1424
+ .to raise_error(RuntimeError, /Connected Elasticsearch 8.10.5 version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{es_version} version./)
1425
+ end
1426
+ end
1427
+ end
1428
+
1429
+ context "ES|QL query and DSL params used together" do
1430
+ let(:config) {
1431
+ super().merge({
1432
+ "index" => "my-index",
1433
+ "size" => 1,
1434
+ "slices" => 1,
1435
+ "search_api" => "auto",
1436
+ "docinfo" => true,
1437
+ "docinfo_target" => "[@metadata][docinfo]",
1438
+ "docinfo_fields" => ["_index"],
1439
+ "response_type" => "hits",
1440
+ "tracking_field" => "[@metadata][tracking]"
1441
+ })}
1442
+
1443
+ it "raises a config error" do
1444
+ mixed_fields = %w[index size slices docinfo_fields response_type tracking_field]
1445
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError, /Configured #{mixed_fields} params are not allowed while using ES|QL query/)
1446
+ end
1447
+ end
1448
+
1449
+ describe "ES|QL query" do
1450
+ context "when query is valid" do
1451
+ it "does not raise an error" do
1452
+ expect { plugin.send(:validate_esql_query!) }.not_to raise_error
1453
+ end
1454
+ end
1455
+
1456
+ context "when query is empty" do
1457
+ let(:config) do
1458
+ {
1459
+ "query" => " "
1460
+ }
1461
+ end
1462
+
1463
+ it "raises a configuration error" do
1464
+ expect { plugin.send(:validate_esql_query!) }
1465
+ .to raise_error(LogStash::ConfigurationError, /`query` cannot be empty/)
1466
+ end
1467
+ end
1468
+
1469
+ context "when query doesn't align with ES syntax" do
1470
+ let(:config) do
1471
+ {
1472
+ "query" => "RANDOM query"
1473
+ }
1474
+ end
1475
+
1476
+ it "raises a configuration error" do
1477
+ source_commands = %w[FROM ROW SHOW]
1478
+ expect { plugin.send(:validate_esql_query!) }
1479
+ .to raise_error(LogStash::ConfigurationError, "`query` needs to start with any of #{source_commands}")
1480
+ end
1481
+ end
1482
+ end
1483
+ end
1484
+ end
1356
1485
  end
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/inputs/elasticsearch"
4
+ require "elasticsearch"
5
+ require_relative "../../../spec/es_helper"
6
+
7
+ describe LogStash::Inputs::Elasticsearch, integration: true do
8
+
9
+ SECURE_INTEGRATION = ENV['SECURE_INTEGRATION'].eql? 'true'
10
+ ES_HOSTS = ["http#{SECURE_INTEGRATION ? 's' : nil}://#{ESHelper.get_host_port}"]
11
+
12
+ let(:plugin) { described_class.new(config) }
13
+ let(:es_index) { "logstash-esql-integration-#{rand(1000)}" }
14
+ let(:test_documents) do
15
+ [
16
+ { "message" => "test message 1", "type" => "a", "count" => 1 },
17
+ { "message" => "test message 2", "type" => "a", "count" => 2 },
18
+ { "message" => "test message 3", "type" => "b", "count" => 3 },
19
+ { "message" => "test message 4", "type" => "b", "count" => 4 },
20
+ { "message" => "test message 5", "type" => "c", "count" => 5 }
21
+ ]
22
+ end
23
+ let(:config) do
24
+ {
25
+ "hosts" => ES_HOSTS,
26
+ "query_type" => "esql"
27
+ }
28
+ end
29
+ let(:es_client) do
30
+ Elasticsearch::Client.new(hosts: ES_HOSTS)
31
+ end
32
+
33
+ before(:all) do
34
+ is_ls_with_esql_supported_client = Gem::Version.create(LOGSTASH_VERSION) >= Gem::Version.create(LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
35
+ skip "LS version does not have ES client which supports ES|QL" unless is_ls_with_esql_supported_client
36
+
37
+ # Skip tests if ES version doesn't support ES||QL
38
+ es_client = Elasticsearch::Client.new(hosts: ES_HOSTS) # need to separately create since let isn't allowed in before(:context)
39
+ es_version_info = es_client.info["version"]
40
+ es_gem_version = Gem::Version.create(es_version_info["number"])
41
+ skip "ES version does not support ES|QL" if es_gem_version.nil? || es_gem_version < Gem::Version.create(LogStash::Inputs::Elasticsearch::ES_ESQL_SUPPORT_VERSION)
42
+ end
43
+
44
+ before(:each) do
45
+ # Create index with test documents
46
+ es_client.indices.create(index: es_index, body: {}) unless es_client.indices.exists?(index: es_index)
47
+
48
+ test_documents.each do |doc|
49
+ es_client.index(index: es_index, body: doc, refresh: true)
50
+ end
51
+ end
52
+
53
+ after(:each) do
54
+ es_client.indices.delete(index: es_index) if es_client.indices.exists?(index: es_index)
55
+ end
56
+
57
+ context "#run ES|QL queries" do
58
+
59
+ before do
60
+ stub_const("LOGSTASH_VERSION", LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
61
+ allow_any_instance_of(LogStash::Inputs::Elasticsearch).to receive(:exit_plugin?).and_return false, true
62
+ end
63
+
64
+ before(:each) do
65
+ plugin.register
66
+ end
67
+
68
+ shared_examples "ESQL query execution" do |expected_count|
69
+ it "correctly retrieves documents" do
70
+ queue = Queue.new
71
+ plugin.run(queue)
72
+
73
+ event_count = 0
74
+ expected_count.times do |i|
75
+ event = queue.pop
76
+ expect(event).to be_a(LogStash::Event)
77
+ event_count += 1
78
+ end
79
+ expect(event_count).to eq(expected_count)
80
+ end
81
+ end
82
+
83
+ context "#FROM query" do
84
+ let(:config) do
85
+ super().merge("query" => "FROM #{es_index} | SORT count")
86
+ end
87
+
88
+ include_examples "ESQL query execution", 5
89
+ end
90
+
91
+ context "#FROM query and WHERE clause" do
92
+ let(:config) do
93
+ super().merge("query" => "FROM #{es_index} | WHERE type == \"a\" | SORT count")
94
+ end
95
+
96
+ include_examples "ESQL query execution", 2
97
+ end
98
+
99
+ context "#STATS aggregation" do
100
+ let(:config) do
101
+ super().merge("query" => "FROM #{es_index} | STATS avg(count) BY type")
102
+ end
103
+
104
+ it "retrieves aggregated stats" do
105
+ queue = Queue.new
106
+ plugin.run(queue)
107
+ results = []
108
+ 3.times do
109
+ event = queue.pop
110
+ expect(event).to be_a(LogStash::Event)
111
+ results << event.get("avg(count)")
112
+ end
113
+
114
+ expected_averages = [1.5, 3.5, 5.0]
115
+ expect(results.sort).to eq(expected_averages)
116
+ end
117
+ end
118
+
119
+ context "#METADATA" do
120
+ let(:config) do
121
+ super().merge("query" => "FROM #{es_index} METADATA _index, _id, _version | DROP message.keyword, type.keyword | SORT count")
122
+ end
123
+
124
+ it "includes document metadata" do
125
+ queue = Queue.new
126
+ plugin.run(queue)
127
+
128
+ 5.times do
129
+ event = queue.pop
130
+ expect(event).to be_a(LogStash::Event)
131
+ expect(event.get("_index")).not_to be_nil
132
+ expect(event.get("_id")).not_to be_nil
133
+ expect(event.get("_version")).not_to be_nil
134
+ end
135
+ end
136
+ end
137
+
138
+ context "#invalid ES|QL query" do
139
+ let(:config) do
140
+ super().merge("query" => "FROM undefined index | LIMIT 1")
141
+ end
142
+
143
+ it "doesn't produce events" do
144
+ queue = Queue.new
145
+ plugin.run(queue)
146
+ expect(queue.empty?).to eq(true)
147
+ end
148
+ end
149
+ end
150
+ end
@@ -76,6 +76,14 @@ describe LogStash::Inputs::Elasticsearch do
76
76
  shared_examples 'secured_elasticsearch' do
77
77
  it_behaves_like 'an elasticsearch index plugin'
78
78
 
79
+ let(:unauth_exception_class) do
80
+ begin
81
+ Elasticsearch::Transport::Transport::Errors::Unauthorized
82
+ rescue
83
+ Elastic::Transport::Transport::Errors::Unauthorized
84
+ end
85
+ end
86
+
79
87
  context "incorrect auth credentials" do
80
88
 
81
89
  let(:config) do
@@ -85,7 +93,7 @@ describe LogStash::Inputs::Elasticsearch do
85
93
  let(:queue) { [] }
86
94
 
87
95
  it "fails to run the plugin" do
88
- expect { plugin.register }.to raise_error Elasticsearch::Transport::Transport::Errors::Unauthorized
96
+ expect { plugin.register }.to raise_error unauth_exception_class
89
97
  end
90
98
  end
91
99
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.21.2
4
+ version: 4.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-17 00:00:00.000000000 Z
11
+ date: 2025-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -278,6 +278,8 @@ files:
278
278
  - lib/logstash/helpers/loggable_try.rb
279
279
  - lib/logstash/inputs/elasticsearch.rb
280
280
  - lib/logstash/inputs/elasticsearch/aggregation.rb
281
+ - lib/logstash/inputs/elasticsearch/cursor_tracker.rb
282
+ - lib/logstash/inputs/elasticsearch/esql.rb
281
283
  - lib/logstash/inputs/elasticsearch/paginated_search.rb
282
284
  - lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_connections_selector.rb
283
285
  - lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_http_manticore.rb
@@ -291,8 +293,11 @@ files:
291
293
  - spec/fixtures/test_certs/es.crt
292
294
  - spec/fixtures/test_certs/es.key
293
295
  - spec/fixtures/test_certs/renew.sh
296
+ - spec/inputs/cursor_tracker_spec.rb
297
+ - spec/inputs/elasticsearch_esql_spec.rb
294
298
  - spec/inputs/elasticsearch_spec.rb
295
299
  - spec/inputs/elasticsearch_ssl_spec.rb
300
+ - spec/inputs/integration/elasticsearch_esql_spec.rb
296
301
  - spec/inputs/integration/elasticsearch_spec.rb
297
302
  - spec/inputs/paginated_search_spec.rb
298
303
  homepage: https://elastic.co/logstash
@@ -330,7 +335,10 @@ test_files:
330
335
  - spec/fixtures/test_certs/es.crt
331
336
  - spec/fixtures/test_certs/es.key
332
337
  - spec/fixtures/test_certs/renew.sh
338
+ - spec/inputs/cursor_tracker_spec.rb
339
+ - spec/inputs/elasticsearch_esql_spec.rb
333
340
  - spec/inputs/elasticsearch_spec.rb
334
341
  - spec/inputs/elasticsearch_ssl_spec.rb
342
+ - spec/inputs/integration/elasticsearch_esql_spec.rb
335
343
  - spec/inputs/integration/elasticsearch_spec.rb
336
344
  - spec/inputs/paginated_search_spec.rb