logstash-filter-elasticsearch 3.19.0 → 4.0.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.
@@ -1,211 +0,0 @@
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
@@ -1,167 +0,0 @@
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