logstash-filter-elasticsearch 3.18.0 → 3.19.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 +4 -4
- data/CHANGELOG.md +3 -0
- data/docs/index.asciidoc +132 -6
- data/lib/logstash/filters/elasticsearch/client.rb +8 -0
- data/lib/logstash/filters/elasticsearch/dsl_executor.rb +140 -0
- data/lib/logstash/filters/elasticsearch/esql_executor.rb +178 -0
- data/lib/logstash/filters/elasticsearch.rb +106 -129
- data/logstash-filter-elasticsearch.gemspec +1 -1
- data/spec/filters/elasticsearch_dsl_spec.rb +372 -0
- data/spec/filters/elasticsearch_esql_spec.rb +211 -0
- data/spec/filters/elasticsearch_spec.rb +129 -326
- data/spec/filters/integration/elasticsearch_esql_spec.rb +167 -0
- metadata +10 -2
@@ -61,7 +61,7 @@ describe LogStash::Filters::Elasticsearch do
|
|
61
61
|
allow(filter_client).to receive(:serverless?).and_return(true)
|
62
62
|
allow(filter_client).to receive(:client).and_return(es_client)
|
63
63
|
|
64
|
-
if
|
64
|
+
if defined?(Elastic::Transport)
|
65
65
|
allow(es_client).to receive(:info)
|
66
66
|
.with(a_hash_including(
|
67
67
|
:headers => LogStash::Filters::ElasticsearchClient::DEFAULT_EAV_HEADER))
|
@@ -93,306 +93,6 @@ describe LogStash::Filters::Elasticsearch do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
describe "data fetch" do
|
97
|
-
let(:config) do
|
98
|
-
{
|
99
|
-
"hosts" => ["localhost:9200"],
|
100
|
-
"query" => "response: 404",
|
101
|
-
"fields" => { "response" => "code" },
|
102
|
-
"docinfo_fields" => { "_index" => "es_index" },
|
103
|
-
"aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
|
104
|
-
}
|
105
|
-
end
|
106
|
-
|
107
|
-
let(:response) do
|
108
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
|
109
|
-
end
|
110
|
-
|
111
|
-
let(:client) { double(:client) }
|
112
|
-
|
113
|
-
before(:each) do
|
114
|
-
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
115
|
-
if elastic_ruby_v8_client_available?
|
116
|
-
allow(client).to receive(:es_transport_client_type).and_return('elastic_transport')
|
117
|
-
else
|
118
|
-
allow(client).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
|
119
|
-
end
|
120
|
-
allow(client).to receive(:search).and_return(response)
|
121
|
-
allow(plugin).to receive(:test_connection!)
|
122
|
-
allow(plugin).to receive(:setup_serverless)
|
123
|
-
plugin.register
|
124
|
-
end
|
125
|
-
|
126
|
-
after(:each) do
|
127
|
-
Thread.current[:filter_elasticsearch_client] = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
it "should enhance the current event with new data" do
|
131
|
-
plugin.filter(event)
|
132
|
-
expect(event.get("code")).to eq(404)
|
133
|
-
expect(event.get("es_index")).to eq("logstash-2014.08.26")
|
134
|
-
expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
|
135
|
-
end
|
136
|
-
|
137
|
-
it "should receive all necessary params to perform the search" do
|
138
|
-
expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"", :sort=>"@timestamp:desc"})
|
139
|
-
plugin.filter(event)
|
140
|
-
end
|
141
|
-
|
142
|
-
context "when asking to hit specific index" do
|
143
|
-
|
144
|
-
let(:config) do
|
145
|
-
{
|
146
|
-
"index" => "foo*",
|
147
|
-
"hosts" => ["localhost:9200"],
|
148
|
-
"query" => "response: 404",
|
149
|
-
"fields" => { "response" => "code" }
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
it "should receive all necessary params to perform the search" do
|
154
|
-
expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"foo*", :sort=>"@timestamp:desc"})
|
155
|
-
plugin.filter(event)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
context "when asking for more than one result" do
|
160
|
-
|
161
|
-
let(:config) do
|
162
|
-
{
|
163
|
-
"hosts" => ["localhost:9200"],
|
164
|
-
"query" => "response: 404",
|
165
|
-
"fields" => { "response" => "code" },
|
166
|
-
"result_size" => 10
|
167
|
-
}
|
168
|
-
end
|
169
|
-
|
170
|
-
let(:response) do
|
171
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
|
172
|
-
end
|
173
|
-
|
174
|
-
it "should enhance the current event with new data" do
|
175
|
-
plugin.filter(event)
|
176
|
-
expect(event.get("code")).to eq([404]*10)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
context 'when Elasticsearch 7.x gives us a totals object instead of an integer' do
|
181
|
-
let(:config) do
|
182
|
-
{
|
183
|
-
"hosts" => ["localhost:9200"],
|
184
|
-
"query" => "response: 404",
|
185
|
-
"fields" => { "response" => "code" },
|
186
|
-
"result_size" => 10
|
187
|
-
}
|
188
|
-
end
|
189
|
-
|
190
|
-
let(:response) do
|
191
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "elasticsearch_7.x_hits_total_as_object.json")))
|
192
|
-
end
|
193
|
-
|
194
|
-
it "should enhance the current event with new data" do
|
195
|
-
plugin.filter(event)
|
196
|
-
expect(event.get("[@metadata][total_hits]")).to eq(13476)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
context "if something wrong happen during connection" do
|
201
|
-
|
202
|
-
before(:each) do
|
203
|
-
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
204
|
-
allow(client).to receive(:search).and_raise("connection exception")
|
205
|
-
plugin.register
|
206
|
-
end
|
207
|
-
|
208
|
-
it "tag the event as something happened, but still deliver it" do
|
209
|
-
expect(plugin.logger).to receive(:warn)
|
210
|
-
plugin.filter(event)
|
211
|
-
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Tagging test for positive results
|
216
|
-
context "Tagging should occur if query returns results" do
|
217
|
-
let(:config) do
|
218
|
-
{
|
219
|
-
"index" => "foo*",
|
220
|
-
"hosts" => ["localhost:9200"],
|
221
|
-
"query" => "response: 404",
|
222
|
-
"add_tag" => ["tagged"]
|
223
|
-
}
|
224
|
-
end
|
225
|
-
|
226
|
-
let(:response) do
|
227
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
|
228
|
-
end
|
229
|
-
|
230
|
-
it "should tag the current event if results returned" do
|
231
|
-
plugin.filter(event)
|
232
|
-
expect(event.to_hash["tags"]).to include("tagged")
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
context "an aggregation search with size 0 that matches" do
|
237
|
-
let(:config) do
|
238
|
-
{
|
239
|
-
"index" => "foo*",
|
240
|
-
"hosts" => ["localhost:9200"],
|
241
|
-
"query" => "response: 404",
|
242
|
-
"add_tag" => ["tagged"],
|
243
|
-
"result_size" => 0,
|
244
|
-
"aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
|
245
|
-
}
|
246
|
-
end
|
247
|
-
|
248
|
-
let(:response) do
|
249
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_size0_agg.json")))
|
250
|
-
end
|
251
|
-
|
252
|
-
it "should tag the current event" do
|
253
|
-
plugin.filter(event)
|
254
|
-
expect(event.get("tags")).to include("tagged")
|
255
|
-
expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
# Tagging test for negative results
|
260
|
-
context "Tagging should not occur if query has no results" do
|
261
|
-
let(:config) do
|
262
|
-
{
|
263
|
-
"index" => "foo*",
|
264
|
-
"hosts" => ["localhost:9200"],
|
265
|
-
"query" => "response: 404",
|
266
|
-
"add_tag" => ["tagged"]
|
267
|
-
}
|
268
|
-
end
|
269
|
-
|
270
|
-
let(:response) do
|
271
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
|
272
|
-
end
|
273
|
-
|
274
|
-
it "should not tag the current event" do
|
275
|
-
plugin.filter(event)
|
276
|
-
expect(event.to_hash["tags"]).to_not include("tagged")
|
277
|
-
end
|
278
|
-
end
|
279
|
-
context "testing a simple query template" do
|
280
|
-
let(:config) do
|
281
|
-
{
|
282
|
-
"hosts" => ["localhost:9200"],
|
283
|
-
"query_template" => File.join(File.dirname(__FILE__), "fixtures", "query_template.json"),
|
284
|
-
"fields" => { "response" => "code" },
|
285
|
-
"result_size" => 1
|
286
|
-
}
|
287
|
-
end
|
288
|
-
|
289
|
-
let(:response) do
|
290
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
|
291
|
-
end
|
292
|
-
|
293
|
-
it "should enhance the current event with new data" do
|
294
|
-
plugin.filter(event)
|
295
|
-
expect(event.get("code")).to eq(404)
|
296
|
-
end
|
297
|
-
|
298
|
-
end
|
299
|
-
|
300
|
-
context "testing a simple index substitution" do
|
301
|
-
let(:event) {
|
302
|
-
LogStash::Event.new(
|
303
|
-
{
|
304
|
-
"subst_field" => "subst_value"
|
305
|
-
}
|
306
|
-
)
|
307
|
-
}
|
308
|
-
let(:config) do
|
309
|
-
{
|
310
|
-
"index" => "foo_%{subst_field}*",
|
311
|
-
"hosts" => ["localhost:9200"],
|
312
|
-
"query" => "response: 404",
|
313
|
-
"fields" => { "response" => "code" }
|
314
|
-
}
|
315
|
-
end
|
316
|
-
|
317
|
-
it "should receive substituted index name" do
|
318
|
-
expect(client).to receive(:search).with({:q => "response: 404", :size => 1, :index => "foo_subst_value*", :sort => "@timestamp:desc"})
|
319
|
-
plugin.filter(event)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
context "if query result errored but no exception is thrown" do
|
324
|
-
let(:response) do
|
325
|
-
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
|
326
|
-
end
|
327
|
-
|
328
|
-
before(:each) do
|
329
|
-
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
330
|
-
allow(client).to receive(:search).and_return(response)
|
331
|
-
plugin.register
|
332
|
-
end
|
333
|
-
|
334
|
-
it "tag the event as something happened, but still deliver it" do
|
335
|
-
expect(plugin.logger).to receive(:warn)
|
336
|
-
plugin.filter(event)
|
337
|
-
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
context 'with client-level retries' do
|
342
|
-
let(:config) do
|
343
|
-
super().merge(
|
344
|
-
"retry_on_failure" => 3,
|
345
|
-
"retry_on_status" => [500]
|
346
|
-
)
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
context "with custom headers" do
|
351
|
-
let(:config) do
|
352
|
-
{
|
353
|
-
"query" => "*",
|
354
|
-
"custom_headers" => { "Custom-Header-1" => "Custom Value 1", "Custom-Header-2" => "Custom Value 2" }
|
355
|
-
}
|
356
|
-
end
|
357
|
-
|
358
|
-
let(:plugin) { LogStash::Filters::Elasticsearch.new(config) }
|
359
|
-
let(:client_double) { double("client") }
|
360
|
-
let(:transport_double) { double("transport", options: { transport_options: { headers: config["custom_headers"] } }) }
|
361
|
-
|
362
|
-
before do
|
363
|
-
allow(plugin).to receive(:get_client).and_return(client_double)
|
364
|
-
if elastic_ruby_v8_client_available?
|
365
|
-
allow(client_double).to receive(:es_transport_client_type).and_return('elastic_transport')
|
366
|
-
else
|
367
|
-
allow(client_double).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
|
368
|
-
end
|
369
|
-
allow(client_double).to receive(:client).and_return(transport_double)
|
370
|
-
end
|
371
|
-
|
372
|
-
it "sets custom headers" do
|
373
|
-
plugin.register
|
374
|
-
client = plugin.send(:get_client).client
|
375
|
-
expect(client.options[:transport_options][:headers]).to match(hash_including(config["custom_headers"]))
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
context "if query is on nested field" do
|
380
|
-
let(:config) do
|
381
|
-
{
|
382
|
-
"hosts" => ["localhost:9200"],
|
383
|
-
"query" => "response: 404",
|
384
|
-
"fields" => [ ["[geoip][ip]", "ip_address"] ]
|
385
|
-
}
|
386
|
-
end
|
387
|
-
|
388
|
-
it "should enhance the current event with new data" do
|
389
|
-
plugin.filter(event)
|
390
|
-
expect(event.get("ip_address")).to eq("66.249.73.185")
|
391
|
-
end
|
392
|
-
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
96
|
class StoppableServer
|
397
97
|
|
398
98
|
attr_reader :port
|
@@ -525,7 +225,7 @@ describe LogStash::Filters::Elasticsearch do
|
|
525
225
|
# this spec is a safeguard to trigger an assessment of thread-safety should
|
526
226
|
# we choose a different transport adapter in the future.
|
527
227
|
transport_class = extract_transport(client).options.fetch(:transport_class)
|
528
|
-
if
|
228
|
+
if defined?(Elastic::Transport)
|
529
229
|
allow(client).to receive(:es_transport_client_type).and_return("elastic_transport")
|
530
230
|
expect(transport_class).to equal ::Elastic::Transport::Transport::HTTP::Manticore
|
531
231
|
else
|
@@ -845,7 +545,7 @@ describe LogStash::Filters::Elasticsearch do
|
|
845
545
|
|
846
546
|
before(:each) do
|
847
547
|
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
848
|
-
if
|
548
|
+
if defined?(Elastic::Transport)
|
849
549
|
allow(client).to receive(:es_transport_client_type).and_return('elastic_transport')
|
850
550
|
else
|
851
551
|
allow(client).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
|
@@ -864,31 +564,141 @@ describe LogStash::Filters::Elasticsearch do
|
|
864
564
|
end
|
865
565
|
end
|
866
566
|
|
867
|
-
describe "
|
567
|
+
describe "ES|QL" do
|
868
568
|
|
869
|
-
|
870
|
-
let(:config) {{ }}
|
569
|
+
describe "compatibility" do
|
570
|
+
let(:config) {{ "hosts" => ["localhost:9200"], "query_type" => "esql", "query" => "FROM my-index" }}
|
871
571
|
|
872
|
-
|
873
|
-
|
874
|
-
|
572
|
+
context "when LS doesn't support ES|QL" do
|
573
|
+
let(:ls_version) { LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION }
|
574
|
+
before(:each) do
|
575
|
+
stub_const("LOGSTASH_VERSION", "8.17.0")
|
576
|
+
end
|
577
|
+
|
578
|
+
it "raises a runtime error" do
|
579
|
+
expect { plugin.send(:validate_ls_version_for_esql_support!) }
|
580
|
+
.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}/)
|
581
|
+
end
|
875
582
|
end
|
876
|
-
end
|
877
583
|
|
878
|
-
|
879
|
-
|
584
|
+
context "when ES doesn't support ES|QL" do
|
585
|
+
let(:es_version) { LogStash::Filters::Elasticsearch::ES_ESQL_SUPPORT_VERSION }
|
586
|
+
let(:client) { double(:client) }
|
587
|
+
|
588
|
+
it "raises a runtime error" do
|
589
|
+
allow(plugin).to receive(:get_client).twice.and_return(client)
|
590
|
+
allow(client).to receive(:es_version).and_return("8.8.0")
|
880
591
|
|
881
|
-
|
882
|
-
|
883
|
-
|
592
|
+
expect { plugin.send(:validate_es_for_esql_support!) }
|
593
|
+
.to raise_error(RuntimeError, /Connected Elasticsearch 8.8.0 version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{es_version} version./)
|
594
|
+
end
|
884
595
|
end
|
596
|
+
end
|
885
597
|
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
598
|
+
context "when non-ES|QL params applied" do
|
599
|
+
let(:config) do
|
600
|
+
{
|
601
|
+
"hosts" => ["localhost:9200"],
|
602
|
+
"query_type" => "esql",
|
603
|
+
"query" => "FROM my-index",
|
604
|
+
"index" => "some-index",
|
605
|
+
"docinfo_fields" => { "_index" => "es_index" },
|
606
|
+
"sort" => "@timestamp:desc",
|
607
|
+
"enable_sort" => true,
|
608
|
+
"aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
|
609
|
+
}
|
610
|
+
end
|
611
|
+
it "raises a config error" do
|
612
|
+
invalid_params_with_esql = %w(index docinfo_fields sort enable_sort aggregation_fields)
|
613
|
+
error_text = /Configured #{invalid_params_with_esql} params cannot be used with ES|QL query/i
|
614
|
+
expect { plugin.register }.to raise_error LogStash::ConfigurationError, error_text
|
890
615
|
end
|
891
616
|
end
|
617
|
+
|
618
|
+
describe "#query placeholder" do
|
619
|
+
let(:config) do
|
620
|
+
{
|
621
|
+
"hosts" => ["localhost:9200"],
|
622
|
+
"query_type" => "esql"
|
623
|
+
}
|
624
|
+
end
|
625
|
+
|
626
|
+
context "when query placeholder doesn't exist in the query" do
|
627
|
+
let(:config) {
|
628
|
+
super()
|
629
|
+
.merge(
|
630
|
+
{
|
631
|
+
"query" => "FROM my-index",
|
632
|
+
"query_params" => { "a" => "b" },
|
633
|
+
})
|
634
|
+
}
|
635
|
+
|
636
|
+
it "doesn't complain since not used" do
|
637
|
+
expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
context "when illegal placeholders appear" do
|
642
|
+
let(:config) {
|
643
|
+
super()
|
644
|
+
.merge(
|
645
|
+
{
|
646
|
+
"query" => "FROM my-index | WHERE type = ?type",
|
647
|
+
"query_params" => { "1abcd_efg1" => "1", "$abcd_efg1" => 2, "type" => 3 },
|
648
|
+
})
|
649
|
+
}
|
650
|
+
it "raises a config error" do
|
651
|
+
message = 'Illegal ["1abcd_efg1", "$abcd_efg1"] placeholder names in `query_params`. A valid parameter name starts with a letter and contains letters, digits and underscores only;'
|
652
|
+
expect { plugin.register }.to raise_error LogStash::ConfigurationError, message
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
context "when query placeholders and `query_params` do not match" do
|
657
|
+
let(:config) {
|
658
|
+
super()
|
659
|
+
.merge(
|
660
|
+
{
|
661
|
+
"query" => "FROM my-index | WHERE type = ?type",
|
662
|
+
"query_params" => {"b" => "c"},
|
663
|
+
})
|
664
|
+
}
|
665
|
+
it "raises a config error" do
|
666
|
+
expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Placeholder type not found in query/
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
context "when `query_params` is an Array contains {key => val} entries" do
|
671
|
+
let(:config) {
|
672
|
+
super()
|
673
|
+
.merge(
|
674
|
+
{
|
675
|
+
"query" => "FROM my-index",
|
676
|
+
"query_params" => [{ "a" => "b" }, { "c" => "[b]" }, { "e" => 1 }, { "f" => "[g]" }],
|
677
|
+
})
|
678
|
+
}
|
679
|
+
|
680
|
+
it "doesn't complain since not used" do
|
681
|
+
expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
|
682
|
+
expect(plugin.query_params).to eq({ "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" })
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
context "when `query_params` is a Hash" do
|
687
|
+
let(:config) {
|
688
|
+
super()
|
689
|
+
.merge(
|
690
|
+
{
|
691
|
+
"query" => "FROM my-index",
|
692
|
+
"query_params" => { "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" },
|
693
|
+
})
|
694
|
+
}
|
695
|
+
|
696
|
+
it "doesn't complain since not used" do
|
697
|
+
expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
|
698
|
+
expect(plugin.query_params).to eq({ "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" })
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end if LOGSTASH_VERSION >= '8.17.4'
|
892
702
|
end
|
893
703
|
|
894
704
|
def extract_transport(client)
|
@@ -897,13 +707,6 @@ describe LogStash::Filters::Elasticsearch do
|
|
897
707
|
client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
|
898
708
|
end
|
899
709
|
|
900
|
-
def elastic_ruby_v8_client_available?
|
901
|
-
Elasticsearch::Transport
|
902
|
-
false
|
903
|
-
rescue NameError # NameError: uninitialized constant Elasticsearch::Transport if Elastic Ruby client is not available
|
904
|
-
true
|
905
|
-
end
|
906
|
-
|
907
710
|
class MockResponse
|
908
711
|
attr_reader :code, :headers
|
909
712
|
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/filters/elasticsearch"
|
4
|
+
require "elasticsearch"
|
5
|
+
require_relative "../../../spec/es_helper"
|
6
|
+
|
7
|
+
describe LogStash::Filters::Elasticsearch, integration: true do
|
8
|
+
|
9
|
+
ELASTIC_SECURITY_ENABLED = ENV['ELASTIC_SECURITY_ENABLED'].eql? 'true'
|
10
|
+
SECURE_INTEGRATION = ENV['SECURE_INTEGRATION'].eql? 'true'
|
11
|
+
ES_HOSTS = ["http#{SECURE_INTEGRATION ? 's' : nil}://#{ESHelper.get_host_port}"]
|
12
|
+
CA_PATH = File.expand_path('../fixtures/test_certs/ca.crt', File.dirname(__FILE__))
|
13
|
+
|
14
|
+
let(:plugin) { described_class.new(config) }
|
15
|
+
let(:es_index) { "es-filter-plugin-esql-integration-#{rand(1000)}" }
|
16
|
+
let(:test_documents) do
|
17
|
+
[
|
18
|
+
{ "message" => "test message 1", "type" => "a", "count" => 1 },
|
19
|
+
{ "message" => "test message 2", "type" => "a", "count" => 2 },
|
20
|
+
{ "message" => "test message 3", "type" => "b", "count" => 3 },
|
21
|
+
{ "message" => "test message 4", "type" => "b", "count" => 4 },
|
22
|
+
{ "message" => "test message 5", "type" => "c", "count" => 5 },
|
23
|
+
{ "message" => "odd test message", "type" => "t" }
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:base_config) do
|
28
|
+
{
|
29
|
+
"query_type" => "esql",
|
30
|
+
"hosts" => ES_HOSTS,
|
31
|
+
"ssl_enabled" => SECURE_INTEGRATION
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:credentials) do
|
36
|
+
if SECURE_INTEGRATION
|
37
|
+
{ 'user' => 'tests', 'password' => 'Tests123' }
|
38
|
+
else
|
39
|
+
{ 'user' => 'elastic', 'password' => ENV['ELASTIC_PASSWORD'] }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:config) do
|
44
|
+
config = ELASTIC_SECURITY_ENABLED ? base_config.merge(credentials) : base_config
|
45
|
+
config = { 'ssl_certificate_authorities' => CA_PATH }.merge(config) if SECURE_INTEGRATION
|
46
|
+
config
|
47
|
+
end
|
48
|
+
|
49
|
+
let(:event) { LogStash::Event.new({}) }
|
50
|
+
|
51
|
+
def es_client
|
52
|
+
@es_client ||= begin
|
53
|
+
user = SECURE_INTEGRATION ? 'tests' : 'elastic'
|
54
|
+
password = SECURE_INTEGRATION ? 'Tests123' : ENV['ELASTIC_PASSWORD']
|
55
|
+
|
56
|
+
es_client_config = { hosts: ES_HOSTS }
|
57
|
+
es_client_config = es_client_config.merge({ user: user, password: password }) if ELASTIC_SECURITY_ENABLED || SECURE_INTEGRATION
|
58
|
+
es_client_config = es_client_config.merge({ transport_options: { ssl: { ca_path: CA_PATH, verify: false }}}) if SECURE_INTEGRATION
|
59
|
+
|
60
|
+
Elasticsearch::Client.new(es_client_config)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
before(:all) do
|
65
|
+
is_ls_with_esql_supported_client = Gem::Version.create(LOGSTASH_VERSION) >= Gem::Version.create(LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
66
|
+
# Skip tests if an ES version doesn't support ES|QL
|
67
|
+
skip "LS version does not have ES client which supports ES|QL" unless is_ls_with_esql_supported_client
|
68
|
+
|
69
|
+
es_version_info = es_client.info["version"]
|
70
|
+
es_gem_version = Gem::Version.create(es_version_info["number"])
|
71
|
+
skip "ES version does not support ES|QL" if es_gem_version.nil? || es_gem_version < Gem::Version.create(LogStash::Filters::Elasticsearch::ES_ESQL_SUPPORT_VERSION)
|
72
|
+
end
|
73
|
+
|
74
|
+
before(:each) do
|
75
|
+
# Create index with test documents
|
76
|
+
es_client.indices.create(index: es_index, body: {}) unless es_client.indices.exists?(index: es_index)
|
77
|
+
|
78
|
+
test_documents.each do |doc|
|
79
|
+
es_client.index(index: es_index, body: doc, refresh: true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
after(:each) do
|
84
|
+
es_client.indices.delete(index: es_index) if es_client.indices.exists?(index: es_index)
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "run ES|QL queries" do
|
88
|
+
|
89
|
+
before do
|
90
|
+
stub_const("LOGSTASH_VERSION", LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
91
|
+
end
|
92
|
+
|
93
|
+
before(:each) do
|
94
|
+
plugin.register
|
95
|
+
end
|
96
|
+
|
97
|
+
shared_examples "ESQL query execution" do |expected_count, fields|
|
98
|
+
it "processes the event" do
|
99
|
+
plugin.filter(event)
|
100
|
+
expect(event.get("[@metadata][total_values]")).to eq(expected_count)
|
101
|
+
fields&.each do | field |
|
102
|
+
expect(event.get(field)).not_to be(nil)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "with simple FROM query with LIMIT" do
|
108
|
+
let(:config) do
|
109
|
+
super().merge("query" => "FROM #{es_index} | LIMIT 99")
|
110
|
+
end
|
111
|
+
|
112
|
+
include_examples "ESQL query execution", 6
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "with simple FROM and WHERE query combinations" do
|
116
|
+
let(:config) do
|
117
|
+
super().merge("query" => "FROM #{es_index} | WHERE type==\"b\" | LIMIT 99")
|
118
|
+
end
|
119
|
+
|
120
|
+
include_examples "ESQL query execution", 2
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "with query params" do
|
124
|
+
let(:config) do
|
125
|
+
super().merge("query" => "FROM #{es_index} | WHERE type==?type", "query_params" => { "type" => "b" })
|
126
|
+
end
|
127
|
+
|
128
|
+
include_examples "ESQL query execution", 2
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "when invalid query used" do
|
132
|
+
let(:config) do
|
133
|
+
super().merge("query" => "FROM undefined index | LIMIT 1")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "tags on failure" do
|
137
|
+
plugin.filter(event)
|
138
|
+
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "when field enrichment requested" do
|
143
|
+
let(:config) do
|
144
|
+
super().merge("query" => "FROM #{es_index} | WHERE type==\"b\" | LIMIT 99")
|
145
|
+
end
|
146
|
+
|
147
|
+
include_examples "ESQL query execution", 2, %w[message count]
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "when non-exist field value appear" do
|
151
|
+
let(:config) do
|
152
|
+
super().merge("query" => "FROM #{es_index}", "target" => "target_field")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "processes the event" do
|
156
|
+
plugin.filter(event)
|
157
|
+
expect(event.get("[@metadata][total_values]")).to eq(6)
|
158
|
+
expect(event.get("target_field").size).to eq(6)
|
159
|
+
values = event.get("target_field")
|
160
|
+
counts = values.count { |entry| entry.key?("count") }
|
161
|
+
messages = values.count { |entry| entry.key?("message") }
|
162
|
+
expect(counts).to eq(5)
|
163
|
+
expect(messages).to eq(6)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|