Skip to content

Instantly share code, notes, and snippets.

@rohanpm
Created September 22, 2023 05:03
Show Gist options
  • Select an option

  • Save rohanpm/5d7bc4cefb772d8cfb34597b336e028c to your computer and use it in GitHub Desktop.

Select an option

Save rohanpm/5d7bc4cefb772d8cfb34597b336e028c to your computer and use it in GitHub Desktop.
Force exodus-gw to retry every S3 request at least three times (even if it succeeded)
diff --git a/exodus_gw/aws/client.py b/exodus_gw/aws/client.py
index 5cbf1f5..beb52f5 100644
--- a/exodus_gw/aws/client.py
+++ b/exodus_gw/aws/client.py
@@ -1,3 +1,4 @@
+import logging
import os
import aioboto3
@@ -38,12 +39,21 @@ class S3ClientWrapper:
client.meta.events.register(
"needs-retry.s3.CreateMultipartUpload", self.no_redirects
)
+ client.meta.events.register("needs-retry.s3", self.force_retry)
return client
async def __aexit__(self, exc_type, exc, tb):
await self._client_context.__aexit__(exc_type, exc, tb)
+ @staticmethod
+ def force_retry(**kwargs):
+ log = logging.getLogger("exodus-gw")
+ log.debug("needs-retry.s3 invoked: %s", kwargs)
+ if kwargs["attempts"] < 3:
+ log.warning("Forcing an S3 retry!")
+ return True
+
@staticmethod
def no_redirects(**kwargs):
# An event handler for needs-s3.retry.* events which will disable implicit
@rohanpm

rohanpm commented Sep 22, 2023

Copy link
Copy Markdown
Author

This can be used to trigger the UnseekableStreamError condition locally.

What it does is force every S3 request to be retried at least 3 times, even if the request actually succeeded. This is good enough to simulate the case where a request really does have to be retried due to some failure, and hits the UnseekableStreamError.

If testing this with exodus-rsync, you MUST use a unique file for every test, because the "retry" happens after the S3 request actually succeeded - which means the content made it into S3 successfully and a follow-up test will detect that the object is already present, and won't upload it.

Example of testing with this:

$ dd if=/dev/urandom of=rand count=10 && ./exodus-rsync -avz ./rand exodus:/test
10+0 records in
10+0 records out
5120 bytes (5.1 kB, 5.0 KiB) copied, 9.9104e-05 s, 51.7 MB/s
Fri Sep 22 05:18:42 UTC 2023 Walking directory tree {}
Fri Sep 22 05:18:42 UTC 2023 Preparing to publish items {"items":"1"}
Fri Sep 22 05:18:42 UTC 2023 Initializing connection {"url":"https://localhost:8010/test/publish"}
Fri Sep 22 05:18:42 UTC 2023 Closing connection {"url":"https://localhost:8010/test/publish"}
Fri Sep 22 05:18:42 UTC 2023 Created publish {"publish":"71e26f80-fe34-4c8e-b3f7-1c889e56b303"}
Fri Sep 22 05:18:42 UTC 2023 Preparing to upload items {"items":"1"}
Fri Sep 22 05:18:42 UTC 2023 Initializing connection {"url":"https://localhost:8010/upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352"}
Fri Sep 22 05:18:45 UTC 2023 Closing connection {"url":"https://localhost:8010/upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352"}
Fri Sep 22 05:18:45 UTC 2023 Uploading {"key":"5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352","src":"./rand"}
Fri Sep 22 05:18:45 UTC 2023 Initializing connection {"url":"https://localhost:8010/upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352"}
Fri Sep 22 05:18:50 UTC 2023 Closing connection {"url":"https://localhost:8010/upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352"}
Fri Sep 22 05:18:50 UTC 2023 Uploading {"duration":"5194","error":"InternalServerError: Internal Server Error\n\tstatus code: 500, request id: , host id: ","key":"5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352","src":"./rand"}
Fri Sep 22 05:18:50 UTC 2023 can't upload files {"error":"upload ./rand: InternalServerError: Internal Server Error\n\tstatus code: 500, request id: , host id: "}

With corresponding server logs:

Sep 22 15:18:49 photinia.usersys.redhat.com sh[45951]: {"level": "INFO", "logger": "exodus-gw", "time": "2023-09-22 05:18:49.446", "request_id": "8731c7bf", "message": "Access permitted; path=/upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352, user=user rmcgover, role=test-blob-uploader", "event": "auth", "success": true}
Sep 22 15:18:49 photinia.usersys.redhat.com sh[45951]: {"level": "WARNING", "logger": "exodus-gw", "time": "2023-09-22 05:18:49.511", "request_id": "8731c7bf", "message": "Forcing an S3 retry!", "event": null, "success": null}
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: INFO:     127.0.0.1:56286 - "PUT /upload/test/5193d6a195e90da3bb16b446e4095196414c49a9e3fc4a7bc806ccad4aeaf352 HTTP/1.1" 500 Internal Server Error
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: ERROR:    Exception in ASGI application
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: Traceback (most recent call last):
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/anyio/streams/memory.py", line 98, in receive
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     return self.receive_nowait()
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/anyio/streams/memory.py", line 93, in receive_nowait
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise WouldBlock
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: anyio.WouldBlock
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: During handling of the above exception, another exception occurred:
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: Traceback (most recent call last):
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/base.py", line 78, in call_next
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     message = await recv_stream.receive()
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/anyio/streams/memory.py", line 118, in receive
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise EndOfStream
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: anyio.EndOfStream
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: During handling of the above exception, another exception occurred:
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: Traceback (most recent call last):
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     result = await app(  # type: ignore[func-returns-value]
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     return await self.app(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/fastapi/applications.py", line 292, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await super().__call__(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/applications.py", line 122, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.middleware_stack(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/errors.py", line 184, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise exc
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive, _send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/asgi_correlation_id/middleware.py", line 90, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive, handle_outgoing_request)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/base.py", line 108, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     response = await self.dispatch_func(request, call_next)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/backoff/_async.py", line 151, in retry
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     ret = await target(*args, **kwargs)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/exodus_gw/main.py", line 206, in db_session_wrap
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     response = await call_next(request)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/base.py", line 84, in call_next
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise app_exc
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/base.py", line 70, in coro
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive_or_disconnect, send_no_error)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise exc
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive, sender)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise e
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/routing.py", line 718, in __call__
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await route.handle(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/routing.py", line 276, in handle
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     await self.app(scope, receive, send)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/starlette/routing.py", line 66, in app
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     response = await func(request)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/fastapi/routing.py", line 273, in app
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raw_response = await run_endpoint_function(
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/fastapi/routing.py", line 190, in run_endpoint_function
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     return await dependant.call(**values)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/exodus_gw/routers/upload.py", line 200, in upload
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     return await object_put(s3, env, key, request, metadata)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/exodus_gw/routers/upload.py", line 221, in object_put
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     response = await s3.put_object(  # type: ignore
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/aiobotocore/client.py", line 366, in _make_api_call
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     http, parsed_response = await self._make_request(
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/aiobotocore/client.py", line 391, in _make_request
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     return await self._endpoint.make_request(
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/aiobotocore/endpoint.py", line 113, in _send_request
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     request.reset_stream()
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:   File "/home/rmcgover/src/exodus-gw/.tox/dev-server/lib/python3.9/site-packages/botocore/awsrequest.py", line 539, in reset_stream
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]:     raise UnseekableStreamError(stream_object=self.body)
Sep 22 15:18:50 photinia.usersys.redhat.com sh[45951]: botocore.exceptions.UnseekableStreamError: Need to rewind the stream <exodus_gw.aws.util.RequestReader object at 0x7f199b1d6190>, but stream is not seekable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment