logstash-output-elasticsearch 5.4.1-java → 6.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
6
6
 
7
7
  def initialize(response_code, url, body)
8
8
  @response_code = response_code
9
- @url = ::LogStash::Outputs::ElasticSearch::SafeURL.without_credentials(url)
9
+ @url = url
10
10
  @body = body
11
11
  end
12
12
 
@@ -19,7 +19,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
19
19
 
20
20
  def initialize(original_error, url)
21
21
  @original_error = original_error
22
- @url = ::LogStash::Outputs::ElasticSearch::SafeURL.without_credentials(url)
22
+ @url = url
23
23
  end
24
24
 
25
25
  def message
@@ -27,13 +27,12 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
27
27
  end
28
28
  end
29
29
 
30
- attr_reader :logger, :adapter, :sniffing, :sniffer_delay, :resurrect_delay, :auth, :healthcheck_path
30
+ attr_reader :logger, :adapter, :sniffing, :sniffer_delay, :resurrect_delay, :healthcheck_path
31
31
 
32
32
  DEFAULT_OPTIONS = {
33
33
  :healthcheck_path => '/'.freeze,
34
34
  :scheme => 'http',
35
35
  :resurrect_delay => 5,
36
- :auth => nil, # Can be set to {:user => 'user', :password => 'pass'}
37
36
  :sniffing => false,
38
37
  :sniffer_delay => 10,
39
38
  }.freeze
@@ -41,30 +40,27 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
41
40
  def initialize(logger, adapter, initial_urls=[], options={})
42
41
  @logger = logger
43
42
  @adapter = adapter
44
-
43
+ @initial_urls = initial_urls
44
+
45
+ raise ArgumentError, "No URL Normalizer specified!" unless options[:url_normalizer]
46
+ @url_normalizer = options[:url_normalizer]
45
47
  DEFAULT_OPTIONS.merge(options).tap do |merged|
46
48
  @healthcheck_path = merged[:healthcheck_path]
47
- @scheme = merged[:scheme]
48
49
  @resurrect_delay = merged[:resurrect_delay]
49
- @auth = merged[:auth]
50
50
  @sniffing = merged[:sniffing]
51
51
  @sniffer_delay = merged[:sniffer_delay]
52
52
  end
53
53
 
54
- # Override the scheme if one is explicitly set in urls
55
- if initial_urls.any? {|u| u.scheme == 'https'} && @scheme == 'http'
56
- raise ArgumentError, "HTTP was set as scheme, but an HTTPS URL was passed in!"
57
- end
58
-
59
54
  # Used for all concurrent operations in this class
60
55
  @state_mutex = Mutex.new
61
56
 
62
57
  # Holds metadata about all URLs
63
58
  @url_info = {}
64
59
  @stopping = false
65
-
66
- update_urls(initial_urls)
67
-
60
+ end
61
+
62
+ def start
63
+ update_urls(@initial_urls)
68
64
  start_resurrectionist
69
65
  start_sniffer if @sniffing
70
66
  end
@@ -204,7 +200,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
204
200
  if matches
205
201
  host = matches[1].empty? ? matches[2] : matches[1]
206
202
  port = matches[3]
207
- LogStash::Util::SafeURI.new("#{host}:#{port}")
203
+ ::LogStash::Util::SafeURI.new("#{host}:#{port}")
208
204
  end
209
205
  end.compact
210
206
  end
@@ -228,26 +224,25 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
228
224
  def healthcheck!
229
225
  # Try to keep locking granularity low such that we don't affect IO...
230
226
  @state_mutex.synchronize { @url_info.select {|url,meta| meta[:state] != :alive } }.each do |url,meta|
231
- safe_url = ::LogStash::Outputs::ElasticSearch::SafeURL.without_credentials(url)
232
227
  begin
233
228
  logger.info("Running health check to see if an Elasticsearch connection is working",
234
- url: safe_url, healthcheck_path: @healthcheck_path)
235
- response = perform_request_to_url(url, "HEAD", @healthcheck_path)
229
+ url: url, healthcheck_path: healthcheck_path)
230
+ response = perform_request_to_url(url, :head, healthcheck_path)
236
231
  # If no exception was raised it must have succeeded!
237
- logger.warn("Restored connection to ES instance", :url => safe_url)
232
+ logger.warn("Restored connection to ES instance", :url => url.sanitized)
238
233
  @state_mutex.synchronize { meta[:state] = :alive }
