logstash-output-elasticsearch 5.4.1-java → 6.0.0-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.
@@ -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