Skip to content

Commit 82872d0

Browse files
committed
WIP: Refactor BadResponseCodeError error handling
Instead of trying to handle this at the manticore adapter layer, let callers decide how to handle exit codes and raise or generate errors. This will allow us to DLQ 413 errors and not retry and hanlde 404 for template API interaction without having to rely on catching generic errors.
1 parent a6f09c6 commit 82872d0

File tree

7 files changed

+76
-55
lines changed

7 files changed

+76
-55
lines changed

Diff for: lib/logstash/outputs/elasticsearch/http_client.rb

+22-23
Original file line numberDiff line numberDiff line change
@@ -181,22 +181,15 @@ def join_bulk_responses(bulk_responses)
181181

182182
def bulk_send(body_stream, batch_actions)
183183
params = compression_level? ? {:headers => {"Content-Encoding" => "gzip"}} : {}
184-
185184
response = @pool.post(@bulk_path, params, body_stream.string)
186-
187185
@bulk_response_metrics.increment(response.code.to_s)
188186

189-
case response.code
190-
when 200 # OK
187+
if response.code == 200
191188
LogStash::Json.load(response.body)
192-
when 413 # Payload Too Large
193-
logger.warn("Bulk request rejected: `413 Payload Too Large`", :action_count => batch_actions.size, :content_length => body_stream.size)
194-
emulate_batch_error_response(batch_actions, response.code, 'payload_too_large')
195189
else
190+
logger.warn("Bulk request rejected: `413 Payload Too Large`", :action_count => batch_actions.size, :content_length => body_stream.size) if response.code == 413
196191
url = ::LogStash::Util::SafeURI.new(response.final_url)
197-
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
198-
response.code, url, body_stream.to_s, response.body
199-
)
192+
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(response.code, url, body_stream.to_s, response.body)
200193
end
201194
end
202195

@@ -414,13 +407,21 @@ def exists?(path, use_get=false)
414407
end
415408

416409
def template_exists?(template_endpoint, name)
417-
exists?("/#{template_endpoint}/#{name}")
410+
response = @pool.get("/#{template_endpoint}/#{name}")
411+
return true if response.code >= 200 && response.code <= 299
412+
return false if response.code == 404
413+
url = ::LogStash::Util::SafeURI.new(response.final_url)
414+
raise BadResponseCodeError.new(response.code, url, nil, response.body)
418415
end
419416

420417
def template_put(template_endpoint, name, template)
421-
path = "#{template_endpoint}/#{name}"
422418
logger.info("Installing Elasticsearch template", name: name)
423-
@pool.put(path, nil, LogStash::Json.dump(template))
419+
path = "#{template_endpoint}/#{name}"
420+
response = @pool.put(path, nil, LogStash::Json.dump(template))
421+
if response.code < 200 || response.code > 299
422+
url = ::LogStash::Util::SafeURI.new(response.final_url)
423+
raise BadResponseCodeError.new(response.code, url, template, response.body)
424+
end
424425
end
425426

426427
# ILM methods
@@ -432,16 +433,14 @@ def rollover_alias_exists?(name)
432433

433434
# Create a new rollover alias
434435
def rollover_alias_put(alias_name, alias_definition)
435-
begin
436-
@pool.put(CGI::escape(alias_name), nil, LogStash::Json.dump(alias_definition))
437-
logger.info("Created rollover alias", name: alias_name)
438-
# If the rollover alias already exists, ignore the error that comes back from Elasticsearch
439-
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
440-
if e.response_code == 400
441-
logger.info("Rollover alias already exists, skipping", name: alias_name)
442-
return
443-
end
444-
raise e
436+
response = @pool.put(CGI::escape(alias_name), nil, LogStash::Json.dump(alias_definition))
437+
if response.code == 400
438+
logger.info("Rollover alias already exists, skipping", name: alias_name)
439+
return
440+
end
441+
unless response.code >= 200 && response.code <= 299
442+
url = ::LogStash::Util::SafeURI.new(response.final_url)
443+
raise BadResponseCodeError.new(response.code, url, alias_definition, response.body)
445444
end
446445
end
447446

Diff for: lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb

-9
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,6 @@ def perform_request(url, method, path, params={}, body=nil)
7676
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError.new(e, request_uri_as_string)
7777
end
7878

79-
# 404s are excluded because they are valid codes in the case of
80-
# template installation. 413s are excluded to allow the bulk_send
81-
# error handling to process "Payload Too Large" responses rather
82-
# than triggering retries.
83-
code = resp.code
84-
if code < 200 || (code > 299 && ![404, 413].include?(code))
85-
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(code, request_uri, body, resp.body)
86-
end
87-
8879
resp
8980
end
9081

Diff for: lib/logstash/outputs/elasticsearch/http_client/pool.rb

+14-12
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ def start_resurrectionist
244244
# @return [Hash] deserialized license document or empty Hash upon any error
245245
def get_license(url)
246246
response = perform_request_to_url(url, :get, LICENSE_PATH)
247+
# CODEREVIEW: is 404 OK here? Previously it seems this would not error/warn if licence check gets 404
248+
if response.code < 200 || response.code > 299
249+
logger.error("Unable to get license information", code: response.code, url: url.sanitized.to_s)
250+
end
247251
LogStash::Json.load(response.body)
248252
rescue => e
249253
logger.error("Unable to get license information", url: url.sanitized.to_s, exception: e.class, message: e.message)
@@ -253,13 +257,12 @@ def get_license(url)
253257
def health_check_request(url)
254258
logger.debug("Running health check to see if an Elasticsearch connection is working",
255259
:healthcheck_url => url.sanitized.to_s, :path => @healthcheck_path)
256-
begin
257-
response = perform_request_to_url(url, :head, @healthcheck_path)
258-
return response, nil
259-
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
260-
logger.warn("Health check failed", code: e.response_code, url: e.url, message: e.message)
261-
return nil, e
260+
response = perform_request_to_url(url, :head, @healthcheck_path)
261+
if response.code < 200 || response.code > 299
262+
logger.warn("Health check failed", code: response.code, url: url.sanitized.to_s)
263+
return nil, BadResponseCodeError.new(response.code, url, nil, response.body)
262264
end
265+
return response, nil
263266
end
264267

265268
def healthcheck!(register_phase = true)
@@ -312,13 +315,12 @@ def healthcheck!(register_phase = true)
312315
end
313316

314317
def get_root_path(url, params={})
315-
begin
316-
resp = perform_request_to_url(url, :get, ROOT_URI_PATH, params)
317-
return resp, nil
318-
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
319-
logger.warn("Elasticsearch main endpoint returns #{e.response_code}", message: e.message, body: e.response_body)
320-
return nil, e
318+
response = perform_request_to_url(url, :get, ROOT_URI_PATH, params)
319+
if response.code < 200 || response.code > 299
320+
logger.warn("Elasticsearch main endpoint returns #{response.code}", body: response.body)
321+
return nil, BadResponseCodeError.new(response.code, url, nil, response.body)
321322
end
323+
return response, nil
322324
end
323325

324326
def test_serverless_connection(url, root_response)

Diff for: lib/logstash/plugin_mixins/elasticsearch/common.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ module Common
77

88
attr_reader :hosts
99

10-
# These codes apply to documents, not at the request level
11-
DOC_DLQ_CODES = [400, 404]
10+
# These codes apply to documents, not at the request level. While the 413 error technically is at the request level
11+
# it should be treated like an error at the document level. Specifically when the payload is too large it is wholesale
12+
# not accepted by ES, in this case it is dumped to DLQ and not retried. Note that this applies to batches or a single message,
13+
# if the batch size results in a 413 due to exceeding ES limit *all* events in the batch are rejected, regardless of whether
14+
# the individual parts that were rejected would have been accepted.
15+
DOC_DLQ_CODES = [400, 404, 413]
1216
DOC_SUCCESS_CODES = [200, 201]
1317
DOC_CONFLICT_CODE = 409
1418

Diff for: spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb

+3-6
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,9 @@
6262
uri_with_path = uri.clone
6363
uri_with_path.path = "/"
6464

65-
expect(::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError).to receive(:new).
66-
with(resp.code, uri_with_path, nil, resp.body).and_call_original
67-
68-
expect do
69-
subject.perform_request(uri, :get, "/")
70-
end.to raise_error(::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError)
65+
result = subject.perform_request(uri, :get, "/")
66+
expect(result).to eq(resp)
67+
expect(result.code).to eq(500)
7168
end
7269
end
7370

Diff for: spec/unit/outputs/elasticsearch_spec.rb

+22-2
Original file line numberDiff line numberDiff line change
@@ -915,9 +915,9 @@
915915
allow(elasticsearch_output_instance.client.pool).to receive(:post) do |path, params, body|
916916
if body.length > max_bytes
917917
max_bytes *= 2 # ensure a successful retry
918-
double("Response", :code => 413, :body => "")
918+
double("Response", :code => 413, :body => "", :final_url => "http://localhost:9200/_bulk")
919919
else
920-
double("Response", :code => 200, :body => '{"errors":false,"items":[{"index":{"status":200,"result":"created"}}]}')
920+
double("Response", :code => 200, :body => '{"errors":false,"items":[{"index":{"status":200,"result":"created"}}]}', :final_url => "http://localhost:9200/_bulk")
921921
end
922922
end
923923
end
@@ -1102,7 +1102,14 @@
11021102
describe "SSL end to end" do
11031103
let(:do_register) { false } # skip the register in the global before block, as is called here.
11041104

1105+
let(:stub_http_client_pool!) do
1106+
[:start_resurrectionist, :start_sniffer, :healthcheck!].each do |method|
1107+
allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient::Pool).to receive(method)
1108+
end
1109+
end
1110+
11051111
before(:each) do
1112+
stub_http_client_pool!
11061113
stub_manticore_client!
11071114
subject.register
11081115
end
@@ -1292,9 +1299,15 @@
12921299
end
12931300

12941301
let(:options) { { 'cloud_id' => valid_cloud_id } }
1302+
let(:stub_http_client_pool!) do
1303+
[:start_resurrectionist, :start_sniffer, :healthcheck!].each do |method|
1304+
allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient::Pool).to receive(method)
1305+
end
1306+
end
12951307

12961308
before(:each) do
12971309
stub_manticore_client!
1310+
stub_http_client_pool!
12981311
end
12991312

13001313
it "should set host(s)" do
@@ -1325,8 +1338,15 @@
13251338

13261339
let(:options) { { 'cloud_auth' => LogStash::Util::Password.new('elastic:my-passwd-00') } }
13271340

1341+
let(:stub_http_client_pool!) do
1342+
[:start_resurrectionist, :start_sniffer, :healthcheck!].each do |method|
1343+
allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient::Pool).to receive(method)
1344+
end
1345+
end
1346+
13281347
before(:each) do
13291348
stub_manticore_client!
1349+
stub_http_client_pool!
13301350
end
13311351

13321352
it "should set host(s)" do

Diff for: spec/unit/outputs/elasticsearch_ssl_spec.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@
1515
allow(manticore_double).to receive(:close)
1616

1717
response_double = double("manticore response").as_null_object
18-
# Allow healtchecks
18+
# Allow healthchecks and version checks
19+
allow(response_double).to receive(:code).and_return(200)
20+
allow(response_double).to receive(:body).and_return('{"version":{"number":"7.10.1"}}')
1921
allow(manticore_double).to receive(:head).with(any_args).and_return(response_double)
2022
allow(manticore_double).to receive(:get).with(any_args).and_return(response_double)
2123
allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
24+
25+
# Skip template installation etc
26+
allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient::Pool).to receive(:start)
27+
allow_any_instance_of(LogStash::Outputs::ElasticSearch).to receive(:install_template)
28+
allow_any_instance_of(LogStash::Outputs::ElasticSearch).to receive(:discover_cluster_uuid)
29+
allow_any_instance_of(LogStash::Outputs::ElasticSearch).to receive(:ilm_in_use?).and_return(nil)
2230
end
2331

2432
after do

0 commit comments

Comments
 (0)