diff --git a/lib/logstash/outputs/elasticsearch/http_client/pool.rb b/lib/logstash/outputs/elasticsearch/http_client/pool.rb index cbafdb26..814c05cb 100644 --- a/lib/logstash/outputs/elasticsearch/http_client/pool.rb +++ b/lib/logstash/outputs/elasticsearch/http_client/pool.rb @@ -265,8 +265,12 @@ def healthcheck! end def elasticsearch?(url) - response = perform_request_to_url(url, :get, "/") - return false if response.code == 401 || response.code == 403 + begin + response = perform_request_to_url(url, :get, "/") + rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e + return false if response.code == 401 || response.code == 403 + raise e + end version_info = LogStash::Json.load(response.body) return false if version_info['version'].nil? @@ -281,7 +285,7 @@ def elasticsearch?(url) return false if build_flavour.nil? || build_flavour != 'default' || !valid_tagline?(version_info) else # case >= 7.14 - product_header = response.headers['X-elastic-product'] + product_header = response.headers['x-elastic-product'] return false if product_header.nil? || product_header != 'Elasticsearch' end return true diff --git a/logstash-output-elasticsearch.gemspec b/logstash-output-elasticsearch.gemspec index 13422e2e..f680554f 100644 --- a/logstash-output-elasticsearch.gemspec +++ b/logstash-output-elasticsearch.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'logstash-devutils' s.add_development_dependency 'flores' s.add_development_dependency 'cabin', ['~> 0.6'] + s.add_development_dependency 'webmock' # Still used in some specs, we should remove this ASAP s.add_development_dependency 'elasticsearch' end diff --git a/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb b/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb index e89e835a..3636e717 100644 --- a/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +++ b/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb @@ -1,6 +1,7 @@ require "logstash/devutils/rspec/spec_helper" require "logstash/outputs/elasticsearch/http_client" require 'cabin' +require 'webmock/rspec' describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do let(:logger) { Cabin::Channel.get } @@ -341,3 +342,125 @@ def code end end end + +describe "#elasticsearch?" do + let(:logger) { Cabin::Channel.get } + let(:adapter) { LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(logger) } + let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] } + let(:options) { {:resurrect_delay => 2, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests + let(:es_node_versions) { [ "0.0.0" ] } + let(:license_status) { 'active' } + + subject { LogStash::Outputs::ElasticSearch::HttpClient::Pool.new(logger, adapter, initial_urls, options) } + + let(:url) { ::LogStash::Util::SafeURI.new("http://localhost:9200") } + + context "in case HTTP error code" do + it "should fail for 401" do + stub_request(:get, "localhost:9200").to_return(status: 401) + expect(subject.elasticsearch?(url)).to be false + + # # stub_request(:post, "www.example.com"). + # # with(body: "abc", headers: { 'Content-Length' => 3 }) + # # + # uri = URI.parse("http://localhost:9300/") + # # req = Net::HTTP::Get.new(uri.path) + # # req['Content-Length'] = 3 + # + # #res = Net::HTTP.start(uri.host, uri.port) do |http| + # # http.request(req, "abc") + # #end + # res = Net::HTTP.get_response(uri) + # expect(res.class).to eq(Net::HTTPUnauthorized) + # expect(res).to be_a(Net::HTTPUnauthorized) + # puts "DNADBG>> HTTP net response: #{res}" + # + # + # # using Manticore + # manticore = ::Manticore::Client.new() + # #resp = manticore.get("http://localhost:9300/") + # resp = manticore.send("get", "http://localhost:9300/", {}) + # resp.call + # puts "DNADBG>> Manticore response: #{resp.class}" + # puts "DNADBG>> resp.code: #{resp.code}" + # expect(resp.code).to eq(401) + + end + + it "should fail for 403" do + stub_request(:get, "localhost:9200").to_return(status: 403) + expect(subject.elasticsearch?(url)).to be false + end + end + + context "when connecting to a cluster which reply without 'version' field" do + it "should fail" do + stub_request(:get, "localhost:9200").to_return(body: '{"field": "funky"}') + expect(subject.elasticsearch?(url)).to be false + end + end + + context "when connecting to a cluster with version < 6.0.0" do + it "should fail" do + stub_request(:get, "localhost:9200").to_return(body: '{"version": {"number": "5.0.0"}}') + expect(subject.elasticsearch?(url)).to be false + end + end + + context "when connecting to a cluster with version in [6.0.0..7.0.0)" do + it "must be successful with valid 'tagline'" do + stub_request(:get, "localhost:9200").to_return(status: 200, body: '{"version": {"number": "6.5.0"}, "tagline": "You Know, for Search"}') + expect(subject.elasticsearch?(url)).to be true + end + + it "should fail if invalid 'tagline'" do + stub_request(:get, "localhost:9200").to_return(body: '{"version": {"number": "6.5.0"}, "tagline": "You don\'t know"}') + expect(subject.elasticsearch?(url)).to be false + end + + it "should fail if 'tagline' is not present" do + stub_request(:get, "localhost:9200").to_return(body: '{"version": {"number": "6.5.0"}}') + expect(subject.elasticsearch?(url)).to be false + end + end + + context "when connecting to a cluster with version in [7.0.0..7.14.0)" do + it "must be successful is 'build_flavour' is 'default' and tagline is correct" do + stub_request(:get, "localhost:9200/").to_return(status: 200, body: '{"version": {"number": "7.5.0", "build_flavour": "default"}, "tagline": "You Know, for Search"}') + expect(subject.elasticsearch?(url)).to be true + end + + it "should fail if 'build_flavour' is not 'default' and tagline is correct" do + stub_request(:get, "localhost:9200").to_return(status: 200, body: '{"version": {"number": "7.5.0", "build_flavour": "oss"}, "tagline": "You Know, for Search"}') + expect(subject.elasticsearch?(url)).to be false + end + + it "should fail if 'build_flavour' is not present and tagline is correct" do + stub_request(:get, "localhost:9200").to_return(status: 200, body: '{"version": {"number": "7.5.0"}, "tagline": "You Know, for Search"}') + expect(subject.elasticsearch?(url)).to be false + end + end + + context "when connecting to a cluster with version >= 7.14.0" do + it "should fail if 'X-elastic-product' header is not present" do + stub_request(:get, "localhost:9200").to_return(status: 200, body: '{"version": {"number": "7.14.0"}}') + expect(subject.elasticsearch?(url)).to be false + end + + it "should fail if 'X-elastic-product' header is present but with bad value" do + stub_request(:get, "localhost:9200") + .to_return(status: 200, + headers: {'X-elastic-product' => 'not good'}, + body: '{"version": {"number": "7.14.0"}}') + expect(subject.elasticsearch?(url)).to be false + end + + it "must be successful when 'X-elastic-product' header is present with expected value" do + stub_request(:get, "localhost:9200") + .to_return(status: 200, + headers: {'X-elastic-product': 'Elasticsearch'}, + body: '{"version": {"number": "7.14.0"}}') + expect(subject.elasticsearch?(url)).to be true + end + end +end