logstash-output-elasticsearch 0.1.8-java

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,42 @@
1
+ {
2
+ "template" : "logstash-*",
3
+ "settings" : {
4
+ "index.refresh_interval" : "5s"
5
+ },
6
+ "mappings" : {
7
+ "_default_" : {
8
+ "_all" : {"enabled" : true},
9
+ "dynamic_templates" : [ {
10
+ "message_field" : {
11
+ "match" : "message",
12
+ "match_mapping_type" : "string",
13
+ "mapping" : {
14
+ "type" : "string", "index" : "analyzed", "omit_norms" : true
15
+ }
16
+ }
17
+ }, {
18
+ "string_fields" : {
19
+ "match" : "*",
20
+ "match_mapping_type" : "string",
21
+ "mapping" : {
22
+ "type" : "string", "index" : "analyzed", "omit_norms" : true,
23
+ "fields" : {
24
+ "raw" : {"type": "string", "index" : "not_analyzed", "ignore_above" : 256}
25
+ }
26
+ }
27
+ }
28
+ } ],
29
+ "properties" : {
30
+ "@version": { "type": "string", "index": "not_analyzed" },
31
+ "geoip" : {
32
+ "type" : "object",
33
+ "dynamic": true,
34
+ "path": "full",
35
+ "properties" : {
36
+ "location" : { "type" : "geo_point" }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,253 @@
1
+ require "logstash/outputs/elasticsearch"
2
+ require "cabin"
3
+
4
+ module LogStash::Outputs::Elasticsearch
5
+ module Protocols
6
+ class Base
7
+ private
8
+ def initialize(options={})
9
+ # host(s), port, cluster
10
+ @logger = Cabin::Channel.get
11
+ end
12
+
13
+ def client
14
+ return @client if @client
15
+ @client = build_client(@options)
16
+ return @client
17
+ end # def client
18
+
19
+
20
+ def template_install(name, template, force=false)
21
+ if template_exists?(name) && !force
22
+ @logger.debug("Found existing Elasticsearch template. Skipping template management", :name => name)
23
+ return
24
+ end
25
+ template_put(name, template)
26
+ end
27
+
28
+ # Do a bulk request with the given actions.
29
+ #
30
+ # 'actions' is expected to be an array of bulk requests as string json
31
+ # values.
32
+ #
33
+ # Each 'action' becomes a single line in the bulk api call. For more
34
+ # details on the format of each.
35
+ def bulk(actions)
36
+ raise NotImplemented, "You must implement this yourself"
37
+ # bulk([
38
+ # '{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }',
39
+ # '{ "field1" : "value1" }'
40
+ #])
41
+ end
42
+
43
+ public(:initialize, :template_install)
44
+ end
45
+
46
+ class HTTPClient < Base
47
+ private
48
+
49
+ DEFAULT_OPTIONS = {
50
+ :port => 9200
51
+ }
52
+
53
+ def initialize(options={})
54
+ super
55
+ require "elasticsearch" # gem 'elasticsearch-ruby'
56
+ # manticore http transport
57
+ require "elasticsearch/transport/transport/http/manticore"
58
+ @options = DEFAULT_OPTIONS.merge(options)
59
+ @client = client
60
+ end
61
+
62
+ def build_client(options)
63
+ uri = "#{options[:protocol]}://#{options[:host]}:#{options[:port]}"
64
+
65
+ client_options = {
66
+ :host => [uri],
67
+ :transport_options => options[:client_settings]
68
+ }
69
+ client_options[:transport_class] = ::Elasticsearch::Transport::Transport::HTTP::Manticore
70
+ client_options[:ssl] = client_options[:transport_options].delete(:ssl)
71
+
72
+ if options[:user] && options[:password] then
73
+ token = Base64.strict_encode64(options[:user] + ":" + options[:password])
74
+ client_options[:headers] = { "Authorization" => "Basic #{token}" }
75
+ end
76
+
77
+ Elasticsearch::Client.new client_options
78
+ end
79
+
80
+ def bulk(actions)
81
+ @client.bulk(:body => actions.collect do |action, args, source|
82
+ if source
83
+ next [ { action => args }, source ]
84
+ else
85
+ next { action => args }
86
+ end
87
+ end.flatten)
88
+ end # def bulk
89
+
90
+ def template_exists?(name)
91
+ @client.indices.get_template(:name => name)
92
+ return true
93
+ rescue Elasticsearch::Transport::Transport::Errors::NotFound
94
+ return false
95
+ end # def template_exists?
96
+
97
+ def template_put(name, template)
98
+ @client.indices.put_template(:name => name, :body => template)
99
+ end # template_put
100
+
101
+ public(:bulk)
102
+ end # class HTTPClient
103
+
104
+ class NodeClient < Base
105
+ private
106
+
107
+ DEFAULT_OPTIONS = {
108
+ :port => 9300,
109
+ }
110
+
111
+ def initialize(options={})
112
+ super
113
+ require "java"
114
+ @options = DEFAULT_OPTIONS.merge(options)
115
+ setup(@options)
116
+ @client = client
117
+ end # def initialize
118
+
119
+ def settings
120
+ return @settings
121
+ end
122
+
123
+ def setup(options={})
124
+ @settings = org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder
125
+ if options[:host]
126
+ @settings.put("discovery.zen.ping.multicast.enabled", false)
127
+ @settings.put("discovery.zen.ping.unicast.hosts", hosts(options))
128
+ end
129
+
130
+ @settings.put("node.client", true)
131
+ @settings.put("http.enabled", false)
132
+
133
+ if options[:client_settings]
134
+ options[:client_settings].each do |key, value|
135
+ @settings.put(key, value)
136
+ end
137
+ end
138
+
139
+ return @settings
140
+ end
141
+
142
+ def hosts(options)
143
+ # http://www.elasticsearch.org/guide/reference/modules/discovery/zen/
144
+ result = Array.new
145
+ if options[:host].class == Array
146
+ options[:host].each do |host|
147
+ if host.to_s =~ /^.+:.+$/
148
+ # For host in format: host:port, ignore options[:port]
149
+ result << host
150
+ else
151
+ if options[:port].to_s =~ /^\d+-\d+$/
152
+ # port ranges are 'host[port1-port2]'
153
+ result << Range.new(*options[:port].split("-")).collect { |p| "#{host}:#{p}" }
154
+ else
155
+ result << "#{host}:#{options[:port]}"
156
+ end
157
+ end
158
+ end
159
+ else
160
+ if options[:host].to_s =~ /^.+:.+$/
161
+ # For host in format: host:port, ignore options[:port]
162
+ result << options[:host]
163
+ else
164
+ if options[:port].to_s =~ /^\d+-\d+$/
165
+ # port ranges are 'host[port1-port2]' according to
166
+ # http://www.elasticsearch.org/guide/reference/modules/discovery/zen/
167
+ # However, it seems to only query the first port.
168
+ # So generate our own list of unicast hosts to scan.
169
+ range = Range.new(*options[:port].split("-"))
170
+ result << range.collect { |p| "#{options[:host]}:#{p}" }
171
+ else
172
+ result << "#{options[:host]}:#{options[:port]}"
173
+ end
174
+ end
175
+ end
176
+ result.flatten.join(",")
177
+ end # def hosts
178
+
179
+ def build_client(options)
180
+ nodebuilder = org.elasticsearch.node.NodeBuilder.nodeBuilder
181
+ return nodebuilder.settings(@settings).node.client
182
+ end # def build_client
183
+
184
+ def bulk(actions)
185
+ # Actions an array of [ action, action_metadata, source ]
186
+ prep = @client.prepareBulk
187
+ actions.each do |action, args, source|
188
+ prep.add(build_request(action, args, source))
189
+ end
190
+ response = prep.execute.actionGet()
191
+
192
+ # TODO(sissel): What format should the response be in?
193
+ end # def bulk
194
+
195
+ def build_request(action, args, source)
196
+ case action
197
+ when "index"
198
+ request = org.elasticsearch.action.index.IndexRequest.new(args[:_index])
199
+ request.id(args[:_id]) if args[:_id]
200
+ request.source(source)
201
+ when "delete"
202
+ request = org.elasticsearch.action.delete.DeleteRequest.new(args[:_index])
203
+ request.id(args[:_id])
204
+ #when "update"
205
+ #when "create"
206
+ end # case action
207
+
208
+ request.type(args[:_type]) if args[:_type]
209
+ return request
210
+ end # def build_request
211
+
212
+ def template_exists?(name)
213
+ request = org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequestBuilder.new(@client.admin.indices, name)
214
+ response = request.get
215
+ return !response.getIndexTemplates.isEmpty
216
+ end # def template_exists?
217
+
218
+ def template_put(name, template)
219
+ request = org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequestBuilder.new(@client.admin.indices, name)
220
+ request.setSource(LogStash::Json.dump(template))
221
+
222
+ # execute the request and get the response, if it fails, we'll get an exception.
223
+ request.get
224
+ end # template_put
225
+
226
+ public(:initialize, :bulk)
227
+ end # class NodeClient
228
+
229
+ class TransportClient < NodeClient
230
+ private
231
+ def build_client(options)
232
+ client = org.elasticsearch.client.transport.TransportClient.new(settings.build)
233
+
234
+ if options[:host]
235
+ client.addTransportAddress(
236
+ org.elasticsearch.common.transport.InetSocketTransportAddress.new(
237
+ options[:host], options[:port].to_i
238
+ )
239
+ )
240
+ end
241
+
242
+ return client
243
+ end # def build_client
244
+ end # class TransportClient
245
+ end # module Protocols
246
+
247
+ module Requests
248
+ class GetIndexTemplates; end
249
+ class Bulk; end
250
+ class Index; end
251
+ class Delete; end
252
+ end
253
+ end
@@ -0,0 +1,42 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-output-elasticsearch'
4
+ s.version = '0.1.8'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Logstash Output to Elasticsearch"
7
+ s.description = "Output events to elasticsearch"
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'info@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
21
+
22
+ # Jar dependencies
23
+ s.requirements << "jar 'org.elasticsearch:elasticsearch', '1.4.0'"
24
+
25
+ # Gem dependencies
26
+ s.add_runtime_dependency 'elasticsearch', ['>= 1.0.6', '~> 1.0']
27
+ s.add_runtime_dependency 'stud', ['>= 0.0.17', '~> 0.0']
28
+ s.add_runtime_dependency 'cabin', ['~> 0.6']
29
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
30
+ s.add_runtime_dependency 'jar-dependencies'
31
+
32
+ s.add_development_dependency 'ftw', ['>= 0.0.40', '~> 0']
33
+ s.add_development_dependency 'logstash-input-generator'
34
+
35
+
36
+ if RUBY_PLATFORM == 'java'
37
+ s.platform = RUBY_PLATFORM
38
+ s.add_runtime_dependency "manticore", '~> 0.3'
39
+ end
40
+
41
+ s.add_development_dependency 'logstash-devutils'
42
+ end
@@ -0,0 +1,517 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "ftw"
3
+ require "logstash/plugin"
4
+ require "logstash/json"
5
+
6
+ describe "outputs/elasticsearch" do
7
+
8
+ it "should register" do
9
+ output = LogStash::Plugin.lookup("output", "elasticsearch").new("embedded" => "false", "protocol" => "transport", "manage_template" => "false")
10
+
11
+ # register will try to load jars and raise if it cannot find jars
12
+ expect {output.register}.to_not raise_error
13
+ end
14
+
15
+ describe "ship lots of events w/ default index_type", :elasticsearch => true do
16
+ # Generate a random index name
17
+ index = 10.times.collect { rand(10).to_s }.join("")
18
+ type = 10.times.collect { rand(10).to_s }.join("")
19
+
20
+ # Write about 10000 events. Add jitter to increase likeliness of finding
21
+ # boundary-related bugs.
22
+ event_count = 10000 + rand(500)
23
+ flush_size = rand(200) + 1
24
+
25
+ config <<-CONFIG
26
+ input {
27
+ generator {
28
+ message => "hello world"
29
+ count => #{event_count}
30
+ type => "#{type}"
31
+ }
32
+ }
33
+ output {
34
+ elasticsearch {
35
+ host => "127.0.0.1"
36
+ index => "#{index}"
37
+ flush_size => #{flush_size}
38
+ }
39
+ }
40
+ CONFIG
41
+
42
+ agent do
43
+ # Try a few times to check if we have the correct number of events stored
44
+ # in ES.
45
+ #
46
+ # We try multiple times to allow final agent flushes as well as allowing
47
+ # elasticsearch to finish processing everything.
48
+ ftw = FTW::Agent.new
49
+ ftw.post!("http://localhost:9200/#{index}/_refresh")
50
+
51
+ # Wait until all events are available.
52
+ Stud::try(10.times) do
53
+ data = ""
54
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_count?q=*")
55
+ response.read_body { |chunk| data << chunk }
56
+ result = LogStash::Json.load(data)
57
+ count = result["count"]
58
+ insist { count } == event_count
59
+ end
60
+
61
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_search?q=*&size=1000")
62
+ data = ""
63
+ response.read_body { |chunk| data << chunk }
64
+ result = LogStash::Json.load(data)
65
+ result["hits"]["hits"].each do |doc|
66
+ # With no 'index_type' set, the document type should be the type
67
+ # set on the input
68
+ insist { doc["_type"] } == type
69
+ insist { doc["_index"] } == index
70
+ insist { doc["_source"]["message"] } == "hello world"
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "testing index_type", :elasticsearch => true do
76
+ describe "no type value" do
77
+ # Generate a random index name
78
+ index = 10.times.collect { rand(10).to_s }.join("")
79
+ event_count = 100 + rand(100)
80
+ flush_size = rand(200) + 1
81
+
82
+ config <<-CONFIG
83
+ input {
84
+ generator {
85
+ message => "hello world"
86
+ count => #{event_count}
87
+ }
88
+ }
89
+ output {
90
+ elasticsearch {
91
+ host => "127.0.0.1"
92
+ index => "#{index}"
93
+ flush_size => #{flush_size}
94
+ }
95
+ }
96
+ CONFIG
97
+
98
+ agent do
99
+ ftw = FTW::Agent.new
100
+ ftw.post!("http://localhost:9200/#{index}/_refresh")
101
+
102
+ # Wait until all events are available.
103
+ Stud::try(10.times) do
104
+ data = ""
105
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_count?q=*")
106
+ response.read_body { |chunk| data << chunk }
107
+ result = LogStash::Json.load(data)
108
+ count = result["count"]
109
+ insist { count } == event_count
110
+ end
111
+
112
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_search?q=*&size=1000")
113
+ data = ""
114
+ response.read_body { |chunk| data << chunk }
115
+ result = LogStash::Json.load(data)
116
+ result["hits"]["hits"].each do |doc|
117
+ insist { doc["_type"] } == "logs"
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "default event type value" do
123
+ # Generate a random index name
124
+ index = 10.times.collect { rand(10).to_s }.join("")
125
+ event_count = 100 + rand(100)
126
+ flush_size = rand(200) + 1
127
+
128
+ config <<-CONFIG
129
+ input {
130
+ generator {
131
+ message => "hello world"
132
+ count => #{event_count}
133
+ type => "generated"
134
+ }
135
+ }
136
+ output {
137
+ elasticsearch {
138
+ host => "127.0.0.1"
139
+ index => "#{index}"
140
+ flush_size => #{flush_size}
141
+ }
142
+ }
143
+ CONFIG
144
+
145
+ agent do
146
+ ftw = FTW::Agent.new
147
+ ftw.post!("http://localhost:9200/#{index}/_refresh")
148
+
149
+ # Wait until all events are available.
150
+ Stud::try(10.times) do
151
+ data = ""
152
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_count?q=*")
153
+ response.read_body { |chunk| data << chunk }
154
+ result = LogStash::Json.load(data)
155
+ count = result["count"]
156
+ insist { count } == event_count
157
+ end
158
+
159
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_search?q=*&size=1000")
160
+ data = ""
161
+ response.read_body { |chunk| data << chunk }
162
+ result = LogStash::Json.load(data)
163
+ result["hits"]["hits"].each do |doc|
164
+ insist { doc["_type"] } == "generated"
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ describe "action => ...", :elasticsearch => true do
171
+ index_name = 10.times.collect { rand(10).to_s }.join("")
172
+
173
+ config <<-CONFIG
174
+ input {
175
+ generator {
176
+ message => "hello world"
177
+ count => 100
178
+ }
179
+ }
180
+ output {
181
+ elasticsearch {
182
+ host => "127.0.0.1"
183
+ index => "#{index_name}"
184
+ }
185
+ }
186
+ CONFIG
187
+
188
+
189
+ agent do
190
+ ftw = FTW::Agent.new
191
+ ftw.post!("http://localhost:9200/#{index_name}/_refresh")
192
+
193
+ # Wait until all events are available.
194
+ Stud::try(10.times) do
195
+ data = ""
196
+ response = ftw.get!("http://127.0.0.1:9200/#{index_name}/_count?q=*")
197
+ response.read_body { |chunk| data << chunk }
198
+ result = LogStash::Json.load(data)
199
+ count = result["count"]
200
+ insist { count } == 100
201
+ end
202
+
203
+ response = ftw.get!("http://127.0.0.1:9200/#{index_name}/_search?q=*&size=1000")
204
+ data = ""
205
+ response.read_body { |chunk| data << chunk }
206
+ result = LogStash::Json.load(data)
207
+ result["hits"]["hits"].each do |doc|
208
+ insist { doc["_type"] } == "logs"
209
+ end
210
+ end
211
+
212
+ describe "default event type value", :elasticsearch => true do
213
+ # Generate a random index name
214
+ index = 10.times.collect { rand(10).to_s }.join("")
215
+ event_count = 100 + rand(100)
216
+ flush_size = rand(200) + 1
217
+
218
+ config <<-CONFIG
219
+ input {
220
+ generator {
221
+ message => "hello world"
222
+ count => #{event_count}
223
+ type => "generated"
224
+ }
225
+ }
226
+ output {
227
+ elasticsearch {
228
+ host => "127.0.0.1"
229
+ index => "#{index}"
230
+ flush_size => #{flush_size}
231
+ }
232
+ }
233
+ CONFIG
234
+
235
+ agent do
236
+ ftw = FTW::Agent.new
237
+ ftw.post!("http://localhost:9200/#{index}/_refresh")
238
+
239
+ # Wait until all events are available.
240
+ Stud::try(10.times) do
241
+ data = ""
242
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_count?q=*")
243
+ response.read_body { |chunk| data << chunk }
244
+ result = LogStash::Json.load(data)
245
+ count = result["count"]
246
+ insist { count } == event_count
247
+ end
248
+
249
+ response = ftw.get!("http://127.0.0.1:9200/#{index}/_search?q=*&size=1000")
250
+ data = ""
251
+ response.read_body { |chunk| data << chunk }
252
+ result = LogStash::Json.load(data)
253
+ result["hits"]["hits"].each do |doc|
254
+ insist { doc["_type"] } == "generated"
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ describe "wildcard substitution in index templates", :todo => true do
261
+ require "logstash/outputs/elasticsearch"
262
+
263
+ let(:template) { '{"template" : "not important, will be updated by :index"}' }
264
+
265
+ def settings_with_index(index)
266
+ return {
267
+ "manage_template" => true,
268
+ "template_overwrite" => true,
269
+ "protocol" => "http",
270
+ "host" => "localhost",
271
+ "index" => "#{index}"
272
+ }
273
+ end
274
+
275
+ it "should substitude placeholders" do
276
+ IO.stub(:read).with(anything) { template }
277
+ es_output = LogStash::Outputs::ElasticSearch.new(settings_with_index("index-%{YYYY}"))
278
+ insist { es_output.get_template['template'] } == "index-*"
279
+ end
280
+
281
+ it "should do nothing to an index with no placeholder" do
282
+ IO.stub(:read).with(anything) { template }
283
+ es_output = LogStash::Outputs::ElasticSearch.new(settings_with_index("index"))
284
+ insist { es_output.get_template['template'] } == "index"
285
+ end
286
+ end
287
+
288
+ describe "index template expected behavior", :elasticsearch => true do
289
+ ["node", "transport", "http"].each do |protocol|
290
+ context "with protocol => #{protocol}" do
291
+ subject do
292
+ require "logstash/outputs/elasticsearch"
293
+ settings = {
294
+ "manage_template" => true,
295
+ "template_overwrite" => true,
296
+ "protocol" => protocol,
297
+ "host" => "localhost"
298
+ }
299
+ next LogStash::Outputs::ElasticSearch.new(settings)
300
+ end
301
+
302
+ before :each do
303
+ # Delete all templates first.
304
+ require "elasticsearch"
305
+
306
+ # Clean ES of data before we start.
307
+ @es = Elasticsearch::Client.new
308
+ @es.indices.delete_template(:name => "*")
309
+
310
+ # This can fail if there are no indexes, ignore failure.
311
+ @es.indices.delete(:index => "*") rescue nil
312
+
313
+ subject.register
314
+
315
+ subject.receive(LogStash::Event.new("message" => "sample message here"))
316
+ subject.receive(LogStash::Event.new("somevalue" => 100))
317
+ subject.receive(LogStash::Event.new("somevalue" => 10))
318
+ subject.receive(LogStash::Event.new("somevalue" => 1))
319
+ subject.receive(LogStash::Event.new("country" => "us"))
320
+ subject.receive(LogStash::Event.new("country" => "at"))
321
+ subject.receive(LogStash::Event.new("geoip" => { "location" => [ 0.0, 0.0 ] }))
322
+ subject.buffer_flush(:final => true)
323
+ @es.indices.refresh
324
+
325
+ # Wait or fail until everything's indexed.
326
+ Stud::try(20.times) do
327
+ r = @es.search
328
+ insist { r["hits"]["total"] } == 7
329
+ end
330
+ end
331
+
332
+ it "permits phrase searching on string fields" do
333
+ results = @es.search(:q => "message:\"sample message\"")
334
+ insist { results["hits"]["total"] } == 1
335
+ insist { results["hits"]["hits"][0]["_source"]["message"] } == "sample message here"
336
+ end
337
+
338
+ it "numbers dynamically map to a numeric type and permit range queries" do
339
+ results = @es.search(:q => "somevalue:[5 TO 105]")
340
+ insist { results["hits"]["total"] } == 2
341
+
342
+ values = results["hits"]["hits"].collect { |r| r["_source"]["somevalue"] }
343
+ insist { values }.include?(10)
344
+ insist { values }.include?(100)
345
+ reject { values }.include?(1)
346
+ end
347
+
348
+ it "creates .raw field fro any string field which is not_analyzed" do
349
+ results = @es.search(:q => "message.raw:\"sample message here\"")
350
+ insist { results["hits"]["total"] } == 1
351
+ insist { results["hits"]["hits"][0]["_source"]["message"] } == "sample message here"
352
+
353
+ # partial or terms should not work.
354
+ results = @es.search(:q => "message.raw:\"sample\"")
355
+ insist { results["hits"]["total"] } == 0
356
+ end
357
+
358
+ it "make [geoip][location] a geo_point" do
359
+ results = @es.search(:body => { "filter" => { "geo_distance" => { "distance" => "1000km", "geoip.location" => { "lat" => 0.5, "lon" => 0.5 } } } })
360
+ insist { results["hits"]["total"] } == 1
361
+ insist { results["hits"]["hits"][0]["_source"]["geoip"]["location"] } == [ 0.0, 0.0 ]
362
+ end
363
+
364
+ it "should index stopwords like 'at' " do
365
+ results = @es.search(:body => { "facets" => { "t" => { "terms" => { "field" => "country" } } } })["facets"]["t"]
366
+ terms = results["terms"].collect { |t| t["term"] }
367
+
368
+ insist { terms }.include?("us")
369
+
370
+ # 'at' is a stopword, make sure stopwords are not ignored.
371
+ insist { terms }.include?("at")
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ describe "elasticsearch protocol", :elasticsearch => true do
378
+ # ElasticSearch related jars
379
+ #LogStash::Environment.load_elasticsearch_jars!
380
+ # Load elasticsearch protocol
381
+ require "logstash/outputs/elasticsearch/protocol"
382
+
383
+ describe "elasticsearch node client" do
384
+ # Test ElasticSearch Node Client
385
+ # Reference: http://www.elasticsearch.org/guide/reference/modules/discovery/zen/
386
+
387
+ it "should support hosts in both string and array" do
388
+ # Because we defined *hosts* method in NodeClient as private,
389
+ # we use *obj.send :method,[args...]* to call method *hosts*
390
+ client = LogStash::Outputs::Elasticsearch::Protocols::NodeClient.new
391
+
392
+ # Node client should support host in string
393
+ # Case 1: default :host in string
394
+ insist { client.send :hosts, :host => "host",:port => 9300 } == "host:9300"
395
+ # Case 2: :port =~ /^\d+_\d+$/
396
+ insist { client.send :hosts, :host => "host",:port => "9300-9302"} == "host:9300,host:9301,host:9302"
397
+ # Case 3: :host =~ /^.+:.+$/
398
+ insist { client.send :hosts, :host => "host:9303",:port => 9300 } == "host:9303"
399
+ # Case 4: :host =~ /^.+:.+$/ and :port =~ /^\d+_\d+$/
400
+ insist { client.send :hosts, :host => "host:9303",:port => "9300-9302"} == "host:9303"
401
+
402
+ # Node client should support host in array
403
+ # Case 5: :host in array with single item
404
+ insist { client.send :hosts, :host => ["host"],:port => 9300 } == ("host:9300")
405
+ # Case 6: :host in array with more than one items
406
+ insist { client.send :hosts, :host => ["host1","host2"],:port => 9300 } == "host1:9300,host2:9300"
407
+ # Case 7: :host in array with more than one items and :port =~ /^\d+_\d+$/
408
+ insist { client.send :hosts, :host => ["host1","host2"],:port => "9300-9302" } == "host1:9300,host1:9301,host1:9302,host2:9300,host2:9301,host2:9302"
409
+ # Case 8: :host in array with more than one items and some :host =~ /^.+:.+$/
410
+ insist { client.send :hosts, :host => ["host1","host2:9303"],:port => 9300 } == "host1:9300,host2:9303"
411
+ # Case 9: :host in array with more than one items, :port =~ /^\d+_\d+$/ and some :host =~ /^.+:.+$/
412
+ insist { client.send :hosts, :host => ["host1","host2:9303"],:port => "9300-9302" } == "host1:9300,host1:9301,host1:9302,host2:9303"
413
+ end
414
+ end
415
+ end
416
+
417
+ describe "Authentication option" do
418
+ ["node", "transport"].each do |protocol|
419
+ context "with protocol => #{protocol}" do
420
+ subject do
421
+ require "logstash/outputs/elasticsearch"
422
+ settings = {
423
+ "protocol" => protocol,
424
+ "node_name" => "logstash",
425
+ "cluster" => "elasticsearch",
426
+ "host" => "node01",
427
+ "user" => "test",
428
+ "password" => "test"
429
+ }
430
+ next LogStash::Outputs::ElasticSearch.new(settings)
431
+ end
432
+
433
+ it "should fail in register" do
434
+ expect {subject.register}.to raise_error
435
+ end
436
+ end
437
+ end
438
+ end
439
+
440
+ describe "SSL option" do
441
+ ["node", "transport"].each do |protocol|
442
+ context "with protocol => #{protocol}" do
443
+ subject do
444
+ require "logstash/outputs/elasticsearch"
445
+ settings = {
446
+ "protocol" => protocol,
447
+ "node_name" => "logstash",
448
+ "cluster" => "elasticsearch",
449
+ "host" => "node01",
450
+ "ssl" => true
451
+ }
452
+ next LogStash::Outputs::ElasticSearch.new(settings)
453
+ end
454
+
455
+ it "should fail in register" do
456
+ expect {subject.register}.to raise_error
457
+ end
458
+ end
459
+ end
460
+ end
461
+
462
+ describe "send messages to ElasticSearch using HTTPS", :elasticsearch_secure => true do
463
+ subject do
464
+ require "logstash/outputs/elasticsearch"
465
+ settings = {
466
+ "protocol" => "http",
467
+ "node_name" => "logstash",
468
+ "cluster" => "elasticsearch",
469
+ "host" => "node01",
470
+ "user" => "user",
471
+ "password" => "changeme",
472
+ "ssl" => true,
473
+ "cacert" => "/tmp/ca/certs/cacert.pem",
474
+ # or
475
+ #"truststore" => "/tmp/ca/truststore.jks",
476
+ #"truststore_password" => "testeteste"
477
+ }
478
+ next LogStash::Outputs::ElasticSearch.new(settings)
479
+ end
480
+
481
+ before :each do
482
+ subject.register
483
+ end
484
+
485
+ it "sends events to ES" do
486
+ expect {
487
+ subject.receive(LogStash::Event.new("message" => "sample message here"))
488
+ subject.buffer_flush(:final => true)
489
+ }.to_not raise_error
490
+ end
491
+ end
492
+
493
+ describe "connect using HTTP Authentication", :elasticsearch_secure => true do
494
+ subject do
495
+ require "logstash/outputs/elasticsearch"
496
+ settings = {
497
+ "protocol" => "http",
498
+ "cluster" => "elasticsearch",
499
+ "host" => "node01",
500
+ "user" => "user",
501
+ "password" => "changeme",
502
+ }
503
+ next LogStash::Outputs::ElasticSearch.new(settings)
504
+ end
505
+
506
+ before :each do
507
+ subject.register
508
+ end
509
+
510
+ it "sends events to ES" do
511
+ expect {
512
+ subject.receive(LogStash::Event.new("message" => "sample message here"))
513
+ subject.buffer_flush(:final => true)
514
+ }.to_not raise_error
515
+ end
516
+ end
517
+ end