logstash-input-elasticsearch 4.2.0 → 4.3.3
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 +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
|