Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions esrally/driver/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2129,34 +2129,37 @@ def _parse_headers(e):
# Some runners return a raw response, causing the 'error' property to be a string literal of the bytes/BytesIO object,
# we should avoid bubbling that up
# e.g. ApiError(413, '<_io.BytesIO object at 0xffffaf146a70>')
# errors="replace" so binary response bodies (e.g. OTLP ingest returns binary protobuf
# error frames) don't crash the driver with UnicodeDecodeError. Undecodable bytes become
# U+FFFD in the logged/recorded message.
if isinstance(e.body, bytes):
# could be an empty body
if error_body := e.body.decode("utf-8"):
if error_body := e.body.decode("utf-8", errors="replace"):
error_message = error_body
else:
# to be consistent with an empty 'e.error'
error_message = str(None)
elif isinstance(e.body, BytesIO):
# could be an empty body
if error_body := e.body.read().decode("utf-8"):
if error_body := e.body.read().decode("utf-8", errors="replace"):
error_message = error_body
else:
# to be consistent with an empty 'e.error'
error_message = str(None)
# fallback to 'error' property if the body isn't bytes/BytesIO
else:
if isinstance(e.error, bytes):
error_message = e.error.decode("utf-8")
error_message = e.error.decode("utf-8", errors="replace")
elif isinstance(e.error, BytesIO):
error_message = e.error.read().decode("utf-8")
error_message = e.error.read().decode("utf-8", errors="replace")
else:
# if the 'error' is empty, we get back str(None)
error_message = e.error

if isinstance(e.info, bytes):
error_info = e.info.decode("utf-8")
error_info = e.info.decode("utf-8", errors="replace")
elif isinstance(e.info, BytesIO):
error_info = e.info.read().decode("utf-8")
error_info = e.info.read().decode("utf-8", errors="replace")
else:
error_info = e.info

Expand Down
19 changes: 19 additions & 0 deletions tests/driver/driver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,25 @@ async def test_execute_single_with_http_400_with_raw_response_body(self):
await driver.execute_single(self.context_managed(runner), es, params, on_error=OnErrorBehavior.ABORT)
assert exc.value.args[0] == ("Request returned an error. Error type: api, Description: Huge error, HTTP Status: 499")

@pytest.mark.asyncio
async def test_execute_single_with_http_400_with_non_utf8_raw_response_body(self):
es = None
params = None
body = io.BytesIO(b"\xff")
str_literal = str(body)
error_meta = elastic_transport.ApiResponseMeta(
status=400,
http_version="1.1",
headers=elastic_transport.HttpHeaders(),
duration=0.0,
node=elastic_transport.NodeConfig(scheme="http", host="localhost", port=9200),
)
runner = mock.AsyncMock(side_effect=elasticsearch.ApiError(message=str_literal, meta=error_meta, body=body))

with pytest.raises(exceptions.RallyAssertionError) as exc:
await driver.execute_single(self.context_managed(runner), es, params, on_error=OnErrorBehavior.ABORT)
assert exc.value.args[0] == ("Request returned an error. Error type: api, Description: �, HTTP Status: 400")

@pytest.mark.asyncio
async def test_execute_single_with_http_400(self):
es = None
Expand Down
Loading