239
234
  rescue HostUnreachableError, BadResponseCodeError => e
240
- logger.warn("Attempted to resurrect connection to dead ES instance, but got an error.", url: safe_url, error_type: e.class, error: e.message)
235
+ logger.warn("Attempted to resurrect connection to dead ES instance, but got an error.", url: url.sanitized, error_type: e.class, error: e.message)
241
236
  end
242
237
  end
243
238
  end
244
239
 
245
240
  def stop_resurrectionist
246
- @resurrectionist.join
241
+ @resurrectionist.join if @resurrectionist
247
242
  end
248
243
 
249
244
  def resurrectionist_alive?
250
- @resurrectionist.alive?
245
+ @resurrectionist ? @resurrectionist.alive? : nil
251
246
  end
252
247
 
253
248
  def perform_request(method, path, params={}, body=nil)
@@ -270,18 +265,11 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
270
265
  end
271
266
 
272
267
  def normalize_url(uri)
273
- raise ArgumentError, "Only URI/SafeURI objects may be passed in!" unless uri.is_a?(URI) || uri.is_a?(LogStash::Util::SafeURI)
274
- uri = uri.clone
275
-
276
- # Set credentials if need be
277
- if @auth && !uri.user
278
- uri.user ||= @auth[:user]
279
- uri.password ||= @auth[:password]
268
+ u = @url_normalizer.call(uri)
269
+ if !u.is_a?(::LogStash::Util::SafeURI)
270
+ raise "URL Normalizer returned a '#{u.class}' rather than a SafeURI! This shouldn't happen!"
280
271
  end
281
-
282
- uri.scheme = @scheme
283
-
284
- uri
272
+ u
285
273
  end
286
274
 
287
275
  def update_urls(new_urls)
@@ -313,7 +301,7 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
313
301
 
314
302
  if state_changes[:removed].size > 0 || state_changes[:added].size > 0
315
303
  if logger.info?
316
- logger.info("Elasticsearch pool URLs updated", :changes => safe_state_changes(state_changes))
304
+ logger.info("Elasticsearch pool URLs updated", :changes => state_changes)
317
305
  end
318
306
  end
319
307
 
@@ -324,14 +312,6 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
324
312
  healthcheck!
325
313
  end
326
314
 
327
- def safe_state_changes(state_changes)
328
- state_changes.reduce({}) do |acc, kv|
329
- k,v = kv
330
- acc[k] = v.map(&LogStash::Outputs::ElasticSearch::SafeURL.method(:without_credentials)).map(&:to_s)
331
- acc
332
- end
333
- end
334
-
335
315
  def size
336
316
  @state_mutex.synchronize { @url_info.size }
337
317
  end
@@ -378,10 +358,9 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
378
358
  meta = @url_info[url]
379
359
  # In case a sniff happened removing the metadata just before there's nothing to mark
380
360
  # This is an extreme edge case, but it can happen!
381
- return unless meta
382
- safe_url = ::LogStash::Outputs::ElasticSearch::SafeURL.without_credentials(url)
383
- logger.warn("Marking url as dead.", :reason => error.message, :url => safe_url,
384
- :error_message => error.message, :error_class => error.class.name)
361
+ return unless meta
362
+ logger.warn("Marking url as dead. Last error: [#{error.class}] #{error.message}",
363
+ :url => url, :error_message => error.message, :error_class => error.class.name)
385
364
  meta[:state] = :dead
386
365
  meta[:last_error] = error
387
366
  meta[:last_errored_at] = Time.now
@@ -6,7 +6,9 @@ module LogStash; module Outputs; class ElasticSearch;
6
6
  :pool_max_per_route => params["pool_max_per_route"],
7
7
  :check_connection_timeout => params["validate_after_inactivity"]
8
8
  }
9
-
9
+
10
+ client_settings[:proxy] = params["proxy"] if params["proxy"]
11
+
10
12
  common_options = {
11
13
  :client_settings => client_settings,
12
14
  :resurrect_delay => params["resurrect_delay"],
@@ -31,7 +33,6 @@ module LogStash; module Outputs; class ElasticSearch;
31
33
  logger.debug? && logger.debug("Normalizing http path", :path => params["path"], :normalized => client_settings[:path])
32
34
 
33
35
  client_settings.merge! setup_ssl(logger, params)
34
- client_settings.merge! setup_proxy(logger, params)
35
36
  common_options.merge! setup_basic_auth(logger, params)
36
37
 
37
38
  # Update API setup
@@ -59,25 +60,8 @@ module LogStash; module Outputs; class ElasticSearch;
59
60
  )
60
61
  end
61
62
 
62
- def self.setup_proxy(logger, params)
63
- proxy = params["proxy"]
64
- return {} unless proxy
65
-
66
- # Symbolize keys
67
- proxy = if proxy.is_a?(Hash)
68
- Hash[proxy.map {|k,v| [k.to_sym, v]}]
69
- elsif proxy.is_a?(String)
70
- proxy
71
- else
72
- raise LogStash::ConfigurationError, "Expected 'proxy' to be a string or hash, not '#{proxy}''!"
73
- end
74
-
75
- return {:proxy => proxy}
76
- end
77
-
78
63
  def self.setup_ssl(logger, params)
79
- # If we have HTTPS hosts we act like SSL is enabled
80
- params["ssl"] = true if params["hosts"].any? {|h| h.start_with?("https://")}
64
+ params["ssl"] = true if params["hosts"].any? {|h| h.scheme == "https" }
81
65
  return {} if params["ssl"].nil?
82
66
 
83
67
  return {:ssl => {:enabled => false}} if params["ssl"] == false
@@ -8,7 +8,7 @@ module LogStash; module Outputs; class ElasticSearch
8
8
  plugin.logger.info("Attempting to install template", :manage_template => template)
9
9
  install(plugin.client, plugin.template_name, template, plugin.template_overwrite)
10
10
  rescue => e
11
- plugin.logger.error("Failed to install template.", :message => e.message, :class => e.class.name)
11
+ plugin.logger.error("Failed to install template.", :message => e.message, :class => e.class.name, :backtrace => e.backtrace)
12
12
  end
13
13
 
14
14
  private
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-output-elasticsearch'
4
- s.version = '5.4.1'
4
+ s.version = '6.0.0'
5
5
  s.licenses = ['apache-2.0']
6
6
  s.summary = "Logstash Output to Elasticsearch"
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"
@@ -7,10 +7,14 @@ describe "pool sniffer", :integration => true do
7
7
  let(:logger) { Cabin::Channel.get }
8
8
  let(:adapter) { LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(logger) }
