logstash-filter-elasticsearch 4.2.0 → 4.3.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.
@@ -0,0 +1,211 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/filters/elasticsearch"
4
+
5
+ describe LogStash::Filters::Elasticsearch::EsqlExecutor do
6
+ let(:client) { instance_double(LogStash::Filters::ElasticsearchClient) }
7
+ let(:logger) { double("logger") }
8
+ let(:plugin) { LogStash::Filters::Elasticsearch.new(plugin_config) }
9
+ let(:plugin_config) do
10
+ {
11
+ "query_type" => "esql",
12
+ "query" => "FROM test-index | STATS count() BY field | LIMIT 10"
13
+ }
14
+ end
15
+ let(:esql_executor) { described_class.new(plugin, logger) }
16
+
17
+ context "when initializes" do
18
+ it "sets up the ESQL executor with correct parameters" do
19
+ allow(logger).to receive(:debug)
20
+ allow(logger).to receive(:warn)
21
+ expect(esql_executor.instance_variable_get(:@query)).to eq(plugin_config["query"])
22
+ expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({})
23
+ expect(esql_executor.instance_variable_get(:@static_params)).to eq([])
24
+ expect(esql_executor.instance_variable_get(:@tag_on_failure)).to eq(["_elasticsearch_lookup_failure"])
25
+ end
26
+ end
27
+
28
+ context "when processes" do
29
+ let(:plugin_config) {
30
+ super()
31
+ .merge(
32
+ {
33
+ "query" => "FROM my-index | WHERE field = ?foo | LIMIT 5",
34
+ "query_params" => { "foo" => "[bar]" }
35
+ })
36
+ }
37
+ let(:event) { LogStash::Event.new({}) }
38
+ let(:response) {
39
+ {
40
+ 'values' => [["foo", "bar", nil]],
41
+ 'columns' => [{ 'name' => 'id', 'type' => 'keyword' }, { 'name' => 'val', 'type' => 'keyword' }, { 'name' => 'odd', 'type' => 'keyword' }]
42
+ }
43
+ }
44
+
45
+ before do
46
+ allow(logger).to receive(:debug)
47
+ allow(logger).to receive(:warn)
48
+ end
49
+
50
+ it "resolves parameters" do
51
+ expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
52
+ resolved_params = esql_executor.send(:resolve_parameters, event)
53
+ expect(resolved_params).to include("foo" => "resolved_value")
54
+ end
55
+
56
+ it "executes the query with resolved parameters" do
57
+ allow(logger).to receive(:debug)
58
+ expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
59
+ expect(client).to receive(:esql_query).with(
60
+ { body: { query: plugin_config["query"], params: [{ "foo" => "resolved_value" }] }, format: 'json', drop_null_columns: true, })
61
+ resolved_params = esql_executor.send(:resolve_parameters, event)
62
+ esql_executor.send(:execute_query, client, resolved_params)
63
+ end
64
+
65
+ it "informs warning if received warning" do
66
+ allow(response).to receive(:headers).and_return({ "warning" => "some warning" })
67
+ expect(logger).to receive(:warn).with("ES|QL executor received warning", { :message => "some warning" })
68
+ esql_executor.send(:inform_warning, response)
69
+ end
70
+
71
+ it "processes the response and adds metadata" do
72
+ expect(event).to receive(:set).with("[@metadata][total_values]", 1)
73
+ # [id], [val] aren't resolved via sprintf, use as it is
74
+ expect(event).to receive(:set).with("[id]", "foo")
75
+ expect(event).to receive(:set).with("[val]", "bar")
76
+ esql_executor.send(:process_response, event, response)
77
+ end
78
+
79
+ it "executes chain of processes" do
80
+ allow(plugin).to receive(:decorate)
81
+ allow(logger).to receive(:debug)
82
+ allow(response).to receive(:headers).and_return({})
83
+ expect(client).to receive(:esql_query).with(
84
+ {
85
+ body: { query: plugin_config["query"], params: [{"foo"=>"resolve_me"}] },
86
+ format: 'json',
87
+ drop_null_columns: true,
88
+ }).and_return(response)
89
+
90
+ event = LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" })
91
+ expect { esql_executor.process(client, event) }.to_not raise_error
92
+ expect(event.get("[@metadata][total_values]")).to eq(1)
93
+ expect(event.get("hello")).to eq("world")
94
+ expect(event.get("val")).to eq("bar")
95
+ expect(event.get("odd")).to be_nil # filters out non-exist fields
96
+ end
97
+
98
+ it "tags on plugin failures" do
99
+ expect(event).to receive(:get).with("[bar]").and_raise("Event#get Invalid FieldReference error")
100
+
101
+ expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
102
+ expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
103
+ esql_executor.process(client, event)
104
+ end
105
+
106
+ it "tags on query execution failures" do
107
+ allow(logger).to receive(:debug)
108
+ allow(client).to receive(:esql_query).and_raise("Query execution error")
109
+
110
+ expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
111
+ expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
112
+ esql_executor.process(client, event)
113
+ end
114
+
115
+ describe "#target" do
116
+ let(:event) { LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" }) }
117
+ let(:response) {
118
+ super().merge({ 'values' => [["foo", "bar", nil], %w[hello again world], %w[another value here]] })
119
+ }
120
+ before(:each) do
121
+ expect(client).to receive(:esql_query).with(any_args).and_return(response)
122
+ allow(plugin).to receive(:decorate)
123
+ allow(logger).to receive(:debug)
124
+ allow(response).to receive(:headers).and_return({})
125
+ end
126
+
127
+ context "when specified" do
128
+ let(:plugin_config) {
129
+ super().merge({ "target" => "my-target" })
130
+ }
131
+
132
+ it "sets all query results into event" do
133
+ expected_result = [
134
+ {"id"=>"foo", "val"=>"bar"},
135
+ {"id"=>"hello", "val"=>"again", "odd"=>"world"},
136
+ {"id"=>"another", "val"=>"value", "odd"=>"here"}
137
+ ]
138
+ expect { esql_executor.process(client, event) }.to_not raise_error
139
+ expect(event.get("[@metadata][total_values]")).to eq(3)
140
+ expect(event.get("my-target").size).to eq(3)
141
+ expect(event.get("my-target")).to eq(expected_result)
142
+ end
143
+ end
144
+
145
+ context "when not specified" do
146
+ shared_examples "first result into the event" do
147
+ it "sets" do
148
+ expect { esql_executor.process(client, event) }.to_not raise_error
149
+ expect(event.get("[@metadata][total_values]")).to eq(3)
150
+ expect(event.get("id")).to eq("foo")
151
+ expect(event.get("val")).to eq("bar")
152
+ expect(event.get("odd")).to eq(nil)
153
+ end
154
+ end
155
+ context "when limit is included in the query" do
156
+ let(:plugin_config) {
157
+ super().merge({ "query" => "FROM my-index | LIMIT 555" })
158
+ }
159
+ it_behaves_like "first result into the event"
160
+ end
161
+
162
+ context "when limit is not included in the query" do
163
+ let(:plugin_config) {
164
+ super().merge({ "query" => "FROM my-index" })
165
+ }
166
+ it_behaves_like "first result into the event"
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ describe "#query placeholders" do
173
+ before(:each) do
174
+ allow(logger).to receive(:debug)
175
+ allow(logger).to receive(:warn)
176
+ plugin.send(:validate_esql_query_and_params!)
177
+ end
178
+
179
+ context "when `query_params` is an Array contains {key => val} entries" do
180
+ let(:plugin_config) {
181
+ super()
182
+ .merge(
183
+ {
184
+ "query" => "FROM my-index | LIMIT 1",
185
+ "query_params" => [{ "a" => "b" }, { "c" => "[b]" }, { "e" => 1 }, { "f" => "[g]" }],
186
+ })
187
+ }
188
+
189
+ it "separates references and static params at initialization" do
190
+ expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
191
+ expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
192
+ end
193
+ end
194
+
195
+ context "when `query_params` is a Hash" do
196
+ let(:plugin_config) {
197
+ super()
198
+ .merge(
199
+ {
200
+ "query" => "FROM my-index | LIMIT 1",
201
+ "query_params" => { "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" },
202
+ })
203
+ }
204
+
205
+ it "separates references and static params at initialization" do
206
+ expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
207
+ expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
208
+ end
209
+ end
210
+ end
211
+ end if LOGSTASH_VERSION >= LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION