logstash-input-elasticsearch 4.2.0 → 4.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/docs/index.asciidoc +37 -6
- data/lib/logstash/inputs/elasticsearch.rb +40 -4
- data/logstash-input-elasticsearch.gemspec +2 -2
- data/spec/es_helper.rb +40 -0
- data/spec/inputs/elasticsearch_spec.rb +248 -27
- data/spec/inputs/integration/elasticsearch_spec.rb +43 -0
- metadata +6 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72f28226e8297df8d4eda501ef9a99a67c11bab0574711f553797c678cd5e9a8
|
4
|
+
data.tar.gz: d141edd1c0664b1f77f4ddc757cda564781aeaa2ada1ebee828ac2f76268c1de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 468f91f896d037895c7479e7ae377cee60c4b0c22b05918695f35b6e56bdfa405524fd04d03440133ac40a71fa4ed00251ad27e38792f7c30a8b736ffaa186c9
|
7
|
+
data.tar.gz: 5ba240ae691f622ab6bf410b7603ebbb959ee5c50cddc90748dd26a1a349fcc36057e9147a15d0501ea1e4f0bbc2861f3042c0addc070568ec805cdd6668d999
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## 4.3.3
|
2
|
+
- Loosen restrictions on Elasticsearch gem [#110](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/110)
|
3
|
+
|
4
|
+
## 4.3.2
|
5
|
+
- Fixed broken link to Elasticsearch Reference [#106](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/106)
|
6
|
+
|
7
|
+
## 4.3.1
|
8
|
+
- Fixed deeplink to Elasticsearch Reference [#103](https://github.com/logstash-plugins/logstash-input-elasticsearch/pull/103)
|
9
|
+
|
10
|
+
## 4.3.0
|
11
|
+
- Added managed slice scrolling with `slices` option
|
12
|
+
|
13
|
+
## 4.2.1
|
14
|
+
- Docs: Set the default_codec doc attribute.
|
15
|
+
|
1
16
|
## 4.2.0
|
2
17
|
- Docs: Deprecate `document_type`
|
3
18
|
- Add support for scheduling periodic execution of the query #81
|
data/docs/index.asciidoc
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
:plugin: elasticsearch
|
2
2
|
:type: input
|
3
|
+
:default_codec: json
|
3
4
|
|
4
5
|
///////////////////////////////////////////
|
5
6
|
START - GENERATED VARIABLES, DO NOT EDIT!
|
@@ -96,6 +97,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
|
|
96
97
|
| <<plugins-{type}s-{plugin}-schedule>> |<<string,string>>|No
|
97
98
|
| <<plugins-{type}s-{plugin}-scroll>> |<<string,string>>|No
|
98
99
|
| <<plugins-{type}s-{plugin}-size>> |<<number,number>>|No
|
100
|
+
| <<plugins-{type}s-{plugin}-slices>> |<<number,number>>|No
|
99
101
|
| <<plugins-{type}s-{plugin}-ssl>> |<<boolean,boolean>>|No
|
100
102
|
| <<plugins-{type}s-{plugin}-user>> |<<string,string>>|No
|
101
103
|
|=======================================================================
|
@@ -161,11 +163,10 @@ It will be removed in the next major version of Logstash.
|
|
161
163
|
* Value type is <<array,array>>
|
162
164
|
* Default value is `["_index", "_type", "_id"]`
|
163
165
|
|
164
|
-
If document metadata storage is requested by enabling the `docinfo`
|
165
|
-
option
|
166
|
-
|
167
|
-
|
168
|
-
in the Elasticsearch documentation for more information.
|
166
|
+
If document metadata storage is requested by enabling the `docinfo` option, this
|
167
|
+
option lists the metadata fields to save in the current event. See
|
168
|
+
{ref}/mapping-fields.html[Meta-Fields] in the Elasticsearch documentation for
|
169
|
+
more information.
|
169
170
|
|
170
171
|
[id="plugins-{type}s-{plugin}-docinfo_target"]
|
171
172
|
===== `docinfo_target`
|
@@ -193,7 +194,11 @@ can be either IP, HOST, IP:port, or HOST:port. The port defaults to
|
|
193
194
|
* Value type is <<string,string>>
|
194
195
|
* Default value is `"logstash-*"`
|
195
196
|
|
196
|
-
The index or alias to search.
|
197
|
+
The index or alias to search. See
|
198
|
+
https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-index.html[Multi Indices documentation]
|
199
|
+
in the Elasticsearch documentation for more information on how to reference
|
200
|
+
multiple indices.
|
201
|
+
|
197
202
|
|
198
203
|
[id="plugins-{type}s-{plugin}-password"]
|
199
204
|
===== `password`
|
@@ -245,6 +250,30 @@ round trip (i.e. between the previous scroll request, to the next).
|
|
245
250
|
|
246
251
|
This allows you to set the maximum number of hits returned per scroll.
|
247
252
|
|
253
|
+
[id="plugins-{type}s-{plugin}-slices"]
|
254
|
+
===== `slices`
|
255
|
+
|
256
|
+
* Value type is <<number,number>>
|
257
|
+
* There is no default value.
|
258
|
+
* Sensible values range from 2 to about 8.
|
259
|
+
|
260
|
+
In some cases, it is possible to improve overall throughput by consuming multiple
|
261
|
+
distinct slices of a query simultaneously using the
|
262
|
+
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#sliced-scroll[Sliced Scroll API],
|
263
|
+
especially if the pipeline is spending significant time waiting on Elasticsearch
|
264
|
+
to provide results.
|
265
|
+
|
266
|
+
If set, the `slices` parameter tells the plugin how many slices to divide the work
|
267
|
+
into, and will produce events from the slices in parallel until all of them are done
|
268
|
+
scrolling.
|
269
|
+
|
270
|
+
NOTE: The Elasticsearch manual indicates that there can be _negative_ performance
|
271
|
+
implications to both the query and the Elasticsearch cluster when a scrolling
|
272
|
+
query uses more slices than shards in the index.
|
273
|
+
|
274
|
+
If the `slices` parameter is left unset, the plugin will _not_ inject slice
|
275
|
+
instructions into the query.
|
276
|
+
|
248
277
|
[id="plugins-{type}s-{plugin}-ssl"]
|
249
278
|
===== `ssl`
|
250
279
|
|
@@ -268,3 +297,5 @@ empty string authentication will be disabled.
|
|
268
297
|
|
269
298
|
[id="plugins-{type}s-{plugin}-common-options"]
|
270
299
|
include::{include_path}/{type}.asciidoc[]
|
300
|
+
|
301
|
+
:default_codec!:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require "logstash/inputs/base"
|
3
3
|
require "logstash/namespace"
|
4
|
+
require "logstash/json"
|
4
5
|
require "base64"
|
5
6
|
|
6
7
|
# .Compatibility Note
|
@@ -83,6 +84,10 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
|
|
83
84
|
# round trip (i.e. between the previous scroll request, to the next).
|
84
85
|
config :scroll, :validate => :string, :default => "1m"
|
85
86
|
|
87
|
+
# This parameter controls the number of parallel slices to be consumed simultaneously
|
88
|
+
# by this pipeline input.
|
89
|
+
config :slices, :validate => :number
|
90
|
+
|
86
91
|
# If set, include Elasticsearch document information such as index, type, and
|
87
92
|
# the id in the event.
|
88
93
|
#
|
@@ -147,10 +152,14 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
|
|
147
152
|
|
148
153
|
@options = {
|
149
154
|
:index => @index,
|
150
|
-
:body => @query,
|
151
155
|
:scroll => @scroll,
|
152
156
|
:size => @size
|
153
157
|
}
|
158
|
+
@base_query = LogStash::Json.load(@query)
|
159
|
+
if @slices
|
160
|
+
@base_query.include?('slice') && fail(LogStash::ConfigurationError, "Elasticsearch Input Plugin's `query` option cannot specify specific `slice` when configured to manage parallel slices with `slices` option")
|
161
|
+
@slices < 1 && fail(LogStash::ConfigurationError, "Elasticsearch Input Plugin's `slices` option must be greater than zero, got `#{@slices}`")
|
162
|
+
end
|
154
163
|
|
155
164
|
transport_options = {}
|
156
165
|
|
@@ -196,16 +205,39 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
|
|
196
205
|
private
|
197
206
|
|
198
207
|
def do_run(output_queue)
|
199
|
-
#
|
200
|
-
|
208
|
+
# if configured to run a single slice, don't bother spinning up threads
|
209
|
+
return do_run_slice(output_queue) if @slices.nil? || @slices <= 1
|
210
|
+
|
211
|
+
logger.warn("managed slices for query is very large (#{@slices}); consider reducing") if @slices > 8
|
212
|
+
|
213
|
+
@slices.times.map do |slice_id|
|
214
|
+
Thread.new do
|
215
|
+
LogStash::Util::set_thread_name("#{@id}_slice_#{slice_id}")
|
216
|
+
do_run_slice(output_queue, slice_id)
|
217
|
+
end
|
218
|
+
end.map(&:join)
|
219
|
+
end
|
220
|
+
|
221
|
+
def do_run_slice(output_queue, slice_id=nil)
|
222
|
+
slice_query = @base_query
|
223
|
+
slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @slices}) unless slice_id.nil?
|
224
|
+
|
225
|
+
slice_options = @options.merge(:body => LogStash::Json.dump(slice_query) )
|
226
|
+
|
227
|
+
logger.info("Slice starting", slice_id: slice_id, slices: @slices) unless slice_id.nil?
|
228
|
+
r = search_request(slice_options)
|
201
229
|
|
202
230
|
r['hits']['hits'].each { |hit| push_hit(hit, output_queue) }
|
231
|
+
logger.debug("Slice progress", slice_id: slice_id, slices: @slices) unless slice_id.nil?
|
232
|
+
|
203
233
|
has_hits = r['hits']['hits'].any?
|
204
234
|
|
205
|
-
while has_hits && !stop?
|
235
|
+
while has_hits && r['_scroll_id'] && !stop?
|
206
236
|
r = process_next_scroll(output_queue, r['_scroll_id'])
|
237
|
+
logger.debug("Slice progress", slice_id: slice_id, slices: @slices) unless slice_id.nil?
|
207
238
|
has_hits = r['has_hits']
|
208
239
|
end
|
240
|
+
logger.info("Slice complete", slice_id: slice_id, slices: @slices) unless slice_id.nil?
|
209
241
|
end
|
210
242
|
|
211
243
|
def process_next_scroll(output_queue, scroll_id)
|
@@ -243,4 +275,8 @@ class LogStash::Inputs::Elasticsearch < LogStash::Inputs::Base
|
|
243
275
|
def scroll_request scroll_id
|
244
276
|
@client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
|
245
277
|
end
|
278
|
+
|
279
|
+
def search_request(options)
|
280
|
+
@client.search(options)
|
281
|
+
end
|
246
282
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-input-elasticsearch'
|
4
|
-
s.version = '4.
|
4
|
+
s.version = '4.3.3'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "Reads query results from an Elasticsearch cluster"
|
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"
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
|
|
22
22
|
# Gem dependencies
|
23
23
|
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
24
24
|
|
25
|
-
s.add_runtime_dependency 'elasticsearch',
|
25
|
+
s.add_runtime_dependency 'elasticsearch', '>= 5.0.3'
|
26
26
|
|
27
27
|
s.add_runtime_dependency 'logstash-codec-json'
|
28
28
|
s.add_runtime_dependency 'logstash-codec-plain'
|
data/spec/es_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module ESHelper
|
2
|
+
def self.get_host_port
|
3
|
+
return "http://elasticsearch:9200" if ENV["INTEGRATION"] == "true"
|
4
|
+
raise "This setting is only used for integration tests"
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.get_client
|
8
|
+
Elasticsearch::Client.new(:hosts => [get_host_port])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.doc_type
|
12
|
+
if ESHelper.es_version_satisfies?(">=8")
|
13
|
+
nil
|
14
|
+
elsif ESHelper.es_version_satisfies?(">=7")
|
15
|
+
"_doc"
|
16
|
+
else
|
17
|
+
"doc"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.index_doc(es, params)
|
22
|
+
type = doc_type
|
23
|
+
params[:type] = doc_type unless type.nil?
|
24
|
+
es.index(params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.es_version
|
28
|
+
ENV['ES_VERSION'] || ENV['ELASTIC_STACK_VERSION']
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.es_version_satisfies?(*requirement)
|
32
|
+
es_version = RSpec.configuration.filter[:es_version] || ENV['ES_VERSION'] || ENV['ELASTIC_STACK_VERSION']
|
33
|
+
if es_version.nil?
|
34
|
+
puts "Info: ES_VERSION, ELASTIC_STACK_VERSION or 'es_version' tag wasn't set. Returning false to all `es_version_satisfies?` call."
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
es_release_version = Gem::Version.new(es_version).release
|
38
|
+
Gem::Requirement.new(requirement).satisfied_by?(es_release_version)
|
39
|
+
end
|
40
|
+
end
|
@@ -84,6 +84,239 @@ describe LogStash::Inputs::Elasticsearch do
|
|
84
84
|
insist { event.get("message") } == [ "ohayo" ]
|
85
85
|
end
|
86
86
|
|
87
|
+
|
88
|
+
# This spec is an adapter-spec, ensuring that we send the right sequence of messages to our Elasticsearch Client
|
89
|
+
# to support sliced scrolling. The underlying implementation will spawn its own threads to consume, so we must be
|
90
|
+
# careful to use thread-safe constructs.
|
91
|
+
context "with managed sliced scrolling" do
|
92
|
+
let(:config) do
|
93
|
+
{
|
94
|
+
'query' => "#{LogStash::Json.dump(query)}",
|
95
|
+
'slices' => slices,
|
96
|
+
'docinfo' => true, # include ids
|
97
|
+
}
|
98
|
+
end
|
99
|
+
let(:query) do
|
100
|
+
{
|
101
|
+
"query" => {
|
102
|
+
"match" => { "city_name" => "Okinawa" }
|
103
|
+
},
|
104
|
+
"fields" => ["message"]
|
105
|
+
}
|
106
|
+
end
|
107
|
+
let(:slices) { 2 }
|
108
|
+
|
109
|
+
context 'with `slices => 0`' do
|
110
|
+
let(:slices) { 0 }
|
111
|
+
it 'fails to register' do
|
112
|
+
expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with `slices => 1`' do
|
117
|
+
let(:slices) { 1 }
|
118
|
+
it 'runs just one slice' do
|
119
|
+
expect(plugin).to receive(:do_run_slice).with(duck_type(:<<))
|
120
|
+
expect(Thread).to_not receive(:new)
|
121
|
+
|
122
|
+
plugin.register
|
123
|
+
plugin.run([])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'without slices directive' do
|
128
|
+
let(:config) { super.tap { |h| h.delete('slices') } }
|
129
|
+
it 'runs just one slice' do
|
130
|
+
expect(plugin).to receive(:do_run_slice).with(duck_type(:<<))
|
131
|
+
expect(Thread).to_not receive(:new)
|
132
|
+
|
133
|
+
plugin.register
|
134
|
+
plugin.run([])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
2.upto(8) do |slice_count|
|
139
|
+
context "with `slices => #{slice_count}`" do
|
140
|
+
let(:slices) { slice_count }
|
141
|
+
it "runs #{slice_count} independent slices" do
|
142
|
+
expect(Thread).to receive(:new).and_call_original.exactly(slice_count).times
|
143
|
+
slice_count.times do |slice_id|
|
144
|
+
expect(plugin).to receive(:do_run_slice).with(duck_type(:<<), slice_id)
|
145
|
+
end
|
146
|
+
|
147
|
+
plugin.register
|
148
|
+
plugin.run([])
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# This section of specs heavily mocks the Elasticsearch::Client, and ensures that the Elasticsearch Input Plugin
|
154
|
+
# behaves as expected when handling a series of sliced, scrolled requests/responses.
|
155
|
+
context 'adapter/integration' do
|
156
|
+
let(:response_template) do
|
157
|
+
{
|
158
|
+
"took" => 12,
|
159
|
+
"timed_out" => false,
|
160
|
+
"shards" => {
|
161
|
+
"total" => 6,
|
162
|
+
"successful" => 6,
|
163
|
+
"failed" => 0
|
164
|
+
}
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
let(:hits_template) do
|
169
|
+
{
|
170
|
+
"total" => 4,
|
171
|
+
"max_score" => 1.0,
|
172
|
+
"hits" => []
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
let(:hit_template) do
|
177
|
+
{
|
178
|
+
"_index" => "logstash-2018.08.23",
|
179
|
+
"_type" => "logs",
|
180
|
+
"_score" => 1.0,
|
181
|
+
"_source" => { "message" => ["hello, world"] }
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
# BEGIN SLICE 0: a sequence of THREE scrolled responses containing 2, 1, and 0 items
|
186
|
+
# end-of-slice is reached when slice0_response2 is empty.
|
187
|
+
begin
|
188
|
+
let(:slice0_response0) do
|
189
|
+
response_template.merge({
|
190
|
+
"_scroll_id" => slice0_scroll1,
|
191
|
+
"hits" => hits_template.merge("hits" => [
|
192
|
+
hit_template.merge('_id' => "slice0-response0-item0"),
|
193
|
+
hit_template.merge('_id' => "slice0-response0-item1")
|
194
|
+
])
|
195
|
+
})
|
196
|
+
end
|
197
|
+
let(:slice0_scroll1) { 'slice:0,scroll:1' }
|
198
|
+
let(:slice0_response1) do
|
199
|
+
response_template.merge({
|
200
|
+
"_scroll_id" => slice0_scroll2,
|
201
|
+
"hits" => hits_template.merge("hits" => [
|
202
|
+
hit_template.merge('_id' => "slice0-response1-item0")
|
203
|
+
])
|
204
|
+
})
|
205
|
+
end
|
206
|
+
let(:slice0_scroll2) { 'slice:0,scroll:2' }
|
207
|
+
let(:slice0_response2) do
|
208
|
+
response_template.merge(
|
209
|
+
"_scroll_id" => slice0_scroll3,
|
210
|
+
"hits" => hits_template.merge({"hits" => []})
|
211
|
+
)
|
212
|
+
end
|
213
|
+
let(:slice0_scroll3) { 'slice:0,scroll:3' }
|
214
|
+
end
|
215
|
+
# END SLICE 0
|
216
|
+
|
217
|
+
# BEGIN SLICE 1: a sequence of TWO scrolled responses containing 2 and 2 items.
|
218
|
+
# end-of-slice is reached when slice1_response1 does not contain a next scroll id
|
219
|
+
begin
|
220
|
+
let(:slice1_response0) do
|
221
|
+
response_template.merge({
|
222
|
+
"_scroll_id" => slice1_scroll1,
|
223
|
+
"hits" => hits_template.merge("hits" => [
|
224
|
+
hit_template.merge('_id' => "slice1-response0-item0"),
|
225
|
+
hit_template.merge('_id' => "slice1-response0-item1")
|
226
|
+
])
|
227
|
+
})
|
228
|
+
end
|
229
|
+
let(:slice1_scroll1) { 'slice:1,scroll:1' }
|
230
|
+
let(:slice1_response1) do
|
231
|
+
response_template.merge({
|
232
|
+
"hits" => hits_template.merge("hits" => [
|
233
|
+
hit_template.merge('_id' => "slice1-response1-item0"),
|
234
|
+
hit_template.merge('_id' => "slice1-response1-item1")
|
235
|
+
])
|
236
|
+
})
|
237
|
+
end
|
238
|
+
end
|
239
|
+
# END SLICE 1
|
240
|
+
|
241
|
+
let(:client) { Elasticsearch::Client.new }
|
242
|
+
|
243
|
+
# RSpec mocks validations are not threadsafe.
|
244
|
+
# Allow caller to synchronize.
|
245
|
+
def synchronize_method!(object, method_name)
|
246
|
+
original_method = object.method(method_name)
|
247
|
+
mutex = Mutex.new
|
248
|
+
allow(object).to receive(method_name).with(any_args) do |*method_args, &method_block|
|
249
|
+
mutex.synchronize do
|
250
|
+
original_method.call(*method_args,&method_block)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
before(:each) do
|
256
|
+
expect(Elasticsearch::Client).to receive(:new).with(any_args).and_return(client)
|
257
|
+
plugin.register
|
258
|
+
|
259
|
+
# SLICE0 is a three-page scroll in which the last page is empty
|
260
|
+
slice0_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 0, 'max' => 2}))
|
261
|
+
expect(client).to receive(:search).with(hash_including(:body => slice0_query)).and_return(slice0_response0)
|
262
|
+
expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll1 })).and_return(slice0_response1)
|
263
|
+
expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice0_scroll2 })).and_return(slice0_response2)
|
264
|
+
|
265
|
+
# SLICE1 is a two-page scroll in which the last page has no next scroll id
|
266
|
+
slice1_query = LogStash::Json.dump(query.merge('slice' => { 'id' => 1, 'max' => 2}))
|
267
|
+
expect(client).to receive(:search).with(hash_including(:body => slice1_query)).and_return(slice1_response0)
|
268
|
+
expect(client).to receive(:scroll).with(hash_including(:body => { :scroll_id => slice1_scroll1 })).and_return(slice1_response1)
|
269
|
+
|
270
|
+
synchronize_method!(plugin, :scroll_request)
|
271
|
+
synchronize_method!(plugin, :search_request)
|
272
|
+
end
|
273
|
+
|
274
|
+
let(:emitted_events) do
|
275
|
+
queue = Queue.new # since we are running slices in threads, we need a thread-safe queue.
|
276
|
+
plugin.run(queue)
|
277
|
+
events = []
|
278
|
+
events << queue.pop until queue.empty?
|
279
|
+
events
|
280
|
+
end
|
281
|
+
|
282
|
+
let(:emitted_event_ids) do
|
283
|
+
emitted_events.map { |event| event.get('[@metadata][_id]') }
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'emits the hits on the first page of the first slice' do
|
287
|
+
expect(emitted_event_ids).to include('slice0-response0-item0')
|
288
|
+
expect(emitted_event_ids).to include('slice0-response0-item1')
|
289
|
+
end
|
290
|
+
it 'emits the hits on the second page of the first slice' do
|
291
|
+
expect(emitted_event_ids).to include('slice0-response1-item0')
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'emits the hits on the first page of the second slice' do
|
295
|
+
expect(emitted_event_ids).to include('slice1-response0-item0')
|
296
|
+
expect(emitted_event_ids).to include('slice1-response0-item1')
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'emits the hitson the second page of the second slice' do
|
300
|
+
expect(emitted_event_ids).to include('slice1-response1-item0')
|
301
|
+
expect(emitted_event_ids).to include('slice1-response1-item1')
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'does not double-emit' do
|
305
|
+
expect(emitted_event_ids.uniq).to eq(emitted_event_ids)
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'emits events with appropriate fields' do
|
309
|
+
emitted_events.each do |event|
|
310
|
+
expect(event).to be_a(LogStash::Event)
|
311
|
+
expect(event.get('message')).to eq(['hello, world'])
|
312
|
+
expect(event.get('[@metadata][_id]')).to_not be_nil
|
313
|
+
expect(event.get('[@metadata][_id]')).to_not be_empty
|
314
|
+
expect(event.get('[@metadata][_index]')).to start_with('logstash-')
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
87
320
|
context "with Elasticsearch document information" do
|
88
321
|
let!(:response) do
|
89
322
|
{
|
@@ -142,15 +375,13 @@ describe LogStash::Inputs::Elasticsearch do
|
|
142
375
|
end
|
143
376
|
|
144
377
|
it 'merges the values if the `docinfo_target` already exist in the `_source` document' do
|
145
|
-
metadata_field = 'metadata_with_hash'
|
146
|
-
|
147
378
|
config_metadata_with_hash = %Q[
|
148
379
|
input {
|
149
380
|
elasticsearch {
|
150
381
|
hosts => ["localhost"]
|
151
382
|
query => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }'
|
152
383
|
docinfo => true
|
153
|
-
docinfo_target => '
|
384
|
+
docinfo_target => 'metadata_with_hash'
|
154
385
|
}
|
155
386
|
}
|
156
387
|
]
|
@@ -159,33 +390,23 @@ describe LogStash::Inputs::Elasticsearch do
|
|
159
390
|
queue.pop
|
160
391
|
end
|
161
392
|
|
162
|
-
expect(event.get("[
|
163
|
-
expect(event.get("[
|
164
|
-
expect(event.get("[
|
165
|
-
expect(event.get("[
|
393
|
+
expect(event.get("[metadata_with_hash][_index]")).to eq('logstash-2014.10.12')
|
394
|
+
expect(event.get("[metadata_with_hash][_type]")).to eq('logs')
|
395
|
+
expect(event.get("[metadata_with_hash][_id]")).to eq('C5b2xLQwTZa76jBmHIbwHQ')
|
396
|
+
expect(event.get("[metadata_with_hash][awesome]")).to eq("logstash")
|
166
397
|
end
|
167
398
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
}
|
179
|
-
}
|
180
|
-
]
|
181
|
-
|
182
|
-
pipeline = new_pipeline_from_string(config_metadata_with_string)
|
183
|
-
queue = Queue.new
|
184
|
-
pipeline.instance_eval do
|
185
|
-
@output_func = lambda { |event| queue << event }
|
399
|
+
context 'if the `docinfo_target` exist but is not of type hash' do
|
400
|
+
let (:config) { {
|
401
|
+
"hosts" => ["localhost"],
|
402
|
+
"query" => '{ "query": { "match": { "city_name": "Okinawa" } }, "fields": ["message"] }',
|
403
|
+
"docinfo" => true,
|
404
|
+
"docinfo_target" => 'metadata_with_string'
|
405
|
+
} }
|
406
|
+
it 'thows an exception if the `docinfo_target` exist but is not of type hash' do
|
407
|
+
plugin.register
|
408
|
+
expect { plugin.run([]) }.to raise_error(Exception, /incompatible event/)
|
186
409
|
end
|
187
|
-
|
188
|
-
expect { pipeline.run }.to raise_error(Exception, /incompatible event/)
|
189
410
|
end
|
190
411
|
|
191
412
|
it "should move the document info to the @metadata field" do
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin"
|
4
|
+
require "logstash/inputs/elasticsearch"
|
5
|
+
require_relative "../../../spec/es_helper"
|
6
|
+
|
7
|
+
describe LogStash::Inputs::Elasticsearch, :integration => true do
|
8
|
+
|
9
|
+
let(:config) { { 'hosts' => [ESHelper.get_host_port],
|
10
|
+
'index' => 'logs',
|
11
|
+
'query' => '{ "query": { "match": { "message": "Not found"} }}' } }
|
12
|
+
let(:plugin) { described_class.new(config) }
|
13
|
+
let(:event) { LogStash::Event.new({}) }
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
@es = ESHelper.get_client
|
17
|
+
# Delete all templates first.
|
18
|
+
# Clean ES of data before we start.
|
19
|
+
@es.indices.delete_template(:name => "*")
|
20
|
+
# This can fail if there are no indexes, ignore failure.
|
21
|
+
@es.indices.delete(:index => "*") rescue nil
|
22
|
+
10.times do
|
23
|
+
ESHelper.index_doc(@es, :index => 'logs', :body => { :response => 404, :message=> 'Not Found'})
|
24
|
+
end
|
25
|
+
@es.indices.refresh
|
26
|
+
plugin.register
|
27
|
+
end
|
28
|
+
|
29
|
+
after(:each) do
|
30
|
+
@es.indices.delete_template(:name => "*")
|
31
|
+
@es.indices.delete(:index => "*") rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'smoke test' do
|
35
|
+
it "should retrieve json event from elasticseach" do
|
36
|
+
queue = []
|
37
|
+
plugin.run(queue)
|
38
|
+
event = queue.pop
|
39
|
+
expect(event).to be_a(LogStash::Event)
|
40
|
+
expect(event.get("response")).to eql(404)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
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.
|
4
|
+
version: 4.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -36,9 +36,6 @@ dependencies:
|
|
36
36
|
- - ">="
|
37
37
|
- !ruby/object:Gem::Version
|
38
38
|
version: 5.0.3
|
39
|
-
- - "<"
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: 6.0.0
|
42
39
|
name: elasticsearch
|
43
40
|
prerelease: false
|
44
41
|
type: :runtime
|
@@ -47,9 +44,6 @@ dependencies:
|
|
47
44
|
- - ">="
|
48
45
|
- !ruby/object:Gem::Version
|
49
46
|
version: 5.0.3
|
50
|
-
- - "<"
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 6.0.0
|
53
47
|
- !ruby/object:Gem::Dependency
|
54
48
|
requirement: !ruby/object:Gem::Requirement
|
55
49
|
requirements:
|
@@ -179,7 +173,9 @@ files:
|
|
179
173
|
- docs/index.asciidoc
|
180
174
|
- lib/logstash/inputs/elasticsearch.rb
|
181
175
|
- logstash-input-elasticsearch.gemspec
|
176
|
+
- spec/es_helper.rb
|
182
177
|
- spec/inputs/elasticsearch_spec.rb
|
178
|
+
- spec/inputs/integration/elasticsearch_spec.rb
|
183
179
|
homepage: http://www.elastic.co/guide/en/logstash/current/index.html
|
184
180
|
licenses:
|
185
181
|
- Apache License (2.0)
|
@@ -207,4 +203,6 @@ signing_key:
|
|
207
203
|
specification_version: 4
|
208
204
|
summary: Reads query results from an Elasticsearch cluster
|
209
205
|
test_files:
|
206
|
+
- spec/es_helper.rb
|
210
207
|
- spec/inputs/elasticsearch_spec.rb
|
208
|
+
- spec/inputs/integration/elasticsearch_spec.rb
|