9
9
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://#{get_host_port}")] }
10
- let(:options) { {:resurrect_delay => 2} } # Shorten the delay a bit to speed up tests
10
+ let(:options) { {:resurrect_delay => 2, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests
11
11
 
12
12
  subject { LogStash::Outputs::ElasticSearch::HttpClient::Pool.new(logger, adapter, initial_urls, options) }
13
13
 
14
+ before do
15
+ subject.start
16
+ end
17
+
14
18
  shared_examples("sniff parsing") do |check_exact|
15
19
  it "should execute a sniff without error" do
16
20
  expect { subject.check_sniff }.not_to raise_error
@@ -9,16 +9,39 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter do
9
9
 
10
10
  it "should raise an exception if requests are issued after close" do
11
11
  subject.close
12
- expect { subject.perform_request("http://localhost:9200", :get, '/') }.to raise_error(::Manticore::ClientStoppedException)
12
+ expect { subject.perform_request(::LogStash::Util::SafeURI.new("http://localhost:9200"), :get, '/') }.to raise_error(::Manticore::ClientStoppedException)
13
13
  end
14
14
 
15
15
  it "should implement host unreachable exceptions" do
16
16
  expect(subject.host_unreachable_exceptions).to be_a(Array)
17
17
  end
18
+
19
+ describe "auth" do
20
+ let(:user) { "myuser" }
21
+ let(:password) { "mypassword" }
22
+ let(:noauth_uri) { clone = uri.uri.clone;clone.user=nil; clone.password=nil; clone.to_s }
23
+ let(:uri) { ::LogStash::Util::SafeURI.new("http://#{user}:#{password}@localhost:9200") }
24
+
25
+ it "should convert the auth to params" do
26
+ resp = double("response")
27
+ allow(resp).to receive(:call)
28
+ allow(resp).to receive(:code).and_return(200)
29
+ expect(subject.manticore).to receive(:get).
30
+ with(noauth_uri, {
31
+ :auth => {
32
+ :user => user,
33
+ :password => password,
34
+ :eager => true
35
+ }
36
+ }).and_return resp
37
+
38
+ subject.perform_request(uri, :get, "/")
39
+ end
40
+ end
18
41
 
19
42
  describe "integration specs", :integration => true do
20
43
  it "should perform correct tests without error" do
21
- resp = subject.perform_request("http://localhost:9200", :get, "/")
44
+ resp = subject.perform_request(::LogStash::Util::SafeURI.new("http://localhost:9200"), :get, "/")
22
45
  expect(resp.code).to eql(200)
23
46
  end
24
47
  end
@@ -5,15 +5,30 @@ require "json"
5
5
  describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
6
6
  let(:logger) { Cabin::Channel.get }
7
7
  let(:adapter) { LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(logger) }
8
- let(:initial_urls) { [URI.parse("http://localhost:9200")] }
9
- let(:options) { {:resurrect_delay => 2} } # Shorten the delay a bit to speed up tests
8
+ let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
9
+ let(:options) { {:resurrect_delay => 2, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests
10
10
 
11
11
  subject { described_class.new(logger, adapter, initial_urls, options) }
12
12
 
13
+ let(:manticore_double) { double("manticore a") }
13
14
  before do
14
- allow(adapter).to receive(:perform_request).with(anything, 'HEAD', subject.healthcheck_path, {}, nil)
15
- end
15
+ allow(adapter).to receive(:perform_request).with(anything, :head, subject.healthcheck_path, {}, nil)
16
+
17
+
18
+ response_double = double("manticore response").as_null_object
19
+ # Allow healtchecks
20
+ allow(manticore_double).to receive(:head).with(any_args).and_return(response_double)
21
+ allow(manticore_double).to receive(:get).with(any_args).and_return(response_double)
22
+
23
+ allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
16
24
 
25
+ subject.start
26
+ end
27
+
28
+ after do
29
+ subject.close
30
+ end
31
+
17
32
  describe "initialization" do
18
33
  it "should be successful" do
19
34
  expect { subject }.not_to raise_error
@@ -51,6 +66,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
51
66
  allow(adapter).to receive(:close).and_call_original
52
67
  allow(subject).to receive(:wait_for_in_use_connections).and_call_original
53
68
  allow(subject).to receive(:in_use_connections).and_return([subject.empty_url_meta()],[])
69
+ allow(subject).to receive(:start)
54
70
  subject.close
55
71
  end
56
72
 
@@ -73,33 +89,18 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
73
89
  end
74
90
  end
75
91
 
76
- describe "safe_state_changes" do
77
- let(:state_changes) do
78
- {
79
- :added => [URI.parse("http://sekretu:sekretp@foo1")],
80
- :removed => [URI.parse("http://sekretu:sekretp@foo2")]
81
- }
82
- end
83
- let(:processed) { subject.safe_state_changes(state_changes)}
84
-
85
- it "should hide passwords" do
86
- expect(processed[:added].any? {|p| p =~ /sekretp/ }).to be false
87
- expect(processed[:removed].any? {|p| p =~ /sekretp/ }).to be false
88
- end
89
- end
90
-
91
92
  describe "connection management" do
92
93
  context "with only one URL in the list" do
93
94
  it "should use the only URL in 'with_connection'" do
94
95
  subject.with_connection do |c|
95
- expect(c).to eql(initial_urls.first)
96
+ expect(c).to eq(initial_urls.first)
96
97
  end
97
98
  end
98
99
  end
99
100
 
100
101
  context "with multiple URLs in the list" do
101
- let(:initial_urls) { [ URI.parse("http://localhost:9200"), URI.parse("http://localhost:9201"), URI.parse("http://localhost:9202") ] }
102
-
102
+ let(:initial_urls) { [ ::LogStash::Util::SafeURI.new("http://localhost:9200"), ::LogStash::Util::SafeURI.new("http://localhost:9201"), ::LogStash::Util::SafeURI.new("http://localhost:9202") ] }
103
+
103
104
  it "should minimize the number of connections to a single URL" do
104
105
  connected_urls = []
105
106
 
@@ -122,7 +123,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
122
123
 
123
124
  # The resurrectionist will call this to check on the backend
124
125
  response = double("response")
125
- expect(adapter).to receive(:perform_request).with(u, 'HEAD', subject.healthcheck_path, {}, nil).and_return(response)
126
+ expect(adapter).to receive(:perform_request).with(u, :head, subject.healthcheck_path, {}, nil).and_return(response)
126
127
 
127
128
  subject.return_connection(u)
128
129
  subject.mark_dead(u, Exception.new)
@@ -3,7 +3,19 @@ require "logstash/outputs/elasticsearch/http_client"
3
3
  require "java"
4
4
 
5
5
  describe LogStash::Outputs::ElasticSearch::HttpClient do
6
- let(:base_options) { {:hosts => ["127.0.0.1"], :logger => Cabin::Channel.get} }
6
+ let(:ssl) { nil }
7
+ let(:base_options) do
8
+ opts = {
9
+ :hosts => [::LogStash::Util::SafeURI.new("127.0.0.1")],
10
+ :logger => Cabin::Channel.get
11
+ }
12
+
13
+ if !ssl.nil? # Shortcut to set this
14
+ opts[:client_settings] = {:ssl => {:enabled => ssl}}
15
+ end
16
+
17
+ opts
18
+ end
7
19
 
8
20
  describe "Host/URL Parsing" do
9
21
  subject { described_class.new(base_options) }
@@ -12,71 +24,83 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
12
24
  let(:ipv6_hostname) { "[::1]" }
13
25
  let(:ipv4_hostname) { "127.0.0.1" }
14
26
  let(:port) { 9202 }
15
- let(:hostname_port) { "#{hostname}:#{port}"}
16
- let(:http_hostname_port) { "http://#{hostname_port}"}
17
- let(:https_hostname_port) { "https://#{hostname_port}"}
18
- let(:http_hostname_port_path) { "http://#{hostname_port}/path"}
19
-
27
+ let(:hostname_port) { "#{hostname}:#{port}" }
28
+ let(:hostname_port_uri) { ::LogStash::Util::SafeURI.new("//#{hostname_port}") }
29
+ let(:http_hostname_port) { ::LogStash::Util::SafeURI.new("http://#{hostname_port}") }
30
+ let(:https_hostname_port) { ::LogStash::Util::SafeURI.new("https://#{hostname_port}") }
31
+ let(:http_hostname_port_path) { ::LogStash::Util::SafeURI.new("http://#{hostname_port}/path") }
32
+
20
33
  shared_examples("proper host handling") do
21
34
  it "should properly transform a host:port string to a URL" do
22
- expect(subject.send(:host_to_url, hostname_port).to_s).to eql(http_hostname_port)
23
- end
24
-
25
- it "should raise an error when a partial URL is an invalid format" do
26
- expect {
27
- subject.send(:host_to_url, "#{hostname_port}/")
28
- }.to raise_error(LogStash::ConfigurationError)
35
+ expect(subject.host_to_url(hostname_port_uri)).to eq(http_hostname_port)
29
36
  end
30
37
 
31
38
  it "should not raise an error with a / for a path" do
32
- expect(subject.send(:host_to_url, "#{http_hostname_port}/").to_s).to eql("#{http_hostname_port}/")
39
+ expect(subject.host_to_url(::LogStash::Util::SafeURI.new("#{http_hostname_port}/"))).to eq(LogStash::Util::SafeURI.new("#{http_hostname_port}/"))
33
40
  end
34
41
 
35
42
  it "should parse full URLs correctly" do
36
- expect(subject.send(:host_to_url, http_hostname_port).to_s).to eql(http_hostname_port)
37
- end
38
-
39
- it "should reject full URLs with usernames and passwords" do
40
- expect {
41
- subject.send(:host_to_url, "http://user:password@host.domain")
42
- }.to raise_error(LogStash::ConfigurationError)
43
+ expect(subject.host_to_url(http_hostname_port)).to eq(http_hostname_port)
43
44
  end
44
45
 
45
46
  describe "ssl" do
46
- it "should refuse to handle an http url when ssl is true" do
47
- expect {
48
- subject.send(:host_to_url, http_hostname_port, true)
49
- }.to raise_error(LogStash::ConfigurationError)
47
+ context "when SSL is true" do
48
+ let(:ssl) { true }
49
+ let(:base_options) { super.merge(:hosts => [http_hostname_port]) }
50
+
51
+ it "should refuse to handle an http url" do
52
+ expect {
53
+ subject.host_to_url(http_hostname_port)
54
+ }.to raise_error(LogStash::ConfigurationError)
55
+ end
50
56
  end
51
57
 
52
- it "should refuse to handle an https url when ssl is false" do
53
- expect {
54
- subject.send(:host_to_url, https_hostname_port, false)
55
- }.to raise_error(LogStash::ConfigurationError)
58
+ context "when SSL is false" do
59
+ let(:ssl) { false }
60
+ let(:base_options) { super.merge(:hosts => [https_hostname_port]) }
61
+
62
+ it "should refuse to handle an https url" do
63
+ expect {
64
+ subject.host_to_url(https_hostname_port)
65
+ }.to raise_error(LogStash::ConfigurationError)
66
+ end
56
67
  end
57
68
 
58
- it "should handle an ssl url correctly when SSL is nil" do
59
- expect(subject.send(:host_to_url, https_hostname_port, nil).to_s).to eql(https_hostname_port)
60
- end
61
-
62
- it "should raise an exception if an unexpected value is passed in" do
63
- expect { subject.send(:host_to_url, https_hostname_port, {})}.to raise_error(ArgumentError)
64
- end
69
+ describe "ssl is nil" do
70
+ let(:base_options) { super.merge(:hosts => [https_hostname_port]) }
71
+ it "should handle an ssl url correctly when SSL is nil" do
72
+ subject
73
+ expect(subject.host_to_url(https_hostname_port)).to eq(https_hostname_port)
74
+ end
75
+ end
65
76
  end
66
77
 
67
78
  describe "path" do
79
+ let(:url) { http_hostname_port_path }
80
+ let(:base_options) { super.merge(:hosts => [url]) }
81
+
68
82
  it "should allow paths in a url" do
69
- expect(subject.send(:host_to_url, http_hostname_port_path, nil).to_s).to eql(http_hostname_port_path)
83
+ expect(subject.host_to_url(url)).to eq(url)
70
84
  end
71
85
 
72
- it "should not allow paths in two places" do
73
- expect {
74
- subject.send(:host_to_url, http_hostname_port_path, false, "/otherpath")
75
- }.to raise_error(LogStash::ConfigurationError)
86
+ context "with the path option set" do
87
+ let(:base_options) { super.merge(:client_settings => {:path => "/otherpath"}) }
88
+
89
+ it "should not allow paths in two places" do
90
+ expect {
91
+ subject.host_to_url(url)
92
+ }.to raise_error(LogStash::ConfigurationError)
93
+ end
76
94
  end
77
-
78
- it "should automatically insert a / in front of path overlays if needed" do
79
- expect(subject.send(:host_to_url, http_hostname_port, false, "otherpath")).to eql(URI.parse(http_hostname_port + "/otherpath"))
95
+
96
+ context "with a path missing a leading /" do
97
+ let(:url) { http_hostname_port }
98
+ let(:base_options) { super.merge(:client_settings => {:path => "otherpath"}) }
99
+
100
+
101
+ it "should automatically insert a / in front of path overlays" do
102
+ expect(subject.host_to_url(url)).to eq(LogStash::Util::SafeURI.new(url + "/otherpath"))
103
+ end
80
104
  end
81
105
  end
82
106
  end
@@ -97,6 +121,49 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
97
121
  end
98
122
  end
99
123
 
124
+ describe "join_bulk_responses" do
125
+ subject { described_class.new(base_options) }
126
+
127
+ context "when items key is available" do
128
+ require "json"
129
+ let(:bulk_response) {
130
+ LogStash::Json.load ('[{
131
+ "items": [{
132
+ "delete": {
133
+ "_index": "website",
134
+ "_type": "blog",
135
+ "_id": "123",
136
+ "_version": 2,
137
+ "status": 200,
138
+ "found": true
139
+ }
140
+ }],
141
+ "errors": false
142
+ }]')
143
+ }
144
+ it "should be handled properly" do
145
+ s = subject.send(:join_bulk_responses, bulk_response)
146
+ expect(s["errors"]).to be false
147
+ expect(s["items"].size).to be 1
148
+ end
149
+ end
150
+
151
+ context "when items key is not available" do
152
+ require "json"
153
+ let(:bulk_response) {
154
+ JSON.parse ('[{
155
+ "took": 4,
156
+ "errors": false
157
+ }]')
158
+ }
159
+ it "should be handled properly" do
160
+ s = subject.send(:join_bulk_responses, bulk_response)
161
+ expect(s["errors"]).to be false
162
+ expect(s["items"].size).to be 0
163
+ end
164
+ end
165
+ end
166
+
100
167
  describe "sniffing" do
101
168
  let(:client) { LogStash::Outputs::ElasticSearch::HttpClient.new(base_options.merge(client_opts)) }
102
169